├── .gitignore ├── .pre-commit-config.yaml ├── .prettierrc ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── AiCommandPalette.jsx ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── NOTES.md ├── README.md ├── TODO.md ├── data ├── builtin_commands.csv ├── config_commands.csv ├── custom_commands.csv ├── menu_commands.csv ├── strings.csv └── tool_commands.csv ├── images ├── actions.png ├── ai-command-palette-youtube-1.png ├── bookmarks.png ├── custom-picker.png ├── document-report.png ├── go-to-functionality.png ├── localization.png ├── menu-commands.png ├── palette-operation.gif ├── palette.png ├── scripts.png ├── settings.png ├── tool-commands.png └── workflow-builder.png ├── jsconfig.json ├── localization.md ├── ruff.toml ├── src ├── include │ ├── Logger.jsxinc │ ├── commands │ │ ├── commands.jsxinc │ │ ├── internal.jsxinc │ │ ├── processing.jsxinc │ │ └── workflows.jsxinc │ ├── config.jsxinc │ ├── data │ │ ├── built_commands.jsxinc │ │ └── built_strings.jsxinc │ ├── helpers.jsxinc │ ├── io.jsxinc │ ├── palettes │ │ ├── commandPalette.jsxinc │ │ ├── customCommands.jsxinc │ │ ├── fuzzy.jsxinc │ │ ├── nofuzz.jsxinc │ │ ├── palettes.jsxinc │ │ ├── pickerBuilder.jsxinc │ │ ├── startupBuilder.jsxinc │ │ └── workflowBuilder.jsxinc │ ├── polyfills.jsxinc │ └── user │ │ ├── actions.jsxinc │ │ ├── history.jsxinc │ │ ├── preferences.jsxinc │ │ └── user.jsxinc └── index.jsx ├── tests ├── manual_command_testing.jsx └── test_tool_commands.jsx └── tools ├── README.md ├── build_commands.py └── build_strings.py /.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | types 3 | venv 4 | working 5 | .prettierignore 6 | scratchpad.* 7 | node_modules 8 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: local 5 | hooks: 6 | - id: build-strings 7 | name: build strings 8 | entry: bash -c 'make build-strings' 9 | language: system 10 | types: [bash] 11 | always_run: true 12 | verbose: true 13 | - repo: local 14 | hooks: 15 | - id: build-commands 16 | name: build commands 17 | entry: bash -c 'make build-commands' 18 | language: system 19 | types: [bash] 20 | always_run: true 21 | verbose: true 22 | - repo: local 23 | hooks: 24 | - id: compile-script 25 | name: compile script 26 | entry: bash -c 'escompile src/index.jsx > AiCommandPalette.jsx && git add AiCommandPalette.jsx' 27 | language: system 28 | types: [bash] 29 | always_run: true 30 | verbose: true -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": true, 4 | "endOfLine": "lf", 5 | "htmlWhitespaceSensitivity": "css", 6 | "insertPragma": false, 7 | "singleAttributePerLine": false, 8 | "bracketSameLine": false, 9 | "jsxBracketSameLine": false, 10 | "jsxSingleQuote": false, 11 | "printWidth": 88, 12 | "proseWrap": "never", 13 | "quoteProps": "as-needed", 14 | "requirePragma": false, 15 | "semi": true, 16 | "singleQuote": false, 17 | "tabWidth": 2, 18 | "trailingComma": "es5", 19 | "useTabs": false, 20 | "vueIndentScriptAndStyle": false, 21 | "parser": "babel" 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "extendscript-debug", 5 | "request": "attach", 6 | "name": "Start debugger—AI", 7 | "hostAppSpecifier": "illustrator", 8 | "engineName": "main" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "DOCREQUIRED", 4 | "edittext", 5 | "LAOL", 6 | "listbox", 7 | "MAXVERSION", 8 | "MINVERSION", 9 | "ngram", 10 | "ngrams", 11 | "prefs", 12 | "SELREQUIRED" 13 | ] 14 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "compile script", 8 | "type": "shell", 9 | "command": "/Users/jbd/Dropbox/DEV/projects/extend-script-compiler/escompile.sh", 10 | "args": ["src/index.jsx", ">", "AiCommandPalette.jsx"], 11 | "group": "none", 12 | "presentation": { 13 | "reveal": "always", 14 | "panel": "shared" 15 | } 16 | }, 17 | { 18 | "label": "format script", 19 | "type": "shell", 20 | "command": "prettier", 21 | "args": ["-w", "AiCommandPalette.jsx"], 22 | "group": "none", 23 | "presentation": { 24 | "reveal": "always", 25 | "panel": "shared" 26 | } 27 | }, 28 | { 29 | "label": "compile", 30 | "type": "shell", 31 | "command": "echo 'compiling script...'", 32 | "group": "build", 33 | "presentation": { 34 | "reveal": "always", 35 | "panel": "shared" 36 | }, 37 | "dependsOrder": "sequence", 38 | "dependsOn": ["compile script", "format script"] 39 | }, 40 | { 41 | "label": "download data", 42 | "type": "shell", 43 | "command": "${command:python.interpreterPath}", 44 | "args": ["tools/build_data.py", "-d", ">", "src/include/data.jsxinc"], 45 | "group": "none", 46 | "presentation": { 47 | "reveal": "always", 48 | "panel": "shared" 49 | } 50 | }, 51 | { 52 | "label": "format data", 53 | "type": "shell", 54 | "command": "prettier", 55 | "args": ["-w", "src/include/data.jsxinc"], 56 | "group": "none", 57 | "presentation": { 58 | "reveal": "always", 59 | "panel": "shared" 60 | } 61 | }, 62 | { 63 | "label": "commands", 64 | "type": "shell", 65 | "command": "echo 'downloading updated commands...'", 66 | "group": "build", 67 | "presentation": { 68 | "reveal": "always", 69 | "panel": "shared" 70 | }, 71 | "dependsOrder": "sequence", 72 | "dependsOn": ["download data", "format data"] 73 | }, 74 | { 75 | "label": "copy-to-scripts-folder", 76 | "type": "shell", 77 | "command": "cp", 78 | "args": ["AiCommandPalette.jsx", "/Applications/Adobe Illustrator 2023/Presets.localized/en_US/Scripts"], 79 | "group": "build", 80 | "presentation": { 81 | "reveal": "always", 82 | "panel": "shared" 83 | } 84 | }, 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ### Added 10 | 11 | - Logger for debugging (with accompanying Enable/Disable Logging command) 12 | 13 | ### Fixed 14 | 15 | - ListBoxWrapper, CommandPalette cleanup 16 | 17 | ## [0.13.2] 2025-06-03 18 | 19 | ### Fixed 20 | 21 | - Update to previous fix for keyboard navigation (on Windows) from within the query box issue #33 [link](https://github.com/joshbduncan/AiCommandPalette/issues/33) 22 | - Fixed bug in Go To Artboard command issue #35 [link](https://github.com/joshbduncan/AiCommandPalette/issues/35) 23 | 24 | ## [0.13.1] 2025-05-28 25 | 26 | ### Fixed 27 | 28 | - Keyboard navigation from within the query box issue #33 [link](https://github.com/joshbduncan/AiCommandPalette/issues/33) 29 | 30 | ## [0.13.0] 2025-05-22 31 | 32 | ### Added 33 | 34 | - Custom User Commands `Add Custom Commands` suggested by [rutgerrrrr](https://github.com/joshbduncan/AiCommandPalette/issues/32) 35 | 36 | ### Fixed 37 | 38 | - Picker builder name and save elements activation validator 39 | 40 | ### Changed 41 | 42 | - `devInfo.folder` changed to `userPrefsFolder` 43 | 44 | ## [0.12.1] 2025-04-29 45 | 46 | ### Added 47 | 48 | - Added new menu command `Help > Tutorials...` provided by [@sttk3](https://community.adobe.com/t5/illustrator-discussions/executemenucommand-command-list/m-p/15295058/page/2#M441548) 49 | 50 | ## [0.12.0] 2025-04-25 51 | 52 | ### Added 53 | 54 | - Added new menu command Type > Type Conversion (Convert To Point Type, Convert To Area Type) provided by [@sttk3](https://community.adobe.com/t5/illustrator-discussions/executemenucommand-command-list/m-p/15289315/page/2#M441331) 55 | 56 | ### Changed 57 | 58 | - All command and string data is now stored in this repo for version control 59 | - Update menu commands provided by [@sttk3](https://community.adobe.com/t5/illustrator-discussions/executemenucommand-command-list/m-p/15289315/page/2#M441331) 60 | 61 | ## [0.11.6] 2025-02-17 62 | 63 | ### Added 64 | 65 | - New menu and tool commands provided by [sttk3](https://community.adobe.com/t5/illustrator-discussions/executemenucommand-command-list/m-p/14915000/page/2#M435227) 66 | - Menu Commands 67 | - Object > Ungroup All 68 | - Window > Toolbars > Getting Started 69 | 70 | ## [0.11.5] 2024-11-25 71 | 72 | ### Fixed 73 | 74 | - added sanity check for command download script 75 | - added finally block to all i/o operations 76 | 77 | ## [0.11.4] 2024-11-14 78 | 79 | ### Fixed 80 | 81 | - `activeDocument` naming conflict with external script (thanks Colt Peterson) 82 | 83 | ## [0.11.3] 2024-10-14 84 | 85 | ### Added 86 | 87 | - New menu and tool commands provided by [sttk3](https://community.adobe.com/t5/illustrator-discussions/executemenucommand-command-list/m-p/14915000/page/2#M423329) 88 | - Menu Commands 89 | - Object > Objects on Path > Attach... 90 | - Object > Objects on Path > Options... 91 | - Object > Objects on Path > Expand 92 | - Window > Type > Reflow Viewer 93 | - Tool Commands 94 | - Objects on Path 95 | 96 | ## [0.11.2] 2024-10-08 97 | 98 | ### Fixed 99 | 100 | - Removed unnecessary spaces at the lines (caused by compiler) 101 | 102 | ## [0.11.1] 2024-09-30 103 | 104 | ### Added 105 | 106 | - New menu commands provided by [sttk3](https://community.adobe.com/t5/illustrator-discussions/executemenucommand-command-list/m-p/14889553/page/2#M421919) 107 | - File > Generate Vectors (Beta)... 108 | - Object > Gen Shape Fill (Beta)... 109 | - Window > Generate Patterns (Beta) 110 | 111 | ### Fixed 112 | 113 | - Fuzzy command matches ignores regex special `.*+?^=!:${}()|\[\]\/\\]` characters when matching commands names 114 | 115 | ## [0.11.0] 2024-07-12 116 | 117 | ### Added 118 | 119 | - fuzzy matching (e.g. looking for 'Outline Stroke', queries 'oust', 'ou st', 'st ou' will all work) [Issue #27](https://github.com/joshbduncan/AiCommandPalette/issues/26) 120 | - query cache to speed things up a bit (especially when backspacing/clearing characters) 121 | - custom pickers [Issue #27](https://github.com/joshbduncan/AiCommandPalette/issues/27) 122 | - in-place editing of some workflow steps (via double-click and edit button) 123 | 124 | ### Changed 125 | 126 | - removed searching on type command (added back 'all menu commands', 'all tools') 127 | 128 | ## [0.10.0] 2024-01-25 129 | 130 | ### Added 131 | 132 | - Startup builder/customizer to set exactly what displays when Ai Command Palette first opens 133 | - Confirmation dialog when clearing recent commands 134 | 135 | ### Changed 136 | 137 | - Complete rebuild of command palette, goto palette, and workflow builder (ScriptUI dialog) 138 | - Better sorting with recent commands showing higher in results list (Issue #24) 139 | - Query now also uses the command type when searching (can be toggled on/off) 140 | - Rebuilt preferences updater to keep old prefs working with new versions and functions 141 | - Split `config` and `builtin` command types in [CSV Builder Sheet](https://docs.google.com/spreadsheets/d/1T-pBrLAOL3WuF1K7h6Wo_vIUa0tui9YiX591YqqKMdA/edit#gid=800918162) 142 | - The above change affected the following commands 143 | - `config_buildWorkflow` -> `builtin_buildWorkflow` 144 | - `config_editWorkflow` -> `builtin_editWorkflow` 145 | - `config_allWorkflows` -> `builtin_allWorkflows` 146 | - `config_loadScript` -> `builtin_loadScript` 147 | - `config_allScripts` -> `builtin_allScripts` 148 | - `config_setFileBookmark` -> `builtin_loadFileBookmark` 149 | - `config_setFolderBookmark` -> `builtin_loadFolderBookmark` 150 | - `config_allBookmarks` -> `builtin_allBookmarks` 151 | - `config_allActions` -> `builtin_allActions` 152 | - `config_clearRecentCommands` -> `config_clearHistory` 153 | - `defaults_recentCommands` -> `builtin_recentCommands` 154 | - `defaults_settings` -> `config_settings` 155 | 156 | ### Fixed 157 | 158 | - Correct number of (full) table rows (commands) show on Windows (Issue #22) 159 | - Added back missing Document Report command 160 | 161 | ## [0.9.3] 2023-11-27 162 | 163 | ### Fixed 164 | 165 | - Bug that caused an error when cycling from the top to the bottom of the commands when there is less than 9 items. 166 | 167 | ### Changed 168 | 169 | - `Recent Commands` changed to a default command so it quicker to access (shows up on start-up). (Issue #20) 170 | 171 | ## [0.9.2] 2023-11-15 172 | 173 | ### Added 174 | 175 | - Ability for end-to-end scrolling. So, if you press the up key while on the first list item it will jump to the bottom and if you are at the bottom of the list options and press the down key, you'll jump all the way back to the top. Thanks for the idea @creold Issue #20. 176 | 177 | ## [0.9.1] 2023-11-09 178 | 179 | ### Fixed 180 | 181 | - Fixed issue where the queryable listbox within the workflow builder was showing incorrect results ([Issue #20](https://github.com/joshbduncan/AiCommandPalette/issues/20)) 182 | 183 | ## [0.9.0] 2023-10-24 184 | 185 | ### [Added] 186 | 187 | - New commands for version 28.0 (provided by [sttk3](https://community.adobe.com/t5/user/viewprofilepage/user-id/6940356)) 188 | 189 | ### [Changed] 190 | 191 | - User commands (Actions, Bookmarks, Scripts, and Workflows) not longer have their command type (e.g. "Action: ", "Bookmark: ", "Script: ", or "Workflow: ") prepended to their name. This was causing issues with localized keys in the user preferences json file. 192 | - To help find loaded Actions, Bookmarks, Scripts, and Workflows, there are four new built-in commands that can be found in the "Ai Command Palette Settings..." menu. 193 | - Commands run inside of a workflow are no longer added to the recent commands list 194 | - Command palette listbox now shows command types 195 | - Update scoring for command palette searching 196 | - sanitizing input and commands 197 | - all string now in lowercase 198 | - scoring bump for exact matches 199 | 200 | ### [Fixed] 201 | 202 | - incorrect minimum version for some of the Select > Same > ..." menu options from issue #19 (thanks @creold for your help) 203 | 204 | ## [0.8.1] 2023-09-18 205 | 206 | ### [Fixed] 207 | 208 | - Missing localized strings 209 | 210 | ## [0.8.1] 2023-09-18 211 | 212 | ### [Added] 213 | 214 | - Old incompatible preferences files will automatically be updated to work with v0.8.0 of the script 215 | 216 | ## [0.8.0] 2023-09-18 217 | 218 | ### [Changed] 219 | 220 | - Commands are now saved to the user preferences file with an 'id' instead of the localized command string (which was causing errors) 221 | - THIS IS A BREAKING CHANGE and will require users to setup new preferences. Users will be alerted is they have an older incompatible preference file. 222 | 223 | ### [Fixed] 224 | 225 | - check command to see if an active selection is required 226 | 227 | ## [0.7.1] 2023-09-11 228 | 229 | ### Fixed 230 | 231 | - Encoding errors for translations in [build_commands.py](/tools/build_commands.py) 232 | 233 | ## [0.7.0] 2023-06-26 234 | 235 | ### Added 236 | 237 | - New Built-In Commands 238 | - Open Recent Files (based on File > Open Recent Files menu) 239 | - Commands requiring an active document filtering and error catching 240 | - Commands are now filtered out (hidden) if they require a document to be open and none are 241 | - This required adding a new column to the build data spreadsheet "docRequired" 242 | - Commands in workflows that are running and require an active document to be open and none are now alert the user asking if they would like to continue. 243 | - Commands requiring an active selection filtering and error catching 244 | - Commands are now filtered out (hidden) if they require an selection selection 245 | - This required adding a new column to the build data spreadsheet "selRequired" 246 | - Commands in workflows that are running and require an active selection to be open and none are now alert the user asking if they would like to continue. 247 | - Recent Commands 248 | - Access your 25 most recent palette commands with 'Recent Commands...' 249 | - You can also clear them using the 'Clear Recent Commands' command 250 | - Command are stored in your preferences file 251 | 252 | ### Fixed 253 | 254 | - While filtering the command palette ScriptUI would truncate some item (bug) so I implemented a fix within the `scoreMatches()` function that adds a temporary item to the list longer than any matches. That temporary item is then removed after the new list is created. 255 | - Fixed workflow saving when workflow with same name already exist. 256 | - 'Go To Artboard' now works correctly when there are multiple artboards of the same name 257 | 258 | ### Changed 259 | 260 | - 'Go To Open Document' dialog 261 | - Active document is now included in the list and is marked with an 'x' 262 | - Document color mode added to the name for reference 263 | - Sorting (scoring) of matched commands now takes into account recently used commands 264 | - New commands from sttk3 on the Adobe Forum 265 | - Window > Retype (Beta) 266 | - Edit > Edit Colors > Generative Recolor (Beta) 267 | 268 | ### Removed 269 | 270 | - Removed `runAction` case in `scriptAction()` switch statement as it is no longer used. 271 | 272 | ## [0.6.1] 2023-02-22 273 | 274 | ### Fixed 275 | 276 | - Document Report checks to see if file has been changed since last saved and alerts user since that could cause incorrect information in the report. 277 | 278 | ## [0.6.0] 2023-02-22 279 | 280 | ### Added 281 | 282 | - New Built-In Commands 283 | - Redraw Windows 284 | - Reveal Active Document On System 285 | - Active Document Report 286 | - Generate a custom document report that includes basic file info, artboards, embedded items, fonts, layers, placed items, and spot color information. 287 | - Customize what is included in the report by selecting checkboxes for the items listed above (all included by default) 288 | - Report can be copied right from the window or saved out to a text file. 289 | - Export Active Artboard As PNG 290 | - Export Document Variables 291 | - Go To Open Document 292 | - Set File and Folder Bookmarks 293 | - File bookmarks open up directly in Ai 294 | - Accepted file types (taken from Ai open dialog): "ai", "ait", "pdf", "dxf", "avif", "BMP", "RLE", "DIB", "cgm", "cdr", "eps", "epsf", "ps", "emf", "gif", "heic", "heif", "eps", "epsf", "ps", "jpg", "jpe", "jpeg", "jpf", "jpx", "jp2", "j2k", "j2c", "jpc", "rtf", "doc", "docx", "PCX", "psd", "psb", "pdd", "PXR", "png", "pns", "svg", "svgz", "TGA", "VDA", "ICB", "VST", "txt", "tif", "tiff", "webp", "wmf" 295 | - Folder bookmarks open on your system (Mac Finder or Windows Explorer) 296 | 297 | ### Changed 298 | 299 | - Updated command query scoring filter to show less matches (only top results) 300 | - Command "Load Scripts..." -> "Load Script(s)..." 301 | 302 | ### Fixed 303 | 304 | - Incorrect localized var name for load script window 305 | - Up/Down keyboard navigation from the command palette search box 306 | 307 | ## [0.5.0] 2023-02-03 308 | 309 | ### Added 310 | 311 | - Go To Functionality 312 | - new commands: "Go To Artboard...", "Go To Named Object" (any named page item) 313 | - Go To Artboard - select and artboard from the list, center and zoom view to selection 314 | - Go To Named Object - select any named page items from the list, select object then center and zoom view to the selected object 315 | - new command palette type `goToPalette` built for these function 316 | - works a bit differently than the original command palette in that is works with an array of actual Ai objects and returns the selected object instead of the `selection.text` from the listbox 317 | 318 | ### Changed 319 | 320 | - build_data.py 321 | - can now download data right from live Google Sheet using the `-d/--download` flag 322 | - input file now must be specified with the `-i, --input` flag 323 | - script can read from stdin using `-i -` 324 | - no more need for `sed` command as new lines are now escaped properly within Python 325 | 326 | ## [0.4.3] 2022-11-06 327 | 328 | ### Fixed 329 | 330 | - versionCheck function comparisons were incorrect 331 | 332 | ## [0.4.2] 2022-10-25 333 | 334 | ### Added 335 | 336 | - Some older tools with maxVersions 337 | - Still need to update translations 338 | - [Simple script](/tools/Test%20Ai%20Commands.jsx) for testing Ai menu and tool commands 339 | 340 | ### Fixed 341 | 342 | - typo for command.maxVersion in version check function of base jsx file 343 | 344 | ## [0.4.1] 2022-10-25 345 | 346 | ### Added 347 | 348 | - New tools and menu items for [Ai 2023 v27.0](https://helpx.adobe.com/illustrator/using/whats-new.html) 349 | - Still need to update translations 350 | - [Simple script](/tools/Test%20Ai%20Commands.jsx) for testing Ai menu and tool commands 351 | 352 | ### Fixed 353 | 354 | - typo for command.maxVersion in version check function 355 | 356 | ## [0.4.0] 2022-10-21 357 | 358 | ### Added 359 | 360 | - Real-Time Localization 361 | - Handled within the main script file via the ExtendScript `localize()` function. 362 | - Commands are built at run-time for the current system locale 363 | - New project folder structure 364 | - Common functionality split into separate files/modules for easier development 365 | - Final script is compiled into the single jsx file [AiCommandPalette.jsx](AiCommandPalette.jsx) 366 | - Settings menu only shows certain commands when applicable 367 | 368 | ### Changed 369 | 370 | - Script was rewritten from the ground up 371 | - Variable names have been changed which will break any setting your have save on your system 372 | - Settings file has been renames to make it easy to roll-back to a previous version 373 | 374 | ### Removed 375 | 376 | - Redundant and no longer needed functions 377 | - Excess command data to speed things up 378 | 379 | ### Fixed 380 | 381 | - Unicode errors for Conté Crayon => Cont\u00E9 Crayon 382 | 383 | ## [0.3.0] 2022-09-15 384 | 385 | ### Added 386 | 387 | - Ai Version Functionality Checker 388 | - Original idea brought up by [Sergey Osokin](https://github.com/creold) in issue [#6 selectTool](https://github.com/joshbduncan/AiCommandPalette/issues/6) 389 | - Ai Command Palette now checks the current Ai version at startup to determine which menu commands and tools are compatible with your version of Adobe Illustrator. 390 | - If your version doesn't meet the minimum version (or maximum version) of a menu command or tool, it will not be available. 391 | - Any previous workflows that use a menu command or tool will no longer work. You'll be notified of this when you try to run the workflow. You can edit the workflow to remove that menu command or tool. 392 | - This should allow Ai Command Palette to easily update with the ever changing Ai menu commands and tools. 393 | - Any commands/tools can now have properties `minVersion` and `maxVersion`. 394 | - If either are present and your Ai version doesn't meet the requirement Ai Command Palette will remove that functionality at startup. 395 | - All current min/max versions for menu commands were referenced from the [menuCommandString](https://judicious-night-bca.notion.site/3346ecd2789d4a55aa043d3619b97c58?v=30f64aad6d39424a9e4f138fad06a126) list provided by [sttk3](https://community.adobe.com/t5/user/viewprofilepage/user-id/6940356). 396 | - Please note that if the min version listed in the list above was below 17 it was ignored and if the max version was listed at 99 it was also ignored. Ai Command Palette doesn't require a min or max version to be listed for menu commands. All menu commands are executed within a try/catch block so it will fail gracefully and let you know why. 397 | - Since they `app.selectTool()` method wasn't introduced until Ai version 24, all tools have that as their min version. 398 | - Ai Tools and Ai Menu Commands CSV files 399 | - [menu_commands.csv](/commands/menu_commands.csv) is where I will track all available menu commands (for use with `app.executeMenuCommand()`) going forward along with their min and max versions. If you see anything that needs to be changed to updated please submit a PR. 400 | - [tool_commands.csv](/commands/tool_commands.csv) will track all available tool commands (for use with `app.selectTool()`) going forward along with their min and max versions. If you see anything that needs to be changed to updated please submit a PR. 401 | - If you have updates to either file or see something that is incorrect, please [file an issue](https://github.com/joshbduncan/AiCommandPalette/issues) and I'll check it out. 402 | - New Build Tools 403 | - [build_commands.py](/tools/build_commands.py) builds the built-in menu commands, the built-in tool commands, and the config commands objects directly from [menu_commands.csv](/commands/menu_commands.csv), [config_commands.csv](/commands/config_commands.csv), and [tool_commands.csv](/commands/tool_commands.csv), so any updates are easier to track and implement. 404 | - New way to build config menu 405 | 406 | ### Changed 407 | 408 | - [translate.py](/tools/build_translations.py) moved to the tools folder. 409 | 410 | ### Fixed 411 | 412 | - Typo in workflow builder move down function caused it to move the wrong items when multiple items were selected. 413 | - Typo on renamed function for moving steps in Workflow builder. (`sortIndexes`) 414 | - Incorrect regex when editing workflows 415 | - Workflow builder name and ok being enabled at wrong times 416 | - Changed some variables from const back to var after reported issues 417 | - Russian and German translations 418 | - ShowBuiltinTools command now checks to make sure there are tools to display before showing the command palette 419 | - EditWorkflows command now checks to make sure there any workflows before showing the command palette 420 | - Windows OS Flicker Bug [issue #8](https://github.com/joshbduncan/AiCommandPalette/issues/8) 421 | - Very clever solution provided by [Sergey Osokin](https://github.com/creold) 422 | - Found that simulating a `` key press via VBScript was the best solution for keeping the user experience the same on Windows and Mac. 423 | - Only effects Windows users. 424 | 425 | ## [0.2.4] - 2022-08-17 426 | 427 | ### Changed 428 | 429 | - More language updates 430 | - Updates to German by Kurt Gold 431 | - Updates ro Russian by [Sergey Osokin](https://github.com/creold) 432 | 433 | ## [0.2.3] - 2022-08-17 434 | 435 | ### Changed 436 | 437 | - Increased the palette size to accommodate alternate languages with longer text 438 | - Workflow steps are now multiselect (you can select more than one at a time) 439 | - You can move multiple steps up and down (if the selection is contiguous) 440 | - You can deleted multiple steps in one-click (not required to be contiguous) 441 | 442 | ## [0.2.2] - 2022-08-17 443 | 444 | ### Added 445 | 446 | - New localized Russian version from @creold 447 | 448 | ### Changed 449 | 450 | - FIX unicode characters where needed 451 | - Updated menu commands 452 | - German translations updates by Kurt Gold 453 | 454 | ## [0.2.1] - 2022-08-16 455 | 456 | ### Changed 457 | 458 | - FIX to German tool names 459 | - FIX to translate.py to only replace whole strings 460 | - Updated sample.csv 461 | - Update dialog strings to make it easier to do translation 462 | 463 | ## [0.2.0] - 2022-08-16 464 | 465 | ### Added 466 | 467 | - Ability to activate 80+ of Ai's built-in tools 468 | - Ability to **edit workflows** (previously called custom commands) 469 | - Workflow action validation 470 | - Any commands no longer available are tagged with **\*\*DELETED\*\*** 471 | - If a workflow has any deleted steps it will present a warning and **not run**. 472 | - New menu command "Workflows Needing Attention" 473 | - Helps locate any workflows that have steps/actions that were deleted and need editing. 474 | - Takes you right to the workflow editor to fix the problem. 475 | - When any commands are deleted using "Delete Commands" they will be marked as **\*\*DELETED\*\*** in any workflows using them at the time of deletion 476 | - Error checking with alerts for most functions 477 | - YouTube Video Demo 478 | - A check to make sure loaded scripts still exist at the location they were loaded. 479 | - If a script doesn't exist it will be removed from user prefs. 480 | - Will update so any workflows that rely on a script that no longer exists can be edited 481 | - Added docstrings 482 | - Added a Python translator for translating commands and dialogs to other languages [LEARN MORE](/localization/README.md). 483 | - All prompts, alerts, and commands can be translated. 484 | - Starting with German (DE) translation compiled with help of [Kurt Gold](https://community.adobe.com/t5/user/viewprofilepage/user-id/8354168). 485 | - Works by creating a CSV file "[Language].txt" in the localization folder. 486 | 487 | ### Changed 488 | 489 | - **CUSTOM COMMANDS** are now called **WORKFLOWS** 490 | - UPDATED and TESTED all built-in menu commands. 491 | - Now match format, text, and order of latest Illustrator version. 492 | - Tested in Ai version 26.4.1. 493 | - Hide Built-In Commands no longer shows workflows 494 | - `scoreMatches()` improvements 495 | - using regex to match instead of indexOf 496 | - not counting repeating words for better scoring 497 | - simplified sorting function 498 | - Up/Down arrow key functionality while in the search box 499 | - One problem with this functionality is that when a listbox listitem is selected via a script the API moves the visible "frame" of items so that the new selection is at the top. This is not standard behavior, and not even how the listbox behaves when you use the up and down keys inside of the actual listbox. 500 | - Also, if a selection is made inside of the actual listbox frame by the user (via mouse or keyboard) the API doesn't offer any way to know which part of the list is currently visible in the listbox "frame". If the user was to re-enter the searchbox and then hit an arrow key the above event listener will not work correctly so I just move the next selection (be it up or down) to the middle of the "frame". 501 | 502 | ### Removed 503 | 504 | - Removed around 100 commands that are no longer active or don't work in Ai version 26.4.1. 505 | 506 | ## [0.1.0] - 2022-07-27 507 | 508 | ### Added 509 | 510 | - First official release! -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Josh Duncan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | PWD := $(realpath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))) 3 | 4 | .DEFAULT_GOAL := help 5 | .PHONY: help 6 | ##@ General 7 | help: ## Display this help section 8 | @echo $(MAKEFILE_LIST) 9 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 10 | 11 | ##@ Development 12 | build-strings: ## build string objects from csv input file 13 | @echo "⚙️ building string objects..." 14 | python3 tools/build_strings.py data/strings.csv | prettier > src/include/data/built_strings.jsxinc 15 | 16 | build-commands: ## build command objects from csv input file 17 | @echo "⚙️ building command objects..." 18 | python3 tools/build_commands.py | prettier > src/include/data/built_commands.jsxinc 19 | 20 | copy: ## copy compiled script to Ai scripts folder 21 | cp AiCommandPalette.jsx /Applications/Adobe\ Illustrator\ 2025/Presets.localized/en_US/Scripts 22 | 23 | reset: compile copy ## re-compile script and copy to Ai scripts folder 24 | 25 | ##@ Build 26 | watch: ## watch for file changes and compile 27 | watchman-make -p 'src/**/*.jsx*' -t reset 28 | 29 | compile: ## compile script using escompile 30 | escompile src/index.jsx > AiCommandPalette.jsx 31 | -------------------------------------------------------------------------------- /NOTES.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | Just some notes to help me remember how things work... 4 | 5 | ## Command Objects 6 | 7 | Every command is a simple object like below. 8 | 9 | ```json 10 | { 11 | "id": "menu_new", 12 | "action": "new", 13 | "type": "menu", 14 | "name": { 15 | "en": "File > New...", 16 | "de": "Datei > Neu...", 17 | "ru": "Файл > Новый...", 18 | }, 19 | "docRequired": false, 20 | "selRequired": false, 21 | "hidden": false, 22 | } 23 | ``` 24 | 25 | ### Command Object Examples 26 | 27 | All commands object have a standard set of properties, but different types of commands may also have extra properties. 28 | 29 | ### Action 30 | 31 | ```json 32 | { 33 | "id": "action_sample_action", 34 | "action": "action", 35 | "type": "action", 36 | "set": "Sample Action Set", 37 | "name": "Sample Action", 38 | "docRequired": false, 39 | "selRequired": false, 40 | "hidden": false, 41 | } 42 | ``` 43 | 44 | ### Bookmark (File or Folder) 45 | 46 | ```json 47 | { 48 | "id": "bookmark_Awesome_Vectors_ai", 49 | "action": "bookmark", 50 | "type": "file", // or "folder" 51 | "path": "/path/to/awesome/vectors/Awesome Vectors.ai", 52 | "name": "Awesome Vectors.ai", 53 | "docRequired": false, 54 | "selRequired": false, 55 | "hidden": false, 56 | } 57 | ``` 58 | 59 | ### Menu 60 | 61 | ```json 62 | { 63 | "id": "menu_new", 64 | "action": "new", 65 | "type": "menu", 66 | "name": { 67 | "en": "File > New...", 68 | "de": "Datei > Neu...", 69 | "ru": "Файл > Новый...", 70 | }, 71 | "docRequired": false, 72 | "selRequired": false, 73 | "hidden": false, 74 | } 75 | ``` 76 | 77 | ### Script 78 | 79 | ```json 80 | { 81 | "id": "script_Automate_Something_jsx", 82 | "action": "script", 83 | "type": "script", 84 | "path": "/path/to/automation/scripts/Automate Something.jsx", 85 | "name": "Automate Something.jsx", 86 | "docRequired": false, 87 | "selRequired": false, 88 | "hidden": false, 89 | } 90 | ``` 91 | 92 | ### Tool 93 | 94 | ```json 95 | { 96 | "id": "tool_Adobe_Direct_Select_Tool", 97 | "action": "Adobe Direct Select Tool", 98 | "type": "tool", 99 | "name": { 100 | "en": "Direct Selection Tool", 101 | "de": "Direktauswahl-Werkzeug", 102 | "ru": "Инструмент: Прямое выделение", 103 | }, 104 | "minVersion": 24, 105 | "docRequired": true, 106 | "selRequired": false, 107 | "hidden": false, 108 | } 109 | ``` 110 | 111 | ### Workflow 112 | 113 | ```json 114 | { 115 | "id": "workflow_time_saving_workflow", 116 | "action": "workflow", 117 | "type": "workflow", 118 | "actions": ["menu_command", "tool_command", "script", "other_workflow"], 119 | "name": "Time Saving Workflow", 120 | "docRequired": false, 121 | "selRequired": false, 122 | "hidden": false, 123 | } 124 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ai Command Palette 2 | 3 | Boost your Adobe Illustrator efficiency with quick access to **most Menu Commands** and **Tools**, all of your **Actions**, **Bookmarked** file or folder, and any **Scripts** right from your keyboard. 4 | 5 | And, with custom **Workflows**, you can combine multiple commands, actions, and scripts to get things done in your own way. Replace repetitive tasks with workflows and boost your productivity. 6 | 7 | !["Ai Command Palette"](/images/palette.png) 8 | 9 | ## Why? 10 | 11 | If you have worked with Alfred app or VS Code you know how great the “command palette” is… Well, I wanted that same functionality in Adobe Illustrator. 12 | 13 | ## Video Demo 14 | 15 | [![Ai Command Palette Demo Video](/images/ai-command-palette-youtube-1.png)](https://www.youtube.com/watch?v=Jhh_Dvfs0ro) 16 | 17 | ## Features 18 | 19 | ### Menu Commands 20 | 21 | With nearly 500 Menu Commands available to Ai Command Palette ([view all here](data/menu_commands.csv)), there isn't much you can't access. I have tested all of them on Adobe Illustrator v26.4.1. 22 | 23 | !["Built-In Menu Commands"](/images/menu-commands.png) 24 | 25 | ### Tools 26 | 27 | Quickly access over almost 80 of Ai's built-in Tools ([view all here](/data/tool_commands.csv)) right from your keyboard without having to remember so many keyboard shortcuts. 28 | 29 | !["Built-In Tools"](/images/tool-commands.png) 30 | 31 | ### Actions 32 | 33 | Access all of your saved Actions using Ai Command Palette. Actions are listed "Action: Action Name [Action Set]" to make searching/accessing easy. If you create a new action you will have to quit Illustrator and reopen before it will become available in Ai Command Palette. 34 | 35 | !["Actions"](/images/actions.png) 36 | 37 | ### Bookmarks 38 | 39 | Find yourself opening the same file inside of Illustrator multiple times a day? Or need to easily access a folder full of project assets? Bookmarks have you covered. 40 | 41 | > [!NOTE] 42 | > File bookmarks open right into Illustrator and Folder bookmarks open in your file system. 43 | 44 | !["Actions"](/images/bookmarks.png) 45 | 46 | ### Custom Commands 47 | 48 | Have any Illustrator plugins or add-ons essential to your workflow? If they offer menu and tool activation via ExtendScript, you can add them as custom commands to access them even faster. 49 | 50 | For example, I use (and love ❤️) [Astute Graphics PlugIns](https://astutegraphics.com), so I have a bunch of them added to my palette as custom commands. 51 | 52 | Add your own with the `Add Custom Commands` command. Custom Commands are input in a CSV like format `Command Name,Command Action,Command Type`. 53 | 54 | > [!TIP] 55 | > I'm keeping a list of known [external plugin commands here](/data/custom_commands.csv) for reference. If you know of more, please do a pull request to add them for others to enjoy. 56 | 57 | To discover the `Command Action` id for your own menu/tool commands, follow the directions below (source [krasnovpro on Adobe Forums](https://community.adobe.com/t5/illustrator-discussions/executemenucommand-command-list/td-p/13131490/page/3)): 58 | 59 | 1. Go to Edit → Keyboard Shortcuts 60 | 2. Bind your tool to the 0 (zero key) 61 | 3. Save keyboard shortcut set 62 | 4. Open saved keyboard shortcuts file 63 | 5. Find any occurrences of /Key 48 (like below) 64 | 6. Remove the leading slash and all backslashes, and you get the command name 65 | 66 | ``` 67 | /Snap\ To\ Collisions\ Tool { 68 | /Context 0 69 | /Modifiers 0 70 | /Represent 48 71 | /Key 48 72 | } 73 | ``` 74 | 75 | ```csv 76 | Astute Graphics - Arc by Points,Arc by Points Tool,tool 77 | Astute Graphics - Block Shadow,AG Block Shadow Tool,tool 78 | Astute Graphics - Circle by Points,Circle by Points Tool,tool 79 | Astute Graphics - Color Select,AG Color Select Tool,tool 80 | ``` 81 | 82 | 83 | 84 | ### Custom Pickers 85 | 86 | In need of a custom command palette picker for something you do on a regular basis, or want to enhance a script you already use with a the ability to pick from a set of options? Use the `Build Picker...` command to create your very own searchable command palette. These are also great for use in [workflows](#workflows). 87 | 88 | !["Actions"](/images/custom-picker.png) 89 | 90 | The selected item will be saved to the environment variable `aic_picker_last` and can be accessed via a script using `$.getenv("aic_picker_last")`. 91 | 92 | Custom Pickers also allow for multiple selections. When enabled, the selected items are saved as an array so to access them make sure to use the [eval()](https://extendscript.docsforadobe.dev/integrating-external-libraries/defining-entry-points-for-indirect-access.html#eval) method like below. 93 | 94 | > [!TIP] 95 | > Any custom pickers inside of workflows can be edited inside of the workflow editor by double-clicking the picker workflow step, or by clicking the workflow steps edit button. 96 | 97 | ```javascript 98 | // anotherScript.jsx 99 | var colorFormats = eval($.getenv("aic_picker_last")); 100 | alert("Picked Color Formats\n" + colorFormats.join("\n")); 101 | ``` 102 | 103 | ### Scripts 104 | 105 | Load any JavaScript files (.js or .jsx) you want quick access to using the `Load Scripts...` command. 106 | 107 | > [!TIP] 108 | > **Want to load a bunch of scripts?** Multiple selections are allowed using your standard OS multiple selection tools. 109 | 110 | !["Scripts"](/images/scripts.png) 111 | 112 | ### Workflows 113 | 114 | Build fully automated automation workflows using Menu Commands, Tools, Actions, Scripts, or even other Workflows 🤯! 115 | 116 | Do You... 117 | - need to combine multiple scripts into one action? 118 | - need to add functionality to a script but don't know how or have the time? 119 | - need to perform an exact set of actions regularly and want to automate that? 120 | - need to quickly prototype the execution steps of a potential script? 121 | 122 | > [!TIP] 123 | > Workflows are a super powerful utility for beginners, pros, and everyone in between. The possibilities are almost limitless! 124 | 125 | !["Workflow Builder"](/images/workflow-builder.png) 126 | 127 | The simple workflow above takes your current selection of objects, blends them together, opens the blend options dialog so you can make any necessary adjustments, then expands your blend, and saves your file with an action. 128 | 129 | ## Go To Functionality 130 | 131 | Without leaving your keyboard, quickly jump to a specific artboard, pop to another open document, or zoom in on a specific names object. 132 | 133 | !["Go To"](/images/go-to-functionality.png) 134 | 135 | ## Document Report 136 | 137 | Illustrators Document Info palette offers lots of great info but it can be a little cumbersome to work with. To quickly get right to the info you need, Ai Command Palette offers a quick Document Report that includes, most of the basic info like fonts, artboards, placed image details, spot colors, etc... You can even save it out for future reference. 138 | 139 | !["Go To"](/images/document-report.png) 140 | 141 | ## Additional Built-In Commands 142 | 143 | Ai Command Palette has access to most of Illustrator's functionality via the menu command system but there are some features only available (or much easier to access) via the API that I have added as I encounter them. 144 | 145 | - Redraw Windows 146 | - Reveal Active Document On System 147 | - Export Active Artboard As PNG 148 | - Export Document Variables 149 | 150 | ## Settings 151 | 152 | You can access the settings for Ai Command Palette by selecting the "Command Palette Settings..." option (or via search). Most settings are self explanatory but I'll cover a few here to try and makes things clear. 153 | 154 | ### Hide Commands... 155 | 156 | If there are any Actions, built-in Menu Commands, or built-in Tools you don't want to see in Ai Command Palette you can easily hide them. Note, they will not be deleted, just hidden from the search results. 157 | 158 | > [!TIP] 159 | > **Need to hide a bunch of commands?** Multiple selections are allowed using your standard OS multiple selection tools. 160 | 161 | > [!TIP] 162 | > **Accidentally hide a command you didn't mean to?** To reveal any hidden commands, just use the "Reveal Commands..." function of Ai Command Palette. 163 | 164 | ### Reveal Preferences File 165 | 166 | All of your Ai Command Palette settings are saved on your system to a "json-like" preferences file. This includes any Scripts you load, Workflows you create, and Commands you have hidden. This command simply "reveals" your preferences file on your system. 167 | 168 | > [!CAUTION] 169 | > I would not recommend directly editing the file unless you know exactly what you are going. This function is mostly for making backups or sharing your preferences with others. 170 | 171 | ## Localization 172 | 173 | With the help of [Kurt Gold](https://community.adobe.com/t5/user/viewprofilepage/user-id/8354168) and [Sergey Osokin](https://github.com/creold), Ai Command Palette is currently localized for German and Russian versions of Illustrator. This includes all dialogs, alerts, menu commands, and tools. 174 | 175 | If anyone wants to offer localization for other languages you can [learn more here](/localization.md). 176 | 177 | !["Localization"](/images/localization.png) 178 | 179 | ## Installation 180 | 181 | I recommend installing this action into your scripts folder ([how-to](https://www.marspremedia.com/software/how-to-adobe-cc#illustrator)), then tying it to a keyboard shortcut using something like [Keyboard Maestro](https://www.keyboardmaestro.com/main/) (Mac), [BetterTouchTool](https://folivora.ai/) (Mac), or [AutoHotkey](https://www.autohotkey.com/) (Windows). 182 | 183 | > [!TIP] 184 | > I like to use the keyboard shortcut Command-Shift-P since it is somewhat mnemonic for "palette". 185 | 186 | ## Known Issues 🤦‍♂️ 187 | 188 | ### Keyboard Use 189 | 190 | Ai Command Palette was created to allow users to stay away from the mouse and do actions and operations via the keyboard. Funnily enough, there is a bug in ExtendScript that will delay/send the `Enter` key event after the palette is dismissed using the enter key. 191 | 192 | This issue doesn't effect very many actions and you may never notice it, but if you run the `About Illusatrator...` command, you'll notice it appears and immediately disappears. This is because ExtendScript essentially "presses" the enter key after showing the about splash screen. 193 | 194 | I have tried lots of work-a-rounds to fix this but haven't found a solution yet. 195 | 196 | ### Moving Between Palette Options Using Arrow Keys 197 | 198 | I have noticed that after some time without restarting Illustrator, the arrow keys stop moving up and down between palette options. Not sure of the cause but restarting Illustrator will get everything back operating normally. 199 | 200 | ### Running Actions 201 | 202 | There is a known ExtendScript issue when running actions from a script (e.g. Ai Command Palette). Some action steps **WILL NOT** work properly. I haven't tested all possible action but I know for sure most operations modifying the document selection are definitely broken. Let me know if you find others and I will file/update a bug report with Adobe. 203 | 204 | ## Warning ‼️ 205 | 206 | Using Ai Command Palette requires some basic knowledge of Illustrator. The script doesn't know which commands can or can't be run at the time of execution so tread carefully. I've included error checking where possible, so in most cases you should get an explanation when something breaks. 207 | 208 | > [!IMPORTANT] 209 | > There are some known issues with executing actions via a script. There are no known solutions to this so if you encounter errors with a particular Action, it just may not be suitable to execute via Ai Command Palette. 210 | 211 | 🐞 If you find a bug please [file an issue](https://github.com/joshbduncan/AiCommandPalette/issues). 212 | 213 | > [!TIP] 214 | > Most every action this script executes can be undone by choosing Edit > Undo (from the Edit menu at the top of your screen), or by pressing Command-Z (Mac) or Control+Z (Windows). 215 | 216 | ## Credits 217 | 218 | ### Localization 219 | - [Kurt Gold](https://community.adobe.com/t5/user/viewprofilepage/user-id/8354168) for his work localizing the German. 220 | - [Sergey Osokin](https://github.com/creold) for his work localizing the Russian version. 221 | 222 | ## Testing 223 | - [Kurt Gold](https://community.adobe.com/t5/user/viewprofilepage/user-id/8354168) 224 | - [Sergey Osokin](https://github.com/creold) 225 | 226 | ### Built-In Menu Commands and Tools 227 | - [krasnovpro](https://community.adobe.com/t5/user/viewprofilepage/user-id/9425584) 228 | - [Shalako Lee](https://github.com/shalakolee) 229 | - [sttk3](https://judicious-night-bca.notion.site/app-executeMenuCommand-43b5a4b7a99d4ba2befd1798ba357b1a) 230 | 231 | ### Other 232 | - Peter Kahrel for the amazing book [ScriptUI for Dummies](https://adobeindd.com/view/publications/a0207571-ff5b-4bbf-a540-07079bd21d75/92ra/publication-web-resources/pdf/scriptui-2-16-j.pdf). 233 | - [Sergey Osokin](https://github.com/creold) 234 | - For his clever `openURL()` function 235 | - For his help fixing the [Windows OS bug · Issue #8](https://github.com/joshbduncan/AiCommandPalette/issues/8) 236 | - [sttk3](https://community.adobe.com/t5/illustrator-discussions/get-names-of-actions-in-some-set/td-p/10365284) for the awesome bit of code that extracts current actions. 237 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Ai Command Palette 2 | 3 | Below you'll find a list of potential new features, needed fixes, issues, and anything else I think I might want to do with this project. ✌️ 4 | 5 | The format is based on [TODO.md](https://github.com/todomd/todo.md) 6 | 7 | ### Todo 8 | 9 | - [ ] documentation command 10 | - [ ] use gh wiki for documentation 11 | - [ ] build prefs screen 12 | - [ ] toggle for including actions in default search (actions, bookmarks, scripts, workflows) 13 | 14 | ### Doing 15 | 16 | 17 | ### Done ✓ 18 | 19 | - [x] fix and reenable old prefs version check 20 | - [x] fix overwriting bookmarks, scripts, and workflows 21 | - [x] ask if new scripts, workflows, and bookmarks should be added to the start screen 22 | - [x] startup screens customization: Allow the user to set what shows up when the command palette is first opened. Could be favorite commands, workflows, etc. 23 | - [x] update hidden commands to now use command id in setting file instead of name 24 | - [x] removal deletion of missing workflow commands, instead alert user of how to remove them 25 | - [x] sort filtered items by most used: Track command usage counts in the user settings file, and use that count in the scoring algorithm for sorting the palette items. 26 | - [x] store recent command history and make accessible via command palette 27 | - [x] recent files built-in action 28 | - [x] fix command truncation (e.g. 'Effect > Distort & Transform > Zig Z...') 29 | - [x] filter commands out if they require a document and none are open 30 | - [x] filter commands out if they require a selection 31 | - [x] alphabetize fonts and spot colors in document report 32 | - [x] file/folder bookmarks: allow users to save 'bookmarks' to commonly used files and folders, the use the File.execute() method to open them up. 33 | - [x] new built-in commands: add new commands available to the api and not already accessible via the menu system 34 | - [x] goto functionality: goto artboard, goto named object [(commit 0ad82b4)](https://github.com/joshbduncan/AiCommandPalette/commit/0ad82b4f250d49ebe5a0aacf87e7bd77bc4f46c0) 35 | -------------------------------------------------------------------------------- /data/builtin_commands.csv: -------------------------------------------------------------------------------- 1 | value,ignore,type,docRequired,selRequired,minVersion,maxVersion,en,de,ru,notes 2 | documentReport,,builtin,TRUE,FALSE,,,Active Document Report,,,https://ai-scripting.docsforadobe.dev/jsobjref/Document.html#document-artboards 3 | addCustomCommands,FALSE,builtin,FALSE,FALSE,,,Add Custom Commands...,,, 4 | allActions,FALSE,builtin,FALSE,FALSE,,,All Actions...,Alle Aktionen …,, 5 | allBookmarks,FALSE,builtin,FALSE,FALSE,,,All Bookmarks...,Alle Lesezeichen …,, 6 | allCustomCommands,FALSE,builtin,FALSE,FALSE,,,All Custom Commands..., 7 | allMenus,FALSE,builtin,FALSE,FALSE,,,All Menu Commands...,,, 8 | allPickers,FALSE,builtin,FALSE,FALSE,,,All Pickers...,,, 9 | allScripts,FALSE,builtin,FALSE,FALSE,,,All Scripts...,Alle Skripte …,, 10 | allTools,FALSE,builtin,TRUE,FALSE,,,All Tools...,,, 11 | allWorkflows,FALSE,builtin,FALSE,FALSE,,,All Workflows...,Alle Arbeitsabläufe …,, 12 | buildWorkflow,FALSE,builtin,FALSE,FALSE,,,Build Workflow...,Arbeitsablauf erstellen …,Создать набор команд, 13 | editWorkflow,FALSE,builtin,FALSE,FALSE,,,Edit Workflow...,Arbeitsablauf bearbeiten …,Редактировать набор команд, 14 | buildPicker,FALSE,builtin,FALSE,FALSE,,,Build Picker...,,, 15 | editPicker,FALSE,builtin,FALSE,FALSE,,,Edit Picker...,,, 16 | imageCapture,FALSE,builtin,TRUE,FALSE,,,Export Active Artboard As PNG,Ausgewählte Zeichenfläche als PNG exportieren,,https://ai-scripting.docsforadobe.dev/jsobjref/Document.html?highlight=document#document-imagecapture 17 | exportVariables,FALSE,builtin,TRUE,FALSE,,,Export Document Variables As XML,Variablen als XML exportieren,,https://ai-scripting.docsforadobe.dev/jsobjref/Document.html#document-exportvariables 18 | goToArtboard,FALSE,builtin,TRUE,FALSE,,,Go To Artboard...,Zeichenflächen auswählen …,Gehen Sie zur Zeichenfläche..., 19 | goToNamedObject,FALSE,builtin,TRUE,FALSE,,,Go To Named Object...,Benannte Objekte auswählen …,Перейти к именованному объекту..., 20 | goToDocument,FALSE,builtin,TRUE,FALSE,,,Go To Open Document,Geöffnete Dokumente auswählen …,, 21 | loadFileBookmark,FALSE,builtin,FALSE,FALSE,,,Load File Bookmark(s)...,Lesezeichen erstellen …,, 22 | loadFolderBookmark,FALSE,builtin,FALSE,FALSE,,,Load Folder Bookmark...,Lesezeichen-Ordner erstellen …,, 23 | loadScript,FALSE,builtin,FALSE,FALSE,,,Load Script(s)...,Skripte laden …,Загрузить скрипты, 24 | recentFiles,FALSE,builtin,FALSE,FALSE,,,Open Recent File...,Letzte Datei öffnen …,, 25 | recentCommands,FALSE,builtin,FALSE,FALSE,,,Recent Commands...,Letzte Befehle …,, 26 | redrawWindows,FALSE,builtin,TRUE,FALSE,,,Redraw Windows,Fenster aktualisieren,,https://ai-scripting.docsforadobe.dev/jsobjref/Application.html#application-redraw 27 | revealActiveDocument,FALSE,builtin,TRUE,FALSE,,,Reveal Active Document On System,Aktuelles Dokument im Dateimanager anzeigen,,https://ai-scripting.docsforadobe.dev/jsobjref/Document.html#document-path -------------------------------------------------------------------------------- /data/config_commands.csv: -------------------------------------------------------------------------------- 1 | value,ignore,type,docRequired,selRequired,minVersion,maxVersion,en,de,ru,notes 2 | about,FALSE,config,FALSE,FALSE,,,About Ai Command Palette...,Über Kurzbefehle …,Об Ai Command Palette, 3 | builtinCommands,FALSE,config,FALSE,FALSE,,,Built-in Commands...,,, 4 | clearHistory,FALSE,config,FALSE,FALSE,,,Clear History,Die letzten Befehle löschen,, 5 | customizeStartup,FALSE,config,FALSE,FALSE,,,Customize Startup Commands...,Customize Startup Commands...,, 6 | deleteCommand,FALSE,config,FALSE,FALSE,,,Delete Commands...,Befehle löschen …,Удалить команды, 7 | enableFuzzyMatching,FALSE,config,FALSE,FALSE,,,Enable Fuzzy Matching,,, 8 | disableFuzzyMatching,FALSE,config,FALSE,FALSE,,,Disable Fuzzy Matching,,, 9 | enableDebugLogging,FALSE,config,FALSE,FALSE,,,Enable Debug Logging,,, 10 | disableDebugLogging,FALSE,config,FALSE,FALSE,,,Disable Debug Logging,,, 11 | disableTypeInSearch,TRUE,config,FALSE,FALSE,,,Disable Searching on Command Type,,, 12 | enableTypeInSearch,TRUE,config,FALSE,FALSE,,,Enable Searching on Command Type,,, 13 | hideCommand,FALSE,config,FALSE,FALSE,,,Hide Commands...,Befehle ausblenden …,Скрыть команды, 14 | revealPrefFile,FALSE,config,FALSE,FALSE,,,Reveal Preferences File,Einstellungen-Datei anzeigen,Показать файл настроек, 15 | settings,FALSE,config,FALSE,FALSE,,,Ai Command Palette Settings...,Kurzbefehle – Einstellungen …,Настройки, 16 | unhideCommand,FALSE,config,FALSE,FALSE,,,Unhide Commands...,Befehle einblenden …,Показать команды, -------------------------------------------------------------------------------- /data/custom_commands.csv: -------------------------------------------------------------------------------- 1 | Command Name,Command Action,Command Type 2 | Astute Graphics - Arc by Points,Arc by Points Tool,tool 3 | Astute Graphics - Block Shadow,AG Block Shadow Tool,tool 4 | Astute Graphics - Circle by Points,Circle by Points Tool,tool 5 | Astute Graphics - Color Select,AG Color Select Tool,tool 6 | Astute Graphics - Connect,Connect Tool,tool 7 | Astute Graphics - Crop Image,Crop Image Tool,tool 8 | Astute Graphics - Curvature Circle,Curvature Circle Tool,tool 9 | Astute Graphics - Dynamic Corners,Dynamic Corners Tool,tool 10 | Astute Graphics - Dynamic Measure,Dynamic Measure Tool,tool 11 | Astute Graphics - Dynamic Shapes,Dynamic Shapes Tool,tool 12 | Astute Graphics - Dynamic Sketch,Dynamic Sketch Tool,tool 13 | Astute Graphics - Extend Path,Extend Path Tool,tool 14 | Astute Graphics - Gradient From Art,Gradient From Art Tool,tool 15 | Astute Graphics - Inflate Deflate,Inflate Deflate Tool,tool 16 | Astute Graphics - InkFlow,InkFlow Tool,tool 17 | Astute Graphics - InkScribe,InkScribe Tool,tool 18 | Astute Graphics - Lock Unlock,Lock Unlock Tool,tool 19 | Astute Graphics - MirrorMe,MirrorMe Tool,tool 20 | Astute Graphics - Offset,AG Offset Tool,tool 21 | Astute Graphics - Opacity Brush,OpacityBrushTool,tool 22 | Astute Graphics - Orient,Orient Tool,tool 23 | Astute Graphics - Orient Transform,Orient Transform Tool,tool 24 | Astute Graphics - PathScribe,PathScribe Tool,tool 25 | Astute Graphics - Perpendicular Line,Perpendicular Line Tool,tool 26 | Astute Graphics - Quick Orient,Quick Orient Tool,tool 27 | Astute Graphics - Randomini,RandominiTool,tool 28 | Astute Graphics - Randomino Start Point,Reference Point Setter Tool,tool 29 | Astute Graphics - Reform,Reform Tool,tool 30 | Astute Graphics - Reform Text,Reform Text Tool,tool 31 | Astute Graphics - Reprofile,Reprofile Tool,tool 32 | Astute Graphics - Reposition Point,Reposition Point Tool,tool 33 | Astute Graphics - Rotate At Collision,Rotate At Collision Tool,tool 34 | Astute Graphics - Rotate To Collision,Rotate To Collision Tool,tool 35 | Astute Graphics - Smart Remove Brush,Smart Remove Brush Tool,tool 36 | Astute Graphics - Snap To Collisions,Snap To Collisions Tool,tool 37 | Astute Graphics - Straighten,Straighten Tool,tool 38 | Astute Graphics - Stylism,Stylism Tool,tool 39 | Astute Graphics - Super Marquee,Super Marquee Tool,tool 40 | Astute Graphics - Tangent Circle,Tangent Circle Tool,tool 41 | Astute Graphics - Tangent Line,Tangent Line Tool,tool 42 | Astute Graphics - Texture,TextureTool,tool 43 | Astute Graphics - Texture Brush,TextureBrushTool,tool 44 | Astute Graphics - Trim and Join,AG Trim and Join Tool,tool 45 | Astute Graphics - Width Brush,Width Brush Tool,tool 46 | Astute Graphics - Width Eraser,Width Eraser Tool,tool 47 | Astute Graphics - Width Gradient,Width Gradient Tool,tool 48 | Astute Graphics - Width Selector,Width Selector Tool,tool -------------------------------------------------------------------------------- /data/strings.csv: -------------------------------------------------------------------------------- 1 | value,en,de,ru 2 | about,About,Über Kurzbefehle …,О скрипте 3 | ac_error_execution,Error executing action:\n%1\n\n%2,Fehler beim Ausführen der Aktion:\n%1\n\n%2,Ошибка запуска операции:\n%1\n\n%2 4 | action,Action,, 5 | Actions,Actions,Aktionen, 6 | active_document_not_saved,Active document not yet saved to the file system.,Das aktuelle Dokument wurde noch nicht gespeichert., 7 | add_custom_commands_dialog_title,Add Custom Commands,, 8 | artboard,Artboard,, 9 | artboards,Artboards,Zeichenflächen, 10 | bm_already_loaded,Bookmark already loaded.,Dieses Lesezeichen wurde bereits erstellt., 11 | bm_error_execution,Error opening bookmark:\n%1\n\n%2,Fehler beim Öffnen des Lesezeichens:\n%1\n\n%2, 12 | bm_error_exists,Bookmark no longer exists at original path. Try reloading.\n%1,"Das Lesezeichen ist an dieser Stelle nicht mehr vorhanden. Versuchen Sie, es nochmal zu laden.\n%1", 13 | bm_error_loading,Error loading bookmark:\n%1,Fehler beim Ladenn des Lesezeichens:\n%1, 14 | bm_load_bookmark,Load Bookmark(s),Lesezeichen erstellen, 15 | bm_total_loaded,Total bookmarks loaded:\n%1,Anzahl der geladenen Lesezeichen:\n%1, 16 | bookmark,Bookmark,Lesezeichen, 17 | Bookmarks,Bookmarks,Lesezeichen, 18 | builtin,Built-In,, 19 | cancel,Cancel,Abbrechen,Отмена 20 | cd_active_document_required,Command '%1' requires an active document. Continue Anyway?,Der Befehl '%1' erfordert ein geöffnetes Dokument. Trotzdem fortfahren?, 21 | cd_active_selection_required,Command '%1' requires an active selection. Continue Anyway?,Der Befehl '%1' erfordert eine Auswahl. Trotzdem fortfahren?, 22 | cd_all,Built-In Commands,, 23 | cd_clear_history_confirm,Are you sure you want to clear your history?\n\n PLEASE NOTE: This will remove any keyword latches you have.\n\nLearn more using builtin 'Documentation' command.,, 24 | cd_add_to_startup,Add new command(s) to your startup?,, 25 | cd_add_to_startup_title,Add To Startup Commands,, 26 | cd_delete_confirm,Delete Commands?\nDeleted commands will longer work in any workflows you previously created where they were used as a step.\n\n%1,Befehle löschen?\nGelöschte Befehle werden in bestehenden Arbeitsabläufen nicht mehr funktionieren.\n\n%1,"Удалить команду?\nУдаленные команды больше не будут работать в любых созданных наборах, где они использовались\n\n%1" 27 | cd_delete_confirm_title,Confirm Commands To Delete,Bestätigen Sie die zu löschenden Befehle.,Подтвердить удаление команд 28 | cd_delete_select,Select Commands To Delete,Wählen Sie die zu löschenden Menübefehle aus.,Выбрать команды меню для удаления 29 | cd_error_delete,Command couldn't be deleted.\n%1,Befehl konnte nicht gelöscht werden.\n%1,Команда не может быть удалена\n%1 30 | cd_error_executing,Error executing command:\n%1\n\n%2,Fehler beim Ausführen des Befehls:\n%1\n\n%2,Ошибка запуска команды:\n%1\n\n%2 31 | cd_exception,Command Exception,Befehls-Ausnahme, 32 | cd_helptip,Double-click a command to add it as a workflow step below.,"Doppelklicken Sie auf einen Befehl, um ihn unten als benutzerdefinierten Schritt hinzuzufügen.","Нажмите дважды на команду, чтобы добавить ее как шаг набора" 33 | cd_hide_confirm_title,Confirm Commands To Hide,Auszublendende Befehle bestätigen,Подтвердить скрытие команд 34 | cd_hide_select,Select Commands To Hide,Wählen Sie die auszublendenden Menübefehle aus.,Выбрать команды меню для скрытия 35 | cd_invalid,Invalid command type:\n%1,Ungültiger Befehlstyp:\n%1,Неправильный тип:\n%1 36 | cd_none_delete,There are no commands to delete.,Es gibt keine Befehle zum Löschen.,Нет команд для удаления 37 | cd_none_hide,There are no commands to hide.,Es gibt keine Befehle zum Ausblenden.,Нет команд для скрытия 38 | cd_none_reveal,There are no hidden commands to reveal.,Keine verborgenen Befehle vorhanden.,Нет скрытых команд 39 | cd_q_helptip,"Search for commands, actions, and loaded scripts.","Befehle, Aktionen und geladene Skripte suchen.","Поиск команд, операций и загруженных скриптов" 40 | cd_reveal_confirm,Unhide Commands?\n%1,Verborgene Befehle anzeigen?\n%1,Показать скрытые команды?\n%1 41 | cd_reveal_confirm_title,Confirm Commands To Unhide,Die ausgewählten Befehle anzeigen?,Подтвердить показ команд 42 | cd_reveal_menu_select,Select Hidden Menu Commands To Unhide,"Wählen Sie die ausgeblendeten Menübefehle aus, die angezeigt werden sollen.",Выберите скрытые команды для показа 43 | cd_revealed_total,Total hidden commands revealed:\n%1,"Anzahl der verborgenen Befehle, die wieder angezeigt werden:\n%1",Показано скрытых команд:\n%1 44 | cd_search_for,"Search for commands, actions, and loaded scripts.","Befehle, Aktionen und geladene Skripte suchen.","Поиск команд, операций и загруженных скриптов" 45 | close,Close,Schließen,Закрывать 46 | config,Configuration,, 47 | copyright,Copyright 2024 Josh Duncan,, 48 | cp_config,Palette Settings and Configuration,Paletteneinstellungen und -konfiguration,Настройка и конфигурация панели 49 | cp_q_helptip,"Search for commands, actions, and loaded scripts.","Befehle, Aktionen und geladene Skripte suchen.","Поиск команд, операций и загруженных скриптов" 50 | custom,"Custom",, 51 | custom_commands_all,All Custom Commands,, 52 | custom_commands_header,"Enter your custom commands below (one per line).\n\nCommands should be in the comma separated (CSV) format:\n'Command Name,Command Action,Command Type'\n\n* No extraneous spaces between commands.",, 53 | defaults,Defaults,, 54 | description,"Boost your Adobe Illustrator efficiency with quick access to most menu commands and tools, all of your actions, and any scripts right from your keyboard. And, with custom workflows, you can combine multiple commands, actions, and scripts to get things done in your own way. Replace repetitive tasks with workflows and boost your productivity.","Steigern Sie Ihre Effizienz in Adobe Illustrator mit schnellem Zugriff auf die meisten Menübefehle und Werkzeuge sowie alle Aktionen und Skripte, die direkt über die Tastatur ausgeführt werden können. Mit benutzerdefinierten Arbeitsabläufen können Sie mehrere Befehle, Aktionen und Skripte kombinieren. Erledigen Sie wiederkehrende Aufgaben mit Arbeitsabläufen und steigern Sie Ihre Produktivität.","Повысьте скорость работы в Adobe Illustrator благодаря быстрому доступу к большинству команд меню, инструментам, всем операциям и любым загруженным скриптам прямо с клавиатуры. А пользовательские наборы позволяют комбинировать несколько команд, операций и скриптов. Замените повторяющиеся задачи наборами команд и повысьте свою производительность." 55 | document,Document,, 56 | document_report,Active Document Report,Dokumentinformationen, 57 | document_report_warning,FILE NOT SAVED. Save and rerun report for updated information.,, 58 | color_space_title_case,Color Space,, 59 | dr_color_space,Color Space: ,Farbmodus: , 60 | dr_file_created,File Created: ,Datei erstellt am: , 61 | dr_file_found,File Found: ,Datei gefunden: , 62 | dr_filename,File: ,Datei: , 63 | dr_header,File Information\n-----\n,Datei-Information\n-----\n, 64 | dr_height,Height: ,Höhe: , 65 | dr_info_string,Ai Document Information,AI-Dokument-Information, 66 | dr_name,Name: ,, 67 | dr_path,Path: ,Pfad: , 68 | dr_width,Width: ,Breite: , 69 | file,File,, 70 | file_saved,File Saved:\n%1,Datei gespeichert:\n%1, 71 | fl_error_loading,Error loading file:\n%1,Fehler beim Laden der Datei:\n%1,Ошибка загрузки файла:\n%1 72 | fl_error_writing,Error writing file:\n%1,Fehler beim Schreiben der Datei:\n%1,Ошибка записи файла:\n%1 73 | folder,Folder,, 74 | fonts,Fonts,Schriften, 75 | github,Click here to learn more,Klicken Sie hier für weitere Informationen,"Нажмите, чтобы узнать больше" 76 | go_to_artboard,Go To Artboard,Gehen Sie zur Zeichenfläche,Перейти к монтажной области 77 | go_to_named_object,Go To Named Object,Gehen Sie zum benannten Objekt,Перейти к именованному объекту 78 | go_to_named_object_limit,Attention:\nThis document contains a lot of page items (%1). Please be patient while they load.,, 79 | go_to_named_object_no_objects,No named page items found.,Keine benannten Objekte vorhanden., 80 | go_to_open_document,Go To Open Document,Geöffnete Dokumente auswählen, 81 | history_cleared,History cleared!,Zuletzt verwendete Befehle wurden gelöscht!, 82 | layer_title_case,Layer,, 83 | layers,Layers,Ebenen, 84 | menu,Menu,, 85 | menu_commands,Menu Commands,, 86 | name_title_case,Name,, 87 | no_active_document,No active documents.,Keine geöffneten Dokumente vorhanden.., 88 | no_document_variables,No document variables.,Keine Variablen vorhanden., 89 | none,None,Ohne, 90 | open_recent_file,Open Recent File,Zuletzt benutzte Datei öffnen, 91 | picker,Picker,, 92 | placed_items,Placed Items,Platzierte Objecte, 93 | history_file_loading_error,Error Loading History\nA backup copy of your history has been created.,, 94 | path_title_case,Path,, 95 | picker_builder_header,Enter your custom commands below (one per line).,, 96 | picker_builder_multi_select,Multi-Select Enabled?,, 97 | picker_builder_name,Custom Picker Name,, 98 | picker_builder_title,Custom Picker Builder,, 99 | picker_builder_save_conflict_message,A custom picker with that name already exists.\nWould you like to overwrite the previous picker with the new one?\n%1,, 100 | picker_builder_save_conflict_title,Save Custom Picker Conflict,, 101 | picker_to_edit,Choose a custom picker to edit.,, 102 | pickers_all,All Pickers,, 103 | pref_file_loading_error,Error Loading Preferences\nA backup copy of your settings has been created.\n\n%1,Fehler beim Laden der Voreinstellungen\nEine Sicherungskopie Ihrer Einstellungen wurde erstellt.\n\n%1, 104 | pref_file_non_compatible,Incompatible Preferences\nYour preferences file isn't compatible with your current version of Ai Command Palette. Your preferences file will be updated.\n\nA backup copy of your settings has been created.,, 105 | pref_update_complete,Preferences Update Complete.,Aktualisierung der Voreinstellungen fertiggestellt,Preferences Update Complete 106 | recent_commands,Recent Commands,Zuletzt verwendete Befehle, 107 | ruler_units_title_case,Ruler Units,, 108 | save,Save,Speichern,Сохранять 109 | save_active_document_report,Save Active Document Report,, 110 | sc_already_loaded_title,Script Load Conflict,Skriptladekonflikt,Проблема загрузки скрипта 111 | sc_error_execution,Error executing script:\n%1\n\n%2,Fehler beim Ausführen des Skripts:\n%1\n\n%2,Ошибка запуска скрипта:\n%1\n\n%2 112 | sc_error_exists,Script no longer exists at original path. Try reloading.\n%1,Skript existiert nicht mehr am ursprünglichen Ort.\n%1,Скрипт не найден в указанной папке\n%1 113 | sc_error_loading,Error loading script:\n%1,Fehler beim Laden des Skripts:\n%1,Ошибка загрузки скрипта:\n%1 114 | sc_load_script,Load Script(s),Skripte laden, 115 | sc_none_selected,No script files selected.\nMust be JavaScript '.js' or '.jsx' files.,Keine Skriptdateien ausgewählt.\nEs müssen JavaScript-'.js'- oder '.jsx'-Dateien sein.,Не выбраны скрипты\nФайлы JavaScript имеют расширение '.js' или '.jsx' 116 | sc_total_loaded,Total scripts loaded:\n%1,Geladene Skripte insgesamt:\n%1,Загружено скриптов:\n%1 117 | script,Script,Skript,Скрипт 118 | Scripts,Scripts,Skripte laden, 119 | set_title_case,Set,, 120 | spot_colors,Spot Colors,Volltonfarben, 121 | startup_builder,Startup Screen Customizer,, 122 | startup_error_saving,Error saving startup commands.\nPrevious settings were reloaded.,, 123 | startup_helptip,Double-click a command to add it to your startup command list below.,, 124 | startup_steps,Startup Commands,, 125 | startup_steps_helptip,Startup commands will displayed in order from top to bottom.,, 126 | step_delete,Delete,Löschen,Удалить 127 | step_down,Move Down,Nach unten,Вниз 128 | step_edit,Edit,, 129 | step_up,Move Up,Nach oben,Наверх 130 | title,Ai Command Palette,Kurzbefehle, 131 | tl_all,Tools,Alle integrierten Werkzeuge,Стандартные инструменты 132 | tl_error_selecting,Error selecting tool:\n%1\n\n%2,Fehler beim Auswählen des Werkzeugs:\n%1\n\n%2,Ошибка выбора инструмента:\n%1\n\n%2 133 | tl_none_available,No tools are currently available.,Zurzeit sind keine Werkzeuge verfügbar.,Инструменты в данный момент недоступны 134 | tool,Tool,, 135 | type_title_case,Type,, 136 | user_prefs_inconsistency,User Preferences Inconsistency\nIt seems your preferences file may be from a different computer than this one.\n\n PLEASE NOTE: There is a small chance this could cause some features to break.,, 137 | version,Version %1,Ausführung %1,версия %1 138 | wf_already_exists,A workflow with that name already exists.\nWould you like to overwrite the previous workflow with the new one?,Ein Arbeitsablauf mit diesem Namen existiert bereits.\nSoll der bestehende Arbeitsablauf überschrieben werden?,Набор с таким именем уже существует\nХотите перезаписать предыдущий? 139 | wf_already_exists_title,Save Workflow Conflict,Arbeitsablauf-Konflikt speichern?,Проблема сохранения набора 140 | wf_builder,Workflow Builder,Arbeitsabläufe erstellen,Редактор наборов команд 141 | wf_choose,Choose A Workflow To Edit,Wählen Sie einen Arbeitsablauf zum Bearbeiten aus.,Выберите набор для редактирования 142 | wf_error_saving,Error saving workflow:\n%1,Fehler beim Speichern des Arbeitsablaufs:\n%1,Ошибка сохранения набора:\n%1 143 | wf_needs_attention,Workflow needs attention.\nThe following action steps from your workflow are not currently available.\n\n%1,Achtung!\nDie folgenden Aktionsschritte sind nicht mehr vorhanden\n\n%1,Набор требует внимания\nУказанные шаги в вашем наборе команд больше недоступны.\n\n%1 144 | wf_none_attention,There are no workflows that need attention.,"Es gibt keine Arbeitsabläufe, die beachtet werden müssen.",Нет наборов требующих внимания 145 | wf_none_edit,There are no workflows to edit.,Es gibt keine Arbeitsabläufe zum Bearbeiten.,Нет наборов для редактирования 146 | wf_not_saved,Workflow not saved.,Arbeitsablauf nicht gespeichert,Набор не сохранен 147 | wf_save,Save,, 148 | wf_save_as,Save Workflow As,Arbeitsablauf speichern als,Сохранить набор как 149 | wf_step_not_editable,Selected Step Not Editable,, 150 | wf_steps,Workflow Steps,Befehlskombinationen,Шаги набора 151 | wf_steps_helptip,Workflows will run in order from top to bottom.,Die Befehlskombinationen werden in der Reihenfolge von oben nach unten ausgeführt.,Набор выполняется сверху вниз 152 | workflow,Workflow,Arbeitsablauf,Наборы 153 | Workflows,Workflows,Arbeitsabläufe, -------------------------------------------------------------------------------- /data/tool_commands.csv: -------------------------------------------------------------------------------- 1 | value,ignore,type,docRequired,selRequired,minVersion,maxVersion,en,de,ru,notes 2 | Adobe Add Anchor Point Tool,FALSE,tool,TRUE,FALSE,24,,Add Anchor Point Tool,Ankerpunkt-hinzufügen-Werkzeug,Инструмент: Добавить опорную точку, 3 | Adobe Anchor Point Tool,FALSE,tool,TRUE,FALSE,24,,Anchor Point Tool,Ankerpunkt-Werkzeug,Инструмент: Опорная точка, 4 | Adobe Arc Tool,FALSE,tool,TRUE,FALSE,24,,Arc Tool,Bogen-Werkzeug,Инструмент: Дуга, 5 | Adobe Area Graph Tool,FALSE,tool,TRUE,FALSE,24,,Area Graph Tool,Flächendiagramm,Инструмент: Диаграмма с областями, 6 | Adobe Area Type Tool,FALSE,tool,TRUE,FALSE,24,,Area Type Tool,Flächentext-Werkzeug,Инструмент: Текст в области, 7 | Adobe Constraints Tool,FALSE,tool,TRUE,FALSE,29,,Objects on Path,,, 8 | Adobe Crop Tool,FALSE,tool,TRUE,FALSE,24,,Artboard Tool,Zeichenflächen-Werkzeug,Инструмент: Монтажная область, 9 | Adobe Bar Graph Tool,FALSE,tool,TRUE,FALSE,24,,Bar Graph Tool,Horizontales Balkendiagramm,Инструмент: Диаграмма горизонтальные полосы, 10 | Adobe Blend Tool,FALSE,tool,TRUE,FALSE,24,,Blend Tool,Angleichen-Werkzeug,Инструмент: Переход, 11 | Adobe Bloat Tool,FALSE,tool,TRUE,FALSE,24,,Bloat Tool,Aufblasen-Werkzeug,Инструмент: Раздувание, 12 | Adobe Blob Brush Tool,FALSE,tool,TRUE,FALSE,24,,Blob Brush Tool,Tropfenpinsel-Werkzeug,Инструмент: Кисть-клякса, 13 | Adobe Column Graph Tool,FALSE,tool,TRUE,FALSE,24,,Column Graph Tool,Vertikales Balkendiagramm,Инструмент: Диаграмма вертикальные полосы, 14 | Adobe Cyrstallize Tool,FALSE,tool,TRUE,FALSE,24,,Crystallize Tool,Kristallisieren-Werkzeug,Инструмент: Кристаллизация, 15 | Adobe Curvature Tool,FALSE,tool,TRUE,FALSE,24,,Curvature Tool,Kurvenzeichner,Инструмент: Кривизна, 16 | Adobe Delete Anchor Point Tool,FALSE,tool,TRUE,FALSE,24,,Delete Anchor Point Tool,Ankerpunkt-löschen-Werkzeug,Инструмент: Удалить опорную точку, 17 | Adobe Dimension Tool,FALSE,tool,TRUE,FALSE,28.1,,Dimension Tool,,, 18 | Adobe Direct Select Tool,FALSE,tool,TRUE,FALSE,24,,Direct Selection Tool,Direktauswahl-Werkzeug,Инструмент: Прямое выделение, 19 | Adobe Ellipse Shape Tool,FALSE,tool,TRUE,FALSE,24,,Ellipse Tool,Ellipse-Werkzeug,Инструмент: Эллипс, 20 | Adobe Eraser Tool,FALSE,tool,TRUE,FALSE,24,,Eraser Tool,Radiergummi-Werkzeug,Инструмент: Ластик, 21 | Adobe Eyedropper Tool,FALSE,tool,TRUE,FALSE,24,,Eyedropper Tool,Pipette-Werkzeug,Инструмент: Пипетка, 22 | Adobe Flare Tool,FALSE,tool,TRUE,FALSE,24,,Flare Tool,Blendenflecke-Werkzeug,Инструмент: Блик, 23 | Adobe Free Transform Tool,FALSE,tool,TRUE,FALSE,24,,Free Transform Tool,Frei-transformieren-Werkzeug,Инструмент: Свободное трансформирование, 24 | Adobe Gradient Vector Tool,FALSE,tool,TRUE,FALSE,24,,Gradient Tool,Verlauf-Werkzeug,Инструмент: Градиент, 25 | Adobe Direct Object Select Tool,FALSE,tool,TRUE,FALSE,24,,Group Selection Tool,Gruppenauswahl-Werkzeug,Инструмент: Групповое выделение, 26 | Adobe Scroll Tool,FALSE,tool,TRUE,FALSE,24,,Hand Tool,Hand-Werkzeug,Инструмент: Рука, 27 | Adobe Intertwine Zone Marker Tool,FALSE,tool,TRUE,FALSE,27,,Intertwine Tool,,, 28 | Adobe Corner Join Tool,FALSE,tool,TRUE,FALSE,24,,Join Tool,Zusammenfügen-Werkzeug,Инструмент: Соединение, 29 | Adobe Knife Tool,FALSE,tool,TRUE,FALSE,24,,Knife Tool,Messer-Werkzeug,Инструмент: Нож, 30 | Adobe Direct Lasso Tool,FALSE,tool,TRUE,FALSE,24,,Lasso Tool,Lasso-Werkzeug,Инструмент: Лассо, 31 | Adobe Line Graph Tool,FALSE,tool,TRUE,FALSE,24,,Line Graph Tool,Liniendiagramm,Инструмент: Линейная диаграмма, 32 | Adobe Line Tool,FALSE,tool,TRUE,FALSE,24,,Line Segment Tool,Liniensegment-Werkzeug,Инструмент: Отрезок линии, 33 | Adobe Planar Paintbucket Tool,FALSE,tool,TRUE,FALSE,24,,Live Paint Bucket Tool,Interaktiv-malen-Werkzeug,Инструмент: Быстрая заливка, 34 | Adobe Planar Face Select Tool,FALSE,tool,TRUE,FALSE,24,,Live Paint Selection Tool,Interaktiv-malen-Auswahlwerkzeug,Инструмент: Выделение быстрых заливок, 35 | Adobe Magic Wand Tool,FALSE,tool,TRUE,FALSE,24,,Magic Wand Tool,Zauberstab-Werkzeug,Инструмент: Волшебная палочка, 36 | Adobe Measure Tool,FALSE,tool,TRUE,FALSE,24,,Measure Tool,Mess-Werkzeug,Инструмент: Линейка, 37 | Adobe Mesh Editing Tool,FALSE,tool,TRUE,FALSE,24,,Mesh Tool,Gitter-Werkzeug,Инструмент: Сетка, 38 | Adobe Brush Tool,FALSE,tool,TRUE,FALSE,24,,Paintbrush Tool,Pinsel-Werkzeug,Инструмент: Кисть, 39 | Adobe Freehand Erase Tool,FALSE,tool,TRUE,FALSE,24,,Path Eraser Tool,Löschen-Werkzeug,Инструмент: Стирание контура, 40 | Adobe Pattern Tile Tool,FALSE,tool,TRUE,FALSE,24,,Pattern Tile Tool,Musterelement-Werkzeug,Инструмент: Элемент узора, 41 | Adobe Pen Tool,FALSE,tool,TRUE,FALSE,24,,Pen Tool,Zeichenstift-Werkzeug,Инструмент: Перо, 42 | Adobe Freehand Tool,FALSE,tool,TRUE,FALSE,24,,Pencil Tool,Buntstift-Werkzeug,Инструмент: Карандаш, 43 | Perspective Grid Tool,FALSE,tool,TRUE,FALSE,24,,Perspective Grid Tool,Perspektivenraster-Werkzeug,Инструмент: Сетка перспективы, 44 | Perspective Selection Tool,FALSE,tool,TRUE,FALSE,24,,Perspective Selection Tool,Perspektivenauswahl-Werkzeug,Инструмент: Выбор перспективы, 45 | Adobe Pie Graph Tool,FALSE,tool,TRUE,FALSE,24,,Pie Graph Tool,Kreisdiagramm-Werkzeug,Инструмент: Круговая диаграмма, 46 | Adobe Place Gun Tool,TRUE,tool,TRUE,FALSE,,,Place Tool,,, 47 | Adobe Polar Grid Tool,FALSE,tool,TRUE,FALSE,24,,Polar Grid Tool,Radiales-Raster-Werkzeug,Инструмент: Полярная сетка, 48 | Adobe Shape Construction Regular Polygon Tool,FALSE,tool,TRUE,FALSE,24,,Polygon Tool,Polygon-Werkzeug,Инструмент: Многоугольник, 49 | Adobe Page Tool,FALSE,tool,TRUE,FALSE,24,,Print Tiling Tool,Druckaufteilungs-Werkzeug,Инструмент: Разбиение для печати, 50 | Adobe Pucker Tool,FALSE,tool,TRUE,FALSE,24,,Pucker Tool,Zusammenziehen-Werkzeug,Инструмент: Втягивание, 51 | Adobe Puppet Warp Tool,FALSE,tool,TRUE,FALSE,24,,Puppet Warp Tool,Formgitter-Werkzeug,Инструмент: Марионеточная деформация, 52 | Adobe Radar Graph Tool,FALSE,tool,TRUE,FALSE,24,,Radar Graph Tool,Netzdiagramm,Инструмент: Диаграмма радар, 53 | Adobe Rectangle Shape Tool,FALSE,tool,TRUE,FALSE,24,,Rectangle Tool,Rechteck-Werkzeug,Инструмент: Прямоугольник, 54 | Adobe Rectangular Grid Tool,FALSE,tool,TRUE,FALSE,24,,Rectangular Grid Tool,Rechteckiges-Raster-Werkzeug,Инструмент: Прямоугольная сетка, 55 | Adobe Reflect Tool,FALSE,tool,TRUE,FALSE,24,,Reflect Tool,Spiegeln-Werkzeug,Инструмент: Зеркальное отражение, 56 | Adobe Reshape Tool,FALSE,tool,TRUE,FALSE,24,,Reshape Tool,Form-ändern-Werkzeug,Инструмент: Перерисовка, 57 | Adobe Rotate Tool,FALSE,tool,TRUE,FALSE,24,,Rotate Tool,Drehen-Werkzeug,Инструмент: Поворот, 58 | Adobe Rotate Canvas Tool,FALSE,tool,TRUE,FALSE,24,,Rotate View Tool,Ansichtdrehung-Werkzeug,Инструмент: Поворот вида, 59 | Adobe Rounded Rectangle Tool,FALSE,tool,TRUE,FALSE,24,,Rounded Rectangle Tool,Abgerundetes-Rechteck-Werkzeug,Инструмент: Прямоугольник со скругленными углами, 60 | Adobe Scale Tool,FALSE,tool,TRUE,FALSE,24,,Scale Tool,Skalieren-Werkzeug,Инструмент: Масштаб, 61 | Adobe Scallop Tool,FALSE,tool,TRUE,FALSE,24,,Scallop Tool,Ausbuchten-Werkzeug,Инструмент: Зубцы, 62 | Adobe Scatter Graph Tool,FALSE,tool,TRUE,FALSE,24,,Scatter Graph Tool,Streudiagramm,Инструмент: Точечная диаграмма, 63 | Adobe Scissors Tool,FALSE,tool,TRUE,FALSE,24,,Scissors Tool,Schere-Werkzeug,Инструмент: Ножницы, 64 | Adobe Select Tool,FALSE,tool,TRUE,FALSE,24,,Selection Tool,Auswahl-Werkzeug,Инструмент: Выделение, 65 | Adobe Shape Builder Tool,FALSE,tool,TRUE,FALSE,24,,Shape Builder Tool,Formerstellungs-Werkzeug,Инструмент: Создание фигур, 66 | Adobe Shaper Tool,FALSE,tool,TRUE,FALSE,24,,Shaper Tool,Shaper-Werkzeug,Инструмент: Произвольная кривая, 67 | Adobe Shear Tool,FALSE,tool,TRUE,FALSE,24,,Shear Tool,Verbiegen-Werkzeug,Инструмент: Наклон, 68 | Adobe Slice Tool,FALSE,tool,TRUE,FALSE,24,,Slice Tool,Slice-Werkzeug,Инструмент: Фрагменты, 69 | Adobe Slice Select Tool,FALSE,tool,TRUE,FALSE,24,,Slice Selection Tool,Slice-Auswahl-Werkzeug,Инструмент: Выделение фрагмента, 70 | Adobe Freehand Smooth Tool,FALSE,tool,TRUE,FALSE,24,,Smooth Tool,Glätten-Werkzeug,Инструмент: Сглаживание, 71 | Adobe Shape Construction Spiral Tool,FALSE,tool,TRUE,FALSE,24,,Spiral Tool,Spirale-Werkzeug,Инструмент: Спираль, 72 | Adobe Stacked Bar Graph Tool,FALSE,tool,TRUE,FALSE,24,,Stacked Bar Graph Tool,Gestapeltes horizontales Balkendiagramm,Инструмент: Диаграмма горизонтальный стек, 73 | Adobe Stacked Column Graph Tool,FALSE,tool,TRUE,FALSE,24,,Stacked Column Graph Tool,Gestapeltes vertikales Balkendiagramm,Инструмент: Диаграмма вертикальный стек, 74 | Adobe Shape Construction Star Tool,FALSE,tool,TRUE,FALSE,24,,Star Tool,Stern-Werkzeug,Инструмент: Звезда, 75 | Adobe Stencil Tool,TRUE,tool,TRUE,FALSE,,,Stencil Tool,,, 76 | Adobe Symbol Screener Tool,FALSE,tool,TRUE,FALSE,24,,Symbol Screener Tool,Symbol-transparent-gestalten-Werkzeug,Инструмент: Прозрачность символов, 77 | Adobe Symbol Scruncher Tool,FALSE,tool,TRUE,FALSE,24,,Symbol Scruncher Tool,Symbol-stauchen-Werkzeug,Инструмент: Уплотнение символов, 78 | Adobe Symbol Shifter Tool,FALSE,tool,TRUE,FALSE,24,,Symbol Shifter Tool,Symbol-verschieben-Werkzeug,Инструмент: Смещение символов, 79 | Adobe Symbol Sizer Tool,FALSE,tool,TRUE,FALSE,24,,Symbol Sizer Tool,Symbol-skalieren-Werkzeug,Инструмент: Размер символов, 80 | Adobe Symbol Spinner Tool,FALSE,tool,TRUE,FALSE,24,,Symbol Spinner Tool,Symbol-drehen-Werkzeug,Инструмент: Вращение символов, 81 | Adobe Symbol Sprayer Tool,FALSE,tool,TRUE,FALSE,24,,Symbol Sprayer Tool,Symbol-aufsprühen-Werkzeug,Инструмент: Распыление символов, 82 | Adobe Symbol Stainer Tool,FALSE,tool,TRUE,FALSE,24,,Symbol Stainer Tool,Symbol-färben-Werkzeug,Инструмент: Обесцвечивание символов, 83 | Adobe Symbol Styler Tool,FALSE,tool,TRUE,FALSE,24,,Symbol Styler Tool,Symbol-gestalten-Werkzeug,Инструмент: Стили символов, 84 | Adobe Touch Type Tool,FALSE,tool,TRUE,FALSE,24,,Touch Type Tool,Touch-Type-Textwerkzeug,Инструмент: Изменение текста, 85 | Adobe New Twirl Tool,FALSE,tool,TRUE,FALSE,24,,Twirl Tool,Strudel-Werkzeug,Инструмент: Воронка, 86 | Adobe Type Tool,FALSE,tool,TRUE,FALSE,24,,Type Tool,Text-Werkzeug,Инструмент: Текст, 87 | Adobe Path Type Tool,FALSE,tool,TRUE,FALSE,24,,Type on a Path Tool,Pfadtext-Werkzeug,Инструмент: Текст по контуру, 88 | Adobe Vertical Area Type Tool,FALSE,tool,TRUE,FALSE,24,,Vertical Area Type Tool,Vertikaler-Flächentext-Werkzeug,Инструмент: Вертикальный текст в области, 89 | Adobe Vertical Type Tool,FALSE,tool,TRUE,FALSE,24,,Vertical Type Tool,Vertikaler-Text-Werkzeug,Инструмент: Вертикальный текст, 90 | Adobe Vertical Path Type Tool,FALSE,tool,TRUE,FALSE,24,,Vertical Type on a Path Tool,Vertikaler-Pfadtext-Werkzeug,Инструмент: Вертикальный текст по контуру, 91 | Adobe Warp Tool,FALSE,tool,TRUE,FALSE,24,,Warp Tool,Verkrümmen-Werkzeug,Инструмент: Деформация, 92 | Adobe Width Tool,FALSE,tool,TRUE,FALSE,24,,Width Tool,Breiten-Werkzeug,Инструмент: Ширина, 93 | Adobe Wrinkle Tool,FALSE,tool,TRUE,FALSE,24,,Wrinkle Tool,Zerknittern-Werkzeug,Инструмент: Морщины, 94 | Adobe Zoom Tool,FALSE,tool,TRUE,FALSE,24,,Zoom Tool,Zoom-Werkzeug,Инструмент: Масштаб, -------------------------------------------------------------------------------- /images/actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshbduncan/AiCommandPalette/713a8f9423ece5ec1a9855d351825ce0ab8a1ee5/images/actions.png -------------------------------------------------------------------------------- /images/ai-command-palette-youtube-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshbduncan/AiCommandPalette/713a8f9423ece5ec1a9855d351825ce0ab8a1ee5/images/ai-command-palette-youtube-1.png -------------------------------------------------------------------------------- /images/bookmarks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshbduncan/AiCommandPalette/713a8f9423ece5ec1a9855d351825ce0ab8a1ee5/images/bookmarks.png -------------------------------------------------------------------------------- /images/custom-picker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshbduncan/AiCommandPalette/713a8f9423ece5ec1a9855d351825ce0ab8a1ee5/images/custom-picker.png -------------------------------------------------------------------------------- /images/document-report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshbduncan/AiCommandPalette/713a8f9423ece5ec1a9855d351825ce0ab8a1ee5/images/document-report.png -------------------------------------------------------------------------------- /images/go-to-functionality.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshbduncan/AiCommandPalette/713a8f9423ece5ec1a9855d351825ce0ab8a1ee5/images/go-to-functionality.png -------------------------------------------------------------------------------- /images/localization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshbduncan/AiCommandPalette/713a8f9423ece5ec1a9855d351825ce0ab8a1ee5/images/localization.png -------------------------------------------------------------------------------- /images/menu-commands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshbduncan/AiCommandPalette/713a8f9423ece5ec1a9855d351825ce0ab8a1ee5/images/menu-commands.png -------------------------------------------------------------------------------- /images/palette-operation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshbduncan/AiCommandPalette/713a8f9423ece5ec1a9855d351825ce0ab8a1ee5/images/palette-operation.gif -------------------------------------------------------------------------------- /images/palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshbduncan/AiCommandPalette/713a8f9423ece5ec1a9855d351825ce0ab8a1ee5/images/palette.png -------------------------------------------------------------------------------- /images/scripts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshbduncan/AiCommandPalette/713a8f9423ece5ec1a9855d351825ce0ab8a1ee5/images/scripts.png -------------------------------------------------------------------------------- /images/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshbduncan/AiCommandPalette/713a8f9423ece5ec1a9855d351825ce0ab8a1ee5/images/settings.png -------------------------------------------------------------------------------- /images/tool-commands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshbduncan/AiCommandPalette/713a8f9423ece5ec1a9855d351825ce0ab8a1ee5/images/tool-commands.png -------------------------------------------------------------------------------- /images/workflow-builder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshbduncan/AiCommandPalette/713a8f9423ece5ec1a9855d351825ce0ab8a1ee5/images/workflow-builder.png -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "ignoreDeprecations": "5.0", 4 | "checkJs": false, 5 | "module": "none", 6 | "noLib": true, 7 | "target": "es3", 8 | "types": ["./types/"], 9 | }, 10 | "include": ["src/**/*"] 11 | } 12 | -------------------------------------------------------------------------------- /localization.md: -------------------------------------------------------------------------------- 1 | # Ai Command Palette - Localization 2 | 3 | This all came about after a suggestion from [Kurt Gold](https://community.adobe.com/t5/illustrator-discussions/command-palette-by-josh-duncan-localised-for-german-illustrator-versions/td-p/13107176) in the Adobe Support [Scripting Forum](https://community.adobe.com/t5/illustrator/ct-p/ct-illustrator?page=1&sort=latest_replies&filter=all&lang=all&tabid=discussions&topics=label-scripting). 4 | 5 | ## Localization Spreadsheet 6 | 7 | There are 650+ strings that need to be translated so to make things easier, Kurt and myself initially used to keep track of everything but now the files are version controlled in this [repository](/data/). 8 | 9 | ## How It Works 10 | 11 | All of the localization is done at runtime via the ExtendScript `localize()` function (learn more below). 12 | 13 | [Localizing ExtendScript strings — JavaScript Tools Guide CC 0.0.1 documentation](https://extendscript.docsforadobe.dev/extendscript-tools-features/localizing-extendscript-strings.html) 14 | 15 | [Localization in ScriptUI objects — JavaScript Tools Guide CC 0.0.1 documentation](https://extendscript.docsforadobe.dev/user-interface-tools/localization-in-scriptui-objects.html) -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | exclude = [ 2 | ".bzr", 3 | ".direnv", 4 | ".eggs", 5 | ".git", 6 | ".git-rewrite", 7 | ".hg", 8 | ".ipynb_checkpoints", 9 | ".mypy_cache", 10 | ".nox", 11 | ".pants.d", 12 | ".pyenv", 13 | ".pytest_cache", 14 | ".pytype", 15 | ".ruff_cache", 16 | ".svn", 17 | ".tox", 18 | ".venv", 19 | ".vscode", 20 | "__pypackages__", 21 | "_build", 22 | "buck-out", 23 | "build", 24 | "dist", 25 | "node_modules", 26 | "site-packages", 27 | "venv", 28 | ] 29 | 30 | # Same as Black. 31 | line-length = 88 32 | indent-width = 4 33 | 34 | # Assume Python 3.10 35 | target-version = "py310" 36 | 37 | [lint] 38 | select = [ 39 | # pycodestyle 40 | "E", 41 | # Pyflakes 42 | "F", 43 | # pyupgrade 44 | "UP", 45 | # flake8-bugbear 46 | "B", 47 | # flake8-simplify 48 | "SIM", 49 | # isort 50 | "I", 51 | ] 52 | 53 | extend-select = ["C4", "I", "SIM", "TCH"] 54 | 55 | # Allow fix for all enabled rules (when `--fix`) is provided. 56 | fixable = ["ALL"] 57 | # fixable = ["B", "I"] 58 | unfixable = [] 59 | 60 | [format] 61 | # Like Black, use double quotes for strings. 62 | quote-style = "double" 63 | 64 | # Like Black, indent with spaces, rather than tabs. 65 | indent-style = "space" 66 | 67 | # Like Black, respect magic trailing commas. 68 | skip-magic-trailing-comma = false 69 | 70 | # Like Black, automatically detect the appropriate line ending. 71 | line-ending = "auto" 72 | 73 | [lint.per-file-ignores] 74 | # Ignore `E402` (import violations) in all `__init__.py`. 75 | "__init__.py" = ["E402"] 76 | # Ignore `TCH004` in `bitmap.py` (https://github.com/charliermarsh/ruff/issues/2852). 77 | "src/word_search_generator/mask/bitmap.py" = ["TCH004"] 78 | -------------------------------------------------------------------------------- /src/include/Logger.jsxinc: -------------------------------------------------------------------------------- 1 | /** 2 | * Determine the base calling script from the current stack. 3 | * @returns {String} Initial script name. 4 | */ 5 | function resolveBaseScriptFromStack() { 6 | var stack = $.stack.split("\n"); 7 | var foo, bar; 8 | for (var i = 0; i < stack.length; i++) { 9 | foo = stack[i]; 10 | if (foo[0] == "[" && foo[foo.length - 1] == "]") { 11 | bar = foo.slice(1, foo.length - 1); 12 | if (isNaN(bar)) { 13 | break; 14 | } 15 | } 16 | } 17 | return bar; 18 | } 19 | 20 | /** 21 | * Module for easy file logging from within Adobe ExtendScript. 22 | * @param {String} fp File path for the log file. Defaults to `Folder.userData/{base_script_file_name}.log`. 23 | * @param {String} mode Optional log file write mode. Write `w` mode or append `a` mode. If write mode 'w', the log file will be overwritten on each script run. Defaults to `w`. 24 | * @param {Number} sizeLimit Log file size limit (in bytes) for rotation. Defaults to 5,000,000. 25 | * @param {Boolean} console Forward calls to `Logger.log()` to the JavaScript Console via `$.writeln()`. Defaults to `false`. 26 | */ 27 | function Logger(fp, mode, sizeLimit, console) { 28 | if (typeof fp == "undefined") 29 | fp = Folder.userData + "/" + resolveBaseScriptFromStack() + ".log"; 30 | 31 | this.mode = typeof mode !== "undefined" ? mode.toLowerCase() : "w"; 32 | this.console = typeof console !== "undefined" ? console : false; 33 | this.file = new File(fp); 34 | this.badPath = false; 35 | 36 | // rotate log if too big 37 | sizeLimit = typeof sizeLimit !== "undefined" ? Number(sizeLimit) : 5000000; 38 | if (this.file.length > sizeLimit) { 39 | var ts = Date.now(); 40 | var rotatedFile = new File(this.file + ts + ".bak"); 41 | this.file.copy(rotatedFile); 42 | this.file.remove(); 43 | alert(this.file); 44 | } 45 | } 46 | 47 | Logger.prototype = { 48 | /** 49 | * Backup the log file. 50 | * @returns {FileObject} Backup file object. 51 | */ 52 | backup: function () { 53 | var backupFile = new File(this.file + ".bak"); 54 | this.file.copy(backupFile); 55 | return backupFile; 56 | }, 57 | /** 58 | * Write data to the log file. 59 | * @param {String} text One or more strings to write, which are concatenated to form a single string. 60 | * @returns {Boolean} Returns true if log file is successfully written, false if unsuccessful. 61 | */ 62 | log: function (text) { 63 | // no need to keep alerting when the log path is bad 64 | if (this.badPath) return false; 65 | 66 | var f = this.file; 67 | var m = this.mode; 68 | var ts = new Date().toLocaleString(); 69 | 70 | // ensure parent folder exists 71 | if (!f.parent.exists) { 72 | if (!f.parent.parent.exists) { 73 | alert("Bad log file path!\n'" + this.file + "'"); 74 | this.badPath = true; 75 | return false; 76 | } 77 | f.parent.create(); 78 | } 79 | 80 | // grab all arguments 81 | var args = ["[" + ts + "]"]; 82 | for (var i = 0; i < arguments.length; ++i) args.push(arguments[i]); 83 | 84 | // write the data 85 | try { 86 | f.encoding = "UTF-8"; 87 | f.open(m); 88 | f.writeln(args.join(" ")); 89 | } catch (e) { 90 | $.writeln("Error writing file:\n" + f); 91 | return false; 92 | } finally { 93 | f.close(); 94 | } 95 | 96 | // write `text` to the console if requested 97 | if (this.console) $.writeln(args.slice(1, args.length).join(" ")); 98 | 99 | return true; 100 | }, 101 | /** 102 | * Open the log file. 103 | */ 104 | open: function () { 105 | this.file.execute(); 106 | }, 107 | /** 108 | * Reveal the log file in the platform-specific file browser. 109 | */ 110 | reveal: function () { 111 | this.file.parent.execute(); 112 | }, 113 | }; 114 | -------------------------------------------------------------------------------- /src/include/commands/commands.jsxinc: -------------------------------------------------------------------------------- 1 | //@include "processing.jsxinc" 2 | //@include "internal.jsxinc" 3 | //@include "workflows.jsxinc" 4 | -------------------------------------------------------------------------------- /src/include/commands/processing.jsxinc: -------------------------------------------------------------------------------- 1 | // COMMAND EXECUTION 2 | 3 | /** 4 | * Process command actions. 5 | * @param {String} id Command id to process. 6 | */ 7 | function processCommand(id) { 8 | var command = commandsData[id]; 9 | logger.log("processing command:", localize(command.name)); 10 | if (command.type == "workflow") { 11 | // check to make sure all workflow commands are valid 12 | badActions = checkWorkflowActions(command.actions); 13 | if (badActions.length > 0) { 14 | alert(localize(strings.wf_needs_attention, badActions.join("\n"))); 15 | buildWorkflow(id, badActions); 16 | userPrefs.save(); 17 | return; 18 | } 19 | // run each action in the workflow 20 | for (var i = 0; i < command.actions.length; i++) processCommand(command.actions[i]); 21 | } else { 22 | executeAction(command); 23 | } 24 | } 25 | 26 | /** 27 | * Execute command action. 28 | * @param {Object} command Command to execute. 29 | */ 30 | function executeAction(command) { 31 | // check command to see if an active document is required 32 | if (command.docRequired && app.documents.length < 1) 33 | if ( 34 | !confirm( 35 | localize(strings.cd_active_document_required, command.action), 36 | "noAsDflt", 37 | localize(strings.cd_exception) 38 | ) 39 | ) 40 | return; 41 | 42 | // check command to see if an active selection is required 43 | if (command.selRequired && app.activeDocument.selection.length < 1) 44 | if ( 45 | !confirm( 46 | localize(strings.cd_active_selection_required, command.action), 47 | "noAsDflt", 48 | localize(strings.cd_exception) 49 | ) 50 | ) 51 | return; 52 | 53 | // execute action based on the command type 54 | var func; 55 | var alertString = strings.cd_error_executing; 56 | switch (command.type.toLowerCase()) { 57 | case "config": 58 | case "builtin": 59 | func = internalAction; 60 | break; 61 | case "custom": 62 | func = command.actionType == "menu" ? menuAction : toolAction; 63 | break; 64 | case "menu": 65 | func = menuAction; 66 | break; 67 | case "tool": 68 | func = toolAction; 69 | alertString = strings.tl_error_selecting; 70 | break; 71 | case "action": 72 | func = actionAction; 73 | alertString = strings.ac_error_execution; 74 | break; 75 | case "bookmark": 76 | case "file": 77 | case "folder": 78 | func = bookmarkAction; 79 | break; 80 | case "picker": 81 | func = runCustomPicker; 82 | break; 83 | case "script": 84 | func = scriptAction; 85 | alertString = strings.sc_error_execution; 86 | break; 87 | default: 88 | alert(localize(strings.cd_invalid, command.type)); 89 | } 90 | 91 | try { 92 | func(command); 93 | } catch (e) { 94 | alert(localize(alertString, localize(command.name), e)); 95 | } 96 | } 97 | 98 | function menuAction(command) { 99 | app.executeMenuCommand(command.action); 100 | } 101 | 102 | function toolAction(command) { 103 | app.selectTool(command.action); 104 | } 105 | 106 | function actionAction(command) { 107 | app.doScript(command.name, command.set); 108 | } 109 | 110 | function bookmarkAction(command) { 111 | f = command.type == "file" ? new File(command.path) : new Folder(command.path); 112 | if (!f.exists) { 113 | alert(localize(strings.bm_error_exists, command.path)); 114 | return; 115 | } 116 | if (command.type == "file") { 117 | app.open(f); 118 | } else { 119 | f.execute(); 120 | } 121 | } 122 | 123 | function runCustomPicker(picker) { 124 | // create custom adhoc commands from provided picker options 125 | var commands = []; 126 | var id, command; 127 | for (var i = 0; i < picker.commands.length; i++) { 128 | id = "picker_option_" + i.toString(); 129 | command = { 130 | id: id, 131 | action: "picker_option", 132 | type: "Option", 133 | docRequired: false, 134 | selRequired: false, 135 | name: picker.commands[i], 136 | hidden: false, 137 | }; 138 | commandsData[id] = command; 139 | commands.push(id); 140 | } 141 | 142 | // present the custom picker 143 | var result = commandPalette( 144 | (commands = commands), 145 | (title = picker.name), 146 | (columns = paletteSettings.columnSets.default), 147 | (multiselect = picker.multiselect) 148 | ); 149 | if (!result) { 150 | // set to null so any previous values are not incorrectly read 151 | $.setenv("aic_picker_last", null); 152 | return false; 153 | } 154 | 155 | // grab the correct name data from the selected commands 156 | var args = []; 157 | if (!picker.multiselect) { 158 | args.push(commandsData[result].name); 159 | } else { 160 | for (var i = 0; i < result.length; i++) { 161 | args.push(commandsData[result[i]].name); 162 | } 163 | } 164 | 165 | // encode the array data into an environment variable for later use 166 | $.setenv("aic_picker_last", args.toSource()); 167 | } 168 | 169 | function scriptAction(command) { 170 | f = new File(command.path); 171 | if (!f.exists) { 172 | alert(localize(strings.sc_error_exists, command.path)); 173 | } else { 174 | $.evalFile(f); 175 | } 176 | } 177 | 178 | /** 179 | * Execute script actions. 180 | * @param {Object} command Command to execute. 181 | */ 182 | function internalAction(command) { 183 | var write = true; 184 | switch (command.action) { 185 | // config commands 186 | case "about": 187 | write = false; 188 | about(); 189 | break; 190 | case "clearHistory": 191 | clearHistory(); 192 | break; 193 | case "customizeStartup": 194 | customizeStartup(); 195 | break; 196 | case "deleteCommand": 197 | deleteCommand(); 198 | break; 199 | case "enableFuzzyMatching": 200 | case "disableFuzzyMatching": 201 | toggleFuzzyMatching(); 202 | break; 203 | case "enableDebugLogging": 204 | case "disableDebugLogging": 205 | toggleDebugLogging(); 206 | break; 207 | case "hideCommand": 208 | hideCommand(); 209 | break; 210 | case "unhideCommand": 211 | unhideCommand(); 212 | break; 213 | case "revealPrefFile": 214 | write = false; 215 | revealPrefFile(); 216 | break; 217 | case "builtinCommands": 218 | write = false; 219 | builtinCommands(); 220 | break; 221 | case "settings": 222 | write = false; 223 | settings(); 224 | break; 225 | 226 | // builtin commands 227 | case "addCustomCommands": 228 | addCustomCommands(); 229 | break; 230 | case "allActions": 231 | write = false; 232 | showAllActions(); 233 | break; 234 | case "allBookmarks": 235 | write = false; 236 | showAllBookmarks(); 237 | break; 238 | case "allCustomCommands": 239 | write = false; 240 | showAllCustomCommands(); 241 | break; 242 | case "allMenus": 243 | write = false; 244 | showAllMenus(); 245 | break; 246 | case "allPickers": 247 | write = false; 248 | showAllPickers(); 249 | break; 250 | case "allScripts": 251 | write = false; 252 | showAllScripts(); 253 | break; 254 | case "allTools": 255 | write = false; 256 | showAllTools(); 257 | break; 258 | case "allWorkflows": 259 | write = false; 260 | showAllWorkflows(); 261 | break; 262 | case "buildWorkflow": 263 | buildWorkflow(); 264 | break; 265 | case "editWorkflow": 266 | editWorkflow(); 267 | break; 268 | case "buildPicker": 269 | buildPicker(); 270 | break; 271 | case "editPicker": 272 | editPicker(); 273 | break; 274 | case "documentReport": 275 | write = false; 276 | documentReport(); 277 | break; 278 | case "exportVariables": 279 | write = false; 280 | exportVariables(); 281 | break; 282 | case "goToArtboard": 283 | write = false; 284 | goToArtboard(); 285 | break; 286 | case "goToDocument": 287 | write = false; 288 | goToOpenDocument(); 289 | break; 290 | case "goToNamedObject": 291 | write = false; 292 | goToNamedObject(); 293 | break; 294 | case "imageCapture": 295 | write = false; 296 | imageCapture(); 297 | break; 298 | case "loadFileBookmark": 299 | loadFileBookmark(); 300 | break; 301 | case "loadFolderBookmark": 302 | loadFolderBookmark(); 303 | break; 304 | case "loadScript": 305 | loadScripts(); 306 | break; 307 | case "recentCommands": 308 | write = false; 309 | recentUserCommands(); 310 | break; 311 | case "recentFiles": 312 | write = false; 313 | recentFiles(); 314 | break; 315 | case "redrawWindows": 316 | write = false; 317 | redrawWindows(); 318 | break; 319 | case "revealActiveDocument": 320 | write = false; 321 | revealActiveDocument(); 322 | break; 323 | default: 324 | alert(localize(strings.cd_invalid, action)); 325 | } 326 | if (!write) return; 327 | userPrefs.save(); 328 | } 329 | -------------------------------------------------------------------------------- /src/include/commands/workflows.jsxinc: -------------------------------------------------------------------------------- 1 | /** 2 | * Check is any workflow actions are currently non-active (non deleted, and Ai version compatible). 3 | * @param {Array} actions Workflow action steps to check. 4 | * @returns {Array} Non-active workflow action. 5 | */ 6 | function checkWorkflowActions(actions) { 7 | var badActions = []; 8 | for (var i = 0; i < actions.length; i++) { 9 | command = actions[i]; 10 | if (!commandsData.hasOwnProperty(actions[i]) || !commandVersionCheck(actions[i])) 11 | badActions.push(actions[i]); 12 | } 13 | return badActions; 14 | } 15 | -------------------------------------------------------------------------------- /src/include/config.jsxinc: -------------------------------------------------------------------------------- 1 | // CONFIGURATION 2 | 3 | // DEVELOPMENT SETTINGS 4 | 5 | // localization testing 6 | // $.locale = false; 7 | // $.locale = "de"; 8 | // $.locale = "ru"; 9 | 10 | // ENVIRONMENT VARIABLES 11 | 12 | var aiVersion = parseFloat(app.version); 13 | var locale = $.locale; 14 | var currentLocale = locale.split("_")[0]; 15 | var os = $.os; 16 | var sysOS = /mac/i.test(os) ? "mac" : "win"; 17 | var windowsFlickerFix = sysOS === "win" && aiVersion < 26.4 ? true : false; 18 | var settingsRequiredUpdateVersion = "0.10.0"; 19 | 20 | // DEVELOPMENT SETTINGS 21 | var devMode = $.getenv("USER") === "jbd" ? true : false; 22 | var debugLogging = $.getenv("AICP_DEBIG_LOGGING") === "true" ? true : false; 23 | var logFilePath = Folder.desktop + "/AiCommandPalette.log"; 24 | var logger; 25 | 26 | if (devMode || debugLogging) { 27 | var logFilePath = Folder.desktop + "/AiCommandPalette.log"; 28 | logger = new Logger(logFilePath, "a", undefined, true); 29 | devMode && logger.log("**DEV MODE ENABLED**"); 30 | } else { 31 | logger = {}; 32 | logger.log = function (text) { 33 | $.writeln(text); 34 | }; 35 | } 36 | 37 | logger.log("**SCRIPT LAUNCH**", _title, "v" + _version, $.fileName); 38 | 39 | // DIALOG SETTINGS 40 | 41 | var paletteSettings = {}; 42 | paletteSettings.paletteWidth = 600; 43 | // was informed windows and mac have different listbox row hights so this makes sure exactly 9 rows show 44 | paletteSettings.paletteHeight = sysOS === "win" ? 211 : 201; 45 | paletteSettings.bounds = [ 46 | 0, 47 | 0, 48 | paletteSettings.paletteWidth, 49 | paletteSettings.paletteHeight, 50 | ]; 51 | 52 | // COMMAND PALETTE COLUMN SETS 53 | 54 | paletteSettings.columnSets = {}; 55 | 56 | paletteSettings.columnSets.default = {}; 57 | paletteSettings.columnSets.default[localize(strings.name_title_case)] = { 58 | width: 450, 59 | key: "name", 60 | }; 61 | paletteSettings.columnSets.default[localize(strings.type_title_case)] = { 62 | width: 100, 63 | key: "type", 64 | }; 65 | 66 | paletteSettings.columnSets.customCommand = {}; 67 | paletteSettings.columnSets.customCommand[localize(strings.name_title_case)] = { 68 | width: 450, 69 | key: "name", 70 | }; 71 | paletteSettings.columnSets.customCommand[localize(strings.type_title_case)] = { 72 | width: 100, 73 | key: "actionType", 74 | }; 75 | 76 | var visibleListItems = 9; 77 | var mostRecentCommandsCount = 25; 78 | 79 | // MISCELLANEOUS SETTINGS 80 | 81 | var namedObjectLimit = 2000; 82 | var regexEllipsis = /\.\.\.$/; 83 | var regexCarrot = /\s>\s/g; 84 | 85 | // DEVELOPMENT HELPERS 86 | 87 | var devInfo = {}; 88 | devInfo.folder = function () { 89 | return userPrefsFolder; 90 | }; 91 | devInfo.prefsFile = function () { 92 | var folder = this.folder(); 93 | var file = setupFileObject(folder, "prefs.json"); 94 | return file; 95 | }; 96 | devInfo.commandsFile = function () { 97 | var folder = this.folder(); 98 | var file = setupFileObject(folder, "commands.json"); 99 | return file; 100 | }; 101 | devInfo.save = function () { 102 | writeJSONData(prefs, this.prefsFile()); 103 | writeJSONData(commandsData, this.commandsFile()); 104 | }; 105 | devInfo.log = function (data, fileName) { 106 | fileName = typeof fileName !== "undefined" ? fileName : "log_" + Date.now() + ".txt"; 107 | var folder = this.folder(); 108 | var file = setupFileObject(folder, fileName); 109 | writeData(data, file.fsName); 110 | }; 111 | 112 | /** 113 | * Show an alert with object data. 114 | * @param obj Command to show data about. 115 | */ 116 | function alertObject(obj) { 117 | var s = ""; 118 | for (var prop in obj) { 119 | var subS = ""; 120 | if (obj[prop] != null && typeof obj[prop] == "object") { 121 | for (var subProp in obj[prop]) { 122 | subS += "> " + subProp + ": " + obj[prop][subProp] + "\n"; 123 | } 124 | s += prop + ":\n" + subS; 125 | } else { 126 | s += prop + ": " + obj[prop] + "\n"; 127 | } 128 | } 129 | alert(s); 130 | } 131 | -------------------------------------------------------------------------------- /src/include/helpers.jsxinc: -------------------------------------------------------------------------------- 1 | /** 2 | * Try and determine which if a localized string should be used or just the value. 3 | * @param command Command in question. 4 | * @param prop Command property to localize 5 | * @returns Correct string. 6 | */ 7 | function determineCorrectString(command, prop) { 8 | var s; 9 | if (typeof command[prop] == "object") { 10 | s = localize(command[prop]); 11 | } else if (strings.hasOwnProperty(command[prop])) { 12 | s = localize(strings[command[prop]]); 13 | } else { 14 | s = command[prop]; 15 | } 16 | return s; 17 | } 18 | 19 | function findLastCarrot(s) { 20 | var p = 0; 21 | var re = / > /g; 22 | 23 | if (re.test(s)) { 24 | var match = s.search(re); 25 | while (true) { 26 | p += match + 3; 27 | match = s.substring(p).search(re); 28 | 29 | if (match == -1) break; 30 | } 31 | } 32 | 33 | return p; 34 | } 35 | 36 | /** 37 | * Generate a unique command id for the data model. 38 | * @param s Base string to generate the id from. 39 | * @returns Valid command id. 40 | */ 41 | function generateCommandId(s) { 42 | var re = new RegExp("\\s|\\.", "gi"); 43 | var id = s.replaceAll(re, "_"); 44 | var n = 0; 45 | while (commandsData.hasOwnProperty(id)) { 46 | n++; 47 | id = s + n.toString(); 48 | } 49 | return id; 50 | } 51 | 52 | /** 53 | * Ask the user if they want to add their new commands to their startup screen. 54 | * @param newCommandIds Ids of the new commands. 55 | * @returns If commands were added to their startup screen. 56 | */ 57 | function addToStartup(newCommandIds) { 58 | // remove any command already in startup commands 59 | var newCommandId; 60 | for (var i = newCommandIds.length - 1; i >= 0; i--) { 61 | newCommandId = newCommandIds[i]; 62 | if (prefs.startupCommands.includes(newCommandId)) { 63 | newCommandIds.splice(i, 1); 64 | } 65 | } 66 | 67 | if (!newCommandIds.length) return; 68 | 69 | if ( 70 | !confirm( 71 | localize(strings.cd_add_to_startup), 72 | "noAsDflt", 73 | localize(strings.cd_add_to_startup_title) 74 | ) 75 | ) 76 | return false; 77 | prefs.startupCommands = newCommandIds.concat(prefs.startupCommands); 78 | } 79 | 80 | /** 81 | * Get every font used inside of an the Ai document. 82 | * @param {Object} doc Ai document. 83 | */ 84 | function getDocumentFonts(doc) { 85 | var fonts = []; 86 | for (var i = 0; i < doc.textFrames.length; i++) { 87 | for (var j = 0; j < doc.textFrames[i].textRanges.length; j++) { 88 | if (!fonts.includes(doc.textFrames[i].textRanges[j].textFont)) { 89 | fonts.push(doc.textFrames[i].textRanges[j].textFont); 90 | } 91 | } 92 | } 93 | return fonts; 94 | } 95 | 96 | /** 97 | * Reset view and zoom in on a specific page item. 98 | * @param pageItem Page item to focus on. 99 | */ 100 | function zoomIntoPageItem(pageItem) { 101 | // get screen information 102 | var screenBounds = app.activeDocument.views[0].bounds; 103 | var screenW = screenBounds[2] - screenBounds[0]; 104 | var screenH = screenBounds[1] - screenBounds[3]; 105 | 106 | // get the (true) visible bounds of the returned object 107 | var bounds = pageItem.visibleBounds; 108 | var itemW = bounds[2] - bounds[0]; 109 | var itemH = bounds[1] - bounds[3]; 110 | var itemCX = bounds[0] + itemW / 2; 111 | var itemCY = bounds[1] - itemH / 2; 112 | 113 | // reset the current view to center of selected object 114 | app.activeDocument.views[0].centerPoint = [itemCX, itemCY]; 115 | 116 | // calculate new zoom ratio to fit view to selected object 117 | var zoomRatio; 118 | if (itemW * (screenH / screenW) >= itemH) { 119 | zoomRatio = screenW / itemW; 120 | } else { 121 | zoomRatio = screenH / itemH; 122 | } 123 | 124 | // set zoom to fit selected object plus a bit of padding 125 | var padding = 0.9; 126 | app.activeDocument.views[0].zoom = zoomRatio * padding; 127 | } 128 | 129 | /** 130 | * Get info for all placed files for the current document. 131 | * @returns {Array} Placed file information. 132 | */ 133 | function getPlacedFileInfoForReport() { 134 | if (ExternalObject.AdobeXMPScript == undefined) 135 | ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript"); 136 | //Read xmp string - You can see document XMP in Illustrator -> File-> File Info -> Raw Data 137 | var xmp = new XMPMeta(app.activeDocument.XMPString); 138 | 139 | var names = []; 140 | var allFilePaths = getAllPlacedFilePaths(xmp); 141 | // var brokenFilePaths = getBrokenFilePaths(xmp); 142 | 143 | // convert path to file object for property access 144 | var fileObjects = []; 145 | for (var i = 0; i < allFilePaths.length; i++) { 146 | fileObjects.push(new File(allFilePaths[i])); 147 | } 148 | // sort the files by name 149 | fileObjects.sort(function (a, b) { 150 | return a.name - b.name; 151 | }); 152 | // build string with file info for the report 153 | var f; 154 | for (var i = 0; i < fileObjects.length; i++) { 155 | f = fileObjects[i]; 156 | names.push( 157 | localize(strings.dr_name) + 158 | decodeURI(f.name) + 159 | "\n" + 160 | localize(strings.dr_path) + 161 | f.fsName.replace(f.name, "") + 162 | "\n" + 163 | localize(strings.dr_file_found) + 164 | f.exists.toString().toUpperCase() + 165 | (i == fileObjects.length - 1 ? "" : "\n") 166 | ); 167 | } 168 | return names; 169 | } 170 | 171 | /** 172 | * Great trick to get all placed files (linked and embeded) @pixxxelschubser 173 | * https://community.adobe.com/t5/user/viewprofilepage/user-id/7720512 174 | * 175 | * If you try to do this using the placedItems collection from the API you will have issues. 176 | * @param {String} xmp Document xml data. 177 | * @returns {Array} Placed file paths. 178 | */ 179 | function getAllPlacedFilePaths(xmp) { 180 | //Read file paths from XMP - this returns file paths of both embedded and linked images 181 | var paths = []; 182 | var xpath; 183 | for (var i = 1; i <= xmp.countArrayItems(XMPConst.NS_XMP_MM, "Manifest"); i++) { 184 | xpath = "xmpMM:Manifest[" + i + "]/stMfs:reference/stRef:filePath"; 185 | paths.push(xmp.getProperty(XMPConst.NS_XMP_MM, xpath).value); 186 | } 187 | return paths; 188 | } 189 | 190 | /** 191 | * Check for any placed files with broken links in the current document. 192 | * @param {String} xmp Document xml data. 193 | * @returns {Array} Broken placed file paths. 194 | */ 195 | function getBrokenFilePaths(xmp) { 196 | //Read file paths from XMP - this returns file paths of both embedded and linked images 197 | var paths = []; 198 | var xpath; 199 | for (var i = 1; i <= xmp.countArrayItems(XMPConst.NS_XMP_MM, "Ingredients"); i++) { 200 | xpath = "xmpMM:Ingredients[" + i + "]/stRef:filePath"; 201 | paths.push(xmp.getProperty(XMPConst.NS_XMP_MM, xpath).value); 202 | } 203 | return paths; 204 | } 205 | 206 | /** 207 | * Check to make sure the command is available in the system Ai version. 208 | * @param command Command to check. 209 | * @returns True if command is available in the current Ai version or false if not. 210 | */ 211 | function commandVersionCheck(command) { 212 | if ( 213 | (command.hasOwnProperty("minVersion") && command.minVersion > aiVersion) || 214 | (command.hasOwnProperty("maxVersion") && command.maxVersion < aiVersion) 215 | ) 216 | return false; 217 | return true; 218 | } 219 | 220 | /** 221 | * Compare semantic version numbers. 222 | * @param {String} a Semantic version number. 223 | * @param {String} b Semantic version number. 224 | * @returns 1 if `a` > `b`, -1 if `b` > `a`, 0 if `a` == `b`. 225 | */ 226 | function semanticVersionComparison(a, b) { 227 | if (a === b) { 228 | return 0; 229 | } 230 | 231 | var a_components = a.split("."); 232 | var b_components = b.split("."); 233 | 234 | var len = Math.min(a_components.length, b_components.length); 235 | 236 | // loop while the components are equal 237 | for (var i = 0; i < len; i++) { 238 | // A bigger than B 239 | if (parseInt(a_components[i]) > parseInt(b_components[i])) { 240 | return 1; 241 | } 242 | 243 | // B bigger than A 244 | if (parseInt(a_components[i]) < parseInt(b_components[i])) { 245 | return -1; 246 | } 247 | } 248 | 249 | // If one's a prefix of the other, the longer one is greater. 250 | if (a_components.length > b_components.length) { 251 | return 1; 252 | } 253 | 254 | if (a_components.length < b_components.length) { 255 | return -1; 256 | } 257 | } 258 | 259 | /** 260 | * Convert Ai points unit to another api ruler constant. 261 | * https://ai-scripting.docsforadobe.dev/jsobjref/scripting-constants.html#jsobjref-scripting-constants-rulerunits 262 | * @param {Number} points Point value to convert. 263 | * @param {String} unit RulerUnit to convert `points` to. 264 | * @returns {Number} Converted number. 265 | */ 266 | function convertPointsTo(points, unit) { 267 | var conversions = { 268 | Centimeters: 28.346, 269 | Qs: 0.709, 270 | Inches: 72.0, 271 | Pixels: 1.0, 272 | Millimeters: 2.834645, 273 | Unknown: 1.0, 274 | Picas: 12.0, 275 | Points: 1.0, 276 | }; 277 | return points / conversions[unit]; 278 | } 279 | 280 | /** 281 | * Return the names of each object in an Ai collection object. 282 | * https://ai-scripting.docsforadobe.dev/scripting/workingWithObjects.html?highlight=collection#collection-objects 283 | * @param {Object} collection Ai collection object. 284 | * @param {Boolean} sorted Should the results be sorted. 285 | * @returns {Array} Names of each object inside of `collection`. 286 | */ 287 | function getCollectionObjectNames(collection, sorted) { 288 | sorted = typeof sorted !== "undefined" ? sorted : false; 289 | names = []; 290 | if (collection.length > 0) { 291 | for (var i = 0; i < collection.length; i++) { 292 | if (collection.typename == "Spots") { 293 | if (collection[i].name != "[Registration]") { 294 | names.push(collection[i].name); 295 | } 296 | } else { 297 | names.push(collection[i].name); 298 | } 299 | } 300 | } 301 | return sorted ? names.sort() : names; 302 | } 303 | 304 | /** 305 | * Present File.openDialog() for user to select files to load. 306 | * @param {String} prompt Prompt for dialog. 307 | * @param {Boolean} multiselect Can multiple files be selected. 308 | * @param {String} fileTypeRegex RegEx search string for file types (e.g. ".jsx$|.js$"). 309 | * @returns {Array} Selected file(s). 310 | */ 311 | function loadFileTypes(prompt, multiselect, fileTypeRegex) { 312 | var results = []; 313 | var files = File.openDialog(prompt, "", multiselect); 314 | if (files) { 315 | for (var i = 0; i < files.length; i++) { 316 | f = files[i]; 317 | fname = decodeURI(f.name); 318 | if (f.name.search(fileTypeRegex) >= 0) { 319 | results.push(f); 320 | } 321 | } 322 | } 323 | return results; 324 | } 325 | 326 | /** 327 | * Simulate a key press for Windows users. 328 | * 329 | * This function is in response to a known ScriptUI bug on Windows. 330 | * You can read more about it in the GitHub issue linked below. 331 | * https://github.com/joshbduncan/AiCommandPalette/issues/8 332 | * 333 | * Basically, on some Windows Ai versions, when a ScriptUI dialog is 334 | * presented and the active attribute is set to true on a field, Windows 335 | * will flash the Windows Explorer app quickly and then bring Ai back 336 | * in focus with the dialog front and center. This is a terrible user 337 | * experience so Sergey and I attempted to fix it the best we could. 338 | * 339 | * This clever solution was created by Sergey Osokin (https://github.com/creold) 340 | * 341 | * @param {String} k Key to simulate. 342 | * @param {Number} n Number of times to simulate the keypress. 343 | */ 344 | function simulateKeypress(k, n) { 345 | if (!n) n = 1; 346 | try { 347 | var f = setupFileObject(settingsFolder, "SimulateKeypress.vbs"); 348 | if (!f.exists) { 349 | var data = 'Set WshShell = WScript.CreateObject("WScript.Shell")\n'; 350 | while (n--) { 351 | data += 'WshShell.SendKeys "{' + k + '}"\n'; 352 | } 353 | f.encoding = "UTF-8"; 354 | f.open("w"); 355 | f.write(data); 356 | } 357 | f.execute(); 358 | } catch (e) { 359 | $.writeln(e); 360 | } finally { 361 | f.close(); 362 | } 363 | } 364 | 365 | /** 366 | * Open a url in the system browser. 367 | * @param {String} url URL to open. 368 | */ 369 | function openURL(url) { 370 | var html = new File(Folder.temp.absoluteURI + "/aisLink.html"); 371 | html.open("w"); 372 | var htmlBody = 373 | '

'; 376 | html.write(htmlBody); 377 | html.close(); 378 | html.execute(); 379 | } 380 | -------------------------------------------------------------------------------- /src/include/io.jsxinc: -------------------------------------------------------------------------------- 1 | // FILE/FOLDER OPERATIONS 2 | 3 | /** 4 | * Setup folder object or create if doesn't exist. 5 | * @param {String} path System folder path. 6 | * @returns {Object} Folder object. 7 | */ 8 | function setupFolderObject(path) { 9 | var folder = new Folder(path); 10 | if (!folder.exists) folder.create(); 11 | return folder; 12 | } 13 | 14 | /** 15 | * Setup file object. 16 | * @param {Object} path Folder object where file should exist, 17 | * @param {String} name File name. 18 | * @returns {Object} File object. 19 | */ 20 | function setupFileObject(path, name) { 21 | return new File(path + "/" + name); 22 | } 23 | 24 | /** 25 | * Write string data to disk. 26 | * @param {String} data Data to be written. 27 | * @param {Object} fp File path. 28 | * @param {string} mode File access mode. 29 | */ 30 | function writeData(data, fp, mode) { 31 | mode = typeof mode !== "undefined" ? mode : "w"; 32 | f = new File(fp); 33 | try { 34 | f.encoding = "UTF-8"; 35 | f.open(mode); 36 | f.write(data); 37 | } catch (e) { 38 | alert(localize(strings.fl_error_writing, f)); 39 | } finally { 40 | f.close(); 41 | } 42 | } 43 | 44 | /** 45 | * Read ExtendScript "json-like" data from file. 46 | * @param {Object} f File object to read. 47 | * @returns {Object} Evaluated JSON data. 48 | */ 49 | function readJSONData(f) { 50 | var json, obj; 51 | try { 52 | f.encoding = "UTF-8"; 53 | f.open("r"); 54 | json = f.read(); 55 | } catch (e) { 56 | alert(localize(strings.fl_error_loading, f)); 57 | } finally { 58 | f.close(); 59 | } 60 | obj = eval(json); 61 | return obj; 62 | } 63 | 64 | /** 65 | * Write ExtendScript "json-like" data to disk. 66 | * @param {Object} obj Data to be written. 67 | * @param {Object} f File object to write to. 68 | */ 69 | function writeJSONData(obj, f) { 70 | var data = obj.toSource(); 71 | try { 72 | f.encoding = "UTF-8"; 73 | f.open("w"); 74 | f.write(data); 75 | } catch (e) { 76 | alert(localize(strings.fl_error_writing, f)); 77 | } finally { 78 | f.close(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/include/palettes/commandPalette.jsxinc: -------------------------------------------------------------------------------- 1 | // CUSTOM SCRIPTUI FILTERABLE LISTBOX 2 | 3 | // set flags for query arrow navigation fix 4 | var fromQuery = false; 5 | var fromQueryShiftKey = false; 6 | 7 | /** 8 | * Custom wrapper for a ScriptUI Listbox. 9 | * @param {Array} commands Commands to load into the list box. 10 | * @param {Object} container ScriptUI container the listbox should be attached to. 11 | * @param {String} name Lookup name for the listbox. 12 | * @param {Array} bounds Bounds array for the listbox. 13 | * @param {Object} columns Listbox column information. 14 | * @param {Boolean} multiselect Should the listbox allow multiple selections (disable some features). 15 | * @param {String} helptip Listbox helptip/tooltip pop-up. 16 | * @param {Array} listeners ScriptUI listeners to add to the listbox. 17 | */ 18 | function ListBoxWrapper( 19 | commands, 20 | container, 21 | name, 22 | bounds, 23 | columns, 24 | multiselect, 25 | helptip, 26 | listeners 27 | ) { 28 | this.container = container; 29 | this.name = name; 30 | this.columns = columns; 31 | this.multiselect = multiselect; 32 | this.helptip = helptip; 33 | this.listeners = listeners; 34 | this.listbox = this.make(commands, bounds); 35 | } 36 | 37 | ListBoxWrapper.prototype = { 38 | /** 39 | * Initialize a new ScriptUI listbox, load the initial commands, and attach event listeners. 40 | * @param commands Item to load into the listbox. 41 | * @param bounds ScriptUI bounds array. 42 | * @returns ScriptUI listbox. 43 | */ 44 | make: function (commands, bounds) { 45 | var column; 46 | var columnTitles = []; 47 | var columnWidths = []; 48 | var columnKeys = []; 49 | for (column in this.columns) { 50 | columnTitles.push(this.columns[column].hideTitle ? "" : column); 51 | columnWidths.push(this.columns[column].width); 52 | columnKeys.push(this.columns[column].key); 53 | } 54 | 55 | var listbox = this.container.add("listbox", bounds, undefined, { 56 | name: this.name, 57 | numberOfColumns: columnTitles.length, 58 | showHeaders: true, 59 | columnTitles: columnTitles, 60 | columnWidths: columnWidths, 61 | multiselect: this.multiselect, 62 | }); 63 | listbox.frameStart = 0; 64 | if (this.helptip) listbox.helpTip = this.helptip; 65 | 66 | if (commands && commands.length) { 67 | this.loadCommands(listbox, commands, columnKeys); 68 | listbox.selection = 0; 69 | } 70 | 71 | this.addListeners(listbox); 72 | return listbox; 73 | }, 74 | /** 75 | * Update the listbox with new items. 76 | * @param {Array} matches Update listbox with new commands. 77 | */ 78 | update: function (matches) { 79 | var temp = this.make(matches, this.listbox.bounds); 80 | this.listbox.window.remove(this.listbox); 81 | this.listbox = temp; 82 | }, 83 | /** 84 | * Load command items in a ScriptUI listbox. 85 | * @param {Array} listbox ScriptUI listbox to load the command item in to. 86 | * @param {Array} commands Commands to load into the list box. 87 | * @param {Array} columnKeys Command lookup key for each column. 88 | */ 89 | loadCommands: function (listbox, commands, columnKeys) { 90 | var id, command, name, str, item; 91 | for (var i = 0; i < commands.length; i++) { 92 | id = commands[i]; 93 | // if command is no longer available just show the id 94 | if (!commandsData.hasOwnProperty(id)) { 95 | item = listbox.add("item", id); 96 | } else { 97 | command = commandsData[id]; 98 | name = determineCorrectString(command, "name"); 99 | 100 | // add base item with info from first column 101 | str = determineCorrectString(command, columnKeys[0]); 102 | item = listbox.add("item", str ? str : name); 103 | 104 | // add remaining columns as subItems 105 | for (var j = 1; j < columnKeys.length; j++) { 106 | str = determineCorrectString(command, columnKeys[j]); 107 | item.subItems[j - 1].text = str ? str : ""; 108 | } 109 | } 110 | item.id = id; 111 | } 112 | }, 113 | /** 114 | * Attach event listeners to the specified listbox. 115 | * @param {Object} listbox ScriptUI listbox to attach the listeners to. 116 | */ 117 | addListeners: function (listbox) { 118 | var listener; 119 | for (var i = 0; i < this.listeners.length; i++) { 120 | listener = this.listeners[i]; 121 | listener(listbox); 122 | } 123 | }, 124 | }; 125 | 126 | // LISTBOXWRAPPER LISTENERS 127 | 128 | /** 129 | * Close listbox when double-clicking a command. 130 | * @param {Object} listbox ScriptUI listbox. 131 | */ 132 | function selectOnDoubleClick(listbox) { 133 | listbox.onDoubleClick = function () { 134 | listbox.window.close(1); 135 | }; 136 | } 137 | 138 | /** 139 | * Add listbox command to Workflow when double-clicking. 140 | * @param {Object} listbox ScriptUI listbox. 141 | */ 142 | function addToStepsOnDoubleClick(listbox) { 143 | listbox.onDoubleClick = function () { 144 | var win, steps, command, newItem, newPicker; 145 | win = listbox.window; 146 | steps = win.findElement("steps"); 147 | command = commandsData[listbox.selection.id]; 148 | 149 | // check for "Build Picker..." command 150 | if (command.id == "builtin_buildPicker") { 151 | newPicker = buildPicker(); 152 | newItem = steps.add("item", newPicker.name); 153 | newItem.subItems[0].text = newPicker.type; 154 | newItem.id = newPicker.id; 155 | } else { 156 | newItem = steps.add("item", determineCorrectString(command, "name")); 157 | newItem.subItems[0].text = determineCorrectString(command, "type"); 158 | newItem.id = command.id; 159 | } 160 | steps.notify("onChange"); 161 | }; 162 | } 163 | 164 | /** 165 | * Swap listbox items in place (along with their corresponding id). 166 | * @param {Object} x Listbox item. 167 | * @param {Object} y Listbox item. 168 | */ 169 | function swapListboxItems(x, y) { 170 | var t = x.text; 171 | var subT = x.subItems[0].text; 172 | var id = x.id; 173 | x.text = y.text; 174 | x.subItems[0].text = y.subItems[0].text; 175 | x.id = y.id; 176 | y.text = t; 177 | y.subItems[0].text = subT; 178 | y.id = id; 179 | } 180 | 181 | /** 182 | * Allow end-to-end scrolling from within a listbox. 183 | * @param {Object} listbox ScriptUI listbox. 184 | */ 185 | function scrollListBoxWithArrows(listbox) { 186 | listbox.addEventListener("keydown", function (e) { 187 | if (fromQuery) { 188 | if (fromQueryShiftKey) { 189 | if (e.keyName == "Up") { 190 | if (listbox.selection.index == 0) { 191 | listbox.selection = listbox.items.length - 1; 192 | e.preventDefault(); 193 | } else { 194 | listbox.selection--; 195 | } 196 | } 197 | if (e.keyName == "Down") { 198 | if (listbox.selection.index == listbox.items.length - 1) { 199 | listbox.selection = 0; 200 | e.preventDefault(); 201 | } else { 202 | if (e.keyName == "Down") listbox.selection++; 203 | } 204 | } 205 | } else { 206 | if (e.keyName == "Up" || e.keyName == "Down") { 207 | if (e.keyName == "Up") { 208 | e.preventDefault(); 209 | if (!listbox.selection) { 210 | listbox.selection = 0; 211 | } else if (listbox.selection.index == 0) { 212 | // jump to the bottom if at top 213 | listbox.selection = listbox.items.length - 1; 214 | listbox.frameStart = listbox.items.length - 1 - visibleListItems; 215 | } else { 216 | if (listbox.selection.index > 0) { 217 | listbox.selection = listbox.selection.index - 1; 218 | if (listbox.selection.index < listbox.frameStart) listbox.frameStart--; 219 | } 220 | } 221 | } else if (e.keyName == "Down") { 222 | e.preventDefault(); 223 | if (!listbox.selection) { 224 | listbox.selection = 0; 225 | } else if (listbox.selection.index === listbox.items.length - 1) { 226 | // jump to the top if at the bottom 227 | listbox.selection = 0; 228 | listbox.frameStart = 0; 229 | } else { 230 | if (listbox.selection.index < listbox.items.length) { 231 | listbox.selection = listbox.selection.index + 1; 232 | if ( 233 | listbox.selection.index > 234 | listbox.frameStart + visibleListItems - 1 235 | ) { 236 | if (listbox.frameStart < listbox.items.length - visibleListItems) { 237 | listbox.frameStart++; 238 | } else { 239 | listbox.frameStart = listbox.frameStart; 240 | } 241 | } 242 | } 243 | } 244 | } 245 | /* 246 | If a selection is made inside of the actual listbox frame by the user, 247 | the API doesn't offer any way to know which part of the list is currently 248 | visible in the listbox "frame". If the user was to re-enter the `q` edittext 249 | and then hit an arrow key the above event listener will not work correctly so 250 | I just move the next selection (be it up or down) to the middle of the "frame". 251 | */ 252 | if (listbox.selection) { 253 | if ( 254 | listbox.selection.index < listbox.frameStart || 255 | listbox.selection.index > listbox.frameStart + visibleListItems - 1 256 | ) 257 | listbox.frameStart = 258 | listbox.selection.index - Math.floor(visibleListItems / 2); 259 | // don't move the frame if list items don't fill the available rows 260 | if (listbox.items.length <= visibleListItems) return; 261 | // move the frame by revealing the calculated `listbox.frameStart` 262 | listbox.revealItem(listbox.frameStart); 263 | } 264 | } 265 | } 266 | fromQuery = false; 267 | fromQueryShiftKey = false; 268 | } else { 269 | if (e.keyName == "Up" && listbox.selection.index == 0) { 270 | listbox.selection = listbox.items.length - 1; 271 | e.preventDefault(); 272 | } 273 | if (e.keyName == "Down" && listbox.selection.index == listbox.items.length - 1) { 274 | listbox.selection = 0; 275 | e.preventDefault(); 276 | } 277 | } 278 | }); 279 | } 280 | 281 | // USER DIALOGS 282 | 283 | function commandPalette(commands, title, columns, multiselect, showOnly, saveHistory) { 284 | var qCache = {}; 285 | 286 | // create the dialog 287 | var win = new Window("dialog"); 288 | win.text = title; 289 | win.alignChildren = "fill"; 290 | 291 | // setup the query input 292 | var q = win.add("edittext"); 293 | q.helpTip = localize(strings.cd_q_helptip); 294 | q.active = true; 295 | 296 | // setup the commands listbox 297 | var matches = showOnly ? showOnly : commands; 298 | var list = new ListBoxWrapper( 299 | matches, 300 | win, 301 | "commands", 302 | paletteSettings.bounds, 303 | columns, 304 | multiselect, 305 | null, 306 | [selectOnDoubleClick, scrollListBoxWithArrows] 307 | ); 308 | 309 | // window buttons 310 | var winButtons = win.add("group"); 311 | winButtons.orientation = "row"; 312 | winButtons.alignChildren = ["center", "center"]; 313 | var ok = winButtons.add("button", undefined, "OK"); 314 | ok.preferredSize.width = 100; 315 | var cancel = winButtons.add("button", undefined, localize(strings.cancel), { 316 | name: "cancel", 317 | }); 318 | cancel.preferredSize.width = 100; 319 | 320 | // work-around to stop windows from flickering/flashing explorer 321 | if (windowsFlickerFix) { 322 | simulateKeypress("TAB", 1); 323 | } 324 | 325 | // as a query is typed update the listbox 326 | q.onChanging = function () { 327 | if (q.text === "") { 328 | var matches = showOnly ? showOnly : commands; 329 | } else if (qCache.hasOwnProperty(q.text)) { 330 | matches = qCache[q.text]; 331 | } else { 332 | matches = matcher(q.text, commands); 333 | qCache[q.text] = matches; 334 | } 335 | list.update(matches); 336 | }; 337 | 338 | // save query and command history 339 | function updateHistory() { 340 | // don't add to history if no query was typed 341 | if (q.text === "") return; 342 | 343 | // don't add `Recent Commands` command 344 | if (list.listbox.selection.id == "builtin_recentCommands") return; 345 | 346 | history.push({ 347 | query: q.text, 348 | command: list.listbox.selection.id, 349 | timestamp: Date.now(), 350 | }); 351 | userHistory.save(); 352 | } 353 | 354 | // allow using arrow key from query input by sending a custom keyboard event to the list box 355 | if (!multiselect) { 356 | var kbEvent = ScriptUI.events.createEvent("KeyboardEvent"); 357 | q.addEventListener("keydown", function (e) { 358 | // hack to keep original commands from reloading before closing command palette when hitting the escape key while within the query box 359 | if (e.keyName == "Escape") { 360 | e.preventDefault(); 361 | win.close(); 362 | } 363 | if (e.keyName == "Up" || e.keyName == "Down") { 364 | kbEvent.initKeyboardEvent( 365 | "keydown", 366 | true, 367 | true, 368 | list.listbox, 369 | e.keyName, 370 | 0, 371 | "" 372 | ); 373 | fromQuery = true; 374 | fromQueryShiftKey = e.getModifierState("shift"); 375 | list.listbox.dispatchEvent(kbEvent); 376 | e.preventDefault(); 377 | } 378 | }); 379 | } 380 | 381 | if (win.show() == 1) { 382 | if (!list.listbox.selection) return; 383 | if (multiselect) { 384 | var items = []; 385 | for (var i = 0; i < list.listbox.selection.length; i++) { 386 | items.push(list.listbox.selection[i].id); 387 | } 388 | logger.log("user selected commands:", items.join(", ")); 389 | return items; 390 | } else { 391 | logger.log("user selected command:", list.listbox.selection); 392 | if (saveHistory) { 393 | updateHistory(); 394 | } 395 | return list.listbox.selection.hasOwnProperty("id") 396 | ? list.listbox.selection.id 397 | : list.listbox.selection.name; 398 | } 399 | } 400 | return false; 401 | } 402 | -------------------------------------------------------------------------------- /src/include/palettes/customCommands.jsxinc: -------------------------------------------------------------------------------- 1 | function addCustomCommandsDialog() { 2 | // create the dialog 3 | var win = new Window("dialog"); 4 | win.text = localize(strings.add_custom_commands_dialog_title); 5 | win.alignChildren = "fill"; 6 | 7 | var header = win.add( 8 | "statictext", 9 | [0, 0, 500, 100], 10 | localize(strings.custom_commands_header), 11 | { 12 | justify: "center", 13 | multiline: true, 14 | } 15 | ); 16 | header.justify = "center"; 17 | 18 | // custom commands csv text 19 | var customCommands = win.add("edittext", [0, 0, 400, 200], "", { multiline: true }); 20 | customCommands.text = ""; 21 | 22 | // window buttons 23 | var winButtons = win.add("group"); 24 | winButtons.orientation = "row"; 25 | winButtons.alignChildren = ["center", "center"]; 26 | var save = winButtons.add("button", undefined, localize(strings.save), { 27 | name: "ok", 28 | }); 29 | save.preferredSize.width = 100; 30 | save.enabled = false; 31 | var cancel = winButtons.add("button", undefined, localize(strings.cancel), { 32 | name: "cancel", 33 | }); 34 | cancel.preferredSize.width = 100; 35 | 36 | customCommands.onChanging = function () { 37 | save.enabled = customCommands.text.length > 0 ? true : false; 38 | }; 39 | 40 | if (win.show() == 1) { 41 | return customCommands.text; 42 | } 43 | return false; 44 | } 45 | -------------------------------------------------------------------------------- /src/include/palettes/fuzzy.jsxinc: -------------------------------------------------------------------------------- 1 | function fuzzy(q, commands) { 2 | function stripRegExpChars(input) { 3 | // Regex pattern to match any of the characters that have special meaning in a regex 4 | return input.replace(/[.*+?^=!:${}()|\[\]\/\\]/g, ""); 5 | } 6 | 7 | q = stripRegExpChars(q.toLowerCase()); 8 | 9 | var scores = {}; 10 | var matches = []; 11 | 12 | var id, command, commandName, spans, score, latch, recent, bonus; 13 | for (var i = 0; i < commands.length; i++) { 14 | // get command info 15 | id = commands[i]; 16 | command = commandsData[id]; 17 | commandName = determineCorrectString(command, "name").toLowerCase(); 18 | if (commandName == "") commandName = id.toLowerCase().replace("_", " "); 19 | 20 | // strip regex protected characters 21 | commandName = stripRegExpChars(commandName); 22 | 23 | // strip out ellipsis for correct full word check 24 | commandName = commandName.replace(regexEllipsis, ""); 25 | 26 | // find fuzzy matches 27 | spans = findMatches(q.split(" "), commandName); 28 | 29 | // no need to track scores of commands without matches 30 | if (!spans.length) continue; 31 | 32 | // calculate the command score 33 | bonus = 0; 34 | score = calculateScore(commandName, spans); 35 | 36 | // // increase score if latched query 37 | if (latches.hasOwnProperty(q) && commands.includes(latches[q])) { 38 | latch = true; 39 | bonus += 1; 40 | } 41 | 42 | // increase score recent command 43 | if (recentCommands.hasOwnProperty(command.id)) { 44 | recent = true; 45 | bonus += 0.5; 46 | } 47 | 48 | scores[id] = score + bonus; 49 | 50 | matches.push(id); 51 | } 52 | 53 | matches.sort(function (a, b) { 54 | return scores[b] - scores[a]; 55 | }); 56 | 57 | return matches; 58 | } 59 | 60 | function calculateScore(command, spans) { 61 | var lastCarrot = findLastCarrot(command); 62 | 63 | var score = 0; 64 | var s, e, wordStart, wordEnd; 65 | for (var i = 0; i < spans.length; i++) { 66 | s = spans[i][0]; 67 | e = spans[i][1]; 68 | 69 | // check for full word 70 | wordStart = s == 0 || command.charAt(s - 1) == " " ? true : false; 71 | wordEnd = e == command.length || command.charAt(e) == " " ? true : false; 72 | 73 | if (wordStart && wordEnd) { 74 | score += (e - s) * 3; 75 | } else if (wordStart) { 76 | score += (e - s) * 2; 77 | } else { 78 | score += e - s; 79 | } 80 | 81 | if (s >= lastCarrot) { 82 | score += 0.5; 83 | } 84 | } 85 | return score; 86 | } 87 | 88 | function findMatches(chunks, str) { 89 | var spans = []; 90 | 91 | var chunk, s, e, offset, lastSpan; 92 | for (var i = 0; i < chunks.length; i++) { 93 | var chunk = chunks[i]; 94 | if (!chunk) { 95 | continue; 96 | } 97 | 98 | s = 0; 99 | e = 1; 100 | offset = 0; 101 | lastSpan = null; 102 | 103 | var chars, match, spanStart, spanEnd; 104 | while (true) { 105 | chars = chunk.substring(s, e); 106 | match = str.substring(offset).match(chars); 107 | 108 | if (match) { 109 | spanStart = match.index + offset; 110 | spanEnd = spanStart + chars.length; 111 | lastSpan = [spanStart, spanEnd]; 112 | e++; 113 | } else { 114 | if (chars.length === 1) { 115 | spans = []; 116 | break; 117 | } 118 | 119 | s = e - 1; 120 | 121 | if (lastSpan !== null) { 122 | var spanStart = lastSpan[0]; 123 | var spanEnd = lastSpan[1]; 124 | offset = spanEnd; 125 | spans.push([spanStart, spanEnd]); 126 | } 127 | 128 | lastSpan = null; 129 | } 130 | 131 | if (e === chunk.length + 1) { 132 | if (lastSpan !== null) { 133 | var hls = lastSpan[0]; 134 | var hle = lastSpan[1]; 135 | spans.push([hls, hle]); 136 | } 137 | break; 138 | } 139 | } 140 | } 141 | return spans; 142 | } 143 | -------------------------------------------------------------------------------- /src/include/palettes/nofuzz.jsxinc: -------------------------------------------------------------------------------- 1 | /** 2 | * Score array items based on regex string match. 3 | * @param {String} query String to search for. 4 | * @param {Array} commands Commands to match `query` against. 5 | * @returns {Array} Matching items sorted by score. 6 | */ 7 | function scoreMatches(query, commands) { 8 | var words = []; 9 | var matches = []; 10 | var scores = {}; 11 | var maxScore = 0; 12 | query = query.toLowerCase(); 13 | var words = query.split(" "); 14 | var id, command, name, type, score, strippedName; 15 | 16 | // query latching 17 | if (latches.hasOwnProperty(query) && commands.includes(latches[query])) { 18 | scores[latches[query]] = 1000; 19 | matches.push(latches[query]); 20 | } 21 | 22 | for (var i = 0; i < commands.length; i++) { 23 | id = commands[i]; 24 | command = commandsData[id]; 25 | score = 0; 26 | name = determineCorrectString(command, "name").toLowerCase(); 27 | 28 | // escape hatch 29 | if (name == "") name = id.toLowerCase().replace("_", " "); 30 | 31 | type = strings.hasOwnProperty(command.type) 32 | ? localize(strings[command.type]).toLowerCase() 33 | : command.type.toLowerCase(); 34 | 35 | // check for exact match 36 | if ( 37 | query === name || 38 | query.replace(regexEllipsis, "").replace(regexCarrot, " ") == strippedName || 39 | query === type 40 | ) { 41 | score += word.length; 42 | } 43 | 44 | // strip junk from command name 45 | strippedName = name.replace(regexEllipsis, "").replace(regexCarrot, " "); 46 | 47 | // add the command type to the name if user requested searching type 48 | if (prefs.searchIncludesType) name = name.concat(" ", type); 49 | // TODO: maybe allow searching on all columns (pulled from paletteSettings.columnSets) 50 | 51 | // check for singular word matches 52 | var word, re; 53 | for (var n = 0; n < words.length; n++) { 54 | word = words[n]; 55 | if (!word) continue; 56 | 57 | re = new RegExp("\\b" + word, "gi"); 58 | 59 | // check for a match at the beginning of a word 60 | if (re.test(name) || re.test(strippedName)) score += word.length; 61 | } 62 | 63 | // updated scores for matches 64 | if (score > 0) { 65 | // increase score if command found in recent commands 66 | if (score >= maxScore && recentCommands.hasOwnProperty(command.id)) { 67 | score += recentCommands[command.id]; 68 | } 69 | if (scores.hasOwnProperty(id)) { 70 | scores[id] += score; 71 | } else { 72 | scores[id] = score; 73 | matches.push(id); 74 | } 75 | if (scores[id] > maxScore) maxScore = scores[id]; 76 | } 77 | } 78 | 79 | /* Sort matched by their respective score */ 80 | function sortByScore(arr) { 81 | for (var i = 0; i < arr.length; i++) { 82 | for (var j = 0; j < arr.length - i - 1; j++) { 83 | if (scores[arr[j + 1]] > scores[arr[j]]) { 84 | var temp = arr[j]; 85 | arr[j] = arr[j + 1]; 86 | arr[j + 1] = temp; 87 | } 88 | } 89 | } 90 | return arr; 91 | } 92 | 93 | return sortByScore(matches); 94 | } 95 | -------------------------------------------------------------------------------- /src/include/palettes/palettes.jsxinc: -------------------------------------------------------------------------------- 1 | //@include "fuzzy.jsxinc" 2 | //@include "nofuzz.jsxinc" 3 | //@include "commandPalette.jsxinc" 4 | //@include "customCommands.jsxinc" 5 | //@include "pickerBuilder.jsxinc" 6 | //@include "workflowBuilder.jsxinc" 7 | //@include "startupBuilder.jsxinc" 8 | 9 | /** 10 | * Filter the supplied commands by multiple factors. 11 | * @param {Array} commands Command `id`s to filter through. 12 | * @param {Array} types Types of commands to include in the results (e.g. builtin, tool, config, etc.). 13 | * @param {Boolean} showHidden Should user-hidden commands be included? 14 | * @param {Boolean} showNonRelevant Should non-relevant commands be included? 15 | * @param {Array} hideSpecificCommands Future me including a hack to hide specific commands. 16 | * @returns {Array} Filtered command ids. 17 | */ 18 | function filterCommands( 19 | commands, 20 | types, 21 | showHidden, 22 | showNonRelevant, 23 | hideSpecificCommands 24 | ) { 25 | var filteredCommands = []; 26 | var id, command; 27 | commands = commands ? commands : Object.keys(commandsData); 28 | for (var i = 0; i < commands.length; i++) { 29 | id = commands[i]; 30 | if (!commandsData.hasOwnProperty(id)) continue; 31 | command = commandsData[id]; 32 | 33 | // make sure Ai version meets command requirements 34 | if (!commandVersionCheck(command)) continue; 35 | 36 | // skip any hidden commands 37 | if (!showHidden && prefs.hiddenCommands.includes(id)) continue; 38 | 39 | // skip any non relevant commands 40 | if (!showNonRelevant && !relevantCommand(command)) continue; 41 | 42 | // skip any specific commands name in hideSpecificCommands 43 | if (hideSpecificCommands && hideSpecificCommands.includes(id)) continue; 44 | 45 | // then check to see if the command should be included 46 | if (!types || types.includes(command.type)) filteredCommands.push(id); 47 | } 48 | return filteredCommands; 49 | } 50 | 51 | /** 52 | * Determine is a command is relevant at the current moment. 53 | * @param {Object} command Command object to check. 54 | * @returns {Boolean} If command is relevant. 55 | */ 56 | function relevantCommand(command) { 57 | // hide commands requiring an active documents if requested 58 | if (command.docRequired && app.documents.length < 1) return false; 59 | // hide commands requiring an active selection if requested 60 | if (command.selRequired && app.activeDocument.selection.length < 1) return false; 61 | 62 | // hide `Edit Workflow...` command if no workflows 63 | if (command.id == "builtin_editWorkflow" && prefs.workflows.length < 1) return false; 64 | // hide `All Workflows...` command if no workflows 65 | if (command.id == "builtin_allWorkflows" && prefs.workflows.length < 1) return false; 66 | // hide `All Scripts...` command if no scripts 67 | if (command.id == "builtin_allScripts" && prefs.scripts.length < 1) return false; 68 | // hide `All Bookmarks...` command if no bookmarks 69 | if (command.id == "builtin_allBookmarks" && prefs.bookmarks.length < 1) return false; 70 | // hide `All Actions...` command if no actions 71 | if (command.id == "builtin_allActions" && !userActions.loadedActions) return false; 72 | // hide `Edit Picker...` command if no pickers 73 | if (command.id == "builtin_editPicker" && prefs.pickers.length < 1) return false; 74 | // hide `All Pickers...` command if no pickers 75 | if (command.id == "builtin_allPickers" && prefs.pickers.length < 1) return false; 76 | 77 | // hide `Enable Fuzzy Matching` command if already enabled 78 | if (command.id == "config_enableFuzzyMatching" && prefs.fuzzy) return false; 79 | // hide `Disable Fuzzy Matching` command if already disabled 80 | if (command.id == "config_disableFuzzyMatching" && !prefs.fuzzy) return false; 81 | 82 | // hide `Enable Debug Logging` command if already enabled 83 | if (command.id == "config_enableDebugLogging" && debugLogging) return false; 84 | // hide `Disable Debug Logging` command if already disabled 85 | if (command.id == "config_disableDebugLogging" && !debugLogging) return false; 86 | 87 | // hide `Unhide Commands...` command if no hidden commands 88 | if (command.id == "config_unhideCommand" && prefs.hiddenCommands.length < 1) 89 | return false; 90 | // hide `Recent Commands...` and `Clear History` if no recent commands 91 | if ( 92 | command.id == "builtin_recentCommands" && 93 | Object.keys(recentCommands).length === 0 94 | ) { 95 | return false; 96 | } 97 | 98 | return true; 99 | } 100 | -------------------------------------------------------------------------------- /src/include/palettes/pickerBuilder.jsxinc: -------------------------------------------------------------------------------- 1 | function pickerBuilder(editPickerId) { 2 | var overwrite = false; 3 | 4 | // create the dialog 5 | var win = new Window("dialog"); 6 | win.text = localize(strings.picker_builder_title); 7 | win.alignChildren = "fill"; 8 | 9 | // picker commands 10 | var header = win.add( 11 | "statictext", 12 | undefined, 13 | localize(strings.picker_builder_header) 14 | ); 15 | header.justify = "center"; 16 | var pickerCommands = win.add("edittext", [0, 0, 400, 200], "", { multiline: true }); 17 | pickerCommands.text = editPickerId 18 | ? commandsData[editPickerId].commands.join("\n") 19 | : ""; 20 | pickerCommands.active = true; 21 | var cbMultiselect = win.add( 22 | "checkbox", 23 | undefined, 24 | localize(strings.picker_builder_multi_select) 25 | ); 26 | cbMultiselect.value = editPickerId ? commandsData[editPickerId].multiselect : false; 27 | 28 | // picker name 29 | var pName = win.add("panel", undefined, localize(strings.picker_builder_name)); 30 | pName.alignChildren = ["fill", "center"]; 31 | pName.margins = 20; 32 | var pickerNameText = editPickerId ? commandsData[editPickerId].name : ""; 33 | var pickerName = pName.add("edittext", undefined, pickerNameText); 34 | pickerName.enabled = editPickerId ? true : false; 35 | 36 | // window buttons 37 | var winButtons = win.add("group"); 38 | winButtons.orientation = "row"; 39 | winButtons.alignChildren = ["center", "center"]; 40 | var save = winButtons.add("button", undefined, localize(strings.save), { 41 | name: "ok", 42 | }); 43 | save.preferredSize.width = 100; 44 | save.enabled = editPickerId ? true : false; 45 | var cancel = winButtons.add("button", undefined, localize(strings.cancel), { 46 | name: "cancel", 47 | }); 48 | cancel.preferredSize.width = 100; 49 | 50 | pickerCommands.onChanging = function () { 51 | pickerName.enabled = pickerCommands.text.length > 0 ? true : false; 52 | save.enabled = 53 | pickerCommands.text.length > 0 && pickerName.text.length > 0 ? true : false; 54 | }; 55 | 56 | pickerName.onChanging = function () { 57 | save.enabled = pickerCommands.text.length > 0 ? true : false; 58 | }; 59 | 60 | save.onClick = function () { 61 | // check for picker overwrite 62 | var currentPickers = []; 63 | for (var i = 0; i < prefs.pickers.length; i++) { 64 | currentPickers.push(prefs.pickers[i].name); 65 | } 66 | if (currentPickers.includes(pickerName.text.trim())) { 67 | overwrite = true; 68 | if ( 69 | !confirm( 70 | localize( 71 | strings.picker_builder_save_conflict_message, 72 | pickerName.text.trim() 73 | ), 74 | "noAsDflt", 75 | localize(strings.picker_builder_save_conflict_title) 76 | ) 77 | ) { 78 | return; 79 | } 80 | } 81 | win.close(1); 82 | }; 83 | 84 | if (win.show() == 1) { 85 | var commands = []; 86 | var lines = pickerCommands.text.split(/\r\n|\r|\n/); 87 | for (var i = 0; i < lines.length; i++) { 88 | commands.push(lines[i].trim()); 89 | } 90 | return { 91 | name: pickerName.text.trim(), 92 | commands: commands, 93 | multiselect: cbMultiselect.value, 94 | overwrite: overwrite, 95 | }; 96 | } 97 | return false; 98 | } 99 | -------------------------------------------------------------------------------- /src/include/palettes/startupBuilder.jsxinc: -------------------------------------------------------------------------------- 1 | function startupBuilder(commands) { 2 | var qCache = {}; 3 | 4 | // create the dialog 5 | var win = new Window("dialog"); 6 | win.text = localize(strings.startup_builder); 7 | win.alignChildren = "fill"; 8 | 9 | // setup the query input 10 | var pSearch = win.add("panel", undefined, localize(strings.cd_search_for)); 11 | pSearch.alignChildren = ["fill", "center"]; 12 | pSearch.margins = 20; 13 | var q = pSearch.add("edittext"); 14 | q.helpTip = localize(strings.cd_q_helptip); 15 | 16 | // setup the commands listbox 17 | var list = new ListBoxWrapper( 18 | commands, 19 | pSearch, 20 | "commands", 21 | [0, 0, paletteSettings.paletteWidth, paletteSettings.paletteHeight], 22 | paletteSettings.columnSets.default, 23 | false, 24 | localize(strings.startup_helptip), 25 | [addToStepsOnDoubleClick, scrollListBoxWithArrows] 26 | ); 27 | 28 | // work-around to stop windows from flickering/flashing explorer 29 | if (windowsFlickerFix) { 30 | simulateKeypress("TAB", 1); 31 | } else { 32 | q.active = true; 33 | } 34 | 35 | var pSteps = win.add("panel", undefined, localize(strings.startup_steps)); 36 | pSteps.alignChildren = ["fill", "center"]; 37 | pSteps.margins = 20; 38 | 39 | // setup the workflow action steps listbox 40 | var steps = new ListBoxWrapper( 41 | prefs.startupCommands, 42 | pSteps, 43 | "steps", 44 | [0, 0, paletteSettings.paletteWidth, paletteSettings.paletteHeight], 45 | paletteSettings.columnSets.default, 46 | true, 47 | localize(strings.startup_steps_helptip), 48 | [] 49 | ); 50 | 51 | var stepButtons = pSteps.add("group"); 52 | stepButtons.alignment = "center"; 53 | var up = stepButtons.add("button", undefined, localize(strings.step_up)); 54 | up.preferredSize.width = 100; 55 | var down = stepButtons.add("button", undefined, localize(strings.step_down)); 56 | down.preferredSize.width = 100; 57 | var del = stepButtons.add("button", undefined, localize(strings.step_delete)); 58 | del.preferredSize.width = 100; 59 | 60 | // window buttons 61 | var winButtons = win.add("group"); 62 | winButtons.orientation = "row"; 63 | winButtons.alignChildren = ["center", "center"]; 64 | var save = winButtons.add("button", undefined, localize(strings.save), { 65 | name: "ok", 66 | }); 67 | save.preferredSize.width = 100; 68 | save.enabled = true; 69 | var cancel = winButtons.add("button", undefined, localize(strings.cancel), { 70 | name: "cancel", 71 | }); 72 | cancel.preferredSize.width = 100; 73 | 74 | // as a query is typed update the listbox 75 | var matches; 76 | q.onChanging = function () { 77 | if (q.text === "") { 78 | matches = commands; 79 | } else if (qCache.hasOwnProperty(q.text)) { 80 | matches = qCache[q.text]; 81 | } else { 82 | matches = matcher(q.text, commands); 83 | qCache[q.text] = matches; 84 | } 85 | if (matches.length > 0) { 86 | list.update(matches); 87 | } 88 | }; 89 | 90 | up.onClick = function () { 91 | var selected = sortIndexes(steps.listbox.selection); 92 | if (selected[i] == 0 || !contiguous(selected)) return; 93 | for (var i = 0; i < selected.length; i++) 94 | swapListboxItems( 95 | steps.listbox.items[selected[i] - 1], 96 | steps.listbox.items[selected[i]] 97 | ); 98 | steps.listbox.selection = null; 99 | for (var n = 0; n < selected.length; n++) steps.listbox.selection = selected[n] - 1; 100 | }; 101 | 102 | down.onClick = function () { 103 | var selected = sortIndexes(steps.listbox.selection); 104 | if ( 105 | selected[selected.length - 1] == steps.listbox.items.length - 1 || 106 | !contiguous(selected) 107 | ) 108 | return; 109 | for (var i = steps.listbox.selection.length - 1; i > -1; i--) 110 | swapListboxItems( 111 | steps.listbox.items[selected[i]], 112 | steps.listbox.items[selected[i] + 1] 113 | ); 114 | steps.listbox.selection = null; 115 | for (var n = 0; n < selected.length; n++) steps.listbox.selection = selected[n] + 1; 116 | }; 117 | 118 | // the api returns the selected items in the order they were 119 | // selected/clicked by the user when you call `list.selection` 120 | // so their actual listbox indexes need to be sorted for the 121 | // up, down, and delete buttons to work when multiple items are selected 122 | function sortIndexes(sel) { 123 | var indexes = []; 124 | for (var i = 0; i < sel.length; i++) indexes.push(sel[i].index); 125 | return indexes.sort(); 126 | } 127 | 128 | // check to make sure selection is contiguous 129 | function contiguous(sel) { 130 | return sel.length == sel[sel.length - 1] - sel[0] + 1; 131 | } 132 | 133 | del.onClick = function () { 134 | var selected = sortIndexes(steps.listbox.selection); 135 | for (var i = selected.length - 1; i > -1; i--) { 136 | // add removed item back to listbox 137 | commands.push(steps.listbox.items[selected[i]].id); 138 | steps.listbox.remove(selected[i]); 139 | } 140 | 141 | // clear cache and re-index matches 142 | qCache = {}; 143 | matches = matcher(q.text, commands); 144 | qCache[q.text] = matches; 145 | if (matches.length > 0) { 146 | list.update(matches); 147 | } 148 | 149 | steps.listbox.selection == null; 150 | }; 151 | 152 | if (win.show() == 1) { 153 | var items = []; 154 | for (var i = 0; i < steps.listbox.items.length; i++) { 155 | items.push(steps.listbox.items[i].id); 156 | } 157 | return items; 158 | } 159 | return false; 160 | } 161 | -------------------------------------------------------------------------------- /src/include/palettes/workflowBuilder.jsxinc: -------------------------------------------------------------------------------- 1 | function workflowBuilder(commands, editWorkflowId) { 2 | var qCache = {}; 3 | var overwrite = false; 4 | var editableCommandTypes = ["picker"]; 5 | 6 | // create the dialog 7 | var win = new Window("dialog"); 8 | win.text = localize(strings.wf_builder); 9 | win.alignChildren = "fill"; 10 | 11 | // setup the query input 12 | var pSearch = win.add("panel", undefined, localize(strings.cd_search_for)); 13 | pSearch.alignChildren = ["fill", "center"]; 14 | pSearch.margins = 20; 15 | var q = pSearch.add("edittext"); 16 | q.helpTip = localize(strings.cd_q_helptip); 17 | 18 | // setup the commands listbox 19 | var list = new ListBoxWrapper( 20 | commands, 21 | pSearch, 22 | "commands", 23 | [0, 0, paletteSettings.paletteWidth, paletteSettings.paletteHeight], 24 | paletteSettings.columnSets.default, 25 | false, 26 | localize(strings.cd_helptip), 27 | [addToStepsOnDoubleClick, scrollListBoxWithArrows] 28 | ); 29 | 30 | // work-around to stop windows from flickering/flashing explorer 31 | if (windowsFlickerFix) { 32 | simulateKeypress("TAB", 1); 33 | } else { 34 | q.active = true; 35 | } 36 | 37 | var pSteps = win.add("panel", undefined, localize(strings.wf_steps)); 38 | pSteps.alignChildren = ["fill", "center"]; 39 | pSteps.margins = 20; 40 | 41 | // if editing a workflow check to make sure all of it's actions are still valid 42 | var editWorkflow, step; 43 | var actionSteps = []; 44 | if (editWorkflowId) { 45 | editWorkflow = commandsData[editWorkflowId]; 46 | for (var i = 0; i < editWorkflow.actions.length; i++) { 47 | step = editWorkflow.actions[i]; 48 | if (!commandsData.hasOwnProperty(editWorkflow.actions[i])) { 49 | step += " [NOT FOUND]"; 50 | } else if (!commandVersionCheck(editWorkflow.actions[i])) { 51 | step += " [INCOMPATIBLE AI VERSION]"; 52 | } 53 | actionSteps.push(step); 54 | } 55 | } 56 | 57 | // setup the workflow action steps listbox 58 | var steps = new ListBoxWrapper( 59 | actionSteps, 60 | pSteps, 61 | "steps", 62 | [0, 0, paletteSettings.paletteWidth, paletteSettings.paletteHeight], 63 | paletteSettings.columnSets.default, 64 | true, 65 | localize(strings.wf_steps_helptip), 66 | [] 67 | ); 68 | 69 | // allow in-line editing of pickers 70 | steps.listbox.onDoubleClick = function () { 71 | var selectedItem, command, updatedPicker; 72 | selectedItem = steps.listbox.selection[0]; 73 | command = commandsData[selectedItem.id]; 74 | if (!editableCommandTypes.includes(command.type.toLowerCase())) { 75 | alert(localize(strings.wf_step_not_editable)); 76 | return; 77 | } 78 | updatedPicker = buildPicker(command.id); 79 | if (updatedPicker.id != command.id) selectedItem.id = updatedPicker.id; 80 | if (updatedPicker.name != command.name) selectedItem.text = updatedPicker.name; 81 | }; 82 | 83 | var stepButtons = pSteps.add("group"); 84 | stepButtons.alignment = "center"; 85 | var up = stepButtons.add("button", undefined, localize(strings.step_up)); 86 | up.preferredSize.width = 100; 87 | var down = stepButtons.add("button", undefined, localize(strings.step_down)); 88 | down.preferredSize.width = 100; 89 | var edit = stepButtons.add("button", undefined, localize(strings.step_edit)); 90 | edit.preferredSize.width = 100; 91 | var del = stepButtons.add("button", undefined, localize(strings.step_delete)); 92 | del.preferredSize.width = 100; 93 | 94 | // workflow name 95 | var pName = win.add("panel", undefined, localize(strings.wf_save_as)); 96 | pName.alignChildren = ["fill", "center"]; 97 | pName.margins = 20; 98 | var workflowNameText = editWorkflow ? editWorkflow.name : ""; 99 | var workflowName = pName.add("edittext", undefined, workflowNameText); 100 | workflowName.enabled = editWorkflow ? true : false; 101 | 102 | // window buttons 103 | var winButtons = win.add("group"); 104 | winButtons.orientation = "row"; 105 | winButtons.alignChildren = ["center", "center"]; 106 | var save = winButtons.add("button", undefined, localize(strings.save), { 107 | name: "ok", 108 | }); 109 | save.preferredSize.width = 100; 110 | save.enabled = editWorkflow ? true : false; 111 | var cancel = winButtons.add("button", undefined, localize(strings.cancel), { 112 | name: "cancel", 113 | }); 114 | cancel.preferredSize.width = 100; 115 | 116 | // as a query is typed update the listbox 117 | var matches; 118 | q.onChanging = function () { 119 | if (q.text === "") { 120 | matches = commands; 121 | } else if (qCache.hasOwnProperty(q.text)) { 122 | matches = qCache[q.text]; 123 | } else { 124 | matches = matcher(q.text, commands); 125 | qCache[q.text] = matches; 126 | } 127 | if (matches.length > 0) { 128 | list.update(matches); 129 | } 130 | }; 131 | 132 | steps.listbox.onChange = function () { 133 | workflowName.enabled = steps.listbox.items.length > 0 ? true : false; 134 | save.enabled = 135 | steps.listbox.items.length > 0 && workflowName.text.length > 0 ? true : false; 136 | }; 137 | 138 | workflowName.onChanging = function () { 139 | save.enabled = workflowName.text.length > 0 ? true : false; 140 | }; 141 | 142 | up.onClick = function () { 143 | var selected = sortIndexes(steps.listbox.selection); 144 | if (selected[i] == 0 || !contiguous(selected)) return; 145 | for (var i = 0; i < selected.length; i++) 146 | swapListboxItems( 147 | steps.listbox.items[selected[i] - 1], 148 | steps.listbox.items[selected[i]] 149 | ); 150 | steps.listbox.selection = null; 151 | for (var n = 0; n < selected.length; n++) steps.listbox.selection = selected[n] - 1; 152 | }; 153 | 154 | down.onClick = function () { 155 | var selected = sortIndexes(steps.listbox.selection); 156 | if ( 157 | selected[selected.length - 1] == steps.listbox.items.length - 1 || 158 | !contiguous(selected) 159 | ) 160 | return; 161 | for (var i = steps.listbox.selection.length - 1; i > -1; i--) 162 | swapListboxItems( 163 | steps.listbox.items[selected[i]], 164 | steps.listbox.items[selected[i] + 1] 165 | ); 166 | steps.listbox.selection = null; 167 | for (var n = 0; n < selected.length; n++) steps.listbox.selection = selected[n] + 1; 168 | }; 169 | 170 | // the api returns the selected items in the order they were 171 | // selected/clicked by the user when you call `list.selection` 172 | // so their actual listbox indexes need to be sorted for the 173 | // up, down, and delete buttons to work when multiple items are selected 174 | function sortIndexes(sel) { 175 | var indexes = []; 176 | for (var i = 0; i < sel.length; i++) indexes.push(sel[i].index); 177 | return indexes.sort(); 178 | } 179 | 180 | // check to make sure selection is contiguous 181 | function contiguous(sel) { 182 | return sel.length == sel[sel.length - 1] - sel[0] + 1; 183 | } 184 | 185 | edit.onClick = function () { 186 | var selectedItem, command, updatedPicker; 187 | selectedItem = steps.listbox.selection[0]; 188 | command = commandsData[selectedItem.id]; 189 | if (!editableCommandTypes.includes(command.type.toLowerCase())) { 190 | alert(localize(strings.wf_step_not_editable)); 191 | return; 192 | } 193 | updatedPicker = buildPicker(command.id); 194 | if (updatedPicker.id != command.id) selectedItem.id = updatedPicker.id; 195 | if (updatedPicker.name != command.name) selectedItem.text = updatedPicker.name; 196 | }; 197 | 198 | del.onClick = function () { 199 | var selected = sortIndexes(steps.listbox.selection); 200 | for (var i = selected.length - 1; i > -1; i--) { 201 | steps.listbox.remove(selected[i]); 202 | } 203 | steps.listbox.selection == null; 204 | workflowName.enabled = steps.listbox.items.length > 0 ? true : false; 205 | save.enabled = 206 | steps.listbox.items.length > 0 && workflowName.text.length > 0 ? true : false; 207 | }; 208 | 209 | save.onClick = function () { 210 | // check for workflow overwrite 211 | var currentWorkflows = []; 212 | for (var i = 0; i < prefs.workflows.length; i++) { 213 | currentWorkflows.push(prefs.workflows[i].name); 214 | } 215 | if (currentWorkflows.includes(workflowName.text.trim())) { 216 | overwrite = true; 217 | if ( 218 | !confirm( 219 | localize(strings.wf_already_exists) + "\n" + workflowName.text.trim(), 220 | "noAsDflt", 221 | localize(strings.wf_already_exists_title) 222 | ) 223 | ) { 224 | return; 225 | } 226 | } 227 | win.close(1); 228 | }; 229 | 230 | if (win.show() == 1) { 231 | var actions = []; 232 | for (var i = 0; i < steps.listbox.items.length; i++) { 233 | actions.push(steps.listbox.items[i].id); 234 | } 235 | return { name: workflowName.text.trim(), actions: actions, overwrite: overwrite }; 236 | } 237 | return false; 238 | } 239 | -------------------------------------------------------------------------------- /src/include/polyfills.jsxinc: -------------------------------------------------------------------------------- 1 | // JAVASCRIPT POLYFILLS 2 | 3 | //ARRAY POLYFILLS 4 | 5 | if (!Array.prototype.indexOf) { 6 | Array.prototype.indexOf = function (obj, start) { 7 | for (var i = start || 0, j = this.length; i < j; i++) { 8 | if (this[i] === obj) { 9 | return i; 10 | } 11 | } 12 | return -1; 13 | }; 14 | } 15 | 16 | if (!Array.prototype.includes) { 17 | Array.prototype.includes = function (search, start) { 18 | if (start === undefined) { 19 | start = 0; 20 | } 21 | return this.indexOf(search, start) !== -1; 22 | }; 23 | } 24 | 25 | // OBJECT POLYFILLS 26 | 27 | /** 28 | * Object.keys() polyfill 29 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys 30 | */ 31 | if (!Object.keys) { 32 | Object.keys = (function () { 33 | "use strict"; 34 | var hasOwnProperty = Object.prototype.hasOwnProperty, 35 | hasDontEnumBug = !{ toString: null }.propertyIsEnumerable("toString"), 36 | dontEnums = [ 37 | "toString", 38 | "toLocaleString", 39 | "valueOf", 40 | "hasOwnProperty", 41 | "isPrototypeOf", 42 | "propertyIsEnumerable", 43 | "constructor", 44 | ], 45 | dontEnumsLength = dontEnums.length; 46 | 47 | return function (obj) { 48 | if (typeof obj !== "function" && (typeof obj !== "object" || obj === null)) { 49 | throw new TypeError("Object.keys called on non-object"); 50 | } 51 | 52 | var result = [], 53 | prop, 54 | i; 55 | 56 | for (prop in obj) { 57 | if (hasOwnProperty.call(obj, prop)) { 58 | result.push(prop); 59 | } 60 | } 61 | 62 | if (hasDontEnumBug) { 63 | for (i = 0; i < dontEnumsLength; i++) { 64 | if (hasOwnProperty.call(obj, dontEnums[i])) { 65 | result.push(dontEnums[i]); 66 | } 67 | } 68 | } 69 | return result; 70 | }; 71 | })(); 72 | } 73 | 74 | // STRING POLYFILLS 75 | 76 | /** 77 | * String.prototype.trim() polyfill 78 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill 79 | */ 80 | if (!String.prototype.trim) { 81 | String.prototype.trim = function () { 82 | return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ""); 83 | }; 84 | } 85 | 86 | /** 87 | * String.prototype.replaceAll() polyfill 88 | * https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/ 89 | * @author Chris Ferdinandi 90 | * @license MIT 91 | */ 92 | if (!String.prototype.replaceAll) { 93 | String.prototype.replaceAll = function (str, newStr) { 94 | // If a regex pattern 95 | if (Object.prototype.toString.call(str).toLowerCase() === "[object regexp]") { 96 | return this.replace(str, newStr); 97 | } 98 | 99 | // If a string 100 | return this.replace(new RegExp(str, "g"), newStr); 101 | }; 102 | } 103 | -------------------------------------------------------------------------------- /src/include/user/actions.jsxinc: -------------------------------------------------------------------------------- 1 | //USER ACTIONS 2 | 3 | var userActions = {}; 4 | userActions.loadedActions = false; 5 | userActions.load = function () { 6 | logger.log("loading user actions"); 7 | 8 | var ct = 0; 9 | var currentPath, set, actionCount, name; 10 | var pref = app.preferences; 11 | var path = "plugin/Action/SavedSets/set-"; 12 | 13 | for (var i = 1; i <= 100; i++) { 14 | currentPath = path + i.toString() + "/"; 15 | // get action sets 16 | set = pref.getStringPreference(currentPath + "name"); 17 | if (!set) { 18 | break; 19 | } 20 | // get actions in set 21 | actionCount = Number(pref.getIntegerPreference(currentPath + "actionCount")); 22 | ct += actionCount; 23 | var name, id, loc, obj; 24 | for (var j = 1; j <= actionCount; j++) { 25 | loc = {}; 26 | obj = {}; 27 | name = pref.getStringPreference(currentPath + "action-" + j.toString() + "/name"); 28 | id = generateCommandId("action_" + set + "_" + name.toLowerCase()); 29 | id = set + "_" + name; // FIXME: why? 30 | obj["id"] = id; 31 | obj["action"] = "action"; 32 | obj["type"] = "action"; 33 | obj["set"] = set; 34 | obj["name"] = name; 35 | obj["docRequired"] = false; 36 | obj["selRequired"] = false; 37 | obj["hidden"] = false; 38 | commandsData[id] = obj; 39 | } 40 | } 41 | this.loadedActions = ct > 0; 42 | }; 43 | -------------------------------------------------------------------------------- /src/include/user/history.jsxinc: -------------------------------------------------------------------------------- 1 | //USER HISTORY 2 | 3 | var userHistoryFolder = setupFolderObject(Folder.userData + "/JBD/AiCommandPalette"); 4 | var userHistoryFileName = "History.json"; 5 | 6 | // setup the base prefs model 7 | var history = []; 8 | var recentCommands = {}; 9 | var mostRecentCommands = []; 10 | var latches = {}; 11 | 12 | var userHistory = {}; 13 | userHistory.folder = function () { 14 | return userHistoryFolder; 15 | }; 16 | userHistory.file = function () { 17 | var folder = this.folder(); 18 | var file = setupFileObject(folder, userHistoryFileName); 19 | return file; 20 | }; 21 | userHistory.load = function () { 22 | var file = this.file(); 23 | logger.log("loading user history:", file.fsName); 24 | if (file.exists) { 25 | var queryCommandsLUT = {}; 26 | var loadedData, entry; 27 | try { 28 | loadedData = readJSONData(file); 29 | if (loadedData == []) return; 30 | history = loadedData; 31 | for (var i = loadedData.length - 1; i >= 0; i--) { 32 | entry = loadedData[i]; 33 | // track how many times a query ties to a command 34 | if (!queryCommandsLUT.hasOwnProperty(entry.query)) 35 | queryCommandsLUT[entry.query] = {}; 36 | if (!queryCommandsLUT[entry.query].hasOwnProperty(entry.command)) 37 | queryCommandsLUT[entry.query][entry.command] = 0; 38 | queryCommandsLUT[entry.query][entry.command]++; 39 | // track how often recent command have been ran 40 | if (!recentCommands.hasOwnProperty(entry.command)) 41 | recentCommands[entry.command] = 0; 42 | recentCommands[entry.command]++; 43 | // track the past 25 most recent commands 44 | if ( 45 | mostRecentCommands.length <= mostRecentCommandsCount && 46 | commandsData.hasOwnProperty(entry.command) && 47 | !mostRecentCommands.includes(entry.command) 48 | ) 49 | mostRecentCommands.push(entry.command); 50 | } 51 | // build latches with most common command for each query 52 | var query, command, commands; 53 | for (query in queryCommandsLUT) { 54 | commands = []; 55 | for (command in queryCommandsLUT[query]) { 56 | commands.push([command, queryCommandsLUT[query][command]]); 57 | } 58 | // sort by most used 59 | commands.sort(function (a, b) { 60 | return b[1] - a[1]; 61 | }); 62 | latches[query] = commands[0][0]; 63 | } 64 | } catch (e) { 65 | file.rename(file.name + ".bak"); 66 | this.reveal(); 67 | Error.runtimeError(1, localize(strings.history_file_loading_error)); 68 | } 69 | } 70 | }; 71 | userHistory.clear = function () { 72 | var file = this.file(); 73 | logger.log("clearing user history"); 74 | file.remove(); 75 | }; 76 | userHistory.save = function () { 77 | var file = this.file(); 78 | logger.log("writing user history"); 79 | if (history.length > 500) history = history.slice(-500); 80 | writeJSONData(history, file); 81 | }; 82 | userHistory.backup = function () { 83 | var backupFile = new File(this.file() + ".bak"); 84 | logger.log("user history backed up to:", backupFile.fsName); 85 | this.file().copy(backupFile); 86 | }; 87 | userHistory.reveal = function () { 88 | var folder = this.folder(); 89 | logger.log("revealing history file"); 90 | folder.execute(); 91 | }; 92 | -------------------------------------------------------------------------------- /src/include/user/preferences.jsxinc: -------------------------------------------------------------------------------- 1 | //USER PREFERENCES 2 | 3 | // keeping around for alerting users of breaking changes 4 | var settingsFolderName = "JBD"; 5 | var settingsFolder = setupFolderObject(Folder.userData + "/" + settingsFolderName); 6 | var settingsFileName = "AiCommandPaletteSettings.json"; 7 | 8 | // new v0.10.0 preferences 9 | var userPrefsFolderName = "JBD"; 10 | var userPrefsFolder = setupFolderObject(Folder.userData + "/JBD/AiCommandPalette"); 11 | var userPrefsFileName = "Preferences.json"; 12 | 13 | // setup the base prefs model 14 | var prefs = {}; 15 | prefs.startupCommands = null; 16 | prefs.hiddenCommands = []; 17 | prefs.workflows = []; 18 | prefs.customCommands = []; 19 | prefs.bookmarks = []; 20 | prefs.scripts = []; 21 | prefs.pickers = []; 22 | prefs.fuzzy = true; // set to new fuzzy matcher as default 23 | prefs.latches = {}; 24 | prefs.version = _version; 25 | prefs.os = os; 26 | prefs.locale = locale; 27 | prefs.aiVersion = aiVersion; 28 | prefs.timestamp = Date.now(); 29 | 30 | var userPrefs = {}; 31 | userPrefs.folder = function () { 32 | return userPrefsFolder; 33 | }; 34 | userPrefs.file = function () { 35 | var folder = this.folder(); 36 | var file = setupFileObject(folder, userPrefsFileName); 37 | return file; 38 | }; 39 | userPrefs.load = function (inject) { 40 | var file = this.file(); 41 | logger.log("loading user preferences:", file.fsName); 42 | 43 | // if the prefs files doesn't exist, check for old 'settings' file 44 | if (!file.exists) { 45 | logger.log("no user prefs files found, checking for old 'settings' file"); 46 | oldFile = setupFileObject(settingsFolder, settingsFileName); 47 | 48 | // no need to continue if no old 'settings' file is present 49 | if (!oldFile.exists) return; 50 | 51 | alert(localize(strings.pref_file_non_compatible)); 52 | var backupFile = new File(oldFile + ".bak"); 53 | logger.log("backing up old `settings` file to: ", backupFile.fsName); 54 | oldFile.copy(backupFile); 55 | 56 | try { 57 | updateOldPreferences(oldFile); 58 | } catch (e) { 59 | alert(localize(strings.pref_file_loading_error) + "\n\n" + e); 60 | settingsFolder.execute(); 61 | return; 62 | } 63 | alert(localize(strings.pref_update_complete)); 64 | } 65 | 66 | if (file.exists) { 67 | var loadedData, prop, propsToSkip; 68 | try { 69 | loadedData = readJSONData(file); 70 | if (loadedData == {}) { 71 | return; 72 | } 73 | 74 | // alert user if locale or os of current machine doesn't match loaded prefs 75 | // TODO: break when OS is updated, check for better machine identifier 76 | // if (locale != loadedData.locale || os != loadedData.os) 77 | // alert(localize(strings.user_prefs_inconsistency)); 78 | 79 | propsToSkip = ["version", "os", "locale", "aiVersion", "timestamp"]; 80 | for (prop in loadedData) { 81 | if (propsToSkip.includes(prop)) continue; 82 | prefs[prop] = loadedData[prop]; 83 | } 84 | 85 | if (inject) { 86 | this.inject(); 87 | } 88 | } catch (e) { 89 | file.rename(file.name + ".bak"); 90 | logger.log("error loading user prefs", e); 91 | logger.log("renaming prefs file:", file.fsName); 92 | this.reveal(); 93 | Error.runtimeError(1, localize(strings.pref_file_loading_error, e)); 94 | } 95 | } 96 | }; 97 | userPrefs.inject = function () { 98 | var typesToInject = [ 99 | "workflows", 100 | "bookmarks", 101 | "scripts", 102 | "pickers", 103 | "customCommands", 104 | ]; 105 | for (var i = 0; i < typesToInject.length; i++) { 106 | for (var j = 0; j < prefs[typesToInject[i]].length; j++) { 107 | commandsData[prefs[typesToInject[i]][j].id] = prefs[typesToInject[i]][j]; 108 | } 109 | } 110 | }; 111 | userPrefs.save = function () { 112 | var file = this.file(); 113 | logger.log("writing user prefs"); 114 | writeJSONData(prefs, file); 115 | }; 116 | userPrefs.backup = function () { 117 | var backupFile = new File(this.file() + ".bak"); 118 | logger.log("user prefs backed up tp:", backupFile.fsName); 119 | this.file().copy(backupFile); 120 | }; 121 | userPrefs.reveal = function () { 122 | var folder = this.folder(); 123 | logger.log("revealing user prefs"); 124 | folder.execute(); 125 | }; 126 | 127 | function updateOldPreferences(oldFile) { 128 | logger.log("converting old 'settings' file to new user prefs file"); 129 | 130 | // read old data 131 | var data = readJSONData(oldFile); 132 | 133 | // no need to continue if we don't know the old version 134 | if (!data.settings.hasOwnProperty("version")) return; 135 | 136 | if (semanticVersionComparison(data.settings.version, "0.8.1") == -1) { 137 | // build lut to convert old localized command strings to new command ids 138 | var commandsLUT = {}; 139 | for (var command in commandsData) { 140 | commandsLUT[localize(commandsData[command].name)] = command; 141 | } 142 | 143 | // update bookmarks 144 | updatedBookmarks = {}; 145 | for (var bookmark in data.commands.bookmark) { 146 | updatedBookmarks[data.commands.bookmark[bookmark].name] = { 147 | type: "bookmark", 148 | path: data.commands.bookmark[bookmark].path, 149 | bookmarkType: data.commands.bookmark[bookmark].bookmarkType, 150 | }; 151 | } 152 | data.commands.bookmark = updatedBookmarks; 153 | 154 | // update scripts 155 | updatedScripts = {}; 156 | for (var script in data.commands.script) { 157 | updatedScripts[data.commands.script[script].name] = { 158 | type: "script", 159 | path: data.commands.script[script].path, 160 | }; 161 | } 162 | data.commands.script = updatedScripts; 163 | 164 | // update workflows 165 | updatedWorkflows = {}; 166 | updatedActions = []; 167 | for (var workflow in data.commands.workflow) { 168 | var cur, updatedAction; 169 | for (var i = 0; i < data.commands.workflow[workflow].actions.length; i++) { 170 | cur = data.commands.workflow[workflow].actions[i]; 171 | // if the action can't be found in the LUT, just leave it as user will be prompted when they attempt to run it 172 | if (!commandsLUT.hasOwnProperty(cur)) { 173 | updatedAction = cur; 174 | } else { 175 | updatedAction = commandsLUT[cur]; 176 | } 177 | updatedActions.push(updatedAction); 178 | } 179 | updatedWorkflows[data.commands.workflow[workflow].name] = { 180 | type: "workflow", 181 | actions: updatedActions, 182 | }; 183 | } 184 | data.commands.workflow = updatedWorkflows; 185 | 186 | // update hidden commands 187 | updatedHiddenCommands = []; 188 | for (var i = 0; i < data.settings.hidden.length; i++) { 189 | if (commandsLUT.hasOwnProperty(data.settings.hidden[i])) { 190 | updatedHiddenCommands.push(commandsLUT[data.settings.hidden[i]]); 191 | } 192 | } 193 | data.settings.hidden = updatedHiddenCommands; 194 | 195 | // update recent commands 196 | updatedRecentCommands = []; 197 | for (var i = 0; i < data.recent.commands.length; i++) { 198 | if (commandsLUT.hasOwnProperty(data.recent.commands[i])) { 199 | updatedRecentCommands.push(commandsLUT[data.recent.commands[i]]); 200 | } 201 | } 202 | data.recent.commands = updatedRecentCommands; 203 | 204 | // update version number so subsequent updates can be applied 205 | data.settings.version = "0.8.1"; 206 | } 207 | 208 | if (semanticVersionComparison(data.settings.version, "0.10.0") == -1) { 209 | var startupCommands = []; 210 | 211 | // update bookmarks 212 | var bookmarks = []; 213 | var f, bookmark; 214 | for (var prop in data.commands.bookmark) { 215 | f = new File(data.commands.bookmark[prop].path); 216 | if (!f.exists) continue; 217 | bookmarkName = decodeURI(f.name); 218 | bookmark = { 219 | id: prop, 220 | name: bookmarkName, 221 | action: "bookmark", 222 | type: data.commands.bookmark[prop].bookmarkType, 223 | path: f.fsName, 224 | docRequired: false, 225 | selRequired: false, 226 | hidden: false, 227 | }; 228 | bookmarks.push(bookmark); 229 | startupCommands.push(prop); 230 | } 231 | prefs.bookmarks = bookmarks; 232 | 233 | // update scripts 234 | var scripts = []; 235 | var f, script; 236 | for (var prop in data.commands.script) { 237 | f = new File(data.commands.script[prop].path); 238 | if (!f.exists) continue; 239 | scriptName = decodeURI(f.name); 240 | script = { 241 | id: prop, 242 | name: scriptName, 243 | action: "script", 244 | type: "script", 245 | path: f.fsName, 246 | docRequired: false, 247 | selRequired: false, 248 | hidden: false, 249 | }; 250 | scripts.push(script); 251 | startupCommands.push(prop); 252 | } 253 | prefs.scripts = scripts; 254 | 255 | // update workflows 256 | var workflows = []; 257 | var workflow, actions, action; 258 | for (var prop in data.commands.workflow) { 259 | // make sure actions are using the new command id format 260 | actions = []; 261 | for (var i = 0; i < data.commands.workflow[prop].actions.length; i++) { 262 | action = data.commands.workflow[prop].actions[i]; 263 | if ( 264 | !commandsData.hasOwnProperty(action) && 265 | oldCommandIdsLUT.hasOwnProperty(action) 266 | ) 267 | action = oldCommandIdsLUT[action]; 268 | actions.push(action); 269 | } 270 | 271 | var workflow = { 272 | id: prop, 273 | name: prop, 274 | actions: actions, 275 | type: "workflow", 276 | docRequired: false, 277 | selRequired: false, 278 | hidden: false, 279 | }; 280 | workflows.push(workflow); 281 | startupCommands.push(prop); 282 | } 283 | prefs.workflows = workflows; 284 | 285 | // add the base startup commands 286 | startupCommands = startupCommands.concat([ 287 | "builtin_recentCommands", 288 | "config_settings", 289 | ]); 290 | prefs.startupCommands = startupCommands; 291 | 292 | // update hidden commands 293 | var hiddenCommands = data.settings.hidden; 294 | prefs.hiddenCommands = hiddenCommands; 295 | 296 | userPrefs.save(); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/include/user/user.jsxinc: -------------------------------------------------------------------------------- 1 | //@include "preferences.jsxinc" 2 | //@include "history.jsxinc" 3 | //@include "actions.jsxinc" 4 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | Ai Command Palette 3 | Copyright 2024 Josh Duncan 4 | https://joshbduncan.com 5 | 6 | This script is distributed under the MIT License. 7 | See the LICENSE file for details. 8 | */ 9 | 10 | (function () { 11 | //@target illustrator 12 | 13 | // SCRIPT INFORMATION 14 | 15 | var _title = "Ai Command Palette"; 16 | var _version = "0.13.2"; 17 | var _copyright = "Copyright 2024 Josh Duncan"; 18 | var _website = "joshbduncan.com"; 19 | var _github = "https://github.com/joshbduncan"; 20 | 21 | //@includepath "include" 22 | //@includepath "include/commands" 23 | //@includepath "include/data" 24 | //@includepath "include/palettes" 25 | //@includepath "include/user" 26 | 27 | //@include "polyfills.jsxinc" 28 | //@include "helpers.jsxinc" 29 | //@include "Logger.jsxinc" 30 | //@include "io.jsxinc" 31 | //@include "built_strings.jsxinc" 32 | //@include "built_commands.jsxinc" 33 | //@include "config.jsxinc" 34 | 35 | //@include "user.jsxinc" 36 | //@include "palettes.jsxinc" 37 | //@include "commands.jsxinc" 38 | 39 | // load the user data 40 | userPrefs.load(true); 41 | userActions.load(); 42 | userHistory.load(); 43 | 44 | // set command palette matching algo 45 | var matcher = prefs["fuzzy"] ? fuzzy : scoreMatches; 46 | 47 | // add basic defaults to the startup on a first-run/fresh install 48 | if (!prefs.startupCommands) { 49 | prefs.startupCommands = ["builtin_recentCommands", "config_settings"]; 50 | } 51 | 52 | // SHOW THE COMMAND PALETTE 53 | var queryableCommands = filterCommands( 54 | (commands = null), 55 | (types = null), 56 | (showHidden = false), 57 | (showNonRelevant = false), 58 | (hideSpecificCommands = null) 59 | ); 60 | logger.log("queryable commands:", queryableCommands.length); 61 | 62 | var startupCommands = filterCommands( 63 | (commands = prefs.startupCommands), 64 | (types = null), 65 | (showHidden = false), 66 | (showNonRelevant = false), 67 | (hideSpecificCommands = null) 68 | ); 69 | logger.log("startup commands:", startupCommands.length); 70 | 71 | var result = commandPalette( 72 | (commands = queryableCommands), 73 | (title = localize(strings.title)), 74 | (columns = paletteSettings.columnSets.default), 75 | (multiselect = false), 76 | (showOnly = startupCommands), 77 | (saveHistory = true) 78 | ); 79 | if (!result) return; 80 | processCommand(result); 81 | })(); 82 | -------------------------------------------------------------------------------- /tests/manual_command_testing.jsx: -------------------------------------------------------------------------------- 1 | var win = new Window("dialog"); 2 | win.text = "Test Ai Menu Command"; 3 | win.orientation = "column"; 4 | win.alignChildren = ["center", "top"]; 5 | win.spacing = 10; 6 | win.margins = 16; 7 | 8 | var cmd = win.add("edittext"); 9 | cmd.preferredSize.width = 300; 10 | cmd.active = true; 11 | 12 | var winButtons = win.add("group"); 13 | winButtons.orientation = "row"; 14 | winButtons.alignChildren = ["center", "center"]; 15 | var cancel = winButtons.add("button", undefined, "Cancel"); 16 | cancel.preferredSize.width = 75; 17 | var menu = winButtons.add("button", undefined, "Menu"); 18 | menu.preferredSize.width = 75; 19 | var tool = winButtons.add("button", undefined, "Tool"); 20 | tool.preferredSize.width = 75; 21 | 22 | menu.onClick = function () { 23 | win.close(); 24 | try { 25 | app.executeMenuCommand(cmd.text); 26 | } catch (e) { 27 | alert(e); 28 | } 29 | }; 30 | 31 | tool.onClick = function () { 32 | win.close(); 33 | try { 34 | app.selectTool(cmd.text); 35 | } catch (e) { 36 | alert(e); 37 | } 38 | }; 39 | 40 | win.show(); 41 | -------------------------------------------------------------------------------- /tests/test_tool_commands.jsx: -------------------------------------------------------------------------------- 1 | // Test every tool command known to Ai Command Palette 2 | 3 | (function () { 4 | //@include "../src/include/polyfills.jsxinc" 5 | //@include "../src/include/helpers.jsxinc" 6 | //@include "../src/include/io.jsxinc" 7 | //@include "../src/include/palettes/palettes.jsxinc" 8 | //@include "../src/include/data.jsxinc" 9 | 10 | var f = new File(Folder.desktop + "/tool_test_results.txt"); 11 | 12 | var aiVersion = parseFloat(app.version); 13 | 14 | // make sure an active document is open 15 | if (app.documents.length == 0) app.documents.add(); 16 | 17 | var toolCommands = filterCommands( 18 | (commands = null), 19 | (types = ["tool"]), 20 | (showHidden = true), 21 | (showNonRelevant = true), 22 | (hideSpecificCommands = null) 23 | ); 24 | 25 | if ( 26 | !confirm( 27 | toolCommands.length + 28 | " tool commands found.\n\nWARNING: This script tends to crash Illustrator on subsequent runs!\n\nProceed with tests?", 29 | "noAsDflt", 30 | "Proceed with tests?" 31 | ) 32 | ) 33 | return; 34 | 35 | // setup counters and strings to hold results 36 | var skipped = 0; 37 | var passed = 0; 38 | var failed = 0; 39 | var s = "Ai Tool Command Tests\n\n"; 40 | var results = ""; 41 | 42 | var tool, toolData; 43 | for (var i = 0; i < toolCommands.length; i++) { 44 | tool = toolCommands[i]; 45 | toolData = commandsData[tool]; 46 | 47 | // skip tool if not version compatible 48 | if (!commandVersionCheck(toolData)) { 49 | skipped++; 50 | results += localize(toolData.name) + " (" + toolData.action + "): SKIPPED\n"; 51 | continue; 52 | } 53 | 54 | // try activating the tool 55 | try { 56 | app.selectTool(tool); 57 | passed++; 58 | results += localize(toolData.name) + " (" + toolData.action + "): PASSED\n"; 59 | } catch (e) { 60 | failed++; 61 | results += 62 | localize(toolData.name) + " (" + toolData.action + "): FAILED (" + e + ")\n"; 63 | } 64 | } 65 | 66 | // report 67 | s += "Total Tests: " + (passed + failed) + "\n"; 68 | s += "Passed: " + passed + "\n"; 69 | s += "Failed: " + failed + "\n"; 70 | s += "Skipped (version): " + skipped + "\n\n"; 71 | s += results + "\n\n"; 72 | s += "File Created: " + new Date(); 73 | 74 | writeData(s, f); 75 | f.execute(); 76 | })(); 77 | -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | # Ai Command Palette - Build Tools 2 | 3 | ## ExtendScript Compiler 4 | 5 | To keep development easier to handle I split the project into multiple files/modules which you will find in the `src` directory. This works great for me but is a pain for users to install. 6 | 7 | So, to make script installation as easy as possible I use a little utility I wrote called [ExtendScript Compiler](https://github.com/joshbduncan/extendscript-compiler) to get everything compiled into a single readable '.jsx' script file. 8 | 9 | ```bash 10 | ./escompile.sh src/script.jsx > compiledScript.jsx 11 | ``` 12 | 13 | ## Build Commands (build_commands.py) 14 | 15 | There are almost 500 menu commands, 80 tools, and a handful of custom configuration commands available in Ai Command Palette and since they get updated often, [this script](/tools/build_commands.py) helps me build/rebuild the objects used in the script from the [command data csv files](/data/). 16 | 17 | ```bash 18 | $ python3 tools/build_commands.py 19 | ``` 20 | 21 | > [!NOTE] 22 | > Please Note: The script **ONLY WORKS** with specifically formatted csv files. 23 | 24 | All commands are built into a single JavaScript objects like below. 25 | 26 | ```javascript 27 | // generated localized commands data object 28 | { 29 | tool: { 30 | "tool_Adobe Add Anchor Point Tool": { 31 | action: "Adobe Add Anchor Point Tool", 32 | type: "tool", 33 | minVersion: 24, 34 | loc: { 35 | en: "Add Anchor Point Tool", 36 | de: "Ankerpunkt-hinzufügen-Werkzeug", 37 | ru: "Добавить опорную точку Инструмент", 38 | }, 39 | }, 40 | }, 41 | // ... 42 | } 43 | ``` 44 | 45 | ## Build Strings (build_strings.py) 46 | 47 | With the help of some contributors, many of the string values used in Ai Command Palette have been localized. This script builds an object ExtendScript can use to localize the script UI for the user. 48 | 49 | ```bash 50 | $ python3 tools/build_strings.py 51 | ``` 52 | 53 | > [!NOTE] 54 | > Please Note: The script **ONLY WORKS** with specifically formatted csv file. 55 | 56 | ```javascript 57 | // generated localized strings data object 58 | var locStrings = { 59 | about: { en: "About", de: "Über Kurzbefehle …", ru: "О скрипте" }, 60 | } 61 | ``` -------------------------------------------------------------------------------- /tools/build_commands.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import json 3 | import re 4 | import sys 5 | from pathlib import Path 6 | 7 | SCRIPT_DIR = Path(__file__).parent 8 | 9 | 10 | def convert_to_num(n) -> int | float: 11 | try: 12 | return int(n) 13 | except ValueError: 14 | return float(n) 15 | 16 | 17 | def read_csv_data(fp: Path) -> list[dict]: 18 | """Safely read CSV data from a file using `csv.DictReader`. 19 | 20 | Args: 21 | fp: CSV data file path. 22 | 23 | Returns: 24 | List of CSV rows as a dictionaries. 25 | """ 26 | with open(fp, newline="", encoding="utf-8") as f: 27 | return list(csv.DictReader(f)) 28 | 29 | 30 | def build_commands(rows: list[dict]) -> dict[dict[str, str]]: 31 | """Build a dictionary of command objects from CSV data in the following format. 32 | 33 | ``` 34 | menu_new: { 35 | id: "menu_new", 36 | action: "new", 37 | type: "menu", 38 | docRequired: false, 39 | selRequired: false, 40 | name: { 41 | en: "File > New...", 42 | de: "Datei > Neu \u2026", 43 | ru: "\u0424\u0430\u0439\u043b > \u041d\u043e\u0432\u044b\u0439...", 44 | "zh-cn": "\u6587\u4ef6>\u65b0\u5efa\u2026", 45 | }, 46 | hidden: false, 47 | } 48 | ``` 49 | 50 | Args: 51 | rows: List of rows from a CSV file (as returned by `csv.DictReader`). 52 | 53 | Returns: 54 | Dictionary of string objects. 55 | """ 56 | 57 | commands = {} 58 | 59 | # regex for cleaning up command ids 60 | regex = re.compile(r"\s|\.") 61 | 62 | for row in rows: 63 | value = row.pop("value", None) 64 | if value is None: 65 | continue 66 | 67 | ignore = row.pop("ignore", "False").lower() == "true" 68 | if ignore: 69 | continue 70 | 71 | command_type = row.pop("type", None) 72 | if command_type is None: 73 | continue 74 | 75 | # extract all other non localization values 76 | doc_required = row.pop("docRequired", "False").lower() == "true" 77 | sel_required = row.pop("selRequired", "False").lower() == "true" 78 | min_version = row.pop("minVersion", None) 79 | max_version = row.pop("maxVersion", None) 80 | notes = row.pop("notes", None) 81 | 82 | # get default english string for incomplete localization 83 | default_value = row.get("en", None) 84 | if default_value is None: 85 | continue 86 | 87 | # cleanup and build final command id 88 | stripped_value = value.replace(".", "", -1) 89 | id = regex.sub("_", f"{command_type}_{stripped_value}") 90 | 91 | # set localized values 92 | localized_strings = {k: v or default_value for k, v in row.items()} 93 | 94 | # build final command object 95 | command = { 96 | "id": id, 97 | "action": value, 98 | "type": command_type, 99 | "docRequired": doc_required, 100 | "selRequired": sel_required, 101 | "name": localized_strings, 102 | "hidden": False, 103 | } 104 | 105 | # only add min and max version if present 106 | if min_version: 107 | command["minVersion"] = convert_to_num(min_version) 108 | if max_version: 109 | command["maxVersion"] = convert_to_num(max_version) 110 | 111 | commands[id] = command 112 | 113 | return commands 114 | 115 | 116 | def main() -> int: 117 | csv_files = [ 118 | Path(SCRIPT_DIR / "../data/menu_commands.csv"), 119 | Path(SCRIPT_DIR / "../data/tool_commands.csv"), 120 | Path(SCRIPT_DIR / "../data/builtin_commands.csv"), 121 | Path(SCRIPT_DIR / "../data/config_commands.csv"), 122 | ] 123 | 124 | all_commands = {} 125 | 126 | # read and parse csv data 127 | for fp in csv_files: 128 | rows = read_csv_data(fp) 129 | commands = build_commands(rows) 130 | assert commands 131 | 132 | all_commands = all_commands | commands 133 | assert all_commands 134 | 135 | output = f"""// GENERATED FROM CSV DATA FILES 136 | 137 | var commandsData = {json.dumps(all_commands)}""" 138 | 139 | print(output.replace("\\\\n", "\\n")) 140 | 141 | return 0 142 | 143 | 144 | if __name__ == "__main__": 145 | sys.exit(main()) 146 | -------------------------------------------------------------------------------- /tools/build_strings.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import json 3 | import sys 4 | from pathlib import Path 5 | 6 | SCRIPT_DIR = Path(__file__).parent 7 | 8 | 9 | def read_csv_data(fp: Path) -> list[dict]: 10 | """Safely read CSV data from a file using `csv.DictReader`. 11 | 12 | Args: 13 | fp: CSV data file path. 14 | 15 | Returns: 16 | List of CSV rows as a dictionaries. 17 | """ 18 | with open(fp, newline="", encoding="utf-8") as f: 19 | return list(csv.DictReader(f)) 20 | 21 | 22 | def build_strings(rows: list[dict]) -> dict[dict[str, str]]: 23 | """Build a dictionary of string objects from CSV data in the following format. 24 | 25 | ``` 26 | about: { 27 | en: "About", 28 | de: "\u00dcber Kurzbefehle \u2026", 29 | ru: "\u041e \u0441\u043a\u0440\u0438\u043f\u0442\u0435", 30 | "zh-cn": "About", 31 | } 32 | ``` 33 | 34 | Args: 35 | rows: List of rows from a CSV file (as returned by `csv.DictReader`). 36 | 37 | Returns: 38 | Dictionary of string objects. 39 | """ 40 | 41 | strings = {} 42 | 43 | for row in rows: 44 | id = row.pop("value", None) 45 | if id is None: 46 | continue 47 | 48 | notes = row.pop("notes", None) 49 | 50 | # get default english string for incomplete localization 51 | default_value = row.get("en", None) 52 | if default_value is None: 53 | continue 54 | 55 | # set localized values 56 | localized_strings = {k: v or default_value for k, v in row.items()} 57 | 58 | strings[id] = localized_strings 59 | 60 | return strings 61 | 62 | 63 | def main() -> int: 64 | # read and parse csv data 65 | fp = Path(SCRIPT_DIR / "../data/strings.csv") 66 | rows = read_csv_data(fp) 67 | strings = build_strings(rows) 68 | assert strings 69 | 70 | output = f"""// GENERATED FROM CSV DATA FILES 71 | 72 | var strings = {json.dumps(strings)}""" 73 | 74 | print(output.replace("\\\\n", "\\n")) 75 | 76 | return 0 77 | 78 | 79 | if __name__ == "__main__": 80 | sys.exit(main()) 81 | --------------------------------------------------------------------------------