From cd5912f90461ccb75807872a9016079fe1ee1dca Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Fri, 23 Jun 2023 11:32:28 +0000 Subject: [PATCH 01/20] ... --- lib/editor.ts | 0 lib/layout.ts | 0 package.json | 3 ++- pnpm-lock.yaml | 50 ++++++++++++++++---------------------------------- 4 files changed, 18 insertions(+), 35 deletions(-) create mode 100644 lib/editor.ts create mode 100644 lib/layout.ts diff --git a/lib/editor.ts b/lib/editor.ts new file mode 100644 index 0000000..e69de29 diff --git a/lib/layout.ts b/lib/layout.ts new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json index 5192e9d..a1d1638 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ }, "dependencies": { "@codemirror/language": "^6.6.0", - "@lezer/highlight": "^1.1.4" + "@lezer/highlight": "^1.1.4", + "superfine": "^8.2.0" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a7aa18..f4d9ec8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,9 @@ dependencies: '@lezer/highlight': specifier: ^1.1.4 version: 1.1.4 + superfine: + specifier: ^8.2.0 + version: 8.2.0 devDependencies: '@codemirror/commands': @@ -17,7 +20,7 @@ devDependencies: version: 6.1.1 '@codemirror/language-data': specifier: ^6.2.0 - version: 6.2.0(@codemirror/state@6.2.0)(@codemirror/view@6.9.4) + version: 6.2.0(@codemirror/state@6.2.0)(@codemirror/view@6.9.4)(@lezer/common@1.0.2) '@codemirror/state': specifier: ^6.2.0 version: 6.2.0 @@ -57,19 +60,6 @@ devDependencies: packages: - /@codemirror/autocomplete@6.5.1(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.9.4): - resolution: {integrity: sha512-/Sv9yJmqyILbZ26U4LBHnAtbikuVxWUp+rQ8BXuRGtxZfbfKOY/WPbsUtvSP2h0ZUZMlkxV/hqbKRFzowlA6xw==} - peerDependencies: - '@codemirror/language': ^6.0.0 - '@codemirror/state': ^6.0.0 - '@codemirror/view': ^6.0.0 - '@lezer/common': ^1.0.0 - dependencies: - '@codemirror/language': 6.6.0 - '@codemirror/state': 6.2.0 - '@codemirror/view': 6.9.4 - dev: true - /@codemirror/autocomplete@6.5.1(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.9.4)(@lezer/common@1.0.2): resolution: {integrity: sha512-/Sv9yJmqyILbZ26U4LBHnAtbikuVxWUp+rQ8BXuRGtxZfbfKOY/WPbsUtvSP2h0ZUZMlkxV/hqbKRFzowlA6xw==} peerDependencies: @@ -110,18 +100,6 @@ packages: '@lezer/cpp': 1.1.0 dev: true - /@codemirror/lang-css@6.1.1(@codemirror/view@6.9.4): - resolution: {integrity: sha512-P6jdNEHyRcqqDgbvHYyC9Wxkek0rnG3a9aVSRi4a7WrjPbQtBTaOmvYpXmm13zZMAatO4Oqpac+0QZs7sy+LnQ==} - dependencies: - '@codemirror/autocomplete': 6.5.1(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.9.4) - '@codemirror/language': 6.6.0 - '@codemirror/state': 6.2.0 - '@lezer/css': 1.1.1 - transitivePeerDependencies: - - '@codemirror/view' - - '@lezer/common' - dev: true - /@codemirror/lang-css@6.1.1(@codemirror/view@6.9.4)(@lezer/common@1.0.2): resolution: {integrity: sha512-P6jdNEHyRcqqDgbvHYyC9Wxkek0rnG3a9aVSRi4a7WrjPbQtBTaOmvYpXmm13zZMAatO4Oqpac+0QZs7sy+LnQ==} dependencies: @@ -195,10 +173,10 @@ packages: '@lezer/php': 1.0.1 dev: true - /@codemirror/lang-python@6.1.2(@codemirror/state@6.2.0)(@codemirror/view@6.9.4): + /@codemirror/lang-python@6.1.2(@codemirror/state@6.2.0)(@codemirror/view@6.9.4)(@lezer/common@1.0.2): resolution: {integrity: sha512-nbQfifLBZstpt6Oo4XxA2LOzlSp4b/7Bc5cmodG1R+Cs5PLLCTUvsMNWDnziiCfTOG/SW1rVzXq/GbIr6WXlcw==} dependencies: - '@codemirror/autocomplete': 6.5.1(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.9.4) + '@codemirror/autocomplete': 6.5.1(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.9.4)(@lezer/common@1.0.2) '@codemirror/language': 6.6.0 '@lezer/python': 1.1.4 transitivePeerDependencies: @@ -214,10 +192,10 @@ packages: '@lezer/rust': 1.0.0 dev: true - /@codemirror/lang-sql@6.4.1(@codemirror/view@6.9.4): + /@codemirror/lang-sql@6.4.1(@codemirror/view@6.9.4)(@lezer/common@1.0.2): resolution: {integrity: sha512-PFB56L+A0WGY35uRya+Trt5g19V9k2V9X3c55xoFW4RgiATr/yLqWsbbnEsdxuMn5tLpuikp7Kmj9smRsqBXAg==} dependencies: - '@codemirror/autocomplete': 6.5.1(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.9.4) + '@codemirror/autocomplete': 6.5.1(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.9.4)(@lezer/common@1.0.2) '@codemirror/language': 6.6.0 '@codemirror/state': 6.2.0 '@lezer/highlight': 1.1.4 @@ -258,21 +236,21 @@ packages: - '@codemirror/view' dev: true - /@codemirror/language-data@6.2.0(@codemirror/state@6.2.0)(@codemirror/view@6.9.4): + /@codemirror/language-data@6.2.0(@codemirror/state@6.2.0)(@codemirror/view@6.9.4)(@lezer/common@1.0.2): resolution: {integrity: sha512-X0k33cmGmhdBVjKABgVtF6dH+zC0ODjLbCHCciY4kGimGX0hr7aui9+upj0Fumu8nr9ggQ36xLAR0EQ1JktKhw==} dependencies: '@codemirror/lang-angular': 0.1.0 '@codemirror/lang-cpp': 6.0.2 - '@codemirror/lang-css': 6.1.1(@codemirror/view@6.9.4) + '@codemirror/lang-css': 6.1.1(@codemirror/view@6.9.4)(@lezer/common@1.0.2) '@codemirror/lang-html': 6.4.3 '@codemirror/lang-java': 6.0.1 '@codemirror/lang-javascript': 6.1.6 '@codemirror/lang-json': 6.0.1 '@codemirror/lang-markdown': 6.1.1 '@codemirror/lang-php': 6.0.1 - '@codemirror/lang-python': 6.1.2(@codemirror/state@6.2.0)(@codemirror/view@6.9.4) + '@codemirror/lang-python': 6.1.2(@codemirror/state@6.2.0)(@codemirror/view@6.9.4)(@lezer/common@1.0.2) '@codemirror/lang-rust': 6.0.1 - '@codemirror/lang-sql': 6.4.1(@codemirror/view@6.9.4) + '@codemirror/lang-sql': 6.4.1(@codemirror/view@6.9.4)(@lezer/common@1.0.2) '@codemirror/lang-vue': 0.1.1 '@codemirror/lang-wast': 6.0.1 '@codemirror/lang-xml': 6.0.2(@codemirror/view@6.9.4) @@ -1937,6 +1915,10 @@ packages: /style-mod@4.0.2: resolution: {integrity: sha512-C4myMmRTO8iaC5Gg+N1ftK2WT4eXUTMAa+HEFPPrfVeO/NtqLTtAmV1HbqnuGtLwCek44Ra76fdGUkSqjiMPcQ==} + /superfine@8.2.0: + resolution: {integrity: sha512-YnmDEesO2COpGEmWIYJ6StZVdJqTiRU4ur+8WwHRLOeU9Rad2ogvd3cZzuX+QXxshWWqP2HE+jWI5wACqTGBjg==} + dev: false + /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} From 4e16490b538e3ffabf6e688a0e35507fd859c322 Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Fri, 23 Jun 2023 16:22:37 +0000 Subject: [PATCH 02/20] .. --- lib/index.ts | 102 ++++++++++++++++++++++++++++++++++++++++++++----- package.json | 1 + pnpm-lock.yaml | 7 ++++ 3 files changed, 100 insertions(+), 10 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index ea4eb9f..390c799 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -5,32 +5,97 @@ import {markdown} from '@codemirror/lang-markdown'; import {syntaxHighlighting} from '@codemirror/language'; import markdownHighlight from './highlight/markdown'; +import {h, patch} from 'superfine'; + type Options = { value: string | undefined; + toolbarClass?: string; + previewClass?: string; + editorContainerClass?: string; + photonEditorClass?: string; +}; + +type LayoutInterface = { + create(): void; + getEditorContainer(): HTMLElement | undefined; }; +class DefaultLayout implements LayoutInterface { + private vdomRoot: HTMLElement | undefined; + private editorContainer: HTMLElement | undefined; + + constructor(private readonly parentElement: HTMLElement) { + this.parentElement = parentElement; + } + + create() { + this.render(); + } + + getEditorContainer() { + return this.editorContainer; + } + + private render() { + const toolbarVdom = h('div', {class: 'toolbar'}, [ + // ここにツールバーのコンテンツを追加 + ]); + + const previewVdom = h('div', {class: 'preview'}, [ + // ここにMarkdownのHTML変換結果を追加 + ]); + + const editorContainerVdom = h('div', {class: 'editor-container'}); + + const rootVdom = h('div', {class: 'photon-editor'}, [ + toolbarVdom, + editorContainerVdom, + previewVdom, + ]); + + if (this.vdomRoot) { + patch(this.vdomRoot, rootVdom); + } else { + this.vdomRoot = document.createElement('div'); + this.parentElement.appendChild(this.vdomRoot); + patch(this.vdomRoot, rootVdom); + } + + const editorContainer = this.vdomRoot.querySelector('.editor-container'); + if (editorContainer !== null) { + this.editorContainer = editorContainer as HTMLElement; + } + } +} + class PhotonEditor { private editor: EditorView | undefined; + private readonly layout: LayoutInterface; constructor( private readonly element: HTMLElement, private readonly options: Options, + layout: LayoutInterface, ) { this.element = element; this.options = options; + this.layout = layout || new DefaultLayout(this.element); } createEditor() { - this.editor = new EditorView({ - state: EditorState.create({ - doc: this.options.value, - extensions: [ - markdown(), - keymap.of(defaultKeymap), - syntaxHighlighting(markdownHighlight), - ], - }), - parent: this.element, + this.layout.create(); + this.waitForContainerReady(editorContainer => { + this.editor = new EditorView({ + state: EditorState.create({ + doc: this.options.value, + extensions: [ + markdown(), + keymap.of(defaultKeymap), + syntaxHighlighting(markdownHighlight), + ], + }), + parent: editorContainer, + }); }); } @@ -47,6 +112,23 @@ class PhotonEditor { }, }); } + + private waitForContainerReady(callback: (element: HTMLElement) => void) { + const observer = new MutationObserver(() => { + const editorContainer = this.layout.getEditorContainer(); + + if (editorContainer) { + observer.disconnect(); + callback(editorContainer); + } + }); + + // Observer構成オブジェクト + const config = {childList: true, subtree: true}; + + // 対象の要素とその子孫を監視を開始 + observer.observe(this.element, config); + } } export default PhotonEditor; diff --git a/package.json b/package.json index a1d1638..9be8a43 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@codemirror/language-data": "^6.2.0", "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.9.4", + "@types/superfine": "^7.0.2", "@typescript-eslint/eslint-plugin": ">=5.57.0", "@typescript-eslint/parser": ">=5.57.0", "eslint": "^8.38.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4d9ec8..6f98938 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,6 +27,9 @@ devDependencies: '@codemirror/view': specifier: ^6.9.4 version: 6.9.4 + '@types/superfine': + specifier: ^7.0.2 + version: 7.0.2 '@typescript-eslint/eslint-plugin': specifier: '>=5.57.0' version: 5.58.0(@typescript-eslint/parser@5.58.0)(eslint@8.38.0)(typescript@5.0.4) @@ -671,6 +674,10 @@ packages: resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} dev: true + /@types/superfine@7.0.2: + resolution: {integrity: sha512-QkU9/EO0EhgmLp1BumX4EuZybdEuHn/n3W2PETDXWOTSQs96Hcv9b5Gps/Po1ql0Mc4mTRKrEy212LI52jQc4A==} + dev: true + /@typescript-eslint/eslint-plugin@5.58.0(@typescript-eslint/parser@5.58.0)(eslint@8.38.0)(typescript@5.0.4): resolution: {integrity: sha512-vxHvLhH0qgBd3/tW6/VccptSfc8FxPQIkmNTVLWcCOVqSBvqpnKkBTYrhcGlXfSnd78azwe+PsjYFj0X34/njA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} From aed468a9c9633a8276b0902c95b380dbf19da8d3 Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Sat, 24 Jun 2023 18:11:01 +0000 Subject: [PATCH 03/20] ... --- lib/index.ts | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index 390c799..fd1d86e 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -9,14 +9,13 @@ import {h, patch} from 'superfine'; type Options = { value: string | undefined; - toolbarClass?: string; previewClass?: string; editorContainerClass?: string; photonEditorClass?: string; }; type LayoutInterface = { - create(): void; + render(): void; getEditorContainer(): HTMLElement | undefined; }; @@ -28,19 +27,11 @@ class DefaultLayout implements LayoutInterface { this.parentElement = parentElement; } - create() { - this.render(); - } - getEditorContainer() { return this.editorContainer; } - private render() { - const toolbarVdom = h('div', {class: 'toolbar'}, [ - // ここにツールバーのコンテンツを追加 - ]); - + render() { const previewVdom = h('div', {class: 'preview'}, [ // ここにMarkdownのHTML変換結果を追加 ]); @@ -48,7 +39,6 @@ class DefaultLayout implements LayoutInterface { const editorContainerVdom = h('div', {class: 'editor-container'}); const rootVdom = h('div', {class: 'photon-editor'}, [ - toolbarVdom, editorContainerVdom, previewVdom, ]); @@ -83,7 +73,7 @@ class PhotonEditor { } createEditor() { - this.layout.create(); + this.layout.render(); this.waitForContainerReady(editorContainer => { this.editor = new EditorView({ state: EditorState.create({ @@ -123,11 +113,7 @@ class PhotonEditor { } }); - // Observer構成オブジェクト - const config = {childList: true, subtree: true}; - - // 対象の要素とその子孫を監視を開始 - observer.observe(this.element, config); + observer.observe(this.element, {childList: true, subtree: true}); } } From 70b4596c5351e8ad19a333fa271c1d3a91c0e617 Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Sat, 24 Jun 2023 18:20:22 +0000 Subject: [PATCH 04/20] ... --- lib/index.ts | 47 +---------------------------------------------- lib/layout.ts | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index fd1d86e..2f48838 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -4,8 +4,7 @@ import {keymap, EditorView} from '@codemirror/view'; import {markdown} from '@codemirror/lang-markdown'; import {syntaxHighlighting} from '@codemirror/language'; import markdownHighlight from './highlight/markdown'; - -import {h, patch} from 'superfine'; +import {type LayoutInterface, DefaultLayout} from './layout'; type Options = { value: string | undefined; @@ -14,50 +13,6 @@ type Options = { photonEditorClass?: string; }; -type LayoutInterface = { - render(): void; - getEditorContainer(): HTMLElement | undefined; -}; - -class DefaultLayout implements LayoutInterface { - private vdomRoot: HTMLElement | undefined; - private editorContainer: HTMLElement | undefined; - - constructor(private readonly parentElement: HTMLElement) { - this.parentElement = parentElement; - } - - getEditorContainer() { - return this.editorContainer; - } - - render() { - const previewVdom = h('div', {class: 'preview'}, [ - // ここにMarkdownのHTML変換結果を追加 - ]); - - const editorContainerVdom = h('div', {class: 'editor-container'}); - - const rootVdom = h('div', {class: 'photon-editor'}, [ - editorContainerVdom, - previewVdom, - ]); - - if (this.vdomRoot) { - patch(this.vdomRoot, rootVdom); - } else { - this.vdomRoot = document.createElement('div'); - this.parentElement.appendChild(this.vdomRoot); - patch(this.vdomRoot, rootVdom); - } - - const editorContainer = this.vdomRoot.querySelector('.editor-container'); - if (editorContainer !== null) { - this.editorContainer = editorContainer as HTMLElement; - } - } -} - class PhotonEditor { private editor: EditorView | undefined; private readonly layout: LayoutInterface; diff --git a/lib/layout.ts b/lib/layout.ts index e69de29..c688527 100644 --- a/lib/layout.ts +++ b/lib/layout.ts @@ -0,0 +1,45 @@ +import {h, patch} from 'superfine'; + +export type LayoutInterface = { + render(): void; + getEditorContainer(): HTMLElement | undefined; +}; + +export class DefaultLayout implements LayoutInterface { + private vdomRoot: HTMLDivElement | undefined; + private editorContainer: HTMLElement | undefined; + + constructor(private readonly parentElement: HTMLElement) { + this.parentElement = parentElement; + } + + getEditorContainer() { + return this.editorContainer; + } + + render() { + const previewVdom = h('div', {className: 'preview'}, [ + // ここにMarkdownのHTML変換結果を追加 + ]); + + const editorContainerVdom = h('div', {className: 'editor-container'}); + + const rootVdom = h('div', {className: 'photon-editor'}, [ + editorContainerVdom, + previewVdom, + ]); + + if (this.vdomRoot) { + patch(this.vdomRoot, rootVdom); + } else { + this.vdomRoot = document.createElement('div'); + this.parentElement.appendChild(this.vdomRoot); + patch(this.vdomRoot, rootVdom); + } + + const editorContainer = this.vdomRoot.querySelector('.editor-container'); + if (editorContainer !== null) { + this.editorContainer = editorContainer as HTMLElement; + } + } +} From a8afa922d2041265de0d322812be17fa087f73c8 Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Sat, 24 Jun 2023 18:28:15 +0000 Subject: [PATCH 05/20] ... --- lib/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/index.ts b/lib/index.ts index 2f48838..186e084 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -28,7 +28,6 @@ class PhotonEditor { } createEditor() { - this.layout.render(); this.waitForContainerReady(editorContainer => { this.editor = new EditorView({ state: EditorState.create({ @@ -42,6 +41,7 @@ class PhotonEditor { parent: editorContainer, }); }); + this.layout.render(); } getValue() { From 00373bcf48642560c92b76300610b72b0afabd52 Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Sat, 24 Jun 2023 18:38:16 +0000 Subject: [PATCH 06/20] ... --- lib/index.ts | 52 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index 186e084..9b5dc70 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,3 +1,19 @@ +/** + * PhotonEditor + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + import {defaultKeymap} from '@codemirror/commands'; import {EditorState} from '@codemirror/state'; import {keymap, EditorView} from '@codemirror/view'; @@ -13,20 +29,31 @@ type Options = { photonEditorClass?: string; }; +/** + * Represents the main PhotonEditor class. + */ class PhotonEditor { private editor: EditorView | undefined; private readonly layout: LayoutInterface; + /** + * Creates a new instance of PhotonEditor. + * + * @param {HTMLElement} element - The DOM element to attach the editor to. + * @param {Options} options - An object containing initial editor options. + * @param {LayoutInterface} [layout] - An optional custom layout object. + */ constructor( private readonly element: HTMLElement, private readonly options: Options, - layout: LayoutInterface, + layout?: LayoutInterface, ) { - this.element = element; - this.options = options; - this.layout = layout || new DefaultLayout(this.element); + this.layout = layout ?? new DefaultLayout(this.element); } + /** + * Creates the editor within the specified container element. + */ createEditor() { this.waitForContainerReady(editorContainer => { this.editor = new EditorView({ @@ -44,10 +71,20 @@ class PhotonEditor { this.layout.render(); } + /** + * Returns the editor's current value as a string. + * + * @returns {string | undefined} + */ getValue() { return this.editor?.state.doc.toString(); } + /** + * Sets the editor's value given a new input value. + * + * @param {string} value - The content to set as the new editor value. + */ setValue(value: string) { this.editor?.dispatch({ changes: { @@ -58,6 +95,13 @@ class PhotonEditor { }); } + /** + * Waits for the editor's container DOM element to become available, then + * executes a callback function on it. + * + * @private + * @param {(element: HTMLElement) => void} callback - The callback function for the ready event. + */ private waitForContainerReady(callback: (element: HTMLElement) => void) { const observer = new MutationObserver(() => { const editorContainer = this.layout.getEditorContainer(); From 71e45ba8e9fa51e63d54566bfe8c79828cae19b2 Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Tue, 27 Jun 2023 16:51:18 +0000 Subject: [PATCH 07/20] ... --- lib/index.ts | 15 + lib/layout.ts | 62 ++-- lib/parser.ts | 55 ++++ lib/types/superfine.d.ts | 41 +++ package.json | 12 +- pnpm-lock.yaml | 640 ++++++++++++++++++++++++++++++++++++++- tsconfig.json | 5 +- 7 files changed, 803 insertions(+), 27 deletions(-) create mode 100644 lib/parser.ts create mode 100644 lib/types/superfine.d.ts diff --git a/lib/index.ts b/lib/index.ts index 9b5dc70..f77a0d9 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -21,6 +21,7 @@ import {markdown} from '@codemirror/lang-markdown'; import {syntaxHighlighting} from '@codemirror/language'; import markdownHighlight from './highlight/markdown'; import {type LayoutInterface, DefaultLayout} from './layout'; +import {type MarkdownParserInterface, MarkdownParser} from './parser'; type Options = { value: string | undefined; @@ -35,6 +36,7 @@ type Options = { class PhotonEditor { private editor: EditorView | undefined; private readonly layout: LayoutInterface; + private readonly parser: MarkdownParserInterface; /** * Creates a new instance of PhotonEditor. @@ -46,9 +48,11 @@ class PhotonEditor { constructor( private readonly element: HTMLElement, private readonly options: Options, + parser?: MarkdownParserInterface, layout?: LayoutInterface, ) { this.layout = layout ?? new DefaultLayout(this.element); + this.parser = parser ?? new MarkdownParser(); } /** @@ -63,6 +67,7 @@ class PhotonEditor { markdown(), keymap.of(defaultKeymap), syntaxHighlighting(markdownHighlight), + EditorView.updateListener.of(this.handleEditorUpdate.bind(this)), ], }), parent: editorContainer, @@ -114,6 +119,16 @@ class PhotonEditor { observer.observe(this.element, {childList: true, subtree: true}); } + + private async handleEditorUpdate(update: any) { + if (update.changes.length) { + await this.renderMarkdownToPreviewNode(this.getValue() ?? ''); + } + } + + private async renderMarkdownToPreviewNode(markdown: string) { + this.layout.updatePreviewNode(await this.parser.parse(markdown)); + } } export default PhotonEditor; diff --git a/lib/layout.ts b/lib/layout.ts index c688527..18a7919 100644 --- a/lib/layout.ts +++ b/lib/layout.ts @@ -1,13 +1,16 @@ -import {h, patch} from 'superfine'; +import {h, patch, type VNode} from 'superfine'; export type LayoutInterface = { render(): void; getEditorContainer(): HTMLElement | undefined; + updatePreviewNode(node: VNode): void; }; export class DefaultLayout implements LayoutInterface { - private vdomRoot: HTMLDivElement | undefined; + private rootElement: HTMLDivElement | undefined; private editorContainer: HTMLElement | undefined; + private previewContainer: HTMLDivElement | undefined; + private readonly previewVdom: any; constructor(private readonly parentElement: HTMLElement) { this.parentElement = parentElement; @@ -17,29 +20,42 @@ export class DefaultLayout implements LayoutInterface { return this.editorContainer; } - render() { - const previewVdom = h('div', {className: 'preview'}, [ - // ここにMarkdownのHTML変換結果を追加 - ]); - - const editorContainerVdom = h('div', {className: 'editor-container'}); - - const rootVdom = h('div', {className: 'photon-editor'}, [ - editorContainerVdom, - previewVdom, - ]); - - if (this.vdomRoot) { - patch(this.vdomRoot, rootVdom); - } else { - this.vdomRoot = document.createElement('div'); - this.parentElement.appendChild(this.vdomRoot); - patch(this.vdomRoot, rootVdom); + createRootElement() { + this.rootElement = document.createElement('div'); + this.parentElement.appendChild(this.rootElement); + } + + createEditorContainer() { + this.editorContainer = document.createElement('div'); + this.editorContainer.classList.add('editor-container'); + + this.parentElement.appendChild(this.editorContainer); + } + + createPreviewElement() { + this.previewContainer = document.createElement('div'); + this.previewContainer.classList.add('preview'); + + this.parentElement.appendChild(this.previewContainer); + } + + mountPreview() { + if (this.previewContainer) { + patch(this.previewContainer, h('div', {})); } + } + + render() { + this.createRootElement(); + this.createEditorContainer(); + this.createPreviewElement(); + + this.mountPreview(); + } - const editorContainer = this.vdomRoot.querySelector('.editor-container'); - if (editorContainer !== null) { - this.editorContainer = editorContainer as HTMLElement; + public updatePreviewNode(node: VNode) { + if (this.previewContainer) { + patch(this.previewContainer, node); } } } diff --git a/lib/parser.ts b/lib/parser.ts new file mode 100644 index 0000000..232e296 --- /dev/null +++ b/lib/parser.ts @@ -0,0 +1,55 @@ +import {unified} from 'unified'; +import remarkParse from 'remark-parse'; +import remarkToRehype from 'remark-rehype'; +import type {Root as MdastRoot} from 'mdast'; +import type {Root as HastRoot, Node as HastNode, Element, Text as HastText} from 'hast'; +import {h, text, type VNode, type TTagName, type Props} from 'superfine'; + +type HtmlElementProperties = Record; + +export type MarkdownParserInterface = { + parse(markdown: string): Promise>; +}; + +export class MarkdownParser { + async parse(markdown: string): Promise> { + const remarkAst = this.markdownToAst(markdown); + const rehypeAst = await this.remarkAstToRehypeAst(remarkAst); + const superfineVdom = this.rehypeAstToSuperfineVdom(rehypeAst); + + return superfineVdom; + } + + private markdownToAst(markdown: string): MdastRoot { + return unified().use(remarkParse).parse(markdown); + } + + private async remarkAstToRehypeAst(remarkAst: MdastRoot): Promise { + return unified().use(remarkToRehype).run(remarkAst) as Promise; + } + + private rehypeAstToSuperfineVdom(rehypeAst: HastRoot): VNode { + const walk = (node: HastNode): VNode | undefined => { + if (node.type === 'element') { + const element = node as Element; + return h( + element.tagName, + element.properties as Props, + (element.children || []).map(walk).filter((child: VNode | undefined) => child !== undefined), + ); + } + + if (node.type === 'text') { + const textNode = node as HastText; + return text(textNode.value); + } + + return undefined; + }; + + const children = rehypeAst.children.map(walk).filter((el: VNode | undefined) => el !== undefined); + const vnode = h('div', {}, children); + + return vnode; + } +} diff --git a/lib/types/superfine.d.ts b/lib/types/superfine.d.ts new file mode 100644 index 0000000..a1a526d --- /dev/null +++ b/lib/types/superfine.d.ts @@ -0,0 +1,41 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +declare module 'superfine' { + import type {Children, Child} from 'superfine'; + + type HtmlOrSvgElementTagNameMap = HTMLElementTagNameMap & Pick>; + + export type VNode = { + readonly name: TTagName; + }; + + export function text( + value: string, + node?: Node + ): VNode; + + export type Props = { + readonly [TAttributeName in keyof HtmlOrSvgElementTagNameMap[TTagName]]?: HtmlOrSvgElementTagNameMap[TTagName][TAttributeName]; + readonly is?: string; + } & { + readonly key?: number | string | undefined; + }; + + export function patch( + rootElement: HtmlOrSvgElementTagNameMap[TTagName], + vNode: VNode + ): HtmlOrSvgElementTagNameMap[TTagName]; + + export function h( + tagName: 'svg', + props: Props<'svg'>, + children?: Children + ): VNode<'svg'>; + + export function h( + tagName: TTagName, + props: Props, + children?: Children<(keyof HTMLElementTagNameMap) | 'svg'> + ): VNode; + + export type {Children, Child, TTagName, VNode}; +} diff --git a/package.json b/package.json index 9be8a43..ce5988b 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,10 @@ "@codemirror/language-data": "^6.2.0", "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.9.4", + "@types/hast": "^2.3.4", + "@types/mdast": "^3.0.11", "@types/superfine": "^7.0.2", + "@types/unist": "^2.0.6", "@typescript-eslint/eslint-plugin": ">=5.57.0", "@typescript-eslint/parser": ">=5.57.0", "eslint": "^8.38.0", @@ -44,6 +47,13 @@ "dependencies": { "@codemirror/language": "^6.6.0", "@lezer/highlight": "^1.1.4", - "superfine": "^8.2.0" + "hyperapp": "^2.0.22", + "rehype": "^12.0.1", + "rehype-parse": "^8.0.4", + "remark": "^14.0.3", + "remark-parse": "^10.0.2", + "remark-rehype": "^10.1.0", + "superfine": "^8.2.0", + "unified": "^10.1.2" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6f98938..061df99 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,9 +7,30 @@ dependencies: '@lezer/highlight': specifier: ^1.1.4 version: 1.1.4 + hyperapp: + specifier: ^2.0.22 + version: 2.0.22 + rehype: + specifier: ^12.0.1 + version: 12.0.1 + rehype-parse: + specifier: ^8.0.4 + version: 8.0.4 + remark: + specifier: ^14.0.3 + version: 14.0.3 + remark-parse: + specifier: ^10.0.2 + version: 10.0.2 + remark-rehype: + specifier: ^10.1.0 + version: 10.1.0 superfine: specifier: ^8.2.0 version: 8.2.0 + unified: + specifier: ^10.1.2 + version: 10.1.2 devDependencies: '@codemirror/commands': @@ -27,9 +48,18 @@ devDependencies: '@codemirror/view': specifier: ^6.9.4 version: 6.9.4 + '@types/hast': + specifier: ^2.3.4 + version: 2.3.4 + '@types/mdast': + specifier: ^3.0.11 + version: 3.0.11 '@types/superfine': specifier: ^7.0.2 version: 7.0.2 + '@types/unist': + specifier: ^2.0.6 + version: 2.0.6 '@typescript-eslint/eslint-plugin': specifier: '>=5.57.0' version: 5.58.0(@typescript-eslint/parser@5.58.0)(eslint@8.38.0)(typescript@5.0.4) @@ -666,10 +696,34 @@ packages: fastq: 1.15.0 dev: true + /@types/debug@4.1.8: + resolution: {integrity: sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==} + dependencies: + '@types/ms': 0.7.31 + dev: false + + /@types/hast@2.3.4: + resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==} + dependencies: + '@types/unist': 2.0.6 + /@types/json-schema@7.0.11: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true + /@types/mdast@3.0.11: + resolution: {integrity: sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==} + dependencies: + '@types/unist': 2.0.6 + + /@types/ms@0.7.31: + resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} + dev: false + + /@types/parse5@6.0.3: + resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} + dev: false + /@types/semver@7.3.13: resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} dev: true @@ -678,6 +732,9 @@ packages: resolution: {integrity: sha512-QkU9/EO0EhgmLp1BumX4EuZybdEuHn/n3W2PETDXWOTSQs96Hcv9b5Gps/Po1ql0Mc4mTRKrEy212LI52jQc4A==} dev: true + /@types/unist@2.0.6: + resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} + /@typescript-eslint/eslint-plugin@5.58.0(@typescript-eslint/parser@5.58.0)(eslint@8.38.0)(typescript@5.0.4): resolution: {integrity: sha512-vxHvLhH0qgBd3/tW6/VccptSfc8FxPQIkmNTVLWcCOVqSBvqpnKkBTYrhcGlXfSnd78azwe+PsjYFj0X34/njA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -882,6 +939,10 @@ packages: engines: {node: '>=8'} dev: true + /bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + dev: false + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true @@ -905,6 +966,10 @@ packages: engines: {node: '>=6'} dev: true + /ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + dev: false + /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -918,6 +983,18 @@ packages: engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} dev: true + /character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + dev: false + + /character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + dev: false + + /character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + dev: false + /clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -961,6 +1038,10 @@ packages: resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} dev: true + /comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + dev: false + /commander@10.0.0: resolution: {integrity: sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==} engines: {node: '>=14'} @@ -997,12 +1078,27 @@ packages: optional: true dependencies: ms: 2.1.2 - dev: true + + /decode-named-character-reference@1.0.2: + resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + dependencies: + character-entities: 2.0.2 + dev: false /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true + /dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + dev: false + + /diff@5.1.0: + resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} + engines: {node: '>=0.3.1'} + dev: false + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -1222,6 +1318,10 @@ packages: strip-final-newline: 3.0.0 dev: true + /extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + dev: false + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -1366,6 +1466,85 @@ packages: function-bind: 1.1.1 dev: true + /hast-util-from-parse5@7.1.2: + resolution: {integrity: sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==} + dependencies: + '@types/hast': 2.3.4 + '@types/unist': 2.0.6 + hastscript: 7.2.0 + property-information: 6.2.0 + vfile: 5.3.7 + vfile-location: 4.1.0 + web-namespaces: 2.0.1 + dev: false + + /hast-util-parse-selector@3.1.1: + resolution: {integrity: sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==} + dependencies: + '@types/hast': 2.3.4 + dev: false + + /hast-util-raw@7.2.3: + resolution: {integrity: sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==} + dependencies: + '@types/hast': 2.3.4 + '@types/parse5': 6.0.3 + hast-util-from-parse5: 7.1.2 + hast-util-to-parse5: 7.1.0 + html-void-elements: 2.0.1 + parse5: 6.0.1 + unist-util-position: 4.0.4 + unist-util-visit: 4.1.2 + vfile: 5.3.7 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + dev: false + + /hast-util-to-html@8.0.4: + resolution: {integrity: sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==} + dependencies: + '@types/hast': 2.3.4 + '@types/unist': 2.0.6 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-raw: 7.2.3 + hast-util-whitespace: 2.0.1 + html-void-elements: 2.0.1 + property-information: 6.2.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.3 + zwitch: 2.0.4 + dev: false + + /hast-util-to-parse5@7.1.0: + resolution: {integrity: sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==} + dependencies: + '@types/hast': 2.3.4 + comma-separated-tokens: 2.0.3 + property-information: 6.2.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + dev: false + + /hast-util-whitespace@2.0.1: + resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} + dev: false + + /hastscript@7.2.0: + resolution: {integrity: sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==} + dependencies: + '@types/hast': 2.3.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 3.1.1 + property-information: 6.2.0 + space-separated-tokens: 2.0.2 + dev: false + + /html-void-elements@2.0.1: + resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} + dev: false + /human-signals@4.3.1: resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} engines: {node: '>=14.18.0'} @@ -1377,6 +1556,10 @@ packages: hasBin: true dev: true + /hyperapp@2.0.22: + resolution: {integrity: sha512-3uf9HjnjrhbfykowFNEObZewBEo4DXJIM+9FnGkiR9E4H2eh2f921SzMCMS69X5nN3A7KFfmZc9KCKh/7TQBFA==} + dev: false + /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} @@ -1411,6 +1594,11 @@ packages: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: true + /is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + dev: false + /is-core-module@2.11.0: resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} dependencies: @@ -1449,6 +1637,11 @@ packages: engines: {node: '>=8'} dev: true + /is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + dev: false + /is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1477,6 +1670,11 @@ packages: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true + /kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + dev: false + /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1553,6 +1751,10 @@ packages: wrap-ansi: 6.2.0 dev: true + /longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + dev: false + /lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -1560,6 +1762,72 @@ packages: yallist: 4.0.0 dev: true + /mdast-util-definitions@5.1.2: + resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==} + dependencies: + '@types/mdast': 3.0.11 + '@types/unist': 2.0.6 + unist-util-visit: 4.1.2 + dev: false + + /mdast-util-from-markdown@1.3.1: + resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==} + dependencies: + '@types/mdast': 3.0.11 + '@types/unist': 2.0.6 + decode-named-character-reference: 1.0.2 + mdast-util-to-string: 3.2.0 + micromark: 3.2.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-decode-string: 1.1.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + unist-util-stringify-position: 3.0.3 + uvu: 0.5.6 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-phrasing@3.0.1: + resolution: {integrity: sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==} + dependencies: + '@types/mdast': 3.0.11 + unist-util-is: 5.2.1 + dev: false + + /mdast-util-to-hast@12.3.0: + resolution: {integrity: sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==} + dependencies: + '@types/hast': 2.3.4 + '@types/mdast': 3.0.11 + mdast-util-definitions: 5.1.2 + micromark-util-sanitize-uri: 1.2.0 + trim-lines: 3.0.1 + unist-util-generated: 2.0.1 + unist-util-position: 4.0.4 + unist-util-visit: 4.1.2 + dev: false + + /mdast-util-to-markdown@1.5.0: + resolution: {integrity: sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==} + dependencies: + '@types/mdast': 3.0.11 + '@types/unist': 2.0.6 + longest-streak: 3.1.0 + mdast-util-phrasing: 3.0.1 + mdast-util-to-string: 3.2.0 + micromark-util-decode-string: 1.1.0 + unist-util-visit: 4.1.2 + zwitch: 2.0.4 + dev: false + + /mdast-util-to-string@3.2.0: + resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==} + dependencies: + '@types/mdast': 3.0.11 + dev: false + /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true @@ -1569,6 +1837,181 @@ packages: engines: {node: '>= 8'} dev: true + /micromark-core-commonmark@1.1.0: + resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==} + dependencies: + decode-named-character-reference: 1.0.2 + micromark-factory-destination: 1.1.0 + micromark-factory-label: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-factory-title: 1.1.0 + micromark-factory-whitespace: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-chunked: 1.1.0 + micromark-util-classify-character: 1.1.0 + micromark-util-html-tag-name: 1.2.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-subtokenize: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + dev: false + + /micromark-factory-destination@1.1.0: + resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==} + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + dev: false + + /micromark-factory-label@1.1.0: + resolution: {integrity: sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==} + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + dev: false + + /micromark-factory-space@1.1.0: + resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==} + dependencies: + micromark-util-character: 1.2.0 + micromark-util-types: 1.1.0 + dev: false + + /micromark-factory-title@1.1.0: + resolution: {integrity: sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==} + dependencies: + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + dev: false + + /micromark-factory-whitespace@1.1.0: + resolution: {integrity: sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==} + dependencies: + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + dev: false + + /micromark-util-character@1.2.0: + resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==} + dependencies: + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + dev: false + + /micromark-util-chunked@1.1.0: + resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==} + dependencies: + micromark-util-symbol: 1.1.0 + dev: false + + /micromark-util-classify-character@1.1.0: + resolution: {integrity: sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==} + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + dev: false + + /micromark-util-combine-extensions@1.1.0: + resolution: {integrity: sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==} + dependencies: + micromark-util-chunked: 1.1.0 + micromark-util-types: 1.1.0 + dev: false + + /micromark-util-decode-numeric-character-reference@1.1.0: + resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==} + dependencies: + micromark-util-symbol: 1.1.0 + dev: false + + /micromark-util-decode-string@1.1.0: + resolution: {integrity: sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==} + dependencies: + decode-named-character-reference: 1.0.2 + micromark-util-character: 1.2.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-symbol: 1.1.0 + dev: false + + /micromark-util-encode@1.1.0: + resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==} + dev: false + + /micromark-util-html-tag-name@1.2.0: + resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==} + dev: false + + /micromark-util-normalize-identifier@1.1.0: + resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==} + dependencies: + micromark-util-symbol: 1.1.0 + dev: false + + /micromark-util-resolve-all@1.1.0: + resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==} + dependencies: + micromark-util-types: 1.1.0 + dev: false + + /micromark-util-sanitize-uri@1.2.0: + resolution: {integrity: sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==} + dependencies: + micromark-util-character: 1.2.0 + micromark-util-encode: 1.1.0 + micromark-util-symbol: 1.1.0 + dev: false + + /micromark-util-subtokenize@1.1.0: + resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==} + dependencies: + micromark-util-chunked: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + dev: false + + /micromark-util-symbol@1.1.0: + resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==} + dev: false + + /micromark-util-types@1.1.0: + resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==} + dev: false + + /micromark@3.2.0: + resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} + dependencies: + '@types/debug': 4.1.8 + debug: 4.3.4 + decode-named-character-reference: 1.0.2 + micromark-core-commonmark: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-chunked: 1.1.0 + micromark-util-combine-extensions: 1.1.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-encode: 1.1.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-sanitize-uri: 1.2.0 + micromark-util-subtokenize: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + transitivePeerDependencies: + - supports-color + dev: false + /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} @@ -1593,9 +2036,13 @@ packages: brace-expansion: 1.1.11 dev: true + /mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + dev: false + /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: true /nanoid@3.3.6: resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} @@ -1687,6 +2134,10 @@ packages: callsites: 3.1.0 dev: true + /parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + dev: false + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1745,6 +2196,10 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /property-information@6.2.0: + resolution: {integrity: sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==} + dev: false + /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} @@ -1754,6 +2209,70 @@ packages: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true + /rehype-parse@8.0.4: + resolution: {integrity: sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==} + dependencies: + '@types/hast': 2.3.4 + hast-util-from-parse5: 7.1.2 + parse5: 6.0.1 + unified: 10.1.2 + dev: false + + /rehype-stringify@9.0.3: + resolution: {integrity: sha512-kWiZ1bgyWlgOxpqD5HnxShKAdXtb2IUljn3hQAhySeak6IOQPPt6DeGnsIh4ixm7yKJWzm8TXFuC/lPfcWHJqw==} + dependencies: + '@types/hast': 2.3.4 + hast-util-to-html: 8.0.4 + unified: 10.1.2 + dev: false + + /rehype@12.0.1: + resolution: {integrity: sha512-ey6kAqwLM3X6QnMDILJthGvG1m1ULROS9NT4uG9IDCuv08SFyLlreSuvOa//DgEvbXx62DS6elGVqusWhRUbgw==} + dependencies: + '@types/hast': 2.3.4 + rehype-parse: 8.0.4 + rehype-stringify: 9.0.3 + unified: 10.1.2 + dev: false + + /remark-parse@10.0.2: + resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==} + dependencies: + '@types/mdast': 3.0.11 + mdast-util-from-markdown: 1.3.1 + unified: 10.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /remark-rehype@10.1.0: + resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==} + dependencies: + '@types/hast': 2.3.4 + '@types/mdast': 3.0.11 + mdast-util-to-hast: 12.3.0 + unified: 10.1.2 + dev: false + + /remark-stringify@10.0.3: + resolution: {integrity: sha512-koyOzCMYoUHudypbj4XpnAKFbkddRMYZHwghnxd7ue5210WzGw6kOBwauJTRUMq16jsovXx8dYNvSSWP89kZ3A==} + dependencies: + '@types/mdast': 3.0.11 + mdast-util-to-markdown: 1.5.0 + unified: 10.1.2 + dev: false + + /remark@14.0.3: + resolution: {integrity: sha512-bfmJW1dmR2LvaMJuAnE88pZP9DktIFYXazkTfOIKZzi3Knk9lT0roItIA24ydOucI3bV/g/tXBA6hzqq3FV9Ew==} + dependencies: + '@types/mdast': 3.0.11 + remark-parse: 10.0.2 + remark-stringify: 10.0.3 + unified: 10.1.2 + transitivePeerDependencies: + - supports-color + dev: false + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1812,6 +2331,13 @@ packages: tslib: 2.5.0 dev: true + /sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + dependencies: + mri: 1.2.0 + dev: false + /semver@7.4.0: resolution: {integrity: sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==} engines: {node: '>=10'} @@ -1872,6 +2398,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + dev: false + /string-argv@0.3.1: resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} engines: {node: '>=0.6.19'} @@ -1895,6 +2425,13 @@ packages: strip-ansi: 7.0.1 dev: true + /stringify-entities@4.0.3: + resolution: {integrity: sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==} + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + dev: false + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1953,6 +2490,14 @@ packages: is-number: 7.0.0 dev: true + /trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + dev: false + + /trough@2.1.0: + resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==} + dev: false + /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true @@ -1994,12 +2539,95 @@ packages: hasBin: true dev: true + /unified@10.1.2: + resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} + dependencies: + '@types/unist': 2.0.6 + bail: 2.0.2 + extend: 3.0.2 + is-buffer: 2.0.5 + is-plain-obj: 4.1.0 + trough: 2.1.0 + vfile: 5.3.7 + dev: false + + /unist-util-generated@2.0.1: + resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==} + dev: false + + /unist-util-is@5.2.1: + resolution: {integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==} + dependencies: + '@types/unist': 2.0.6 + dev: false + + /unist-util-position@4.0.4: + resolution: {integrity: sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==} + dependencies: + '@types/unist': 2.0.6 + dev: false + + /unist-util-stringify-position@3.0.3: + resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} + dependencies: + '@types/unist': 2.0.6 + dev: false + + /unist-util-visit-parents@5.1.3: + resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==} + dependencies: + '@types/unist': 2.0.6 + unist-util-is: 5.2.1 + dev: false + + /unist-util-visit@4.1.2: + resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==} + dependencies: + '@types/unist': 2.0.6 + unist-util-is: 5.2.1 + unist-util-visit-parents: 5.1.3 + dev: false + /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: punycode: 2.3.0 dev: true + /uvu@0.5.6: + resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + dequal: 2.0.3 + diff: 5.1.0 + kleur: 4.1.5 + sade: 1.8.1 + dev: false + + /vfile-location@4.1.0: + resolution: {integrity: sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==} + dependencies: + '@types/unist': 2.0.6 + vfile: 5.3.7 + dev: false + + /vfile-message@3.1.4: + resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} + dependencies: + '@types/unist': 2.0.6 + unist-util-stringify-position: 3.0.3 + dev: false + + /vfile@5.3.7: + resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} + dependencies: + '@types/unist': 2.0.6 + is-buffer: 2.0.5 + unist-util-stringify-position: 3.0.3 + vfile-message: 3.1.4 + dev: false + /vite@4.2.1: resolution: {integrity: sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==} engines: {node: ^14.18.0 || >=16.0.0} @@ -2036,6 +2664,10 @@ packages: /w3c-keyname@2.2.6: resolution: {integrity: sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==} + /web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + dev: false + /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2084,3 +2716,7 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + /zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + dev: false diff --git a/tsconfig.json b/tsconfig.json index f4354e0..05f2487 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,9 @@ "moduleResolution": "node", "esModuleInterop": true, "skipLibCheck": true, - "forceConsistentCasingInFileNames": true + "forceConsistentCasingInFileNames": true, + "paths": { + "superfine": ["./types/superfine.d.ts"] + } } } \ No newline at end of file From 056af670a5dcebe080b919066e0c6d9cd361c7c2 Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Wed, 28 Jun 2023 08:41:41 +0000 Subject: [PATCH 08/20] ... --- lib/parser.ts | 12 ++++------ lib/types/superfine.d.ts | 49 +++++++++++++++++++--------------------- 2 files changed, 28 insertions(+), 33 deletions(-) diff --git a/lib/parser.ts b/lib/parser.ts index 232e296..c719a85 100644 --- a/lib/parser.ts +++ b/lib/parser.ts @@ -3,9 +3,7 @@ import remarkParse from 'remark-parse'; import remarkToRehype from 'remark-rehype'; import type {Root as MdastRoot} from 'mdast'; import type {Root as HastRoot, Node as HastNode, Element, Text as HastText} from 'hast'; -import {h, text, type VNode, type TTagName, type Props} from 'superfine'; - -type HtmlElementProperties = Record; +import {h, text, type VNode, type TTagName, type Props, type Children} from 'superfine'; export type MarkdownParserInterface = { parse(markdown: string): Promise>; @@ -29,13 +27,13 @@ export class MarkdownParser { } private rehypeAstToSuperfineVdom(rehypeAst: HastRoot): VNode { - const walk = (node: HastNode): VNode | undefined => { + const walk = (node: HastNode): Children | undefined => { if (node.type === 'element') { const element = node as Element; return h( - element.tagName, + element.tagName as TTagName, element.properties as Props, - (element.children || []).map(walk).filter((child: VNode | undefined) => child !== undefined), + (element.children || []).map(walk).filter(child => child !== undefined) as Children, ); } @@ -47,7 +45,7 @@ export class MarkdownParser { return undefined; }; - const children = rehypeAst.children.map(walk).filter((el: VNode | undefined) => el !== undefined); + const children = rehypeAst.children.map(walk).filter(child => child !== undefined) as Children; const vnode = h('div', {}, children); return vnode; diff --git a/lib/types/superfine.d.ts b/lib/types/superfine.d.ts index a1a526d..d8b37d1 100644 --- a/lib/types/superfine.d.ts +++ b/lib/types/superfine.d.ts @@ -1,41 +1,38 @@ /* eslint-disable @typescript-eslint/naming-convention */ declare module 'superfine' { - import type {Children, Child} from 'superfine'; - type HtmlOrSvgElementTagNameMap = HTMLElementTagNameMap & Pick>; + export type TTagName = keyof HtmlOrSvgElementTagNameMap | 'text'; - export type VNode = { - readonly name: TTagName; - }; - - export function text( - value: string, - node?: Node - ): VNode; - - export type Props = { + export type Props = { readonly [TAttributeName in keyof HtmlOrSvgElementTagNameMap[TTagName]]?: HtmlOrSvgElementTagNameMap[TTagName][TAttributeName]; - readonly is?: string; } & { + readonly is?: string; readonly key?: number | string | undefined; }; - export function patch( - rootElement: HtmlOrSvgElementTagNameMap[TTagName], - vNode: VNode - ): HtmlOrSvgElementTagNameMap[TTagName]; + export type VNode = { + readonly tag: TTagName; + readonly props: Props | Record; + readonly key: number | string | undefined; + readonly children?: Children; + readonly type: number; + readonly node?: Node; + }; - export function h( - tagName: 'svg', - props: Props<'svg'>, - children?: Children - ): VNode<'svg'>; + export type Children = + | VNode + | ReadonlyArray>; - export function h( + export function h( tagName: TTagName, - props: Props, - children?: Children<(keyof HTMLElementTagNameMap) | 'svg'> + props: Props | Record, + children?: Children + ): VNode; + + export function patch( + rootElement: HtmlOrSvgElementTagNameMap[TTagName], + vNode: VNode ): VNode; - export type {Children, Child, TTagName, VNode}; + export function text(value: string, node?: Node): VNode<'text'>; } From 3840b8cbd3151a6e8a313b12915b8e31ed9f187e Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Thu, 29 Jun 2023 06:44:03 +0000 Subject: [PATCH 09/20] ..... --- lib/layout.ts | 4 ++-- lib/parser.ts | 6 +++--- lib/types/superfine.d.ts | 31 ++++++++++++++++--------------- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/lib/layout.ts b/lib/layout.ts index 18a7919..2ca518e 100644 --- a/lib/layout.ts +++ b/lib/layout.ts @@ -1,4 +1,4 @@ -import {h, patch, type VNode} from 'superfine'; +import {h, patch, type VNode, type HtmlOrSvgElementTagNameMap} from 'superfine'; export type LayoutInterface = { render(): void; @@ -53,7 +53,7 @@ export class DefaultLayout implements LayoutInterface { this.mountPreview(); } - public updatePreviewNode(node: VNode) { + public updatePreviewNode(node: VNode) { if (this.previewContainer) { patch(this.previewContainer, node); } diff --git a/lib/parser.ts b/lib/parser.ts index c719a85..7d1cafd 100644 --- a/lib/parser.ts +++ b/lib/parser.ts @@ -3,7 +3,7 @@ import remarkParse from 'remark-parse'; import remarkToRehype from 'remark-rehype'; import type {Root as MdastRoot} from 'mdast'; import type {Root as HastRoot, Node as HastNode, Element, Text as HastText} from 'hast'; -import {h, text, type VNode, type TTagName, type Props, type Children} from 'superfine'; +import {h, text, type VNode, type TTagName, type Props, type Children, type HtmlOrSvgElementTagNameMap} from 'superfine'; export type MarkdownParserInterface = { parse(markdown: string): Promise>; @@ -31,8 +31,8 @@ export class MarkdownParser { if (node.type === 'element') { const element = node as Element; return h( - element.tagName as TTagName, - element.properties as Props, + element.tagName as keyof HtmlOrSvgElementTagNameMap, + element.properties as Props, (element.children || []).map(walk).filter(child => child !== undefined) as Children, ); } diff --git a/lib/types/superfine.d.ts b/lib/types/superfine.d.ts index d8b37d1..42a4984 100644 --- a/lib/types/superfine.d.ts +++ b/lib/types/superfine.d.ts @@ -1,38 +1,39 @@ /* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable @typescript-eslint/no-redundant-type-constituents */ declare module 'superfine' { - type HtmlOrSvgElementTagNameMap = HTMLElementTagNameMap & Pick>; - export type TTagName = keyof HtmlOrSvgElementTagNameMap | 'text'; + type HtmlOrSvgElementTagNameMap = HTMLElementTagNameMap & + Pick>; - export type Props = { + export type TTagName = keyof HtmlOrSvgElementTagNameMap | string; + + export type Props = { readonly [TAttributeName in keyof HtmlOrSvgElementTagNameMap[TTagName]]?: HtmlOrSvgElementTagNameMap[TTagName][TAttributeName]; } & { readonly is?: string; readonly key?: number | string | undefined; }; - export type VNode = { - readonly tag: TTagName; + export type VNode = { + readonly tag: TTagName extends keyof HtmlOrSvgElementTagNameMap ? TTagName : string; readonly props: Props | Record; readonly key: number | string | undefined; - readonly children?: Children; - readonly type: number; + readonly children: Children; + readonly type: TTagName extends keyof HtmlOrSvgElementTagNameMap ? number : 3; readonly node?: Node; }; - export type Children = - | VNode - | ReadonlyArray>; + type Children = VNode | ReadonlyArray>; - export function h( + export function h( tagName: TTagName, props: Props | Record, - children?: Children + children?: Children, ): VNode; - export function patch( + export function patch( rootElement: HtmlOrSvgElementTagNameMap[TTagName], - vNode: VNode + vNode: VNode, ): VNode; - export function text(value: string, node?: Node): VNode<'text'>; + export function text(value: string, node?: Node): VNode; } From ad142769f2bbbf0984f4bcf4d5f503229cd851f7 Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Fri, 30 Jun 2023 14:33:24 +0000 Subject: [PATCH 10/20] ... --- lib/index.ts | 6 +++++- lib/layout.ts | 34 ++++++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index f77a0d9..90e1c49 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -73,7 +73,11 @@ class PhotonEditor { parent: editorContainer, }); }); - this.layout.render(); + this.layout.render({ + previewClass: this.options.previewClass, + editorContainerClass: this.options.editorContainerClass, + photonEditorClass: this.options.photonEditorClass, + }); } /** diff --git a/lib/layout.ts b/lib/layout.ts index 2ca518e..dfbdaed 100644 --- a/lib/layout.ts +++ b/lib/layout.ts @@ -1,7 +1,13 @@ import {h, patch, type VNode, type HtmlOrSvgElementTagNameMap} from 'superfine'; +type Options = { + previewClass?: string; + editorContainerClass?: string; + photonEditorClass?: string; +}; + export type LayoutInterface = { - render(): void; + render(options: Options): void; getEditorContainer(): HTMLElement | undefined; updatePreviewNode(node: VNode): void; }; @@ -20,21 +26,29 @@ export class DefaultLayout implements LayoutInterface { return this.editorContainer; } - createRootElement() { + createRootElement(options: Options) { this.rootElement = document.createElement('div'); + if (options.photonEditorClass) { + this.rootElement.classList.add(options.photonEditorClass); + } + this.parentElement.appendChild(this.rootElement); } - createEditorContainer() { + createEditorContainer(options: Options) { this.editorContainer = document.createElement('div'); - this.editorContainer.classList.add('editor-container'); + if (options.editorContainerClass) { + this.editorContainer.classList.add(options.editorContainerClass); + } this.parentElement.appendChild(this.editorContainer); } - createPreviewElement() { + createPreviewElement(options: Options) { this.previewContainer = document.createElement('div'); - this.previewContainer.classList.add('preview'); + if (options.previewClass) { + this.previewContainer.classList.add(options.previewClass); + } this.parentElement.appendChild(this.previewContainer); } @@ -45,10 +59,10 @@ export class DefaultLayout implements LayoutInterface { } } - render() { - this.createRootElement(); - this.createEditorContainer(); - this.createPreviewElement(); + render(options: Options) { + this.createRootElement(options); + this.createEditorContainer(options); + this.createPreviewElement(options); this.mountPreview(); } From 362d88b0ad7581c46f0ce84ff75e2f7ca2740cb1 Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Sun, 2 Jul 2023 06:03:04 +0000 Subject: [PATCH 11/20] ... --- lib/layout.ts | 29 ++++++++++--- lib/styles/default.css | 15 +++++++ package.json | 1 + pnpm-lock.yaml | 97 ++++++++++++++++++++++++++++++++++++++++++ vite.config.js | 3 ++ 5 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 lib/styles/default.css diff --git a/lib/layout.ts b/lib/layout.ts index dfbdaed..0e9e7c6 100644 --- a/lib/layout.ts +++ b/lib/layout.ts @@ -1,4 +1,5 @@ import {h, patch, type VNode, type HtmlOrSvgElementTagNameMap} from 'superfine'; +import './styles/default.css'; type Options = { previewClass?: string; @@ -12,6 +13,12 @@ export type LayoutInterface = { updatePreviewNode(node: VNode): void; }; +const defaultOptions: Options = { + previewClass: 'preview', + editorContainerClass: 'editor-container', + photonEditorClass: 'parent-container', +}; + export class DefaultLayout implements LayoutInterface { private rootElement: HTMLDivElement | undefined; private editorContainer: HTMLElement | undefined; @@ -35,22 +42,22 @@ export class DefaultLayout implements LayoutInterface { this.parentElement.appendChild(this.rootElement); } - createEditorContainer(options: Options) { + createEditorContainer(rootElement: HTMLDivElement, options: Options) { this.editorContainer = document.createElement('div'); if (options.editorContainerClass) { this.editorContainer.classList.add(options.editorContainerClass); } - this.parentElement.appendChild(this.editorContainer); + rootElement.appendChild(this.editorContainer); } - createPreviewElement(options: Options) { + createPreviewElement(rootElement: HTMLDivElement, options: Options) { this.previewContainer = document.createElement('div'); if (options.previewClass) { this.previewContainer.classList.add(options.previewClass); } - this.parentElement.appendChild(this.previewContainer); + rootElement.appendChild(this.previewContainer); } mountPreview() { @@ -59,10 +66,18 @@ export class DefaultLayout implements LayoutInterface { } } - render(options: Options) { + render(options: Partial = {}) { + options = { + previewClass: options.previewClass ?? defaultOptions.previewClass, + editorContainerClass: options.editorContainerClass ?? defaultOptions.editorContainerClass, + photonEditorClass: options.photonEditorClass ?? defaultOptions.photonEditorClass, + }; + this.createRootElement(options); - this.createEditorContainer(options); - this.createPreviewElement(options); + if (this.rootElement) { + this.createEditorContainer(this.rootElement, options); + this.createPreviewElement(this.rootElement, options); + } this.mountPreview(); } diff --git a/lib/styles/default.css b/lib/styles/default.css new file mode 100644 index 0000000..a52e9e5 --- /dev/null +++ b/lib/styles/default.css @@ -0,0 +1,15 @@ +.parent-container { + display: flex; + justify-content: space-between; +} + +.editor-container { + flex: 1 1 auto; + margin-right: 10px; + overflow: auto; +} + +.preview { + flex: 1 1 auto; + overflow: auto; +} diff --git a/package.json b/package.json index ce5988b..94ce2a1 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "eslint-config-xo-space": "^0.34.0", "eslint-config-xo-typescript": "^0.57.0", "husky": "^8.0.3", + "lightningcss": "^1.21.2", "lint-staged": "^13.2.1", "typescript": "^5.0.4", "vite": "^4.2.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 061df99..ae08013 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -81,6 +81,9 @@ devDependencies: husky: specifier: ^8.0.3 version: 8.0.3 + lightningcss: + specifier: ^1.21.2 + version: 1.21.2 lint-staged: specifier: ^13.2.1 version: 13.2.1 @@ -1094,6 +1097,12 @@ packages: engines: {node: '>=6'} dev: false + /detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + dev: true + /diff@5.1.0: resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} engines: {node: '>=0.3.1'} @@ -1683,6 +1692,94 @@ packages: type-check: 0.4.0 dev: true + /lightningcss-darwin-arm64@1.21.2: + resolution: {integrity: sha512-uh2+MUWWc6rQpMfD3YoIRRxxRaUdhJSI3zeq7EXWQI4pDFcgV8LxGq6yfyMB0cJT1Hh55elQHM3Y139YJrhHFQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /lightningcss-darwin-x64@1.21.2: + resolution: {integrity: sha512-Qv+Tra9p+Lqw4o3XMrDOJBWz5HhNueFHPG+AZMatyQKyWIVKTxDtW9/J7J7J8I2PE26UeLhlQE26ych5PGAp4Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /lightningcss-linux-arm-gnueabihf@1.21.2: + resolution: {integrity: sha512-83KqiPvvOuAocrUyuV+V3peLe5cfBZRHV312vSJq/OGluHzeg2meb36q73q1gIcz/qX9RdAcx6GlhZ/vwbGpnQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /lightningcss-linux-arm64-gnu@1.21.2: + resolution: {integrity: sha512-YAFPWM8Wi2UXIf382aDGxx0/zxOROZ7ADTBi8coXmI5FjDqUjxypc4U9+o962oi3k/N3K6IaL2f/E172+Xj0vA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /lightningcss-linux-arm64-musl@1.21.2: + resolution: {integrity: sha512-dTIaIbOgo/mYzkEi2ESI9+ciZz7un8G0AxiM8E8lrkelm3lgTnSO/4fWaEfx+u9LsVM7dgAN5cfiDBQCr2koYA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /lightningcss-linux-x64-gnu@1.21.2: + resolution: {integrity: sha512-xFieDlPN2cfoVKVBg5XLyxvIAJPw2J3njlOFUQOJEDbLbp+qI8hgxqz34dlI/zZMq4Dmzroc7LPB/MengYjYHQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /lightningcss-linux-x64-musl@1.21.2: + resolution: {integrity: sha512-1w+oiZY3H1V/5FxpvtEUXHBZFuAPzUFoFolpDKNDphr9h9xPo3xc2eg3zuz96ia+tb2QmBB5S3QZrQ/dGGc8GA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /lightningcss-win32-x64-msvc@1.21.2: + resolution: {integrity: sha512-NzuZx5gH7H6Me7h88Boil0vhIqs5+kQ8K2B6ZYQAllj8yk0zCs4cqOLUxXmm6fiykRxCzdRPcEP8ZYvbBCcGgw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /lightningcss@1.21.2: + resolution: {integrity: sha512-6GoGcH4CHGLN6hq5lcmtkKxolXw8nsmgoaYsy6AilwH0vS0GUpKlgFegaf7LmDZqxawjV6LmymQFBZYe+sS45w==} + engines: {node: '>= 12.0.0'} + dependencies: + detect-libc: 1.0.3 + optionalDependencies: + lightningcss-darwin-arm64: 1.21.2 + lightningcss-darwin-x64: 1.21.2 + lightningcss-linux-arm-gnueabihf: 1.21.2 + lightningcss-linux-arm64-gnu: 1.21.2 + lightningcss-linux-arm64-musl: 1.21.2 + lightningcss-linux-x64-gnu: 1.21.2 + lightningcss-linux-x64-musl: 1.21.2 + lightningcss-win32-x64-msvc: 1.21.2 + dev: true + /lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} diff --git a/vite.config.js b/vite.config.js index 38eceae..3e9b9e7 100644 --- a/vite.config.js +++ b/vite.config.js @@ -4,4 +4,7 @@ export default defineConfig({ build: { outDir: 'build', }, + css: { + transformer: 'lightningcss', + }, }); From d92cdda4f7f5ec53d3797ba29771a94d234d2f71 Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Sun, 2 Jul 2023 06:32:30 +0000 Subject: [PATCH 12/20] ... --- lib/styles/default.css | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/styles/default.css b/lib/styles/default.css index a52e9e5..c07d905 100644 --- a/lib/styles/default.css +++ b/lib/styles/default.css @@ -1,15 +1,21 @@ .parent-container { display: flex; justify-content: space-between; + border: 1px solid #cecfd5; + border-radius: 5px; } .editor-container { flex: 1 1 auto; - margin-right: 10px; overflow: auto; + height: auto; + border-right: 2px solid #cecfd5; + padding: 5px; } .preview { flex: 1 1 auto; overflow: auto; + padding: 5px; + height: auto; } From a3484d6edff6e2038c23d68fb9f5f74bc9136602 Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Sun, 2 Jul 2023 06:57:38 +0000 Subject: [PATCH 13/20] .... --- lib/layout.ts | 45 +++++++++++++++++++++++++++++++++++++++--- lib/styles/default.css | 44 +++++++++++++++++++++++++---------------- 2 files changed, 69 insertions(+), 20 deletions(-) diff --git a/lib/layout.ts b/lib/layout.ts index 0e9e7c6..daaaa55 100644 --- a/lib/layout.ts +++ b/lib/layout.ts @@ -4,6 +4,7 @@ import './styles/default.css'; type Options = { previewClass?: string; editorContainerClass?: string; + editorPreviewContainerClass?: string; photonEditorClass?: string; }; @@ -16,13 +17,16 @@ export type LayoutInterface = { const defaultOptions: Options = { previewClass: 'preview', editorContainerClass: 'editor-container', - photonEditorClass: 'parent-container', + editorPreviewContainerClass: 'editor-preview-container', + photonEditorClass: 'photon-editor', }; export class DefaultLayout implements LayoutInterface { private rootElement: HTMLDivElement | undefined; private editorContainer: HTMLElement | undefined; private previewContainer: HTMLDivElement | undefined; + private editorPreviewContaienr: HTMLDivElement | undefined; + private toolbarContainer: HTMLDivElement | undefined; private readonly previewVdom: any; constructor(private readonly parentElement: HTMLElement) { @@ -42,6 +46,15 @@ export class DefaultLayout implements LayoutInterface { this.parentElement.appendChild(this.rootElement); } + createEditorPreviewContainer(rootElement: HTMLDivElement, options: Options) { + this.editorPreviewContaienr = document.createElement('div'); + if (options.editorPreviewContainerClass) { + this.editorPreviewContaienr.classList.add(options.editorPreviewContainerClass); + } + + rootElement.appendChild(this.editorPreviewContaienr); + } + createEditorContainer(rootElement: HTMLDivElement, options: Options) { this.editorContainer = document.createElement('div'); if (options.editorContainerClass) { @@ -60,6 +73,24 @@ export class DefaultLayout implements LayoutInterface { rootElement.appendChild(this.previewContainer); } + createToolbarContainer(rootElement: HTMLDivElement) { + this.toolbarContainer = document.createElement('div'); + this.toolbarContainer.classList.add('toolbar'); + rootElement.insertBefore(this.toolbarContainer, rootElement.firstChild); + } + + initializeToolbar() { + if (this.toolbarContainer) { + patch(this.toolbarContainer, h('div', {}, [])); + } + } + + updateToolbarNode(node: VNode) { + if (this.toolbarContainer) { + patch(this.toolbarContainer, node); + } + } + mountPreview() { if (this.previewContainer) { patch(this.previewContainer, h('div', {})); @@ -71,12 +102,20 @@ export class DefaultLayout implements LayoutInterface { previewClass: options.previewClass ?? defaultOptions.previewClass, editorContainerClass: options.editorContainerClass ?? defaultOptions.editorContainerClass, photonEditorClass: options.photonEditorClass ?? defaultOptions.photonEditorClass, + editorPreviewContainerClass: options.editorPreviewContainerClass ?? defaultOptions.editorPreviewContainerClass, }; this.createRootElement(options); if (this.rootElement) { - this.createEditorContainer(this.rootElement, options); - this.createPreviewElement(this.rootElement, options); + this.createEditorPreviewContainer(this.rootElement, options); + + this.createToolbarContainer(this.rootElement); + this.initializeToolbar(); + } + + if (this.editorPreviewContaienr) { + this.createEditorContainer(this.editorPreviewContaienr, options); + this.createPreviewElement(this.editorPreviewContaienr, options); } this.mountPreview(); diff --git a/lib/styles/default.css b/lib/styles/default.css index c07d905..ac71888 100644 --- a/lib/styles/default.css +++ b/lib/styles/default.css @@ -1,21 +1,31 @@ -.parent-container { - display: flex; - justify-content: space-between; +.photon-editor { border: 1px solid #cecfd5; border-radius: 5px; -} -.editor-container { - flex: 1 1 auto; - overflow: auto; - height: auto; - border-right: 2px solid #cecfd5; - padding: 5px; -} + .toolbar { + width: 100%; + height: 45px; + background-color: #f9faff; + border-bottom: 1px solid #cecfd5; + } -.preview { - flex: 1 1 auto; - overflow: auto; - padding: 5px; - height: auto; -} + .editor-preview-container { + display: flex; + justify-content: space-between; + } + + .editor-container { + flex: 1 1 auto; + overflow: auto; + height: auto; + border-right: 2px solid #cecfd5; + padding: 10px; + } + + .preview { + flex: 1 1 auto; + overflow: auto; + padding: 10px; + height: auto; + } +} \ No newline at end of file From 344dfc1aab1faa8fe3a631336d4a78328fa348c5 Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Tue, 4 Jul 2023 05:34:06 +0000 Subject: [PATCH 14/20] .... --- lib/index.ts | 15 ++++++++++- lib/layout.ts | 58 +++++++++++++++++++++++++++++++++++++----- lib/styles/default.css | 5 ++++ package.json | 1 + pnpm-lock.yaml | 7 +++++ 5 files changed, 79 insertions(+), 7 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index 90e1c49..014a713 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -19,6 +19,8 @@ import {EditorState} from '@codemirror/state'; import {keymap, EditorView} from '@codemirror/view'; import {markdown} from '@codemirror/lang-markdown'; import {syntaxHighlighting} from '@codemirror/language'; +import mitt, {type Emitter} from 'mitt'; + import markdownHighlight from './highlight/markdown'; import {type LayoutInterface, DefaultLayout} from './layout'; import {type MarkdownParserInterface, MarkdownParser} from './parser'; @@ -34,6 +36,7 @@ type Options = { * Represents the main PhotonEditor class. */ class PhotonEditor { + private readonly emitter: Emitter; private editor: EditorView | undefined; private readonly layout: LayoutInterface; private readonly parser: MarkdownParserInterface; @@ -51,10 +54,20 @@ class PhotonEditor { parser?: MarkdownParserInterface, layout?: LayoutInterface, ) { - this.layout = layout ?? new DefaultLayout(this.element); + this.emitter = mitt(); + + this.layout = layout ?? new DefaultLayout(this.emitter, this.element); this.parser = parser ?? new MarkdownParser(); } + on(eventName: string, callback: (event: any) => void) { + this.emitter.on(eventName, callback); + } + + getEmitter(): Emitter { + return this.emitter; + } + /** * Creates the editor within the specified container element. */ diff --git a/lib/layout.ts b/lib/layout.ts index daaaa55..aa7aa37 100644 --- a/lib/layout.ts +++ b/lib/layout.ts @@ -1,14 +1,21 @@ -import {h, patch, type VNode, type HtmlOrSvgElementTagNameMap} from 'superfine'; +import {h, patch, text, type VNode, type HtmlOrSvgElementTagNameMap} from 'superfine'; +import {type Emitter} from 'mitt'; + import './styles/default.css'; +type ToolbarItem = string | VNode; + type Options = { previewClass?: string; editorContainerClass?: string; editorPreviewContainerClass?: string; photonEditorClass?: string; + toolbarItems?: ToolbarItem[][]; }; export type LayoutInterface = { + emitter: Emitter; + render(options: Options): void; getEditorContainer(): HTMLElement | undefined; updatePreviewNode(node: VNode): void; @@ -19,6 +26,13 @@ const defaultOptions: Options = { editorContainerClass: 'editor-container', editorPreviewContainerClass: 'editor-preview-container', photonEditorClass: 'photon-editor', + toolbarItems: [ + ['heading', 'bold', 'italic', 'strike'], + ['hr', 'quote'], + ['ul', 'ol', 'task'], + ['table', 'image', 'link'], + ['code', 'codeblock'], + ], }; export class DefaultLayout implements LayoutInterface { @@ -29,7 +43,8 @@ export class DefaultLayout implements LayoutInterface { private toolbarContainer: HTMLDivElement | undefined; private readonly previewVdom: any; - constructor(private readonly parentElement: HTMLElement) { + constructor(readonly emitter: Emitter, private readonly parentElement: HTMLElement) { + this.emitter = emitter; this.parentElement = parentElement; } @@ -79,10 +94,40 @@ export class DefaultLayout implements LayoutInterface { rootElement.insertBefore(this.toolbarContainer, rootElement.firstChild); } - initializeToolbar() { - if (this.toolbarContainer) { - patch(this.toolbarContainer, h('div', {}, [])); + initializeToolbar(options: Options) { + if (!this.toolbarContainer) { + return; } + + const toolbarItems = options.toolbarItems ?? []; + const toolbarChildren: Array> = []; + + for (const itemGroup of toolbarItems) { + const groupChildren: Array> = []; + + for (const item of itemGroup) { + if (typeof item === 'string') { + const buttonNode = h( + 'button', + { + class: `toolbar-button-${item}`, + onclick: () => { + this.emitter.emit(`${item}ButtonClicked`); + }, + }, + [text(item)], + ); + groupChildren.push(buttonNode); + } else { + groupChildren.push(item); + } + } + + toolbarChildren.push(h('div', {}, groupChildren)); + } + + console.log(this.toolbarContainer, toolbarChildren); + patch(this.toolbarContainer, h('div', {}, toolbarChildren)); } updateToolbarNode(node: VNode) { @@ -103,6 +148,7 @@ export class DefaultLayout implements LayoutInterface { editorContainerClass: options.editorContainerClass ?? defaultOptions.editorContainerClass, photonEditorClass: options.photonEditorClass ?? defaultOptions.photonEditorClass, editorPreviewContainerClass: options.editorPreviewContainerClass ?? defaultOptions.editorPreviewContainerClass, + toolbarItems: options.toolbarItems ?? defaultOptions.toolbarItems, }; this.createRootElement(options); @@ -110,7 +156,7 @@ export class DefaultLayout implements LayoutInterface { this.createEditorPreviewContainer(this.rootElement, options); this.createToolbarContainer(this.rootElement); - this.initializeToolbar(); + this.initializeToolbar(options); } if (this.editorPreviewContaienr) { diff --git a/lib/styles/default.css b/lib/styles/default.css index ac71888..cd54b08 100644 --- a/lib/styles/default.css +++ b/lib/styles/default.css @@ -7,6 +7,11 @@ height: 45px; background-color: #f9faff; border-bottom: 1px solid #cecfd5; + + > div { + display: inline-block; + margin: 0 10px; + } } .editor-preview-container { diff --git a/package.json b/package.json index 94ce2a1..74bcace 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@codemirror/language": "^6.6.0", "@lezer/highlight": "^1.1.4", "hyperapp": "^2.0.22", + "mitt": "^3.0.0", "rehype": "^12.0.1", "rehype-parse": "^8.0.4", "remark": "^14.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae08013..eafd445 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,6 +10,9 @@ dependencies: hyperapp: specifier: ^2.0.22 version: 2.0.22 + mitt: + specifier: ^3.0.0 + version: 3.0.0 rehype: specifier: ^12.0.1 version: 12.0.1 @@ -2133,6 +2136,10 @@ packages: brace-expansion: 1.1.11 dev: true + /mitt@3.0.0: + resolution: {integrity: sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==} + dev: false + /mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} From d141e1e774d4b0c2cd831deb70adb1bcb93bc7d1 Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Tue, 4 Jul 2023 07:45:40 +0000 Subject: [PATCH 15/20] ... --- lib/index.ts | 5 +++++ lib/layout.ts | 2 +- lib/listener.ts | 19 +++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 lib/listener.ts diff --git a/lib/index.ts b/lib/index.ts index 014a713..a0813e8 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -24,6 +24,7 @@ import mitt, {type Emitter} from 'mitt'; import markdownHighlight from './highlight/markdown'; import {type LayoutInterface, DefaultLayout} from './layout'; import {type MarkdownParserInterface, MarkdownParser} from './parser'; +import {defaultButtonTypes, createDefaultButtonListener} from './listener'; type Options = { value: string | undefined; @@ -85,6 +86,10 @@ class PhotonEditor { }), parent: editorContainer, }); + + for (const buttonType of defaultButtonTypes) { + this.emitter.on(`toolbarButton:${buttonType}:clicked`, createDefaultButtonListener(buttonType, this.editor)); + } }); this.layout.render({ previewClass: this.options.previewClass, diff --git a/lib/layout.ts b/lib/layout.ts index aa7aa37..aa807fd 100644 --- a/lib/layout.ts +++ b/lib/layout.ts @@ -112,7 +112,7 @@ export class DefaultLayout implements LayoutInterface { { class: `toolbar-button-${item}`, onclick: () => { - this.emitter.emit(`${item}ButtonClicked`); + this.emitter.emit(`toolbarButton:${item}:clicked`); }, }, [text(item)], diff --git a/lib/listener.ts b/lib/listener.ts new file mode 100644 index 0000000..6fe3910 --- /dev/null +++ b/lib/listener.ts @@ -0,0 +1,19 @@ +import {type EditorView} from '@codemirror/view'; +import {EditorSelection} from '@codemirror/state'; + +const defaultListenerMapping = { + bold: (view: EditorView) => () => { + view.dispatch(view.state.changeByRange(range => ({ + changes: [ + {from: range.from, insert: '**'}, + {from: range.to, insert: '**'}, + ], + range: EditorSelection.range(range.from, range.to + 4), + }))); + }, +} as const; + +export type ButtonType = keyof typeof defaultListenerMapping; +export const defaultButtonTypes: ButtonType[] = Object.keys(defaultListenerMapping) as ButtonType[]; + +export const createDefaultButtonListener = (buttonType: ButtonType, view: EditorView) => defaultListenerMapping[buttonType](view); From 1c6871acd154b9780ab312b607ef5c981835c2b8 Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Tue, 4 Jul 2023 08:22:21 +0000 Subject: [PATCH 16/20] ... --- lib/index.ts | 3 +- lib/listener.ts | 84 ++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 79 insertions(+), 8 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index a0813e8..39a517d 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -14,7 +14,7 @@ * limitations under the License */ -import {defaultKeymap} from '@codemirror/commands'; +import {defaultKeymap, history} from '@codemirror/commands'; import {EditorState} from '@codemirror/state'; import {keymap, EditorView} from '@codemirror/view'; import {markdown} from '@codemirror/lang-markdown'; @@ -80,6 +80,7 @@ class PhotonEditor { extensions: [ markdown(), keymap.of(defaultKeymap), + history(), syntaxHighlighting(markdownHighlight), EditorView.updateListener.of(this.handleEditorUpdate.bind(this)), ], diff --git a/lib/listener.ts b/lib/listener.ts index 6fe3910..8b0ba6e 100644 --- a/lib/listener.ts +++ b/lib/listener.ts @@ -3,16 +3,86 @@ import {EditorSelection} from '@codemirror/state'; const defaultListenerMapping = { bold: (view: EditorView) => () => { - view.dispatch(view.state.changeByRange(range => ({ - changes: [ - {from: range.from, insert: '**'}, - {from: range.to, insert: '**'}, - ], - range: EditorSelection.range(range.from, range.to + 4), - }))); + wrapWithChars('**', view); + view.focus(); + }, + italic: (view: EditorView) => () => { + wrapWithChars('_', view); + view.focus(); + }, + strike: (view: EditorView) => () => { + wrapWithChars('~', view); + view.focus(); + }, + hr: (view: EditorView) => () => { + insertLine('---', view); + view.focus(); + }, + quote: (view: EditorView) => () => { + insertLine('> ', view); + view.focus(); + }, + ul: (view: EditorView) => () => { + insertLine('- ', view); + view.focus(); + }, + ol: (view: EditorView) => () => { + insertLine('1. ', view); + view.focus(); + }, + task: (view: EditorView) => () => { + insertLine('- [ ] ', view); + view.focus(); + }, + code: (view: EditorView) => () => { + wrapWithChars('`', view); + view.focus(); + }, + codeblock: (view: EditorView) => () => { + wrapWithChars('```', view); + view.focus(); }, } as const; +function wrapWithChars(chars: string, view: EditorView) { + const {state} = view; + + state.changeByRange(range => { + const changes = [ + {from: range.from, insert: chars}, + {from: range.to, insert: chars}, + ]; + + view.dispatch({ + changes, + selection: {anchor: range.to + (2 * chars.length)}, + scrollIntoView: true, + }); + + return { + changes, + range: EditorSelection.range(range.from, range.to + (2 * chars.length)), + }; + }); +} + +function insertLine(line: string, view: EditorView) { + const {state} = view; + + state.changeByRange(range => { + const insertPos = range.from; + const changes = {from: insertPos, insert: '\n' + line}; + + view.dispatch({ + changes, + selection: {anchor: insertPos + line.length + 1}, + scrollIntoView: true, + }); + + return {changes, range: EditorSelection.range(insertPos, insertPos + line.length + 1)}; + }); +} + export type ButtonType = keyof typeof defaultListenerMapping; export const defaultButtonTypes: ButtonType[] = Object.keys(defaultListenerMapping) as ButtonType[]; From 549e244e688e8820603a911333e84e94d6642a22 Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Wed, 5 Jul 2023 06:05:26 +0000 Subject: [PATCH 17/20] ... --- lib/layout.ts | 4 +- lib/styles/default.css | 84 +++++++++++++++++++++++++++++++++++++++++- package.json | 1 + pnpm-lock.yaml | 7 ++++ 4 files changed, 93 insertions(+), 3 deletions(-) diff --git a/lib/layout.ts b/lib/layout.ts index aa807fd..458d30f 100644 --- a/lib/layout.ts +++ b/lib/layout.ts @@ -110,12 +110,12 @@ export class DefaultLayout implements LayoutInterface { const buttonNode = h( 'button', { - class: `toolbar-button-${item}`, + class: `toolbar-button ${item}`, onclick: () => { this.emitter.emit(`toolbarButton:${item}:clicked`); }, }, - [text(item)], + [], ); groupChildren.push(buttonNode); } else { diff --git a/lib/styles/default.css b/lib/styles/default.css index cd54b08..2f55eef 100644 --- a/lib/styles/default.css +++ b/lib/styles/default.css @@ -1,12 +1,19 @@ +@import url(https://fonts.googleapis.com/icon?family=Material+Icons); + .photon-editor { border: 1px solid #cecfd5; border-radius: 5px; + .cm-focused { + outline: none; + } + .toolbar { width: 100%; - height: 45px; + height: 34px; background-color: #f9faff; border-bottom: 1px solid #cecfd5; + padding: 5px; > div { display: inline-block; @@ -33,4 +40,79 @@ padding: 10px; height: auto; } +} + +.toolbar-button { + width: 34px; + height: 34px; + padding: 0; + + &::before { + content: attr(data-icon); + font-family: 'Material Icons'; + font-size: 24px; + display: inline-block; + font-style: normal; + -webkit-font-feature-settings: 'liga'; + font-feature-settings: 'liga'; + text-rendering: auto; + width: 24px; + overflow: hidden; + } + + &.heading::before { + content: 'title'; + } + + &.bold::before { + content: 'format_bold'; + } + + &.italic::before { + content: 'format_italic'; + } + + &.strike::before { + content: 'format_strikethrough'; + } + + &.hr::before { + content: 'remove'; + } + + &.quote::before { + content: 'format_quote'; + } + + &.ul::before { + content: 'format_list_bulleted'; + } + + &.ol::before { + content: 'format_list_numbered'; + } + + &.task::before { + content: 'check_box_outline_blank'; + } + + &.table::before { + content: 'table'; + } + + &.image::before { + content: 'image'; + } + + &.link::before { + content: 'link'; + } + + &.code::before { + content: 'code'; + } + + &.codeblock::before { + content: 'code_off'; + } } \ No newline at end of file diff --git a/package.json b/package.json index 74bcace..38f5551 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@codemirror/language": "^6.6.0", "@lezer/highlight": "^1.1.4", "hyperapp": "^2.0.22", + "material-icons": "^1.13.8", "mitt": "^3.0.0", "rehype": "^12.0.1", "rehype-parse": "^8.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eafd445..86ca33a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,6 +10,9 @@ dependencies: hyperapp: specifier: ^2.0.22 version: 2.0.22 + material-icons: + specifier: ^1.13.8 + version: 1.13.8 mitt: specifier: ^3.0.0 version: 3.0.0 @@ -1862,6 +1865,10 @@ packages: yallist: 4.0.0 dev: true + /material-icons@1.13.8: + resolution: {integrity: sha512-vnLGXKa/AwFUxUgkiX39EpYVFttPhDQcKdVylIqmUUqz+Eo/O9A3BkdPCU3/G5cJOTezHi5B/b8sEpKYgUNAwQ==} + dev: false + /mdast-util-definitions@5.1.2: resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==} dependencies: From a11a700a2ac30404b6828f89a180661227369d6b Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Wed, 5 Jul 2023 06:17:38 +0000 Subject: [PATCH 18/20] ... --- lib/styles/default.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/styles/default.css b/lib/styles/default.css index 2f55eef..634c11a 100644 --- a/lib/styles/default.css +++ b/lib/styles/default.css @@ -9,7 +9,7 @@ } .toolbar { - width: 100%; + width: calc(100% - 10px); height: 34px; background-color: #f9faff; border-bottom: 1px solid #cecfd5; From 2253afaf7e455b61e43f56cf60396d0dccc71a09 Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Wed, 5 Jul 2023 20:11:55 +0000 Subject: [PATCH 19/20] ... --- lib/index.ts | 30 ++++++++++++++++++++++++------ lib/layout.ts | 36 +++++++++++------------------------- lib/parser.ts | 24 ++++++++++++------------ lib/types/superfine.d.ts | 39 --------------------------------------- 4 files changed, 47 insertions(+), 82 deletions(-) delete mode 100644 lib/types/superfine.d.ts diff --git a/lib/index.ts b/lib/index.ts index 39a517d..5e07cce 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -20,6 +20,7 @@ import {keymap, EditorView} from '@codemirror/view'; import {markdown} from '@codemirror/lang-markdown'; import {syntaxHighlighting} from '@codemirror/language'; import mitt, {type Emitter} from 'mitt'; +import {app, h, type Dispatch} from 'hyperapp'; import markdownHighlight from './highlight/markdown'; import {type LayoutInterface, DefaultLayout} from './layout'; @@ -39,6 +40,7 @@ type Options = { class PhotonEditor { private readonly emitter: Emitter; private editor: EditorView | undefined; + private previewAppDispatch: Dispatch | undefined; private readonly layout: LayoutInterface; private readonly parser: MarkdownParserInterface; @@ -92,11 +94,27 @@ class PhotonEditor { this.emitter.on(`toolbarButton:${buttonType}:clicked`, createDefaultButtonListener(buttonType, this.editor)); } }); + this.layout.render({ previewClass: this.options.previewClass, editorContainerClass: this.options.editorContainerClass, photonEditorClass: this.options.photonEditorClass, }); + + const previewContainer = this.layout.getPreviewContainer(); + if (previewContainer) { + this.previewAppDispatch = app({ + init: { + markdown: this.getValue() ?? '', + }, + view: state => h('div', {}, this.parser.parse(state.markdown)), + node: previewContainer, + dispatch: dispatch => { + this.previewAppDispatch = dispatch; + return dispatch; + }, + }); + } } /** @@ -143,14 +161,14 @@ class PhotonEditor { observer.observe(this.element, {childList: true, subtree: true}); } - private async handleEditorUpdate(update: any) { - if (update.changes.length) { - await this.renderMarkdownToPreviewNode(this.getValue() ?? ''); - } + private updatePreviewState(state: any, markdown: string): any { + return {...state, markdown}; } - private async renderMarkdownToPreviewNode(markdown: string) { - this.layout.updatePreviewNode(await this.parser.parse(markdown)); + private async handleEditorUpdate(update: any) { + if (update.changes.length && this.previewAppDispatch) { + this.previewAppDispatch(this.updatePreviewState, this.getValue() ?? ''); + } } } diff --git a/lib/layout.ts b/lib/layout.ts index 458d30f..ac3fb6f 100644 --- a/lib/layout.ts +++ b/lib/layout.ts @@ -1,4 +1,4 @@ -import {h, patch, text, type VNode, type HtmlOrSvgElementTagNameMap} from 'superfine'; +import {h, app, type VNode} from 'hyperapp'; import {type Emitter} from 'mitt'; import './styles/default.css'; @@ -18,7 +18,7 @@ export type LayoutInterface = { render(options: Options): void; getEditorContainer(): HTMLElement | undefined; - updatePreviewNode(node: VNode): void; + getPreviewContainer(): HTMLDivElement | undefined; }; const defaultOptions: Options = { @@ -41,7 +41,6 @@ export class DefaultLayout implements LayoutInterface { private previewContainer: HTMLDivElement | undefined; private editorPreviewContaienr: HTMLDivElement | undefined; private toolbarContainer: HTMLDivElement | undefined; - private readonly previewVdom: any; constructor(readonly emitter: Emitter, private readonly parentElement: HTMLElement) { this.emitter = emitter; @@ -52,6 +51,10 @@ export class DefaultLayout implements LayoutInterface { return this.editorContainer; } + getPreviewContainer() { + return this.previewContainer; + } + createRootElement(options: Options) { this.rootElement = document.createElement('div'); if (options.photonEditorClass) { @@ -126,20 +129,11 @@ export class DefaultLayout implements LayoutInterface { toolbarChildren.push(h('div', {}, groupChildren)); } - console.log(this.toolbarContainer, toolbarChildren); - patch(this.toolbarContainer, h('div', {}, toolbarChildren)); - } - - updateToolbarNode(node: VNode) { - if (this.toolbarContainer) { - patch(this.toolbarContainer, node); - } - } - - mountPreview() { - if (this.previewContainer) { - patch(this.previewContainer, h('div', {})); - } + app({ + init: {}, + view: () => h('div', {}, toolbarChildren), + node: this.toolbarContainer, + }); } render(options: Partial = {}) { @@ -163,13 +157,5 @@ export class DefaultLayout implements LayoutInterface { this.createEditorContainer(this.editorPreviewContaienr, options); this.createPreviewElement(this.editorPreviewContaienr, options); } - - this.mountPreview(); - } - - public updatePreviewNode(node: VNode) { - if (this.previewContainer) { - patch(this.previewContainer, node); - } } } diff --git a/lib/parser.ts b/lib/parser.ts index 7d1cafd..e82b67e 100644 --- a/lib/parser.ts +++ b/lib/parser.ts @@ -3,16 +3,16 @@ import remarkParse from 'remark-parse'; import remarkToRehype from 'remark-rehype'; import type {Root as MdastRoot} from 'mdast'; import type {Root as HastRoot, Node as HastNode, Element, Text as HastText} from 'hast'; -import {h, text, type VNode, type TTagName, type Props, type Children, type HtmlOrSvgElementTagNameMap} from 'superfine'; +import {h, text, type VNode, type Props} from 'hyperapp'; export type MarkdownParserInterface = { - parse(markdown: string): Promise>; + parse(markdown: string): VNode; }; export class MarkdownParser { - async parse(markdown: string): Promise> { + parse(markdown: string): VNode { const remarkAst = this.markdownToAst(markdown); - const rehypeAst = await this.remarkAstToRehypeAst(remarkAst); + const rehypeAst = this.remarkAstToRehypeAst(remarkAst); const superfineVdom = this.rehypeAstToSuperfineVdom(rehypeAst); return superfineVdom; @@ -22,18 +22,18 @@ export class MarkdownParser { return unified().use(remarkParse).parse(markdown); } - private async remarkAstToRehypeAst(remarkAst: MdastRoot): Promise { - return unified().use(remarkToRehype).run(remarkAst) as Promise; + private remarkAstToRehypeAst(remarkAst: MdastRoot): MdastRoot { + return unified().use(remarkToRehype).runSync(remarkAst); } - private rehypeAstToSuperfineVdom(rehypeAst: HastRoot): VNode { - const walk = (node: HastNode): Children | undefined => { + private rehypeAstToSuperfineVdom(rehypeAst: MdastRoot): VNode { + const walk = (node: HastNode): VNode | undefined => { if (node.type === 'element') { const element = node as Element; return h( - element.tagName as keyof HtmlOrSvgElementTagNameMap, - element.properties as Props, - (element.children || []).map(walk).filter(child => child !== undefined) as Children, + element.tagName, + element.properties as Props, + (element.children || []).map(walk).filter(child => child !== undefined) as Array>, ); } @@ -45,7 +45,7 @@ export class MarkdownParser { return undefined; }; - const children = rehypeAst.children.map(walk).filter(child => child !== undefined) as Children; + const children = rehypeAst.children.map(walk).filter(child => child !== undefined) as Array>; const vnode = h('div', {}, children); return vnode; diff --git a/lib/types/superfine.d.ts b/lib/types/superfine.d.ts deleted file mode 100644 index 42a4984..0000000 --- a/lib/types/superfine.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -/* eslint-disable @typescript-eslint/no-redundant-type-constituents */ -declare module 'superfine' { - type HtmlOrSvgElementTagNameMap = HTMLElementTagNameMap & - Pick>; - - export type TTagName = keyof HtmlOrSvgElementTagNameMap | string; - - export type Props = { - readonly [TAttributeName in keyof HtmlOrSvgElementTagNameMap[TTagName]]?: HtmlOrSvgElementTagNameMap[TTagName][TAttributeName]; - } & { - readonly is?: string; - readonly key?: number | string | undefined; - }; - - export type VNode = { - readonly tag: TTagName extends keyof HtmlOrSvgElementTagNameMap ? TTagName : string; - readonly props: Props | Record; - readonly key: number | string | undefined; - readonly children: Children; - readonly type: TTagName extends keyof HtmlOrSvgElementTagNameMap ? number : 3; - readonly node?: Node; - }; - - type Children = VNode | ReadonlyArray>; - - export function h( - tagName: TTagName, - props: Props | Record, - children?: Children, - ): VNode; - - export function patch( - rootElement: HtmlOrSvgElementTagNameMap[TTagName], - vNode: VNode, - ): VNode; - - export function text(value: string, node?: Node): VNode; -} From 1eaf3e58f909586ca097ea5c9f25296118229f4d Mon Sep 17 00:00:00 2001 From: Kousuke Ebihara Date: Tue, 11 Jul 2023 11:14:55 +0000 Subject: [PATCH 20/20] ... --- lib/components/HeadingDrawer.ts | 27 ++++++++++++ lib/layout.ts | 74 +++++++++++++++++++++------------ lib/styles/default.css | 10 +++++ 3 files changed, 84 insertions(+), 27 deletions(-) create mode 100644 lib/components/HeadingDrawer.ts diff --git a/lib/components/HeadingDrawer.ts b/lib/components/HeadingDrawer.ts new file mode 100644 index 0000000..65046d3 --- /dev/null +++ b/lib/components/HeadingDrawer.ts @@ -0,0 +1,27 @@ +import {h, app, text, type Action, type VNode, type CustomPayloads} from 'hyperapp'; + +type HeadingDrawerProps = CustomPayloads; +}>; + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const HeadingDrawer = ({levels = [1, 2, 3], onSelect}: HeadingDrawerProps) => { + if (!onSelect) { + throw new Error('onSelect action must be provided to HeadingDrawer component.'); + } + + return h('ul', {}, levels.map(level => + h('li', {onclick: [onSelect, level]}, text(`Heading ${level}`)), + )); +}; + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const HeadingButton = (props: CustomPayloads) => { + const onclick = (state: any): any => ({...state, active: state.active === 'heading' ? '' : 'heading'}); + + return h('button', {class: 'toolbar-button heading', onclick}, [ + // eslint-disable-next-line new-cap + h('div', {style: {display: props.active === 'heading' ? 'block' : 'none'}}, HeadingDrawer(props)), + ]); +}; diff --git a/lib/layout.ts b/lib/layout.ts index ac3fb6f..7c4ba9a 100644 --- a/lib/layout.ts +++ b/lib/layout.ts @@ -1,9 +1,14 @@ -import {h, app, type VNode} from 'hyperapp'; +import {h, app, text, type VNode, type CustomPayloads} from 'hyperapp'; import {type Emitter} from 'mitt'; +import {HeadingButton} from './components/HeadingDrawer'; + import './styles/default.css'; -type ToolbarItem = string | VNode; +type ToolbarItem = string | { + component: ((props: CustomPayloads) => VNode); + props: CustomPayloads; +}; type Options = { previewClass?: string; @@ -27,7 +32,14 @@ const defaultOptions: Options = { editorPreviewContainerClass: 'editor-preview-container', photonEditorClass: 'photon-editor', toolbarItems: [ - ['heading', 'bold', 'italic', 'strike'], + [ + {component: HeadingButton, props: {levels: [1, 2, 3, 4, 5, 6], onSelect(ev: any) { + console.log(ev); + }}}, + 'bold', + 'italic', + 'strike', + ], ['hr', 'quote'], ['ul', 'ol', 'task'], ['table', 'image', 'link'], @@ -102,36 +114,44 @@ export class DefaultLayout implements LayoutInterface { return; } - const toolbarItems = options.toolbarItems ?? []; - const toolbarChildren: Array> = []; - - for (const itemGroup of toolbarItems) { - const groupChildren: Array> = []; - - for (const item of itemGroup) { - if (typeof item === 'string') { - const buttonNode = h( - 'button', - { - class: `toolbar-button ${item}`, - onclick: () => { - this.emitter.emit(`toolbarButton:${item}:clicked`); + const toolbarComponent = (props: CustomPayloads) => { + const toolbarItems = options.toolbarItems ?? []; + const toolbarChildren: Array> = []; + + for (const itemGroup of toolbarItems) { + const groupChildren: Array> = []; + + for (const item of itemGroup) { + if (typeof item === 'string') { + const buttonNode = h( + 'button', + { + class: `toolbar-button ${item}`, + onclick: () => { + this.emitter.emit(`toolbarButton:${item}:clicked`); + }, }, - }, - [], - ); - groupChildren.push(buttonNode); - } else { - groupChildren.push(item); + [], + ); + groupChildren.push(buttonNode); + } else { + groupChildren.push(item.component({...props, ...item.props})); + } } + + toolbarChildren.push(h('div', {}, groupChildren)); } - toolbarChildren.push(h('div', {}, groupChildren)); - } + return toolbarChildren; + }; app({ - init: {}, - view: () => h('div', {}, toolbarChildren), + init: { + active: '', + params: { + }, + }, + view: state => h('div', {}, toolbarComponent(state)), node: this.toolbarContainer, }); } diff --git a/lib/styles/default.css b/lib/styles/default.css index 634c11a..77dd023 100644 --- a/lib/styles/default.css +++ b/lib/styles/default.css @@ -115,4 +115,14 @@ &.codeblock::before { content: 'code_off'; } +} + +.toolbar-button.heading > div { + background-color: white; + width: 300px; + z-index: 1000; +} + +.toolbar-button.heading > div ul { + list-style: none; } \ No newline at end of file