diff options
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | src/Layout.mjs | 26 | ||||
-rw-r--r-- | src/MenuItem.mjs | 36 | ||||
-rw-r--r-- | src/dumbymap.mjs | 18 | ||||
-rw-r--r-- | src/editor.mjs | 63 |
5 files changed, 104 insertions, 40 deletions
diff --git a/package.json b/package.json index 78a217c..7487e9c 100644 --- a/package.json +++ b/package.json | |||
@@ -28,6 +28,7 @@ | |||
28 | "dev": "npm run server", | 28 | "dev": "npm run server", |
29 | "lint": "standard --fix", | 29 | "lint": "standard --fix", |
30 | "style": "scripts/stylelint.sh", | 30 | "style": "scripts/stylelint.sh", |
31 | "docs": "jsdoc -c scripts/jsdoc.conf; xdg-open http://localhost:8080/docs/", | ||
31 | "prepack": "npm run lint && npm run style && npm run build", | 32 | "prepack": "npm run lint && npm run style && npm run build", |
32 | "postpack": "rm -rf dist/css dist/renderers; ln -sf `pwd`/src/css dist; cp node_modules/easymde/dist/easymde.min.css src/css; ln -sf `pwd`/mapclay/dist/renderers dist" | 33 | "postpack": "rm -rf dist/css dist/renderers; ln -sf `pwd`/src/css dist; cp node_modules/easymde/dist/easymde.min.css src/css; ln -sf `pwd`/mapclay/dist/renderers dist" |
33 | }, | 34 | }, |
diff --git a/src/Layout.mjs b/src/Layout.mjs index 5236475..7b14926 100644 --- a/src/Layout.mjs +++ b/src/Layout.mjs | |||
@@ -2,13 +2,17 @@ import PlainDraggable from 'plain-draggable' | |||
2 | import { onRemove, animateRectTransition } from './utils' | 2 | import { onRemove, animateRectTransition } from './utils' |
3 | 3 | ||
4 | /** | 4 | /** |
5 | * Layout. Basic class for layout | 5 | * Basic class for layout |
6 | */ | 6 | */ |
7 | export class Layout { | 7 | export class Layout { |
8 | /** | 8 | /** |
9 | * constructor. | 9 | * Creates a new Layout instance |
10 | * | 10 | * |
11 | * @param {} options | 11 | * @param {Object} options - The options for the layout |
12 | * @param {string} options.name - The name of the layout | ||
13 | * @param {Function} [options.enterHandler] - Handler called when entering the layout | ||
14 | * @param {Function} [options.leaveHandler] - Handler called when leaving the layout | ||
15 | * @throws {Error} If the layout name is not provided | ||
12 | */ | 16 | */ |
13 | constructor (options = {}) { | 17 | constructor (options = {}) { |
14 | if (!options.name) throw Error('Layout name is not given') | 18 | if (!options.name) throw Error('Layout name is not given') |
@@ -18,7 +22,9 @@ export class Layout { | |||
18 | } | 22 | } |
19 | 23 | ||
20 | /** | 24 | /** |
21 | * valueOf. | 25 | * Returns the name of the layout |
26 | * | ||
27 | * @returns {string} The name of the layout | ||
22 | */ | 28 | */ |
23 | valueOf = () => this.name | 29 | valueOf = () => this.name |
24 | } | 30 | } |
@@ -35,9 +41,12 @@ export class SideBySide extends Layout { | |||
35 | name = 'side-by-side' | 41 | name = 'side-by-side' |
36 | 42 | ||
37 | /** | 43 | /** |
38 | * enterHandler. | 44 | * Handler called when entering the Side-By-Side layout |
39 | * | 45 | * |
40 | * @param {} | 46 | * @param {Object} options - The options object |
47 | * @param {HTMLElement} options.container - The main container element | ||
48 | * @param {HTMLElement} options.htmlHolder - The HTML content holder | ||
49 | * @param {HTMLElement} options.showcase - The showcase element | ||
41 | */ | 50 | */ |
42 | enterHandler = ({ container, htmlHolder, showcase }) => { | 51 | enterHandler = ({ container, htmlHolder, showcase }) => { |
43 | const bar = document.createElement('div') | 52 | const bar = document.createElement('div') |
@@ -71,9 +80,10 @@ export class SideBySide extends Layout { | |||
71 | } | 80 | } |
72 | 81 | ||
73 | /** | 82 | /** |
74 | * leaveHandler. | 83 | * Handler called when leaving the Side-By-Side layout |
75 | * | 84 | * |
76 | * @param {} | 85 | * @param {Object} options - The options object |
86 | * @param {HTMLElement} options.container - The main container element | ||
77 | */ | 87 | */ |
78 | leaveHandler = ({ container }) => { | 88 | leaveHandler = ({ container }) => { |
79 | container.querySelector('.bar')?.remove() | 89 | container.querySelector('.bar')?.remove() |
diff --git a/src/MenuItem.mjs b/src/MenuItem.mjs index 0fea539..8b54539 100644 --- a/src/MenuItem.mjs +++ b/src/MenuItem.mjs | |||
@@ -1,15 +1,28 @@ | |||
1 | import { shiftByWindow } from './utils.mjs' | 1 | import { shiftByWindow } from './utils.mjs' |
2 | 2 | ||
3 | /** | 3 | /** |
4 | * Item. Basic Element for menu item | 4 | * @typedef {Object} RefLink |
5 | * @property {string} ref -- name of link | ||
6 | * @property {string} link -- content of link | ||
7 | * @property {string|null} title -- title of link | ||
8 | */ | ||
9 | |||
10 | /** | ||
11 | * Basic Element for menu item | ||
5 | * | 12 | * |
6 | * @extends {window.HTMLDivElement} | 13 | * @extends {window.HTMLDivElement} |
7 | */ | 14 | */ |
8 | export class Item extends window.HTMLDivElement { | 15 | export class Item extends window.HTMLDivElement { |
9 | /** | 16 | /** |
10 | * constructor. | 17 | * Creates a new Item instance |
11 | * | 18 | * |
12 | * @param {Object} | 19 | * @param {Object} options - The options for the item |
20 | * @param {string} [options.text] - The text content of the item | ||
21 | * @param {string} [options.innerHTML] - The HTML content of the item | ||
22 | * @param {string} [options.title] - The title attribute for the item | ||
23 | * @param {Function} [options.onclick] - The click event handler | ||
24 | * @param {string} [options.style] - The CSS style string | ||
25 | * @param {string[]} [options.className] - Additional CSS classes | ||
13 | */ | 26 | */ |
14 | constructor ({ text, innerHTML, title, onclick, style, className }) { | 27 | constructor ({ text, innerHTML, title, onclick, style, className }) { |
15 | super() | 28 | super() |
@@ -30,15 +43,18 @@ export class Item extends window.HTMLDivElement { | |||
30 | window.customElements.define('menu-item', Item, { extends: 'div' }) | 43 | window.customElements.define('menu-item', Item, { extends: 'div' }) |
31 | 44 | ||
32 | /** | 45 | /** |
33 | * Folder. Basic Element for menu item, it generate submenu on hover | 46 | * Basic Element for menu item that generates a submenu on hover |
34 | * | 47 | * |
35 | * @extends {window.HTMLDivElement} | 48 | * @extends {window.HTMLDivElement} |
36 | */ | 49 | */ |
37 | export class Folder extends window.HTMLDivElement { | 50 | export class Folder extends window.HTMLDivElement { |
38 | /** | 51 | /** |
39 | * constructor. | 52 | * Creates a new Folder instance |
40 | * | 53 | * |
41 | * @param {} | 54 | * @param {Object} options - The options for the folder |
55 | * @param {string} [options.text] - The text content of the folder | ||
56 | * @param {string} [options.innerHTML] - The HTML content of the folder | ||
57 | * @param {Item[]} options.items - The submenu items | ||
42 | */ | 58 | */ |
43 | constructor ({ text, innerHTML, items }) { | 59 | constructor ({ text, innerHTML, items }) { |
44 | super() | 60 | super() |
@@ -67,9 +83,11 @@ export class Folder extends window.HTMLDivElement { | |||
67 | window.customElements.define('menu-folder', Folder, { extends: 'div' }) | 83 | window.customElements.define('menu-folder', Folder, { extends: 'div' }) |
68 | 84 | ||
69 | /** | 85 | /** |
70 | * pickMapItem. | 86 | * Creates a menu item for picking a map |
71 | * | 87 | * |
72 | * @param {Function[]} options.utils | 88 | * @param {Object} options - The options object |
89 | * @param {Object} options.utils - Utility functions | ||
90 | * @returns {Folder} A Folder instance for picking a map | ||
73 | */ | 91 | */ |
74 | export const pickMapItem = ({ utils }) => | 92 | export const pickMapItem = ({ utils }) => |
75 | new Folder({ | 93 | new Folder({ |
@@ -372,7 +390,7 @@ export const restoreCamera = map => | |||
372 | * addRefLink. replace selected text into markdown link by reference style links | 390 | * addRefLink. replace selected text into markdown link by reference style links |
373 | * | 391 | * |
374 | * @param {CodeMirror} cm | 392 | * @param {CodeMirror} cm |
375 | * @param {Object[]} refLinks -- object for { ref, link } | 393 | * @param {RefLink[]} refLinks |
376 | */ | 394 | */ |
377 | export const addRefLink = (cm, refLinks) => | 395 | export const addRefLink = (cm, refLinks) => |
378 | new Folder({ | 396 | new Folder({ |
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index ac8392c..b01c73e 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs | |||
@@ -26,10 +26,11 @@ const defaultLayouts = [ | |||
26 | const mapCache = {} | 26 | const mapCache = {} |
27 | 27 | ||
28 | /** | 28 | /** |
29 | * markdown2HTML. | 29 | * Converts Markdown content to HTML and prepares it for DumbyMap rendering |
30 | * | 30 | * |
31 | * @param {HTMLElement} container -- Target Element to include generated HTML contents | 31 | * @param {HTMLElement} container - Target Element to include generated HTML contents |
32 | * @param {String} mdContent -- Texts in Markdown | 32 | * @param {string} mdContent - Texts in Markdown format |
33 | * @returns {Object} An object representing the DumbyMap instance | ||
33 | */ | 34 | */ |
34 | export const markdown2HTML = (container, mdContent) => { | 35 | export const markdown2HTML = (container, mdContent) => { |
35 | /** Prepare Elements for Container */ | 36 | /** Prepare Elements for Container */ |
@@ -106,11 +107,12 @@ export const markdown2HTML = (container, mdContent) => { | |||
106 | } | 107 | } |
107 | 108 | ||
108 | /** | 109 | /** |
109 | * generateMaps. | 110 | * Generates maps based on the provided configuration |
110 | * | 111 | * |
111 | * @param {HTMLElement} container -- Target Element contains HTML contents | 112 | * @param {HTMLElement} container - The container element for the maps |
112 | * @param {Number} options.delay -- delay of map generation, milliseconds | 113 | * @param {Object} options - Configuration options |
113 | * @return {Object} dumbymap -- Include and Elements and Methods about managing contents | 114 | * @param {number} [options.delay=1000] - Delay before rendering maps (in milliseconds) |
115 | * @param {Function} options.mapCallback - Callback function to be called after map rendering | ||
114 | */ | 116 | */ |
115 | export const generateMaps = (container, { layouts = [], delay, renderCallback } = {}) => { | 117 | export const generateMaps = (container, { layouts = [], delay, renderCallback } = {}) => { |
116 | /** Prepare Contaner/HTML Holder/Showcase */ | 118 | /** Prepare Contaner/HTML Holder/Showcase */ |
@@ -509,4 +511,4 @@ export const generateMaps = (container, { layouts = [], delay, renderCallback } | |||
509 | ) | 511 | ) |
510 | 512 | ||
511 | return Object.seal(dumbymap) | 513 | return Object.seal(dumbymap) |
512 | } | 514 | } \ No newline at end of file |
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) |