1+ // CodeInput
2+ // by WebCoder49
3+ // Based on a CSS-Tricks Post
4+
5+ var codeInput = {
6+ usedTemplates : {
7+ } ,
8+ defaultTemplate : undefined ,
9+ CodeInput : class extends HTMLElement { // Create code input element
10+ constructor ( ) {
11+ super ( ) ; // Element
12+ }
13+
14+ /* Syntax-highlighting functions */
15+ update ( text ) {
16+
17+ if ( this . value != text ) this . value = text ; // Change value attribute if necessary.
18+ if ( this . querySelector ( "textarea" ) . value != text ) this . querySelector ( "textarea" ) . value = text ;
19+
20+
21+ let result_element = this . querySelector ( "pre code" ) ;
22+
23+ // Handle final newlines (see article)
24+ if ( text [ text . length - 1 ] == "\n" ) {
25+ text += " " ;
26+ }
27+ // Update code
28+ result_element . innerHTML = this . escape_html ( text ) ;
29+ // Syntax Highlight
30+ if ( this . template . includeCodeInputInHighlightFunc ) this . template . highlight ( result_element , this ) ;
31+ else this . template . highlight ( result_element ) ;
32+ }
33+
34+ sync_scroll ( ) {
35+ /* Scroll result to scroll coords of event - sync with textarea */
36+ let input_element = this . querySelector ( "textarea" ) ;
37+ let result_element = this . template . preElementStyled ? this . querySelector ( "pre" ) : this . querySelector ( "pre code" ) ;
38+ // Get and set x and y
39+ result_element . scrollTop = input_element . scrollTop ;
40+ result_element . scrollLeft = input_element . scrollLeft ;
41+ }
42+
43+ check_tab ( event ) {
44+ if ( this . template . isCode ) {
45+ let input_element = this . querySelector ( "textarea" ) ;
46+ let code = input_element . value ;
47+ if ( event . key == "Tab" ) {
48+ /* Tab key pressed */
49+ event . preventDefault ( ) ; // stop normal
50+
51+ if ( input_element . selectionStart == input_element . selectionEnd ) {
52+
53+ let before_selection = code . slice ( 0 , input_element . selectionStart ) ; // text before tab
54+ let after_selection = code . slice ( input_element . selectionEnd , input_element . value . length ) ; // text after tab
55+
56+ let cursor_pos = input_element . selectionEnd + 1 ; // where cursor moves after tab - moving forward by 1 char to after tab
57+ input_element . value = before_selection + "\t" + after_selection ; // add tab char
58+
59+ // move cursor
60+ input_element . selectionStart = cursor_pos ;
61+ input_element . selectionEnd = cursor_pos ;
62+
63+ } else {
64+ let lines = input_element . value . split ( "\n" ) ;
65+ let letter_i = 0 ;
66+
67+ let selection_start = input_element . selectionStart ; // where cursor moves after tab - moving forward by 1 indent
68+ let selection_end = input_element . selectionEnd ; // where cursor moves after tab - moving forward by 1 indent
69+
70+ let number_indents = 0 ;
71+ let first_line_indents = 0 ;
72+
73+ for ( let i = 0 ; i < lines . length ; i ++ ) {
74+ letter_i += lines [ i ] . length ;
75+ if ( input_element . selectionStart < letter_i && input_element . selectionEnd > letter_i - lines [ i ] . length ) {
76+ if ( event . shiftKey ) {
77+ if ( lines [ i ] [ 0 ] == "\t" ) {
78+ lines [ i ] = lines [ i ] . slice ( 1 ) ;
79+ if ( number_indents == 0 ) first_line_indents -- ;
80+ number_indents -- ;
81+ }
82+ } else {
83+ lines [ i ] = "\t" + lines [ i ] ;
84+ if ( number_indents == 0 ) first_line_indents ++ ;
85+ number_indents ++ ;
86+ }
87+
88+ }
89+ }
90+ input_element . value = lines . join ( "\n" ) ;
91+
92+ // move cursor
93+ input_element . selectionStart = selection_start + first_line_indents ;
94+ input_element . selectionEnd = selection_end + number_indents ;
95+
96+ }
97+
98+ this . update ( input_element . value ) ;
99+ }
100+ }
101+ }
102+ escape_html ( text ) {
103+ return text . replace ( new RegExp ( "&" , "g" ) , "&" ) . replace ( new RegExp ( "<" , "g" ) , "<" ) ; /* Global RegExp */
104+ }
105+
106+ /* Callbacks */
107+ connectedCallback ( ) {
108+ // Added to document
109+ this . template = codeInput . usedTemplates [ this . getAttribute ( "template" ) || codeInput . defaultTemplate ] ;
110+ if ( this . template . preElementStyled ) this . classList . add ( "code-input_pre-element-styled" ) ;
111+
112+ /* Defaults */
113+ let lang = this . getAttribute ( "lang" ) ;
114+ let placeholder = this . getAttribute ( "placeholder" ) || this . getAttribute ( "lang" ) || "" ;
115+ let value = this . value || this . innerHTML || "" ;
116+
117+ this . innerHTML = "" ; // Clear Content
118+
119+ /* Create Textarea */
120+ let textarea = document . createElement ( "textarea" ) ;
121+ textarea . placeholder = placeholder ;
122+ textarea . value = value ;
123+ textarea . setAttribute ( "spellcheck" , "false" ) ;
124+
125+ if ( this . getAttribute ( "name" ) ) {
126+ textarea . setAttribute ( "name" , this . getAttribute ( "name" ) ) ; // for use in forms
127+ this . removeAttribute ( "name" ) ;
128+ }
129+
130+ textarea . setAttribute ( "oninput" , "this.parentElement.update(this.value); this.parentElement.sync_scroll();" ) ;
131+ textarea . setAttribute ( "onscroll" , "this.parentElement.sync_scroll();" ) ;
132+ textarea . setAttribute ( "onkeydown" , "this.parentElement.check_tab(event);" ) ;
133+
134+ this . append ( textarea ) ;
135+
136+ /* Create pre code */
137+ let code = document . createElement ( "code" ) ;
138+ if ( this . template . isCode && lang != null ) code . classList . add ( "language-" + lang ) ;
139+ code . innerText = value ;
140+
141+ let pre = document . createElement ( "pre" ) ;
142+ pre . setAttribute ( "aria-hidden" , "true" ) ; // Hide for screen readers
143+ pre . append ( code ) ;
144+ this . append ( pre ) ;
145+
146+ /* Add code from value attribute - useful for loading from backend */
147+ this . update ( value , this ) ;
148+ }
149+ static get observedAttributes ( ) {
150+ return [ "value" , "placeholder" , "lang" , "template" ] ; // Attributes to monitor
151+ }
152+ attributeChangedCallback ( name , oldValue , newValue ) {
153+
154+ switch ( name ) {
155+
156+ case "value" :
157+
158+ // Update code
159+ this . update ( newValue ) ;
160+
161+ break ;
162+
163+ case "placeholder" :
164+ this . querySelector ( "textarea" ) . placeholder = newValue ;
165+ break ;
166+ case "template" :
167+ this . template = codeInput . usedTemplates [ newValue || codeInput . defaultTemplate ] ;
168+ if ( this . template . preElementStyled ) this . classList . add ( "code-input_pre-element-styled" ) ;
169+ else this . classList . remove ( "code-input_pre-element-styled" ) ;
170+ // Syntax Highlight
171+ this . update ( this . value ) ;
172+
173+ case "lang" :
174+ let code = this . querySelector ( "pre code" ) ;
175+ let textarea = this . querySelector ( "textarea" ) ;
176+
177+ if ( newValue != null ) code . className = ( "language-" + newValue ) ;
178+ else code . className = "" ;
179+
180+ if ( textarea . placeholder == oldValue ) textarea . placeholder = newValue
181+
182+ this . update ( this . value ) ;
183+ }
184+ }
185+
186+ /* Value attribute */
187+ get value ( ) {
188+ return this . getAttribute ( "value" ) ;
189+ }
190+ set value ( val ) {
191+ return this . setAttribute ( "value" , val ) ;
192+ }
193+ /* Placeholder attribute */
194+ get placeholder ( ) {
195+ return this . getAttribute ( "placeholder" ) ;
196+ }
197+ set placeholder ( val ) {
198+ return this . setAttribute ( "placeholder" , val ) ;
199+ }
200+ } ,
201+ registerTemplate : function ( template_name , template ) {
202+ // Set default class
203+ codeInput . usedTemplates [ template_name ] = template ;
204+ codeInput . defaultTemplate = template_name ;
205+ } ,
206+ templates : {
207+ custom ( highlight = function ( ) { } , preElementStyled = true , isCode = true , includeCodeInputInHighlightFunc = false ) {
208+ return {
209+ highlight : highlight ,
210+ includeCodeInputInHighlightFunc : includeCodeInputInHighlightFunc ,
211+ preElementStyled : preElementStyled ,
212+ isCode : isCode ,
213+ } ;
214+ } ,
215+ prism ( prism ) { // Dependency: Prism.js (https://prismjs.com/)
216+ return {
217+ includeCodeInputInHighlightFunc : false ,
218+ highlight : prism . highlightElement ,
219+ preElementStyled : true ,
220+ isCode : true
221+ } ;
222+ } ,
223+ hljs ( hljs ) { // Dependency: Highlight.js (https://highlightjs.org/)
224+ return {
225+ includeCodeInputInHighlightFunc : false ,
226+ highlight : hljs . highlightElement ,
227+ preElementStyled : false ,
228+ isCode : true
229+ } ;
230+ } ,
231+ characterLimit ( ) {
232+ return {
233+ highlight : function ( result_element , code_input ) {
234+
235+ let character_limit = Number ( code_input . getAttribute ( "data-character-limit" ) ) ;
236+
237+ let normal_characters = code_input . escape_html ( code_input . value . slice ( 0 , character_limit ) ) ;
238+ let overflow_characters = code_input . escape_html ( code_input . value . slice ( character_limit ) ) ;
239+
240+ result_element . innerHTML = `${ normal_characters } <mark class="overflow">${ overflow_characters } </mark>` ;
241+ if ( overflow_characters . length > 0 ) {
242+ result_element . innerHTML += ` <mark class="overflow-msg">${ code_input . getAttribute ( "data-overflow-msg" ) || "(Character limit reached)" } </mark>` ;
243+ }
244+ } ,
245+ preElementStyled : true ,
246+ isCode : false
247+ }
248+ } ,
249+ rainbowText ( rainbow_colors = [ "red" , "orangered" , "orange" , "goldenrod" , "gold" , "green" , "darkgreen" , "navy" , "blue" , "magenta" ] , delimiter = "" ) {
250+ return {
251+ highlight : function ( result_element , code_input ) {
252+ let html_result = [ ] ;
253+ let sections = code_input . value . split ( code_input . template . delimiter ) ;
254+ for ( let i = 0 ; i < sections . length ; i ++ ) {
255+ 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>` ) ;
256+ }
257+ result_element . innerHTML = html_result . join ( code_input . template . delimiter ) ;
258+ } ,
259+ preElementStyled : true ,
260+ isCode : false ,
261+ rainbow_colors : rainbow_colors ,
262+ delimiter : delimiter
263+ }
264+ }
265+ }
266+ }
267+
268+ customElements . define ( "code-input" , codeInput . CodeInput ) ; // Set tag
0 commit comments