├── .appcast.xml ├── .gitignore ├── README.md ├── assets └── icon.png ├── package.json ├── preview.gif ├── sketch-textbox-fit-content.sketchplugin └── Contents │ ├── Resources │ └── icon.png │ └── Sketch │ ├── fit.js │ └── manifest.json └── src ├── fit.js └── manifest.json /.appcast.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build artifacts 2 | plugin.sketchplugin 3 | 4 | # npm 5 | node_modules 6 | .npm 7 | npm-debug.log 8 | 9 | # mac 10 | .DS_Store 11 | 12 | # WebStorm 13 | .idea 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Textbox fit Content - Sketch Plugin 2 | This plugin sets the height of a selected text layer or all text layers in a selected group to it's content's height. 3 | 4 | ![Plugin preview](https://github.com/juliussohn/sketch-textbox-fit-content/blob/master/preview.gif) 5 | 6 | 7 | ## Usage 8 | Select a text layer or a group containing text layers. 9 | 10 | 11 | **To remove whitespace and linebreaks on the beginning and end of the text:** 12 | 13 | Select `Plugins` > `Textbox fit Content` > `Trim and fit text` or press `⌘`+ `Shift`+`F` 14 | 15 | 16 | **To keep all whitespace and linebreaks on the beginning and end of the text:** 17 | 18 | Select `Plugins` > `Textbox fit Content` > `Fit text` or press `⌘`+`alt`+`Shift`+`F` 19 | 20 | **To automatically make the textbox fit it's content height when the text changes enable the auto-fit mode:** 21 | 22 | Select `Plugins` > `Textbox fit Content` > `Toggle auto-fit text` or press `⌘`+`ctrl`+`Shift`+`F` 23 | 24 | 25 | ## Changelog 26 | 27 | ### 1.4.0 28 | 29 | [Fixed] Run auto fit after the text box has been resized. 30 | 31 | ### 1.3.0 32 | 33 | [New] Auto fit mode: Automatically adjust text box size when it's content changes 34 | 35 | ### 1.2.0 36 | 37 | [Fixed] \> 48.1 compatibilty 38 | 39 | [New] Plugin update Support 40 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliussohn/sketch-textbox-fit-content/48297c99103978b6a7bd6870b892bf75ee7f696f/assets/icon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sketch-textbox-fit-content", 3 | "version": "1.4.0", 4 | "description": "This plugin sets the height of a selected text layer or all text layers in a selected group to it's content's height.", 5 | "engines": { 6 | "sketch": ">=3.0" 7 | }, 8 | "skpm": { 9 | "name": "Text box fit content", 10 | "manifest": "src/manifest.json", 11 | "main": "sketch-textbox-fit-content.sketchplugin", 12 | "assets": [ 13 | "assets/**/*" 14 | ] 15 | }, 16 | "scripts": { 17 | "build": "skpm-build", 18 | "watch": "skpm-build --watch", 19 | "start": "skpm-build --watch --run", 20 | "postinstall": "npm run build && skpm-link" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/juliussohn/sketch-textbox-fit-content.git" 25 | }, 26 | "devDependencies": { 27 | "@skpm/builder": "^0.4.0" 28 | }, 29 | "author": "Julius Sohn " 30 | } 31 | -------------------------------------------------------------------------------- /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliussohn/sketch-textbox-fit-content/48297c99103978b6a7bd6870b892bf75ee7f696f/preview.gif -------------------------------------------------------------------------------- /sketch-textbox-fit-content.sketchplugin/Contents/Resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliussohn/sketch-textbox-fit-content/48297c99103978b6a7bd6870b892bf75ee7f696f/sketch-textbox-fit-content.sketchplugin/Contents/Resources/icon.png -------------------------------------------------------------------------------- /sketch-textbox-fit-content.sketchplugin/Contents/Sketch/fit.js: -------------------------------------------------------------------------------- 1 | var that = this; 2 | function __skpm_run (key, context) { 3 | that.context = context; 4 | 5 | var exports = 6 | /******/ (function(modules) { // webpackBootstrap 7 | /******/ // The module cache 8 | /******/ var installedModules = {}; 9 | /******/ 10 | /******/ // The require function 11 | /******/ function __webpack_require__(moduleId) { 12 | /******/ 13 | /******/ // Check if module is in cache 14 | /******/ if(installedModules[moduleId]) { 15 | /******/ return installedModules[moduleId].exports; 16 | /******/ } 17 | /******/ // Create a new module (and put it into the cache) 18 | /******/ var module = installedModules[moduleId] = { 19 | /******/ i: moduleId, 20 | /******/ l: false, 21 | /******/ exports: {} 22 | /******/ }; 23 | /******/ 24 | /******/ // Execute the module function 25 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 26 | /******/ 27 | /******/ // Flag the module as loaded 28 | /******/ module.l = true; 29 | /******/ 30 | /******/ // Return the exports of the module 31 | /******/ return module.exports; 32 | /******/ } 33 | /******/ 34 | /******/ 35 | /******/ // expose the modules object (__webpack_modules__) 36 | /******/ __webpack_require__.m = modules; 37 | /******/ 38 | /******/ // expose the module cache 39 | /******/ __webpack_require__.c = installedModules; 40 | /******/ 41 | /******/ // define getter function for harmony exports 42 | /******/ __webpack_require__.d = function(exports, name, getter) { 43 | /******/ if(!__webpack_require__.o(exports, name)) { 44 | /******/ Object.defineProperty(exports, name, { 45 | /******/ configurable: false, 46 | /******/ enumerable: true, 47 | /******/ get: getter 48 | /******/ }); 49 | /******/ } 50 | /******/ }; 51 | /******/ 52 | /******/ // getDefaultExport function for compatibility with non-harmony modules 53 | /******/ __webpack_require__.n = function(module) { 54 | /******/ var getter = module && module.__esModule ? 55 | /******/ function getDefault() { return module['default']; } : 56 | /******/ function getModuleExports() { return module; }; 57 | /******/ __webpack_require__.d(getter, 'a', getter); 58 | /******/ return getter; 59 | /******/ }; 60 | /******/ 61 | /******/ // Object.prototype.hasOwnProperty.call 62 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 63 | /******/ 64 | /******/ // __webpack_public_path__ 65 | /******/ __webpack_require__.p = ""; 66 | /******/ 67 | /******/ // Load entry module and return exports 68 | /******/ return __webpack_require__(__webpack_require__.s = 1); 69 | /******/ }) 70 | /************************************************************************/ 71 | /******/ ([ 72 | /* 0 */ 73 | /***/ (function(module, exports) { 74 | 75 | module.exports = require("sketch/dom"); 76 | 77 | /***/ }), 78 | /* 1 */ 79 | /***/ (function(module, exports, __webpack_require__) { 80 | 81 | Object.defineProperty(exports, "__esModule", { 82 | value: true 83 | }); 84 | exports.runWithTrim = runWithTrim; 85 | exports.runWithoutTrim = runWithoutTrim; 86 | exports.onTextChanged = onTextChanged; 87 | exports.onHandlerLostFocus = onHandlerLostFocus; 88 | exports.toggleAutoResizing = toggleAutoResizing; 89 | exports.run = run; 90 | var Group = __webpack_require__(0).Group; 91 | var Rectangle = __webpack_require__(0).Rectangle; 92 | var Text = __webpack_require__(0).Text; 93 | var UI = __webpack_require__(2); 94 | var Settings = __webpack_require__(3); 95 | var Document = __webpack_require__(0).Document; 96 | var document = Document.getSelectedDocument(); 97 | var trim = true; 98 | 99 | function runWithTrim() { 100 | trim = true; 101 | run(); 102 | } 103 | function runWithoutTrim() { 104 | trim = false; 105 | run(); 106 | } 107 | 108 | function onTextChanged() { 109 | var autoResize = Settings.settingForKey('auto-resize'); 110 | if (autoResize) { 111 | runWithoutTrim(); 112 | } 113 | } 114 | function onHandlerLostFocus(e) { 115 | onTextChanged(); 116 | } 117 | 118 | function toggleAutoResizing() { 119 | var autoResize = Settings.settingForKey('auto-resize'); 120 | 121 | if (!autoResize) { 122 | Settings.setSettingForKey('auto-resize', true); 123 | UI.message('✅ Text box auto-fit enabled '); 124 | runWithoutTrim(); 125 | } else { 126 | Settings.setSettingForKey('auto-resize', false); 127 | UI.message('❌ Text box auto-fit disabled'); 128 | } 129 | } 130 | 131 | function run() { 132 | var selectedLayers = document.selectedLayers; 133 | var selectedCount = selectedLayers.length; 134 | 135 | if (selectedCount === 0) { 136 | UI.message('No layers selected.'); 137 | } else { 138 | selectedLayers.forEach(function (layer) { 139 | return checkLayer(layer); 140 | }); 141 | } 142 | } 143 | 144 | function checkLayer(layer) { 145 | if (layer.type === "Text") { 146 | fitLayer(layer); 147 | } else if (layer.type === "Group") { 148 | var layers = layer.layers(); 149 | for (var i = 0; i < layers.count(); i++) { 150 | checkLayer(layers[i]); 151 | } 152 | Group.fromNative(layer).adjustToFit(); 153 | } 154 | } 155 | 156 | function fitLayer(textLayer) { 157 | if (trim) { 158 | var content = textLayer.sketchObject.stringValue(); 159 | textLayer.sketchObject.setStringValue(content.replace(/^\s+|\s+$/g, '').trim()); 160 | } 161 | 162 | var lineCount = textLayer.fragments.length; 163 | var baseHeight = textLayer.fragments[lineCount - 1].rect.y + textLayer.fragments[lineCount - 1].rect.height; 164 | textLayer.sketchObject.frame().height = baseHeight; 165 | } 166 | 167 | /***/ }), 168 | /* 2 */ 169 | /***/ (function(module, exports) { 170 | 171 | module.exports = require("sketch/ui"); 172 | 173 | /***/ }), 174 | /* 3 */ 175 | /***/ (function(module, exports) { 176 | 177 | module.exports = require("sketch/settings"); 178 | 179 | /***/ }) 180 | /******/ ]); 181 | if (key === 'default' && typeof exports === 'function') { 182 | exports(context); 183 | } else { 184 | exports[key](context); 185 | } 186 | } 187 | that['runWithTrim'] = __skpm_run.bind(this, 'runWithTrim'); 188 | that['onRun'] = __skpm_run.bind(this, 'default'); 189 | that['runWithoutTrim'] = __skpm_run.bind(this, 'runWithoutTrim'); 190 | that['toggleAutoResizing'] = __skpm_run.bind(this, 'toggleAutoResizing'); 191 | that['onTextChanged'] = __skpm_run.bind(this, 'onTextChanged'); 192 | that['onHandlerLostFocus'] = __skpm_run.bind(this, 'onHandlerLostFocus') 193 | -------------------------------------------------------------------------------- /sketch-textbox-fit-content.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "compatibleVersion": 3, 3 | "bundleVersion": 1, 4 | "commands": [ 5 | { 6 | "script": "fit.js", 7 | "handler": "runWithTrim", 8 | "shortcut": "command shift f", 9 | "name": "Trim and fit text", 10 | "identifier": "tfc.fitWithTrim" 11 | }, 12 | { 13 | "script": "fit.js", 14 | "handler": "runWithoutTrim", 15 | "shortcut": "command alt shift f", 16 | "name": "Fit text", 17 | "identifier": "tfc.fitWithoutTrim" 18 | }, 19 | { 20 | "script": "fit.js", 21 | "handler": "toggleAutoResizing", 22 | "name": "Toggle auto-fit text", 23 | "shortcut": "command ctrl shift f", 24 | "identifier": "tfc.toggleAutoResizing" 25 | }, 26 | { 27 | "script": "fit.js", 28 | "handlers": { 29 | "actions": { 30 | "TextChanged.finish": "onTextChanged", 31 | "HandlerLostFocus": "onHandlerLostFocus" 32 | } 33 | }, 34 | "name": "Auto resize", 35 | "identifier": "tfc.autoResize" 36 | } 37 | ], 38 | "menu": { 39 | "title": "Textbox fit content", 40 | "items": [ 41 | "tfc.fitWithTrim", 42 | "tfc.fitWithoutTrim", 43 | "-", 44 | "tfc.toggleAutoResizing" 45 | ] 46 | }, 47 | "version": "1.4.0", 48 | "description": "This plugin sets the height of a selected text layer or all text layers in a selected group to it's content's height.", 49 | "name": "Text box fit content", 50 | "disableCocoaScriptPreprocessor": true, 51 | "appcast": "https://raw.githubusercontent.com/juliussohn/sketch-textbox-fit-content/master/.appcast.xml", 52 | "author": "Julius Sohn", 53 | "authorEmail": "juliussohn@icloud.com" 54 | } -------------------------------------------------------------------------------- /src/fit.js: -------------------------------------------------------------------------------- 1 | var Group = require('sketch/dom').Group 2 | var Rectangle = require('sketch/dom').Rectangle 3 | var Text = require('sketch/dom').Text 4 | var UI = require('sketch/ui') 5 | var Settings = require('sketch/settings') 6 | var Document = require('sketch/dom').Document 7 | var document = Document.getSelectedDocument() 8 | var trim = true; 9 | 10 | export function runWithTrim() { 11 | trim = true; 12 | run(); 13 | } 14 | export function runWithoutTrim() { 15 | trim = false; 16 | run(); 17 | } 18 | 19 | export function onTextChanged() { 20 | var autoResize = Settings.settingForKey('auto-resize') 21 | if(autoResize){ 22 | runWithoutTrim() 23 | } 24 | } 25 | export function onHandlerLostFocus(e){ 26 | onTextChanged(); 27 | } 28 | 29 | export function toggleAutoResizing() { 30 | var autoResize = Settings.settingForKey('auto-resize') 31 | 32 | if(!autoResize){ 33 | Settings.setSettingForKey('auto-resize', true); 34 | UI.message('✅ Text box auto-fit enabled '); 35 | runWithoutTrim(); 36 | }else{ 37 | Settings.setSettingForKey('auto-resize', false) 38 | UI.message('❌ Text box auto-fit disabled'); 39 | } 40 | } 41 | 42 | export function run() { 43 | var selectedLayers = document.selectedLayers 44 | var selectedCount = selectedLayers.length; 45 | 46 | if (selectedCount === 0) { 47 | UI.message('No layers selected.') 48 | } else { 49 | selectedLayers.forEach(layer => checkLayer(layer)); 50 | } 51 | } 52 | 53 | function checkLayer(layer) { 54 | if (layer.type === "Text") { 55 | fitLayer(layer) 56 | } else if (layer.type === "Group") { 57 | var layers = layer.layers(); 58 | for (var i = 0; i < layers.count(); i++) { 59 | checkLayer(layers[i]); 60 | } 61 | Group.fromNative(layer).adjustToFit() 62 | 63 | } 64 | } 65 | 66 | function fitLayer(textLayer) { 67 | if (trim) { 68 | var content = textLayer.sketchObject.stringValue(); 69 | textLayer.sketchObject.setStringValue(content.replace(/^\s+|\s+$/g, '').trim()) 70 | } 71 | 72 | var lineCount = textLayer.fragments.length 73 | var baseHeight = textLayer.fragments[lineCount - 1].rect.y + textLayer.fragments[lineCount - 1].rect.height 74 | textLayer.sketchObject.frame().height = baseHeight 75 | 76 | } -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "compatibleVersion": 3, 3 | "bundleVersion": 1, 4 | "commands": [ 5 | { 6 | "script": "fit.js", 7 | "handler": "runWithTrim", 8 | "shortcut": "command shift f", 9 | "name": "Trim and fit text", 10 | "identifier": "tfc.fitWithTrim" 11 | }, 12 | { 13 | "script": "fit.js", 14 | "handler": "runWithoutTrim", 15 | "shortcut": "command alt shift f", 16 | "name": "Fit text", 17 | "identifier": "tfc.fitWithoutTrim" 18 | }, 19 | { 20 | "script": "fit.js", 21 | "handler": "toggleAutoResizing", 22 | "name": "Toggle auto-fit text", 23 | "shortcut": "command ctrl shift f", 24 | "identifier": "tfc.toggleAutoResizing" 25 | }, 26 | { 27 | "script": "fit.js", 28 | "handlers": { 29 | "actions": { 30 | "TextChanged.finish": "onTextChanged", 31 | "HandlerLostFocus":"onHandlerLostFocus" 32 | } 33 | }, 34 | "name": "Auto resize", 35 | "identifier": "tfc.autoResize" 36 | } 37 | ], 38 | "menu": { 39 | "title": "Textbox fit content", 40 | "items": [ 41 | "tfc.fitWithTrim", 42 | "tfc.fitWithoutTrim", 43 | "-", 44 | "tfc.toggleAutoResizing" 45 | ] 46 | } 47 | } --------------------------------------------------------------------------------