@milkdown/plugin-listener
Version:
The listener plugin of [milkdown](https://milkdown.dev/).
161 lines (160 loc) • 5.07 kB
JavaScript
import { InitReady, SerializerReady, serializerCtx, prosePluginsCtx, EditorViewReady } from "@milkdown/core";
import { createSlice } from "@milkdown/ctx";
import { PluginKey, Plugin } from "@milkdown/prose/state";
import { debounce } from "lodash-es";
class ListenerManager {
constructor() {
this.beforeMountedListeners = [];
this.mountedListeners = [];
this.updatedListeners = [];
this.selectionUpdatedListeners = [];
this.markdownUpdatedListeners = [];
this.blurListeners = [];
this.focusListeners = [];
this.destroyListeners = [];
this.beforeMount = (fn) => {
this.beforeMountedListeners.push(fn);
return this;
};
this.mounted = (fn) => {
this.mountedListeners.push(fn);
return this;
};
this.updated = (fn) => {
this.updatedListeners.push(fn);
return this;
};
}
/// A getter to get all [subscribers](#interface-subscribers). You should not use this method directly.
get listeners() {
return {
beforeMount: this.beforeMountedListeners,
mounted: this.mountedListeners,
updated: this.updatedListeners,
markdownUpdated: this.markdownUpdatedListeners,
blur: this.blurListeners,
focus: this.focusListeners,
destroy: this.destroyListeners,
selectionUpdated: this.selectionUpdatedListeners
};
}
/// Subscribe to the markdownUpdated event.
/// This event will be triggered after the editor state is updated and **the document is changed**.
/// The second parameter is the current markdown and the third parameter is the previous markdown.
markdownUpdated(fn) {
this.markdownUpdatedListeners.push(fn);
return this;
}
/// Subscribe to the blur event.
/// This event will be triggered when the editor is blurred.
blur(fn) {
this.blurListeners.push(fn);
return this;
}
/// Subscribe to the focus event.
/// This event will be triggered when the editor is focused.
focus(fn) {
this.focusListeners.push(fn);
return this;
}
/// Subscribe to the destroy event.
/// This event will be triggered before the editor is destroyed.
destroy(fn) {
this.destroyListeners.push(fn);
return this;
}
/// Subscribe to the selectionUpdated event.
/// This event will be triggered when the editor selection is updated.
selectionUpdated(fn) {
this.selectionUpdatedListeners.push(fn);
return this;
}
}
const listenerCtx = createSlice(
new ListenerManager(),
"listener"
);
const key = new PluginKey("MILKDOWN_LISTENER");
const listener = (ctx) => {
ctx.inject(listenerCtx, new ListenerManager());
return async () => {
await ctx.wait(InitReady);
const listener2 = ctx.get(listenerCtx);
const { listeners } = listener2;
listeners.beforeMount.forEach((fn) => fn(ctx));
await ctx.wait(SerializerReady);
const serializer = ctx.get(serializerCtx);
let prevDoc = null;
let prevMarkdown = null;
let prevSelection = null;
const plugin = new Plugin({
key,
view: () => {
return {
destroy: () => {
listeners.destroy.forEach((fn) => fn(ctx));
}
};
},
props: {
handleDOMEvents: {
focus: () => {
listeners.focus.forEach((fn) => fn(ctx));
return false;
},
blur: () => {
listeners.blur.forEach((fn) => fn(ctx));
return false;
}
}
},
state: {
init: (_, instance) => {
prevDoc = instance.doc;
prevMarkdown = serializer(instance.doc);
},
apply: (tr) => {
const currentSelection = tr.selection;
if (!prevSelection && currentSelection || prevSelection && !currentSelection.eq(prevSelection)) {
listeners.selectionUpdated.forEach((fn) => {
fn(ctx, currentSelection, prevSelection);
});
prevSelection = currentSelection;
}
if (!tr.docChanged || tr.getMeta("addToHistory") === false) return;
const handler = debounce(() => {
const { doc } = tr;
if (listeners.updated.length > 0 && prevDoc && !prevDoc.eq(doc)) {
listeners.updated.forEach((fn) => {
fn(ctx, doc, prevDoc);
});
}
if (listeners.markdownUpdated.length > 0 && prevDoc && !prevDoc.eq(doc)) {
const markdown = serializer(doc);
listeners.markdownUpdated.forEach((fn) => {
fn(ctx, markdown, prevMarkdown);
});
prevMarkdown = markdown;
}
prevDoc = doc;
}, 200);
return handler();
}
}
});
ctx.update(prosePluginsCtx, (x) => x.concat(plugin));
await ctx.wait(EditorViewReady);
listeners.mounted.forEach((fn) => fn(ctx));
};
};
listener.meta = {
package: "@milkdown/plugin-listener",
displayName: "Listener"
};
export {
ListenerManager,
key,
listener,
listenerCtx
};
//# sourceMappingURL=index.js.map