-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRichTplEditor.tish
More file actions
92 lines (88 loc) · 3.32 KB
/
Copy pathRichTplEditor.tish
File metadata and controls
92 lines (88 loc) · 3.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import {
tplDocumentToHighlightHtml
} from './SongEditorHighlight.tish'
// Reusable syntax-highlighted deck editor — the SAME transparent-textarea-over-highlighted-<pre> mechanism the
// main deck editor (CodeDebugView) uses, extracted so every "Raw" deck surface gets syntax highlighting.
//
// A vdom element can't host a contentEditable/highlight stack cleanly, so (like the main editor) we render a
// slot <div> and imperatively mount the pre+textarea into it AFTER paint, persisting the pair on rt._richEd[slotId]
// so a re-render doesn't recreate it (keeping focus/cursor) and re-appending it if the vdom cleared the slot.
// onCommit(text) fires on blur (matching the old <textarea onchange> commit semantics — partial deck while typing
// would error, so we commit on settle, not per keystroke). The <pre> re-highlights live on every keystroke.
import { h } from '@tishlang/lattish'
// Syntax highlight only — no playback step / no "recently applied" flash (those are song-editor specific).
fn highlightFragment(src) {
return tplDocumentToHighlightHtml(src, -1, false, 0, 0)
}
fn attachRichEditor(rt, slotId, value, onCommit) {
if ((globalThis.document === null) || (globalThis.document.getElementById === null)) {
return
}
let doc = globalThis.document
let slot = doc.getElementById(slotId)
if ((slot === null)) {
return
}
if ((rt._richEd === null)) {
rt._richEd = {}
}
let st = rt._richEd[slotId]
if (st !== null) {
// Already mounted — re-append if the vdom diff cleared the slot, and refresh the value when not focused
// (so an external change to the underlying deck is reflected; while focused we leave the user's edit alone).
if (!slot.contains(st.wrap)) {
slot.innerHTML = ""
slot.appendChild(st.wrap)
}
if (doc.activeElement !== st.ta && st.ta.value !== value) {
st.ta.value = value
st.pre.innerHTML = highlightFragment(value)
}
st.onCommit = onCommit
return
}
let wrap = doc.createElement("div")
wrap.className = "rich-tpl-stack tpl-song-mirror-stack"
let pre = doc.createElement("pre")
pre.className = "tpl-song-highlight-layer"
pre.setAttribute("aria-hidden", "true")
let ta = doc.createElement("textarea")
ta.className = "tpl-song-input-layer"
ta.setAttribute("spellcheck", "false")
ta.value = value
pre.innerHTML = highlightFragment(value)
let state = { wrap: wrap, pre: pre, ta: ta, onCommit: onCommit }
ta.oninput = () => {
pre.innerHTML = highlightFragment(ta.value)
}
ta.onscroll = () => {
pre.scrollTop = ta.scrollTop
pre.scrollLeft = ta.scrollLeft
}
ta.onchange = () => {
if (state.onCommit) {
state.onCommit(ta.value)
}
}
// Don't let a click in the editor start a card drag / collapse.
ta.onmousedown = (e) => {
if (e.stopPropagation) {
e.stopPropagation()
}
}
wrap.appendChild(pre)
wrap.appendChild(ta)
slot.innerHTML = ""
slot.appendChild(wrap)
rt._richEd[slotId] = state
}
// Render the editor. slotId must be STABLE per logical editor (e.g. channel+module) so the textarea persists.
// onCommit(text) fires on blur.
export fn RichTplEditor(rt, slotId, value, onCommit) {
if (rt !== null && typeof setTimeout !== "undefined") {
setTimeout(() => {
attachRichEditor(rt, slotId, value, onCommit)
}, 0)
}
return <div class="rich-tpl-slot" id={slotId} />
}