├── Export All Text.sketchplugin └── Contents │ ├── Resources │ ├── UIBundle │ │ └── Contents │ │ │ └── Resources │ │ │ └── MyNibUI.nib │ │ │ ├── designable.nib │ │ │ └── keyedobjects.nib │ └── icon.png │ └── Sketch │ ├── LastSettings.plist │ ├── manifest.json │ ├── script.cocoascript │ └── sketch-nibui.js └── README.md /Export All Text.sketchplugin/Contents/Resources/UIBundle/Contents/Resources/MyNibUI.nib/designable.nib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 78 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 149 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 193 | 204 | 215 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | -------------------------------------------------------------------------------- /Export All Text.sketchplugin/Contents/Resources/UIBundle/Contents/Resources/MyNibUI.nib/keyedobjects.nib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Sketch-Export-Text/f30936faa403e3ba0467a99f7bda03579fff03d1/Export All Text.sketchplugin/Contents/Resources/UIBundle/Contents/Resources/MyNibUI.nib/keyedobjects.nib -------------------------------------------------------------------------------- /Export All Text.sketchplugin/Contents/Resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Sketch-Export-Text/f30936faa403e3ba0467a99f7bda03579fff03d1/Export All Text.sketchplugin/Contents/Resources/icon.png -------------------------------------------------------------------------------- /Export All Text.sketchplugin/Contents/Sketch/LastSettings.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | afterArtboardDivider 6 | 7 | afterArtboardDividerIsEnabled 8 | 0.0 9 | beforeArtboardDivider 10 | 11 | beforeArtboardDividerIsEnabled 12 | 0.0 13 | maxLength 14 | 9007199254740991 15 | minLength 16 | 0.0 17 | saveToFile 18 | 1 19 | showArtboardNames 20 | 0.0 21 | skipLayerNames 22 | 0.0 23 | 24 | 25 | -------------------------------------------------------------------------------- /Export All Text.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : "", 3 | "commands" : [ 4 | { 5 | "script" : "script.cocoascript", 6 | "name" : "Export text from Page...", 7 | "handlers" : { 8 | "run" : "onRun" 9 | }, 10 | "identifier" : "com.bohemiancoding.sketch.runscriptidentifier" 11 | } 12 | ], 13 | "menu" : { 14 | "items" : [ 15 | "com.bohemiancoding.sketch.runscriptidentifier" 16 | ], 17 | "title" : "Export text from Page...", 18 | "isRoot" : true 19 | }, 20 | "identifier" : "com.example.sketch.f16cc26a-dbde-49e7-8abd-4d81585247df", 21 | "version" : "1.1", 22 | "description" : "Exports all text from current page", 23 | "authorEmail" : "m@dbv.ae", 24 | "name" : "Export All Text" 25 | } 26 | -------------------------------------------------------------------------------- /Export All Text.sketchplugin/Contents/Sketch/script.cocoascript: -------------------------------------------------------------------------------- 1 | @import 'sketch-nibui.js' 2 | 3 | var onRun = function(context) { 4 | 5 | // General Declarations 6 | var selection = context.selection 7 | var document = context.document 8 | var originalPage = document.currentPage() 9 | 10 | var pathToContentsFolder = context.scriptPath.stringByDeletingLastPathComponent().stringByDeletingLastPathComponent() 11 | var pathToSettingsFile = pathToContentsFolder.stringByAppendingPathComponent("/Sketch/LastSettings.plist") 12 | 13 | /* Alert */ 14 | 15 | // Getting accessory view 16 | var nibUI = new NibUI(context, "UIBundle", "MyNibUI", [ 17 | "useLastSettingsButton", 18 | 19 | "minSymbolsTextField", 20 | "maxSymbolsTextField", 21 | "skipLayersTextField", 22 | 23 | "beforeArtboardDividerButton", 24 | "beforeArtboardDividerTextField", 25 | "showArtboardNamesButton", 26 | "afterArtboardDividerButton", 27 | "afterArtboardDividerTextField", 28 | 29 | "saveToFileButton", 30 | "saveToClipboardButton", 31 | ]) 32 | 33 | // Configuring alert 34 | var alert = NSAlert.alloc().init() 35 | 36 | var iconPath = pathToContentsFolder.stringByAppendingPathComponent("/Resources/Icon.png") 37 | alert.icon = NSImage.new().initWithContentsOfFile(iconPath) 38 | 39 | alert.messageText = 'Export text from "' + originalPage.nodeName() + '" to File' 40 | var alertMessageTextLastWordLocation = alert.messageText().rangeOfString_options(" ", NSBackwardsSearch).location 41 | var alertMessageTextWithoutLastWord = alert.messageText().substringToIndex(alertMessageTextLastWordLocation) 42 | 43 | alert.addButtonWithTitle("Run") 44 | alert.addButtonWithTitle("Cancel") 45 | 46 | alert.accessoryView = nibUI.view 47 | 48 | // Restoring last parameters 49 | if (!NSFileManager.defaultManager().fileExistsAtPath(pathToSettingsFile)) { 50 | nibUI.useLastSettingsButton.enabled = 0 51 | } 52 | 53 | nibUI.attachTargetAndAction(nibUI.useLastSettingsButton, function() { 54 | var lastParameters = NSDictionary.dictionaryWithContentsOfFile(pathToSettingsFile) 55 | 56 | nibUI.minSymbolsTextField.stringValue = lastParameters["minLength"] != 0 ? lastParameters["minLength"] : "" 57 | nibUI.maxSymbolsTextField.stringValue = lastParameters["maxLength"] != Number.MAX_SAFE_INTEGER ? lastParameters["maxLength"] : "" 58 | nibUI.skipLayersTextField.stringValue = lastParameters["skipLayerNames"] != 0 ? lastParameters["skipLayerNames"].componentsJoinedByString(", ") : "" 59 | 60 | 61 | selectUseBeforeArtboardDivider(lastParameters["beforeArtboardDividerIsEnabled"]) 62 | nibUI.beforeArtboardDividerTextField.stringValue = lastParameters["beforeArtboardDividerIsEnabled"] != "" ? lastParameters["beforeArtboardDivider"] : "" 63 | nibUI.showArtboardNamesButton.state = lastParameters["showArtboardNames"] 64 | selectUseAfterArtboardDivider(lastParameters["afterArtboardDividerIsEnabled"]) 65 | nibUI.afterArtboardDividerTextField.stringValue = lastParameters["afterArtboardDividerIsEnabled"] != "" ? lastParameters["afterArtboardDivider"] : "" 66 | 67 | lastParameters["saveToFile"] != 0 ? selectSaveToFile() : selectSaveToClipboard() 68 | }); 69 | 70 | // Divider checkboxes actions 71 | nibUI.attachTargetAndAction(nibUI.beforeArtboardDividerButton, function() { 72 | selectUseBeforeArtboardDivider(nibUI.beforeArtboardDividerButton.state()) 73 | }); 74 | function selectUseBeforeArtboardDivider(inputBool) { 75 | nibUI.beforeArtboardDividerButton.state = inputBool 76 | nibUI.beforeArtboardDividerButton.title = inputBool ? "" : "No Divider" 77 | nibUI.beforeArtboardDividerTextField.hidden = !inputBool 78 | } 79 | 80 | nibUI.attachTargetAndAction(nibUI.afterArtboardDividerButton, function() { 81 | selectUseAfterArtboardDivider(nibUI.afterArtboardDividerButton.state()) 82 | }); 83 | function selectUseAfterArtboardDivider(inputBool) { 84 | nibUI.afterArtboardDividerButton.state = inputBool 85 | nibUI.afterArtboardDividerButton.title = inputBool ? "" : "No Divider" 86 | nibUI.afterArtboardDividerTextField.hidden = !inputBool 87 | } 88 | 89 | // Radio buttons actions 90 | nibUI.attachTargetAndAction(nibUI.saveToFileButton, function() { 91 | selectSaveToFile() 92 | }); 93 | function selectSaveToFile() { 94 | alert.messageText = alertMessageTextWithoutLastWord + " File" 95 | nibUI.saveToFileButton.state = 1 96 | nibUI.saveToClipboardButton.state = 0 97 | } 98 | 99 | nibUI.attachTargetAndAction(nibUI.saveToClipboardButton, function() { 100 | selectSaveToClipboard() 101 | }); 102 | function selectSaveToClipboard() { 103 | alert.messageText = alertMessageTextWithoutLastWord + " Clipboard" 104 | nibUI.saveToFileButton.state = 0 105 | nibUI.saveToClipboardButton.state = 1 106 | } 107 | 108 | // Launching alert 109 | var result = alert.runModal() 110 | 111 | if (result == NSAlertFirstButtonReturn) { 112 | var parameters = NSDictionary.dictionaryWithDictionary({ 113 | "minLength" : nibUI.minSymbolsTextField.intValue() || 0, 114 | "maxLength" : nibUI.maxSymbolsTextField.intValue() || Number.MAX_SAFE_INTEGER, 115 | "skipLayerNames" : nibUI.skipLayersTextField.stringValue() != "" ? nibUI.skipLayersTextField.stringValue().componentsSeparatedByString(", ") : 0, 116 | 117 | "beforeArtboardDividerIsEnabled" : nibUI.beforeArtboardDividerButton.state(), 118 | "beforeArtboardDivider" : nibUI.beforeArtboardDividerTextField.stringValue() != "" ? nibUI.beforeArtboardDividerTextField.stringValue() : "", 119 | "showArtboardNames" : nibUI.showArtboardNamesButton.state(), 120 | "afterArtboardDividerIsEnabled" : nibUI.afterArtboardDividerButton.state(), 121 | "afterArtboardDivider" : nibUI.afterArtboardDividerTextField.stringValue() != "" ? nibUI.afterArtboardDividerTextField.stringValue() : "", 122 | 123 | "saveToFile" : nibUI.saveToFileButton.state(), 124 | }) 125 | 126 | // Saving settings to file 127 | parameters.writeToFile_atomically(pathToSettingsFile, false) 128 | 129 | mainFunction(parameters) 130 | } 131 | 132 | /* Main Function */ 133 | 134 | function mainFunction(parameters) { 135 | 136 | // Creating Temp Page 137 | var tempPage = originalPage.copy() 138 | tempPage.name = "Temp Page" 139 | document.documentData().addPage(tempPage) 140 | 141 | // Getting rid of symbols 142 | var exportedInstanceLoopDict = NSMutableDictionary.dictionary() 143 | 144 | var pageChildrenLoop = tempPage.children().objectEnumerator() 145 | while (pageLayer = pageChildrenLoop.nextObject()) { 146 | findAndDetachFromSymbol(pageLayer) 147 | } 148 | 149 | function findAndDetachFromSymbol(layer) { 150 | if (layer.isMemberOfClass(MSSymbolInstance)) { 151 | 152 | var layerName = layer.nodeName() 153 | layer = layer.detachStylesAndReplaceWithGroupRecursively(false) 154 | 155 | if (layer) { 156 | if (layer.nodeName()) { layer.nodeName = layerName } 157 | 158 | exportedInstanceLoopDict[layer.objectID()] = layer.children().objectEnumerator() 159 | while (innerLayer = exportedInstanceLoopDict[layer.objectID()].nextObject()) { 160 | findAndDetachFromSymbol(innerLayer) 161 | } 162 | } 163 | } 164 | } 165 | 166 | // Finding text layers 167 | var resultDict = NSMutableDictionary.dictionary() 168 | 169 | iterateThroughInnerLayersAndValidate(tempPage) 170 | 171 | function iterateThroughInnerLayersAndValidate(layerGroup) { 172 | if ( !isLayerNameValid(layerGroup.name()) ) { return } 173 | 174 | var groupLayersLoop = layerGroup.layers().objectEnumerator() 175 | while (groupChildLayer = groupLayersLoop.nextObject()) { 176 | if ( groupChildLayer.isMemberOfClass(MSArtboardGroup) || groupChildLayer.isMemberOfClass(MSLayerGroup) || groupChildLayer.isMemberOfClass(MSSymbolMaster) ) { 177 | iterateThroughInnerLayersAndValidate(groupChildLayer) 178 | } else { 179 | var artboardName = groupChildLayer.parentArtboard() ? groupChildLayer.parentArtboard().nodeName() : "No Artboard" 180 | itentifyAndAddResult(groupChildLayer, artboardName) 181 | } 182 | } 183 | } 184 | 185 | // Validating results and making dictionary 186 | function itentifyAndAddResult(layer, key) { 187 | if (layer.isMemberOfClass(MSTextLayer)) { 188 | if ( !isLayerNameValid(layer.nodeName()) || !isTextValid(layer)) { return } 189 | var preparedString = prepareString(layer.attributedString().string()) 190 | addResult(preparedString, key) 191 | } 192 | 193 | function isTextValid(layer) { 194 | var string = layer.attributedString().string() 195 | if (string.length() < parameters["minLength"] || string.length() > parameters["maxLength"]) { return false } /* Settings: Length */ 196 | return true 197 | } 198 | 199 | function prepareString(string) { 200 | return string.stringByReplacingOccurrencesOfString_withString("\n"," ") 201 | } 202 | 203 | function addResult(string, key) { 204 | if (resultDict.objectForKey(key)) { 205 | resultDict[key].addObject(string) 206 | } else { 207 | resultDict[key] = NSMutableArray.array() 208 | resultDict[key].addObject(string) 209 | } 210 | } 211 | } 212 | 213 | // Settings: Skip Layers 214 | function isLayerNameValid(layerName) { 215 | if (parameters["skipLayerNames"] != 0) { 216 | var skipLayerNamesLoop = parameters["skipLayerNames"].objectEnumerator() 217 | while (skipLayerName = skipLayerNamesLoop.nextObject()) { 218 | if (skipLayerName.isEqualToString(layerName)) { return false } 219 | } 220 | } 221 | return true 222 | } 223 | 224 | document.documentData().removePage(tempPage) 225 | 226 | // Preparing result string 227 | var resultString = "" 228 | 229 | var resultKeysLoop = resultDict.allKeys().objectEnumerator() 230 | while (resultKey = resultKeysLoop.nextObject()) { 231 | 232 | if (parameters["beforeArtboardDividerIsEnabled"] != 0) { resultString += "\n" + parameters["beforeArtboardDivider"] } /* Settings: Before Divider */ 233 | if (parameters["showArtboardNames"] != 0) { resultString += "\n" + resultKey } /* Settings: Artboard Names */ 234 | if (parameters["afterArtboardDividerIsEnabled"] != 0) { resultString += "\n" + parameters["afterArtboardDivider"] } /* Settings: After Divider */ 235 | 236 | var stringsLoop = resultDict[resultKey].objectEnumerator() 237 | while (string = stringsLoop.nextObject()) { 238 | resultString += "\n" + string 239 | } 240 | } 241 | resultString = NSString.stringWithString(resultString) 242 | resultString = resultString.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) 243 | 244 | // Final actions 245 | parameters["saveToFile"] != 0 ? saveToFile(resultString) : saveToClipboard(resultString) /* Settings: Save to File or Clipboard */ 246 | 247 | function saveToFile(string) { 248 | 249 | // Configuring save panel 250 | var savePanel = NSSavePanel.savePanel() 251 | savePanel.allowedFileTypes = ["txt"] 252 | savePanel.nameFieldStringValue = originalPage.nodeName() + " Text" 253 | 254 | // Launching alert 255 | var result = savePanel.runModal() 256 | if (result == NSFileHandlingPanelOKButton) { 257 | string.writeToFile_atomically_encoding_error(savePanel.URL().path(), 258 | true, NSUTF8StringEncoding, null) 259 | } 260 | } 261 | 262 | function saveToClipboard(string) { 263 | var pasteBoard = NSPasteboard.generalPasteboard() 264 | pasteBoard.clearContents() 265 | pasteBoard.setString_forType(string, NSStringPboardType) 266 | document.showMessage("Your list has been copied to clipboard") 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /Export All Text.sketchplugin/Contents/Sketch/sketch-nibui.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | function NibUI(context, bundleResourceName, nibName, bindViewNames) { 18 | bindViewNames = bindViewNames || []; 19 | 20 | var bundlePath = context.plugin.urlForResourceNamed(bundleResourceName).path(); 21 | this._bundle = NSBundle.bundleWithPath(bundlePath); 22 | 23 | var superclass = NSClassFromString('NSObject'); 24 | 25 | // create a class name that doesn't exist yet. note that we can't reuse the same 26 | // definition lest Sketch will throw an MOJavaScriptException when binding the UI, 27 | // probably due to JavaScript context / plugin lifecycle incompatibility 28 | 29 | var tempClassName; 30 | while (true) { 31 | tempClassName = 'NibOwner' + _randomId(); 32 | if (NSClassFromString(tempClassName) == null) { 33 | break; 34 | } 35 | } 36 | 37 | var me = this; 38 | 39 | // register the temporary class and set up instance methods that will be called for 40 | // each bound view 41 | 42 | this._cls = MOClassDescription.allocateDescriptionForClassWithName_superclass_(tempClassName, superclass); 43 | 44 | bindViewNames.forEach(function(bindViewName) { 45 | var setterName = 'set' + bindViewName.substring(0, 1).toUpperCase() + bindViewName.substring(1); 46 | me._cls.addInstanceMethodWithSelector_function_( 47 | NSSelectorFromString(setterName + ':'), 48 | function(arg) { 49 | me[bindViewName] = arg; 50 | }); 51 | }); 52 | 53 | this._cls.registerClass(); 54 | this._nibOwner = NSClassFromString(tempClassName).alloc().init(); 55 | 56 | var tloPointer = MOPointer.alloc().initWithValue(null); 57 | 58 | if (this._bundle.loadNibNamed_owner_topLevelObjects_(nibName, this._nibOwner, tloPointer)) { 59 | var topLevelObjects = tloPointer.value(); 60 | for (var i = 0; i < topLevelObjects.count(); i++) { 61 | var obj = topLevelObjects.objectAtIndex(i); 62 | if (obj.className().endsWith('View')) { 63 | this.view = obj; 64 | break; 65 | } 66 | } 67 | } else { 68 | throw new Error('Could not load nib'); 69 | } 70 | } 71 | 72 | function _randomId() { 73 | return (1000000 * Math.random()).toFixed(0); 74 | } 75 | 76 | /** 77 | * Helper function for making click handlers (for use in NSButton.setAction). 78 | */ 79 | NibUI.prototype.attachTargetAndAction = function(view, fn) { 80 | if (!this._clickActionNames) { 81 | this._clickActionNames = {}; 82 | } 83 | 84 | var clickActionName; 85 | while (true) { 86 | clickActionName = 'zzzTempClickAction' + _randomId(); 87 | if (!(clickActionName in this._clickActionNames)) { 88 | break; 89 | } 90 | } 91 | 92 | this._clickActionNames[clickActionName] = true; 93 | 94 | var selector = NSSelectorFromString(clickActionName + ':'); 95 | this._cls.addInstanceMethodWithSelector_function_( 96 | selector, 97 | function() { 98 | fn(); 99 | }); 100 | 101 | view.setTarget(this._nibOwner); 102 | view.setAction(selector); 103 | }; 104 | 105 | /** 106 | * Release all resources. 107 | */ 108 | NibUI.prototype.destroy = function() { 109 | this._bundle.unload(); 110 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Export Text Sketch Plugin 2 | Export Text plugin should help you to save all text data on current page in customizable format and filter the excess. 3 | 4 | ## Setting up 5 | 6 | 7 | * **Use Last Settings** — restoring settings you used previously 8 | * **Text Length** — excluding layer from output if it's text length is more or less than provided values 9 | * **Skip Layers** — skips any layer, artboard or symbol with given name. Enter any names you wanted to skip separated by comma and space e.g. `Rectangle 1, Screen 2, Symbol 3`.¹ 10 | * **Before Artboard Divider** — adding a divider before next artboard. Empty line by default. You can add more lines by using `⌥ + return` 11 | * **Show Artboard Name** — adding an artboard name before its contents 12 | * **After Artboard Divider** — adding a divider between artboard name and its contents. Empty line by default. You can add more lines by using `⌥ + return` 13 | * **Save to:** — choose how you'd like to save an output 14 | 15 | *1. Please keep in mind that if you used symbol with skip-name as override or just added it as an instance to another symbol's master then contents of this instance or override (not the whole symbol) also will not be added to output. It's really confusing so please double check your symbols data if you want to use this function.* 16 | 17 | ## Example Output 18 | ![](http://i.dbv.ae/iEkn/Screen%20Shot%202016-11-25%20at%2020.33.10.png) 19 | 20 | ## Feedback 21 | Your feedback is always appreciated. You can [Create an Issue](https://github.com/exevil/Sketch-Export-Text/issues/new) to report errors and feature requests or drop me a line directly to [m@dbv.ae](mailto:m@dbv.ae?Subject=Sketch%20Export%20Text%20Feedback) 22 | 23 | ## Donation 24 | Even a small donation from your side is important. This is really motivating to see that people want to pay for your work. 25 | 26 | [![](https://www.paypalobjects.com/en_GB/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=evil%2emrfix%40gmail%2ecom&lc=GB&item_name=Sketch%20Plugin%20Donation&item_number=sketch%2dplugin¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHosted) 27 | --------------------------------------------------------------------------------