├── .gitignore ├── README.md ├── YammerSketchTools.sketchplugin └── Contents │ └── Sketch │ ├── Icons │ └── ExportIcons.js │ ├── Layers │ └── Unlock.js │ ├── appcast.xml │ ├── layers │ ├── CompactHorizontalSpace.js │ ├── CompactVerticalSpace.js │ └── ToggleMarginBlocks.js │ ├── library │ └── io.mamuso.tools.cocoascript │ ├── manifest.json │ └── text │ └── TruncateLines.js ├── doc ├── assets │ ├── compacthorizontal.gif │ ├── compactvertical.gif │ ├── iconstructure.png │ ├── marginbox.gif │ ├── trimline-character.gif │ ├── trimline-word.gif │ └── unlock.gif ├── markdox.js └── template.md.ejs ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yammer Sketch Commands 2 | Plugins and commands we use at Yammer. 3 | 4 | ## List of available commands 5 | 6 | ### Layers / 🔓 Unlock All the Layers 7 | **Shortcut:** ctrl alt cmd u 8 | 9 | Unlocks all the layers of the current artboard. If there are no artboards in the document, then it unlocks all the layers of the current page. 10 | 11 | We found inspiration in [this article](https://blog.truthlabs.com/sketch-plugin-unlock-all-layers-1ff9252f0689#.layqla2bc). 12 | 13 | ![Unlock](doc/assets/unlock.gif) 14 | 15 | ### Layers / 👏 Toggle Margin Boxes 16 | **Shortcut:** ctrl alt cmd m 17 | 18 | Toggles the visibility of layers with named ```y-marginbox```, using the first element found as a reference for showing or hiding the elements. 19 | 20 | - If you don't select anything and you don't have artboards, it will toggle the layers of your current page. 21 | - If you are using artboards, it will toggle the layers of your current artboard. 22 | - If you have selected some layers or groups, it will toggle just the layers included in the selection. 23 | 24 | ![Unlock](doc/assets/marginbox.gif) 25 | 26 | ### Layers / :arrow_up: Compact Vertical Space 27 | **Shortcut:** ctrl alt ↑ 28 | 29 | Distributes the selected elements vertically removing the spacing between them. The code is borrowed from @bomberstudios [Sketch Commands](https://github.com/bomberstudios/sketch-commands/blob/master/Sketch%20Commands.sketchplugin/Contents/Sketch/Align/Space%20Vertical.cocoascript) and slightly modified. 30 | 31 | ![Unlock](doc/assets/compactvertical.gif) 32 | 33 | ### Layers / :arrow_left: Compact Horizontal Space 34 | **Shortcut:** ctrl alt ← 35 | 36 | Distributes the selected elements horizontally removing the spacing between them. The code is borrowed from @bomberstudios [Sketch Commands](https://github.com/bomberstudios/sketch-commands/blob/master/Sketch%20Commands.sketchplugin/Contents/Sketch/Align/Space%20Horizontal.cocoascript) and slightly modified. 37 | 38 | ![Unlock](doc/assets/compacthorizontal.gif) 39 | 40 | ### Text / :scissors: Truncate text lines 41 | **Shortcut:** ctrl alt cmd t 42 | 43 | Cut the number of lines of a textbox, adding an ellipsis if needed. It works using words or characters. 44 | 45 | Most of the ideas are taken from the [sketch data populator plugin](https://github.com/preciousforever/sketch-data-populator/). 46 | 47 | ![Trim](doc/assets/trimline-word.gif) 48 | 49 | ![Trim](doc/assets/trimline-character.gif) 50 | 51 | ### Icons / :ant: Export SVG Icons 52 | 53 | Based on Yammer Icon MasterDoc conventions. The plugin only acts over the elements of the 54 | artboard 'icons' in the current page. This way we can manage icons also for different platform 55 | with different export needs. 56 | 57 | For making this plugin work you need to have SVGo installed. 58 | If you have brew, try ```brew install svgo``` 59 | 60 | Naming conventions: 61 | - The export group needs to be in a "symbol" group 62 | - The icon masking the color needs to be named "mask" 63 | 64 | ![iconstructure](doc/assets/iconstructure.png) 65 | 66 | -------------------------------------------------------------------------------- /YammerSketchTools.sketchplugin/Contents/Sketch/Icons/ExportIcons.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ### Icons / :ant: Export SVG Icons 3 | * 4 | * Based on Yammer Icon MasterDoc conventions. The plugin only acts over the elements of the 5 | * artboard 'icons' in the current page. This way we can manage icons also for different platform 6 | * with different export needs. 7 | * 8 | * For making this plugin work you need to have SVGo installed. 9 | * If you have brew, try ```brew install svgo``` 10 | * 11 | * Naming conventions: 12 | * - The export group needs to be in a "symbol" group 13 | * - The icon masking the color needs to be named "mask" 14 | * 15 | * ![iconstructure](doc/assets/iconstructure.png) 16 | * 17 | * @todo: make the plugin change pages automatically 18 | * 19 | * 20 | */ 21 | 22 | @import '../library/io.mamuso.tools.cocoascript'; 23 | 24 | var onRun = function(context) { 25 | try { 26 | var doc = context.document, 27 | scriptPath = context.scriptPath, 28 | homeFolder = "/Users/" + NSUserName(); 29 | 30 | io.mamuso.config.doc = doc; 31 | io.mamuso.config.basePath = io.mamuso.tools.getDirFromPrompt(); 32 | 33 | 34 | // Were are we saving the files? 35 | if(io.mamuso.config.basePath == null) { 36 | return; 37 | } else { 38 | 39 | // looking for the artboard named 'icons' 40 | var iconsPage = io.mamuso.tools.findObjectsByName("icons", doc.pages()).firstObject(); 41 | 42 | if(iconsPage == null) { 43 | return; 44 | } else { 45 | // get all the icons of the page 46 | var icons = iconsPage.children(); 47 | for (var i = 0; i < icons.count(); i++) { 48 | 49 | if(icons[i].class() == "MSSymbolInstance") { 50 | var master = icons[i].symbolMaster(); 51 | var masterchildren = master.children(); 52 | var icongroups = io.mamuso.tools.findObjectsOfType(MSLayerGroup, masterchildren); 53 | 54 | // toggling groups off 55 | for (var j = 0; j < icongroups.count(); j++) { 56 | if(icongroups[j].class() == MSLayerGroup) { 57 | icongroups[j].setIsVisible(false); 58 | } 59 | } 60 | 61 | // toggle on the group we want to export 62 | var symbolgroup = io.mamuso.tools.findObjectsByName("symbol", icongroups).firstObject(); 63 | if(symbolgroup != null) { 64 | symbolgroup.setIsVisible(true); 65 | var symbolelements = symbolgroup.children(); 66 | // Toggle all the layers off except "mask" + turn the mask off 67 | for (var k = 0; k < symbolelements.count(); k++) { 68 | if(symbolelements[k].class() != MSLayerGroup) { 69 | if(symbolelements[k].name() == "mask") { 70 | symbolelements[k].setIsVisible(true); 71 | symbolelements[k].hasClippingMask = 0; 72 | // What if is a boolean shape? 73 | for (var l = 0; l < symbolelements[k].children().count(); l++) { 74 | symbolelements[k].children()[l].setIsVisible(true); 75 | } 76 | } else { 77 | symbolelements[k].setIsVisible(false); 78 | } 79 | } 80 | } 81 | 82 | // export the svg 83 | filename = master.name() + ".svg"; 84 | filePath = io.mamuso.config.basePath.stringByAppendingPathComponent(filename); 85 | io.mamuso.tools.sliceAndExport(master, filePath); 86 | 87 | // Go back to normal 88 | for (var k = 0; k < symbolelements.count(); k++) { 89 | symbolelements[k].setIsVisible(true); 90 | if(symbolelements[k].class() != MSLayerGroup) { 91 | if(symbolelements[k].name() == "mask") { 92 | symbolelements[k].hasClippingMask = 1; 93 | } 94 | } 95 | } 96 | 97 | 98 | } 99 | } 100 | } 101 | // svgo 102 | io.mamuso.tools.svgo(); 103 | } 104 | } 105 | 106 | } catch (e) { 107 | NSApplication.sharedApplication().displayDialog_withTitle_(e, "Something went 💩"); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /YammerSketchTools.sketchplugin/Contents/Sketch/Layers/Unlock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ### Layers / 🔓 Unlock All the Layers 3 | * **Shortcut:** ctrl alt cmd u 4 | * 5 | * Unlocks all the layers of the current artboard. If there are no artboards in the document, then it unlocks all the layers of the current page. 6 | * 7 | * We found inspiration in [this article](https://blog.truthlabs.com/sketch-plugin-unlock-all-layers-1ff9252f0689#.layqla2bc). 8 | * 9 | * ![Unlock](doc/assets/unlock.gif) 10 | * 11 | */ 12 | 13 | var onRun = function(context) { 14 | try { 15 | 16 | var doc = context.document, 17 | page = doc.currentPage(), 18 | artboard = page.currentArtboard(), 19 | layers = artboard == null ? page.children() : artboard.children(), 20 | unlocked = 0; 21 | 22 | for (var i = 0; i < layers.count(); i++) { 23 | var layer = layers[i]; 24 | 25 | if (layer.isLocked()) { 26 | layer.setIsLocked(false); 27 | unlocked++; 28 | } 29 | } 30 | 31 | doc.showMessage("🔓 " + unlocked + " layer" + (unlocked != 1 ? "s" : "") + " unlocked"); 32 | 33 | } catch (e) { 34 | NSApplication.sharedApplication().displayDialog_withTitle_(e, "Something went 💩"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /YammerSketchTools.sketchplugin/Contents/Sketch/appcast.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 🙆 Yammer Sketch Tools 5 | http://sparkle-project.org/files/sparkletestcast.xml 6 | Yammer design team swiss knife. 7 | en 8 | 9 | Version 0.0.9 10 | 11 | 13 |
  • Adding appcast.
  • 14 |
  • Removing masking when exporting icons
  • 15 | 16 | ]]> 17 |
    18 | 19 |
    20 |
    21 |
    -------------------------------------------------------------------------------- /YammerSketchTools.sketchplugin/Contents/Sketch/layers/CompactHorizontalSpace.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ### Layers / :arrow_left: Compact Horizontal Space 3 | * **Shortcut:** ctrl alt ← 4 | * 5 | * Distributes the selected elements horizontally removing the spacing between them. The code is borrowed from @bomberstudios [Sketch Commands](https://github.com/bomberstudios/sketch-commands/blob/master/Sketch%20Commands.sketchplugin/Contents/Sketch/Align/Space%20Horizontal.cocoascript) and slightly modified. 6 | * 7 | * ![Unlock](doc/assets/compacthorizontal.gif) 8 | * 9 | */ 10 | 11 | var onRun = function(context) { 12 | var doc = context.document, 13 | selection = context.selection 14 | 15 | function toJSArray(arr) { 16 | var len = arr.count(), 17 | res = []; 18 | while(len--){ 19 | res.push(arr[len]); 20 | } 21 | return res; 22 | } 23 | 24 | function sort_by_position(a,b){ 25 | return a.frame().left() - b.frame().left(); 26 | } 27 | 28 | var sorted_selection = toJSArray(selection).sort(sort_by_position), 29 | first_element = sorted_selection[0], 30 | left_position = first_element.frame().left(); 31 | 32 | sorted_selection.forEach(function(layer){ 33 | layer.frame().setX(left_position); 34 | left_position = layer.frame().left() + layer.frame().width(); 35 | }); 36 | } -------------------------------------------------------------------------------- /YammerSketchTools.sketchplugin/Contents/Sketch/layers/CompactVerticalSpace.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ### Layers / :arrow_up: Compact Vertical Space 3 | * **Shortcut:** ctrl alt ↑ 4 | * 5 | * Distributes the selected elements vertically removing the spacing between them. The code is borrowed from @bomberstudios [Sketch Commands](https://github.com/bomberstudios/sketch-commands/blob/master/Sketch%20Commands.sketchplugin/Contents/Sketch/Align/Space%20Vertical.cocoascript) and slightly modified. 6 | * 7 | * ![Unlock](doc/assets/compactvertical.gif) 8 | * 9 | */ 10 | 11 | var onRun = function(context) { 12 | var doc = context.document, 13 | selection = context.selection 14 | 15 | function toJSArray(arr) { 16 | var len = arr.count(), 17 | res = []; 18 | while(len--){ 19 | res.push(arr[len]); 20 | } 21 | return res; 22 | } 23 | 24 | function sort_by_position(a,b){ 25 | return a.frame().top() - b.frame().top(); 26 | } 27 | 28 | var sorted_selection = toJSArray(selection).sort(sort_by_position), 29 | first_element = sorted_selection[0], 30 | top_position = first_element.frame().top(); 31 | 32 | sorted_selection.forEach(function(layer){ 33 | layer.frame().setY(top_position); 34 | top_position = layer.frame().top() + layer.frame().height(); 35 | }); 36 | 37 | } -------------------------------------------------------------------------------- /YammerSketchTools.sketchplugin/Contents/Sketch/layers/ToggleMarginBlocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ### Layers / 👏 Toggle Margin Boxes 3 | * **Shortcut:** ctrl alt cmd m 4 | * 5 | * Toggles the visibility of layers with named ```y-marginbox```, using the first element found as a reference for showing or hiding the elements. 6 | * 7 | * - If you don't select anything and you don't have artboards, it will toggle the layers of your current page. 8 | * - If you are using artboards, it will toggle the layers of your current artboard. 9 | * - If you have selected some layers or groups, it will toggle just the layers included in the selection. 10 | * 11 | * ![Unlock](doc/assets/marginbox.gif) 12 | * 13 | */ 14 | 15 | @import '../library/io.mamuso.tools.cocoascript'; 16 | 17 | var onRun = function(context) { 18 | try { 19 | 20 | var doc = context.document, 21 | page = doc.currentPage(), 22 | artboard = page.currentArtboard(), 23 | selection = context.selection 24 | layers = null 25 | visible = null; 26 | 27 | if (page) { layers = page } 28 | if (artboard) { layers = artboard } 29 | if (selection.count() > 0) { layers = selection } 30 | if (layers.class() == '__NSArrayI') { 31 | for (var i = 0; i < layers.count(); i++) { 32 | if(layers[i].class() == "MSSymbolInstance") { 33 | toggleMarginBox(layers[i].symbolMaster()); 34 | } else { 35 | toggleMarginBox(layers[i]); 36 | } 37 | } 38 | } else { 39 | toggleMarginBox(layers); 40 | } 41 | 42 | doc.showMessage("👏 Done!"); 43 | 44 | function toggleMarginBox(obj) { 45 | if(obj.children != undefined) { 46 | obj = obj.children(); 47 | } 48 | 49 | var marginbox = io.mamuso.tools.findObjectsByName("y-marginbox", obj); 50 | if (marginbox.count() > 0) { 51 | visible = visible == null? !marginbox.firstObject().isVisible() : visible; 52 | } 53 | 54 | for (var i = 0; i < marginbox.count(); i++) { 55 | marginbox[i].setIsVisible(visible); 56 | } 57 | 58 | // Do we have instance symbols? 59 | var symbolinstances = io.mamuso.tools.findObjectsOfType(MSSymbolInstance, obj); 60 | for (var i = 0; i < symbolinstances.count(); i++) { 61 | toggleMarginBox(symbolinstances[i].symbolMaster()); 62 | } 63 | } 64 | 65 | } catch (e) { 66 | NSApplication.sharedApplication().displayDialog_withTitle_(e, "Something went 💩"); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /YammerSketchTools.sketchplugin/Contents/Sketch/library/io.mamuso.tools.cocoascript: -------------------------------------------------------------------------------- 1 | var io = io ? io : {}; 2 | io.mamuso = io.mamuso ? io.mamuso : {}; 3 | io.mamuso.config = {}; 4 | 5 | io.mamuso.tools = { 6 | 7 | // ------------------------------------------------------------------- 8 | // Find an object by name in a given scope 9 | // ------------------------------------------------------------------- 10 | findObjectsByName: function(name,scope) { 11 | var predicate = NSPredicate.predicateWithFormat("name == %@",name); 12 | return scope.filteredArrayUsingPredicate(predicate); 13 | }, 14 | 15 | // ------------------------------------------------------------------- 16 | // Find an object by type in a given scope 17 | // ------------------------------------------------------------------- 18 | findObjectsOfType: function(classType,scope) { 19 | var predicate = NSPredicate.predicateWithFormat("self isKindOfClass: %@",classType); 20 | return scope.filteredArrayUsingPredicate(predicate); 21 | }, 22 | 23 | // ------------------------------------------------------------------- 24 | // Get the solid color of a shape 25 | // ------------------------------------------------------------------- 26 | getColor: function(object) { 27 | try{ 28 | var fills = object.style().fills(); 29 | if (fills.count() > 0 && fills.firstObject().fillType() == 0) { 30 | return "#" + fills.firstObject().color().hexValue().toString(); 31 | } 32 | } catch (e) { 33 | return nil; 34 | } 35 | }, 36 | 37 | // ------------------------------------------------------------------- 38 | // Make slice and export 39 | // ------------------------------------------------------------------- 40 | sliceAndExport: function(obj, filename) { 41 | var sliceLayerAncestry = MSImmutableLayerAncestry.ancestryWithMSLayer(obj), 42 | rect = MSSliceTrimming.trimmedRectForLayerAncestry(sliceLayerAncestry), 43 | slices = MSExportRequest.exportRequestsFromExportableLayer_inRect_useIDForName(obj, rect, false), 44 | slice = null; 45 | if (slices.count() > 0) { 46 | slice = slices[0]; 47 | slice.scale = io.mamuso.config.scale; 48 | } 49 | io.mamuso.config.doc.saveArtboardOrSlice_toFile(slice, filename); 50 | }, 51 | 52 | // ------------------------------------------------------------------- 53 | // Let the user specify a directory 54 | // ------------------------------------------------------------------- 55 | getDirFromPrompt: function() { 56 | var panel = [NSOpenPanel openPanel]; 57 | [panel setMessage:"🎟 Pick a folder?"]; 58 | [panel setCanChooseDirectories: true]; 59 | [panel setCanChooseFiles: false]; 60 | [panel setCanCreateDirectories: true]; 61 | var defaultDir = io.mamuso.config.doc.fileURL().URLByDeletingLastPathComponent(); 62 | [panel setDirectoryURL:defaultDir]; 63 | if ([panel runModal] == NSOKButton) { 64 | var message = [panel filename]; 65 | return message; 66 | } else { 67 | return null; 68 | } 69 | }, 70 | 71 | // ------------------------------------------------------------------- 72 | // Import Frameworks 73 | // ------------------------------------------------------------------- 74 | loadFramework: function(pluginRoot, frameworkName) { 75 | if (NSClassFromString(frameworkName) == null) { 76 | var mocha = [Mocha sharedRuntime]; 77 | return [mocha loadFrameworkWithName:frameworkName inDirectory:pluginRoot]; 78 | } else { 79 | return true; 80 | } 81 | }, 82 | 83 | // ------------------------------------------------------------------- 84 | // Text to Markdown 85 | // ------------------------------------------------------------------- 86 | markdownToHTML: function(text) { 87 | var html = [MMMarkdown HTMLStringWithMarkdown:text extensions:[1 << 0|1 << 3|1 << 5|1 << 6|1 << 8|1 << 9] error:null]; 88 | return html; 89 | }, 90 | 91 | 92 | // ------------------------------------------------------------------- 93 | // Exec, from https://github.com/mathieudutour/git-sketch-plugin/blob/master/Git.sketchplugin/Contents/Sketch/shared.cocoascript 94 | // ------------------------------------------------------------------- 95 | exec: function(command) { 96 | var task = NSTask.alloc().init(); 97 | var pipe = NSPipe.pipe(); 98 | var errPipe = NSPipe.pipe(); 99 | 100 | task.setLaunchPath_(@"/bin/bash"); 101 | task.setArguments_(NSArray.arrayWithObjects_("-c", command, nil)); 102 | task.setStandardOutput_(pipe); 103 | task.setStandardError_(errPipe); 104 | task.launch(); 105 | 106 | var data = errPipe.fileHandleForReading().readDataToEndOfFile(); 107 | if (data != nil && data.length()) 108 | { 109 | var message = NSString.alloc().initWithData_encoding_(data, NSUTF8StringEncoding); 110 | return NSException.raise_format_("failed", message); 111 | } 112 | data = pipe.fileHandleForReading().readDataToEndOfFile(); 113 | return NSString.alloc().initWithData_encoding_(data, NSUTF8StringEncoding); 114 | }, 115 | 116 | // ------------------------------------------------------------------- 117 | // SVGo 118 | // ------------------------------------------------------------------- 119 | svgo: function() { 120 | var command = NSString.stringWithFormat_("\\export PATH=\"\/usr\/local\/bin\:\$PATH\" && cd \"%@\" && \\svgo \--pretty \--indent\=2 \--config='{ \"plugins\": [ {\"removeTitle\": true}, { \"removeNonInheritableGroupAttrs\": true }, { \"collapseGroups\": true }, { \"removeAttrs\": { \"attrs\": \"(fill|stroke)\" }} ]}' .\/", io.mamuso.config.basePath); 121 | var stdout = io.mamuso.tools.exec(command); 122 | log(stdout); 123 | } 124 | }; -------------------------------------------------------------------------------- /YammerSketchTools.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "🙆 Yammer Sketch Tools", 3 | "description": "Our design team swiss knife.", 4 | "author": "Manuel Muñoz Solera", 5 | "authorEmail": "mamuso@mamuso.net", 6 | "version": "0.0.9", 7 | "appcast" : "https://raw.githubusercontent.com/mamuso/yammer-sketch-tools/master/appcast.xml", 8 | "identifier": "io.mamuso.sketch.ytools", 9 | "commands": [ 10 | { 11 | "name": "🔓 Unlock All the Layers", 12 | "identifier": "layers-unlock", 13 | "shortcut": "ctrl alt cmd u", 14 | "script": "layers/Unlock.js" 15 | }, 16 | { 17 | "name": "👏 Toggle Margin Boxes", 18 | "identifier": "layers-toggle-margin-block", 19 | "shortcut": "ctrl alt cmd m", 20 | "script": "layers/ToggleMarginBlocks.js" 21 | }, 22 | { 23 | "name": "⬆️ Compact Vertical Space", 24 | "identifier": "layers-compact-vertical-space", 25 | "shortcut": "ctrl alt ↑", 26 | "script": "layers/CompactVerticalSpace.js" 27 | }, 28 | { 29 | "name": "⬅️ Compact Horizontal Space", 30 | "identifier": "layers-compact-horizontal-space", 31 | "shortcut": "ctrl alt ←", 32 | "script": "layers/CompactHorizontalSpace.js" 33 | }, 34 | { 35 | "name": "✂️ Truncate text lines", 36 | "identifier": "text-truncate-text-lines", 37 | "shortcut": "ctrl alt cmd t", 38 | "script": "Text/TruncateLines.js" 39 | }, 40 | { 41 | "name": "🐜 Export SVG icons", 42 | "identifier": "icons-export-svg-icons", 43 | "script": "Icons/ExportIcons.js" 44 | } 45 | ], 46 | "menu": { 47 | "items": [ 48 | "unlock", 49 | "layers-toggle-margin-block", 50 | "layers-compact-vertical-space", 51 | "layers-compact-horizontal-space", 52 | "text-truncate-text-lines", 53 | "icons-export-svg-icons" 54 | ] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /YammerSketchTools.sketchplugin/Contents/Sketch/text/TruncateLines.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ### Text / :scissors: Truncate text lines 3 | * **Shortcut:** ctrl alt cmd t 4 | * 5 | * Cut the number of lines of a textbox, adding an ellipsis if needed. It works using words or characters. 6 | * 7 | * Most of the ideas are taken from the [sketch data populator plugin](https://github.com/preciousforever/sketch-data-populator/). 8 | * 9 | * ![Trim](doc/assets/trimline-word.gif) 10 | * 11 | * ![Trim](doc/assets/trimline-character.gif) 12 | * 13 | */ 14 | 15 | var onRun = function(context) { 16 | var doc = context.document; 17 | var selection = context.selection; 18 | 19 | // Ask for some info 20 | var options = ['Truncate words', 'Truncate characters'] 21 | var choices = createDialog('How many lines do you need?', options, 0) 22 | var lines = choices[1]; 23 | var go = choices[0] == 1000 ? true : false; 24 | var truncateMode = choices[2]; 25 | 26 | if(lines && go) { 27 | 28 | selection.forEach(function(layer){ 29 | if(isLayerText(layer)) { 30 | var text = String(layer.stringValue()); 31 | var ellipsis = text; 32 | var popped; 33 | 34 | // Creates a duplicated text field to use it as a playground 35 | var layerCopy = layer.duplicate(); 36 | layerCopy.setStringValue('-'); 37 | refreshTextLayer(layerCopy); 38 | 39 | // Defines the height of the line 40 | var lineHeight = layerCopy.frame().height(); 41 | 42 | // Get ready to iterate 43 | layerCopy.setStringValue(text); 44 | refreshTextLayer(layerCopy); 45 | actualHeight = layerCopy.frame().height(); 46 | 47 | 48 | while (actualHeight > lineHeight * lines) { 49 | // Shrinking the text 50 | if(truncateMode > 0) { 51 | text = text.slice(0,-1); 52 | } else { 53 | text = text.split(' '); 54 | popped = text.pop(); 55 | text = text.join(' '); 56 | popped = popped.split('\n'); 57 | if (popped.length > 1 ) { 58 | text = text + ' ' + popped[0] 59 | } 60 | } 61 | 62 | if (popped && popped.length > 1) { 63 | ellipsis = text; 64 | } else { 65 | // Remove unwanted characters 66 | if(text.slice(-1).match(/[\,\.\;\:\-\n\r]/)) { text = text.slice(0,-1) } 67 | ellipsis = text + '…'; 68 | } 69 | 70 | //set trimmed text and re-evaluate height 71 | layerCopy.setStringValue(ellipsis); 72 | refreshTextLayer(layerCopy); 73 | actualHeight = layerCopy.frame().height(); 74 | 75 | } 76 | 77 | if(ellipsis.length > 1) { 78 | layer.setStringValue(ellipsis); 79 | } 80 | 81 | layerCopy.removeFromParent(); 82 | } 83 | }); 84 | } 85 | 86 | 87 | // Checks if the layer is a text layer. 88 | function isLayerText(layer) { 89 | return layer.isKindOfClass(MSTextLayer.class()); 90 | } 91 | 92 | // Refreshes text layer boundaries after setting text. 93 | function refreshTextLayer(layer) { 94 | layer.setHeightIsClipped(0); 95 | layer.adjustFrameToFit(); 96 | layer.select_byExpandingSelection(true, false); 97 | layer.setIsEditingText(true); 98 | layer.setIsEditingText(false); 99 | layer.select_byExpandingSelection(false, false); 100 | } 101 | 102 | function createDialog(msg, items, selectedItemIndex) { 103 | selectedItemIndex = selectedItemIndex || 0 104 | 105 | var accessoryInput = NSView.alloc().initWithFrame(NSMakeRect(0,0,300,25)); 106 | var input = NSTextField.alloc().initWithFrame(NSMakeRect(0,0,300,25)); 107 | input.editable = true; 108 | input.stringValue = '2'; 109 | accessoryInput.addSubview(input); 110 | 111 | var accessoryList = NSComboBox.alloc().initWithFrame(NSMakeRect(0,0,160,25)); 112 | accessoryList.addItemsWithObjectValues(items); 113 | accessoryList.selectItemAtIndex(selectedItemIndex); 114 | 115 | var alert = COSAlertWindow.alloc().init(); 116 | alert.setMessageText(msg); 117 | alert.addButtonWithTitle('OK'); 118 | alert.addButtonWithTitle('Cancel'); 119 | alert.addAccessoryView(accessoryInput); 120 | alert.addAccessoryView(accessoryList); 121 | 122 | var responseCode = alert.runModal(); 123 | 124 | return [responseCode, input.stringValue(), accessoryList.indexOfSelectedItem()]; 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /doc/assets/compacthorizontal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamuso/yammer-sketch-tools/6ff84aaed76ade78f7f80d9d12013a34ef6bede2/doc/assets/compacthorizontal.gif -------------------------------------------------------------------------------- /doc/assets/compactvertical.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamuso/yammer-sketch-tools/6ff84aaed76ade78f7f80d9d12013a34ef6bede2/doc/assets/compactvertical.gif -------------------------------------------------------------------------------- /doc/assets/iconstructure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamuso/yammer-sketch-tools/6ff84aaed76ade78f7f80d9d12013a34ef6bede2/doc/assets/iconstructure.png -------------------------------------------------------------------------------- /doc/assets/marginbox.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamuso/yammer-sketch-tools/6ff84aaed76ade78f7f80d9d12013a34ef6bede2/doc/assets/marginbox.gif -------------------------------------------------------------------------------- /doc/assets/trimline-character.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamuso/yammer-sketch-tools/6ff84aaed76ade78f7f80d9d12013a34ef6bede2/doc/assets/trimline-character.gif -------------------------------------------------------------------------------- /doc/assets/trimline-word.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamuso/yammer-sketch-tools/6ff84aaed76ade78f7f80d9d12013a34ef6bede2/doc/assets/trimline-word.gif -------------------------------------------------------------------------------- /doc/assets/unlock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamuso/yammer-sketch-tools/6ff84aaed76ade78f7f80d9d12013a34ef6bede2/doc/assets/unlock.gif -------------------------------------------------------------------------------- /doc/markdox.js: -------------------------------------------------------------------------------- 1 | var markdox = require('markdox'); 2 | var async = require('async'); 3 | var path = require('path'); 4 | var docFolder = 'YammerSketchTools.sketchplugin/Contents/Sketch/'; 5 | 6 | // Fixtures 7 | var fixtures = [ 8 | docFolder + '/layers/Unlock.js', 9 | docFolder + '/layers/ToggleMarginBlocks.js', 10 | docFolder + '/layers/CompactVerticalSpace.js', 11 | docFolder + '/layers/CompactHorizontalSpace.js', 12 | docFolder + '/text/TruncateLines.js', 13 | docFolder + '/icons/Exporticons.js', 14 | ]; 15 | 16 | var options = { 17 | output: 'README.md', 18 | template: 'doc/template.md.ejs', 19 | }; 20 | 21 | markdox.process(fixtures, options, function(){ 22 | console.log('You are all set'); 23 | }); -------------------------------------------------------------------------------- /doc/template.md.ejs: -------------------------------------------------------------------------------- 1 | # Yammer Sketch Commands 2 | Plugins and commands we use at Yammer. 3 | 4 | ## List of available commands 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yammer-sketch-tools", 3 | "version": "0.0.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "argparse": { 8 | "version": "1.0.9", 9 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", 10 | "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", 11 | "dev": true, 12 | "requires": { 13 | "sprintf-js": "1.0.3" 14 | } 15 | }, 16 | "async": { 17 | "version": "2.5.0", 18 | "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", 19 | "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==", 20 | "dev": true, 21 | "requires": { 22 | "lodash": "4.17.4" 23 | } 24 | }, 25 | "coffee-script": { 26 | "version": "1.12.7", 27 | "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", 28 | "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", 29 | "dev": true 30 | }, 31 | "commander": { 32 | "version": "2.11.0", 33 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", 34 | "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", 35 | "dev": true 36 | }, 37 | "dox": { 38 | "version": "https://github.com/visionmedia/dox/tarball/master", 39 | "integrity": "sha1-d6WAvh+B7zK5anaIUgdh/3leiKc=", 40 | "dev": true, 41 | "requires": { 42 | "commander": "2.9.0", 43 | "jsdoctypeparser": "1.2.0", 44 | "markdown-it": "7.0.1" 45 | }, 46 | "dependencies": { 47 | "commander": { 48 | "version": "2.9.0", 49 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", 50 | "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", 51 | "dev": true, 52 | "requires": { 53 | "graceful-readlink": "1.0.1" 54 | } 55 | } 56 | } 57 | }, 58 | "ejs": { 59 | "version": "1.0.0", 60 | "resolved": "https://registry.npmjs.org/ejs/-/ejs-1.0.0.tgz", 61 | "integrity": "sha1-ycYKSKRu5FL7MqccMXuV5aofyz0=", 62 | "dev": true 63 | }, 64 | "entities": { 65 | "version": "1.1.1", 66 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", 67 | "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", 68 | "dev": true 69 | }, 70 | "graceful-readlink": { 71 | "version": "1.0.1", 72 | "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", 73 | "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", 74 | "dev": true 75 | }, 76 | "iced-coffee-script": { 77 | "version": "108.0.11", 78 | "resolved": "https://registry.npmjs.org/iced-coffee-script/-/iced-coffee-script-108.0.11.tgz", 79 | "integrity": "sha1-HXH/k8kElyimRoOFqpvIkf10xY8=", 80 | "dev": true, 81 | "requires": { 82 | "iced-runtime": "1.0.3" 83 | } 84 | }, 85 | "iced-runtime": { 86 | "version": "1.0.3", 87 | "resolved": "https://registry.npmjs.org/iced-runtime/-/iced-runtime-1.0.3.tgz", 88 | "integrity": "sha1-LU9PuZmreqVDCxk8d6f85BGDGc4=", 89 | "dev": true 90 | }, 91 | "jsdoctypeparser": { 92 | "version": "1.2.0", 93 | "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-1.2.0.tgz", 94 | "integrity": "sha1-597cFToRhJ/8UUEUSuhqfvDCU5I=", 95 | "dev": true, 96 | "requires": { 97 | "lodash": "3.10.1" 98 | }, 99 | "dependencies": { 100 | "lodash": { 101 | "version": "3.10.1", 102 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", 103 | "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", 104 | "dev": true 105 | } 106 | } 107 | }, 108 | "linkify-it": { 109 | "version": "2.0.3", 110 | "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.0.3.tgz", 111 | "integrity": "sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=", 112 | "dev": true, 113 | "requires": { 114 | "uc.micro": "1.0.3" 115 | } 116 | }, 117 | "lodash": { 118 | "version": "4.17.4", 119 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", 120 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", 121 | "dev": true 122 | }, 123 | "markdown-it": { 124 | "version": "7.0.1", 125 | "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-7.0.1.tgz", 126 | "integrity": "sha1-8S2LiKk+ZCVDSN/Rg71wv2BWekI=", 127 | "dev": true, 128 | "requires": { 129 | "argparse": "1.0.9", 130 | "entities": "1.1.1", 131 | "linkify-it": "2.0.3", 132 | "mdurl": "1.0.1", 133 | "uc.micro": "1.0.3" 134 | } 135 | }, 136 | "markdox": { 137 | "version": "0.1.10", 138 | "resolved": "https://registry.npmjs.org/markdox/-/markdox-0.1.10.tgz", 139 | "integrity": "sha1-kLZLNatwOgDxg4RXjO6K4S82Scs=", 140 | "dev": true, 141 | "requires": { 142 | "async": "2.5.0", 143 | "coffee-script": "1.12.7", 144 | "commander": "2.11.0", 145 | "dox": "https://github.com/visionmedia/dox/tarball/master", 146 | "ejs": "1.0.0", 147 | "iced-coffee-script": "108.0.11", 148 | "underscore": "1.8.3" 149 | } 150 | }, 151 | "mdurl": { 152 | "version": "1.0.1", 153 | "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", 154 | "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", 155 | "dev": true 156 | }, 157 | "sprintf-js": { 158 | "version": "1.0.3", 159 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 160 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 161 | "dev": true 162 | }, 163 | "uc.micro": { 164 | "version": "1.0.3", 165 | "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.3.tgz", 166 | "integrity": "sha1-ftUNXg+an7ClczeSWfKndFjVAZI=", 167 | "dev": true 168 | }, 169 | "underscore": { 170 | "version": "1.8.3", 171 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", 172 | "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", 173 | "dev": true 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yammer-sketch-tools", 3 | "version": "0.0.2", 4 | "description": "Plugins and commands we use at Yammer", 5 | "scripts": { 6 | "doc": "node doc/markdox.js" 7 | }, 8 | "devDependencies": { 9 | "markdox": "^0.1.10" 10 | } 11 | } 12 | --------------------------------------------------------------------------------