├── .gitignore ├── LICENSE.md ├── README.md ├── Style Inventory.sketchplugin └── Contents │ └── Sketch │ ├── Inventory │ ├── Generate.js │ ├── Import.js │ ├── Name Color Swatch.js │ └── pattern.png │ ├── Selection │ ├── Select Layers by Size.sketchplugin │ ├── by Color │ │ ├── Choose.sketchplugin │ │ ├── Select Layers by Color on Artboard.js │ │ └── Select Layers by Color on Page.js │ ├── by Name │ │ ├── Select Layers by Name on Artboard.js │ │ └── Select Layers by Name on Page.js │ ├── by String │ │ ├── Replace Strings on Artboard.js │ │ └── Replace Strings on Page.js │ └── by Text Style │ │ ├── Select Layers by Text Style on Artboard.js │ │ └── Select Layers by Text Style on Page.js │ ├── inventory.js │ ├── manifest.json │ ├── persistence.js │ └── sandbox.js ├── appcast.xml └── sketch-style-inventory-runner.png /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ***** 2 | **⚠️ 19 August 2019:** This plugin is no longer maintained and does not work with Sketch 50+ 3 | 4 | I’m working on a [Ratio](https://useratio.com), web design tool that will make some of the commands and concepts of this plugin a core principle. Instead of relying on a plugin to stay organised and consistent, Ratio will support and guide you. 5 | 6 | This was one of my most complex plugins and maintaining it required in-depth understanding of the internals of Sketch that constantly changed over the years. 7 | 8 | I know that many of you wish that this plugin would still work, but I can no longer commit time to it. 9 | 10 | Thank you for using the plugin, providing feedback and reporting issues. Feel free to fork it, fix it, and share it. 11 | 12 | I think the problems adddressed by the plugin are better solved by a design tool that treats design systems as a first class citizen. 13 | 14 | If you liked what this plugin did for you in Sketch, stay tuned for Ratio. 15 | https://useratio.com 16 | 17 | 18 | ***** 19 | **14 April 2018:** The plugin has been updated to work with Sketch 49. 20 | 21 | If you are using the plugin and want to provide feedback, please comment on [Q&A: How and why do you use this plugin?](https://github.com/getflourish/Sketch-Style-Inventory/issues/94) 22 | 23 | ***** 24 | 25 | # Style Inventory for Sketch 26 | 27 | Design requires free, sometimes chaotic exploration. But design also means organization and structure. Sketch can be good in both aspects, but moving from exploration to structured layouts with text styles and unified colors is hard. Either you start clean files from scratch, or you use what you have and try to tidy it up. The Style Inventory is meant to help you with that. It gives you an overview of all your used styles and helps you to merge styles of similar layers into one. 28 | 29 | ![Generate dialog](http://f.cl.ly/items/3944230o3a0V1u2u463t/export%20metadata.gif) 30 | 31 | ## Plugin Directory 32 | 33 | #### Inventory 34 | * Generate `ctrl` + `⌘` + `⌥` + `I` 35 | * Import Colors 36 | * Name Color Swatch 37 | 38 | #### Selection 39 | * by Color/Select Layers by Color `shift` + `ctrl` + `⌘` + `C` 40 | * by Color/Select Layers by Color on Artboard `ctrl` + `⌘` + `C` 41 | * by Name/Select Layers by Name `shift` + `ctrl` + `⌘` + `N` 42 | * by Name/Select Layers by Name on Artboard `ctrl` + `⌘` + `N` 43 | * by String/Replace String `shift` + `⌘` + `K` 44 | * by Text Style/Select Similar Text Layers `shift` + `ctrl` + `⌘` + `T` 45 | * by Text Style/Select Similar Text Layers on Artboard `⌘` + `control` + `T` 46 | 47 | 48 | ## Installation 49 | 50 | To install all plugins, [download](https://github.com/getflourish/Sketch-Style-Inventory/archive/master.zip) and double click. 51 | 52 | ## Install with Sketch Runner 53 | With Sketch Runner, just go to the `install` command and search for `Sketch Style Inventory`. Runner allows you to manage plugins and do much more to speed up your workflow in Sketch. [Download Runner here](http://www.sketchrunner.com). 54 | 55 | ![Install with Sketch Runner](sketch-style-inventory-runner.png) 56 | 57 | ## Keyboard Shortcuts 58 | 59 | You can change keyboard shortcuts using [Sketch Plugin Monster](https://github.com/PeachScript/sketch-plugin-monster), the native System Preferences, or by editing the `manifest.json` 60 | 61 | ## Style Inventory 62 | Generate a visual style sheet with all colors, text styles and symbols that you are using. This will help you to get an overview of your used styles so you can merge styles that are very similar. 63 | 64 | Choose what you want to generate in the configurator. The inventory will be generated on a new page called "Style Inventory". 65 | 66 | **Shortcut:** `ctrl` + `⌘` + `⌥` + `I` 67 | 68 | ![](https://dzwonsemrish7.cloudfront.net/items/3T0W380P081I1a1E422N/Bildschirmfoto%202018-04-14%20um%2012.12.59.png) 69 | 70 | 71 | ## Name Color Swatch 72 | If you want to organize your colors, you can name color swatches (groups) on the color inventory artboard. The plugin uses the following naming convention for palettes and colors: 73 | 74 | Colors as part of a palette: 75 | 76 | - `Brand > Primary` 77 | - `Grays > 80` 78 | 79 | A single color, not part of a palette: 80 | 81 | - `Some Color` 82 | 83 | You can rename the generated color swatches, or use the plugin that will show a dialog where you can enter the color name. 84 | 85 | ![](https://dzwonsemrish7.cloudfront.net/items/1i2J142t2L1I3o250S09/Bildschirmfoto%202018-04-14%20um%2012.11.38.png) 86 | 87 | ### Export 88 | 89 | When you select this option in the generator, Sketch will export the Style Inventory. 90 | 91 | - Colors and palettes as JSON 92 | - Text Styles as CSS 93 | - Symbols as PNG 94 | 95 | ![](https://dzwonsemrish7.cloudfront.net/items/0d2R2j0Q3m0f2F3y1T1x/Bildschirmfoto%202018-04-14%20um%2012.09.32.png) 96 | 97 | ### Select Layers by Color 98 | 99 | Based on a selected layer, all layers that match the fill or text color will be selected. 100 | 101 | **Shortcut:** `ctrl` + `⌘` + `C` 102 | 103 | ### Select Layers by Text Style 104 | 105 | Based on a selected layer, all layers that match its text style will be selected. 106 | 107 | **Shortcut:** `ctrl` + `⌘` + `T` 108 | 109 | 110 | ### Select Layers by Name 111 | 112 | Based on a selected layer, all layers that match the name of the reference layer will be selected. This will also include layers that have appended numbers from duplication (e.g. Rectangle 1, Rectangle 2, …) 113 | 114 | **Shortcut:** `ctrl` + `⌘` + `N` 115 | 116 | ### Replace String 117 | 118 | Replaces all occurences of the text string found in the selected text layer 119 | 120 | **Shortcut:** `shift` + `⌘` + `K` 121 | 122 | 123 | ## Change Log 124 | 125 | **April 14, 2018** 126 | * Updated for Sketch 49 127 | * Added support for Sketch Plugin Updater 128 | 129 | **February 28, 2016** 130 | * Refactored to new plugin bundle format 131 | 132 | **April 26, 2015** 133 | * Added Symbols Inventory 134 | * Simplified export 135 | * Added configurator 136 | 137 | **April 14, 2015** 138 | * Updated plugins for Sketch 3.3+ 139 | 140 | **December 7, 2014** 141 | * Moved all unrelated plugins to a new repository called [Sketch Mate](https://github.com/getflourish/Sketch-Mate). It will take a few days to update the documentation for the new plugins. 142 | -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/Inventory/Generate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Style Inventory Generator 3 | * 4 | * This command shows a configuration dialog before running the selected inventories. 5 | * If the export checkbox is selected, the styles will be exported to a export folder near the Sketch file. 6 | */ 7 | 8 | @import '../inventory.js' 9 | @import '../sandbox.js' 10 | @import '../persistence.js' 11 | 12 | var colors; 13 | var textStyles; 14 | var symbols; 15 | var exportMetadata; 16 | var artboards; 17 | 18 | // turn the text styles panel on / off 19 | var showTextStylesPanel = false; 20 | 21 | var ca, ta, sa; 22 | 23 | 24 | function layout (artboards) { 25 | var x = 0; 26 | var artboard; 27 | for (var i = 0; i < artboards.length; i++) { 28 | artboard = artboards[i]; 29 | artboard.frame().setX(x); 30 | x += artboard.frame().width() + 100; 31 | } 32 | 33 | } 34 | 35 | function handleExport () { 36 | 37 | // let the user choose the export location 38 | // var fileURL = com.getflourish.common.fileSaver(); 39 | // path = fileURL.path(); 40 | 41 | var documentPath = [[doc fileURL] path].split([doc displayName] + ".sketch")[0]; 42 | 43 | // export path + "/" + filename 44 | // 45 | 46 | // get authorization to write to the export folder 47 | new AppSandbox().authorize(documentPath, function () { 48 | 49 | // Everything will be exported to a new folder in the .sketch file's parent folder 50 | var baseExportPath = documentPath + 'export/' + doc.displayName().split(".sketch") + '/'; 51 | createFolder(baseExportPath); 52 | 53 | var exportPath; 54 | 55 | 56 | if (colors == 1) { 57 | 58 | // set the export path 59 | exportPath = baseExportPath + "colors/"; 60 | 61 | // create the folder 62 | createFolder(exportPath); 63 | 64 | // export 65 | com.getflourish.colorInventory.export(exportPath); 66 | 67 | com.getflourish.doc.showMessage("colors done") 68 | 69 | } 70 | 71 | if (textStyles == 1) { 72 | 73 | com.getflourish.doc.showMessage("exporting text") 74 | 75 | // set the export path 76 | exportPath = baseExportPath + "typography/"; 77 | 78 | // create the folder 79 | createFolder(exportPath); 80 | 81 | // export 82 | com.getflourish.textStyleInventory.export(ta, exportPath, showTextStylesPanel) 83 | 84 | com.getflourish.doc.showMessage("exported text") 85 | } 86 | 87 | if (symbols == 1) { 88 | 89 | // set the export path 90 | exportPath = baseExportPath + "symbols/"; 91 | 92 | // create the folder 93 | createFolder(exportPath); 94 | 95 | // export 96 | com.getflourish.symbolInventory.export(exportPath); 97 | } 98 | 99 | com.getflourish.utils.openInFinder(baseExportPath); 100 | }); 101 | 102 | } 103 | 104 | function createFolder(name) { 105 | var fileManager = [NSFileManager defaultManager]; 106 | [fileManager createDirectoryAtPath:name withIntermediateDirectories:true attributes:nil error:nil]; 107 | } 108 | 109 | function showConfigurationDialog (states) { 110 | var accessoryView = NSView.alloc().initWithFrame(NSMakeRect(0.0, 0.0, 200.0, 90.0)) 111 | 112 | var buttonStates = states || [1, 1, 1, 0]; 113 | 114 | if (persist.get('com.getflourish.inventory.configuration') != null) { 115 | buttonStates = persist.get('com.getflourish.inventory.configuration'); 116 | } 117 | 118 | var buttonOne = NSButton.alloc().initWithFrame(NSMakeRect(0.0, 70.0, 200.0, 20.0)) 119 | buttonOne.setButtonType(NSSwitchButton) 120 | buttonOne.setTitle("Colors") 121 | buttonOne.setState(buttonStates[0]) 122 | buttonOne.setCOSJSTargetFunction(function(sender){ 123 | buttonStates[0] = buttonStates[0] == 0 ? 1 : 0 124 | }) 125 | accessoryView.addSubview(buttonOne) 126 | 127 | var buttonTwo = NSButton.alloc().initWithFrame(NSMakeRect(0.0, 50.0, 200.0, 20.0)) 128 | buttonTwo.setButtonType(NSSwitchButton) 129 | buttonTwo.setTitle("Text Styles") 130 | buttonTwo.setState(buttonStates[1]) 131 | buttonTwo.setCOSJSTargetFunction(function(sender){ 132 | buttonStates[1] = buttonStates[1] == 0 ? 1 : 0 133 | }) 134 | accessoryView.addSubview(buttonTwo) 135 | 136 | var buttonThree = NSButton.alloc().initWithFrame(NSMakeRect(0.0, 30.0, 200.0, 20.0)) 137 | buttonThree.setButtonType(NSSwitchButton) 138 | buttonThree.setTitle("Symbols") 139 | buttonThree.setState(buttonStates[2]) 140 | buttonThree.setCOSJSTargetFunction(function(sender){ 141 | buttonStates[2] = buttonStates[2] == 0 ? 1 : 0 142 | }) 143 | accessoryView.addSubview(buttonThree) 144 | 145 | var buttonFour = NSButton.alloc().initWithFrame(NSMakeRect(0.0, 0.0, 200.0, 20.0)) 146 | buttonFour.setButtonType(NSSwitchButton) 147 | buttonFour.setTitle("Export") 148 | buttonFour.setState(buttonStates[3]) 149 | buttonFour.setCOSJSTargetFunction(function(sender){ 150 | buttonStates[3] = buttonStates[3] == 0 ? 1 : 0 151 | }) 152 | accessoryView.addSubview(buttonFour) 153 | 154 | var alert = NSAlert.alloc().init() 155 | alert.messageText = "Style Inventory Configuration" 156 | alert.addButtonWithTitle("Generate") 157 | alert.addButtonWithTitle("Cancel") 158 | alert.setAccessoryView(accessoryView) 159 | 160 | var responseCode = alert.runModal() 161 | 162 | return [responseCode, buttonStates] 163 | } 164 | 165 | var onRun = function (context) { 166 | 167 | // old school variable 168 | doc = context.document; 169 | selection = context.selection; 170 | 171 | com.getflourish.common.init(context); 172 | 173 | com.getflourish.config.samePage = false; 174 | 175 | // if (colorInventory != null) rma = colorInventory; 176 | 177 | var states = persist.get('com.getflourish.inventory.configuration'); 178 | 179 | com.getflourish.common.getStyleSheetPage(); 180 | 181 | var configuration = showConfigurationDialog(states); 182 | 183 | if (configuration[0] == 1000) { 184 | states = configuration[1]; 185 | 186 | persist.set('com.getflourish.inventory.configuration', states) 187 | 188 | colors = states[0]; 189 | textStyles = states[1]; 190 | symbols = states[2]; 191 | exportMetadata = states[3]; 192 | artboards = []; 193 | 194 | if (colors == 1) { 195 | ca = com.getflourish.colorInventory.generate(); 196 | if (ca) artboards.push(ca); 197 | } 198 | if (textStyles == 1) { 199 | ta = com.getflourish.textStyleInventory.generate(showTextStylesPanel); 200 | artboards.push(ta); 201 | } 202 | if (symbols == 1) { 203 | sa = com.getflourish.symbolInventory.generate(); 204 | artboards.push(sa); 205 | } 206 | 207 | layout(artboards) 208 | 209 | var view = doc.documentWindow(); 210 | com.getflourish.utils.selectLayers(artboards); 211 | 212 | com.getflourish.view.zoomToLayers(artboards); 213 | // view.refresh(); 214 | 215 | doc.scheduleLayerListRefresh() 216 | doc.reloadInspector() 217 | doc.reloadView() 218 | 219 | com.getflourish.utils.selectLayers([]); 220 | com.getflourish.utils.sendAction("collapseGroupsInLayerList:"); 221 | 222 | if (exportMetadata == 1) handleExport(); 223 | 224 | doc.showMessage("Generated Style Inventory"); 225 | } 226 | 227 | } 228 | -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/Inventory/Import.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Define Swatches 3 | * 4 | * This command can be used to define a color swatch. After generating the Color Inventory, select a color swatch (layer group) and 5 | * run this command which will show a dialog where a name can be entered. 6 | */ 7 | 8 | @import '../inventory.js' 9 | 10 | 11 | var onRun = function (context) { 12 | 13 | // old school variable 14 | doc = context.document; 15 | selection = context.selection; 16 | 17 | com.getflourish.common.init(context); 18 | 19 | com.getflourish.colorInventory.import(); 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/Inventory/Name Color Swatch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Define Swatches 3 | * 4 | * This command can be used to define a color swatch. After generating the Color Inventory, select a color swatch (layer group) and 5 | * run this command which will show a dialog where a name can be entered. 6 | */ 7 | 8 | @import '../inventory.js' 9 | 10 | 11 | var onRun = function (context) { 12 | 13 | // old school variable 14 | doc = context.document; 15 | selection = context.selection; 16 | 17 | com.getflourish.common.init(context); 18 | 19 | // doc.deselectAllLayers(); 20 | 21 | // selection.clear(); 22 | 23 | var selected = selection; 24 | 25 | for (var i = 0; i < selected.count(); i++) { 26 | var layer = selected.objectAtIndex(i); 27 | layer.isSelected = true; 28 | // com.getflourish.view.centerTo(layer); 29 | 30 | var name = [doc askForUserInput:"Color name: (e.g. Primary > Blue)" initialValue:""] 31 | if(name != "") { 32 | layer.setName(name); 33 | var children = layer.children(); 34 | for (var j = 0; j < children.count(); j++) { 35 | if(children[j].name() == "Swatch Name") { 36 | var label = children[j]; 37 | label.setStringValue(name); 38 | label.adjustFrameToFit(); 39 | label.setTextAlignment(0); 40 | } 41 | } 42 | } else { 43 | break; 44 | } 45 | layer.isSelected = false 46 | } 47 | 48 | // com.getflourish.view.centerTo(com.getflourish.doc.currentPage().currentArtboard()); 49 | 50 | com.getflourish.colorInventory.generate(); 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/Inventory/pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getflourish/Sketch-Style-Inventory/6e439741a910b67fec8449a89211b72892a54a96/Style Inventory.sketchplugin/Contents/Sketch/Inventory/pattern.png -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/Selection/Select Layers by Size.sketchplugin: -------------------------------------------------------------------------------- 1 | // (shift control cmd n) 2 | 3 | /** 4 | * This plugin selects similar name layers 5 | * Scope: current page 6 | * 7 | * Florian Schulz Copyright 2014, MIT License 8 | */ 9 | 10 | #import 'inventory.js' 11 | 12 | if (selection.count() == 1) { 13 | 14 | var layer = selection[0]; 15 | var scope = doc.currentPage().currentArtboard().children(); 16 | var result = com.getflourish.layers.selectLayersBySize(layer.frame().width(), layer.frame().height(), scope); 17 | 18 | doc.showMessage("Selected " + result.count()); 19 | } else { 20 | doc.showMessage("Please select a reference layer."); 21 | } 22 | -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/Selection/by Color/Choose.sketchplugin: -------------------------------------------------------------------------------- 1 | // 2 | 3 | /** 4 | * This plugin selects all same-colored layers based on the selected layer. 5 | * Scope: current artboard 6 | * 7 | * Florian Schulz Copyright 2014, MIT License 8 | */ 9 | 10 | #import 'inventory.js' 11 | 12 | if (selection.count() == 1) { 13 | var referenceLayer = selection[0]; 14 | var scope = doc.currentPage().children(); 15 | 16 | var result = com.getflourish.utils.arrayFromImmutableArray(com.getflourish.layers.selectLayersByColor(referenceLayer, scope)); 17 | 18 | doc.currentPage().deselectAllLayers(); 19 | // populate options 20 | var options = []; 21 | 22 | for(var i = 0; i < result.length; i++) { 23 | options.push(result[i].name()) 24 | } 25 | 26 | 27 | // create the dialog with drop down 28 | var choice = com.getflourish.common.createSelect('Color Inventory', options, 0) 29 | 30 | // get the choice 31 | var index = choice[1]; 32 | result[index].setIsSelected(true); 33 | 34 | com.getflourish.view.centerTo(result[index]); 35 | } else { 36 | doc.showMessage("Please select a reference layer."); 37 | } -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/Selection/by Color/Select Layers by Color on Artboard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This plugin selects all same-colored layers based on the selected layer. 3 | * Scope: current artboard 4 | * 5 | * Florian Schulz Copyright 2014, MIT License 6 | */ 7 | 8 | @import '../../inventory.js' 9 | 10 | var onRun = function (context) { 11 | 12 | // old school variable 13 | doc = context.document; 14 | selection = context.selection; 15 | 16 | if (selection.count() == 1) { 17 | var referenceLayer = selection[0]; 18 | var scope = doc.currentPage().currentArtboard().children(); 19 | var result = com.getflourish.layers.selectLayersByColor(referenceLayer, scope); 20 | 21 | // display message 22 | doc.showMessage(result.count() + " layers of " + scope.count() + " selected. "); 23 | } else { 24 | doc.showMessage("Please select a reference layer."); 25 | } 26 | } -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/Selection/by Color/Select Layers by Color on Page.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This plugin selects all same-colored layers based on the selected layer. 3 | * Scope: current page 4 | * 5 | * Florian Schulz Copyright 2014, MIT License 6 | */ 7 | 8 | @import '../../inventory.js' 9 | 10 | var onRun = function (context) { 11 | 12 | // old school variable 13 | doc = context.document; 14 | selection = context.selection; 15 | 16 | if (selection.count() == 1) { 17 | var referenceLayer = selection[0]; 18 | var scope = doc.currentPage().children(); 19 | var result = com.getflourish.layers.selectLayersByColor(referenceLayer, scope); 20 | 21 | // display message 22 | doc.showMessage(result.count() + " layers of " + scope.count() + " selected."); 23 | } else { 24 | doc.showMessage("Please select a reference layer."); 25 | } 26 | } -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/Selection/by Name/Select Layers by Name on Artboard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This plugin selects all layers with the same layer name based on the selected layer. 3 | * Scope: current artboard 4 | * 5 | * Florian Schulz Copyright 2014, MIT License 6 | */ 7 | 8 | @import '../../inventory.js' 9 | 10 | var onRun = function (context) { 11 | 12 | // old school variable 13 | doc = context.document; 14 | selection = context.selection; 15 | 16 | if (selection.count() == 1) { 17 | 18 | var layer = selection[0]; 19 | var referenceName = layer.name(); 20 | var scope = doc.currentPage().currentArtboard().children(); 21 | var result = com.getflourish.layers.selectLayersByName(referenceName, scope); 22 | 23 | doc.showMessage(result.count() + " layers of " + scope.count() + " selected. " + referenceName); 24 | 25 | } else { 26 | doc.showMessage("Please select a reference layer."); 27 | } 28 | } -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/Selection/by Name/Select Layers by Name on Page.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This plugin selects all layers with the same layer name based on the selected layer. 3 | * Scope: current page 4 | * 5 | * Florian Schulz Copyright 2014, MIT License 6 | */ 7 | 8 | @import '../../inventory.js' 9 | 10 | var onRun = function (context) { 11 | 12 | // old school variable 13 | doc = context.document; 14 | selection = context.selection; 15 | 16 | if (selection.count() == 1) { 17 | 18 | var layer = selection[0]; 19 | var referenceName = layer.name(); 20 | var scope = doc.currentPage().children(); 21 | var result = com.getflourish.layers.selectLayersByName(referenceName, scope); 22 | 23 | doc.showMessage(result.count() + " layers of " + scope.count() + " selected. " + referenceName); 24 | 25 | } else { 26 | doc.showMessage("Please select a reference layer."); 27 | } 28 | } -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/Selection/by String/Replace Strings on Artboard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This plugin replaces all strings based on the selected text layer 3 | * Scope: current artboard 4 | * 5 | * Florian Schulz Copyright 2014, MIT License 6 | */ 7 | 8 | @import '../../inventory.js' 9 | 10 | var onRun = function (context) { 11 | 12 | // old school variable 13 | doc = context.document; 14 | selection = context.selection; 15 | 16 | if (selection.count() == 1) { 17 | 18 | var layer = selection[0]; 19 | var scope = doc.currentPage().currentArtboard().children(); 20 | 21 | var referenceString = layer.stringValue(); 22 | 23 | // Prompt user for input of new button text 24 | var newString = [doc askForUserInput:"Search string" initialValue:[layer stringValue]]; 25 | 26 | var result = com.getflourish.layers.selectLayersByString(referenceString, scope); 27 | 28 | doc.showMessage("Selected " + result.count() + " layers."); 29 | 30 | for (var i = 0; i < result.count(); i++) { 31 | layer.setTextBehaviour(0) 32 | layer.adjustFrameToFit(); 33 | 34 | // set the new value 35 | result.objectAtIndex(i).setStringValue(newString); 36 | 37 | // refresh text layout 38 | layer.setTextBehaviour(0) 39 | layer.adjustFrameToFit(); 40 | } 41 | 42 | doc.reloadInspector(); 43 | 44 | // refresh view 45 | var view = [doc currentView]; 46 | view.refresh(); 47 | 48 | } else { 49 | doc.showMessage("Please select a reference layer."); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/Selection/by String/Replace Strings on Page.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This plugin replaces all strings based on the selected text layer 3 | * Scope: current page 4 | * 5 | * Florian Schulz Copyright 2014, MIT License 6 | */ 7 | 8 | @import '../../inventory.js' 9 | 10 | var onRun = function (context) { 11 | 12 | // old school variable 13 | doc = context.document; 14 | selection = context.selection; 15 | 16 | if (selection.count() == 1) { 17 | 18 | var layer = selection[0]; 19 | var scope = doc.currentPage().children(); 20 | 21 | var referenceString = layer.stringValue(); 22 | 23 | // Prompt user for input of new button text 24 | var newString = [doc askForUserInput:"Search string" initialValue:[layer stringValue]]; 25 | 26 | var result = com.getflourish.layers.selectLayersByString(referenceString, scope); 27 | 28 | doc.showMessage("Selected " + result.count() + " layers."); 29 | 30 | for (var i = 0; i < result.count(); i++) { 31 | layer.setTextBehaviour(0) 32 | layer.adjustFrameToFit(); 33 | 34 | // set the new value 35 | result.objectAtIndex(i).setStringValue(newString); 36 | 37 | // refresh text layout 38 | layer.setTextBehaviour(0) 39 | layer.adjustFrameToFit(); 40 | } 41 | 42 | doc.reloadInspector(); 43 | 44 | // refresh view 45 | var view = [doc currentView]; 46 | view.refresh(); 47 | 48 | } else { 49 | doc.showMessage("Please select a reference layer."); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/Selection/by Text Style/Select Layers by Text Style on Artboard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This plugin selects all layers with the same text style based on the selected layer. 3 | * Scope: current artboard 4 | * 5 | * Florian Schulz Copyright 2014, MIT License 6 | */ 7 | 8 | @import '../../inventory.js' 9 | 10 | var onRun = function (context) { 11 | 12 | // old school variable 13 | doc = context.document; 14 | selection = context.selection; 15 | 16 | var scope = doc.currentPage().currentArtboard().children(); 17 | 18 | if (selection.count() === 1) { 19 | 20 | // the selected layer 21 | var selected = selection[0]; 22 | 23 | // Only proceed if a text layer is selected 24 | if (selected.isKindOfClass(MSTextLayer)) { 25 | doc.showMessage("Looking for similar text layers…"); 26 | 27 | var textStyle = selected.style().textStyle(); 28 | var results = com.getflourish.layers.selectLayersByTextStyle(textStyle, scope); 29 | 30 | // Show how many layers have been selected 31 | doc.showMessage(results.count() + " text layers selected"); 32 | 33 | } else { 34 | doc.showMessage("Please select a text layer."); 35 | } 36 | } else { 37 | doc.showMessage("Please select a text layer."); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/Selection/by Text Style/Select Layers by Text Style on Page.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This plugin selects all layers with the same text style based on the selected layer. 3 | * Scope: current page 4 | * 5 | * Florian Schulz Copyright 2014, MIT License 6 | */ 7 | 8 | @import '../../inventory.js' 9 | 10 | var onRun = function (context) { 11 | 12 | // old school variable 13 | doc = context.document; 14 | selection = context.selection; 15 | 16 | var scope = doc.currentPage().children(); 17 | 18 | if (selection.count() === 1) { 19 | 20 | // the selected layer 21 | var selected = selection[0]; 22 | 23 | // Only proceed if a text layer is selected 24 | if (selected.isKindOfClass(MSTextLayer)) { 25 | doc.showMessage("Looking for similar text layers…"); 26 | 27 | var textStyle = selected.style().textStyle(); 28 | var results = com.getflourish.layers.selectLayersByTextStyle(textStyle, scope); 29 | 30 | // Show how many layers have been selected 31 | doc.showMessage(results.count() + " text layers selected"); 32 | 33 | } else { 34 | doc.showMessage("Please select a text layer."); 35 | } 36 | } else { 37 | doc.showMessage("Please select a text layer."); 38 | } 39 | } -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/inventory.js: -------------------------------------------------------------------------------- 1 | @import 'sandbox.js' 2 | @import 'persistence.js' 3 | 4 | 5 | // Namespaced library of functions common across multiple plugins 6 | var com = com || {}; 7 | var sketch; 8 | 9 | // Get the path of the plugin, without the name of the plugin 10 | // todo: Could need some regex love for sure 11 | 12 | com.getflourish = { 13 | 14 | } 15 | 16 | com.getflourish.config = { 17 | colorInventoryName: "Color Inventory", 18 | pageName: "Style Inventory", 19 | samePage: false, 20 | textStylePlaceholder: "The big brown fox jumps over the lazy dog.", 21 | maxColorsPerRow: 5, 22 | BACKGROUND_COLOR: "#E7EAEE", 23 | CARD_COLOR: "#ffffff", 24 | FONT: "HelveticaNeue-Bold", 25 | TEXT_COLOR: "#333333", 26 | TEXTSTYLE_NAME: "Style Inventory / Label" 27 | } 28 | 29 | com.getflourish.common = { 30 | init: function (context) { 31 | 32 | com.getflourish.context = context; 33 | com.getflourish.doc = context.document; 34 | com.getflourish.selection = context.selection; 35 | 36 | var doc = context.document; 37 | 38 | sketch = context.api() 39 | 40 | var pluginPath = context.scriptPath; 41 | var lastSlash = pluginPath.lastIndexOf("/"); 42 | var basePath = pluginPath.substr(0, lastSlash); 43 | 44 | com.getflourish.config.background_image = basePath + "/pattern.png"; 45 | 46 | }, 47 | // Adds an artboard to the given page 48 | addArtboard: function (page, name) { 49 | var artboard = MSArtboardGroup.new(); 50 | frame = artboard.frame(); 51 | frame.setWidth(400); 52 | frame.setHeight(400); 53 | frame.setConstrainProportions(false); 54 | page.addLayers([artboard]); 55 | artboard.setName(name); 56 | return artboard; 57 | }, 58 | addCheckeredBackground: function (artboard) { 59 | 60 | var layer = com.getflourish.common.addRectangleLayer(artboard) 61 | 62 | layer.frame().setWidth(artboard.frame().width()); 63 | layer.frame().setHeight(artboard.frame().height()); 64 | layer.setName("Background"); 65 | 66 | // todo: add pattern 67 | 68 | var image = NSImage.alloc().initWithContentsOfFile(com.getflourish.config.background_image); 69 | 70 | var fill = layer.style().addStylePartOfType(0); 71 | if (fill) { 72 | fill.setFillType(4); 73 | fill.setImage(MSImageData.alloc().initWithImage(image)) 74 | fill.setPatternFillType(0); 75 | fill.setPatternTileScale(1); 76 | } 77 | com.getflourish.utils.deselectAllLayers() 78 | 79 | layer.isSelected = true 80 | com.getflourish.utils.sendToBack(); 81 | layer.setIsLocked(true); 82 | 83 | return layer; 84 | }, 85 | addSolidBackground: function (artboard, hex_string) { 86 | var layer = com.getflourish.common.addRectangleLayer(artboard) 87 | layer.frame().setWidth(artboard.frame().width()); 88 | layer.frame().setHeight(artboard.frame().height()); 89 | layer.style().addStylePartOfType(0); 90 | layer.style().fills()[0].setFillType(0); 91 | layer.setName("Background"); 92 | 93 | var color = MSImmutableColor.colorWithSVGString(hex_string); 94 | 95 | layer.style().fills()[0].setColor(color) 96 | 97 | return layer; 98 | }, 99 | // Adds a new page to the document 100 | addPage: function (name) { 101 | // look for existing style sheet, otherwise create a new page with the styles 102 | var page = doc.addBlankPage(); 103 | page.setName(name); 104 | doc.setCurrentPage(page); 105 | // com.getflourish.common.refreshPage(); 106 | return page; 107 | }, 108 | addRectangleLayer: function (target) { 109 | 110 | var rect = NSMakeRect(0, 0, 200, 25) 111 | var shape = [[MSRectangleShape alloc] init] 112 | [shape setFrame:[MSRect rectWithRect:rect]] 113 | var shapeGroup = [MSShapeGroup shapeWithPath:shape] 114 | // var fill = [[shapeGroup style] addStylePartOfType:1] 115 | // var color = [MSColor colorWithNSColor:nsColor] 116 | // [fill setColor:color] 117 | 118 | 119 | target.addLayers([shapeGroup]) 120 | 121 | return shapeGroup; 122 | }, 123 | addTextLayer: function (target, label) { 124 | // var textLayer = NSTextField.alloc().initWithFrame_(NSMakeRect(x, y, 100, 25)); 125 | var textLayer = MSTextLayer.new(); 126 | textLayer.setStringValue(label) 127 | textLayer.setName(label) 128 | target.addLayers([textLayer]); 129 | return textLayer; 130 | }, 131 | addTextLayerEmphasis: function (target, label) { 132 | 133 | var textLayer = MSTextLayer.new(); 134 | textLayer.setStringValue(label) 135 | textLayer.setName(label) 136 | textLayer.setFontPostscriptName("HelveticaNeue-Bold"); 137 | target.addLayers([textLayer]); 138 | return textLayer; 139 | }, 140 | addTextLayerTitle: function (target, label) { 141 | var textLayer = MSTextLayer.new(); 142 | textLayer.setStringValue(label) 143 | textLayer.setName(label) 144 | textLayer.setFontSize(44); 145 | textLayer.setFontPostscriptName("HelveticaNeue-Thin"); 146 | target.addLayers([textLayer]); 147 | return textLayer; 148 | }, 149 | areOfEqualClass: function (layers) { 150 | var baseClass = layers[0].className(); 151 | for (var i = 0; i < layers.count(); i++) { 152 | if (layers[i].className() != baseClass) { 153 | return false; 154 | } 155 | } 156 | return true; 157 | }, 158 | createSelect: function (msg, items, selectedItemIndex) { 159 | selectedItemIndex = selectedItemIndex || 0 160 | 161 | var accessory = [[NSComboBox alloc] initWithFrame: NSMakeRect(0, 0, 200, 25)] 162 | [accessory addItemsWithObjectValues: items] 163 | [accessory selectItemAtIndex: selectedItemIndex] 164 | 165 | var alert = [[NSAlert alloc] init] 166 | [alert setMessageText: msg] 167 | [alert addButtonWithTitle: 'OK'] 168 | [alert addButtonWithTitle: 'Cancel'] 169 | [alert setAccessoryView: accessory] 170 | 171 | var responseCode = [alert runModal] 172 | var sel = [accessory indexOfSelectedItem] 173 | var value = [accessory stringValue] 174 | 175 | return [responseCode, sel, value] 176 | }, 177 | createWebView: function (url) { 178 | // WebView tests (shift alt ctrl y) 179 | 180 | /** 181 | * The first script call creates the panel with the saved HTML page. 182 | * The following script calls execute the JavaScript calls in the webView. 183 | */ 184 | 185 | // Add a hint to whomever is instantiating us, 186 | // that we'd like to stick around for a while. 187 | coscript.shouldKeepAround = true; 188 | 189 | // Include additionl frameworks. 190 | framework('WebKit'); 191 | framework('AppKit'); 192 | 193 | // Define a WebViewLoad delegate function - should be converted to an ObjC class. 194 | // See https://github.com/logancollins/Mocha 195 | var WebViewLoadDelegate = function () {}; 196 | 197 | // Add the initiating delegate (callback) function. 198 | WebViewLoadDelegate.prototype.webView_didClearWindowObject_forFrame = function (sender, scriptObject, frame) { 199 | var jswrapper = 'try {[[js]]} catch (e) {e.toString();}', 200 | jscode = 'document.body.innerHTML;', 201 | js = jswrapper.replace('[[js]]', jscode); 202 | 203 | var result = scriptObject.evaluateWebScript(js); 204 | 205 | }; 206 | 207 | // Add the delegate (callback) function which is called when the page has loaded. 208 | WebViewLoadDelegate.prototype.webView_didFinishLoadForFrame = function (sender, frame) { 209 | var jswrapper = 'try {[[js]]} catch (e) {e.toString();}', 210 | jscode = 'document.body.innerHTML;', 211 | js = jswrapper.replace('[[js]]', jscode); 212 | 213 | var scriptObject = sender.windowScriptObject(); 214 | 215 | var result = scriptObject.evaluateWebScript(js); 216 | 217 | }; 218 | 219 | // Prepare the function that will be used as an object added to the 220 | // scriptObject. The object should be visible with it's methods and properties 221 | // from the page JavaScript and should be usable to call Sketch script 222 | // functions from the page JavaScript. 223 | var runThis = function runThis() { 224 | var that = function () {}; 225 | 226 | // Property visible to JavaScript. 227 | that.fixed = "Property named fixed"; 228 | // Method visible to JavaScript. 229 | that.log = function () { 230 | log('Called from JavaScript via the Sketch script RunThis object.'); 231 | return true; 232 | }; 233 | // Method returns whether a selector should be hidden 234 | // from the scripting environment. If "false" is returned none is hidden. 235 | that.isSelectorExcludedFromWebScript = function (selector) { 236 | log('selector'); 237 | log(selector); 238 | return false; 239 | }; 240 | // Method returns whether a key should be hidden 241 | // from the scripting environment. If "false" is returned none is hidden. 242 | that.isKeyExcludedFromWebScript = function (key) { 243 | log('key'); 244 | log(key); 245 | return false; 246 | }; 247 | 248 | return that; 249 | }; 250 | 251 | // Set the url to the saved webpage 252 | // Split the scriptpath into an array, remove last item which is 253 | // the script name, create a string from the array again and wrap the 254 | // path with 'file://' and the html file name. 255 | var URL = ''; 256 | var path = url.split('/'); 257 | 258 | path.pop(); 259 | path = path.join('/') + '/'; 260 | URL = encodeURI('file://' + url + "styles.html"); 261 | log(URL) 262 | 263 | /** 264 | * Prepare the panel, show it and save it into the persistent store. 265 | */ 266 | var setupWebViewPanel = function () { 267 | // Prepare the panel: 268 | // Set the panel size and 269 | // initialize the webview and load the URL 270 | var frame = NSMakeRect(0, 0, 320, 480); 271 | var webView = WebView.alloc() 272 | .initWithFrame(frame); 273 | var webViewFrame = webView.mainFrame(); 274 | 275 | // The FrameLoadDelegate offers the webView_didFinishLoadForFrame 276 | // method which is called when the web page is loaded. 277 | // !!! The methods never fire because: 278 | // - it is implemented wrong? 279 | // - the delegate's method never is called because the script ends before the 280 | // page is loaded? 281 | var loadDelegate = new WebViewLoadDelegate(); 282 | webView.setFrameLoadDelegate(loadDelegate); 283 | 284 | webViewFrame.loadRequest(NSURLRequest.requestWithURL(NSURL.URLWithString(URL))); 285 | 286 | // Set up the panel window 287 | var mask = NSTitledWindowMask + NSClosableWindowMask + NSMiniaturizableWindowMask + NSUtilityWindowMask; 288 | var panel = NSPanel.alloc() 289 | .initWithContentRect_styleMask_backing_defer(frame, mask, NSBackingStoreBuffered, true); 290 | 291 | // Add the webView to the prepared panel 292 | panel.contentView() 293 | .addSubview(webView); 294 | 295 | // Show the panel 296 | panel.makeKeyAndOrderFront(panel); 297 | 298 | // persist the panel and the webView. 299 | persist.set('panel', panel); 300 | persist.set('webView', webView); 301 | }; 302 | 303 | var update = function () { 304 | var webView = persist.get('webView') 305 | .mainFrame() 306 | .loadRequest(NSURLRequest.requestWithURL(NSURL.URLWithString(URL))); 307 | } 308 | 309 | var doScript = function () { 310 | var jswrapper = 'try {[[js]]} catch (e) {e.toString();}', 311 | jscode = '', 312 | js = jswrapper.replace('[[js]]', jscode); 313 | 314 | // var result = webView.stringByEvaluatingJavaScriptFromString(js); 315 | 316 | // Get the windowScriptObject as the scripting connector 317 | var scriptObject = webView.windowScriptObject(); 318 | 319 | // Add the RunThis object with the key 'callThis'. The callThis 320 | // object should be accessible by the page JavaScript. 321 | // !!! The object is seen by the page JavaScript but the methods/porperties 322 | // are not present. 323 | var runThisFunc = runThis(); 324 | scriptObject.setValue_forKey(runThisFunc, 'callThis'); 325 | 326 | // Add a text line. 327 | jscode = 'de.unodo.writeTest("From the Sketch script ' + new Date() + '");'; 328 | js = jswrapper.replace('[[js]]', jscode); 329 | var result = scriptObject.evaluateWebScript(js); 330 | log(result); 331 | 332 | // Call the callback function to check if the 'callThis' class is visible 333 | // in the page for JavaScript. 334 | jscode = 'de.unodo.callBack();'; 335 | js = jswrapper.replace('[[js]]', jscode); 336 | var result = scriptObject.evaluateWebScript(js); 337 | log(result); 338 | 339 | // Get the form data. 340 | jscode = 'de.unodo.getFormData();'; 341 | js = jswrapper.replace('[[js]]', jscode); 342 | var result = scriptObject.evaluateWebScript(js); 343 | log('Formfield "Name" value: ' + result); 344 | }; 345 | 346 | var panel = persist.get('panel'); 347 | var webView = persist.get('webView'); 348 | 349 | // If the panel does not exisit (null is returned from persist.get), 350 | // set the panel up and show it. 351 | // Else make the panel the front window and run the JavaScript functions. 352 | if (panel === null) { 353 | log('setupWebViewPanel'); 354 | setupWebViewPanel(); 355 | } else { 356 | // Show the panel 357 | update(); 358 | panel.makeKeyAndOrderFront(panel); 359 | 360 | // var loadDelegate = new WebViewLoadDelegate; 361 | // webView.setFrameLoadDelegate(loadDelegate); 362 | 363 | // Run the scripts 364 | // doScript(); 365 | } 366 | 367 | log('done'); 368 | 369 | }, 370 | filePicker: function (url, fileTypes) { 371 | // Panel 372 | var openPanel = [NSOpenPanel openPanel] 373 | 374 | [openPanel setTitle: "Import Colors"] 375 | [openPanel setMessage: "Select a JSON file containing color information."]; 376 | [openPanel setPrompt: "Import"]; 377 | 378 | [openPanel setCanCreateDirectories: false] 379 | [openPanel setCanChooseFiles: true] 380 | [openPanel setCanChooseDirectories: false] 381 | [openPanel setAllowsMultipleSelection: false] 382 | [openPanel setShowsHiddenFiles: false] 383 | [openPanel setExtensionHidden: false] 384 | [openPanel setAllowedFileTypes: fileTypes] 385 | 386 | // [openPanel setDirectoryURL:url] 387 | 388 | var openPanelButtonPressed = [openPanel runModal] 389 | if (openPanelButtonPressed == NSFileHandlingPanelOKButton) { 390 | allowedUrl = [openPanel URL] 391 | } 392 | return allowedUrl 393 | }, 394 | fileSaver: function () { 395 | // Panel 396 | var openPanel = [NSOpenPanel openPanel] 397 | 398 | [openPanel setTitle: "Choose a location…"] 399 | [openPanel setMessage: "Select the export location…"]; 400 | [openPanel setPrompt: "Export"]; 401 | 402 | [openPanel setCanCreateDirectories: true] 403 | [openPanel setCanChooseFiles: false] 404 | [openPanel setCanChooseDirectories: true] 405 | [openPanel setAllowsMultipleSelection: false] 406 | [openPanel setShowsHiddenFiles: false] 407 | [openPanel setExtensionHidden: false] 408 | 409 | // [openPanel setDirectoryURL:url] 410 | 411 | var openPanelButtonPressed = [openPanel runModal] 412 | if (openPanelButtonPressed == NSFileHandlingPanelOKButton) { 413 | allowedUrl = [openPanel URL] 414 | } 415 | return allowedUrl 416 | }, 417 | // Returns the page if it exists or creates a new page 418 | getPageByName: function (name) { 419 | for (var i = 0; i < doc.pages().count(); i++) { 420 | var page = doc.pages() 421 | .objectAtIndex(i); 422 | if (page.name() == name) { 423 | doc.setCurrentPage(page); 424 | return page; 425 | } 426 | } 427 | var page = com.getflourish.common.addPage(name); 428 | return page; 429 | }, 430 | getStyleSheetPage: function () { 431 | if (com.getflourish.config.samePage == true) { 432 | return doc.currentPage(); 433 | } else { 434 | if (com.getflourish.config.stylesheetPage == null) { 435 | com.getflourish.config.stylesheetPage = com.getflourish.common.getPageByName(com.getflourish.config.pageName); 436 | } 437 | return com.getflourish.config.stylesheetPage; 438 | } 439 | }, 440 | dump: function (obj) { 441 | log("#####################################################################################") 442 | log("## Dumping object " + obj) 443 | log("## obj class is: " + [obj className]) 444 | log("#####################################################################################") 445 | log("obj.properties:") 446 | log([obj class].mocha().properties()) 447 | log("obj.propertiesWithAncestors:") 448 | log([obj class].mocha().propertiesWithAncestors()) 449 | log("obj.classMethods:") 450 | log([obj class].mocha().classMethods()) 451 | log("obj.classMethodsWithAncestors:") 452 | log([obj class].mocha().classMethodsWithAncestors()) 453 | log("obj.instanceMethods:") 454 | log([obj class].mocha().instanceMethods()) 455 | log("obj.instanceMethodsWithAncestors:") 456 | log([obj class].mocha().instanceMethodsWithAncestors()) 457 | log("obj.protocols:") 458 | log([obj class].mocha().protocols()) 459 | log("obj.protocolsWithAncestors:") 460 | log([obj class].mocha().protocolsWithAncestors()) 461 | log("obj.treeAsDictionary():") 462 | log(obj.treeAsDictionary()) 463 | }, 464 | // Returns an artboard from a given page 465 | getArtboardByPageAndName: function (page, name) { 466 | 467 | var scope = page.children(); 468 | // setup predicate 469 | var predicate = NSPredicate.predicateWithFormat("name == %@ AND className == %@", name, "MSArtboardGroup"); 470 | 471 | // get the color artboard 472 | var results = scope.filteredArrayUsingPredicate(predicate); 473 | 474 | if (results.count() > 0) { 475 | return results.objectAtIndex(0); 476 | } else { 477 | return com.getflourish.common.addArtboard(page, name); 478 | } 479 | }, 480 | isIncluded: function (arr, obj) { 481 | return (arr.indexOf(obj) != -1); 482 | }, 483 | getDirectoryFromBrowserForFilename: function (filename) { 484 | // Path and file access 485 | var document_path = [[doc fileURL] path].split([doc displayName])[0]; 486 | var path = document_path + filename; 487 | 488 | var fileTypes = []; 489 | var fileURL = com.getflourish.common.fileSaver(); 490 | path = fileURL.path(); 491 | 492 | // Authorize Sketch to save a file 493 | new AppSandbox().authorize(path, function () {}); 494 | 495 | return path; 496 | 497 | }, 498 | getTextStyleByName: function (name) { 499 | var textStyles = doc.documentData().layerTextStyles(); 500 | 501 | if (textStyles) { 502 | // get index 503 | for (var i = 0; i < textStyles.objects().count(); i++) { 504 | if (textStyles.objects().objectAtIndex(i).name() == name) { 505 | return textStyles.objects().objectAtIndex(i); 506 | } 507 | } 508 | } 509 | return null; 510 | }, 511 | getLayerStyleByName: function (name) { 512 | var layerStyles = doc.documentData().layerStyles(); 513 | 514 | if (layerStyles) { 515 | // get index 516 | for (var i = 0; i < layerStyles.objects().count(); i++) { 517 | if (layerStyles.objects().objectAtIndex(i).name() == name) { 518 | return layerStyles.objects().objectAtIndex(i); 519 | } 520 | } 521 | } 522 | return null; 523 | }, 524 | placeNextTo: function (self, other) { 525 | var x = other.frame().x() + other.frame().width() + 100; 526 | var y = other.frame().y(); 527 | self.frame().setX(x); 528 | self.frame().setY(y); 529 | }, 530 | showMarginsOf: function (layer) { 531 | // calculates margins and displays them 532 | var parent = layer.parentGroup(); 533 | var ml = layer.absoluteRect().x() - parent.absoluteRect().x(); 534 | var mt = layer.absoluteRect().y() - parent.absoluteRect().y(); 535 | var mr = parent.frame().width() - ml - layer.frame().width(); 536 | var mb = parent.absoluteRect().y() + parent.frame().height() - layer.absoluteRect().y() - layer.frame().height(); 537 | 538 | var margin = "x: " + ml + ", y: " + mt + " / right: " + mr + ", bottom: " + mb; 539 | doc.showMessage(margin); 540 | }, 541 | refreshPage: function () { 542 | var c = doc.currentPage(); 543 | doc.setCurrentPage(0); 544 | doc.setCurrentPage(doc.pages().count() - 1); 545 | doc.setCurrentPage(c); 546 | }, 547 | removeArtboardFromPage: function (page, name) { 548 | var theArtboard = null; 549 | var abs = page.artboards().objectEnumerator(); 550 | 551 | while (a = abs.nextObject()) { 552 | if (a.name() == name) { 553 | page.removeLayer(a) 554 | break; 555 | } 556 | } 557 | }, 558 | // Removes all layers from an artboard 559 | removeAllLayersFromArtboard: function (artboard) { 560 | 561 | var layers = artboard.children().objectEnumerator(); 562 | while (layer = layers.nextObject()) { 563 | artboard.removeLayer(layer); 564 | } 565 | }, 566 | resize: function (layer, width, height) { 567 | var frame = layer.frame(); 568 | frame.setWidth(width); 569 | frame.setHeight(height); 570 | }, 571 | // Saves a string to a file 572 | // save_file_from_string: function (filename, the_string) { 573 | // var path = [@"" stringByAppendingString:filename], 574 | // str = [@"" stringByAppendingString:the_string]; 575 | // 576 | // if (in_sandbox()) { 577 | // sandboxAccess.accessFilePath_withBlock_persistPermission(filename, function(){ 578 | // [str writeToFile:path atomically:true encoding:NSUTF8StringEncoding error:null]; 579 | // }, true) 580 | // } else { 581 | // [str writeToFile:path atomically:true encoding:NSUTF8StringEncoding error:null]; 582 | // } 583 | // }, 584 | save_file_from_string: function (filename, the_string) { 585 | var path = [@"" 586 | stringByAppendingString: filename], 587 | str = [@"" 588 | stringByAppendingString: the_string]; 589 | str.dataUsingEncoding_(NSUTF8StringEncoding).writeToFile_atomically_(path, true) 590 | } 591 | } 592 | 593 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 594 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 595 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 596 | 597 | 598 | com.getflourish.colorInventory = { 599 | generate: function (palettes) { 600 | 601 | 602 | var currentlySelectedArtboard = doc.currentPage().currentArtboard(); 603 | 604 | if (currentlySelectedArtboard == null) { 605 | doc.showMessage("Please select an artboard. The Inventory will be placed next to it." + currentlySelectedArtboard); 606 | } 607 | 608 | var exists = false; 609 | 610 | // start tracking the time 611 | var startTime = new Date(); 612 | 613 | // page that the artboard will be created on 614 | var styleSheetPage = com.getflourish.common.getStyleSheetPage(); 615 | 616 | // the right most artboard 617 | var rma; 618 | if (currentlySelectedArtboard != null) rma = com.getflourish.layers.getRightmostArtboard(); 619 | 620 | // get hex colors from document 621 | var hexColors = com.getflourish.colorInventory.getDocumentColors(); 622 | 623 | if (hexColors.length > 0) { 624 | 625 | // feedback 626 | doc.showMessage("Analyzing Colors…"); 627 | 628 | // setup predicate 629 | var predicate = NSPredicate.predicateWithFormat("name == %@ AND className == %@", com.getflourish.config.colorInventoryName, "MSArtboardGroup"); 630 | 631 | // get the color artboard 632 | var colorArtboard = styleSheetPage.children().filteredArrayUsingPredicate(predicate); 633 | if (colorArtboard.count() == 0) { 634 | // todo: adding is broken 635 | colorArtboard = com.getflourish.common.addArtboard(styleSheetPage, com.getflourish.config.colorInventoryName); 636 | } else { 637 | colorArtboard = colorArtboard[0]; 638 | exists = true; 639 | } 640 | 641 | doc.showMessage("Checking Palettes…"); 642 | 643 | // if no palettes are provided, analyse the document to get them 644 | if (palettes == null) { 645 | // get hex colors from color artboard 646 | var colorArtboardColors = com.getflourish.utils.arrayFromImmutableArray(com.getflourish.colorInventory.getColorArtboardColors(colorArtboard)); 647 | 648 | // get defined colors 649 | var queryResult = com.getflourish.colorInventory.getDefinedColors(colorArtboard); 650 | 651 | // todo: optimize 652 | var palettes = com.getflourish.colorInventory.getPalettesFromMergingColors(queryResult, hexColors); 653 | 654 | // add text colors to the palettes 655 | 656 | } 657 | 658 | // clear the artboard before adding swatches 659 | colorArtboard.removeAllLayers(); 660 | 661 | doc.showMessage("Adding background…"); 662 | 663 | // Add background 664 | var bg = com.getflourish.common.addCheckeredBackground(colorArtboard); 665 | 666 | doc.showMessage("Added…"); 667 | 668 | var execTime = (new Date() - startTime) / 1000; 669 | doc.showMessage("Painting Swatches… " + execTime + " s"); 670 | 671 | // create colorsheet by passing palettes that contain multiple color objects (name, value) 672 | com.getflourish.colors.createColorSheet(colorArtboard, palettes); 673 | 674 | doc.showMessage("Created color sheet…"); 675 | 676 | // finish it up 677 | com.getflourish.colorInventory.finish(colorArtboard); 678 | 679 | doc.showMessage("Finished…"); 680 | 681 | // resize background 682 | if (bg) { 683 | bg.frame().setWidth(colorArtboard.frame().width()) 684 | bg.frame().setHeight(colorArtboard.frame().height()) 685 | } 686 | 687 | // Feedback 688 | var execTime = (new Date() - startTime) / 1000; 689 | doc.showMessage("Generated Color Inventory in " + execTime + " s"); 690 | 691 | return colorArtboard; 692 | 693 | } else { 694 | doc.showMessage("No colors found :("); 695 | return null; 696 | } 697 | }, 698 | getColorArtboardColors: function (colorArtboard) { 699 | 700 | // get all layers of the current page 701 | var layers = colorArtboard.children(); 702 | 703 | // analyse the colors 704 | var rawHexColors = [layers valueForKeyPath: "@distinctUnionOfObjects.style.fills.color"]; 705 | return rawHexColors; 706 | }, 707 | getColorURL: function () { 708 | var url = [doc askForUserInput: "Paste URL from Coolors or Hailpixel…" 709 | initialValue: "http://coolors.co/e0e086-acbe14-2c1f19-327a76-bce2d7"] 710 | return url; 711 | }, 712 | getColorsFromCoolorsString: function (string) { 713 | var pos = string.lastIndexOf("/") + 1; 714 | var colorString = string.substring(pos); 715 | var colors = colorString.split("-"); 716 | var swatches = []; 717 | for (var i = 0; i < colors.length; i++) { 718 | var color = colors[i]; 719 | swatches.push({ 720 | name: "Imported Color", 721 | color: color 722 | }) 723 | } 724 | return swatches; 725 | }, 726 | getColorsFromHailString: function (string) { 727 | var pos = string.lastIndexOf("/#") + 1; 728 | var colorString = string.substring(pos); 729 | var colors = colorString.split(","); 730 | var swatches = []; 731 | for (var i = 0; i < colors.length; i++) { 732 | var color = colors[i]; 733 | if (color != ",") { 734 | swatches.push({ 735 | name: "Imported Color", 736 | color: color 737 | }) 738 | } 739 | } 740 | return swatches; 741 | }, 742 | getColorsFromURL: function (url) { 743 | if (url.indexOf("http://color.hailpixel.com/") == 0) { 744 | return com.getflourish.colorInventory.getColorsFromHailString(url); 745 | } else if (url.indexOf("http://coolors.co/") == 0) { 746 | return com.getflourish.colorInventory.getColorsFromCoolorsString(url); 747 | } 748 | return null; 749 | }, 750 | getDefinedColors: function (artboard) { 751 | // get defined colors, rename existing swatches 752 | var predicate = NSPredicate.predicateWithFormat("NOT (name BEGINSWITH %@) && className == %@", "Untitled", "MSLayerGroup"); 753 | var queryResult = artboard.children().filteredArrayUsingPredicate(predicate); 754 | return queryResult; 755 | }, 756 | getDocumentColors: function () { 757 | var borderColors = com.getflourish.colorInventory.getDocumentBorderColors(); 758 | var gradients = com.getflourish.colorInventory.getDocumentGradients(); 759 | var imageFills = com.getflourish.colorInventory.getDocumentImageFills(); 760 | var solidFillColors = com.getflourish.colorInventory.getDocumentSolidFillColors(); 761 | var textColors = com.getflourish.colorInventory.getDocumentTextColors(); 762 | 763 | // concat 764 | // todo: concat and display by type 765 | var allColors = solidFillColors.concat(textColors).uniqueColors(); 766 | var finalColors = []; 767 | 768 | for (var i = 0; i < allColors.length; i++) { 769 | var color = allColors[i] 770 | if (color.r) { 771 | finalColors.push(color) 772 | } else { 773 | if (color.className() != "__NSArray0") { 774 | finalColors.push(color[0]) 775 | } 776 | } 777 | } 778 | 779 | return finalColors; 780 | }, 781 | getDocumentBorderColors: function () { 782 | return com.getflourish.colorInventory.getDestinctProperties("style.borders.color"); 783 | }, 784 | getDocumentGradients: function () { 785 | return com.getflourish.colorInventory.getDestinctProperties("style.fills.gradient"); 786 | }, 787 | getDocumentImageFills: function () { 788 | return com.getflourish.colorInventory.getDestinctProperties("style.fills.image"); 789 | }, 790 | getDocumentSolidFillColors: function () { 791 | return com.getflourish.colorInventory.getDestinctProperties("style.fills.color"); 792 | }, 793 | getDocumentTextColors: function () { 794 | return com.getflourish.colorInventory.getDestinctProperties("textColor"); 795 | }, 796 | getDestinctProperties: function (keyPath, scope) { 797 | // get all layers of the current page, except the ones used on the color artboard 798 | var props = []; 799 | for (var i = 0; i < doc.pages().count(); i++) { 800 | var page = doc.pages().objectAtIndex(i); 801 | var layers = page.children(); 802 | 803 | var predicate = NSPredicate.predicateWithFormat("NOT(parentArtboard.name == %@) AND className != %@ AND className != %@", com.getflourish.config.colorInventoryName, "MSPage", "MSArtboardGroup"); 804 | var result = layers.filteredArrayUsingPredicate(predicate); 805 | 806 | // analyse the colors 807 | var keyPath = "@distinctUnionOfObjects." + keyPath; 808 | 809 | var objects = [result valueForKeyPath: keyPath]; 810 | 811 | var properties = com.getflourish.utils.arrayFromImmutableArray(objects); 812 | props.push(properties); 813 | } 814 | props = [].concat.apply([], props) 815 | 816 | return props; 817 | }, 818 | export: function (exportPath) { 819 | 820 | var data = {}; 821 | var colorName = null; 822 | var colorSheet = com.getflourish.common.getArtboardByPageAndName(doc.currentPage(), com.getflourish.config.colorInventoryName); 823 | var hexColor = null; 824 | var pName; 825 | 826 | // get defined colors, rename existing swatches 827 | var predicate = NSPredicate.predicateWithFormat("className == %@ AND name != %@", "MSLayerGroup", "Untitled Color Swatch"); 828 | var queryResult = colorSheet.children().filteredArrayUsingPredicate(predicate); 829 | 830 | for (var j = 0; j < queryResult.count(); j++) { 831 | // check if there are swatches (groups of color swatches) 832 | 833 | var group = queryResult[j]; 834 | colorName = group.name(); 835 | pName = "Defined"; 836 | 837 | // loop through all child layers to find the color 838 | var layers = group.layers(); 839 | for (var i = 0; i < layers.count(); i++) { 840 | 841 | // get the current layer 842 | var currentLayer = layers[i]; 843 | if (currentLayer.name().indexOf("Color Swatch") == 0) { 844 | 845 | hexColor = currentLayer.name().substr(13); 846 | rgbColor = currentLayer.style().fills()[0].color() 847 | 848 | // remember color and name 849 | // todo: format string for use in SCSS? 850 | colorName = colorName; 851 | 852 | // check for palette in name 853 | if (colorName.indexOf(">") != -1) { 854 | pName = colorName.substr(0, colorName.indexOf(">") - 1); 855 | colorName = colorName.substr(colorName.indexOf(">") + 2); 856 | } 857 | if (data[pName] == null) data[pName] = {}; 858 | var colorString = [rgbColor.red().toFixed(2), rgbColor.green().toFixed(2), rgbColor.blue().toFixed(2), rgbColor.alpha().toFixed(2)].join(", ") 859 | data[pName][colorName] = "rgba(" + colorString +")"; 860 | } 861 | } 862 | } 863 | 864 | var output = JSON.stringify(data, undefined, 2); 865 | 866 | 867 | if (output == "{}") { 868 | doc.showMessage("Nothing to export. You need to define swatches.") 869 | } else { 870 | exportPath += "colors.json"; 871 | com.getflourish.common.save_file_from_string(exportPath, output); 872 | doc.showMessage("Exported to " + exportPath); 873 | } 874 | }, 875 | import: function () { 876 | 877 | // let user select a json file from the file browser 878 | var fileTypes = ["json"]; 879 | var document_path = [[doc fileURL] path].split([doc displayName])[0]; 880 | var fileURL = com.getflourish.common.filePicker(document_path, fileTypes); 881 | var str = JSON.parse(NSString.stringWithContentsOfFile(fileURL)); 882 | 883 | var importedPalettes = []; 884 | var newSwatches = []; 885 | 886 | var color; 887 | var newSwatch; 888 | 889 | if (str != null) { 890 | for (var key in str) { 891 | if (str.hasOwnProperty(key)) { 892 | var palette = str[key]; 893 | var swatches = []; 894 | for (var swatch in palette) { 895 | 896 | // color = palette[swatch].replace("#", ""); 897 | var rgbaComponents = palette[swatch].substr(5, palette[swatch].length - 6).split(", ") 898 | 899 | var msc = MSColor.colorWithRGBADictionary({ 900 | r: rgbaComponents[0], 901 | g: rgbaComponents[1], 902 | b: rgbaComponents[2], 903 | a: rgbaComponents[3] 904 | }) 905 | 906 | newSwatch = { 907 | name: swatch, 908 | color: msc 909 | } 910 | newSwatches.push(newSwatch); 911 | } 912 | var newPalette = { 913 | name: key, 914 | swatches: newSwatches 915 | } 916 | importedPalettes.push(newPalette); 917 | newSwatches = []; 918 | } 919 | } 920 | } 921 | 922 | if (importedPalettes.length > 0) { 923 | com.getflourish.colorInventory.generate(importedPalettes); 924 | } else { 925 | doc.showMessage("Nothing to import."); 926 | } 927 | }, 928 | importFromURL: function () { 929 | // get coolors url from user input 930 | var url = com.getflourish.colorInventory.getColorURL(); 931 | 932 | // get colors from url 933 | var swatches = com.getflourish.colorInventory.getColorsFromURL(url); 934 | 935 | // add them to palettes 936 | var palettes = []; 937 | 938 | palettes.push({ 939 | name: "Imported", 940 | swatches: swatches 941 | }) 942 | 943 | // create a new artboard 944 | var artboard = com.getflourish.common.getArtboardByPageAndName(doc.currentPage(), "Imported Colors"); 945 | artboard.removeAllLayers(); 946 | var bg = com.getflourish.common.addCheckeredBackground(artboard); 947 | 948 | // generate color sheet 949 | com.getflourish.colors.createColorSheet(artboard, palettes); 950 | 951 | // finish 952 | com.getflourish.colorInventory.finish(artboard); 953 | 954 | if (bg) { 955 | // resize background 956 | bg.frame().setWidth(artboard.frame().width()) 957 | bg.frame().setHeight(artboard.frame().height()) 958 | } 959 | }, 960 | getPalettesFromMergingColors: function (queryResult, definedColors) { 961 | 962 | var documentColors = []; 963 | var primaryColors = []; 964 | var palettes = []; 965 | var paletteNames = []; 966 | 967 | if (queryResult.count() > 0) { 968 | for (var i = 0; i < queryResult.count(); i++) { 969 | 970 | var group = queryResult[i]; 971 | 972 | // check if there are swatches (groups of color swatches) 973 | 974 | colorName = group.name(); 975 | 976 | 977 | // check if the color swatch has a defined name 978 | 979 | predicate2 = NSPredicate.predicateWithFormat("name BEGINSWITH %@", "Color Swatch"); 980 | querySwatches = group.children().filteredArrayUsingPredicate(predicate2); 981 | // do something with the color swatch 982 | if (querySwatches.count() == 1) { 983 | // get color 984 | 985 | c = querySwatches[0].style().fills()[0].color(); 986 | 987 | index = definedColors.indexOf(c); 988 | primaryIndex = primaryColors.indexOf(c); 989 | 990 | // see wether the color name contains palette information 991 | pIndex = colorName.indexOf(">"); 992 | if (pIndex != -1) { 993 | 994 | // has palette name 995 | pName = colorName.substr(0, pIndex); 996 | 997 | // add new palette 998 | if (paletteNames.indexOf(pName) == -1) { 999 | paletteNames.push(pName); 1000 | 1001 | // push the palette 1002 | palettes.push({ 1003 | name: pName, 1004 | swatches: [] 1005 | }); 1006 | } 1007 | var foo = NSPredicate.predicateWithFormat("(style.fills != NULL) && (style.fills.color isEqual:%@) && NOT(parentArtboard.name == %@)", c, com.getflourish.config.colorInventoryName); 1008 | var bar = doc.currentPage().children().filteredArrayUsingPredicate(foo); 1009 | 1010 | palettes[paletteNames.indexOf(pName)].swatches.push({ 1011 | name: colorName, 1012 | color: c, 1013 | occurences: bar.count() 1014 | }); 1015 | definedColors.splice(index, 1); 1016 | } else { 1017 | // not part of a palette 1018 | var foo = NSPredicate.predicateWithFormat("(style.fills != NULL) && (style.fills.color isEqual:%@) && NOT(parentArtboard.name == %@)", c, com.getflourish.config.colorInventoryName); 1019 | var bar = doc.currentPage().children().filteredArrayUsingPredicate(foo); 1020 | 1021 | primaryColors.push({ 1022 | name: colorName, 1023 | color: c, 1024 | occurences: bar.count() 1025 | }); 1026 | definedColors.splice(index, 1); 1027 | } 1028 | // check if defined color is still part of the documents colors 1029 | if (index == -1) { 1030 | // do not remove the color from the defined swatches 1031 | // definedColors.splice(index, 1); 1032 | // primaryColors.splice(primaryIndex, 1); 1033 | } 1034 | } 1035 | }; 1036 | } 1037 | 1038 | // todo: get occurences for all pages 1039 | // todo: include symbol colors 1040 | 1041 | for (var i = 0; i < definedColors.length; i++) { 1042 | predicate = NSPredicate.predicateWithFormat("(style.fills != NULL) && (style.fills.color isEqual:%@) && NOT(parentArtboard.name == %@)", definedColors[i], com.getflourish.config.colorInventoryName); 1043 | queryResult = doc.currentPage().children().filteredArrayUsingPredicate(predicate); 1044 | 1045 | var c = definedColors[i] 1046 | 1047 | documentColors.push({ 1048 | name: "Untitled Color Swatch", 1049 | color: c, 1050 | occurences: queryResult.count() || 1 1051 | }); 1052 | 1053 | } 1054 | 1055 | documentColors.sort(function (a, b) { 1056 | return b.occurences - a.occurences; 1057 | }); 1058 | 1059 | palettes.push({ 1060 | name: "Defined Colors", 1061 | swatches: primaryColors 1062 | }); 1063 | palettes.push({ 1064 | name: "Undefined Colors", 1065 | swatches: documentColors 1066 | }); 1067 | 1068 | // experimental border colors 1069 | // var borderColors = com.getflourish.colorInventory.getDocumentBorderColors(); 1070 | // log(borderColors) 1071 | // var borderSwatches = []; 1072 | // for (var i = 0; i < borderColors.length; i++) { 1073 | // borderSwatches.push({ 1074 | // name: "Untitled Color Swatch", 1075 | // color: borderColors[i].hexValue() 1076 | // }) 1077 | // } 1078 | // palettes.push({ 1079 | // name: "Border Colors", 1080 | // swatches: borderSwatches 1081 | // }); 1082 | 1083 | return palettes; 1084 | 1085 | }, 1086 | positionArtboard: function (what, where) { 1087 | // Position the artboard next to the last artboard 1088 | // The actual position that we want is the right edge of the 1089 | // rightmost artboard plus a margin of 100px. 1090 | 1091 | 1092 | // the right most artboard 1093 | var rma = com.getflourish.layers.getRightmostArtboard(); 1094 | // var rma = where; 1095 | 1096 | if (rma.name() != what.name()) { 1097 | // var shift = what.frame().width(); 1098 | // com.getflourish.colorInventory.shiftArtboardsFromArtboardBy(where, shift); 1099 | var left = rma.frame().width() + rma.frame().x() + 100; 1100 | var top = rma.frame().y(); 1101 | what.frame().setX(left); 1102 | what.frame().setY(top); 1103 | } else { 1104 | var left = rma.frame().x(); 1105 | var top = rma.frame().y(); 1106 | what.frame().setX(left); 1107 | what.frame().setY(top); 1108 | } 1109 | }, 1110 | shiftArtboardsFromArtboardBy: function (artboard, shift) { 1111 | // Make sure an artboard is selected 1112 | var selectedArtboard = artboard; 1113 | com.getflourish.utils.deselectAllLayers() 1114 | 1115 | selectedArtboard.isSelected = true 1116 | var width = selectedArtboard.frame().width(); 1117 | 1118 | artboards = doc.currentPage().artboards(); 1119 | 1120 | // Move all artboards that are next to the selected one 1121 | for (var i = 0; i < artboards.count(); i++)  { 1122 | // only move artboards on the same y position 1123 | if (artboards[i] != selectedArtboard) { 1124 | if (artboards[i].frame().y() == selectedArtboard.frame().y() && artboards[i].frame().x() > selectedArtboard.frame().x()) { 1125 | var newX = artboards[i].frame().x() + shift + 100; 1126 | artboards[i].frame().setX(newX); 1127 | } 1128 | } 1129 | 1130 | } 1131 | }, 1132 | finish: function (artboard) { 1133 | 1134 | // deselect all layers 1135 | com.getflourish.utils.deselectAllLayers() 1136 | 1137 | 1138 | // select current artboard 1139 | artboard.isSelected = true 1140 | 1141 | // collapse artboards 1142 | com.getflourish.utils.sendAction("collapseGroupsInLayerList:"); 1143 | artboard.isSelected = true 1144 | 1145 | // focus the view on the artboard 1146 | // com.getflourish.view.centerTo(artboard); 1147 | } 1148 | } 1149 | 1150 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 1151 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 1152 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 1153 | 1154 | com.getflourish.colors = { 1155 | 1156 | // compares two colors and returns true if they are equal 1157 | areEqual: function (a, b) { 1158 | 1159 | var ca = null; 1160 | var cb = null; 1161 | var hex_a = null; 1162 | var hex_b = null; 1163 | 1164 | try { 1165 | ca = String(a.className()); 1166 | cb = String(b.className()); 1167 | 1168 | } catch (error) { 1169 | log("could not receive classnames"); 1170 | } 1171 | 1172 | try { 1173 | if (ca === "MSColor" && cb === "MSColor") { 1174 | try { 1175 | hex_a = String(a.immutableModelObject().svgRepresentation()); 1176 | } catch (error)  { 1177 | log("couldn’t get hex a"); 1178 | } 1179 | 1180 | try { 1181 | hex_b = String(b.immutableModelObject().svgRepresentation()); 1182 | } catch (error)  { 1183 | log("couldn’t get hex a"); 1184 | } 1185 | 1186 | if (hex_a && hex_b) { 1187 | return hex_a === hex_b; 1188 | } 1189 | } else if (ca === "MSStyleFill" && cb === "MSStyleFill") { 1190 | switch (a.fillType()) { 1191 | // pattern 1192 | case 4: 1193 | if (a.image() == b.image()) return true; 1194 | break; 1195 | // gradient 1196 | case 1: 1197 | // check if both gradients have the same number of stops 1198 | var stopsA = a.gradient().stops(); 1199 | var stopsB = b.gradient().stops(); 1200 | 1201 | if (stopsA.count() == stopsB.count()) { 1202 | for (var i = 0; i < stopsA.count(); i++) { 1203 | if (stopsA.objectAtIndex(i).color() != stopsB.objectAtIndex(i).color() || stopsA.objectAtIndex(i).position() != stopsB.objectAtIndex(i).position()) { 1204 | return false; 1205 | } 1206 | } 1207 | return true; 1208 | } 1209 | break; 1210 | 1211 | default: 1212 | 1213 | break; 1214 | } 1215 | } 1216 | } catch (error) { 1217 | log("error comparing " + a.fillType() + " / " + b.fillType()); 1218 | } 1219 | return false; 1220 | }, 1221 | createColorSheet: function (artboard, palettes) { 1222 | // create color chip for each color 1223 | var left = 0; 1224 | var top = 30; 1225 | var margin = 45; 1226 | var margin_top = 125; 1227 | var width = 120; 1228 | 1229 | for (var i = 0; i < palettes.length; i++) { 1230 | var palette = palettes[i]; 1231 | 1232 | // add a title if palette has colors 1233 | if (palette.swatches.length > 0) { 1234 | var title = com.getflourish.common.addTextLayerTitle(artboard, palette.name); 1235 | title.frame().setY(top) 1236 | title.frame().setX(margin); 1237 | top += 80; 1238 | 1239 | 1240 | // var colorChip = com.getflourish.colors.addColorChip(artboard, palette.swatches[0]); 1241 | 1242 | // colorChip.frame().setX(left); 1243 | // colorChip.frame().setY(top); 1244 | var colorChip; 1245 | for (var j = 0; j < palette.swatches.length; j++) { 1246 | // colorChip.duplicate() 1247 | 1248 | if (palette.swatches[j].color) { 1249 | 1250 | left += margin; 1251 | 1252 | colorChip = com.getflourish.colors.addColorChip(artboard, palette.swatches[j]); 1253 | 1254 | width = colorChip.frame().width(); 1255 | // offset color chip 1256 | colorChip.frame().setX(left); 1257 | colorChip.frame().setY(top); 1258 | left += width; 1259 | 1260 | // after x color chips, star a new row 1261 | if ((j + 1) % com.getflourish.config.maxColorsPerRow == 0) { 1262 | top += width + margin_top; 1263 | left = 0; 1264 | } 1265 | } 1266 | 1267 | } 1268 | // move next palette 1269 | if (left != 0) { 1270 | left = 0; 1271 | top += colorChip.frame() 1272 | .height() + 1.2 * margin_top; 1273 | } 1274 | } 1275 | } 1276 | com.getflourish.common.resize(artboard, 870, top); 1277 | }, 1278 | update: function () { 1279 | var scope = doc.currentPage().children(); 1280 | var result = com.getflourish.layers.selectLayersByName("Untitled Color Swatch", scope); 1281 | var swatches = result.objectEnumerator(); 1282 | var left = 0; 1283 | var top = 0; 1284 | var margin = 0; 1285 | var width = 0; 1286 | 1287 | while (swatch = swatches.nextObject()) { 1288 | left += margin; 1289 | 1290 | swatch = com.getflourish.colors.addColorChip(artboard, palette.swatches[j]); 1291 | 1292 | width = swatch.frame().width(); 1293 | // offset color chip 1294 | swatch.frame().setX(left); 1295 | swatch.frame().setY(top); 1296 | left += width; 1297 | 1298 | // after x color chips, star a new row 1299 | if ((j + 1) % com.getflourish.config.maxColorsPerRow == 0) { 1300 | top += width + margin_top; 1301 | left = 0; 1302 | } 1303 | } 1304 | com.getflourish.common.resize(artboard, 790, top); 1305 | }, 1306 | // todo: change method to accept a color object with name and color value 1307 | addColorChip: function (artboard, swatch) { 1308 | 1309 | var padding = 8; 1310 | var color = swatch.color[0] || swatch.color; 1311 | 1312 | var hex_string = color.immutableModelObject().svgRepresentation() 1313 | 1314 | var colorName = ""; 1315 | 1316 | // add layer group 1317 | 1318 | var groupFrame = NSMakeRect(0, 0, 120, 195); 1319 | var group = [[MSLayerGroup alloc] initWithFrame:groupFrame]; 1320 | artboard.addLayers([group]); 1321 | 1322 | var group_name = ""; 1323 | if (swatch.name == "Untitled Color Swatch") { 1324 | swatch.name = "Untitled Color Swatch"; 1325 | colorName = "Untitled"; 1326 | } else { 1327 | if (swatch.name.indexOf(">") != -1) { 1328 | colorName = swatch.name.substring(swatch.name.indexOf(">") + 2); 1329 | } else { 1330 | colorName = swatch.name; 1331 | } 1332 | } 1333 | group.setName(swatch.name); 1334 | 1335 | // draw white label rectangle 1336 | var white = MSImmutableColor.colorWithSVGString("#FFFFFF"); 1337 | var labelBG = com.getflourish.colors.addColorShape(group, white, 120, 195); 1338 | 1339 | labelBG.frame().setY(0); 1340 | labelBG.setName("Background"); 1341 | 1342 | labelBG.isSelected = true 1343 | 1344 | var bottomBG = com.getflourish.colors.addColorShape(group, white, 120, 75); 1345 | bottomBG.frame().setY(120); 1346 | bottomBG.setName("Background-Bottom"); 1347 | bottomBG.isSelected = true 1348 | 1349 | // draw square color 1350 | var colorSquare = com.getflourish.colors.addColorShape(group, color, 120, 120); 1351 | 1352 | colorSquare.setName("Color Swatch " + hex_string); 1353 | colorSquare.isSelected = true 1354 | 1355 | // Name Label 1356 | var nameLabel = com.getflourish.common.addTextLayerEmphasis(group, colorName); 1357 | 1358 | nameLabel.frame().setY(colorSquare.frame().height() + padding); 1359 | nameLabel.frame().setX(8); 1360 | nameLabel.setName("Swatch Name"); 1361 | 1362 | // Name Label 1363 | if (swatch.occurences == null) swatch.occurences = 0; 1364 | 1365 | // var countLabel = com.getflourish.common.addTextLayerEmphasis(group, "Test"); 1366 | // countLabel.frame().setY(8); 1367 | // countLabel.frame().setX(85); 1368 | // countLabel.setTextAlignment(1); 1369 | // countLabel.setStringValue("" + swatch.occurences + "×"); 1370 | // countLabel.adjustFrameToFit(); 1371 | // countLabel.setTextAlignment(1); 1372 | // countLabel.setName("Swatch Count"); 1373 | // countLabel.setTextColor(white); 1374 | // Shadow 1375 | // var textShadow = countLabel.style().addStylePartOfType(2); 1376 | 1377 | var black = MSImmutableColor.colorWithSVGString("#000000"); 1378 | // black.alpha = 0.5; 1379 | // textShadow.setOffsetX(0); 1380 | // textShadow.setOffsetY(1); 1381 | // textShadow.setBlurRadius(2); 1382 | // textShadow.setSpread(0); 1383 | // 1384 | // textShadow.setColor(black) 1385 | 1386 | // Hex Label 1387 | var hexLabel = com.getflourish.common.addTextLayer(group, hex_string); 1388 | hexLabel.frame().setY(nameLabel.frame().y() + 14 + padding); 1389 | hexLabel.frame().setX(8); 1390 | hexLabel.setName("Hex Label"); 1391 | 1392 | if (color.red() != null)) { 1393 | // RGB Label 1394 | var rgb = String(Math.ceil(color.red().toFixed(2) * 255)) + ", " + String(Math.ceil(color.green().toFixed(2) * 255)) + ", " + String(Math.ceil(color.blue().toFixed(2) * 255)) + ", " + String(color.alpha().toFixed(2)); 1395 | 1396 | var rgbLabel = com.getflourish.common.addTextLayer(group, rgb); 1397 | rgbLabel.frame().setY(hexLabel.frame().y() + 14 + padding); 1398 | rgbLabel.adjustFrameToFit(); 1399 | rgbLabel.frame().setX(8); 1400 | rgbLabel.setName("RGB Label"); 1401 | } 1402 | 1403 | // Shadow 1404 | var shadow = labelBG.style().addStylePartOfType(2); 1405 | 1406 | black.alpha = 0.2; 1407 | shadow.setOffsetX(0); 1408 | shadow.setOffsetY(2); 1409 | shadow.setBlurRadius(3); 1410 | shadow.setSpread(0); 1411 | 1412 | shadow.setColor(black); 1413 | 1414 | 1415 | 1416 | return group; 1417 | 1418 | }, 1419 | addColorShape: function (artboard, color, width, height) { 1420 | // add layer 1421 | var layer = com.getflourish.common.addRectangleLayer(artboard) 1422 | layer.frame().setWidth(width); 1423 | layer.frame().setHeight(height); 1424 | layer.style().addStylePartOfType(0); 1425 | layer.style().fills()[0].setFillType(0); 1426 | layer.style().fills()[0].setColor(color); 1427 | 1428 | return layer; 1429 | }, 1430 | addGradientShape: function (artboard, gradient, width, height) { 1431 | // add layer 1432 | var layer = com.getflourish.common.addRectangleLayer(artboard) 1433 | layer.frame().setWidth(width); 1434 | layer.frame().setHeight(height); 1435 | layer.style().addStylePartOfType(0); 1436 | layer.style().fills()[0].setFillType(1); 1437 | layer.style().fills()[0].setGradient(gradient); 1438 | 1439 | return layer; 1440 | }, 1441 | getColorOf: function (_layer) { 1442 | var color = null, 1443 | fill = null, 1444 | style = null, 1445 | fills = null, 1446 | className = String(_layer.className()); 1447 | 1448 | switch (className) { 1449 | 1450 | case "MSTextLayer": 1451 | 1452 | try { 1453 | // get the text color 1454 | color = _layer.textColor(); 1455 | 1456 | // check if the text layer has a fill color 1457 | fill = layer.style() 1458 | .fills() 1459 | .firstObject(); 1460 | if (fill != undefined && fill.isEnabled()) { 1461 | color = fill.color(); 1462 | } 1463 | } catch (error) { 1464 | // log(error); 1465 | } 1466 | break; 1467 | case "MSOvalShape": 1468 | case "MSShapeGroup": 1469 | case "MSShapePathLayer": 1470 | case "MSRectangleShape": 1471 | try { 1472 | // try to determin the fill type 1473 | style = _layer.style(); 1474 | if (style.fills()) { 1475 | fills = style.fills(); 1476 | if (fills.count() > 0) { 1477 | fill = fills.firstObject(); 1478 | if (fill != null && fill.isEnabled()) { 1479 | if (fill.fillType() == 0) { 1480 | // solid color 1481 | color = fill.color(); 1482 | } else { 1483 | // any other fill 1484 | color = fill; 1485 | } 1486 | } 1487 | } 1488 | } 1489 | } catch (error) { 1490 | log("could not get color"); 1491 | } 1492 | break; 1493 | } 1494 | return color; 1495 | }, 1496 | getNameForColor: function (hexColor) { 1497 | var predicate = NSPredicate.predicateWithFormat("name == %@", "Color Swatch #" + hexColor); 1498 | 1499 | // get the color artboard 1500 | var result = doc.currentPage().children().filteredArrayUsingPredicate(predicate); 1501 | 1502 | if (result.count() > 0 && result[0].parentGroup().name() != "Untitled Color Swatch") { 1503 | return result[0].parentGroup() 1504 | .name(); 1505 | } else { 1506 | return null; 1507 | } 1508 | }, 1509 | hasColorInventory: function () { 1510 | // setup predicate 1511 | var predicate = NSPredicate.predicateWithFormat("name == %@", com.getflourish.config.colorInventoryName); 1512 | 1513 | // get the color artboard 1514 | var result = doc.currentPage().artboards().filteredArrayUsingPredicate(predicate); 1515 | if (result.count() != 0) { 1516 | return true; 1517 | } else { 1518 | return false; 1519 | } 1520 | }, 1521 | getColorInventory: function () { 1522 | // setup predicate 1523 | var predicate = NSPredicate.predicateWithFormat("name == %@", com.getflourish.config.colorInventoryName); 1524 | 1525 | // get the color artboard 1526 | var result = doc.currentPage().artboards().filteredArrayUsingPredicate(predicate); 1527 | if (result.count() != 0) { 1528 | return result.objectAtIndex(0); 1529 | } else { 1530 | return null; 1531 | } 1532 | }, 1533 | getTextColorOf: function (layer) { 1534 | var color = null; 1535 | 1536 | // check if layer is a text layer 1537 | if (layer.className() == "MSTextLayer") { 1538 | 1539 | // get the text color 1540 | color = layer.textColor(); 1541 | 1542 | // check if the text layer has a fill color 1543 | var fill = layer.style() 1544 | .fills() 1545 | .firstObject(); 1546 | if (fill != undefined && fill.isEnabled()) { 1547 | color = fill.color(); 1548 | } 1549 | } 1550 | return color; 1551 | }, 1552 | // Convert Hexadecimal value to RGB 1553 | // http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb 1554 | hexToRgb: function (hex) { 1555 | // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") 1556 | var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 1557 | hex = hex.replace(shorthandRegex, function (m, r, g, b) { 1558 | return r + r + g + g + b + b 1559 | }) 1560 | 1561 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) 1562 | return result ? { 1563 | r: parseInt(result[1], 16), 1564 | g: parseInt(result[2], 16), 1565 | b: parseInt(result[3], 16) 1566 | } : null 1567 | }, 1568 | rgbToHex: function (r, g, b) { 1569 | 1570 | function componentToHex(c) { 1571 | var hex = c.toString(16); 1572 | return hex.length == 1 ? "0" + hex : hex; 1573 | } 1574 | 1575 | if (r && g && b) { 1576 | return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); 1577 | } else { 1578 | return "ERROR" 1579 | } 1580 | 1581 | } 1582 | 1583 | } 1584 | 1585 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 1586 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 1587 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 1588 | 1589 | 1590 | com.getflourish.clipboard = { 1591 | // store the pasetboard object 1592 | pasteBoard: null, 1593 | 1594 | // save the pasteboard object 1595 | init: function () { 1596 | this.pasteBoard = NSPasteboard.generalPasteboard(); 1597 | }, 1598 | // set the clipboard to the given text 1599 | set: function (text) { 1600 | if (typeof text === 'undefined') return null; 1601 | 1602 | if (!this.pasteBoard) 1603 | this.init(); 1604 | 1605 | this.pasteBoard.declareTypes_owner([NSPasteboardTypeString], null); 1606 | this.pasteBoard.setString_forType(text, NSPasteboardTypeString); 1607 | 1608 | return true; 1609 | }, 1610 | // get text from the clipbaoard 1611 | get: function () { 1612 | if (!this.pasteBoard) 1613 | this.init(); 1614 | 1615 | var text = this.pasteBoard.stringForType(NSPasteboardTypeString); 1616 | 1617 | return text.toString(); 1618 | } 1619 | }; 1620 | 1621 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 1622 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 1623 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 1624 | 1625 | com.getflourish.view = { 1626 | centerTo: function (layer) { 1627 | var selected_object = layer; 1628 | var view = doc.documentWindow(); 1629 | view.centerInBounds(selected_object.absoluteRect().rect()) 1630 | view.scheduleRedraw() 1631 | }, 1632 | zoomTo: function (layer) { 1633 | var selected_object = layer; 1634 | var view = doc.contentDrawView(); 1635 | view.zoomToFitRect(selected_object.absoluteRect().rect()) 1636 | view.scheduleRedraw() 1637 | }, 1638 | zoomToLayers: function (layers) { 1639 | if (layers.length) bounds = MSLayer.alignmentRectForLayers(layers); 1640 | var view = doc.contentDrawView(); 1641 | view.zoomToFitRect(bounds) 1642 | view.scheduleRedraw() 1643 | } 1644 | } 1645 | 1646 | 1647 | // UTILS //////////////////////////////////////////////////////////////////////////////////////////////////// 1648 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 1649 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 1650 | 1651 | 1652 | com.getflourish.utils = { 1653 | 1654 | selectLayers: function (layers) { 1655 | doc.currentPage().changeSelectionBySelectingLayers(layers) 1656 | }, 1657 | 1658 | deselectAllLayers: function () { 1659 | com.getflourish.utils.selectLayers([]) 1660 | }, 1661 | openInFinder: function(path) { 1662 | var finderTask = [[NSTask alloc] init]; 1663 | 1664 | [finderTask setLaunchPath:"/usr/bin/open"]; 1665 | finderTask.setArguments_(NSArray.arrayWithArray_(['-R', path])); 1666 | [finderTask launch]; 1667 | }, 1668 | sendAction: function (commandToPerform) { 1669 | try { 1670 | log("sending action") 1671 | [NSApp sendAction: commandToPerform to: nil from: doc]; 1672 | } catch (e) { 1673 | com.getflourish.log(e) 1674 | } 1675 | }, 1676 | sendToBack: function () { 1677 | log("send to back") 1678 | com.getflourish.utils.sendAction('moveToBack:'); 1679 | }, 1680 | sendBackward: function () { 1681 | com.getflourish.utils.sendAction('moveBackward:'); 1682 | }, 1683 | sendDelete: function () { 1684 | com.getflourish.utils.sendAction('delete:'); 1685 | }, 1686 | sendAlignBottom: function () { 1687 | com.getflourish.utils.sendAction('alignLayersBottom:'); 1688 | }, 1689 | sendAlignLeft: function () { 1690 | com.getflourish.utils.sendAction('alignLayersLeft:'); 1691 | }, 1692 | sendAlignHorizontally: function () { 1693 | com.getflourish.utils.sendAction('alignLayersCenter:'); 1694 | }, 1695 | sendAlignVertically: function () { 1696 | com.getflourish.utils.sendAction('alignLayersMiddle:'); 1697 | }, 1698 | sendAlignRight: function () { 1699 | com.getflourish.utils.sendAction('alignLayersRight:'); 1700 | }, 1701 | sendAlignTop: function () { 1702 | com.getflourish.utils.sendAction('alignLayersTop:'); 1703 | }, 1704 | sendDistributeVertically: function () { 1705 | com.getflourish.utils.sendAction('distributeVertically:'); 1706 | }, 1707 | sendDistributeHorizontally: function () { 1708 | com.getflourish.utils.sendAction('distributeHorizontally:'); 1709 | }, 1710 | sendForward: function () { 1711 | com.getflourish.utils.sendAction('moveForward:'); 1712 | }, 1713 | sendPasteInPlace: function () { 1714 | com.getflourish.utils.sendAction('pasteInPlace:'); 1715 | }, 1716 | arrayFromImmutableArray: function (nsarray) { 1717 | var output = []; 1718 | // convert immutable NSArray to mutable array 1719 | for (var i = 0; i < nsarray.count(); i++) { 1720 | output.push(nsarray[i]); 1721 | } 1722 | return output; 1723 | }, 1724 | naturalSort: function (a, b) { 1725 | /* 1726 | * Natural Sort algorithm for Javascript - Version 0.7 - Released under MIT license 1727 | * Author: Jim Palmer (based on chunking idea from Dave Koelle) 1728 | */ 1729 | 1730 | var re = /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi, 1731 | sre = /(^[ ]*|[ ]*$)/g, 1732 | dre = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/, 1733 | hre = /^0x[0-9a-f]+$/i, 1734 | ore = /^0/, 1735 | i = function(s) { return com.getflourish.utils.naturalSort.insensitive && (''+s).toLowerCase() || ''+s }, 1736 | // convert all to strings strip whitespace 1737 | x = i(a).replace(sre, '') || '', 1738 | y = i(b).replace(sre, '') || '', 1739 | // chunk/tokenize 1740 | xN = x.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'), 1741 | yN = y.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'), 1742 | // numeric, hex or date detection 1743 | xD = parseInt(x.match(hre)) || (xN.length != 1 && x.match(dre) && Date.parse(x)), 1744 | yD = parseInt(y.match(hre)) || xD && y.match(dre) && Date.parse(y) || null, 1745 | oFxNcL, oFyNcL; 1746 | // first try and sort Hex codes or Dates 1747 | if (yD) 1748 | if ( xD < yD ) return -1; 1749 | else if ( xD > yD ) return 1; 1750 | // natural sorting through split numeric strings and default strings 1751 | for(var cLoc=0, numS=Math.max(xN.length, yN.length); cLoc < numS; cLoc++) { 1752 | // find floats not starting with '0', string or 0 if not defined (Clint Priest) 1753 | oFxNcL = !(xN[cLoc] || '').match(ore) && parseFloat(xN[cLoc]) || xN[cLoc] || 0; 1754 | oFyNcL = !(yN[cLoc] || '').match(ore) && parseFloat(yN[cLoc]) || yN[cLoc] || 0; 1755 | // handle numeric vs string comparison - number < string - (Kyle Adams) 1756 | if (isNaN(oFxNcL) !== isNaN(oFyNcL)) { return (isNaN(oFxNcL)) ? 1 : -1; } 1757 | // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2' 1758 | else if (typeof oFxNcL !== typeof oFyNcL) { 1759 | oFxNcL += ''; 1760 | oFyNcL += ''; 1761 | } 1762 | if (oFxNcL < oFyNcL) return -1; 1763 | if (oFxNcL > oFyNcL) return 1; 1764 | } 1765 | return 0; 1766 | 1767 | }, 1768 | sortName: function (_a, _b) { 1769 | var a = _a.name; 1770 | var b = _b.name; 1771 | 1772 | return com.getflourish.utils.naturalSort(a, b); 1773 | } 1774 | } 1775 | 1776 | com.getflourish.css = { 1777 | /** 1778 | * createRuleSetStr by Tyler Gaw https://gist.github.com/tylergaw/adc3d6ad044f5afac446 1779 | **/ 1780 | 1781 | createRuleSetStr: function (layer) { 1782 | 1783 | var baseunit = com.getflourish.css.getBaseUnit(); 1784 | 1785 | var str = ''; 1786 | 1787 | var selector = "." + com.getflourish.css.getClassName(layer); 1788 | 1789 | // get the css attributes from sketch, yay! 1790 | var attrs = layer.CSSAttributes(); 1791 | 1792 | str += selector + ' {'; 1793 | 1794 | for (var i = 0; i < attrs.count(); i += 1) { 1795 | // raw css declaration provided by Sketch 1796 | var declaration = attrs.objectAtIndex(i); 1797 | 1798 | // formatted declaration 1799 | // for example font-size and line height will be expressed in relative units instead of pixels 1800 | var property = declaration.substring(0, declaration.indexOf(":")); 1801 | var value = declaration.substring(declaration.indexOf(":") + 1, declaration.indexOf("px")); 1802 | 1803 | switch (property) { 1804 | case "font-size": 1805 | if (com.getflourish.css.formatOptions.useRelativeFontSize) { 1806 | value = value / baseunit; 1807 | declaration = property + ": " + value + "rem;"; 1808 | } 1809 | break; 1810 | case "line-height": 1811 | if (com.getflourish.css.formatOptions.useRelativeLineHeight) { 1812 | value = com.getflourish.css.getRelativeLineHeight(layer); 1813 | declaration = property + ": " + value + "em;"; 1814 | } 1815 | break; 1816 | // case "color": 1817 | // if (com.getflourish.css.formatOptions.useSassColorVariables) { 1818 | // var value = declaration.substring(declaration.indexOf("#"), declaration.indexOf(";")); 1819 | // value = com.getflourish.colors.getNameForColor(value) 1820 | // declaration = property + ": " + value + ";"; 1821 | // log(declaration) 1822 | // } 1823 | // break; 1824 | default: 1825 | break; 1826 | } 1827 | 1828 | if (declaration.indexOf('/*') < 0) { 1829 | str += '\n\t' + declaration; 1830 | } 1831 | } 1832 | str += '\n}'; 1833 | return str; 1834 | }, 1835 | formatOptions: { 1836 | useRelativeFontSize: true, 1837 | useRelativeLineHeight: true, 1838 | useSassColorVariables: true, 1839 | splitFontFamily: true 1840 | }, 1841 | getBaseUnit: function () { 1842 | // access the base unit for formatting purposes 1843 | var persistent = [[NSThread mainThread] threadDictionary]; 1844 | 1845 | if (persistent["com.getflourish.baseunit"] == null) { 1846 | var value = parseFloat([doc askForUserInput: "Base unit:" 1847 | initialValue: 16]); 1848 | persistent["com.getflourish.baseunit"] = value; 1849 | } 1850 | 1851 | var baseunit = persistent["com.getflourish.baseunit"]; 1852 | return baseunit; 1853 | }, 1854 | getClassName: function (layer) { 1855 | // get the layer name 1856 | var name = layer.name().toLowerCase(); 1857 | 1858 | // replace special characters by a dash 1859 | var regex = new RegExp("[/:(). ]", "g"); 1860 | name = name.replace(regex, "-"); 1861 | 1862 | // construct the css selector 1863 | var selector = name; 1864 | 1865 | return selector; 1866 | }, 1867 | getRelativeFontSize: function (textLayer, baseunit) { 1868 | 1869 | var relativeFontSize = null; 1870 | 1871 | if (layer.className() == "MSTextLayer") { 1872 | var fontSize = layer.fontSize(); 1873 | relativeFontSize = fontSize / baseunit; 1874 | } 1875 | return relativeFontSize; 1876 | }, 1877 | getRelativeLineHeight: function (textLayer) { 1878 | 1879 | var relativeLineHeight = null; 1880 | 1881 | if (textLayer.className() == "MSTextLayer") { 1882 | var fontSize = textLayer.fontSize(); 1883 | var lineHeight = textLayer.lineHeight(); 1884 | relativeLineHeight = (lineHeight / fontSize) 1885 | .toFixed(2); 1886 | } 1887 | return relativeLineHeight; 1888 | }, 1889 | 1890 | generateStyleSheet: function (layers) { 1891 | var stylesheet = ''; 1892 | var stylesheet = "/* Text Styles from Sketch */"; 1893 | 1894 | for (var i = 0; i < layers.count(); i++) { 1895 | 1896 | var layer = layers.objectAtIndex(i); 1897 | 1898 | // only get CSS for text layers 1899 | if ([layer isKindOfClass: MSTextLayer] && layer.style() 1900 | .sharedObjectID() != null) { 1901 | stylesheet += '\n\n' + com.getflourish.css.createRuleSetStr(layer); 1902 | } 1903 | } 1904 | return stylesheet; 1905 | }, 1906 | generateMarkup: function (layers) { 1907 | var markup = ''; 1908 | markup += '\n'; 1909 | markup += ''; 1910 | markup += '\n'; 1911 | markup += ''; 1912 | markup += '\n\t'; 1913 | markup += ''; 1914 | markup += '\n\t'; 1915 | markup += 'Style Inventory'; 1916 | markup += '\n\t'; 1917 | markup += ''; 1918 | markup += '\n\t'; 1919 | markup += ''; 1930 | markup += '\n'; 1931 | markup += ''; 1932 | 1933 | markup += '\n'; 1934 | markup += ''; 1935 | markup += '\n\t'; 1936 | markup += '' + new Date() + ''; 1937 | markup += '\n\t'; 1938 | // markup += '

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Temporibus fugit voluptatem aliquam dolore ipsa, ut sapiente? Sunt unde eius cumque quia eos. Rem inventore possimus error sapiente atque, id, nisi!

'; 1939 | for (var i = 0; i < layers.count(); i++) { 1940 | 1941 | var layer = layers.objectAtIndex(i); 1942 | 1943 | // only get CSS for text layers 1944 | if ([layer isKindOfClass: MSTextLayer] && layer.style() 1945 | .sharedObjectID() != null) { 1946 | // markup += "" + com.getflourish.css.createRuleSetStr(layer) + ""; 1947 | markup += '\n\t' + com.getflourish.css.createMarkupStr(layer); 1948 | markup += '\n\t'; 1949 | markup += '\n\t\t' + com.getflourish.css.createRuleSetStr(layer) + '\n\t'; 1950 | markup += '\n'; 1951 | } 1952 | } 1953 | markup += '\n'; 1954 | // markup += ''; 1957 | markup += ''; 1958 | markup += '\n'; 1959 | markup += ''; 1960 | return markup; 1961 | }, 1962 | createMarkupStr: function (layer) { 1963 | var selector = com.getflourish.css.getClassName(layer); 1964 | var html = "\

" + layer.name() + "

"; 1965 | return html; 1966 | } 1967 | } 1968 | 1969 | com.getflourish.layers = { 1970 | alignLayersHorizontally: function (layers) { 1971 | if (layers.count() == 1)  { 1972 | var layer = layers[0]; 1973 | var parent = layer.parentGroup(); 1974 | var midX = parent.absoluteRect() 1975 | .midX(); 1976 | var targetX = Math.ceil(midX - layer.frame() 1977 | .width() / 2); 1978 | layer.absoluteRect() 1979 | .setX(targetX); 1980 | } else if (layers.count() > 1) { 1981 | for (var i = 0; i < layers.count(); i++) { 1982 | 1983 | } 1984 | } 1985 | }, 1986 | alignLayersVertically: function (layers) { 1987 | if (layers.count() == 1)  { 1988 | var layer = layers[0]; 1989 | var parent = layer.parentGroup(); 1990 | var midY = parent.absoluteRect() 1991 | .midY(); 1992 | var targetY = Math.ceil(midY - layer.frame() 1993 | .height() / 2); 1994 | layer.absoluteRect() 1995 | .setY(targetY); 1996 | } else if (layers.count() > 1) { 1997 | for (var i = 0; i < layers.count(); i++) { 1998 | 1999 | } 2000 | } 2001 | }, 2002 | areEqual: function (layer1, layer2) { 2003 | return layer1.objectID() === layer2.objectID(); 2004 | }, 2005 | /** 2006 | * Returns the x position of the rightmost artboard 2007 | * @return {artboard} artboard 2008 | */ 2009 | getRightmostArtboard: function () { 2010 | var rma = null; 2011 | 2012 | var scope = doc.currentPage().artboards(); 2013 | if(scope.count() > 0) { 2014 | var maxX = [scope valueForKeyPath: "@max.frame.maxX"]; 2015 | var predicate = NSPredicate.predicateWithFormat("frame.maxX == %@", maxX); 2016 | var artboard = scope.filteredArrayUsingPredicate(predicate); 2017 | rma = artboard[0]; 2018 | } 2019 | 2020 | return rma; 2021 | }, 2022 | getLayersSortedByName: function (layers) { 2023 | var layer; 2024 | var layersMeta = []; 2025 | 2026 | for (var i = 0; i < layers.count(); i++) { 2027 | 2028 | layer = layers.objectAtIndex(i); 2029 | 2030 | layersMeta.push({ 2031 | "name": layer.name(), 2032 | "layer": layer 2033 | }); 2034 | } 2035 | 2036 | return layersMeta.sort(com.getflourish.utils.sortName); 2037 | }, 2038 | getLayersByTextStyle: function (textStyle, scope) { 2039 | var predicate = NSPredicate.predicateWithFormat("(style.textStyle != NULL) && (FUNCTION(style.textStyle, 'isEqual:', %@) == YES)", textStyle); 2040 | // query page layers 2041 | var queryResult = scope.filteredArrayUsingPredicate(predicate); 2042 | 2043 | return queryResult; 2044 | }, 2045 | getLayersByLayerStyle: function (layerStyle, scope) { 2046 | var predicate = NSPredicate.predicateWithFormat("(style.fills != NULL) && (FUNCTION(style.fills, 'isEqual:', %@) == YES)", layerStyle.fills()); 2047 | // query page layers 2048 | var queryResult = scope.filteredArrayUsingPredicate(predicate); 2049 | 2050 | return queryResult; 2051 | }, 2052 | getLayersBySize: function (referenceLayer, scope) { 2053 | var predicate = NSPredicate.predicateWithFormat("(frame != NULL) && frame.width == %@ && frame.height == %@ && className == %@ && children.count == 0", referenceLayer.frame().width(), referenceLayer.frame().height()); 2054 | var queryResult = scope.filteredArrayUsingPredicate(predicate); 2055 | 2056 | return queryResult; 2057 | }, 2058 | getEqualLayers: function (referenceLayer, scope) { 2059 | var predicate = NSPredicate.predicateWithFormat("(FUNCTION(self, 'isEqual:', %@) == YES)", context.selection[0]); 2060 | var queryResult = scope.filteredArrayUsingPredicate(predicate); 2061 | return queryResult; 2062 | }, 2063 | select: function (layers) { 2064 | // deselect first 2065 | com.getflourish.utils.deselectAllLayers() 2066 | 2067 | 2068 | // then select all layers from the original selection 2069 | for (var i = 0; i < layers.count(); i++) { 2070 | layers[i].isSelected = true 2071 | } 2072 | }, 2073 | selectLayersByColor: function (referenceLayer, scope) { 2074 | var color = null, 2075 | results = 0, 2076 | referenceColor = null, 2077 | selected = null; 2078 | 2079 | // deselect 2080 | com.getflourish.utils.deselectAllLayers() 2081 | 2082 | // get color of selected layer 2083 | color = com.getflourish.colors.getColorOf(referenceLayer); 2084 | 2085 | log(color) 2086 | 2087 | var ftype = 0; 2088 | 2089 | try { 2090 | ftype = color.fillType(); 2091 | } catch (error) {} 2092 | 2093 | // init predicate 2094 | var predicate = null; 2095 | 2096 | 2097 | // use appropriate predicate depending on the selected layer class 2098 | 2099 | switch (String(referenceLayer.className())) { 2100 | case "MSTextLayer": 2101 | predicate = NSPredicate.predicateWithFormat("textColor == %@", color); 2102 | break; 2103 | case "MSOvalShape": 2104 | case "MSShapeGroup": 2105 | case "MSShapePathLayer": 2106 | case "MSRectangleShape": 2107 | // check if color is solid 2108 | if (ftype != 0) { 2109 | predicate = NSPredicate.predicateWithFormat("(style.fills != NULL) && SUBQUERY(style.fills, $fill, $fill isEqual:%@).@count > 0", color); 2110 | } else { 2111 | predicate = NSPredicate.predicateWithFormat("(style.fills != NULL) && SUBQUERY(style.fills, $fill, $fill.fillType == 0).@count > 0 && SUBQUERY(style.fills, $fill, $fill.color == %@).@count > 0", color); 2112 | } 2113 | break; 2114 | default: 2115 | 2116 | break; 2117 | } 2118 | 2119 | // query page layers 2120 | var queryResult = scope.filteredArrayUsingPredicate(predicate); 2121 | 2122 | 2123 | // select all results 2124 | com.getflourish.utils.selectLayers(queryResult); 2125 | 2126 | return queryResult; 2127 | }, 2128 | selectLayersByBorderColor: function (referenceLayer, scope) { 2129 | var color = null, 2130 | results = 0, 2131 | referenceColor = null, 2132 | selected = null; 2133 | 2134 | // deselect 2135 | com.getflourish.utils.deselectAllLayers() 2136 | 2137 | // get color of selected layer 2138 | color = com.getflourish.colors.getColorOf(referenceLayer); 2139 | 2140 | var predicate = NSPredicate.predicateWithFormat("(style.border != NULL) && style.border.color == %@", color); 2141 | 2142 | // query page layers 2143 | var queryResult = scope.filteredArrayUsingPredicate(predicate); 2144 | 2145 | // select all results 2146 | com.getflourish.utils.selectLayers(queryResult); 2147 | 2148 | return queryResult; 2149 | }, 2150 | selectLayersByName: function (referenceName, scope) { 2151 | 2152 | // deselect 2153 | com.getflourish.utils.deselectAllLayers() 2154 | 2155 | // init predicate 2156 | var predicate = null; 2157 | 2158 | // define base name 2159 | var baseName = referenceName; 2160 | 2161 | // Get the last part of the layer name and check if it is an appended number 2162 | var end = referenceName.lastIndexOf(" "); 2163 | if (end != -1) { 2164 | var endString = referenceName.substring(end); 2165 | // Check if the last part is a number 2166 | var numberRegex = new RegExp("[0-9]"); 2167 | if (endString.match(numberRegex) != null) { 2168 | // Found a number, so the search string needs to be trimmed 2169 | baseName = referenceName.substring(0, end); 2170 | } 2171 | } 2172 | 2173 | // setup predicate 2174 | var predicate = NSPredicate.predicateWithFormat("name beginswith[c] %@", baseName); 2175 | 2176 | // query page layers 2177 | var result = scope.filteredArrayUsingPredicate(predicate); 2178 | 2179 | // select all results 2180 | com.getflourish.utils.selectLayers(result); 2181 | 2182 | return result; 2183 | }, 2184 | selectLayersBySize: function (width, height, scope) { 2185 | 2186 | // deselect 2187 | com.getflourish.utils.deselectAllLayers() 2188 | 2189 | // init predicate 2190 | var predicate = null; 2191 | 2192 | // setup predicate 2193 | var predicate = NSPredicate.predicateWithFormat("frame.width == %@ AND frame.height == %@ AND className == %@", width, height, "MSShapePathLayer"); 2194 | 2195 | // query page layers 2196 | var result = scope.filteredArrayUsingPredicate(predicate); 2197 | 2198 | // select all results 2199 | com.getflourish.utils.selectLayers(result); 2200 | 2201 | return result; 2202 | }, 2203 | selectLayersByString: function (referenceString, scope) { 2204 | 2205 | // deselect 2206 | com.getflourish.utils.deselectAllLayers() 2207 | 2208 | // init predicate 2209 | var predicate = null; 2210 | 2211 | // setup predicate 2212 | var predicate = NSPredicate.predicateWithFormat("stringValue == %@", referenceString); 2213 | 2214 | // query page layers 2215 | var result = scope.filteredArrayUsingPredicate(predicate); 2216 | 2217 | // select all results 2218 | com.getflourish.utils.selectLayers(result); 2219 | 2220 | return result; 2221 | }, 2222 | selectLayersByTextStyle: function (textStyle, scope) { 2223 | 2224 | var predicate = NSPredicate.predicateWithFormat("(style.textStyle != NULL) && (FUNCTION(style.textStyle.attributes, 'isEqual:', %@, nil) == YES)", textStyle.attributes()); 2225 | 2226 | // query page layers 2227 | var queryResult = scope.filteredArrayUsingPredicate(predicate); 2228 | 2229 | // select all results 2230 | com.getflourish.utils.selectLayers(queryResult); 2231 | 2232 | return queryResult; 2233 | }, 2234 | selectParentGroup: function (layer) { 2235 | 2236 | // Selects the parent group of a layer (option cmd g) 2237 | 2238 | // get parent 2239 | var parent = layer.parentGroup(); 2240 | 2241 | // deselect all layers 2242 | com.getflourish.utils.deselectAllLayers() 2243 | 2244 | 2245 | // select parent group 2246 | parent.isSelected = true 2247 | 2248 | }, 2249 | sortIndices: function (array) { 2250 | // Orders the array in the layer list 2251 | for (var i = 0; i < array.length - 1; i++) { 2252 | 2253 | // get two array 2254 | var a = array[i]; 2255 | var b = array[i + 1]; 2256 | 2257 | // check if both layers are in the same group 2258 | var parent_a = a.parentGroup(); 2259 | var parent_b = b.parentGroup(); 2260 | 2261 | if (parent_a == parent_b) { 2262 | var parent = parent_a; 2263 | 2264 | if (parent.indexOfLayer(a) > parent.indexOfLayer(b)) { 2265 | // swap index 2266 | com.getflourish.layers.swapIndex(b, a); 2267 | } 2268 | } else { 2269 | doc.showMessage("couldn’t sort indices"); 2270 | } 2271 | } 2272 | }, 2273 | swapIndex: function (layer1, layer2) { 2274 | var a = layer1; 2275 | var b = layer2; 2276 | 2277 | // check if both layers are in the same group 2278 | var parent_a = null; 2279 | var parent_b = null; 2280 | 2281 | try { 2282 | parent_a = a.parentGroup(); 2283 | parent_b = b.parentGroup(); 2284 | } catch (error) {} 2285 | 2286 | if (parent_a == parent_b) { 2287 | 2288 | var parent = parent_a; 2289 | 2290 | // deselect all layers 2291 | doc.currentPage() 2292 | .deselectAllLayers(); 2293 | 2294 | // select b 2295 | a.isSelected = true 2296 | 2297 | var steps = Math.abs(parent.indexOfLayer(b) - parent.indexOfLayer(a)); 2298 | 2299 | for (var i = 0; i < steps; i++) { 2300 | com.getflourish.utils.sendForward(); 2301 | } 2302 | } else { 2303 | doc.showMessage("Please select layers of the same group") 2304 | } 2305 | }, 2306 | reverseLayerOrder: function (layers) { 2307 | // Reverses the layer order 2308 | for (var i = 0; i < layers.length; i++) { 2309 | var first = layers[i].layer; 2310 | var next = layers[layers.length - 1].layer; 2311 | 2312 | com.getflourish.layers.swapIndex(first, next); 2313 | } 2314 | } 2315 | } 2316 | 2317 | com.getflourish.textStyleInventory = { 2318 | 2319 | generate: function (_virtual) { 2320 | 2321 | // start tracking the time 2322 | var startTime = new Date(); 2323 | 2324 | var virtual = _virtual || false; 2325 | // ask for base unit 2326 | if (com.getflourish.css.formatOptions.useRelativeFontSize == true) var baseunit = com.getflourish.css.getBaseUnit(); 2327 | 2328 | // get defined text styles 2329 | var definedTextStyles = com.getflourish.textStyleInventory.analyseTextStyles(); 2330 | 2331 | // for large files, sketch crashes after adding the style sheet page (a second time) 2332 | var styleSheetPage = com.getflourish.common.getStyleSheetPage(); 2333 | 2334 | var artboard = com.getflourish.common.getArtboardByPageAndName(styleSheetPage, "Text Styles Inventory"); 2335 | 2336 | artboard.removeAllLayers(); 2337 | 2338 | var bg = com.getflourish.common.addCheckeredBackground(artboard); 2339 | 2340 | // if (virtual) com.getflourish.textStyleInventory.createTextStylesVirtual(artboard, definedTextStyles); 2341 | 2342 | com.getflourish.textStyleInventory.createTextStyles(artboard, definedTextStyles); 2343 | 2344 | // resize background 2345 | bg.frame().setWidth(artboard.frame().width()) 2346 | bg.frame().setHeight(artboard.frame().height()) 2347 | 2348 | com.getflourish.utils.deselectAllLayers() 2349 | 2350 | bg.isSelected = true 2351 | 2352 | com.getflourish.utils.sendToBack(); 2353 | com.getflourish.utils.deselectAllLayers() 2354 | // com.getflourish.textStyleInventory.exportStyles(artboard, path); 2355 | 2356 | // Feedback 2357 | var execTime = (new Date() - startTime) / 1000; 2358 | doc.showMessage("Generated Text Styles in " + execTime + " s"); 2359 | 2360 | return artboard; 2361 | 2362 | }, 2363 | 2364 | analyseTextStyles: function ()  { 2365 | var definedTextStyles = []; 2366 | var styles = doc.documentData().layerTextStyles().objects(); 2367 | 2368 | for (var i = 0; i < styles.count(); i++) { 2369 | var style = styles.objectAtIndex(i); 2370 | var attributes = style.style() 2371 | .textStyle() 2372 | .attributes(); 2373 | var textStyle = style 2374 | if (!com.getflourish.common.isIncluded(definedTextStyles, textStyle) && style.name().indexOf("Style Inventory") == -1) { 2375 | definedTextStyles.push({ 2376 | "attributes": attributes, 2377 | "textStyle": style, 2378 | "name": style.name() 2379 | }); 2380 | } 2381 | } 2382 | // sort by font size 2383 | definedTextStyles.sort(com.getflourish.textStyleInventory.compareTextStyleFontSize); 2384 | return definedTextStyles; 2385 | }, 2386 | 2387 | compareTextStyleFontSize: function (b, a) { 2388 | if (a.attributes.NSFont.fontDescriptor().objectForKey(NSFontSizeAttribute) < b.attributes.NSFont.fontDescriptor().objectForKey(NSFontSizeAttribute)) 2389 | return -1; 2390 | if (a.attributes.NSFont.fontDescriptor().objectForKey(NSFontSizeAttribute) > b.attributes.NSFont.fontDescriptor().objectForKey(NSFontSizeAttribute)) 2391 | return 1; 2392 | return 0; 2393 | }, 2394 | createTextStylesVirtual: function (artboard, definedTextStyles) { 2395 | 2396 | 2397 | for (var i = 0; i < definedTextStyles.length; i++) { 2398 | 2399 | var definedTextStyle = definedTextStyles[i]; 2400 | var textLayer = com.getflourish.common.addTextLayer(artboard, ""); 2401 | 2402 | textLayer.setStyle(definedTextStyle.textStyle.newInstance()); 2403 | textLayer.setName(definedTextStyle.name); 2404 | } 2405 | }, 2406 | createTextStyles: function (artboard, definedTextStyles) { 2407 | 2408 | var has = com.getflourish.colors.hasColorInventory(); 2409 | var top = 30; 2410 | var padding = 30; 2411 | var margin = 20; 2412 | var maxWidth = 0; 2413 | var colorName; 2414 | 2415 | for (var i = 0; i < definedTextStyles.length; i++) { 2416 | 2417 | var $grey = MSImmutableColor.colorWithSVGString("#000000"); 2418 | $grey.alpha = 0.5; 2419 | 2420 | var definedTextStyle = definedTextStyles[i]; 2421 | var textLayer = com.getflourish.common.addTextLayer(artboard, ""); 2422 | 2423 | var fontString = String(definedTextStyle.attributes.NSFont); 2424 | var font = fontString.substring(1, fontString.indexOf("pt.")); 2425 | var label = "" + font + "pt"; 2426 | 2427 | colorName = null; 2428 | 2429 | // Text layer holding the style name 2430 | var styleName = definedTextStyle.name; 2431 | var styleNameLayer = com.getflourish.common.addTextLayer(artboard, ""); 2432 | 2433 | textLayer.setTextAlignment(0); 2434 | textLayer.setStringValue(styleName); 2435 | textLayer.adjustFrameToFit(); 2436 | textLayer.setTextAlignment(0); 2437 | 2438 | textLayer.setStyle(definedTextStyle.textStyle.newInstance()); 2439 | textLayer.setTextAlignment(0); 2440 | textLayer.setName(definedTextStyle.name); 2441 | var theWidth = textLayer.frame() 2442 | .width(); 2443 | 2444 | var color = textLayer.textColor(); 2445 | 2446 | var hexColor = color.immutableModelObject().svgRepresentation(); 2447 | var rgb = String(Math.ceil(color.red() * 255)) + ", " + String(Math.ceil(color.green() * 255)) + ", " + String(Math.ceil(color.blue() * 255)) + ", " + String(color.alpha() 2448 | .toFixed(2)); 2449 | 2450 | var alpha = String(color.alpha() 2451 | .toFixed(2) * 100); 2452 | if (has == true) colorName = com.getflourish.colors.getNameForColor(hexColor); 2453 | 2454 | if (colorName == null) { 2455 | colorName = hexColor; 2456 | if (alpha != 100) colorName += " @" + alpha + "%" 2457 | } 2458 | 2459 | 2460 | var label = textLayer.fontPostscriptName() + "\n" + textLayer.fontSize() + "pt, " + colorName; 2461 | styleNameLayer.setStringValue(label); 2462 | styleNameLayer.setName(definedTextStyle.name); 2463 | styleNameLayer.setTextColor($grey); 2464 | styleNameLayer.setLineHeight(18); 2465 | 2466 | if (theWidth > maxWidth) maxWidth = theWidth; 2467 | textLayer.frame() 2468 | .setX(margin + 150); 2469 | textLayer.frame() 2470 | .setY(top); 2471 | 2472 | // position the style name layer 2473 | styleNameLayer.frame() 2474 | .setX(margin); 2475 | styleNameLayer.frame() 2476 | .setY(top); 2477 | 2478 | top += textLayer.frame() 2479 | .height(); 2480 | top += styleNameLayer.frame() 2481 | .height(); 2482 | } 2483 | 2484 | // centerView(textLayer) 2485 | 2486 | // Resize artboard and background to match the newly created text layers 2487 | var bounds; 2488 | 2489 | if (artboard.layers()) bounds = MSLayer.alignmentRectForLayers(artboard.layers()); 2490 | 2491 | var scope = artboard.children(); 2492 | var predicate = NSPredicate.predicateWithFormat("className == %@", "MSTextLayer"); 2493 | var result = scope.filteredArrayUsingPredicate(predicate); 2494 | 2495 | var maxWidth = [result valueForKeyPath: "@max.frame.width"]; 2496 | 2497 | artboard.frame().setWidth(maxWidth + 150 + 2 * margin); 2498 | 2499 | if (bounds) artboard.frame().setHeight(top + padding); 2500 | 2501 | 2502 | }, 2503 | export: function (artboard, path, virtual) { 2504 | 2505 | // ask for base unit 2506 | var baseunit = com.getflourish.css.getBaseUnit(); 2507 | 2508 | // Document path 2509 | if (doc.fileURL() != null) { 2510 | 2511 | // generate css 2512 | var styleSheetString = com.getflourish.css.generateStyleSheet(artboard.children()); 2513 | var markup = com.getflourish.css.generateMarkup(artboard.children()); 2514 | 2515 | // save css 2516 | com.getflourish.common.save_file_from_string(path + "typography.css", styleSheetString); 2517 | com.getflourish.common.save_file_from_string(path + "styles.html", markup); 2518 | 2519 | // refresh view 2520 | var view = doc.documentWindow(); 2521 | // view.refresh(); 2522 | 2523 | // create web view and show the generated html 2524 | if (virtual) com.getflourish.common.createWebView(path); 2525 | } else { 2526 | doc.showMessage("Export failed. Please save your file first.") 2527 | } 2528 | } 2529 | } 2530 | 2531 | /** 2532 | * [symbolInventory description] 2533 | * @type {Object} 2534 | */ 2535 | com.getflourish.symbolInventory = { 2536 | addSymbolsSheetToPage: function (page) { 2537 | 2538 | // get symbols 2539 | var symbols = doc.documentData().allSymbols() 2540 | 2541 | // add an artboard where all symbols will be placed 2542 | 2543 | var artboard = MSArtboardGroup.new(); 2544 | artboard.setName("Symbols"); 2545 | var frame = artboard.frame(); 2546 | frame.setX(0); 2547 | frame.setY(0); 2548 | frame.setConstrainProportions(false); 2549 | frame.setWidth(800) 2550 | frame.setHeight(500) 2551 | 2552 | // add artboard to page 2553 | page.addLayers([artboard]) 2554 | 2555 | for (var i = 0; i < symbols.count(); i++) { 2556 | 2557 | // get symbol reference 2558 | var symbolRef = symbols.objectAtIndex(i); 2559 | 2560 | // add layer from symbol to document 2561 | var symbol = symbols.objectAtIndex(i).newSymbolInstance(); 2562 | 2563 | // set name 2564 | symbol.setName(symbolRef.name()); 2565 | 2566 | // add symbol to page 2567 | artboard.addLayers([symbol]); 2568 | } 2569 | com.getflourish.symbolInventory.layout(artboard); 2570 | return artboard; 2571 | 2572 | }, 2573 | generate: function () { 2574 | 2575 | // start tracking the time 2576 | var startTime = new Date(); 2577 | 2578 | // get page 2579 | var page = com.getflourish.common.getStyleSheetPage(); 2580 | var artboard = com.getflourish.common.getArtboardByPageAndName(page, "Symbols"); 2581 | 2582 | // clear existing artboard 2583 | page.removeLayer(artboard); 2584 | artboard = com.getflourish.symbolInventory.addSymbolsSheetToPage(page); 2585 | 2586 | var execTime = (new Date() - startTime) / 1000; 2587 | doc.showMessage("Generated Symbols in " + execTime + " s"); 2588 | 2589 | return artboard; 2590 | }, 2591 | 2592 | layout: function (artboard) { 2593 | 2594 | /** 2595 | * Very basic layout routine that places all layers with the same base name next to each other. 2596 | * Every time a new base name is found, a new row will be started. 2597 | * The naming convention is: set/element/state 2598 | * 2599 | * e.g. ui/checkbox/normal 2600 | * ui/checkbox/selected 2601 | * 2602 | * todo: improve rendering time 2603 | */ 2604 | 2605 | var name; 2606 | var moduleName; 2607 | var prevName = ""; 2608 | var layer; 2609 | var layers = com.getflourish.layers.getLayersSortedByName(artboard.layers()); 2610 | 2611 | var padding = 200; 2612 | var nextX = padding; 2613 | var nextY = 0; 2614 | var gridX = 80; 2615 | var gridY = 200; 2616 | var maxX = 0; 2617 | var maxY = 0; 2618 | 2619 | var artboardWidth = 0; 2620 | 2621 | // storage 2622 | var cards = []; 2623 | var lines = []; 2624 | var card; 2625 | 2626 | for (var i = 0; i < layers.length; i++) { 2627 | layer = layers[i].layer; 2628 | name = layer.name(); 2629 | moduleName = name.substr(0, name.lastIndexOf("/")); 2630 | 2631 | // new group 2632 | if (moduleName != prevName) { 2633 | 2634 | var nextX = padding; 2635 | 2636 | // finish up the card 2637 | if (card) { 2638 | card.frame().setHeight(nextY - card.frame().y() + maxY + gridY / 4); 2639 | card.frame().setX(nextX); 2640 | card.frame().setWidth(maxX - padding); 2641 | } 2642 | 2643 | if (line) { 2644 | line.frame().setWidth(maxX - padding - gridX); 2645 | } 2646 | 2647 | // add a margin before the next group 2648 | nextY += gridY + maxY; 2649 | nextX += gridX / 2; 2650 | 2651 | // add a card background 2652 | // card = com.getflourish.symbolInventory.addCard(artboard); 2653 | // card.setName("card"); 2654 | // card.frame().setY(nextY - gridY / 4); 2655 | // 2656 | // // resize card 2657 | // cards.push(card); 2658 | 2659 | // add a label 2660 | var label = com.getflourish.symbolInventory.addLabel(artboard, moduleName, nextX, nextY); 2661 | 2662 | // add margin before separator 2663 | nextY += 24; 2664 | 2665 | // draw a separator 2666 | var line = com.getflourish.symbolInventory.addLine(artboard); 2667 | line.frame().setX(nextX); 2668 | line.frame().setY(nextY); 2669 | line.frame().setWidth(maxX - padding - gridX); 2670 | lines.push(line); 2671 | 2672 | nextY += gridY / 4; 2673 | 2674 | if (maxX > artboardWidth) artboardWidth = maxX; 2675 | 2676 | maxX = 0; 2677 | maxY = 0; 2678 | 2679 | // set the prevname 2680 | prevName = moduleName; 2681 | 2682 | } else { 2683 | nextX += gridX; 2684 | } 2685 | if (layer.frame().height() > maxY) { 2686 | maxY = layer.frame().height(); 2687 | } 2688 | 2689 | // in any case: add the symbol 2690 | layer.frame().setX(nextX); 2691 | layer.frame().setY(nextY); 2692 | 2693 | var r = layer.frame().x() + layer.frame().width() + gridX / 2; 2694 | if (r > maxX) maxX = r; 2695 | 2696 | nextX += layer.frame().width(); 2697 | } 2698 | 2699 | // fix the very last card 2700 | if (card) { 2701 | card.frame().setHeight(nextY - card.frame().y() + maxY + gridY / 4); 2702 | card.frame().setX(padding) 2703 | card.frame().setWidth(maxX - padding) 2704 | line.frame().setWidth(maxX - padding - gridX); 2705 | } 2706 | 2707 | // resize artboard 2708 | 2709 | artboard.frame().setHeight(nextY + padding + maxY) 2710 | artboard.frame().setWidth(artboardWidth + 2 * padding) 2711 | 2712 | // add background 2713 | com.getflourish.symbolInventory.addBackground(artboard); 2714 | }, 2715 | 2716 | addLabel: function (artboard, string, x, y) { 2717 | 2718 | if (string == "") { 2719 | string = "undefined"; 2720 | } 2721 | 2722 | // add module name as text layer 2723 | var label = com.getflourish.common.addTextLayer(artboard, string.toUpperCase()); 2724 | 2725 | label.setFontSize(15); 2726 | label.setFontPostscriptName(com.getflourish.config.FONT); 2727 | label.setCharacterSpacing(2); 2728 | 2729 | // color 2730 | var color = MSImmutableColor.colorWithSVGString(com.getflourish.config.TEXT_COLOR); 2731 | 2732 | // position 2733 | label.frame().setX(x); 2734 | label.frame().setY(y); 2735 | 2736 | // set text style 2737 | label.setTextColor(color); 2738 | 2739 | // add shared style 2740 | var sharedStyles = doc.documentData().layerTextStyles(); 2741 | 2742 | var styleName = "Style Inventory / Label"; 2743 | var textStyle = com.getflourish.common.getTextStyleByName(styleName); 2744 | 2745 | if (textStyle == null) { 2746 | sharedStyles.addSharedStyleWithName_firstInstance(styleName, label.style()); 2747 | } else { 2748 | // apply style 2749 | com.getflourish.symbolInventory.styleAllTextLayersBasedOnLayerWithTextStyle(label, textStyle); 2750 | } 2751 | 2752 | return label; 2753 | }, 2754 | 2755 | addBackground: function (artboard) { 2756 | 2757 | // add background 2758 | var bg = com.getflourish.common.addSolidBackground(artboard, com.getflourish.config.BACKGROUND_COLOR); 2759 | 2760 | // add shared style 2761 | var sharedStyles = doc.documentData().layerStyles(); 2762 | 2763 | var styleName = "Style Inventory / Background"; 2764 | var layerStyle = com.getflourish.common.getLayerStyleByName(styleName); 2765 | 2766 | if (layerStyle == null) { 2767 | sharedStyles.addSharedStyleWithName_firstInstance("Style Inventory / Background", bg.style()); 2768 | } else { 2769 | // apply style 2770 | com.getflourish.symbolInventory.styleAllLayersBasedOnLayerWithLayerStyle(bg, layerStyle); 2771 | } 2772 | 2773 | com.getflourish.utils.selectLayers([bg]); 2774 | com.getflourish.utils.sendToBack(); 2775 | bg.setIsLocked(true); 2776 | bg.isSelected = false; 2777 | }, 2778 | 2779 | addCard: function (artboard) { 2780 | // add background 2781 | var color = MSImmutableColor.colorWithSVGString(com.getflourish.config.CARD_COLOR); 2782 | var card = com.getflourish.colors.addColorShape(artboard, color, 100, 100); 2783 | 2784 | // Shadow 2785 | var shadow = card.style().addStylePartOfType(2); 2786 | 2787 | var black = MSImmutableColor.colorWithSVGString("#000000"); 2788 | black.alpha = 0.1; 2789 | shadow.setOffsetX(0); 2790 | shadow.setOffsetY(2); 2791 | shadow.setBlurRadius(3); 2792 | shadow.setSpread(0); 2793 | 2794 | shadow.setColor(black); 2795 | 2796 | // add shared style 2797 | var sharedStyles = doc.documentData().layerStyles(); 2798 | 2799 | var styleName = "Style Inventory / Card"; 2800 | var layerStyle = com.getflourish.common.getLayerStyleByName(styleName); 2801 | 2802 | if (layerStyle == null) { 2803 | sharedStyles.addSharedStyleWithName_firstInstance("Style Inventory / Card", card.style()); 2804 | } else { 2805 | // apply style 2806 | com.getflourish.symbolInventory.styleAllLayersBasedOnLayerWithLayerStyle(card, layerStyle, card.children()); 2807 | } 2808 | 2809 | com.getflourish.utils.deselectAllLayers() 2810 | 2811 | card.isSelected = true 2812 | com.getflourish.utils.sendToBack(); 2813 | return card; 2814 | 2815 | }, 2816 | 2817 | addLine: function (target) { 2818 | var black = MSImmutableColor.colorWithSVGString("#000000"); 2819 | black.alpha = 0.15; 2820 | var line = com.getflourish.colors.addColorShape(target, black, 100, 1); 2821 | line.setName("separator"); 2822 | 2823 | // add shared style 2824 | var sharedStyles = doc.documentData().layerStyles(); 2825 | 2826 | var styleName = "Style Inventory / Separator"; 2827 | var layerStyle = com.getflourish.common.getLayerStyleByName(styleName); 2828 | 2829 | if (layerStyle == null) { 2830 | sharedStyles.addSharedStyleWithName_firstInstance("Style Inventory / Separator", line.style()); 2831 | } else { 2832 | // apply style 2833 | com.getflourish.symbolInventory.styleAllLayersBasedOnLayerWithLayerStyle(line, layerStyle); 2834 | } 2835 | 2836 | return line; 2837 | }, 2838 | /** 2839 | * Exports all symbols as PNG 2840 | */ 2841 | export: function (exportPath) { 2842 | 2843 | var scope = doc.currentPage().children(); 2844 | 2845 | var predicate = NSPredicate.predicateWithFormat("className == %@", "MSSymbolInstance"); 2846 | 2847 | // hide non symbols 2848 | var results = scope.filteredArrayUsingPredicate(predicate); 2849 | 2850 | var layers = results.objectEnumerator(); 2851 | 2852 | // while (layer = layers.nextObject()) { 2853 | // layer.setIsVisible(false); 2854 | // } 2855 | 2856 | /** 2857 | * export 2858 | */ 2859 | 2860 | var exportLayers = layers 2861 | 2862 | // todo: fix export of symbols 2863 | 2864 | while (slice = exportLayers.nextObject()) { 2865 | 2866 | var path = exportPath + slice.name() + '.png'; 2867 | 2868 | // with shadows 2869 | // exportLayerToPath(slice, path, 1, "", "png", true); 2870 | processSlice(slice, [1], path) 2871 | 2872 | } 2873 | 2874 | 2875 | doc.showMessage('Symbols exported to: ' + exportPath); 2876 | }, 2877 | 2878 | styleAll: function (layers, style) { 2879 | for (var i = 0; i < layers.count(); i++) { 2880 | com.getflourish.symbolInventory.styleLayer(layers[i], style); 2881 | } 2882 | }, 2883 | 2884 | styleLayer: function (layer, style) { 2885 | layer.setStyle(style.newInstance()); 2886 | }, 2887 | 2888 | styleAllTextLayersBasedOnLayerWithTextStyle: function (layer, textStyle) { 2889 | 2890 | // get all text layers that match the style 2891 | var layers = com.getflourish.layers.getLayersByTextStyle(layer.style().textStyle(), doc.currentPage().children()); 2892 | com.getflourish.symbolInventory.styleAll(layers, textStyle); 2893 | return layers; 2894 | }, 2895 | 2896 | styleAllLayersBasedOnLayerWithLayerStyle: function (layer, layerStyle, scope) { 2897 | 2898 | var scope = scope || doc.currentPage().children(); 2899 | // get all text layers that match the style 2900 | var layers = com.getflourish.layers.getLayersByLayerStyle(layer.style(), scope); 2901 | com.getflourish.symbolInventory.styleAll(layers, layerStyle); 2902 | return layers; 2903 | }, 2904 | 2905 | selectTextStyle: function (preselect) { 2906 | var preselect = preselect || 0; 2907 | var textStyles = []; 2908 | var textItems = []; 2909 | 2910 | var selection; 2911 | 2912 | var styles = doc.documentData().layerTextStyles().objects(); 2913 | if (styles.count() != 0) { 2914 | for (var i = 0; i < styles.count(); i++) { 2915 | var style = styles.objectAtIndex(i); 2916 | textStyles.push({ 2917 | "textStyle": style, 2918 | "name": style.name() 2919 | }); 2920 | textItems.push(style.name()); 2921 | } 2922 | if (textItems) selection = com.getflourish.common.createSelect("Select a text style or create a new one", textItems, preselect); 2923 | } else { 2924 | return; 2925 | } 2926 | return selection; 2927 | }, 2928 | 2929 | selectLayerStyle: function () { 2930 | var layerStyles = []; 2931 | var layerStyleItems = []; 2932 | 2933 | var selection; 2934 | 2935 | var styles = doc.documentData().layerStyles().objects(); 2936 | if (styles.count() != 0) { 2937 | for (var i = 0; i < styles.count(); i++) { 2938 | var style = styles.objectAtIndex(i); 2939 | layerStyles.push({ 2940 | "layerStyle": style, 2941 | "name": style.name() 2942 | }); 2943 | layerStyleItems.push(style.name()); 2944 | } 2945 | if (layerStyleItems) selection = com.getflourish.common.createSelect("Select a style or create a new one", layerStyleItems, 0); 2946 | } else { 2947 | return; 2948 | } 2949 | return selection; 2950 | } 2951 | } 2952 | 2953 | 2954 | Array.prototype.uniqueColors = function () { 2955 | var a = this.concat(); 2956 | for (var i = 0; i < a.length; ++i) { 2957 | for (var j = i + 1; j < a.length; ++j) { 2958 | if (a[i].isEqual(a[j])) { 2959 | a.splice(j--, 1); 2960 | } 2961 | } 2962 | } 2963 | 2964 | return a; 2965 | }; 2966 | 2967 | 2968 | /* 2969 | Exports a layer to a given path 2970 | */ 2971 | 2972 | function processSlice(slice, factors, fileName){ 2973 | 2974 | var frame = [slice frame], 2975 | sliceName = [slice name]; 2976 | 2977 | for (var i = 0; i < factors.length; i++) { 2978 | var factor = factors[i], 2979 | version = makeSliceAndResizeWithFactor(slice, factor); 2980 | 2981 | [doc saveArtboardOrSlice: version toFile:fileName]; 2982 | 2983 | } 2984 | } 2985 | 2986 | function makeSliceAndResizeWithFactor (layer, factor) { 2987 | var loopLayerChildren = [[layer children] objectEnumerator], 2988 | sliceLayerAncestry = [MSImmutableLayerAncestry ancestryWithMSLayer:layer]; 2989 | rect = [MSSliceTrimming trimmedRectForLayerAncestry:sliceLayerAncestry]; 2990 | useSliceLayer = false, 2991 | slice 2992 | ; 2993 | 2994 | // Check for MSSliceLayer and overwrite the rect if present 2995 | while (layerChild = [loopLayerChildren nextObject]) { 2996 | if ([layerChild class] == 'MSSliceLayer') { 2997 | sliceLayerAncestry = [MSImmutableLayerAncestry ancestryWithMSLayer:layerChild]; 2998 | rect = [MSSliceTrimming trimmedRectForLayerAncestry:sliceLayerAncestry]; 2999 | useSliceLayer = true; 3000 | } 3001 | } 3002 | 3003 | var slices = [MSExportRequest exportRequestsFromExportableLayer:layer inRect:rect useIDForName:false]; 3004 | var slice = null; 3005 | if (slices.count() > 0) { 3006 | slice = slices[0]; 3007 | slice.scale = (factor / (this.baseDensity + 1)) 3008 | } 3009 | 3010 | if (!useSliceLayer) { 3011 | slice.shouldTrim = true; 3012 | } 3013 | // slice.saveForWeb = true; 3014 | // slice.compression = 0; 3015 | // slice.includeArtboardBackground = false; 3016 | return slice; 3017 | } 3018 | -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Style Inventory", 3 | "description": "Review, import & export styles in Sketch.", 4 | "author": "Florian Schulz", 5 | "homepage": "https://github.com/getflourish/Sketch-Style-Inventory", 6 | "version": "1.5.1", 7 | "identifier": "com.getflourish.sketch.styleinventory", 8 | "updateURL": "https://github.com/downloads/example/sketchplugins/sketchplugins.json", 9 | "compatibleVersion": 4.9, 10 | "bundleVersion": 1, 11 | "commands": [ 12 | { 13 | "name": "Generate", 14 | "identifier": "com.getflourish.styleinventory.inventory.generate", 15 | "shortcut": "control alt cmd i", 16 | "script": "Inventory/Generate.js" 17 | }, 18 | { 19 | "name": "Import Colors", 20 | "identifier": "com.getflourish.styleinventory.inventory.import", 21 | "shortcut": "", 22 | "script": "Inventory/Import.js" 23 | }, 24 | { 25 | "name": "Name Color Swatch", 26 | "identifier": "com.getflourish.styleinventory.inventory.namecolorswatch", 27 | "shortcut": "", 28 | "script": "Inventory/Name Color Swatch.js" 29 | }, 30 | { 31 | "name": "Select Layers by Color on Artboard", 32 | "identifier": "com.getflourish.styleinventory.selection.bycolor.onartboard", 33 | "shortcut": "control cmd c", 34 | "script": "selection/by Color/Select Layers by Color on Artboard.js" 35 | }, 36 | { 37 | "name": "Select Layers by Color on Page", 38 | "identifier": "com.getflourish.styleinventory.selection.bycolor.onpage", 39 | "shortcut": "shift control cmd c", 40 | "script": "selection/by Color/Select Layers by Color on Page.js" 41 | }, 42 | { 43 | "name": "Select Layers by Name on Artboard", 44 | "identifier": "com.getflourish.styleinventory.selection.byname.onartboard", 45 | "shortcut": "control cmd n", 46 | "script": "selection/by Name/Select Layers by Name on Artboard.js" 47 | }, 48 | { 49 | "name": "Select Layers by Name on Page", 50 | "identifier": "com.getflourish.styleinventory.selection.byname.onpage", 51 | "shortcut": "shift control cmd n", 52 | "script": "selection/by Name/Select Layers by Name on Page.js" 53 | }, 54 | { 55 | "name": "Select Layers by Text Style on Artboard", 56 | "identifier": "com.getflourish.styleinventory.selection.bytextstyle.onartboard", 57 | "shortcut": "control cmd t", 58 | "script": "selection/by Text Style/Select Layers by Text Style on Artboard.js" 59 | }, 60 | { 61 | "name": "Select Layers by Text Style on Page", 62 | "identifier": "com.getflourish.styleinventory.selection.bytextstyle.onpage", 63 | "shortcut": "shift control cmd t", 64 | "script": "selection/by Text Style/Select Layers by Text Style on Page.js" 65 | }, 66 | { 67 | "name": "Replace Strings on Artboard", 68 | "identifier": "com.getflourish.styleinventory.selection.bystring.onartboard", 69 | "shortcut": "", 70 | "script": "selection/by String/Replace Strings on Artboard.js" 71 | }, 72 | { 73 | "name": "Replace Strings on Page", 74 | "identifier": "com.getflourish.styleinventory.selection.bystring.onpage", 75 | "shortcut": "", 76 | "script": "selection/by String/Replace Strings on Page.js" 77 | } 78 | ], 79 | "menu": { 80 | "items": [ 81 | { 82 | "title": "Inventory", 83 | "items": [ 84 | "com.getflourish.styleinventory.inventory.generate", 85 | "com.getflourish.styleinventory.inventory.import", 86 | "com.getflourish.styleinventory.inventory.namecolorswatch" 87 | ] 88 | }, 89 | { 90 | "title": "Selection", 91 | "items": [ 92 | { 93 | "title": "by Color", 94 | "items": [ 95 | "com.getflourish.styleinventory.selection.bycolor.onartboard", 96 | "com.getflourish.styleinventory.selection.bycolor.onpage" 97 | ] 98 | }, 99 | { 100 | "title": "by Name", 101 | "items": [ 102 | "com.getflourish.styleinventory.selection.byname.onartboard", 103 | "com.getflourish.styleinventory.selection.byname.onpage" 104 | ] 105 | }, 106 | { 107 | "title": "by String", 108 | "items": [ 109 | "com.getflourish.styleinventory.selection.bystring.onartboard", 110 | "com.getflourish.styleinventory.selection.bystring.onpage" 111 | ] 112 | }, 113 | { 114 | "title": "by Text Style", 115 | "items": [ 116 | "com.getflourish.styleinventory.selection.bytextstyle.onartboard", 117 | "com.getflourish.styleinventory.selection.bytextstyle.onpage" 118 | ] 119 | } 120 | ] 121 | } 122 | ] 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/persistence.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A persitent thread dictionary manager that handles CocoaScript boxed objects 3 | * and JavaScript data. JS data is saved as a JSON string because JS objects 4 | * can't be saved in the dictoionary directly. 5 | * 6 | * The persist object offers a setter and getter function which deal with the 7 | * data conversion where neccessary. 8 | * 9 | * To save data: persist.set(keyname, value); 10 | * To retrieve data: persist.get(keyname); 11 | * 12 | * @type {Object} 13 | */ 14 | var persist = { 15 | dict: NSThread.mainThread().threadDictionary(), 16 | 17 | /** 18 | * Check if the returend value is a CocoaScript string, if so 19 | * it is a JSON object and will be parsed. 20 | * 21 | * @param {string} key The name of the element to get. 22 | * @return {mixed} The saved element or null 23 | */ 24 | get: function (key) { 25 | var val = this.dict[key]); 26 | 27 | if (val !== null && val.className() == '__NSCFString') { 28 | val = JSON.parse(val); 29 | } 30 | 31 | return val; 32 | }, 33 | 34 | /** 35 | * Check if the given value is not a CocoaScript MOBoxedObject - 36 | * it is a JavaScript type and must be JSON stringified. 37 | * 38 | * @param {string} key The name of the element to save 39 | * @param {mixed} val The element to save 40 | */ 41 | set: function (key, val) { 42 | if (Object.prototype.toString.call(val) !== "[object MOBoxedObject]") { 43 | val = JSON.stringify(val); 44 | } 45 | 46 | this.dict[key] = val; 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /Style Inventory.sketchplugin/Contents/Sketch/sandbox.js: -------------------------------------------------------------------------------- 1 | var environ = [[NSProcessInfo processInfo] environment], 2 | in_sandbox= (nil != [environ objectForKey:@"APP_SANDBOX_CONTAINER_ID"]) 3 | 4 | if(in_sandbox){ 5 | print("We’re sandboxed: here be dragons") 6 | } 7 | 8 | AppSandbox = function(){ } 9 | AppSandbox.prototype.authorize = function(path, callback){ 10 | log("AppSandbox.authorize("+path+")") 11 | var success = false 12 | 13 | if (in_sandbox) { 14 | var url = [[[NSURL fileURLWithPath:path] URLByStandardizingPath] URLByResolvingSymlinksInPath], 15 | allowedUrl = false 16 | 17 | // Key for bookmark data: 18 | var bd_key = this.key_for_url(url) 19 | 20 | // this.clear_key(bd_key) // For debug only, this clears the key we're looking for :P 21 | 22 | // Bookmark 23 | var bookmark = this.get_data_for_key(bd_key) 24 | if(!bookmark){ 25 | log("– No bookmark found, let's create one") 26 | var target = this.file_picker(url) 27 | bookmark = [target bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope 28 | includingResourceValuesForKeys:nil 29 | relativeToURL:nil 30 | error:{}] 31 | // Store bookmark 32 | this.set_data_for_key(bookmark,bd_key) 33 | } else { 34 | log("– Bookmark found") 35 | } 36 | log(" " + bookmark) 37 | 38 | // Thanks to @joethephish for this pointer (pun totally intended) 39 | var bookmarkDataIsStalePtr = MOPointer.alloc().init() 40 | var allowedURL = [NSURL URLByResolvingBookmarkData:bookmark 41 | options:NSURLBookmarkResolutionWithSecurityScope 42 | relativeToURL:nil 43 | bookmarkDataIsStale:bookmarkDataIsStalePtr 44 | error:{}] 45 | 46 | if(bookmarkDataIsStalePtr.value() != 0){ 47 | log("— Bookmark data is stale") 48 | log(bookmarkDataIsStalePtr.value()) 49 | } 50 | 51 | if(allowedURL) { 52 | success = true 53 | } 54 | } else { 55 | success = true 56 | } 57 | 58 | // [allowedUrl startAccessingSecurityScopedResource] 59 | callback.call(this,success) 60 | // [allowedUrl stopAccessingSecurityScopedResource] 61 | } 62 | AppSandbox.prototype.key_for_url = function(url){ 63 | return "bd_" + [url absoluteString] 64 | } 65 | AppSandbox.prototype.clear_key = function(key){ 66 | var def = [NSUserDefaults standardUserDefaults] 67 | [def setObject:nil forKey:key] 68 | } 69 | AppSandbox.prototype.file_picker = function(url){ 70 | // Panel 71 | var openPanel = [NSOpenPanel openPanel] 72 | 73 | [openPanel setTitle:"Sketch Authorization"] 74 | [openPanel setMessage:"Due to Apple's Sandboxing technology, Sketch needs your permission to write to this folder."]; 75 | [openPanel setPrompt:"Authorize"]; 76 | 77 | [openPanel setCanCreateDirectories:false] 78 | [openPanel setCanChooseFiles:true] 79 | [openPanel setCanChooseDirectories:true] 80 | [openPanel setAllowsMultipleSelection:false] 81 | [openPanel setShowsHiddenFiles:false] 82 | [openPanel setExtensionHidden:false] 83 | 84 | [openPanel setDirectoryURL:url] 85 | 86 | var openPanelButtonPressed = [openPanel runModal] 87 | if (openPanelButtonPressed == NSFileHandlingPanelOKButton) { 88 | allowedUrl = [openPanel URL] 89 | } 90 | return allowedUrl 91 | } 92 | 93 | AppSandbox.prototype.get_data_for_key = function(key){ 94 | var def = [NSUserDefaults standardUserDefaults] 95 | return [def objectForKey:key] 96 | } 97 | AppSandbox.prototype.set_data_for_key = function(data,key){ 98 | var defaults = [NSUserDefaults standardUserDefaults], 99 | default_values = [NSMutableDictionary dictionary] 100 | 101 | [default_values setObject:data forKey:key] 102 | [defaults registerDefaults:default_values] 103 | } -------------------------------------------------------------------------------- /appcast.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sketch Style Inventory 5 | https://github.com/getflourish/Sketch-Style-Inventory/master/appcast.xml 6 | Review, import & export styles in Sketch. 7 | en 8 | 9 | Version 1.5.1 10 | 11 | 13 |
  • Fixes for 49.3
  • 14 | 15 | ]]> 16 |
    17 | 18 |
    19 |
    20 |
    21 | -------------------------------------------------------------------------------- /sketch-style-inventory-runner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getflourish/Sketch-Style-Inventory/6e439741a910b67fec8449a89211b72892a54a96/sketch-style-inventory-runner.png --------------------------------------------------------------------------------