├── .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 |  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 |  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 |  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 |  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 |  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 += '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!