├── 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 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
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 | 
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.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 |
--------------------------------------------------------------------------------