diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..19152a5 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(git *)", + "Bash(pbcopy)" + ] + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..59b58dd --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,8 @@ +# Text Guidelines + +- Check the grammar of any text and adjust if necessary. Text must ALWAYS be written in ENGLISH. +- Write the text using user-manual style language: clear, simple, and accessible, avoiding unnecessary technical jargon, while retaining all technical details explicitly documented in the source, without omitting or oversimplifying them. + +# Versioning + +Do not ask to commit, operations like `commit`, `add`, `push` are done manually by the user diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/colors.html b/colors.html new file mode 100644 index 0000000..e7c32c2 --- /dev/null +++ b/colors.html @@ -0,0 +1,1975 @@ + + + + + + +VisualDiffer — Color Scheme Editor + + + + + + + + + +
+
+ +
+

VisualDiffer — Color Scheme Editor

+

Start with a built-in color scheme, adjust it in the live previews, then download the colors.json file.

+
+
+ +
+ + + +
+ Preview appearance +
+ + +
+
+ + + +
+
+ + + +
+ + +
+
+
+

Folder differ

+

Icons are omitted from this text-only preview. Select any colored element to locate its setting.

+
+
+
+
+ Folder rows retain the canvas background. Only file and filtered rows use a fill color. + Folder sizes use the Folder size color. +
+
+
+ +
+
+

File differ

+

Preview line numbers, the gutter separator, hatched missing lines, inline highlights, and the thumbnail position box.

+
+
+
+
+ Selected rows retain their status text color. + Thumbnail blocks use the text color from each side. +
+
+
+ +
+

Console log

+
+
+
+
+ +
+
+

colors.json

+

Uses the same structure as the bundled files.

+
+
+

+                
+ + + Values can use hexadecimal #rgb, #rrggbb, or #rrggbbaa notation, or the dynamic textForeground and textBackground values. +
+
+
+
+
+ + + + + + diff --git a/css/input.css b/css/input.css index 2dc284d..ca19462 100644 --- a/css/input.css +++ b/css/input.css @@ -159,14 +159,69 @@ td.copy-cmd.copied::after { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23059669' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E"); } +#main .code-block { + position: relative; + margin: 1.7142857em 0; +} + +#main .code-block pre { + margin: 0; + padding-right: 3rem; +} + +.copy-code-button { + position: absolute; + top: 0.625rem; + right: 0.625rem; + width: 20px; + height: 20px; + padding: 0; + border: 0; + background-color: transparent; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23e2e8f0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='9' y='9' width='13' height='13' rx='2' ry='2'/%3E%3Cpath d='M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1'/%3E%3C/svg%3E"); + background-position: center; + background-repeat: no-repeat; + background-size: contain; + cursor: pointer; + opacity: 0; + transition: opacity 0.15s; +} + +.code-block:hover .copy-code-button, +.copy-code-button:focus-visible { + opacity: 1; +} + +.copy-code-button:focus-visible { + outline: 2px solid #e2e8f0; + outline-offset: 2px; +} + +.copy-code-button.copied { + opacity: 1; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23e2e8f0' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E"); +} + +@media (hover: none) { + .copy-code-button { + opacity: 1; + } +} + img.wiki-img { - max-width: 50%; + max-width: 100%; border: 1px solid #94a3b8; border-radius: 4px; background-color: #fff; padding: 3px; } +@media (min-width: 768px) { + img.wiki-img { + max-width: 50%; + } +} + table.file-legend tr td { padding: 6px 10px; } @@ -264,4 +319,4 @@ table.file-legend tr td { .finder-callout a:hover { color: var(--color-brand-800); } -} \ No newline at end of file +} diff --git a/css/theme.css b/css/theme.css index b2f4ddf..382f266 100644 --- a/css/theme.css +++ b/css/theme.css @@ -1583,13 +1583,59 @@ td.copy-cmd.copied::after { opacity: 1; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23059669' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E"); } +#main .code-block { + position: relative; + margin: 1.7142857em 0; +} +#main .code-block pre { + margin: 0; + padding-right: 3rem; +} +.copy-code-button { + position: absolute; + top: 0.625rem; + right: 0.625rem; + width: 20px; + height: 20px; + padding: 0; + border: 0; + background-color: transparent; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23e2e8f0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='9' y='9' width='13' height='13' rx='2' ry='2'/%3E%3Cpath d='M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1'/%3E%3C/svg%3E"); + background-position: center; + background-repeat: no-repeat; + background-size: contain; + cursor: pointer; + opacity: 0; + transition: opacity 0.15s; +} +.code-block:hover .copy-code-button, .copy-code-button:focus-visible { + opacity: 1; +} +.copy-code-button:focus-visible { + outline: 2px solid #e2e8f0; + outline-offset: 2px; +} +.copy-code-button.copied { + opacity: 1; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23e2e8f0' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E"); +} +@media (hover: none) { + .copy-code-button { + opacity: 1; + } +} img.wiki-img { - max-width: 50%; + max-width: 100%; border: 1px solid #94a3b8; border-radius: 4px; background-color: #fff; padding: 3px; } +@media (min-width: 768px) { + img.wiki-img { + max-width: 50%; + } +} table.file-legend tr td { padding: 6px 10px; } diff --git a/fileDiff.md b/fileDiff.md index 1bf31c5..e188125 100644 --- a/fileDiff.md +++ b/fileDiff.md @@ -82,7 +82,7 @@ A *section* is a contiguous block of differing lines. Two shortcuts in the Edit # [Show Details](#show_details) -**View → Show Details** opens a panel below the diff tables that shows the selected line at character level, highlighting exactly which characters differ. Hide it again from the same menu item or from the right-click menu inside the panel. +**View → Show Details** opens a panel below the diff tables that shows the selected line. Hide it again from the same menu item or from the right-click menu inside the panel. > **Line-ending characters are always shown in the Details panel**, even when Show Whitespace is off. Unix lines end with **¶**, DOS lines end with **¤**. This makes it easy to identify the source of a difference that looks identical at first glance. diff --git a/hiddenSettings.md b/hiddenSettings.md index b712845..42ee175 100644 --- a/hiddenSettings.md +++ b/hiddenSettings.md @@ -114,3 +114,35 @@ Click any row in the **Example** column to copy the full command to the clipboar + +# [How to Use a Custom Color Scheme](#how_to_use_a_custom_color_scheme) + +**Installation** + +Use the Color Scheme Editor to create a custom color scheme, then download the JSON file. For example, save the file as `~/Downloads/my-scheme.json`. + +Open Terminal and run the following commands. + +Copy the file to a directory that the VisualDiffer sandbox can access. + +```sh +cp ~/Downloads/my-scheme.json ~/Library/Application\ Scripts/com.visualdiffer +``` + +Set the `colorsConfigPath` property to the full path of the copied file. + +```sh +defaults write com.visualdiffer colorsConfigPath ~/Library/Application\ Scripts/com.visualdiffer/my-scheme.json +``` + +Changes to the color scheme take effect only after you restart VisualDiffer. + +Custom color schemes do not respond to changes between Light and Dark appearances. If you switch the appearance, the custom color scheme remains active. + +**Restore the Default Setting** + +To stop using the custom color scheme, run the following command: + +```sh +defaults delete com.visualdiffer colorsConfigPath +``` diff --git a/index.md b/index.md index aaa5294..68cc8b7 100644 --- a/index.md +++ b/index.md @@ -30,6 +30,7 @@ Integration & Settings Extra Features ============== +* [Color Scheme Editor](colors.html) * [visdiff — Command-Line Tool](unixshell.html) * [Hidden Settings](hiddenSettings.html) * [Frequently Asked Questions](faq.html) diff --git a/js/copy-cmd.js b/js/copy-cmd.js index 015d34b..72e61d6 100644 --- a/js/copy-cmd.js +++ b/js/copy-cmd.js @@ -1,10 +1,62 @@ document.addEventListener('DOMContentLoaded', function () { + function copyText(text) { + if (navigator.clipboard && window.isSecureContext) { + return navigator.clipboard.writeText(text); + } + + return new Promise(function (resolve, reject) { + var textArea = document.createElement('textarea'); + textArea.value = text; + textArea.setAttribute('readonly', ''); + textArea.style.position = 'fixed'; + textArea.style.opacity = '0'; + document.body.appendChild(textArea); + textArea.select(); + + try { + if (!document.execCommand('copy')) { + throw new Error('Copy command failed'); + } + resolve(); + } catch (error) { + reject(error); + } finally { + textArea.remove(); + } + }); + } + document.querySelectorAll('.copy-cmd').forEach(function (cell) { cell.addEventListener('click', function () { - navigator.clipboard.writeText(cell.dataset.cmd).then(function () { + copyText(cell.dataset.cmd).then(function () { cell.classList.add('copied'); setTimeout(function () { cell.classList.remove('copied'); }, 1500); }); }); }); + + document.querySelectorAll('#main pre').forEach(function (codeBlock) { + var wrapper = document.createElement('div'); + var button = document.createElement('button'); + + wrapper.className = 'code-block'; + codeBlock.parentNode.insertBefore(wrapper, codeBlock); + wrapper.appendChild(codeBlock); + + button.className = 'copy-code-button'; + button.type = 'button'; + button.setAttribute('aria-label', 'Copy code to clipboard'); + wrapper.appendChild(button); + + button.addEventListener('click', function () { + copyText(codeBlock.textContent).then(function () { + button.setAttribute('aria-label', 'Code copied'); + button.classList.add('copied'); + setTimeout(function () { + button.setAttribute('aria-label', 'Copy code to clipboard'); + button.classList.remove('copied'); + }, 1500); + }); + }); + }); }); diff --git a/trustedPaths.md b/trustedPaths.md index 8da8601..0451183 100755 --- a/trustedPaths.md +++ b/trustedPaths.md @@ -19,24 +19,37 @@ VisualDiffer prompts a file panel when it can't access a file due to a permissio [Using VisualDiffer as external diff tool with sandbox](#using_visualdiffer_as_external_diff_tool_with_sandbox) ===================================================== -VisualDiffer can be used to compare files using other applications, for example to compare a remote file version with the local one. +Other applications can use VisualDiffer to compare files, for example, to compare a remote version of a file with its local version. -Applications like Dreamweaver save the remote version to a temporary file and then pass the generated full path to VisualDiffer, but this path isn't trusted, so VisualDiffer shows the file panel every time. +An application can save the remote version to a temporary file and then pass its full path to VisualDiffer. However, the path is not trusted, so VisualDiffer displays the file panel every time. -This behaviour generates a very frustrating user experience because the user must select the file from the panel again and again. +This behavior can be frustrating because you must select the file from the panel repeatedly. [Use Trusted Paths](#use_trusted_paths) ================= -Trusted paths resolve this problem because applications save files in their own specific temporary folders; for example SourceTree can generate the following path: +Trusted paths solve this problem because applications save files in their own temporary folders. For example, an application can generate the following path: /var/folders/mr/5dfd6w717cj5j374q24fdxsh0000gn/T/2PhpDQ_UnifiedDiff.m -but trusting it works only once because the path component **5dfd6w717cj5j374q24fdxsh0000gn/T/** is randomly generated and changes every time. +Trusting this path works only once because the **5dfd6w717cj5j374q24fdxsh0000gn/T/** path component is generated randomly and changes every time. -We can trust **/var/folders**, which is the root used by SourceTree to save temporary files. +Instead, you can trust **/var/folders**, the root directory used to save temporary files. -Trusting **/var/folders** ensures every path generated inside it (at any deeper subfolder level) doesn't require any additional user permission and the file panel prompts no longer appear. +Trusting **/var/folders** ensures that paths generated anywhere inside it do not require additional permission, preventing further file panel prompts. + +[Trust the File System from the Root (`/`)?](#trust_high_level) +============================= + +Integrating VisualDiffer with [Finder](finder.html) through Services or Quick Actions can make the user experience even more frustrating. The items you want to compare can be located anywhere, either on the main disk or on external disks under `/Volumes/`, causing the file selection panel to appear repeatedly. + +You may wonder, “I have just selected this path in Finder, so why am I being asked to open it?” This behavior is required by the sandbox. Finder and VisualDiffer are separate processes, and trusted paths cannot be transferred between them. + +### [Workaround](#trusted_workaround) + +⚠️ This workaround is not a complete solution. You can manually add a high-level directory, such as `/Volumes` or `/`, to Trusted Paths. Every path below that directory will then be accessible without requiring you to select it. + +Use this workaround only if you understand that it grants VisualDiffer access to everything below the selected directory. VisualDiffer does not send or index data, and it performs file operations only when explicitly initiated by the user, so this approach can be considered “safe.” [Where to configure Trusted Paths](#where_configure_trusted_paths) =============================