fix: Task List Behaviour in Editor (#2789)

* better variable names and comments

* drag drop migrated

* custom horizontal rule created

* init transaction hijack

* fixed code block with better contrast, keyboard tripple enter press disabled and syntax highlighting

* fixed link selector closing on open behaviour

* added better keymaps and syntax highlights

* made drag and drop working for code blocks

* fixed drag drop for code blocks

* moved drag drop only to rich text editor

* fixed drag and drop only for description

* enabled drag handles for peek overview and main issues

* got images to old state

* fixed task lists to be smaller

* removed validate image functions and uncessary imports

* table icons svg attributes fixed

* custom list keymap extension added

* more uncessary imports of validate image removed

* removed console logs

* fixed drag-handle styles

* space styles updated for the editor

* removed showing quotes from blockquotes

* removed validateImage for now

* added better comments and improved redundant renders

* removed uncessary console logs

* created util for creating the drag handle element

* fixed file names
This commit is contained in:
M. Palanikannan 2023-11-18 16:20:35 +05:30 committed by sriram veeraghanta
parent 3d8da99eec
commit 2cca0b1e76
24 changed files with 508 additions and 88 deletions

View File

@ -31,11 +31,10 @@
"@blueprintjs/popover2": "^2.0.10", "@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",
"highlight.js": "^11.8.0",
"lowlight": "^3.0.0",
"@tiptap/extension-color": "^2.1.11", "@tiptap/extension-color": "^2.1.11",
"@tiptap/extension-image": "^2.1.7", "@tiptap/extension-image": "^2.1.7",
"@tiptap/extension-link": "^2.1.7", "@tiptap/extension-link": "^2.1.7",
"@tiptap/extension-list-item": "^2.1.12",
"@tiptap/extension-mention": "^2.1.12", "@tiptap/extension-mention": "^2.1.12",
"@tiptap/extension-table": "^2.1.6", "@tiptap/extension-table": "^2.1.6",
"@tiptap/extension-table-cell": "^2.1.6", "@tiptap/extension-table-cell": "^2.1.6",
@ -58,7 +57,9 @@
"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", "eventsource-parser": "^0.1.0",
"highlight.js": "^11.8.0",
"jsx-dom-cjs": "^8.0.3", "jsx-dom-cjs": "^8.0.3",
"lowlight": "^3.0.0",
"lucide-react": "^0.244.0", "lucide-react": "^0.244.0",
"prosemirror-async-query": "^0.0.4", "prosemirror-async-query": "^0.0.4",
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",

View File

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

View File

@ -0,0 +1 @@
export * from "./list-keymap";

View File

@ -0,0 +1,30 @@
import { getNodeType } from '@tiptap/core'
import { NodeType } from '@tiptap/pm/model'
import { EditorState } from '@tiptap/pm/state'
export const findListItemPos = (typeOrName: string | NodeType, state: EditorState) => {
const { $from } = state.selection
const nodeType = getNodeType(typeOrName, state.schema)
let currentNode = null
let currentDepth = $from.depth
let currentPos = $from.pos
let targetDepth: number | null = null
while (currentDepth > 0 && targetDepth === null) {
currentNode = $from.node(currentDepth)
if (currentNode.type === nodeType) {
targetDepth = currentDepth
} else {
currentDepth -= 1
currentPos -= 1
}
}
if (targetDepth === null) {
return null
}
return { $pos: state.doc.resolve(currentPos), depth: targetDepth }
}

View File

@ -0,0 +1,20 @@
import { getNodeAtPosition } from "@tiptap/core";
import { EditorState } from "@tiptap/pm/state";
import { findListItemPos } from "./find-list-item-pos";
export const getNextListDepth = (typeOrName: string, state: EditorState) => {
const listItemPos = findListItemPos(typeOrName, state);
if (!listItemPos) {
return false;
}
const [, depth] = getNodeAtPosition(
state,
typeOrName,
listItemPos.$pos.pos + 4,
);
return depth;
};

View File

@ -0,0 +1,78 @@
import { Editor, isAtStartOfNode, isNodeActive } from "@tiptap/core";
import { Node } from "@tiptap/pm/model";
import { findListItemPos } from "./find-list-item-pos";
import { hasListBefore } from "./has-list-before";
export const handleBackspace = (
editor: Editor,
name: string,
parentListTypes: string[],
) => {
// this is required to still handle the undo handling
if (editor.commands.undoInputRule()) {
return true;
}
// if the cursor is not at the start of a node
// do nothing and proceed
if (!isAtStartOfNode(editor.state)) {
return false;
}
// if the current item is NOT inside a list item &
// the previous item is a list (orderedList or bulletList)
// move the cursor into the list and delete the current item
if (
!isNodeActive(editor.state, name) &&
hasListBefore(editor.state, name, parentListTypes)
) {
const { $anchor } = editor.state.selection;
const $listPos = editor.state.doc.resolve($anchor.before() - 1);
const listDescendants: Array<{ node: Node; pos: number }> = [];
$listPos.node().descendants((node, pos) => {
if (node.type.name === name) {
listDescendants.push({ node, pos });
}
});
const lastItem = listDescendants.at(-1);
if (!lastItem) {
return false;
}
const $lastItemPos = editor.state.doc.resolve(
$listPos.start() + lastItem.pos + 1,
);
return editor
.chain()
.cut(
{ from: $anchor.start() - 1, to: $anchor.end() + 1 },
$lastItemPos.end(),
)
.joinForward()
.run();
}
// if the cursor is not inside the current node type
// do nothing and proceed
if (!isNodeActive(editor.state, name)) {
return false;
}
const listItemPos = findListItemPos(name, editor.state);
if (!listItemPos) {
return false;
}
// if current node is a list item and cursor it at start of a list node,
// simply lift the list item i.e. remove it as a list item (task/bullet/ordered)
// irrespective of above node being a list or not
return editor.chain().liftListItem(name).run();
};

View File

@ -0,0 +1,34 @@
import { Editor, isAtEndOfNode, isNodeActive } from "@tiptap/core";
import { nextListIsDeeper } from "./next-list-is-deeper";
import { nextListIsHigher } from "./next-list-is-higher";
export const handleDelete = (editor: Editor, name: string) => {
// if the cursor is not inside the current node type
// do nothing and proceed
if (!isNodeActive(editor.state, name)) {
return false;
}
// if the cursor is not at the end of a node
// do nothing and proceed
if (!isAtEndOfNode(editor.state, name)) {
return false;
}
// check if the next node is a list with a deeper depth
if (nextListIsDeeper(name, editor.state)) {
return editor
.chain()
.focus(editor.state.selection.from + 4)
.lift(name)
.joinBackward()
.run();
}
if (nextListIsHigher(name, editor.state)) {
return editor.chain().joinForward().joinBackward().run();
}
return editor.commands.joinItemForward();
};

View File

@ -0,0 +1,15 @@
import { EditorState } from '@tiptap/pm/state'
export const hasListBefore = (editorState: EditorState, name: string, parentListTypes: string[]) => {
const { $anchor } = editorState.selection
const previousNodePos = Math.max(0, $anchor.pos - 2)
const previousNode = editorState.doc.resolve(previousNodePos).node()
if (!previousNode || !parentListTypes.includes(previousNode.type.name)) {
return false
}
return true
}

View File

@ -0,0 +1,17 @@
import { EditorState } from '@tiptap/pm/state'
export const hasListItemAfter = (typeOrName: string, state: EditorState): boolean => {
const { $anchor } = state.selection
const $targetPos = state.doc.resolve($anchor.pos - $anchor.parentOffset - 2)
if ($targetPos.index() === $targetPos.parent.childCount - 1) {
return false
}
if ($targetPos.nodeAfter?.type.name !== typeOrName) {
return false
}
return true
}

View File

@ -0,0 +1,17 @@
import { EditorState } from '@tiptap/pm/state'
export const hasListItemBefore = (typeOrName: string, state: EditorState): boolean => {
const { $anchor } = state.selection
const $targetPos = state.doc.resolve($anchor.pos - 2)
if ($targetPos.index() === 0) {
return false
}
if ($targetPos.nodeBefore?.type.name !== typeOrName) {
return false
}
return true
}

View File

@ -0,0 +1,9 @@
export * from "./find-list-item-pos";
export * from "./get-next-list-depth";
export * from "./handle-backspace";
export * from "./handle-delete";
export * from "./has-list-before";
export * from "./has-list-item-after";
export * from "./has-list-item-before";
export * from "./next-list-is-deeper";
export * from "./next-list-is-higher";

View File

@ -0,0 +1,19 @@
import { EditorState } from "@tiptap/pm/state";
import { findListItemPos } from "./find-list-item-pos";
import { getNextListDepth } from "./get-next-list-depth";
export const nextListIsDeeper = (typeOrName: string, state: EditorState) => {
const listDepth = getNextListDepth(typeOrName, state);
const listItemPos = findListItemPos(typeOrName, state);
if (!listItemPos || !listDepth) {
return false;
}
if (listDepth > listItemPos.depth) {
return true;
}
return false;
};

View File

@ -0,0 +1,19 @@
import { EditorState } from "@tiptap/pm/state";
import { findListItemPos } from "./find-list-item-pos";
import { getNextListDepth } from "./get-next-list-depth";
export const nextListIsHigher = (typeOrName: string, state: EditorState) => {
const listDepth = getNextListDepth(typeOrName, state);
const listItemPos = findListItemPos(typeOrName, state);
if (!listItemPos || !listDepth) {
return false;
}
if (listDepth < listItemPos.depth) {
return true;
}
return false;
};

View File

@ -0,0 +1,94 @@
import { Extension } from "@tiptap/core";
import { handleBackspace, handleDelete } from "./list-helpers";
export type ListKeymapOptions = {
listTypes: Array<{
itemName: string;
wrapperNames: string[];
}>;
};
export const ListKeymap = Extension.create<ListKeymapOptions>({
name: "listKeymap",
addOptions() {
return {
listTypes: [
{
itemName: "listItem",
wrapperNames: ["bulletList", "orderedList"],
},
{
itemName: "taskItem",
wrapperNames: ["taskList"],
},
],
};
},
addKeyboardShortcuts() {
return {
Delete: ({ editor }) => {
let handled = false;
this.options.listTypes.forEach(({ itemName }) => {
if (editor.state.schema.nodes[itemName] === undefined) {
return;
}
if (handleDelete(editor, itemName)) {
handled = true;
}
});
return handled;
},
"Mod-Delete": ({ editor }) => {
let handled = false;
this.options.listTypes.forEach(({ itemName }) => {
if (editor.state.schema.nodes[itemName] === undefined) {
return;
}
if (handleDelete(editor, itemName)) {
handled = true;
}
});
return handled;
},
Backspace: ({ editor }) => {
let handled = false;
this.options.listTypes.forEach(({ itemName, wrapperNames }) => {
if (editor.state.schema.nodes[itemName] === undefined) {
return;
}
if (handleBackspace(editor, itemName, wrapperNames)) {
handled = true;
}
});
return handled;
},
"Mod-Backspace": ({ editor }) => {
let handled = false;
this.options.listTypes.forEach(({ itemName, wrapperNames }) => {
if (editor.state.schema.nodes[itemName] === undefined) {
return;
}
if (handleBackspace(editor, itemName, wrapperNames)) {
handled = true;
}
});
return handled;
},
};
},
});

View File

@ -11,7 +11,6 @@ import TableHeader from "./table/table-header/table-header";
import Table from "./table/table"; import Table from "./table/table";
import TableCell from "./table/table-cell/table-cell"; import TableCell from "./table/table-cell/table-cell";
import TableRow from "./table/table-row/table-row"; import TableRow from "./table/table-row/table-row";
import DragDrop from "./drag-drop";
import HorizontalRule from "./horizontal-rule"; import HorizontalRule from "./horizontal-rule";
import ImageExtension from "./image"; import ImageExtension from "./image";
@ -20,10 +19,10 @@ import { DeleteImage } from "../../types/delete-image";
import { isValidHttpUrl } from "../../lib/utils"; import { isValidHttpUrl } from "../../lib/utils";
import { IMentionSuggestion } from "../../types/mention-suggestion"; import { IMentionSuggestion } from "../../types/mention-suggestion";
import { Mentions } from "../mentions"; import { Mentions } from "../mentions";
import { ValidateImage } from "../../types/validate-image";
import { CustomKeymap } from "./keymap"; import { CustomKeymap } from "./keymap";
import { CustomCodeBlock } from "./code"; import { CustomCodeBlock } from "./code";
import { ListKeymap } from "./custom-list-keymap";
export const CoreEditorExtensions = ( export const CoreEditorExtensions = (
mentionConfig: { mentionConfig: {
@ -31,7 +30,6 @@ export const CoreEditorExtensions = (
mentionHighlights: string[]; mentionHighlights: string[];
}, },
deleteFile: DeleteImage, deleteFile: DeleteImage,
validateFile?: ValidateImage,
cancelUploadImage?: () => any, cancelUploadImage?: () => any,
) => [ ) => [
StarterKit.configure({ StarterKit.configure({
@ -64,6 +62,7 @@ export const CoreEditorExtensions = (
}, },
}), }),
CustomKeymap, CustomKeymap,
ListKeymap,
TiptapLink.configure({ TiptapLink.configure({
protocols: ["http", "https"], protocols: ["http", "https"],
validate: (url) => isValidHttpUrl(url), validate: (url) => isValidHttpUrl(url),
@ -72,7 +71,7 @@ export const CoreEditorExtensions = (
"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer", "text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
}, },
}), }),
ImageExtension(deleteFile, validateFile, cancelUploadImage).configure({ ImageExtension(deleteFile, cancelUploadImage).configure({
HTMLAttributes: { HTMLAttributes: {
class: "rounded-lg border border-custom-border-300", class: "rounded-lg border border-custom-border-300",
}, },

View File

@ -1,11 +1,10 @@
const icons = { const icons = {
colorPicker: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" style="transform: ;msFilter:;"><path fill="rgb(var(--color-text-300))" d="M20 14c-.092.064-2 2.083-2 3.5 0 1.494.949 2.448 2 2.5.906.044 2-.891 2-2.5 0-1.5-1.908-3.436-2-3.5zM9.586 20c.378.378.88.586 1.414.586s1.036-.208 1.414-.586l7-7-.707-.707L11 4.586 8.707 2.293 7.293 3.707 9.586 6 4 11.586c-.378.378-.586.88-.586 1.414s.208 1.036.586 1.414L9.586 20zM11 7.414 16.586 13H5.414L11 7.414z"></path></svg>`, colorPicker: `<svg xmlns="http://www.w3.org/2000/svg" length="24" viewBox="0 0 24 24" style="transform: ;msFilter:;"><path fill="rgb(var(--color-text-300))" d="M20 14c-.092.064-2 2.083-2 3.5 0 1.494.949 2.448 2 2.5.906.044 2-.891 2-2.5 0-1.5-1.908-3.436-2-3.5zM9.586 20c.378.378.88.586 1.414.586s1.036-.208 1.414-.586l7-7-.707-.707L11 4.586 8.707 2.293 7.293 3.707 9.586 6 4 11.586c-.378.378-.586.88-.586 1.414s.208 1.036.586 1.414L9.586 20zM11 7.414 16.586 13H5.414L11 7.414z"></path></svg>`,
deleteColumn: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="#e53e3e" d="M0 0H24V24H0z"/><path d="M12 3c.552 0 1 .448 1 1v8c.835-.628 1.874-1 3-1 2.761 0 5 2.239 5 5s-2.239 5-5 5c-1.032 0-1.99-.313-2.787-.848L13 20c0 .552-.448 1-1 1H6c-.552 0-1-.448-1-1V4c0-.552.448-1 1-1h6zm-1 2H7v14h4V5zm8 10h-6v2h6v-2z"/></svg>`, deleteColumn: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" length="24"><path fill="#e53e3e" d="M0 0H24V24H0z"/><path d="M12 3c.552 0 1 .448 1 1v8c.835-.628 1.874-1 3-1 2.761 0 5 2.239 5 5s-2.239 5-5 5c-1.032 0-1.99-.313-2.787-.848L13 20c0 .552-.448 1-1 1H6c-.552 0-1-.448-1-1V4c0-.552.448-1 1-1h6zm-1 2H7v14h4V5zm8 10h-6v2h6v-2z"/></svg>`,
deleteRow: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="#e53e3e" d="M0 0H24V24H0z"/><path d="M20 5c.552 0 1 .448 1 1v6c0 .552-.448 1-1 1 .628.835 1 1.874 1 3 0 2.761-2.239 5-5 5s-5-2.239-5-5c0-1.126.372-2.165 1-3H4c-.552 0-1-.448-1-1V6c0-.552.448-1 1-1h16zm-7 10v2h6v-2h-6zm6-8H5v4h14V7z"/></svg>`, deleteRow: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" length="24"><path fill="#e53e3e" d="M0 0H24V24H0z"/><path d="M20 5c.552 0 1 .448 1 1v6c0 .552-.448 1-1 1 .628.835 1 1.874 1 3 0 2.761-2.239 5-5 5s-5-2.239-5-5c0-1.126.372-2.165 1-3H4c-.552 0-1-.448-1-1V6c0-.552.448-1 1-1h16zm-7 10v2h6v-2h-6zm6-8H5v4h14V7z"/></svg>`,
insertLeftTableIcon: `<svg insertLeftTableIcon: `<svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width={24} length={24}
height={24}
viewBox="0 -960 960 960" viewBox="0 -960 960 960"
> >
<path <path
@ -16,8 +15,7 @@ const icons = {
`, `,
insertRightTableIcon: `<svg insertRightTableIcon: `<svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width={24} length={24}
height={24}
viewBox="0 -960 960 960" viewBox="0 -960 960 960"
> >
<path <path
@ -28,8 +26,7 @@ const icons = {
`, `,
insertTopTableIcon: `<svg insertTopTableIcon: `<svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width={24} length={24}
height={24}
viewBox="0 -960 960 960" viewBox="0 -960 960 960"
> >
<path <path
@ -40,8 +37,7 @@ const icons = {
`, `,
insertBottomTableIcon: `<svg insertBottomTableIcon: `<svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width={24} length={24}
height={24}
viewBox="0 -960 960 960" viewBox="0 -960 960 960"
> >
<path <path

View File

@ -1,12 +1,6 @@
import { useEditor as useCustomEditor, Editor } from "@tiptap/react"; import { useEditor as useCustomEditor, Editor } from "@tiptap/react";
import { import { useImperativeHandle, useRef, MutableRefObject } from "react";
useImperativeHandle,
useRef,
MutableRefObject,
useEffect,
} from "react";
import { DeleteImage } from "../../types/delete-image"; import { DeleteImage } from "../../types/delete-image";
import { ValidateImage } from "../../types/validate-image";
import { CoreEditorProps } from "../props"; import { CoreEditorProps } from "../props";
import { CoreEditorExtensions } from "../extensions"; import { CoreEditorExtensions } from "../extensions";
import { EditorProps } from "@tiptap/pm/view"; import { EditorProps } from "@tiptap/pm/view";
@ -17,7 +11,6 @@ import { IMentionSuggestion } from "../../types/mention-suggestion";
interface CustomEditorProps { interface CustomEditorProps {
uploadFile: UploadImage; uploadFile: UploadImage;
validateFile?: ValidateImage;
setIsSubmitting?: ( setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved", isSubmitting: "submitting" | "submitted" | "saved",
) => void; ) => void;
@ -37,7 +30,6 @@ interface CustomEditorProps {
export const useEditor = ({ export const useEditor = ({
uploadFile, uploadFile,
deleteFile, deleteFile,
validateFile,
cancelUploadImage, cancelUploadImage,
editorProps = {}, editorProps = {},
value, value,
@ -62,7 +54,6 @@ export const useEditor = ({
mentionHighlights: mentionHighlights ?? [], mentionHighlights: mentionHighlights ?? [],
}, },
deleteFile, deleteFile,
validateFile,
cancelUploadImage, cancelUploadImage,
), ),
...extensions, ...extensions,

View File

@ -0,0 +1,23 @@
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

@ -3,6 +3,7 @@ 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";
export interface DragHandleOptions { export interface DragHandleOptions {
dragHandleWidth: number; dragHandleWidth: number;
@ -135,25 +136,7 @@ function DragHandle(options: DragHandleOptions) {
return new Plugin({ return new Plugin({
key: new PluginKey("dragHandle"), key: new PluginKey("dragHandle"),
view: (view) => { view: (view) => {
dragHandleElement = document.createElement("div"); dragHandleElement = createDragHandleElement();
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);
dragHandleElement.addEventListener("dragstart", (e) => { dragHandleElement.addEventListener("dragstart", (e) => {
handleDragStart(e, view); handleDragStart(e, view);
}); });
@ -213,7 +196,7 @@ function DragHandle(options: DragHandleOptions) {
if (!dragHandleElement) return; if (!dragHandleElement) return;
dragHandleElement.style.left = `${rect.left - rect.width}px`; dragHandleElement.style.left = `${rect.left - rect.width}px`;
dragHandleElement.style.top = `${rect.top}px`; dragHandleElement.style.top = `${rect.top + 3}px`;
showDragHandle(); showDragHandle();
}, },
keydown: () => { keydown: () => {

View File

@ -11,7 +11,6 @@ import { RichTextEditorExtensions } from "./extensions";
export type UploadImage = (file: File) => Promise<string>; export type UploadImage = (file: File) => Promise<string>;
export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise<any>; export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise<any>;
export type ValidateImage = (assetUrlWithWorkspaceId: string) => Promise<any>;
export type IMentionSuggestion = { export type IMentionSuggestion = {
id: string; id: string;
@ -29,7 +28,6 @@ interface IRichTextEditor {
dragDropEnabled?: boolean; dragDropEnabled?: boolean;
uploadFile: UploadImage; uploadFile: UploadImage;
deleteFile: DeleteImage; deleteFile: DeleteImage;
validateFile?: ValidateImage;
noBorder?: boolean; noBorder?: boolean;
borderOnFocus?: boolean; borderOnFocus?: boolean;
cancelUploadImage?: () => any; cancelUploadImage?: () => any;
@ -65,7 +63,6 @@ const RichTextEditor = ({
value, value,
uploadFile, uploadFile,
deleteFile, deleteFile,
validateFile,
noBorder, noBorder,
cancelUploadImage, cancelUploadImage,
borderOnFocus, borderOnFocus,
@ -82,7 +79,6 @@ const RichTextEditor = ({
value, value,
uploadFile, uploadFile,
cancelUploadImage, cancelUploadImage,
validateFile,
deleteFile, deleteFile,
forwardedRef, forwardedRef,
extensions: RichTextEditorExtensions( extensions: RichTextEditorExtensions(

View File

@ -103,7 +103,6 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => {
editor={props.editor!} editor={props.editor!}
isOpen={isNodeSelectorOpen} isOpen={isNodeSelectorOpen}
setIsOpen={() => { setIsOpen={() => {
console.log("setIsNodeSelectorOpen");
setIsNodeSelectorOpen(!isNodeSelectorOpen); setIsNodeSelectorOpen(!isNodeSelectorOpen);
setIsLinkSelectorOpen(false); setIsLinkSelectorOpen(false);
}} }}
@ -113,7 +112,6 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => {
editor={props.editor!!} editor={props.editor!!}
isOpen={isLinkSelectorOpen} isOpen={isLinkSelectorOpen}
setIsOpen={() => { setIsOpen={() => {
console.log("setIsLinkSelectorOpen");
setIsLinkSelectorOpen(!isLinkSelectorOpen); setIsLinkSelectorOpen(!isLinkSelectorOpen);
setIsNodeSelectorOpen(false); setIsNodeSelectorOpen(false);
}} }}

View File

@ -53,11 +53,12 @@ ul[data-type="taskList"] li > label input[type="checkbox"] {
background-color: rgb(var(--color-background-100)); background-color: rgb(var(--color-background-100));
margin: 0; margin: 0;
cursor: pointer; cursor: pointer;
width: 1.2rem; width: 0.8rem;
height: 1.2rem; height: 0.8rem;
position: relative; position: relative;
border: 2px solid rgb(var(--color-text-100)); border: 1.5px solid rgb(var(--color-text-100));
margin-right: 0.3rem; margin-right: 0.2rem;
margin-top: 0.15rem;
display: grid; display: grid;
place-content: center; place-content: center;
@ -71,8 +72,8 @@ ul[data-type="taskList"] li > label input[type="checkbox"] {
&::before { &::before {
content: ""; content: "";
width: 0.65em; width: 0.5em;
height: 0.65em; height: 0.5em;
transform: scale(0); transform: scale(0);
transition: 120ms transform ease-in-out; transition: 120ms transform ease-in-out;
box-shadow: inset 1em 1em; box-shadow: inset 1em 1em;
@ -229,3 +230,93 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
.ProseMirror table * .is-empty::before { .ProseMirror table * .is-empty::before {
opacity: 0; opacity: 0;
} }
.ProseMirror pre {
background: rgba(var(--color-background-80));
border-radius: 0.5rem;
color: rgba(var(--color-text-100));
font-family: "JetBrainsMono", monospace;
padding: 0.75rem 1rem;
}
.ProseMirror pre code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
.ProseMirror:not(.dragging) .ProseMirror-selectednode:not(img):not(pre) {
outline: none !important;
border-radius: 0.2rem;
background-color: rgb(var(--color-background-90));
border: 1px solid #5abbf7;
padding: 4px 2px 4px 2px;
transition: background-color 0.2s;
box-shadow: none;
}
.drag-handle {
position: fixed;
opacity: 1;
transition: opacity ease-in 0.2s;
height: 18px;
width: 15px;
display: grid;
place-items: center;
z-index: 10;
cursor: grab;
border-radius: 2px;
background-color: rgb(var(--color-background-90));
}
.drag-handle:hover {
background-color: rgb(var(--color-background-80));
transition: background-color 0.2s;
}
.drag-handle.hidden {
opacity: 0;
pointer-events: none;
}
@media screen and (max-width: 600px) {
.drag-handle {
display: none;
pointer-events: none;
}
}
.drag-handle-container {
height: 15px;
width: 15px;
cursor: grab;
display: grid;
place-items: center;
}
.drag-handle-dots {
height: 100%;
width: 12px;
display: grid;
grid-template-columns: repeat(2, 1fr);
place-items: center;
}
.drag-handle-dot {
height: 2.75px;
width: 3px;
background-color: rgba(var(--color-text-200));
border-radius: 50%;
}
div[data-type="horizontalRule"] {
line-height: 0;
padding: 0.25rem 0;
margin-top: 0;
margin-bottom: 0;
& > div {
border-bottom: 1px solid rgb(var(--color-text-100));
}
}

View File

@ -34,7 +34,6 @@ export class FileService extends APIService {
constructor() { constructor() {
super(API_BASE_URL); super(API_BASE_URL);
this.uploadFile = this.uploadFile.bind(this); this.uploadFile = this.uploadFile.bind(this);
// this.validateFile = this.validateFile.bind(this);
this.deleteImage = this.deleteImage.bind(this); this.deleteImage = this.deleteImage.bind(this);
this.cancelUpload = this.cancelUpload.bind(this); this.cancelUpload = this.cancelUpload.bind(this);
} }
@ -63,14 +62,6 @@ export class FileService extends APIService {
this.cancelSource.cancel("Upload cancelled"); this.cancelSource.cancel("Upload cancelled");
} }
// async validateFile(assetUrlWithWorkspaceId: string): Promise<any> {
// console.log("bruh", assetUrlWithWorkspaceId);
// const res = await this.get(`/api/workspaces/file-assets/${assetUrlWithWorkspaceId}/`);
// const data = res?.data;
// console.log("data inside fucntion");
// return data.status;
// }
//
getUploadFileFunction(workspaceSlug: string): (file: File) => Promise<string> { getUploadFileFunction(workspaceSlug: string): (file: File) => Promise<string> {
return async (file: File) => { return async (file: File) => {
const formData = new FormData(); const formData = new FormData();

View File

@ -6,6 +6,12 @@
height: 0; height: 0;
} }
/* block quotes */
.ProseMirror blockquote p::before,
.ProseMirror blockquote p::after {
display: none;
}
.ProseMirror .is-empty::before { .ProseMirror .is-empty::before {
content: attr(data-placeholder); content: attr(data-placeholder);
float: left; float: left;
@ -53,11 +59,12 @@ ul[data-type="taskList"] li > label input[type="checkbox"] {
background-color: rgb(var(--color-background-100)); background-color: rgb(var(--color-background-100));
margin: 0; margin: 0;
cursor: pointer; cursor: pointer;
width: 1.2rem; width: 0.8rem;
height: 1.2rem; height: 0.8rem;
position: relative; position: relative;
border: 2px solid rgb(var(--color-text-100)); border: 1.5px solid rgb(var(--color-text-100));
margin-right: 0.3rem; margin-right: 0.2rem;
margin-top: 0.15rem;
display: grid; display: grid;
place-content: center; place-content: center;
@ -71,8 +78,8 @@ ul[data-type="taskList"] li > label input[type="checkbox"] {
&::before { &::before {
content: ""; content: "";
width: 0.65em; width: 0.5em;
height: 0.65em; height: 0.5em;
transform: scale(0); transform: scale(0);
transition: 120ms transform ease-in-out; transition: 120ms transform ease-in-out;
box-shadow: inset 1em 1em; box-shadow: inset 1em 1em;
@ -259,26 +266,18 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
position: fixed; position: fixed;
opacity: 1; opacity: 1;
transition: opacity ease-in 0.2s; transition: opacity ease-in 0.2s;
height: 18px;
width: 15px;
display: grid; display: grid;
place-items: center; place-items: center;
height: 20px;
width: 15px;
z-index: 10; z-index: 10;
cursor: grab; cursor: grab;
border-radius: 2px; border-radius: 2px;
background-color: rgb(var(--color-background-90)); background-color: rgb(var(--color-background-90));
&:hover {
background-color: rgb(var(--color-background-80));
}
&.hide {
opacity: 0;
pointer-events: none;
}
} }
.drag-handle:hover { .drag-handle:hover {
background-color: #0d0d0d 10; background-color: rgb(var(--color-background-80));
transition: background-color 0.2s; transition: background-color 0.2s;
} }
@ -295,7 +294,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
} }
.drag-handle-container { .drag-handle-container {
height: 20px; height: 15px;
width: 15px; width: 15px;
cursor: grab; cursor: grab;
display: grid; display: grid;
@ -313,7 +312,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
.drag-handle-dot { .drag-handle-dot {
height: 2.75px; height: 2.75px;
width: 3px; width: 3px;
background-color: rgba(var(--color-text-100)); background-color: rgba(var(--color-text-200));
border-radius: 50%; border-radius: 50%;
} }