├── demo.png ├── Readme.md └── remove duplicated styles.sketchplugin └── Contents └── Sketch ├── manifest.json └── script.cocoascript /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinozavrix/sketch_fix_styles/HEAD/demo.png -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Remove duplicate styles in Sketch 2 | 3 | ## BETA Version 4 | 5 | 6 | Styles duplicate when you copy objects with the same styles from different files. The script finds duplicate style names, changes style to first instance, and delete copies. 7 | 8 | The plugin fixes the styles names only and don't change the real properties (don't synchronize them) so all styles with the same names should have the same properties. 9 | 10 | You can "Check for duplicates" or "Remove duplicates". If you have big amount of elements (layers), script will work extended period of time, so don't worry if Sketch is "frozen". 11 | 12 | Please backup your .sketch file before use 13 | 14 | 15 | ## Before and After Demo 16 | ![Before and After Demo](https://raw.githubusercontent.com/dinozavrix/sketch_fix_styles/master/demo.png) -------------------------------------------------------------------------------- /remove duplicated styles.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : "", 3 | "commands" : [ 4 | { 5 | "script" : "script.cocoascript", 6 | "name" : "Remove duplicates", 7 | "handlers" : { 8 | "run" : "onRun" 9 | }, 10 | "identifier" : "com.bohemiancoding.sketch.runscriptidentifier" 11 | }, 12 | { 13 | "script" : "script.cocoascript", 14 | "name" : "Check for duplicates", 15 | "handlers" : { 16 | "run" : "onCheck" 17 | }, 18 | "identifier" : "com.bohemiancoding.sketch.runscriptidentifier_test" 19 | } 20 | 21 | ], 22 | "menu" : { 23 | "items" : [ 24 | "com.bohemiancoding.sketch.runscriptidentifier_test", 25 | "com.bohemiancoding.sketch.runscriptidentifier" 26 | ], 27 | "title" : "Remove duplicated styles" 28 | }, 29 | "identifier" : "com.example.sketch.2899bc59-a313-42ab-908b-a70264fd0420", 30 | "version" : "1.0", 31 | "description" : "", 32 | "authorEmail" : "", 33 | "name" : "styles fix" 34 | } 35 | -------------------------------------------------------------------------------- /remove duplicated styles.sketchplugin/Contents/Sketch/script.cocoascript: -------------------------------------------------------------------------------- 1 | var full_info_shape; 2 | var full_title_shape; 3 | var full_info_text; 4 | var full_title_text; 5 | 6 | var onRun = function(context) { 7 | var doc = context.document; 8 | var app = [NSApplication sharedApplication]; 9 | fix(context); 10 | doc.showMessage(full_title_shape); 11 | fix(context,true); 12 | doc.showMessage(full_title_text); 13 | [app displayDialog:full_info_shape withTitle:full_title_shape]; 14 | [app displayDialog:full_info_text withTitle:full_title_text]; 15 | onClear(context); 16 | } 17 | 18 | var onCheck = function(context) { 19 | var doc = context.document; 20 | var app = [NSApplication sharedApplication]; 21 | fix(context,false,true); 22 | doc.showMessage(full_title_shape); 23 | fix(context,true,true); 24 | doc.showMessage(full_title_text); 25 | [app displayDialog:full_info_shape withTitle:full_title_shape]; 26 | [app displayDialog:full_info_text withTitle:full_title_text]; 27 | } 28 | 29 | var onClear = function(context) { 30 | var app = [NSApplication sharedApplication]; 31 | var doc = context.document; 32 | var sharedStyleObjects = NSArray.alloc(); 33 | sharedStyleObjects = doc.documentData().layerStyles().objects(); 34 | //delete all empty name styles 35 | var info=0; 36 | for (var i = 0; i < sharedStyleObjects.count(); i++) { 37 | var sharedStyle = sharedStyleObjects.objectAtIndex(i); 38 | //log (sharedStyle.name().toString()); 39 | if (sharedStyle.name().toString().search(" ~ ")==0) 40 | { 41 | sharedStyleObjects.removeObjectAtIndex(i); 42 | i=0; 43 | info++; 44 | } 45 | } 46 | var infot=0; 47 | sharedStyleObjects = doc.documentData().layerTextStyles().objects(); 48 | //delete all empty name styles 49 | for (var i = 0; i < sharedStyleObjects.count(); i++) { 50 | var sharedStyle = sharedStyleObjects.objectAtIndex(i); 51 | if (sharedStyle.name().toString().search(" ~ ")==0) 52 | { 53 | //context.document.reloadInspector(); 54 | sharedStyleObjects.removeObjectAtIndex(i); 55 | i=0; 56 | //context.document.reloadInspector(); 57 | infot++; 58 | } 59 | } 60 | context.document.reloadInspector(); 61 | [app displayDialog:"Have a nice day!" withTitle:"Deleted shape styles: "+info+"\nDeleted text styles: "+infot]; 62 | 63 | 64 | 65 | } 66 | 67 | var fix = function(context,isText,check) { 68 | var info=""; 69 | var app = [NSApplication sharedApplication]; 70 | var doc = context.document; 71 | var sharedStyleObjects = NSArray.alloc(); 72 | var sharedStyleArr=[]; 73 | var sharedStyleId=[]; 74 | var sharedStyleToId=[]; 75 | var sharedStyleReplace=[]; 76 | sharedStyleObjects = doc.documentData().layerStyles().objects(); 77 | if (isText) sharedStyleObjects = doc.documentData().layerTextStyles().objects(); 78 | 79 | //delete all empty name styles 80 | for (var i = 0; i < sharedStyleObjects.count(); i++) { 81 | var sharedStyle = sharedStyleObjects.objectAtIndex(i); 82 | if (sharedStyle.name()==null){ 83 | sharedStyleObjects.removeObjectAtIndex(i); 84 | i=0; 85 | } 86 | } 87 | context.document.reloadInspector(); 88 | 89 | for (var i = 0; i < sharedStyleObjects.count(); i++) { 90 | var sharedStyle = sharedStyleObjects.objectAtIndex(i); 91 | found=false; 92 | for (var j = 0; j < i; j++){ 93 | //log(sharedStyle.name()+"&"+sharedStyleArr[j]+sharedStyleArr[j]+sharedStyleArr[j]+"="+(sharedStyle.name().toString().localeCompare(sharedStyleArr[j].toString()))) 94 | if (sharedStyle.name().toString().localeCompare(sharedStyleArr[j].toString())==0){ 95 | found=true; 96 | //[app displayDialog:sharedStyle.name() withTitle:"Alert Box Title"]; 97 | //j = sharedStyleArr.length 98 | sharedStyleReplace.push([j,sharedStyle.objectID(), sharedStyleId[j], sharedStyle.name()]); 99 | info+=sharedStyle.name()+"\n"; 100 | if (!check){ 101 | sharedStyle.setName(" ~ "+sharedStyle.name()); 102 | //sharedStyleObjects.removeObjectAtIndex(i); 103 | } 104 | //log () 105 | 106 | //delete sharedStyleObjects 107 | break; 108 | } 109 | } 110 | sharedStyleArr[i]=sharedStyle.name(); 111 | sharedStyleId[i]=sharedStyle.objectID(); 112 | if (!found){ 113 | //sharedStyleArr.push (sharedStyle.name()) 114 | //sharedStyleReplace[i]=sharedStyleId[j]; 115 | } 116 | } 117 | var text="" 118 | for (var j = 0; j < sharedStyleArr.length; j++){ 119 | text+=sharedStyleArr[j]+", "+sharedStyleId[j]+"\n"; 120 | } 121 | 122 | //[app displayDialog:sharedStyleReplace withTitle:"Alert Box Title"]; 123 | log(text); 124 | log(sharedStyleReplace); 125 | log(doc.currentPage().children().count()); 126 | count=0; 127 | doc.showMessage("Total objects: " +doc.currentPage().children().count()); 128 | //return; 129 | for (var i = 0; i < doc.currentPage().children().count(); i++) { 130 | var layer = doc.currentPage().children().objectAtIndex(i); 131 | if (layer.style && layer.style().sharedObjectID() != null) { 132 | //log (i+":"+layer.name()+layer.style().sharedObjectID()) 133 | for (j=0;j"+sharedStyleReplace[j][2]+" : "+sharedStyleReplace[j][3]); 144 | count++; 145 | //break; 146 | //log (layer.name()+" : "+layer.style().sharedObjectID().toString()+"->"+sharedStyleReplace[j][1].toString()) 147 | } 148 | } 149 | 150 | } 151 | 152 | } 153 | context.document.reloadInspector(); 154 | 155 | var changed="Found"; 156 | if (!check) changed="Changed"; 157 | 158 | 159 | 160 | if (!isText){ 161 | //[app displayDialog:info withTitle:changed+" "+count+" shape styles, with styles:"]; 162 | full_info_shape=info; 163 | full_title_shape=changed+" "+count+" shape styles, with styles:"; 164 | } 165 | else{ 166 | //[app displayDialog:info withTitle:changed+" "+count+" text styles, with styles:"]; 167 | full_info_text=info; 168 | full_title_text=changed+" "+count+" text styles, with styles:"; 169 | } 170 | 171 | 172 | }; 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | /* 183 | 184 | var text="" 185 | 186 | 187 | sharedStyleObjects = doc.documentData().layerStyles().objects(); 188 | for (var i = 0; i < sharedStyleObjects.count(); i++) { 189 | var sharedStyle = sharedStyleObjects.objectAtIndex(i); 190 | text+=(sharedStyle.objectID())+" ("+sharedStyle.name()+")\n" 191 | //print(sharedStyle.name()) 192 | } 193 | //var cnt=doc.currentPage().children().count() 194 | //var cnt="tets" 195 | //doc.showMessage(cnt.toString()); 196 | //[app displayDialog:text withTitle:"Alert Box Title"]; 197 | //doc.showMessage("df") 198 | var ids="" 199 | for (var i = 0; i < doc.currentPage().children().count(); i++) { 200 | var layer = doc.currentPage().children().objectAtIndex(i); 201 | //ids+=layer.style()+"\n" 202 | if ([layer isKindOfClass: MSShapeGroup] && layer.style() 203 | .sharedObjectID() != null) { 204 | ids=ids+i+"="+layer.name()+"="+layer.style().sharedObjectID()+"\n"; 205 | layer.style().setSharedObjectID("B220B18E-F34D-4F73-9A96-BC9974E1E543"); 206 | //layer.ResetSharedStyle(); 207 | //synchroniseInstancesOfSharedObject_withInstance() 208 | //[app displayDialog:layer.style().tests() withTitle:"Alert Box Title"]; 209 | context.document.reloadInspector(); 210 | 211 | //[app displayDialog:i withTitle:"Alert Box Title"]; 212 | } 213 | //if (layer.style().sharedObjectID()=="F4EDA26E-E34A-4D5E-960A-89D6986225FA") 214 | // [app displayDialog:i withTitle:"Alert Box Title"]; 215 | //print(sharedStyle.objectID()) 216 | //print(sharedStyle.name()) 217 | } 218 | [app displayDialog:ids withTitle:"Alert Box Title"]; 219 | 220 | 221 | 222 | 223 | 224 | }; 225 | 226 | 227 | /*function getLayersByLayerStyle (layerStyle, scope) { 228 | var predicate = NSPredicate.predicateWithFormat("(style.fill != NULL) && (FUNCTION(style.fill, 'isEqualForSync:asPartOfSymbol:', %@, nil) == YES)", layerStyle.fill()); 229 | // query page layers 230 | var queryResult = scope.filteredArrayUsingPredicate(predicate); 231 | 232 | return queryResult; 233 | }*/ 234 | --------------------------------------------------------------------------------