一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
127 行
3.0 KiB
TypeScript
127 行
3.0 KiB
TypeScript
import { Command, EditorState, Plugin } from 'prosemirror-state';
|
|
import { EditorView } from 'prosemirror-view';
|
|
import { Node } from 'prosemirror-model';
|
|
|
|
// See the fold example for more information about
|
|
// writing similar ProseMirror plugins:
|
|
// https://prosemirror.net/examples/fold/
|
|
|
|
type EditRequest = {
|
|
nodeStart: number;
|
|
showEditor: true;
|
|
} | {
|
|
nodeStart?: undefined;
|
|
showEditor: false;
|
|
};
|
|
|
|
interface PluginState {
|
|
editingNodeAt: number|null;
|
|
}
|
|
|
|
interface EditorDialog {
|
|
onPositionChange: (position: number)=> void;
|
|
dismiss: ()=> void;
|
|
}
|
|
|
|
export type OnHide = ()=> void;
|
|
interface Options {
|
|
canEdit: (node: Node, pos: number)=> boolean;
|
|
showEditor: (pos: number, view: EditorView, onHide: OnHide)=> EditorDialog;
|
|
}
|
|
|
|
const createExternalEditorPlugin = (options: Options) => {
|
|
const plugin = new Plugin<PluginState>({
|
|
state: {
|
|
init: () => ({
|
|
editingNodeAt: null,
|
|
}),
|
|
apply: (tr, oldValue) => {
|
|
let editingAt = oldValue.editingNodeAt;
|
|
|
|
const editRequest: EditRequest|null = tr.getMeta(plugin);
|
|
if (editRequest) {
|
|
if (editRequest.showEditor) {
|
|
editingAt = editRequest.nodeStart;
|
|
} else {
|
|
editingAt = null;
|
|
}
|
|
}
|
|
|
|
if (editingAt) {
|
|
editingAt = tr.mapping.map(editingAt, 1);
|
|
}
|
|
return { editingNodeAt: editingAt };
|
|
},
|
|
},
|
|
view: () => {
|
|
let dialog: EditorDialog|null = null;
|
|
|
|
return {
|
|
update(view, prevState) {
|
|
const oldState = plugin.getState(prevState);
|
|
const newState = plugin.getState(view.state);
|
|
|
|
if (newState.editingNodeAt !== null) {
|
|
if (oldState.editingNodeAt === null) {
|
|
const onHide = () => {
|
|
hideEditor(view.state, view.dispatch, view);
|
|
};
|
|
dialog = options.showEditor(newState.editingNodeAt, view, onHide);
|
|
}
|
|
dialog?.onPositionChange(newState.editingNodeAt);
|
|
} else if (dialog) {
|
|
const lastDialog = dialog;
|
|
// Set dialog to null before dismissing to prevent infinite recursion.
|
|
// Dismissing the dialog can cause the editor state to update, which can
|
|
// result in this callback being re-run.
|
|
dialog = null;
|
|
|
|
lastDialog.dismiss();
|
|
}
|
|
},
|
|
};
|
|
},
|
|
});
|
|
|
|
const editAt = (nodeStart: number): Command => (state, dispatch) => {
|
|
const node = state.doc.nodeAt(nodeStart);
|
|
if (!options.canEdit(node, nodeStart)) {
|
|
return false;
|
|
}
|
|
|
|
if (dispatch) {
|
|
const editRequest: EditRequest = {
|
|
nodeStart,
|
|
showEditor: true,
|
|
};
|
|
dispatch(state.tr.setMeta(plugin, editRequest));
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
const isEditorVisible = (state: EditorState) => {
|
|
return plugin.getState(state).editingNodeAt !== null;
|
|
};
|
|
|
|
const hideEditor: Command = (state, dispatch) => {
|
|
const isEditing = isEditorVisible(state);
|
|
if (!isEditing) {
|
|
return false;
|
|
}
|
|
|
|
if (dispatch) {
|
|
const editRequest: EditRequest = {
|
|
showEditor: false,
|
|
};
|
|
dispatch(state.tr.setMeta(plugin, editRequest));
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
return { plugin, hideEditor, editAt, isEditorVisible };
|
|
};
|
|
|
|
export default createExternalEditorPlugin;
|