Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/core/history-commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { chainCommands } from 'prosemirror-commands';
import { undo as historyUndo, redo as historyRedo } from 'prosemirror-history';
import { undoInputRule } from 'prosemirror-inputrules';

export const undoCommand = chainCommands(undoInputRule, historyUndo);
export const redoCommand = historyRedo;
16 changes: 4 additions & 12 deletions src/core/keymap.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import {
joinUp, joinDown, lift, newlineInCode, liftEmptyBlock, createParagraphNear
} from 'prosemirror-commands';
import { wrapInList, splitListItem } from 'prosemirror-schema-list';
import { undo, redo } from 'prosemirror-history';
import { undoInputRule } from 'prosemirror-inputrules';
import { schema } from './schema';
import { changeIndent, removeBlockIndent, customSplitBlock } from './commands';
import { undoCommand, redoCommand } from './history-commands';
import { isMac } from './utils';

export function buildKeymap(options) {
Expand All @@ -19,11 +19,11 @@ export function buildKeymap(options) {
keys[key] = cmd;
}

bind('Mod-z', customUndo);
bind('Shift-Mod-z', redo);
bind('Mod-z', undoCommand);
bind('Shift-Mod-z', redoCommand);
bind('Backspace', undoInputRule);
bind('Backspace', removeBlockIndent());
if (!isMac()) bind('Ctrl-y', redo);
if (!isMac()) bind('Ctrl-y', redoCommand);

bind('Alt-F10', focusToolbar);
bind('Alt-ArrowUp', joinUp);
Expand Down Expand Up @@ -100,14 +100,6 @@ export function buildKeymap(options) {
return keys;
}

function customUndo(state, dispatch) {
if(undoInputRule(state, dispatch)) {
return true;
} else {
return undo(state, dispatch);
}
}

function focusToolbar() {
document.querySelector('.toolbar button').focus();
}
Expand Down
42 changes: 42 additions & 0 deletions src/index.zotero.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createRoot } from 'react-dom/client';
import { addFTL, getLocalizedString } from './fluent';
import { randomString } from './core/utils';
import { schema } from './core/schema';
import { undoCommand, redoCommand } from './core/history-commands';
import Editor from './ui/editor';
import EditorCore from './core/editor-core';

Expand Down Expand Up @@ -87,6 +88,31 @@ class EditorInstance {
return this._editorCore.getData(onlyChanged);
}

_getHistoryView() {
let editorCore = this._editorCore;
return editorCore?.view && !editorCore.readOnly ? editorCore.view : null;
}

canUndo() {
let view = this._getHistoryView();
return view ? undoCommand(view.state) : false;
}

canRedo() {
let view = this._getHistoryView();
return view ? redoCommand(view.state) : false;
}

doUndo() {
let view = this._getHistoryView();
return view ? undoCommand(view.state, view.dispatch) : false;
}

doRedo() {
let view = this._getHistoryView();
return view ? redoCommand(view.state, view.dispatch) : false;
}

_setFont(font) {
let root = document.documentElement;
root.style.setProperty('--font-family', font.fontFamily);
Expand Down Expand Up @@ -619,6 +645,22 @@ window.getDataSync = (onlyChanged) => {
return null;
};

window.canUndo = () => {
return currentInstance?.canUndo() ?? false;
};

window.canRedo = () => {
return currentInstance?.canRedo() ?? false;
};

window.doUndo = () => {
return currentInstance?.doUndo() ?? false;
};

window.doRedo = () => {
return currentInstance?.doRedo() ?? false;
};

// Called from Zotero, because file picker can only be opened from user-triggered event or privileged code
window.openImageFilePicker = () => {
if (currentInstance) {
Expand Down
71 changes: 71 additions & 0 deletions test/history-commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/* global describe, it */

import { expect } from 'chai';
import { schema } from 'prosemirror-schema-basic';
import { history } from 'prosemirror-history';
import {
inputRules,
textblockTypeInputRule,
} from 'prosemirror-inputrules';
import { EditorState } from 'prosemirror-state';

import { redoCommand, undoCommand } from '../src/core/history-commands.js';

function createState() {
let inputRulesPlugin = inputRules({
rules: [
textblockTypeInputRule(
/^(#{1,6}) $/,
schema.nodes.heading,
match => ({ level: match[1].length })
),
],
});
let state = EditorState.create({
schema,
plugins: [inputRulesPlugin, history()],
});
let dispatch = (transaction) => {
state = state.apply(transaction);
};

return {
get state() {
return state;
},
dispatch,
inputRulesPlugin,
};
}

describe('history commands', function () {
it('should undo and redo document changes', function () {
let editor = createState();
editor.dispatch(editor.state.tr.insertText('text', 1));

expect(undoCommand(editor.state)).to.equal(true);
expect(undoCommand(editor.state, editor.dispatch)).to.equal(true);
expect(editor.state.doc.textContent).to.equal('');
expect(redoCommand(editor.state)).to.equal(true);
expect(redoCommand(editor.state, editor.dispatch)).to.equal(true);
expect(editor.state.doc.textContent).to.equal('text');
});

it('should undo the latest input rule before document history', function () {
let editor = createState();
editor.dispatch(editor.state.tr.insertText('#', 1));
let view = {
get state() {
return editor.state;
},
dispatch: editor.dispatch,
composing: false,
};

expect(editor.inputRulesPlugin.props.handleTextInput(view, 2, 2, ' ')).to.equal(true);
expect(editor.state.doc.firstChild.type).to.equal(schema.nodes.heading);
expect(undoCommand(editor.state, editor.dispatch)).to.equal(true);
expect(editor.state.doc.firstChild.type).to.equal(schema.nodes.paragraph);
expect(editor.state.doc.textContent).to.equal('# ');
});
});
Loading