aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/editor.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'src/editor.mjs')
-rw-r--r--src/editor.mjs102
1 files changed, 91 insertions, 11 deletions
diff --git a/src/editor.mjs b/src/editor.mjs
index e0c90c3..5409c3f 100644
--- a/src/editor.mjs
+++ b/src/editor.mjs
@@ -8,21 +8,22 @@ import { shiftByWindow } from './utils.mjs'
8// Set up Containers {{{ 8// Set up Containers {{{
9 9
10const context = document.querySelector('[data-mode]') 10const context = document.querySelector('[data-mode]')
11const HtmlContainer = document.querySelector('.DumbyMap') 11const dumbyContainer = document.querySelector('.DumbyMap')
12const textArea = document.querySelector('.editor textarea') 12const textArea = document.querySelector('.editor textarea')
13let dumbymap 13let dumbymap
14 14
15new window.MutationObserver(() => { 15new window.MutationObserver(() => {
16 const mode = context.getAttribute('data-mode') 16 const mode = context.getAttribute('data-mode')
17 const layout = HtmlContainer.getAttribute('data-layout') 17 const layout = dumbyContainer.getAttribute('data-layout')
18 if (mode === 'editing' && layout !== 'normal') { 18 if (mode === 'editing' && layout !== 'normal') {
19 HtmlContainer.setAttribute('data-layout', 'normal') 19 dumbyContainer.setAttribute('data-layout', 'normal')
20 } 20 }
21}).observe(context, { 21}).observe(context, {
22 attributes: true, 22 attributes: true,
23 attributeFilter: ['data-mode'], 23 attributeFilter: ['data-mode'],
24 attributeOldValue: true 24 attributeOldValue: true
25}) 25})
26
26/** 27/**
27 * toggle editing mode 28 * toggle editing mode
28 */ 29 */
@@ -167,6 +168,82 @@ if (contentFromHash) {
167} 168}
168// }}} 169// }}}
169// Set up logic about editor content {{{ 170// Set up logic about editor content {{{
171
172const htmlOnScroll = (ele) => () => {
173 if (textArea.dataset.scrollLine) return
174
175 const threshold = ele.scrollTop + window.innerHeight / 2 + 30
176 const block = Array.from(ele.children)
177 .findLast(e => e.offsetTop < threshold) ??
178 ele.firstChild
179
180 const line = Array.from(block.querySelectorAll('p'))
181 .findLast(e => e.offsetTop + block.offsetTop < threshold)
182 const linenumber = line?.dataset?.sourceLine
183 if (!linenumber) return
184 const offset = (line.offsetTop + block.offsetTop - ele.scrollTop)
185
186 clearTimeout(dumbyContainer.timer)
187 if (linenumber) {
188 dumbyContainer.dataset.scrollLine = linenumber + '/' + offset
189 dumbyContainer.timer = setTimeout(
190 () => delete dumbyContainer.dataset.scrollLine,
191 50
192 )
193 }
194}
195
196// Sync CodeMirror LineNumber with HTML Contents
197new window.MutationObserver(() => {
198 const line = dumbyContainer.dataset.scrollLine
199 if (line) {
200 const [lineNumber, offset] = line.split('/')
201
202 if (!isNaN(lineNumber)) {
203 cm.scrollIntoView({ line: lineNumber, ch: 0 }, offset)
204 }
205 }
206}).observe(dumbyContainer, {
207 attributes: true,
208 attributeFilter: ['data-scroll-line']
209})
210
211cm.on('scroll', () => {
212 if (dumbyContainer.dataset.scrollLine) return
213
214 const scrollInfo = cm.getScrollInfo()
215 const lineNumber = cm.lineAtHeight(scrollInfo.top, 'local')
216 textArea.dataset.scrollLine = lineNumber
217
218 clearTimeout(textArea.timer)
219 textArea.timer = setTimeout(
220 () => delete textArea.dataset.scrollLine,
221 1000
222 )
223})
224
225// Sync HTML Contents with CodeMirror LineNumber
226new window.MutationObserver(() => {
227 const line = textArea.dataset.scrollLine
228 let lineNumber = Number(line)
229 let p
230 if (isNaN(lineNumber)) return
231
232 const paragraphs = Array.from(dumbymap.htmlHolder.querySelectorAll('p'))
233 do {
234 p = paragraphs.find(p => Number(p.dataset.sourceLine) === lineNumber)
235 lineNumber++
236 } while (!p && lineNumber < cm.doc.size)
237 if (!p) return
238
239 const coords = cm.charCoords({ line: lineNumber, ch: 0 }, 'window')
240 p.scrollIntoView()
241 dumbymap.htmlHolder.scrollBy(0, -coords.top + 30)
242}).observe(textArea, {
243 attributes: true,
244 attributeFilter: ['data-scroll-line']
245})
246
170/** 247/**
171 * afterMapRendered. Callback of map rendered 248 * afterMapRendered. Callback of map rendered
172 * 249 *
@@ -180,8 +257,8 @@ const afterMapRendered = map => {
180 // // TODO... 257 // // TODO...
181 // } 258 // }
182} 259}
183markdown2HTML(HtmlContainer, editor.value()) 260markdown2HTML(dumbyContainer, editor.value())
184dumbymap = generateMaps(HtmlContainer, afterMapRendered) 261dumbymap = generateMaps(dumbyContainer, afterMapRendered)
185 262
186/** 263/**
187 * addClassToCodeLines. Quick hack to style lines inside code block 264 * addClassToCodeLines. Quick hack to style lines inside code block
@@ -264,10 +341,13 @@ const completeForCodeBlock = change => {
264 * update content of HTML about Dumbymap 341 * update content of HTML about Dumbymap
265 */ 342 */
266const updateDumbyMap = () => { 343const updateDumbyMap = () => {
267 markdown2HTML(HtmlContainer, editor.value()) 344 markdown2HTML(dumbyContainer, editor.value())
268 // TODO Test if generate maps intantly is OK with map cache 345 // TODO Test if generate maps intantly is OK with map cache
269 // debounceForMap(HtmlContainer, afterMapRendered) 346 // debounceForMap(HtmlContainer, afterMapRendered)
270 dumbymap = generateMaps(HtmlContainer, afterMapRendered) 347 dumbymap = generateMaps(dumbyContainer, afterMapRendered)
348
349 const htmlHolder = dumbymap.htmlHolder
350 htmlHolder.onscroll = htmlOnScroll(htmlHolder)
271} 351}
272 352
273updateDumbyMap() 353updateDumbyMap()
@@ -282,7 +362,7 @@ cm.on('change', (_, change) => {
282// Set class for focus 362// Set class for focus
283cm.on('focus', () => { 363cm.on('focus', () => {
284 cm.getWrapperElement().classList.add('focus') 364 cm.getWrapperElement().classList.add('focus')
285 HtmlContainer.classList.remove('focus') 365 dumbyContainer.classList.remove('focus')
286}) 366})
287 367
288cm.on('beforeChange', (_, change) => { 368cm.on('beforeChange', (_, change) => {
@@ -643,7 +723,7 @@ cm.on('blur', () => {
643 cm.focus() 723 cm.focus()
644 } else { 724 } else {
645 cm.getWrapperElement().classList.remove('focus') 725 cm.getWrapperElement().classList.remove('focus')
646 HtmlContainer.classList.add('focus') 726 dumbyContainer.classList.add('focus')
647 } 727 }
648}) 728})
649// }}} 729// }}}
@@ -730,11 +810,11 @@ document.onkeydown = e => {
730// Layout Switch {{{ 810// Layout Switch {{{
731new window.MutationObserver(mutaions => { 811new window.MutationObserver(mutaions => {
732 const mutation = mutaions.at(-1) 812 const mutation = mutaions.at(-1)
733 const layout = HtmlContainer.getAttribute('data-layout') 813 const layout = dumbyContainer.getAttribute('data-layout')
734 if (layout !== 'normal' || mutation.oldValue === 'normal') { 814 if (layout !== 'normal' || mutation.oldValue === 'normal') {
735 context.setAttribute('data-mode', '') 815 context.setAttribute('data-mode', '')
736 } 816 }
737}).observe(HtmlContainer, { 817}).observe(dumbyContainer, {
738 attributes: true, 818 attributes: true,
739 attributeFilter: ['data-layout'], 819 attributeFilter: ['data-layout'],
740 attributeOldValue: true 820 attributeOldValue: true