@witchcraft/editor 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/runtime/components/EditorDemoApp.vue +31 -1
- package/dist/runtime/components/EditorDemoControls.d.vue.ts +3 -0
- package/dist/runtime/components/EditorDemoControls.vue +55 -12
- package/dist/runtime/components/EditorDemoControls.vue.d.ts +3 -0
- package/dist/runtime/pm/features/Collaboration/Collaboration.d.ts +14 -63
- package/dist/runtime/pm/features/Collaboration/Collaboration.js +4 -164
- package/dist/runtime/pm/features/Collaboration/createCollaborationPlugins.d.ts +16 -0
- package/dist/runtime/pm/features/Collaboration/createCollaborationPlugins.js +82 -0
- package/dist/runtime/pm/features/DocumentApi/DocumentApi.d.ts +16 -3
- package/dist/runtime/pm/features/DocumentApi/DocumentApi.js +19 -2
- package/dist/runtime/pm/features/DocumentApi/composables/useEditorContent.js +2 -0
- package/dist/runtime/pm/features/DocumentApi/composables/useTestDocumentApi.d.ts +4 -1
- package/dist/runtime/pm/features/DocumentApi/composables/useTestDocumentApi.js +39 -8
- package/dist/runtime/pm/features/DocumentApi/types.d.ts +26 -48
- package/package.json +32 -31
- package/src/runtime/components/EditorDemoApp.vue +32 -1
- package/src/runtime/components/EditorDemoControls.vue +57 -12
- package/src/runtime/pm/features/Collaboration/Collaboration.ts +19 -286
- package/src/runtime/pm/features/Collaboration/createCollaborationPlugins.ts +129 -0
- package/src/runtime/pm/features/DocumentApi/DocumentApi.ts +35 -5
- package/src/runtime/pm/features/DocumentApi/composables/useEditorContent.ts +2 -0
- package/src/runtime/pm/features/DocumentApi/composables/useTestDocumentApi.ts +56 -8
- package/src/runtime/pm/features/DocumentApi/types.ts +30 -52
- package/dist/runtime/demo/App.d.vue.ts +0 -3
- package/dist/runtime/demo/App.vue +0 -100
- package/dist/runtime/demo/App.vue.d.ts +0 -3
- package/src/runtime/demo/App.vue +0 -113
|
@@ -3,10 +3,16 @@ import { keys } from "@alanscodelog/utils/keys";
|
|
|
3
3
|
import { unreachable } from "@alanscodelog/utils/unreachable";
|
|
4
4
|
import { createDocument, generateJSON } from "@tiptap/core";
|
|
5
5
|
import { EditorState } from "@tiptap/pm/state";
|
|
6
|
+
import { prosemirrorToYDoc } from "@tiptap/y-tiptap";
|
|
6
7
|
import { ref, toRaw } from "vue";
|
|
7
8
|
import { testExtensions } from "../../../testSchema.js";
|
|
9
|
+
import { Collaboration } from "../../Collaboration/Collaboration.js";
|
|
10
|
+
import { createCollaborationPlugins } from "../../Collaboration/createCollaborationPlugins.js";
|
|
8
11
|
import { DocumentApi } from "../DocumentApi.js";
|
|
9
|
-
export function useTestDocumentApi(editorOptions, embeds, {
|
|
12
|
+
export function useTestDocumentApi(editorOptions, embeds, {
|
|
13
|
+
useCollab = false,
|
|
14
|
+
loadDelay = 0
|
|
15
|
+
} = {}) {
|
|
10
16
|
const cache = ref({});
|
|
11
17
|
const documentApi = new DocumentApi({
|
|
12
18
|
editorOptions,
|
|
@@ -32,28 +38,53 @@ export function useTestDocumentApi(editorOptions, embeds, { loadDelay = 0 } = {}
|
|
|
32
38
|
cache.value[docId].state = state;
|
|
33
39
|
}
|
|
34
40
|
},
|
|
35
|
-
|
|
41
|
+
preEditorInit(docId, options) {
|
|
42
|
+
if (!cache.value[docId]) unreachable();
|
|
43
|
+
const yDoc = cache.value[docId].yDoc;
|
|
44
|
+
if (useCollab && !yDoc) unreachable();
|
|
45
|
+
if (!cache.value[docId].state) unreachable();
|
|
46
|
+
const perDoc = ["history"];
|
|
47
|
+
options.content = cache.value[docId].state.doc.toJSON();
|
|
48
|
+
options.extensions = [
|
|
49
|
+
...(options.extensions ?? []).filter((ext) => !perDoc.includes(ext.name)),
|
|
50
|
+
...useCollab ? [Collaboration] : []
|
|
51
|
+
];
|
|
52
|
+
return options;
|
|
53
|
+
},
|
|
54
|
+
load: async (docId, schema, plugins, getConnectedEditors) => {
|
|
36
55
|
if (loadDelay) {
|
|
37
56
|
await delay(loadDelay);
|
|
38
57
|
}
|
|
39
58
|
if (!embeds[docId]) {
|
|
40
59
|
throw new Error(`No embed found for docId ${docId} in: ${JSON.stringify(embeds, null, " ")}`);
|
|
41
60
|
}
|
|
61
|
+
if (cache.value[docId]?.state) {
|
|
62
|
+
return { state: toRaw(cache.value[docId].state), data: { yDoc: cache.value[docId].yDoc } };
|
|
63
|
+
}
|
|
42
64
|
const json = generateJSON(embeds[docId].content, testExtensions);
|
|
43
65
|
const doc = createDocument(json, schema);
|
|
66
|
+
const yDoc = useCollab ? prosemirrorToYDoc(doc, "prosemirror") : void 0;
|
|
44
67
|
const state = EditorState.create({
|
|
45
68
|
doc,
|
|
46
69
|
schema,
|
|
47
|
-
plugins
|
|
70
|
+
plugins: [
|
|
71
|
+
...plugins,
|
|
72
|
+
...useCollab ? createCollaborationPlugins(
|
|
73
|
+
{
|
|
74
|
+
document: yDoc,
|
|
75
|
+
field: "prosemirror",
|
|
76
|
+
enableContentCheck: true
|
|
77
|
+
},
|
|
78
|
+
schema,
|
|
79
|
+
getConnectedEditors
|
|
80
|
+
) : []
|
|
81
|
+
]
|
|
48
82
|
});
|
|
49
|
-
return {
|
|
50
|
-
state
|
|
51
|
-
/** , data: {...any additional data} */
|
|
52
|
-
};
|
|
83
|
+
return { state, data: { yDoc } };
|
|
53
84
|
},
|
|
54
85
|
refCounter: {
|
|
55
86
|
load(docId, loaded) {
|
|
56
|
-
cache.value[docId] ??= { ...loaded, count: 0 };
|
|
87
|
+
cache.value[docId] ??= { ...loaded, yDoc: loaded.data.yDoc, count: 0 };
|
|
57
88
|
cache.value[docId].count++;
|
|
58
89
|
},
|
|
59
90
|
unload: (docId) => {
|
|
@@ -22,16 +22,33 @@ export type DocumentApiInterface<T extends Record<string, any> = Record<string,
|
|
|
22
22
|
}) => EditorState | undefined;
|
|
23
23
|
/** Load should be called the first time, before attempting to load the state. */
|
|
24
24
|
getFullState: (docId: DocId) => EditorState;
|
|
25
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* For replacing {@link DocumentApi.preEditorInit} which runs after initializing and loading the document but before the transaction listeners are added.
|
|
27
|
+
*
|
|
28
|
+
* Can be used to add the Collaboration extension for example (see useTestDocumentApi for an example).
|
|
29
|
+
*
|
|
30
|
+
* The default implementation just sets the content:
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* preEditorInit: (_docId, options, state) => {
|
|
34
|
+
* options.content = state.doc.toJSON()
|
|
35
|
+
* return options
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
*/
|
|
26
40
|
postEditorInit: (docId: string, editor: Editor) => void;
|
|
41
|
+
connectedEditors: Record<string, Editor[]>;
|
|
42
|
+
connectEditor: (docId: string, editor: Editor) => void;
|
|
43
|
+
disconnectEditor: (docId: string, editor: Editor) => void;
|
|
27
44
|
/**
|
|
28
|
-
* Sets options before initializing the editor. By default just does `options.content = state.doc.toJSON()`, but can be useful
|
|
45
|
+
* Sets options before initializing the editor. By default just does `options.content = state.doc.toJSON()`, but can be useful for using **per editor component** plugins.
|
|
29
46
|
*
|
|
30
|
-
*
|
|
47
|
+
* This is normally a bit tricky to do since the editor component initializes the editor before the document is loaded and is re-used (the wrapper Editor *component*, not the editor) when the document changes.
|
|
31
48
|
*
|
|
32
|
-
*
|
|
49
|
+
* So this hook can be used to add these additional per-editor instances of extensions. Be sure to clone the properties you are modifying. They are only shallow cloned before being passed to the function.
|
|
33
50
|
*
|
|
34
|
-
*
|
|
51
|
+
* If you need **per doc** plugins use `load` instead. See {@link useTestDocumentApi} for an example.
|
|
35
52
|
*
|
|
36
53
|
* ```ts
|
|
37
54
|
* preEditorInit(docId, options: Partial<EditorOptions>, state: EditorState) {
|
|
@@ -40,58 +57,19 @@ export type DocumentApiInterface<T extends Record<string, any> = Record<string,
|
|
|
40
57
|
* const ydoc = cache.value[docId].ydoc
|
|
41
58
|
* // it's suggested you add the collab extension only here
|
|
42
59
|
* // otherwise you would have to initially configure it with a dummy document
|
|
43
|
-
* const collabExt = Collaboration.configure({
|
|
44
|
-
* document: ydoc
|
|
45
|
-
* }) as any
|
|
46
60
|
* options.extensions = [
|
|
47
61
|
* ...(options.extensions ?? []),
|
|
48
|
-
*
|
|
62
|
+
* // per editor extensions
|
|
49
63
|
* ]
|
|
50
64
|
* return options
|
|
51
65
|
* },
|
|
52
|
-
* load: async (
|
|
53
|
-
* docId: string,
|
|
54
|
-
* schema: Schema,
|
|
55
|
-
* plugins: Plugin[],
|
|
56
|
-
* ) => {
|
|
57
|
-
* if (cache.value[docId]?.state) {
|
|
58
|
-
* return { state: toRaw(cache.value[docId].state) }
|
|
59
|
-
* }
|
|
60
|
-
* const doc = getFromYourDb(docId)
|
|
61
|
-
* const decoded = toUint8Array(doc.contentBinary)
|
|
62
|
-
* const yDoc = new Y.Doc()
|
|
63
|
-
* Y.applyUpdate(yDoc, decoded)
|
|
64
|
-
*
|
|
65
|
-
* const yjs = initProseMirrorDoc(yDoc.getXmlFragment("prosemirror"), schema)
|
|
66
|
-
* const state = EditorState.create({
|
|
67
|
-
* doc: yjs.doc,
|
|
68
|
-
* schema,
|
|
69
|
-
* plugins:[
|
|
70
|
-
* ...plugins,
|
|
71
|
-
* // the document api's yjs instance
|
|
72
|
-
* ySyncPlugin(yDoc.getXmlFragment("prosemirror"), {mapping:yjs.mapping}),
|
|
73
|
-
* ]
|
|
74
|
-
* })
|
|
75
|
-
* // return the state and any additional data we want refCounter.load to be called with.
|
|
76
|
-
* return { state, doc, yDoc }
|
|
77
|
-
* },
|
|
78
|
-
* updateFilter(tr:Transaction) {
|
|
79
|
-
* const meta = tr.getMeta(ySyncPluginKey)
|
|
80
|
-
* if (meta) return false
|
|
81
|
-
* return true
|
|
82
|
-
* },
|
|
83
66
|
* ```
|
|
84
|
-
* See {@link DocumentApi.updateFilter} for why yjs (and other syncronization mechanisms) might need to ignore transactions.
|
|
85
67
|
*/
|
|
86
68
|
preEditorInit: (docId: string, options: Partial<EditorOptions>, state: EditorState) => Partial<EditorOptions>;
|
|
87
69
|
/**
|
|
88
|
-
* Return false to
|
|
89
|
-
*
|
|
90
|
-
* This is useful when using a secondary syncronization mechanism, such as yjs.
|
|
91
|
-
*
|
|
92
|
-
* If you load all editors of a file with yjs's plugin and point to the same ydoc, yjs's plugin will sync them. But that means that when the DocumentApi tries to sync the transactions they will have already been applied and the document update will fail.
|
|
70
|
+
* Return false to prevent applying the transaction to the state in the cache.
|
|
93
71
|
*
|
|
94
|
-
*
|
|
72
|
+
* This used to be needed to ignore yjs transactions, but that's no longer the case. Even with multiple editors loaded to use the same ydoc, everything should work. Leaving the option in case it's needed for some other rare use case.
|
|
95
73
|
*/
|
|
96
74
|
updateFilter?: (tr: Transaction) => boolean | undefined;
|
|
97
75
|
updateDocument: (embedId: DocId, tr: Transaction, selfSymbol?: symbol) => void;
|
|
@@ -110,7 +88,7 @@ export type DocumentApiInterface<T extends Record<string, any> = Record<string,
|
|
|
110
88
|
* Tells the document api how to load an unloaded document and any additional data. Whatever this function returns will be passed to the refCounter.load option in the default DocumentApi implementation.
|
|
111
89
|
*
|
|
112
90
|
* ```ts
|
|
113
|
-
* load: async ( docId: string, schema: Schema, plugins: Plugin[],) => {
|
|
91
|
+
* load: async ( docId: string, schema: Schema, plugins: Plugin[], getConnectedEditors: () => Editor[]) => {
|
|
114
92
|
* const dbDoc = getFromYourDb(docId)
|
|
115
93
|
*
|
|
116
94
|
* const state = EditorState.create({
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@witchcraft/editor",
|
|
3
3
|
"description": "Block base prosemirror editor with partial/full editable document embeds, infinite embeds, and document uploads.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"main": "./dist/runtime/main.lib.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -64,34 +64,36 @@
|
|
|
64
64
|
"failOnWarn": false
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
|
-
"@tiptap/core": "^3.
|
|
68
|
-
"@tiptap/extension-blockquote": "^3.
|
|
69
|
-
"@tiptap/extension-bold": "^3.
|
|
70
|
-
"@tiptap/extension-code": "^3.
|
|
71
|
-
"@tiptap/extension-
|
|
72
|
-
"@tiptap/extension-code-block
|
|
73
|
-
"@tiptap/extension-
|
|
74
|
-
"@tiptap/extension-
|
|
75
|
-
"@tiptap/extension-
|
|
76
|
-
"@tiptap/extension-
|
|
77
|
-
"@tiptap/extension-
|
|
78
|
-
"@tiptap/extension-
|
|
79
|
-
"@tiptap/extension-
|
|
80
|
-
"@tiptap/extension-
|
|
81
|
-
"@tiptap/extension-
|
|
82
|
-
"@tiptap/extension-
|
|
83
|
-
"@tiptap/extension-
|
|
84
|
-
"@tiptap/extension-
|
|
85
|
-
"@tiptap/extension-
|
|
86
|
-
"@tiptap/extension-
|
|
87
|
-
"@tiptap/extension-table
|
|
88
|
-
"@tiptap/extension-table-
|
|
89
|
-
"@tiptap/extension-table-
|
|
90
|
-
"@tiptap/extension-
|
|
91
|
-
"@tiptap/extension-
|
|
92
|
-
"@tiptap/
|
|
93
|
-
"@tiptap/
|
|
94
|
-
"@tiptap/
|
|
67
|
+
"@tiptap/core": "^3.12.0",
|
|
68
|
+
"@tiptap/extension-blockquote": "^3.12.0",
|
|
69
|
+
"@tiptap/extension-bold": "^3.12.0",
|
|
70
|
+
"@tiptap/extension-code": "^3.12.0",
|
|
71
|
+
"@tiptap/extension-collaboration": "^3.12.0",
|
|
72
|
+
"@tiptap/extension-code-block": "^3.12.0",
|
|
73
|
+
"@tiptap/extension-code-block-lowlight": "^3.12.0",
|
|
74
|
+
"@tiptap/extension-dropcursor": "^3.12.0",
|
|
75
|
+
"@tiptap/extension-gapcursor": "^3.12.0",
|
|
76
|
+
"@tiptap/extension-hard-break": "^3.12.0",
|
|
77
|
+
"@tiptap/extension-heading": "^3.12.0",
|
|
78
|
+
"@tiptap/extension-highlight": "^3.12.0",
|
|
79
|
+
"@tiptap/extension-history": "^3.12.0",
|
|
80
|
+
"@tiptap/extension-image": "^3.12.0",
|
|
81
|
+
"@tiptap/extension-italic": "^3.12.0",
|
|
82
|
+
"@tiptap/extension-link": "^3.12.0",
|
|
83
|
+
"@tiptap/extension-paragraph": "^3.12.0",
|
|
84
|
+
"@tiptap/extension-strike": "^3.12.0",
|
|
85
|
+
"@tiptap/extension-subscript": "^3.12.0",
|
|
86
|
+
"@tiptap/extension-superscript": "^3.12.0",
|
|
87
|
+
"@tiptap/extension-table": "^3.12.0",
|
|
88
|
+
"@tiptap/extension-table-cell": "^3.12.0",
|
|
89
|
+
"@tiptap/extension-table-header": "^3.12.0",
|
|
90
|
+
"@tiptap/extension-table-row": "^3.12.0",
|
|
91
|
+
"@tiptap/extension-text": "^3.12.0",
|
|
92
|
+
"@tiptap/extension-underline": "^3.12.0",
|
|
93
|
+
"@tiptap/html": "^3.12.0",
|
|
94
|
+
"@tiptap/pm": "^3.12.0",
|
|
95
|
+
"@tiptap/vue-3": "^3.12.0",
|
|
96
|
+
"@tiptap/y-tiptap": "^3.0.1",
|
|
95
97
|
"@witchcraft/ui": "^0.3.19",
|
|
96
98
|
"tailwindcss": "^4.1.17",
|
|
97
99
|
"vue": "^3.5.25"
|
|
@@ -117,7 +119,7 @@
|
|
|
117
119
|
"@fortawesome/free-regular-svg-icons": "^7.0.1",
|
|
118
120
|
"@fortawesome/free-solid-svg-icons": "^7.0.1",
|
|
119
121
|
"@nuxt/eslint-config": "^1.11.0",
|
|
120
|
-
"@tiptap/html": "^3.
|
|
122
|
+
"@tiptap/html": "^3.12.0",
|
|
121
123
|
"@witchcraft/nuxt-utils": "^0.3.6",
|
|
122
124
|
"@witchcraft/ui": "^0.3.19",
|
|
123
125
|
"colord": "^2.9.3",
|
|
@@ -131,7 +133,6 @@
|
|
|
131
133
|
"tailwind-merge": "^3.4.0",
|
|
132
134
|
"unplugin-vue-components": "^30.0.0",
|
|
133
135
|
"uuid": "^13.0.0",
|
|
134
|
-
"y-prosemirror": "^1.3.7",
|
|
135
136
|
"yjs": "^13.6.27"
|
|
136
137
|
},
|
|
137
138
|
"devDependencies": {
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
<EditorDemoControls
|
|
11
11
|
:code-blocks-theme-list="codeBlocksThemeList"
|
|
12
12
|
v-model:code-blocks-theme="codeBlocksTheme"
|
|
13
|
+
v-model:use-two-editors="useTwoEditors"
|
|
13
14
|
/>
|
|
15
|
+
|
|
14
16
|
<Editor
|
|
15
17
|
class="
|
|
16
18
|
max-w-[700px]
|
|
@@ -34,6 +36,30 @@
|
|
|
34
36
|
menus
|
|
35
37
|
}"
|
|
36
38
|
/>
|
|
39
|
+
<Editor
|
|
40
|
+
v-if="useTwoEditors"
|
|
41
|
+
class="
|
|
42
|
+
max-w-[700px]
|
|
43
|
+
flex-1
|
|
44
|
+
flex
|
|
45
|
+
border
|
|
46
|
+
border-neutral-300
|
|
47
|
+
dark:border-neutral-700
|
|
48
|
+
rounded-sm
|
|
49
|
+
min-h-0
|
|
50
|
+
"
|
|
51
|
+
v-bind="{
|
|
52
|
+
codeBlocksThemeIsDark,
|
|
53
|
+
cssVariables: {
|
|
54
|
+
pmCodeBlockBgColor: codeBlocksThemeBgColor
|
|
55
|
+
},
|
|
56
|
+
docId,
|
|
57
|
+
documentApi,
|
|
58
|
+
linkOptions,
|
|
59
|
+
editorOptions,
|
|
60
|
+
menus
|
|
61
|
+
}"
|
|
62
|
+
/>
|
|
37
63
|
</WRoot>
|
|
38
64
|
</template>
|
|
39
65
|
|
|
@@ -41,6 +67,7 @@
|
|
|
41
67
|
// all imports must be explicit so this also works without nuxt
|
|
42
68
|
import type { EditorOptions } from "@tiptap/core"
|
|
43
69
|
import WRoot from "@witchcraft/ui/components/LibRoot"
|
|
70
|
+
import { useRoute } from "nuxt/app"
|
|
44
71
|
import { reactive, ref, shallowRef } from "vue"
|
|
45
72
|
|
|
46
73
|
import Editor from "./Editor.vue"
|
|
@@ -110,9 +137,13 @@ const menus = shallowRef<Record<string, MenuRenderInfo>>({
|
|
|
110
137
|
}
|
|
111
138
|
})
|
|
112
139
|
|
|
140
|
+
const useYjs = useRoute().query.useYjs as string
|
|
141
|
+
const useTwoEditors = ref(false)
|
|
142
|
+
|
|
113
143
|
const { documentApi } = useTestDocumentApi(
|
|
114
144
|
editorOptions as any,
|
|
115
|
-
testDocuments
|
|
145
|
+
testDocuments,
|
|
146
|
+
{ useCollab: useYjs === "true" }
|
|
116
147
|
)
|
|
117
148
|
const docId = ref("root")
|
|
118
149
|
</script>
|
|
@@ -1,33 +1,74 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
3
|
class="
|
|
4
|
+
max-w-[700px]
|
|
4
5
|
w-full
|
|
5
6
|
flex
|
|
6
|
-
|
|
7
|
+
flex-col
|
|
8
|
+
items-center
|
|
7
9
|
justify-center
|
|
8
10
|
gap-2
|
|
9
|
-
|
|
11
|
+
"
|
|
12
|
+
>
|
|
13
|
+
<div
|
|
14
|
+
class="
|
|
15
|
+
flex
|
|
16
|
+
gap-4
|
|
17
|
+
items-center
|
|
18
|
+
justify-center
|
|
19
|
+
border
|
|
20
|
+
border-neutral-300
|
|
21
|
+
dark:border-neutral-700
|
|
22
|
+
rounded-md
|
|
23
|
+
p-2
|
|
24
|
+
"
|
|
25
|
+
>
|
|
26
|
+
<!-- external is to force a reload, otherwise the editors won't work because of how they're setup for the demo (document api is created here and would need to be recreated with the route changes) -->
|
|
27
|
+
<NuxtLink
|
|
28
|
+
v-if="useYjs !== undefined"
|
|
29
|
+
:to="{ path: '/', query: { } }"
|
|
30
|
+
:external="true"
|
|
31
|
+
>Go to Non-Yjs Example</NuxtLink>
|
|
32
|
+
<NuxtLink
|
|
33
|
+
v-else
|
|
34
|
+
:to="{ path: '/', query: { useYjs: 'true' } }"
|
|
35
|
+
:external="true"
|
|
36
|
+
>Go to Yjs Example </NuxtLink>
|
|
37
|
+
<WCheckbox v-model="useTwoEditors">
|
|
38
|
+
Use Two Editors (same document)
|
|
39
|
+
</WCheckbox>
|
|
40
|
+
</div>
|
|
41
|
+
<div
|
|
42
|
+
class="
|
|
43
|
+
flex
|
|
44
|
+
justify-center
|
|
45
|
+
items-center
|
|
46
|
+
gap-2
|
|
47
|
+
[&>*]:rounded-md
|
|
10
48
|
[&>*]:p-2
|
|
11
49
|
[&>*]:border
|
|
12
50
|
[&>*]:border-neutral-300
|
|
13
51
|
[&>*]:dark:border-neutral-700
|
|
52
|
+
|
|
14
53
|
"
|
|
15
|
-
>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
54
|
+
>
|
|
55
|
+
<div class="flex items-center gap-2">
|
|
56
|
+
<span>
|
|
57
|
+
Global Theme:
|
|
58
|
+
</span>
|
|
59
|
+
<WDarkModeSwitcher/>
|
|
60
|
+
</div>
|
|
61
|
+
<CodeBlockThemePicker
|
|
62
|
+
:code-blocks-theme-list="codeBlocksThemeList"
|
|
63
|
+
v-model:code-blocks-theme="codeBlocksTheme"
|
|
64
|
+
/>
|
|
21
65
|
</div>
|
|
22
|
-
<CodeBlockThemePicker
|
|
23
|
-
:code-blocks-theme-list="codeBlocksThemeList"
|
|
24
|
-
v-model:code-blocks-theme="codeBlocksTheme"
|
|
25
|
-
/>
|
|
26
66
|
</div>
|
|
27
67
|
</template>
|
|
28
68
|
|
|
29
69
|
<script setup lang="ts">
|
|
30
70
|
import WDarkModeSwitcher from "@witchcraft/ui/components/LibDarkModeSwitcher"
|
|
71
|
+
import { useRoute } from "nuxt/app"
|
|
31
72
|
|
|
32
73
|
import CodeBlockThemePicker from "./CodeBlockThemePicker.vue"
|
|
33
74
|
|
|
@@ -35,4 +76,8 @@ defineProps<{
|
|
|
35
76
|
codeBlocksThemeList: string[]
|
|
36
77
|
}>()
|
|
37
78
|
const codeBlocksTheme = defineModel<string>("codeBlocksTheme", { required: true })
|
|
79
|
+
|
|
80
|
+
const useTwoEditors = defineModel<boolean>("useTwoEditors", { required: true })
|
|
81
|
+
|
|
82
|
+
const useYjs = useRoute().query.useYjs as string
|
|
38
83
|
</script>
|