feat: Add @mention support to comments (#5001)

* Refactor, remove confusing 'packages' language

* Basic notifications when mentioned in comment

* fix: Incorrect trimming of comments

* test
This commit is contained in:
Tom Moor
2023-03-06 22:19:49 -05:00
committed by GitHub
parent 28c4854985
commit d3b099819d
18 changed files with 301 additions and 220 deletions

View File

@@ -0,0 +1,117 @@
import Extension from "../lib/Extension";
import Bold from "../marks/Bold";
import Code from "../marks/Code";
import Comment from "../marks/Comment";
import Highlight from "../marks/Highlight";
import Italic from "../marks/Italic";
import Link from "../marks/Link";
import Mark from "../marks/Mark";
import TemplatePlaceholder from "../marks/Placeholder";
import Strikethrough from "../marks/Strikethrough";
import Underline from "../marks/Underline";
import BlockMenuTrigger from "../plugins/BlockMenuTrigger";
import ClipboardTextSerializer from "../plugins/ClipboardTextSerializer";
import DateTime from "../plugins/DateTime";
import Folding from "../plugins/Folding";
import History from "../plugins/History";
import Keys from "../plugins/Keys";
import MaxLength from "../plugins/MaxLength";
import PasteHandler from "../plugins/PasteHandler";
import Placeholder from "../plugins/Placeholder";
import PreventTab from "../plugins/PreventTab";
import SmartText from "../plugins/SmartText";
import TrailingNode from "../plugins/TrailingNode";
import Attachment from "./Attachment";
import Blockquote from "./Blockquote";
import BulletList from "./BulletList";
import CheckboxItem from "./CheckboxItem";
import CheckboxList from "./CheckboxList";
import CodeBlock from "./CodeBlock";
import CodeFence from "./CodeFence";
import Doc from "./Doc";
import Embed from "./Embed";
import Emoji from "./Emoji";
import HardBreak from "./HardBreak";
import Heading from "./Heading";
import HorizontalRule from "./HorizontalRule";
import Image from "./Image";
import ListItem from "./ListItem";
import Math from "./Math";
import MathBlock from "./MathBlock";
import Mention from "./Mention";
import Node from "./Node";
import Notice from "./Notice";
import OrderedList from "./OrderedList";
import Paragraph from "./Paragraph";
import Table from "./Table";
import TableCell from "./TableCell";
import TableHeadCell from "./TableHeadCell";
import TableRow from "./TableRow";
import Text from "./Text";
type Nodes = (typeof Node | typeof Mark | typeof Extension)[];
/**
* The basic set of nodes that are used in the editor. This is used for simple
* editors that need basic formatting.
*/
export const basicExtensions: Nodes = [
Doc,
Paragraph,
Emoji,
Text,
Image,
Bold,
Code,
Italic,
Underline,
Link,
Strikethrough,
History,
SmartText,
TrailingNode,
PasteHandler,
Placeholder,
MaxLength,
DateTime,
Keys,
ClipboardTextSerializer,
];
/**
* The full set of nodes that are used in the editor. This is used for rich
* editors that need advanced formatting.
*/
export const richExtensions: Nodes = [
...basicExtensions,
HardBreak,
CodeBlock,
CodeFence,
CheckboxList,
CheckboxItem,
Blockquote,
BulletList,
OrderedList,
Embed,
ListItem,
Attachment,
Notice,
Heading,
HorizontalRule,
Table,
TableCell,
TableHeadCell,
TableRow,
Highlight,
TemplatePlaceholder,
Folding,
BlockMenuTrigger,
Math,
MathBlock,
PreventTab,
];
/**
* Add commenting and mentions to a set of nodes
*/
export const withComments = (nodes: Nodes) => [...nodes, Mention, Comment];

View File

@@ -1,2 +0,0 @@
Packages are preselected collections of extensions that form the different types
of editors within Outline.

View File

@@ -1,50 +0,0 @@
import Extension from "../lib/Extension";
import Bold from "../marks/Bold";
import Code from "../marks/Code";
import Italic from "../marks/Italic";
import Link from "../marks/Link";
import Mark from "../marks/Mark";
import Strikethrough from "../marks/Strikethrough";
import Underline from "../marks/Underline";
import Doc from "../nodes/Doc";
import Emoji from "../nodes/Emoji";
import Image from "../nodes/Image";
import Mention from "../nodes/Mention";
import Node from "../nodes/Node";
import Paragraph from "../nodes/Paragraph";
import Text from "../nodes/Text";
import ClipboardTextSerializer from "../plugins/ClipboardTextSerializer";
import DateTime from "../plugins/DateTime";
import History from "../plugins/History";
import Keys from "../plugins/Keys";
import MaxLength from "../plugins/MaxLength";
import PasteHandler from "../plugins/PasteHandler";
import Placeholder from "../plugins/Placeholder";
import SmartText from "../plugins/SmartText";
import TrailingNode from "../plugins/TrailingNode";
const basicPackage: (typeof Node | typeof Mark | typeof Extension)[] = [
Doc,
Paragraph,
Emoji,
Text,
Image,
Bold,
Code,
Italic,
Underline,
Link,
Strikethrough,
History,
SmartText,
TrailingNode,
PasteHandler,
Placeholder,
MaxLength,
DateTime,
Keys,
ClipboardTextSerializer,
Mention,
];
export default basicPackage;

View File

@@ -1,60 +0,0 @@
import Extension from "../lib/Extension";
import Highlight from "../marks/Highlight";
import Mark from "../marks/Mark";
import TemplatePlaceholder from "../marks/Placeholder";
import Attachment from "../nodes/Attachment";
import Blockquote from "../nodes/Blockquote";
import BulletList from "../nodes/BulletList";
import CheckboxItem from "../nodes/CheckboxItem";
import CheckboxList from "../nodes/CheckboxList";
import CodeBlock from "../nodes/CodeBlock";
import CodeFence from "../nodes/CodeFence";
import Embed from "../nodes/Embed";
import HardBreak from "../nodes/HardBreak";
import Heading from "../nodes/Heading";
import HorizontalRule from "../nodes/HorizontalRule";
import ListItem from "../nodes/ListItem";
import Math from "../nodes/Math";
import MathBlock from "../nodes/MathBlock";
import Node from "../nodes/Node";
import Notice from "../nodes/Notice";
import OrderedList from "../nodes/OrderedList";
import Table from "../nodes/Table";
import TableCell from "../nodes/TableCell";
import TableHeadCell from "../nodes/TableHeadCell";
import TableRow from "../nodes/TableRow";
import BlockMenuTrigger from "../plugins/BlockMenuTrigger";
import Folding from "../plugins/Folding";
import PreventTab from "../plugins/PreventTab";
import basicPackage from "./basic";
const fullPackage: (typeof Node | typeof Mark | typeof Extension)[] = [
...basicPackage,
HardBreak,
CodeBlock,
CodeFence,
CheckboxList,
CheckboxItem,
Blockquote,
BulletList,
OrderedList,
Embed,
ListItem,
Attachment,
Notice,
Heading,
HorizontalRule,
Table,
TableCell,
TableHeadCell,
TableRow,
Highlight,
TemplatePlaceholder,
Folding,
BlockMenuTrigger,
Math,
MathBlock,
PreventTab,
];
export default fullPackage;

View File

@@ -1,13 +0,0 @@
import Extension from "../lib/Extension";
import Comment from "../marks/Comment";
import Mark from "../marks/Mark";
import Node from "../nodes/Node";
import fullPackage from "./full";
const fullWithCommentsPackage: (
| typeof Node
| typeof Mark
| typeof Extension
)[] = [...fullPackage, Comment];
export default fullWithCommentsPackage;

View File

@@ -1,4 +1,5 @@
import { Node } from "prosemirror-model";
import { Node, Schema } from "prosemirror-model";
import textBetween from "@shared/editor/lib/textBetween";
import headingToSlug from "../editor/lib/headingToSlug";
export type Heading = {
@@ -25,6 +26,23 @@ export type Task = {
};
export default class ProsemirrorHelper {
/**
* Returns the node as plain text.
*
* @param node The node to convert.
* @param schema The schema to use.
* @returns The document content as plain text without formatting.
*/
static toPlainText(node: Node, schema: Schema) {
const textSerializers = Object.fromEntries(
Object.entries(schema.nodes)
.filter(([, node]) => node.spec.toPlainText)
.map(([name, node]) => [name, node.spec.toPlainText])
);
return textBetween(node, 0, node.content.size, textSerializers);
}
/**
* Removes any empty paragraphs from the beginning and end of the document.
*
@@ -34,9 +52,11 @@ export default class ProsemirrorHelper {
const first = doc.firstChild;
const last = doc.lastChild;
const firstIsEmpty =
first?.type.name === "paragraph" && !first.textContent.trim();
first &&
ProsemirrorHelper.toPlainText(first, doc.type.schema).trim() === "";
const lastIsEmpty =
last?.type.name === "paragraph" && !last.textContent.trim();
last &&
ProsemirrorHelper.toPlainText(last, doc.type.schema).trim() === "";
const firstIsLast = first === last;
return doc.cut(