chore: Upgrade all of prosemirror (#5366)
Co-authored-by: Apoorv Mishra <apoorvmishra101092@gmail.com>
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { NodeType } from "prosemirror-model";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import { Dispatch } from "../types";
|
||||
import { Command } from "prosemirror-state";
|
||||
|
||||
/**
|
||||
* Converts the current node to a paragraph when pressing backspace at the
|
||||
@@ -9,28 +8,28 @@ import { Dispatch } from "../types";
|
||||
* @param type The node type
|
||||
* @returns A prosemirror command.
|
||||
*/
|
||||
export default function backspaceToParagraph(type: NodeType) {
|
||||
return (state: EditorState, dispatch: Dispatch) => {
|
||||
export default function backspaceToParagraph(type: NodeType): Command {
|
||||
return (state, dispatch) => {
|
||||
const { $from, from, to, empty } = state.selection;
|
||||
|
||||
// if the selection has anything in it then use standard delete behavior
|
||||
if (!empty) {
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// check we're in a matching node
|
||||
if ($from.parent.type !== type) {
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if we're at the beginning of the heading
|
||||
const $pos = state.doc.resolve(from - 1);
|
||||
if ($pos.parent === $from.parent) {
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// okay, replace it with a paragraph
|
||||
dispatch(
|
||||
dispatch?.(
|
||||
state.tr
|
||||
.setBlockType(from, to, type.schema.nodes.paragraph)
|
||||
.scrollIntoView()
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import { Command } from "prosemirror-state";
|
||||
import { liftTarget } from "prosemirror-transform";
|
||||
import { Dispatch } from "../types";
|
||||
|
||||
const clearNodes = () => (state: EditorState, dispatch?: Dispatch) => {
|
||||
const clearNodes = (): Command => (state, dispatch) => {
|
||||
const { tr } = state;
|
||||
const { selection } = tr;
|
||||
const { ranges } = selection;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { EditorState, TextSelection } from "prosemirror-state";
|
||||
import { Command, 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
|
||||
@@ -8,10 +7,7 @@ import { Dispatch } from "../types";
|
||||
*
|
||||
* @returns A prosemirror command.
|
||||
*/
|
||||
export const moveToPreviousNewline = (
|
||||
state: EditorState,
|
||||
dispatch: Dispatch
|
||||
) => {
|
||||
export const moveToPreviousNewline: Command = (state, dispatch) => {
|
||||
if (!isInCode(state)) {
|
||||
return false;
|
||||
}
|
||||
@@ -34,7 +30,7 @@ export const moveToPreviousNewline = (
|
||||
return false;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
dispatch?.(
|
||||
state.tr.setSelection(
|
||||
TextSelection.create(
|
||||
state.doc,
|
||||
@@ -52,7 +48,7 @@ export const moveToPreviousNewline = (
|
||||
*
|
||||
* @returns A prosemirror command.
|
||||
*/
|
||||
export const moveToNextNewline = (state: EditorState, dispatch: Dispatch) => {
|
||||
export const moveToNextNewline: Command = (state, dispatch) => {
|
||||
if (!isInCode(state)) {
|
||||
return false;
|
||||
}
|
||||
@@ -69,7 +65,7 @@ export const moveToNextNewline = (state: EditorState, dispatch: Dispatch) => {
|
||||
return false;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
dispatch?.(
|
||||
state.tr.setSelection(
|
||||
TextSelection.create(state.doc, beginningOfNode + endOfLine)
|
||||
)
|
||||
@@ -85,7 +81,7 @@ export const moveToNextNewline = (state: EditorState, dispatch: Dispatch) => {
|
||||
*
|
||||
* @returns A prosemirror command
|
||||
*/
|
||||
export const newlineInCode = (state: EditorState, dispatch: Dispatch) => {
|
||||
export const newlineInCode: Command = (state, dispatch) => {
|
||||
if (!isInCode(state)) {
|
||||
return false;
|
||||
}
|
||||
@@ -101,7 +97,7 @@ export const newlineInCode = (state: EditorState, dispatch: Dispatch) => {
|
||||
newText += " ".repeat(numOfSpaces);
|
||||
}
|
||||
|
||||
dispatch(tr.insertText(newText, selection.from, selection.to));
|
||||
dispatch?.(tr.insertText(newText, selection.from, selection.to));
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -110,12 +106,12 @@ export const newlineInCode = (state: EditorState, dispatch: Dispatch) => {
|
||||
*
|
||||
* @returns A prosemirror command
|
||||
*/
|
||||
export const insertSpaceTab = (state: EditorState, dispatch: Dispatch) => {
|
||||
export const insertSpaceTab: Command = (state, dispatch) => {
|
||||
if (!isInCode(state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { tr, selection } = state;
|
||||
dispatch(tr.insertText(" ", selection.from, selection.to));
|
||||
dispatch?.(tr.insertText(" ", selection.from, selection.to));
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { EditorState, TextSelection } from "prosemirror-state";
|
||||
import { Dispatch } from "../types";
|
||||
import { Command, TextSelection } from "prosemirror-state";
|
||||
|
||||
const collapseSelection = () => (state: EditorState, dispatch?: Dispatch) => {
|
||||
const collapseSelection = (): Command => (state, dispatch) => {
|
||||
dispatch?.(
|
||||
state.tr.setSelection(
|
||||
TextSelection.create(state.doc, state.tr.selection.from)
|
||||
|
||||
31
shared/editor/commands/deleteEmptyFirstParagraph.ts
Normal file
31
shared/editor/commands/deleteEmptyFirstParagraph.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Command } from "prosemirror-state";
|
||||
import isNodeActive from "../queries/isNodeActive";
|
||||
|
||||
/**
|
||||
* Deletes the first paragraph node if it is empty and the cursor is at the
|
||||
* beginning of the document.
|
||||
*/
|
||||
const deleteEmptyFirstParagraph: Command = (state, dispatch) => {
|
||||
if (!isNodeActive(state.schema.nodes.paragraph)(state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (state.selection.from !== 1 || state.selection.to !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parent = state.selection.$from.parent;
|
||||
if (parent.textContent !== "" || parent.childCount > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// delete the empty paragraph node
|
||||
dispatch?.(
|
||||
state.tr
|
||||
.delete(state.selection.from - 1, state.selection.from)
|
||||
.scrollIntoView()
|
||||
);
|
||||
return true;
|
||||
};
|
||||
|
||||
export default deleteEmptyFirstParagraph;
|
||||
@@ -17,9 +17,13 @@ limitations under the License.
|
||||
// This file is based on the implementation found here:
|
||||
// https://bitbucket.org/atlassian/design-system-mirror/src/master/editor/editor-core/src/plugins/text-formatting/commands/text-formatting.ts
|
||||
|
||||
import { Selection, EditorState, TextSelection } from "prosemirror-state";
|
||||
import {
|
||||
Selection,
|
||||
EditorState,
|
||||
TextSelection,
|
||||
Command,
|
||||
} from "prosemirror-state";
|
||||
import isMarkActive from "../queries/isMarkActive";
|
||||
import { Dispatch } from "../types";
|
||||
|
||||
function hasCode(state: EditorState, pos: number) {
|
||||
const { code_inline } = state.schema.marks;
|
||||
@@ -30,8 +34,8 @@ function hasCode(state: EditorState, pos: number) {
|
||||
: false;
|
||||
}
|
||||
|
||||
export default function moveLeft() {
|
||||
return (state: EditorState, dispatch: Dispatch): boolean => {
|
||||
export default function moveLeft(): Command {
|
||||
return (state, dispatch): boolean => {
|
||||
const { code_inline } = state.schema.marks;
|
||||
const { empty, $cursor } = state.selection as TextSelection;
|
||||
if (!empty || !$cursor) {
|
||||
@@ -70,14 +74,14 @@ export default function moveLeft() {
|
||||
Selection.near(state.doc.resolve($cursor.pos - 1))
|
||||
);
|
||||
|
||||
dispatch(tr.removeStoredMark(code_inline));
|
||||
dispatch?.(tr.removeStoredMark(code_inline));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// entering code mark (from right edge): don't move the cursor, just add the mark
|
||||
if (!insideCode && enteringCode) {
|
||||
dispatch(state.tr.addStoredMark(code_inline.create()));
|
||||
dispatch?.(state.tr.addStoredMark(code_inline.create()));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -87,7 +91,7 @@ export default function moveLeft() {
|
||||
Selection.near(state.doc.resolve($cursor.pos - 1))
|
||||
);
|
||||
|
||||
dispatch(tr.addStoredMark(code_inline.create()));
|
||||
dispatch?.(tr.addStoredMark(code_inline.create()));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -97,7 +101,7 @@ export default function moveLeft() {
|
||||
insideCode &&
|
||||
(exitingCode || (!$cursor.nodeBefore && isFirstChild))
|
||||
) {
|
||||
dispatch(state.tr.removeStoredMark(code_inline));
|
||||
dispatch?.(state.tr.removeStoredMark(code_inline));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,12 +17,11 @@ limitations under the License.
|
||||
// This file is based on the implementation found here:
|
||||
// https://bitbucket.org/atlassian/design-system-mirror/src/master/editor/editor-core/src/plugins/text-formatting/commands/text-formatting.ts
|
||||
|
||||
import { EditorState, TextSelection } from "prosemirror-state";
|
||||
import { Command, TextSelection } from "prosemirror-state";
|
||||
import isMarkActive from "../queries/isMarkActive";
|
||||
import { Dispatch } from "../types";
|
||||
|
||||
export default function moveRight() {
|
||||
return (state: EditorState, dispatch: Dispatch): boolean => {
|
||||
export default function moveRight(): Command {
|
||||
return (state, dispatch): boolean => {
|
||||
const { code_inline } = state.schema.marks;
|
||||
const { empty, $cursor } = state.selection as TextSelection;
|
||||
if (!empty || !$cursor) {
|
||||
@@ -54,14 +53,14 @@ export default function moveRight() {
|
||||
|
||||
// entering code mark (from the left edge): don't move the cursor, just add the mark
|
||||
if (!insideCode && enteringCode) {
|
||||
dispatch(state.tr.addStoredMark(code_inline.create()));
|
||||
dispatch?.(state.tr.addStoredMark(code_inline.create()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// exiting code mark: don't move the cursor, just remove the mark
|
||||
if (insideCode && exitingCode) {
|
||||
dispatch(state.tr.removeStoredMark(code_inline));
|
||||
dispatch?.(state.tr.removeStoredMark(code_inline));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { NodeType } from "prosemirror-model";
|
||||
import { EditorState, TextSelection } from "prosemirror-state";
|
||||
import { findBlockNodes } from "prosemirror-utils";
|
||||
import { Command, TextSelection } from "prosemirror-state";
|
||||
import { findBlockNodes } from "../queries/findChildren";
|
||||
import findCollapsedNodes from "../queries/findCollapsedNodes";
|
||||
import { Dispatch } from "../types";
|
||||
|
||||
export default function splitHeading(type: NodeType) {
|
||||
return (state: EditorState, dispatch: Dispatch): boolean => {
|
||||
export default function splitHeading(type: NodeType): Command {
|
||||
return (state, dispatch): boolean => {
|
||||
const { $from, from, $to, to } = state.selection;
|
||||
|
||||
// check we're in a matching heading node
|
||||
@@ -33,7 +32,7 @@ export default function splitHeading(type: NodeType) {
|
||||
);
|
||||
|
||||
// Move the selection into the new heading node and make sure it's on screen
|
||||
dispatch(
|
||||
dispatch?.(
|
||||
transaction
|
||||
.setSelection(
|
||||
TextSelection.near(transaction.doc.resolve($from.before()))
|
||||
@@ -75,7 +74,7 @@ export default function splitHeading(type: NodeType) {
|
||||
);
|
||||
|
||||
// Move the selection into the new heading node and make sure it's on screen
|
||||
dispatch(
|
||||
dispatch?.(
|
||||
transaction
|
||||
.setSelection(
|
||||
TextSelection.near(
|
||||
|
||||
152
shared/editor/commands/table.ts
Normal file
152
shared/editor/commands/table.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { Fragment, Node, NodeType } from "prosemirror-model";
|
||||
import {
|
||||
Command,
|
||||
EditorState,
|
||||
TextSelection,
|
||||
Transaction,
|
||||
} from "prosemirror-state";
|
||||
import {
|
||||
CellSelection,
|
||||
addRow,
|
||||
isInTable,
|
||||
selectedRect,
|
||||
tableNodeTypes,
|
||||
} from "prosemirror-tables";
|
||||
import { getCellsInColumn } from "../queries/table";
|
||||
|
||||
export function createTable(
|
||||
state: EditorState,
|
||||
rowsCount: number,
|
||||
colsCount: number,
|
||||
withHeaderRow = true,
|
||||
cellContent?: Node
|
||||
) {
|
||||
const types = tableNodeTypes(state.schema);
|
||||
const headerCells: Node[] = [];
|
||||
const cells: Node[] = [];
|
||||
const rows: Node[] = [];
|
||||
|
||||
const createCell = (
|
||||
cellType: NodeType,
|
||||
cellContent: Fragment | Node | readonly Node[] | null | undefined
|
||||
) =>
|
||||
cellContent
|
||||
? cellType.createChecked(null, cellContent)
|
||||
: cellType.createAndFill();
|
||||
|
||||
for (let index = 0; index < colsCount; index += 1) {
|
||||
const cell = createCell(types.cell, cellContent);
|
||||
|
||||
if (cell) {
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
if (withHeaderRow) {
|
||||
const headerCell = createCell(types.header_cell, cellContent);
|
||||
|
||||
if (headerCell) {
|
||||
headerCells.push(headerCell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let index = 0; index < rowsCount; index += 1) {
|
||||
rows.push(
|
||||
types.row.createChecked(
|
||||
null,
|
||||
withHeaderRow && index === 0 ? headerCells : cells
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return types.table.createChecked(null, rows);
|
||||
}
|
||||
|
||||
export function addRowAfterAndMoveSelection({
|
||||
index,
|
||||
}: {
|
||||
index?: number;
|
||||
} = {}): Command {
|
||||
return (state, dispatch) => {
|
||||
if (!isInTable(state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dispatch) {
|
||||
const rect = selectedRect(state);
|
||||
const indexAfter = index !== undefined ? index + 1 : rect.bottom;
|
||||
const tr = addRow(state.tr, rect, indexAfter);
|
||||
const cells = getCellsInColumn(0)(state);
|
||||
|
||||
// Special case when adding row to the end of the table as the calculated
|
||||
// rect does not include the row that we just added.
|
||||
if (indexAfter !== rect.map.height) {
|
||||
const pos = cells[Math.min(cells.length - 1, indexAfter)];
|
||||
const $pos = tr.doc.resolve(pos);
|
||||
dispatch(tr.setSelection(TextSelection.near($pos)));
|
||||
} else {
|
||||
const $pos = tr.doc.resolve(rect.tableStart + rect.table.nodeSize);
|
||||
dispatch(tr.setSelection(TextSelection.near($pos)));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
export function setColumnAttr({
|
||||
index,
|
||||
alignment,
|
||||
}: {
|
||||
index: number;
|
||||
alignment: string;
|
||||
}): Command {
|
||||
return (state, dispatch) => {
|
||||
if (dispatch) {
|
||||
const cells = getCellsInColumn(index)(state) || [];
|
||||
let transaction = state.tr;
|
||||
cells.forEach((pos) => {
|
||||
transaction = transaction.setNodeMarkup(pos, undefined, {
|
||||
alignment,
|
||||
});
|
||||
});
|
||||
dispatch(transaction);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
export function selectRow(index: number, expand = false) {
|
||||
return (state: EditorState): Transaction => {
|
||||
const rect = selectedRect(state);
|
||||
const pos = rect.map.positionAt(index, 0, rect.table);
|
||||
const $pos = state.doc.resolve(rect.tableStart + pos);
|
||||
const rowSelection =
|
||||
expand && state.selection instanceof CellSelection
|
||||
? CellSelection.rowSelection(state.selection.$anchorCell, $pos)
|
||||
: CellSelection.rowSelection($pos);
|
||||
return state.tr.setSelection(rowSelection);
|
||||
};
|
||||
}
|
||||
|
||||
export function selectColumn(index: number, expand = false) {
|
||||
return (state: EditorState): Transaction => {
|
||||
const rect = selectedRect(state);
|
||||
const pos = rect.map.positionAt(0, index, rect.table);
|
||||
const $pos = state.doc.resolve(rect.tableStart + pos);
|
||||
const colSelection =
|
||||
expand && state.selection instanceof CellSelection
|
||||
? CellSelection.colSelection(state.selection.$anchorCell, $pos)
|
||||
: CellSelection.colSelection($pos);
|
||||
return state.tr.setSelection(colSelection);
|
||||
};
|
||||
}
|
||||
|
||||
export function selectTable(state: EditorState): Transaction {
|
||||
const rect = selectedRect(state);
|
||||
const map = rect.map.map;
|
||||
const $anchor = state.doc.resolve(rect.tableStart + map[0]);
|
||||
const $head = state.doc.resolve(rect.tableStart + map[map.length - 1]);
|
||||
const tableSelection = new CellSelection($anchor, $head);
|
||||
return state.tr.setSelection(tableSelection);
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
import { setBlockType } from "prosemirror-commands";
|
||||
import { NodeType } from "prosemirror-model";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import { Command } from "prosemirror-state";
|
||||
import isNodeActive from "../queries/isNodeActive";
|
||||
import { Dispatch } from "../types";
|
||||
|
||||
export default function toggleBlockType(
|
||||
type: NodeType,
|
||||
toggleType: NodeType,
|
||||
attrs = {}
|
||||
) {
|
||||
return (state: EditorState, dispatch?: Dispatch) => {
|
||||
): Command {
|
||||
return (state, dispatch) => {
|
||||
const isActive = isNodeActive(type, attrs)(state);
|
||||
|
||||
if (isActive) {
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { NodeType } from "prosemirror-model";
|
||||
import { wrapInList, liftListItem } from "prosemirror-schema-list";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import { findParentNode } from "prosemirror-utils";
|
||||
import { Command } from "prosemirror-state";
|
||||
import chainTransactions from "../lib/chainTransactions";
|
||||
import { findParentNode } from "../queries/findParentNode";
|
||||
import isList from "../queries/isList";
|
||||
import { Dispatch } from "../types";
|
||||
import clearNodes from "./clearNodes";
|
||||
|
||||
export default function toggleList(listType: NodeType, itemType: NodeType) {
|
||||
return (state: EditorState, dispatch?: Dispatch) => {
|
||||
export default function toggleList(
|
||||
listType: NodeType,
|
||||
itemType: NodeType
|
||||
): Command {
|
||||
return (state, dispatch) => {
|
||||
const { schema, selection } = state;
|
||||
const { $from, $to } = selection;
|
||||
const range = $from.blockRange($to);
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { wrapIn, lift } from "prosemirror-commands";
|
||||
import { NodeType } from "prosemirror-model";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import { Command } from "prosemirror-state";
|
||||
import isNodeActive from "../queries/isNodeActive";
|
||||
import { Dispatch } from "../types";
|
||||
|
||||
export default function toggleWrap(
|
||||
type: NodeType,
|
||||
attrs?: Record<string, any>
|
||||
) {
|
||||
return (state: EditorState, dispatch?: Dispatch) => {
|
||||
): Command {
|
||||
return (state, dispatch) => {
|
||||
const isActive = isNodeActive(type)(state);
|
||||
|
||||
if (isActive) {
|
||||
|
||||
@@ -1245,7 +1245,7 @@ table {
|
||||
* https://github.com/ProseMirror/prosemirror/issues/947 */
|
||||
&::after {
|
||||
content: "";
|
||||
cursor: pointer;
|
||||
cursor: var(--pointer);
|
||||
position: absolute;
|
||||
top: -16px;
|
||||
${props.rtl ? "right" : "left"}: 0;
|
||||
@@ -1273,7 +1273,7 @@ table {
|
||||
.grip-row {
|
||||
&::after {
|
||||
content: "";
|
||||
cursor: pointer;
|
||||
cursor: var(--pointer);
|
||||
position: absolute;
|
||||
${props.rtl ? "right" : "left"}: -16px;
|
||||
top: 0;
|
||||
@@ -1302,7 +1302,7 @@ table {
|
||||
.grip-table {
|
||||
&::after {
|
||||
content: "";
|
||||
cursor: pointer;
|
||||
cursor: var(--pointer);
|
||||
background: ${props.theme.tableDivider};
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { PlusIcon } from "outline-icons";
|
||||
import { Plugin } from "prosemirror-state";
|
||||
import { findParentNode } from "prosemirror-utils";
|
||||
import { Decoration, DecorationSet } from "prosemirror-view";
|
||||
import * as React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { SuggestionsMenuType } from "../plugins/Suggestions";
|
||||
import { findParentNode } from "../queries/findParentNode";
|
||||
import { EventType } from "../types";
|
||||
import Suggestion from "./Suggestion";
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { InputRule } from "prosemirror-inputrules";
|
||||
import { Schema } from "prosemirror-model";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import { Command } from "prosemirror-state";
|
||||
import {
|
||||
getCurrentDateAsString,
|
||||
getCurrentDateTimeAsString,
|
||||
getCurrentTimeAsString,
|
||||
} from "../../utils/date";
|
||||
import Extension from "../lib/Extension";
|
||||
import { Dispatch, EventType } from "../types";
|
||||
import { EventType } from "../types";
|
||||
|
||||
/**
|
||||
* An editor extension that adds commands to insert the current date and time.
|
||||
@@ -43,16 +43,16 @@ export default class DateTime extends Extension {
|
||||
|
||||
commands(_options: { schema: Schema }) {
|
||||
return {
|
||||
date: () => (state: EditorState, dispatch: Dispatch) => {
|
||||
dispatch(state.tr.insertText(getCurrentDateAsString() + " "));
|
||||
date: (): Command => (state, dispatch) => {
|
||||
dispatch?.(state.tr.insertText(getCurrentDateAsString() + " "));
|
||||
return true;
|
||||
},
|
||||
time: () => (state: EditorState, dispatch: Dispatch) => {
|
||||
dispatch(state.tr.insertText(getCurrentTimeAsString() + " "));
|
||||
time: (): Command => (state, dispatch) => {
|
||||
dispatch?.(state.tr.insertText(getCurrentTimeAsString() + " "));
|
||||
return true;
|
||||
},
|
||||
datetime: () => (state: EditorState, dispatch: Dispatch) => {
|
||||
dispatch(state.tr.insertText(getCurrentDateTimeAsString() + " "));
|
||||
datetime: (): Command => (state, dispatch) => {
|
||||
dispatch?.(state.tr.insertText(getCurrentDateTimeAsString() + " "));
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,8 +5,9 @@ import {
|
||||
AllSelection,
|
||||
TextSelection,
|
||||
EditorState,
|
||||
Command,
|
||||
} from "prosemirror-state";
|
||||
import Extension, { Command } from "../lib/Extension";
|
||||
import Extension from "../lib/Extension";
|
||||
import isInCode from "../queries/isInCode";
|
||||
|
||||
export default class Keys extends Extension {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { MathView } from "@benrbray/prosemirror-math";
|
||||
import { Node as ProseNode } from "prosemirror-model";
|
||||
import { Plugin, PluginKey, PluginSpec } from "prosemirror-state";
|
||||
import { EditorView } from "prosemirror-view";
|
||||
import { NodeViewConstructor } from "prosemirror-view";
|
||||
|
||||
export interface IMathPluginState {
|
||||
macros: { [cmd: string]: string };
|
||||
@@ -11,12 +10,8 @@ export interface IMathPluginState {
|
||||
|
||||
const MATH_PLUGIN_KEY = new PluginKey<IMathPluginState>("prosemirror-math");
|
||||
|
||||
export function createMathView(displayMode: boolean) {
|
||||
return (
|
||||
node: ProseNode,
|
||||
view: EditorView,
|
||||
getPos: boolean | (() => number)
|
||||
): MathView => {
|
||||
export function createMathView(displayMode: boolean): NodeViewConstructor {
|
||||
return (node, view, getPos) => {
|
||||
// dynamically load katex styles and fonts
|
||||
import("katex/dist/katex.min.css");
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Node } from "prosemirror-model";
|
||||
import { Plugin, PluginKey, Transaction } from "prosemirror-state";
|
||||
import { findBlockNodes } from "prosemirror-utils";
|
||||
import { Decoration, DecorationSet } from "prosemirror-view";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { findBlockNodes } from "../queries/findChildren";
|
||||
|
||||
type MermaidState = {
|
||||
decorationSet: DecorationSet;
|
||||
@@ -135,7 +135,7 @@ export default function Mermaid({
|
||||
return new Plugin({
|
||||
key: new PluginKey("mermaid"),
|
||||
state: {
|
||||
init: (_: Plugin, { doc }) => {
|
||||
init: (_, { doc }) => {
|
||||
const pluginState: MermaidState = {
|
||||
decorationSet: DecorationSet.create(doc, []),
|
||||
diagramVisibility: {},
|
||||
@@ -208,7 +208,7 @@ export default function Mermaid({
|
||||
},
|
||||
props: {
|
||||
decorations(state) {
|
||||
return this.getState(state).decorationSet;
|
||||
return this.getState(state)?.decorationSet;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -165,6 +165,10 @@ export default class PasteHandler extends Extension {
|
||||
const paste = this.editor.pasteParser.parse(
|
||||
normalizePastedMarkdown(text)
|
||||
);
|
||||
if (!paste) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const slice = paste.slice(0);
|
||||
const tr = view.state.tr;
|
||||
let currentPos = view.state.selection.from;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Extension, { Command } from "../lib/Extension";
|
||||
import { Command } from "prosemirror-state";
|
||||
import Extension from "../lib/Extension";
|
||||
|
||||
export default class PreventTab extends Extension {
|
||||
get name() {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { flattenDeep, padStart } from "lodash";
|
||||
import { Node } from "prosemirror-model";
|
||||
import { Plugin, PluginKey, Transaction } from "prosemirror-state";
|
||||
import { findBlockNodes } from "prosemirror-utils";
|
||||
import { Decoration, DecorationSet } from "prosemirror-view";
|
||||
import refractor from "refractor/core";
|
||||
import { findBlockNodes } from "../queries/findChildren";
|
||||
|
||||
export const LANGUAGES = {
|
||||
none: "None", // additional entry to disable highlighting
|
||||
@@ -75,18 +75,23 @@ function getDecorations({
|
||||
function parseNodes(
|
||||
nodes: refractor.RefractorNode[],
|
||||
classNames: string[] = []
|
||||
): any {
|
||||
return nodes.map((node) => {
|
||||
if (node.type === "element") {
|
||||
const classes = [...classNames, ...(node.properties.className || [])];
|
||||
return parseNodes(node.children, classes);
|
||||
}
|
||||
): {
|
||||
text: string;
|
||||
classes: string[];
|
||||
}[] {
|
||||
return flattenDeep(
|
||||
nodes.map((node) => {
|
||||
if (node.type === "element") {
|
||||
const classes = [...classNames, ...(node.properties.className || [])];
|
||||
return parseNodes(node.children, classes);
|
||||
}
|
||||
|
||||
return {
|
||||
text: node.value,
|
||||
classes: classNames,
|
||||
};
|
||||
});
|
||||
return {
|
||||
text: node.value,
|
||||
classes: classNames,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
blocks.forEach((block) => {
|
||||
@@ -125,7 +130,7 @@ function getDecorations({
|
||||
}
|
||||
|
||||
const nodes = refractor.highlight(block.node.textContent, language);
|
||||
const newDecorations = flattenDeep(parseNodes(nodes))
|
||||
const newDecorations = parseNodes(nodes)
|
||||
.map((node: ParsedNode) => {
|
||||
const from = startPos;
|
||||
const to = from + node.text.length;
|
||||
@@ -180,7 +185,7 @@ export default function Prism({
|
||||
return new Plugin({
|
||||
key: new PluginKey("prism"),
|
||||
state: {
|
||||
init: (_: Plugin, { doc }) => DecorationSet.create(doc, []),
|
||||
init: (_, { doc }) => DecorationSet.create(doc, []),
|
||||
apply: (transaction: Transaction, decorationSet, oldState, state) => {
|
||||
const nodeName = state.selection.$head.parent.type.name;
|
||||
const previousNodeName = oldState.selection.$head.parent.type.name;
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import { PluginSimple } from "markdown-it";
|
||||
import { InputRule } from "prosemirror-inputrules";
|
||||
import { NodeType, MarkType, Schema } from "prosemirror-model";
|
||||
import { EditorState, Plugin } from "prosemirror-state";
|
||||
import { EditorView } from "prosemirror-view";
|
||||
import { Command, Plugin } from "prosemirror-state";
|
||||
import { Editor } from "../../../app/editor";
|
||||
import { Dispatch } from "../types";
|
||||
|
||||
export type Command = (state: EditorState, dispatch: Dispatch) => boolean;
|
||||
|
||||
export type CommandFactory = (
|
||||
attrs?: Record<string, any>
|
||||
) => (state: EditorState, dispatch: Dispatch, view: EditorView) => boolean;
|
||||
export type CommandFactory = (attrs?: Record<string, any>) => Command;
|
||||
|
||||
export default class Extension {
|
||||
options: any;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PluginSimple } from "markdown-it";
|
||||
import { keymap } from "prosemirror-keymap";
|
||||
import { MarkdownParser, TokenConfig } from "prosemirror-markdown";
|
||||
import { MarkdownParser } from "prosemirror-markdown";
|
||||
import { Schema } from "prosemirror-model";
|
||||
import { EditorView } from "prosemirror-view";
|
||||
import { Editor } from "~/editor";
|
||||
@@ -110,19 +110,19 @@ export default class ExtensionManager {
|
||||
rules?: Record<string, any>;
|
||||
plugins?: PluginSimple[];
|
||||
}): MarkdownParser {
|
||||
const tokens: Record<string, TokenConfig> = this.extensions
|
||||
const tokens = this.extensions
|
||||
.filter(
|
||||
(extension) => extension.type === "mark" || extension.type === "node"
|
||||
)
|
||||
.reduce((nodes, extension: Node | Mark) => {
|
||||
const md = extension.parseMarkdown();
|
||||
if (!md) {
|
||||
const parseSpec = extension.parseMarkdown();
|
||||
if (!parseSpec) {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
return {
|
||||
...nodes,
|
||||
[extension.markdownToken || extension.name]: md,
|
||||
[extension.markdownToken || extension.name]: parseSpec,
|
||||
};
|
||||
}, {});
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { EditorState, Transaction } from "prosemirror-state";
|
||||
import { Dispatch } from "../types";
|
||||
import { Command, Transaction } from "prosemirror-state";
|
||||
|
||||
export default function chainTransactions(
|
||||
...commands: ((state: EditorState, dispatch?: Dispatch) => boolean)[]
|
||||
) {
|
||||
return (state: EditorState, dispatch?: Dispatch): boolean => {
|
||||
export default function chainTransactions(...commands: Command[]): Command {
|
||||
return (state, dispatch): boolean => {
|
||||
const dispatcher = (tr: Transaction): void => {
|
||||
state = state.apply(tr);
|
||||
dispatch?.(tr);
|
||||
|
||||
@@ -95,7 +95,7 @@ export function findPlaceholder(
|
||||
state: EditorState,
|
||||
id: string
|
||||
): [number, number] | null {
|
||||
const decos: DecorationSet = uploadPlaceholder.getState(state);
|
||||
const found = decos.find(undefined, undefined, (spec) => spec.id === id);
|
||||
return found.length ? [found[0].from, found[0].to] : null;
|
||||
const decos = uploadPlaceholder.getState(state);
|
||||
const found = decos?.find(undefined, undefined, (spec) => spec.id === id);
|
||||
return found?.length ? [found[0].from, found[0].to] : null;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { toggleMark } from "prosemirror-commands";
|
||||
import { MarkSpec, MarkType, Schema } from "prosemirror-model";
|
||||
import { EditorState, Plugin } from "prosemirror-state";
|
||||
import { Command, Plugin } from "prosemirror-state";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import collapseSelection from "../commands/collapseSelection";
|
||||
import { Command } from "../lib/Extension";
|
||||
import chainTransactions from "../lib/chainTransactions";
|
||||
import isMarkActive from "../queries/isMarkActive";
|
||||
import { Dispatch } from "../types";
|
||||
import Mark from "./Mark";
|
||||
|
||||
export default class Comment extends Mark {
|
||||
@@ -32,7 +30,7 @@ export default class Comment extends Mark {
|
||||
keys({ type }: { type: MarkType }): Record<string, Command> {
|
||||
return this.options.onCreateCommentMark
|
||||
? {
|
||||
"Mod-Alt-m": (state: EditorState, dispatch: Dispatch) => {
|
||||
"Mod-Alt-m": (state, dispatch) => {
|
||||
if (isMarkActive(state.schema.marks.comment)(state)) {
|
||||
return false;
|
||||
}
|
||||
@@ -53,7 +51,7 @@ export default class Comment extends Mark {
|
||||
|
||||
commands({ type }: { type: MarkType; schema: Schema }) {
|
||||
return this.options.onCreateCommentMark
|
||||
? () => (state: EditorState, dispatch: Dispatch) => {
|
||||
? (): Command => (state, dispatch) => {
|
||||
if (isMarkActive(state.schema.marks.comment)(state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { toggleMark } from "prosemirror-commands";
|
||||
import { InputRule } from "prosemirror-inputrules";
|
||||
import { MarkSpec, MarkType } from "prosemirror-model";
|
||||
import { Command } from "../lib/Extension";
|
||||
import { Command } from "prosemirror-state";
|
||||
import markInputRule from "../lib/markInputRule";
|
||||
import Mark from "./Mark";
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
Node,
|
||||
Mark as ProsemirrorMark,
|
||||
} from "prosemirror-model";
|
||||
import { EditorState, Plugin } from "prosemirror-state";
|
||||
import { Command, EditorState, Plugin } from "prosemirror-state";
|
||||
import { Decoration, DecorationSet, EditorView } from "prosemirror-view";
|
||||
import * as React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
@@ -17,7 +17,7 @@ import { isExternalUrl, sanitizeUrl } from "../../utils/urls";
|
||||
import findLinkNodes from "../queries/findLinkNodes";
|
||||
import getMarkRange from "../queries/getMarkRange";
|
||||
import isMarkActive from "../queries/isMarkActive";
|
||||
import { EventType, Dispatch } from "../types";
|
||||
import { EventType } from "../types";
|
||||
import Mark from "./Mark";
|
||||
|
||||
const LINK_INPUT_REGEX = /\[([^[]+)]\((\S+)\)$/;
|
||||
@@ -113,9 +113,9 @@ export default class Link extends Mark {
|
||||
];
|
||||
}
|
||||
|
||||
keys({ type }: { type: MarkType }) {
|
||||
keys({ type }: { type: MarkType }): Record<string, Command> {
|
||||
return {
|
||||
"Mod-k": (state: EditorState, dispatch: Dispatch) => {
|
||||
"Mod-k": (state, dispatch) => {
|
||||
if (state.selection.empty) {
|
||||
this.editor.events.emit(EventType.LinkToolbarOpen);
|
||||
return true;
|
||||
@@ -123,7 +123,7 @@ export default class Link extends Mark {
|
||||
|
||||
return toggleMark(type, { href: "" })(state, dispatch);
|
||||
},
|
||||
"Mod-Enter": (state: EditorState) => {
|
||||
"Mod-Enter": (state) => {
|
||||
if (isMarkActive(type)(state)) {
|
||||
const range = getMarkRange(
|
||||
state.selection.$from,
|
||||
@@ -303,7 +303,7 @@ export default class Link extends Mark {
|
||||
? ">"
|
||||
: "](" +
|
||||
state.esc(mark.attrs.href) +
|
||||
(mark.attrs.title ? " " + state.quote(mark.attrs.title) : "") +
|
||||
(mark.attrs.title ? " " + this.quote(mark.attrs.title) : "") +
|
||||
")";
|
||||
},
|
||||
};
|
||||
@@ -318,4 +318,10 @@ export default class Link extends Mark {
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
private quote(str: string) {
|
||||
const wrap =
|
||||
str.indexOf('"') === -1 ? '""' : str.indexOf("'") === -1 ? "''" : "()";
|
||||
return wrap[0] + str + wrap[1];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { toggleMark } from "prosemirror-commands";
|
||||
import { InputRule } from "prosemirror-inputrules";
|
||||
import { TokenConfig } from "prosemirror-markdown";
|
||||
import { ParseSpec } from "prosemirror-markdown";
|
||||
import {
|
||||
MarkSpec,
|
||||
MarkType,
|
||||
Node as ProsemirrorNode,
|
||||
Schema,
|
||||
} from "prosemirror-model";
|
||||
import Extension, { Command, CommandFactory } from "../lib/Extension";
|
||||
import { Command } from "prosemirror-state";
|
||||
import Extension, { CommandFactory } from "../lib/Extension";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
|
||||
export default abstract class Mark extends Extension {
|
||||
@@ -35,7 +36,7 @@ export default abstract class Mark extends Extension {
|
||||
throw new Error("toMarkdown not implemented");
|
||||
}
|
||||
|
||||
parseMarkdown(): TokenConfig | void {
|
||||
parseMarkdown(): ParseSpec | void {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { wrappingInputRule } from "prosemirror-inputrules";
|
||||
import { NodeSpec, Node as ProsemirrorNode, NodeType } from "prosemirror-model";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import { Command } from "prosemirror-state";
|
||||
import toggleWrap from "../commands/toggleWrap";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import isNodeActive from "../queries/isNodeActive";
|
||||
import { Dispatch } from "../types";
|
||||
import Node from "./Node";
|
||||
|
||||
export default class Blockquote extends Node {
|
||||
@@ -34,17 +33,17 @@ export default class Blockquote extends Node {
|
||||
return () => toggleWrap(type);
|
||||
}
|
||||
|
||||
keys({ type }: { type: NodeType }) {
|
||||
keys({ type }: { type: NodeType }): Record<string, Command> {
|
||||
return {
|
||||
"Ctrl->": toggleWrap(type),
|
||||
"Mod-]": toggleWrap(type),
|
||||
"Shift-Enter": (state: EditorState, dispatch: Dispatch) => {
|
||||
"Shift-Enter": (state, dispatch) => {
|
||||
if (!isNodeActive(type)(state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { tr, selection } = state;
|
||||
dispatch(tr.split(selection.to));
|
||||
dispatch?.(tr.split(selection.to));
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import Token from "markdown-it/lib/token";
|
||||
import { NodeSpec, NodeType, Node as ProsemirrorNode } from "prosemirror-model";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import { Command } from "prosemirror-state";
|
||||
import * as React from "react";
|
||||
import { sanitizeUrl } from "../../utils/urls";
|
||||
import DisabledEmbed from "../components/DisabledEmbed";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import embedsRule from "../rules/embeds";
|
||||
import { ComponentProps, Dispatch } from "../types";
|
||||
import { ComponentProps } from "../types";
|
||||
import Node from "./Node";
|
||||
|
||||
const cache = {};
|
||||
@@ -114,9 +114,9 @@ export default class Embed extends Node {
|
||||
}
|
||||
|
||||
commands({ type }: { type: NodeType }) {
|
||||
return (attrs: Record<string, any>) =>
|
||||
(state: EditorState, dispatch: Dispatch) => {
|
||||
dispatch(
|
||||
return (attrs: Record<string, any>): Command =>
|
||||
(state, dispatch) => {
|
||||
dispatch?.(
|
||||
state.tr.replaceSelectionWith(type.create(attrs)).scrollIntoView()
|
||||
);
|
||||
return true;
|
||||
|
||||
@@ -6,12 +6,11 @@ import {
|
||||
NodeType,
|
||||
Schema,
|
||||
} from "prosemirror-model";
|
||||
import { EditorState, TextSelection } from "prosemirror-state";
|
||||
import { Command, TextSelection } from "prosemirror-state";
|
||||
import Suggestion from "../extensions/Suggestion";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import { SuggestionsMenuType } from "../plugins/Suggestions";
|
||||
import emojiRule from "../rules/emoji";
|
||||
import { Dispatch } from "../types";
|
||||
|
||||
export default class Emoji extends Suggestion {
|
||||
get type() {
|
||||
@@ -78,8 +77,8 @@ export default class Emoji extends Suggestion {
|
||||
}
|
||||
|
||||
commands({ type }: { type: NodeType; schema: Schema }) {
|
||||
return (attrs: Record<string, string>) =>
|
||||
(state: EditorState, dispatch: Dispatch) => {
|
||||
return (attrs: Record<string, string>): Command =>
|
||||
(state, dispatch) => {
|
||||
const { selection } = state;
|
||||
const position =
|
||||
selection instanceof TextSelection
|
||||
@@ -91,7 +90,7 @@ export default class Emoji extends Suggestion {
|
||||
|
||||
const node = type.create(attrs);
|
||||
const transaction = state.tr.insert(position, node);
|
||||
dispatch(transaction);
|
||||
dispatch?.(transaction);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { NodeSpec, NodeType } from "prosemirror-model";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import { Command } from "prosemirror-state";
|
||||
import { isInTable } from "prosemirror-tables";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import isNodeActive from "../queries/isNodeActive";
|
||||
import breakRule from "../rules/breaks";
|
||||
import { Dispatch } from "../types";
|
||||
import Node from "./Node";
|
||||
|
||||
export default class HardBreak extends Node {
|
||||
@@ -28,22 +27,24 @@ export default class HardBreak extends Node {
|
||||
}
|
||||
|
||||
commands({ type }: { type: NodeType }) {
|
||||
return () => (state: EditorState, dispatch: Dispatch) => {
|
||||
dispatch(state.tr.replaceSelectionWith(type.create()).scrollIntoView());
|
||||
return (): Command => (state, dispatch) => {
|
||||
dispatch?.(state.tr.replaceSelectionWith(type.create()).scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
keys({ type }: { type: NodeType }) {
|
||||
keys({ type }: { type: NodeType }): Record<string, Command> {
|
||||
return {
|
||||
"Shift-Enter": (state: EditorState, dispatch: Dispatch) => {
|
||||
"Shift-Enter": (state, dispatch) => {
|
||||
if (
|
||||
!isInTable(state) &&
|
||||
!isNodeActive(state.schema.nodes.paragraph)(state)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
dispatch(state.tr.replaceSelectionWith(type.create()).scrollIntoView());
|
||||
dispatch?.(
|
||||
state.tr.replaceSelectionWith(type.create()).scrollIntoView()
|
||||
);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -6,13 +6,12 @@ import {
|
||||
NodeType,
|
||||
Schema,
|
||||
} from "prosemirror-model";
|
||||
import { Plugin, Selection } from "prosemirror-state";
|
||||
import { Command, Plugin, Selection } from "prosemirror-state";
|
||||
import { Decoration, DecorationSet } from "prosemirror-view";
|
||||
import Storage from "../../utils/Storage";
|
||||
import backspaceToParagraph from "../commands/backspaceToParagraph";
|
||||
import splitHeading from "../commands/splitHeading";
|
||||
import toggleBlockType from "../commands/toggleBlockType";
|
||||
import { Command } from "../lib/Extension";
|
||||
import headingToSlug, { headingToPersistenceKey } from "../lib/headingToSlug";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import { FoldingHeadersPlugin } from "../plugins/FoldingHeaders";
|
||||
@@ -119,7 +118,10 @@ export default class Heading extends Node {
|
||||
|
||||
handleFoldContent = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
if (!(event.currentTarget instanceof HTMLButtonElement)) {
|
||||
if (
|
||||
!(event.currentTarget instanceof HTMLButtonElement) ||
|
||||
event.button !== 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import Token from "markdown-it/lib/token";
|
||||
import { InputRule } from "prosemirror-inputrules";
|
||||
import { NodeSpec, NodeType, Node as ProsemirrorNode } from "prosemirror-model";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import { Command } from "prosemirror-state";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import { Dispatch } from "../types";
|
||||
import Node from "./Node";
|
||||
|
||||
export default class HorizontalRule extends Node {
|
||||
@@ -28,19 +27,21 @@ export default class HorizontalRule extends Node {
|
||||
}
|
||||
|
||||
commands({ type }: { type: NodeType }) {
|
||||
return (attrs: Record<string, any>) =>
|
||||
(state: EditorState, dispatch: Dispatch) => {
|
||||
dispatch(
|
||||
return (attrs: Record<string, any>): Command =>
|
||||
(state, dispatch) => {
|
||||
dispatch?.(
|
||||
state.tr.replaceSelectionWith(type.create(attrs)).scrollIntoView()
|
||||
);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
keys({ type }: { type: NodeType }) {
|
||||
keys({ type }: { type: NodeType }): Record<string, Command> {
|
||||
return {
|
||||
"Mod-_": (state: EditorState, dispatch: Dispatch) => {
|
||||
dispatch(state.tr.replaceSelectionWith(type.create()).scrollIntoView());
|
||||
"Mod-_": (state, dispatch) => {
|
||||
dispatch?.(
|
||||
state.tr.replaceSelectionWith(type.create()).scrollIntoView()
|
||||
);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import Token from "markdown-it/lib/token";
|
||||
import { InputRule } from "prosemirror-inputrules";
|
||||
import { Node as ProsemirrorNode, NodeSpec, NodeType } from "prosemirror-model";
|
||||
import { NodeSelection, EditorState, Plugin } from "prosemirror-state";
|
||||
import { NodeSelection, Plugin, Command } from "prosemirror-state";
|
||||
import * as React from "react";
|
||||
import { sanitizeUrl } from "../../utils/urls";
|
||||
import { default as ImageComponent, Caption } from "../components/Image";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import { ComponentProps, Dispatch } from "../types";
|
||||
import { ComponentProps } from "../types";
|
||||
import SimpleImage from "./SimpleImage";
|
||||
|
||||
const imageSizeRegex = /\s=(\d+)?x(\d+)?$/;
|
||||
@@ -282,7 +282,7 @@ export default class Image extends SimpleImage {
|
||||
commands({ type }: { type: NodeType }) {
|
||||
return {
|
||||
...super.commands({ type }),
|
||||
downloadImage: () => (state: EditorState) => {
|
||||
downloadImage: (): Command => (state) => {
|
||||
if (!(state.selection instanceof NodeSelection)) {
|
||||
return false;
|
||||
}
|
||||
@@ -296,7 +296,7 @@ export default class Image extends SimpleImage {
|
||||
|
||||
return true;
|
||||
},
|
||||
alignRight: () => (state: EditorState, dispatch: Dispatch) => {
|
||||
alignRight: (): Command => (state, dispatch) => {
|
||||
if (!(state.selection instanceof NodeSelection)) {
|
||||
return false;
|
||||
}
|
||||
@@ -306,10 +306,10 @@ export default class Image extends SimpleImage {
|
||||
layoutClass: "right-50",
|
||||
};
|
||||
const { selection } = state;
|
||||
dispatch(state.tr.setNodeMarkup(selection.from, undefined, attrs));
|
||||
dispatch?.(state.tr.setNodeMarkup(selection.from, undefined, attrs));
|
||||
return true;
|
||||
},
|
||||
alignLeft: () => (state: EditorState, dispatch: Dispatch) => {
|
||||
alignLeft: (): Command => (state, dispatch) => {
|
||||
if (!(state.selection instanceof NodeSelection)) {
|
||||
return false;
|
||||
}
|
||||
@@ -319,10 +319,10 @@ export default class Image extends SimpleImage {
|
||||
layoutClass: "left-50",
|
||||
};
|
||||
const { selection } = state;
|
||||
dispatch(state.tr.setNodeMarkup(selection.from, undefined, attrs));
|
||||
dispatch?.(state.tr.setNodeMarkup(selection.from, undefined, attrs));
|
||||
return true;
|
||||
},
|
||||
alignFullWidth: () => (state: EditorState, dispatch: Dispatch) => {
|
||||
alignFullWidth: (): Command => (state, dispatch) => {
|
||||
if (!(state.selection instanceof NodeSelection)) {
|
||||
return false;
|
||||
}
|
||||
@@ -332,16 +332,16 @@ export default class Image extends SimpleImage {
|
||||
layoutClass: "full-width",
|
||||
};
|
||||
const { selection } = state;
|
||||
dispatch(state.tr.setNodeMarkup(selection.from, undefined, attrs));
|
||||
dispatch?.(state.tr.setNodeMarkup(selection.from, undefined, attrs));
|
||||
return true;
|
||||
},
|
||||
alignCenter: () => (state: EditorState, dispatch: Dispatch) => {
|
||||
alignCenter: (): Command => (state, dispatch) => {
|
||||
if (!(state.selection instanceof NodeSelection)) {
|
||||
return false;
|
||||
}
|
||||
const attrs = { ...state.selection.node.attrs, layoutClass: null };
|
||||
const { selection } = state;
|
||||
dispatch(state.tr.setNodeMarkup(selection.from, undefined, attrs));
|
||||
dispatch?.(state.tr.setNodeMarkup(selection.from, undefined, attrs));
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -9,14 +9,14 @@ import {
|
||||
EditorState,
|
||||
Plugin,
|
||||
TextSelection,
|
||||
Command,
|
||||
} from "prosemirror-state";
|
||||
import { findParentNodeClosestToPos } from "prosemirror-utils";
|
||||
import { DecorationSet, Decoration } from "prosemirror-view";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import { findParentNodeClosestToPos } from "../queries/findParentNode";
|
||||
import getParentListItem from "../queries/getParentListItem";
|
||||
import isInList from "../queries/isInList";
|
||||
import isList from "../queries/isList";
|
||||
import { Dispatch } from "../types";
|
||||
import Node from "./Node";
|
||||
|
||||
export default class ListItem extends Node {
|
||||
@@ -200,14 +200,14 @@ export default class ListItem extends Node {
|
||||
};
|
||||
}
|
||||
|
||||
keys({ type }: { type: NodeType }) {
|
||||
keys({ type }: { type: NodeType }): Record<string, Command> {
|
||||
return {
|
||||
Enter: splitListItem(type),
|
||||
Tab: sinkListItem(type),
|
||||
"Shift-Tab": liftListItem(type),
|
||||
"Mod-]": sinkListItem(type),
|
||||
"Mod-[": liftListItem(type),
|
||||
"Shift-Enter": (state: EditorState, dispatch: Dispatch) => {
|
||||
"Shift-Enter": (state, dispatch) => {
|
||||
if (!isInList(state)) {
|
||||
return false;
|
||||
}
|
||||
@@ -216,10 +216,10 @@ export default class ListItem extends Node {
|
||||
}
|
||||
|
||||
const { tr, selection } = state;
|
||||
dispatch(tr.split(selection.to));
|
||||
dispatch?.(tr.split(selection.to));
|
||||
return true;
|
||||
},
|
||||
"Alt-ArrowUp": (state: EditorState, dispatch: Dispatch) => {
|
||||
"Alt-ArrowUp": (state, dispatch) => {
|
||||
if (!state.selection.empty) {
|
||||
return false;
|
||||
}
|
||||
@@ -241,7 +241,7 @@ export default class ListItem extends Node {
|
||||
const { tr } = state;
|
||||
const newPos = pos - $pos.nodeBefore.nodeSize;
|
||||
|
||||
dispatch(
|
||||
dispatch?.(
|
||||
tr
|
||||
.delete(pos, pos + li.nodeSize)
|
||||
.insert(newPos, li)
|
||||
@@ -249,7 +249,7 @@ export default class ListItem extends Node {
|
||||
);
|
||||
return true;
|
||||
},
|
||||
"Alt-ArrowDown": (state: EditorState, dispatch: Dispatch) => {
|
||||
"Alt-ArrowDown": (state, dispatch) => {
|
||||
if (!state.selection.empty) {
|
||||
return false;
|
||||
}
|
||||
@@ -271,7 +271,7 @@ export default class ListItem extends Node {
|
||||
const { tr } = state;
|
||||
const newPos = pos + li.nodeSize + $pos.nodeAfter.nodeSize;
|
||||
|
||||
dispatch(
|
||||
dispatch?.(
|
||||
tr
|
||||
.insert(newPos, li)
|
||||
.setSelection(TextSelection.near(tr.doc.resolve(newPos)))
|
||||
|
||||
@@ -17,11 +17,10 @@ import {
|
||||
Schema,
|
||||
Node as ProsemirrorNode,
|
||||
} from "prosemirror-model";
|
||||
import { EditorState, Plugin } from "prosemirror-state";
|
||||
import { Command, Plugin } from "prosemirror-state";
|
||||
import MathPlugin from "../extensions/Math";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import mathRule, { REGEX_INLINE_MATH_DOLLARS } from "../rules/math";
|
||||
import { Dispatch } from "../types";
|
||||
import Node from "./Node";
|
||||
|
||||
export default class Math extends Node {
|
||||
@@ -34,8 +33,8 @@ export default class Math extends Node {
|
||||
}
|
||||
|
||||
commands({ type }: { type: NodeType }) {
|
||||
return () => (state: EditorState, dispatch: Dispatch) => {
|
||||
dispatch(state.tr.replaceSelectionWith(type.create()).scrollIntoView());
|
||||
return (): Command => (state, dispatch) => {
|
||||
dispatch?.(state.tr.replaceSelectionWith(type.create()).scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,10 +4,9 @@ import {
|
||||
} from "@benrbray/prosemirror-math";
|
||||
import { PluginSimple } from "markdown-it";
|
||||
import { NodeSpec, NodeType, Node as ProsemirrorNode } from "prosemirror-model";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import { Command } from "prosemirror-state";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import mathRule, { REGEX_BLOCK_MATH_DOLLARS } from "../rules/math";
|
||||
import { Dispatch } from "../types";
|
||||
import Node from "./Node";
|
||||
|
||||
export default class MathBlock extends Node {
|
||||
@@ -24,8 +23,8 @@ export default class MathBlock extends Node {
|
||||
}
|
||||
|
||||
commands({ type }: { type: NodeType }) {
|
||||
return () => (state: EditorState, dispatch: Dispatch) => {
|
||||
dispatch(state.tr.replaceSelectionWith(type.create()).scrollIntoView());
|
||||
return (): Command => (state, dispatch) => {
|
||||
dispatch?.(state.tr.replaceSelectionWith(type.create()).scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,12 +5,11 @@ import {
|
||||
NodeType,
|
||||
Schema,
|
||||
} from "prosemirror-model";
|
||||
import { EditorState, TextSelection } from "prosemirror-state";
|
||||
import { Command, TextSelection } from "prosemirror-state";
|
||||
import Suggestion from "../extensions/Suggestion";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import { SuggestionsMenuType } from "../plugins/Suggestions";
|
||||
import mentionRule from "../rules/mention";
|
||||
import { Dispatch } from "../types";
|
||||
|
||||
export default class Mention extends Suggestion {
|
||||
get type() {
|
||||
@@ -80,8 +79,8 @@ export default class Mention extends Suggestion {
|
||||
}
|
||||
|
||||
commands({ type }: { type: NodeType; schema: Schema }) {
|
||||
return (attrs: Record<string, string>) =>
|
||||
(state: EditorState, dispatch: Dispatch) => {
|
||||
return (attrs: Record<string, string>): Command =>
|
||||
(state, dispatch) => {
|
||||
const { selection } = state;
|
||||
const position =
|
||||
selection instanceof TextSelection
|
||||
@@ -93,7 +92,7 @@ export default class Mention extends Suggestion {
|
||||
|
||||
const node = type.create(attrs);
|
||||
const transaction = state.tr.insert(position, node);
|
||||
dispatch(transaction);
|
||||
dispatch?.(transaction);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { InputRule } from "prosemirror-inputrules";
|
||||
import { TokenConfig } from "prosemirror-markdown";
|
||||
import { ParseSpec } from "prosemirror-markdown";
|
||||
import {
|
||||
NodeSpec,
|
||||
Node as ProsemirrorNode,
|
||||
NodeType,
|
||||
Schema,
|
||||
} from "prosemirror-model";
|
||||
import Extension, { Command, CommandFactory } from "../lib/Extension";
|
||||
import { Command } from "prosemirror-state";
|
||||
import Extension, { CommandFactory } from "../lib/Extension";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
|
||||
export default abstract class Node extends Extension {
|
||||
@@ -41,7 +42,7 @@ export default abstract class Node extends Extension {
|
||||
throw new Error("toMarkdown not implemented");
|
||||
}
|
||||
|
||||
parseMarkdown(): TokenConfig | void {
|
||||
parseMarkdown(): ParseSpec | void {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { setBlockType } from "prosemirror-commands";
|
||||
import { NodeSpec, NodeType, Node as ProsemirrorNode } from "prosemirror-model";
|
||||
import deleteEmptyFirstParagraph from "../commands/deleteEmptyFirstParagraph";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import Node from "./Node";
|
||||
|
||||
@@ -20,6 +21,7 @@ export default class Paragraph extends Node {
|
||||
keys({ type }: { type: NodeType }) {
|
||||
return {
|
||||
"Shift-Ctrl-0": setBlockType(type),
|
||||
Backspace: deleteEmptyFirstParagraph,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Token from "markdown-it/lib/token";
|
||||
import { InputRule } from "prosemirror-inputrules";
|
||||
import { Node as ProsemirrorNode, NodeSpec, NodeType } from "prosemirror-model";
|
||||
import { TextSelection, NodeSelection, EditorState } from "prosemirror-state";
|
||||
import { TextSelection, NodeSelection, Command } from "prosemirror-state";
|
||||
import * as React from "react";
|
||||
import { getEventFiles } from "../../utils/files";
|
||||
import { sanitizeUrl } from "../../utils/urls";
|
||||
@@ -11,7 +11,7 @@ import { default as ImageComponent } from "../components/Image";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import uploadPlaceholderPlugin from "../lib/uploadPlaceholder";
|
||||
import uploadPlugin from "../lib/uploadPlugin";
|
||||
import { ComponentProps, Dispatch } from "../types";
|
||||
import { ComponentProps } from "../types";
|
||||
import Node from "./Node";
|
||||
|
||||
export default class SimpleImage extends Node {
|
||||
@@ -171,11 +171,11 @@ export default class SimpleImage extends Node {
|
||||
|
||||
commands({ type }: { type: NodeType }) {
|
||||
return {
|
||||
deleteImage: () => (state: EditorState, dispatch: Dispatch) => {
|
||||
dispatch(state.tr.deleteSelection());
|
||||
deleteImage: (): Command => (state, dispatch) => {
|
||||
dispatch?.(state.tr.deleteSelection());
|
||||
return true;
|
||||
},
|
||||
replaceImage: () => (state: EditorState) => {
|
||||
replaceImage: (): Command => (state) => {
|
||||
if (!(state.selection instanceof NodeSelection)) {
|
||||
return false;
|
||||
}
|
||||
@@ -214,8 +214,8 @@ export default class SimpleImage extends Node {
|
||||
return true;
|
||||
},
|
||||
createImage:
|
||||
(attrs: Record<string, any>) =>
|
||||
(state: EditorState, dispatch: Dispatch) => {
|
||||
(attrs: Record<string, any>): Command =>
|
||||
(state, dispatch) => {
|
||||
const { selection } = state;
|
||||
const position =
|
||||
selection instanceof TextSelection
|
||||
@@ -227,7 +227,7 @@ export default class SimpleImage extends Node {
|
||||
|
||||
const node = type.create(attrs);
|
||||
const transaction = state.tr.insert(position, node);
|
||||
dispatch(transaction);
|
||||
dispatch?.(transaction);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NodeSpec, Node as ProsemirrorNode, Schema } from "prosemirror-model";
|
||||
import { EditorState, Plugin, TextSelection } from "prosemirror-state";
|
||||
import { chainCommands } from "prosemirror-commands";
|
||||
import { NodeSpec, Node as ProsemirrorNode } from "prosemirror-model";
|
||||
import { Command, Plugin, TextSelection } from "prosemirror-state";
|
||||
import {
|
||||
addColumnAfter,
|
||||
addColumnBefore,
|
||||
@@ -7,25 +8,19 @@ import {
|
||||
deleteRow,
|
||||
deleteTable,
|
||||
goToNextCell,
|
||||
isInTable,
|
||||
tableEditing,
|
||||
toggleHeaderCell,
|
||||
toggleHeaderColumn,
|
||||
toggleHeaderRow,
|
||||
CellSelection,
|
||||
} from "prosemirror-tables";
|
||||
import {
|
||||
addRowAt,
|
||||
createTable,
|
||||
getCellsInColumn,
|
||||
moveRow,
|
||||
setTextSelection,
|
||||
} from "prosemirror-utils";
|
||||
import { Decoration, DecorationSet } from "prosemirror-view";
|
||||
import {
|
||||
addRowAfterAndMoveSelection,
|
||||
setColumnAttr,
|
||||
createTable,
|
||||
} from "../commands/table";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import { getRowIndexFromText } from "../queries/getRowIndex";
|
||||
import tablesRule from "../rules/tables";
|
||||
import { Dispatch } from "../types";
|
||||
import Node from "./Node";
|
||||
|
||||
export default class Table extends Node {
|
||||
@@ -58,49 +53,32 @@ export default class Table extends Node {
|
||||
return [tablesRule];
|
||||
}
|
||||
|
||||
commands({ schema }: { schema: Schema }) {
|
||||
commands() {
|
||||
return {
|
||||
createTable:
|
||||
({ rowsCount, colsCount }: { rowsCount: number; colsCount: number }) =>
|
||||
(state: EditorState, dispatch: Dispatch) => {
|
||||
const offset = state.tr.selection.anchor + 1;
|
||||
const nodes = createTable(schema, rowsCount, colsCount);
|
||||
const tr = state.tr.replaceSelectionWith(nodes).scrollIntoView();
|
||||
const resolvedPos = tr.doc.resolve(offset);
|
||||
|
||||
tr.setSelection(TextSelection.near(resolvedPos));
|
||||
dispatch(tr);
|
||||
return true;
|
||||
},
|
||||
setColumnAttr:
|
||||
({ index, alignment }: { index: number; alignment: string }) =>
|
||||
(state: EditorState, dispatch: Dispatch) => {
|
||||
const cells = getCellsInColumn(index)(state.selection) || [];
|
||||
let transaction = state.tr;
|
||||
cells.forEach(({ pos }) => {
|
||||
transaction = transaction.setNodeMarkup(pos, undefined, {
|
||||
alignment,
|
||||
});
|
||||
});
|
||||
dispatch(transaction);
|
||||
return true;
|
||||
},
|
||||
addColumnBefore: () => addColumnBefore,
|
||||
addColumnAfter: () => addColumnAfter,
|
||||
deleteColumn: () => deleteColumn,
|
||||
addRowAfter:
|
||||
({ index }: { index: number }) =>
|
||||
(state: EditorState, dispatch: Dispatch) => {
|
||||
if (index === 0) {
|
||||
// A little hack to avoid cloning the heading row by cloning the row
|
||||
// beneath and then moving it to the right index.
|
||||
const tr = addRowAt(index + 2, true)(state.tr);
|
||||
dispatch(moveRow(index + 2, index + 1)(tr));
|
||||
} else {
|
||||
dispatch(addRowAt(index + 1, true)(state.tr));
|
||||
({
|
||||
rowsCount,
|
||||
colsCount,
|
||||
}: {
|
||||
rowsCount: number;
|
||||
colsCount: number;
|
||||
}): Command =>
|
||||
(state, dispatch) => {
|
||||
if (dispatch) {
|
||||
const offset = state.tr.selection.anchor + 1;
|
||||
const nodes = createTable(state, rowsCount, colsCount);
|
||||
const tr = state.tr.replaceSelectionWith(nodes).scrollIntoView();
|
||||
const resolvedPos = tr.doc.resolve(offset);
|
||||
tr.setSelection(TextSelection.near(resolvedPos));
|
||||
dispatch(tr);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
setColumnAttr,
|
||||
addColumnBefore: () => addColumnBefore,
|
||||
addColumnAfter: () => addColumnAfter,
|
||||
deleteColumn: () => deleteColumn,
|
||||
addRowAfter: addRowAfterAndMoveSelection,
|
||||
deleteRow: () => deleteRow,
|
||||
deleteTable: () => deleteTable,
|
||||
toggleHeaderColumn: () => toggleHeaderColumn,
|
||||
@@ -111,31 +89,9 @@ export default class Table extends Node {
|
||||
|
||||
keys() {
|
||||
return {
|
||||
Tab: goToNextCell(1),
|
||||
Tab: chainCommands(goToNextCell(1), addRowAfterAndMoveSelection()),
|
||||
"Shift-Tab": goToNextCell(-1),
|
||||
Enter: (state: EditorState, dispatch: Dispatch) => {
|
||||
if (!isInTable(state)) {
|
||||
return false;
|
||||
}
|
||||
const index = getRowIndexFromText(
|
||||
state.selection as unknown as CellSelection
|
||||
);
|
||||
|
||||
if (index === 0) {
|
||||
const cells = getCellsInColumn(0)(state.selection);
|
||||
if (!cells) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tr = addRowAt(index + 2, true)(state.tr);
|
||||
dispatch(
|
||||
setTextSelection(cells[1].pos)(moveRow(index + 2, index + 1)(tr))
|
||||
);
|
||||
} else {
|
||||
dispatch(addRowAt(index + 1, true)(state.tr));
|
||||
}
|
||||
return true;
|
||||
},
|
||||
Enter: addRowAfterAndMoveSelection(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import Token from "markdown-it/lib/token";
|
||||
import { NodeSpec } from "prosemirror-model";
|
||||
import { Plugin } from "prosemirror-state";
|
||||
import {
|
||||
isTableSelected,
|
||||
isRowSelected,
|
||||
getCellsInColumn,
|
||||
selectRow,
|
||||
selectTable,
|
||||
} from "prosemirror-utils";
|
||||
import { DecorationSet, Decoration } from "prosemirror-view";
|
||||
import { selectRow, selectTable } from "../commands/table";
|
||||
import {
|
||||
getCellsInColumn,
|
||||
isRowSelected,
|
||||
isTableSelected,
|
||||
} from "../queries/table";
|
||||
import Node from "./Node";
|
||||
|
||||
export default class TableCell extends Node {
|
||||
@@ -55,17 +54,17 @@ export default class TableCell extends Node {
|
||||
new Plugin({
|
||||
props: {
|
||||
decorations: (state) => {
|
||||
const { doc, selection } = state;
|
||||
const { doc } = state;
|
||||
const decorations: Decoration[] = [];
|
||||
const cells = getCellsInColumn(0)(selection);
|
||||
const cells = getCellsInColumn(0)(state);
|
||||
|
||||
if (cells) {
|
||||
cells.forEach(({ pos }, index) => {
|
||||
cells.forEach((pos, index) => {
|
||||
if (index === 0) {
|
||||
decorations.push(
|
||||
Decoration.widget(pos + 1, () => {
|
||||
let className = "grip-table";
|
||||
const selected = isTableSelected(selection);
|
||||
const selected = isTableSelected(state);
|
||||
if (selected) {
|
||||
className += " selected";
|
||||
}
|
||||
@@ -74,7 +73,7 @@ export default class TableCell extends Node {
|
||||
grip.addEventListener("mousedown", (event) => {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
this.editor.view.dispatch(selectTable(state.tr));
|
||||
this.editor.view.dispatch(selectTable(state));
|
||||
});
|
||||
return grip;
|
||||
})
|
||||
@@ -82,7 +81,7 @@ export default class TableCell extends Node {
|
||||
}
|
||||
decorations.push(
|
||||
Decoration.widget(pos + 1, () => {
|
||||
const rowSelected = isRowSelected(index)(selection);
|
||||
const rowSelected = isRowSelected(index)(state);
|
||||
|
||||
let className = "grip-row";
|
||||
if (rowSelected) {
|
||||
@@ -100,10 +99,7 @@ export default class TableCell extends Node {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
this.editor.view.dispatch(
|
||||
selectRow(
|
||||
index,
|
||||
event.metaKey || event.shiftKey
|
||||
)(state.tr)
|
||||
selectRow(index, event.metaKey || event.shiftKey)(state)
|
||||
);
|
||||
});
|
||||
return grip;
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import Token from "markdown-it/lib/token";
|
||||
import { NodeSpec } from "prosemirror-model";
|
||||
import { Plugin } from "prosemirror-state";
|
||||
import {
|
||||
isColumnSelected,
|
||||
getCellsInRow,
|
||||
selectColumn,
|
||||
} from "prosemirror-utils";
|
||||
import { DecorationSet, Decoration } from "prosemirror-view";
|
||||
import { selectColumn } from "../commands/table";
|
||||
import { getCellsInRow, isColumnSelected } from "../queries/table";
|
||||
|
||||
import Node from "./Node";
|
||||
|
||||
export default class TableHeadCell extends Node {
|
||||
@@ -53,15 +51,15 @@ export default class TableHeadCell extends Node {
|
||||
new Plugin({
|
||||
props: {
|
||||
decorations: (state) => {
|
||||
const { doc, selection } = state;
|
||||
const { doc } = state;
|
||||
const decorations: Decoration[] = [];
|
||||
const cells = getCellsInRow(0)(selection);
|
||||
const cells = getCellsInRow(0)(state);
|
||||
|
||||
if (cells) {
|
||||
cells.forEach(({ pos }, index) => {
|
||||
cells.forEach((pos, index) => {
|
||||
decorations.push(
|
||||
Decoration.widget(pos + 1, () => {
|
||||
const colSelected = isColumnSelected(index)(selection);
|
||||
const colSelected = isColumnSelected(index)(state);
|
||||
let className = "grip-column";
|
||||
if (colSelected) {
|
||||
className += " selected";
|
||||
@@ -80,7 +78,7 @@ export default class TableHeadCell extends Node {
|
||||
selectColumn(
|
||||
index,
|
||||
event.metaKey || event.shiftKey
|
||||
)(state.tr)
|
||||
)(state)
|
||||
);
|
||||
});
|
||||
return grip;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Plugin, PluginKey } from "prosemirror-state";
|
||||
import { findBlockNodes } from "prosemirror-utils";
|
||||
import { Decoration, DecorationSet } from "prosemirror-view";
|
||||
import Storage from "../../utils/Storage";
|
||||
import { headingToPersistenceKey } from "../lib/headingToSlug";
|
||||
import { findBlockNodes } from "../queries/findChildren";
|
||||
import findCollapsedNodes from "../queries/findCollapsedNodes";
|
||||
|
||||
export class FoldingHeadersPlugin extends Plugin {
|
||||
|
||||
57
shared/editor/queries/findChildren.ts
Normal file
57
shared/editor/queries/findChildren.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Node } from "prosemirror-model";
|
||||
|
||||
type Predicate = (node: Node) => boolean;
|
||||
|
||||
export type NodeWithPos = {
|
||||
pos: number;
|
||||
node: Node;
|
||||
};
|
||||
|
||||
export function flatten(node: Node, descend = true): NodeWithPos[] {
|
||||
if (!node) {
|
||||
throw new Error('Invalid "node" parameter');
|
||||
}
|
||||
const result: NodeWithPos[] = [];
|
||||
node.descendants((child, pos) => {
|
||||
result.push({ node: child, pos });
|
||||
if (!descend) {
|
||||
return false;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over descendants of a given `node`, returning child nodes predicate
|
||||
* returns truthy for. It doesn't descend into a node when descend argument is
|
||||
* `false` (defaults to `true`).
|
||||
*
|
||||
* @param node The node to iterate over
|
||||
* @param predicate Filtering predicate function
|
||||
* @param descend Whether to descend into a node
|
||||
* @returns Child nodes
|
||||
*/
|
||||
export function findChildren(
|
||||
node: Node,
|
||||
predicate: Predicate,
|
||||
descend = false
|
||||
) {
|
||||
if (!node) {
|
||||
throw new Error('Invalid "node" parameter');
|
||||
} else if (!predicate) {
|
||||
throw new Error('Invalid "predicate" parameter');
|
||||
}
|
||||
return flatten(node, descend).filter((child) => predicate(child.node));
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over descendants of a given `node`, returning child nodes that
|
||||
* are blocks.
|
||||
*
|
||||
* @param node The node to iterate over
|
||||
* @returns Child nodes that are blocks
|
||||
*/
|
||||
export function findBlockNodes(node: Node): NodeWithPos[] {
|
||||
return findChildren(node, (child) => child.isBlock);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Node } from "prosemirror-model";
|
||||
import { findBlockNodes, NodeWithPos } from "prosemirror-utils";
|
||||
import { findBlockNodes, NodeWithPos } from "./findChildren";
|
||||
|
||||
export default function findCollapsedNodes(doc: Node): NodeWithPos[] {
|
||||
const blocks = findBlockNodes(doc);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Node } from "prosemirror-model";
|
||||
import { findTextNodes, NodeWithPos } from "prosemirror-utils";
|
||||
import { findChildren, NodeWithPos } from "./findChildren";
|
||||
|
||||
export default function findLinkNodes(doc: Node): NodeWithPos[] {
|
||||
const textNodes = findTextNodes(doc);
|
||||
const textNodes = findChildren(doc, (child) => child.isText);
|
||||
const nodes: NodeWithPos[] = [];
|
||||
|
||||
for (const nodeWithPos of textNodes) {
|
||||
|
||||
44
shared/editor/queries/findParentNode.ts
Normal file
44
shared/editor/queries/findParentNode.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Node, ResolvedPos } from "prosemirror-model";
|
||||
import { Selection } from "prosemirror-state";
|
||||
|
||||
type Predicate = (node: Node) => boolean;
|
||||
|
||||
type ContentNodeWithPos = {
|
||||
pos: number;
|
||||
start: number;
|
||||
depth: number;
|
||||
node: Node;
|
||||
};
|
||||
|
||||
export const findParentNode =
|
||||
(predicate: Predicate) =>
|
||||
({ $from }: Selection) =>
|
||||
findParentNodeClosestToPos($from, predicate);
|
||||
|
||||
/**
|
||||
* Iterates over parent nodes starting from the given `$pos`, returning the
|
||||
* closest node and its start position `predicate` returns truthy for. `start`
|
||||
* points to the start position of the node, `pos` points directly before the node.
|
||||
*
|
||||
* @param $pos position to start from
|
||||
* @param predicate filtering predicate function
|
||||
* @returns node and its start position
|
||||
*/
|
||||
export const findParentNodeClosestToPos = (
|
||||
$pos: ResolvedPos,
|
||||
predicate: Predicate
|
||||
): ContentNodeWithPos | undefined => {
|
||||
for (let i = $pos.depth; i > 0; i--) {
|
||||
const node = $pos.node(i);
|
||||
if (predicate(node)) {
|
||||
return {
|
||||
pos: i > 0 ? $pos.before(i) : 0,
|
||||
start: $pos.start(i),
|
||||
depth: i,
|
||||
node,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
import { CellSelection } from "prosemirror-tables";
|
||||
|
||||
export default function getColumnIndex(selection: CellSelection) {
|
||||
const isColSelection = selection.isColSelection && selection.isColSelection();
|
||||
if (!isColSelection) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const path = (selection.$from as any).path;
|
||||
return path[path.length - 5];
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { CellSelection } from "prosemirror-tables";
|
||||
|
||||
export default function getRowIndex(selection: CellSelection) {
|
||||
const isRowSelection = selection.isRowSelection && selection.isRowSelection();
|
||||
if (!isRowSelection) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const path = (selection.$from as any).path;
|
||||
return path[path.length - 8];
|
||||
}
|
||||
|
||||
export function getRowIndexFromText(selection: CellSelection) {
|
||||
const isRowSelection = selection.isRowSelection && selection.isRowSelection();
|
||||
const path = (selection.$from as any).path;
|
||||
if (isRowSelection) {
|
||||
return path[path.length - 8];
|
||||
} else {
|
||||
return path[path.length - 11];
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NodeType } from "prosemirror-model";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import { findParentNode, findSelectedNodeOfType } from "prosemirror-utils";
|
||||
import { findParentNode } from "./findParentNode";
|
||||
|
||||
const isNodeActive =
|
||||
(type: NodeType, attrs: Record<string, any> = {}) =>
|
||||
@@ -9,15 +9,21 @@ const isNodeActive =
|
||||
return false;
|
||||
}
|
||||
|
||||
const node =
|
||||
findSelectedNodeOfType(type)(state.selection) ||
|
||||
findParentNode((node) => node.type === type)(state.selection);
|
||||
const nodeAfter = state.selection.$from.nodeAfter;
|
||||
let node = nodeAfter?.type === type ? nodeAfter : undefined;
|
||||
|
||||
if (!node) {
|
||||
const parent = findParentNode((node) => node.type === type)(
|
||||
state.selection
|
||||
);
|
||||
node = parent?.node;
|
||||
}
|
||||
|
||||
if (!Object.keys(attrs).length || !node) {
|
||||
return !!node;
|
||||
}
|
||||
|
||||
return node.node.hasMarkup(type, { ...node.node.attrs, ...attrs });
|
||||
return node.hasMarkup(type, { ...node.attrs, ...attrs });
|
||||
};
|
||||
|
||||
export default isNodeActive;
|
||||
|
||||
95
shared/editor/queries/table.ts
Normal file
95
shared/editor/queries/table.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import { CellSelection, isInTable, selectedRect } from "prosemirror-tables";
|
||||
|
||||
export function getColumnIndex(state: EditorState): number | undefined {
|
||||
if (state.selection instanceof CellSelection) {
|
||||
if (state.selection.isColSelection()) {
|
||||
const rect = selectedRect(state);
|
||||
return rect.left;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getRowIndex(state: EditorState): number | undefined {
|
||||
if (state.selection instanceof CellSelection) {
|
||||
if (state.selection.isRowSelection()) {
|
||||
const rect = selectedRect(state);
|
||||
return rect.top;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getCellsInColumn(index: number) {
|
||||
return (state: EditorState): number[] => {
|
||||
if (!isInTable(state)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const rect = selectedRect(state);
|
||||
const cells = [];
|
||||
|
||||
for (let i = index; i < rect.map.map.length; i += rect.map.width) {
|
||||
const cell = rect.tableStart + rect.map.map[i];
|
||||
cells.push(cell);
|
||||
}
|
||||
return cells;
|
||||
};
|
||||
}
|
||||
|
||||
export function getCellsInRow(index: number) {
|
||||
return (state: EditorState): number[] => {
|
||||
if (!isInTable(state)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const rect = selectedRect(state);
|
||||
const cells = [];
|
||||
|
||||
for (let i = 0; i < rect.map.width; i += 1) {
|
||||
const cell = rect.tableStart + rect.map.map[index * rect.map.width + i];
|
||||
cells.push(cell);
|
||||
}
|
||||
return cells;
|
||||
};
|
||||
}
|
||||
|
||||
export function isColumnSelected(index: number) {
|
||||
return (state: EditorState): boolean => {
|
||||
if (state.selection instanceof CellSelection) {
|
||||
if (state.selection.isColSelection()) {
|
||||
const rect = selectedRect(state);
|
||||
return rect.left <= index && rect.right > index;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
export function isRowSelected(index: number) {
|
||||
return (state: EditorState): boolean => {
|
||||
if (state.selection instanceof CellSelection) {
|
||||
if (state.selection.isRowSelection()) {
|
||||
const rect = selectedRect(state);
|
||||
return rect.top <= index && rect.bottom > index;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
export function isTableSelected(state: EditorState): boolean {
|
||||
const rect = selectedRect(state);
|
||||
|
||||
return (
|
||||
rect.top === 0 &&
|
||||
rect.left === 0 &&
|
||||
rect.bottom === rect.map.height &&
|
||||
rect.right === rect.map.width
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Node as ProsemirrorNode } from "prosemirror-model";
|
||||
import { EditorState, Transaction } from "prosemirror-state";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import { EditorView } from "prosemirror-view";
|
||||
import * as React from "react";
|
||||
import { DefaultTheme } from "styled-components";
|
||||
@@ -34,5 +34,3 @@ export type ComponentProps = {
|
||||
isEditable: boolean;
|
||||
getPos: () => number;
|
||||
};
|
||||
|
||||
export type Dispatch = (tr: Transaction) => void;
|
||||
|
||||
Reference in New Issue
Block a user