diff --git a/Zeplin.sketchplugin/Contents/Sketch/export.cocoascript b/Zeplin.sketchplugin/Contents/Sketch/export.cocoascript deleted file mode 100644 index 1a4503b..0000000 --- a/Zeplin.sketchplugin/Contents/Sketch/export.cocoascript +++ /dev/null @@ -1,323 +0,0 @@ -var onRun = function (context) { - var doc = context.document; - - if (!documentExportable(context)) { - return; - } - - var foreignSymbolsUpToDate = true; - // `MSBadgeController` is defined on Sketch 44, `activeWindowBadgingActions` is defined on Sketch 46. - try { - var activeActions = [[doc badgeController] activeWindowBadgingActions]; - var activeActionsLoop = [activeActions objectEnumerator]; - var action = nil; - while (action = [activeActionsLoop nextObject]) { - if ([action isKindOfClass:NSClassFromString(@"MSSyncLibraryAction")]) { - foreignSymbolsUpToDate = false; - - break; - } - } - - action = nil; - activeActionsLoop = nil; - activeActions = nil; - } catch (error) { - log("Foreign symbols up to date failed with error “" + error + "”."); - } - - if (!foreignSymbolsUpToDate) { - var alert = [NSAlert alertWithMessageText:@"Symbols not up to date" defaultButton:@"Continue and Export" alternateButton:@"Cancel" otherButton:nil informativeTextWithFormat:@"To capture the latest changes in your libraries, make sure that your symbols are up to date before exporting artboards to Zeplin.\n\n☝️ Select “Library Update Available” on the top right to review changes."]; - - if ([alert runModal] == NSAlertAlternateReturn) { - return; - } - - alert = nil; - } - - var artboards = [context valueForKeyPath:@"selection.@distinctUnionOfObjects.parentArtboard"]; - if (![artboards count]) { - [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"]; - return; - } - - var artboardIds = [artboards valueForKeyPath:@"objectID"]; - - var layers = [[[doc documentData] allSymbols] arrayByAddingObjectsFromArray:artboards]; - var pageIds = [layers valueForKeyPath:@"@distinctUnionOfObjects.parentPage.objectID"]; - - layers = nil; - - var containsArtboard = false; - var artboardsLoop = [artboards objectEnumerator]; - var artboard = nil; - while (artboard = [artboardsLoop nextObject]) { - var artboardClassName = NSStringFromClass([artboard class]); - if ([artboardClassName isEqualToString:@"MSArtboardGroup"]) { - containsArtboard = true; - - break; - } - } - - artboard = nil; - artboardsLoop = nil; - - var uniqueArtboardSizes = []; - // `size` on `CGRect` fails on Mocha, on macOS 10.13, Sketch 45 and below. - try { - var loop = [artboards objectEnumerator]; - var artboard = nil; - while (artboard = [loop nextObject]) { - var artboardSize = artboard.rect().size; - - var isUnique = true; - for (var k = 0; k < uniqueArtboardSizes.length; k++) { - if (uniqueArtboardSizes[k].width == artboardSize.width && uniqueArtboardSizes[k].height == artboardSize.height) { - isUnique = false; - - break; - } - } - - if (isUnique) { - uniqueArtboardSizes.push({ - width: artboardSize.width, - height: artboardSize.height - }); - } - - artboardSize = nil; - isUnique = nil; - } - - artboard = nil; - loop = nil; - } catch (error) { - log("Unique artboard sizes failed with error “" + error + "”."); - } - - artboards = nil; - - var artboardNamesByIdentifier = {}; - var allArtboardsLoop = [[doc valueForKeyPath:@"pages.@distinctUnionOfArrays.artboards"] objectEnumerator]; - var artboard = nil; - while (artboard = [allArtboardsLoop nextObject]) { - artboardNamesByIdentifier[artboard.objectID()] = artboard.name(); - } - - artboard = nil; - allArtboardsLoop = nil; - - var path = temporaryPath(); - var directives = defaultDirectives(context, path); - [directives setObject:@"artboards" forKey:@"type"]; - [directives setObject:artboardIds forKey:@"artboardIds"]; - [directives setObject:pageIds forKey:@"pageIds"]; - [directives setObject:uniqueArtboardSizes forKey:@"artboardSizes"]; - [directives setObject:artboardNamesByIdentifier forKey:@"artboardNames"]; - [directives setObject:containsArtboard forKey:@"containsArtboard"]; - - artboardIds = nil; - pageIds = nil; - uniqueArtboardSizes = nil; - artboardNamesByIdentifier = nil; - - writeDirectives(directives, path); - - directives = nil; - - launchZeplin(context, path); - - path = nil; -} - -var exportColors = function (context) { - if (!documentExportable(context)) { - return; - } - - var path = temporaryPath(); - var directives = defaultDirectives(context, path); - [directives setObject:@"colors" forKey:@"type"]; - - writeDirectives(directives, path); - - directives = nil; - - launchZeplin(context, path); - - path = nil; -} - -var exportTextStyles = function (context) { - if (!documentExportable(context)) { - return; - } - - var path = temporaryPath(); - var directives = defaultDirectives(context, path); - [directives setObject:@"textStyles" forKey:@"type"]; - - writeDirectives(directives, path); - - directives = nil; - - launchZeplin(context, path); - - path = nil; -} - -var documentExportable = function (context) { - var doc = context.document; - - if (![doc fileURL] || [doc isDraft]) { - [NSApp displayDialog:@"Please save the document before exporting to Zeplin." withTitle:@"Document not saved"]; - - return false; - } - - if ([doc isDocumentEdited]) { - var alert = [NSAlert alertWithMessageText:@"Document not saved" defaultButton:@"Save and Continue" alternateButton:@"Cancel" otherButton:@"Continue" informativeTextWithFormat:@"To capture the latest changes in this Sketch document, Zeplin needs to save it first.\n\n☝️ This might take a bit, depending on the document size."]; - - var response = [alert runModal]; - if (response == NSAlertDefaultReturn) { - [doc showMessage:@"Saving document…"]; - - [doc saveDocument:nil]; - while ([doc isDocumentEdited]) { - [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; - } - } else if (response == NSAlertAlternateReturn) { - return false; - } - - response = nil; - alert = nil; - } - - return true; -} - -var temporaryPath = function() { - var name = [[[NSUUID UUID] UUIDString] stringByAppendingPathExtension:@"zpl"]; - var temporaryDirectory = NSTemporaryDirectory(); - var path = [temporaryDirectory stringByAppendingPathComponent:name]; - - temporaryDirectory = nil; - name = nil; - - return path; -} - -var defaultDirectives = function(context, path) { - var doc = context.document; - - var format = @"json"; - var readerClass = NSClassFromString(@"MSDocumentReader"); - var jsonReaderClass = NSClassFromString(@"MSDocumentZippedJSONReader"); - if (!readerClass || !jsonReaderClass || ![[readerClass readerForDocumentAtURL:[doc fileURL]] isKindOfClass:jsonReaderClass]) { - format = @"legacy"; - } - - jsonReaderClass = nil; - readerClass = nil; - - var assetLibraries = []; - // `MSAssetLibraryController` defined on Sketch 47. - try { - var assetLibrariesLoop = [[[[AppController sharedInstance] librariesController] libraries] objectEnumerator]; - var assetLibrary = nil; - while (assetLibrary = [assetLibrariesLoop nextObject]) { - if (![assetLibrary enabled]) { - continue; - } - - var libraryID = [assetLibrary libraryID]; - if (!libraryID) { - continue; - } - - var url = [assetLibrary locationOnDisk]; - if (!url) { - continue; - } - - assetLibraries.push({ - id: libraryID, - path: [url path] - }); - } - - assetLibrary = nil; - assetLibrariesLoop = nil; - } catch (error) { - log("Asset library paths by identifier failed with error “" + error + "”."); - } - - var version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; - var sketchtoolPath = [[NSBundle mainBundle] pathForResource:@"sketchtool" ofType:nil inDirectory:@"sketchtool/bin"]; - var sketchmigratePath = [[NSBundle mainBundle] pathForResource:@"sketchmigrate" ofType:nil inDirectory:@"sketchtool/bin"]; - - var directives = [NSMutableDictionary dictionary]; - [directives setObject:[[doc fileURL] path] forKey:@"path"]; - [directives setObject:format forKey:@"format"]; - [directives setObject:assetLibraries forKey:@"assetLibraries"]; - if (version) { - [directives setObject:version forKey:@"version"]; - } - if (sketchtoolPath) { - [directives setObject:sketchtoolPath forKey:@"sketchtoolPath"]; - } - if (sketchmigratePath) { - [directives setObject:sketchmigratePath forKey:@"sketchmigratePath"]; - } - - format = nil; - version = nil; - sketchmigratePath = nil; - sketchtoolPath = nil; - assetLibraries = nil; - - return directives; -} - -var writeDirectives = function (directives, path) { - if (!path) { - return nil; - } - - [directives writeToFile:path atomically:false]; - - return path; -} - -var launchZeplin = function (context, path) { - var doc = context.document; - var workspace = [NSWorkspace sharedWorkspace]; - - var applicationPath = [workspace absolutePathForAppBundleWithIdentifier:@"io.zeplin.osx"]; - if (!applicationPath) { - [NSApp displayDialog:@"Please make sure that you installed and launched it: https://zpl.io/download" withTitle:"Could not find Zeplin"]; - - return; - } - - [doc showMessage:@"Launching Zeplin!"]; - - [workspace openFile:path withApplication:applicationPath andDeactivate:true]; - - workspace = nil; - applicationPath = nil; -} - -var shortcutHelp = function (context) { - var alert = [NSAlert alertWithMessageText:@"Shortcut changed to ⌃⌘E" defaultButton:@"Continue and Export" alternateButton:@"Cancel" otherButton:nil informativeTextWithFormat:@"Due to recent shortcuts tweaks on Sketch, Zeplin's “Export Selected Artboards…” shortcut is now ⌃⌘E.\n\n☝️ That's Command, Control, E."]; - - if ([alert runModal] == NSAlertDefaultReturn) { - onRun(context); - } - - alert = nil; -} diff --git a/Zeplin.sketchplugin/Contents/Sketch/main.cocoascript b/Zeplin.sketchplugin/Contents/Sketch/main.cocoascript new file mode 100644 index 0000000..ae5ec8f --- /dev/null +++ b/Zeplin.sketchplugin/Contents/Sketch/main.cocoascript @@ -0,0 +1,135 @@ +@import "utils.cocoascript"; + +var onRun = function (context) { + if (!documentExportable(context)) { + return; + } + + var artboards = [context valueForKeyPath:@"selection.@distinctUnionOfObjects.parentArtboard"]; + if (![artboards count]) { + [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"]; + return; + } + + exportArtboards(context, artboards); +} + +var exportColors = function (context) { + if (!documentExportable(context)) { + return; + } + + exportObjectsForType(context, @"colors"); +} + +var exportTextStyles = function (context) { + if (!documentExportable(context)) { + return; + } + + exportObjectsForType(context, @"textStyles"); +} + +var exportArtboardsFromCurrentPage = function (context) { + if (!documentExportable(context)) { + return; + } + + var artboards = layersOfPagesWithClassName(NSArray.arrayWithObject(context.document.currentPage()), "MSArtboardGroup"); + + if (![artboards count]) { + [NSApp displayDialog:@"Please create an artboard to export to Zeplin." withTitle:@"No artboard found"]; + + return; + } + + exportArtboards(context, artboards); +} + +var exportArtboardsFromAllPages = function (context) { + if (!documentExportable(context)) { + return; + } + + var artboards = layersOfPagesWithClassName(context.document.pages(), "MSArtboardGroup"); + + if (![artboards count]) { + [NSApp displayDialog:@"Please create an artboard to export to Zeplin." withTitle:@"No artboard found"]; + + return; + } + + exportArtboards(context, artboards); +} + +var exportSymbolsFromCurrentPage = function (context) { + if (!documentExportable(context)) { + return; + } + + var artboards = layersOfPagesWithClassName(NSArray.arrayWithObject(context.document.currentPage()), "MSSymbolMaster"); + + if (![artboards count]) { + [NSApp displayDialog:@"Please create a symbol to export to Zeplin." withTitle:@"No symbol found"]; + + return; + } + + exportArtboards(context, artboards); +} + +var exportSymbolsFromAllPages = function (context) { + if (!documentExportable(context)) { + return; + } + + var artboards = layersOfPagesWithClassName(context.document.pages(), "MSSymbolMaster"); + + if (![artboards count]) { + [NSApp displayDialog:@"Please create a symbol to export to Zeplin." withTitle:@"No symbol found"]; + + return; + } + + exportArtboards(context, artboards); +} + +var excludeSublayers = function (context) { + var selection = context.selection; + var layerEnumerator = [selection objectEnumerator]; + var layer; + + while (layer = [layerEnumerator nextObject]) { + var layerName = [layer name]; + + if (![layerName hasPrefix:@"-g-"]) { + [layer setName:[@"-g-" stringByAppendingString:layerName]]; + } + + layerName = nil; + } + + layer = nil; + layerEnumerator = nil; + selection = nil; +} + +var includeSublayers = function (context) { + var selection = context.selection; + var layerEnumerator = [selection objectEnumerator]; + var layer; + + while (layer = [layerEnumerator nextObject]) { + var layerName = [layer name]; + + if ([layerName hasPrefix:@"-g-"]) { + [layer setName:[layerName substringFromIndex:3]]; + } + + layerName = nil; + } + + layer = nil; + layerEnumerator = nil; + selection = nil; +} diff --git a/Zeplin.sketchplugin/Contents/Sketch/manifest.json b/Zeplin.sketchplugin/Contents/Sketch/manifest.json index ff4de2d..5ec23a0 100644 --- a/Zeplin.sketchplugin/Contents/Sketch/manifest.json +++ b/Zeplin.sketchplugin/Contents/Sketch/manifest.json @@ -11,28 +11,56 @@ "name": "Export Selected…", "identifier": "export", "shortcut": "cmd ctrl e", - "script": "export.cocoascript", + "script": "main.cocoascript", "icon": "Icons/icZeplinRunner.png", "description": "Export selected artboards and symbols to Zeplin." }, { "name": "Export Colors…", "identifier": "exportColors", - "script": "export.cocoascript", + "script": "main.cocoascript", "handler": "exportColors", "icon": "Icons/icZeplinRunner.png", "description": "Export colors to Zeplin." }, { "name": "Export Text Styles…", "identifier": "exportTextStyles", - "script": "export.cocoascript", + "script": "main.cocoascript", "handler": "exportTextStyles", "icon": "Icons/icZeplinRunner.png", "description": "Export text styles to Zeplin." + }, { + "name": "Artboards from Current Page…", + "identifier": "artboardsFromCurrentPage", + "script": "main.cocoascript", + "handler": "exportArtboardsFromCurrentPage", + "icon": "Icons/icZeplinRunner.png", + "description": "Export artboards from current page to Zeplin." + }, { + "name": "Artboards from All Pages…", + "identifier": "artboardsFromAllPages", + "script": "main.cocoascript", + "handler": "exportArtboardsFromAllPages", + "icon": "Icons/icZeplinRunner.png", + "description": "Export artboards from all pages to Zeplin." + }, { + "name": "Symbols from Current Page…", + "identifier": "symbolsFromCurrentPage", + "script": "main.cocoascript", + "handler": "exportSymbolsFromCurrentPage", + "icon": "Icons/icZeplinRunner.png", + "description": "Export symbols from current page to Zeplin." + }, { + "name": "Symbols from All Pages…", + "identifier": "symbolsFromAllPages", + "script": "main.cocoascript", + "handler": "exportSymbolsFromAllPages", + "icon": "Icons/icZeplinRunner.png", + "description": "Export symbols from all pages to Zeplin." }, { "name": "Exclude Sublayers", "identifier": "exclude-sublayers", "shortcut": "cmd shift x", - "script": "utils.cocoascript", + "script": "main.cocoascript", "handler": "excludeSublayers", "icon": "Icons/icZeplinRunner.png", "description": "Exclude sublayers of selected groups or symbols." @@ -40,31 +68,33 @@ "name": "Include Sublayers", "identifier": "include-sublayers", "shortcut": "cmd shift i", - "script": "utils.cocoascript", + "script": "main.cocoascript", "handler": "includeSublayers", "icon": "Icons/icZeplinRunner.png", "description": "Include sublayers of selected groups or symbols." - }, { - "name": "Shortcut Help", - "identifier": "shortcut-help", - "shortcut": "cmd e", - "script": "export.cocoascript", - "handler": "shortcutHelp", - "icon": "Icons/icZeplinRunner.png", - "description": "Learn more about “Export Selected…” shortcut change." }], "menu": { "items": [ "export", + "-", "exportColors", "exportTextStyles", + { + "title": "Export All", + "items": [ + "artboardsFromCurrentPage", + "artboardsFromAllPages", + "-", + "symbolsFromCurrentPage", + "symbolsFromAllPages" + ] + }, + "-", { "title": "Utilities", "items": [ "exclude-sublayers", - "include-sublayers", - "-", - "shortcut-help" + "include-sublayers" ] } ] diff --git a/Zeplin.sketchplugin/Contents/Sketch/utils.cocoascript b/Zeplin.sketchplugin/Contents/Sketch/utils.cocoascript index fb61e21..d5c49f0 100644 --- a/Zeplin.sketchplugin/Contents/Sketch/utils.cocoascript +++ b/Zeplin.sketchplugin/Contents/Sketch/utils.cocoascript @@ -1,39 +1,305 @@ -var excludeSublayers = function (context) { - var selection = context.selection; - var layerEnumerator = [selection objectEnumerator]; - var layer; +var documentExportable = function (context) { + var doc = context.document; - while (layer = [layerEnumerator nextObject]) { - var layerName = [layer name]; + if (![doc fileURL] || [doc isDraft]) { + [NSApp displayDialog:@"Please save the document before exporting to Zeplin." withTitle:@"Document not saved"]; + + return false; + } + + if ([doc isDocumentEdited]) { + var alert = [NSAlert alertWithMessageText:@"Document not saved" defaultButton:@"Save and Continue" alternateButton:@"Cancel" otherButton:@"Continue" informativeTextWithFormat:@"To capture the latest changes in this Sketch document, Zeplin needs to save it first.\n\n☝️ This might take a bit, depending on the document size."]; + + var response = [alert runModal]; + if (response == NSAlertDefaultReturn) { + [doc showMessage:@"Saving document…"]; + + [doc saveDocument:nil]; + while ([doc isDocumentEdited]) { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; + } + } else if (response == NSAlertAlternateReturn) { + return false; + } - if (![layerName hasPrefix:@"-g-"]) { - [layer setName:[@"-g-" stringByAppendingString:layerName]]; + response = nil; + alert = nil; + } + + return true; +} + +var temporaryPath = function() { + var name = [[[NSUUID UUID] UUIDString] stringByAppendingPathExtension:@"zpl"]; + var temporaryDirectory = NSTemporaryDirectory(); + var path = [temporaryDirectory stringByAppendingPathComponent:name]; + + temporaryDirectory = nil; + name = nil; + + return path; +} + +var defaultDirectives = function(context, path) { + var doc = context.document; + + var format = @"json"; + var readerClass = NSClassFromString(@"MSDocumentReader"); + var jsonReaderClass = NSClassFromString(@"MSDocumentZippedJSONReader"); + if (!readerClass || !jsonReaderClass || ![[readerClass readerForDocumentAtURL:[doc fileURL]] isKindOfClass:jsonReaderClass]) { + format = @"legacy"; + } + + jsonReaderClass = nil; + readerClass = nil; + + var assetLibraries = []; + // `MSAssetLibraryController` defined on Sketch 47. + try { + var assetLibrariesLoop = [[[[AppController sharedInstance] librariesController] libraries] objectEnumerator]; + var assetLibrary = nil; + while (assetLibrary = [assetLibrariesLoop nextObject]) { + if (![assetLibrary enabled]) { + continue; + } + + var libraryID = [assetLibrary libraryID]; + if (!libraryID) { + continue; + } + + var url = [assetLibrary locationOnDisk]; + if (!url) { + continue; + } + + assetLibraries.push({ + id: libraryID, + path: [url path] + }); } - layerName = nil; + assetLibrary = nil; + assetLibrariesLoop = nil; + } catch (error) { + log("Asset library paths by identifier failed with error “" + error + "”."); + } + + var version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + var sketchtoolPath = [[NSBundle mainBundle] pathForResource:@"sketchtool" ofType:nil inDirectory:@"sketchtool/bin"]; + var sketchmigratePath = [[NSBundle mainBundle] pathForResource:@"sketchmigrate" ofType:nil inDirectory:@"sketchtool/bin"]; + + var directives = [NSMutableDictionary dictionary]; + [directives setObject:[[doc fileURL] path] forKey:@"path"]; + [directives setObject:format forKey:@"format"]; + [directives setObject:assetLibraries forKey:@"assetLibraries"]; + if (version) { + [directives setObject:version forKey:@"version"]; + } + if (sketchtoolPath) { + [directives setObject:sketchtoolPath forKey:@"sketchtoolPath"]; + } + if (sketchmigratePath) { + [directives setObject:sketchmigratePath forKey:@"sketchmigratePath"]; + } + + format = nil; + version = nil; + sketchmigratePath = nil; + sketchtoolPath = nil; + assetLibraries = nil; + + return directives; +} + +var writeDirectives = function (directives, path) { + if (!path) { + return nil; + } + + [directives writeToFile:path atomically:false]; + + return path; +} + +var launchZeplin = function (context, path) { + var doc = context.document; + var workspace = [NSWorkspace sharedWorkspace]; + + var applicationPath = [workspace absolutePathForAppBundleWithIdentifier:@"io.zeplin.osx"]; + if (!applicationPath) { + [NSApp displayDialog:@"Please make sure that you installed and launched it: https://zpl.io/download" withTitle:"Could not find Zeplin"]; + + return; } - layer = nil; - layerEnumerator = nil; - selection = nil; + [doc showMessage:@"Launching Zeplin!"]; + + [workspace openFile:path withApplication:applicationPath andDeactivate:true]; + + workspace = nil; + applicationPath = nil; } -var includeSublayers = function (context) { - var selection = context.selection; - var layerEnumerator = [selection objectEnumerator]; - var layer; +var exportArtboards = function (context, artboards) { + var doc = context.document; + + var foreignSymbolsUpToDate = true; + // `MSBadgeController` is defined on Sketch 44, `activeWindowBadgingActions` is defined on Sketch 46. + try { + var activeActions = [[doc badgeController] activeWindowBadgingActions]; + var activeActionsLoop = [activeActions objectEnumerator]; + var action = nil; + while (action = [activeActionsLoop nextObject]) { + if ([action isKindOfClass:NSClassFromString(@"MSSyncLibraryAction")]) { + foreignSymbolsUpToDate = false; + + break; + } + } + + action = nil; + activeActionsLoop = nil; + activeActions = nil; + } catch (error) { + log("Foreign symbols up to date failed with error “" + error + "”."); + } + + if (!foreignSymbolsUpToDate) { + var alert = [NSAlert alertWithMessageText:@"Symbols not up to date" defaultButton:@"Continue and Export" alternateButton:@"Cancel" otherButton:nil informativeTextWithFormat:@"To capture the latest changes in your libraries, make sure that your symbols are up to date before exporting artboards to Zeplin.\n\n☝️ Select “Library Update Available” on the top right to review changes."]; + + if ([alert runModal] == NSAlertAlternateReturn) { + return; + } + + alert = nil; + } + + var artboardIds = [artboards valueForKeyPath:@"objectID"]; + + var layers = [[[doc documentData] allSymbols] arrayByAddingObjectsFromArray:artboards]; + var pageIds = [layers valueForKeyPath:@"@distinctUnionOfObjects.parentPage.objectID"]; - while (layer = [layerEnumerator nextObject]) { - var layerName = [layer name]; + layers = nil; + + var containsArtboard = false; + var artboardsLoop = [artboards objectEnumerator]; + var artboard = nil; + while (artboard = [artboardsLoop nextObject]) { + var artboardClassName = NSStringFromClass([artboard class]); + if ([artboardClassName isEqualToString:@"MSArtboardGroup"]) { + containsArtboard = true; + + break; + } + } + + artboard = nil; + artboardsLoop = nil; + + var uniqueArtboardSizes = []; + // `size` on `CGRect` fails on Mocha, on macOS 10.13, Sketch 45 and below. + try { + var loop = [artboards objectEnumerator]; + var artboard = nil; + while (artboard = [loop nextObject]) { + var artboardSize = artboard.rect().size; + + var isUnique = true; + for (var k = 0; k < uniqueArtboardSizes.length; k++) { + if (uniqueArtboardSizes[k].width == artboardSize.width && uniqueArtboardSizes[k].height == artboardSize.height) { + isUnique = false; - if ([layerName hasPrefix:@"-g-"]) { - [layer setName:[layerName substringFromIndex:3]]; + break; + } + } + + if (isUnique) { + uniqueArtboardSizes.push({ + width: artboardSize.width, + height: artboardSize.height + }); + } + + artboardSize = nil; + isUnique = nil; } - layerName = nil; + artboard = nil; + loop = nil; + } catch (error) { + log("Unique artboard sizes failed with error “" + error + "”."); } - layer = nil; - layerEnumerator = nil; - selection = nil; + artboards = nil; + + var artboardNamesByIdentifier = {}; + var allArtboardsLoop = [[doc valueForKeyPath:@"pages.@distinctUnionOfArrays.artboards"] objectEnumerator]; + var artboard = nil; + while (artboard = [allArtboardsLoop nextObject]) { + artboardNamesByIdentifier[artboard.objectID()] = artboard.name(); + } + + artboard = nil; + allArtboardsLoop = nil; + + var path = temporaryPath(); + var directives = defaultDirectives(context, path); + [directives setObject:@"artboards" forKey:@"type"]; + [directives setObject:artboardIds forKey:@"artboardIds"]; + [directives setObject:pageIds forKey:@"pageIds"]; + [directives setObject:uniqueArtboardSizes forKey:@"artboardSizes"]; + [directives setObject:artboardNamesByIdentifier forKey:@"artboardNames"]; + [directives setObject:containsArtboard forKey:@"containsArtboard"]; + + artboardIds = nil; + pageIds = nil; + uniqueArtboardSizes = nil; + artboardNamesByIdentifier = nil; + + writeDirectives(directives, path); + + directives = nil; + + launchZeplin(context, path); + + path = nil; +} + +var exportObjectsForType = function (context, type) { + var path = temporaryPath(); + var directives = defaultDirectives(context, path); + [directives setObject:type forKey:@"type"]; + + writeDirectives(directives, path); + + directives = nil; + + launchZeplin(context, path); + + path = nil; +} + +var layersOfPagesWithClassName = function (pages, className) { + var layers = []; + var pagesLoop = pages.objectEnumerator(); + var page = nil; + + while (page = [pagesLoop nextObject]) { + var pageLayers = []; + var layersLoop = page.layers().objectEnumerator(); + var layer = nil; + + while (layer = [layersLoop nextObject]) { + var layerClassName = NSStringFromClass([layer class]); + + if ([layerClassName isEqualToString:className]) { + pageLayers.push(layer); + } + } + + layers = layers.concat(pageLayers); + } + + return NSArray.arrayWithArray(layers); }