@webcoder49/code-input 1.5.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/code-input.min.js CHANGED
@@ -1 +1 @@
1
- var codeInput={observedAttributes:["value","name","placeholder","lang","template"],usedTemplates:{},defaultTemplate:void 0,templateQueue:{},plugins:{},Plugin:class{constructor(){console.log("code-input: plugin: Created plugin!"),codeInput.observedAttributes=codeInput.observedAttributes.concat(self.observedAttributes)}beforeHighlight(){}afterHighlight(){}beforeElementsAdded(){}afterElementsAdded(){}attributeChanged(){}observedAttributes=[]},CodeInput:class extends HTMLElement{constructor(){super()}bound_callbacks={};plugin_evt(a,b){for(let c in this.template.plugins){let d=this.template.plugins[c];a in d&&(b===void 0?d[a](this):d[a](this,...b))}}update(a){if(!this.ignoreValueUpdate){this.ignoreValueUpdate=!0,this.value=a,this.ignoreValueUpdate=!1,this.querySelector("textarea").value!=a&&(this.querySelector("textarea").value=a);let b=this.querySelector("pre code");"\n"==a[a.length-1]&&(a+=" "),b.innerHTML=this.escape_html(a),this.plugin_evt("beforeHighlight"),this.template.includeCodeInputInHighlightFunc?this.template.highlight(b,this):this.template.highlight(b),this.plugin_evt("afterHighlight")}}sync_scroll(){let a=this.querySelector("textarea"),b=this.template.preElementStyled?this.querySelector("pre"):this.querySelector("pre code");b.scrollTop=a.scrollTop,b.scrollLeft=a.scrollLeft}escape_html(a){return a.replace(/&/g,"&amp;").replace(/</g,"&lt;")}get_template(){let a;return a=null==this.getAttribute("template")?codeInput.defaultTemplate:this.getAttribute("template"),a in codeInput.usedTemplates?codeInput.usedTemplates[a]:(a in codeInput.templateQueue||(codeInput.templateQueue[a]=[]),void codeInput.templateQueue[a].push(this))}setup(){this.classList.add("code-input_registered"),this.template.preElementStyled&&this.classList.add("code-input_pre-element-styled"),this.plugin_evt("beforeElementsAdded");let a=this.getAttribute("lang"),b=this.getAttribute("placeholder")||this.getAttribute("lang")||"",c=this.value||this.innerHTML||"";this.innerHTML="";let d=document.createElement("textarea");d.placeholder=b,d.value=c,d.setAttribute("spellcheck","false"),this.getAttribute("name")&&d.setAttribute("name",this.getAttribute("name")),d.addEventListener("input",()=>{d.parentElement.update(d.value),d.parentElement.sync_scroll()}),d.addEventListener("scroll",()=>d.parentElement.sync_scroll()),this.append(d);let e=document.createElement("code"),f=document.createElement("pre");f.setAttribute("aria-hidden","true"),f.append(e),this.append(f),this.template.isCode&&a!=null&&""!=a&&e.classList.add("language-"+a),this.plugin_evt("afterElementsAdded"),this.update(c,this)}connectedCallback(){this.template=this.get_template(),this.template!=null&&this.setup()}static get observedAttributes(){return codeInput.observedAttributes}attributeChangedCallback(a,b,c){if(this.isConnected)switch(this.plugin_evt("attributeChanged",[a,b,c]),a){case"value":this.update(c);break;case"name":null!==this.querySelector("textarea")&&this.querySelector("textarea").setAttribute("name",c);break;case"placeholder":this.querySelector("textarea").placeholder=c;break;case"template":this.template=codeInput.usedTemplates[c||codeInput.defaultTemplate],this.template.preElementStyled?this.classList.add("code-input_pre-element-styled"):this.classList.remove("code-input_pre-element-styled"),this.update(this.value);break;case"lang":let a=this.querySelector("pre code"),d=this.querySelector("textarea");if(null!=c&&(c=c.toLowerCase(),a.classList.contains(`language-${c}`)))break;b=b.toLowerCase(),console.log("code-input: Language: REMOVE","language-"+b),a.classList.remove("language-"+b),a.parentElement.classList.remove("language-"+b),a.classList.remove("language-none"),a.parentElement.classList.remove("language-none"),null!=c&&""!=c&&(a.classList.add("language-"+c),console.log("code-input: Language:ADD","language-"+c)),d.placeholder==b&&(d.placeholder=c),this.update(this.value)}}addEventListener(a,b,c=null){let d=b.bind(this);this.bound_callbacks[b]=d,"change"==a?null===c?this.querySelector("textarea").addEventListener("change",d):this.querySelector("textarea").addEventListener("change",d,c):"selectionchange"==a&&(null===c?this.querySelector("textarea").addEventListener("selectionchange",d):this.querySelector("textarea").addEventListener("selectionchange",d,c))}removeEventListener(a,b,c=null){let d=this.bound_callbacks[b];"change"==a?null===c?this.querySelector("textarea").removeEventListener("change",d):this.querySelector("textarea").removeEventListener("change",d,c):"selectionchange"==a&&(null===c?this.querySelector("textarea").removeEventListener("selectionchange",d):this.querySelector("textarea").removeEventListener("selectionchange",d,c))}get value(){return this.getAttribute("value")}set value(a){return this.setAttribute("value",a)}get placeholder(){return this.getAttribute("placeholder")}set placeholder(a){return this.setAttribute("placeholder",a)}pluginData={}},registerTemplate:function(a,b){if(codeInput.usedTemplates[a]=b,a in codeInput.templateQueue){for(let c in codeInput.templateQueue[a])elem=codeInput.templateQueue[a][c],elem.template=b,elem.setup();console.log(`code-input: template: Added existing elements with template ${a}`)}if(null==codeInput.defaultTemplate){if(codeInput.defaultTemplate=a,void 0 in codeInput.templateQueue)for(let a in codeInput.templateQueue[void 0])elem=codeInput.templateQueue[void 0][a],elem.template=b,elem.setup();console.log(`code-input: template: Set template ${a} as default`)}console.log(`code-input: template: Created template ${a}`)},templates:{custom(a=function(){},b=!0,c=!0,d=!1,e=[]){return{highlight:a,includeCodeInputInHighlightFunc:d,preElementStyled:b,isCode:c,plugins:e}},prism(a,b=[]){return{includeCodeInputInHighlightFunc:!1,highlight:a.highlightElement,preElementStyled:!0,isCode:!0,plugins:b}},hljs(a,b=[]){return{includeCodeInputInHighlightFunc:!1,highlight:a.highlightElement,preElementStyled:!1,isCode:!0,plugins:b}},characterLimit(){return{highlight:function(a,b,c=[]){let d=+b.getAttribute("data-character-limit"),e=b.escape_html(b.value.slice(0,d)),f=b.escape_html(b.value.slice(d));a.innerHTML=`${e}<mark class="overflow">${f}</mark>`,0<f.length&&(a.innerHTML+=` <mark class="overflow-msg">${b.getAttribute("data-overflow-msg")||"(Character limit reached)"}</mark>`)},includeCodeInputInHighlightFunc:!0,preElementStyled:!0,isCode:!1,plugins:plugins}},rainbowText(a=["red","orangered","orange","goldenrod","gold","green","darkgreen","navy","blue","magenta"],b="",c=[]){return{highlight:function(a,b){let c=[],d=b.value.split(b.template.delimiter);for(let e=0;e<d.length;e++)c.push(`<span style="color: ${b.template.rainbow_colors[e%b.template.rainbow_colors.length]}">${b.escape_html(d[e])}</span>`);a.innerHTML=c.join(b.template.delimiter)},includeCodeInputInHighlightFunc:!0,preElementStyled:!0,isCode:!1,rainbow_colors:a,delimiter:b,plugins:c}}}};customElements.define("code-input",codeInput.CodeInput);
1
+ var codeInput={observedAttributes:["value","placeholder","lang","template"],textareaSyncAttributes:["value","name","required","minlength","maxlength","min","max","type","pattern"],textareaSyncEvents:["change","selectionchange","invalid","input"],usedTemplates:{},defaultTemplate:void 0,templateNotYetRegisteredQueue:{},registerTemplate:function(a,b){if(!("string"==typeof a||a instanceof String))throw TypeError(`Template for "${a}" must be a string.`);if(!("function"==typeof b.highlight||b.highlight instanceof Function))throw TypeError(`Template for "${a}" invalid, because the highlight function provided is not a function; it is "${b.highlight}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);if(!("boolean"==typeof b.includeCodeInputInHighlightFunc||b.includeCodeInputInHighlightFunc instanceof Boolean))throw TypeError(`Template for "${a}" invalid, because the includeCodeInputInHighlightFunc value provided is not a true or false; it is "${b.includeCodeInputInHighlightFunc}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);if(!("boolean"==typeof b.preElementStyled||b.preElementStyled instanceof Boolean))throw TypeError(`Template for "${a}" invalid, because the preElementStyled value provided is not a true or false; it is "${b.preElementStyled}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);if(!("boolean"==typeof b.isCode||b.isCode instanceof Boolean))throw TypeError(`Template for "${a}" invalid, because the isCode value provided is not a true or false; it is "${b.isCode}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);if(!Array.isArray(b.plugins))throw TypeError(`Template for "${a}" invalid, because the plugin array provided is not an array; it is "${b.plugins}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);if(b.plugins.forEach((c,d)=>{if(!(c instanceof codeInput.Plugin))throw TypeError(`Template for "${a}" invalid, because the plugin provided at index ${d} is not valid; it is "${b.plugins[d]}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`)}),codeInput.usedTemplates[a]=b,a in codeInput.templateNotYetRegisteredQueue){for(let c in codeInput.templateNotYetRegisteredQueue[a])elem=codeInput.templateNotYetRegisteredQueue[a][c],elem.template=b,elem.setup();console.log(`code-input: template: Added existing elements with template ${a}`)}if(null==codeInput.defaultTemplate){if(codeInput.defaultTemplate=a,void 0 in codeInput.templateNotYetRegisteredQueue)for(let a in codeInput.templateNotYetRegisteredQueue[void 0])elem=codeInput.templateNotYetRegisteredQueue[void 0][a],elem.template=b,elem.setup();console.log(`code-input: template: Set template ${a} as default`)}console.log(`code-input: template: Created template ${a}`)},templates:{custom(a=function(){},b=!0,c=!0,d=!1,e=[]){return{highlight:a,includeCodeInputInHighlightFunc:d,preElementStyled:b,isCode:c,plugins:e}},prism(a,b=[]){return{includeCodeInputInHighlightFunc:!1,highlight:a.highlightElement,preElementStyled:!0,isCode:!0,plugins:b}},hljs(a,b=[]){return{includeCodeInputInHighlightFunc:!1,highlight:a.highlightElement,preElementStyled:!1,isCode:!0,plugins:b}},characterLimit(a){return{highlight:function(a,b,c=[]){let d=+b.getAttribute("data-character-limit"),e=b.escapeHtml(b.value.slice(0,d)),f=b.escapeHtml(b.value.slice(d));a.innerHTML=`${e}<mark class="overflow">${f}</mark>`,0<f.length&&(a.innerHTML+=` <mark class="overflow-msg">${b.getAttribute("data-overflow-msg")||"(Character limit reached)"}</mark>`)},includeCodeInputInHighlightFunc:!0,preElementStyled:!0,isCode:!1,plugins:a}},rainbowText(a=["red","orangered","orange","goldenrod","gold","green","darkgreen","navy","blue","magenta"],b="",c=[]){return{highlight:function(a,b){let c=[],d=b.value.split(b.template.delimiter);for(let e=0;e<d.length;e++)c.push(`<span style="color: ${b.template.rainbowColors[e%b.template.rainbowColors.length]}">${b.escapeHtml(d[e])}</span>`);a.innerHTML=c.join(b.template.delimiter)},includeCodeInputInHighlightFunc:!0,preElementStyled:!0,isCode:!1,rainbowColors:a,delimiter:b,plugins:c}},character_limit(){return this.characterLimit([])},rainbow_text(a=["red","orangered","orange","goldenrod","gold","green","darkgreen","navy","blue","magenta"],b="",c=[]){return this.rainbowText(a,b,c)}},plugins:{},Plugin:class{constructor(){console.log("code-input: plugin: Created plugin"),codeInput.observedAttributes=codeInput.observedAttributes.concat(self.observedAttributes)}beforeHighlight(){}afterHighlight(){}beforeElementsAdded(){}afterElementsAdded(){}attributeChanged(){}observedAttributes=[]},CodeInput:class extends HTMLElement{constructor(){super()}boundEventCallbacks={};pluginEvt(a,b){for(let c in this.template.plugins){let d=this.template.plugins[c];a in d&&(b===void 0?d[a](this):d[a](this,...b))}}update(a){if(!this.ignoreValueUpdate){this.ignoreValueUpdate=!0,this.value=a,this.ignoreValueUpdate=!1,this.querySelector("textarea").value!=a&&(this.querySelector("textarea").value=a);let b=this.querySelector("pre code");"\n"==a[a.length-1]&&(a+=" "),b.innerHTML=this.escapeHtml(a),this.pluginEvt("beforeHighlight"),this.template.includeCodeInputInHighlightFunc?this.template.highlight(b,this):this.template.highlight(b),this.pluginEvt("afterHighlight")}}syncScroll(){let a=this.querySelector("textarea"),b=this.template.preElementStyled?this.querySelector("pre"):this.querySelector("pre code");b.scrollTop=a.scrollTop,b.scrollLeft=a.scrollLeft}escapeHtml(a){return a.replace(/&/g,"&amp;").replace(/</g,"&lt;")}getTemplate(){let a;return a=null==this.getAttribute("template")?codeInput.defaultTemplate:this.getAttribute("template"),a in codeInput.usedTemplates?codeInput.usedTemplates[a]:(a in codeInput.templateNotYetRegisteredQueue||(codeInput.templateNotYetRegisteredQueue[a]=[]),void codeInput.templateNotYetRegisteredQueue[a].push(this))}setup(){this.classList.add("code-input_registered"),this.template.preElementStyled&&this.classList.add("code-input_pre-element-styled"),this.pluginEvt("beforeElementsAdded");let a=this.getAttribute("lang"),b=this.getAttribute("placeholder")||this.getAttribute("lang")||"",c=this.value||this.innerHTML||"";this.innerHTML="";let d=document.createElement("textarea");d.placeholder=b,d.value=c,d.setAttribute("spellcheck","false"),codeInput.textareaSyncAttributes.forEach(a=>{this.hasAttribute(a)&&d.setAttribute(a,this.getAttribute(a))}),d.addEventListener("input",()=>{d.parentElement.update(d.value),d.parentElement.sync_scroll()}),d.addEventListener("scroll",()=>d.parentElement.sync_scroll()),this.append(d);let e=document.createElement("code"),f=document.createElement("pre");f.setAttribute("aria-hidden","true"),f.append(e),this.append(f),this.template.isCode&&a!=null&&""!=a&&e.classList.add("language-"+a),this.pluginEvt("afterElementsAdded"),this.update(c)}sync_scroll(){this.syncScroll()}escape_html(a){return this.escapeHtml(a)}get_template(){return this.getTemplate()}connectedCallback(){this.template=this.getTemplate(),this.template!=null&&this.setup()}static get observedAttributes(){return codeInput.observedAttributes.concat(codeInput.textareaSyncAttributes)}attributeChangedCallback(a,b,c){if(this.isConnected)switch(this.pluginEvt("attributeChanged",[a,b,c]),a){case"value":this.update(c);break;case"placeholder":this.querySelector("textarea").placeholder=c;break;case"template":this.template=codeInput.usedTemplates[c||codeInput.defaultTemplate],this.template.preElementStyled?this.classList.add("code-input_pre-element-styled"):this.classList.remove("code-input_pre-element-styled"),this.update(this.value);break;case"lang":let d=this.querySelector("pre code"),e=this.querySelector("textarea");if(null!=c&&(c=c.toLowerCase(),d.classList.contains(`language-${c}`)))break;b=b.toLowerCase(),console.log("code-input: Language: REMOVE","language-"+b),d.classList.remove("language-"+b),d.parentElement.classList.remove("language-"+b),d.classList.remove("language-none"),d.parentElement.classList.remove("language-none"),null!=c&&""!=c&&(d.classList.add("language-"+c),console.log("code-input: Language:ADD","language-"+c)),e.placeholder==b&&(e.placeholder=c),this.update(this.value);break;default:codeInput.textareaSyncAttributes.includes(a)&&this.querySelector("textarea").setAttribute(a,c)}}addEventListener(a,b,c=void 0){let d=b.bind(this);this.boundEventCallbacks[b]=d,codeInput.textareaSyncEvents.includes(a)?c===void 0?this.querySelector("textarea").addEventListener(a,d):this.querySelector("textarea").addEventListener(a,d,c):c===void 0?super.addEventListener(a,d):super.addEventListener(a,d,c)}removeEventListener(a,b,c=null){let d=this.boundEventCallbacks[b];"change"==a?null===c?this.querySelector("textarea").removeEventListener("change",d):this.querySelector("textarea").removeEventListener("change",d,c):"selectionchange"==a?null===c?this.querySelector("textarea").removeEventListener("selectionchange",d):this.querySelector("textarea").removeEventListener("selectionchange",d,c):super.removeEventListener(a,b,c)}get value(){return this.getAttribute("value")}set value(a){return this.setAttribute("value",a)}get placeholder(){return this.getAttribute("placeholder")}set placeholder(a){return this.setAttribute("placeholder",a)}get validity(){return this.querySelector("textarea").validity}get validationMessage(){return this.querySelector("textarea").validationMessage}setCustomValidity(a){return this.querySelector("textarea").setCustomValidity(a)}checkValidity(){return this.querySelector("textarea").checkValidity()}reportValidity(){return this.querySelector("textarea").reportValidity()}setAttribute(a,b){super.setAttribute(a,b),this.querySelector("textarea").setAttribute(a,b)}getAttribute(a){return null==this.querySelector("textarea")?super.getAttribute(a):this.querySelector("textarea").getAttribute(a)}pluginData={}}};customElements.define("code-input",codeInput.CodeInput);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webcoder49/code-input",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "Fully customisable, editable syntax-highlighted textareas.",
5
5
  "browser": "code-input.js",
6
6
  "scripts": {
package/plugins/README.md CHANGED
@@ -1,68 +1,68 @@
1
- # Code-input: Plugins
2
- ## List Of Plugins
3
-
4
- ### Autocomplete
5
- Display a popup under the caret using the text in the code-input element. This works well with autocomplete suggestions.
6
-
7
- Files: [autocomplete.js](./autocomplete.js) / [autocomplete.css](./autocomplete.css)
8
-
9
- [🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/xxapjXB)
10
-
11
- ### Autodetect
12
- Autodetect the language live and change the `lang` attribute using the syntax highlighter's autodetect capabilities. Works with highlight.js.
13
-
14
- Files: [autodetect.js](./autodetect.js)
15
-
16
- [🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/eYLyMae)
17
-
18
- ### Debounce Update
19
- Debounce the update and highlighting function ([What is Debouncing?](https://medium.com/@jamischarles/what-is-debouncing-2505c0648ff1))
20
-
21
- Files: [debounce-update.js](./debounce-update.js)
22
-
23
- [🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/GRXyxzV)
24
-
25
- ### Indent
26
- Adds indentation using the `Tab` key, and auto-indents after a newline, as well as making it possible to indent/unindent multiple lines using Tab/Shift+Tab
27
-
28
- Files: [indent.js](./indent.js)
29
-
30
- [🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/WNgdzar)
31
-
32
- ### Prism Line Numbers
33
- Allows code-input elements to be used with the Prism.js line-numbers plugin, as long as the code-input element or a parent element of it has the CSS class `line-numbers`. [Prism.js Plugin Docs](https://prismjs.com/plugins/line-numbers/)
34
-
35
- Files: [prism-line-numbers.css](./prism-line-numbers.css) (NO JS FILE)
36
-
37
- [🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/XWPVrWv)
38
-
39
- ### Special Chars
40
- Render special characters and control characters as a symbol
41
- with their hex code.
42
-
43
- Files: [special-chars.js](./special-chars.js) / [special-chars.css](./special-chars.css)
44
-
45
- [🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/jOeYJbm)
46
-
47
- ## Using Plugins
48
- Plugins allow you to add extra features to a template, like [automatic indentation](./indent.js) or [support for highlight.js's language autodetection](./autodetect.js). To use them, just:
49
- - Import the plugins' JS/CSS files (there may only be one of these; import all of the files that exist) after you have imported `code-input` and before registering the template.
50
- - If a JavaScript file is present, Place an instance of each plugin in the array of plugins argument when registering, like this:
51
- ```html
52
- <script src="code-input.js"></script>
53
- <!--...-->
54
- <script src="plugins/autodetect.js"></script>
55
- <script src="plugins/indent.js"></script>
56
- <!--...-->
57
- <script>
58
- codeInput.registerTemplate("syntax-highlighted",
59
- codeInput.templates.hljs(
60
- hljs,
61
- [
62
- new codeInput.plugins.Autodetect(),
63
- new codeInput.plugins.Indent()
64
- ]
65
- )
66
- );
67
- </script>
68
- ```
1
+ # Code-input: Plugins
2
+ ## List Of Plugins
3
+
4
+ ### Autocomplete
5
+ Display a popup under the caret using the text in the code-input element. This works well with autocomplete suggestions.
6
+
7
+ Files: [autocomplete.js](./autocomplete.js) / [autocomplete.css](./autocomplete.css)
8
+
9
+ [🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/xxapjXB)
10
+
11
+ ### Autodetect
12
+ Autodetect the language live and change the `lang` attribute using the syntax highlighter's autodetect capabilities. Works with highlight.js.
13
+
14
+ Files: [autodetect.js](./autodetect.js)
15
+
16
+ [🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/eYLyMae)
17
+
18
+ ### Debounce Update
19
+ Debounce the update and highlighting function ([What is Debouncing?](https://medium.com/@jamischarles/what-is-debouncing-2505c0648ff1))
20
+
21
+ Files: [debounce-update.js](./debounce-update.js)
22
+
23
+ [🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/GRXyxzV)
24
+
25
+ ### Indent
26
+ Adds indentation using the `Tab` key, and auto-indents after a newline, as well as making it possible to indent/unindent multiple lines using Tab/Shift+Tab
27
+
28
+ Files: [indent.js](./indent.js)
29
+
30
+ [🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/WNgdzar)
31
+
32
+ ### Prism Line Numbers
33
+ Allows code-input elements to be used with the Prism.js line-numbers plugin, as long as the code-input element or a parent element of it has the CSS class `line-numbers`. [Prism.js Plugin Docs](https://prismjs.com/plugins/line-numbers/)
34
+
35
+ Files: [prism-line-numbers.css](./prism-line-numbers.css) (NO JS FILE)
36
+
37
+ [🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/XWPVrWv)
38
+
39
+ ### Special Chars
40
+ Render special characters and control characters as a symbol
41
+ with their hex code.
42
+
43
+ Files: [special-chars.js](./special-chars.js) / [special-chars.css](./special-chars.css)
44
+
45
+ [🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/jOeYJbm)
46
+
47
+ ## Using Plugins
48
+ Plugins allow you to add extra features to a template, like [automatic indentation](./indent.js) or [support for highlight.js's language autodetection](./autodetect.js). To use them, just:
49
+ - Import the plugins' JS/CSS files (there may only be one of these; import all of the files that exist) after you have imported `code-input` and before registering the template.
50
+ - If a JavaScript file is present, Place an instance of each plugin in the array of plugins argument when registering, like this:
51
+ ```html
52
+ <script src="code-input.js"></script>
53
+ <!--...-->
54
+ <script src="plugins/autodetect.js"></script>
55
+ <script src="plugins/indent.js"></script>
56
+ <!--...-->
57
+ <script>
58
+ codeInput.registerTemplate("syntax-highlighted",
59
+ codeInput.templates.hljs(
60
+ hljs,
61
+ [
62
+ new codeInput.plugins.Autodetect(),
63
+ new codeInput.plugins.Indent()
64
+ ]
65
+ )
66
+ );
67
+ </script>
68
+ ```
@@ -1,15 +1,15 @@
1
- /**
2
- * Display a popup under the caret using the text in the code-input element. This works well with autocomplete suggestions.
3
- * Files: autocomplete.js / autocomplete.css
4
- */
5
- code-input .code-input_autocomplete_popup {
6
- display: block;
7
- position: absolute;
8
- margin-top: 1em; /* Popup shows under the caret */
9
- z-index: 100;
10
- }
11
-
12
-
13
- code-input .code-input_autocomplete_testpos {
14
- opacity: 0;
1
+ /**
2
+ * Display a popup under the caret using the text in the code-input element. This works well with autocomplete suggestions.
3
+ * Files: autocomplete.js / autocomplete.css
4
+ */
5
+ code-input .code-input_autocomplete_popup {
6
+ display: block;
7
+ position: absolute;
8
+ margin-top: 1em; /* Popup shows under the caret */
9
+ z-index: 100;
10
+ }
11
+
12
+
13
+ code-input .code-input_autocomplete_testpos {
14
+ opacity: 0;
15
15
  }
@@ -1,80 +1,80 @@
1
- /**
2
- * Display a popup under the caret using the text in the code-input element. This works well with autocomplete suggestions.
3
- * Files: autocomplete.js / autocomplete.css
4
- */
5
- codeInput.plugins.Autocomplete = class extends codeInput.Plugin {
6
- /**
7
- * Pass in a function to display the popup that takes in (popup element, textarea, textarea.selectionEnd).
8
- * @param {function} updatePopupCallback
9
- */
10
- constructor(updatePopupCallback) {
11
- super();
12
- this.updatePopupCallback = updatePopupCallback;
13
- }
14
- /* When a key is pressed, or scrolling occurs, update the autocomplete */
15
- updatePopup(codeInput, onlyScrolled) {
16
- let textarea = codeInput.querySelector("textarea");
17
- let caretCoords = this.getCaretCoordinates(codeInput, textarea, textarea.selectionEnd, onlyScrolled);
18
- let popupElem = codeInput.querySelector(".code-input_autocomplete_popup");
19
- popupElem.style.top = caretCoords.top + "px";
20
- popupElem.style.left = caretCoords.left + "px";
21
-
22
- if(!onlyScrolled) {
23
- this.updatePopupCallback(popupElem, textarea, textarea.selectionEnd);
24
- }
25
- }
26
- /* Runs after elements are added into a `code-input` (useful for adding events to the textarea); Params: codeInput element) */
27
- afterElementsAdded(codeInput) {
28
- let popupElem = document.createElement("div");
29
- popupElem.classList.add("code-input_autocomplete_popup");
30
- codeInput.appendChild(popupElem);
31
-
32
- let testPosElem = document.createElement("pre");
33
- testPosElem.classList.add("code-input_autocomplete_testpos");
34
- codeInput.appendChild(testPosElem); // Styled like first pre, but first pre found to update
35
-
36
- let textarea = codeInput.querySelector("textarea");
37
- textarea.addEventListener("keyup", this.updatePopup.bind(this, codeInput, false)); // Override this+args in bind - not just scrolling
38
- textarea.addEventListener("click", this.updatePopup.bind(this, codeInput, false)); // Override this+args in bind - not just scrolling
39
- textarea.addEventListener("scroll", this.updatePopup.bind(this, codeInput, true)); // Override this+args in bind - just scrolling
40
- }
41
- /**
42
- * Return the coordinates of the caret in a code-input
43
- * @param {codeInput.CodeInput} codeInput
44
- * @param {HTMLElement} textarea
45
- * @param {Number} charIndex
46
- * @param {boolean} onlyScrolled True if no edits have been made to the text and the caret hasn't been repositioned
47
- * @returns
48
- */
49
- getCaretCoordinates(codeInput, textarea, charIndex, onlyScrolled) {
50
- let afterSpan;
51
- if(onlyScrolled) {
52
- // No edits to text; don't update element - span at index 1 is after span
53
- let spans = codeInput.querySelector(".code-input_autocomplete_testpos").querySelectorAll("span");
54
- if(spans.length < 2) {
55
- // Hasn't saved text in test pre to find pos
56
- // Need to regenerate text in test pre
57
- return this.getCaretCoordinates(codeInput, textarea, charIndex, false);
58
- }
59
- afterSpan = spans[1];
60
- } else {
61
- /* Inspired by https://github.com/component/textarea-caret-position */
62
- let testPosElem = codeInput.querySelector(".code-input_autocomplete_testpos");
63
-
64
- let beforeSpan = document.createElement("span");
65
- beforeSpan.textContent = textarea.value.substring(0, charIndex);
66
- afterSpan = document.createElement("span");
67
- afterSpan.textContent = "."; // Placeholder
68
-
69
- // Clear test pre - https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild
70
- while (testPosElem.firstChild) {
71
- testPosElem.removeChild(testPosElem.firstChild);
72
- }
73
- testPosElem.appendChild(beforeSpan);
74
- testPosElem.appendChild(afterSpan);
75
- }
76
- return {"top": afterSpan.offsetTop - textarea.scrollTop, "left": afterSpan.offsetLeft - textarea.scrollLeft};
77
- }
78
- observedAttributes = [];
79
- updatePopupCallback = function() {};
1
+ /**
2
+ * Display a popup under the caret using the text in the code-input element. This works well with autocomplete suggestions.
3
+ * Files: autocomplete.js / autocomplete.css
4
+ */
5
+ codeInput.plugins.Autocomplete = class extends codeInput.Plugin {
6
+ /**
7
+ * Pass in a function to display the popup that takes in (popup element, textarea, textarea.selectionEnd).
8
+ * @param {function} updatePopupCallback
9
+ */
10
+ constructor(updatePopupCallback) {
11
+ super();
12
+ this.updatePopupCallback = updatePopupCallback;
13
+ }
14
+ /* When a key is pressed, or scrolling occurs, update the autocomplete */
15
+ updatePopup(codeInput, onlyScrolled) {
16
+ let textarea = codeInput.querySelector("textarea");
17
+ let caretCoords = this.getCaretCoordinates(codeInput, textarea, textarea.selectionEnd, onlyScrolled);
18
+ let popupElem = codeInput.querySelector(".code-input_autocomplete_popup");
19
+ popupElem.style.top = caretCoords.top + "px";
20
+ popupElem.style.left = caretCoords.left + "px";
21
+
22
+ if(!onlyScrolled) {
23
+ this.updatePopupCallback(popupElem, textarea, textarea.selectionEnd);
24
+ }
25
+ }
26
+ /* Runs after elements are added into a `code-input` (useful for adding events to the textarea); Params: codeInput element) */
27
+ afterElementsAdded(codeInput) {
28
+ let popupElem = document.createElement("div");
29
+ popupElem.classList.add("code-input_autocomplete_popup");
30
+ codeInput.appendChild(popupElem);
31
+
32
+ let testPosElem = document.createElement("pre");
33
+ testPosElem.classList.add("code-input_autocomplete_testpos");
34
+ codeInput.appendChild(testPosElem); // Styled like first pre, but first pre found to update
35
+
36
+ let textarea = codeInput.querySelector("textarea");
37
+ textarea.addEventListener("keyup", this.updatePopup.bind(this, codeInput, false)); // Override this+args in bind - not just scrolling
38
+ textarea.addEventListener("click", this.updatePopup.bind(this, codeInput, false)); // Override this+args in bind - not just scrolling
39
+ textarea.addEventListener("scroll", this.updatePopup.bind(this, codeInput, true)); // Override this+args in bind - just scrolling
40
+ }
41
+ /**
42
+ * Return the coordinates of the caret in a code-input
43
+ * @param {codeInput.CodeInput} codeInput
44
+ * @param {HTMLElement} textarea
45
+ * @param {Number} charIndex
46
+ * @param {boolean} onlyScrolled True if no edits have been made to the text and the caret hasn't been repositioned
47
+ * @returns
48
+ */
49
+ getCaretCoordinates(codeInput, textarea, charIndex, onlyScrolled) {
50
+ let afterSpan;
51
+ if(onlyScrolled) {
52
+ // No edits to text; don't update element - span at index 1 is after span
53
+ let spans = codeInput.querySelector(".code-input_autocomplete_testpos").querySelectorAll("span");
54
+ if(spans.length < 2) {
55
+ // Hasn't saved text in test pre to find pos
56
+ // Need to regenerate text in test pre
57
+ return this.getCaretCoordinates(codeInput, textarea, charIndex, false);
58
+ }
59
+ afterSpan = spans[1];
60
+ } else {
61
+ /* Inspired by https://github.com/component/textarea-caret-position */
62
+ let testPosElem = codeInput.querySelector(".code-input_autocomplete_testpos");
63
+
64
+ let beforeSpan = document.createElement("span");
65
+ beforeSpan.textContent = textarea.value.substring(0, charIndex);
66
+ afterSpan = document.createElement("span");
67
+ afterSpan.textContent = "."; // Placeholder
68
+
69
+ // Clear test pre - https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild
70
+ while (testPosElem.firstChild) {
71
+ testPosElem.removeChild(testPosElem.firstChild);
72
+ }
73
+ testPosElem.appendChild(beforeSpan);
74
+ testPosElem.appendChild(afterSpan);
75
+ }
76
+ return {"top": afterSpan.offsetTop - textarea.scrollTop, "left": afterSpan.offsetLeft - textarea.scrollLeft};
77
+ }
78
+ observedAttributes = [];
79
+ updatePopupCallback = function() {};
80
80
  }
@@ -1,29 +1,29 @@
1
- /**
2
- * Autodetect the language live and change the `lang` attribute using the syntax highlighter's
3
- * autodetect capabilities. Works with highlight.js.
4
- * Files: autodetect.js
5
- */
6
- codeInput.plugins.Autodetect = class extends codeInput.Plugin {
7
- constructor() {
8
- super();
9
- }
10
- /* Remove previous language class */
11
- beforeHighlight(codeInput) {
12
- let result_element = codeInput.querySelector("pre code");
13
- result_element.className = ""; // CODE
14
- result_element.parentElement.className = ""; // PRE
15
- }
16
- /* Get new language class and set `lang` attribute */
17
- afterHighlight(codeInput) {
18
- let result_element = codeInput.querySelector("pre code");
19
- let lang_class = result_element.className || result_element.parentElement.className;
20
- let lang = lang_class.match(/lang(\w|-)*/i)[0]; // Get word starting with lang...; Get outer bracket
21
- lang = lang.split("-")[1];
22
- if(lang == "undefined") {
23
- codeInput.removeAttribute("lang");
24
- } else {
25
- codeInput.setAttribute("lang", lang);
26
- }
27
- }
28
- observedAttributes = []
1
+ /**
2
+ * Autodetect the language live and change the `lang` attribute using the syntax highlighter's
3
+ * autodetect capabilities. Works with highlight.js.
4
+ * Files: autodetect.js
5
+ */
6
+ codeInput.plugins.Autodetect = class extends codeInput.Plugin {
7
+ constructor() {
8
+ super();
9
+ }
10
+ /* Remove previous language class */
11
+ beforeHighlight(codeInput) {
12
+ let result_element = codeInput.querySelector("pre code");
13
+ result_element.className = ""; // CODE
14
+ result_element.parentElement.className = ""; // PRE
15
+ }
16
+ /* Get new language class and set `lang` attribute */
17
+ afterHighlight(codeInput) {
18
+ let result_element = codeInput.querySelector("pre code");
19
+ let lang_class = result_element.className || result_element.parentElement.className;
20
+ let lang = lang_class.match(/lang(\w|-)*/i)[0]; // Get word starting with lang...; Get outer bracket
21
+ lang = lang.split("-")[1];
22
+ if(lang == "undefined") {
23
+ codeInput.removeAttribute("lang");
24
+ } else {
25
+ codeInput.setAttribute("lang", lang);
26
+ }
27
+ }
28
+ observedAttributes = []
29
29
  }
@@ -1,41 +1,41 @@
1
- /**
2
- * Debounce the update and highlighting function
3
- * https://medium.com/@jamischarles/what-is-debouncing-2505c0648ff1
4
- * Files: debounce-update.js
5
- */
6
- codeInput.plugins.DebounceUpdate = class extends codeInput.Plugin {
7
- /**
8
- * Create a debounced update plugin to pass into a template
9
- * @param {Number} delayMs Delay, in ms, to wait until updating the syntax highlighting
10
- */
11
- constructor(delayMs) {
12
- super();
13
- this.delayMs = delayMs;
14
- }
15
- /* Runs before elements are added into a `code-input`; Params: codeInput element) */
16
- beforeElementsAdded(codeInput) {
17
- console.log(codeInput, "before elements added");
18
- this.update = codeInput.update.bind(codeInput); // Save previous update func
19
- codeInput.update = this.updateDebounced.bind(this, codeInput);
20
- }
21
-
22
- /**
23
- * Debounce the `update` function
24
- */
25
- updateDebounced(codeInput, text) {
26
- // Editing - cancel prev. timeout
27
- if(this.debounceTimeout != null) {
28
- window.clearTimeout(this.debounceTimeout);
29
- }
30
-
31
- this.debounceTimeout = window.setTimeout(() => {
32
- // Closure arrow function can take in variables like `text`
33
- this.update(text);
34
- }, this.delayMs);
35
- }
36
-
37
- // this.`update` function is original function
38
-
39
- debounceTimeout = null; // Timeout until update
40
- delayMs = 0; // Time until update
1
+ /**
2
+ * Debounce the update and highlighting function
3
+ * https://medium.com/@jamischarles/what-is-debouncing-2505c0648ff1
4
+ * Files: debounce-update.js
5
+ */
6
+ codeInput.plugins.DebounceUpdate = class extends codeInput.Plugin {
7
+ /**
8
+ * Create a debounced update plugin to pass into a template
9
+ * @param {Number} delayMs Delay, in ms, to wait until updating the syntax highlighting
10
+ */
11
+ constructor(delayMs) {
12
+ super();
13
+ this.delayMs = delayMs;
14
+ }
15
+ /* Runs before elements are added into a `code-input`; Params: codeInput element) */
16
+ beforeElementsAdded(codeInput) {
17
+ console.log(codeInput, "before elements added");
18
+ this.update = codeInput.update.bind(codeInput); // Save previous update func
19
+ codeInput.update = this.updateDebounced.bind(this, codeInput);
20
+ }
21
+
22
+ /**
23
+ * Debounce the `update` function
24
+ */
25
+ updateDebounced(codeInput, text) {
26
+ // Editing - cancel prev. timeout
27
+ if(this.debounceTimeout != null) {
28
+ window.clearTimeout(this.debounceTimeout);
29
+ }
30
+
31
+ this.debounceTimeout = window.setTimeout(() => {
32
+ // Closure arrow function can take in variables like `text`
33
+ this.update(text);
34
+ }, this.delayMs);
35
+ }
36
+
37
+ // this.`update` function is original function
38
+
39
+ debounceTimeout = null; // Timeout until update
40
+ delayMs = 0; // Time until update
41
41
  }