feat: 添加 Markdown 预览组件和相关样式,更新编辑器功能
This commit is contained in:
2
mod.ts
2
mod.ts
@@ -1,2 +1,4 @@
|
|||||||
import './src/index.css'
|
import './src/index.css'
|
||||||
export * from './src/md-editor.ts';
|
export * from './src/md-editor.ts';
|
||||||
|
|
||||||
|
export * from './src/md-preview.ts';
|
||||||
@@ -33,13 +33,17 @@
|
|||||||
"@tiptap/starter-kit": "^3.13.0",
|
"@tiptap/starter-kit": "^3.13.0",
|
||||||
"@tiptap/suggestion": "^3.13.0",
|
"@tiptap/suggestion": "^3.13.0",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
|
"github-markdown-css": "^5.8.1",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"lit-html": "^3.3.1",
|
"lit-html": "^3.3.1",
|
||||||
"lowlight": "^3.3.0",
|
"lowlight": "^3.3.0",
|
||||||
|
"marked": "^17.0.1",
|
||||||
|
"marked-highlight": "^2.2.3",
|
||||||
"nanoid": "^5.1.6",
|
"nanoid": "^5.1.6",
|
||||||
"react": "^19.2.1",
|
"react": "^19.2.1",
|
||||||
"react-dom": "^19.2.1",
|
"react-dom": "^19.2.1",
|
||||||
"tiptap-markdown": "^0.9.0",
|
"tiptap-markdown": "^0.9.0",
|
||||||
|
"turndown": "^7.2.2",
|
||||||
"zustand": "^5.0.9"
|
"zustand": "^5.0.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -61,7 +65,6 @@
|
|||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/kv-md.js",
|
".": "./dist/kv-md.js",
|
||||||
"./kv-md.js": "./dist/kv-md.js",
|
"./kv-md.js": "./dist/kv-md.js",
|
||||||
"./kv-md.css": "./dist/kv-md.css",
|
"./kv-md.css": "./dist/kv-md.css"
|
||||||
"./mod.ts": "./mod.ts"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
46
pnpm-lock.yaml
generated
46
pnpm-lock.yaml
generated
@@ -44,6 +44,9 @@ importers:
|
|||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.19
|
specifier: ^1.11.19
|
||||||
version: 1.11.19
|
version: 1.11.19
|
||||||
|
github-markdown-css:
|
||||||
|
specifier: ^5.8.1
|
||||||
|
version: 5.8.1
|
||||||
highlight.js:
|
highlight.js:
|
||||||
specifier: ^11.11.1
|
specifier: ^11.11.1
|
||||||
version: 11.11.1
|
version: 11.11.1
|
||||||
@@ -53,6 +56,12 @@ importers:
|
|||||||
lowlight:
|
lowlight:
|
||||||
specifier: ^3.3.0
|
specifier: ^3.3.0
|
||||||
version: 3.3.0
|
version: 3.3.0
|
||||||
|
marked:
|
||||||
|
specifier: ^17.0.1
|
||||||
|
version: 17.0.1
|
||||||
|
marked-highlight:
|
||||||
|
specifier: ^2.2.3
|
||||||
|
version: 2.2.3(marked@17.0.1)
|
||||||
nanoid:
|
nanoid:
|
||||||
specifier: ^5.1.6
|
specifier: ^5.1.6
|
||||||
version: 5.1.6
|
version: 5.1.6
|
||||||
@@ -65,6 +74,9 @@ importers:
|
|||||||
tiptap-markdown:
|
tiptap-markdown:
|
||||||
specifier: ^0.9.0
|
specifier: ^0.9.0
|
||||||
version: 0.9.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
|
version: 0.9.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
|
||||||
|
turndown:
|
||||||
|
specifier: ^7.2.2
|
||||||
|
version: 7.2.2
|
||||||
zustand:
|
zustand:
|
||||||
specifier: ^5.0.9
|
specifier: ^5.0.9
|
||||||
version: 5.0.9(@types/react@19.2.7)(react@19.2.1)
|
version: 5.0.9(@types/react@19.2.7)(react@19.2.1)
|
||||||
@@ -392,6 +404,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
dotenv: ^17
|
dotenv: ^17
|
||||||
|
|
||||||
|
'@mixmark-io/domino@2.2.0':
|
||||||
|
resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==}
|
||||||
|
|
||||||
'@noble/hashes@1.4.0':
|
'@noble/hashes@1.4.0':
|
||||||
resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==}
|
resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
@@ -983,6 +998,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
github-markdown-css@5.8.1:
|
||||||
|
resolution: {integrity: sha512-8G+PFvqigBQSWLQjyzgpa2ThD9bo7+kDsriUIidGcRhXgmcaAWUIpCZf8DavJgc+xifjbCG+GvMyWr0XMXmc7g==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
graceful-fs@4.2.11:
|
graceful-fs@4.2.11:
|
||||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||||
|
|
||||||
@@ -1109,6 +1128,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
|
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
marked-highlight@2.2.3:
|
||||||
|
resolution: {integrity: sha512-FCfZRxW/msZAiasCML4isYpxyQWKEEx44vOgdn5Kloae+Qc3q4XR7WjpKKf8oMLk7JP9ZCRd2vhtclJFdwxlWQ==}
|
||||||
|
peerDependencies:
|
||||||
|
marked: '>=4 <18'
|
||||||
|
|
||||||
|
marked@17.0.1:
|
||||||
|
resolution: {integrity: sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==}
|
||||||
|
engines: {node: '>= 20'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
mdurl@2.0.0:
|
mdurl@2.0.0:
|
||||||
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
|
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
|
||||||
|
|
||||||
@@ -1317,6 +1346,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==}
|
resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==}
|
||||||
engines: {node: '>= 6.0.0'}
|
engines: {node: '>= 6.0.0'}
|
||||||
|
|
||||||
|
turndown@7.2.2:
|
||||||
|
resolution: {integrity: sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ==}
|
||||||
|
|
||||||
uc.micro@2.1.0:
|
uc.micro@2.1.0:
|
||||||
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
|
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
|
||||||
|
|
||||||
@@ -1649,6 +1681,8 @@ snapshots:
|
|||||||
'@kevisual/load': 0.0.6
|
'@kevisual/load': 0.0.6
|
||||||
dotenv: 17.2.3
|
dotenv: 17.2.3
|
||||||
|
|
||||||
|
'@mixmark-io/domino@2.2.0': {}
|
||||||
|
|
||||||
'@noble/hashes@1.4.0': {}
|
'@noble/hashes@1.4.0': {}
|
||||||
|
|
||||||
'@peculiar/asn1-cms@2.6.0':
|
'@peculiar/asn1-cms@2.6.0':
|
||||||
@@ -2240,6 +2274,8 @@ snapshots:
|
|||||||
|
|
||||||
gensync@1.0.0-beta.2: {}
|
gensync@1.0.0-beta.2: {}
|
||||||
|
|
||||||
|
github-markdown-css@5.8.1: {}
|
||||||
|
|
||||||
graceful-fs@4.2.11: {}
|
graceful-fs@4.2.11: {}
|
||||||
|
|
||||||
highlight.js@11.11.1: {}
|
highlight.js@11.11.1: {}
|
||||||
@@ -2346,6 +2382,12 @@ snapshots:
|
|||||||
punycode.js: 2.3.1
|
punycode.js: 2.3.1
|
||||||
uc.micro: 2.1.0
|
uc.micro: 2.1.0
|
||||||
|
|
||||||
|
marked-highlight@2.2.3(marked@17.0.1):
|
||||||
|
dependencies:
|
||||||
|
marked: 17.0.1
|
||||||
|
|
||||||
|
marked@17.0.1: {}
|
||||||
|
|
||||||
mdurl@2.0.0: {}
|
mdurl@2.0.0: {}
|
||||||
|
|
||||||
mime-db@1.54.0: {}
|
mime-db@1.54.0: {}
|
||||||
@@ -2603,6 +2645,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib: 1.14.1
|
tslib: 1.14.1
|
||||||
|
|
||||||
|
turndown@7.2.2:
|
||||||
|
dependencies:
|
||||||
|
'@mixmark-io/domino': 2.2.0
|
||||||
|
|
||||||
uc.micro@2.1.0: {}
|
uc.micro@2.1.0: {}
|
||||||
|
|
||||||
undici-types@7.16.0: {}
|
undici-types@7.16.0: {}
|
||||||
|
|||||||
23
src/app.tsx
23
src/app.tsx
@@ -1,6 +1,5 @@
|
|||||||
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { TextEditor, TextEditorProps } from './tiptap/editor.ts';
|
|
||||||
const markdown = `
|
const markdown = `
|
||||||
# 欢迎使用 KeVisual Markdown 编辑器!
|
# 欢迎使用 KeVisual Markdown 编辑器!
|
||||||
这是一个基于 Tiptap 和 React 构建的富文本编辑器,支持 Markdown 语法。
|
这是一个基于 Tiptap 和 React 构建的富文本编辑器,支持 Markdown 语法。
|
||||||
@@ -33,20 +32,6 @@ function greet(name: string): string {
|
|||||||
}
|
}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
`
|
`
|
||||||
const init = () => {
|
|
||||||
const editor = new TextEditor();
|
|
||||||
const editorContainer = document.getElementById('editor') as HTMLDivElement;
|
|
||||||
|
|
||||||
const opts: TextEditorProps = {
|
|
||||||
markdown,
|
|
||||||
placeholder: '输入内容...',
|
|
||||||
onUpdateHtml: (html) => {
|
|
||||||
console.log('Content changed:', html);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
editor.createEditor(editorContainer, opts);
|
|
||||||
return editor;
|
|
||||||
}
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// const editor = init();
|
// const editor = init();
|
||||||
@@ -62,9 +47,11 @@ export const App = () => {
|
|||||||
Click me
|
Click me
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<kv-md-editor markdown={markdown} placeholder='请输入内容...'>
|
{/* <kv-md-editor markdown={markdown} placeholder='请输入内容...'>
|
||||||
</kv-md-editor>
|
</kv-md-editor> */}
|
||||||
|
<kv-md-preview>
|
||||||
|
<kv-md-content>{markdown}</kv-md-content>
|
||||||
|
</kv-md-preview>
|
||||||
{/* <kv-template></kv-template> */}
|
{/* <kv-template></kv-template> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ import './lib.ts'
|
|||||||
import { App } from './app.tsx';
|
import { App } from './app.tsx';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import './md-editor.ts';
|
import './md-editor.ts';
|
||||||
|
import './md-preview.ts';
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(<App />);
|
createRoot(document.getElementById('root')!).render(<App />);
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ class KvMdEditor extends HTMLElement {
|
|||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
||||||
return ['markdown', 'placeholder'];
|
return ['markdown', 'placeholder'];
|
||||||
}
|
}
|
||||||
getId() {
|
|
||||||
return this.id;
|
|
||||||
}
|
|
||||||
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
|
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
|
||||||
if (oldValue === newValue) return;
|
if (oldValue === newValue) return;
|
||||||
|
|
||||||
@@ -78,18 +75,18 @@ class KvMdEditor extends HTMLElement {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
render(content, this.shadowRoot!);
|
render(content, this.shadowRoot!);
|
||||||
let id = this.getId();
|
let id = this.id;
|
||||||
if(!id) {
|
if (!id) {
|
||||||
id = `kv-md-editor-${Math.random().toString(36).substr(2, 9)}`;
|
id = `kv-md-editor-${Math.random().toString(36).substr(2, 9)}`;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
const inlineId = `${id}-inline`;
|
const inlineId = `${id}-inline`;
|
||||||
let editor = this.shadowRoot!.querySelector(`#${inlineId}`)!;
|
let editor = this.shadowRoot!.querySelector(`#${inlineId}`)!;
|
||||||
if(!editor) {
|
if (!editor) {
|
||||||
editor = document.createElement('div');
|
editor = document.createElement('div');
|
||||||
editor.id = inlineId;
|
editor.id = inlineId;
|
||||||
const host = document.querySelector(`#${id}`);
|
const host = document.querySelector(`#${id}`);
|
||||||
host!.appendChild(editor);
|
host!.appendChild(editor);
|
||||||
}
|
}
|
||||||
this.editorContainer = editor as HTMLDivElement;
|
this.editorContainer = editor as HTMLDivElement;
|
||||||
}
|
}
|
||||||
|
|||||||
33
src/md-preview.css
Normal file
33
src/md-preview.css
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
.markdown-body {
|
||||||
|
min-width: 200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 45px;
|
||||||
|
}
|
||||||
|
.markdown-body ul {
|
||||||
|
list-style-type: disc;
|
||||||
|
padding-left: 2em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body ul ul {
|
||||||
|
list-style-type: circle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body ul ul ul {
|
||||||
|
list-style-type: square;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body li {
|
||||||
|
margin-top: 0.25em;
|
||||||
|
margin-bottom: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body ol {
|
||||||
|
list-style-type: decimal;
|
||||||
|
padding-left: 2em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
61
src/md-preview.ts
Normal file
61
src/md-preview.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { render } from 'lit-html';
|
||||||
|
import { html, TemplateResult } from 'lit-html';
|
||||||
|
import { md2html } from './tiptap/utils/index.ts';
|
||||||
|
import 'highlight.js/styles/github.css';
|
||||||
|
import 'github-markdown-css/github-markdown.css';
|
||||||
|
import './md-preview.css';
|
||||||
|
class KvMdPreview extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.attachShadow({ mode: 'open' });
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
getContent(): string {
|
||||||
|
const md = this.querySelector('kv-md-content')?.textContent;
|
||||||
|
if (md !== null && md !== undefined) {
|
||||||
|
return md;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
getInlineDiv(opts?: { prefixId?: string }): HTMLDivElement {
|
||||||
|
const prefixId = opts?.prefixId || 'kv-md-editor';
|
||||||
|
const slotName = 'container';
|
||||||
|
let id = this.id;
|
||||||
|
if (!id) {
|
||||||
|
id = `${prefixId}-${Math.random().toString(36).substring(2, 9)}`;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
const inlineId = `${id}-inline`;
|
||||||
|
let editor = this.shadowRoot!.querySelector(`#${inlineId}`)!;
|
||||||
|
if (!editor) {
|
||||||
|
editor = document.createElement('div');
|
||||||
|
editor.id = inlineId;
|
||||||
|
editor.slot = slotName;
|
||||||
|
const host = document.querySelector(`#${id}`);
|
||||||
|
host!.appendChild(editor);
|
||||||
|
}
|
||||||
|
return editor as HTMLDivElement;
|
||||||
|
}
|
||||||
|
async asyncRender(): Promise<void> {
|
||||||
|
const mdContent = this.getContent();
|
||||||
|
const htmlContent = await md2html(mdContent);
|
||||||
|
const contentWithHtml = html`
|
||||||
|
<div class="md-preview markdown-body" .innerHTML=${htmlContent}></div>
|
||||||
|
`;
|
||||||
|
const el = this.getInlineDiv();
|
||||||
|
render(contentWithHtml, el);
|
||||||
|
}
|
||||||
|
private render(): void {
|
||||||
|
const contentWithHtml = html`
|
||||||
|
<slot name="container"></slot>
|
||||||
|
`;
|
||||||
|
render(contentWithHtml, this.shadowRoot!);
|
||||||
|
this.asyncRender();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the custom element globally
|
||||||
|
customElements.define('kv-md-preview', KvMdPreview);
|
||||||
47
src/tiptap/styles/drag.css
Normal file
47
src/tiptap/styles/drag.css
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
.ProseMirror {
|
||||||
|
padding-inline: 4rem;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-top: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-id] {
|
||||||
|
border: 3px solid #0d0d0d;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
position: relative;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding: 2rem 1rem 1rem;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: attr(data-id);
|
||||||
|
background-color: #0d0d0d;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #fff;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 0 0 0.5rem 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-handle {
|
||||||
|
align-items: center;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
cursor: grab;
|
||||||
|
display: flex;
|
||||||
|
height: 1.5rem;
|
||||||
|
justify-content: center;
|
||||||
|
width: 1.5rem;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/tiptap/utils/index.ts
Normal file
45
src/tiptap/utils/index.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { Marked } from 'marked';
|
||||||
|
import TurndownService from 'turndown';
|
||||||
|
import hljs from 'highlight.js';
|
||||||
|
import { markedHighlight } from 'marked-highlight';
|
||||||
|
// import { marked } from 'marked';
|
||||||
|
|
||||||
|
const markedAndHighlight = new Marked(
|
||||||
|
markedHighlight({
|
||||||
|
emptyLangClass: 'hljs',
|
||||||
|
langPrefix: 'hljs language-',
|
||||||
|
highlight(code, lang, info) {
|
||||||
|
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
||||||
|
return hljs.highlight(code, { language }).value;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const md2html = async (md: string) => {
|
||||||
|
const html = markedAndHighlight.parse(md);
|
||||||
|
return html;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const html2md = async (html: string, opts?: TurndownService.Options) => {
|
||||||
|
const turndownService = new TurndownService({ ...opts });
|
||||||
|
// 添加代码块规则,保留语言标记
|
||||||
|
turndownService.addRule('codeBlocks', {
|
||||||
|
filter: function (node) {
|
||||||
|
return node.nodeName === 'PRE' && node.firstChild?.nodeName === 'CODE';
|
||||||
|
},
|
||||||
|
replacement: function (content, node) {
|
||||||
|
const code = node.firstChild as HTMLElement;
|
||||||
|
// 从类名中提取语言 (hljs language-xxx)
|
||||||
|
const className = code.className || '';
|
||||||
|
const language = className.match(/language-(\w+)/)?.[1] || '';
|
||||||
|
|
||||||
|
// 确保内容首尾没有多余空行
|
||||||
|
const trimmedContent = content.trim();
|
||||||
|
|
||||||
|
return `\n\n\`\`\`${language}\n${trimmedContent}\n\`\`\`\n\n`;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const md = turndownService.turndown(html);
|
||||||
|
return md;
|
||||||
|
};
|
||||||
3
typings.d.ts
vendored
3
typings.d.ts
vendored
@@ -9,6 +9,9 @@ declare module 'react' {
|
|||||||
namespace JSX {
|
namespace JSX {
|
||||||
interface IntrinsicElements {
|
interface IntrinsicElements {
|
||||||
'kv-template': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
'kv-template': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
||||||
|
'kv-md-preview': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
||||||
|
'kv-md-content': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
||||||
|
'md': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
||||||
'kv-md-editor': DetailedHTMLProps<KvMdEditorProps, HTMLElement>;
|
'kv-md-editor': DetailedHTMLProps<KvMdEditorProps, HTMLElement>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user