@witchcraft/editor 0.1.1 → 0.2.1

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.
Files changed (42) hide show
  1. package/README.md +1 -0
  2. package/dist/module.d.mts +1 -1
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +2 -2
  5. package/dist/runtime/components/EditorDemoApp.vue +31 -1
  6. package/dist/runtime/components/EditorDemoControls.d.vue.ts +3 -0
  7. package/dist/runtime/components/EditorDemoControls.vue +55 -12
  8. package/dist/runtime/components/EditorDemoControls.vue.d.ts +3 -0
  9. package/dist/runtime/pm/features/Base/plugins/debugSelectionPlugin.d.ts +1 -5
  10. package/dist/runtime/pm/features/Base/plugins/debugSelectionPlugin.js +43 -17
  11. package/dist/runtime/pm/features/CodeBlock/components/CodeBlockView.vue +0 -1
  12. package/dist/runtime/pm/features/Collaboration/Collaboration.d.ts +14 -63
  13. package/dist/runtime/pm/features/Collaboration/Collaboration.js +4 -164
  14. package/dist/runtime/pm/features/Collaboration/createCollaborationPlugins.d.ts +16 -0
  15. package/dist/runtime/pm/features/Collaboration/createCollaborationPlugins.js +85 -0
  16. package/dist/runtime/pm/features/DocumentApi/DocumentApi.d.ts +16 -3
  17. package/dist/runtime/pm/features/DocumentApi/DocumentApi.js +19 -2
  18. package/dist/runtime/pm/features/DocumentApi/composables/useEditorContent.js +8 -2
  19. package/dist/runtime/pm/features/DocumentApi/composables/useTestDocumentApi.d.ts +4 -1
  20. package/dist/runtime/pm/features/DocumentApi/composables/useTestDocumentApi.js +39 -8
  21. package/dist/runtime/pm/features/DocumentApi/types.d.ts +26 -48
  22. package/dist/runtime/pm/features/Link/components/BubbleMenuExternalLink.vue +0 -2
  23. package/dist/runtime/pm/features/Link/components/BubbleMenuInternalLink.vue +0 -1
  24. package/package.json +38 -37
  25. package/src/module.ts +3 -3
  26. package/src/runtime/components/EditorDemoApp.vue +32 -1
  27. package/src/runtime/components/EditorDemoControls.vue +57 -12
  28. package/src/runtime/pm/features/Base/plugins/debugSelectionPlugin.ts +53 -28
  29. package/src/runtime/pm/features/CodeBlock/components/CodeBlockView.vue +0 -1
  30. package/src/runtime/pm/features/Collaboration/Collaboration.ts +19 -286
  31. package/src/runtime/pm/features/Collaboration/createCollaborationPlugins.ts +132 -0
  32. package/src/runtime/pm/features/DocumentApi/DocumentApi.ts +35 -5
  33. package/src/runtime/pm/features/DocumentApi/composables/useEditorContent.ts +8 -2
  34. package/src/runtime/pm/features/DocumentApi/composables/useTestDocumentApi.ts +56 -8
  35. package/src/runtime/pm/features/DocumentApi/types.ts +30 -52
  36. package/src/runtime/pm/features/Link/components/BubbleMenuExternalLink.vue +0 -2
  37. package/src/runtime/pm/features/Link/components/BubbleMenuInternalLink.vue +0 -1
  38. package/src/runtime/pm/utils/generator/createPsuedoSentence.ts +1 -1
  39. package/dist/runtime/demo/App.d.vue.ts +0 -3
  40. package/dist/runtime/demo/App.vue +0 -100
  41. package/dist/runtime/demo/App.vue.d.ts +0 -3
  42. package/src/runtime/demo/App.vue +0 -113
@@ -0,0 +1,85 @@
1
+ import { Plugin, PluginKey } from "@tiptap/pm/state";
2
+ import {
3
+ ySyncPlugin,
4
+ yUndoPlugin,
5
+ yUndoPluginKey,
6
+ yXmlFragmentToProsemirrorJSON
7
+ } from "@tiptap/y-tiptap";
8
+ export function createCollaborationPlugins(options, schema, getConnectedEditors) {
9
+ const storage = {
10
+ isDisabled: false
11
+ };
12
+ const fragment = options.fragment ? options.fragment : options.document?.getXmlFragment(options.field);
13
+ if (!fragment) {
14
+ throw new Error("No fragment or document provided.");
15
+ }
16
+ const yUndoPluginInstance = yUndoPlugin(options.yUndoOptions);
17
+ const originalUndoPluginView = yUndoPluginInstance.spec.view;
18
+ yUndoPluginInstance.spec.view = (view) => {
19
+ const { undoManager } = yUndoPluginKey.getState(view.state);
20
+ if (undoManager.restore) {
21
+ undoManager.restore();
22
+ undoManager.restore = () => {
23
+ };
24
+ }
25
+ const viewRet = originalUndoPluginView ? originalUndoPluginView(view) : void 0;
26
+ return {
27
+ destroy: () => {
28
+ const hasUndoManSelf = undoManager.trackedOrigins.has(undoManager);
29
+ const observers = undoManager._observers;
30
+ undoManager.restore = () => {
31
+ if (hasUndoManSelf) {
32
+ undoManager.trackedOrigins.add(undoManager);
33
+ }
34
+ undoManager.doc.on("afterTransaction", undoManager.afterTransactionHandler);
35
+ undoManager._observers = observers;
36
+ };
37
+ if (viewRet?.destroy) {
38
+ viewRet.destroy();
39
+ }
40
+ }
41
+ };
42
+ };
43
+ const ySyncPluginOptions = {
44
+ ...options.ySyncOptions,
45
+ onFirstRender: options.onFirstRender
46
+ };
47
+ const ySyncPluginInstance = ySyncPlugin(fragment, ySyncPluginOptions);
48
+ if (options.enableContentCheck) {
49
+ fragment.doc?.on("beforeTransaction", () => {
50
+ try {
51
+ const jsonContent = yXmlFragmentToProsemirrorJSON(fragment);
52
+ if (jsonContent.content.length === 0) {
53
+ return;
54
+ }
55
+ schema.nodeFromJSON(jsonContent).check();
56
+ } catch (error) {
57
+ for (const editor of getConnectedEditors?.() ?? []) {
58
+ editor.emit("contentError", {
59
+ error,
60
+ editor,
61
+ disableCollaboration: () => {
62
+ fragment.doc?.destroy();
63
+ storage.isDisabled = true;
64
+ }
65
+ });
66
+ }
67
+ return false;
68
+ }
69
+ });
70
+ }
71
+ return [
72
+ ySyncPluginInstance,
73
+ yUndoPluginInstance,
74
+ options.enableContentCheck && new Plugin({
75
+ key: new PluginKey("filterInvalidContent"),
76
+ filterTransaction: () => {
77
+ if (storage.isDisabled !== false) {
78
+ fragment.doc?.destroy();
79
+ return true;
80
+ }
81
+ return true;
82
+ }
83
+ })
84
+ ].filter(Boolean);
85
+ }
@@ -1,7 +1,7 @@
1
1
  import type { Content, EditorOptions } from "@tiptap/core";
2
+ import { Editor } from "@tiptap/core";
2
3
  import type { Schema } from "@tiptap/pm/model";
3
4
  import type { EditorState, Plugin, Transaction } from "@tiptap/pm/state";
4
- import { Editor } from "@tiptap/vue-3";
5
5
  import type { DocId, DocumentApiInterface, EmbedId, OnSaveDocumentCallback, OnUpdateDocumentCallback } from "./types.js";
6
6
  /**
7
7
  * Configures the document api which tells the editor how to load/unload/save/cache documents, including embedded ones.
@@ -20,7 +20,17 @@ import type { DocId, DocumentApiInterface, EmbedId, OnSaveDocumentCallback, OnUp
20
20
  *
21
21
  * If there are extensions that use onCreate to set state or have plugins that need to change the state on init with appendTransaction, they will not work since there is no view to initialize the plugins. To get around this, plugins can specify a stateInit function that will be called with a transaction from the initial loaded state which it can then modify while having access to `this` and the extension options.
22
22
  *
23
- * The api creates a default instance of the editor to copy plugins from, this can be replaced by passing your own editor instance.
23
+ * ### Internals / How does it work?
24
+ *
25
+ * It can be a bit confusing how this works. It's quite different from how tiptap works.
26
+ *
27
+ * First, the api creates a **single** instance of the editor with the editor options **initially** provided (it can be replaced by passing your own editor instance).
28
+ *
29
+ * On load **per document**, the state of the doc is initialized. The plugins are copied from the single editor instance so that we can properly initialize the state. The plugin's `stateInit` is called, see above.
30
+ *
31
+ * When an editor **component** is mounted, useEditorContent will call the document api's `preEditorInit` to get a configuration for that component. This configuration can be edited in `preEditorInit` and is **per editor component**. It is disconnected from the **per document** configuration.
32
+ *
33
+ * There is no **per document** editor instance. This makes it tricky to work with extensions like Collaboration that expect this setup. They require some weird workarounds unfortunately.
24
34
  *
25
35
  * See {@link useTestDocumentApi} for an example of how to set things up.
26
36
  */
@@ -45,6 +55,7 @@ export declare class DocumentApi<T extends Record<string, any> = Record<string,
45
55
  preEditorInit: DocumentApiInterface["preEditorInit"];
46
56
  updateFilter?: DocumentApiInterface["updateFilter"];
47
57
  editor: Editor;
58
+ connectedEditors: Record<string, Editor[]>;
48
59
  constructor({ editorOptions, getTitle, getSuggestions, load, save, saveDebounce, cache, refCounter, postEditorInit, preEditorInit, updateFilter }: {
49
60
  /**
50
61
  * The editor options for the internal instance to use to keep track of the document state. It should include the same extensions as the root editor.
@@ -53,7 +64,7 @@ export declare class DocumentApi<T extends Record<string, any> = Record<string,
53
64
  getTitle?: (docId: string, blockId?: string) => string;
54
65
  getSuggestions: DocumentApiInterface["getSuggestions"];
55
66
  /** Load should create the editor state and return it. It can also optionally return extra data which will be passed to the refCounter's load function. */
56
- load: (docId: string, schema: Schema, plugins: Plugin[]) => Promise<{
67
+ load: (docId: string, schema: Schema, plugins: Plugin[], getConnectedEditors: () => Editor[]) => Promise<{
57
68
  state: EditorState;
58
69
  data?: T;
59
70
  }>;
@@ -86,4 +97,6 @@ export declare class DocumentApi<T extends Record<string, any> = Record<string,
86
97
  errorIfNotFound?: boolean;
87
98
  }): EditorState | undefined;
88
99
  getEmbeddedContent(embedId: EmbedId): Content | undefined;
100
+ connectEditor(docId: string, editor: Editor): void;
101
+ disconnectEditor(docId: string, editor: Editor): void;
89
102
  }
@@ -1,6 +1,6 @@
1
1
  import { debounce } from "@alanscodelog/utils/debounce";
2
2
  import { unreachable } from "@alanscodelog/utils/unreachable";
3
- import { Editor } from "@tiptap/vue-3";
3
+ import { Editor } from "@tiptap/core";
4
4
  import { isProxy } from "vue";
5
5
  import { convertTransactionForFullState } from "./utils/convertTransactionForFullState.js";
6
6
  import { getEmbedJson } from "./utils/getEmbedJson.js";
@@ -30,6 +30,7 @@ export class DocumentApi {
30
30
  };
31
31
  updateFilter = () => true;
32
32
  editor;
33
+ connectedEditors = {};
33
34
  constructor({
34
35
  editorOptions,
35
36
  getTitle,
@@ -132,7 +133,8 @@ export class DocumentApi {
132
133
  const loaded = await this._load(
133
134
  docId,
134
135
  schema,
135
- [...this.editor.extensionManager.plugins]
136
+ [...this.editor.extensionManager.plugins],
137
+ () => this.connectedEditors[docId] ?? []
136
138
  );
137
139
  let state = loaded.state;
138
140
  const tr = state.tr;
@@ -169,4 +171,19 @@ export class DocumentApi {
169
171
  return getEmbedJson(nodeWanted) ?? json;
170
172
  }
171
173
  }
174
+ connectEditor(docId, editor) {
175
+ if (!this.connectedEditors[docId]) {
176
+ this.connectedEditors[docId] = [];
177
+ }
178
+ this.connectedEditors[docId].push(editor);
179
+ }
180
+ disconnectEditor(docId, editor) {
181
+ if (!this.connectedEditors[docId]) {
182
+ return;
183
+ }
184
+ const index = this.connectedEditors[docId].indexOf(editor);
185
+ if (index !== -1) {
186
+ this.connectedEditors[docId].splice(index, 1);
187
+ }
188
+ }
172
189
  }
@@ -25,13 +25,18 @@ export function useEditorContent(editor, content, id, documentApi, recreate) {
25
25
  let attached = false;
26
26
  async function load(changed) {
27
27
  if (content.value) {
28
- editor.value?.commands.setContent(content.value);
28
+ if (!editor.value) {
29
+ recreate((options) => ({ ...options, content: content.value }));
30
+ } else {
31
+ editor.value?.commands.setContent(content.value);
32
+ }
29
33
  } else if (documentApi) {
30
34
  if (changed && id.value) {
31
35
  await documentApi.load({ docId: id.value });
32
36
  const state = documentApi.getFullState({ docId: id.value });
33
37
  recreate((options) => documentApi.preEditorInit?.(id.value, { ...options }, state));
34
38
  await nextTick(async () => {
39
+ documentApi.connectEditor(id.value, editor.value);
35
40
  editor.value.on("transaction", onTransaction);
36
41
  documentApi.addEventListener("update", onUpdateDocument);
37
42
  documentApi.postEditorInit(id.value, editor.value);
@@ -43,6 +48,7 @@ export function useEditorContent(editor, content, id, documentApi, recreate) {
43
48
  function unload(oldId) {
44
49
  if (oldId !== void 0 && documentApi) {
45
50
  documentApi.unload({ docId: oldId });
51
+ documentApi.disconnectEditor(id.value, editor.value);
46
52
  if (attached) {
47
53
  documentApi.removeEventListener("update", onUpdateDocument);
48
54
  editor.value?.off("transaction", onTransaction);
@@ -63,7 +69,7 @@ export function useEditorContent(editor, content, id, documentApi, recreate) {
63
69
  if (oldId !== void 0 && newId !== oldId && documentApi) {
64
70
  unload(oldId);
65
71
  await load(true);
66
- } else if (_newContent) {
72
+ } else if (_newContent !== _oldContent && _newContent) {
67
73
  await load(false);
68
74
  }
69
75
  }, { deep: false });
@@ -1,16 +1,19 @@
1
1
  import type { EditorOptions } from "@tiptap/core";
2
2
  import { EditorState } from "@tiptap/pm/state";
3
3
  import { type Ref } from "vue";
4
+ import type * as Y from "yjs";
4
5
  import type { DocumentApiInterface } from "../types.js";
5
6
  type Cache = Record<string, {
6
7
  state?: EditorState;
7
8
  count: number;
9
+ yDoc?: Y.Doc;
8
10
  }>;
9
11
  /** Creates a simple instance of the DocumentApi for testing purposes. */
10
12
  export declare function useTestDocumentApi(editorOptions: Partial<EditorOptions>, embeds: Record<string, {
11
13
  content: any;
12
14
  title?: string;
13
- }>, { loadDelay }?: {
15
+ }>, { useCollab, loadDelay }?: {
16
+ useCollab?: boolean;
14
17
  loadDelay?: number;
15
18
  }): {
16
19
  cache: Ref<Cache>;
@@ -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, { loadDelay = 0 } = {}) {
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
- load: async (docId, schema, plugins) => {
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
- /** Like {@link DocumentApi.preEditorInit}, but after initializing and loading the document (and before the transaction listeners are added). */
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 when managing the document state in some other way (e.g. collab.
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
- * Also useful for creating per-doc instances for certain extensions, such as Collaboration.
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
- * This is a bit tricky to do normally 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.
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
- * So this hook can be used to add these additional per-doc instances of extensions. Be sure to clone the properties you are modifying. They are only shallow cloned before being passed to the function.
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
- * collabExt
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 ignore the transaction.
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
- * So we have to ignore all of yjs's transactions, but NOT transactions from partially embedded docs => full state, as these do not pass through yjs.
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({
@@ -4,7 +4,6 @@
4
4
  ref="el"
5
5
  >
6
6
  <div class="flex flex-col gap-1">
7
- <!-- @vue-expect-error -->
8
7
  <WSimpleInput
9
8
  class="text-input"
10
9
  wrapper-class="flex flex-nowrap gap-1"
@@ -24,7 +23,6 @@
24
23
  </WLabel>
25
24
  </template>
26
25
  </WSimpleInput>
27
- <!-- @vue-expect-error -->
28
26
  <WSimpleInput
29
27
  class="link-input"
30
28
  wrapper-class="flex flex-nowrap gap-1"
@@ -15,7 +15,6 @@
15
15
  >
16
16
  <i-ic-round-link class="w-[1.25em] !h-auto"/>
17
17
  </WLabel>
18
- <!-- @vue-expect-error -->
19
18
  <WSimpleInput
20
19
  id="menu-link-input"
21
20
  class="link-input"
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.1.1",
4
+ "version": "0.2.1",
5
5
  "main": "./dist/runtime/main.lib.js",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -64,35 +64,37 @@
64
64
  "failOnWarn": false
65
65
  },
66
66
  "peerDependencies": {
67
- "@tiptap/core": "^3.11.1",
68
- "@tiptap/extension-blockquote": "^3.11.1",
69
- "@tiptap/extension-bold": "^3.11.1",
70
- "@tiptap/extension-code": "^3.11.1",
71
- "@tiptap/extension-code-block": "^3.11.1",
72
- "@tiptap/extension-code-block-lowlight": "^3.11.1",
73
- "@tiptap/extension-dropcursor": "^3.11.1",
74
- "@tiptap/extension-gapcursor": "^3.11.1",
75
- "@tiptap/extension-hard-break": "^3.11.1",
76
- "@tiptap/extension-heading": "^3.11.1",
77
- "@tiptap/extension-highlight": "^3.11.1",
78
- "@tiptap/extension-history": "^3.11.1",
79
- "@tiptap/extension-image": "^3.11.1",
80
- "@tiptap/extension-italic": "^3.11.1",
81
- "@tiptap/extension-link": "^3.11.1",
82
- "@tiptap/extension-paragraph": "^3.11.1",
83
- "@tiptap/extension-strike": "^3.11.1",
84
- "@tiptap/extension-subscript": "^3.11.1",
85
- "@tiptap/extension-superscript": "^3.11.1",
86
- "@tiptap/extension-table": "^3.11.1",
87
- "@tiptap/extension-table-cell": "^3.11.1",
88
- "@tiptap/extension-table-header": "^3.11.1",
89
- "@tiptap/extension-table-row": "^3.11.1",
90
- "@tiptap/extension-text": "^3.11.1",
91
- "@tiptap/extension-underline": "^3.11.1",
92
- "@tiptap/html": "^3.11.1",
93
- "@tiptap/pm": "^3.11.1",
94
- "@tiptap/vue-3": "^3.11.1",
95
- "@witchcraft/ui": "^0.3.19",
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",
97
+ "@witchcraft/ui": "^0.3.25",
96
98
  "tailwindcss": "^4.1.17",
97
99
  "vue": "^3.5.25"
98
100
  },
@@ -109,7 +111,7 @@
109
111
  },
110
112
  "dependencies": {
111
113
  "@alanscodelog/eslint-config": "^6.3.1",
112
- "@alanscodelog/utils": "^6.0.2",
114
+ "@alanscodelog/utils": "^6.2.0",
113
115
  "@commitlint/cli": "^20.1.0",
114
116
  "@faker-js/faker": "^10.0.0",
115
117
  "@fortawesome/fontawesome-svg-core": "^7.0.1",
@@ -117,9 +119,9 @@
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.11.1",
122
+ "@tiptap/html": "^3.12.0",
121
123
  "@witchcraft/nuxt-utils": "^0.3.6",
122
- "@witchcraft/ui": "^0.3.19",
124
+ "@witchcraft/ui": "^0.3.25",
123
125
  "colord": "^2.9.3",
124
126
  "defu": "^6.1.4",
125
127
  "highlight.js": "^11.11.1",
@@ -131,13 +133,12 @@
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": {
138
139
  "@alanscodelog/commitlint-config": "^3.1.2",
139
- "@alanscodelog/semantic-release-config": "^6.0.0",
140
- "@alanscodelog/tsconfigs": "^6.2.0",
140
+ "@alanscodelog/semantic-release-config": "^6.0.2",
141
+ "@alanscodelog/tsconfigs": "^6.3.0",
141
142
  "@alanscodelog/vite-config": "^0.0.7",
142
143
  "@iconify/json": "^2.2.412",
143
144
  "@nuxt/kit": "^4.2.1",
@@ -153,7 +154,7 @@
153
154
  "@vitejs/plugin-vue": "^6.0.2",
154
155
  "@vitest/browser-playwright": "^4.0.14",
155
156
  "@vitest/coverage-v8": "^4.0.14",
156
- "@witchcraft/ui": "^0.3.19",
157
+ "@witchcraft/ui": "^0.3.25",
157
158
  "concurrently": "^9.2.1",
158
159
  "cross-env": "^10.0.0",
159
160
  "eslint": "^9.39.1",
package/src/module.ts CHANGED
@@ -21,7 +21,7 @@ declare module "@nuxt/schema" {
21
21
 
22
22
  export interface ModuleOptions {
23
23
  /** @internal */
24
- _playgroundWorkaround?: boolean
24
+ _playgroundWorkaround?: string
25
25
  }
26
26
 
27
27
  export default defineNuxtModule<ModuleOptions>({
@@ -30,7 +30,7 @@ export default defineNuxtModule<ModuleOptions>({
30
30
  configKey: "witchcraftEditor"
31
31
  },
32
32
  defaults: {
33
- _playgroundWorkaround: false
33
+ _playgroundWorkaround: undefined
34
34
  },
35
35
  moduleDependencies: {
36
36
  "@witchcraft/ui/nuxt": {
@@ -60,7 +60,7 @@ export default defineNuxtModule<ModuleOptions>({
60
60
  addTemplate({
61
61
  filename: "witchcraft-editor.css",
62
62
  write: true,
63
- getContents: () => options._playgroundWorkaround
63
+ getContents: () => options._playgroundWorkaround === "@witchcraft/editor"
64
64
  ? crop`
65
65
  @import "${resolve("./runtime/assets/base.css")}";
66
66
  @import "${resolve("./runtime/assets/utils.css")}";