diff options
Diffstat (limited to 'src/editor.mjs')
-rw-r--r-- | src/editor.mjs | 63 |
1 files changed, 48 insertions, 15 deletions
diff --git a/src/editor.mjs b/src/editor.mjs index 8cb7045..5c65af4 100644 --- a/src/editor.mjs +++ b/src/editor.mjs | |||
@@ -1,5 +1,4 @@ | |||
1 | /* global EasyMDE */ | 1 | /* global EasyMDE */ |
2 | /* eslint no-undef: "error" */ | ||
3 | import { markdown2HTML, generateMaps } from './dumbymap' | 2 | import { markdown2HTML, generateMaps } from './dumbymap' |
4 | import { defaultAliases, parseConfigsFromYaml } from 'mapclay' | 3 | import { defaultAliases, parseConfigsFromYaml } from 'mapclay' |
5 | import * as menuItem from './MenuItem' | 4 | import * as menuItem from './MenuItem' |
@@ -7,18 +6,41 @@ import { addAnchorByPoint } from './dumbyUtils.mjs' | |||
7 | import { shiftByWindow } from './utils.mjs' | 6 | import { shiftByWindow } from './utils.mjs' |
8 | import LeaderLine from 'leader-line' | 7 | import LeaderLine from 'leader-line' |
9 | 8 | ||
9 | /** | ||
10 | * @typedef {Object} RefLink | ||
11 | * @property {string} ref -- name of link | ||
12 | * @property {string} link -- content of link | ||
13 | * @property {string|null} title -- title of link | ||
14 | */ | ||
15 | |||
10 | // Set up Containers {{{ | 16 | // Set up Containers {{{ |
11 | /** Variables about dumbymap and editor **/ | 17 | /** Variables about dumbymap and editor **/ |
12 | const url = new URL(window.location) | 18 | const url = new URL(window.location) |
13 | const context = document.querySelector('[data-mode]') | 19 | const context = document.querySelector('[data-mode]') |
14 | const dumbyContainer = document.querySelector('.DumbyMap') | 20 | const dumbyContainer = document.querySelector('.DumbyMap') |
21 | dumbyContainer.dataset.scrollLine = '' | ||
15 | const textArea = document.querySelector('.editor textarea') | 22 | const textArea = document.querySelector('.editor textarea') |
16 | let dumbymap | 23 | let dumbymap |
17 | 24 | ||
18 | const refLinkPattern = /\[([^\x5B\x5D]+)\]:\s+(.+)/ | 25 | /** Variables about Reference Style Links in Markdown */ |
26 | const refLinkPattern = /\[([^\x5B\x5D]+)\]:\s+(\S+)(\s["'](\S+)["'])?/ | ||
19 | let refLinks = [] | 27 | let refLinks = [] |
28 | |||
29 | /** | ||
30 | * Validates if the given anchor name is unique | ||
31 | * | ||
32 | * @param {string} anchorName - The anchor name to validate | ||
33 | * @returns {boolean} True if the anchor name is unique, false otherwise | ||
34 | */ | ||
20 | const validateAnchorName = anchorName => | 35 | const validateAnchorName = anchorName => |
21 | !refLinks.find(obj => obj.ref === anchorName) | 36 | !refLinks.find(obj => obj.ref === anchorName) |
37 | |||
38 | /** | ||
39 | * Appends a reference link to the CodeMirror instance | ||
40 | * | ||
41 | * @param {CodeMirror} cm - The CodeMirror instance | ||
42 | * @param {RefLink} refLink - The reference link to append | ||
43 | */ | ||
22 | const appendRefLink = (cm, refLink) => { | 44 | const appendRefLink = (cm, refLink) => { |
23 | const { ref, link, title } = refLink | 45 | const { ref, link, title } = refLink |
24 | let refLinkString = `\n[${ref}]: ${link} "${title ?? ''}"` | 46 | let refLinkString = `\n[${ref}]: ${link} "${title ?? ''}"` |
@@ -46,7 +68,7 @@ new window.MutationObserver(() => { | |||
46 | attributeOldValue: true, | 68 | attributeOldValue: true, |
47 | }) | 69 | }) |
48 | /** | 70 | /** |
49 | * toggleEditing: toggle editing mode | 71 | * Toggles the editing mode |
50 | */ | 72 | */ |
51 | const toggleEditing = () => { | 73 | const toggleEditing = () => { |
52 | const mode = context.dataset.mode | 74 | const mode = context.dataset.mode |
@@ -236,12 +258,15 @@ const editor = new EasyMDE({ | |||
236 | /** CodeMirror Instance **/ | 258 | /** CodeMirror Instance **/ |
237 | const cm = editor.codemirror | 259 | const cm = editor.codemirror |
238 | 260 | ||
239 | /** Ref Links **/ | 261 | /** |
262 | * getRefLinks from contents of editor | ||
263 | * @return {RefLink[]} refLinks | ||
264 | */ | ||
240 | const getRefLinks = () => editor.value() | 265 | const getRefLinks = () => editor.value() |
241 | .split('\n') | 266 | .split('\n') |
242 | .map(line => { | 267 | .map(line => { |
243 | const [, ref, link] = line.match(refLinkPattern) ?? [] | 268 | const [, ref, link,, title] = line.match(refLinkPattern) ?? [] |
244 | return { ref, link } | 269 | return { ref, link, title } |
245 | }) | 270 | }) |
246 | .filter(({ ref, link }) => ref && link) | 271 | .filter(({ ref, link }) => ref && link) |
247 | 272 | ||
@@ -284,8 +309,12 @@ if (url.searchParams.get('content') === 'tutorial') { | |||
284 | // }}} | 309 | // }}} |
285 | // Set up logic about editor content {{{ | 310 | // Set up logic about editor content {{{ |
286 | 311 | ||
287 | /** Sync scroll from HTML to CodeMirror **/ | 312 | /** |
288 | const htmlOnScroll = (ele) => () => { | 313 | * updateScrollLine. Update data attribute by scroll on given element |
314 | * | ||
315 | * @param {HTMLElement} ele | ||
316 | */ | ||
317 | const updateScrollLine = (ele) => () => { | ||
289 | if (textArea.dataset.scrollLine) return | 318 | if (textArea.dataset.scrollLine) return |
290 | 319 | ||
291 | const threshold = ele.scrollTop + window.innerHeight / 2 + 30 | 320 | const threshold = ele.scrollTop + window.innerHeight / 2 + 30 |
@@ -300,14 +329,14 @@ const htmlOnScroll = (ele) => () => { | |||
300 | const offset = (line.offsetTop + block.offsetTop - ele.scrollTop) | 329 | const offset = (line.offsetTop + block.offsetTop - ele.scrollTop) |
301 | 330 | ||
302 | if (linenumber) { | 331 | if (linenumber) { |
303 | dumbyContainer.dataset.scrollLine = linenumber + '/' + offset | 332 | ele.closest('[data-scroll-line]').dataset.scrollLine = linenumber + '/' + offset |
304 | } | 333 | } |
305 | } | 334 | } |
306 | 335 | ||
307 | new window.MutationObserver(() => { | 336 | new window.MutationObserver(() => { |
308 | clearTimeout(dumbyContainer.timer) | 337 | clearTimeout(dumbyContainer.timer) |
309 | dumbyContainer.timer = setTimeout( | 338 | dumbyContainer.timer = setTimeout( |
310 | () => delete dumbyContainer.dataset.scrollLine, | 339 | () => { dumbyContainer.dataset.scrollLine = '' }, |
311 | 50, | 340 | 50, |
312 | ) | 341 | ) |
313 | 342 | ||
@@ -324,7 +353,11 @@ new window.MutationObserver(() => { | |||
324 | attributeFilter: ['data-scroll-line'], | 353 | attributeFilter: ['data-scroll-line'], |
325 | }) | 354 | }) |
326 | 355 | ||
327 | const setScrollLine = () => { | 356 | /** |
357 | * updateScrollLineByCodeMirror. | ||
358 | * @param {CodeMirror} cm | ||
359 | */ | ||
360 | const updateCMScrollLine = (cm) => { | ||
328 | if (dumbyContainer.dataset.scrollLine) return | 361 | if (dumbyContainer.dataset.scrollLine) return |
329 | 362 | ||
330 | const lineNumber = cm.getCursor()?.line ?? | 363 | const lineNumber = cm.getCursor()?.line ?? |
@@ -332,14 +365,14 @@ const setScrollLine = () => { | |||
332 | textArea.dataset.scrollLine = lineNumber | 365 | textArea.dataset.scrollLine = lineNumber |
333 | } | 366 | } |
334 | cm.on('scroll', () => { | 367 | cm.on('scroll', () => { |
335 | if (cm.hasFocus()) setScrollLine() | 368 | if (cm.hasFocus()) updateCMScrollLine(cm) |
336 | }) | 369 | }) |
337 | 370 | ||
338 | /** Sync scroll from CodeMirror to HTML **/ | 371 | /** Sync scroll from CodeMirror to HTML **/ |
339 | new window.MutationObserver(() => { | 372 | new window.MutationObserver(() => { |
340 | clearTimeout(textArea.timer) | 373 | clearTimeout(textArea.timer) |
341 | textArea.timer = setTimeout( | 374 | textArea.timer = setTimeout( |
342 | () => delete textArea.dataset.scrollLine, | 375 | () => { textArea.dataset.scrollLine = '' }, |
343 | 1000, | 376 | 1000, |
344 | ) | 377 | ) |
345 | 378 | ||
@@ -486,7 +519,7 @@ const updateDumbyMap = (callback = null) => { | |||
486 | dumbymap = generateMaps(dumbyContainer) | 519 | dumbymap = generateMaps(dumbyContainer) |
487 | // Set onscroll callback | 520 | // Set onscroll callback |
488 | const htmlHolder = dumbymap.htmlHolder | 521 | const htmlHolder = dumbymap.htmlHolder |
489 | htmlHolder.onscroll = htmlOnScroll(htmlHolder) | 522 | htmlHolder.onscroll = updateScrollLine(htmlHolder) |
490 | // Set oncontextmenu callback | 523 | // Set oncontextmenu callback |
491 | dumbymap.utils.setContextMenu(menuForEditor) | 524 | dumbymap.utils.setContextMenu(menuForEditor) |
492 | 525 | ||
@@ -497,7 +530,7 @@ updateDumbyMap() | |||
497 | // Re-render HTML by editor content | 530 | // Re-render HTML by editor content |
498 | cm.on('change', (_, change) => { | 531 | cm.on('change', (_, change) => { |
499 | updateDumbyMap(() => { | 532 | updateDumbyMap(() => { |
500 | setScrollLine() | 533 | updateCMScrollLine(cm) |
501 | }) | 534 | }) |
502 | addClassToCodeLines() | 535 | addClassToCodeLines() |
503 | completeForCodeBlock(change) | 536 | completeForCodeBlock(change) |