├── .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 | 
2 |
3 | Quickly select all layers of a type, whose name matches or contains a given string.
4 |
5 | 
6 |
7 |
8 |
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
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
--------------------------------------------------------------------------------