no message

This commit is contained in:
KuroSago 2025-07-10 16:09:10 +08:00
parent 966cb55c25
commit bb1b91b6a6
5 changed files with 403 additions and 35 deletions

View File

@ -6,5 +6,6 @@
</template>
<script lang="ts" setup>
import { TiptapEditor, ThemeToggle } from "@tiptap/component";
import { TiptapEditor, ThemeToggle, useTiptapContext } from "@tiptap/component";
useTiptapContext();
</script>

View File

@ -0,0 +1,88 @@
import { provide, inject, reactive, readonly, type InjectionKey } from "vue";
const TiptapStoreKey: InjectionKey<TiptapStore> = Symbol("TiptapStore");
export interface TiptapState {
content: any;
theme: string | null;
hideToolbar: boolean;
hideMenubar: boolean;
disabled: boolean;
minimal: boolean;
}
export interface TiptapActions {
toggleMinimal: () => void;
updateContent: (newContent: string) => void;
setTheme: (theme: string) => void;
toggleToolbar: () => void;
toggleMenubar: () => void;
toggleEditable: () => void;
}
export type TiptapStore = {
state: Readonly<TiptapState>;
actions: TiptapActions;
};
export function useTiptapStore(initialState?: Partial<TiptapState>) {
const state = reactive<TiptapState>({
content: "",
theme: null,
hideToolbar: false,
hideMenubar: false,
disabled: false,
minimal: false,
...initialState,
});
const toggleMinimal = () => {
state.minimal = !state.minimal;
};
const updateContent = (newContent: string) => {
state.content = newContent;
};
const setTheme = (theme: string) => {
state.theme = theme;
};
const toggleToolbar = () => {
state.hideToolbar = !state.hideToolbar;
};
const toggleMenubar = () => {
state.hideMenubar = !state.hideMenubar;
};
const toggleEditable = () => {
state.disabled = !state.disabled;
};
const actions: TiptapActions = {
toggleMinimal,
updateContent,
setTheme,
toggleToolbar,
toggleMenubar,
toggleEditable,
};
const store: TiptapStore = {
state: readonly(state),
actions,
};
provide(TiptapStoreKey, store);
return store;
}
export function useTiptapContext() {
const store = inject(TiptapStoreKey);
if (!store) {
throw new Error("useTiptapContext must be used within a TiptapProvider");
}
return store;
}

View File

@ -1,5 +1,5 @@
import TiptapEditor from "./views/index.vue";
import { useTiptapStore, useTiptapContext } from "./context/useTiptapStore";
import { ThemeToggle } from "echo-editor";
export { TiptapEditor, ThemeToggle };
export { TiptapEditor, ThemeToggle, useTiptapStore, useTiptapContext };

View File

@ -1,54 +1,57 @@
<template>
<div class="min-h-screen bg-background">
<EditorActions
:minimal="minimal"
:hide-toolbar="hideToolbar"
:hide-menubar="hideMenubar"
:disabled="disabled"
@toggle-minimal="toggleMinimal"
@toggle-toolbar="hideToolbar = !hideToolbar"
@toggle-menubar="hideMenubar = !hideMenubar"
@toggle-editable="disabled = !disabled"
:minimal="state.minimal"
:hide-toolbar="state.hideToolbar"
:hide-menubar="state.hideMenubar"
:disabled="state.disabled"
@toggle-minimal="actions.toggleMinimal"
@toggle-toolbar="actions.toggleToolbar"
@toggle-menubar="actions.toggleMenubar"
@toggle-editable="actions.toggleEditable"
/>
<echo-editor
v-model="content"
:extensions="extensions"
:hideToolbar="hideToolbar"
:hideMenubar="hideMenubar || minimal"
:key="minimal ? 'minimal' : 'full'"
:disabled="disabled"
:model-value="state.content"
@update:model-value="actions.updateContent"
:extensions="editorExtensions"
:hideToolbar="state.hideToolbar"
:hideMenubar="state.hideMenubar || state.minimal"
:key="state.minimal ? 'minimal' : 'full'"
:disabled="state.disabled"
:maxHeight="512"
output="html"
v-model:theme="theme"
:dark="theme === 'dark'"
:theme="state.theme"
@update:theme="actions.setTheme"
:dark="state.theme === 'dark'"
/>
<HtmlOutput :content="content" />
<HtmlOutput :content="state.content" />
minimal : {{ minimal }}
minimal : {{ state.minimal }}
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { computed } from "vue";
import { EchoEditor } from "echo-editor";
import { DEMO_CONTENT } from "./initContent";
import EditorActions from "../components/EditorActions.vue";
import HtmlOutput from "../components/HtmlOutput.vue";
import { useEditorConfig } from "../components/useEditorConfig";
import { useTiptapStore, type TiptapState } from "../context/useTiptapStore";
import "./style.css";
import "echo-editor/style.css";
const content = ref(DEMO_CONTENT);
const theme = ref<string | null>(null);
const hideToolbar = ref<boolean>(false);
const hideMenubar = ref<boolean>(false);
const disabled = ref<boolean>(false);
const minimal = ref(false);
const props = defineProps<{
initialState?: Partial<TiptapState>;
}>();
const { extensions } = useEditorConfig(minimal.value);
const { state, actions } = useTiptapStore({
...props.initialState,
content: props.initialState?.content || DEMO_CONTENT,
});
function toggleMinimal() {
minimal.value = !minimal.value;
}
const editorExtensions = computed(
() => useEditorConfig(state.minimal).extensions
);
</script>

View File

@ -1,3 +1,279 @@
export const DEMO_CONTENT = `
<h1 style="text-align: center">Echo Editor</h1><p style="text-align: left">A modern WYSIWYG AI rich text editor based on <a target="_blank" rel="noopener noreferrer nofollow" class="link" href="https://github.com/scrumpy/tiptap" stricturl="true">tiptap</a> and <a target="_blank" rel="noopener noreferrer nofollow" class="link" href="https://www.shadcn-vue.com/" stricturl="true">shadcn ui</a> for Vue.js</p><p style="text-align: left"></p><p style="text-align: left"></p><p style="text-align: left"></p><img height="auto" style="margin-left: auto; margin-right: auto; text-align: center" src="https://picsum.photos/1920/1080.webp?t=1" flipx="false" flipy="false" originwidth="500" originheight="281.25" width="500"><p style="text-align: left"></p><div data-type="horizontalRule"><hr></div><h2 style="text-align: left">Demo</h2><p style="text-align: left">👉<a target="_blank" rel="noopener noreferrer nofollow" class="link" href="https://echo-editor.jzcloud.site/" stricturl="true">Demo</a></p><h2 style="text-align: left">Features</h2><ul style="list-style-type: disc"><li><p style="text-align: left">Use <a target="_blank" rel="noopener noreferrer nofollow" class="link" href="https://www.shadcn-vue.com/" stricturl="true">shadcn ui</a> components</p></li><li><p style="text-align: left">Markdown support</p></li><li><p style="text-align: left">TypeScript support</p></li><li><p style="text-align: left">I18n support</p></li><li><p style="text-align: left">Vue 3.x support</p></li><li><p style="text-align: left">Slash Command</p></li><li><p style="text-align: left">Dark Mode</p></li><li><p style="text-align: left">Multi Column</p></li><li><p style="text-align: left">AI Power</p></li><li><p style="text-align: left">Embed</p></li><li><p style="text-align: left">TailwindCss</p></li></ul><h2 style="text-align: left">Installation</h2><pre vnode="true" code="pnpm add echo-editor // or or yarn add echo-editor // or or npm i echo-editor -S" language="bash" linenumbers="true" wordwrap="false" tabsize="2" shouldfocus="false"><code>pnpm add echo-editor // or or yarn add echo-editor // or or npm i echo-editor -S</code></pre><h3 style="text-align: left">install plugin</h3><pre vnode="true" code="import { createApp } from 'vue' import App from './App.vue' import EchoEditor from 'echo-editor' import 'echo-editor/style.css' const app = createApp(App) app.use(EchoEditor) app.mount('#app')" language="typescript" linenumbers="true" wordwrap="false" tabsize="2" shouldfocus="false"><code>import { createApp } from 'vue' import App from './App.vue' import EchoEditor from 'echo-editor' import 'echo-editor/style.css' const app = createApp(App) app.use(EchoEditor) app.mount('#app')</code></pre><p style="text-align: left"></p>
`;
export const DEMO_CONTENT = {
type: "doc",
content: [
{
type: "heading",
attrs: { textAlign: "center", indent: 0, lineHeight: null, level: 1 },
content: [{ type: "text", text: "Echo Editor" }],
},
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
content: [
{
type: "text",
text: "A modern WYSIWYG AI rich text editor based on ",
},
{
type: "text",
marks: [
{
type: "link",
attrs: {
href: "https://github.com/scrumpy/tiptap",
target: "_blank",
rel: "noopener noreferrer nofollow",
class: "link",
},
},
],
text: "tiptap",
},
{ type: "text", text: " and " },
{
type: "text",
marks: [
{
type: "link",
attrs: {
href: "https://www.shadcn-vue.com/",
target: "_blank",
rel: "noopener noreferrer nofollow",
class: "link",
},
},
],
text: "shadcn ui",
},
{ type: "text", text: " for Vue.js" },
],
},
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
},
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
},
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
},
{
type: "image",
attrs: {
textAlign: "center",
src: "https://picsum.photos/1920/1080.webp?t=1",
alt: null,
title: null,
width: 500,
},
},
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
},
{ type: "horizontalRule" },
{
type: "heading",
attrs: { textAlign: "left", indent: 0, lineHeight: null, level: 2 },
content: [{ type: "text", text: "Demo" }],
},
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
content: [
{ type: "text", text: "👉" },
{
type: "text",
marks: [
{
type: "link",
attrs: {
href: "https://echo-editor.jzcloud.site/",
target: "_blank",
rel: "noopener noreferrer nofollow",
class: "link",
},
},
],
text: "Demo",
},
],
},
{
type: "heading",
attrs: { textAlign: "left", indent: 0, lineHeight: null, level: 2 },
content: [{ type: "text", text: "Features" }],
},
{
type: "bulletList",
attrs: { listStyleType: "disc" },
content: [
{
type: "listItem",
content: [
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
content: [
{ type: "text", text: "Use " },
{
type: "text",
marks: [
{
type: "link",
attrs: {
href: "https://www.shadcn-vue.com/",
target: "_blank",
rel: "noopener noreferrer nofollow",
class: "link",
},
},
],
text: "shadcn ui",
},
{ type: "text", text: " components" },
],
},
],
},
{
type: "listItem",
content: [
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
content: [{ type: "text", text: "Markdown support" }],
},
],
},
{
type: "listItem",
content: [
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
content: [{ type: "text", text: "TypeScript support" }],
},
],
},
{
type: "listItem",
content: [
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
content: [{ type: "text", text: "I18n support" }],
},
],
},
{
type: "listItem",
content: [
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
content: [{ type: "text", text: "Vue 3.x support" }],
},
],
},
{
type: "listItem",
content: [
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
content: [{ type: "text", text: "Slash Command" }],
},
],
},
{
type: "listItem",
content: [
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
content: [{ type: "text", text: "Dark Mode" }],
},
],
},
{
type: "listItem",
content: [
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
content: [{ type: "text", text: "Multi Column" }],
},
],
},
{
type: "listItem",
content: [
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
content: [{ type: "text", text: "AI Power" }],
},
],
},
{
type: "listItem",
content: [
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
content: [{ type: "text", text: "Embed" }],
},
],
},
{
type: "listItem",
content: [
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
content: [{ type: "text", text: "TailwindCss" }],
},
],
},
],
},
{
type: "heading",
attrs: { textAlign: "left", indent: 0, lineHeight: null, level: 2 },
content: [{ type: "text", text: "Installation" }],
},
{
type: "codeBlock",
attrs: { language: "bash" },
content: [
{
type: "text",
text: "pnpm add echo-editor\n// or\nor yarn add echo-editor\n// or\nor npm i echo-editor -S",
},
],
},
{
type: "heading",
attrs: { textAlign: "left", indent: 0, lineHeight: null, level: 3 },
content: [{ type: "text", text: "install plugin" }],
},
{
type: "codeBlock",
attrs: { language: "typescript" },
content: [
{
type: "text",
text: "import { createApp } from 'vue'\nimport App from './App.vue'\nimport EchoEditor from 'echo-editor'\nimport 'echo-editor/style.css'\nconst app = createApp(App)\napp.use(EchoEditor)\napp.mount('#app')",
},
],
},
{
type: "paragraph",
attrs: { textAlign: "left", indent: 0, lineHeight: null },
},
],
};