├── .babelrc
├── .gitignore
├── LICENSE
├── README.md
├── Sketch Search Everywhere.sketchplugin
└── Contents
│ ├── Resources
│ ├── index_bundle.js
│ └── window.html
│ └── Sketch
│ ├── MochaJSDelegate.js
│ ├── app.js
│ ├── main.js
│ ├── manifest.json
│ ├── webView.js
│ └── window.js
├── package.json
├── src
├── components
│ ├── App.jsx
│ ├── App.less
│ ├── Filter.jsx
│ └── Filter.less
└── index.html
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "react"
5 | ],
6 | "plugins": [
7 | [
8 | "import",
9 | {
10 | "libraryName": "antd",
11 | "style": true // or 'css'
12 | }
13 | ]
14 | ]
15 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log.*
3 | .DS_Store
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 捻捻转儿
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 | ## Sketch Search Everywhere
2 |
3 | > Sketch App plugin
4 |
5 | Search layer and select it, by matching `textValue`, `name` or `ObjectID`.
6 |
7 | ## Installation
8 |
9 | [Download](https://github.com/MrPeak/sketch-search-everywhere/archive/master.zip) and extract the contents of this repository. Then double-click the `Sketch Search Everywhere.sketchplugin` bundle to install the plugin.
10 |
11 | ## Usage
12 |
13 | Just type Alt +Command +F everywhere.
14 |
15 | And enter the textContent/Name/ObjectId that the layers cotain
16 |
17 | Then select the layer you find!
18 |
19 | 
20 |
21 |
22 | ## Change Log
23 |
24 | #### v1.0.3: June 25, 2017
25 |
26 | - [x] Add 'All' Class filter
27 |
28 | #### v1.0.2: June 23, 2017
29 |
30 | - [x] Disable ClassType filter search
31 |
32 | #### v1.0.1: June 19, 2017
33 |
34 | - [x] Fix filter delay issue
35 | - [x] Limit search results' length to 15
36 |
37 | #### v1.0.0: June 18, 2017
38 |
39 | - Initial release
40 |
--------------------------------------------------------------------------------
/Sketch Search Everywhere.sketchplugin/Contents/Resources/window.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Search Everywhere
6 |
7 |
8 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Sketch Search Everywhere.sketchplugin/Contents/Sketch/MochaJSDelegate.js:
--------------------------------------------------------------------------------
1 | // @see https://github.com/matt-curtis/MochaJSDelegate/blob/master/MochaJSDelegate.js
2 |
3 | //
4 | // MochaJSDelegate.js
5 | // MochaJSDelegate
6 | //
7 | // Created by Matt Curtis
8 | // Copyright (c) 2015. All rights reserved.
9 | //
10 |
11 | var MochaJSDelegate = function(selectorHandlerDict) {
12 | var uniqueClassName =
13 | "MochaJSDelegate_DynamicClass_" + NSUUID.UUID().UUIDString();
14 |
15 | var delegateClassDesc = MOClassDescription.allocateDescriptionForClassWithName_superclass_(
16 | uniqueClassName,
17 | NSObject
18 | );
19 |
20 | delegateClassDesc.registerClass();
21 |
22 | // Handler storage
23 |
24 | var handlers = {};
25 |
26 | // Define interface
27 |
28 | this.setHandlerForSelector = function(selectorString, func) {
29 | var handlerHasBeenSet = selectorString in handlers;
30 | var selector = NSSelectorFromString(selectorString);
31 |
32 | handlers[selectorString] = func;
33 |
34 | if (!handlerHasBeenSet) {
35 | /*
36 | For some reason, Mocha acts weird about arguments:
37 | https://github.com/logancollins/Mocha/issues/28
38 |
39 | We have to basically create a dynamic handler with a likewise dynamic number of predefined arguments.
40 | */
41 |
42 | var dynamicHandler = function() {
43 | var functionToCall = handlers[selectorString];
44 |
45 | if (!functionToCall) return;
46 |
47 | return functionToCall.apply(delegateClassDesc, arguments);
48 | };
49 |
50 | var args = [], regex = /:/g;
51 | while ((match = regex.exec(selectorString)))
52 | args.push("arg" + args.length);
53 |
54 | dynamicFunction = eval(
55 | "(function(" +
56 | args.join(",") +
57 | "){ return dynamicHandler.apply(this, arguments); })"
58 | );
59 |
60 | delegateClassDesc.addInstanceMethodWithSelector_function_(
61 | selector,
62 | dynamicFunction
63 | );
64 | }
65 | };
66 |
67 | this.removeHandlerForSelector = function(selectorString) {
68 | delete handlers[selectorString];
69 | };
70 |
71 | this.getHandlerForSelector = function(selectorString) {
72 | return handlers[selectorString];
73 | };
74 |
75 | this.getAllHandlers = function() {
76 | return handlers;
77 | };
78 |
79 | this.getClass = function() {
80 | return NSClassFromString(uniqueClassName);
81 | };
82 |
83 | this.getClassInstance = function() {
84 | return NSClassFromString(uniqueClassName).new();
85 | };
86 |
87 | // Conveience
88 |
89 | if (typeof selectorHandlerDict == "object") {
90 | for (var selectorString in selectorHandlerDict) {
91 | this.setHandlerForSelector(
92 | selectorString,
93 | selectorHandlerDict[selectorString]
94 | );
95 | }
96 | }
97 | };
98 |
--------------------------------------------------------------------------------
/Sketch Search Everywhere.sketchplugin/Contents/Sketch/app.js:
--------------------------------------------------------------------------------
1 | var App = {
2 | ADDITIONS_HEIGHT: 200,
3 | IS_EXPAND: false,
4 | filters: {
5 | type: "stringValue",
6 | layerClass: "TextLayer"
7 | },
8 | init: function(ctx) {
9 | this.ctx = ctx;
10 | this.doc = ctx.document;
11 | this.sketch = ctx.api();
12 | },
13 | closeWindow: function() {
14 | log("Window closed!");
15 | App.COScript.setShouldKeepAround(false);
16 | App.threadDictionary.removeObjectForKey(App.identifier);
17 | App.SearchEverywhere.close();
18 | },
19 | setFrameExpand: function() {
20 | if (this.IS_EXPAND) return false;
21 |
22 | var _frame = App.SearchEverywhere.frame();
23 | _frame.size.height += this.ADDITIONS_HEIGHT;
24 | _frame.origin.y -= this.ADDITIONS_HEIGHT;
25 | App.SearchEverywhere.setFrame_display(_frame, true);
26 |
27 | var _frame_ = App.webView.frame();
28 | _frame_.size.height += this.ADDITIONS_HEIGHT;
29 | App.webView.setFrame(_frame_);
30 |
31 | log("expanded");
32 | this.IS_EXPAND = true;
33 | },
34 | setFrameContract: function(_coscript) {
35 | if (!App.IS_EXPAND) return false;
36 |
37 | var _frame = App.SearchEverywhere.frame();
38 | _frame.size.height -= App.ADDITIONS_HEIGHT;
39 | _frame.origin.y += App.ADDITIONS_HEIGHT;
40 | App.SearchEverywhere.setFrame_display(_frame, true);
41 |
42 | var _frame_ = App.webView.frame();
43 | _frame_.size.height -= App.ADDITIONS_HEIGHT;
44 | App.webView.setFrame(_frame_);
45 |
46 | App.IS_EXPAND = false;
47 | },
48 | findLayersMatchingPredicate_inContainer_filterByType: function(
49 | predicate,
50 | container,
51 | layerType
52 | ) {
53 | var scope;
54 | switch (layerType) {
55 | case MSPage:
56 | scope = this.doc.pages();
57 | return scope.filteredArrayUsingPredicate(predicate);
58 |
59 | case MSArtboardGroup:
60 | if (typeof container !== "undefined" && container != nil) {
61 | if (container.className == "MSPage") {
62 | scope = container.artboards();
63 | return scope.filteredArrayUsingPredicate(predicate);
64 | }
65 | } else {
66 | // search all pages
67 | var filteredArray = NSArray.array();
68 | var loopPages = this.doc.pages().objectEnumerator(), page;
69 | while ((page = loopPages.nextObject())) {
70 | scope = page.artboards();
71 | filteredArray = filteredArray.arrayByAddingObjectsFromArray(
72 | scope.filteredArrayUsingPredicate(predicate)
73 | );
74 | }
75 | return filteredArray;
76 | }
77 | break;
78 |
79 | default:
80 | if (typeof container !== "undefined" && container != nil) {
81 | scope = container.children();
82 | return scope.filteredArrayUsingPredicate(predicate);
83 | } else {
84 | // search all pages
85 | var filteredArray = NSArray.array();
86 | var loopPages = this.doc.pages().objectEnumerator(), page;
87 | var pages = this.doc.pages();
88 |
89 | while ((page = loopPages.nextObject())) {
90 | scope = page.children();
91 | filteredArray = filteredArray.arrayByAddingObjectsFromArray(
92 | scope.filteredArrayUsingPredicate(predicate)
93 | );
94 | }
95 | return filteredArray;
96 | }
97 | }
98 | return NSArray.array(); // Return an empty array if no matches were found
99 | },
100 | findLayers_inContainer_filterByType: function(
101 | textContent,
102 | container,
103 | layerType,
104 | predicateString
105 | ) {
106 | var predicate = typeof layerType == nil || !layerType
107 | ? NSPredicate.predicateWithFormat(predicateString, textContent)
108 | : NSPredicate.predicateWithFormat(
109 | predicateString + " && className == %@",
110 | textContent,
111 | "MS" + layerType
112 | );
113 |
114 | log(predicate);
115 | return this.findLayersMatchingPredicate_inContainer_filterByType(
116 | predicate,
117 | container,
118 | undefined
119 | );
120 | },
121 | getLayersAttrs: function(layers) {
122 | var arr = [];
123 | layers.forEach(function(layer) {
124 | var str =
125 | "name: " +
126 | encodeURIComponent(layer.name()) +
127 | ";" +
128 | "id: " +
129 | layer.objectID() +
130 | ";" +
131 | "class: " +
132 | layer.class() +
133 | ";" +
134 | // "isFlippedVertical: " +
135 | // layer.isFlippedVertical() +
136 | // ";" +
137 | // "isVisible: " +
138 | // layer.isVisible() +
139 | // ";" +
140 | // "nameIsFixed: " +
141 | // layer.nameIsFixed() +
142 | // ";" +
143 | // "heightIsClipped: " +
144 | // layer.heightIsClipped() +
145 | // ";" +
146 | layer.CSSAttributes().join("");
147 |
148 | if (layer.class() == "MSTextLayer") {
149 | str += "value: " + encodeURIComponent(layer.stringValue()) + ";";
150 | }
151 |
152 | if (layer.parentGroup()) {
153 | str +=
154 | "parentClass: " +
155 | layer.parentGroup().class() +
156 | ";" +
157 | "parentName: " +
158 | encodeURIComponent(layer.parentGroup().name()) +
159 | ";";
160 | }
161 |
162 | arr.push(str);
163 | });
164 |
165 | return arr;
166 | },
167 | // debounce: function(fn, delay) {
168 | // var timer = null;
169 | // return function() {
170 | // var context = this, args = arguments;
171 | // clearTimeout(timer);
172 |
173 | // coscript.setShouldKeepAround_(true);
174 | // coscript.scheduleWithInterval_jsFunction(delay / 1000, function() {
175 | // fn.apply(context, args);
176 | // });
177 | // };
178 | // },
179 | selectLayer: function(objectID) {
180 | var selectedLayer = App.findLayers_inContainer_filterByType(
181 | objectID,
182 | nil,
183 | App.filters.layerClass,
184 | "objectID == %@"
185 | );
186 |
187 | selectedLayer = selectedLayer.firstObject();
188 |
189 | var pageOfLayer = this.findPageOfLayer(selectedLayer);
190 |
191 | // log(pageOfLayer);
192 |
193 | // Set current page
194 | this.doc.setCurrentPage(pageOfLayer);
195 |
196 | // Clear other selections, and select this layer
197 | selectedLayer.select_byExpandingSelection(true, false);
198 |
199 | // Center the selection
200 | this.doc.currentView().centerRect_(selectedLayer.absoluteRect().rect());
201 |
202 | // Tell user that choose layer sucess!
203 | this.doc.showMessage("Select success!!!");
204 |
205 | this.IS_EXPAND = false;
206 | App.closeWindow();
207 | },
208 | findPageOfLayer(layer) {
209 | var _layer = layer;
210 | // log(_layer);
211 | while (_layer.class().toString().toLowerCase() != "mspage") {
212 | // log(_layer);
213 | _layer = _layer.parentGroup();
214 | }
215 |
216 | return _layer;
217 | }
218 | };
219 |
--------------------------------------------------------------------------------
/Sketch Search Everywhere.sketchplugin/Contents/Sketch/main.js:
--------------------------------------------------------------------------------
1 | @import 'MochaJSDelegate.js';
2 | @import 'webView.js';
3 | @import 'window.js';
4 | @import 'app.js';
5 |
6 | function onRun(context) {
7 | // initializing...
8 | App.init(context);
9 |
10 | openWindow(
11 | getWebView(
12 | context.plugin.urlForResourceNamed("window.html").path()
13 | )
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/Sketch Search Everywhere.sketchplugin/Contents/Sketch/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "author" : "MrPeak",
3 | "commands" : [
4 | {
5 | "script" : "main.js",
6 | "handler" : "onRun",
7 | "shortcut" : "command alt f",
8 | "name" : "Search Everywhere",
9 | "identifier" : "run"
10 | }
11 | ],
12 | "menu": {
13 | "items": [
14 | "run"
15 | ]
16 | },
17 | "identifier" : "com.sketchapp.examples.search-text",
18 | "version" : "1.0",
19 | "description" : "Search layer and select it, by matching `textValue`, `name` or `ObjectID`.",
20 | "authorEmail" : "gfeng.peak@gmail.com",
21 | "name" : "Search Everywhere"
22 | }
23 |
--------------------------------------------------------------------------------
/Sketch Search Everywhere.sketchplugin/Contents/Sketch/webView.js:
--------------------------------------------------------------------------------
1 | // Add Web View to window
2 |
3 | @import 'app.js';
4 |
5 | function getWebView(urlPath) {
6 | var webView = WebView.alloc().initWithFrame(NSMakeRect(0, 0, 480, 120));
7 | var windowObject = webView.windowScriptObject();
8 |
9 | // Awesome library
10 | var delegate = new MochaJSDelegate({
11 | "webView:didFinishLoadForFrame:": function(webView, webFrame) {
12 | // Something to be done after loading the webview
13 | //
14 | //
15 | },
16 |
17 | "webView:didChangeLocationWithinPageForFrame:": function(
18 | webView,
19 | webFrame
20 | ) {
21 | var locationHash = windowObject.evaluateWebScript("window.location.hash");
22 | // log(locationHash);
23 | if (locationHash.indexOf("@selectedLayerID=") > -1) {
24 | // Recieve "selete" signal
25 | var objectID = locationHash.slice("#@selectedLayerID=".length).trim();
26 | App.selectLayer(objectID);
27 | } else if (locationHash.indexOf("@query=") > -1) {
28 | // Reciew "query" signal
29 | var queryData = JSON.parse(
30 | locationHash.slice("#@query=".length).trim()
31 | );
32 | var arr = [];
33 |
34 | if (queryData.value.trim()) {
35 | var filters = queryData.filters;
36 | // log(queryData);
37 |
38 | App.filters = filters;
39 |
40 | // Find qualified layers
41 | var matchedLayers = App.findLayers_inContainer_filterByType(
42 | queryData.value,
43 | nil,
44 | App.filters.layerClass || nil,
45 | App.filters.type + " CONTAINS [c] %@"
46 | );
47 |
48 | // Get layers' info
49 | arr = App.getLayersAttrs(matchedLayers);
50 | }
51 |
52 | // Limit results' length to 15.
53 | arr = arr.slice(0, 15);
54 |
55 | // Execute webview's gloabl function, and pass data to it
56 | windowObject.evaluateWebScript(
57 | 'window.App.renderList("' + arr.join("|||") + '")'
58 | );
59 |
60 | if (arr.length) {
61 | // Expand box
62 | App.setFrameExpand();
63 | } else {
64 | // Contract box
65 | App.setFrameContract();
66 | }
67 | }
68 | }
69 | });
70 |
71 | webView.setFrameLoadDelegate_(delegate.getClassInstance());
72 | webView.setMainFrameURL_(urlPath);
73 |
74 | App.webView = webView;
75 | return webView;
76 | }
77 |
--------------------------------------------------------------------------------
/Sketch Search Everywhere.sketchplugin/Contents/Sketch/window.js:
--------------------------------------------------------------------------------
1 | @import 'webView.js';
2 | @import 'app.js';
3 |
4 | function openWindow(webView) {
5 | // SearchEverywhere main window
6 | var threadDictionary = NSThread.mainThread().threadDictionary();
7 | var identifier = "com.github.mrpeak.search_text";
8 |
9 | // Set to global
10 | App.threadDictionary = threadDictionary;
11 | App.identifier = identifier;
12 |
13 | if (threadDictionary[identifier]) {
14 | return;
15 | }
16 |
17 | var windowWidth = 480, windowHeight = 120;
18 | var SearchEverywhere = NSPanel.alloc().init();
19 | SearchEverywhere.setFrame_display(
20 | NSMakeRect(0, 0, windowWidth, windowHeight),
21 | true
22 | );
23 |
24 | SearchEverywhere.setStyleMask(
25 | NSTexturedBackgroundWindowMask | NSTitledWindowMask | NSClosableWindowMask
26 | );
27 |
28 | SearchEverywhere.setBackgroundColor(NSColor.colorWithRed_green_blue_alpha(255 / 255, 250 / 255, 240 / 255, 1));
29 |
30 | // Titlebar
31 | SearchEverywhere.setTitle("Search Everywhere");
32 | SearchEverywhere.setTitlebarAppearsTransparent(true);
33 | SearchEverywhere.becomeKeyWindow();
34 | SearchEverywhere.setLevel(NSFloatingWindowLevel);
35 |
36 | threadDictionary[identifier] = SearchEverywhere;
37 |
38 | // Long-running script
39 | COScript.currentCOScript().setShouldKeepAround_(true);
40 |
41 | App.COScript = COScript.currentCOScript();
42 |
43 | SearchEverywhere.contentView().addSubview(webView);
44 | SearchEverywhere.center();
45 | SearchEverywhere.makeKeyAndOrderFront(nil);
46 |
47 | SearchEverywhere.standardWindowButton(NSWindowZoomButton).setHidden(true);
48 | SearchEverywhere.standardWindowButton(NSWindowMiniaturizeButton).setHidden(true);
49 | // Close Window
50 | var closeButton = SearchEverywhere.standardWindowButton(NSWindowCloseButton);
51 |
52 | App.SearchEverywhere = SearchEverywhere; // set to global
53 |
54 | closeButton.setCOSJSTargetFunction(function() {
55 | App.closeWindow();
56 | });
57 |
58 | closeButton.setAction("callAction:");
59 |
60 | return SearchEverywhere;
61 | }
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sketch-search-everywhere",
3 | "version": "1.0.3",
4 | "description": "Search layer and select it, by matching `textValue`, `name` or `ObjectID`.",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "webpack",
8 | "start": "webpack-dev-server",
9 | "watch": "webpack --progress --colors --watch",
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "author": "MrPeak",
13 | "license": "MIT",
14 | "dependencies": {
15 | "antd": "^2.11.0",
16 | "react": "^15.6.0",
17 | "react-dom": "^15.6.0"
18 | },
19 | "devDependencies": {
20 | "babel-plugin-import": "^1.2.1",
21 | "css-loader": "^0.28.4",
22 | "html-webpack-plugin": "^2.28.0",
23 | "less": "^2.7.2",
24 | "less-loader": "^4.0.4",
25 | "style-loader": "^0.18.2",
26 | "babel-core": "^6.25.0",
27 | "babel-loader": "^7.0.0",
28 | "babel-preset-es2015": "^6.24.1",
29 | "babel-preset-react": "^6.24.1",
30 | "webpack": "^2.6.1",
31 | "webpack-dev-server": "^2.4.5"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/App.jsx:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | import React from "react";
4 | import ReactDOM, { findDOMNode } from "react-dom";
5 | import { LocaleProvider, Select, Row, Col } from "antd";
6 | import enUS from "antd/lib/locale-provider/en_US";
7 | import Filter from "./Filter.jsx";
8 | const Option = Select.Option;
9 |
10 | import "./App.less";
11 |
12 | class App extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | this.isSelecting = false;
16 | this.query = "";
17 | this.state = {
18 | selectID: null,
19 | filters: {
20 | type: "stringValue",
21 | layerClass: "TextLayer"
22 | }
23 | };
24 | }
25 |
26 | componentDidMount() {
27 | this.el = findDOMNode(this.refs.searcher);
28 | this.el.querySelector(".ant-select-search__field").focus();
29 | }
30 |
31 | onChangeSearchType(ev) {
32 | const value = ev.target.value;
33 | console.log("onChangeSearchType", value);
34 | const filters = {
35 | type: value,
36 | layerClass: this.state.filters.layerClass
37 | };
38 |
39 | this._query(filters);
40 | this.setState({ filters });
41 | }
42 |
43 | onFilterClassType(value) {
44 | console.log("onFilterClassType", value);
45 |
46 | const filters = {
47 | type: this.state.filters.type,
48 | layerClass: value || ""
49 | };
50 | this._query(filters);
51 | this.setState({ filters });
52 | }
53 |
54 | renderList(value) {
55 | console.log('responseData:', value.length);
56 | let list = value.split("|||");
57 |
58 | list = list.map(el => {
59 | const obj = {};
60 | el.split(";").forEach(_el => {
61 | const item = _el.split(":");
62 | obj[item[0]] = item[1];
63 | });
64 | return obj;
65 | });
66 |
67 | this.isSelecting = false;
68 | const options = [];
69 | list.forEach((el, i) => {
70 | if (Object.keys(el).length && el.id) {
71 | const isTextLayer = el.class.toLowerCase().trim() == "mstextlayer";
72 | options.push(
73 |
74 |
75 |
76 | {decodeURIComponent(el.name)}
77 |
78 |
79 | {(() => {
80 | if (isTextLayer) {
81 | return (
82 |
83 |
92 |
93 | );
94 | } else {
95 | return {el.class};
96 | }
97 | })()}
98 |
99 |
100 | {decodeURIComponent(el.parentName)}
101 |
102 | {el.parentClass}
103 |
104 |
105 | );
106 | }
107 | });
108 |
109 | if (options.length) {
110 | options.unshift(
111 |
112 |
113 | name
114 |
115 | {this.state.filters.layerClass.indexOf("TextLayer") > -1
116 | ? "color"
117 | : "class"}
118 |
119 | parent name
120 | parent class
121 |
122 |
123 | );
124 | }
125 |
126 | console.log(options);
127 | this.el.querySelector(".ant-select-search__field").focus();
128 | this.setState({ options });
129 | }
130 |
131 | onSelect(value) {
132 | // Send ObjectID to sketch plugin
133 | console.log("onSelect!");
134 | this.isSelecting = true;
135 |
136 | // send data
137 | window.location.hash = "@selectedLayerID=" + value;
138 |
139 | setTimeout(() => {
140 | this.isSelecting = false;
141 | }, 10);
142 | }
143 |
144 | onBlur() {
145 | console.log("onBlur!");
146 | // request
147 | window.location.hash = "@query=" + JSON.stringify({ value: "" });
148 | }
149 |
150 | onInput(value) {
151 | if (this.isSelecting) {
152 | console.log("onSelect?!");
153 | return false;
154 | }
155 |
156 | this.query = value;
157 |
158 | console.log("onInput!");
159 |
160 | this._query(this.state.filters);
161 | }
162 | _query(filters) {
163 | const data = {
164 | value: this.query,
165 | filters: filters,
166 | callback: "renderList"
167 | };
168 |
169 | console.log('queryParam:', data);
170 | // request
171 | window.location.hash = "@query=" + JSON.stringify(data);
172 | }
173 | render() {
174 | return (
175 |
176 |
177 |
182 |
198 | {this.state.options}
199 |
200 |
201 |
202 |
203 | );
204 | }
205 | }
206 |
207 | // exports to global
208 | window.App = ReactDOM.render( , document.getElementById("app-container"));
209 |
--------------------------------------------------------------------------------
/src/components/App.less:
--------------------------------------------------------------------------------
1 | @import '~antd/dist/antd.less';
2 |
3 | #app-container {
4 | padding: 20px;
5 | padding-top: 40px;
6 | border-radius: 4px;
7 | height: 100%;
8 | }
9 |
10 | body {
11 | height: 100%;
12 | }
13 |
14 | .container {
15 | width: 100%;
16 | height: 100%;
17 | margin: 0 auto;
18 | background: rgba(255,250,240,1);
19 | background: -moz-linear-gradient(top, rgba(255,250,240,1) 0%, rgba(254,249,241,1) 11%, rgba(246,246,246,1) 78%, rgba(255,255,255,1) 100%);
20 | background: -webkit-gradient(left top, left bottom, color-stop(0%, rgba(255,250,240,1)), color-stop(11%, rgba(254,249,241,1)), color-stop(78%, rgba(246,246,246,1)), color-stop(100%, rgba(255,255,255,1)));
21 | background: -webkit-linear-gradient(top, rgba(255,250,240,1) 0%, rgba(254,249,241,1) 11%, rgba(246,246,246,1) 78%, rgba(255,255,255,1) 100%);
22 | background: -o-linear-gradient(top, rgba(255,250,240,1) 0%, rgba(254,249,241,1) 11%, rgba(246,246,246,1) 78%, rgba(255,255,255,1) 100%);
23 | background: -ms-linear-gradient(top, rgba(255,250,240,1) 0%, rgba(254,249,241,1) 11%, rgba(246,246,246,1) 78%, rgba(255,255,255,1) 100%);
24 | background: linear-gradient(to bottom, rgba(255,250,240,1) 0%, rgba(254,249,241,1) 11%, rgba(246,246,246,1) 78%, rgba(255,255,255,1) 100%);
25 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fffaf0', endColorstr='#ffffff', GradientType=0 );
26 | }
27 |
28 | .results-container {
29 | max-height: 192px;
30 | // overflow: scroll;
31 | }
32 |
33 | .searcher {
34 | margin-top: 12px;
35 | }
36 |
37 | [class^="ant-col-"] {
38 | text-overflow: ellipsis;
39 | white-space: nowrap;
40 | overflow: hidden;
41 | }
--------------------------------------------------------------------------------
/src/components/Filter.jsx:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | import React from "react";
4 | import ReactDOM from "react-dom";
5 | import { Select, Switch, Radio, Tooltip } from "antd";
6 | const Option = Select.Option;
7 | const RadioButton = Radio.Button;
8 | const RadioGroup = Radio.Group;
9 |
10 | import "./Filter.less";
11 |
12 | class Filter extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | }
16 |
17 | render() {
18 | const children = [];
19 | const classDictionary = [
20 | "All",
21 | "ShapeGroup",
22 | "BitmapLayer",
23 | "TextLayer",
24 | "SymbolInstance",
25 | "ArtboardGroup",
26 | "LayerGroup"
27 | ];
28 |
29 | classDictionary.forEach((_class, i) => {
30 | children.push(
31 | {_class}
32 | );
33 | });
34 |
35 | console.log(5413)
36 |
37 | return (
38 |
39 |
40 | By Name
41 | By Text
42 | By Id
43 |
44 |
45 |
52 | option.props.children.toLowerCase().indexOf(input.toLowerCase()) >=
53 | 0}
54 | onChange={this.props.onFilterClassType}
55 | disabled={this.props.filters.type === 'stringValue'}
56 | >
57 | {children}
58 |
59 |
60 | );
61 | }
62 | }
63 |
64 | export default Filter;
65 |
--------------------------------------------------------------------------------
/src/components/Filter.less:
--------------------------------------------------------------------------------
1 |
2 | // .class-selector {
3 | // max-height: 64px;
4 | // overflow: scroll;
5 | // }
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Search Everywhere
6 |
7 |
8 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | const HtmlWebpackPlugin = require('html-webpack-plugin');
6 | const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
7 | template: './src/index.html',
8 | filename: 'window.html',
9 | inject: 'body'
10 | });
11 |
12 | module.exports = {
13 | entry: './src/components/App.jsx',
14 | output: {
15 | path: path.resolve('./Sketch Search Everywhere.sketchplugin/Contents/Resources/'), // Contents/Resources/script
16 | filename: 'index_bundle.js'
17 | },
18 | module: {
19 | rules: [
20 | {
21 | test: /\.js$/,
22 | exclude: /node_modules/,
23 | use: {
24 | loader: 'babel-loader'
25 | }
26 | },
27 | {
28 | test: /\.jsx$/,
29 | exclude: /node_modules/,
30 | use: {
31 | loader: 'babel-loader'
32 | }
33 | },
34 | {
35 | test: /\.less$/,
36 | use: [
37 | {
38 | loader: 'style-loader' // creates style nodes from JS strings
39 | },
40 | {
41 | loader: 'css-loader' // translates CSS into CommonJS
42 | },
43 | {
44 | loader: 'less-loader' // compiles Less to CSS
45 | }
46 | ]
47 | }
48 | ]
49 | },
50 | plugins: [HtmlWebpackPluginConfig]
51 | };
52 |
--------------------------------------------------------------------------------