@webcoder49/code-input 1.5.0

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.
@@ -0,0 +1,22 @@
1
+ name: Auto-minify
2
+ on:
3
+ push:
4
+ branches:
5
+ - main
6
+ jobs:
7
+ Minify:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ # Checks-out your repository under $GITHUB_WORKSPACE, so auto-minify job can access it
11
+ - uses: actions/checkout@v2
12
+
13
+ - name: Auto Minify
14
+ uses: nizarmah/auto-minify@v2.1
15
+
16
+ # Auto commits minified files to the repository
17
+ # Ignore it if you don't want to commit the files to the repository
18
+ - name: Auto committing minified files
19
+ uses: stefanzweifel/git-auto-commit-action@v4
20
+ with:
21
+ commit_message: "Auto Minified JS and CSS files"
22
+ branch: ${{ github.ref }}
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 WebCoder49
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.
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # code-input
2
+ [![View License](https://img.shields.io/github/license/webcoder49/code-input?style=for-the-badge)](LICENSE) [![View Releases](https://img.Shields.io/github/v/release/webcoder49/code-input?style=for-the-badge)](https://github.com/WebCoder49/code-input/releases) [![View the demo on CodePen](https://img.shields.io/static/v1?label=Demo&message=on%20CodePen&color=orange&logo=codepen&style=for-the-badge)](https://codepen.io/WebCoder49/details/jOypJOx)
3
+
4
+ > ___Fully customisable syntax-highlighted textareas.___ [[🚀 View the Demo](https://codepen.io/WebCoder49/details/jOypJOx)]
5
+
6
+ ![Using code-input with many different themes](https://user-images.githubusercontent.com/69071853/133924472-05edde5c-23e7-4350-a41b-5a74d2dc1a9a.gif)
7
+ *This demonstration uses themes from [Prism.js](https://prismjs.com/) and [highlight.js](https://highlightjs.org/), two syntax-highlighting programs which work well and have compatibility built-in with code-input.*
8
+
9
+ ## What does it do?
10
+ **`code-input`** lets you **turn any ordinary JavaScript syntax-highlighting theme and program into customisable syntax-highlighted textareas** using an HTML custom element. It uses vanilla CSS to superimpose a `textarea` on a `pre code` block, then handles indentations, scrolling and fixes any resulting bugs with JavaScript. To see how it works in more detail, please see [this CSS-Tricks article](https://css-tricks.com/creating-an-editable-textarea-that-supports-syntax-highlighted-code/ "Creating an Editable Textarea That Supports Syntax-Highlighted Code") I wrote.
11
+
12
+ ## What are the advantages of using code-input, and what can it be used for?
13
+ Unlike other front-end code-editor projects, the simplicity of how `code-input` works means it is **highly customisable**. As it is not a full-featured editor, you can **choose what features you want it to include, and use your favourite syntax-highlighting algorithms and themes**.
14
+
15
+ The `<code-input>` element works like a `<textarea>` and therefore **works in HTML5 forms and supports using the `value` and `placeholder` attributes, as well as the `onchange` event.**
16
+
17
+ <details>
18
+ <summary>
19
+
20
+ ## Getting Started With `code-input`
21
+ </summary>
22
+
23
+ `code-input` is designed to be **both easy to use and customisable**. Here's how to use it to create syntax-highlighted textareas:
24
+
25
+ ### Import `code-input`
26
+ - **First, import your favourite syntax-highlighter's JS and CSS theme files** to turn editable.
27
+ - Then, import the CSS and JS files of `code-input` from a downloaded release or a CDN. The non-minified files are useful for using during development.
28
+
29
+ <details>
30
+ <summary>
31
+ Locally downloaded
32
+ </summary>
33
+
34
+ ```html
35
+ <!--In the <head>-->
36
+ <script src="path/to/code-input.min.js"></script>
37
+ <link rel="stylesheet" href="path/to/code-input.min.css">
38
+ ```
39
+ </details>
40
+ <details>
41
+ <summary>
42
+ From JSDelivr CDN
43
+ </summary>
44
+
45
+ ```html
46
+ <!--In the <head>-->
47
+ <script src="https://cdn.jsdelivr.net/gh/WebCoder49/code-input@1.3/code-input.min.js"></script>
48
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/WebCoder49/code-input@1.3/code-input.min.css">
49
+ ```
50
+ </details>
51
+
52
+ ### Creating a template
53
+ The next step is to set up a `template` to link `code-input` to your syntax-highlighter. If you're using Prism.js or highlight.js, you can use the built-in template, or you can create your own otherwise. In these examples, I am registering the template as `"syntax-highlighted"`, but you can use any template name as long as you are consistent.
54
+
55
+ - *Highlight.js:*
56
+ ```js
57
+ codeInput.registerTemplate("syntax-highlighted", codeInput.templates.hljs(hljs, [] /* Array of plugins (see below) */));
58
+ ```
59
+
60
+ - *Prism.js:*
61
+ ```js
62
+ codeInput.registerTemplate("syntax-highlighted", codeInput.templates.prism(Prism, [] /* Array of plugins (see below) */));
63
+ ```
64
+
65
+ - *Custom:*
66
+ ```js
67
+ codeInput.registerTemplate("syntax-highlighted", codeInput.templates.custom(
68
+ function(result_element) { /* Highlight function - with `pre code` code element */
69
+ /* Highlight code in result_element - code is already escaped so it doesn't become HTML */
70
+ },
71
+ true, /* Optional - Is the `pre` element styled as well as the `code` element? Changing this to false uses the code element as the scrollable one rather than the pre element */
72
+ true, /* Optional - This is used for editing code - setting this to true overrides the Tab key and uses it for indentation */
73
+ false /* Optional - Setting this to true passes the `<code-input>` element as a second argument to the highlight function to be used for getting data- attribute values and using the DOM for the code-input */,
74
+ [] // Array of plugins (see below)
75
+ ));
76
+ ```
77
+
78
+ ### Adding plugins
79
+ [Plugins](./plugins/) allow you to add extra features to a template, like [automatic indentation](plugins/indent.js) or [support for highlight.js's language autodetection](plugins/autodetect.js). To use them, just:
80
+ - Import the plugins' JS files after you have imported `code-input` and before registering the template.
81
+ - Place instances of the plugins in the array of plugins argument when registering, like this:
82
+ ```html
83
+ <script src="code-input.js"></script>
84
+ <!--...-->
85
+ <script src="plugins/autodetect.js"></script>
86
+ <script src="plugins/indent.js"></script>
87
+ <!--...-->
88
+ <script>
89
+ codeInput.registerTemplate("syntax-highlighted",
90
+ codeInput.templates.hljs(
91
+ hljs,
92
+ [
93
+ new codeInput.plugins.Autodetect(),
94
+ new codeInput.plugins.Indent()
95
+ ]
96
+ )
97
+ );
98
+ </script>
99
+ ```
100
+
101
+ To see a full list of plugins and their functions, please see [plugins/README.md](./plugins/README.md).
102
+
103
+ ### Using the component
104
+ Now that you have registered a template, you can use the custom `<code-input>` element in HTML. If you have more than one template registered, you need to add the template name as the `template` attribute. With the element, using the `lang` attribute will add a `language-{value}` class to the `pre code` block. You can now use HTML attributes and events to make your element as simple or interactive as you like!
105
+ ```HTML
106
+ <code-input lang="HTML"></code-input>
107
+ ```
108
+ *or*
109
+ ```HTML
110
+ <code-input lang="HTML" placeholder="Type code here" value="<a href='https://github.com/WebCoder49/code-input'>code-input</a>" template="syntax-highlighted" onchange="console.log('Your code is', this.value)"></code-input>
111
+ ```
112
+ </details>
113
+
114
+ ## Contributing
115
+ If you have any features you would like to add to `code-input`, or have found any bugs, please [open an issue](https://github.com/WebCoder49/code-input/issues) or [fork and submit a pull request](https://github.com/WebCoder49/code-input/fork)! All contributions to this open-source project would be greatly appreciated.
package/code-input.css ADDED
@@ -0,0 +1,100 @@
1
+ /* Code-Input Compatibility */
2
+ /* By WebCoder49 */
3
+ /* First Published on CSS-Tricks.com */
4
+
5
+
6
+ code-input {
7
+ /* Allow other elems to be inside */
8
+ position: relative;
9
+ top: 0;
10
+ left: 0;
11
+ display: block;
12
+ /* Only scroll inside elems */
13
+ overflow: hidden;
14
+
15
+ /* Normal inline styles */
16
+ padding: 8px;
17
+ margin: 0!important;
18
+ width: calc(100% - 16px);
19
+ height: 250px;
20
+ font-size: normal;
21
+ font-family: monospace;
22
+ line-height: 1.5; /* Inherited to child elements */
23
+ tab-size: 2;
24
+ caret-color: darkgrey;
25
+ white-space: pre;
26
+ }
27
+
28
+ code-input textarea, code-input:not(.code-input_pre-element-styled) pre code, code-input.code-input_pre-element-styled pre {
29
+ /* Both elements need the same text and space styling so they are directly on top of each other */
30
+ margin: 0px!important;
31
+ padding: var(--padding, 16px)!important;
32
+ border: 0;
33
+ width: calc(100% - var(--padding, 16px) * 2);
34
+ height: calc(100% - var(--padding, 16px) * 2);
35
+ }
36
+ code-input:not(.code-input_pre-element-styled) pre, code-input.code-input_pre-element-styled pre code {
37
+ /* Remove all margin and padding from others */
38
+ margin: 0px!important;
39
+ padding: 0px!important;
40
+ width: 100%;
41
+ height: 100%;
42
+ }
43
+
44
+ code-input textarea, code-input pre, code-input pre * {
45
+ /* Also add text styles to highlighing tokens */
46
+ font-size: inherit!important;
47
+ font-family: inherit!important;
48
+ line-height: inherit!important;
49
+ tab-size: inherit!important;
50
+ }
51
+
52
+
53
+ code-input textarea, code-input pre {
54
+ /* In the same place */
55
+ position: absolute;
56
+ top: 0;
57
+ left: 0;
58
+ }
59
+
60
+
61
+ /* Move the textarea in front of the result */
62
+
63
+ code-input textarea {
64
+ z-index: 1;
65
+ }
66
+ code-input pre {
67
+ z-index: 0;
68
+ }
69
+
70
+
71
+ /* Make textarea almost completely transparent */
72
+
73
+ code-input textarea {
74
+ color: transparent;
75
+ background: transparent;
76
+ caret-color: inherit!important; /* Or choose your favourite color */
77
+ }
78
+
79
+ /* Can be scrolled */
80
+ code-input textarea, code-input pre {
81
+ overflow: auto!important;
82
+
83
+ white-space: inherit;
84
+ word-spacing: normal;
85
+ word-break: normal;
86
+ word-wrap: normal;
87
+ }
88
+
89
+ /* No resize on textarea; stop outline */
90
+ code-input textarea {
91
+ resize: none;
92
+ outline: none!important;
93
+ }
94
+
95
+ code-input:not(.code-input_registered)::before {
96
+ /* Display message to register */
97
+ content: "Use codeInput.registerTemplate to set up.";
98
+ display: block;
99
+ color: grey;
100
+ }
package/code-input.js ADDED
@@ -0,0 +1,422 @@
1
+ // CodeInput
2
+ // by WebCoder49
3
+ // Based on a CSS-Tricks Post
4
+
5
+ var codeInput = {
6
+ observedAttributes: [ // Doesn't include events, as they are transferred by overriding {add/remove}EventListener
7
+ "value",
8
+ "name",
9
+ "placeholder",
10
+ "lang",
11
+ "template"
12
+ ],
13
+ // Attributes to monitor - needs to be global and static
14
+
15
+ /* Templates */
16
+ usedTemplates: {
17
+ },
18
+ defaultTemplate: undefined,
19
+ templateQueue: {}, // lists of elements for each unrecognised template
20
+
21
+ /* Plugins */
22
+ plugins: { // Import a plugin from the plugins folder and it will be saved here.
23
+ },
24
+ Plugin: class {
25
+ constructor() {
26
+ console.log("code-input: plugin: Created plugin!");
27
+
28
+ // Add attributes
29
+ codeInput.observedAttributes = codeInput.observedAttributes.concat(self.observedAttributes);
30
+ }
31
+
32
+ /* Runs before code is highlighted; Params: codeInput element) */
33
+ beforeHighlight(codeInput) {}
34
+ /* Runs after code is highlighted; Params: codeInput element) */
35
+ afterHighlight(codeInput) {}
36
+ /* Runs before elements are added into a `code-input`; Params: codeInput element) */
37
+ beforeElementsAdded(codeInput) {}
38
+ /* Runs after elements are added into a `code-input` (useful for adding events to the textarea); Params: codeInput element) */
39
+ afterElementsAdded(codeInput) {}
40
+ /* Runs when an attribute of a `code-input` is changed (you must add the attribute name to observedAttributes); Params: codeInput element, name attribute name, oldValue previous value of attribute, newValue changed value of attribute) */
41
+ attributeChanged(codeInput, name, oldValue, newValue) {}
42
+ observedAttributes = []
43
+ },
44
+
45
+ /* Main */
46
+ CodeInput: class extends HTMLElement { // Create code input element
47
+ constructor() {
48
+ super(); // Element
49
+ }
50
+
51
+ bound_callbacks = {}; // Callback without this context > Callback with forced codeInput elem this
52
+
53
+ /* Run this event in all plugins with a optional list of arguments */
54
+ plugin_evt(id, args) {
55
+ // Run the event `id` in each plugin
56
+ for (let i in this.template.plugins) {
57
+ let plugin = this.template.plugins[i];
58
+ if (id in plugin) {
59
+ if(args === undefined) {
60
+ plugin[id](this);
61
+ } else {
62
+ plugin[id](this, ...args);
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ /* Syntax-highlighting functions */
69
+ update(text) {
70
+ // Prevent this from running multiple times on the same input when "value" attribute is changed,
71
+ // by not running when value is already equal to the input of this (implying update has already
72
+ // been run). Thank you to peterprvy for this.
73
+ if(this.ignoreValueUpdate) return;
74
+
75
+ this.ignoreValueUpdate = true;
76
+ this.value = text; // Change value attribute if necessary.
77
+ this.ignoreValueUpdate = false;
78
+ if(this.querySelector("textarea").value != text) this.querySelector("textarea").value = text;
79
+
80
+
81
+ let result_element = this.querySelector("pre code");
82
+
83
+ // Handle final newlines (see article)
84
+ if (text[text.length - 1] == "\n") {
85
+ text += " ";
86
+ }
87
+
88
+ // Update code
89
+ result_element.innerHTML = this.escape_html(text);
90
+ this.plugin_evt("beforeHighlight");
91
+
92
+ // Syntax Highlight
93
+ if(this.template.includeCodeInputInHighlightFunc) this.template.highlight(result_element, this);
94
+ else this.template.highlight(result_element);
95
+
96
+ this.plugin_evt("afterHighlight");
97
+ }
98
+
99
+ sync_scroll() {
100
+ /* Scroll result to scroll coords of event - sync with textarea */
101
+ let input_element = this.querySelector("textarea");
102
+ let result_element = this.template.preElementStyled ? this.querySelector("pre") : this.querySelector("pre code");
103
+ // Get and set x and y
104
+ result_element.scrollTop = input_element.scrollTop;
105
+ result_element.scrollLeft = input_element.scrollLeft;
106
+ }
107
+
108
+ escape_html(text) {
109
+ return text.replace(new RegExp("&", "g"), "&amp;").replace(new RegExp("<", "g"), "&lt;"); /* Global RegExp */
110
+ }
111
+
112
+ /* Get the template for this element or add to the unrecognised template queue. */
113
+ get_template() {
114
+ // Get name of template
115
+ let template_name;
116
+ if(this.getAttribute("template") == undefined) {
117
+ // Default
118
+ template_name = codeInput.defaultTemplate;
119
+ } else {
120
+ template_name = this.getAttribute("template");
121
+ }
122
+ // Get template
123
+ if(template_name in codeInput.usedTemplates) {
124
+ return codeInput.usedTemplates[template_name];
125
+ } else {
126
+ // Doesn't exist - add to queue
127
+ if( !(template_name in codeInput.templateQueue)) {
128
+ codeInput.templateQueue[template_name] = [];
129
+ }
130
+ codeInput.templateQueue[template_name].push(this);
131
+ return undefined;
132
+ }
133
+ codeInput.usedTemplates[codeInput.defaultTemplate]
134
+ }
135
+ /* Set up element when a template is added */
136
+ setup() {
137
+ this.classList.add("code-input_registered"); // Remove register message
138
+ if(this.template.preElementStyled) this.classList.add("code-input_pre-element-styled");
139
+
140
+ this.plugin_evt("beforeElementsAdded");
141
+
142
+ /* Defaults */
143
+ let lang = this.getAttribute("lang");
144
+ let placeholder = this.getAttribute("placeholder") || this.getAttribute("lang") || "";
145
+ let value = this.value || this.innerHTML || "";
146
+
147
+ this.innerHTML = ""; // Clear Content
148
+
149
+ /* Create Textarea */
150
+ let textarea = document.createElement("textarea");
151
+ textarea.placeholder = placeholder;
152
+ textarea.value = value;
153
+ textarea.setAttribute("spellcheck", "false");
154
+
155
+ if (this.getAttribute("name")) {
156
+ textarea.setAttribute("name", this.getAttribute("name")); // for use in forms
157
+ }
158
+
159
+ textarea.addEventListener('input',(evt) => { textarea.parentElement.update(textarea.value); textarea.parentElement.sync_scroll();});
160
+ textarea.addEventListener('scroll',(evt) => textarea.parentElement.sync_scroll());
161
+ this.append(textarea);
162
+
163
+ /* Create pre code */
164
+ let code = document.createElement("code");
165
+ let pre = document.createElement("pre");
166
+ pre.setAttribute("aria-hidden", "true"); // Hide for screen readers
167
+ pre.append(code);
168
+ this.append(pre);
169
+
170
+ if(this.template.isCode) {
171
+ if(lang != undefined && lang != "") {
172
+ code.classList.add("language-" + lang);
173
+ }
174
+ }
175
+
176
+ this.plugin_evt("afterElementsAdded");
177
+
178
+ /* Add code from value attribute - useful for loading from backend */
179
+ this.update(value, this);
180
+ }
181
+
182
+ /* Callbacks */
183
+ connectedCallback() {
184
+ // Added to document
185
+ this.template = this.get_template();
186
+ if(this.template != undefined) this.setup();
187
+ }
188
+ static get observedAttributes() {
189
+ return codeInput.observedAttributes;
190
+ }
191
+
192
+ attributeChangedCallback(name, oldValue, newValue) {
193
+ if(this.isConnected) {
194
+ // This will sometimes be called before the element has been created, so trying to update an attribute causes an error.
195
+ // Thanks to Kevin Loughead for pointing this out.
196
+
197
+ this.plugin_evt("attributeChanged", [name, oldValue, newValue]); // Plugin event
198
+ switch (name) {
199
+
200
+ case "value":
201
+ this.update(newValue);
202
+ break;
203
+
204
+ case "name":
205
+ if(this.querySelector("textarea") !== null) {
206
+ this.querySelector("textarea").setAttribute("name", newValue); // for use in forms
207
+ }
208
+ break;
209
+
210
+ case "placeholder":
211
+ this.querySelector("textarea").placeholder = newValue;
212
+ break;
213
+ case "template":
214
+ this.template = codeInput.usedTemplates[newValue || codeInput.defaultTemplate];
215
+ if(this.template.preElementStyled) this.classList.add("code-input_pre-element-styled");
216
+ else this.classList.remove("code-input_pre-element-styled");
217
+ // Syntax Highlight
218
+ this.update(this.value);
219
+
220
+ break;
221
+
222
+ case "lang":
223
+
224
+ let code = this.querySelector("pre code");
225
+ let main_textarea = this.querySelector("textarea");
226
+
227
+ // Check not already updated
228
+ if(newValue != null) {
229
+ newValue = newValue.toLowerCase();
230
+
231
+ if(code.classList.contains(`language-${newValue}`)) break; // Already updated
232
+ }
233
+
234
+
235
+ // Case insensitive
236
+ oldValue = oldValue.toLowerCase();
237
+
238
+ // Remove old language class and add new
239
+ console.log("code-input: Language: REMOVE", "language-" + oldValue);
240
+ code.classList.remove("language-" + oldValue); // From CODE
241
+ code.parentElement.classList.remove("language-" + oldValue); // From PRE
242
+ code.classList.remove("language-none"); // Prism
243
+ code.parentElement.classList.remove("language-none"); // Prism
244
+
245
+ if(newValue != undefined && newValue != "") {
246
+ code.classList.add("language-" + newValue);
247
+ console.log("code-input: Language:ADD", "language-" + newValue);
248
+ }
249
+
250
+ if(main_textarea.placeholder == oldValue) main_textarea.placeholder = newValue;
251
+
252
+ this.update(this.value);
253
+
254
+ break;
255
+ }
256
+ }
257
+
258
+ }
259
+
260
+ // /* Transfer an event by name from this to an inner element. */
261
+ // transfer_event(evt_name, transfer_to, oldValue, newValue) {
262
+ // if(oldValue) { // Remove old listener
263
+ // transfer_to.removeEventListener(evt_name, this.last_events[evt_name]);
264
+ // }
265
+ // if(newValue) {
266
+ // this.last_events[evt_name] = this[`on${evt_name}`].bind(this);
267
+ // transfer_to.addEventListener(evt_name, this.last_events[evt_name]);
268
+ // this.removeEventListener(evt_name, newValue);
269
+ // }
270
+ // }
271
+
272
+ /* Override addEventListener so event listener added to necessary child. Returns callback bound to code-input element as `this` */
273
+ addEventListener(evt_name, callback, thirdParameter=null) {
274
+ let boundCallback = callback.bind(this);
275
+ this.bound_callbacks[callback] = boundCallback;
276
+ if(evt_name == "change") {
277
+ if(thirdParameter === null) {
278
+ this.querySelector("textarea").addEventListener("change", boundCallback);
279
+ } else {
280
+ this.querySelector("textarea").addEventListener("change", boundCallback, thirdParameter);
281
+ }
282
+ } else if(evt_name == "selectionchange") {
283
+ if(thirdParameter === null) {
284
+ this.querySelector("textarea").addEventListener("selectionchange", boundCallback);
285
+ } else {
286
+ this.querySelector("textarea").addEventListener("selectionchange", boundCallback, thirdParameter);
287
+ }
288
+ }
289
+ }
290
+
291
+ /* Override removeEventListener so event listener removed from necessary child */
292
+ removeEventListener(evt_name, callback, thirdParameter=null) {
293
+ let boundCallback = this.bound_callbacks[callback];
294
+ if(evt_name == "change") {
295
+ if(thirdParameter === null) {
296
+ this.querySelector("textarea").removeEventListener("change", boundCallback);
297
+ } else {
298
+ this.querySelector("textarea").removeEventListener("change", boundCallback, thirdParameter);
299
+ }
300
+ } else if(evt_name == "selectionchange") {
301
+ if(thirdParameter === null) {
302
+ this.querySelector("textarea").removeEventListener("selectionchange", boundCallback);
303
+ } else {
304
+ this.querySelector("textarea").removeEventListener("selectionchange", boundCallback, thirdParameter);
305
+ }
306
+ }
307
+ }
308
+
309
+ /* Value attribute */
310
+ get value() {
311
+ return this.getAttribute("value");
312
+ }
313
+ set value(val) {
314
+ return this.setAttribute("value", val);
315
+ }
316
+ /* Placeholder attribute */
317
+ get placeholder() {
318
+ return this.getAttribute("placeholder");
319
+ }
320
+ set placeholder(val) {
321
+ return this.setAttribute("placeholder", val);
322
+ }
323
+
324
+ pluginData = {}; // For plugins to store element-specific data under their name, e.g. <code-input>.pluginData.specialChars
325
+ },
326
+
327
+ registerTemplate: function(template_name, template) {
328
+ // Set default class
329
+ codeInput.usedTemplates[template_name] = template;
330
+ // Add elements w/ template from queue
331
+ if(template_name in codeInput.templateQueue) {
332
+ for(let i in codeInput.templateQueue[template_name]) {
333
+ elem = codeInput.templateQueue[template_name][i];
334
+ elem.template = template;
335
+ elem.setup();
336
+ }
337
+ console.log(`code-input: template: Added existing elements with template ${template_name}`);
338
+ }
339
+ if(codeInput.defaultTemplate == undefined) {
340
+ codeInput.defaultTemplate = template_name;
341
+ // Add elements w/ default template from queue
342
+ if(undefined in codeInput.templateQueue) {
343
+ for(let i in codeInput.templateQueue[undefined]) {
344
+ elem = codeInput.templateQueue[undefined][i];
345
+ elem.template = template;
346
+ elem.setup();
347
+ }
348
+ }
349
+ console.log(`code-input: template: Set template ${template_name} as default`);
350
+ }
351
+ console.log(`code-input: template: Created template ${template_name}`);
352
+ },
353
+ templates: {
354
+ custom(highlight=function() {}, preElementStyled=true, isCode=true, includeCodeInputInHighlightFunc=false, plugins=[]) {
355
+ return {
356
+ highlight: highlight,
357
+ includeCodeInputInHighlightFunc: includeCodeInputInHighlightFunc,
358
+ preElementStyled: preElementStyled,
359
+ isCode: isCode,
360
+ plugins: plugins,
361
+ };
362
+ },
363
+ prism(prism, plugins=[]) { // Dependency: Prism.js (https://prismjs.com/)
364
+ return {
365
+ includeCodeInputInHighlightFunc: false,
366
+ highlight: prism.highlightElement,
367
+ preElementStyled: true,
368
+ isCode: true,
369
+ plugins: plugins,
370
+ };
371
+ },
372
+ hljs(hljs, plugins=[]) { // Dependency: Highlight.js (https://highlightjs.org/)
373
+ return {
374
+ includeCodeInputInHighlightFunc: false,
375
+ highlight: hljs.highlightElement,
376
+ preElementStyled: false,
377
+ isCode: true,
378
+ plugins: plugins,
379
+ };
380
+ },
381
+ characterLimit() {
382
+ return {
383
+ highlight: function(result_element, code_input, plugins=[]) {
384
+
385
+ let character_limit = Number(code_input.getAttribute("data-character-limit"));
386
+
387
+ let normal_characters = code_input.escape_html(code_input.value.slice(0, character_limit));
388
+ let overflow_characters = code_input.escape_html(code_input.value.slice(character_limit));
389
+
390
+ result_element.innerHTML = `${normal_characters}<mark class="overflow">${overflow_characters}</mark>`;
391
+ if(overflow_characters.length > 0) {
392
+ result_element.innerHTML += ` <mark class="overflow-msg">${code_input.getAttribute("data-overflow-msg") || "(Character limit reached)"}</mark>`;
393
+ }
394
+ },
395
+ includeCodeInputInHighlightFunc: true,
396
+ preElementStyled: true,
397
+ isCode: false,
398
+ plugins: plugins,
399
+ }
400
+ },
401
+ rainbowText(rainbow_colors=["red", "orangered", "orange", "goldenrod", "gold", "green", "darkgreen", "navy", "blue", "magenta"], delimiter="", plugins=[]) {
402
+ return {
403
+ highlight: function(result_element, code_input) {
404
+ let html_result = [];
405
+ let sections = code_input.value.split(code_input.template.delimiter);
406
+ for (let i = 0; i < sections.length; i++) {
407
+ html_result.push(`<span style="color: ${code_input.template.rainbow_colors[i % code_input.template.rainbow_colors.length]}">${code_input.escape_html(sections[i])}</span>`);
408
+ }
409
+ result_element.innerHTML = html_result.join(code_input.template.delimiter);
410
+ },
411
+ includeCodeInputInHighlightFunc: true,
412
+ preElementStyled: true,
413
+ isCode: false,
414
+ rainbow_colors: rainbow_colors,
415
+ delimiter: delimiter,
416
+ plugins: plugins,
417
+ }
418
+ }
419
+ }
420
+ }
421
+
422
+ customElements.define("code-input", codeInput.CodeInput); // Set tag