Download app/src/editor-ui.mjs from Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP: direct link, hf CLI and curl.
- Browser
- Download file 3.03 kB
-
https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/app/src/editor-ui.mjs
- Command line
-
hf download hf://spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/app/src/editor-ui.mjs
-
curl -L -o editor-ui.mjs https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/app/src/editor-ui.mjs
3.03 kB
| import { canHighlight, editorLanguage } from './editor-language.mjs'; | |
| export function createEditorUI(element) { | |
| const input = element.querySelector('#editor'), highlight = element.querySelector('#editor-highlight'); | |
| let path = '', revision = 0, timer, watchdog, worker, active = false, composing = false, queued; | |
| function syncScroll() { | |
| // Match the textarea's actual viewport, excluding either native scrollbar. | |
| highlight.style.width = input.clientWidth + 'px'; | |
| highlight.style.height = input.clientHeight + 'px'; | |
| highlight.scrollTop = input.scrollTop; | |
| highlight.scrollLeft = input.scrollLeft; | |
| } | |
| function plain() { element.classList.remove('is-highlighted'); } | |
| function stopWorker() { | |
| clearTimeout(watchdog); | |
| worker?.terminate(); worker = undefined; active = false; | |
| } | |
| function send() { | |
| if (active || !queued) return; | |
| const job = queued; queued = undefined; | |
| try { | |
| if (!worker) { | |
| worker = new Worker(new URL('./editor-highlight.worker.mjs', import.meta.url), { type: 'module' }); | |
| worker.onmessage = ({ data }) => { | |
| clearTimeout(watchdog); active = false; | |
| if (data.id === revision && data.html !== null) { | |
| // Prism escapes file contents before adding its own token spans. | |
| highlight.innerHTML = data.html + '\n'; | |
| syncScroll(); element.classList.add('is-highlighted'); | |
| } | |
| send(); | |
| }; | |
| worker.onerror = () => { stopWorker(); queued = undefined; }; | |
| } | |
| active = true; | |
| worker.postMessage(job); | |
| // A pathological regex can only occupy this worker, and is terminated. | |
| watchdog = setTimeout(() => { stopWorker(); send(); }, 1500); | |
| } catch { stopWorker(); } | |
| } | |
| function refresh(delay = 0) { | |
| revision++; clearTimeout(timer); queued = undefined; plain(); | |
| const language = editorLanguage(path), text = input.value; | |
| element.dataset.language = language ?? 'plain'; | |
| if (composing || !text || !language || !canHighlight(text)) return; | |
| const id = revision; | |
| timer = setTimeout(() => { queued = { id, text, language }; send(); }, delay); | |
| } | |
| input.addEventListener('input', () => refresh(80)); | |
| input.addEventListener('compositionstart', () => { composing = true; refresh(); }); | |
| input.addEventListener('compositionend', () => { composing = false; refresh(80); }); | |
| input.addEventListener('scroll', syncScroll, { passive: true }); | |
| const resize = new ResizeObserver(syncScroll); resize.observe(input); | |
| return { | |
| setFile(nextPath, text, placeholder = nextPath ? 'Empty file.' : 'Select a file to inspect or edit it.') { | |
| input.placeholder = placeholder; | |
| if (path === nextPath && input.value === text) return; | |
| const changed = path !== nextPath; | |
| path = nextPath; | |
| if (input.value !== text) input.value = text; | |
| if (changed) { input.scrollTop = 0; input.scrollLeft = 0; } | |
| refresh(); | |
| }, | |
| dispose() { clearTimeout(timer); stopWorker(); resize.disconnect(); }, | |
| }; | |
| } | |