@@ -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" ) , "&" ) . replace ( new RegExp ( "<" , "g" ) , "<" ) ; /* 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 ( ) {
0 commit comments