├── .gitignore ├── Zeplin.sketchplugin └── Contents │ ├── Resources │ └── Icons │ │ ├── icZeplin.png │ │ └── icZeplinRunner.png │ └── Sketch │ ├── manifest.json │ ├── main.cocoascript │ └── utils.cocoascript ├── .github └── pull_request_template.md └── pull_request_template.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /Zeplin.sketchplugin/Contents/Resources/Icons/icZeplin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeplin/zeplin-sketch-plugin/HEAD/Zeplin.sketchplugin/Contents/Resources/Icons/icZeplin.png -------------------------------------------------------------------------------- /Zeplin.sketchplugin/Contents/Resources/Icons/icZeplinRunner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeplin/zeplin-sketch-plugin/HEAD/Zeplin.sketchplugin/Contents/Resources/Icons/icZeplinRunner.png -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Summary of the changes 2 | 3 | The changes in this PR include 4 | 5 | - [ ] ... 6 | 7 | Ask yourself the questions below and write their answers if needed: 8 | 9 | - Is there anything important to consider while reviewing? 10 | - Do you need to do any additional development or fixes before merging this PR? 11 | 12 | ## Checklist 13 | 14 | - [ ] My PR has a clear and complete description. 15 | - [ ] I have performed a self-review of my own code. 16 | - [ ] I have considered the security implications of this change. 17 | - [ ] I have tested the changes against older versions of Sketch. 18 | -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Change description 2 | 3 | > Description here 4 | 5 | ## Type of change 6 | - [ ] Bug fix (fixes an issue) 7 | - [ ] New feature (adds functionality) 8 | 9 | ## Related issues 10 | 11 | > Fix [#1]() 12 | 13 | ## Checklists 14 | 15 | ### Development 16 | 17 | - [ ] Lint rules pass locally 18 | - [ ] Application changes have been tested thoroughly 19 | - [ ] Automated tests covering modified code pass 20 | 21 | ### Security 22 | 23 | - [ ] Security impact of change has been considered 24 | - [ ] Code follows company security practices and guidelines 25 | 26 | ### Code review 27 | 28 | - [ ] Pull request has a descriptive title and context useful to a reviewer. Screenshots or screencasts are attached as necessary 29 | - [ ] "Ready for review" label attached and reviewers assigned 30 | - [ ] Changes have been reviewed by at least one other contributor 31 | - [ ] Pull request linked to task tracker where applicable 32 | -------------------------------------------------------------------------------- /Zeplin.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Zeplin", 3 | "description": "Export frames/artboards, symbols and styles to Zeplin. 🛵", 4 | "author": "Zeplin, Inc.", 5 | "authorEmail": "dev@zeplin.io", 6 | "homepage": "https://zeplin.io", 7 | "version": "1.16", 8 | "identifier": "io.zeplin.sketch-plugin", 9 | "icon": "Icons/icZeplin.png", 10 | "commands": [ 11 | { 12 | "name": "Export Selected…", 13 | "identifier": "export", 14 | "shortcut": "cmd ctrl e", 15 | "script": "main.cocoascript", 16 | "icon": "Icons/icZeplinRunner.png", 17 | "description": "Export selected frames/artboards and symbols to Zeplin." 18 | }, 19 | { 20 | "name": "Export Colors…", 21 | "identifier": "exportColors", 22 | "script": "main.cocoascript", 23 | "handler": "exportColors", 24 | "icon": "Icons/icZeplinRunner.png", 25 | "description": "Export colors to Zeplin." 26 | }, 27 | { 28 | "name": "Export Text Styles…", 29 | "identifier": "exportTextStyles", 30 | "script": "main.cocoascript", 31 | "handler": "exportTextStyles", 32 | "icon": "Icons/icZeplinRunner.png", 33 | "description": "Export text styles to Zeplin." 34 | }, 35 | { 36 | "name": "Frames/Artboards from Current Page…", 37 | "identifier": "artboardsFromCurrentPage", 38 | "script": "main.cocoascript", 39 | "handler": "exportArtboardsFromCurrentPage", 40 | "icon": "Icons/icZeplinRunner.png", 41 | "description": "Export frames/artboards from current page to Zeplin." 42 | }, 43 | { 44 | "name": "Frames/Artboards from All Pages…", 45 | "identifier": "artboardsFromAllPages", 46 | "script": "main.cocoascript", 47 | "handler": "exportArtboardsFromAllPages", 48 | "icon": "Icons/icZeplinRunner.png", 49 | "description": "Export frames/artboards from all pages to Zeplin." 50 | }, 51 | { 52 | "name": "Symbols from Current Page…", 53 | "identifier": "symbolsFromCurrentPage", 54 | "script": "main.cocoascript", 55 | "handler": "exportSymbolsFromCurrentPage", 56 | "icon": "Icons/icZeplinRunner.png", 57 | "description": "Export symbols from current page to Zeplin." 58 | }, 59 | { 60 | "name": "Symbols from All Pages…", 61 | "identifier": "symbolsFromAllPages", 62 | "script": "main.cocoascript", 63 | "handler": "exportSymbolsFromAllPages", 64 | "icon": "Icons/icZeplinRunner.png", 65 | "description": "Export symbols from all pages to Zeplin." 66 | }, 67 | { 68 | "name": "Exclude Sublayers", 69 | "identifier": "exclude-sublayers", 70 | "shortcut": "cmd shift x", 71 | "script": "main.cocoascript", 72 | "handler": "excludeSublayers", 73 | "icon": "Icons/icZeplinRunner.png", 74 | "description": "Exclude sublayers of selected groups or symbols." 75 | }, 76 | { 77 | "name": "Include Sublayers", 78 | "identifier": "include-sublayers", 79 | "shortcut": "cmd shift i", 80 | "script": "main.cocoascript", 81 | "handler": "includeSublayers", 82 | "icon": "Icons/icZeplinRunner.png", 83 | "description": "Include sublayers of selected groups or symbols." 84 | } 85 | ], 86 | "menu": { 87 | "items": [ 88 | "export", 89 | "-", 90 | "exportColors", 91 | "exportTextStyles", 92 | { 93 | "title": "Export All", 94 | "items": [ 95 | "artboardsFromCurrentPage", 96 | "artboardsFromAllPages", 97 | "-", 98 | "symbolsFromCurrentPage", 99 | "symbolsFromAllPages" 100 | ] 101 | }, 102 | "-", 103 | { 104 | "title": "Utilities", 105 | "items": [ 106 | "exclude-sublayers", 107 | "include-sublayers" 108 | ] 109 | } 110 | ] 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Zeplin.sketchplugin/Contents/Sketch/main.cocoascript: -------------------------------------------------------------------------------- 1 | @import "utils.cocoascript"; 2 | 3 | var onRun = function (context) { 4 | var artboards = []; 5 | if (supportsFrame()) { 6 | artboards = [context valueForKeyPath:@"selection.@distinctUnionOfObjects.parentFrame"]; 7 | } else { 8 | artboards = [context valueForKeyPath:@"selection.@distinctUnionOfObjects.parentArtboard"]; 9 | } 10 | 11 | if (![artboards count]) { 12 | [NSApp displayDialog:@"Please select the artboards you want to export to Zeplin.\n\n☝️ Selecting a layer inside the artboard should be enough." withTitle:@"No artboard selected"]; 13 | return; 14 | } 15 | 16 | if (!isSelectionExportable(artboards)) { 17 | return; 18 | } 19 | 20 | var temporaryPath = temporaryPathForDesignFile(context); 21 | if (!temporaryPath) { 22 | return; 23 | } 24 | 25 | exportArtboards(context, artboards, temporaryPath); 26 | } 27 | 28 | var exportColors = function (context) { 29 | var temporaryPath = temporaryPathForDesignFile(context); 30 | if (!temporaryPath) { 31 | return; 32 | } 33 | 34 | exportObjectsForType(context, @"colors", temporaryPath); 35 | } 36 | 37 | var exportTextStyles = function (context) { 38 | var temporaryPath = temporaryPathForDesignFile(context); 39 | if (!temporaryPath) { 40 | return; 41 | } 42 | 43 | exportObjectsForType(context, @"textStyles", temporaryPath); 44 | } 45 | 46 | var exportArtboardsFromCurrentPage = function (context) { 47 | var artboards = layersOfPagesWithType(NSArray.arrayWithObject(context.document.currentPage()), @"artboard"); 48 | if (![artboards count]) { 49 | var artboardType = supportsFrame() ? @"a frame" : @"an artboard"; 50 | [NSApp displayDialog:@"Please create " + artboardType + " to export to Zeplin." withTitle:@"No " + artboardType + " found"]; 51 | 52 | return; 53 | } 54 | 55 | if (!isSelectionExportable(artboards)) { 56 | return; 57 | } 58 | 59 | var temporaryPath = temporaryPathForDesignFile(context); 60 | if (!temporaryPath) { 61 | return; 62 | } 63 | 64 | exportArtboards(context, artboards, temporaryPath); 65 | } 66 | 67 | var exportArtboardsFromAllPages = function (context) { 68 | var artboards = layersOfPagesWithType(context.document.pages(), @"artboard"); 69 | if (![artboards count]) { 70 | var artboardType = supportsFrame() ? @"a frame" : @"an artboard"; 71 | [NSApp displayDialog:@"Please create " + artboardType + " to export to Zeplin." withTitle:@"No " + artboardType + " found"]; 72 | 73 | return; 74 | } 75 | 76 | if (!isSelectionExportable(artboards)) { 77 | return; 78 | } 79 | 80 | var temporaryPath = temporaryPathForDesignFile(context); 81 | if (!temporaryPath) { 82 | return; 83 | } 84 | 85 | exportArtboards(context, artboards, temporaryPath); 86 | } 87 | 88 | var exportSymbolsFromCurrentPage = function (context) { 89 | var artboards = layersOfPagesWithType(NSArray.arrayWithObject(context.document.currentPage()), @"symbol"); 90 | if (![artboards count]) { 91 | [NSApp displayDialog:@"Please create a symbol to export to Zeplin." withTitle:@"No symbol found"]; 92 | 93 | return; 94 | } 95 | 96 | if (!isSelectionExportable(artboards)) { 97 | return; 98 | } 99 | 100 | var temporaryPath = temporaryPathForDesignFile(context); 101 | if (!temporaryPath) { 102 | return; 103 | } 104 | 105 | exportArtboards(context, artboards, temporaryPath); 106 | } 107 | 108 | var exportSymbolsFromAllPages = function (context) { 109 | var artboards = layersOfPagesWithType(context.document.pages(), @"symbol"); 110 | if (![artboards count]) { 111 | [NSApp displayDialog:@"Please create a symbol to export to Zeplin." withTitle:@"No symbol found"]; 112 | 113 | return; 114 | } 115 | 116 | if (!isSelectionExportable(artboards)) { 117 | return; 118 | } 119 | 120 | var temporaryPath = temporaryPathForDesignFile(context); 121 | if (!temporaryPath) { 122 | return; 123 | } 124 | 125 | exportArtboards(context, artboards, temporaryPath); 126 | } 127 | 128 | var excludeSublayers = function (context) { 129 | var selection = context.selection; 130 | var layerEnumerator = [selection objectEnumerator]; 131 | var layer; 132 | 133 | while (layer = [layerEnumerator nextObject]) { 134 | var layerName = [layer name]; 135 | 136 | if (![layerName hasPrefix:@"-g-"]) { 137 | [layer setName:[@"-g-" stringByAppendingString:layerName]]; 138 | } 139 | 140 | layerName = nil; 141 | } 142 | 143 | layer = nil; 144 | layerEnumerator = nil; 145 | selection = nil; 146 | } 147 | 148 | var includeSublayers = function (context) { 149 | var selection = context.selection; 150 | var layerEnumerator = [selection objectEnumerator]; 151 | var layer; 152 | 153 | while (layer = [layerEnumerator nextObject]) { 154 | var layerName = [layer name]; 155 | 156 | if ([layerName hasPrefix:@"-g-"]) { 157 | [layer setName:[layerName substringFromIndex:3]]; 158 | } 159 | 160 | layerName = nil; 161 | } 162 | 163 | layer = nil; 164 | layerEnumerator = nil; 165 | selection = nil; 166 | } 167 | -------------------------------------------------------------------------------- /Zeplin.sketchplugin/Contents/Sketch/utils.cocoascript: -------------------------------------------------------------------------------- 1 | var supportsFrame = function() { 2 | var version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; 3 | 4 | return [version compare:@"2025.1" options:NSNumericSearch] != NSOrderedAscending; 5 | } 6 | 7 | var isSelectionExportable = function (layers) { 8 | var overSizedLayerName = nil; 9 | // `size` on `CGRect` fails on Mocha, on macOS 13.0 same as macOS 10.13, Sketch 45 and below. 10 | try { 11 | var layerPixelLimit = 1000000000; // 1e9 12 | 13 | var layersLoop = [layers objectEnumerator]; 14 | var layer = nil; 15 | while (layer = [layersLoop nextObject]) { 16 | var layerSize = layer.rect().size; 17 | if (layerSize.width * layerSize.height > layerPixelLimit) { 18 | overSizedLayerName = layer.name(); 19 | break; 20 | } 21 | } 22 | 23 | layerSize = nil; 24 | layer = nil; 25 | layersLoop = nil; 26 | layerPixelLimit = nil; 27 | } catch (error) { 28 | log("Checking oversized artboards failed with error “" + error + "”."); 29 | 30 | return true; 31 | } 32 | 33 | if (overSizedLayerName) { 34 | var artboardType = supportsFrame() ? @"frame" : @"artboard"; 35 | [NSApp displayDialog:@"Selected " + artboardType + " “" + overSizedLayerName + "” is too large to export. Due to a limitation in Sketch, it’s only possible to export " + artboardType + "s smaller than 30,000 px ⨉ 30,000 px.\n\n☝️ You can divide the " + artboardType + " into multiple " + artboardType + "s and export again." withTitle: [artboardType capitalizedString] + @" too large"]; 36 | 37 | return false; 38 | } 39 | 40 | return true; 41 | } 42 | 43 | var temporaryPath = function() { 44 | var name = [[[NSUUID UUID] UUIDString] stringByAppendingPathExtension:@"sketch"]; 45 | var temporaryDirectory = NSTemporaryDirectory(); 46 | var temporaryZeplinDirectory = [temporaryDirectory stringByAppendingPathComponent:@"io.zeplin.osx"]; 47 | 48 | var fileManager = [NSFileManager defaultManager]; 49 | if (![fileManager fileExistsAtPath:temporaryZeplinDirectory]) { 50 | [fileManager createDirectoryAtPath:temporaryZeplinDirectory withIntermediateDirectories:true attributes:nil error:nil]; 51 | } 52 | 53 | var path = [temporaryZeplinDirectory stringByAppendingPathComponent:name]; 54 | 55 | name = nil; 56 | temporaryDirectory = nil; 57 | temporaryZeplinDirectory = nil; 58 | isDir = nil; 59 | fileManager = nil; 60 | 61 | return path; 62 | } 63 | 64 | var temporaryPathForDesignFile = function (context) { 65 | var doc = context.document; 66 | var path = temporaryPath(); 67 | 68 | [doc showMessage:@"Saving document…"]; 69 | [doc saveToURL:[NSURL fileURLWithPath: path] ofType:@"com.bohemiancoding.sketch.drawing.single" forSaveOperation:NSSaveToOperation delegate:nil didSaveSelector:nil contextInfo:nil]; 70 | 71 | var fileManager = [NSFileManager defaultManager]; 72 | var stopTime = [NSDate dateWithTimeIntervalSinceNow:30]; 73 | while (![fileManager fileExistsAtPath:path] && [stopTime compare:[NSDate date]] == NSOrderedDescending) { 74 | [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 75 | } 76 | 77 | if (![fileManager fileExistsAtPath:path]) { 78 | [doc showMessage:@"Saving document failed…"]; 79 | 80 | return nil; 81 | } 82 | 83 | fileManager = nil; 84 | stopTime = nil; 85 | 86 | return path; 87 | } 88 | 89 | var directivesPath = function() { 90 | var name = [[[NSUUID UUID] UUIDString] stringByAppendingPathExtension:@"zpl"]; 91 | var temporaryDirectory = NSTemporaryDirectory(); 92 | var path = [temporaryDirectory stringByAppendingPathComponent:name]; 93 | 94 | temporaryDirectory = nil; 95 | name = nil; 96 | 97 | return path; 98 | } 99 | 100 | var defaultDirectives = function(context, temporaryPath) { 101 | var doc = context.document; 102 | 103 | var version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; 104 | var sketchtoolPath = [[NSBundle mainBundle] pathForResource:@"sketchtool" ofType:nil inDirectory:@"sketchtool/bin"]; 105 | var sketchmigratePath = [[NSBundle mainBundle] pathForResource:@"sketchmigrate" ofType:nil inDirectory:@"sketchtool/bin"]; 106 | 107 | var directives = [NSMutableDictionary dictionary]; 108 | [directives setObject:temporaryPath forKey:@"temporaryPath"]; 109 | [directives setObject:[[doc fileURL] path] forKey:@"path"]; 110 | [directives setObject:@"json" forKey:@"format"]; 111 | if (version) { 112 | [directives setObject:version forKey:@"version"]; 113 | } 114 | if (sketchtoolPath) { 115 | [directives setObject:sketchtoolPath forKey:@"sketchtoolPath"]; 116 | } 117 | if (sketchmigratePath) { 118 | [directives setObject:sketchmigratePath forKey:@"sketchmigratePath"]; 119 | } 120 | 121 | format = nil; 122 | version = nil; 123 | sketchmigratePath = nil; 124 | sketchtoolPath = nil; 125 | assetLibraries = nil; 126 | 127 | return directives; 128 | } 129 | 130 | var writeDirectives = function (directives, path) { 131 | if (!path) { 132 | return nil; 133 | } 134 | 135 | [directives writeToFile:path atomically:false]; 136 | 137 | return path; 138 | } 139 | 140 | var getShareId = function (context) { 141 | try { 142 | return context.document.cloudShare().shortID(); 143 | } catch (error) { 144 | log("Getting cloud file “shortID” failed with error “" + error + "”.")); 145 | } 146 | 147 | return nil; 148 | } 149 | 150 | var getSourceFileName = function (context) { 151 | try { 152 | return context.document.displayName(); 153 | } catch (error) { 154 | log("Getting source file name failed with error “" + error + "”.")); 155 | } 156 | 157 | return nil; 158 | } 159 | 160 | var launchZeplin = function (context, path) { 161 | var doc = context.document; 162 | var workspace = [NSWorkspace sharedWorkspace]; 163 | 164 | var applicationPath = [workspace absolutePathForAppBundleWithIdentifier:@"io.zeplin.osx"]; 165 | if (!applicationPath) { 166 | [NSApp displayDialog:@"Please make sure that you installed and launched it: https://zpl.io/download" withTitle:"Could not find Zeplin"]; 167 | 168 | return; 169 | } 170 | 171 | [doc showMessage:@"Launching Zeplin!"]; 172 | 173 | [workspace openFile:path withApplication:applicationPath andDeactivate:true]; 174 | 175 | workspace = nil; 176 | applicationPath = nil; 177 | } 178 | 179 | var exportArtboards = function (context, artboards, temporaryPath) { 180 | var doc = context.document; 181 | var shareId = getShareId(context); 182 | var sourceFileName = getSourceFileName(context); 183 | 184 | var artboardIds = [artboards valueForKeyPath:@"objectID"]; 185 | 186 | var layers = [[[doc documentData] allSymbols] arrayByAddingObjectsFromArray:artboards]; 187 | var pageIds = [layers valueForKeyPath:@"@distinctUnionOfObjects.parentPage.objectID"]; 188 | 189 | layers = nil; 190 | 191 | var containsArtboard = false; 192 | var artboardsLoop = [artboards objectEnumerator]; 193 | var artboard = nil; 194 | while (artboard = [artboardsLoop nextObject]) { 195 | if (isLayerOfType(artboard, @"artboard")) { 196 | containsArtboard = true; 197 | 198 | break; 199 | } 200 | } 201 | 202 | artboard = nil; 203 | artboardsLoop = nil; 204 | 205 | var uniqueArtboardSizes = []; 206 | // `size` on `CGRect` fails on Mocha, on macOS 13.0 same as macOS 10.13, Sketch 45 and below. 207 | try { 208 | var loop = [artboards objectEnumerator]; 209 | var artboard = nil; 210 | while (artboard = [loop nextObject]) { 211 | var artboardSize = artboard.rect().size; 212 | 213 | var isUnique = true; 214 | for (var k = 0; k < uniqueArtboardSizes.length; k++) { 215 | if (uniqueArtboardSizes[k].width == artboardSize.width && uniqueArtboardSizes[k].height == artboardSize.height) { 216 | isUnique = false; 217 | 218 | break; 219 | } 220 | } 221 | 222 | if (isUnique) { 223 | uniqueArtboardSizes.push({ 224 | width: artboardSize.width, 225 | height: artboardSize.height 226 | }); 227 | } 228 | 229 | artboardSize = nil; 230 | isUnique = nil; 231 | } 232 | 233 | artboard = nil; 234 | loop = nil; 235 | } catch (error) { 236 | log("Unique artboard sizes failed with error “" + error + "”."); 237 | } 238 | 239 | artboards = nil; 240 | 241 | var artboardNamesByIdentifier = {}; 242 | var pages = [doc pages]; 243 | var allArtboardsLoop = layersOfPagesWithType(pages, @"artboard").objectEnumerator(); 244 | var artboard = nil; 245 | while (artboard = [allArtboardsLoop nextObject]) { 246 | artboardNamesByIdentifier[artboard.objectID()] = artboard.name(); 247 | } 248 | 249 | pages = nil; 250 | artboard = nil; 251 | allArtboardsLoop = nil; 252 | 253 | var directives = defaultDirectives(context, temporaryPath); 254 | [directives setObject:@"artboards" forKey:@"type"]; 255 | [directives setObject:artboardIds forKey:@"artboardIds"]; 256 | [directives setObject:pageIds forKey:@"pageIds"]; 257 | [directives setObject:uniqueArtboardSizes forKey:@"artboardSizes"]; 258 | [directives setObject:artboardNamesByIdentifier forKey:@"artboardNames"]; 259 | [directives setObject:containsArtboard forKey:@"containsArtboard"]; 260 | 261 | if (shareId) { 262 | [directives setObject:shareId forKey:@"shareId"]; 263 | } 264 | 265 | if (sourceFileName) { 266 | [directives setObject:sourceFileName forKey:@"sourceFileName"]; 267 | } 268 | 269 | shareId = nil; 270 | sourceFileName = nil; 271 | artboardIds = nil; 272 | pageIds = nil; 273 | uniqueArtboardSizes = nil; 274 | artboardNamesByIdentifier = nil; 275 | 276 | var path = directivesPath(); 277 | writeDirectives(directives, path); 278 | 279 | directives = nil; 280 | 281 | launchZeplin(context, path); 282 | 283 | path = nil; 284 | } 285 | 286 | var exportObjectsForType = function (context, type, temporaryPath) { 287 | var directives = defaultDirectives(context, temporaryPath); 288 | [directives setObject:type forKey:@"type"]; 289 | 290 | var path = directivesPath(); 291 | writeDirectives(directives, path); 292 | 293 | directives = nil; 294 | 295 | launchZeplin(context, path); 296 | 297 | path = nil; 298 | } 299 | 300 | var layersOfPagesWithType = function (pages, type) { 301 | var layers = []; 302 | var pagesLoop = pages.objectEnumerator(); 303 | var page = nil; 304 | 305 | while (page = [pagesLoop nextObject]) { 306 | var pageLayers = []; 307 | var layersLoop = page.layers().objectEnumerator(); 308 | var layer = nil; 309 | 310 | while (layer = [layersLoop nextObject]) { 311 | if (isLayerOfType(layer, type)) { 312 | pageLayers.push(layer); 313 | } 314 | } 315 | 316 | layers = layers.concat(pageLayers); 317 | } 318 | 319 | return NSArray.arrayWithArray(layers); 320 | } 321 | 322 | var isLayerOfType = function (layer, type) { 323 | var layerClassName = NSStringFromClass([layer class]); 324 | if ([type isEqualToString: @"symbol"]) { 325 | if ([layerClassName isEqualToString:@"MSSymbolMaster"]) { 326 | return true; 327 | } 328 | } else if ([type isEqualToString: @"artboard"]) { 329 | if (supportsFrame()) { 330 | if ([layerClassName isEqualToString: @"MSLayerGroup"]) { 331 | return [layer valueForKeyPath: @"groupBehavior"] != 0; 332 | } 333 | } else { 334 | return [layerClassName isEqualToString: @"MSArtboardGroup"]; 335 | } 336 | } 337 | 338 | return false; 339 | } 340 | --------------------------------------------------------------------------------