Skip to content

Instantly share code, notes, and snippets.

@Means88
Created May 6, 2019 07:24
Show Gist options
  • Save Means88/6232c74045e78ea233e3b705eb0fad7a to your computer and use it in GitHub Desktop.
Save Means88/6232c74045e78ea233e3b705eb0fad7a to your computer and use it in GitHub Desktop.
zeplin.sketchplugin
var onRun = 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;
}
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;
}
response = nil;
alert = nil;
}
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 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 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 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 name = [[[NSUUID UUID] UUIDString] stringByAppendingPathExtension:@"zpl"];
var temporaryDirectory = NSTemporaryDirectory();
var path = [temporaryDirectory stringByAppendingPathComponent:name];
temporaryDirectory = nil;
name = nil;
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:artboardIds forKey:@"artboardIds"];
[directives setObject:pageIds forKey:@"pageIds"];
[directives setObject:format forKey:@"format"];
[directives setObject:uniqueArtboardSizes forKey:@"artboardSizes"];
[directives setObject:assetLibraries forKey:@"assetLibraries"];
[directives setObject:artboardNamesByIdentifier forKey:@"artboardNames"];
if (version) {
[directives setObject:version forKey:@"version"];
}
if (sketchtoolPath) {
[directives setObject:sketchtoolPath forKey:@"sketchtoolPath"];
}
if (sketchmigratePath) {
[directives setObject:sketchmigratePath forKey:@"sketchmigratePath"];
}
version = nil;
sketchmigratePath = nil;
sketchtoolPath = nil;
artboardNames = nil;
assetLibraries = nil;
format = nil;
uniqueArtboardSizes = nil;
pageIds = nil;
artboardIds = nil;
[directives writeToFile:path atomically:false];
directives = nil;
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;
path = 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;
}
{
"name": "Zeplin",
"description": "Export artboards to a Zeplin project. 🚀",
"author": "Zeplin, Inc.",
"authorEmail": "dev@zeplin.io",
"homepage": "https://zeplin.io",
"version": "1.6.4",
"identifier": "io.zeplin.sketch-plugin",
"commands": [{
"name": "Export Selected Artboards…",
"identifier": "export",
"shortcut": "cmd ctrl e",
"script": "export.cocoascript",
"icon": "Icons/icZeplin.png",
"description": "Export selected artboards to a Zeplin project. 🚀"
}, {
"name": "Exclude Sublayers",
"identifier": "exclude-sublayers",
"shortcut": "cmd shift x",
"script": "utils.cocoascript",
"handler": "excludeSublayers",
"icon": "Icons/icZeplin.png",
"description": "Exclude sublayers of selected groups or symbols."
}, {
"name": "Include Sublayers",
"identifier": "include-sublayers",
"shortcut": "cmd shift i",
"script": "utils.cocoascript",
"handler": "includeSublayers",
"icon": "Icons/icZeplin.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/icZeplin.png",
"description": "Learn more about “Export Selected Artboards…” shortcut change."
}],
"menu": {
"items": [
"export",
{
"title": "Utilities",
"items": [
"exclude-sublayers",
"include-sublayers",
"-",
"shortcut-help"
]
}
]
}
}
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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment