image can't be inserted inside table (#2904)

* image can't be inserted inside table

Now we've diabled image icon from showing up if the cursor is inside a table node or if a table cell is selected

* added drag drop support for document editor

* fixed missing dependencies
This commit is contained in:
M. Palanikannan 2023-11-27 20:37:40 +05:30 committed by sriram veeraghanta
parent 041c3af35a
commit 10cde58363
32 changed files with 555 additions and 553 deletions

View File

@ -23,12 +23,10 @@
}, },
"peerDependencies": { "peerDependencies": {
"next": "12.3.2", "next": "12.3.2",
"next-themes": "^0.2.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "18.2.0" "react-dom": "18.2.0"
}, },
"dependencies": { "dependencies": {
"@blueprintjs/popover2": "^2.0.10",
"@tiptap/core": "^2.1.7", "@tiptap/core": "^2.1.7",
"@tiptap/extension-code-block-lowlight": "^2.1.12", "@tiptap/extension-code-block-lowlight": "^2.1.12",
"@tiptap/extension-color": "^2.1.11", "@tiptap/extension-color": "^2.1.11",
@ -49,29 +47,24 @@
"@tiptap/react": "^2.1.7", "@tiptap/react": "^2.1.7",
"@tiptap/starter-kit": "^2.1.10", "@tiptap/starter-kit": "^2.1.10",
"@tiptap/suggestion": "^2.0.4", "@tiptap/suggestion": "^2.0.4",
"@types/node": "18.15.3",
"@types/react": "^18.2.5",
"@types/react-dom": "18.0.11",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"eslint": "8.36.0",
"eslint-config-next": "13.2.4",
"eventsource-parser": "^0.1.0",
"highlight.js": "^11.8.0", "highlight.js": "^11.8.0",
"jsx-dom-cjs": "^8.0.3", "jsx-dom-cjs": "^8.0.3",
"lowlight": "^3.0.0", "lowlight": "^3.0.0",
"lucide-react": "^0.244.0", "lucide-react": "^0.244.0",
"prosemirror-async-query": "^0.0.4",
"react-markdown": "^8.0.7",
"react-moveable": "^0.54.2", "react-moveable": "^0.54.2",
"tailwind-merge": "^1.14.0", "tailwind-merge": "^1.14.0",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"tiptap-markdown": "^0.8.2", "tiptap-markdown": "^0.8.2"
"use-debounce": "^9.0.4"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^7.32.0", "eslint": "^7.32.0",
"postcss": "^8.4.29", "postcss": "^8.4.29",
"eslint-config-next": "13.2.4",
"@types/node": "18.15.3",
"@types/react": "^18.2.5",
"@types/react-dom": "18.0.11",
"tailwind-config-custom": "*", "tailwind-config-custom": "*",
"tsconfig": "*", "tsconfig": "*",
"tsup": "^7.2.0", "tsup": "^7.2.0",

View File

@ -27,33 +27,17 @@
"react-dom": "18.2.0" "react-dom": "18.2.0"
}, },
"dependencies": { "dependencies": {
"@headlessui/react": "^1.7.17",
"@plane/ui": "*", "@plane/ui": "*",
"@plane/editor-core": "*", "@plane/editor-core": "*",
"@popperjs/core": "^2.11.8", "@plane/editor-extensions": "*",
"@tiptap/core": "^2.1.7", "@tiptap/core": "^2.1.7",
"@tiptap/extension-code-block-lowlight": "^2.1.11",
"@tiptap/extension-horizontal-rule": "^2.1.11",
"@tiptap/extension-list-item": "^2.1.11",
"@tiptap/extension-placeholder": "^2.1.11", "@tiptap/extension-placeholder": "^2.1.11",
"@tiptap/suggestion": "^2.1.7",
"@types/node": "18.15.3", "@types/node": "18.15.3",
"@types/react": "^18.2.5", "@types/react": "^18.2.5",
"@types/react-dom": "18.0.11", "@types/react-dom": "18.0.11",
"class-variance-authority": "^0.7.0",
"clsx": "^1.2.1",
"eslint": "8.36.0", "eslint": "8.36.0",
"eslint-config-next": "13.2.4", "eslint-config-next": "13.2.4",
"eventsource-parser": "^0.1.0", "react-popper": "^2.3.0"
"highlight.js": "^11.8.0",
"lowlight": "^3.0.0",
"lucide-react": "^0.244.0",
"react-markdown": "^8.0.7",
"react-popper": "^2.3.0",
"tailwind-merge": "^1.14.0",
"tippy.js": "^6.3.7",
"tiptap-markdown": "^0.8.2",
"use-debounce": "^9.0.4"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^7.32.0", "eslint": "^7.32.0",

View File

@ -1,59 +1,28 @@
import HorizontalRule from "@tiptap/extension-horizontal-rule";
import Placeholder from "@tiptap/extension-placeholder"; import Placeholder from "@tiptap/extension-placeholder";
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; import { SlashCommand } from "@plane/editor-extensions";
import { common, createLowlight } from 'lowlight'
import { InputRule } from "@tiptap/core";
import ts from "highlight.js/lib/languages/typescript"; import { UploadImage } from "@plane/editor-types";
import { DragAndDrop } from "@plane/editor-extensions";
import SlashCommand from "./slash-command";
import { UploadImage } from "../";
const lowlight = createLowlight(common)
lowlight.register("ts", ts);
export const DocumentEditorExtensions = ( export const DocumentEditorExtensions = (
uploadFile: UploadImage, uploadFile: UploadImage,
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void,
) => [ ) => [
HorizontalRule.extend({ SlashCommand(uploadFile, setIsSubmitting),
addInputRules() { DragAndDrop,
return [ Placeholder.configure({
new InputRule({ placeholder: ({ node }) => {
find: /^(?:---|—-|___\s|\*\*\*\s)$/, if (node.type.name === "heading") {
handler: ({ state, range, commands }) => { return `Heading ${node.attrs.level}`;
commands.splitBlock(); }
if (node.type.name === "image" || node.type.name === "table") {
return "";
}
const attributes = {}; return "Press '/' for commands...";
const { tr } = state; },
const start = range.from; includeChildren: true,
const end = range.to; }),
// @ts-ignore ];
tr.replaceWith(start - 1, end, this.type.create(attributes));
},
}),
];
},
}).configure({
HTMLAttributes: {
class: "mb-6 border-t border-custom-border-300",
},
}),
SlashCommand(uploadFile, setIsSubmitting),
CodeBlockLowlight.configure({
lowlight,
}),
Placeholder.configure({
placeholder: ({ node }) => {
if (node.type.name === "heading") {
return `Heading ${node.attrs.level}`;
}
if (node.type.name === "image" || node.type.name === "table") {
return "";
}
return "Press '/' for commands...";
},
includeChildren: true,
}),
];

View File

@ -1,343 +0,0 @@
import { useState, useEffect, useCallback, ReactNode, useRef, useLayoutEffect } from "react";
import { Editor, Range, Extension } from "@tiptap/core";
import Suggestion from "@tiptap/suggestion";
import { ReactRenderer } from "@tiptap/react";
import tippy from "tippy.js";
import {
Heading1,
Heading2,
Heading3,
List,
ListOrdered,
Text,
TextQuote,
Code,
MinusSquare,
CheckSquare,
ImageIcon,
Table,
} from "lucide-react";
import { UploadImage } from "../";
import { cn, insertTableCommand, toggleBlockquote, toggleBulletList, toggleOrderedList, toggleTaskList, insertImageCommand, toggleHeadingOne, toggleHeadingTwo, toggleHeadingThree } from "@plane/editor-core";
interface CommandItemProps {
title: string;
description: string;
icon: ReactNode;
}
interface CommandProps {
editor: Editor;
range: Range;
}
const Command = Extension.create({
name: "slash-command",
addOptions() {
return {
suggestion: {
char: "/",
command: ({ editor, range, props }: { editor: Editor; range: Range; props: any }) => {
props.command({ editor, range });
},
},
};
},
addProseMirrorPlugins() {
return [
Suggestion({
editor: this.editor,
allow({ editor }) {
return !editor.isActive("table");
},
...this.options.suggestion,
}),
];
},
});
const getSuggestionItems =
(
uploadFile: UploadImage,
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void
) =>
({ query }: { query: string }) =>
[
{
title: "Text",
description: "Just start typing with plain text.",
searchTerms: ["p", "paragraph"],
icon: <Text size={18} />,
command: ({ editor, range }: CommandProps) => {
editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run();
},
},
{
title: "Heading 1",
description: "Big section heading.",
searchTerms: ["title", "big", "large"],
icon: <Heading1 size={18} />,
command: ({ editor, range }: CommandProps) => {
toggleHeadingOne(editor, range);
},
},
{
title: "Heading 2",
description: "Medium section heading.",
searchTerms: ["subtitle", "medium"],
icon: <Heading2 size={18} />,
command: ({ editor, range }: CommandProps) => {
toggleHeadingTwo(editor, range);
},
},
{
title: "Heading 3",
description: "Small section heading.",
searchTerms: ["subtitle", "small"],
icon: <Heading3 size={18} />,
command: ({ editor, range }: CommandProps) => {
toggleHeadingThree(editor, range);
},
},
{
title: "To-do List",
description: "Track tasks with a to-do list.",
searchTerms: ["todo", "task", "list", "check", "checkbox"],
icon: <CheckSquare size={18} />,
command: ({ editor, range }: CommandProps) => {
toggleTaskList(editor, range)
},
},
{
title: "Bullet List",
description: "Create a simple bullet list.",
searchTerms: ["unordered", "point"],
icon: <List size={18} />,
command: ({ editor, range }: CommandProps) => {
toggleBulletList(editor, range);
},
},
{
title: "Divider",
description: "Visually divide blocks",
searchTerms: ["line", "divider", "horizontal", "rule", "separate"],
icon: <MinusSquare size={18} />,
command: ({ editor, range }: CommandProps) => {
editor.chain().focus().deleteRange(range).setHorizontalRule().run();
},
},
{
title: "Table",
description: "Create a Table",
searchTerms: ["table", "cell", "db", "data", "tabular"],
icon: <Table size={18} />,
command: ({ editor, range }: CommandProps) => {
insertTableCommand(editor, range);
},
},
{
title: "Numbered List",
description: "Create a list with numbering.",
searchTerms: ["ordered"],
icon: <ListOrdered size={18} />,
command: ({ editor, range }: CommandProps) => {
toggleOrderedList(editor, range)
},
},
{
title: "Quote",
description: "Capture a quote.",
searchTerms: ["blockquote"],
icon: <TextQuote size={18} />,
command: ({ editor, range }: CommandProps) =>
toggleBlockquote(editor, range)
},
{
title: "Code",
description: "Capture a code snippet.",
searchTerms: ["codeblock"],
icon: <Code size={18} />,
command: ({ editor, range }: CommandProps) =>
editor.chain().focus().deleteRange(range).toggleCodeBlock().run(),
},
{
title: "Image",
description: "Upload an image from your computer.",
searchTerms: ["photo", "picture", "media"],
icon: <ImageIcon size={18} />,
command: ({ editor, range }: CommandProps) => {
insertImageCommand(editor, uploadFile, setIsSubmitting, range);
},
},
].filter((item) => {
if (typeof query === "string" && query.length > 0) {
const search = query.toLowerCase();
return (
item.title.toLowerCase().includes(search) ||
item.description.toLowerCase().includes(search) ||
(item.searchTerms && item.searchTerms.some((term: string) => term.includes(search)))
);
}
return true;
});
export const updateScrollView = (container: HTMLElement, item: HTMLElement) => {
const containerHeight = container.offsetHeight;
const itemHeight = item ? item.offsetHeight : 0;
const top = item.offsetTop;
const bottom = top + itemHeight;
if (top < container.scrollTop) {
container.scrollTop -= container.scrollTop - top + 5;
} else if (bottom > containerHeight + container.scrollTop) {
container.scrollTop += bottom - containerHeight - container.scrollTop + 5;
}
};
const CommandList = ({
items,
command,
}: {
items: CommandItemProps[];
command: any;
editor: any;
range: any;
}) => {
const [selectedIndex, setSelectedIndex] = useState(0);
const selectItem = useCallback(
(index: number) => {
const item = items[index];
if (item) {
command(item);
}
},
[command, items]
);
useEffect(() => {
const navigationKeys = ["ArrowUp", "ArrowDown", "Enter"];
const onKeyDown = (e: KeyboardEvent) => {
if (navigationKeys.includes(e.key)) {
e.preventDefault();
if (e.key === "ArrowUp") {
setSelectedIndex((selectedIndex + items.length - 1) % items.length);
return true;
}
if (e.key === "ArrowDown") {
setSelectedIndex((selectedIndex + 1) % items.length);
return true;
}
if (e.key === "Enter") {
selectItem(selectedIndex);
return true;
}
return false;
}
};
document.addEventListener("keydown", onKeyDown);
return () => {
document.removeEventListener("keydown", onKeyDown);
};
}, [items, selectedIndex, setSelectedIndex, selectItem]);
useEffect(() => {
setSelectedIndex(0);
}, [items]);
const commandListContainer = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
const container = commandListContainer?.current;
const item = container?.children[selectedIndex] as HTMLElement;
if (item && container) updateScrollView(container, item);
}, [selectedIndex]);
return items.length > 0 ? (
<div
id="slash-command"
ref={commandListContainer}
className="z-50 fixed h-auto max-h-[330px] w-72 overflow-y-auto rounded-md border border-custom-border-300 bg-custom-background-100 px-1 py-2 shadow-md transition-all"
>
{items.map((item: CommandItemProps, index: number) => (
<button
className={cn(
`flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm text-custom-text-200 hover:bg-custom-primary-100/5 hover:text-custom-text-100`,
{ "bg-custom-primary-100/5 text-custom-text-100": index === selectedIndex }
)}
key={index}
onClick={() => selectItem(index)}
>
<div>
<p className="font-medium">{item.title}</p>
<p className="text-xs text-custom-text-300">{item.description}</p>
</div>
</button>
))}
</div>
) : null;
};
const renderItems = () => {
let component: ReactRenderer | null = null;
let popup: any | null = null;
return {
onStart: (props: { editor: Editor; clientRect: DOMRect }) => {
component = new ReactRenderer(CommandList, {
props,
// @ts-ignore
editor: props.editor,
});
// @ts-ignore
popup = tippy("body", {
getReferenceClientRect: props.clientRect,
appendTo: () => document.querySelector("#editor-container"),
content: component.element,
showOnCreate: true,
interactive: true,
trigger: "manual",
placement: "bottom-start",
});
},
onUpdate: (props: { editor: Editor; clientRect: DOMRect }) => {
component?.updateProps(props);
popup &&
popup[0].setProps({
getReferenceClientRect: props.clientRect,
});
},
onKeyDown: (props: { event: KeyboardEvent }) => {
if (props.event.key === "Escape") {
popup?.[0].hide();
return true;
}
// @ts-ignore
return component?.ref?.onKeyDown(props);
},
onExit: () => {
popup?.[0].destroy();
component?.destroy();
},
};
};
export const SlashCommand = (
uploadFile: UploadImage,
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void
) =>
Command.configure({
suggestion: {
items: getSuggestionItems(uploadFile, setIsSubmitting),
render: renderItems,
},
});
export default SlashCommand;

View File

@ -4,6 +4,7 @@ import { BoldIcon } from "lucide-react";
import { import {
BoldItem, BoldItem,
BulletListItem, BulletListItem,
isCellSelection,
cn, cn,
CodeItem, CodeItem,
ImageItem, ImageItem,
@ -16,6 +17,7 @@ import {
HeadingOneItem, HeadingOneItem,
HeadingTwoItem, HeadingTwoItem,
HeadingThreeItem, HeadingThreeItem,
findTableAncestor,
} from "@plane/editor-core"; } from "@plane/editor-core";
import { UploadImage } from ".."; import { UploadImage } from "..";
@ -57,10 +59,36 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
CodeItem(editor), CodeItem(editor),
]; ];
const complexItems: BubbleMenuItem[] = [ function getComplexItems(): BubbleMenuItem[] {
TableItem(editor), const items: BubbleMenuItem[] = [TableItem(editor)];
ImageItem(editor, uploadFile, setIsSubmitting),
]; if (shouldShowImageItem()) {
items.push(ImageItem(editor, uploadFile, setIsSubmitting));
}
return items;
}
const complexItems: BubbleMenuItem[] = getComplexItems();
function shouldShowImageItem(): boolean {
if (typeof window !== "undefined") {
const selectionRange: any = window?.getSelection();
const { selection } = props.editor.state;
if (selectionRange.rangeCount !== 0) {
const range = selectionRange.getRangeAt(0);
if (findTableAncestor(range.startContainer)) {
return false;
}
if (isCellSelection(selection)) {
return false;
}
}
return true;
}
return false;
}
return ( return (
<div className="flex items-center divide-x divide-custom-border-200"> <div className="flex items-center divide-x divide-custom-border-200">

View File

@ -0,0 +1,97 @@
# @plane/editor-extensions
## Description
The `@plane/lite-text-editor` package extends from the `editor-core` package, inheriting its base functionality while adding its own unique features of Custom control over Enter key, etc.
## Key Features
- **Exported Components**: There are two components exported from the Lite text editor (with and without Ref), you can choose to use the `withRef` instance whenever you want to control the Editors state via a side effect of some external action from within the application code.
`LiteTextEditor` & `LiteTextEditorWithRef`
- **Read Only Editor Instances**: We have added a really light weight _Read Only_ Editor instance for the Lite editor types (with and without Ref)
`LiteReadOnlyEditor` &`LiteReadOnlyEditorWithRef`
## LiteTextEditor
| Prop | Type | Description |
| ------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `uploadFile` | `(file: File) => Promise<string>` | A function that handles file upload. It takes a file as input and handles the process of uploading that file. |
| `deleteFile` | `(assetUrlWithWorkspaceId: string) => Promise<any>` | A function that handles deleting an image. It takes the asset url from your bucket and handles the process of deleting that image. |
| `value` | `html string` | The initial content of the editor. |
| `onEnterKeyPress` | `(e) => void` | The event that happens on Enter key press |
| `debouncedUpdatesEnabled` | `boolean` | If set to true, the `onChange` event handler is debounced, meaning it will only be invoked after the specified delay (default 1500ms) once the user has stopped typing. |
| `onChange` | `(json: any, html: string) => void` | This function is invoked whenever the content of the editor changes. It is passed the new content in both JSON and HTML formats. |
| `setIsSubmitting` | `(isSubmitting: "submitting" \| "submitted" \| "saved") => void` | This function is called to update the submission status. |
| `setShouldShowAlert` | `(showAlert: boolean) => void` | This function is used to show or hide an alert incase of content not being "saved". |
| `noBorder` | `boolean` | If set to true, the editor will not have a border. |
| `borderOnFocus` | `boolean` | If set to true, the editor will show a border when it is focused. |
| `customClassName` | `string` | This is a custom CSS class that can be applied to the editor. |
| `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. |
### Usage
1. Here is an example of how to use the `RichTextEditor` component
```tsx
<LiteTextEditor
onEnterKeyPress={handleSubmit(handleCommentUpdate)}
uploadFile={fileService.getUploadFileFunction(workspaceSlug)}
deleteFile={fileService.deleteImage}
value={value}
debouncedUpdatesEnabled={false}
customClassName="min-h-[50px] p-3 shadow-sm"
onChange={(comment_json: Object, comment_html: string) => {
onChange(comment_html);
}}
/>
```
2. Example of how to use the `LiteTextEditorWithRef` component
```tsx
const editorRef = useRef<any>(null);
// can use it to set the editor's value
editorRef.current?.setEditorValue(`${watch("description_html")}`);
// can use it to clear the editor
editorRef?.current?.clearEditor();
return (
<LiteTextEditorWithRef
onEnterKeyPress={handleSubmit(handleCommentUpdate)}
uploadFile={fileService.getUploadFileFunction(workspaceSlug)}
deleteFile={fileService.deleteImage}
ref={editorRef}
value={value}
debouncedUpdatesEnabled={false}
customClassName="min-h-[50px] p-3 shadow-sm"
onChange={(comment_json: Object, comment_html: string) => {
onChange(comment_html);
}}
/>
);
```
## LiteReadOnlyEditor
| Prop | Type | Description |
| ------------------------------- | ------------- | --------------------------------------------------------------------- |
| `value` | `html string` | The initial content of the editor. |
| `noBorder` | `boolean` | If set to true, the editor will not have a border. |
| `borderOnFocus` | `boolean` | If set to true, the editor will show a border when it is focused. |
| `customClassName` | `string` | This is a custom CSS class that can be applied to the editor. |
| `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. |
### Usage
Here is an example of how to use the `RichReadOnlyEditor` component
```tsx
<LiteReadOnlyEditor
value={comment.comment_html}
customClassName="text-xs border border-custom-border-200 bg-custom-background-100"
/>
```

View File

@ -0,0 +1,60 @@
{
"name": "@plane/editor-extensions",
"version": "0.1.0",
"description": "Package that powers Plane's Editor with extensions",
"private": true,
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.mts",
"files": [
"dist/**/*"
],
"exports": {
".": {
"types": "./dist/index.d.mts",
"import": "./dist/index.mjs",
"module": "./dist/index.mjs"
}
},
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"check-types": "tsc --noEmit"
},
"peerDependencies": {
"next": "12.3.2",
"next-themes": "^0.2.1",
"react": "^18.2.0",
"react-dom": "18.2.0"
},
"dependencies": {
"@tiptap/react": "^2.1.7",
"@tiptap/core": "^2.1.7",
"@tiptap/suggestion": "^2.0.4",
"@plane/editor-types": "*",
"@plane/editor-core": "*",
"eslint": "8.36.0",
"eslint-config-next": "13.2.4",
"lucide-react": "^0.244.0",
"tippy.js": "^6.3.7",
"@tiptap/pm": "^2.1.7"
},
"devDependencies": {
"@types/node": "18.15.3",
"@types/react": "^18.2.35",
"@types/react-dom": "^18.2.14",
"eslint": "^7.32.0",
"postcss": "^8.4.29",
"tailwind-config-custom": "*",
"tsconfig": "*",
"tsup": "^7.2.0",
"typescript": "4.9.5"
},
"keywords": [
"editor",
"rich-text",
"markdown",
"nextjs",
"react"
]
}

View File

@ -0,0 +1,9 @@
// If you want to use other PostCSS plugins, see the following:
// https://tailwindcss.com/docs/using-with-preprocessors
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View File

@ -3,7 +3,30 @@ import { Extension } from "@tiptap/core";
import { PluginKey, NodeSelection, Plugin } from "@tiptap/pm/state"; import { PluginKey, NodeSelection, Plugin } from "@tiptap/pm/state";
// @ts-ignore // @ts-ignore
import { __serializeForClipboard, EditorView } from "@tiptap/pm/view"; import { __serializeForClipboard, EditorView } from "@tiptap/pm/view";
import { createDragHandleElement } from "../../lib/utils/DragHandleElement";
function createDragHandleElement(): HTMLElement {
let dragHandleElement = document.createElement("div");
dragHandleElement.draggable = true;
dragHandleElement.dataset.dragHandle = "";
dragHandleElement.classList.add("drag-handle");
const dragHandleContainer = document.createElement("div");
dragHandleContainer.classList.add("drag-handle-container");
dragHandleElement.appendChild(dragHandleContainer);
const dotsContainer = document.createElement("div");
dotsContainer.classList.add("drag-handle-dots");
for (let i = 0; i < 6; i++) {
const spanElement = document.createElement("span");
spanElement.classList.add("drag-handle-dot");
dotsContainer.appendChild(spanElement);
}
dragHandleContainer.appendChild(dotsContainer);
return dragHandleElement;
}
export interface DragHandleOptions { export interface DragHandleOptions {
dragHandleWidth: number; dragHandleWidth: number;
@ -220,7 +243,7 @@ function DragHandle(options: DragHandleOptions) {
}); });
} }
const DragAndDrop = Extension.create({ export const DragAndDrop = Extension.create({
name: "dragAndDrop", name: "dragAndDrop",
addProseMirrorPlugins() { addProseMirrorPlugins() {
@ -231,5 +254,3 @@ const DragAndDrop = Extension.create({
]; ];
}, },
}); });
export default DragAndDrop;

View File

@ -10,6 +10,7 @@ import { Editor, Range, Extension } from "@tiptap/core";
import Suggestion from "@tiptap/suggestion"; import Suggestion from "@tiptap/suggestion";
import { ReactRenderer } from "@tiptap/react"; import { ReactRenderer } from "@tiptap/react";
import tippy from "tippy.js"; import tippy from "tippy.js";
import type { UploadImage } from "@plane/editor-types";
import { import {
Heading1, Heading1,
Heading2, Heading2,
@ -24,7 +25,6 @@ import {
ImageIcon, ImageIcon,
Table, Table,
} from "lucide-react"; } from "lucide-react";
import { UploadImage } from "../";
import { import {
cn, cn,
insertTableCommand, insertTableCommand,
@ -156,6 +156,7 @@ const getSuggestionItems =
searchTerms: ["line", "divider", "horizontal", "rule", "separate"], searchTerms: ["line", "divider", "horizontal", "rule", "separate"],
icon: <MinusSquare size={18} />, icon: <MinusSquare size={18} />,
command: ({ editor, range }: CommandProps) => { command: ({ editor, range }: CommandProps) => {
// @ts-expect-error I have to move this to the core
editor.chain().focus().deleteRange(range).setHorizontalRule().run(); editor.chain().focus().deleteRange(range).setHorizontalRule().run();
}, },
}, },
@ -191,6 +192,7 @@ const getSuggestionItems =
searchTerms: ["codeblock"], searchTerms: ["codeblock"],
icon: <Code size={18} />, icon: <Code size={18} />,
command: ({ editor, range }: CommandProps) => command: ({ editor, range }: CommandProps) =>
// @ts-expect-error I have to move this to the core
editor.chain().focus().deleteRange(range).toggleCodeBlock().run(), editor.chain().focus().deleteRange(range).toggleCodeBlock().run(),
}, },
{ {
@ -381,5 +383,3 @@ export const SlashCommand = (
render: renderItems, render: renderItems,
}, },
}); });
export default SlashCommand;

View File

@ -0,0 +1,2 @@
export { SlashCommand } from "./extensions/slash-commands";
export { DragAndDrop } from "./extensions/drag-drop";

View File

@ -0,0 +1,6 @@
const sharedConfig = require("tailwind-config-custom/tailwind.config.js");
module.exports = {
// prefix ui lib classes to avoid conflicting with the app
...sharedConfig,
};

View File

@ -0,0 +1,5 @@
{
"extends": "tsconfig/react-library.json",
"include": ["src/**/*", "index.d.ts"],
"exclude": ["dist", "build", "node_modules"]
}

View File

@ -0,0 +1,11 @@
import { defineConfig, Options } from "tsup";
export default defineConfig((options: Options) => ({
entry: ["src/index.ts"],
format: ["cjs", "esm"],
dts: true,
clean: false,
external: ["react"],
injectStyle: true,
...options,
}));

View File

@ -29,20 +29,7 @@
}, },
"dependencies": { "dependencies": {
"@plane/editor-core": "*", "@plane/editor-core": "*",
"@plane/ui": "*", "@plane/ui": "*"
"@tiptap/extension-list-item": "^2.1.11",
"class-variance-authority": "^0.7.0",
"clsx": "^1.2.1",
"eslint": "8.36.0",
"eslint-config-next": "13.2.4",
"eventsource-parser": "^0.1.0",
"lowlight": "^2.9.0",
"lucide-react": "^0.244.0",
"react-markdown": "^8.0.7",
"tailwind-merge": "^1.14.0",
"tippy.js": "^6.3.7",
"tiptap-markdown": "^0.8.2",
"use-debounce": "^9.0.4"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "18.15.3", "@types/node": "18.15.3",
@ -51,6 +38,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"postcss": "^8.4.29", "postcss": "^8.4.29",
"tailwind-config-custom": "*", "tailwind-config-custom": "*",
"eslint-config-custom": "*",
"tsconfig": "*", "tsconfig": "*",
"tsup": "^7.2.0", "tsup": "^7.2.0",
"typescript": "4.9.5" "typescript": "4.9.5"

View File

@ -1,12 +1,13 @@
import { Editor } from "@tiptap/react"; import { Editor } from "@tiptap/react";
import { BoldIcon } from "lucide-react";
import { import {
BoldItem, BoldItem,
BulletListItem, BulletListItem,
cn, cn,
CodeItem, CodeItem,
findTableAncestor,
ImageItem, ImageItem,
isCellSelection,
ItalicItem, ItalicItem,
NumberedListItem, NumberedListItem,
QuoteItem, QuoteItem,
@ -16,12 +17,18 @@ import {
} from "@plane/editor-core"; } from "@plane/editor-core";
import { Tooltip } from "@plane/ui"; import { Tooltip } from "@plane/ui";
import { UploadImage } from "../../"; import { UploadImage } from "../../";
import type { SVGProps } from "react";
interface LucideProps extends Partial<SVGProps<SVGSVGElement>> {
size?: string | number
absoluteStrokeWidth?: boolean
}
type LucideIcon = (props: LucideProps) => JSX.Element;
export interface BubbleMenuItem { export interface BubbleMenuItem {
name: string; name: string;
isActive: () => boolean; isActive: () => boolean;
command: () => void; command: () => void;
icon: typeof BoldIcon; icon: LucideIcon;
} }
type EditorBubbleMenuProps = { type EditorBubbleMenuProps = {
@ -63,10 +70,38 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
CodeItem(props.editor), CodeItem(props.editor),
]; ];
const complexItems: BubbleMenuItem[] = [ function getComplexItems(): BubbleMenuItem[] {
TableItem(props.editor), const items: BubbleMenuItem[] = [TableItem(props.editor)];
ImageItem(props.editor, props.uploadFile, props.setIsSubmitting),
]; if (shouldShowImageItem()) {
items.push(
ImageItem(props.editor, props.uploadFile, props.setIsSubmitting),
);
}
return items;
}
const complexItems: BubbleMenuItem[] = getComplexItems();
function shouldShowImageItem(): boolean {
if (typeof window !== "undefined") {
const selectionRange: any = window?.getSelection();
const { selection } = props.editor.state;
if (selectionRange.rangeCount !== 0) {
const range = selectionRange.getRangeAt(0);
if (findTableAncestor(range.startContainer)) {
return false;
}
if (isCellSelection(selection)) {
return false;
}
}
return true;
}
return false;
}
const handleAccessChange = (accessKey: string) => { const handleAccessChange = (accessKey: string) => {
props.commentAccessSpecifier?.onAccessChange(accessKey); props.commentAccessSpecifier?.onAccessChange(accessKey);

View File

@ -22,7 +22,6 @@
"check-types": "tsc --noEmit" "check-types": "tsc --noEmit"
}, },
"peerDependencies": { "peerDependencies": {
"@tiptap/core": "^2.1.11",
"next": "12.3.2", "next": "12.3.2",
"next-themes": "^0.2.1", "next-themes": "^0.2.1",
"react": "^18.2.0", "react": "^18.2.0",
@ -30,11 +29,9 @@
}, },
"dependencies": { "dependencies": {
"@plane/editor-core": "*", "@plane/editor-core": "*",
"@tiptap/extension-horizontal-rule": "^2.1.11", "@tiptap/core": "^2.1.11",
"@plane/editor-extensions": "*",
"@tiptap/extension-placeholder": "^2.1.11", "@tiptap/extension-placeholder": "^2.1.11",
"@tiptap/suggestion": "^2.1.7",
"class-variance-authority": "^0.7.0",
"clsx": "^1.2.1",
"lucide-react": "^0.244.0" "lucide-react": "^0.244.0"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,23 +0,0 @@
export function createDragHandleElement(): HTMLElement {
let dragHandleElement = document.createElement("div");
dragHandleElement.draggable = true;
dragHandleElement.dataset.dragHandle = "";
dragHandleElement.classList.add("drag-handle");
const dragHandleContainer = document.createElement("div");
dragHandleContainer.classList.add("drag-handle-container");
dragHandleElement.appendChild(dragHandleContainer);
const dotsContainer = document.createElement("div");
dotsContainer.classList.add("drag-handle-dots");
for (let i = 0; i < 6; i++) {
const spanElement = document.createElement("span");
spanElement.classList.add("drag-handle-dot");
dotsContainer.appendChild(spanElement);
}
dragHandleContainer.appendChild(dotsContainer);
return dragHandleElement;
}

View File

@ -1,8 +1,7 @@
import { SlashCommand } from "@plane/editor-extensions";
import Placeholder from "@tiptap/extension-placeholder"; import Placeholder from "@tiptap/extension-placeholder";
import { DragAndDrop } from "@plane/editor-extensions";
import SlashCommand from "./slash-command";
import { UploadImage } from "../"; import { UploadImage } from "../";
import DragAndDrop from "./drag-drop";
export const RichTextEditorExtensions = ( export const RichTextEditorExtensions = (
uploadFile: UploadImage, uploadFile: UploadImage,

View File

@ -0,0 +1,97 @@
# @plane/editor-extensions
## Description
The `@plane/lite-text-editor` package extends from the `editor-core` package, inheriting its base functionality while adding its own unique features of Custom control over Enter key, etc.
## Key Features
- **Exported Components**: There are two components exported from the Lite text editor (with and without Ref), you can choose to use the `withRef` instance whenever you want to control the Editors state via a side effect of some external action from within the application code.
`LiteTextEditor` & `LiteTextEditorWithRef`
- **Read Only Editor Instances**: We have added a really light weight _Read Only_ Editor instance for the Lite editor types (with and without Ref)
`LiteReadOnlyEditor` &`LiteReadOnlyEditorWithRef`
## LiteTextEditor
| Prop | Type | Description |
| ------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `uploadFile` | `(file: File) => Promise<string>` | A function that handles file upload. It takes a file as input and handles the process of uploading that file. |
| `deleteFile` | `(assetUrlWithWorkspaceId: string) => Promise<any>` | A function that handles deleting an image. It takes the asset url from your bucket and handles the process of deleting that image. |
| `value` | `html string` | The initial content of the editor. |
| `onEnterKeyPress` | `(e) => void` | The event that happens on Enter key press |
| `debouncedUpdatesEnabled` | `boolean` | If set to true, the `onChange` event handler is debounced, meaning it will only be invoked after the specified delay (default 1500ms) once the user has stopped typing. |
| `onChange` | `(json: any, html: string) => void` | This function is invoked whenever the content of the editor changes. It is passed the new content in both JSON and HTML formats. |
| `setIsSubmitting` | `(isSubmitting: "submitting" \| "submitted" \| "saved") => void` | This function is called to update the submission status. |
| `setShouldShowAlert` | `(showAlert: boolean) => void` | This function is used to show or hide an alert incase of content not being "saved". |
| `noBorder` | `boolean` | If set to true, the editor will not have a border. |
| `borderOnFocus` | `boolean` | If set to true, the editor will show a border when it is focused. |
| `customClassName` | `string` | This is a custom CSS class that can be applied to the editor. |
| `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. |
### Usage
1. Here is an example of how to use the `RichTextEditor` component
```tsx
<LiteTextEditor
onEnterKeyPress={handleSubmit(handleCommentUpdate)}
uploadFile={fileService.getUploadFileFunction(workspaceSlug)}
deleteFile={fileService.deleteImage}
value={value}
debouncedUpdatesEnabled={false}
customClassName="min-h-[50px] p-3 shadow-sm"
onChange={(comment_json: Object, comment_html: string) => {
onChange(comment_html);
}}
/>
```
2. Example of how to use the `LiteTextEditorWithRef` component
```tsx
const editorRef = useRef<any>(null);
// can use it to set the editor's value
editorRef.current?.setEditorValue(`${watch("description_html")}`);
// can use it to clear the editor
editorRef?.current?.clearEditor();
return (
<LiteTextEditorWithRef
onEnterKeyPress={handleSubmit(handleCommentUpdate)}
uploadFile={fileService.getUploadFileFunction(workspaceSlug)}
deleteFile={fileService.deleteImage}
ref={editorRef}
value={value}
debouncedUpdatesEnabled={false}
customClassName="min-h-[50px] p-3 shadow-sm"
onChange={(comment_json: Object, comment_html: string) => {
onChange(comment_html);
}}
/>
);
```
## LiteReadOnlyEditor
| Prop | Type | Description |
| ------------------------------- | ------------- | --------------------------------------------------------------------- |
| `value` | `html string` | The initial content of the editor. |
| `noBorder` | `boolean` | If set to true, the editor will not have a border. |
| `borderOnFocus` | `boolean` | If set to true, the editor will show a border when it is focused. |
| `customClassName` | `string` | This is a custom CSS class that can be applied to the editor. |
| `editorContentCustomClassNames` | `string` | This is a custom CSS class that can be applied to the editor content. |
### Usage
Here is an example of how to use the `RichReadOnlyEditor` component
```tsx
<LiteReadOnlyEditor
value={comment.comment_html}
customClassName="text-xs border border-custom-border-200 bg-custom-background-100"
/>
```

View File

@ -0,0 +1,50 @@
{
"name": "@plane/editor-types",
"version": "0.1.0",
"description": "Package that powers Plane's Editor with extensions",
"private": true,
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.mts",
"files": [
"dist/**/*"
],
"exports": {
".": {
"types": "./dist/index.d.mts",
"import": "./dist/index.mjs",
"module": "./dist/index.mjs"
}
},
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"check-types": "tsc --noEmit"
},
"peerDependencies": {
"next": "12.3.2",
"next-themes": "^0.2.1",
"react": "^18.2.0",
"react-dom": "18.2.0"
},
"dependencies": {
"eslint": "8.36.0",
"eslint-config-next": "13.2.4"
},
"devDependencies": {
"@types/node": "18.15.3",
"@types/react": "^18.2.35",
"@types/react-dom": "^18.2.14",
"eslint": "^7.32.0",
"tsconfig": "*",
"tsup": "^7.2.0",
"typescript": "4.9.5"
},
"keywords": [
"editor",
"rich-text",
"markdown",
"nextjs",
"react"
]
}

View File

@ -0,0 +1,9 @@
// If you want to use other PostCSS plugins, see the following:
// https://tailwindcss.com/docs/using-with-preprocessors
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View File

@ -0,0 +1,6 @@
export type { DeleteImage } from "./types/delete-image";
export type { UploadImage } from "./types/upload-image";
export type {
IMentionHighlight,
IMentionSuggestion,
} from "./types/mention-suggestion";

View File

@ -0,0 +1 @@
export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise<any>;

View File

@ -0,0 +1,10 @@
export type IMentionSuggestion = {
id: string;
type: string;
avatar: string;
title: string;
subtitle: string;
redirect_uri: string;
};
export type IMentionHighlight = string;

View File

@ -0,0 +1 @@
export type UploadImage = (file: File) => Promise<string>;

View File

@ -0,0 +1,6 @@
const sharedConfig = require("tailwind-config-custom/tailwind.config.js");
module.exports = {
// prefix ui lib classes to avoid conflicting with the app
...sharedConfig,
};

View File

@ -0,0 +1,5 @@
{
"extends": "tsconfig/react-library.json",
"include": ["src/**/*", "index.d.ts"],
"exclude": ["dist", "build", "node_modules"]
}

View File

@ -0,0 +1,11 @@
import { defineConfig, Options } from "tsup";
export default defineConfig((options: Options) => ({
entry: ["src/index.ts"],
format: ["cjs", "esm"],
dts: true,
clean: false,
external: ["react"],
injectStyle: true,
...options,
}));

View File

@ -22,8 +22,13 @@
], ],
"pipeline": { "pipeline": {
"build": { "build": {
"dependsOn": ["^build"], "dependsOn": [
"outputs": [".next/**", "dist/**"] "^build"
],
"outputs": [
".next/**",
"dist/**"
]
}, },
"web#develop": { "web#develop": {
"cache": false, "cache": false,
@ -65,20 +70,32 @@
}, },
"@plane/lite-text-editor#build": { "@plane/lite-text-editor#build": {
"cache": true, "cache": true,
"dependsOn": ["@plane/editor-core#build"] "dependsOn": [
"@plane/editor-core#build",
"@plane/editor-extensions#build",
"@plane/editor-types#build"
]
}, },
"@plane/rich-text-editor#build": { "@plane/rich-text-editor#build": {
"cache": true, "cache": true,
"dependsOn": ["@plane/editor-core#build"] "dependsOn": [
"@plane/editor-core#build",
"@plane/editor-extensions#build",
"@plane/editor-types#build"
]
}, },
"@plane/document-editor#build": { "@plane/document-editor#build": {
"cache": true, "cache": true,
"dependsOn": [ "dependsOn": [
"@plane/editor-core#build" "@plane/editor-core#build",
"@plane/editor-extensions#build",
"@plane/editor-types#build"
] ]
}, },
"test": { "test": {
"dependsOn": ["^build"], "dependsOn": [
"^build"
],
"outputs": [] "outputs": []
}, },
"lint": { "lint": {

View File

@ -8,7 +8,8 @@
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"export": "next export" "export": "next export",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
}, },
"dependencies": { "dependencies": {
"@blueprintjs/popover2": "^1.13.3", "@blueprintjs/popover2": "^1.13.3",
@ -57,7 +58,8 @@
"sharp": "^0.32.1", "sharp": "^0.32.1",
"swr": "^2.1.3", "swr": "^2.1.3",
"tailwind-merge": "^2.0.0", "tailwind-merge": "^2.0.0",
"uuid": "^9.0.0" "uuid": "^9.0.0",
"use-debounce": "^9.0.4"
}, },
"devDependencies": { "devDependencies": {
"@types/js-cookie": "^3.0.2", "@types/js-cookie": "^3.0.2",

View File

@ -974,13 +974,6 @@
dependencies: dependencies:
tslib "~2.5.0" tslib "~2.5.0"
"@blueprintjs/colors@^5.0.5":
version "5.0.5"
resolved "https://registry.yarnpkg.com/@blueprintjs/colors/-/colors-5.0.5.tgz#3a8faa640dd2877aa4fd00b886cf8e58daf5f868"
integrity sha512-UcCsBxE8GTF6GW1oHBb+cuhPpKiJFWbIRkemwcRkp9HvXXQHxEaXlFFC6jAx5pf3JmRwde5/ck3r+lJFP1YqzA==
dependencies:
tslib "~2.6.2"
"@blueprintjs/core@^4.16.3", "@blueprintjs/core@^4.20.2": "@blueprintjs/core@^4.16.3", "@blueprintjs/core@^4.20.2":
version "4.20.2" version "4.20.2"
resolved "https://registry.yarnpkg.com/@blueprintjs/core/-/core-4.20.2.tgz#ae1bbaf13bd1bf887b506760c478cc940f6d6e20" resolved "https://registry.yarnpkg.com/@blueprintjs/core/-/core-4.20.2.tgz#ae1bbaf13bd1bf887b506760c478cc940f6d6e20"
@ -998,20 +991,6 @@
react-transition-group "^4.4.5" react-transition-group "^4.4.5"
tslib "~2.5.0" tslib "~2.5.0"
"@blueprintjs/core@^5.6.0":
version "5.6.0"
resolved "https://registry.yarnpkg.com/@blueprintjs/core/-/core-5.6.0.tgz#915b708a8c2d7e916d30dae704829e2d948301d9"
integrity sha512-NtQL/iu8P8DhHUCWCstc9Ps+JkRZCPRJ2ZoxubOt21pfxN50CN0sKHkDETHUQyZ73RviveVIIK+m32mT5Wwdqg==
dependencies:
"@blueprintjs/colors" "^5.0.5"
"@blueprintjs/icons" "^5.3.0"
"@popperjs/core" "^2.11.7"
classnames "^2.3.1"
normalize.css "^8.0.1"
react-popper "^2.3.0"
react-transition-group "^4.4.5"
tslib "~2.6.2"
"@blueprintjs/icons@^4.16.0": "@blueprintjs/icons@^4.16.0":
version "4.16.0" version "4.16.0"
resolved "https://registry.yarnpkg.com/@blueprintjs/icons/-/icons-4.16.0.tgz#47f9e8abe64d84fc18721080b8f191d8aac075d8" resolved "https://registry.yarnpkg.com/@blueprintjs/icons/-/icons-4.16.0.tgz#47f9e8abe64d84fc18721080b8f191d8aac075d8"
@ -1021,15 +1000,6 @@
classnames "^2.3.1" classnames "^2.3.1"
tslib "~2.5.0" tslib "~2.5.0"
"@blueprintjs/icons@^5.3.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@blueprintjs/icons/-/icons-5.3.0.tgz#9c9498df415bf7e028ceac15f90228d0ffd9a521"
integrity sha512-PGZHbWZ41b/SDOENlZQE1pAab4eluzf/hZ6sHB5nPrQNJuGNr94yaPp6u//Tu24iqVFFP20Soi3+ckhf/o3V/g==
dependencies:
change-case "^4.1.2"
classnames "^2.3.1"
tslib "~2.6.2"
"@blueprintjs/popover2@^1.13.3": "@blueprintjs/popover2@^1.13.3":
version "1.14.11" version "1.14.11"
resolved "https://registry.yarnpkg.com/@blueprintjs/popover2/-/popover2-1.14.11.tgz#0698fdeaf6710460cef0b71bed592ca37f40d1f9" resolved "https://registry.yarnpkg.com/@blueprintjs/popover2/-/popover2-1.14.11.tgz#0698fdeaf6710460cef0b71bed592ca37f40d1f9"
@ -1043,15 +1013,6 @@
react-popper "^2.3.0" react-popper "^2.3.0"
tslib "~2.5.0" tslib "~2.5.0"
"@blueprintjs/popover2@^2.0.10":
version "2.0.17"
resolved "https://registry.yarnpkg.com/@blueprintjs/popover2/-/popover2-2.0.17.tgz#ed1fd3a6fbb0a7ae623ccb649b150a7f22a09534"
integrity sha512-sJTlLD9ihKUITC8xEm1lzae4IumXkwoTEu/ajUTgd3GlLyQsUSC5CdlBihX7LlA60WbuyVSE3Jbazhqw2Fud2w==
dependencies:
"@blueprintjs/core" "^5.6.0"
classnames "^2.3.1"
tslib "~2.6.2"
"@cfcs/core@^0.0.6": "@cfcs/core@^0.0.6":
version "0.0.6" version "0.0.6"
resolved "https://registry.yarnpkg.com/@cfcs/core/-/core-0.0.6.tgz#9f8499dcd2ad29fd96d8fa72055411cd4a249121" resolved "https://registry.yarnpkg.com/@cfcs/core/-/core-0.0.6.tgz#9f8499dcd2ad29fd96d8fa72055411cd4a249121"
@ -2356,7 +2317,7 @@
lodash.merge "^4.6.2" lodash.merge "^4.6.2"
postcss-selector-parser "6.0.10" postcss-selector-parser "6.0.10"
"@tiptap/core@^2.1.12", "@tiptap/core@^2.1.7": "@tiptap/core@^2.1.11", "@tiptap/core@^2.1.12", "@tiptap/core@^2.1.7":
version "2.1.12" version "2.1.12"
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.1.12.tgz#904fdf147e91b5e60561c76e7563c1b5a32f54ab" resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.1.12.tgz#904fdf147e91b5e60561c76e7563c1b5a32f54ab"
integrity sha512-ZGc3xrBJA9KY8kln5AYTj8y+GDrKxi7u95xIl2eccrqTY5CQeRu6HRNM1yT4mAjuSaG9jmazyjGRlQuhyxCKxQ== integrity sha512-ZGc3xrBJA9KY8kln5AYTj8y+GDrKxi7u95xIl2eccrqTY5CQeRu6HRNM1yT4mAjuSaG9jmazyjGRlQuhyxCKxQ==
@ -2383,7 +2344,7 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.1.12.tgz#7c905a577ce30ef2cb335870a23f9d24fd26f6aa" resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.1.12.tgz#7c905a577ce30ef2cb335870a23f9d24fd26f6aa"
integrity sha512-vtD8vWtNlmAZX8LYqt2yU9w3mU9rPCiHmbp4hDXJs2kBnI0Ju/qAyXFx6iJ3C3XyuMnMbJdDI9ee0spAvFz7cQ== integrity sha512-vtD8vWtNlmAZX8LYqt2yU9w3mU9rPCiHmbp4hDXJs2kBnI0Ju/qAyXFx6iJ3C3XyuMnMbJdDI9ee0spAvFz7cQ==
"@tiptap/extension-code-block-lowlight@^2.1.11", "@tiptap/extension-code-block-lowlight@^2.1.12": "@tiptap/extension-code-block-lowlight@^2.1.12":
version "2.1.12" version "2.1.12"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.1.12.tgz#ccbca5d0d92bee373dc8e2e2ae6c27f62f66437c" resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.1.12.tgz#ccbca5d0d92bee373dc8e2e2ae6c27f62f66437c"
integrity sha512-dtIbpI9QrWa9TzNO4v5q/zf7+d83wpy5i9PEccdJAVtRZ0yOI8JIZAWzG5ex3zAoCA0CnQFdsPSVykYSDdxtDA== integrity sha512-dtIbpI9QrWa9TzNO4v5q/zf7+d83wpy5i9PEccdJAVtRZ0yOI8JIZAWzG5ex3zAoCA0CnQFdsPSVykYSDdxtDA==
@ -2440,7 +2401,7 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.1.12.tgz#03bcb9422e8ea2b82dc45207d1a1b0bc0241b055" resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.1.12.tgz#03bcb9422e8ea2b82dc45207d1a1b0bc0241b055"
integrity sha512-6b7UFVkvPjq3LVoCTrYZAczt5sQrQUaoDWAieVClVZoFLfjga2Fwjcfgcie8IjdPt8YO2hG/sar/c07i9vM0Sg== integrity sha512-6b7UFVkvPjq3LVoCTrYZAczt5sQrQUaoDWAieVClVZoFLfjga2Fwjcfgcie8IjdPt8YO2hG/sar/c07i9vM0Sg==
"@tiptap/extension-horizontal-rule@^2.1.11", "@tiptap/extension-horizontal-rule@^2.1.12": "@tiptap/extension-horizontal-rule@^2.1.12":
version "2.1.12" version "2.1.12"
resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.1.12.tgz#2191d4ff68ed39381d65971ad8e2aa1be43e6d6b" resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.1.12.tgz#2191d4ff68ed39381d65971ad8e2aa1be43e6d6b"
integrity sha512-RRuoK4KxrXRrZNAjJW5rpaxjiP0FJIaqpi7nFbAua2oHXgsCsG8qbW2Y0WkbIoS8AJsvLZ3fNGsQ8gpdliuq3A== integrity sha512-RRuoK4KxrXRrZNAjJW5rpaxjiP0FJIaqpi7nFbAua2oHXgsCsG8qbW2Y0WkbIoS8AJsvLZ3fNGsQ8gpdliuq3A==
@ -2462,7 +2423,7 @@
dependencies: dependencies:
linkifyjs "^4.1.0" linkifyjs "^4.1.0"
"@tiptap/extension-list-item@^2.1.11", "@tiptap/extension-list-item@^2.1.12": "@tiptap/extension-list-item@^2.1.12":
version "2.1.12" version "2.1.12"
resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.1.12.tgz#3eb28dc998490a98f14765783770b3cf6587d39e" resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.1.12.tgz#3eb28dc998490a98f14765783770b3cf6587d39e"
integrity sha512-Gk7hBFofAPmNQ8+uw8w5QSsZOMEGf7KQXJnx5B022YAUJTYYxO3jYVuzp34Drk9p+zNNIcXD4kc7ff5+nFOTrg== integrity sha512-Gk7hBFofAPmNQ8+uw8w5QSsZOMEGf7KQXJnx5B022YAUJTYYxO3jYVuzp34Drk9p+zNNIcXD4kc7ff5+nFOTrg==
@ -2599,7 +2560,7 @@
"@tiptap/extension-strike" "^2.1.12" "@tiptap/extension-strike" "^2.1.12"
"@tiptap/extension-text" "^2.1.12" "@tiptap/extension-text" "^2.1.12"
"@tiptap/suggestion@^2.0.4", "@tiptap/suggestion@^2.1.7": "@tiptap/suggestion@^2.0.4":
version "2.1.12" version "2.1.12"
resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.1.12.tgz#a13782d1e625ec03b3f61b6839ecc95b6b685d3f" resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.1.12.tgz#a13782d1e625ec03b3f61b6839ecc95b6b685d3f"
integrity sha512-rhlLWwVkOodBGRMK0mAmE34l2a+BqM2Y7q1ViuQRBhs/6sZ8d83O4hARHKVwqT5stY4i1l7d7PoemV3uAGI6+g== integrity sha512-rhlLWwVkOodBGRMK0mAmE34l2a+BqM2Y7q1ViuQRBhs/6sZ8d83O4hARHKVwqT5stY4i1l7d7PoemV3uAGI6+g==
@ -4840,11 +4801,6 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
eventsource-parser@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-0.1.0.tgz#4a6b84751ca8e704040e6f7f50e7d77344fa1b7c"
integrity sha512-M9QjFtEIkwytUarnx113HGmgtk52LSn3jNAtnWKi3V+b9rqSfQeVdLsaD5AG/O4IrGQwmAAHBIsqbmURPTd2rA==
execa@^5.0.0: execa@^5.0.0:
version "5.1.1" version "5.1.1"
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
@ -6509,7 +6465,7 @@ mz@^2.7.0:
object-assign "^4.0.1" object-assign "^4.0.1"
thenify-all "^1.0.0" thenify-all "^1.0.0"
nanoid@^3.1.30, nanoid@^3.3.4, nanoid@^3.3.6: nanoid@^3.3.4, nanoid@^3.3.6:
version "3.3.7" version "3.3.7"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
@ -7084,13 +7040,6 @@ property-information@^6.0.0:
resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.4.0.tgz#6bc4c618b0c2d68b3bb8b552cbb97f8e300a0f82" resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.4.0.tgz#6bc4c618b0c2d68b3bb8b552cbb97f8e300a0f82"
integrity sha512-9t5qARVofg2xQqKtytzt+lZ4d1Qvj8t5B8fEwXK6qOfgRLgH/b13QlgEyDh033NOS31nXeFbYv7CLUDG1CeifQ== integrity sha512-9t5qARVofg2xQqKtytzt+lZ4d1Qvj8t5B8fEwXK6qOfgRLgH/b13QlgEyDh033NOS31nXeFbYv7CLUDG1CeifQ==
prosemirror-async-query@^0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/prosemirror-async-query/-/prosemirror-async-query-0.0.4.tgz#4fedbee082692e659ab1f472645aac7765133b1d"
integrity sha512-eliJ722n+fVuChcvoZeS3pE/mpN/TJnqMkhIfVSTAH8Vd9S7aGfT9t31idD+mwnptgIc7OUPy56UdYN+ph++TQ==
dependencies:
nanoid "^3.1.30"
prosemirror-changeset@^2.2.0: prosemirror-changeset@^2.2.0:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/prosemirror-changeset/-/prosemirror-changeset-2.2.1.tgz#dae94b63aec618fac7bb9061648e6e2a79988383" resolved "https://registry.yarnpkg.com/prosemirror-changeset/-/prosemirror-changeset-2.2.1.tgz#dae94b63aec618fac7bb9061648e6e2a79988383"
@ -8408,7 +8357,7 @@ tslib@^1.8.1:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@~2.6.2: tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0:
version "2.6.2" version "2.6.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==