├── bower.json ├── src ├── BehaveAsset.php ├── Plugin.php ├── icon.svg └── Field.php ├── LICENSE.md ├── composer.json └── lib └── behave.js ├── behave.min.js ├── behave.min.js.map └── behave.js /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "craftcms-simple-text", 3 | "main": "gulpfile.js", 4 | "authors": ["Brandon Kelly "], 5 | "license": "MIT", 6 | "homepage": "https://github.com/craftcms/simple-text", 7 | "private": true, 8 | "devDependencies": { 9 | "behave.js": "^1.7.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/BehaveAsset.php: -------------------------------------------------------------------------------- 1 | sourcePath = dirname(__DIR__) . '/lib/behave.js'; 19 | 20 | // define the dependencies 21 | $this->depends = [ 22 | CpAsset::class, 23 | ]; 24 | 25 | // define the relative path to CSS/JS files that should be registered with the page 26 | // when this asset bundle is registered 27 | $this->js = [ 28 | 'behave.min.js', 29 | ]; 30 | 31 | parent::init(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Plugin.php: -------------------------------------------------------------------------------- 1 | types[] = Field::class; 37 | } 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Pixel & Tonic, Inc. 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. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "craftcms/simple-text", 3 | "description": "This plugin adds a new “Simple Text” field type to Craft, which provides a textarea that’s optimized for entering documentation.", 4 | "type": "craft-plugin", 5 | "keywords": [ 6 | "html", 7 | "cms", 8 | "craftcms", 9 | "yii2" 10 | ], 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Pixel & Tonic", 15 | "homepage": "https://pixelandtonic.com/" 16 | } 17 | ], 18 | "support": { 19 | "email": "support@craftcms.com", 20 | "issues": "https://github.com/craftcms/simple-text/issues?state=open", 21 | "source": "https://github.com/craftcms/simple-text", 22 | "docs": "https://github.com/craftcms/simple-text/blob/main/README.md", 23 | "rss": "https://github.com/craftcms/simple-text/commits/main.atom" 24 | }, 25 | "minimum-stability": "dev", 26 | "prefer-stable": true, 27 | "require": { 28 | "craftcms/cms": "^4.0.0-RC3|^5.0.0-beta.1" 29 | }, 30 | "require-dev": { 31 | "craftcms/ecs": "dev-main", 32 | "craftcms/phpstan": "dev-main", 33 | "craftcms/rector": "dev-main" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "craft\\simpletext\\": "src/" 38 | } 39 | }, 40 | "scripts": { 41 | "check-cs": "ecs check --ansi", 42 | "fix-cs": "ecs check --ansi --fix", 43 | "phpstan": "phpstan --memory-limit=1G" 44 | }, 45 | "extra": { 46 | "name": "Simple Text", 47 | "handle": "simple-text", 48 | "documentationUrl": "https://github.com/craftcms/simple-text/blob/main/README.md" 49 | }, 50 | "config": { 51 | "platform": { 52 | "php": "8.0.2" 53 | }, 54 | "allow-plugins": { 55 | "yiisoft/yii2-composer": true, 56 | "craftcms/plugin-installer": true 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | simple-text 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Field.php: -------------------------------------------------------------------------------- 1 | Craft::t('simple-text', 'Initial Rows'), 50 | 'id' => 'initialRows', 51 | 'name' => 'initialRows', 52 | 'value' => $this->initialRows, 53 | 'size' => 3, 54 | 'errors' => $this->getErrors('initialRows'), 55 | ]); 56 | } 57 | 58 | /** 59 | * Returns the field's input HTML. 60 | * 61 | * @param mixed $value 62 | * @param ElementInterface|null $element 63 | * @return string 64 | */ 65 | public function getInputHtml(mixed $value, ?\craft\base\ElementInterface $element = null): string 66 | { 67 | $id = $this->getInputId(); 68 | $namespacedId = Craft::$app->getView()->namespaceInputId($id); 69 | 70 | Craft::$app->getView()->registerAssetBundle(BehaveAsset::class); 71 | Craft::$app->getView()->registerJs("new Behave({ textarea: document.getElementById('{$namespacedId}') });"); 72 | 73 | return Cp::textareaFieldHtml([ 74 | 'id' => $id, 75 | 'name' => $this->handle, 76 | 'value' => $value, 77 | 'class' => 'nicetext fullwidth code', 78 | 'rows' => $this->initialRows, 79 | ]); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/behave.js/behave.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Behave.js 3 | * 4 | * Copyright 2013, Jacob Kelley - http://jakiestfu.com/ 5 | * Released under the MIT Licence 6 | * http://opensource.org/licenses/MIT 7 | * 8 | * Github: http://github.com/iamso/Behave.js/ 9 | * Version: 1.7.1 10 | */ 11 | (function(a){"use strict";var b=b||function(){var a={};return{add:function(b,c){if("object"==typeof b){var d;for(d=0;da)return"";if(a%2)return this.repeat(a-1)+this;var b=this.repeat(a/2);return b+b}),"function"!=typeof Array.prototype.filter&&(Array.prototype.filter=function(a){if(null===this)throw new TypeError;var b=Object(this),c=b.length>>>0;if("function"!=typeof a)throw new TypeError;for(var d=[],e=arguments[1],f=0;c>f;f++)if(f in b){var g=b[f];a.call(e,g,f,b)&&d.push(g)}return d});var d,e,f={textarea:null,replaceTab:!0,softTabs:!0,tabSize:4,autoOpen:!0,overwrite:!0,autoStrip:!0,autoIndent:!0,continueList:!0,fence:!1},g={keyMap:[{open:'"',close:'"',canBreak:!1},{open:"'",close:"'",canBreak:!1},{open:"(",close:")",canBreak:!1},{open:"[",close:"]",canBreak:!0},{open:"{",close:"}",canBreak:!0},{open:"<",close:">",canBreak:!1}]},h={_callHook:function(c,d,e){var g=b.get(c);if(e="boolean"==typeof e&&e===!1?!1:!0,g)if(e){var i,j=f.textarea,k=j.value,l=h.cursor.get();for(i=0;i-1?j=k=d:(j=-c.moveStart("character",-d),j+=a.slice(0,j).split(e).length-1,c.compareEndPoints("EndToEnd",g)>-1?k=d:(k=-c.moveEnd("character",-d),k+=a.slice(0,k).split(e).length-1)))),j==k?!1:{start:j,end:k}}},editor:{getLines:function(a){return a.split("\n").length},get:function(){return f.textarea.value.replace(/\r/g,"")},set:function(a){f.textarea.value=a}},fenceRange:function(){if("string"==typeof f.fence){for(var a=h.editor.get(),b=h.cursor.get(),c=0,d=a.indexOf(f.fence),e=0;d>=0&&(e++,!(d+c>b));)c+=d+f.fence.length,a=a.substring(d+f.fence.length),d=a.indexOf(f.fence);return b>c&&d+c>b&&e%2===0?!0:!1}return!0},isEven:function(a,b){return b%2},levelsDeep:function(){var a,b,c=h.cursor.get(),d=h.editor.get(),e=d.substring(0,c),f=0;for(a=0;a=0?k:0},deepExtend:function(a,b){for(var c in b)b[c]&&b[c].constructor&&b[c].constructor===Object?(a[c]=a[c]||{},h.deepExtend(a[c],b[c])):a[c]=b[c];return a},addEvent:function(a,b,c){a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent&&a.attachEvent("on"+b,c)},removeEvent:function(a,b,c){a.addEventListener?a.removeEventListener(b,c,!1):a.attachEvent&&a.detachEvent("on"+b,c)},preventDefaultEvent:function(a){a.preventDefault?a.preventDefault():a.returnValue=!1}},i={tabKey:function(a){if(h.fenceRange()){if(9==a.keyCode){h.preventDefaultEvent(a);var b=!0;h._callHook("tab:before");var c=h.cursor.selection(),e=h.cursor.get(),f=h.editor.get();if(c){for(var g=c.start;g--;)if("\n"==f.charAt(g)){c.start=g+1;break}var i,j=f.substring(c.start,c.end),k=j.split("\n");if(a.shiftKey){for(i=0;i]|\d+\.)\s(\[[\sx]\]\s)?/))&&(k===b[0]?(d-=k.length,j[i]="",c=j.join("\n")):(c=f+b[0]+g,d+=b[0].length),h.editor.set(c),h.cursor.set(d)),h._callHook("enter:after")}},deleteKey:function(a){if(h.fenceRange()&&8==a.keyCode&&!a.altKey){h.preventDefaultEvent(a),h._callHook("delete:before");var b,c=h.cursor.get(),d=h.editor.get(),e=d.substring(0,c),f=d.substring(c),i=e.charAt(e.length-1),j=f.charAt(0);if(h.cursor.selection()===!1){for(b=0;b>> 0; 67 | if (typeof func != "function"){ 68 | throw new TypeError(); 69 | } 70 | var res = [], 71 | thisp = arguments[1]; 72 | for (var i = 0; i < len; i++) { 73 | if (i in t) { 74 | var val = t[i]; 75 | if (func.call(thisp, val, i, t)) { 76 | res.push(val); 77 | } 78 | } 79 | } 80 | return res; 81 | }; 82 | } 83 | 84 | var defaults = { 85 | textarea: null, 86 | replaceTab: true, 87 | softTabs: true, 88 | tabSize: 4, 89 | autoOpen: true, 90 | overwrite: true, 91 | autoStrip: true, 92 | autoIndent: true, 93 | continueList: true, 94 | fence: false 95 | }, 96 | tab, 97 | newLine, 98 | charSettings = { 99 | 100 | keyMap: [ 101 | { open: "\"", close: "\"", canBreak: false }, 102 | { open: "'", close: "'", canBreak: false }, 103 | { open: "(", close: ")", canBreak: false }, 104 | { open: "[", close: "]", canBreak: true }, 105 | { open: "{", close: "}", canBreak: true }, 106 | { open: "<", close: ">", canBreak: false } 107 | ] 108 | 109 | }, 110 | utils = { 111 | 112 | _callHook: function(hookName, event, passData){ 113 | var hooks = BehaveHooks.get(hookName); 114 | passData = typeof passData=="boolean" && passData === false ? false : true; 115 | 116 | if(hooks){ 117 | if(passData){ 118 | var theEditor = defaults.textarea, 119 | textVal = theEditor.value, 120 | caretPos = utils.cursor.get(), 121 | i; 122 | 123 | for(i=0; i -1) { 232 | start = end = len; 233 | } else { 234 | start = -textInputRange.moveStart("character", -len); 235 | start += normalizedValue.slice(0, start).split(newLine).length - 1; 236 | 237 | if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) { 238 | end = len; 239 | } else { 240 | end = -textInputRange.moveEnd("character", -len); 241 | end += normalizedValue.slice(0, end).split(newLine).length - 1; 242 | } 243 | } 244 | } 245 | } 246 | 247 | return start==end ? false : { 248 | start: start, 249 | end: end 250 | }; 251 | } 252 | }, 253 | editor: { 254 | getLines: function(textVal){ 255 | return (textVal).split("\n").length; 256 | }, 257 | get: function(){ 258 | return defaults.textarea.value.replace(/\r/g,''); 259 | }, 260 | set: function(data){ 261 | defaults.textarea.value = data; 262 | } 263 | }, 264 | fenceRange: function(){ 265 | if(typeof defaults.fence == "string"){ 266 | 267 | var data = utils.editor.get(), 268 | pos = utils.cursor.get(), 269 | hacked = 0, 270 | matchedFence = data.indexOf(defaults.fence), 271 | matchCase = 0; 272 | 273 | while(matchedFence>=0){ 274 | matchCase++; 275 | if( pos < (matchedFence+hacked) ){ 276 | break; 277 | } 278 | 279 | hacked += matchedFence+defaults.fence.length; 280 | data = data.substring(matchedFence+defaults.fence.length); 281 | matchedFence = data.indexOf(defaults.fence); 282 | 283 | } 284 | 285 | if( (hacked) < pos && ( (matchedFence+hacked) > pos ) && matchCase%2===0){ 286 | return true; 287 | } 288 | return false; 289 | } else { 290 | return true; 291 | } 292 | }, 293 | isEven: function(_this,i){ 294 | return i%2; 295 | }, 296 | levelsDeep: function(){ 297 | var pos = utils.cursor.get(), 298 | val = utils.editor.get(); 299 | 300 | var left = val.substring(0, pos), 301 | levels = 0, 302 | i, j; 303 | 304 | for(i=0; i=0 ? finalLevels : 0; 331 | }, 332 | deepExtend: function(destination, source) { 333 | for (var property in source) { 334 | if (source[property] && source[property].constructor && 335 | source[property].constructor === Object) { 336 | destination[property] = destination[property] || {}; 337 | utils.deepExtend(destination[property], source[property]); 338 | } else { 339 | destination[property] = source[property]; 340 | } 341 | } 342 | return destination; 343 | }, 344 | addEvent: function addEvent(element, eventName, func) { 345 | if (element.addEventListener){ 346 | element.addEventListener(eventName,func,false); 347 | } else if (element.attachEvent) { 348 | element.attachEvent("on"+eventName, func); 349 | } 350 | }, 351 | removeEvent: function addEvent(element, eventName, func){ 352 | if (element.addEventListener){ 353 | element.removeEventListener(eventName,func,false); 354 | } else if (element.attachEvent) { 355 | element.detachEvent("on"+eventName, func); 356 | } 357 | }, 358 | 359 | preventDefaultEvent: function(e){ 360 | if(e.preventDefault){ 361 | e.preventDefault(); 362 | } else { 363 | e.returnValue = false; 364 | } 365 | } 366 | }, 367 | intercept = { 368 | tabKey: function (e) { 369 | 370 | if(!utils.fenceRange()){ return; } 371 | 372 | if (e.keyCode == 9) { 373 | utils.preventDefaultEvent(e); 374 | 375 | var toReturn = true; 376 | utils._callHook('tab:before'); 377 | 378 | var selection = utils.cursor.selection(), 379 | pos = utils.cursor.get(), 380 | val = utils.editor.get(); 381 | 382 | if(selection){ 383 | 384 | var tempStart = selection.start; 385 | while(tempStart--){ 386 | if(val.charAt(tempStart)=="\n"){ 387 | selection.start = tempStart + 1; 388 | break; 389 | } 390 | } 391 | 392 | var toIndent = val.substring(selection.start, selection.end), 393 | lines = toIndent.split("\n"), 394 | i; 395 | 396 | if(e.shiftKey){ 397 | for(i = 0; i]|\d+\.)\s(\[[\sx]\]\s)?/)) { 498 | if (line === match[0]) { 499 | pos -= line.length; 500 | lines[nr] = ''; 501 | edited = lines.join('\n'); 502 | } 503 | else { 504 | edited = left + match[0] + right; 505 | pos += match[0].length; 506 | } 507 | utils.editor.set(edited); 508 | utils.cursor.set(pos); 509 | } 510 | 511 | utils._callHook('enter:after'); 512 | } 513 | }, 514 | deleteKey: function (e) { 515 | 516 | if(!utils.fenceRange()){ return; } 517 | 518 | if(e.keyCode == 8 && !e.altKey) { 519 | utils.preventDefaultEvent(e); 520 | 521 | utils._callHook('delete:before'); 522 | 523 | var pos = utils.cursor.get(), 524 | val = utils.editor.get(), 525 | left = val.substring(0, pos), 526 | right = val.substring(pos), 527 | leftChar = left.charAt(left.length - 1), 528 | rightChar = right.charAt(0), 529 | i; 530 | 531 | if( utils.cursor.selection() === false ){ 532 | for(i=0; i