@milkdown/core
Version:
The core module of [milkdown](https://milkdown.dev/).
697 lines (696 loc) • 23.9 kB
JavaScript
var __typeError = (msg) => {
throw TypeError(msg);
};
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
var _container, _ctx, _enableInspector, _status, _configureList, _onStatusChange, _container2, _clock, _usrPluginStore, _sysPluginStore, _ctx2, _loadInternal, _prepare, _cleanup, _cleanupInternal, _setStatus, _loadPluginInStore;
import { createSlice, createTimer, Container, Clock, Ctx } from "@milkdown/ctx";
import { Schema, DOMParser, Node } from "@milkdown/prose/model";
import remarkParse from "remark-parse";
import remarkStringify from "remark-stringify";
import { unified } from "unified";
import { callCommandBeforeEditorView, ctxCallOutOfScope, docTypeError } from "@milkdown/exception";
import { ParserState, SerializerState } from "@milkdown/transformer";
import { customInputRules } from "@milkdown/prose";
import { chainCommands, deleteSelection, joinBackward, selectNodeBackward, baseKeymap } from "@milkdown/prose/commands";
import { undoInputRule } from "@milkdown/prose/inputrules";
import { keymap } from "@milkdown/prose/keymap";
import { Plugin, PluginKey, EditorState } from "@milkdown/prose/state";
import { EditorView } from "@milkdown/prose/view";
function withMeta(plugin, meta) {
plugin.meta = {
package: "@milkdown/core",
group: "System",
...meta
};
return plugin;
}
const remarkHandlers = {
strong: (node, _, state, info) => {
const marker = node.marker || state.options.strong || "*";
const exit = state.enter("strong");
const tracker = state.createTracker(info);
let value = tracker.move(marker + marker);
value += tracker.move(
state.containerPhrasing(node, {
before: value,
after: marker,
...tracker.current()
})
);
value += tracker.move(marker + marker);
exit();
return value;
},
emphasis: (node, _, state, info) => {
const marker = node.marker || state.options.emphasis || "*";
const exit = state.enter("emphasis");
const tracker = state.createTracker(info);
let value = tracker.move(marker);
value += tracker.move(
state.containerPhrasing(node, {
before: value,
after: marker,
...tracker.current()
})
);
value += tracker.move(marker);
exit();
return value;
}
};
const editorViewCtx = createSlice({}, "editorView");
const editorStateCtx = createSlice({}, "editorState");
const initTimerCtx = createSlice([], "initTimer");
const editorCtx = createSlice({}, "editor");
const inputRulesCtx = createSlice([], "inputRules");
const prosePluginsCtx = createSlice([], "prosePlugins");
const remarkPluginsCtx = createSlice(
[],
"remarkPlugins"
);
const nodeViewCtx = createSlice([], "nodeView");
const markViewCtx = createSlice([], "markView");
const remarkCtx = createSlice(
unified().use(remarkParse).use(remarkStringify),
"remark"
);
const remarkStringifyOptionsCtx = createSlice(
{
handlers: remarkHandlers
},
"remarkStringifyOptions"
);
const ConfigReady = createTimer("ConfigReady");
function config(configure) {
const plugin = (ctx) => {
ctx.record(ConfigReady);
return async () => {
await configure(ctx);
ctx.done(ConfigReady);
return () => {
ctx.clearTimer(ConfigReady);
};
};
};
withMeta(plugin, {
displayName: "Config"
});
return plugin;
}
const InitReady = createTimer("InitReady");
function init(editor) {
const plugin = (ctx) => {
ctx.inject(editorCtx, editor).inject(prosePluginsCtx, []).inject(remarkPluginsCtx, []).inject(inputRulesCtx, []).inject(nodeViewCtx, []).inject(markViewCtx, []).inject(remarkStringifyOptionsCtx, {
handlers: remarkHandlers
}).inject(remarkCtx, unified().use(remarkParse).use(remarkStringify)).inject(initTimerCtx, [ConfigReady]).record(InitReady);
return async () => {
await ctx.waitTimers(initTimerCtx);
const options = ctx.get(remarkStringifyOptionsCtx);
ctx.set(
remarkCtx,
unified().use(remarkParse).use(remarkStringify, options)
);
ctx.done(InitReady);
return () => {
ctx.remove(editorCtx).remove(prosePluginsCtx).remove(remarkPluginsCtx).remove(inputRulesCtx).remove(nodeViewCtx).remove(markViewCtx).remove(remarkStringifyOptionsCtx).remove(remarkCtx).remove(initTimerCtx).clearTimer(InitReady);
};
};
};
withMeta(plugin, {
displayName: "Init"
});
return plugin;
}
const SchemaReady = createTimer("SchemaReady");
const schemaTimerCtx = createSlice([], "schemaTimer");
const schemaCtx = createSlice({}, "schema");
const nodesCtx = createSlice([], "nodes");
const marksCtx = createSlice([], "marks");
function extendPriority(x) {
var _a;
return {
...x,
parseDOM: (_a = x.parseDOM) == null ? void 0 : _a.map((rule) => ({ priority: x.priority, ...rule }))
};
}
const schema = (ctx) => {
ctx.inject(schemaCtx, {}).inject(nodesCtx, []).inject(marksCtx, []).inject(schemaTimerCtx, [InitReady]).record(SchemaReady);
return async () => {
await ctx.waitTimers(schemaTimerCtx);
const remark = ctx.get(remarkCtx);
const remarkPlugins = ctx.get(remarkPluginsCtx);
const processor = remarkPlugins.reduce(
(acc, plug) => acc.use(plug.plugin, plug.options),
remark
);
ctx.set(remarkCtx, processor);
const nodes = Object.fromEntries(
ctx.get(nodesCtx).map(([key2, x]) => [key2, extendPriority(x)])
);
const marks = Object.fromEntries(
ctx.get(marksCtx).map(([key2, x]) => [key2, extendPriority(x)])
);
const schema2 = new Schema({ nodes, marks });
ctx.set(schemaCtx, schema2);
ctx.done(SchemaReady);
return () => {
ctx.remove(schemaCtx).remove(nodesCtx).remove(marksCtx).remove(schemaTimerCtx).clearTimer(SchemaReady);
};
};
};
withMeta(schema, {
displayName: "Schema"
});
class CommandManager {
constructor() {
__privateAdd(this, _container);
__privateAdd(this, _ctx);
__privateSet(this, _container, new Container());
__privateSet(this, _ctx, null);
this.setCtx = (ctx) => {
__privateSet(this, _ctx, ctx);
};
}
get ctx() {
return __privateGet(this, _ctx);
}
/// Register a command into the manager.
create(meta, value) {
const slice = meta.create(__privateGet(this, _container).sliceMap);
slice.set(value);
return slice;
}
get(slice) {
return __privateGet(this, _container).get(slice).get();
}
remove(slice) {
return __privateGet(this, _container).remove(slice);
}
call(slice, payload) {
if (__privateGet(this, _ctx) == null) throw callCommandBeforeEditorView();
const cmd = this.get(slice);
const command = cmd(payload);
const view = __privateGet(this, _ctx).get(editorViewCtx);
return command(view.state, view.dispatch, view);
}
}
_container = new WeakMap();
_ctx = new WeakMap();
function createCmdKey(key2 = "cmdKey") {
return createSlice(() => () => false, key2);
}
const commandsCtx = createSlice(new CommandManager(), "commands");
const commandsTimerCtx = createSlice([SchemaReady], "commandsTimer");
const CommandsReady = createTimer("CommandsReady");
const commands = (ctx) => {
const cmd = new CommandManager();
cmd.setCtx(ctx);
ctx.inject(commandsCtx, cmd).inject(commandsTimerCtx, [SchemaReady]).record(CommandsReady);
return async () => {
await ctx.waitTimers(commandsTimerCtx);
ctx.done(CommandsReady);
return () => {
ctx.remove(commandsCtx).remove(commandsTimerCtx).clearTimer(CommandsReady);
};
};
};
withMeta(commands, {
displayName: "Commands"
});
const ParserReady = createTimer("ParserReady");
const outOfScope$1 = () => {
throw ctxCallOutOfScope();
};
const parserCtx = createSlice(outOfScope$1, "parser");
const parserTimerCtx = createSlice([], "parserTimer");
const parser = (ctx) => {
ctx.inject(parserCtx, outOfScope$1).inject(parserTimerCtx, [SchemaReady]).record(ParserReady);
return async () => {
await ctx.waitTimers(parserTimerCtx);
const remark = ctx.get(remarkCtx);
const schema2 = ctx.get(schemaCtx);
ctx.set(parserCtx, ParserState.create(schema2, remark));
ctx.done(ParserReady);
return () => {
ctx.remove(parserCtx).remove(parserTimerCtx).clearTimer(ParserReady);
};
};
};
withMeta(parser, {
displayName: "Parser"
});
const SerializerReady = createTimer("SerializerReady");
const serializerTimerCtx = createSlice(
[],
"serializerTimer"
);
const outOfScope = () => {
throw ctxCallOutOfScope();
};
const serializerCtx = createSlice(
outOfScope,
"serializer"
);
const serializer = (ctx) => {
ctx.inject(serializerCtx, outOfScope).inject(serializerTimerCtx, [SchemaReady]).record(SerializerReady);
return async () => {
await ctx.waitTimers(serializerTimerCtx);
const remark = ctx.get(remarkCtx);
const schema2 = ctx.get(schemaCtx);
ctx.set(serializerCtx, SerializerState.create(schema2, remark));
ctx.done(SerializerReady);
return () => {
ctx.remove(serializerCtx).remove(serializerTimerCtx).clearTimer(SerializerReady);
};
};
};
withMeta(serializer, {
displayName: "Serializer"
});
const defaultValueCtx = createSlice("", "defaultValue");
const editorStateOptionsCtx = createSlice(
(x) => x,
"stateOptions"
);
const editorStateTimerCtx = createSlice(
[],
"editorStateTimer"
);
const EditorStateReady = createTimer("EditorStateReady");
function getDoc(defaultValue, parser2, schema2) {
if (typeof defaultValue === "string") return parser2(defaultValue);
if (defaultValue.type === "html")
return DOMParser.fromSchema(schema2).parse(defaultValue.dom);
if (defaultValue.type === "json")
return Node.fromJSON(schema2, defaultValue.value);
throw docTypeError(defaultValue);
}
const key$1 = new PluginKey("MILKDOWN_STATE_TRACKER");
function overrideBaseKeymap(keymap2) {
const handleBackspace = chainCommands(
undoInputRule,
deleteSelection,
joinBackward,
selectNodeBackward
);
keymap2.Backspace = handleBackspace;
return keymap2;
}
const editorState = (ctx) => {
ctx.inject(defaultValueCtx, "").inject(editorStateCtx, {}).inject(editorStateOptionsCtx, (x) => x).inject(editorStateTimerCtx, [ParserReady, SerializerReady, CommandsReady]).record(EditorStateReady);
return async () => {
await ctx.waitTimers(editorStateTimerCtx);
const schema2 = ctx.get(schemaCtx);
const parser2 = ctx.get(parserCtx);
const rules = ctx.get(inputRulesCtx);
const optionsOverride = ctx.get(editorStateOptionsCtx);
const prosePlugins = ctx.get(prosePluginsCtx);
const defaultValue = ctx.get(defaultValueCtx);
const doc = getDoc(defaultValue, parser2, schema2);
const plugins = [
...prosePlugins,
new Plugin({
key: key$1,
state: {
init: () => {
},
apply: (_tr, _value, _oldState, newState) => {
ctx.set(editorStateCtx, newState);
}
}
}),
customInputRules({ rules }),
keymap(overrideBaseKeymap(baseKeymap))
];
ctx.set(prosePluginsCtx, plugins);
const options = optionsOverride({
schema: schema2,
doc,
plugins
});
const state = EditorState.create(options);
ctx.set(editorStateCtx, state);
ctx.done(EditorStateReady);
return () => {
ctx.remove(defaultValueCtx).remove(editorStateCtx).remove(editorStateOptionsCtx).remove(editorStateTimerCtx).clearTimer(EditorStateReady);
};
};
};
withMeta(editorState, {
displayName: "EditorState"
});
const EditorViewReady = createTimer("EditorViewReady");
const editorViewTimerCtx = createSlice(
[],
"editorViewTimer"
);
const editorViewOptionsCtx = createSlice(
{},
"editorViewOptions"
);
const rootCtx = createSlice(null, "root");
const rootDOMCtx = createSlice(null, "rootDOM");
const rootAttrsCtx = createSlice(
{},
"rootAttrs"
);
function createViewContainer(root, ctx) {
const container = document.createElement("div");
container.className = "milkdown";
root.appendChild(container);
ctx.set(rootDOMCtx, container);
const attrs = ctx.get(rootAttrsCtx);
Object.entries(attrs).forEach(
([key2, value]) => container.setAttribute(key2, value)
);
return container;
}
function prepareViewDom(dom) {
dom.classList.add("editor");
dom.setAttribute("role", "textbox");
}
const key = new PluginKey("MILKDOWN_VIEW_CLEAR");
const editorView = (ctx) => {
ctx.inject(rootCtx, document.body).inject(editorViewCtx, {}).inject(editorViewOptionsCtx, {}).inject(rootDOMCtx, null).inject(rootAttrsCtx, {}).inject(editorViewTimerCtx, [EditorStateReady]).record(EditorViewReady);
return async () => {
await ctx.wait(InitReady);
const root = ctx.get(rootCtx) || document.body;
const el = typeof root === "string" ? document.querySelector(root) : root;
ctx.update(prosePluginsCtx, (xs) => [
new Plugin({
key,
view: (editorView2) => {
const container = el ? createViewContainer(el, ctx) : void 0;
const handleDOM = () => {
if (container && el) {
const editor = editorView2.dom;
el.replaceChild(container, editor);
container.appendChild(editor);
}
};
handleDOM();
return {
destroy: () => {
if (container == null ? void 0 : container.parentNode)
container == null ? void 0 : container.parentNode.replaceChild(editorView2.dom, container);
container == null ? void 0 : container.remove();
}
};
}
}),
...xs
]);
await ctx.waitTimers(editorViewTimerCtx);
const state = ctx.get(editorStateCtx);
const options = ctx.get(editorViewOptionsCtx);
const nodeViews = Object.fromEntries(ctx.get(nodeViewCtx));
const markViews = Object.fromEntries(ctx.get(markViewCtx));
const view = new EditorView(el, {
state,
nodeViews,
markViews,
...options
});
prepareViewDom(view.dom);
ctx.set(editorViewCtx, view);
ctx.done(EditorViewReady);
return () => {
view == null ? void 0 : view.destroy();
ctx.remove(rootCtx).remove(editorViewCtx).remove(editorViewOptionsCtx).remove(rootDOMCtx).remove(rootAttrsCtx).remove(editorViewTimerCtx).clearTimer(EditorViewReady);
};
};
};
withMeta(editorView, {
displayName: "EditorView"
});
var EditorStatus = /* @__PURE__ */ ((EditorStatus2) => {
EditorStatus2["Idle"] = "Idle";
EditorStatus2["OnCreate"] = "OnCreate";
EditorStatus2["Created"] = "Created";
EditorStatus2["OnDestroy"] = "OnDestroy";
EditorStatus2["Destroyed"] = "Destroyed";
return EditorStatus2;
})(EditorStatus || {});
const _Editor = class _Editor {
constructor() {
__privateAdd(this, _enableInspector);
__privateAdd(this, _status);
__privateAdd(this, _configureList);
__privateAdd(this, _onStatusChange);
__privateAdd(this, _container2);
__privateAdd(this, _clock);
__privateAdd(this, _usrPluginStore);
__privateAdd(this, _sysPluginStore);
__privateAdd(this, _ctx2);
__privateAdd(this, _loadInternal);
__privateAdd(this, _prepare);
__privateAdd(this, _cleanup);
__privateAdd(this, _cleanupInternal);
__privateAdd(this, _setStatus);
__privateAdd(this, _loadPluginInStore);
__privateSet(this, _enableInspector, false);
__privateSet(this, _status, "Idle");
__privateSet(this, _configureList, []);
__privateSet(this, _onStatusChange, () => void 0);
__privateSet(this, _container2, new Container());
__privateSet(this, _clock, new Clock());
__privateSet(this, _usrPluginStore, /* @__PURE__ */ new Map());
__privateSet(this, _sysPluginStore, /* @__PURE__ */ new Map());
__privateSet(this, _ctx2, new Ctx(__privateGet(this, _container2), __privateGet(this, _clock)));
__privateSet(this, _loadInternal, () => {
const configPlugin = config(async (ctx) => {
await Promise.all(__privateGet(this, _configureList).map((fn) => fn(ctx)));
});
const internalPlugins = [
schema,
parser,
serializer,
commands,
editorState,
editorView,
init(this),
configPlugin
];
__privateGet(this, _prepare).call(this, internalPlugins, __privateGet(this, _sysPluginStore));
});
__privateSet(this, _prepare, (plugins, store) => {
plugins.forEach((plugin) => {
const ctx = __privateGet(this, _ctx2).produce(
__privateGet(this, _enableInspector) ? plugin.meta : void 0
);
const handler = plugin(ctx);
store.set(plugin, { ctx, handler, cleanup: void 0 });
});
});
__privateSet(this, _cleanup, (plugins, remove = false) => {
return Promise.all(
[plugins].flat().map((plugin) => {
const loader = __privateGet(this, _usrPluginStore).get(plugin);
const cleanup = loader == null ? void 0 : loader.cleanup;
if (remove) __privateGet(this, _usrPluginStore).delete(plugin);
else
__privateGet(this, _usrPluginStore).set(plugin, {
ctx: void 0,
handler: void 0,
cleanup: void 0
});
if (typeof cleanup === "function") return cleanup();
return cleanup;
})
);
});
__privateSet(this, _cleanupInternal, async () => {
await Promise.all(
[...__privateGet(this, _sysPluginStore).entries()].map(([_, { cleanup }]) => {
if (typeof cleanup === "function") return cleanup();
return cleanup;
})
);
__privateGet(this, _sysPluginStore).clear();
});
__privateSet(this, _setStatus, (status) => {
__privateSet(this, _status, status);
__privateGet(this, _onStatusChange).call(this, status);
});
__privateSet(this, _loadPluginInStore, (store) => {
return [...store.entries()].map(async ([key2, loader]) => {
const { ctx, handler } = loader;
if (!handler) return;
const cleanup = await handler();
store.set(key2, { ctx, handler, cleanup });
});
});
this.enableInspector = (enable = true) => {
__privateSet(this, _enableInspector, enable);
return this;
};
this.onStatusChange = (onChange) => {
__privateSet(this, _onStatusChange, onChange);
return this;
};
this.config = (configure) => {
__privateGet(this, _configureList).push(configure);
return this;
};
this.removeConfig = (configure) => {
__privateSet(this, _configureList, __privateGet(this, _configureList).filter((x) => x !== configure));
return this;
};
this.use = (plugins) => {
const _plugins = [plugins].flat();
_plugins.flat().forEach((plugin) => {
__privateGet(this, _usrPluginStore).set(plugin, {
ctx: void 0,
handler: void 0,
cleanup: void 0
});
});
if (__privateGet(this, _status) === "Created")
__privateGet(this, _prepare).call(this, _plugins, __privateGet(this, _usrPluginStore));
return this;
};
this.remove = async (plugins) => {
if (__privateGet(this, _status) === "OnCreate") {
console.warn(
"[Milkdown]: You are trying to remove plugins when the editor is creating, this is not recommended, please check your code."
);
return new Promise((resolve) => {
setTimeout(() => {
resolve(this.remove(plugins));
}, 50);
});
}
await __privateGet(this, _cleanup).call(this, [plugins].flat(), true);
return this;
};
this.create = async () => {
if (__privateGet(this, _status) === "OnCreate") return this;
if (__privateGet(this, _status) === "Created") await this.destroy();
__privateGet(this, _setStatus).call(this, "OnCreate");
__privateGet(this, _loadInternal).call(this);
__privateGet(this, _prepare).call(this, [...__privateGet(this, _usrPluginStore).keys()], __privateGet(this, _usrPluginStore));
await Promise.all(
[
__privateGet(this, _loadPluginInStore).call(this, __privateGet(this, _sysPluginStore)),
__privateGet(this, _loadPluginInStore).call(this, __privateGet(this, _usrPluginStore))
].flat()
);
__privateGet(this, _setStatus).call(this, "Created");
return this;
};
this.destroy = async (clearPlugins = false) => {
if (__privateGet(this, _status) === "Destroyed" || __privateGet(this, _status) === "OnDestroy")
return this;
if (__privateGet(this, _status) === "OnCreate") {
return new Promise((resolve) => {
setTimeout(() => {
resolve(this.destroy(clearPlugins));
}, 50);
});
}
if (clearPlugins) __privateSet(this, _configureList, []);
__privateGet(this, _setStatus).call(this, "OnDestroy");
await __privateGet(this, _cleanup).call(this, [...__privateGet(this, _usrPluginStore).keys()], clearPlugins);
await __privateGet(this, _cleanupInternal).call(this);
__privateGet(this, _setStatus).call(this, "Destroyed");
return this;
};
this.action = (action) => action(__privateGet(this, _ctx2));
this.inspect = () => {
if (!__privateGet(this, _enableInspector)) {
console.warn(
"[Milkdown]: You are trying to collect inspection when inspector is disabled, please enable inspector by `editor.enableInspector()` first."
);
return [];
}
return [...__privateGet(this, _sysPluginStore).values(), ...__privateGet(this, _usrPluginStore).values()].map(({ ctx }) => {
var _a;
return (_a = ctx == null ? void 0 : ctx.inspector) == null ? void 0 : _a.read();
}).filter((x) => Boolean(x));
};
}
/// Create a new editor instance.
static make() {
return new _Editor();
}
/// Get the ctx of the editor.
get ctx() {
return __privateGet(this, _ctx2);
}
/// Get the status of the editor.
get status() {
return __privateGet(this, _status);
}
};
_enableInspector = new WeakMap();
_status = new WeakMap();
_configureList = new WeakMap();
_onStatusChange = new WeakMap();
_container2 = new WeakMap();
_clock = new WeakMap();
_usrPluginStore = new WeakMap();
_sysPluginStore = new WeakMap();
_ctx2 = new WeakMap();
_loadInternal = new WeakMap();
_prepare = new WeakMap();
_cleanup = new WeakMap();
_cleanupInternal = new WeakMap();
_setStatus = new WeakMap();
_loadPluginInStore = new WeakMap();
let Editor = _Editor;
export {
CommandManager,
CommandsReady,
ConfigReady,
Editor,
EditorStateReady,
EditorStatus,
EditorViewReady,
InitReady,
ParserReady,
SchemaReady,
SerializerReady,
commands,
commandsCtx,
commandsTimerCtx,
config,
createCmdKey,
defaultValueCtx,
editorCtx,
editorState,
editorStateCtx,
editorStateOptionsCtx,
editorStateTimerCtx,
editorView,
editorViewCtx,
editorViewOptionsCtx,
editorViewTimerCtx,
getDoc,
init,
initTimerCtx,
inputRulesCtx,
markViewCtx,
marksCtx,
nodeViewCtx,
nodesCtx,
parser,
parserCtx,
parserTimerCtx,
prosePluginsCtx,
remarkCtx,
remarkPluginsCtx,
remarkStringifyOptionsCtx,
rootAttrsCtx,
rootCtx,
rootDOMCtx,
schema,
schemaCtx,
schemaTimerCtx,
serializer,
serializerCtx,
serializerTimerCtx
};
//# sourceMappingURL=index.js.map