├── .gitignore ├── LICENSE ├── README.md ├── appcast.xml └── shared-textstyles.sketchplugin └── Contents ├── Resources └── icon@2x.png └── Sketch ├── export.cocoascript ├── import.cocoascript └── manifest.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac Files 2 | .Spotlight-V100 3 | .AppleDouble 4 | .LSOverride 5 | .DS_Store 6 | .Trashes 7 | Icon? 8 | !Icons 9 | ._* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Nils Hoenson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shared Text Styles 2 | A Sketch plugin that lets you import & export text styles throughout different Sketch documents. Shared Text Styles is built for Sketch 3.7+ 3 | 4 | [More info](http:/www.textstyl.es) 5 | 6 | ## Features 7 | Shared Text Styles currently let's you import and export text styles through a .json file. More features coming soon! 8 | 9 | ### Export text styles 10 | To create a consistent typographic style throughout your project, it's a good idea to create an overview of the Text Styles you're going to use in your project. 11 | 12 | When you defined your Text Styles, simply go to your Plugins → Shared Text Styles → Export Text Styles..., to export your Text Styles to a .json file. You can also use the keyboard shortcut `cmd + alt + e`. 13 | 14 | 15 | ![](http://i.imgur.com/FSVdvaz.gif) 16 | 17 | ### Import text styles 18 | When you've exported your Text Styles, you can import them into any Sketch document, so the typography is consistent. 19 | 20 | Go to your Plugins → Shared Text Styles → Import Text Styles..., to import the Text Styles. You can also use the keyboard shortcut `cmd + alt + n`. 21 | 22 | ![](http://i.imgur.com/XMp1L5R.gif) 23 | 24 | ### Re-Import text styles 25 | Typography is never done and changes quite a lot. Shared Text Styles let's you re-import Text Styles that overwrite their initial states, so you can keep on iterating. 26 | 27 | Go to your Plugins → Shared Text Styles → Import Text Styles..., to re-import the Text Styles. You can also use the keyboard shortcut `cmd + alt + n`. 28 | 29 | ![](http://i.giphy.com/l3vRgmedusQrkSjTO.gif) 30 | 31 | ## Installing 32 | 1. [Download the ZIP file](https://github.com/nilshoenson/shared-text-styles/archive/master.zip) and unzip on your machine. 33 | 2. Double click the `shared-textstyles.sketchplugin` 34 | 3. Restart Sketch.app 35 | 4. You're done! 36 | 37 | ## Contact/Feedback 38 | If you have any questions, run into any bugs or have suggestions to improve the plugin: [create an issue](https://github.com/nilshoenson/shared-text-styles/issues), [hit me up on Twitter](http://twitter.com/nilshoenson) or [send me an email](mailto:nils@hoenson.eu). Feedback is always appreciated! 39 | 40 | Shared Text Styles is made by [Nils Hoenson](https://twitter.com/nilshoenson) with help by [Yummygum](http://yummygum.com) to improve sharing text styles across teams and documents. 41 | 42 | ## Changelog 43 | Stay up-to-date with the latest release and check everything that's new or fixed. 44 | 45 | [Changelog](http://www.textstyl.es/changelog.html) 46 | -------------------------------------------------------------------------------- /appcast.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Shared Text Styles 5 | https://raw.githubusercontent.com/nilshoenson/shared-text-styles/master/appcast.xml 6 | Import and export text styles. 7 | en 8 | 9 | Version 1.3 10 | 11 | 13 |
  • Updated for Sketch's plugin update format
  • 14 | 15 | ]]> 16 |
    17 | 18 |
    19 | 20 | Version 1.4 21 | 22 | 24 |
  • Compatible with Sketch 47+
  • 25 | 26 | ]]> 27 |
    28 | 29 |
    30 | 31 | Version 1.5 32 | 33 | 35 |
  • Compatible with Sketch 48+
  • 36 | 37 | ]]> 38 |
    39 | 40 |
    41 | 42 | Version 1.5.1 43 | 44 | 46 |
  • Small bug fix
  • 47 | 48 | ]]> 49 |
    50 | 51 |
    52 | 53 | Version 1.5.2 54 | 55 | 57 |
  • Fixed version number
  • 58 | 59 | ]]> 60 |
    61 | 62 |
    63 | 64 | Version 1.5.4 65 | 66 | 68 |
  • Fixed an importing issue.
  • 69 | 70 | ]]> 71 |
    72 | 73 |
    74 | 75 | Version 1.6.1 76 | 77 | 79 |
  • Added paragraph value to import
  • 80 |
  • Added paragraph value to export
  • 81 | 82 | ]]> 83 |
    84 | 85 |
    86 | 87 | Version 1.6.2 88 | 89 | 91 |
  • Added support for underline and strikethrough values
  • 92 | 93 | ]]> 94 |
    95 | 96 |
    97 | 98 | Version 1.6.3 99 | 100 | 102 |
  • Fixed importing issue
  • 103 | 104 | ]]> 105 |
    106 | 107 |
    108 | 109 | Version 1.7.0 110 | 111 | 113 |
  • Updated plugin to work with Sketch 50
  • 114 | 115 | ]]> 116 |
    117 | 118 |
    119 | 120 | Version 1.7.2 121 | 122 | 124 |
  • Fixed ugly bug with empty values
  • 125 | 126 | ]]> 127 |
    128 | 129 |
    130 | 131 | Version 1.8 132 | 133 | 135 |
  • Updated to work with Sketch 52
  • 136 | 137 | ]]> 138 |
    139 | 140 |
    141 | 142 | Version 1.8.1 143 | 144 | 146 |
  • Fixed importing bug
  • 147 | 148 | ]]> 149 |
    150 | 151 |
    152 |
    153 |
    154 | -------------------------------------------------------------------------------- /shared-textstyles.sketchplugin/Contents/Resources/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilshoenson/shared-text-styles/726bf64254121ba28dfb8cf7fa4da03e195e0d2c/shared-textstyles.sketchplugin/Contents/Resources/icon@2x.png -------------------------------------------------------------------------------- /shared-textstyles.sketchplugin/Contents/Sketch/export.cocoascript: -------------------------------------------------------------------------------- 1 | // Import and export text styles 2 | 3 | var doc; 4 | 5 | function initVars(context) { 6 | doc = context.document; 7 | } 8 | 9 | function saveFonts(context,target) { 10 | initVars(context) 11 | var app = NSApp.delegate(); 12 | 13 | var definedTextStyles = []; 14 | var text = doc.documentData().layerTextStyles().objects(); 15 | 16 | for (var i = 0; i < text.count(); i++) { 17 | var style = text.objectAtIndex(i); 18 | var attributes = style.style().textStyle().attributes(); 19 | var textStyle = style 20 | 21 | definedTextStyles.push({ 22 | "attributes": attributes, 23 | "textStyle": style, 24 | "name": style.name() 25 | }); 26 | 27 | } 28 | 29 | if (definedTextStyles.length > 0) { 30 | 31 | var save = NSSavePanel.savePanel(); 32 | save.setNameFieldStringValue("text-styles.json"); 33 | save.setAllowedFileTypes([@"json"]); 34 | save.setAllowsOtherFileTypes(false); 35 | save.setExtensionHidden(false); 36 | 37 | if (save.runModal()) { 38 | 39 | var styles = []; 40 | 41 | for (var i = 0; i < definedTextStyles.length; i++) { 42 | var definedTextStyle = definedTextStyles[i]; 43 | var color = definedTextStyle.attributes.MSAttributedStringColorAttribute; 44 | 45 | if (color != null) { 46 | var red = color.red(); 47 | var green = color.green(); 48 | var blue = color.blue(); 49 | var alpha = color.alpha(); 50 | } 51 | 52 | var name = String(definedTextStyle.name); 53 | var family = String(definedTextStyle.attributes.NSFont.fontDescriptor().objectForKey(NSFontNameAttribute)) 54 | var size = String(definedTextStyle.attributes.NSFont.fontDescriptor().objectForKey(NSFontSizeAttribute)) * 1 55 | 56 | var par = definedTextStyle.attributes.NSParagraphStyle; 57 | 58 | if (par != null) { 59 | var align = par.alignment(); 60 | var lineHeight = par.maximumLineHeight(); 61 | var paragraphSpacing = par.paragraphSpacing(); 62 | } 63 | 64 | var spacing = String(definedTextStyle.attributes.NSKern) * 1; 65 | 66 | var text = definedTextStyle.attributes.MSAttributedStringTextTransformAttribute; 67 | 68 | if (text != null) { 69 | var textTransform = String(definedTextStyle.attributes.MSAttributedStringTextTransformAttribute) * 1; 70 | } else { 71 | var textTransform = 0; 72 | } 73 | 74 | var strike = String(definedTextStyle.attributes.NSStrikethrough) * 1 75 | var underline = String(definedTextStyle.attributes.NSUnderline) * 1 76 | 77 | styles.push({ 78 | name: name, 79 | font: family, 80 | size: size, 81 | color: { 82 | red: red, 83 | green: green, 84 | blue: blue, 85 | alpha: alpha 86 | }, 87 | alignment: align, 88 | spacing: spacing, 89 | lineHeight: lineHeight, 90 | paragraphSpacing: paragraphSpacing, 91 | textTransform: textTransform, 92 | strikethrough: strike, 93 | underline: underline 94 | }); 95 | 96 | }; 97 | 98 | var fileData = { "styles": styles }; 99 | var path = save.URL().path(); 100 | var file = NSString.stringWithString(JSON.stringify(fileData)); 101 | 102 | [file writeToFile:path atomically:true encoding:NSUTF8StringEncoding error:null]; 103 | 104 | [doc showMessage: 'Text style(s) are succesfully saved.'] 105 | } 106 | 107 | } else { NSApp.displayDialog("There are no text styles to export."); } 108 | 109 | } 110 | 111 | function exportFonts(context) { 112 | saveFonts(context, "document"); 113 | } 114 | -------------------------------------------------------------------------------- /shared-textstyles.sketchplugin/Contents/Sketch/import.cocoascript: -------------------------------------------------------------------------------- 1 | // Import and export text styles 2 | 3 | var doc; 4 | var sharedStyles; 5 | 6 | function initVars(context) { 7 | doc = context.document; 8 | sharedStyles = doc.documentData().layerTextStyles() 9 | } 10 | 11 | function loadFonts(context, target) { 12 | initVars(context) 13 | var app = NSApp.delegate(); 14 | var open = NSOpenPanel.openPanel(); 15 | 16 | // Open file picker 17 | open.setAllowedFileTypes(["json"]); 18 | open.setCanChooseDirectories(true); 19 | open.setCanChooseFiles(true); 20 | open.setCanCreateDirectories(true); 21 | open.setTitle("Choose a file"); 22 | open.setPrompt("Choose"); 23 | open.runModal(); 24 | 25 | var path = open.URLs().firstObject().path(); 26 | var errorPtr = MOPointer.alloc().init() 27 | var fileContents = NSString.stringWithContentsOfFile_encoding_error(path, NSUTF8StringEncoding, errorPtr); 28 | var stylesContents = JSON.parse(fileContents.toString()); 29 | var styles = stylesContents.styles; 30 | var fonts = []; 31 | 32 | if (styles.length > 0) { 33 | for (var i = 0; i < styles.length; i++) { 34 | var size = styles[i].size; 35 | var family = styles[i].font; 36 | var name = styles[i].name; 37 | 38 | var red = styles[i].color.red; 39 | var green = styles[i].color.green; 40 | var blue = styles[i].color.blue; 41 | var alpha = styles[i].color.alpha; 42 | 43 | var align = styles[i].alignment || 0; 44 | var spacing = styles[i].spacing || 0; 45 | var paragraphSpacing = styles[i].paragraphSpacing || 0; 46 | var lineHeight = styles[i].lineHeight || 0; 47 | 48 | var textTransform = styles[i].textTransform || 0; 49 | 50 | var strikethrough = styles[i].strikethrough || 0; 51 | var underline = styles[i].underline || 0; 52 | 53 | var rectTextFrame = NSMakeRect(0, 0, 250, 50); 54 | var newText = [[MSTextLayer alloc] initWithFrame:rectTextFrame]; 55 | 56 | fonts.push(MSColor.colorWithRed_green_blue_alpha(red, green, blue, alpha)) 57 | 58 | var color = fonts[i]; 59 | 60 | newText.name = name; 61 | newText.stringValue = name + ' ' + size + 'px'; 62 | newText.fontSize = size; 63 | newText.fontPostscriptName = family; 64 | 65 | if (isNaN(red) != true) { 66 | newText.textColor = color; 67 | } else { 68 | newText.textColor = MSColor.colorWithNSColor(NSColor.colorWithGray(0.0)); 69 | } 70 | 71 | newText.textAlignment = align; 72 | [newText setCharacterSpacing: spacing]; 73 | [newText setLineHeight: lineHeight]; 74 | newText.addAttribute_value("MSAttributedStringTextTransformAttribute", textTransform) 75 | 76 | var paragraphStyle = newText.paragraphStyle(); 77 | paragraphStyle.setParagraphSpacing(paragraphSpacing); 78 | newText.addAttribute_value("NSParagraphStyle", paragraphStyle); 79 | 80 | newText.addAttribute_value("NSStrikethrough", strikethrough); 81 | newText.addAttribute_value("NSUnderline", underline); 82 | 83 | checkForMatchingStyles(context, sharedStyles.objects(), name, newText.style()); 84 | findLayersWithSharedStyleNamed_inContainer(context, newText.name() , newText.style()) 85 | 86 | doc.reloadInspector() 87 | }; 88 | 89 | [doc showMessage: 'Text style(s) are imported.'] 90 | 91 | } else { NSApp.displayDialog("The file you're importing is empty."); } 92 | 93 | } 94 | 95 | function importFonts(context){ 96 | loadFonts(context, "document"); 97 | } 98 | 99 | function checkForMatchingStyles(context, existingTextObjects, newStyleName, newStyle) { 100 | initVars(context) 101 | 102 | if (existingTextObjects.count() != 0) { 103 | for (var i = 0; i < existingTextObjects.count(); i++) { 104 | var existingName = existingTextObjects[i].name(); 105 | var style = existingTextObjects.objectAtIndex(i); 106 | var textStyle; 107 | 108 | if(existingName == newStyleName) { 109 | existingTextObjects[i].updateToMatch(newStyle) 110 | return; 111 | } 112 | } 113 | 114 | var s = MSSharedStyle.alloc().initWithName_style(newStyleName,newStyle); 115 | sharedStyles.addSharedObject(s); 116 | 117 | } else { 118 | var s = MSSharedStyle.alloc().initWithName_style(newStyleName,newStyle); 119 | sharedStyles.addSharedObject(s); 120 | } 121 | } 122 | 123 | var findLayersMatchingPredicate_inContainer_filterByType = function(context, predicate, container, layerType) { 124 | var scope; 125 | initVars(context) 126 | 127 | switch (layerType) { 128 | case MSPage : 129 | scope = doc.pages() 130 | return scope.filteredArrayUsingPredicate(predicate) 131 | break; 132 | 133 | case MSArtboardGroup : 134 | if(typeof container !== 'undefined' && container != nil) { 135 | if (container.className == "MSPage") { 136 | scope = container.artboards() 137 | return scope.filteredArrayUsingPredicate(predicate) 138 | } 139 | } else { 140 | // search all pages 141 | var filteredArray = NSArray.array() 142 | var loopPages = doc.pages().objectEnumerator(), page; 143 | while (page = loopPages.nextObject()) { 144 | scope = page.artboards() 145 | filteredArray = filteredArray.arrayByAddingObjectsFromArray(scope.filteredArrayUsingPredicate(predicate)) 146 | } 147 | return filteredArray 148 | } 149 | break; 150 | 151 | default : 152 | if(typeof container !== 'undefined' && container != nil) { 153 | scope = container.children() 154 | return scope.filteredArrayUsingPredicate(predicate) 155 | } else { 156 | var filteredArray = NSArray.array() 157 | var loopPages = doc.pages().objectEnumerator(), page; 158 | while (page = loopPages.nextObject()) { 159 | scope = page.children() 160 | filteredArray = filteredArray.arrayByAddingObjectsFromArray(scope.filteredArrayUsingPredicate(predicate)) 161 | } 162 | return filteredArray 163 | } 164 | } 165 | return NSArray.array() // Return an empty array if no matches were found 166 | } 167 | 168 | var findLayersWithSharedStyleNamed_inContainer = function(context, styleName, newStyle, container) { 169 | initVars(context) 170 | 171 | // Get sharedObjectID of shared style with specified name 172 | var allStyles = doc.documentData().layerTextStyles().objects() 173 | var styleSearchPredicate = NSPredicate.predicateWithFormat("name == %@", styleName) 174 | var filteredStyles = allStyles.filteredArrayUsingPredicate(styleSearchPredicate) 175 | 176 | var filteredLayers = NSArray.array() 177 | var loopStyles = filteredStyles.objectEnumerator(), style, predicate; 178 | 179 | while (style = loopStyles.nextObject()) { 180 | predicate = NSPredicate.predicateWithFormat("style.sharedObjectID == %@", style.objectID()) 181 | filteredLayers = filteredLayers.arrayByAddingObjectsFromArray(findLayersMatchingPredicate_inContainer_filterByType(context, predicate, container)) 182 | } 183 | 184 | for (var i = 0; i < filteredLayers.length; i++) { 185 | filteredLayers[i].style = newStyle; 186 | } 187 | 188 | return filteredLayers 189 | } 190 | -------------------------------------------------------------------------------- /shared-textstyles.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Shared Text Styles", 3 | "description" : "Import and export text styles.", 4 | "author" : "Nils Hoenson/Yummygum", 5 | "homepage": "https://github.com/nilshoenson/shared-text-styles", 6 | "version" : "1.8.1", 7 | "identifier" : "com.nilshoenson.sketch.shared-text-styles", 8 | "appcast": "https://raw.githubusercontent.com/nilshoenson/shared-text-styles/master/appcast.xml", 9 | "icon": "icon@2x.png", 10 | "compatibleVersion": "52", 11 | "bundleVersion": "1", 12 | "commands" : [ 13 | { 14 | "script" : "import.cocoascript", 15 | "handler" : "importFonts", 16 | "shortcut" : "cmd alt n", 17 | "name" : "Import Text Styles...", 18 | "identifier" : "importFonts" 19 | }, 20 | { 21 | "script" : "export.cocoascript", 22 | "handler" : "exportFonts", 23 | "shortcut" : "cmd alt e", 24 | "name" : "Export Text Styles...", 25 | "identifier" : "exportFonts" 26 | } 27 | ], 28 | "menu": { 29 | "items": [ 30 | "importFonts", 31 | "exportFonts" 32 | ] 33 | } 34 | } 35 | --------------------------------------------------------------------------------