├── .gitattributes ├── LICENSE.md ├── Presto Selecto.sketchplugin └── Contents │ ├── Resources │ └── icon.png │ └── Sketch │ ├── delegate.js │ ├── manifest.json │ └── script.js ├── README.md ├── appcast.xml └── images ├── logo.png └── screenshot.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jason Burns (Sonburn) 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 | -------------------------------------------------------------------------------- /Presto Selecto.sketchplugin/Contents/Resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/presto-selecto/9a9bf78914cadcda20ef0d3cf1dc68e6a7a7cd54/Presto Selecto.sketchplugin/Contents/Resources/icon.png -------------------------------------------------------------------------------- /Presto Selecto.sketchplugin/Contents/Sketch/delegate.js: -------------------------------------------------------------------------------- 1 | // 2 | // MochaJSDelegate.js 3 | // MochaJSDelegate 4 | // 5 | // Created by Matt Curtis 6 | // Copyright (c) 2015. All rights reserved. 7 | // 8 | 9 | var MochaJSDelegate = function(selectorHandlerDict){ 10 | var uniqueClassName = "MochaJSDelegate_DynamicClass_" + NSUUID.UUID().UUIDString(); 11 | 12 | var delegateClassDesc = MOClassDescription.allocateDescriptionForClassWithName_superclass_(uniqueClassName, NSObject); 13 | 14 | delegateClassDesc.registerClass(); 15 | 16 | // Handler storage 17 | 18 | var handlers = {}; 19 | 20 | // Define interface 21 | 22 | this.setHandlerForSelector = function(selectorString, func){ 23 | var handlerHasBeenSet = (selectorString in handlers); 24 | var selector = NSSelectorFromString(selectorString); 25 | 26 | handlers[selectorString] = func; 27 | 28 | if(!handlerHasBeenSet){ 29 | /* 30 | For some reason, Mocha acts weird about arguments: 31 | https://github.com/logancollins/Mocha/issues/28 32 | 33 | We have to basically create a dynamic handler with a likewise dynamic number of predefined arguments. 34 | */ 35 | 36 | var dynamicHandler = function(){ 37 | var functionToCall = handlers[selectorString]; 38 | 39 | if(!functionToCall) return; 40 | 41 | return functionToCall.apply(delegateClassDesc, arguments); 42 | }; 43 | 44 | var args = [], regex = /:/g; 45 | while(match = regex.exec(selectorString)) args.push("arg"+args.length); 46 | 47 | dynamicFunction = eval("(function("+args.join(",")+"){ return dynamicHandler.apply(this, arguments); })"); 48 | 49 | delegateClassDesc.addInstanceMethodWithSelector_function_(selector, dynamicFunction); 50 | } 51 | }; 52 | 53 | this.removeHandlerForSelector = function(selectorString){ 54 | delete handlers[selectorString]; 55 | }; 56 | 57 | this.getHandlerForSelector = function(selectorString){ 58 | return handlers[selectorString]; 59 | }; 60 | 61 | this.getAllHandlers = function(){ 62 | return handlers; 63 | }; 64 | 65 | this.getClass = function(){ 66 | return NSClassFromString(uniqueClassName); 67 | }; 68 | 69 | this.getClassInstance = function(){ 70 | return NSClassFromString(uniqueClassName).new(); 71 | }; 72 | 73 | // Conveience 74 | 75 | if(typeof selectorHandlerDict == "object"){ 76 | for(var selectorString in selectorHandlerDict){ 77 | this.setHandlerForSelector(selectorString, selectorHandlerDict[selectorString]); 78 | } 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /Presto Selecto.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Presto Selecto", 3 | "description" : "Quickly select all layers of a type, whose name matches or contains a given string.", 4 | "author" : "Jason Burns", 5 | "homepage" : "https://github.com/sonburn/presto-selecto", 6 | "version" : "1.1", 7 | "identifier" : "com.sonburn.sketchplugins.presto-selecto", 8 | "appcast" : "https://raw.githubusercontent.com/sonburn/presto-selecto/master/appcast.xml", 9 | "icon" : "icon.png", 10 | "commands" : [ 11 | { 12 | "name" : "Presto Selecto", 13 | "shortcut" : "cmd option shift p", 14 | "identifier" : "select", 15 | "description" : "Configure and run Presto Selecto.", 16 | "icon" : "icon.png", 17 | "script" : "script.js", 18 | "handler" : "select" 19 | }, 20 | { 21 | "name" : "Report Issue", 22 | "identifier" : "report", 23 | "description" : "Report an issue with Presto Selecto.", 24 | "icon" : "icon.png", 25 | "script" : "script.js", 26 | "handler" : "report" 27 | }, 28 | { 29 | "name" : "Other Plugins", 30 | "identifier" : "plugins", 31 | "description" : "View all of Sonburn's plugins.", 32 | "icon" : "icon.png", 33 | "script" : "script.js", 34 | "handler" : "plugins" 35 | }, 36 | { 37 | "name" : "Donate", 38 | "identifier" : "donate", 39 | "description" : "Donate to the development of Presto Selecto.", 40 | "icon" : "icon.png", 41 | "script" : "script.js", 42 | "handler" : "donate" 43 | } 44 | ], 45 | "menu" : { 46 | "title" : "Presto Selecto", 47 | "items" : [ 48 | "select", 49 | "-", 50 | "report", 51 | "plugins", 52 | "donate" 53 | ] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Presto Selecto.sketchplugin/Contents/Sketch/script.js: -------------------------------------------------------------------------------- 1 | @import "delegate.js"; 2 | 3 | var sketch = require("sketch"); 4 | 5 | var pluginName = "Presto Selecto"; 6 | var pluginDomain = "com.sonburn.sketchplugins.presto-selecto"; 7 | 8 | var layerLabels = ["Everything","Artboards","Groups","Shape Group","Shape Path","Slices","Symbol Instances","Symbol Masters","Text Layers"]; 9 | var layerTypes = ["*","MSArtboardGroup","MSLayerGroup","MSShapeGroup",["MSShapePathLayer","MSOvalShape","MSPolygonShape","MSRectangleShape","MSStarShape","MSTriangleShape"],"MSSliceLayer","MSSymbolInstance","MSSymbolMaster","MSTextLayer"]; 10 | var targetLabels = ["Page","Selection"]; 11 | var parentLabels = ["Artboard","Group"]; 12 | var parentTypes = ["parentArtboard.name","parentGroup.name"]; 13 | var matchTypes = ["is","is not","contains","begins with","ends with"]; 14 | var matchFormats = ["==","!=","CONTAINS","BEGINSWITH","ENDSWITH"]; 15 | var debugMode = false; 16 | 17 | var windowWidth = 400; 18 | 19 | var select = function(context) { 20 | var defaultSettings = {}; 21 | 22 | defaultSettings.layerClassSelect = 0; 23 | defaultSettings.layerTargetSelect = 0; 24 | defaultSettings.layerMatchToggle = 0; 25 | defaultSettings.layerMatchSelect = 0; 26 | defaultSettings.layerMatchString = ""; 27 | defaultSettings.parentIncludeToggle = 0; 28 | defaultSettings.parentClassSelect = 0; 29 | defaultSettings.parentAncestorToggle = 0; 30 | defaultSettings.parentMatchToggle = 0; 31 | defaultSettings.parentMatchSelect = 0; 32 | defaultSettings.parentMatchString = ""; 33 | defaultSettings.stringCaseToggle = 0; 34 | 35 | var userSettings = Object.assign({},defaultSettings); 36 | 37 | userSettings = getSettings(context,userSettings); 38 | 39 | var pluginWindow = NSAlert.alloc().init(); 40 | var pluginIconPath = context.plugin.urlForResourceNamed("icon.png").path(); 41 | var pluginIcon = NSImage.alloc().initByReferencingFile(pluginIconPath); 42 | var pluginContent = createView(NSMakeRect(0,0,windowWidth,186)); 43 | 44 | pluginWindow.setIcon(pluginIcon); 45 | pluginWindow.setMessageText(pluginName); 46 | 47 | var layerClassSelect = createSelect(layerLabels,userSettings.layerClassSelect,NSMakeRect(44,0,128,28)); 48 | var layerTargetSelect = createSelect(targetLabels,userSettings.layerTargetSelect,NSMakeRect(283,0,81,28)); 49 | var layerMatchToggle = createCheckbox({name:"where the name",value:1},userSettings.layerMatchToggle,NSMakeRect(0,38,112,16)); 50 | var layerMatchSelect = createSelect(matchTypes,userSettings.layerMatchSelect,NSMakeRect(118,32,93,28)); 51 | var layerMatchString = createField(userSettings.layerMatchString,"Layer string to match",NSMakeRect(216,34,windowWidth-216,24)); 52 | var parentIncludeToggle = createCheckbox({name:"and has a parent",value:1},userSettings.parentIncludeToggle,NSMakeRect(0,88,116,16)); 53 | var parentClassSelect = createSelect(parentLabels,userSettings.parentClassSelect,NSMakeRect(121,82,79,28)); 54 | var parentAncestorToggle = createCheckbox({name:"include ancestors",value:1},userSettings.parentAncestorToggle,NSMakeRect(205,88,121,16)); 55 | var parentMatchToggle = createCheckbox({name:"where the name",value:1},userSettings.parentMatchToggle,NSMakeRect(0,120,112,16)); 56 | var parentMatchSelect = createSelect(matchTypes,userSettings.parentMatchSelect,NSMakeRect(118,114,93,28)); 57 | var parentMatchString = createField(userSettings.parentMatchString,"Parent string to match",NSMakeRect(216,116,windowWidth-216,24)); 58 | var stringCaseToggle = createCheckbox({name:"Case sensitive",value:1},userSettings.stringCaseToggle,NSMakeRect(0,170,windowWidth,16)); 59 | 60 | var layerClassSelectDelegate = new MochaJSDelegate({ 61 | "comboBoxSelectionDidChange:" : (function() { 62 | if (layerClassSelect.indexOfSelectedItem() == 1) { 63 | parentIncludeToggle.setState(0); 64 | parentIncludeToggle.setEnabled(0); 65 | parentClassSelect.setEnabled(0); 66 | parentAncestorToggle.setEnabled(0); 67 | parentMatchToggle.setEnabled(0); 68 | parentMatchSelect.setEnabled(0); 69 | parentMatchString.setEnabled(0); 70 | } else { 71 | parentIncludeToggle.setEnabled(1); 72 | } 73 | }) 74 | }); 75 | 76 | layerClassSelect.setDelegate(layerClassSelectDelegate.getClassInstance()); 77 | 78 | var parentClassSelectDelegate = new MochaJSDelegate({ 79 | "comboBoxSelectionDidChange:" : (function() { 80 | if (parentClassSelect.indexOfSelectedItem() == 1) { 81 | parentAncestorToggle.setEnabled(1); 82 | } else { 83 | parentAncestorToggle.setEnabled(0); 84 | } 85 | }) 86 | }); 87 | 88 | parentClassSelect.setDelegate(parentClassSelectDelegate.getClassInstance()); 89 | 90 | parentIncludeToggle.setAction("callAction:"); 91 | parentIncludeToggle.setCOSJSTargetFunction(function(sender) { 92 | if (sender.state() == 1) { 93 | parentClassSelect.setEnabled(1); 94 | parentMatchToggle.setEnabled(1); 95 | parentMatchSelect.setEnabled(1); 96 | parentMatchString.setEnabled(1); 97 | 98 | if (parentClassSelect.indexOfSelectedItem() == 1) { 99 | parentAncestorToggle.setEnabled(1); 100 | } else { 101 | parentAncestorToggle.setEnabled(0); 102 | } 103 | } else { 104 | parentClassSelect.setEnabled(0); 105 | parentAncestorToggle.setEnabled(0); 106 | parentMatchToggle.setEnabled(0); 107 | parentMatchSelect.setEnabled(0); 108 | parentMatchString.setEnabled(0); 109 | } 110 | }); 111 | 112 | if (userSettings.layerClassSelect == 1) { 113 | parentIncludeToggle.setEnabled(0); 114 | } 115 | 116 | if (userSettings.parentIncludeToggle == 0) { 117 | parentClassSelect.setEnabled(0); 118 | parentMatchToggle.setEnabled(0); 119 | parentMatchSelect.setEnabled(0); 120 | parentMatchString.setEnabled(0); 121 | parentAncestorToggle.setEnabled(0); 122 | } else { 123 | if (parentClassSelect.indexOfSelectedItem() == 1) { 124 | parentAncestorToggle.setEnabled(1); 125 | } else { 126 | parentAncestorToggle.setEnabled(0); 127 | } 128 | } 129 | 130 | pluginContent.addSubview(createLabel("Select",NSMakeRect(0,5,40,28))); 131 | pluginContent.addSubview(layerClassSelect); 132 | pluginContent.addSubview(createLabel("within the current",NSMakeRect(174,5,104,28))); 133 | pluginContent.addSubview(layerTargetSelect); 134 | pluginContent.addSubview(layerMatchToggle); 135 | pluginContent.addSubview(layerMatchSelect); 136 | pluginContent.addSubview(layerMatchString); 137 | pluginContent.addSubview(createDivider(NSMakeRect(0,70,windowWidth,1))); 138 | pluginContent.addSubview(parentIncludeToggle); 139 | pluginContent.addSubview(parentClassSelect); 140 | pluginContent.addSubview(parentAncestorToggle); 141 | pluginContent.addSubview(parentMatchToggle); 142 | pluginContent.addSubview(parentMatchSelect); 143 | pluginContent.addSubview(parentMatchString); 144 | pluginContent.addSubview(createDivider(NSMakeRect(0,152,windowWidth,1))); 145 | pluginContent.addSubview(stringCaseToggle); 146 | 147 | pluginWindow.setAccessoryView(pluginContent); 148 | 149 | var selectButton = pluginWindow.addButtonWithTitle("Abracadabra!"); 150 | 151 | pluginWindow.addButtonWithTitle("Cancel"); 152 | 153 | var defaultsButton = pluginWindow.addButtonWithTitle("Defaults"); 154 | 155 | defaultsButton.setCOSJSTargetFunction(function() { 156 | layerClassSelect.selectItemAtIndex(defaultSettings.layerClassSelect); 157 | layerTargetSelect.selectItemAtIndex(defaultSettings.layerTargetSelect); 158 | layerMatchToggle.setState(defaultSettings.layerMatchToggle); 159 | layerMatchSelect.selectItemAtIndex(defaultSettings.layerMatchSelect); 160 | layerMatchString.setStringValue(defaultSettings.layerMatchString); 161 | parentIncludeToggle.setState(defaultSettings.parentIncludeToggle); 162 | parentClassSelect.selectItemAtIndex(defaultSettings.parentClassSelect); 163 | parentAncestorToggle.setState(defaultSettings.parentAncestorToggle); 164 | parentMatchToggle.setState(defaultSettings.parentMatchToggle); 165 | parentMatchSelect.selectItemAtIndex(defaultSettings.parentMatchSelect); 166 | parentMatchString.setStringValue(defaultSettings.parentMatchString); 167 | stringCaseToggle.setState(defaultSettings.stringCaseToggle); 168 | 169 | parentClassSelect.setEnabled(0); 170 | parentAncestorToggle.setEnabled(0); 171 | parentMatchToggle.setEnabled(0); 172 | parentMatchSelect.setEnabled(0); 173 | parentMatchString.setEnabled(0); 174 | 175 | context.command.setValue_forKey_onLayer(nil,"layerClassSelect",context.document.documentData()); 176 | context.command.setValue_forKey_onLayer(nil,"layerTargetSelect",context.document.documentData()); 177 | context.command.setValue_forKey_onLayer(nil,"layerMatchToggle",context.document.documentData()); 178 | context.command.setValue_forKey_onLayer(nil,"layerMatchSelect",context.document.documentData()); 179 | context.command.setValue_forKey_onLayer(nil,"layerMatchString",context.document.documentData()); 180 | context.command.setValue_forKey_onLayer(nil,"parentIncludeToggle",context.document.documentData()); 181 | context.command.setValue_forKey_onLayer(nil,"parentClassSelect",context.document.documentData()); 182 | context.command.setValue_forKey_onLayer(nil,"parentAncestorToggle",context.document.documentData()); 183 | context.command.setValue_forKey_onLayer(nil,"parentMatchToggle",context.document.documentData()); 184 | context.command.setValue_forKey_onLayer(nil,"parentMatchSelect",context.document.documentData()); 185 | context.command.setValue_forKey_onLayer(nil,"parentMatchString",context.document.documentData()); 186 | context.command.setValue_forKey_onLayer(nil,"stringCaseToggle",context.document.documentData()); 187 | }); 188 | 189 | setKeyOrder(pluginWindow,[ 190 | layerClassSelect, 191 | layerTargetSelect, 192 | layerMatchToggle, 193 | layerMatchSelect, 194 | layerMatchString, 195 | parentIncludeToggle, 196 | parentClassSelect, 197 | parentAncestorToggle, 198 | parentMatchToggle, 199 | parentMatchSelect, 200 | parentMatchString, 201 | stringCaseToggle, 202 | selectButton 203 | ]); 204 | 205 | windowLoop(); 206 | 207 | function windowLoop() { 208 | var windowStatus = null; 209 | 210 | while (windowStatus == null) { 211 | var windowResponse = pluginWindow.runModal(); 212 | 213 | if (windowResponse == 1000) { 214 | var layerMatchType = layerTypes[layerClassSelect.indexOfSelectedItem()]; 215 | var layerTargetType = layerTargetSelect.indexOfSelectedItem(); 216 | var layerMatchToggleState = layerMatchToggle.state(); 217 | var layerMatchFormat = matchFormats[layerMatchSelect.indexOfSelectedItem()]; 218 | var layerMatchCase = (stringCaseToggle.state() == 1) ? "" : "[c]"; 219 | var layerMatchValue = layerMatchString.stringValue(); 220 | var parentIncludeToggleState = parentIncludeToggle.state(); 221 | var parentAncestorToggleState = parentAncestorToggle.state(); 222 | var parentMatchToggleState = parentMatchToggle.state(); 223 | var parentMatchTypeValue = parentClassSelect.indexOfSelectedItem(); 224 | var parentMatchType = parentTypes[parentMatchTypeValue]; 225 | var parentMatchFormat = matchFormats[parentMatchSelect.indexOfSelectedItem()]; 226 | var parentMatchCase = (stringCaseToggle.state() == 1) ? "" : "[c]"; 227 | var parentMatchValue = parentMatchString.stringValue(); 228 | 229 | if (layerMatchToggleState == 0 && parentIncludeToggleState == 0 || 230 | layerMatchToggleState == 0 && parentIncludeToggleState == 1 && parentMatchValue != "" || 231 | layerMatchToggleState == 1 && layerMatchValue != "" && parentIncludeToggleState == 0 || 232 | layerMatchToggleState == 1 && layerMatchValue != "" && parentIncludeToggleState == 1 && parentMatchValue != "") { 233 | windowStatus = true; 234 | } 235 | } else { 236 | windowStatus = false; 237 | } 238 | 239 | switch (windowStatus) { 240 | case null : 241 | if (layerMatchToggleState == 1 && layerMatchValue != "") { 242 | sketch.UI.alert(pluginName,"Please provide a layer string to search for."); 243 | } else { 244 | sketch.UI.alert(pluginName,"Please provide a parent string to search for."); 245 | } 246 | 247 | break; 248 | case true : 249 | context.command.setValue_forKey_onLayer(layerClassSelect.indexOfSelectedItem(),"layerClassSelect",context.document.documentData()); 250 | context.command.setValue_forKey_onLayer(layerTargetSelect.indexOfSelectedItem(),"layerTargetSelect",context.document.documentData()); 251 | context.command.setValue_forKey_onLayer(layerMatchToggle.state(),"layerMatchToggle",context.document.documentData()); 252 | context.command.setValue_forKey_onLayer(layerMatchSelect.indexOfSelectedItem(),"layerMatchSelect",context.document.documentData()); 253 | context.command.setValue_forKey_onLayer(layerMatchString.stringValue(),"layerMatchString",context.document.documentData()); 254 | context.command.setValue_forKey_onLayer(parentIncludeToggle.state(),"parentIncludeToggle",context.document.documentData()); 255 | context.command.setValue_forKey_onLayer(parentClassSelect.indexOfSelectedItem(),"parentClassSelect",context.document.documentData()); 256 | context.command.setValue_forKey_onLayer(parentAncestorToggle.state(),"parentAncestorToggle",context.document.documentData()); 257 | context.command.setValue_forKey_onLayer(parentMatchToggle.state(),"parentMatchToggle",context.document.documentData()); 258 | context.command.setValue_forKey_onLayer(parentMatchSelect.indexOfSelectedItem(),"parentMatchSelect",context.document.documentData()); 259 | context.command.setValue_forKey_onLayer(parentMatchString.stringValue(),"parentMatchString",context.document.documentData()); 260 | context.command.setValue_forKey_onLayer(stringCaseToggle.state(),"stringCaseToggle",context.document.documentData()); 261 | 262 | context.command.setValue_forKey_onLayer(nil,"layerTargetType",context.document.documentData()); 263 | 264 | var page = context.document.currentPage(); 265 | var predicate; 266 | var matches; 267 | var ancestorFilter = false; 268 | 269 | let compareType = (layerMatchType instanceof Array) ? 'IN' : 'LIKE' 270 | 271 | if (layerMatchToggleState == 0 && parentIncludeToggleState == 0) { 272 | predicate = NSPredicate.predicateWithFormat("className " + compareType + " %@",layerMatchType); 273 | } else if (layerMatchToggleState == 0 && parentIncludeToggleState == 1 && parentMatchTypeValue == 0 && parentMatchToggleState == 0) { 274 | predicate = NSPredicate.predicateWithFormat("className " + compareType + " %@ AND parentArtboard != nil",layerMatchType); 275 | } else if (layerMatchToggleState == 0 && parentIncludeToggleState == 1 && parentMatchTypeValue == 0 && parentMatchToggleState == 1) { 276 | predicate = NSPredicate.predicateWithFormat("className " + compareType + " %@ AND parentArtboard != nil AND " + parentMatchType + " " + parentMatchFormat + parentMatchCase + " %@",layerMatchType,parentMatchValue); 277 | } else if (layerMatchToggleState == 0 && parentIncludeToggleState == 1 && parentMatchTypeValue == 1 && parentMatchToggleState == 0) { 278 | predicate = NSPredicate.predicateWithFormat("className " + compareType + " %@ AND parentGroup.className == %@",layerMatchType,"MSLayerGroup"); 279 | } else if (layerMatchToggleState == 0 && parentIncludeToggleState == 1 && parentMatchTypeValue == 1 && parentMatchToggleState == 1 && parentAncestorToggleState == 0) { 280 | predicate = NSPredicate.predicateWithFormat("className " + compareType + " %@ AND parentGroup.className == %@ AND " + parentMatchType + " " + parentMatchFormat + parentMatchCase + " %@",layerMatchType,"MSLayerGroup",parentMatchValue); 281 | } else if (layerMatchToggleState == 0 && parentIncludeToggleState == 1 && parentMatchTypeValue == 1 && parentMatchToggleState == 1 && parentAncestorToggleState == 1) { 282 | predicate = NSPredicate.predicateWithFormat("className " + compareType + " %@ AND parentGroup.className == %@",layerMatchType,"MSLayerGroup"); 283 | ancestorFilter = true; 284 | } else if (layerMatchToggleState == 1 && parentIncludeToggleState == 0) { 285 | predicate = NSPredicate.predicateWithFormat("className " + compareType + " %@ AND name " + layerMatchFormat + layerMatchCase + " %@",layerMatchType,layerMatchValue); 286 | } else if (layerMatchToggleState == 1 && parentIncludeToggleState == 1 && parentMatchTypeValue == 0 && parentMatchToggleState == 0) { 287 | predicate = NSPredicate.predicateWithFormat("className " + compareType + " %@ AND name " + layerMatchFormat + layerMatchCase + " %@ AND parentArtboard != nil",layerMatchType,layerMatchValue); 288 | } else if (layerMatchToggleState == 1 && parentIncludeToggleState == 1 && parentMatchTypeValue == 0 && parentMatchToggleState == 1) { 289 | predicate = NSPredicate.predicateWithFormat("className " + compareType + " %@ AND name " + layerMatchFormat + layerMatchCase + " %@ AND parentArtboard != nil AND " + parentMatchType + " " + parentMatchFormat + parentMatchCase + " %@",layerMatchType,layerMatchValue,parentMatchValue); 290 | } else if (layerMatchToggleState == 1 && parentIncludeToggleState == 1 && parentMatchTypeValue == 1 && parentMatchToggleState == 0) { 291 | predicate = NSPredicate.predicateWithFormat("className " + compareType + " %@ AND name " + layerMatchFormat + layerMatchCase + " %@ AND parentGroup.className == %@",layerMatchType,layerMatchValue,"MSLayerGroup"); 292 | } else if (layerMatchToggleState == 1 && parentIncludeToggleState == 1 && parentMatchTypeValue == 1 && parentMatchToggleState == 1 && parentAncestorToggleState == 0) { 293 | predicate = NSPredicate.predicateWithFormat("className " + compareType + " %@ AND name " + layerMatchFormat + layerMatchCase + " %@ AND parentGroup.className == %@ AND " + parentMatchType + " " + parentMatchFormat + parentMatchCase + " %@",layerMatchType,layerMatchValue,"MSLayerGroup",parentMatchValue); 294 | } else if (layerMatchToggleState == 1 && parentIncludeToggleState == 1 && parentMatchTypeValue == 1 && parentMatchToggleState == 1 && parentAncestorToggleState == 1) { 295 | predicate = NSPredicate.predicateWithFormat("className " + compareType + " %@ AND name " + layerMatchFormat + layerMatchCase + " %@ AND parentGroup.className == %@",layerMatchType,layerMatchValue,"MSLayerGroup"); 296 | ancestorFilter = true; 297 | } 298 | 299 | if (layerTargetType == 0) { 300 | matches = page.children().filteredArrayUsingPredicate(predicate); 301 | } else { 302 | matches = NSMutableArray.array(); 303 | 304 | context.selection.filteredArrayUsingPredicate(predicate).forEach(match => matches.addObject(match)); 305 | 306 | context.selection.forEach(function(selection){ 307 | selection.children().filteredArrayUsingPredicate(predicate).forEach(match => matches.addObject(match)); 308 | }); 309 | } 310 | 311 | if (ancestorFilter) { 312 | var matchesWithAncestors = NSMutableArray.array(); 313 | var loop = matches.objectEnumerator(); 314 | var match; 315 | 316 | while (match = loop.nextObject()) { 317 | var predicate = NSPredicate.predicateWithFormat("name " + parentMatchFormat + parentMatchCase + " %@",parentMatchValue), 318 | ancestors = match.ancestors().filteredArrayUsingPredicate(predicate); 319 | 320 | if (ancestors.count() > 0) { 321 | matchesWithAncestors.addObject(match); 322 | } 323 | } 324 | 325 | matches = matchesWithAncestors; 326 | } 327 | 328 | var loop = matches.objectEnumerator(); 329 | var match; 330 | var count = 0; 331 | 332 | page.changeSelectionBySelectingLayers(nil); 333 | 334 | while (match = loop.nextObject()) { 335 | match.select_byExtendingSelection(1,1); 336 | count++; 337 | } 338 | 339 | if (count == 1) { 340 | sketch.UI.message(matches.count() + " match selected"); 341 | } else if (count > 1) { 342 | sketch.UI.message(matches.count() + " matches selected"); 343 | } else { 344 | sketch.UI.message("No matches found"); 345 | } 346 | 347 | if (!debugMode) googleAnalytics(context,"select","run"); 348 | 349 | break; 350 | case false : 351 | break; 352 | } 353 | } 354 | } 355 | } 356 | 357 | var report = function(context) { 358 | openUrl("https://github.com/sonburn/presto-selecto/issues/new"); 359 | 360 | if (!debugMode) googleAnalytics(context,"report","report"); 361 | } 362 | 363 | var plugins = function(context) { 364 | openUrl("https://sonburn.github.io/"); 365 | 366 | if (!debugMode) googleAnalytics(context,"plugins","plugins"); 367 | } 368 | 369 | var donate = function(context) { 370 | openUrl("https://www.paypal.me/sonburn"); 371 | 372 | if (!debugMode) googleAnalytics(context,"donate","donate"); 373 | } 374 | 375 | function createCheckbox(item,flag,frame) { 376 | var checkbox = NSButton.alloc().initWithFrame(frame); 377 | var flag = (flag == false) ? NSOffState : NSOnState; 378 | 379 | checkbox.setButtonType(NSSwitchButton); 380 | checkbox.setBezelStyle(0); 381 | checkbox.setTitle(item.name); 382 | checkbox.setTag(item.value); 383 | checkbox.setState(flag); 384 | 385 | return checkbox; 386 | } 387 | 388 | function createDivider(frame) { 389 | var divider = NSView.alloc().initWithFrame(frame); 390 | 391 | divider.setWantsLayer(1); 392 | divider.layer().setBackgroundColor(CGColorCreateGenericRGB(204/255,204/255,204/255,1.0)); 393 | 394 | return divider; 395 | } 396 | 397 | function createField(string,placeholder,frame) { 398 | var textField = NSTextField.alloc().initWithFrame(frame); 399 | 400 | textField.setStringValue(string); 401 | textField.setPlaceholderString(placeholder); 402 | 403 | return textField; 404 | } 405 | 406 | function createLabel(string,frame) { 407 | var textLabel = NSTextField.alloc().initWithFrame(frame); 408 | 409 | textLabel.setStringValue(string); 410 | textLabel.setBezeled(0); 411 | textLabel.setEditable(0); 412 | textLabel.setDrawsBackground(0); 413 | 414 | return textLabel; 415 | } 416 | 417 | function createSelect(items,selection,frame) { 418 | var comboBox = NSComboBox.alloc().initWithFrame(frame); 419 | var selection = (selection > -1) ? selection : 0; 420 | 421 | comboBox.addItemsWithObjectValues(items); 422 | comboBox.selectItemAtIndex(selection); 423 | comboBox.setNumberOfVisibleItems(12); 424 | 425 | return comboBox; 426 | } 427 | 428 | function createView(frame) { 429 | var view = NSView.alloc().initWithFrame(frame); 430 | 431 | view.setFlipped(1); 432 | 433 | return view; 434 | } 435 | 436 | function getSettings(context,settings) { 437 | try { 438 | for (i in settings) { 439 | var value = context.command.valueForKey_onLayer_forPluginIdentifier(i,context.document.documentData(),pluginDomain); 440 | if (value) settings[i] = value; 441 | } 442 | 443 | return settings; 444 | } catch(err) { 445 | log("There was a problem fetching cached settings."); 446 | } 447 | } 448 | 449 | function googleAnalytics(context,category,action,label,value) { 450 | var trackingID = "UA-118972367-1"; 451 | var uuidKey = "google.analytics.uuid"; 452 | var uuid = NSUserDefaults.standardUserDefaults().objectForKey(uuidKey); 453 | 454 | if (!uuid) { 455 | uuid = NSUUID.UUID().UUIDString(); 456 | NSUserDefaults.standardUserDefaults().setObject_forKey(uuid,uuidKey); 457 | } 458 | 459 | var url = "https://www.google-analytics.com/collect?v=1"; 460 | // Tracking ID 461 | url += "&tid=" + trackingID; 462 | // Source 463 | url += "&ds=sketch" + sketch.version.sketch; 464 | // Client ID 465 | url += "&cid=" + uuid; 466 | // pageview, screenview, event, transaction, item, social, exception, timing 467 | url += "&t=event"; 468 | // App Name 469 | url += "&an=" + encodeURI(context.plugin.name()); 470 | // App ID 471 | url += "&aid=" + context.plugin.identifier(); 472 | // App Version 473 | url += "&av=" + context.plugin.version(); 474 | // Event category 475 | url += "&ec=" + encodeURI(category); 476 | // Event action 477 | url += "&ea=" + encodeURI(action); 478 | // Event label 479 | if (label) { 480 | url += "&el=" + encodeURI(label); 481 | } 482 | // Event value 483 | if (value) { 484 | url += "&ev=" + encodeURI(value); 485 | } 486 | 487 | var session = NSURLSession.sharedSession(); 488 | var task = session.dataTaskWithURL(NSURL.URLWithString(NSString.stringWithString(url))); 489 | 490 | task.resume(); 491 | } 492 | 493 | function openUrl(url) { 494 | NSWorkspace.sharedWorkspace().openURL(NSURL.URLWithString(url)); 495 | } 496 | 497 | function setKeyOrder(alert,order) { 498 | for (var i = 0; i < order.length; i++) { 499 | var thisItem = order[i]; 500 | var nextItem = order[i+1]; 501 | 502 | if (nextItem) thisItem.setNextKeyView(nextItem); 503 | } 504 | 505 | alert.window().setInitialFirstResponder(order[0]); 506 | } 507 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Presto Selecto](https://raw.githubusercontent.com/sonburn/presto-selecto/master/images/logo.png) 2 | 3 | Quickly select all layers of a type, whose name matches or contains a given string. 4 | 5 | ![Presto Selecto](https://raw.githubusercontent.com/sonburn/presto-selecto/master/images/screenshot.png) 6 | 7 | 8 | runner-badge-blue 9 | 10 | 11 | 12 | 13 | 14 | 15 | # Usage 16 | 17 | * cmd option shift p - Configure and run Presto Selecto 18 | 19 | # Installation 20 | 21 | ## Automatic 22 | Search for Presto Selecto in [Sketchrunner](http://sketchrunner.com/) or [Sketch Toolbox](http://sketchtoolbox.com/) if you have one of those installed. 23 | 24 | Once installed, Sketch will automatically notify you when an update is available (version 0.4 and later). 25 | 26 | ## Manual 27 | 28 | 1. Download and open presto-selecto-master.zip 29 | 2. Navigate to Presto Selecto.sketchplugin and copy/move to your plugins directory 30 | 31 | To find your plugins directory... 32 | 33 | 1. In the Sketch menu, navigate to Plugins > Manage Plugins... 34 | 2. Click the cog in the lower left of the plugins window, and click Reveal Plugins Folder 35 | 36 | # Changelog 37 | 38 | * **1.1** - Improved Shape selections. Fix for Sketch 72. 39 | * **1.0** - Added ability to select within selection. 40 | * **0.9** - Fixed issue where not all selections were being made. 41 | * **0.8** - Update for Sketch 53. 42 | * **0.7** - General optimizations. 43 | * **0.6** - Added plugin icon to manifest for Sketch 50. 44 | * **0.5** - Added the ability to select symbol masters and instances, and match within a group's ancestors. 45 | * **0.4** - Matching strings is now optional, meaning you can simply select layers of a type within a parent of a type. Fixed appcast.xml issue. 46 | * **0.3** - Matching against Everything now allows for a parent, a parent can now also be a group, and added the ability to search for shapes. 47 | * **0.2** - Verbiage improvements. 48 | * **0.1** - Initial commit. 49 | 50 | # Contact 51 | 52 | Find me on Twitter @sonburn 53 | 54 | # Support 55 | 56 | If you find this plugin helpful, or would like to support my plugins in general, buy me ☕️ via PayPal. 57 | 58 | # License 59 | 60 | Copyright (c) 2021 Jason Burns (Sonburn). See LICENSE.md for further details. 61 | -------------------------------------------------------------------------------- /appcast.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Presto Selecto 5 | http://sparkle-project.org/files/sparkletestcast.xml 6 | Quickly select all layers of a type, whose name matches or contains a given string. 7 | en 8 | 9 | Version 1.1 10 | 11 | 13 |
  • Improved Shape selections. Fix for Sketch 72.
  • 14 | 15 | ]]> 16 |
    17 | 18 |
    19 |
    20 |
    21 | -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/presto-selecto/9a9bf78914cadcda20ef0d3cf1dc68e6a7a7cd54/images/logo.png -------------------------------------------------------------------------------- /images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/presto-selecto/9a9bf78914cadcda20ef0d3cf1dc68e6a7a7cd54/images/screenshot.png --------------------------------------------------------------------------------