Skip to content

Commit 61adc73

Browse files
committed
MD Editor: Finished conversion to Typescript
1 parent 7bbf591 commit 61adc73

File tree

9 files changed

+188
-138
lines changed

9 files changed

+188
-138
lines changed

package-lock.json

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"devDependencies": {
2222
"@eslint/js": "^9.21.0",
2323
"@lezer/generator": "^1.7.2",
24+
"@types/markdown-it": "^14.1.2",
2425
"@types/sortablejs": "^1.15.8",
2526
"chokidar-cli": "^3.0",
2627
"esbuild": "^0.25.0",

resources/js/markdown/codemirror.js renamed to resources/js/markdown/codemirror.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
import {provideKeyBindings} from './shortcuts';
2-
import {debounce} from '../services/util.ts';
3-
import {Clipboard} from '../services/clipboard.ts';
2+
import {debounce} from '../services/util';
3+
import {Clipboard} from '../services/clipboard';
4+
import {EditorView, ViewUpdate} from "@codemirror/view";
5+
import {MarkdownEditor} from "./index.mjs";
46

57
/**
68
* Initiate the codemirror instance for the markdown editor.
7-
* @param {MarkdownEditor} editor
8-
* @returns {Promise<EditorView>}
99
*/
10-
export async function init(editor) {
11-
const Code = await window.importVersioned('code');
10+
export async function init(editor: MarkdownEditor): Promise<EditorView> {
11+
const Code = await window.importVersioned('code') as (typeof import('../code/index.mjs'));
1212

13-
/**
14-
* @param {ViewUpdate} v
15-
*/
16-
function onViewUpdate(v) {
13+
function onViewUpdate(v: ViewUpdate) {
1714
if (v.docChanged) {
1815
editor.actions.updateAndRender();
1916
}
@@ -27,9 +24,13 @@ export async function init(editor) {
2724

2825
const domEventHandlers = {
2926
// Handle scroll to sync display view
30-
scroll: event => syncActive && onScrollDebounced(event),
27+
scroll: (event: Event) => syncActive && onScrollDebounced(event),
3128
// Handle image & content drag n drop
32-
drop: event => {
29+
drop: (event: DragEvent) => {
30+
if (!event.dataTransfer) {
31+
return;
32+
}
33+
3334
const templateId = event.dataTransfer.getData('bookstack/template');
3435
if (templateId) {
3536
event.preventDefault();
@@ -45,12 +46,16 @@ export async function init(editor) {
4546
}
4647
},
4748
// Handle dragover event to allow as drop-target in chrome
48-
dragover: event => {
49+
dragover: (event: DragEvent) => {
4950
event.preventDefault();
5051
},
5152
// Handle image paste
52-
paste: event => {
53-
const clipboard = new Clipboard(event.clipboardData || event.dataTransfer);
53+
paste: (event: ClipboardEvent) => {
54+
if (!event.clipboardData) {
55+
return;
56+
}
57+
58+
const clipboard = new Clipboard(event.clipboardData);
5459

5560
// Don't handle the event ourselves if no items exist of contains table-looking data
5661
if (!clipboard.hasItems() || clipboard.containsTabularData()) {
@@ -71,8 +76,9 @@ export async function init(editor) {
7176
provideKeyBindings(editor),
7277
);
7378

74-
// Add editor view to window for easy access/debugging.
79+
// Add editor view to the window for easy access/debugging.
7580
// Not part of official API/Docs
81+
// @ts-ignore
7682
window.mdEditorView = cm;
7783

7884
return cm;
Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,57 @@
1-
import {patchDomFromHtmlString} from '../services/vdom.ts';
1+
import { patchDomFromHtmlString } from '../services/vdom';
2+
import {MarkdownEditor} from "./index.mjs";
23

34
export class Display {
5+
protected editor: MarkdownEditor;
6+
protected container: HTMLIFrameElement;
7+
protected doc: Document | null = null;
8+
protected lastDisplayClick: number = 0;
49

5-
/**
6-
* @param {MarkdownEditor} editor
7-
*/
8-
constructor(editor) {
10+
constructor(editor: MarkdownEditor) {
911
this.editor = editor;
1012
this.container = editor.config.displayEl;
1113

12-
this.doc = null;
13-
this.lastDisplayClick = 0;
14-
15-
if (this.container.contentDocument.readyState === 'complete') {
14+
if (this.container.contentDocument?.readyState === 'complete') {
1615
this.onLoad();
1716
} else {
1817
this.container.addEventListener('load', this.onLoad.bind(this));
1918
}
2019

21-
this.updateVisibility(editor.settings.get('showPreview'));
22-
editor.settings.onChange('showPreview', show => this.updateVisibility(show));
20+
this.updateVisibility(Boolean(editor.settings.get('showPreview')));
21+
editor.settings.onChange('showPreview', (show) => this.updateVisibility(Boolean(show)));
2322
}
2423

25-
updateVisibility(show) {
26-
const wrap = this.container.closest('.markdown-editor-wrap');
27-
wrap.style.display = show ? null : 'none';
24+
protected updateVisibility(show: boolean): void {
25+
const wrap = this.container.closest('.markdown-editor-wrap') as HTMLElement;
26+
wrap.style.display = show ? '' : 'none';
2827
}
2928

30-
onLoad() {
29+
protected onLoad(): void {
3130
this.doc = this.container.contentDocument;
3231

32+
if (!this.doc) return;
33+
3334
this.loadStylesIntoDisplay();
3435
this.doc.body.className = 'page-content';
3536

3637
// Prevent markdown display link click redirect
3738
this.doc.addEventListener('click', this.onDisplayClick.bind(this));
3839
}
3940

40-
/**
41-
* @param {MouseEvent} event
42-
*/
43-
onDisplayClick(event) {
41+
protected onDisplayClick(event: MouseEvent): void {
4442
const isDblClick = Date.now() - this.lastDisplayClick < 300;
4543

46-
const link = event.target.closest('a');
44+
const link = (event.target as Element).closest('a');
4745
if (link !== null) {
4846
event.preventDefault();
49-
window.open(link.getAttribute('href'));
47+
const href = link.getAttribute('href');
48+
if (href) {
49+
window.open(href);
50+
}
5051
return;
5152
}
5253

53-
const drawing = event.target.closest('[drawio-diagram]');
54+
const drawing = (event.target as Element).closest('[drawio-diagram]') as HTMLElement;
5455
if (drawing !== null && isDblClick) {
5556
this.editor.actions.editDrawing(drawing);
5657
return;
@@ -59,10 +60,12 @@ export class Display {
5960
this.lastDisplayClick = Date.now();
6061
}
6162

62-
loadStylesIntoDisplay() {
63+
protected loadStylesIntoDisplay(): void {
64+
if (!this.doc) return;
65+
6366
this.doc.documentElement.classList.add('markdown-editor-display');
6467

65-
// Set display to be dark mode if parent is
68+
// Set display to be dark mode if the parent is
6669
if (document.documentElement.classList.contains('dark-mode')) {
6770
this.doc.documentElement.style.backgroundColor = '#222';
6871
this.doc.documentElement.classList.add('dark-mode');
@@ -71,39 +74,42 @@ export class Display {
7174
this.doc.head.innerHTML = '';
7275
const styles = document.head.querySelectorAll('style,link[rel=stylesheet]');
7376
for (const style of styles) {
74-
const copy = style.cloneNode(true);
77+
const copy = style.cloneNode(true) as HTMLElement;
7578
this.doc.head.appendChild(copy);
7679
}
7780
}
7881

7982
/**
8083
* Patch the display DOM with the given HTML content.
81-
* @param {String} html
8284
*/
83-
patchWithHtml(html) {
84-
const {body} = this.doc;
85+
public patchWithHtml(html: string): void {
86+
if (!this.doc) return;
87+
88+
const { body } = this.doc;
8589

8690
if (body.children.length === 0) {
8791
const wrap = document.createElement('div');
8892
this.doc.body.append(wrap);
8993
}
9094

91-
const target = body.children[0];
95+
const target = body.children[0] as HTMLElement;
9296

9397
patchDomFromHtmlString(target, html);
9498
}
9599

96100
/**
97101
* Scroll to the given block index within the display content.
98102
* Will scroll to the end if the index is -1.
99-
* @param {Number} index
100103
*/
101-
scrollToIndex(index) {
102-
const elems = this.doc.body?.children[0]?.children;
103-
if (elems && elems.length <= index) return;
104+
public scrollToIndex(index: number): void {
105+
const elems = this.doc?.body?.children[0]?.children;
106+
if (!elems || elems.length <= index) return;
104107

105108
const topElem = (index === -1) ? elems[elems.length - 1] : elems[index];
106-
topElem.scrollIntoView({block: 'start', inline: 'nearest', behavior: 'smooth'});
109+
(topElem as Element).scrollIntoView({
110+
block: 'start',
111+
inline: 'nearest',
112+
behavior: 'smooth'
113+
});
107114
}
108-
109-
}
115+
}

resources/js/markdown/index.mts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {EditorView} from "@codemirror/view";
99
export interface MarkdownEditorConfig {
1010
pageId: string;
1111
container: Element;
12-
displayEl: Element;
12+
displayEl: HTMLIFrameElement;
1313
inputEl: HTMLTextAreaElement;
1414
drawioUrl: string;
1515
settingInputs: HTMLInputElement[];
@@ -27,18 +27,13 @@ export interface MarkdownEditor {
2727

2828
/**
2929
* Initiate a new Markdown editor instance.
30-
* @param {MarkdownEditorConfig} config
31-
* @returns {Promise<MarkdownEditor>}
3230
*/
33-
export async function init(config) {
34-
/**
35-
* @type {MarkdownEditor}
36-
*/
31+
export async function init(config: MarkdownEditorConfig): Promise<MarkdownEditor> {
3732
const editor: MarkdownEditor = {
3833
config,
3934
markdown: new Markdown(),
4035
settings: new Settings(config.settingInputs),
41-
};
36+
} as MarkdownEditor;
4237

4338
editor.actions = new Actions(editor);
4439
editor.display = new Display(editor);

resources/js/markdown/markdown.js renamed to resources/js/markdown/markdown.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
11
import MarkdownIt from 'markdown-it';
2+
// @ts-ignore
23
import mdTasksLists from 'markdown-it-task-lists';
34

45
export class Markdown {
6+
protected renderer: MarkdownIt;
57

68
constructor() {
79
this.renderer = new MarkdownIt({html: true});
810
this.renderer.use(mdTasksLists, {label: true});
911
}
1012

1113
/**
12-
* Get the front-end render used to convert markdown to HTML.
13-
* @returns {MarkdownIt}
14+
* Get the front-end render used to convert Markdown to HTML.
1415
*/
15-
getRenderer() {
16+
getRenderer(): MarkdownIt {
1617
return this.renderer;
1718
}
1819

1920
/**
2021
* Convert the given Markdown to HTML.
21-
* @param {String} markdown
22-
* @returns {String}
2322
*/
24-
render(markdown) {
23+
render(markdown: string): string {
2524
return this.renderer.render(markdown);
2625
}
2726

0 commit comments

Comments
 (0)