From 13bd61326d554ebecfeec973043811237419f379 Mon Sep 17 00:00:00 2001 From: Timi Date: Tue, 25 Aug 2026 17:35:42 +0800 Subject: [PATCH] refactor editor --- examples/Root.vue | 103 ++- examples/components/ArticleShowcase.vue | 177 +++++ examples/components/DurationShowcase.vue | 88 +++ examples/components/EditorShowcase.vue | 186 ++++++ examples/components/NotifyShowcase.vue | 137 ++++ examples/demo-data.ts | 59 ++ examples/main.ts | 5 +- index.html | 13 +- package.json | 8 +- pnpm-lock.yaml | 180 +++++- src/components/article-editor/index.ts | 11 + src/components/article-editor/index.vue | 289 +++++++++ src/components/article-view/index.ts | 5 + src/components/article-view/index.vue | 118 ++++ src/components/code-editor/index.ts | 25 + src/components/code-editor/index.vue | 434 +++++++++++++ src/components/code-editor/style.less | 202 ++++++ src/components/duration-picker/index.ts | 5 + src/components/duration-picker/index.vue | 158 +++++ src/components/editor-search/index.vue | 273 ++++++++ src/components/index.ts | 44 +- src/components/message.ts | 9 + src/components/notify-editor/index.ts | 11 + src/components/notify-editor/index.vue | 76 +++ src/components/notify-view/index.ts | 5 + src/components/notify-view/index.vue | 32 + src/components/tiptap-content.less | 46 ++ .../tiptap-editor/TiptapCodeBlockButton.vue | 95 +++ .../tiptap-editor/TiptapIframeButton.vue | 149 +++++ .../tiptap-editor/TiptapLinkButton.vue | 131 ++++ .../tiptap-editor/TiptapMediaButton.vue | 216 +++++++ .../tiptap-editor/TiptapTableButton.vue | 113 ++++ .../tiptap-editor/TiptapToolbar.vue | 405 ++++++++++++ src/components/tiptap-editor/index.ts | 19 + src/components/tiptap-editor/index.vue | 603 ++++++++++++++++++ src/components/tiptap-view/index.ts | 5 + src/components/tiptap-view/index.vue | 54 ++ src/index.ts | 3 + tsconfig.json | 3 + vite.config.ts | 4 + 40 files changed, 4444 insertions(+), 55 deletions(-) create mode 100644 examples/components/ArticleShowcase.vue create mode 100644 examples/components/DurationShowcase.vue create mode 100644 examples/components/EditorShowcase.vue create mode 100644 examples/components/NotifyShowcase.vue create mode 100644 examples/demo-data.ts create mode 100644 src/components/article-editor/index.ts create mode 100644 src/components/article-editor/index.vue create mode 100644 src/components/article-view/index.ts create mode 100644 src/components/article-view/index.vue create mode 100644 src/components/code-editor/index.ts create mode 100644 src/components/code-editor/index.vue create mode 100644 src/components/code-editor/style.less create mode 100644 src/components/duration-picker/index.ts create mode 100644 src/components/duration-picker/index.vue create mode 100644 src/components/editor-search/index.vue create mode 100644 src/components/message.ts create mode 100644 src/components/notify-editor/index.ts create mode 100644 src/components/notify-editor/index.vue create mode 100644 src/components/notify-view/index.ts create mode 100644 src/components/notify-view/index.vue create mode 100644 src/components/tiptap-content.less create mode 100644 src/components/tiptap-editor/TiptapCodeBlockButton.vue create mode 100644 src/components/tiptap-editor/TiptapIframeButton.vue create mode 100644 src/components/tiptap-editor/TiptapLinkButton.vue create mode 100644 src/components/tiptap-editor/TiptapMediaButton.vue create mode 100644 src/components/tiptap-editor/TiptapTableButton.vue create mode 100644 src/components/tiptap-editor/TiptapToolbar.vue create mode 100644 src/components/tiptap-editor/index.ts create mode 100644 src/components/tiptap-editor/index.vue create mode 100644 src/components/tiptap-view/index.ts create mode 100644 src/components/tiptap-view/index.vue diff --git a/examples/Root.vue b/examples/Root.vue index b38a799..82e35c0 100644 --- a/examples/Root.vue +++ b/examples/Root.vue @@ -1,21 +1,102 @@ diff --git a/examples/components/ArticleShowcase.vue b/examples/components/ArticleShowcase.vue new file mode 100644 index 0000000..24014e4 --- /dev/null +++ b/examples/components/ArticleShowcase.vue @@ -0,0 +1,177 @@ + + + + + diff --git a/examples/components/DurationShowcase.vue b/examples/components/DurationShowcase.vue new file mode 100644 index 0000000..c065dcd --- /dev/null +++ b/examples/components/DurationShowcase.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/examples/components/EditorShowcase.vue b/examples/components/EditorShowcase.vue new file mode 100644 index 0000000..4720216 --- /dev/null +++ b/examples/components/EditorShowcase.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/examples/components/NotifyShowcase.vue b/examples/components/NotifyShowcase.vue new file mode 100644 index 0000000..7db37f1 --- /dev/null +++ b/examples/components/NotifyShowcase.vue @@ -0,0 +1,137 @@ + + + + + diff --git a/examples/demo-data.ts b/examples/demo-data.ts new file mode 100644 index 0000000..f2defa0 --- /dev/null +++ b/examples/demo-data.ts @@ -0,0 +1,59 @@ +import type { Article, NotifyDetail, UploadResult } from "timi-web/editor"; + +/** 编辑器演示使用的固定内容,避免每个页面各自维护一份 HTML。 */ +export const DEMO_ARTICLE_CONTENT = `

跨平台编辑器

+

这段内容同时验证标题、段落、链接、表格和代码块的渲染。

+

移动端和 PC 端只替换交互层,共享文档模型。

+
const message: string = "timi editor";
`; + +export const DEMO_CODE = `type DemoUser = { +\tid: string; +\tname: string; +}; + +const user: DemoUser = { +\tid: "demo-001", +\tname: "Timi" +}; + +console.log(user.name);`; + +export const DEMO_ARTICLE: Article = { + title: "编辑器组件演示文章", + description: "用于验证文章编辑、预览和附件处理。", + contentType: "TIP_TAP", + content: DEMO_ARTICLE_CONTENT, + attachmentList: [] +}; + +export const DEMO_NOTIFY: NotifyDetail = { + subject: "编辑器通知演示", + msgType: "INTERNAL", + contentType: "TIP_TAP", + content: DEMO_ARTICLE_CONTENT, + status: "WAITING", + attachmentList: [] +}; + +export function cloneArticle(): Article { + return { + ...DEMO_ARTICLE, + attachmentList: [...(DEMO_ARTICLE.attachmentList || [])] + }; +} + +export function cloneNotify(): NotifyDetail { + return { + ...DEMO_NOTIFY, + attachmentList: [...(DEMO_NOTIFY.attachmentList || [])] + }; +} + +/** 返回本地预览地址,让示例无需依赖后端上传接口。 */ +export async function uploadDemoMedia(file: File): Promise { + return { + id: `demo-${Date.now()}-${file.name}`, + name: file.name, + url: URL.createObjectURL(file) + }; +} diff --git a/examples/main.ts b/examples/main.ts index 3deee76..fcec06c 100644 --- a/examples/main.ts +++ b/examples/main.ts @@ -1,15 +1,14 @@ import { createApp } from "vue"; import Root from "./Root.vue"; -import { axios, VPopup } from "timi-web"; +import TimiWebUI, { VPopup } from "timi-web"; import "timi-web/style.css"; import "tdesign-mobile-vue/es/style/index.css"; import "../src/assets/style/tencent-cloud.less"; import "../src/assets/style/tencent-cloud-custom.less"; -axios.defaults.baseURL = "http://localhost:8091"; - const app = createApp(Root); +app.use(TimiWebUI); app.directive("popup", VPopup); app.mount("#root"); diff --git a/index.html b/index.html index 166eaae..a6af72d 100644 --- a/index.html +++ b/index.html @@ -1,9 +1,16 @@ - + - - Vite + Vue + TS + + + + + + + + + Timi TDesign Mobile
diff --git a/package.json b/package.json index e411bcb..15ba167 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "module": "./dist/timi-tdesign-mobile.mjs", "style": "./dist/timi-tdesign-mobile.css", "private": false, - "version": "0.0.9", + "version": "0.0.10", "license": "MIT", "scripts": { "dev": "vite", @@ -36,6 +36,8 @@ "node": ">=16.0.0" }, "dependencies": { + "tdesign-mobile-vue": "^1.13.2", + "timi-web": "0.0.24", "vue": "^3.5.13", "vue-router": "4.5.0" }, @@ -63,8 +65,6 @@ "vite-plugin-vue-setup-extend": "^0.4.0", "@vue/tsconfig": "^0.7.0", "vite": "7.1.11", - "vue-tsc": "^2.2.10", - "tdesign-mobile-vue": "^1.13.2", - "timi-web": "0.0.2" + "vue-tsc": "^2.2.10" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a90f868..769a1ea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + tdesign-mobile-vue: + specifier: ^1.13.2 + version: 1.13.2(vue@3.5.13(typescript@5.8.3)) + timi-web: + specifier: 0.0.24 + version: 0.0.24(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)) vue: specifier: ^3.5.13 version: 3.5.13(typescript@5.8.3) @@ -66,12 +72,6 @@ importers: prettier: specifier: ^3.5.2 version: 3.5.3 - tdesign-mobile-vue: - specifier: ^1.13.2 - version: 1.13.2(vue@3.5.13(typescript@5.8.3)) - timi-web: - specifier: 0.0.2 - version: 0.0.2 typescript: specifier: ^5.8.3 version: 5.8.3 @@ -1455,6 +1455,15 @@ packages: '@vue/devtools-api@6.6.4': resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + '@vue/devtools-api@7.7.10': + resolution: {integrity: sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==} + + '@vue/devtools-kit@7.7.10': + resolution: {integrity: sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==} + + '@vue/devtools-shared@7.7.10': + resolution: {integrity: sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==} + '@vue/eslint-config-prettier@10.2.0': resolution: {integrity: sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw==} peerDependencies: @@ -1613,6 +1622,10 @@ packages: resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} engines: {node: '>= 10.0.0'} + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + ajv-draft-04@1.0.0: resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} peerDependencies: @@ -1730,8 +1743,8 @@ packages: peerDependencies: postcss: ^8.1.0 - axios@1.13.5: - resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} + axios@1.18.0: + resolution: {integrity: sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==} babel-loader@8.4.1: resolution: {integrity: sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==} @@ -1779,6 +1792,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + birpc@2.9.0: + resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -2169,6 +2185,10 @@ packages: copy-anything@2.0.6: resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==} + copy-anything@4.1.0: + resolution: {integrity: sha512-ufbM3smX/Jbnpk5wcQjzd1MgBpzmqfNETUAyZNrGwU9foRlyHoGzMMBBCRzEhQLBjZfFDE1W2ufPXX2vdWkV8Q==} + engines: {node: '>=18'} + copy-webpack-plugin@9.1.0: resolution: {integrity: sha512-rxnR7PaGigJzhqETHGmAcxKnLZSR5u1Y3/bcIv/1FnqXedcL/E2ewK7ZCNrArJKCiSv8yVXhTqetJh8inDvfsA==} engines: {node: '>= 12.13.0'} @@ -2756,6 +2776,15 @@ packages: debug: optional: true + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + form-data@4.0.5: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} @@ -2912,6 +2941,9 @@ packages: resolution: {integrity: sha512-kyWP5PAiMooEvGrA9jcD3IXF7ATu8+o7B3KCbPXid5se52NPqnOpM/r9qeW2heMnOekF4kqR1fXJqCYeCLKrZg==} engines: {node: '>=16.9.0'} + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -2979,6 +3011,10 @@ packages: resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} engines: {node: '>=8.0.0'} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -3449,6 +3485,9 @@ packages: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} engines: {node: '>=8'} + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -3708,6 +3747,9 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + picocolors@0.2.1: resolution: {integrity: sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==} @@ -3730,6 +3772,15 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + pinia@3.0.4: + resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==} + peerDependencies: + typescript: '>=4.5.0' + vue: ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + pkce-challenge@5.0.1: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} @@ -4001,8 +4052,9 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} @@ -4132,6 +4184,9 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -4322,6 +4377,10 @@ packages: resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==} engines: {node: '>=6.0.0'} + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -4395,6 +4454,10 @@ packages: peerDependencies: postcss: ^8.2.15 + superjson@2.2.6: + resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} + engines: {node: '>=16'} + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -4454,11 +4517,6 @@ packages: uglify-js: optional: true - terser@5.39.0: - resolution: {integrity: sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==} - engines: {node: '>=10'} - hasBin: true - terser@5.46.0: resolution: {integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==} engines: {node: '>=10'} @@ -4480,8 +4538,8 @@ packages: thunky@1.1.0: resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} - timi-web@0.0.2: - resolution: {integrity: sha512-tXznHwCjFy/AtmWBky35V0SkLTeHG4FayzUhBRoUNe6a7xxlSQ0p8XcQAuSFkugQuCz8/pJsZNHgplfpzs041A==} + timi-web@0.0.24: + resolution: {integrity: sha512-qzcm72W9EE3BrYiUzc8a20J8xqxH17moEm/ovdijhRbIJtJUr81sslXFe2+OjNtx14meEIPJm3IfRooFzRswLg==} engines: {node: '>=16.0.0'} tinycolor2@1.6.0: @@ -4652,6 +4710,7 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true validate-npm-package-license@3.0.4: @@ -5062,7 +5121,7 @@ snapshots: '@babel/core': 7.26.9 '@babel/helper-compilation-targets': 7.26.5 '@babel/helper-plugin-utils': 7.26.5 - debug: 4.4.0 + debug: 4.4.3 lodash.debounce: 4.0.8 resolve: 1.22.10 transitivePeerDependencies: @@ -6717,6 +6776,24 @@ snapshots: '@vue/devtools-api@6.6.4': {} + '@vue/devtools-api@7.7.10': + dependencies: + '@vue/devtools-kit': 7.7.10 + + '@vue/devtools-kit@7.7.10': + dependencies: + '@vue/devtools-shared': 7.7.10 + birpc: 2.9.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.6 + + '@vue/devtools-shared@7.7.10': + dependencies: + rfdc: 1.4.1 + '@vue/eslint-config-prettier@10.2.0(@types/eslint@9.6.1)(eslint@9.26.0)(prettier@3.5.3)': dependencies: eslint: 9.26.0 @@ -6921,6 +6998,12 @@ snapshots: address@1.2.2: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + ajv-draft-04@1.0.0(ajv@8.13.0): optionalDependencies: ajv: 8.13.0 @@ -7031,13 +7114,15 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 - axios@1.13.5: + axios@1.18.0: dependencies: - follow-redirects: 1.15.11(debug@4.4.3) + follow-redirects: 1.16.0 form-data: 4.0.5 - proxy-from-env: 1.1.0 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 transitivePeerDependencies: - debug + - supports-color babel-loader@8.4.1(@babel/core@7.26.9)(webpack@5.98.0): dependencies: @@ -7094,6 +7179,8 @@ snapshots: binary-extensions@2.3.0: {} + birpc@2.9.0: {} + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -7359,6 +7446,8 @@ snapshots: dependencies: is-what: 3.14.1 + copy-anything@4.1.0: {} + copy-webpack-plugin@9.1.0(webpack@5.98.0): dependencies: fast-glob: 3.3.3 @@ -8046,6 +8135,8 @@ snapshots: optionalDependencies: debug: 4.4.3 + follow-redirects@1.16.0: {} + form-data@4.0.5: dependencies: asynckit: 0.4.0 @@ -8190,6 +8281,8 @@ snapshots: hono@4.11.10: {} + hookable@5.5.3: {} + hosted-git-info@2.8.9: {} hpack.js@2.1.6: @@ -8279,6 +8372,13 @@ snapshots: transitivePeerDependencies: - debug + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + human-signals@2.1.0: {} iconv-lite@0.4.24: @@ -8683,6 +8783,8 @@ snapshots: dependencies: yallist: 4.0.0 + mitt@3.0.1: {} + mkdirp@0.5.6: dependencies: minimist: 1.2.8 @@ -8917,6 +9019,8 @@ snapshots: pathe@2.0.3: {} + perfect-debounce@1.0.0: {} + picocolors@0.2.1: {} picocolors@1.1.1: {} @@ -8930,6 +9034,13 @@ snapshots: pify@4.0.1: optional: true + pinia@3.0.4(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)): + dependencies: + '@vue/devtools-api': 7.7.10 + vue: 3.5.13(typescript@5.8.3) + optionalDependencies: + typescript: 5.8.3 + pkce-challenge@5.0.1: {} pkg-dir@4.2.0: @@ -9194,7 +9305,7 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 - proxy-from-env@1.1.0: {} + proxy-from-env@2.1.0: {} prr@1.0.1: optional: true @@ -9338,6 +9449,8 @@ snapshots: reusify@1.1.0: {} + rfdc@1.4.1: {} + rimraf@3.0.2: dependencies: glob: 7.2.3 @@ -9623,6 +9736,8 @@ snapshots: transitivePeerDependencies: - supports-color + speakingurl@14.0.1: {} + sprintf-js@1.0.3: {} ssri@8.0.1: @@ -9684,6 +9799,10 @@ snapshots: postcss: 8.5.6 postcss-selector-parser: 6.1.2 + superjson@2.2.6: + dependencies: + copy-anything: 4.1.0 + supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -9742,16 +9861,9 @@ snapshots: jest-worker: 27.5.1 schema-utils: 4.3.0 serialize-javascript: 6.0.2 - terser: 5.39.0 + terser: 5.46.0 webpack: 5.98.0 - terser@5.39.0: - dependencies: - '@jridgewell/source-map': 0.3.6 - acorn: 8.14.1 - commander: 2.20.3 - source-map-support: 0.5.21 - terser@5.46.0: dependencies: '@jridgewell/source-map': 0.3.6 @@ -9778,18 +9890,22 @@ snapshots: thunky@1.1.0: {} - timi-web@0.0.2: + timi-web@0.0.24(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)): dependencies: - axios: 1.13.5 + axios: 1.18.0 less: 4.5.1 marked: 17.0.3 marked-gfm-heading-id: 4.1.3(marked@17.0.3) marked-highlight: 2.2.3(marked@17.0.3) marked-mangle: 1.1.12(marked@17.0.3) + pinia: 3.0.4(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)) prismjs: 1.30.0 terser: 5.46.0 transitivePeerDependencies: - debug + - supports-color + - typescript + - vue tinycolor2@1.6.0: {} diff --git a/src/components/article-editor/index.ts b/src/components/article-editor/index.ts new file mode 100644 index 0000000..4fa4ced --- /dev/null +++ b/src/components/article-editor/index.ts @@ -0,0 +1,11 @@ +import view from "./index.vue"; +import { Toolkit } from "timi-web"; +import type { Article } from "timi-web/editor"; + +export type ArticleEditorExpose = { + submit: () => Promise
; + getPendingMediaCount: () => number; +}; + +export const ArticleEditor = Toolkit.withInstall(view); +export default ArticleEditor; diff --git a/src/components/article-editor/index.vue b/src/components/article-editor/index.vue new file mode 100644 index 0000000..c6b1978 --- /dev/null +++ b/src/components/article-editor/index.vue @@ -0,0 +1,289 @@ + + + diff --git a/src/components/article-view/index.ts b/src/components/article-view/index.ts new file mode 100644 index 0000000..190b030 --- /dev/null +++ b/src/components/article-view/index.ts @@ -0,0 +1,5 @@ +import view from "./index.vue"; +import { Toolkit } from "timi-web"; + +export const ArticleView = Toolkit.withInstall(view); +export default ArticleView; diff --git a/src/components/article-view/index.vue b/src/components/article-view/index.vue new file mode 100644 index 0000000..47e643d --- /dev/null +++ b/src/components/article-view/index.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/src/components/code-editor/index.ts b/src/components/code-editor/index.ts new file mode 100644 index 0000000..79a6b45 --- /dev/null +++ b/src/components/code-editor/index.ts @@ -0,0 +1,25 @@ +import view from "./index.vue"; +import { Toolkit } from "timi-web"; +import "./style.less"; + +export type CodeEditorScrollState = { + top: number; + left: number; +}; + +export type CodeEditorExpose = { + focus: () => void; + blur: () => void; + getScrollState: () => CodeEditorScrollState; + setScrollState: (state: Partial) => void; + findNext: () => boolean; + findPrevious: () => boolean; + replaceCurrent: () => boolean; + replaceAll: () => number; + openSearch: (showReplace: boolean) => void; + closeSearch: () => void; + textareaRef?: HTMLTextAreaElement; +}; + +export const CodeEditor = Toolkit.withInstall(view); +export default CodeEditor; diff --git a/src/components/code-editor/index.vue b/src/components/code-editor/index.vue new file mode 100644 index 0000000..0858da7 --- /dev/null +++ b/src/components/code-editor/index.vue @@ -0,0 +1,434 @@ + + + diff --git a/src/components/code-editor/style.less b/src/components/code-editor/style.less new file mode 100644 index 0000000..b9453f4 --- /dev/null +++ b/src/components/code-editor/style.less @@ -0,0 +1,202 @@ +.tui-code-editor { + width: calc(100% - 2px); + min-width: 0; + border: 1px solid var(--td-component-stroke, #B8BBC9); + display: flex; + overflow: hidden; + background: transparent; + border-radius: var(--td-radius-medium, 0); + + .gutter { + flex: none; + color: var(--td-text-color-placeholder, #999); + overflow: hidden; + text-align: right; + background: #F2F2F2E6; + border-right: 1px solid var(--td-component-stroke, #B8BBC9); + + > span { + width: 3.25rem; + height: 1.5rem; + display: block; + font-size: 1rem; + font-family: "JetBrains Mono", "Fira Code", Consolas, "Courier New", monospace; + line-height: 1.5rem; + padding-right: .25rem; + } + } + + .body { + flex: 1; + min-width: 0; + min-height: 0; + position: relative; + overflow: hidden; + } + + .search-bar { + top: .5rem; + right: .5rem; + gap: .5rem; + z-index: 3; + display: flex; + padding: .5rem; + position: absolute; + align-items: center; + border-radius: var(--td-radius-default, .375rem); + background: var(--td-bg-color-container, #fff); + border: 1px solid var(--td-component-stroke, #B8BBC9); + box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .12); + } + + .search-input { + width: 10rem; + height: 2rem; + padding: 0 .5rem; + color: var(--td-text-color-primary, #111); + border: 1px solid var(--td-component-stroke, #B8BBC9); + background: var(--td-bg-color-container, #fff); + border-radius: var(--td-radius-default, .375rem); + font: inherit; + } + + .search-actions { + gap: .25rem; + display: flex; + align-items: center; + } + + .search-button { + height: 2rem; + padding: 0 .625rem; + color: var(--td-text-color-primary, #111); + border: 1px solid var(--td-component-stroke, #B8BBC9); + background: var(--td-bg-color-container, #fff); + border-radius: var(--td-radius-default, .375rem); + cursor: pointer; + + &:hover { + background: var(--td-bg-color-container-hover, #f6f6f6); + } + } + + .search-status { + color: var(--td-text-color-secondary, #666); + white-space: nowrap; + font-size: .875rem; + } + + .preview, + .input { + inset: 0; + width: 100%; + height: 100%; + margin: 0; + tab-size: 4; + overflow: auto; + max-width: 100%; + min-width: 0; + line-height: 1.5rem; + font-size: 1rem; + box-sizing: border-box; + font-family: "JetBrains Mono", "Fira Code", Consolas, "Courier New", monospace; + } + + .preview { + top: 0; + left: 0; + border: none; + position: absolute; + word-break: normal; + background: transparent; + transition: none; + white-space: pre; + border-radius: 0; + pointer-events: none; + + code { + padding: 0; + display: block; + width: max-content; + min-width: 100%; + min-height: 100%; + color: inherit; + background: transparent; + text-shadow: none; + font-size: inherit; + line-height: inherit; + font-family: inherit; + } + } + + .input { + top: 0; + left: 0; + color: transparent; + resize: none; + border: none; + outline: none; + position: absolute; + background: transparent; + white-space: pre; + word-break: normal; + overflow-wrap: normal; + caret-color: var(--td-text-color-primary, #111); + -webkit-text-fill-color: transparent; + } + + .input::placeholder { + color: var(--td-text-color-placeholder, #999); + -webkit-text-fill-color: var(--td-text-color-placeholder, #999); + } + + .input::selection { + background: rgb(0 122 255 / 18%); + } + + &.disabled { + opacity: .65; + } + + &.readonly { + .preview { + pointer-events: auto; + } + } +} + +@media (prefers-color-scheme: dark) { + .tui-code-editor { + border-color: var(--td-component-stroke, #434343); + + .gutter { + border-right-color: var(--td-component-stroke, #434343); + } + + .search-bar { + background: var(--td-bg-color-container, #1f1f1f); + border-color: var(--td-component-stroke, #434343); + box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .28); + } + + .search-input, + .search-button { + color: var(--td-text-color-primary, #F5F5F5); + border-color: var(--td-component-stroke, #434343); + background: var(--td-bg-color-container, #1f1f1f); + } + + .search-button:hover { + background: var(--td-bg-color-container-hover, #2b2b2b); + } + + .input { + caret-color: var(--td-text-color-primary, #F5F5F5); + } + + .input::selection { + background: rgb(64 153 255 / 28%); + } + } +} + diff --git a/src/components/duration-picker/index.ts b/src/components/duration-picker/index.ts new file mode 100644 index 0000000..1790071 --- /dev/null +++ b/src/components/duration-picker/index.ts @@ -0,0 +1,5 @@ +import view from "./index.vue"; +import { Toolkit } from "timi-web"; + +export const DurationPicker = Toolkit.withInstall(view); +export default DurationPicker; diff --git a/src/components/duration-picker/index.vue b/src/components/duration-picker/index.vue new file mode 100644 index 0000000..66d6735 --- /dev/null +++ b/src/components/duration-picker/index.vue @@ -0,0 +1,158 @@ + + + diff --git a/src/components/editor-search/index.vue b/src/components/editor-search/index.vue new file mode 100644 index 0000000..1217022 --- /dev/null +++ b/src/components/editor-search/index.vue @@ -0,0 +1,273 @@ + + + + + diff --git a/src/components/index.ts b/src/components/index.ts index dd97e73..779f1e5 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,10 +1,50 @@ /** 导出所有组件 */ import RootLayout from "./root-layout"; +import DurationPicker from "./duration-picker"; +import CodeEditor from "./code-editor"; +import TiptapEditor from "./tiptap-editor"; +import TiptapView from "./tiptap-view"; +import ArticleEditor from "./article-editor"; +import ArticleView from "./article-view"; +import NotifyEditor from "./notify-editor"; +import NotifyView from "./notify-view"; export default [ - RootLayout + RootLayout, + DurationPicker, + CodeEditor, + TiptapEditor, + TiptapView, + ArticleEditor, + ArticleView, + NotifyEditor, + NotifyView ]; export { - RootLayout + RootLayout, + DurationPicker, + CodeEditor, + TiptapEditor, + TiptapView, + ArticleEditor, + ArticleView, + NotifyEditor, + NotifyView }; + +export type { + CodeEditorExpose +} from "./code-editor"; + +export type { + TiptapEditorExpose +} from "./tiptap-editor"; + +export type { + ArticleEditorExpose +} from "./article-editor"; + +export type { + NotifyEditorExpose +} from "./notify-editor"; diff --git a/src/components/message.ts b/src/components/message.ts new file mode 100644 index 0000000..01354d2 --- /dev/null +++ b/src/components/message.ts @@ -0,0 +1,9 @@ +import { ToastPlugin } from "tdesign-mobile-vue"; + +type MessageType = "warning" | "error"; + +export async function showPlatformMessage(type: MessageType, content: string) { + await Promise.resolve(ToastPlugin[type]({ + message: content + })); +} diff --git a/src/components/notify-editor/index.ts b/src/components/notify-editor/index.ts new file mode 100644 index 0000000..2a28533 --- /dev/null +++ b/src/components/notify-editor/index.ts @@ -0,0 +1,11 @@ +import view from "./index.vue"; +import { Toolkit } from "timi-web"; +import type { NotifyDetail } from "timi-web/editor"; + +export type NotifyEditorExpose = { + submit: () => Promise; + getPendingMediaCount: () => number; +}; + +export const NotifyEditor = Toolkit.withInstall(view); +export default NotifyEditor; diff --git a/src/components/notify-editor/index.vue b/src/components/notify-editor/index.vue new file mode 100644 index 0000000..da7d622 --- /dev/null +++ b/src/components/notify-editor/index.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/components/notify-view/index.ts b/src/components/notify-view/index.ts new file mode 100644 index 0000000..0bd6ef4 --- /dev/null +++ b/src/components/notify-view/index.ts @@ -0,0 +1,5 @@ +import view from "./index.vue"; +import { Toolkit } from "timi-web"; + +export const NotifyView = Toolkit.withInstall(view); +export default NotifyView; diff --git a/src/components/notify-view/index.vue b/src/components/notify-view/index.vue new file mode 100644 index 0000000..674ec41 --- /dev/null +++ b/src/components/notify-view/index.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/components/tiptap-content.less b/src/components/tiptap-content.less new file mode 100644 index 0000000..73ffa63 --- /dev/null +++ b/src/components/tiptap-content.less @@ -0,0 +1,46 @@ +.tp-content-theme() { + color: var(--td-text-color-primary); + line-height: 1.6; + + p { + margin: 0 0 .75rem; + } + + p:empty:before { + content: "\00a0"; + } + + ul, + ol { + margin: 0 0 .75rem; + padding-left: 1.25rem; + } + + table { + width: 100%; + margin: .75rem 0; + table-layout: fixed; + border-collapse: collapse; + } + + th, + td { + border: 1px solid var(--td-component-border); + } + + th { + background: var(--td-bg-color-secondarycontainer); + } + + img, + video, + iframe { + margin: 0 auto; + max-width: 100%; + } +} + +.tp-content-body { + .tp-content-theme(); +} + diff --git a/src/components/tiptap-editor/TiptapCodeBlockButton.vue b/src/components/tiptap-editor/TiptapCodeBlockButton.vue new file mode 100644 index 0000000..8c26c55 --- /dev/null +++ b/src/components/tiptap-editor/TiptapCodeBlockButton.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/components/tiptap-editor/TiptapIframeButton.vue b/src/components/tiptap-editor/TiptapIframeButton.vue new file mode 100644 index 0000000..86a4fd4 --- /dev/null +++ b/src/components/tiptap-editor/TiptapIframeButton.vue @@ -0,0 +1,149 @@ + + + + + diff --git a/src/components/tiptap-editor/TiptapLinkButton.vue b/src/components/tiptap-editor/TiptapLinkButton.vue new file mode 100644 index 0000000..67dfc5c --- /dev/null +++ b/src/components/tiptap-editor/TiptapLinkButton.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/src/components/tiptap-editor/TiptapMediaButton.vue b/src/components/tiptap-editor/TiptapMediaButton.vue new file mode 100644 index 0000000..1695797 --- /dev/null +++ b/src/components/tiptap-editor/TiptapMediaButton.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/src/components/tiptap-editor/TiptapTableButton.vue b/src/components/tiptap-editor/TiptapTableButton.vue new file mode 100644 index 0000000..bc61182 --- /dev/null +++ b/src/components/tiptap-editor/TiptapTableButton.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/src/components/tiptap-editor/TiptapToolbar.vue b/src/components/tiptap-editor/TiptapToolbar.vue new file mode 100644 index 0000000..4566c71 --- /dev/null +++ b/src/components/tiptap-editor/TiptapToolbar.vue @@ -0,0 +1,405 @@ + + + + + diff --git a/src/components/tiptap-editor/index.ts b/src/components/tiptap-editor/index.ts new file mode 100644 index 0000000..5f44f8e --- /dev/null +++ b/src/components/tiptap-editor/index.ts @@ -0,0 +1,19 @@ +import view from "./index.vue"; +import { Toolkit } from "timi-web"; +import type { TiptapEditorInstance } from "timi-web/editor"; + +export * from "timi-web/editor"; +export type TiptapEditorExpose = { + submit: () => Promise; + getPendingMediaCount: () => number; + openSearch: (showReplace: boolean) => void; + closeSearch: () => void; + findNext: () => boolean; + findPrevious: () => boolean; + replaceCurrent: () => boolean; + replaceAll: () => number; + editor?: TiptapEditorInstance; +}; + +export const TiptapEditor = Toolkit.withInstall(view); +export default TiptapEditor; diff --git a/src/components/tiptap-editor/index.vue b/src/components/tiptap-editor/index.vue new file mode 100644 index 0000000..f50fea0 --- /dev/null +++ b/src/components/tiptap-editor/index.vue @@ -0,0 +1,603 @@ + + + + + diff --git a/src/components/tiptap-view/index.ts b/src/components/tiptap-view/index.ts new file mode 100644 index 0000000..1c4cd1d --- /dev/null +++ b/src/components/tiptap-view/index.ts @@ -0,0 +1,5 @@ +import view from "./index.vue"; +import { Toolkit } from "timi-web"; + +export const TiptapView = Toolkit.withInstall(view); +export default TiptapView; diff --git a/src/components/tiptap-view/index.vue b/src/components/tiptap-view/index.vue new file mode 100644 index 0000000..1b7b059 --- /dev/null +++ b/src/components/tiptap-view/index.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/src/index.ts b/src/index.ts index 04ff76e..f596468 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,8 @@ import "./assets/style/tencent-cloud.less"; import "./assets/style/tencent-cloud-custom.less"; +import "./components/tiptap-content.less"; +import "timi-web/style.css"; +import "../../timi-web/src/components/prism-code-block.less"; export * from "./components"; diff --git a/tsconfig.json b/tsconfig.json index 3d40232..8354f2d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,6 +20,9 @@ "@/*": [ "./examples/*" ], + "timi-web/editor": [ + "../timi-web/src/editor/index.ts" + ], "timi-tdesign-mobile": [ "./src/index.ts" ] diff --git a/vite.config.ts b/vite.config.ts index 88f81d1..959fa9c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -12,6 +12,10 @@ const alias: Alias[] = [ find: "@", replacement: resolve(__dirname, "./examples") }, + { + find: /^timi-web\/editor$/, + replacement: resolve(__dirname, "../timi-web/src/editor/index.ts") + }, { find: /^timi-tdesign-mobile(\/(es|lib))?$/, replacement: resolve(__dirname, "./src/index.ts")