├── Utility Belt.sketchplugin └── Contents │ ├── Resources │ └── utility-belt.png │ └── Sketch │ ├── move.cocoascript │ ├── _disabled.txt │ ├── font.cocoascript │ ├── image.cocoascript │ ├── info.cocoascript │ ├── guide.cocoascript │ ├── select.cocoascript │ ├── manifest.json │ └── _utilitybelt.js ├── appcast.xml └── README.md /Utility Belt.sketchplugin/Contents/Resources/utility-belt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frankko/UtilityBelt/HEAD/Utility Belt.sketchplugin/Contents/Resources/utility-belt.png -------------------------------------------------------------------------------- /Utility Belt.sketchplugin/Contents/Sketch/move.cocoascript: -------------------------------------------------------------------------------- 1 | @import "_utilitybelt.js" 2 | 3 | var move_selected_to_x_y = function(context) { 4 | var doc = context.document; 5 | var page = [doc currentPage]; 6 | var selection = context.selection; 7 | 8 | if ([selection count] > 0) { 9 | var minX, minY; 10 | var layersRect = UtilityBelt.layer.getRect(selection); 11 | 12 | minX = layersRect.left; 13 | minY = layersRect.top; 14 | 15 | var newXY = [doc askForUserInput:"Enter new x,y:" initialValue:minX + ',' + minY]; 16 | if (newXY) { 17 | var newCoords = newXY.split(','); 18 | 19 | if (isNaN(newCoords[0]) || isNaN(newCoords[1])) { 20 | UtilityBelt.util.displayAlert("Move Selection…","Canceling. Enter coordinates in format “x,y”"); 21 | } else { 22 | var newX = parseFloat(newCoords[0]); 23 | var newY = parseFloat(newCoords[1]); 24 | 25 | UtilityBelt.layer.moveLayersToXY(selection,newX,newY); 26 | } 27 | } 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /Utility Belt.sketchplugin/Contents/Sketch/_disabled.txt: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | "UBguide-newguideaty", 4 | "UBguide-newguideatx", 5 | "UBguide-selectedtoguides", 6 | "UBguide-selectedtoguideshorizontal", 7 | "UBguide-selectedtoguidesvertical", 8 | "UBguide-selectedtocenterguides", 9 | "UBguide-selectedtocenterguideshorizontal", 10 | "UBguide-selectedtocenterguidesvertical", 11 | "UBguide-removeallguidesfromartboard" 12 | ], 13 | "title": "Guide" 14 | }, 15 | { 16 | "items": [ 17 | "UBInfo-selectedinfo" 18 | ], 19 | "title": "Info" 20 | }, 21 | { 22 | "items": [ 23 | "UBSelect-selectlayersbyname", 24 | "UBSelect-selectlayersbyfontname", 25 | "UBSelect-selectsimilartextlayers", 26 | "UBSelect-selectsimilarcolorborder", 27 | "UBSelect-selectsimilarcolorfill" 28 | ], 29 | "title": "Select" 30 | } 31 | -------------------------------------------------------------------------------- /Utility Belt.sketchplugin/Contents/Sketch/font.cocoascript: -------------------------------------------------------------------------------- 1 | @import "_utilitybelt.js" 2 | 3 | var list_all_fonts = function(context) { 4 | var doc = context.document; 5 | var pages = [doc pages] 6 | var foundFonts = []; 7 | 8 | var loop = [pages objectEnumerator]; 9 | while (page = [loop nextObject]) { 10 | var scope = [page children]; 11 | var predicate = NSPredicate.predicateWithFormat("className == %@",'MSTextLayer'); 12 | var queryResult = scope.filteredArrayUsingPredicate(predicate); 13 | if ([queryResult count] > 0) { 14 | var loop2 = [queryResult objectEnumerator]; 15 | while (textLayer = [loop2 nextObject]) { 16 | foundFonts.push([textLayer fontPostscriptName]); 17 | } 18 | } 19 | } 20 | foundFonts = UtilityBelt.array.arrayUnique(foundFonts); 21 | if (foundFonts.length > 0) { 22 | foundFonts = foundFonts.sort(); 23 | var fontString = foundFonts.join("\n"); 24 | UtilityBelt.util.displayAlert('Fonts in Use',fontString); 25 | } else { 26 | UtilityBelt.util.displayAlert('Fonts in Use','No fonts used in this document.'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /Utility Belt.sketchplugin/Contents/Sketch/image.cocoascript: -------------------------------------------------------------------------------- 1 | @import "_utilitybelt.js" 2 | 3 | var clipboard_image_to_image_fill = function(context) { 4 | var doc = context.document; 5 | var page = [doc currentPage]; 6 | var selection = context.selection; 7 | 8 | var img = UtilityBelt.image.getImageFromClipboard(); 9 | 10 | if (img) { 11 | if ([selection count] > 0) { 12 | var loop = [selection objectEnumerator]; 13 | while (layer = [loop nextObject]) { 14 | var layerClass = [layer className]; 15 | if (layerClass == "MSShapeGroup") { 16 | UtilityBelt.image.setImageAsFill(layer,img); 17 | } 18 | } 19 | UtilityBelt.util.reloadInspector(doc); 20 | } 21 | } 22 | }; 23 | 24 | var url_to_image_fill = function(context) { 25 | var doc = context.document; 26 | var page = [doc currentPage]; 27 | var selection = context.selection; 28 | 29 | var url = UtilityBelt.util.displayPrompt(doc,"Enter image URL:",""); 30 | 31 | if (url) { 32 | if ([selection count] > 0) { 33 | var loop = [selection objectEnumerator]; 34 | while (layer = [loop nextObject]) { 35 | var layerClass = [layer className]; 36 | if (layerClass == "MSShapeGroup") { 37 | UtilityBelt.image.setImageURLasFill(layer,url); 38 | } 39 | } 40 | UtilityBelt.util.reloadInspector(doc); 41 | } 42 | } 43 | }; -------------------------------------------------------------------------------- /appcast.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Utility Belt 5 | https://raw.githubusercontent.com/frankko/UtilityBelt/master/appcast.xml 6 | An always-expanding collection of simple, focused plugins for Sketch. 7 | en 8 | 9 | Version 1.50.0 10 | 11 | 13 |
  • updates for Sketch 50.
  • 14 | 15 | ]]> 16 |
    17 | 18 |
    19 | 20 | Version 1.45.1 21 | 22 | 24 |
  • Support for Sketch 45's plugin auto-updating.
  • 25 |
  • Updated some actions to work with Sketch 45.
  • 26 |
  • Temporarily disabled some actions that don't work with Sketch 45.
  • 27 | 28 | ]]> 29 |
    30 | 31 |
    32 |
    33 |
    -------------------------------------------------------------------------------- /Utility Belt.sketchplugin/Contents/Sketch/info.cocoascript: -------------------------------------------------------------------------------- 1 | @import "_utilitybelt.js" 2 | 3 | var selected_info = function(context) { 4 | var doc = context.document; 5 | var page = [doc currentPage]; 6 | var selection = context.selection; 7 | 8 | if ([selection count] > 0) { 9 | var minX, minY; 10 | var layersRect = UtilityBelt.layer.getRect(selection); 11 | 12 | var allLeft = [], 13 | allTop = [], 14 | allRight = [], 15 | allBottom = []; 16 | 17 | var loop = [selection objectEnumerator]; 18 | while (layer = [loop nextObject]) { 19 | /* 20 | var frameInArtboard = [layer frameInArtboard]; 21 | var layerX = parseFloat(frameInArtboard.origin.x); 22 | var layerY = parseFloat(frameInArtboard.origin.y); 23 | var layerR = parseFloat(layerX + frameInArtboard.size.width); 24 | var layerB = parseFloat(layerY + frameInArtboard.size.height); 25 | */ 26 | 27 | var frameInArtboard = [layer absoluteRect]; 28 | var layerX = parseFloat(frameInArtboard.x()); 29 | var layerY = parseFloat(frameInArtboard.y()); 30 | var layerR = parseFloat(layerX + frameInArtboard.width()); 31 | var layerB = parseFloat(layerY + frameInArtboard.height()); 32 | 33 | 34 | allLeft.push(layerX); 35 | allTop.push(layerY); 36 | allRight.push(layerR); 37 | allBottom.push(layerB); 38 | } 39 | 40 | allLeft.sort(function(a,b){return a-b}); 41 | allTop.sort(function(a,b){return a-b}); 42 | allRight.sort(function(a,b){return b-a}); 43 | allBottom.sort(function(a,b){return b-a}); 44 | 45 | var totalWidth = allRight[0] - allLeft[0]; 46 | var totalHeight = allBottom[0] - allTop[0]; 47 | 48 | var text = "X: " + allLeft[0] + "\n"; 49 | text += "Y: " + allTop[0] + "\n"; 50 | text += "W: " + totalWidth + "\n"; 51 | text += "H: " + totalHeight; 52 | 53 | UtilityBelt.util.displayAlert("Selected Info",text); 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ★ Utility Belt 2 | 3 | An always-expanding collection of small, simple, focused plugins for Sketch 45+. 4 | 5 | ## Font → 6 | 7 | * `List all Fonts`: displays an alert with the PostScript names of all the fonts used by the document. (Warning: if you’re using a lot of fonts the alert could get huge.) 8 | 9 | ## Guide → 10 | 11 | * **[Temporarily Disabled]** `New Horizontal Guide at…`: prompts you for a location to create a horizontal guide. (An object or an artboard must be selected so the plugin knows where to place the guide.) 12 | * **[Temporarily Disabled]** `New Vertical Guide at…`: prompts you for a location to create a vertical guide. (An object or an artboard must be selected so the plugin knows where to place the guide.) 13 | * **[Temporarily Disabled]** `Selected Layer to Guides (Horizontal)`: draws horizontal guides along the top and bottom edges of the selected object(s). 14 | * **[Temporarily Disabled]** `Selected Layer to Guides (Vertical)`: draws vertical guides along the left and right edges of the selected object(s). 15 | * **[Temporarily Disabled]** `Selected Layer to Guides (Both)`: draws both horizontal and vertical guides along all four sides of the selected object(s). 16 | * **[Temporarily Disabled]** `Selected Layer to Center Guide (Horizontal)`: draws a horizontal guide at the vertical center point of the selected object(s). 17 | * **[Temporarily Disabled]** `Selected Layer to Center Guide (Vertical)`: draws a vertical guide at the horizontal center point of the selected object(s). 18 | * **[Temporarily Disabled]** `Selected Layer to Center Guide (Both)`: draws a horizontal and vertical guide at center point of the selected object(s). 19 | * **[Temporarily Disabled]** `Remove Guides from Selected Artboard`: removes all guides from the selected artboard 20 | 21 | ## Image → 22 | 23 | * `Image (Clipboard) to Layer Pattern Fill`: if an image is in the clipboard, it will be pasted as a pattern fill on to the currently selected layer(s) 24 | * `Image (URL) to Layer Pattern Fill`: prompts you for an image URL, which is downloaded and set as a pattern fill on to the currently selected layer(s) 25 | * [**Be careful.** There’s no error-checking in this yet, so be sure you’re pasting a URL to a bitmap image, otherwise Sketch will crash.] 26 | 27 | ## Info → 28 | 29 | * **[Temporarily Disabled]** `Get Info on Selected Layers`: pops an alert box containing the cumulative x, y, width, and height of the selected layers. 30 | 31 | ## Move → 32 | 33 | * `Move Selected Layers to [x,y]`: prompts you for new x,y coordinates and moves the selected objects to those new coordinates, _preserving each object’s relative position_. 34 | 35 | ## Select → 36 | 37 | * `Select Layers by Name`: takes a user-inputted string and selects all artboards, layer groups, and/or layers that contain that string 38 | * `Select Layers by Font Name`: enter a font’s PostScript family name and this will select all text layers in the current page matching the inputted font (use `Font → List all Fonts` to get a list of all document fonts.) 39 | * **[Temporarily Disabled]** `Select Similar Color Border`: finds layers with a border color matching the currently selected layer 40 | * **[Temporarily Disabled]** `Select Similar Color Fill`: finds layers with a fill color matching the currently selected layer 41 | 42 | * * * 43 | 44 | ### Who? 45 | 46 | I’m Frank Kolodziej, a Wichita, KS-based freelance designer & developer. I am [available for hire](http://kolo.io/). I’m [@frankko](https://twitter.com/frankko) on Twitter. 47 | 48 | #### Other Plugins 49 | 50 | - [Place Linked Bitmap](https://github.com/frankko/Place-Linked-Bitmap): A plugin to place external bitmap files into Sketch and update Sketch layers after external bitmaps are updated 51 | - [Artboard Tools](https://github.com/frankko/Artboard-Tools): Plugins for arranging artboards and navigating between artboards. -------------------------------------------------------------------------------- /Utility Belt.sketchplugin/Contents/Sketch/guide.cocoascript: -------------------------------------------------------------------------------- 1 | @import "_utilitybelt.js" 2 | 3 | var new_guide_at_x = function(context) { 4 | var doc = context.document; 5 | var page = [doc currentPage]; 6 | var selection = context.selection; 7 | if ([selection count] > 0) { 8 | var artboard = UtilityBelt.artboard.getCurrentArtboard([selection objectAtIndex:0],page); 9 | var guideX = [doc askForUserInput:"Create new vertical guide at:" initialValue:"0"]; 10 | if (guideX) { 11 | UtilityBelt.guide.addGuide(artboard,'v',guideX); 12 | } 13 | } 14 | }; 15 | var new_guide_at_y = function(context) { 16 | var doc = context.document; 17 | var page = [doc currentPage]; 18 | var selection = context.selection; 19 | if ([selection count] > 0) { 20 | var artboard = UtilityBelt.artboard.getCurrentArtboard([selection objectAtIndex:0],page); 21 | var guideY = [doc askForUserInput:"Create new horizontal guide at:" initialValue:"0"]; 22 | if (guideY) { 23 | UtilityBelt.guide.addGuide(artboard,'h',guideY); 24 | } 25 | } 26 | }; 27 | var selected_to_center_guides = function(context) { 28 | var doc = context.document; 29 | var page = [doc currentPage]; 30 | var selection = context.selection; 31 | if ([selection count] > 0) { 32 | var loop = [selection objectEnumerator]; 33 | while (obj = [loop nextObject]) { 34 | var artboard = UtilityBelt.artboard.getCurrentArtboard(obj,page); 35 | var frame = [obj frameInArtboard]; 36 | 37 | log(frame); 38 | 39 | var guideCH = frame.origin.y + (frame.size.height / 2); 40 | UtilityBelt.guide.addGuide(artboard,'h',guideCH); 41 | 42 | var guideCV = frame.origin.x + (frame.size.width / 2); 43 | UtilityBelt.guide.addGuide(artboard,'v',guideCV); 44 | } 45 | } 46 | }; 47 | var selected_to_center_guides_horizontal = function(context) { 48 | var doc = context.document; 49 | var page = [doc currentPage]; 50 | var selection = context.selection; 51 | if ([selection count] > 0) { 52 | var loop = [selection objectEnumerator]; 53 | while (obj = [loop nextObject]) { 54 | var artboard = UtilityBelt.artboard.getCurrentArtboard(obj,page); 55 | var frame = [obj frameInArtboard]; 56 | 57 | log(frame); 58 | 59 | var guideCH = frame.origin.y + (frame.size.height / 2); 60 | UtilityBelt.guide.addGuide(artboard,'h',guideCH); 61 | } 62 | } 63 | }; 64 | var selected_to_center_guides_vertical = function(context) { 65 | var doc = context.document; 66 | var page = [doc currentPage]; 67 | var selection = context.selection; 68 | if ([selection count] > 0) { 69 | var loop = [selection objectEnumerator]; 70 | while (obj = [loop nextObject]) { 71 | var artboard = UtilityBelt.artboard.getCurrentArtboard(obj,page); 72 | var frame = [obj frameInArtboard]; 73 | 74 | log(frame); 75 | 76 | var guideCV = frame.origin.x + (frame.size.width / 2); 77 | UtilityBelt.guide.addGuide(artboard,'v',guideCV); 78 | } 79 | } 80 | }; 81 | 82 | 83 | var selected_to_guides = function(context) { 84 | var doc = context.document; 85 | var page = [doc currentPage]; 86 | var selection = context.selection; 87 | if ([selection count] > 0) { 88 | var loop = [selection objectEnumerator]; 89 | while (obj = [loop nextObject]) { 90 | var artboard = UtilityBelt.artboard.getCurrentArtboard(obj,page); 91 | var frame = [obj frameInArtboard]; 92 | 93 | log(frame); 94 | 95 | var guideT = frame.origin.y 96 | UtilityBelt.guide.addGuide(artboard,'h',guideT); 97 | var guideB = frame.origin.y + frame.size.height 98 | UtilityBelt.guide.addGuide(artboard,'h',guideB); 99 | 100 | var guideL = frame.origin.x 101 | UtilityBelt.guide.addGuide(artboard,'v',guideL); 102 | var guideR = frame.origin.x + frame.size.width 103 | UtilityBelt.guide.addGuide(artboard,'v',guideR); 104 | } 105 | } 106 | }; 107 | 108 | var selected_to_guides_horizontal = function(context) { 109 | var doc = context.document; 110 | var page = [doc currentPage]; 111 | var selection = context.selection; 112 | if ([selection count] > 0) { 113 | var loop = [selection objectEnumerator]; 114 | while (obj = [loop nextObject]) { 115 | var artboard = UtilityBelt.artboard.getCurrentArtboard(obj,page); 116 | var frame = [obj frameInArtboard]; 117 | log(frame); 118 | 119 | var guideT = frame.origin.y 120 | UtilityBelt.guide.addGuide(artboard,'h',guideT); 121 | var guideB = frame.origin.y + frame.size.height 122 | UtilityBelt.guide.addGuide(artboard,'h',guideB); 123 | } 124 | } 125 | }; 126 | 127 | var selected_to_guides_vertical = function(context) { 128 | var doc = context.document; 129 | var page = [doc currentPage]; 130 | var selection = context.selection; 131 | if ([selection count] > 0) { 132 | var loop = [selection objectEnumerator]; 133 | while (obj = [loop nextObject]) { 134 | var artboard = UtilityBelt.artboard.getCurrentArtboard(obj,page); 135 | var frame = [obj frameInArtboard]; 136 | log(frame); 137 | 138 | var guideL = frame.origin.x 139 | UtilityBelt.guide.addGuide(artboard,'v',guideL); 140 | var guideR = frame.origin.x + frame.size.width 141 | UtilityBelt.guide.addGuide(artboard,'v',guideR); 142 | } 143 | } 144 | }; 145 | 146 | var remove_all_guides_from_artboard = function(context) { 147 | var doc = context.document; 148 | var page = [doc currentPage]; 149 | var selection = context.selection; 150 | 151 | if ([selection count] > 0) { 152 | var loop = [selection objectEnumerator]; 153 | while (obj = [loop nextObject]) { 154 | var artboard = UtilityBelt.artboard.getCurrentArtboard(obj,page); 155 | UtilityBelt.guide.removeGuidesFromArtboard(artboard); 156 | } 157 | } 158 | }; 159 | -------------------------------------------------------------------------------- /Utility Belt.sketchplugin/Contents/Sketch/select.cocoascript: -------------------------------------------------------------------------------- 1 | @import "_utilitybelt.js" 2 | 3 | var select_layers_by_font_name = function(context) { 4 | var doc = context.document; 5 | var page = [doc currentPage]; 6 | 7 | var fontName = UtilityBelt.util.displayPrompt(doc,"Enter a font PostScript name…",""); 8 | 9 | if (fontName != '') { 10 | var scope = [page children]; 11 | var predicate = NSPredicate.predicateWithFormat("fontPostscriptName == %@",fontName); 12 | var queryResult = scope.filteredArrayUsingPredicate(predicate); 13 | log(queryResult); 14 | if ([queryResult count] > 0) { 15 | // var sketchVersion = UtilityBelt.util.getSketchVersion(context); 16 | // log(sketchVersion); 17 | // if (sketchVersion >= 45) { 18 | UtilityBelt.select.deselectAllLayers(context); 19 | UtilityBelt.select.selectLayers(queryResult); 20 | // } else { 21 | // [page deselectAllLayers]; 22 | // [page selectLayers:queryResult]; 23 | // } 24 | } 25 | } 26 | }; 27 | 28 | var select_similar_text_layers = function(context) { 29 | var doc = context.document; 30 | var selection = context.selection; 31 | var page = [doc currentPage]; 32 | 33 | if ([selection count] > 0) { 34 | var selectedLayer = [selection objectAtIndex:0]; 35 | var selectedFontName = [selectedLayer fontPostscriptName]; 36 | var selectedFontSize = [selectedLayer fontSize]; 37 | var selectedTextColor = [selectedLayer textColor]; 38 | var selectedTextColorString = selectedTextColor.immutableModelObject().hexValue(); 39 | 40 | var scope = [page children]; 41 | var predicate = NSPredicate.predicateWithFormat("className == %@","MSTextLayer"); 42 | var queryResult = scope.filteredArrayUsingPredicate(predicate); 43 | if ([queryResult count] > 0) { 44 | UtilityBelt.select.deselectAllLayers(context); 45 | var loop = [queryResult objectEnumerator]; 46 | while (layer = [loop nextObject]) { 47 | var thisFontName = [layer fontPostscriptName]; 48 | var thisFontSize = [layer fontSize]; 49 | var thisTextColor = [layer textColor]; 50 | var thisTextColorString = thisTextColor.immutableModelObject().hexValue(); 51 | 52 | if ((thisFontName == selectedFontName) && (thisFontSize == selectedFontSize) && (thisTextColorString == selectedTextColorString)) { 53 | // log(layer); 54 | layer.select_byExpandingSelection(true,true); 55 | } 56 | } 57 | } 58 | } else { 59 | UtilityBelt.util.displayAlert('Select Similar Text Layers','Select one text layer then try again.'); 60 | } 61 | }; 62 | 63 | var select_layers_by_name = function(context) { 64 | var doc = context.document; 65 | var selection = context.selection; 66 | var page = [doc currentPage]; 67 | var findString = UtilityBelt.util.displayPrompt(doc,"Enter text to find in a layer name…",""); 68 | 69 | if (findString != '') { 70 | // if (findString.match(/^\/.+\/[a-z]+$/)) { 71 | // var find = new RegExp(findString); 72 | // } else { 73 | var find = findString.toLowerCase(); 74 | // } 75 | 76 | var allLayersTMP = []; 77 | 78 | if ([selection count] > 0) { 79 | var loop = [selection objectEnumerator]; 80 | while (layer = [loop nextObject]) { 81 | if ([layer children]) { 82 | allLayersTMP.push([layer children]); 83 | } else { 84 | allLayersTMP.push(layer); 85 | } 86 | } 87 | } else { 88 | allLayersTMP.push([page children]); 89 | } 90 | 91 | var allLayers = UtilityBelt.array.arrayFlatten(allLayersTMP); 92 | var matchedLayers = []; 93 | 94 | for (var x = 0; x < allLayers.length; x++) { 95 | var layer = allLayers[x]; 96 | var layerName = [layer name].toLowerCase(); 97 | if (layerName.match(find)) { 98 | matchedLayers.push(layer); 99 | } 100 | } 101 | 102 | if (matchedLayers.length > 0) { 103 | // var sketchVersion = UtilityBelt.util.getSketchVersion(context); 104 | // log(sketchVersion); 105 | // if (sketchVersion >= 45) { 106 | UtilityBelt.select.deselectAllLayers(context); 107 | UtilityBelt.select.selectLayers(matchedLayers); 108 | // } else { 109 | // [page deselectAllLayers]; 110 | // [page selectLayers:matchedLayers]; 111 | // } 112 | } 113 | } 114 | }; 115 | 116 | var select_similar_color_border = function(context) { 117 | var doc = context.document; 118 | var page = [doc currentPage]; 119 | var selection = context.selection; 120 | 121 | if ([selection count] > 0) { 122 | var hexValue = [[[[[selection objectAtIndex:0] style] border] color] hexValue]; 123 | } else { 124 | var hexValue = UtilityBelt.util.displayPrompt(doc,"Enter a hex value…",""); 125 | } 126 | hexValue = UtilityBelt.util.sanitizeHexValue(hexValue); 127 | UtilityBelt.select.selectSimilarColorBorder(context,page,hexValue); 128 | }; 129 | 130 | var select_similar_color_fill = function(context) { 131 | var doc = context.document; 132 | var page = [doc currentPage]; 133 | var selection = context.selection; 134 | 135 | if ([selection count] > 0) { 136 | var selected = [selection objectAtIndex:0]; 137 | if ([selected className] == "MSTextLayer") { 138 | var hexValue = [[selected textColor] hexValue]; 139 | } else { 140 | // UtilityBelt.util.dumpObj(selected); 141 | // var hexValue = [[[[selected style] fill] color] hexValue]; 142 | var hexValue = selected.style().fills().firstObject().color().immutableModelObject().hexValue(); 143 | } 144 | } else { 145 | var hexValue = UtilityBelt.util.displayPrompt(doc,"Enter a hex value…",""); 146 | } 147 | hexValue = UtilityBelt.util.sanitizeHexValue(hexValue); 148 | UtilityBelt.select.selectSimilarColorFill(context,page,hexValue); 149 | }; 150 | 151 | -------------------------------------------------------------------------------- /Utility Belt.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "appcast": "https://raw.githubusercontent.com/frankko/UtilityBelt/master/appcast.xml", 3 | "author" : "Frank Kolodziej", 4 | "commands" : [ 5 | { 6 | "script" : "font.cocoascript", 7 | "handler" : "list_all_fonts", 8 | "shortcut" : "", 9 | "name" : "List all Fonts", 10 | "identifier" : "UBFont-listallfonts" 11 | }, 12 | { 13 | "script" : "image.cocoascript", 14 | "handler" : "clipboard_image_to_image_fill", 15 | "shortcut" : "", 16 | "name" : "Image (Clipboard) to Layer Pattern Fill", 17 | "identifier" : "UBImage-clipboardtoimagefill" 18 | }, 19 | { 20 | "script" : "image.cocoascript", 21 | "handler" : "url_to_image_fill", 22 | "shortcut" : "", 23 | "name" : "Image (URL) to Layer Pattern Fill", 24 | "identifier" : "UBImage-urltoimagefill" 25 | }, 26 | { 27 | "script" : "guide.cocoascript", 28 | "handler" : "remove_all_guides_from_artboard", 29 | "shortcut" : "", 30 | "name" : "Remove Guides from Selected Artboard", 31 | "identifier" : "UBguide-removeallguidesfromartboard" 32 | }, 33 | { 34 | "script" : "guide.cocoascript", 35 | "handler" : "selected_to_guides_horizontal", 36 | "shortcut" : "", 37 | "name" : "Selected Layer to Guides (Horizontal)", 38 | "identifier" : "UBguide-selectedtoguideshorizontal" 39 | }, 40 | { 41 | "script" : "guide.cocoascript", 42 | "handler" : "selected_to_guides_vertical", 43 | "shortcut" : "", 44 | "name" : "Selected Layer to Guides (Vertical)", 45 | "identifier" : "UBguide-selectedtoguidesvertical" 46 | }, 47 | { 48 | "script" : "guide.cocoascript", 49 | "handler" : "selected_to_guides", 50 | "shortcut" : "", 51 | "name" : "Selected Layer to Guides (Both)", 52 | "identifier" : "UBguide-selectedtoguides" 53 | }, 54 | { 55 | "script" : "guide.cocoascript", 56 | "handler" : "selected_to_center_guides_horizontal", 57 | "shortcut" : "", 58 | "name" : "Selected Layer to Center Guide (Horizontal)", 59 | "identifier" : "UBguide-selectedtocenterguideshorizontal" 60 | }, 61 | { 62 | "script" : "guide.cocoascript", 63 | "handler" : "selected_to_center_guides_vertical", 64 | "shortcut" : "", 65 | "name" : "Selected Layer to Center Guide (Vertical)", 66 | "identifier" : "UBguide-selectedtocenterguidesvertical" 67 | }, 68 | { 69 | "script" : "guide.cocoascript", 70 | "handler" : "selected_to_center_guides", 71 | "shortcut" : "", 72 | "name" : "Selected Layer to Center Guide (Both)", 73 | "identifier" : "UBguide-selectedtocenterguides" 74 | }, 75 | { 76 | "script" : "guide.cocoascript", 77 | "handler" : "new_guide_at_y", 78 | "shortcut" : "", 79 | "name" : "New Horizontal Guide at…", 80 | "identifier" : "UBguide-newguideaty" 81 | }, 82 | { 83 | "script" : "guide.cocoascript", 84 | "handler" : "new_guide_at_x", 85 | "shortcut" : "", 86 | "name" : "New Vertical Guide at…", 87 | "identifier" : "UBguide-newguideatx" 88 | }, 89 | { 90 | "script" : "info.cocoascript", 91 | "handler" : "selected_info", 92 | "shortcut" : "ctrl cmd option i", 93 | "name" : "Get Info on Selected Layers", 94 | "identifier" : "UBInfo-selectedinfo" 95 | }, 96 | { 97 | "script" : "move.cocoascript", 98 | "handler" : "move_selected_to_x_y", 99 | "shortcut" : "", 100 | "name" : "Move Selected Layers to [x,y]", 101 | "identifier" : "UBMove-moveselectedtoxy" 102 | }, 103 | { 104 | "script" : "select.cocoascript", 105 | "handler" : "select_layers_by_name", 106 | "shortcut" : "control option command f", 107 | "name" : "Select Layers by Name", 108 | "identifier" : "UBSelect-selectlayersbyname" 109 | }, 110 | { 111 | "script" : "select.cocoascript", 112 | "handler" : "select_layers_by_font_name", 113 | "shortcut" : "", 114 | "name" : "Select Layers by Font Name", 115 | "identifier" : "UBSelect-selectlayersbyfontname" 116 | }, 117 | { 118 | "script" : "select.cocoascript", 119 | "handler" : "select_similar_text_layers", 120 | "shortcut" : "", 121 | "name" : "Select Similar Text Layers", 122 | "identifier" : "UBSelect-selectsimilartextlayers" 123 | }, 124 | { 125 | "script" : "select.cocoascript", 126 | "handler" : "select_similar_color_border", 127 | "shortcut" : "", 128 | "name" : "Select Similar Color Border", 129 | "identifier" : "UBSelect-selectsimilarcolorborder" 130 | }, 131 | { 132 | "script" : "select.cocoascript", 133 | "handler" : "select_similar_color_fill", 134 | "shortcut" : "", 135 | "name" : "Select Similar Color Fill", 136 | "identifier" : "UBSelect-selectsimilarcolorfill" 137 | } 138 | ], 139 | "icon": "utility-belt.png", 140 | "menu" : { 141 | "items" : [ 142 | { 143 | "items": [ 144 | "UBFont-listallfonts" 145 | ], 146 | "title": "Font" 147 | }, 148 | { 149 | "items": [ 150 | "UBImage-clipboardtoimagefill", 151 | "UBImage-urltoimagefill" 152 | ], 153 | "title": "Image" 154 | }, 155 | { 156 | "items": [ 157 | "UBMove-moveselectedtoxy" 158 | ], 159 | "title": "Move" 160 | }, 161 | { 162 | "items": [ 163 | "UBSelect-selectlayersbyname", 164 | "UBSelect-selectlayersbyfontname", 165 | "UBSelect-selectsimilartextlayers" 166 | ], 167 | "title": "Select" 168 | } 169 | ], 170 | "title" : "★ Utility Belt" 171 | }, 172 | "identifier" : "io.kolo.sketch.utility-belt", 173 | "compatibleVersion": "48", 174 | "version" : "1.50.0", 175 | "description" : "An always-expanding collection of small, simple, focused plugins for Sketch.", 176 | "authorEmail" : "frank@kolo.io", 177 | "name" : "Utility Belt" 178 | } -------------------------------------------------------------------------------- /Utility Belt.sketchplugin/Contents/Sketch/_utilitybelt.js: -------------------------------------------------------------------------------- 1 | var UtilityBelt = { 2 | "array": { 3 | "arrayFlatten": function(arr) { 4 | var flattened = []; 5 | if (arr.length > 0) { 6 | for (var x = 0; x < arr.length; x++) { 7 | var arrX = arr[x]; 8 | for (var y = 0; y < [arrX count]; y++) { 9 | flattened.push(arrX[y]); 10 | } 11 | } 12 | } 13 | return flattened; 14 | }, 15 | "arrayUnique": function(a) { 16 | return a.reduce(function(p, c) { 17 | if (p.indexOf(c) < 0) p.push(c); 18 | return p; 19 | }, []); 20 | } 21 | }, 22 | "artboard": { 23 | "getCurrentArtboard": function(obj,page) { 24 | while ([obj parentGroup] != page) { 25 | obj = [obj parentGroup]; 26 | } 27 | if ([obj className] != "MSArtboardGroup") { 28 | obj = page; 29 | } 30 | return obj; 31 | } 32 | }, 33 | "guide": { 34 | "addGuide": function(artboard,dir,val) { 35 | if (dir == "h") { 36 | var rulerData = [artboard verticalRulerData]; 37 | } else { 38 | var rulerData = [artboard horizontalRulerData]; 39 | } 40 | [rulerData addGuideWithValue:val]; 41 | }, 42 | "removeGuidesFromArtboard": function(artboard) { 43 | var rulerDataH = [artboard horizontalRulerData]; 44 | while ([rulerDataH numberOfGuides] > 0) { 45 | [rulerDataH removeGuideAtIndex:0]; 46 | } 47 | var rulerDataV = [artboard verticalRulerData]; 48 | while ([rulerDataV numberOfGuides] > 0) { 49 | [rulerDataV removeGuideAtIndex:0]; 50 | } 51 | } 52 | }, 53 | "image": { 54 | "getImageFromClipboard": function() { 55 | var pasteBoard = [NSPasteboard generalPasteboard]; 56 | var validPBTypes = [[NSArray alloc] initWithObjects:[NSImage class], nil]; 57 | var pbOptions = [NSDictionary dictionary]; 58 | var copiedItems = [pasteBoard readObjectsForClasses:validPBTypes options:pbOptions]; 59 | if ([copiedItems count] > 0) { 60 | return [copiedItems objectAtIndex:0]; 61 | } else { 62 | return false; 63 | } 64 | }, 65 | "setImageAsFill": function(layer,img) { 66 | var fill = layer.style().fills().firstObject(); 67 | if (MSApplicationMetadata.metadata().appVersion < 47) { 68 | var newImage = [[MSImageData alloc] initWithImage:img convertColorSpace:false]]; 69 | } else { 70 | var newImage = [[MSImageData alloc] initWithImage:img]]; 71 | } 72 | [fill setImage:newImage]; 73 | [fill setFillType:4]; 74 | [fill setPatternFillType:1]; 75 | }, 76 | "setImageURLasFill": function(layer,url) { 77 | var imageData = UtilityBelt.network.getURL(url); 78 | var img = [[NSImage alloc] initWithData:imageData]; 79 | UtilityBelt.image.setImageAsFill(layer,img); 80 | } 81 | }, 82 | "layer": { 83 | "getRect": function(selection) { 84 | var allLeft = [], 85 | allTop = [], 86 | allRight = [], 87 | allBottom = []; 88 | 89 | var loop = [selection objectEnumerator]; 90 | while (layer = [loop nextObject]) { 91 | var layerX = parseFloat([[layer frame] x]); 92 | var layerY = parseFloat([[layer frame] y]); 93 | var layerR = parseFloat(layerX + [[layer frame] width]); 94 | var layerB = parseFloat(layerY + [[layer frame] height]); 95 | 96 | allLeft.push(layerX); 97 | allTop.push(layerY); 98 | allRight.push(layerR); 99 | allBottom.push(layerB); 100 | } 101 | 102 | allLeft.sort(function(a,b){return a-b}); 103 | allTop.sort(function(a,b){return a-b}); 104 | 105 | allRight.sort(function(a,b){return b-a}); 106 | allBottom.sort(function(a,b){return b-a}); 107 | 108 | var returnObj = { 109 | top:allTop[0], 110 | right:allRight[0], 111 | bottom:allBottom[0], 112 | left:allLeft[0] 113 | }; 114 | 115 | return returnObj; 116 | }, 117 | "moveLayersToXY": function(layers,newX,newY) { 118 | var minX, minY; 119 | var layersRect = UtilityBelt.layer.getRect(layers); 120 | 121 | minX = layersRect.left; 122 | minY = layersRect.top; 123 | 124 | var loop = [layers objectEnumerator]; 125 | while (layer = [loop nextObject]) { 126 | var layerX = parseFloat([[layer frame] x]); 127 | var layerY = parseFloat([[layer frame] y]); 128 | var layerNewX = newX + layerX - minX; 129 | var layerNewY = newY + layerY - minY; 130 | 131 | [[layer frame] setX:layerNewX]; 132 | [[layer frame] setY:layerNewY]; 133 | } 134 | } 135 | }, 136 | "network": { 137 | "getJSON": function(url) { 138 | var response = UtilityBelt.network.getURL(url); 139 | return JSON.parse(NSString.alloc().initWithData_encoding(response, NSUTF8StringEncoding)); 140 | }, 141 | "getURL": function(url) { 142 | var request = NSURLRequest.requestWithURL(NSURL.URLWithString(url)); 143 | var response = NSURLConnection.sendSynchronousRequest_returningResponse_error(request, null, null); 144 | return response; 145 | } 146 | }, 147 | "select": { 148 | "deselectAllLayers": function(context) { 149 | if(context.selection.count()) { 150 | context.selection.firstObject().select_byExtendingSelection(false, false); 151 | } 152 | }, 153 | "selectLayers": function(matchedLayers) { 154 | for (var x = 0; x < matchedLayers.length; x++) { 155 | var layer = matchedLayers[x]; 156 | layer.select_byExtendingSelection(true,true); 157 | } 158 | }, 159 | "selectSimilarColorBorder": function(context,target,hexValue) { 160 | var doc = context.document; 161 | var scope = [target children]; 162 | var predicate = NSPredicate.predicateWithFormat("style.border.color.immutableModelObject.hexValue == %@",hexValue); 163 | var queryResult = scope.filteredArrayUsingPredicate(predicate); 164 | 165 | if ([queryResult count] > 1) { 166 | // [[doc currentPage] deselectAllLayers]; 167 | [target selectLayers:queryResult]; 168 | } else { 169 | UtilityBelt.util.displayMessage(doc,"No similar layers (border color) found."); 170 | } 171 | }, 172 | "selectSimilarColorFill": function(context,target,hexValue) { 173 | var doc = context.document; 174 | var scope = [target children]; 175 | var predicate = NSPredicate.predicateWithFormat("style.fill.color.immutableModelObject.hexValue == %@",hexValue); 176 | var queryResult = scope.filteredArrayUsingPredicate(predicate); 177 | 178 | if ([queryResult count] > 1) { 179 | // [[doc currentPage] deselectAllLayers]; 180 | [target selectLayers:queryResult]; 181 | } else { 182 | UtilityBelt.util.displayMessage(doc,"No similar layers (fill color) found."); 183 | } 184 | } 185 | }, 186 | "util": { 187 | "displayAlert": function(title,text) { 188 | var app = [NSApplication sharedApplication]; 189 | [app displayDialog:text withTitle:title]; 190 | }, 191 | "displayPrompt": function(doc,text,initialValue) { 192 | var capturedInput = [doc askForUserInput:text initialValue:initialValue]; 193 | return capturedInput; 194 | }, 195 | "displayMessage": function(doc,text) { 196 | [doc showMessage:text]; 197 | }, 198 | "dumpObj": function(obj) { 199 | log("#####################################################################################") 200 | log("## Dumping object " + obj ) 201 | log("## obj class is: " + [obj className]) 202 | log("#####################################################################################") 203 | 204 | log("obj.properties:") 205 | log([obj class].mocha().properties()) 206 | log("obj.propertiesWithAncestors:") 207 | log([obj class].mocha().propertiesWithAncestors()) 208 | 209 | log("obj.classMethods:") 210 | log([obj class].mocha().classMethods()) 211 | log("obj.classMethodsWithAncestors:") 212 | log([obj class].mocha().classMethodsWithAncestors()) 213 | 214 | log("obj.instanceMethods:") 215 | log([obj class].mocha().instanceMethods()) 216 | log("obj.instanceMethodsWithAncestors:") 217 | log([obj class].mocha().instanceMethodsWithAncestors()) 218 | 219 | log("obj.protocols:") 220 | log([obj class].mocha().protocols()) 221 | log("obj.protocolsWithAncestors:") 222 | log([obj class].mocha().protocolsWithAncestors()) 223 | 224 | log("obj.treeAsDictionary():") 225 | log(obj.treeAsDictionary()) 226 | }, 227 | "getSketchVersion": function(context) { 228 | var sketch = context.api(); 229 | return sketch.version; 230 | }, 231 | "reloadInspector": function(doc) { 232 | [doc reloadInspector]; 233 | }, 234 | "sanitizeHexValue": function(hexValue) { 235 | hexValue = hexValue.replace("#",""); 236 | hexValue = hexValue.toUpperCase(); 237 | 238 | hexValueLength = hexValue.length; 239 | 240 | if (hexValueLength == 3) { 241 | var hexR = hexValue.substr(0,1); 242 | var hexG = hexValue.substr(1,1); 243 | var hexB = hexValue.substr(2,1); 244 | hexValue = hexR + hexR + hexG + hexG + hexB + hexB; 245 | } else if (hexValueLength == 6) { 246 | } else { 247 | hexValue = hexValue.substr(0,6); 248 | } 249 | 250 | return hexValue; 251 | }, 252 | "sendAction": function(context,commandToPerform) { 253 | var doc = context.document; 254 | try { 255 | [NSApp sendAction:commandToPerform to:nil from:doc]; 256 | } catch(e) { 257 | log(e); 258 | } 259 | } 260 | } 261 | }; --------------------------------------------------------------------------------