fix: Ctrl-a/e in code fences
This commit is contained in:
@@ -2,6 +2,13 @@ import { NodeType } from "prosemirror-model";
|
|||||||
import { EditorState } from "prosemirror-state";
|
import { EditorState } from "prosemirror-state";
|
||||||
import { Dispatch } from "../types";
|
import { Dispatch } from "../types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the current node to a paragraph when pressing backspace at the
|
||||||
|
* beginning of the node and not already a paragraph.
|
||||||
|
*
|
||||||
|
* @param type The node type
|
||||||
|
* @returns A prosemirror command.
|
||||||
|
*/
|
||||||
export default function backspaceToParagraph(type: NodeType) {
|
export default function backspaceToParagraph(type: NodeType) {
|
||||||
return (state: EditorState, dispatch: Dispatch) => {
|
return (state: EditorState, dispatch: Dispatch) => {
|
||||||
const { $from, from, to, empty } = state.selection;
|
const { $from, from, to, empty } = state.selection;
|
||||||
|
|||||||
122
shared/editor/commands/codeFence.ts
Normal file
122
shared/editor/commands/codeFence.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import { EditorState, TextSelection } from "prosemirror-state";
|
||||||
|
import isInCode from "../queries/isInCode";
|
||||||
|
import { Dispatch } from "../types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the current selection to the previous newline, this is used inside
|
||||||
|
* code fences only, prosemirror handles this functionality fine in other nodes.
|
||||||
|
*
|
||||||
|
* @returns A prosemirror command.
|
||||||
|
*/
|
||||||
|
export const moveToPreviousNewline = (
|
||||||
|
state: EditorState,
|
||||||
|
dispatch: Dispatch
|
||||||
|
) => {
|
||||||
|
if (!isInCode(state)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $pos = state.selection.$from;
|
||||||
|
if (!$pos.parent.type.isTextblock) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The easiest way to find the previous newline is to reverse the string and
|
||||||
|
// perform a forward seach as if looking for the next newline.
|
||||||
|
const beginningOfNode = $pos.pos - $pos.parentOffset;
|
||||||
|
const startOfLine = $pos.parent.textContent
|
||||||
|
.split("")
|
||||||
|
.reverse()
|
||||||
|
.join("")
|
||||||
|
.indexOf("\n", $pos.parent.nodeSize - $pos.parentOffset - 2);
|
||||||
|
|
||||||
|
if (startOfLine === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
state.tr.setSelection(
|
||||||
|
TextSelection.create(
|
||||||
|
state.doc,
|
||||||
|
beginningOfNode + ($pos.parent.nodeSize - startOfLine - 2)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the current selection to the next newline, this is used inside code
|
||||||
|
* fences only, prosemirror handles this functionality fine in other nodes.
|
||||||
|
*
|
||||||
|
* @returns A prosemirror command.
|
||||||
|
*/
|
||||||
|
export const moveToNextNewline = (state: EditorState, dispatch: Dispatch) => {
|
||||||
|
if (!isInCode(state)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $pos = state.selection.$to;
|
||||||
|
if (!$pos.parent.type.isTextblock) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find next newline
|
||||||
|
const beginningOfNode = $pos.pos - $pos.parentOffset;
|
||||||
|
const endOfLine = $pos.parent.textContent.indexOf("\n", $pos.parentOffset);
|
||||||
|
if (endOfLine === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
state.tr.setSelection(
|
||||||
|
TextSelection.create(state.doc, beginningOfNode + endOfLine)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the selection with a newline character preceeded by a number of
|
||||||
|
* spaces to have the new line align with the code on the previous. This is
|
||||||
|
* standard code editor behavior.
|
||||||
|
*
|
||||||
|
* @returns A prosemirror command
|
||||||
|
*/
|
||||||
|
export const newlineInCode = (state: EditorState, dispatch: Dispatch) => {
|
||||||
|
if (!isInCode(state)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { tr, selection } = state;
|
||||||
|
const text = selection?.$anchor?.nodeBefore?.text;
|
||||||
|
|
||||||
|
let newText = "\n";
|
||||||
|
|
||||||
|
if (text) {
|
||||||
|
const splitByNewLine = text.split("\n");
|
||||||
|
const numOfSpaces = splitByNewLine[splitByNewLine.length - 1].search(
|
||||||
|
/\S|$/
|
||||||
|
);
|
||||||
|
newText += " ".repeat(numOfSpaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(tr.insertText(newText, selection.from, selection.to));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert two spaces to simulate the tab key.
|
||||||
|
*
|
||||||
|
* @returns A prosemirror command
|
||||||
|
*/
|
||||||
|
export const insertSpaceTab = (state: EditorState, dispatch: Dispatch) => {
|
||||||
|
if (!isInCode(state)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tr, selection } = state;
|
||||||
|
dispatch(tr.insertText(" ", selection.from, selection.to));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
@@ -7,14 +7,7 @@ import {
|
|||||||
Schema,
|
Schema,
|
||||||
Node as ProsemirrorNode,
|
Node as ProsemirrorNode,
|
||||||
} from "prosemirror-model";
|
} from "prosemirror-model";
|
||||||
import {
|
import { Selection, Plugin, PluginKey } from "prosemirror-state";
|
||||||
EditorState,
|
|
||||||
Selection,
|
|
||||||
TextSelection,
|
|
||||||
Transaction,
|
|
||||||
Plugin,
|
|
||||||
PluginKey,
|
|
||||||
} from "prosemirror-state";
|
|
||||||
import refractor from "refractor/core";
|
import refractor from "refractor/core";
|
||||||
import bash from "refractor/lang/bash";
|
import bash from "refractor/lang/bash";
|
||||||
import clike from "refractor/lang/clike";
|
import clike from "refractor/lang/clike";
|
||||||
@@ -56,12 +49,17 @@ import zig from "refractor/lang/zig";
|
|||||||
import { Dictionary } from "~/hooks/useDictionary";
|
import { Dictionary } from "~/hooks/useDictionary";
|
||||||
import { UserPreferences } from "../../types";
|
import { UserPreferences } from "../../types";
|
||||||
import Storage from "../../utils/Storage";
|
import Storage from "../../utils/Storage";
|
||||||
|
import {
|
||||||
|
newlineInCode,
|
||||||
|
insertSpaceTab,
|
||||||
|
moveToNextNewline,
|
||||||
|
moveToPreviousNewline,
|
||||||
|
} from "../commands/codeFence";
|
||||||
import toggleBlockType from "../commands/toggleBlockType";
|
import toggleBlockType from "../commands/toggleBlockType";
|
||||||
import Mermaid from "../extensions/Mermaid";
|
import Mermaid from "../extensions/Mermaid";
|
||||||
import Prism, { LANGUAGES } from "../extensions/Prism";
|
import Prism, { LANGUAGES } from "../extensions/Prism";
|
||||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||||
import isInCode from "../queries/isInCode";
|
import isInCode from "../queries/isInCode";
|
||||||
import { Dispatch } from "../types";
|
|
||||||
import Node from "./Node";
|
import Node from "./Node";
|
||||||
|
|
||||||
const PERSISTENCE_KEY = "rme-code-language";
|
const PERSISTENCE_KEY = "rme-code-language";
|
||||||
@@ -228,38 +226,11 @@ export default class CodeFence extends Node {
|
|||||||
keys({ type, schema }: { type: NodeType; schema: Schema }) {
|
keys({ type, schema }: { type: NodeType; schema: Schema }) {
|
||||||
return {
|
return {
|
||||||
"Shift-Ctrl-\\": toggleBlockType(type, schema.nodes.paragraph),
|
"Shift-Ctrl-\\": toggleBlockType(type, schema.nodes.paragraph),
|
||||||
"Shift-Enter": (state: EditorState, dispatch: Dispatch) => {
|
Tab: insertSpaceTab,
|
||||||
if (!isInCode(state)) {
|
Enter: newlineInCode,
|
||||||
return false;
|
"Shift-Enter": newlineInCode,
|
||||||
}
|
"Ctrl-a": moveToPreviousNewline,
|
||||||
const {
|
"Ctrl-e": moveToNextNewline,
|
||||||
tr,
|
|
||||||
selection,
|
|
||||||
}: { tr: Transaction; selection: TextSelection } = state;
|
|
||||||
const text = selection?.$anchor?.nodeBefore?.text;
|
|
||||||
|
|
||||||
let newText = "\n";
|
|
||||||
|
|
||||||
if (text) {
|
|
||||||
const splitByNewLine = text.split("\n");
|
|
||||||
const numOfSpaces = splitByNewLine[splitByNewLine.length - 1].search(
|
|
||||||
/\S|$/
|
|
||||||
);
|
|
||||||
newText += " ".repeat(numOfSpaces);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(tr.insertText(newText, selection.from, selection.to));
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
Tab: (state: EditorState, dispatch: Dispatch) => {
|
|
||||||
if (!isInCode(state)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { tr, selection } = state;
|
|
||||||
dispatch(tr.insertText(" ", selection.from, selection.to));
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user