@webcoder49/code-input 2.1.0 → 2.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.
- package/CONTRIBUTING.md +11 -1
- package/README.md +26 -11
- package/code-input.css +126 -29
- package/code-input.d.ts +153 -11
- package/code-input.js +218 -193
- package/code-input.min.css +1 -1
- package/code-input.min.js +1 -1
- package/package.json +1 -1
- package/plugins/README.md +28 -6
- package/plugins/auto-close-brackets.js +61 -0
- package/plugins/auto-close-brackets.min.js +1 -0
- package/plugins/autocomplete.js +21 -12
- package/plugins/autocomplete.min.js +1 -1
- package/plugins/autodetect.js +4 -4
- package/plugins/autodetect.min.js +1 -1
- package/plugins/find-and-replace.css +145 -0
- package/plugins/find-and-replace.js +746 -0
- package/plugins/find-and-replace.min.css +1 -0
- package/plugins/find-and-replace.min.js +1 -0
- package/plugins/go-to-line.css +77 -0
- package/plugins/go-to-line.js +175 -0
- package/plugins/go-to-line.min.css +1 -0
- package/plugins/go-to-line.min.js +1 -0
- package/plugins/indent.js +166 -15
- package/plugins/indent.min.js +1 -1
- package/plugins/prism-line-numbers.css +10 -9
- package/plugins/prism-line-numbers.min.css +1 -1
- package/plugins/select-token-callbacks.js +289 -0
- package/plugins/select-token-callbacks.min.js +1 -0
- package/plugins/special-chars.css +1 -5
- package/plugins/special-chars.js +65 -61
- package/plugins/special-chars.min.css +2 -2
- package/plugins/special-chars.min.js +1 -1
- package/plugins/test.js +1 -2
- package/plugins/test.min.js +1 -1
- package/tests/hljs.html +55 -0
- package/tests/i18n.html +197 -0
- package/tests/prism-match-braces-compatibility.js +215 -0
- package/tests/prism-match-braces-compatibility.min.js +1 -0
- package/tests/prism.html +54 -0
- package/tests/tester.js +593 -0
- package/tests/tester.min.js +21 -0
- package/plugins/debounce-update.js +0 -40
- package/plugins/debounce-update.min.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.code-input_find-and-replace_find-match{color:inherit;text-shadow:none!important;background-color:#ff0!important}.code-input_find-and-replace_find-match-focused,.code-input_find-and-replace_find-match-focused *{background-color:#f80!important;color:#000!important}.code-input_find-and-replace_start-newline::before{content:"⤶"}@keyframes code-input_find-and-replace_roll-in{0%{opacity:0;transform:translateY(-34px)}100%{opacity:1;transform:translateY(0)}}@keyframes code-input_find-and-replace_roll-out{0%{opacity:1;top:0}100%{opacity:0;top:-34px}}.code-input_find-and-replace_dialog{position:absolute;top:0;right:14px;padding:6px;padding-top:8px;border:solid 1px #00000044;background-color:#fff;border-radius:6px;box-shadow:0 .2em 1em .2em rgba(0,0,0,.16)}.code-input_find-and-replace_dialog:not(.code-input_find-and-replace_hidden-dialog){animation:code-input_find-and-replace_roll-in .2s;opacity:1;pointer-events:all}.code-input_find-and-replace_dialog.code-input_find-and-replace_hidden-dialog{animation:code-input_find-and-replace_roll-out .2s;opacity:0;pointer-events:none}.code-input_find-and-replace_dialog input::placeholder{font-size:80%}.code-input_find-and-replace_dialog input{position:relative;width:240px;height:32px;top:-3px;font-size:large;color:#000000aa;border:0}.code-input_find-and-replace_dialog input:hover{outline:0}.code-input_find-and-replace_dialog input.code-input_find-and-replace_error{color:#ff0000aa}.code-input_find-and-replace_dialog button,.code-input_find-and-replace_dialog input[type=checkbox]{display:inline-block;line-height:24px;font-size:22px;cursor:pointer;appearance:none;width:min-content;margin:5px;padding:5px;border:0;background-color:#ddd;text-align:center;color:#000;vertical-align:top}.code-input_find-and-replace_dialog input[type=checkbox].code-input_find-and-replace_case-sensitive-checkbox::before{content:"Aa"}.code-input_find-and-replace_dialog input[type=checkbox].code-input_find-and-replace_reg-exp-checkbox::before{content:".*"}.code-input_find-and-replace_dialog button:hover,.code-input_find-and-replace_dialog input[type=checkbox]:hover{background-color:#bbb}.code-input_find-and-replace_dialog input[type=checkbox]:checked{background-color:#222;color:#fff}.code-input_find-and-replace_match-description{display:block;color:#444}.code-input_find-and-replace_dialog button,.code-input_find-and-replace_dialog details summary{cursor:pointer}.code-input_find-and-replace_dialog button.code-input_find-and-replace_button-hidden{opacity:0;pointer-events:none}.code-input_find-and-replace_dialog span{display:block;float:right;margin:5px;padding:5px;width:24px;line-height:24px;font-family:system-ui;font-size:22px;font-weight:500;text-align:center;border-radius:50%;color:#000;opacity:.6}.code-input_find-and-replace_dialog span:before{content:"\00d7"}.code-input_find-and-replace_dialog span:hover{opacity:.8;background-color:#00000018}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
codeInput.plugins.FindAndReplace=class extends codeInput.Plugin{useCtrlF=!1;useCtrlH=!1;findMatchesOnValueChange=!0;instructions={start:"Search for matches in your code.",none:"No matches",oneFound:"1 match found.",matchIndex:(a,b)=>`${a} of ${b} matches.`,error:a=>`Error: ${a}`,infiniteLoopError:"Causes an infinite loop",closeDialog:"Close Dialog and Return to Editor",findPlaceholder:"Find",findCaseSensitive:"Match Case Sensitive",findRegExp:"Use JavaScript Regular Expression",replaceTitle:"Replace",replacePlaceholder:"Replace with",findNext:"Find Next Occurrence",findPrevious:"Find Previous Occurrence",replaceActionShort:"Replace",replaceAction:"Replace This Occurrence",replaceAllActionShort:"Replace All",replaceAllAction:"Replace All Occurrences"};constructor(a=!0,b=!0,c={}){super([]),this.useCtrlF=a,this.useCtrlH=b,this.addTranslations(this.instructions,c)}afterElementsAdded(a){const b=a.textareaElement;this.useCtrlF&&b.addEventListener("keydown",b=>{this.checkCtrlF(a,b)}),this.useCtrlH&&b.addEventListener("keydown",b=>{this.checkCtrlH(a,b)})}afterHighlight(a){a.pluginData.findAndReplace==null||a.pluginData.findAndReplace.dialog==null||a.pluginData.findAndReplace.dialog.classList.contains("code-input_find-and-replace_hidden-dialog")||(a.pluginData.findAndReplace.dialog.findMatchState.rehighlightMatches(),this.updateMatchDescription(a.pluginData.findAndReplace.dialog),0==a.pluginData.findAndReplace.dialog.findMatchState.numMatches&&a.pluginData.findAndReplace.dialog.findInput.classList.add("code-input_find-and-replace_error"))}text2RegExp(a,b,c){return new RegExp(c?a:a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),b?"g":"gi")}updateMatchDescription(a){a.matchDescription.textContent=0==a.findInput.value.length?this.instructions.start:0>=a.findMatchState.numMatches?this.instructions.none:1==a.findMatchState.numMatches?this.instructions.oneFound:this.instructions.matchIndex(a.findMatchState.focusedMatchID+1,a.findMatchState.numMatches)}updateFindMatches(a){let b=a.findInput.value;setTimeout(()=>{if(b==a.findInput.value){if(a.findMatchState.clearMatches(),0<b.length){try{a.findMatchState.updateMatches(this.text2RegExp(a.findInput.value,a.findCaseSensitiveCheckbox.checked,a.findRegExpCheckbox.checked))}catch(b){if(b instanceof SyntaxError){a.findInput.classList.add("code-input_find-and-replace_error");let c=b.message.split(": ");return void(a.matchDescription.textContent=this.instructions.error(c[c.length-1]))}throw b}0<a.findMatchState.numMatches?a.findInput.classList.remove("code-input_find-and-replace_error"):a.findInput.classList.add("code-input_find-and-replace_error")}this.updateMatchDescription(a)}},100)}checkFindPrompt(a,b,c){"Enter"==c.key&&(a.findMatchState.nextMatch(),this.updateMatchDescription(a))}checkReplacePrompt(a,b,c){"Enter"==c.key&&(a.findMatchState.replaceOnce(a.replaceInput.value),a.replaceInput.focus(),this.updateMatchDescription(a))}cancelPrompt(a,b,c){c.preventDefault(),this.findMatchesOnValueChange=!1,a.findInput.focus(),a.findInput.selectionStart=0,a.findInput.selectionEnd=a.findInput.value.length,document.execCommand("insertText",!1,a.findInput.value),this.findMatchesOnValueChange=!0,a.textarea.focus(),a.setAttribute("inert",!0),a.setAttribute("tabindex",-1),a.setAttribute("aria-hidden",!0),0<a.findMatchState.numMatches?(b.textareaElement.selectionStart=a.findMatchState.matchStartIndexes[a.findMatchState.focusedMatchID],b.textareaElement.selectionEnd=a.findMatchState.matchEndIndexes[a.findMatchState.focusedMatchID]):(b.textareaElement.selectionStart=a.selectionStart,b.textareaElement.selectionEnd=a.selectionEnd),a.findMatchState.clearMatches(),a.classList.add("code-input_find-and-replace_hidden-dialog")}showPrompt(a,b){let c;if(null==a.pluginData.findAndReplace||null==a.pluginData.findAndReplace.dialog){const d=a.textareaElement;c=document.createElement("div");const e=document.createElement("input"),f=document.createElement("input"),g=document.createElement("input"),h=document.createElement("code");h.setAttribute("aria-live","assertive");const i=document.createElement("input"),j=document.createElement("details"),k=document.createElement("summary"),l=document.createElement("div"),m=document.createElement("button"),n=document.createElement("button"),o=document.createElement("button"),p=document.createElement("button"),q=document.createElement("span");if(q.setAttribute("tabindex",0),q.setAttribute("title",this.instructions.closeDialog),l.appendChild(m),l.appendChild(n),l.appendChild(o),l.appendChild(p),l.appendChild(q),c.appendChild(l),c.appendChild(e),c.appendChild(g),c.appendChild(f),c.appendChild(h),j.appendChild(k),j.appendChild(i),c.appendChild(j),c.className="code-input_find-and-replace_dialog",e.spellcheck=!1,e.placeholder=this.instructions.findPlaceholder,f.setAttribute("type","checkbox"),f.title=this.instructions.findCaseSensitive,f.classList.add("code-input_find-and-replace_case-sensitive-checkbox"),g.setAttribute("type","checkbox"),g.title=this.instructions.findRegExp,g.classList.add("code-input_find-and-replace_reg-exp-checkbox"),h.textContent="Search for matches in your code.",h.classList.add("code-input_find-and-replace_match-description"),k.innerText=this.instructions.replaceTitle,i.spellcheck=!1,i.placeholder=this.instructions.replacePlaceholder,m.innerText="\u2193",m.title=this.instructions.findNext,n.innerText="\u2191",n.title=this.instructions.findPrevious,o.className="code-input_find-and-replace_button-hidden",o.innerText=this.instructions.replaceActionShort,o.title=this.instructions.replaceAction,o.addEventListener("focus",()=>{j.setAttribute("open",!0)}),p.className="code-input_find-and-replace_button-hidden",p.innerText=this.instructions.replaceAllActionShort,p.title=this.instructions.replaceAllAction,p.addEventListener("focus",()=>{j.setAttribute("open",!0)}),m.addEventListener("click",a=>{a.preventDefault(),c.findMatchState.nextMatch(),this.updateMatchDescription(c)}),n.addEventListener("click",()=>{event.preventDefault(),c.findMatchState.previousMatch(),this.updateMatchDescription(c)}),o.addEventListener("click",a=>{a.preventDefault(),c.findMatchState.replaceOnce(i.value),c.focus()}),p.addEventListener("click",a=>{a.preventDefault(),c.findMatchState.replaceAll(i.value),p.focus()}),j.addEventListener("toggle",()=>{o.classList.toggle("code-input_find-and-replace_button-hidden"),p.classList.toggle("code-input_find-and-replace_button-hidden")}),c.findMatchState=new codeInput.plugins.FindAndReplace.FindMatchState(a),c.codeInput=a,c.textarea=d,c.findInput=e,c.findCaseSensitiveCheckbox=f,c.findRegExpCheckbox=g,c.matchDescription=h,c.replaceInput=i,c.replaceDropdown=j,this.checkCtrlH&&e.addEventListener("keydown",a=>{a.ctrlKey&&"h"==a.key&&(a.preventDefault(),j.setAttribute("open",!0))}),e.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),i.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),i.addEventListener("input",()=>{c.classList.contains("code-input_find-and-replace_hidden-dialog")?this.showPrompt(c.codeInput,!0):!c.replaceDropdown.hasAttribute("open")&&c.replaceDropdown.setAttribute("open",!0)}),c.addEventListener("keyup",b=>{"Escape"==b.key&&this.cancelPrompt(c,a,b)}),e.addEventListener("keyup",b=>{this.checkFindPrompt(c,a,b)}),e.addEventListener("input",()=>{this.findMatchesOnValueChange&&this.updateFindMatches(c),c.classList.contains("code-input_find-and-replace_hidden-dialog")&&this.showPrompt(c.codeInput,!1)}),f.addEventListener("click",()=>{this.updateFindMatches(c)}),g.addEventListener("click",()=>{this.updateFindMatches(c)}),i.addEventListener("keyup",b=>{this.checkReplacePrompt(c,a,b),i.focus()}),q.addEventListener("click",b=>{this.cancelPrompt(c,a,b)}),q.addEventListener("keypress",b=>{("Space"==b.key||"Enter"==b.key)&&this.cancelPrompt(c,a,b)}),a.dialogContainerElement.appendChild(c),a.pluginData.findAndReplace={dialog:c},e.focus(),b&&j.setAttribute("open",!0),c.selectionStart=a.textareaElement.selectionStart,c.selectionEnd=a.textareaElement.selectionEnd,c.selectionStart<c.selectionEnd){let b=a.textareaElement.value.substring(c.selectionStart,c.selectionEnd);c.findInput.focus(),c.findInput.selectionStart=0,c.findInput.selectionEnd=c.findInput.value.length,document.execCommand("insertText",!1,b)}}else c=a.pluginData.findAndReplace.dialog,c.classList.remove("code-input_find-and-replace_hidden-dialog"),c.removeAttribute("inert"),c.setAttribute("tabindex",0),c.removeAttribute("aria-hidden"),c.findInput.focus(),b?c.replaceDropdown.setAttribute("open",!0):c.replaceDropdown.removeAttribute("open");if(c.selectionStart=a.textareaElement.selectionStart,c.selectionEnd=a.textareaElement.selectionEnd,c.selectionStart<c.selectionEnd){let b=a.textareaElement.value.substring(c.selectionStart,c.selectionEnd);c.findInput.focus(),c.findInput.selectionStart=0,c.findInput.selectionEnd=c.findInput.value.length,document.execCommand("insertText",!1,b)}this.updateFindMatches(c)}checkCtrlF(a,b){b.ctrlKey&&"f"==b.key&&(b.preventDefault(),this.showPrompt(a,!1))}checkCtrlH(a,b){b.ctrlKey&&"h"==b.key&&(b.preventDefault(),this.showPrompt(a,!0))}};const CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE=500;codeInput.plugins.FindAndReplace.FindMatchState=class{codeInput=null;lastValue=null;lastSearchRegexp=null;numMatches=0;focusedMatchID=0;matchStartIndexes=[];matchEndIndexes=[];focusedMatchStartIndex=0;matchBlocksHighlighted=[];constructor(a){this.focusedMatchStartIndex=a.textareaElement.selectionStart,this.codeInput=a}clearMatches(){this.numMatches=0,this.matchStartIndexes=[],this.matchEndIndexes=[];let a=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_temporary-span");for(let b=0;b<a.length;b++)a[b].parentElement.replaceChild(new Text(a[b].textContent),a[b]);let b=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_find-match");for(let a=0;a<b.length;a++)b[a].removeAttribute("data-code-input_find-and-replace_match-id"),b[a].classList.remove("code-input_find-and-replace_find-match"),b[a].classList.remove("code-input_find-and-replace_find-match-focused")}updateMatches(a){var b=Math.floor;this.lastSearchRegexp=a,this.lastValue=this.codeInput.value;let c,d=0;this.matchStartIndexes=[],this.matchEndIndexes=[],this.matchBlocksHighlighted=[];let e=b(this.focusedMatchID/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);for(let b=0;b<e;b++)this.matchBlocksHighlighted.push(!1);for(this.matchBlocksHighlighted.push(!0);null!==(c=a.exec(this.codeInput.value));){let a=c[0];if(0==a.length)throw SyntaxError(this.instructions.infiniteLoopError);let e=b(d/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);this.matchBlocksHighlighted.length<e&&this.matchBlocksHighlighted.push(!1),this.matchBlocksHighlighted[e]&&this.highlightMatch(d,this.codeInput.codeElement,c.index,c.index+a.length),this.matchStartIndexes.push(c.index),this.matchEndIndexes.push(c.index+a.length),d++}this.numMatches=d,0<this.numMatches&&this.focusMatch()}rehighlightMatches(){this.updateMatches(this.lastSearchRegexp),this.focusMatch()}replaceOnce(a){0<this.numMatches&&a!=this.codeInput.value.substring(0,this.matchStartIndexes[this.focusedMatchID],this.matchEndIndexes[this.focusedMatchID])&&(this.focusedMatchStartIndex+=a.length,this.codeInput.handleEventsFromTextarea=!1,this.codeInput.textareaElement.focus(),this.codeInput.textareaElement.selectionStart=this.matchStartIndexes[this.focusedMatchID],this.codeInput.textareaElement.selectionEnd=this.matchEndIndexes[this.focusedMatchID],document.execCommand("insertText",!1,a),this.codeInput.handleEventsFromTextarea=!0)}replaceAll(a){const b=a.length;let c=0;for(let d=0;d<this.numMatches;d++)this.codeInput.handleEventsFromTextarea=!1,this.codeInput.textareaElement.focus(),this.codeInput.textareaElement.selectionStart=this.matchStartIndexes[d]+c,this.codeInput.textareaElement.selectionEnd=this.matchEndIndexes[d]+c,c+=b-(this.matchEndIndexes[d]-this.matchStartIndexes[d]),document.execCommand("insertText",!1,a),this.codeInput.handleEventsFromTextarea=!0}nextMatch(){this.focusMatch((this.focusedMatchID+1)%this.numMatches)}previousMatch(){this.focusMatch((this.focusedMatchID+this.numMatches-1)%this.numMatches)}focusMatch(a=void 0){if(a===void 0){for(a=0;a<this.matchStartIndexes.length&&this.matchStartIndexes[a]<this.focusedMatchStartIndex;)a++;a>=this.matchStartIndexes.length&&(a=0)}this.focusedMatchStartIndex=this.matchStartIndexes[a],this.focusedMatchID=a;let b=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_find-match-focused");for(let c=0;c<b.length;c++)b[c].classList.remove("code-input_find-and-replace_find-match-focused");let c=Math.floor(a/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);if(!this.matchBlocksHighlighted[c]){this.matchBlocksHighlighted[c]=!0;for(let a=CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE*c;a<CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE*(c+1);a++)this.highlightMatch(a,this.codeInput.codeElement,this.matchStartIndexes[a],this.matchEndIndexes[a])}let d=this.codeInput.codeElement.querySelectorAll(`.code-input_find-and-replace_find-match[data-code-input_find-and-replace_match-id="${a}"]`);for(let b=0;b<d.length;b++)d[b].classList.add("code-input_find-and-replace_find-match-focused");0<d.length&&this.codeInput.scrollTo(d[0].offsetLeft-this.codeInput.offsetWidth/2,d[0].offsetTop-this.codeInput.offsetHeight/2)}highlightMatch(a,b,c,d){for(let e=0;e<b.childNodes.length;e++){let f=b.childNodes[e],g=f.textContent,h=!1;if(3==f.nodeType){if(e+1<b.childNodes.length&&3==b.childNodes[e+1].nodeType){b.childNodes[e+1].textContent=f.textContent+b.childNodes[e+1].textContent,b.removeChild(f),e--;continue}h=!0;let a=document.createElement("span");a.textContent=g,a.classList.add("code-input_find-and-replace_temporary-span"),b.replaceChild(a,f),f=a}if(0>=c){if(g.length>=d){if(h){let b=document.createElement("span");b.classList.add("code-input_find-and-replace_find-match"),b.setAttribute("data-code-input_find-and-replace_match-id",a),b.classList.add("code-input_find-and-replace_temporary-span"),b.textContent=g.substring(0,d),"\n"==b.textContent[0]&&b.classList.add("code-input_find-and-replace_start-newline");let c=g.substring(d);return f.textContent=c,f.insertAdjacentElement("beforebegin",b),void e++}return void this.highlightMatch(a,f,0,d)}f.classList.add("code-input_find-and-replace_find-match"),f.setAttribute("data-code-input_find-and-replace_match-id",a),"\n"==f.textContent[0]&&f.classList.add("code-input_find-and-replace_start-newline")}else if(g.length>c){if(!h)this.highlightMatch(a,f,c,d);else if(g.length>d){let b=document.createElement("span");b.classList.add("code-input_find-and-replace_temporary-span"),b.textContent=g.substring(0,c);let h=g.substring(c,d);f.textContent=h,f.classList.add("code-input_find-and-replace_find-match"),f.setAttribute("data-code-input_find-and-replace_match-id",a),"\n"==f.textContent[0]&&f.classList.add("code-input_find-and-replace_start-newline");let i=document.createElement("span");i.classList.add("code-input_find-and-replace_temporary-span"),i.textContent=g.substring(d),f.insertAdjacentElement("beforebegin",b),f.insertAdjacentElement("afterend",i),e++}else{let b=g.substring(0,c);f.textContent=b;let d=document.createElement("span");d.classList.add("code-input_find-and-replace_find-match"),d.setAttribute("data-code-input_find-and-replace_match-id",a),d.classList.add("code-input_find-and-replace_temporary-span"),d.textContent=g.substring(c),"\n"==d.textContent[0]&&d.classList.add("code-input_find-and-replace_start-newline"),f.insertAdjacentElement("afterend",d),e++}if(g.length>d)return}c-=g.length,d-=g.length}}};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
@keyframes code-input_go-to-line_roll-in {
|
|
2
|
+
0% {opacity: 0; transform: translateY(-34px);}
|
|
3
|
+
100% {opacity: 1; transform: translateY(0px);}
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
@keyframes code-input_go-to-line_roll-out {
|
|
7
|
+
0% {opacity: 1; transform: translateY(0px);}
|
|
8
|
+
100% {opacity: 0; transform: translateY(-34px);}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.code-input_go-to-line_dialog {
|
|
12
|
+
position: absolute;
|
|
13
|
+
top: 0; right: 14px;
|
|
14
|
+
height: 28px;
|
|
15
|
+
padding: 6px;
|
|
16
|
+
padding-top: 8px;
|
|
17
|
+
border: solid 1px #00000044;
|
|
18
|
+
background-color: white;
|
|
19
|
+
border-radius: 6px;
|
|
20
|
+
box-shadow: 0 .2em 1em .2em rgba(0, 0, 0, 0.16);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.code-input_go-to-line_dialog:not(.code-input_go-to-line_hidden-dialog) {
|
|
24
|
+
animation: code-input_go-to-line_roll-in .2s;
|
|
25
|
+
opacity: 1;
|
|
26
|
+
pointer-events: all;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.code-input_go-to-line_dialog.code-input_go-to-line_hidden-dialog {
|
|
30
|
+
animation: code-input_go-to-line_roll-out .2s;
|
|
31
|
+
opacity: 0;
|
|
32
|
+
pointer-events: none;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.code-input_go-to-line_dialog input::placeholder {
|
|
36
|
+
font-size: 80%;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.code-input_go-to-line_dialog input {
|
|
40
|
+
position: relative;
|
|
41
|
+
width: 240px; height: 32px; top: -3px;
|
|
42
|
+
font-size: large;
|
|
43
|
+
color: #000000aa;
|
|
44
|
+
border: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.code-input_go-to-line_dialog input.code-input_go-to-line_error {
|
|
48
|
+
color: #ff0000aa;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.code-input_go-to-line_dialog input:focus {
|
|
52
|
+
outline: none;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Cancel icon */
|
|
56
|
+
.code-input_go-to-line_dialog span {
|
|
57
|
+
display: inline-block;
|
|
58
|
+
width: 24px;
|
|
59
|
+
line-height: 24px;
|
|
60
|
+
font-family: system-ui;
|
|
61
|
+
font-size: 22px;
|
|
62
|
+
font-weight: 500;
|
|
63
|
+
text-align: center;
|
|
64
|
+
border-radius: 50%;
|
|
65
|
+
color: black;
|
|
66
|
+
opacity: 0.6;
|
|
67
|
+
vertical-align: top;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.code-input_go-to-line_dialog span:before {
|
|
71
|
+
content: "\00d7";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.code-input_go-to-line_dialog span:hover {
|
|
75
|
+
opacity: .8;
|
|
76
|
+
background-color: #00000018;
|
|
77
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add basic Go-To-Line (Ctrl+G by default) functionality to the code editor.
|
|
3
|
+
* Files: go-to-line.js / go-to-line.css
|
|
4
|
+
*/
|
|
5
|
+
codeInput.plugins.GoToLine = class extends codeInput.Plugin {
|
|
6
|
+
useCtrlG = false;
|
|
7
|
+
|
|
8
|
+
instructions = {
|
|
9
|
+
closeDialog: "Close Dialog and Return to Editor",
|
|
10
|
+
input: "Line:Column / Line no. then Enter",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create a go-to-line command plugin to pass into a template
|
|
15
|
+
* @param {boolean} useCtrlG Should Ctrl+G be overriden for go-to-line functionality? If not, you can trigger it yourself using (instance of this plugin)`.showPrompt(code-input element)`.
|
|
16
|
+
* @param {Object} instructionTranslations: user interface string keys mapped to translated versions for localisation. Look at the go-to-line.js source code for the available keys and English text.
|
|
17
|
+
*/
|
|
18
|
+
constructor(useCtrlG = true, instructionTranslations = {}) {
|
|
19
|
+
super([]); // No observed attributes
|
|
20
|
+
this.useCtrlG = useCtrlG;
|
|
21
|
+
this.addTranslations(this.instructions, instructionTranslations);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Add keystroke events */
|
|
25
|
+
afterElementsAdded(codeInput) {
|
|
26
|
+
const textarea = codeInput.textareaElement;
|
|
27
|
+
if(this.useCtrlG) {
|
|
28
|
+
textarea.addEventListener('keydown', (event) => { this.checkCtrlG(codeInput, event); });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Called with a dialog box keyup event to check the validity of the line number entered and submit the dialog if Enter is pressed */
|
|
33
|
+
checkPrompt(dialog, event) {
|
|
34
|
+
// Line number(:column number)
|
|
35
|
+
const lines = dialog.textarea.value.split('\n');
|
|
36
|
+
const maxLineNo = lines.length;
|
|
37
|
+
const lineNo = Number(dialog.input.value.split(':')[0]);
|
|
38
|
+
let columnNo = 0; // Means go to start of indented line
|
|
39
|
+
let maxColumnNo = 1;
|
|
40
|
+
const querySplitByColons = dialog.input.value.split(':');
|
|
41
|
+
if(querySplitByColons.length > 2) return dialog.input.classList.add('code-input_go-to-line_error');
|
|
42
|
+
|
|
43
|
+
if (event.key == 'Escape') return this.cancelPrompt(dialog, event);
|
|
44
|
+
|
|
45
|
+
if (dialog.input.value) {
|
|
46
|
+
if (!/^[0-9:]*$/.test(dialog.input.value) || lineNo < 1 || lineNo > maxLineNo) {
|
|
47
|
+
return dialog.input.classList.add('code-input_go-to-line_error');
|
|
48
|
+
} else {
|
|
49
|
+
// Check if line:column
|
|
50
|
+
if(querySplitByColons.length >= 2) {
|
|
51
|
+
columnNo = Number(querySplitByColons[1]);
|
|
52
|
+
maxColumnNo = lines[lineNo-1].length;
|
|
53
|
+
}
|
|
54
|
+
if(columnNo < 0 || columnNo > maxColumnNo) {
|
|
55
|
+
return dialog.input.classList.add('code-input_go-to-line_error');
|
|
56
|
+
} else {
|
|
57
|
+
dialog.input.classList.remove('code-input_go-to-line_error');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (event.key == 'Enter') {
|
|
63
|
+
this.goTo(dialog.textarea, lineNo, columnNo);
|
|
64
|
+
this.cancelPrompt(dialog, event);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Called with a dialog box keyup event to close and clear the dialog box */
|
|
69
|
+
cancelPrompt(dialog, event) {
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
dialog.codeInput.handleEventsFromTextarea = false;
|
|
72
|
+
dialog.textarea.focus();
|
|
73
|
+
dialog.codeInput.handleEventsFromTextarea = true;
|
|
74
|
+
dialog.setAttribute("inert", true); // Hide from keyboard navigation when closed.
|
|
75
|
+
dialog.setAttribute("tabindex", -1); // Hide from keyboard navigation when closed.
|
|
76
|
+
dialog.setAttribute("aria-hidden", true); // Hide from screen reader when closed.
|
|
77
|
+
|
|
78
|
+
// Remove dialog after animation
|
|
79
|
+
dialog.classList.add('code-input_go-to-line_hidden-dialog');
|
|
80
|
+
dialog.input.value = "";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Show a search-like dialog prompting line number.
|
|
85
|
+
* @param {codeInput.CodeInput} codeInput the `<code-input>` element.
|
|
86
|
+
*/
|
|
87
|
+
showPrompt(codeInput) {
|
|
88
|
+
if(codeInput.pluginData.goToLine == undefined || codeInput.pluginData.goToLine.dialog == undefined) {
|
|
89
|
+
const textarea = codeInput.textareaElement;
|
|
90
|
+
|
|
91
|
+
const dialog = document.createElement('div');
|
|
92
|
+
const input = document.createElement('input');
|
|
93
|
+
const cancel = document.createElement('span');
|
|
94
|
+
cancel.setAttribute("tabindex", 0); // Visible to keyboard navigation
|
|
95
|
+
cancel.setAttribute("title", this.instructions.closeDialog);
|
|
96
|
+
|
|
97
|
+
dialog.appendChild(input);
|
|
98
|
+
dialog.appendChild(cancel);
|
|
99
|
+
|
|
100
|
+
dialog.className = 'code-input_go-to-line_dialog';
|
|
101
|
+
input.spellcheck = false;
|
|
102
|
+
input.placeholder = this.instructions.input;
|
|
103
|
+
dialog.codeInput = codeInput;
|
|
104
|
+
dialog.textarea = textarea;
|
|
105
|
+
dialog.input = input;
|
|
106
|
+
|
|
107
|
+
input.addEventListener('keypress', (event) => {
|
|
108
|
+
/* Stop enter from submitting form */
|
|
109
|
+
if (event.key == 'Enter') event.preventDefault();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
input.addEventListener('keyup', (event) => { return this.checkPrompt(dialog, event); });
|
|
113
|
+
cancel.addEventListener('click', (event) => { this.cancelPrompt(dialog, event); });
|
|
114
|
+
cancel.addEventListener('keypress', (event) => { if (event.key == "Space" || event.key == "Enter") this.cancelPrompt(dialog, event); });
|
|
115
|
+
|
|
116
|
+
codeInput.dialogContainerElement.appendChild(dialog);
|
|
117
|
+
codeInput.pluginData.goToLine = {dialog: dialog};
|
|
118
|
+
input.focus();
|
|
119
|
+
} else {
|
|
120
|
+
codeInput.pluginData.goToLine.dialog.classList.remove("code-input_go-to-line_hidden-dialog");
|
|
121
|
+
codeInput.pluginData.goToLine.dialog.removeAttribute("inert"); // Show to keyboard navigation when open.
|
|
122
|
+
codeInput.pluginData.goToLine.dialog.setAttribute("tabindex", 0); // Show to keyboard navigation when open.
|
|
123
|
+
codeInput.pluginData.goToLine.dialog.removeAttribute("aria-hidden"); // Show to screen reader when open.
|
|
124
|
+
codeInput.pluginData.goToLine.dialog.input.focus();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Set the cursor on the first non-space char of textarea's nth line, or to the columnNo-numbered character in the line if it's not 0; and scroll it into view */
|
|
129
|
+
goTo(textarea, lineNo, columnNo = 0) {
|
|
130
|
+
let fontSize;
|
|
131
|
+
let lineHeight;
|
|
132
|
+
let scrollAmount;
|
|
133
|
+
let topPadding;
|
|
134
|
+
let cursorPos = -1;
|
|
135
|
+
let lines = textarea.value.split('\n');
|
|
136
|
+
|
|
137
|
+
if (lineNo > 0 && lineNo <= lines.length) {
|
|
138
|
+
if (textarea.computedStyleMap) {
|
|
139
|
+
fontSize = textarea.computedStyleMap().get('font-size').value;
|
|
140
|
+
lineHeight = fontSize * textarea.computedStyleMap().get('line-height').value;
|
|
141
|
+
} else {
|
|
142
|
+
fontSize = document.defaultView.getComputedStyle(textarea, null).getPropertyValue('font-size').split('px')[0];
|
|
143
|
+
lineHeight = document.defaultView.getComputedStyle(textarea, null).getPropertyValue('line-height').split('px')[0];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// scroll amount and initial top padding (3 lines above, if possible)
|
|
147
|
+
scrollAmount = (lineNo > 3 ? lineNo - 3 : 1) * lineHeight;
|
|
148
|
+
topPadding = (lineHeight - fontSize) / 2;
|
|
149
|
+
|
|
150
|
+
if (lineNo > 1) {
|
|
151
|
+
// cursor positon just after n - 1 full lines
|
|
152
|
+
cursorPos = lines.slice(0, lineNo - 1).join('\n').length;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// scan first non-space char in nth line
|
|
156
|
+
if (columnNo == 0) {
|
|
157
|
+
do cursorPos++; while (textarea.value[cursorPos] != '\n' && /\s/.test(textarea.value[cursorPos]));
|
|
158
|
+
} else {
|
|
159
|
+
cursorPos += 1 + columnNo - 1;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
textarea.scrollTop = scrollAmount - topPadding;
|
|
163
|
+
textarea.setSelectionRange(cursorPos, cursorPos);
|
|
164
|
+
textarea.click();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* Event handler for keydown event that makes Ctrl+G open go to line dialog */
|
|
169
|
+
checkCtrlG(codeInput, event) {
|
|
170
|
+
if (event.ctrlKey && event.key == 'g') {
|
|
171
|
+
event.preventDefault();
|
|
172
|
+
this.showPrompt(codeInput);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@keyframes code-input_go-to-line_roll-in{0%{opacity:0;transform:translateY(-34px)}100%{opacity:1;transform:translateY(0)}}@keyframes code-input_go-to-line_roll-out{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(-34px)}}.code-input_go-to-line_dialog{position:absolute;top:0;right:14px;height:28px;padding:6px;padding-top:8px;border:solid 1px #00000044;background-color:#fff;border-radius:6px;box-shadow:0 .2em 1em .2em rgba(0,0,0,.16)}.code-input_go-to-line_dialog:not(.code-input_go-to-line_hidden-dialog){animation:code-input_go-to-line_roll-in .2s;opacity:1;pointer-events:all}.code-input_go-to-line_dialog.code-input_go-to-line_hidden-dialog{animation:code-input_go-to-line_roll-out .2s;opacity:0;pointer-events:none}.code-input_go-to-line_dialog input::placeholder{font-size:80%}.code-input_go-to-line_dialog input{position:relative;width:240px;height:32px;top:-3px;font-size:large;color:#000000aa;border:0}.code-input_go-to-line_dialog input.code-input_go-to-line_error{color:#ff0000aa}.code-input_go-to-line_dialog input:focus{outline:0}.code-input_go-to-line_dialog span{display:inline-block;width:24px;line-height:24px;font-family:system-ui;font-size:22px;font-weight:500;text-align:center;border-radius:50%;color:#000;opacity:.6;vertical-align:top}.code-input_go-to-line_dialog span:before{content:"\00d7"}.code-input_go-to-line_dialog span:hover{opacity:.8;background-color:#00000018}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
codeInput.plugins.GoToLine=class extends codeInput.Plugin{useCtrlG=!1;instructions={closeDialog:"Close Dialog and Return to Editor",input:"Line:Column / Line no. then Enter"};constructor(a=!0,b={}){super([]),this.useCtrlG=a,this.addTranslations(this.instructions,b)}afterElementsAdded(a){const b=a.textareaElement;this.useCtrlG&&b.addEventListener("keydown",b=>{this.checkCtrlG(a,b)})}checkPrompt(a,b){const c=a.textarea.value.split("\n"),d=c.length,e=+a.input.value.split(":")[0];let f=0,g=1;const h=a.input.value.split(":");if(2<h.length)return a.input.classList.add("code-input_go-to-line_error");if("Escape"==b.key)return this.cancelPrompt(a,b);if(a.input.value){if(!/^[0-9:]*$/.test(a.input.value)||1>e||e>d)return a.input.classList.add("code-input_go-to-line_error");if(2<=h.length&&(f=+h[1],g=c[e-1].length),0>f||f>g)return a.input.classList.add("code-input_go-to-line_error");a.input.classList.remove("code-input_go-to-line_error")}"Enter"==b.key&&(this.goTo(a.textarea,e,f),this.cancelPrompt(a,b))}cancelPrompt(a,b){b.preventDefault(),a.codeInput.handleEventsFromTextarea=!1,a.textarea.focus(),a.codeInput.handleEventsFromTextarea=!0,a.setAttribute("inert",!0),a.setAttribute("tabindex",-1),a.setAttribute("aria-hidden",!0),a.classList.add("code-input_go-to-line_hidden-dialog"),a.input.value=""}showPrompt(a){if(a.pluginData.goToLine==null||a.pluginData.goToLine.dialog==null){const b=a.textareaElement,c=document.createElement("div"),d=document.createElement("input"),e=document.createElement("span");e.setAttribute("tabindex",0),e.setAttribute("title",this.instructions.closeDialog),c.appendChild(d),c.appendChild(e),c.className="code-input_go-to-line_dialog",d.spellcheck=!1,d.placeholder=this.instructions.input,c.codeInput=a,c.textarea=b,c.input=d,d.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),d.addEventListener("keyup",a=>this.checkPrompt(c,a)),e.addEventListener("click",a=>{this.cancelPrompt(c,a)}),e.addEventListener("keypress",a=>{("Space"==a.key||"Enter"==a.key)&&this.cancelPrompt(c,a)}),a.dialogContainerElement.appendChild(c),a.pluginData.goToLine={dialog:c},d.focus()}else a.pluginData.goToLine.dialog.classList.remove("code-input_go-to-line_hidden-dialog"),a.pluginData.goToLine.dialog.removeAttribute("inert"),a.pluginData.goToLine.dialog.setAttribute("tabindex",0),a.pluginData.goToLine.dialog.removeAttribute("aria-hidden"),a.pluginData.goToLine.dialog.input.focus()}goTo(a,b,c=0){let d,e,f,g,h=-1,i=a.value.split("\n");if(0<b&&b<=i.length){if(a.computedStyleMap?(d=a.computedStyleMap().get("font-size").value,e=d*a.computedStyleMap().get("line-height").value):(d=document.defaultView.getComputedStyle(a,null).getPropertyValue("font-size").split("px")[0],e=document.defaultView.getComputedStyle(a,null).getPropertyValue("line-height").split("px")[0]),f=(3<b?b-3:1)*e,g=(e-d)/2,1<b&&(h=i.slice(0,b-1).join("\n").length),0==c)do h++;while("\n"!=a.value[h]&&/\s/.test(a.value[h]));else h+=1+c-1;a.scrollTop=f-g,a.setSelectionRange(h,h),a.click()}}checkCtrlG(a,b){b.ctrlKey&&"g"==b.key&&(b.preventDefault(),this.showPrompt(a))}};
|
package/plugins/indent.js
CHANGED
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Add indentation using the `Tab` key, and auto-indents after a newline, as well as making it
|
|
3
3
|
* possible to indent/unindent multiple lines using Tab/Shift+Tab
|
|
4
4
|
* Files: indent.js
|
|
5
5
|
*/
|
|
6
6
|
codeInput.plugins.Indent = class extends codeInput.Plugin {
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
bracketPairs = {}; // No bracket-auto-indentation used when {}
|
|
9
9
|
indentation = "\t";
|
|
10
10
|
indentationNumChars = 1;
|
|
11
|
+
tabIndentationEnabled = true; // Can be disabled for accessibility reasons to allow keyboard navigation
|
|
12
|
+
escTabToChangeFocus = true;
|
|
13
|
+
escJustPressed = false; // Becomes true when Escape key is pressed and false when another key is pressed
|
|
14
|
+
|
|
15
|
+
instructions = {
|
|
16
|
+
tabForIndentation: "Tab and Shift-Tab currently for indentation. Press Esc to enable keyboard navigation.",
|
|
17
|
+
tabForNavigation: "Tab and Shift-Tab currently for keyboard navigation. Type to return to indentation.",
|
|
18
|
+
};
|
|
11
19
|
|
|
12
20
|
/**
|
|
13
21
|
* Create an indentation plugin to pass into a template
|
|
14
|
-
* @param {
|
|
22
|
+
* @param {boolean} defaultSpaces Should the Tab key enter spaces rather than tabs? Defaults to false.
|
|
15
23
|
* @param {Number} numSpaces How many spaces is each tab character worth? Defaults to 4.
|
|
24
|
+
* @param {Object} bracketPairs Opening brackets mapped to closing brackets, default and example {"(": ")", "[": "]", "{": "}"}. All brackets must only be one character, and this can be left as null to remove bracket-based indentation behaviour.
|
|
25
|
+
* @param {boolean} escTabToChangeFocus Whether pressing the Escape key before Tab and Shift-Tab should make this keypress focus on a different element (Tab's default behaviour). You should always either enable this or use this plugin's disableTabIndentation and enableTabIndentation methods linked to other keyboard shortcuts, for accessibility.
|
|
26
|
+
* @param {Object} instructionTranslations: user interface string keys mapped to translated versions for localisation. Look at the go-to-line.js source code for the available keys and English text.
|
|
16
27
|
*/
|
|
17
|
-
constructor(defaultSpaces=false, numSpaces=4) {
|
|
28
|
+
constructor(defaultSpaces=false, numSpaces=4, bracketPairs={"(": ")", "[": "]", "{": "}"}, escTabToChangeFocus=true, instructionTranslations = {}) {
|
|
18
29
|
super([]); // No observed attributes
|
|
19
30
|
|
|
20
|
-
this.
|
|
31
|
+
this.bracketPairs = bracketPairs;
|
|
21
32
|
if(defaultSpaces) {
|
|
22
33
|
this.indentation = "";
|
|
23
34
|
for(let i = 0; i < numSpaces; i++) {
|
|
@@ -25,19 +36,81 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
|
|
|
25
36
|
}
|
|
26
37
|
this.indentationNumChars = numSpaces;
|
|
27
38
|
}
|
|
39
|
+
|
|
40
|
+
this.escTabToChangeFocus = true;
|
|
41
|
+
|
|
42
|
+
this.addTranslations(this.instructions, instructionTranslations);
|
|
28
43
|
}
|
|
29
44
|
|
|
30
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Make the Tab key
|
|
47
|
+
*/
|
|
48
|
+
disableTabIndentation() {
|
|
49
|
+
this.tabIndentationEnabled = false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
enableTabIndentation() {
|
|
53
|
+
this.tabIndentationEnabled = true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Add keystroke events, and get the width of the indentation in pixels. */
|
|
31
57
|
afterElementsAdded(codeInput) {
|
|
58
|
+
|
|
32
59
|
let textarea = codeInput.textareaElement;
|
|
60
|
+
textarea.addEventListener('focus', (event) => { if(this.escTabToChangeFocus) codeInput.setKeyboardNavInstructions(this.instructions.tabForIndentation, true); })
|
|
33
61
|
textarea.addEventListener('keydown', (event) => { this.checkTab(codeInput, event); this.checkEnter(codeInput, event); this.checkBackspace(codeInput, event); });
|
|
62
|
+
textarea.addEventListener('beforeinput', (event) => { this.checkCloseBracket(codeInput, event); });
|
|
63
|
+
|
|
64
|
+
// Get the width of the indentation in pixels
|
|
65
|
+
let testIndentationWidthPre = document.createElement("pre");
|
|
66
|
+
testIndentationWidthPre.setAttribute("aria-hidden", "true"); // Hide for screen readers
|
|
67
|
+
let testIndentationWidthSpan = document.createElement("span");
|
|
68
|
+
if(codeInput.template.preElementStyled) {
|
|
69
|
+
testIndentationWidthPre.appendChild(testIndentationWidthSpan);
|
|
70
|
+
testIndentationWidthPre.classList.add("code-input_autocomplete_test-indentation-width");
|
|
71
|
+
codeInput.appendChild(testIndentationWidthPre); // Styled like first pre, but first pre found to update
|
|
72
|
+
} else {
|
|
73
|
+
let testIndentationWidthCode = document.createElement("code");
|
|
74
|
+
testIndentationWidthCode.appendChild(testIndentationWidthSpan);
|
|
75
|
+
testIndentationWidthCode.classList.add("code-input_autocomplete_test-indentation-width");
|
|
76
|
+
testIndentationWidthPre.appendChild(testIndentationWidthCode);
|
|
77
|
+
codeInput.appendChild(testIndentationWidthPre); // Styled like first pre, but first pre found to update
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
testIndentationWidthSpan.innerHTML = codeInput.escapeHtml(this.indentation);
|
|
81
|
+
let indentationWidthPx = testIndentationWidthSpan.offsetWidth;
|
|
82
|
+
codeInput.removeChild(testIndentationWidthPre);
|
|
83
|
+
|
|
84
|
+
codeInput.pluginData.indent = {indentationWidthPx: indentationWidthPx};
|
|
34
85
|
}
|
|
35
86
|
|
|
36
|
-
/*
|
|
87
|
+
/* Deal with the Tab key causing indentation, and Tab+Selection indenting / Shift+Tab+Selection unindenting lines, and the mechanism through which Tab can be used to switch focus instead (accessibility). */
|
|
37
88
|
checkTab(codeInput, event) {
|
|
38
|
-
if(
|
|
89
|
+
if(!this.tabIndentationEnabled) return;
|
|
90
|
+
if(this.escTabToChangeFocus) {
|
|
91
|
+
// Accessibility - allow Tab for keyboard navigation when Esc pressed right before it.
|
|
92
|
+
if(event.key == "Escape") {
|
|
93
|
+
this.escJustPressed = true;
|
|
94
|
+
codeInput.setKeyboardNavInstructions(this.instructions.tabForNavigation, false);
|
|
95
|
+
return;
|
|
96
|
+
} else if(event.key != "Tab") {
|
|
97
|
+
if(event.key == "Shift") {
|
|
98
|
+
return; // Shift+Tab after Esc should still be keyboard navigation
|
|
99
|
+
}
|
|
100
|
+
codeInput.setKeyboardNavInstructions(this.instructions.tabForIndentation, false);
|
|
101
|
+
this.escJustPressed = false;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if(!this.enableTabIndentation || this.escJustPressed) {
|
|
106
|
+
codeInput.setKeyboardNavInstructions("", false);
|
|
107
|
+
this.escJustPressed = false;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
} else if(event.key != "Tab") {
|
|
39
111
|
return;
|
|
40
112
|
}
|
|
113
|
+
|
|
41
114
|
let inputElement = codeInput.textareaElement;
|
|
42
115
|
event.preventDefault(); // stop normal
|
|
43
116
|
|
|
@@ -91,18 +164,39 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
|
|
|
91
164
|
// move cursor
|
|
92
165
|
inputElement.selectionStart = selectionStartI;
|
|
93
166
|
inputElement.selectionEnd = selectionEndI;
|
|
167
|
+
|
|
168
|
+
// move scroll position to follow code
|
|
169
|
+
const textDirection = getComputedStyle(codeInput).direction;
|
|
170
|
+
if(textDirection == "rtl") {
|
|
171
|
+
if(event.shiftKey) {
|
|
172
|
+
// Scroll right
|
|
173
|
+
codeInput.scrollBy(codeInput.pluginData.indent.indentationWidthPx, 0);
|
|
174
|
+
} else {
|
|
175
|
+
// Scroll left
|
|
176
|
+
codeInput.scrollBy(-codeInput.pluginData.indent.indentationWidthPx, 0);
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
if(event.shiftKey) {
|
|
180
|
+
// Scroll left
|
|
181
|
+
codeInput.scrollBy(-codeInput.pluginData.indent.indentationWidthPx, 0);
|
|
182
|
+
} else {
|
|
183
|
+
// Scroll right
|
|
184
|
+
codeInput.scrollBy(codeInput.pluginData.indent.indentationWidthPx, 0);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
94
187
|
}
|
|
95
188
|
|
|
96
|
-
codeInput.
|
|
189
|
+
codeInput.value = inputElement.value;
|
|
97
190
|
}
|
|
98
191
|
|
|
192
|
+
/* Deal with new lines retaining indentation */
|
|
99
193
|
checkEnter(codeInput, event) {
|
|
100
194
|
if(event.key != "Enter") {
|
|
101
195
|
return;
|
|
102
196
|
}
|
|
103
197
|
event.preventDefault(); // Stop normal \n only
|
|
104
198
|
|
|
105
|
-
let inputElement = codeInput.
|
|
199
|
+
let inputElement = codeInput.textareaElement;
|
|
106
200
|
let lines = inputElement.value.split("\n");
|
|
107
201
|
let letterI = 0;
|
|
108
202
|
let currentLineI = lines.length - 1;
|
|
@@ -135,6 +229,39 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
|
|
|
135
229
|
lines[currentLineI] = lines[currentLineI].substring(0, cursorPosInLine);
|
|
136
230
|
}
|
|
137
231
|
|
|
232
|
+
let bracketThreeLinesTriggered = false;
|
|
233
|
+
let furtherIndentation = "";
|
|
234
|
+
if(this.bracketPairs != null) {
|
|
235
|
+
for(let openingBracket in this.bracketPairs) {
|
|
236
|
+
if(lines[currentLineI][lines[currentLineI].length-1] == openingBracket) {
|
|
237
|
+
let closingBracket = this.bracketPairs[openingBracket];
|
|
238
|
+
if(textAfterCursor.length > 0 && textAfterCursor[0] == closingBracket) {
|
|
239
|
+
// Create new line and then put textAfterCursor on yet another line:
|
|
240
|
+
// {
|
|
241
|
+
// |CARET|
|
|
242
|
+
// }
|
|
243
|
+
bracketThreeLinesTriggered = true;
|
|
244
|
+
for (let i = 0; i < numberIndents+1; i++) {
|
|
245
|
+
furtherIndentation += this.indentation;
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
// Just create new line:
|
|
249
|
+
// {
|
|
250
|
+
// |CARET|
|
|
251
|
+
numberIndents++;
|
|
252
|
+
}
|
|
253
|
+
break;
|
|
254
|
+
} else {
|
|
255
|
+
// Check whether brackets cause unindent
|
|
256
|
+
let closingBracket = this.bracketPairs[openingBracket];
|
|
257
|
+
if(textAfterCursor.length > 0 && textAfterCursor[0] == closingBracket) {
|
|
258
|
+
numberIndents--;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
138
265
|
// insert our indents and any text from the previous line that might have been after the line break
|
|
139
266
|
for (let i = 0; i < numberIndents; i++) {
|
|
140
267
|
newLine += this.indentation;
|
|
@@ -143,6 +270,10 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
|
|
|
143
270
|
// save the current cursor position
|
|
144
271
|
let selectionStartI = inputElement.selectionStart;
|
|
145
272
|
|
|
273
|
+
if(bracketThreeLinesTriggered) {
|
|
274
|
+
document.execCommand("insertText", false, "\n" + furtherIndentation); // Write indented line
|
|
275
|
+
numberIndents += 1; // Reflects the new indent
|
|
276
|
+
}
|
|
146
277
|
document.execCommand("insertText", false, "\n" + newLine); // Write new line, including auto-indentation
|
|
147
278
|
|
|
148
279
|
// move cursor to new position
|
|
@@ -153,17 +284,18 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
|
|
|
153
284
|
// Scroll down to cursor if necessary
|
|
154
285
|
let paddingTop = Number(getComputedStyle(inputElement).paddingTop.replace("px", ""));
|
|
155
286
|
let lineHeight = Number(getComputedStyle(inputElement).lineHeight.replace("px", ""));
|
|
156
|
-
let inputHeight = Number(getComputedStyle(
|
|
287
|
+
let inputHeight = Number(getComputedStyle(codeInput).height.replace("px", ""));
|
|
157
288
|
if(currentLineI*lineHeight + lineHeight*2 + paddingTop >= inputElement.scrollTop + inputHeight) { // Cursor too far down
|
|
158
|
-
|
|
289
|
+
codeInput.scrollBy(0, Number(getComputedStyle(inputElement).lineHeight.replace("px", "")));
|
|
159
290
|
}
|
|
160
291
|
|
|
161
|
-
codeInput.
|
|
292
|
+
codeInput.value = inputElement.value;
|
|
162
293
|
}
|
|
163
294
|
|
|
295
|
+
/* Deal with one 'tab' of spaces-based-indentation being deleted by each backspace, rather than one space */
|
|
164
296
|
checkBackspace(codeInput, event) {
|
|
165
297
|
if(event.key != "Backspace" || this.indentationNumChars == 1) {
|
|
166
|
-
return; // Normal backspace
|
|
298
|
+
return; // Normal backspace when indentation of 1
|
|
167
299
|
}
|
|
168
300
|
|
|
169
301
|
let inputElement = codeInput.textareaElement;
|
|
@@ -175,4 +307,23 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
|
|
|
175
307
|
document.execCommand("delete", false, "");
|
|
176
308
|
}
|
|
177
309
|
}
|
|
178
|
-
|
|
310
|
+
|
|
311
|
+
/* Deal with the typing of closing brackets causing a decrease in indentation */
|
|
312
|
+
checkCloseBracket(codeInput, event) {
|
|
313
|
+
if(codeInput.textareaElement.selectionStart != codeInput.textareaElement.selectionEnd) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
for(let openingBracket in this.bracketPairs) {
|
|
318
|
+
let closingBracket = this.bracketPairs[openingBracket];
|
|
319
|
+
if(event.data == closingBracket) {
|
|
320
|
+
// Closing bracket unindents line
|
|
321
|
+
if(codeInput.value.substring(codeInput.textareaElement.selectionStart - this.indentationNumChars, codeInput.textareaElement.selectionStart) == this.indentation) {
|
|
322
|
+
// Indentation before cursor = delete it
|
|
323
|
+
codeInput.textareaElement.selectionStart -= this.indentationNumChars;
|
|
324
|
+
document.execCommand("delete", false, "");
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
package/plugins/indent.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
codeInput.plugins.Indent=class extends codeInput.Plugin{
|
|
1
|
+
codeInput.plugins.Indent=class extends codeInput.Plugin{bracketPairs={};indentation="\t";indentationNumChars=1;tabIndentationEnabled=!0;escTabToChangeFocus=!0;escJustPressed=!1;instructions={tabForIndentation:"Tab and Shift-Tab currently for indentation. Press Esc to enable keyboard navigation.",tabForNavigation:"Tab and Shift-Tab currently for keyboard navigation. Type to return to indentation."};constructor(a=!1,b=4,c={"(":")","[":"]","{":"}"},d=!0,e={}){if(super([]),this.bracketPairs=c,a){this.indentation="";for(let a=0;a<b;a++)this.indentation+=" ";this.indentationNumChars=b}this.escTabToChangeFocus=!0,this.addTranslations(this.instructions,e)}disableTabIndentation(){this.tabIndentationEnabled=!1}enableTabIndentation(){this.tabIndentationEnabled=!0}afterElementsAdded(a){let b=a.textareaElement;b.addEventListener("focus",()=>{this.escTabToChangeFocus&&a.setKeyboardNavInstructions(this.instructions.tabForIndentation,!0)}),b.addEventListener("keydown",b=>{this.checkTab(a,b),this.checkEnter(a,b),this.checkBackspace(a,b)}),b.addEventListener("beforeinput",b=>{this.checkCloseBracket(a,b)});let c=document.createElement("pre");c.setAttribute("aria-hidden","true");let d=document.createElement("span");if(a.template.preElementStyled)c.appendChild(d),c.classList.add("code-input_autocomplete_test-indentation-width"),a.appendChild(c);else{let b=document.createElement("code");b.appendChild(d),b.classList.add("code-input_autocomplete_test-indentation-width"),c.appendChild(b),a.appendChild(c)}d.innerHTML=a.escapeHtml(this.indentation);let e=d.offsetWidth;a.removeChild(c),a.pluginData.indent={indentationWidthPx:e}}checkTab(a,b){var c=Math.max;if(this.tabIndentationEnabled){if(this.escTabToChangeFocus){if("Escape"==b.key)return this.escJustPressed=!0,void a.setKeyboardNavInstructions(this.instructions.tabForNavigation,!1);if("Tab"!=b.key)return"Shift"==b.key?void 0:(a.setKeyboardNavInstructions(this.instructions.tabForIndentation,!1),void(this.escJustPressed=!1));if(!this.enableTabIndentation||this.escJustPressed)return a.setKeyboardNavInstructions("",!1),void(this.escJustPressed=!1)}else if("Tab"!=b.key)return;let d=a.textareaElement;if(b.preventDefault(),!b.shiftKey&&d.selectionStart==d.selectionEnd)document.execCommand("insertText",!1,this.indentation);else{let e=d.value.split("\n"),f=0,g=d.selectionStart,h=d.selectionEnd;for(let a=0;a<e.length;a++)(g<=f+e[a].length&&h>=f+1||g==h&&g<=f+e[a].length+1&&h>=f)&&(b.shiftKey?e[a].substring(0,this.indentationNumChars)==this.indentation&&(d.selectionStart=f,d.selectionEnd=f+this.indentationNumChars,document.execCommand("delete",!1,""),g>f&&(g=c(g-this.indentationNumChars,f)),h-=this.indentationNumChars,f-=this.indentationNumChars):(d.selectionStart=f,d.selectionEnd=f,document.execCommand("insertText",!1,this.indentation),g>f&&(g+=this.indentationNumChars),h+=this.indentationNumChars,f+=this.indentationNumChars)),f+=e[a].length+1;d.selectionStart=g,d.selectionEnd=h;const i=getComputedStyle(a).direction;"rtl"==i?b.shiftKey?a.scrollBy(a.pluginData.indent.indentationWidthPx,0):a.scrollBy(-a.pluginData.indent.indentationWidthPx,0):b.shiftKey?a.scrollBy(-a.pluginData.indent.indentationWidthPx,0):a.scrollBy(a.pluginData.indent.indentationWidthPx,0)}a.value=d.value}}checkEnter(a,b){if("Enter"!=b.key)return;b.preventDefault();let c=a.textareaElement,d=c.value.split("\n"),e=0,f=d.length-1,g="",h=0;for(let g=0;g<d.length;g++)if(e+=d[g].length+1,c.selectionEnd<=e){f=g;break}let j=d[f].length-(e-c.selectionEnd)+1;for(let c=0;c<j&&d[f].substring(c,c+this.indentationNumChars)==this.indentation;c+=this.indentationNumChars)h++;let k="";j!=d[f].length&&(k=d[f].substring(j),d[f]=d[f].substring(0,j));let l=!1,m="";if(null!=this.bracketPairs)for(let a in this.bracketPairs)if(d[f][d[f].length-1]==a){let b=this.bracketPairs[a];if(0<k.length&&k[0]==b){l=!0;for(let a=0;a<h+1;a++)m+=this.indentation}else h++;break}else{let b=this.bracketPairs[a];if(0<k.length&&k[0]==b){h--;break}}for(let c=0;c<h;c++)g+=this.indentation;let n=c.selectionStart;l&&(document.execCommand("insertText",!1,"\n"+m),h+=1),document.execCommand("insertText",!1,"\n"+g),c.selectionStart=n+h*this.indentationNumChars+1,c.selectionEnd=c.selectionStart;let o=+getComputedStyle(c).paddingTop.replace("px",""),p=+getComputedStyle(c).lineHeight.replace("px",""),q=+getComputedStyle(a).height.replace("px","");f*p+2*p+o>=c.scrollTop+q&&a.scrollBy(0,+getComputedStyle(c).lineHeight.replace("px","")),a.value=c.value}checkBackspace(a,b){if("Backspace"==b.key&&1!=this.indentationNumChars){let c=a.textareaElement;c.selectionStart==c.selectionEnd&&a.value.substring(c.selectionStart-this.indentationNumChars,c.selectionStart)==this.indentation&&(c.selectionStart-=this.indentationNumChars,b.preventDefault(),document.execCommand("delete",!1,""))}}checkCloseBracket(a,b){if(a.textareaElement.selectionStart==a.textareaElement.selectionEnd)for(let c in this.bracketPairs){let d=this.bracketPairs[c];b.data==d&&a.value.substring(a.textareaElement.selectionStart-this.indentationNumChars,a.textareaElement.selectionStart)==this.indentation&&(a.textareaElement.selectionStart-=this.indentationNumChars,document.execCommand("delete",!1,""))}}};
|
|
@@ -7,14 +7,15 @@
|
|
|
7
7
|
/* Update padding to match line-numbers plugin */
|
|
8
8
|
code-input.line-numbers textarea, code-input.line-numbers.code-input_pre-element-styled pre,
|
|
9
9
|
.line-numbers code-input textarea, .line-numbers code-input.code-input_pre-element-styled pre {
|
|
10
|
-
padding-
|
|
11
|
-
padding-bottom: 1em!important;
|
|
12
|
-
padding-right: 1em!important;
|
|
13
|
-
padding-left: 3.8em!important;
|
|
10
|
+
padding-left: max(3.8em, var(--padding, 16px))!important;
|
|
14
11
|
}
|
|
15
12
|
|
|
16
|
-
/*
|
|
17
|
-
code-input.line-numbers,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
/* Ensure pre code/textarea just wide enough to give 100% width with line numbers */
|
|
14
|
+
code-input.line-numbers, .line-numbers code-input {
|
|
15
|
+
grid-template-columns: calc(100% - max(0em, calc(3.8em - var(--padding, 16px))));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* Make keyboard navigation still fill width */
|
|
19
|
+
code-input .code-input_dialog-container .code-input_keyboard-navigation-instructions {
|
|
20
|
+
width: calc(100% + max(3.8em, var(--padding, 16px)))!important;
|
|
21
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.line-numbers code-input textarea,.line-numbers code-input.code-input_pre-element-styled pre,code-input.line-numbers textarea,code-input.line-numbers.code-input_pre-element-styled pre{padding-
|
|
1
|
+
.line-numbers code-input textarea,.line-numbers code-input.code-input_pre-element-styled pre,code-input.line-numbers textarea,code-input.line-numbers.code-input_pre-element-styled pre{padding-left:max(3.8em,var(--padding,16px))!important}.line-numbers code-input,code-input.line-numbers{grid-template-columns:calc(100% - max(0em,calc(3.8em - var(--padding,16px))))}code-input .code-input_dialog-container .code-input_keyboard-navigation-instructions{width:calc(100% + max(3.8em,var(--padding,16px)))!important}
|