mirror of
https://github.com/outline/outline.git
synced 2025-12-21 10:39:41 -06:00
chore: Replace custom toPlainText serialization with leafText (#10039)
This commit is contained in:
@@ -44,9 +44,7 @@ export default class ClipboardTextSerializer extends Extension {
|
||||
softBreak: true,
|
||||
})
|
||||
: slice.content.content
|
||||
.map((node) =>
|
||||
ProsemirrorHelper.toPlainText(node, this.editor.schema)
|
||||
)
|
||||
.map((node) => ProsemirrorHelper.toPlainText(node))
|
||||
.join("");
|
||||
},
|
||||
},
|
||||
|
||||
@@ -35,7 +35,6 @@ import Extension, {
|
||||
import ExtensionManager from "@shared/editor/lib/ExtensionManager";
|
||||
import { MarkdownSerializer } from "@shared/editor/lib/markdown/serializer";
|
||||
import textBetween from "@shared/editor/lib/textBetween";
|
||||
import { getTextSerializers } from "@shared/editor/lib/textSerializers";
|
||||
import Mark from "@shared/editor/marks/Mark";
|
||||
import { basicExtensions as extensions } from "@shared/editor/nodes";
|
||||
import Node from "@shared/editor/nodes/Node";
|
||||
@@ -627,8 +626,7 @@ export class Editor extends React.PureComponent<
|
||||
*
|
||||
* @returns A list of headings in the document
|
||||
*/
|
||||
public getHeadings = () =>
|
||||
ProsemirrorHelper.getHeadings(this.view.state.doc, this.schema);
|
||||
public getHeadings = () => ProsemirrorHelper.getHeadings(this.view.state.doc);
|
||||
|
||||
/**
|
||||
* Return the images in the current editor.
|
||||
@@ -721,9 +719,8 @@ export class Editor extends React.PureComponent<
|
||||
*/
|
||||
public getPlainText = () => {
|
||||
const { doc } = this.view.state;
|
||||
const textSerializers = getTextSerializers(this.schema);
|
||||
|
||||
return textBetween(doc, 0, doc.content.size, textSerializers);
|
||||
return textBetween(doc, 0, doc.content.size);
|
||||
};
|
||||
|
||||
private dispatchThemeChanged = (event: CustomEvent) => {
|
||||
|
||||
@@ -723,8 +723,7 @@ export default class Document extends ArchivableModel implements Searchable {
|
||||
marks: extensionManager.marks,
|
||||
});
|
||||
const text = ProsemirrorHelper.toPlainText(
|
||||
Node.fromJSON(schema, this.data),
|
||||
schema
|
||||
Node.fromJSON(schema, this.data)
|
||||
);
|
||||
return text;
|
||||
};
|
||||
|
||||
@@ -136,7 +136,7 @@ class Comment extends ParanoidModel<
|
||||
*/
|
||||
public toPlainText() {
|
||||
const node = Node.fromJSON(schema, this.data);
|
||||
return ProsemirrorHelper.toPlainText(node, schema);
|
||||
return ProsemirrorHelper.toPlainText(node);
|
||||
}
|
||||
|
||||
// hooks
|
||||
|
||||
@@ -4,7 +4,6 @@ import ukkonen from "ukkonen";
|
||||
import { updateYFragment, yDocToProsemirrorJSON } from "y-prosemirror";
|
||||
import * as Y from "yjs";
|
||||
import textBetween from "@shared/editor/lib/textBetween";
|
||||
import { getTextSerializers } from "@shared/editor/lib/textSerializers";
|
||||
import { EditorStyleHelper } from "@shared/editor/styles/EditorStyleHelper";
|
||||
import { IconType, ProsemirrorData } from "@shared/types";
|
||||
import { determineIconType } from "@shared/utils/icon";
|
||||
@@ -141,8 +140,7 @@ export class DocumentHelper {
|
||||
*/
|
||||
static toPlainText(document: Document | Revision | ProsemirrorData) {
|
||||
const node = DocumentHelper.toProsemirror(document);
|
||||
|
||||
return textBetween(node, 0, node.content.size, this.textSerializers);
|
||||
return textBetween(node, 0, node.content.size);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -523,6 +521,4 @@ export class DocumentHelper {
|
||||
const distance = ukkonen(first, second, threshold + 1);
|
||||
return distance > threshold;
|
||||
}
|
||||
|
||||
private static textSerializers = getTextSerializers(schema);
|
||||
}
|
||||
|
||||
@@ -25,10 +25,7 @@ export default function TextLength({
|
||||
let text;
|
||||
|
||||
try {
|
||||
text = ProsemirrorHelper.toPlainText(
|
||||
Node.fromJSON(schema, value),
|
||||
schema
|
||||
);
|
||||
text = ProsemirrorHelper.toPlainText(Node.fromJSON(schema, value));
|
||||
} catch (_err) {
|
||||
throw new Error("Invalid data");
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ export function exportTable({
|
||||
.map((row) =>
|
||||
row
|
||||
.map((cell) => {
|
||||
let value = ProsemirrorHelper.toPlainText(cell, state.schema);
|
||||
let value = ProsemirrorHelper.toPlainText(cell);
|
||||
|
||||
// Escape double quotes by doubling them
|
||||
if (value.includes('"')) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Node as ProseMirrorNode } from "prosemirror-model";
|
||||
import { PlainTextSerializer } from "../types";
|
||||
|
||||
/**
|
||||
* Returns the text content between two positions.
|
||||
@@ -7,25 +6,22 @@ import { PlainTextSerializer } from "../types";
|
||||
* @param doc The Prosemirror document to use
|
||||
* @param from A start point
|
||||
* @param to An end point
|
||||
* @param plainTextSerializers A map of node names to PlainTextSerializers which convert a node to plain text
|
||||
* @returns A string of plain text
|
||||
*/
|
||||
export default function textBetween(
|
||||
doc: ProseMirrorNode,
|
||||
from: number,
|
||||
to: number,
|
||||
plainTextSerializers: Record<string, PlainTextSerializer | undefined>
|
||||
to: number
|
||||
): string {
|
||||
let text = "";
|
||||
let first = true;
|
||||
const blockSeparator = "\n";
|
||||
|
||||
doc.nodesBetween(from, to, (node, pos) => {
|
||||
const toPlainText = plainTextSerializers[node.type.name];
|
||||
let nodeText = "";
|
||||
|
||||
if (toPlainText) {
|
||||
nodeText += toPlainText(node);
|
||||
if (node.type.spec.leafText) {
|
||||
nodeText += node.type.spec.leafText(node);
|
||||
} else if (node.isText) {
|
||||
nodeText += node.textBetween(
|
||||
Math.max(from, pos) - pos,
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Schema } from "prosemirror-model";
|
||||
|
||||
/**
|
||||
* Generate a map of text serializers for a given schema
|
||||
* @param schema
|
||||
* @returns Text serializers
|
||||
*/
|
||||
export function getTextSerializers(schema: Schema) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(schema.nodes)
|
||||
.filter(([, node]) => node.spec.toPlainText)
|
||||
.map(([name, node]) => [name, node.spec.toPlainText])
|
||||
);
|
||||
}
|
||||
@@ -65,7 +65,7 @@ export default class Attachment extends Node {
|
||||
},
|
||||
String(node.attrs.title),
|
||||
],
|
||||
toPlainText: (node) => node.attrs.title,
|
||||
leafText: (node) => node.attrs.title,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ export default class Embed extends Node {
|
||||
];
|
||||
}
|
||||
},
|
||||
toPlainText: (node) => node.attrs.href,
|
||||
leafText: (node) => node.attrs.href,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ export default class Emoji extends Extension {
|
||||
getEmojiFromName(name),
|
||||
];
|
||||
},
|
||||
toPlainText: (node) => getEmojiFromName(node.attrs["data-name"]),
|
||||
leafText: (node) => getEmojiFromName(node.attrs["data-name"]),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export default class HardBreak extends Node {
|
||||
selectable: false,
|
||||
parseDOM: [{ tag: "br" }],
|
||||
toDOM: () => ["br"],
|
||||
toPlainText: () => "\n",
|
||||
leafText: () => "\n",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ export default class Image extends SimpleImage {
|
||||
...children,
|
||||
];
|
||||
},
|
||||
toPlainText: (node) =>
|
||||
leafText: (node) =>
|
||||
node.attrs.alt ? `(image: ${node.attrs.alt})` : "(image)",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -119,7 +119,6 @@ export default class Mention extends Node {
|
||||
},
|
||||
toPlainText(node),
|
||||
],
|
||||
toPlainText,
|
||||
leafText: toPlainText,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ export default class Video extends Node {
|
||||
String(node.attrs.title),
|
||||
],
|
||||
],
|
||||
toPlainText: (node) => node.attrs.title,
|
||||
leafText: (node) => node.attrs.title,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
7
shared/typings/prosemirror-model.d.ts
vendored
7
shared/typings/prosemirror-model.d.ts
vendored
@@ -8,11 +8,4 @@ declare module "prosemirror-model" {
|
||||
// https://github.com/ProseMirror/prosemirror-model/blob/bd13a2329fda39f1c4d09abd8f0db2032bdc8014/src/replace.js#L51
|
||||
removeBetween(from: number, to: number): Slice;
|
||||
}
|
||||
|
||||
interface NodeSpec {
|
||||
/**
|
||||
* Defines the text representation of the node when copying to clipboard.
|
||||
*/
|
||||
toPlainText?: PlainTextSerializer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Node, Schema } from "prosemirror-model";
|
||||
import headingToSlug from "../editor/lib/headingToSlug";
|
||||
import textBetween from "../editor/lib/textBetween";
|
||||
import { getTextSerializers } from "../editor/lib/textSerializers";
|
||||
import { ProsemirrorData } from "../types";
|
||||
import { TextHelper } from "./TextHelper";
|
||||
import env from "../env";
|
||||
@@ -91,9 +90,8 @@ export class ProsemirrorHelper {
|
||||
* @param schema The schema to use.
|
||||
* @returns The document content as plain text without formatting.
|
||||
*/
|
||||
static toPlainText(root: Node, schema: Schema) {
|
||||
const textSerializers = getTextSerializers(schema);
|
||||
return textBetween(root, 0, root.content.size, textSerializers);
|
||||
static toPlainText(root: Node) {
|
||||
return textBetween(root, 0, root.content.size);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,7 +100,6 @@ export class ProsemirrorHelper {
|
||||
* @returns True if the editor is empty
|
||||
*/
|
||||
static trim(doc: Node) {
|
||||
const { schema } = doc.type;
|
||||
let index = 0,
|
||||
start = 0,
|
||||
end = doc.nodeSize - 2,
|
||||
@@ -118,7 +115,7 @@ export class ProsemirrorHelper {
|
||||
if (!node) {
|
||||
break;
|
||||
}
|
||||
isEmpty = ProsemirrorHelper.toPlainText(node, schema).trim() === "";
|
||||
isEmpty = ProsemirrorHelper.toPlainText(node).trim() === "";
|
||||
if (isEmpty) {
|
||||
start += node.nodeSize;
|
||||
}
|
||||
@@ -131,7 +128,7 @@ export class ProsemirrorHelper {
|
||||
if (!node) {
|
||||
break;
|
||||
}
|
||||
isEmpty = ProsemirrorHelper.toPlainText(node, schema).trim() === "";
|
||||
isEmpty = ProsemirrorHelper.toPlainText(node).trim() === "";
|
||||
if (isEmpty) {
|
||||
end -= node.nodeSize;
|
||||
}
|
||||
@@ -150,8 +147,6 @@ export class ProsemirrorHelper {
|
||||
return !doc || doc.textContent.trim() === "";
|
||||
}
|
||||
|
||||
const textSerializers = getTextSerializers(schema);
|
||||
|
||||
let empty = true;
|
||||
doc.descendants((child: Node) => {
|
||||
// If we've already found non-empty data, we can stop descending further
|
||||
@@ -159,9 +154,8 @@ export class ProsemirrorHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
const toPlainText = textSerializers[child.type.name];
|
||||
if (toPlainText) {
|
||||
empty = !toPlainText(child).trim();
|
||||
if (child.type.spec.leafText) {
|
||||
empty = !child.type.spec.leafText(child).trim();
|
||||
} else if (child.isText) {
|
||||
empty = !child.text?.trim();
|
||||
}
|
||||
@@ -331,10 +325,9 @@ export class ProsemirrorHelper {
|
||||
* Iterates through the document to find all of the headings and their level.
|
||||
*
|
||||
* @param doc Prosemirror document node
|
||||
* @param schema Prosemirror schema
|
||||
* @returns Array<Heading>
|
||||
*/
|
||||
static getHeadings(doc: Node, schema: Schema) {
|
||||
static getHeadings(doc: Node) {
|
||||
const headings: Heading[] = [];
|
||||
const previouslySeen: Record<string, number> = {};
|
||||
|
||||
@@ -356,7 +349,7 @@ export class ProsemirrorHelper {
|
||||
previouslySeen[id] !== undefined ? previouslySeen[id] + 1 : 1;
|
||||
|
||||
headings.push({
|
||||
title: ProsemirrorHelper.toPlainText(node, schema),
|
||||
title: ProsemirrorHelper.toPlainText(node),
|
||||
level: node.attrs.level,
|
||||
id: name,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user