├── .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 | ![Unused Style Remover](https://raw.githubusercontent.com/sonburn/unused-style-remover/master/images/logo.png) 2 | 3 | Remove unused layer and text styles. 4 | 5 | ![Unused Style Remover](https://raw.githubusercontent.com/sonburn/unused-style-remover/master/images/screenshot.png) 6 | 7 | 8 | runner-badge-blue 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 @sonburn 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 | --------------------------------------------------------------------------------