├── .gitattributes
├── images
├── logo.png
└── screenshot.png
├── Unused Style Remover.sketchplugin
└── Contents
│ ├── Resources
│ └── icon.png
│ └── Sketch
│ ├── manifest.json
│ └── script.js
├── appcast.xml
├── LICENSE.md
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
--------------------------------------------------------------------------------
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/unused-style-remover/HEAD/images/logo.png
--------------------------------------------------------------------------------
/images/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/unused-style-remover/HEAD/images/screenshot.png
--------------------------------------------------------------------------------
/Unused Style Remover.sketchplugin/Contents/Resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/unused-style-remover/HEAD/Unused Style Remover.sketchplugin/Contents/Resources/icon.png
--------------------------------------------------------------------------------
/appcast.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Unused Style Remover
5 | http://sparkle-project.org/files/sparkletestcast.xml
6 | Remove unused layer and text styles.
7 | en
8 | -
9 | Version 0.5
10 |
11 |
13 | Fixes for Sketch 53.
14 |
15 | ]]>
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 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 |
--------------------------------------------------------------------------------
/Unused Style Remover.sketchplugin/Contents/Sketch/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "Unused Style Remover",
3 | "description" : "Remove unused layer and text styles.",
4 | "author" : "Jason Burns",
5 | "homepage" : "https://github.com/sonburn/unused-style-remover",
6 | "version" : "0.5",
7 | "identifier" : "com.sonburn.sketchplugins.unused-style-remover",
8 | "appcast" : "https://raw.githubusercontent.com/sonburn/unused-style-remover/master/appcast.xml",
9 | "icon" : "icon.png",
10 | "commands" : [
11 | {
12 | "name" : "Unused Style Remover",
13 | "shortcut" : "cmd option shift u",
14 | "identifier" : "remover",
15 | "description" : "Remove unused layer and text styles.",
16 | "icon" : "icon.png",
17 | "script" : "script.js",
18 | "handler" : "remover"
19 | },
20 | {
21 | "name" : "Report Issue",
22 | "identifier" : "report",
23 | "description" : "Report an issue with Unused Style Remover.",
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 Unused Style Remover.",
40 | "icon" : "icon.png",
41 | "script" : "script.js",
42 | "handler" : "donate"
43 | }
44 | ],
45 | "menu" : {
46 | "title" : "Unused Style Remover",
47 | "items" : [
48 | "remover",
49 | "-",
50 | "report",
51 | "plugins",
52 | "donate"
53 | ]
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | Remove unused layer and text styles.
4 |
5 | 
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | # Usage
20 |
21 | * cmd option shift u - Remove unused layer and text styles
22 |
23 | # Installation
24 |
25 | ## Automatic
26 | Search for Unused Style Remover in [Sketchrunner](http://sketchrunner.com/), [Sketchpacks](https://sketchpacks.com/), or [Sketch Toolbox](http://sketchtoolbox.com/) if you have one of those installed.
27 |
28 | Once installed, Sketch will automatically notify you when an update is available (version 0.1 and later).
29 |
30 | ## Manual
31 |
32 | 1. Download and open unused-style-remover-master.zip
33 | 2. Navigate to Unused Style Remover.sketchplugin and copy/move to your plugins directory
34 |
35 | To find your plugins directory...
36 |
37 | 1. In the Sketch menu, navigate to Plugins > Manage Plugins...
38 | 2. Click the cog in the lower left of the plugins window, and click Reveal Plugins Folder
39 |
40 | # Changelog
41 |
42 | * **0.5** - Fixes for Sketch 53.
43 | * **0.4** - Fixes for Sketch 52.
44 | * **0.3** - Added plugin icon to manifest for Sketch 50.
45 | * **0.2** - Added section checkboxes, parameterized strings, and improved empty states.
46 | * **0.1** - Initial commit.
47 |
48 | # Contact
49 |
50 | Find me on Twitter
51 |
52 | # Support
53 |
54 | If you find this plugin helpful, or would like to support my plugins in general, buy me ☕️ via PayPal.
55 |
56 | # License
57 |
58 | Copyright (c) 2019 Jason Burns (Sonburn). See LICENSE.md for further details.
59 |
--------------------------------------------------------------------------------
/Unused Style Remover.sketchplugin/Contents/Sketch/script.js:
--------------------------------------------------------------------------------
1 | var debugMode = false;
2 |
3 | var remover = function(context) {
4 | var alertWindow = COSAlertWindow.new(),
5 | pluginIconPath = context.plugin.urlForResourceNamed("icon.png").path(),
6 | pluginIcon = NSImage.alloc().initByReferencingFile(pluginIconPath);
7 |
8 | alertWindow.setIcon(pluginIcon);
9 | alertWindow.setMessageText("Unused Style Remover");
10 | alertWindow.setInformativeText("Remove unused layer and text styles.");
11 |
12 | var contentFrameWidth = 300,
13 | contentFrameHeight = 192,
14 | contentFrameGutter = 15,
15 | listItemHeight = 24;
16 |
17 | var unusedLayerStyles = getUnusedStyles(0);
18 |
19 | if (unusedLayerStyles.length > 0) {
20 | var layerStyleTitle = createContentView(NSMakeRect(0,0,contentFrameWidth,18)),
21 | layerStyleCheckbox = createCheckbox({name:"",value:1},1,NSMakeRect(0,0,18,18)),
22 | layerStyleLabel = createBoldLabel("Unused Layer Styles (" + unusedLayerStyles.length + ")",12,NSMakeRect(22,0,contentFrameWidth-22,16));
23 |
24 | layerStyleCheckbox.setAction("callAction:");
25 | layerStyleCheckbox.setCOSJSTargetFunction(function(sender) {
26 | for (var i = 0; i < layerStyleCheckboxes.length; i++) {
27 | layerStyleCheckboxes[i].state = sender.state();
28 | }
29 | });
30 |
31 | layerStyleTitle.addSubview(layerStyleCheckbox);
32 | layerStyleTitle.addSubview(layerStyleLabel);
33 |
34 | alertWindow.addAccessoryView(layerStyleTitle);
35 |
36 | var layerStyleWidth = contentFrameWidth - contentFrameGutter,
37 | layerStyleFrameHeight = (unusedLayerStyles.length < 8) ? unusedLayerStyles.length * listItemHeight : contentFrameHeight,
38 | layerStyleFrame = createScrollView(NSMakeRect(0,0,contentFrameWidth,layerStyleFrameHeight)),
39 | layerStyleContent = createContentView(NSMakeRect(0,0,layerStyleWidth,unusedLayerStyles.length*listItemHeight)),
40 | layerStyleCount = 0,
41 | layerStyleCheckboxes = [];
42 |
43 | for (var i = 0; i < unusedLayerStyles.length; i++) {
44 | var unusedLayerStyle = createCheckbox({name:unusedLayerStyles[i].name(),value:i},1,NSMakeRect(0,listItemHeight*layerStyleCount,layerStyleWidth,listItemHeight));
45 |
46 | layerStyleCheckboxes.push(unusedLayerStyle);
47 | layerStyleContent.addSubview(unusedLayerStyle);
48 |
49 | layerStyleCount++;
50 | }
51 |
52 | layerStyleFrame.setDocumentView(layerStyleContent);
53 |
54 | alertWindow.addAccessoryView(layerStyleFrame);
55 | } else {
56 | var layerStyleLabel = createBoldLabel("Unused Layer Styles (" + unusedLayerStyles.length + ")",12,NSMakeRect(0,0,contentFrameWidth,16));
57 |
58 | alertWindow.addAccessoryView(layerStyleLabel);
59 | }
60 |
61 | var unusedTextStyles = getUnusedStyles(1);
62 |
63 | if (unusedTextStyles.length > 0) {
64 | var textStyleTitle = createContentView(NSMakeRect(0,0,contentFrameWidth,18)),
65 | textStyleCheckbox = createCheckbox({name:"",value:1},1,NSMakeRect(0,0,18,18)),
66 | textStyleLabel = createBoldLabel("Unused Text Styles (" + unusedTextStyles.length + ")",12,NSMakeRect(22,0,contentFrameWidth-22,16));
67 |
68 | textStyleCheckbox.setAction("callAction:");
69 | textStyleCheckbox.setCOSJSTargetFunction(function(sender) {
70 | for (var i = 0; i < textStyleCheckboxes.length; i++) {
71 | textStyleCheckboxes[i].state = sender.state();
72 | }
73 | });
74 |
75 | textStyleTitle.addSubview(textStyleCheckbox);
76 | textStyleTitle.addSubview(textStyleLabel);
77 |
78 | alertWindow.addAccessoryView(textStyleTitle);
79 |
80 | var textStyleWidth = contentFrameWidth - contentFrameGutter,
81 | textStyleFrameHeight = (unusedTextStyles.length < 8) ? unusedTextStyles.length * listItemHeight : contentFrameHeight,
82 | textStyleFrame = createScrollView(NSMakeRect(0,0,contentFrameWidth,textStyleFrameHeight)),
83 | textStyleContent = createContentView(NSMakeRect(0,0,textStyleWidth,unusedTextStyles.length*listItemHeight)),
84 | textStyleCount = 0,
85 | textStyleCheckboxes = [];
86 |
87 | for (var i = 0; i < unusedTextStyles.length; i++) {
88 | var unusedTextStyle = createCheckbox({name:unusedTextStyles[i].name(),value:i},1,NSMakeRect(0,listItemHeight*textStyleCount,textStyleWidth,listItemHeight));
89 |
90 | textStyleCheckboxes.push(unusedTextStyle);
91 | textStyleContent.addSubview(unusedTextStyle);
92 |
93 | textStyleCount++;
94 | }
95 |
96 | textStyleFrame.setDocumentView(textStyleContent);
97 |
98 | alertWindow.addAccessoryView(textStyleFrame);
99 | } else {
100 | var textStyleLabel = createBoldLabel("Unused Text Styles (" + unusedTextStyles.length + ")",12,NSMakeRect(0,0,contentFrameWidth,16));
101 |
102 | alertWindow.addAccessoryView(textStyleLabel);
103 | }
104 |
105 | if (unusedLayerStyles.length == 0 && unusedTextStyles.length == 0) {
106 | alertWindow.addButtonWithTitle("Close");
107 | } else {
108 | alertWindow.addButtonWithTitle("Remove Unused Styles");
109 | alertWindow.addButtonWithTitle("Cancel");
110 | }
111 |
112 | var alertResponse = alertWindow.runModal();
113 |
114 | if (alertResponse == 1000) {
115 | var layerStylesToRemove = NSMutableArray.array(),
116 | textStylesToRemove = NSMutableArray.array();
117 |
118 | for (var i = 0; i < unusedLayerStyles.length; i++) {
119 | if (layerStyleCheckboxes[i].state() == 1) layerStylesToRemove.addObject(unusedLayerStyles[i]);
120 | }
121 |
122 | for (var i = 0; i < unusedTextStyles.length; i++) {
123 | if (textStyleCheckboxes[i].state() == 1) textStylesToRemove.addObject(unusedTextStyles[i]);
124 | }
125 |
126 | for (var i = 0; i < layerStylesToRemove.length; i++) {
127 | var styles = context.document.documentData().layerStyles();
128 |
129 | if (styles.sharedStyleWithID) {
130 | styles.removeSharedStyle(styles.sharedStyleWithID(layerStylesToRemove[i].objectID()));
131 | } else {
132 | styles.removeSharedStyle(layerStylesToRemove[i]);
133 | }
134 | }
135 |
136 | for (var i = 0; i < textStylesToRemove.length; i++) {
137 | var styles = context.document.documentData().layerTextStyles();
138 |
139 | if (styles.sharedStyleWithID) {
140 | styles.removeSharedStyle(styles.sharedStyleWithID(textStylesToRemove[i].objectID()));
141 | } else {
142 | styles.removeSharedStyle(textStylesToRemove[i]);
143 | }
144 | }
145 |
146 | context.document.reloadInspector();
147 |
148 | context.document.showMessage(layerStylesToRemove.length + " layer styles, and " + textStylesToRemove.length + " text styles were removed");
149 |
150 | if (!debugMode) googleAnalytics(context,"remove","run");
151 | } else return false;
152 | }
153 |
154 | var report = function(context) {
155 | openUrl("https://github.com/sonburn/unused-style-remover/issues/new");
156 |
157 | if (!debugMode) googleAnalytics(context,"report","report");
158 | }
159 |
160 | var plugins = function(context) {
161 | openUrl("https://sonburn.github.io/");
162 |
163 | if (!debugMode) googleAnalytics(context,"plugins","plugins");
164 | }
165 |
166 | var donate = function(context) {
167 | openUrl("https://www.paypal.me/sonburn");
168 |
169 | if (!debugMode) googleAnalytics(context,"donate","donate");
170 | }
171 |
172 | function createBoldLabel(text,size,frame) {
173 | var label = NSTextField.alloc().initWithFrame(frame);
174 |
175 | label.setStringValue(text);
176 | label.setFont(NSFont.boldSystemFontOfSize(size));
177 | label.setBezeled(0);
178 | label.setDrawsBackground(0);
179 | label.setEditable(0);
180 | label.setSelectable(0);
181 |
182 | return label;
183 | }
184 |
185 | function createCheckbox(item,state,frame) {
186 | var checkbox = NSButton.alloc().initWithFrame(frame),
187 | state = (state == false) ? NSOffState : NSOnState;
188 |
189 | checkbox.setButtonType(NSSwitchButton);
190 | checkbox.setBezelStyle(0);
191 | checkbox.setTitle(item.name);
192 | checkbox.setTag(item.value);
193 | checkbox.setState(state);
194 |
195 | return checkbox;
196 | }
197 |
198 | function createContentView(frame) {
199 | var view = NSView.alloc().initWithFrame(frame);
200 |
201 | view.setFlipped(1);
202 |
203 | return view;
204 | }
205 |
206 | function createScrollView(frame) {
207 | var view = NSScrollView.alloc().initWithFrame(frame);
208 |
209 | view.setHasVerticalScroller(1);
210 |
211 | return view;
212 | }
213 |
214 | function getUnusedStyles(type) {
215 | var documentData = MSDocument.currentDocument().documentData();
216 | var unusedStyles = NSMutableArray.array();
217 | var styles = (type == 0) ? documentData.layerStyles().objects() : documentData.layerTextStyles().objects();
218 |
219 | styles.forEach(function(style) {
220 | var styles = style.allInstances();
221 |
222 | if (!styles.length) {
223 | unusedStyles.addObject(style);
224 | }
225 | });
226 |
227 | var sortByName = NSSortDescriptor.sortDescriptorWithKey_ascending("name",1);
228 |
229 | return unusedStyles.sortedArrayUsingDescriptors([sortByName]);
230 | }
231 |
232 | function googleAnalytics(context,category,action,label,value) {
233 | var trackingID = "UA-118988821-1",
234 | uuidKey = "google.analytics.uuid",
235 | uuid = NSUserDefaults.standardUserDefaults().objectForKey(uuidKey);
236 |
237 | if (!uuid) {
238 | uuid = NSUUID.UUID().UUIDString();
239 | NSUserDefaults.standardUserDefaults().setObject_forKey(uuid,uuidKey);
240 | }
241 |
242 | var url = "https://www.google-analytics.com/collect?v=1";
243 | // Tracking ID
244 | url += "&tid=" + trackingID;
245 | // Source
246 | url += "&ds=sketch" + MSApplicationMetadata.metadata().appVersion;
247 | // Client ID
248 | url += "&cid=" + uuid;
249 | // pageview, screenview, event, transaction, item, social, exception, timing
250 | url += "&t=event";
251 | // App Name
252 | url += "&an=" + encodeURI(context.plugin.name());
253 | // App ID
254 | url += "&aid=" + context.plugin.identifier();
255 | // App Version
256 | url += "&av=" + context.plugin.version();
257 | // Event category
258 | url += "&ec=" + encodeURI(category);
259 | // Event action
260 | url += "&ea=" + encodeURI(action);
261 | // Event label
262 | if (label) {
263 | url += "&el=" + encodeURI(label);
264 | }
265 | // Event value
266 | if (value) {
267 | url += "&ev=" + encodeURI(value);
268 | }
269 |
270 | var session = NSURLSession.sharedSession(),
271 | task = session.dataTaskWithURL(NSURL.URLWithString(NSString.stringWithString(url)));
272 |
273 | task.resume();
274 | }
275 |
276 | function openUrl(url) {
277 | NSWorkspace.sharedWorkspace().openURL(NSURL.URLWithString(url));
278 | }
279 |
--------------------------------------------------------------------------------