集成 Tiptap Markdown 编辑器到文章撰写页面
- 新增 libs/tiptap-editor/ 打包子项目(Tiptap Core + StarterKit + Markdown) - 构建产物输出到 public/tiptap/ - 替换原有的 textarea + pulldown_cmark 预览为 WYSIWYG 编辑器 - Makefile 新增 build-editor target - Dioxus.toml 引入 editor.js 和 editor.css Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
91d9c04a3d
commit
f6d60520eb
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,6 +3,7 @@
|
||||
/.dioxus
|
||||
/.omc
|
||||
/node_modules
|
||||
**/**/node_modules
|
||||
/package-lock.json
|
||||
others/
|
||||
public/style.css
|
||||
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4629,6 +4629,7 @@ dependencies = [
|
||||
"dioxus",
|
||||
"dotenvy",
|
||||
"getrandom 0.2.17",
|
||||
"js-sys",
|
||||
"pulldown-cmark",
|
||||
"rand 0.8.6",
|
||||
"regex",
|
||||
|
||||
@ -19,8 +19,9 @@ rand = { version = "0.8", features = ["getrandom"] }
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
web-sys = { version = "0.3", features = ["Document", "Window", "HtmlDocument", "Storage", "Element", "DomTokenList", "MediaQueryList"] }
|
||||
web-sys = { version = "0.3", features = ["Document", "Window", "HtmlDocument", "Storage", "Element", "DomTokenList", "MediaQueryList", "HtmlScriptElement"] }
|
||||
wasm-bindgen = "0.2"
|
||||
js-sys = "0.3"
|
||||
|
||||
[profile.release]
|
||||
debug = false
|
||||
|
||||
@ -10,9 +10,9 @@ title = "Yggdrasil - Dioxus SSR"
|
||||
watch_path = ["src", "Cargo.toml"]
|
||||
|
||||
[web.resource]
|
||||
style = ["/style.css"]
|
||||
script = []
|
||||
style = ["/style.css", "/tiptap/editor.css"]
|
||||
script = ["/tiptap/editor.js"]
|
||||
|
||||
[web.resource.dev]
|
||||
style = ["/style.css"]
|
||||
style = []
|
||||
script = []
|
||||
|
||||
10
Makefile
10
Makefile
@ -1,9 +1,17 @@
|
||||
.PHONY: dev build css css-watch clean
|
||||
.PHONY: dev build css css-watch clean build-editor
|
||||
|
||||
build:
|
||||
@$(MAKE) build-editor
|
||||
@tailwindcss -i input.css -o public/style.css --minify
|
||||
@dx build --release
|
||||
|
||||
build-editor:
|
||||
@echo "Building Tiptap editor..."
|
||||
@cd libs/tiptap-editor && npm install && npx vite build
|
||||
@mv public/tiptap/editor.iife.js public/tiptap/editor.js 2>/dev/null || true
|
||||
@mv public/tiptap/editor.iife.js.map public/tiptap/editor.js.map 2>/dev/null || true
|
||||
@echo "Tiptap editor built."
|
||||
|
||||
dev:
|
||||
@echo "Starting tailwindcss watch and dx serve..."
|
||||
@tailwindcss -i input.css -o public/style.css --watch & \
|
||||
|
||||
1542
libs/tiptap-editor/package-lock.json
generated
Normal file
1542
libs/tiptap-editor/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
libs/tiptap-editor/package.json
Normal file
19
libs/tiptap-editor/package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@yggdrasil/tiptap-editor",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tiptap/core": "^3.0.0",
|
||||
"@tiptap/markdown": "^3.0.0",
|
||||
"@tiptap/pm": "^3.0.0",
|
||||
"@tiptap/starter-kit": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.4.20"
|
||||
}
|
||||
}
|
||||
116
libs/tiptap-editor/src/index.ts
Normal file
116
libs/tiptap-editor/src/index.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import { Editor } from '@tiptap/core'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { Markdown } from '@tiptap/markdown'
|
||||
import './style.css'
|
||||
|
||||
export interface EditorOptions {
|
||||
content?: string
|
||||
placeholder?: string
|
||||
onUpdate?: (markdown: string) => void
|
||||
onFocus?: () => void
|
||||
onBlur?: () => void
|
||||
editable?: boolean
|
||||
}
|
||||
|
||||
class TiptapEditorInstance {
|
||||
private editor: Editor | null = null
|
||||
private container: HTMLElement
|
||||
private options: EditorOptions
|
||||
|
||||
constructor(container: HTMLElement, options: EditorOptions = {}) {
|
||||
this.container = container
|
||||
this.options = options
|
||||
this.init()
|
||||
}
|
||||
|
||||
private init() {
|
||||
const el = document.createElement('div')
|
||||
el.className = 'tiptap-editor'
|
||||
this.container.appendChild(el)
|
||||
|
||||
this.editor = new Editor({
|
||||
element: el,
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
heading: {
|
||||
levels: [1, 2, 3],
|
||||
},
|
||||
}),
|
||||
Markdown.configure({
|
||||
html: false,
|
||||
}),
|
||||
],
|
||||
content: this.options.content || '',
|
||||
editable: this.options.editable !== false,
|
||||
autofocus: false,
|
||||
onUpdate: ({ editor }) => {
|
||||
if (this.options.onUpdate) {
|
||||
this.options.onUpdate(editor.getMarkdown())
|
||||
}
|
||||
},
|
||||
onFocus: () => {
|
||||
if (this.options.onFocus) {
|
||||
this.options.onFocus()
|
||||
}
|
||||
},
|
||||
onBlur: () => {
|
||||
if (this.options.onBlur) {
|
||||
this.options.onBlur()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
getMarkdown(): string {
|
||||
return this.editor?.getMarkdown() || ''
|
||||
}
|
||||
|
||||
setMarkdown(content: string): void {
|
||||
this.editor?.commands.setContent(content, false, { contentType: 'markdown' })
|
||||
}
|
||||
|
||||
getHTML(): string {
|
||||
return this.editor?.getHTML() || ''
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.editor?.commands.focus()
|
||||
}
|
||||
|
||||
blur(): void {
|
||||
this.editor?.commands.blur()
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.editor?.isEmpty ?? true
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.editor?.destroy()
|
||||
this.editor = null
|
||||
this.container.innerHTML = ''
|
||||
}
|
||||
}
|
||||
|
||||
const TiptapEditor = {
|
||||
_instances: new Map<string, TiptapEditorInstance>(),
|
||||
|
||||
create(containerId: string, options: EditorOptions = {}): TiptapEditorInstance | null {
|
||||
const container = document.getElementById(containerId)
|
||||
if (!container) {
|
||||
console.error(`[TiptapEditor] Container not found: #${containerId}`)
|
||||
return null
|
||||
}
|
||||
|
||||
const existing = this._instances.get(containerId)
|
||||
if (existing) {
|
||||
existing.destroy()
|
||||
}
|
||||
|
||||
const instance = new TiptapEditorInstance(container, options)
|
||||
this._instances.set(containerId, instance)
|
||||
return instance
|
||||
},
|
||||
}
|
||||
|
||||
export default TiptapEditor
|
||||
219
libs/tiptap-editor/src/style.css
Normal file
219
libs/tiptap-editor/src/style.css
Normal file
@ -0,0 +1,219 @@
|
||||
/* Tiptap Editor - Typora-inspired, theme-aware */
|
||||
|
||||
.tiptap-editor {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.75;
|
||||
color: #2c2c2c;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 24px 32px;
|
||||
outline: none;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror-focused {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Placeholder */
|
||||
.tiptap-editor .ProseMirror p.is-editor-empty:first-child::before {
|
||||
content: attr(data-placeholder);
|
||||
float: left;
|
||||
color: #999;
|
||||
pointer-events: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
.tiptap-editor h1 {
|
||||
font-size: 2em;
|
||||
font-weight: 700;
|
||||
margin: 1.2em 0 0.6em;
|
||||
line-height: 1.3;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.tiptap-editor h2 {
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
margin: 1em 0 0.5em;
|
||||
line-height: 1.35;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.tiptap-editor h3 {
|
||||
font-size: 1.25em;
|
||||
font-weight: 600;
|
||||
margin: 0.8em 0 0.4em;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.tiptap-editor p {
|
||||
margin: 0.6em 0;
|
||||
}
|
||||
|
||||
.tiptap-editor p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.tiptap-editor strong {
|
||||
font-weight: 700;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.tiptap-editor em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.tiptap-editor s {
|
||||
text-decoration: line-through;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.tiptap-editor code {
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace;
|
||||
font-size: 0.875em;
|
||||
background: #f3f3f3;
|
||||
padding: 0.15em 0.4em;
|
||||
border-radius: 3px;
|
||||
color: #d73a49;
|
||||
}
|
||||
|
||||
.tiptap-editor pre {
|
||||
background: #f6f8fa;
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
overflow-x: auto;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.tiptap-editor pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: #24292e;
|
||||
font-size: 0.85em;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.tiptap-editor blockquote {
|
||||
border-left: 4px solid #dfe2e5;
|
||||
padding-left: 16px;
|
||||
margin: 1em 0;
|
||||
color: #6a737d;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.tiptap-editor ul,
|
||||
.tiptap-editor ol {
|
||||
margin: 0.6em 0;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.tiptap-editor li {
|
||||
margin: 0.2em 0;
|
||||
}
|
||||
|
||||
.tiptap-editor li > p {
|
||||
margin: 0.2em 0;
|
||||
}
|
||||
|
||||
.tiptap-editor hr {
|
||||
border: none;
|
||||
border-top: 1px solid #e1e4e8;
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
|
||||
.tiptap-editor a {
|
||||
color: #0366d6;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tiptap-editor a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Task list */
|
||||
.tiptap-editor ul[data-type="taskList"] {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.tiptap-editor ul[data-type="taskList"] li {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.tiptap-editor ul[data-type="taskList"] li > label {
|
||||
flex-shrink: 0;
|
||||
margin-top: 0.3em;
|
||||
}
|
||||
|
||||
.tiptap-editor ul[data-type="taskList"] li > div {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Selection */
|
||||
.tiptap-editor .ProseMirror ::selection {
|
||||
background: #b4d7ff;
|
||||
}
|
||||
|
||||
/* Cursor */
|
||||
.tiptap-editor .ProseMirror-focused .ProseMirror-gapcursor {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ========== Dark Theme ========== */
|
||||
|
||||
[data-theme="dark"] .tiptap-editor {
|
||||
color: #dadadb;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiptap-editor h1,
|
||||
[data-theme="dark"] .tiptap-editor h2,
|
||||
[data-theme="dark"] .tiptap-editor h3,
|
||||
[data-theme="dark"] .tiptap-editor strong {
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiptap-editor .ProseMirror p.is-editor-empty:first-child::before {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiptap-editor code {
|
||||
background: #2e2e33;
|
||||
color: #ff7b72;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiptap-editor pre {
|
||||
background: #2e2e33;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiptap-editor pre code {
|
||||
color: #dadadb;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiptap-editor blockquote {
|
||||
border-left-color: #444;
|
||||
color: #9b9c9d;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiptap-editor hr {
|
||||
border-top-color: #333;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiptap-editor a {
|
||||
color: #58a6ff;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiptap-editor .ProseMirror ::selection {
|
||||
background: #1c4e80;
|
||||
}
|
||||
25
libs/tiptap-editor/vite.config.ts
Normal file
25
libs/tiptap-editor/vite.config.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import { resolve } from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
outDir: resolve(__dirname, '../../public/tiptap'),
|
||||
emptyOutDir: true,
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/index.ts'),
|
||||
name: 'TiptapEditor',
|
||||
fileName: 'editor',
|
||||
formats: ['iife'],
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
exports: 'default',
|
||||
assetFileNames: 'editor.[ext]',
|
||||
inlineDynamicImports: true,
|
||||
},
|
||||
},
|
||||
cssCodeSplit: false,
|
||||
minify: true,
|
||||
sourcemap: true,
|
||||
},
|
||||
});
|
||||
1
public/tiptap/editor.css
Normal file
1
public/tiptap/editor.css
Normal file
@ -0,0 +1 @@
|
||||
.tiptap-editor{position:relative;width:100%;height:100%;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:16px;line-height:1.75;color:#2c2c2c;outline:none}.tiptap-editor .ProseMirror{width:100%;height:100%;padding:24px 32px;outline:none;overflow-y:auto}.tiptap-editor .ProseMirror-focused{outline:none}.tiptap-editor .ProseMirror p.is-editor-empty:first-child:before{content:attr(data-placeholder);float:left;color:#999;pointer-events:none;height:0}.tiptap-editor h1{font-size:2em;font-weight:700;margin:1.2em 0 .6em;line-height:1.3;color:#1a1a1a}.tiptap-editor h2{font-size:1.5em;font-weight:600;margin:1em 0 .5em;line-height:1.35;color:#1a1a1a}.tiptap-editor h3{font-size:1.25em;font-weight:600;margin:.8em 0 .4em;color:#1a1a1a}.tiptap-editor p{margin:.6em 0}.tiptap-editor p:first-child{margin-top:0}.tiptap-editor strong{font-weight:700;color:#1a1a1a}.tiptap-editor em{font-style:italic}.tiptap-editor s{text-decoration:line-through;opacity:.7}.tiptap-editor code{font-family:SF Mono,Monaco,Cascadia Code,Roboto Mono,Consolas,monospace;font-size:.875em;background:#f3f3f3;padding:.15em .4em;border-radius:3px;color:#d73a49}.tiptap-editor pre{background:#f6f8fa;border-radius:6px;padding:16px;overflow-x:auto;margin:1em 0}.tiptap-editor pre code{background:none;padding:0;color:#24292e;font-size:.85em;line-height:1.6}.tiptap-editor blockquote{border-left:4px solid #dfe2e5;padding-left:16px;margin:1em 0;color:#6a737d;font-style:italic}.tiptap-editor ul,.tiptap-editor ol{margin:.6em 0;padding-left:2em}.tiptap-editor li{margin:.2em 0}.tiptap-editor li>p{margin:.2em 0}.tiptap-editor hr{border:none;border-top:1px solid #e1e4e8;margin:1.5em 0}.tiptap-editor a{color:#0366d6;text-decoration:none}.tiptap-editor a:hover{text-decoration:underline}.tiptap-editor ul[data-type=taskList]{list-style:none;padding-left:0}.tiptap-editor ul[data-type=taskList] li{display:flex;align-items:flex-start;gap:.5em}.tiptap-editor ul[data-type=taskList] li>label{flex-shrink:0;margin-top:.3em}.tiptap-editor ul[data-type=taskList] li>div{flex:1}.tiptap-editor .ProseMirror ::selection{background:#b4d7ff}.tiptap-editor .ProseMirror-focused .ProseMirror-gapcursor{display:none}[data-theme=dark] .tiptap-editor{color:#dadadb}[data-theme=dark] .tiptap-editor h1,[data-theme=dark] .tiptap-editor h2,[data-theme=dark] .tiptap-editor h3,[data-theme=dark] .tiptap-editor strong{color:#f0f0f0}[data-theme=dark] .tiptap-editor .ProseMirror p.is-editor-empty:first-child:before{color:#666}[data-theme=dark] .tiptap-editor code{background:#2e2e33;color:#ff7b72}[data-theme=dark] .tiptap-editor pre{background:#2e2e33}[data-theme=dark] .tiptap-editor pre code{color:#dadadb}[data-theme=dark] .tiptap-editor blockquote{border-left-color:#444;color:#9b9c9d}[data-theme=dark] .tiptap-editor hr{border-top-color:#333}[data-theme=dark] .tiptap-editor a{color:#58a6ff}[data-theme=dark] .tiptap-editor .ProseMirror ::selection{background:#1c4e80}
|
||||
207
public/tiptap/editor.js
Normal file
207
public/tiptap/editor.js
Normal file
File diff suppressed because one or more lines are too long
1
public/tiptap/editor.js.map
Normal file
1
public/tiptap/editor.js.map
Normal file
File diff suppressed because one or more lines are too long
@ -2,19 +2,37 @@ use dioxus::prelude::*;
|
||||
|
||||
use crate::components::admin_layout::AdminLayout;
|
||||
|
||||
fn markdown_to_html(input: &str) -> String {
|
||||
let parser = pulldown_cmark::Parser::new(input);
|
||||
let mut html = String::new();
|
||||
pulldown_cmark::html::push_html(&mut html, parser);
|
||||
html
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn WritePage() -> Element {
|
||||
let mut title = use_signal(|| "".to_string());
|
||||
let mut content = use_signal(|| "".to_string());
|
||||
let preview_html = use_memo(move || {
|
||||
markdown_to_html(&content())
|
||||
|
||||
use_effect(move || {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let _ = js_sys::eval(
|
||||
r#"
|
||||
(function initEditor() {
|
||||
var container = document.getElementById('tiptap-editor');
|
||||
if (!container) {
|
||||
setTimeout(initEditor, 50);
|
||||
return;
|
||||
}
|
||||
if (typeof window.TiptapEditor !== 'undefined' && window.TiptapEditor) {
|
||||
window.TiptapEditor.create('tiptap-editor', {
|
||||
content: '',
|
||||
placeholder: '在此输入内容...',
|
||||
onUpdate: function(markdown) {
|
||||
window.__tiptap_content = markdown;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
setTimeout(initEditor, 50);
|
||||
})();
|
||||
"#,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
rsx! {
|
||||
@ -28,40 +46,31 @@ pub fn WritePage() -> Element {
|
||||
oninput: move |evt| title.set(evt.value()),
|
||||
}
|
||||
|
||||
// 两栏布局
|
||||
div { class: "grid grid-cols-1 md:grid-cols-2 gap-6",
|
||||
// 编辑区
|
||||
div { class: "space-y-2",
|
||||
label { class: "text-sm text-gray-500 dark:text-[#9b9c9d]",
|
||||
"Markdown"
|
||||
}
|
||||
textarea {
|
||||
class: "w-full h-[500px] bg-gray-50 dark:bg-[#2e2e33] rounded-lg p-4 font-mono text-sm text-gray-800 dark:text-[#dadadb] placeholder-gray-400 dark:placeholder-[#9b9c9d] border border-gray-200 dark:border-[#333] focus:outline-none focus:border-gray-400 dark:focus:border-gray-600 resize-none",
|
||||
placeholder: "在此输入 Markdown...",
|
||||
value: "{content}",
|
||||
oninput: move |evt| content.set(evt.value()),
|
||||
}
|
||||
}
|
||||
|
||||
// 预览区
|
||||
div { class: "space-y-2",
|
||||
label { class: "text-sm text-gray-500 dark:text-[#9b9c9d]",
|
||||
"预览"
|
||||
}
|
||||
div {
|
||||
class: "w-full h-[500px] overflow-y-auto bg-white dark:bg-[#2e2e33] rounded-lg p-4 border border-gray-200 dark:border-[#333] prose dark:prose-invert max-w-none",
|
||||
dangerous_inner_html: "{preview_html}",
|
||||
}
|
||||
}
|
||||
// Tiptap 编辑器容器
|
||||
div {
|
||||
class: "w-full h-[600px] border border-gray-200 dark:border-[#333] rounded-lg overflow-hidden bg-white dark:bg-[#1e1e1e]",
|
||||
id: "tiptap-editor",
|
||||
}
|
||||
|
||||
// 保存按钮
|
||||
button {
|
||||
class: "mt-4 px-6 py-2 bg-gray-900 dark:bg-[#dadadb] text-white dark:text-gray-900 rounded-full font-medium hover:opacity-80 transition-opacity",
|
||||
onclick: move |_| {
|
||||
let t = title();
|
||||
let c = content();
|
||||
println!("保存文章: title={}, content_len={}", t, c.len());
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let md = js_sys::eval(r#"
|
||||
(function() {
|
||||
var editor = window.TiptapEditor && window.TiptapEditor._instances && window.TiptapEditor._instances.get('tiptap-editor');
|
||||
return editor ? editor.getMarkdown() : (window.__tiptap_content || '');
|
||||
})()
|
||||
"#).ok().and_then(|v| v.as_string()).unwrap_or_default();
|
||||
content.set(md.clone());
|
||||
println!("保存文章: title={}, content_len={}", title(), md.len());
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
println!("保存文章: title={}, content_len={}", title(), content().len());
|
||||
}
|
||||
},
|
||||
"保存草稿"
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user