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.
OptionalgetTitle?: (docId: string, blockId?: string) => stringLoad 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.
OptionalpostEditorInit?: (docId: string, editor: Editor) => voidOptionalpreEditorInit?: (Optionalsave?: (docId: string) => Promise<void>OptionalsaveDebounce?: numberOptionalupdateFilter?: (tr: Transaction) => boolean | undefinedHow to format the title of the embedded document. Defaults to docId#blockId
For the embedded document picker, should return suggestions for the search string.
Like DocumentApi.preEditorInit, but after initializing and loading the document (and before the transaction listeners are added).
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.
Also useful for creating per-doc instances for certain extensions, such as Collaboration.
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.
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.
preEditorInit(docId, options: Partial<EditorOptions>, state: EditorState) {
// we do not need to set options.content when using collab
// so no options.content = state.doc.toJSON()
const ydoc = cache.value[docId].ydoc
// it's suggested you add the collab extension only here
// otherwise you would have to initially configure it with a dummy document
const collabExt = Collaboration.configure({
document: ydoc
}) as any
options.extensions = [
...(options.extensions ?? []),
collabExt
]
return options
},
load: async (
docId: string,
schema: Schema,
plugins: Plugin[],
) => {
if (cache.value[docId]?.state) {
return { state: toRaw(cache.value[docId].state) }
}
const doc = getFromYourDb(docId)
const decoded = toUint8Array(doc.contentBinary)
const yDoc = new Y.Doc()
Y.applyUpdate(yDoc, decoded)
const yjs = initProseMirrorDoc(yDoc.getXmlFragment("prosemirror"), schema)
const state = EditorState.create({
doc: yjs.doc,
schema,
plugins:[
...plugins,
// the document api's yjs instance
ySyncPlugin(yDoc.getXmlFragment("prosemirror"), {mapping:yjs.mapping}),
]
})
// return the state and any additional data we want refCounter.load to be called with.
return { state, doc, yDoc }
},
updateFilter(tr:Transaction) {
const meta = tr.getMeta(ySyncPluginKey)
if (meta) return false
return true
},
See DocumentApi.updateFilter for why yjs (and other syncronization mechanisms) might need to ignore transactions.
ReadonlysaveDebounced save (to storage) function. Use the event listeners to get notified when saving finishes.
OptionalupdateReturn false to ignore the transaction.
This is useful when using a secondary syncronization mechanism, such as yjs.
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.
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.
Load should be called the first time, before attempting to load the state.
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.
load: async ( docId: string, schema: Schema, plugins: Plugin[],) => {
const dbDoc = getFromYourDb(docId)
const state = EditorState.create({
doc: yjs.doc,
schema,
plugins
})
// return the state and any additional data we want to cache
return { state, data: { dbDoc } }
},
See DocumentApi.preEditorInit for how to set this up with sync (e.g. yjs).
Notifies the document api that an editor has unloaded the document.
OptionalupdaterSymbol: symbol
Configures the document api which tells the editor how to load/unload/save/cache documents, including embedded ones.
The cache implementation is left up to the user, hence why it's defined as a get/set interface. A load function must be provided for requesting uncached documents.
saveis optional, but you probably want to save. The function is automatically debounced the configured amount.**Any function that needs to return a document should return it unwrapped with toRaw if it was a ref or in a ref or reactive. **
A
refCounterobject can be provided with the respective load/unload functions to be notified when embedded views load/unload documents to reference count them and unload them once no editors have them in use.It is safe to call load multiple times for the same document, the actual load function will only be called once per document, but the refCounter will be called multiple times.
The
getTitlefunction can be provided to customize the title of the embedded document. It's passed the full embed id and returns it as docId#blockId by default.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
thisand the extension options.The api creates a default instance of the editor to copy plugins from, this can be replaced by passing your own editor instance.
See useTestDocumentApi for an example of how to set things up.