1/* globals Babel */ 2 3import { basicSetup } from "https://esm.sh/[email protected]"; 4import { EditorState } from "https://esm.sh/@codemirror/state@^6.0.0"; 5import { EditorView } from "https://esm.sh/@codemirror/view@^6.0.0"; 6import { oneDark } from "https://esm.sh/@codemirror/theme-one-dark@^6.0.0"; 7import { javascriptLanguage } from "https://esm.sh/@codemirror/lang-javascript@^6.0.0"; 8import debounce from "https://esm.sh/lodash.debounce"; 9 10const template = document.createElement("template"); 11template.innerHTML = ` 12 <div> 13 <div class="repl"> 14 <h3 class="input-title">Input code</h3> 15 <h3 class="output-title">Output code</h3> 16 <div id="repl-in" class="repl__code input-editor"></div> 17 <div class="output-editor-container"> 18 <div id="repl-out" class="repl__code"></div> 19 <div id="error" class="output-error"></div> 20 </div> 21 </div> 22 23 <style> 24 .repl { 25 color: white; 26 27 display: grid; 28 grid-template: 29 "input-title" 30 "input-editor" 31 "output-title" 32 "output-editor"; 33 34 background: #353634; 35 } 36 37 @media (min-width: 992px) { 38 .repl:not(.vertical) { 39 grid-template: 40 "input-title output-title" 41 "input-editor output-editor" 42 / 50% 50%; 43 } 44 } 45 46 .input-title { grid-area: input-title } 47 .output-title { grid-area: output-title } 48 .input-editor { grid-area: input-editor } 49 .output-editor-container { grid-area: output-editor } 50 51 @media (min-width: 992px) { 52 .input-title, .input-editor { 53 border-right: 1px solid #4f504d; 54 } 55 } 56 57 .input-editor, .output-editor-container { 58 overflow-x: auto; 59 } 60 61 .output-editor-container { 62 position: relative; 63 } 64 65 .output-error { 66 position: absolute; 67 top: 0; 68 bottom: 0; 69 right: 0; 70 left: 0; 71 72 background: #702141; 73 font-family: monospace; 74 font-size: 0.83rem; 75 opacity: 0; 76 overflow-y: auto; 77 padding: 16px 24px; 78 text-align: left; 79 transition: opacity 0.25s ease-out; 80 white-space: pre; 81 display: none; 82 } 83 84 .output-error--visible { 85 opacity: 1; 86 display: block; 87 } 88 89 .repl__pane { 90 background: #353634; 91 border: 1px solid #4f504d; 92 position: relative; 93 } 94 95 @media (min-width: 992px) { 96 .repl__pane { 97 width: 50%; 98 } 99 } 100 101 .repl__code { 102 background: rgb(40, 44, 52); 103 font-family: monospace; 104 font-size: 0.83rem; 105 min-height: 125px; 106 height: 100%; 107 text-align: left; 108 } 109 @media (min-width: 992px) { 110 .repl__code { 111 font-size: 1rem; 112 min-height: 200px; 113 } 114 115 .vertical .repl__code { 116 min-height: 150px; 117 } 118 } 119 120 .repl h3 { 121 color: #b7b8b7; 122 font-size: 0.875rem; 123 margin: 0; 124 padding: 12px 24px; 125 text-align: center; 126 } 127 @media (min-width: 992px) { 128 .repl h3 { 129 font-size: 1rem; 130 text-align: left; 131 } 132 } 133 134 .repl__code .cm-wrap { 135 height: 100%; 136 } 137 </style> 138 </div> 139`; 140 141class MiniRepl extends HTMLElement { 142 _inEditor; 143 _outEditor; 144 145 _debouncedShowError = debounce(this._showError.bind(this), 1000); 146 147 constructor() { 148 super(); 149 150 this.attachShadow({ mode: "open" }); 151 this.shadowRoot.appendChild(template.content.cloneNode(true)); 152 } 153 154 connectedCallback() { 155 this._defaultCode = this.getAttribute("default-code"); 156 this._vertical = this.hasAttribute("vertical"); 157 this._options = JSON.parse(this.getAttribute("options")) ?? {}; 158 159 if (this._vertical) { 160 this.shadowRoot.querySelector(".repl").classList.add("vertical"); 161 } 162 163 const defaultCode = this.getAttribute("default-code") ?? ""; 164 165 this._inEditor = new EditorView({ 166 state: this._createInputEditorState(defaultCode), 167 parent: this.shadowRoot.querySelector("#repl-in"), 168 root: this.shadowRoot, 169 dispatch: (tr) => { 170 this._inEditor.update([tr]); 171 this._render(); 172 }, 173 }); 174 this._outEditor = new EditorView({ 175 state: this._createOutputEditorState(""), 176 parent: this.shadowRoot.querySelector("#repl-out"), 177 root: this.shadowRoot, 178 }); 179 180 this.shadowRoot.querySelector("#repl-in").addEventListener("click", () => { 181 this._inEditor.focus(); 182 }); 183 } 184 185 setInput(code) { 186 this._inEditor.setState(this._createInputEditorState(code)); 187 this._render(); 188 } 189 190 get options() { 191 return this._options; 192 } 193 set options(options) { 194 this._options = options; 195 this._render(); 196 } 197 198 _render() { 199 const inputCode = this._inEditor.state.doc.toString(); 200 201 if (!inputCode) { 202 this._outEditor.setState(this._createOutputEditorState("")); 203 this._hideError(); 204 return; 205 } 206 207 try { 208 const compiled = this._compile(inputCode); 209 this._outEditor.setState(this._createOutputEditorState(compiled)); 210 this._hideError(); 211 } catch (err) { 212 this._debouncedShowError(err.message); 213 } 214 } 215 216 _compile(code) { 217 return Babel.transform(code, this._options).code; 218 } 219 220 _createInputEditorState(content) { 221 return EditorState.create({ 222 doc: content, 223 extensions: [ 224 basicSetup, 225 oneDark, 226 javascriptLanguage, 227 //EditorView.lineWrapping, 228 ], 229 }); 230 } 231 232 _createOutputEditorState(content) { 233 return EditorState.create({ 234 doc: content, 235 extensions: [ 236 basicSetup, 237 oneDark, 238 javascriptLanguage, 239 //EditorView.lineWrapping, 240 EditorView.editable.of(false), 241 ], 242 }); 243 } 244 245 _showError(babelError) { 246 const err = this.shadowRoot.getElementById("error"); 247 err.textContent = babelError; 248 err.classList.add("output-error--visible"); 249 } 250 251 _hideError() { 252 this._debouncedShowError.cancel(); 253 this.shadowRoot 254 .getElementById("error") 255 .classList.remove("output-error--visible"); 256 } 257} 258 259customElements.define("mini-repl", MiniRepl); 260 261/* 262<h2>noDocumentAll</h2> 263 264<div class="repl" data-original-input="foo?.bar"></div> 265*/
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.