Skip to content

Commit d8f6f5c

Browse files
authored
Merge pull request #10 from WebCoder49/add-plugins
Add plugins
2 parents ab30c56 + a69b837 commit d8f6f5c

File tree

4 files changed

+261
-141
lines changed

4 files changed

+261
-141
lines changed

code-input.js

Lines changed: 60 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,44 @@ var codeInput = {
77
},
88
defaultTemplate: undefined,
99
templateQueue: {}, // lists of elements for each unrecognised template
10+
plugins: { // Import a plugin from the plugins folder and it will be saved here.
11+
},
12+
Plugin: class {
13+
/* Runs before code is highlighted; Params: codeInput element) */
14+
beforeHighlight(codeInput) {}
15+
/* Runs after code is highlighted; Params: codeInput element) */
16+
afterHighlight(codeInput) {}
17+
/* Runs before elements are added into a `code-input`; Params: codeInput element) */
18+
beforeElementsAdded(codeInput) {}
19+
/* Runs after elements are added into a `code-input` (useful for adding events to the textarea); Params: codeInput element) */
20+
afterElementsAdded(codeInput) {}
21+
/* 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) */
22+
attributeChanged(codeInput, name, oldValue, newValue) {}
23+
observedAttributes = []
24+
},
1025
CodeInput: class extends HTMLElement { // Create code input element
1126
constructor() {
1227
super(); // Element
1328
}
1429

30+
31+
/* Run this event in all plugins with a optional list of arguments */
32+
plugin_evt(id, args) {
33+
// Run the event `id` in each plugin
34+
for (let i in this.template.plugins) {
35+
let plugin = this.template.plugins[i];
36+
if (id in plugin) {
37+
if(args === undefined) {
38+
plugin[id](this);
39+
} else {
40+
plugin[id](this, ...args);
41+
}
42+
}
43+
}
44+
}
45+
1546
/* Syntax-highlighting functions */
1647
update(text) {
17-
1848
if(this.value != text) this.value = text; // Change value attribute if necessary.
1949
if(this.querySelector("textarea").value != text) this.querySelector("textarea").value = text;
2050

@@ -27,13 +57,13 @@ var codeInput = {
2757
}
2858
// Update code
2959
result_element.innerHTML = this.escape_html(text);
30-
if(this.autodetect) { // Autodetection
31-
result_element.className = ""; // CODE
32-
result_element.parentElement.className = ""; // PRE
33-
}
60+
this.plugin_evt("beforeHighlight");
61+
3462
// Syntax Highlight
3563
if(this.template.includeCodeInputInHighlightFunc) this.template.highlight(result_element, this);
3664
else this.template.highlight(result_element);
65+
66+
this.plugin_evt("afterHighlight");
3767
}
3868

3969
sync_scroll() {
@@ -45,127 +75,6 @@ var codeInput = {
4575
result_element.scrollLeft = input_element.scrollLeft;
4676
}
4777

48-
check_tab(event) {
49-
if(event.key != "Tab" || !this.template.isCode) {
50-
return;
51-
}
52-
let input_element = this.querySelector("textarea");
53-
let code = input_element.value;
54-
event.preventDefault(); // stop normal
55-
56-
if(!event.shiftKey && input_element.selectionStart == input_element.selectionEnd) {
57-
// Shift always means dedent - this places a tab here.
58-
let before_selection = code.slice(0, input_element.selectionStart); // text before tab
59-
let after_selection = code.slice(input_element.selectionEnd, input_element.value.length); // text after tab
60-
61-
let cursor_pos = input_element.selectionEnd + 1; // where cursor moves after tab - moving forward by 1 char to after tab
62-
input_element.value = before_selection + "\t" + after_selection; // add tab char
63-
64-
// move cursor
65-
input_element.selectionStart = cursor_pos;
66-
input_element.selectionEnd = cursor_pos;
67-
68-
} else {
69-
let lines = input_element.value.split("\n");
70-
let letter_i = 0;
71-
72-
let selection_start = input_element.selectionStart; // where cursor moves after tab - moving forward by 1 indent
73-
let selection_end = input_element.selectionEnd; // where cursor moves after tab - moving forward by 1 indent
74-
75-
let number_indents = 0;
76-
let first_line_indents = 0;
77-
78-
for (let i = 0; i < lines.length; i++) {
79-
letter_i += lines[i].length+1; // newline counted
80-
81-
console.log(lines[i], ": start", input_element.selectionStart, letter_i, "&& end", input_element.selectionEnd , letter_i - lines[i].length)
82-
if(input_element.selectionStart <= letter_i && input_element.selectionEnd >= letter_i - lines[i].length) {
83-
// Starts before or at last char and ends after or at first char
84-
if(event.shiftKey) {
85-
if(lines[i][0] == "\t") {
86-
// Remove first tab
87-
lines[i] = lines[i].slice(1);
88-
if(number_indents == 0) first_line_indents--;
89-
number_indents--;
90-
}
91-
} else {
92-
lines[i] = "\t" + lines[i];
93-
if(number_indents == 0) first_line_indents++;
94-
number_indents++;
95-
}
96-
97-
}
98-
}
99-
input_element.value = lines.join("\n");
100-
101-
// move cursor
102-
input_element.selectionStart = selection_start + first_line_indents;
103-
input_element.selectionEnd = selection_end + number_indents;
104-
}
105-
106-
this.update(input_element.value);
107-
}
108-
109-
check_enter(event) {
110-
if(event.key != "Enter" || !this.template.isCode) {
111-
return;
112-
}
113-
event.preventDefault(); // stop normal
114-
115-
let input_element = this.querySelector("textarea");
116-
let lines = input_element.value.split("\n");
117-
let letter_i = 0;
118-
let current_line = lines.length - 1;
119-
let new_line = "";
120-
let number_indents = 0;
121-
122-
// find the index of the line our cursor is currently on
123-
for (let i = 0; i < lines.length; i++) {
124-
letter_i += lines[i].length + 1;
125-
if(input_element.selectionEnd <= letter_i) {
126-
current_line = i;
127-
break;
128-
}
129-
}
130-
131-
// count the number of indents the current line starts with (up to our cursor position in the line)
132-
let cursor_pos_in_line = lines[current_line].length - (letter_i - input_element.selectionEnd) + 1;
133-
for (let i = 0; i < cursor_pos_in_line; i++) {
134-
if (lines[current_line][i] == "\t") {
135-
number_indents++;
136-
} else {
137-
break;
138-
}
139-
}
140-
141-
// determine the text before and after the cursor and chop the current line at the new line break
142-
let text_after_cursor = "";
143-
if (cursor_pos_in_line != lines[current_line].length) {
144-
text_after_cursor = lines[current_line].substring(cursor_pos_in_line);
145-
lines[current_line] = lines[current_line].substring(0, cursor_pos_in_line);
146-
}
147-
148-
// insert our indents and any text from the previous line that might have been after the line break
149-
for (let i = 0; i < number_indents; i++) {
150-
new_line += "\t";
151-
}
152-
new_line += text_after_cursor;
153-
154-
// save the current cursor position
155-
let selection_start = input_element.selectionStart;
156-
let selection_end = input_element.selectionEnd;
157-
158-
// splice our new line into the list of existing lines and join them all back up
159-
lines.splice(current_line + 1, 0, new_line);
160-
input_element.value = lines.join("\n");
161-
162-
// move cursor to new position
163-
input_element.selectionStart = selection_start + number_indents + 1; // count the indent level and the newline character
164-
input_element.selectionEnd = selection_end + number_indents + 1;
165-
166-
this.update(input_element.value);
167-
}
168-
16978
escape_html(text) {
17079
return text.replace(new RegExp("&", "g"), "&amp;").replace(new RegExp("<", "g"), "&lt;"); /* Global RegExp */
17180
}
@@ -197,7 +106,9 @@ var codeInput = {
197106
setup() {
198107
this.classList.add("code-input_registered"); // Remove register message
199108
if(this.template.preElementStyled) this.classList.add("code-input_pre-element-styled");
200-
109+
110+
this.plugin_evt("beforeElementsAdded");
111+
201112
/* Defaults */
202113
let lang = this.getAttribute("lang");
203114
let placeholder = this.getAttribute("placeholder") || this.getAttribute("lang") || "";
@@ -218,10 +129,8 @@ var codeInput = {
218129

219130
textarea.setAttribute("oninput", "this.parentElement.update(this.value); this.parentElement.sync_scroll();");
220131
textarea.setAttribute("onscroll", "this.parentElement.sync_scroll();");
221-
textarea.setAttribute("onkeydown", "this.parentElement.check_tab(event); this.parentElement.check_enter(event);");
222-
223132
this.append(textarea);
224-
133+
225134
/* Create pre code */
226135
let code = document.createElement("code");
227136
let pre = document.createElement("pre");
@@ -233,22 +142,30 @@ var codeInput = {
233142
if(lang != undefined && lang != "") {
234143
code.classList.add("language-" + lang);
235144
}
236-
else this.autodetect = true // No lang attribute
237145
}
238146

147+
this.plugin_evt("afterElementsAdded");
148+
239149
/* Add code from value attribute - useful for loading from backend */
240150
this.update(value, this);
241151
}
242-
152+
243153
/* Callbacks */
244154
connectedCallback() {
245155
// Added to document
246156
this.template = this.get_template();
247157
if(this.template != undefined) this.setup();
248158
}
249159
static get observedAttributes() {
250-
return ["value", "placeholder", "lang", "template"]; // Attributes to monitor
160+
let attrs = ["value", "placeholder", "lang", "template"]; // Attributes to monitor
161+
162+
/* Add from plugins */
163+
for (let plugin in this.template.plugins) {
164+
attrs = attrs.concat(plugin.observedAttributes);
165+
}
166+
return attrs;
251167
}
168+
252169
attributeChangedCallback(name, oldValue, newValue) {
253170
if(this.isConnected) {
254171
// This will sometimes be called before the element has been created, so trying to update an attribute causes an error.
@@ -290,14 +207,14 @@ var codeInput = {
290207
if(newValue != undefined && newValue != "") {
291208
code.classList.add("language-" + newValue);
292209
console.log("ADD", "language-" + newValue);
293-
} else {
294-
// Autodetect - works with HLJS
295-
this.autodetect = true;
296210
}
297211

298212
if(textarea.placeholder == oldValue) textarea.placeholder = newValue;
299213

300214
this.update(this.value);
215+
216+
default:
217+
this.plugin_evt("attributeChanged", [name, oldValue, newValue]); // Plugin event
301218
}
302219
}
303220

@@ -342,28 +259,30 @@ var codeInput = {
342259
}
343260
},
344261
templates: {
345-
custom(highlight=function() {}, preElementStyled=true, isCode=true, includeCodeInputInHighlightFunc=false) {
262+
custom(highlight=function() {}, preElementStyled=true, isCode=true, includeCodeInputInHighlightFunc=false, plugins=[]) {
346263
return {
347264
highlight: highlight,
348265
includeCodeInputInHighlightFunc: includeCodeInputInHighlightFunc,
349266
preElementStyled: preElementStyled,
350267
isCode: isCode,
351268
};
352269
},
353-
prism(prism) { // Dependency: Prism.js (https://prismjs.com/)
270+
prism(prism, plugins=[]) { // Dependency: Prism.js (https://prismjs.com/)
354271
return {
355272
includeCodeInputInHighlightFunc: false,
356273
highlight: prism.highlightElement,
357274
preElementStyled: true,
358-
isCode: true
275+
isCode: true,
276+
plugins: plugins,
359277
};
360278
},
361-
hljs(hljs) { // Dependency: Highlight.js (https://highlightjs.org/)
279+
hljs(hljs, plugins=[]) { // Dependency: Highlight.js (https://highlightjs.org/)
362280
return {
363281
includeCodeInputInHighlightFunc: false,
364282
highlight: hljs.highlightElement,
365283
preElementStyled: false,
366-
isCode: true
284+
isCode: true,
285+
plugins: plugins,
367286
};
368287
},
369288
characterLimit() {

plugins/autodetect.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Autodetect the language live and change the `lang` attribute using the syntax highlighter's
3+
* autodetect capabilities. Works with highlight.js.
4+
*/
5+
codeInput.plugins.Autodetect = class extends codeInput.Plugin {
6+
/* Remove previous language class */
7+
beforeHighlight(codeInput) {
8+
let result_element = codeInput.querySelector("pre code");
9+
result_element.className = ""; // CODE
10+
result_element.parentElement.className = ""; // PRE
11+
}
12+
/* Get new language class and set `lang` attribute */
13+
afterHighlight(codeInput) {
14+
let result_element = codeInput.querySelector("pre code");
15+
let lang_class = result_element.className || result_element.parentElement.className;
16+
let lang = lang_class.match(/lang(\w|-)*/i)[0]; // Get word starting with lang...; Get outer bracket
17+
lang = lang.split("-")[1];
18+
if(lang == "undefined") {
19+
codeInput.removeAttribute("lang");
20+
} else {
21+
codeInput.setAttribute("lang", lang);
22+
}
23+
}
24+
observedAttributes = []
25+
}

0 commit comments

Comments
 (0)