├── assets ├── trimline-word.gif └── trimline-character.gif ├── README.md └── sketch-truncate-textlines.sketchplugin └── Contents └── Sketch ├── manifest.json └── script.js /assets/trimline-word.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamuso/sketch-truncate-textlines/HEAD/assets/trimline-word.gif -------------------------------------------------------------------------------- /assets/trimline-character.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamuso/sketch-truncate-textlines/HEAD/assets/trimline-character.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sketch-truncate-textlines 2 | 3 | **Shortcut:** ctrl alt cmd t 4 | 5 | Cut the number of lines of a textbox, adding an ellipsis if needed. It works using words or characters. Most of the ideas are taken from the [sketch data populator plugin](https://github.com/preciousforever/sketch-data-populator/). 6 | 7 | ![Trim](assets/trimline-word.gif) 8 | 9 | ![Trim](assets/trimline-character.gif) -------------------------------------------------------------------------------- /sketch-truncate-textlines.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "🙆 Yammer Sketch Tools", 3 | "description": "Our design team swiss knife.", 4 | "author": "Manuel Muñoz Solera", 5 | "authorEmail": "mamuso@mamuso.net", 6 | "version": "0.0.1", 7 | "identifier": "io.mamuso.sketch.text-truncate-text-lines", 8 | "commands": [ 9 | { 10 | "name": "✂️ Truncate text lines", 11 | "identifier": "text-truncate-text-lines", 12 | "shortcut": "ctrl alt cmd t", 13 | "script": "script.js" 14 | } 15 | ], 16 | "menu": { 17 | "isRoot": true, 18 | "items": [ 19 | "text-truncate-text-lines" 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /sketch-truncate-textlines.sketchplugin/Contents/Sketch/script.js: -------------------------------------------------------------------------------- 1 | var onRun = function(context) { 2 | var doc = context.document; 3 | var selection = context.selection; 4 | 5 | // Ask for some info 6 | var options = ["Truncate words", "Truncate characters"]; 7 | var choices = createDialog("How many lines do you need?", options, 0); 8 | var lines = choices[1]; 9 | var go = choices[0] == 1000 ? true : false; 10 | var truncateMode = choices[2]; 11 | 12 | if (lines && go) { 13 | selection.forEach(function(layer) { 14 | if (isLayerText(layer)) { 15 | var text = String(layer.stringValue()); 16 | var ellipsis = text; 17 | var popped; 18 | 19 | // Creates a duplicated text field to use it as a playground 20 | var layerCopy = layer.duplicate(); 21 | layerCopy.setStringValue("-"); 22 | refreshTextLayer(layerCopy); 23 | 24 | // Defines the height of the line 25 | var lineHeight = layerCopy.frame().height(); 26 | 27 | // Get ready to iterate 28 | layerCopy.setStringValue(text); 29 | refreshTextLayer(layerCopy); 30 | actualHeight = layerCopy.frame().height(); 31 | 32 | while (actualHeight > lineHeight * lines) { 33 | // Shrinking the text 34 | if (truncateMode > 0) { 35 | text = text.slice(0, -1); 36 | } else { 37 | text = text.split(" "); 38 | popped = text.pop(); 39 | text = text.join(" "); 40 | popped = popped.split("\n"); 41 | if (popped.length > 1) { 42 | text = text + " " + popped[0]; 43 | } 44 | } 45 | 46 | if (popped && popped.length > 1) { 47 | ellipsis = text; 48 | } else { 49 | // Remove unwanted characters 50 | if (text.slice(-1).match(/[\,\.\;\:\-\n\r]/)) { 51 | text = text.slice(0, -1); 52 | } 53 | ellipsis = text + "…"; 54 | } 55 | 56 | //set trimmed text and re-evaluate height 57 | layerCopy.setStringValue(ellipsis); 58 | refreshTextLayer(layerCopy); 59 | actualHeight = layerCopy.frame().height(); 60 | } 61 | 62 | if (ellipsis.length > 1) { 63 | layer.setStringValue(ellipsis); 64 | } 65 | 66 | layerCopy.removeFromParent(); 67 | } 68 | }); 69 | } 70 | 71 | // Checks if the layer is a text layer. 72 | function isLayerText(layer) { 73 | return layer.isKindOfClass(MSTextLayer.class()); 74 | } 75 | 76 | // Refreshes text layer boundaries after setting text. 77 | function refreshTextLayer(layer) { 78 | var width = layer.frame().width(); 79 | layer.textBehaviour = 0; 80 | layer.adjustFrameToFit(); 81 | layer.textBehaviour = 1; 82 | layer.frame().width = width; 83 | layer.adjustFrameToFit(); 84 | if (MSApplicationMetadata.metadata().appVersion > 45) { 85 | layer.select_byExtendingSelection(true, false); 86 | } else { 87 | layer.select_byExpandingSelection(true, false); 88 | } 89 | layer.setIsEditingText(true); 90 | layer.setIsEditingText(false); 91 | if (MSApplicationMetadata.metadata().appVersion > 45) { 92 | layer.select_byExtendingSelection(false, false); 93 | } else { 94 | layer.select_byExpandingSelection(false, false); 95 | } 96 | } 97 | 98 | function createDialog(msg, items, selectedItemIndex) { 99 | selectedItemIndex = selectedItemIndex || 0; 100 | 101 | var accessoryInput = NSView.alloc().initWithFrame( 102 | NSMakeRect(0, 0, 300, 25) 103 | ); 104 | var input = NSTextField.alloc().initWithFrame(NSMakeRect(0, 0, 300, 25)); 105 | input.editable = true; 106 | input.stringValue = "2"; 107 | accessoryInput.addSubview(input); 108 | 109 | var accessoryList = NSComboBox.alloc().initWithFrame( 110 | NSMakeRect(0, 0, 160, 25) 111 | ); 112 | accessoryList.addItemsWithObjectValues(items); 113 | accessoryList.selectItemAtIndex(selectedItemIndex); 114 | 115 | var alert = COSAlertWindow.alloc().init(); 116 | alert.setMessageText(msg); 117 | alert.addButtonWithTitle("OK"); 118 | alert.addButtonWithTitle("Cancel"); 119 | alert.addAccessoryView(accessoryInput); 120 | alert.addAccessoryView(accessoryList); 121 | 122 | var responseCode = alert.runModal(); 123 | 124 | return [ 125 | responseCode, 126 | input.stringValue(), 127 | accessoryList.indexOfSelectedItem() 128 | ]; 129 | } 130 | }; 131 | --------------------------------------------------------------------------------