[WEB-2494] dev: custom text color and background color extensions (#5786)

* dev: created custom text color and background color extensions

* chore: update slash commands icon style

* chore: update constants

* chore: update variables css file selectors
This commit is contained in:
Aaryan Khandelwal 2024-10-11 19:11:39 +05:30 committed by GitHub
parent 74695e561a
commit e7065af358
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 604 additions and 393 deletions

View file

@ -42,8 +42,6 @@
"@tiptap/extension-blockquote": "^2.1.13", "@tiptap/extension-blockquote": "^2.1.13",
"@tiptap/extension-character-count": "^2.6.5", "@tiptap/extension-character-count": "^2.6.5",
"@tiptap/extension-collaboration": "^2.3.2", "@tiptap/extension-collaboration": "^2.3.2",
"@tiptap/extension-color": "^2.7.1",
"@tiptap/extension-highlight": "^2.7.1",
"@tiptap/extension-image": "^2.1.13", "@tiptap/extension-image": "^2.1.13",
"@tiptap/extension-list-item": "^2.1.13", "@tiptap/extension-list-item": "^2.1.13",
"@tiptap/extension-mention": "^2.1.13", "@tiptap/extension-mention": "^2.1.13",

View file

@ -16,12 +16,8 @@ type Props = {
export const BubbleMenuColorSelector: FC<Props> = (props) => { export const BubbleMenuColorSelector: FC<Props> = (props) => {
const { editor, isOpen, setIsOpen } = props; const { editor, isOpen, setIsOpen } = props;
const activeTextColor = COLORS_LIST.find((c) => editor.getAttributes("textStyle").color === c.textColor); const activeTextColor = COLORS_LIST.find((c) => editor.getAttributes("textStyle").color === c.key);
const activeBackgroundColor = COLORS_LIST.find((c) => const activeBackgroundColor = COLORS_LIST.find((c) => editor.getAttributes("textStyle").backgroundColor === c.key);
editor.isActive("highlight", {
color: c.backgroundColor,
})
);
return ( return (
<div className="relative h-full"> <div className="relative h-full">
@ -41,25 +37,17 @@ export const BubbleMenuColorSelector: FC<Props> = (props) => {
"bg-custom-background-100": !activeBackgroundColor, "bg-custom-background-100": !activeBackgroundColor,
} }
)} )}
style={ style={{
activeBackgroundColor backgroundColor: activeBackgroundColor ? activeBackgroundColor.backgroundColor : "transparent",
? { }}
backgroundColor: activeBackgroundColor.backgroundColor,
}
: {}
}
> >
<ALargeSmall <ALargeSmall
className={cn("size-3.5", { className={cn("size-3.5", {
"text-custom-text-100": !activeTextColor, "text-custom-text-100": !activeTextColor,
})} })}
style={ style={{
activeTextColor color: activeTextColor ? activeTextColor.textColor : "inherit",
? { }}
color: activeTextColor.textColor,
}
: {}
}
/> />
</span> </span>
</button> </button>
@ -70,13 +58,13 @@ export const BubbleMenuColorSelector: FC<Props> = (props) => {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{COLORS_LIST.map((color) => ( {COLORS_LIST.map((color) => (
<button <button
key={color.textColor} key={color.key}
type="button" type="button"
className="flex-shrink-0 size-6 rounded border-[0.5px] border-custom-border-400 hover:opacity-60 transition-opacity" className="flex-shrink-0 size-6 rounded border-[0.5px] border-custom-border-400 hover:opacity-60 transition-opacity"
style={{ style={{
backgroundColor: color.textColor, backgroundColor: color.textColor,
}} }}
onClick={() => TextColorItem(editor).command(color.textColor)} onClick={() => TextColorItem(editor).command(color.key)}
/> />
))} ))}
<button <button
@ -93,13 +81,13 @@ export const BubbleMenuColorSelector: FC<Props> = (props) => {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{COLORS_LIST.map((color) => ( {COLORS_LIST.map((color) => (
<button <button
key={color.backgroundColor} key={color.key}
type="button" type="button"
className="flex-shrink-0 size-6 rounded border-[0.5px] border-custom-border-400 hover:opacity-60 transition-opacity" className="flex-shrink-0 size-6 rounded border-[0.5px] border-custom-border-400 hover:opacity-60 transition-opacity"
style={{ style={{
backgroundColor: color.backgroundColor, backgroundColor: color.backgroundColor,
}} }}
onClick={() => BackgroundColorItem(editor).command(color.backgroundColor)} onClick={() => BackgroundColorItem(editor).command(color.key)}
/> />
))} ))}
<button <button

View file

@ -219,7 +219,7 @@ export const TextColorItem = (editor: Editor): EditorMenuItem => ({
export const BackgroundColorItem = (editor: Editor): EditorMenuItem => ({ export const BackgroundColorItem = (editor: Editor): EditorMenuItem => ({
key: "background-color", key: "background-color",
name: "Background color", name: "Background color",
isActive: (color) => editor.isActive("highlight", { color }), isActive: (color) => editor.getAttributes("textStyle").backgroundColor === color,
command: (color: string) => toggleBackgroundColor(color, editor), command: (color: string) => toggleBackgroundColor(color, editor),
icon: Palette, icon: Palette,
}); });

View file

@ -1,51 +1,61 @@
export const COLORS_LIST: { export const COLORS_LIST: {
backgroundColor: string; key: string;
textColor: string;
label: string; label: string;
textColor: string;
backgroundColor: string;
}[] = [ }[] = [
// {
// backgroundColor: "#1c202426",
// textColor: "#1c2024",
// label: "Black",
// },
{ {
backgroundColor: "#5c5e6326", key: "gray",
textColor: "#5c5e63",
label: "Gray", label: "Gray",
textColor: "var(--editor-colors-gray-text)",
backgroundColor: "var(--editor-colors-gray-background)",
}, },
{ {
backgroundColor: "#ff5b5926", key: "peach",
textColor: "#ff5b59",
label: "Peach", label: "Peach",
textColor: "var(--editor-colors-peach-text)",
backgroundColor: "var(--editor-colors-peach-background)",
}, },
{ {
backgroundColor: "#f6538526", key: "pink",
textColor: "#f65385",
label: "Pink", label: "Pink",
textColor: "var(--editor-colors-pink-text)",
backgroundColor: "var(--editor-colors-pink-background)",
}, },
{ {
backgroundColor: "#fd903826", key: "orange",
textColor: "#fd9038",
label: "Orange", label: "Orange",
textColor: "var(--editor-colors-orange-text)",
backgroundColor: "var(--editor-colors-orange-background)",
}, },
{ {
backgroundColor: "#0fc27b26", key: "green",
textColor: "#0fc27b",
label: "Green", label: "Green",
textColor: "var(--editor-colors-green-text)",
backgroundColor: "var(--editor-colors-green-background)",
}, },
{ {
backgroundColor: "#17bee926", key: "light-blue",
textColor: "#17bee9",
label: "Light blue", label: "Light blue",
textColor: "var(--editor-colors-light-blue-text)",
backgroundColor: "var(--editor-colors-light-blue-background)",
}, },
{ {
backgroundColor: "#266df026", key: "dark-blue",
textColor: "#266df0",
label: "Dark blue", label: "Dark blue",
textColor: "var(--editor-colors-dark-blue-text)",
backgroundColor: "var(--editor-colors-dark-blue-background)",
}, },
{ {
backgroundColor: "#9162f926", key: "purple",
textColor: "#9162f9",
label: "Purple", label: "Purple",
textColor: "var(--editor-colors-purple-text)",
backgroundColor: "var(--editor-colors-purple-background)",
}, },
// {
// key: "pink-blue-gradient",
// label: "Pink blue gradient",
// textColor: "var(--editor-colors-pink-blue-gradient-text)",
// backgroundColor: "var(--editor-colors-pink-blue-gradient-background)",
// },
]; ];

View file

@ -1,5 +1,3 @@
import { Color } from "@tiptap/extension-color";
import Highlight from "@tiptap/extension-highlight";
import TaskItem from "@tiptap/extension-task-item"; import TaskItem from "@tiptap/extension-task-item";
import TaskList from "@tiptap/extension-task-list"; import TaskList from "@tiptap/extension-task-list";
import TextStyle from "@tiptap/extension-text-style"; import TextStyle from "@tiptap/extension-text-style";
@ -18,6 +16,8 @@ import { IssueWidgetWithoutProps } from "./issue-embed/issue-embed-without-props
import { CustomMentionWithoutProps } from "./mentions/mentions-without-props"; import { CustomMentionWithoutProps } from "./mentions/mentions-without-props";
import { CustomQuoteExtension } from "./quote"; import { CustomQuoteExtension } from "./quote";
import { TableHeader, TableCell, TableRow, Table } from "./table"; import { TableHeader, TableCell, TableRow, Table } from "./table";
import { CustomTextColorExtension } from "./custom-text-color";
import { CustomBackgroundColorExtension } from "./custom-background-color";
export const CoreEditorExtensionsWithoutProps = [ export const CoreEditorExtensionsWithoutProps = [
StarterKit.configure({ StarterKit.configure({
@ -85,10 +85,8 @@ export const CoreEditorExtensionsWithoutProps = [
TableCell, TableCell,
TableRow, TableRow,
CustomMentionWithoutProps(), CustomMentionWithoutProps(),
Color, CustomTextColorExtension,
Highlight.configure({ CustomBackgroundColorExtension,
multicolor: true,
}),
]; ];
export const DocumentEditorExtensionsWithoutProps = [IssueWidgetWithoutProps()]; export const DocumentEditorExtensionsWithoutProps = [IssueWidgetWithoutProps()];

View file

@ -0,0 +1,78 @@
import { Extension } from "@tiptap/core";
// constants
import { COLORS_LIST } from "@/constants/common";
declare module "@tiptap/core" {
interface Commands<ReturnType> {
backgroundColor: {
/**
* Set the background color
* @param color The color to set
* @example editor.commands.setBackgroundColor('red')
*/
setBackgroundColor: (color: string) => ReturnType;
/**
* Unset the background color
* @example editor.commands.unsetBackgroundColor()
*/
unsetBackgroundColor: () => ReturnType;
};
}
}
export const CustomBackgroundColorExtension = Extension.create({
name: "customBackgroundColor",
addOptions() {
return {
types: ["textStyle"],
};
},
addGlobalAttributes() {
return [
{
types: this.options.types,
attributes: {
backgroundColor: {
default: null,
parseHTML: (element: HTMLElement) => element.getAttribute("data-background-color"),
renderHTML: (attributes: { backgroundColor: string }) => {
const { backgroundColor } = attributes;
if (!backgroundColor) {
return {};
}
let elementAttributes: Record<string, string> = {
"data-background-color": backgroundColor,
};
if (!COLORS_LIST.find((c) => c.key === backgroundColor)) {
elementAttributes = {
...elementAttributes,
style: `background-color: ${backgroundColor}`,
};
}
return elementAttributes;
},
},
},
},
];
},
addCommands() {
return {
setBackgroundColor:
(backgroundColor: string) =>
({ chain }) =>
chain().setMark("textStyle", { backgroundColor }).run(),
unsetBackgroundColor:
() =>
({ chain }) =>
chain().setMark("textStyle", { backgroundColor: null }).run(),
};
},
});

View file

@ -0,0 +1,78 @@
import { Extension } from "@tiptap/core";
// constants
import { COLORS_LIST } from "@/constants/common";
declare module "@tiptap/core" {
interface Commands<ReturnType> {
color: {
/**
* Set the text color
* @param color The color to set
* @example editor.commands.setColor('red')
*/
setTextColor: (color: string) => ReturnType;
/**
* Unset the text color
* @example editor.commands.unsetColor()
*/
unsetTextColor: () => ReturnType;
};
}
}
export const CustomTextColorExtension = Extension.create({
name: "customTextColor",
addOptions() {
return {
types: ["textStyle"],
};
},
addGlobalAttributes() {
return [
{
types: this.options.types,
attributes: {
color: {
default: null,
parseHTML: (element: HTMLElement) => element.getAttribute("data-text-color"),
renderHTML: (attributes: { color: string }) => {
const { color } = attributes;
if (!color) {
return {};
}
let elementAttributes: Record<string, string> = {
"data-text-color": color,
};
if (!COLORS_LIST.find((c) => c.key === color)) {
elementAttributes = {
...elementAttributes,
style: `color: ${color}`,
};
}
return elementAttributes;
},
},
},
},
];
},
addCommands() {
return {
setTextColor:
(color: string) =>
({ chain }) =>
chain().setMark("textStyle", { color }).run(),
unsetTextColor:
() =>
({ chain }) =>
chain().setMark("textStyle", { color: null }).run(),
};
},
});

View file

@ -1,6 +1,4 @@
import CharacterCount from "@tiptap/extension-character-count"; import CharacterCount from "@tiptap/extension-character-count";
import { Color } from "@tiptap/extension-color";
import Highlight from "@tiptap/extension-highlight";
import Placeholder from "@tiptap/extension-placeholder"; import Placeholder from "@tiptap/extension-placeholder";
import TaskItem from "@tiptap/extension-task-item"; import TaskItem from "@tiptap/extension-task-item";
import TaskList from "@tiptap/extension-task-list"; import TaskList from "@tiptap/extension-task-list";
@ -10,6 +8,7 @@ import StarterKit from "@tiptap/starter-kit";
import { Markdown } from "tiptap-markdown"; import { Markdown } from "tiptap-markdown";
// extensions // extensions
import { import {
CustomBackgroundColorExtension,
CustomCodeBlockExtension, CustomCodeBlockExtension,
CustomCodeInlineExtension, CustomCodeInlineExtension,
CustomCodeMarkPlugin, CustomCodeMarkPlugin,
@ -19,6 +18,7 @@ import {
CustomLinkExtension, CustomLinkExtension,
CustomMention, CustomMention,
CustomQuoteExtension, CustomQuoteExtension,
CustomTextColorExtension,
CustomTypographyExtension, CustomTypographyExtension,
DropHandlerExtension, DropHandlerExtension,
ImageExtension, ImageExtension,
@ -168,8 +168,6 @@ export const CoreEditorExtensions = ({
includeChildren: true, includeChildren: true,
}), }),
CharacterCount, CharacterCount,
Color, CustomTextColorExtension,
Highlight.configure({ CustomBackgroundColorExtension,
multicolor: true,
}),
]; ];

View file

@ -10,7 +10,9 @@ export * from "./slash-commands";
export * from "./table"; export * from "./table";
export * from "./typography"; export * from "./typography";
export * from "./core-without-props"; export * from "./core-without-props";
export * from "./custom-background-color";
export * from "./custom-code-inline"; export * from "./custom-code-inline";
export * from "./custom-text-color";
export * from "./drop"; export * from "./drop";
export * from "./enter-key-extension"; export * from "./enter-key-extension";
export * from "./extensions"; export * from "./extensions";

View file

@ -1,6 +1,4 @@
import CharacterCount from "@tiptap/extension-character-count"; import CharacterCount from "@tiptap/extension-character-count";
import { Color } from "@tiptap/extension-color";
import Highlight from "@tiptap/extension-highlight";
import TaskItem from "@tiptap/extension-task-item"; import TaskItem from "@tiptap/extension-task-item";
import TaskList from "@tiptap/extension-task-list"; import TaskList from "@tiptap/extension-task-list";
import TextStyle from "@tiptap/extension-text-style"; import TextStyle from "@tiptap/extension-text-style";
@ -23,6 +21,8 @@ import {
CustomMention, CustomMention,
HeadingListExtension, HeadingListExtension,
CustomReadOnlyImageExtension, CustomReadOnlyImageExtension,
CustomTextColorExtension,
CustomBackgroundColorExtension,
} from "@/extensions"; } from "@/extensions";
// helpers // helpers
import { isValidHttpUrl } from "@/helpers/common"; import { isValidHttpUrl } from "@/helpers/common";
@ -111,9 +111,7 @@ export const CoreReadOnlyEditorExtensions = (mentionConfig: {
readonly: true, readonly: true,
}), }),
CharacterCount, CharacterCount,
Color, CustomTextColorExtension,
Highlight.configure({ CustomBackgroundColorExtension,
multicolor: true,
}),
HeadingListExtension, HeadingListExtension,
]; ];

View file

@ -44,237 +44,237 @@ export type TSlashCommandSection = {
items: ISlashCommandItem[]; items: ISlashCommandItem[];
}; };
const SLASH_COMMAND_SECTIONS: TSlashCommandSection[] = [ export const getSlashCommandFilteredSections =
{ (additionalOptions?: ISlashCommandItem[]) =>
key: "general", ({ query }: { query: string }): TSlashCommandSection[] => {
items: [ const SLASH_COMMAND_SECTIONS: TSlashCommandSection[] = [
{ {
commandKey: "text", key: "general",
key: "text", items: [
title: "Text", {
description: "Just start typing with plain text.", commandKey: "text",
searchTerms: ["p", "paragraph"], key: "text",
icon: <CaseSensitive className="size-3.5" />, title: "Text",
command: ({ editor, range }: CommandProps) => { description: "Just start typing with plain text.",
if (range) { searchTerms: ["p", "paragraph"],
editor.chain().focus().deleteRange(range).clearNodes().run(); icon: <CaseSensitive className="size-3.5" />,
} command: ({ editor, range }: CommandProps) => {
editor.chain().focus().clearNodes().run(); if (range) {
}, editor.chain().focus().deleteRange(range).clearNodes().run();
}
editor.chain().focus().clearNodes().run();
},
},
{
commandKey: "h1",
key: "h1",
title: "Heading 1",
description: "Big section heading.",
searchTerms: ["title", "big", "large"],
icon: <Heading1 className="size-3.5" />,
command: ({ editor, range }) => toggleHeadingOne(editor, range),
},
{
commandKey: "h2",
key: "h2",
title: "Heading 2",
description: "Medium section heading.",
searchTerms: ["subtitle", "medium"],
icon: <Heading2 className="size-3.5" />,
command: ({ editor, range }) => toggleHeadingTwo(editor, range),
},
{
commandKey: "h3",
key: "h3",
title: "Heading 3",
description: "Small section heading.",
searchTerms: ["subtitle", "small"],
icon: <Heading3 className="size-3.5" />,
command: ({ editor, range }) => toggleHeadingThree(editor, range),
},
{
commandKey: "h4",
key: "h4",
title: "Heading 4",
description: "Small section heading.",
searchTerms: ["subtitle", "small"],
icon: <Heading4 className="size-3.5" />,
command: ({ editor, range }) => toggleHeadingFour(editor, range),
},
{
commandKey: "h5",
key: "h5",
title: "Heading 5",
description: "Small section heading.",
searchTerms: ["subtitle", "small"],
icon: <Heading5 className="size-3.5" />,
command: ({ editor, range }) => toggleHeadingFive(editor, range),
},
{
commandKey: "h6",
key: "h6",
title: "Heading 6",
description: "Small section heading.",
searchTerms: ["subtitle", "small"],
icon: <Heading6 className="size-3.5" />,
command: ({ editor, range }) => toggleHeadingSix(editor, range),
},
{
commandKey: "to-do-list",
key: "to-do-list",
title: "To do",
description: "Track tasks with a to-do list.",
searchTerms: ["todo", "task", "list", "check", "checkbox"],
icon: <ListTodo className="size-3.5" />,
command: ({ editor, range }) => toggleTaskList(editor, range),
},
{
commandKey: "bulleted-list",
key: "bulleted-list",
title: "Bullet list",
description: "Create a simple bullet list.",
searchTerms: ["unordered", "point"],
icon: <List className="size-3.5" />,
command: ({ editor, range }) => toggleBulletList(editor, range),
},
{
commandKey: "numbered-list",
key: "numbered-list",
title: "Numbered list",
description: "Create a list with numbering.",
searchTerms: ["ordered"],
icon: <ListOrdered className="size-3.5" />,
command: ({ editor, range }) => toggleOrderedList(editor, range),
},
{
commandKey: "table",
key: "table",
title: "Table",
description: "Create a table",
searchTerms: ["table", "cell", "db", "data", "tabular"],
icon: <Table className="size-3.5" />,
command: ({ editor, range }) => insertTableCommand(editor, range),
},
{
commandKey: "quote",
key: "quote",
title: "Quote",
description: "Capture a quote.",
searchTerms: ["blockquote"],
icon: <Quote className="size-3.5" />,
command: ({ editor, range }) => toggleBlockquote(editor, range),
},
{
commandKey: "code",
key: "code",
title: "Code",
description: "Capture a code snippet.",
searchTerms: ["codeblock"],
icon: <Code2 className="size-3.5" />,
command: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleCodeBlock().run(),
},
{
commandKey: "image",
key: "image",
title: "Image",
icon: <ImageIcon className="size-3.5" />,
description: "Insert an image",
searchTerms: ["img", "photo", "picture", "media", "upload"],
command: ({ editor, range }: CommandProps) => insertImage({ editor, event: "insert", range }),
},
{
commandKey: "divider",
key: "divider",
title: "Divider",
description: "Visually divide blocks.",
searchTerms: ["line", "divider", "horizontal", "rule", "separate"],
icon: <MinusSquare className="size-3.5" />,
command: ({ editor, range }) => editor.chain().focus().deleteRange(range).setHorizontalRule().run(),
},
],
}, },
{ {
commandKey: "h1", key: "text-color",
key: "h1", title: "Colors",
title: "Heading 1", items: [
description: "Big section heading.", {
searchTerms: ["title", "big", "large"],
icon: <Heading1 className="size-3.5" />,
command: ({ editor, range }) => toggleHeadingOne(editor, range),
},
{
commandKey: "h2",
key: "h2",
title: "Heading 2",
description: "Medium section heading.",
searchTerms: ["subtitle", "medium"],
icon: <Heading2 className="size-3.5" />,
command: ({ editor, range }) => toggleHeadingTwo(editor, range),
},
{
commandKey: "h3",
key: "h3",
title: "Heading 3",
description: "Small section heading.",
searchTerms: ["subtitle", "small"],
icon: <Heading3 className="size-3.5" />,
command: ({ editor, range }) => toggleHeadingThree(editor, range),
},
{
commandKey: "h4",
key: "h4",
title: "Heading 4",
description: "Small section heading.",
searchTerms: ["subtitle", "small"],
icon: <Heading4 className="size-3.5" />,
command: ({ editor, range }) => toggleHeadingFour(editor, range),
},
{
commandKey: "h5",
key: "h5",
title: "Heading 5",
description: "Small section heading.",
searchTerms: ["subtitle", "small"],
icon: <Heading5 className="size-3.5" />,
command: ({ editor, range }) => toggleHeadingFive(editor, range),
},
{
commandKey: "h6",
key: "h6",
title: "Heading 6",
description: "Small section heading.",
searchTerms: ["subtitle", "small"],
icon: <Heading6 className="size-3.5" />,
command: ({ editor, range }) => toggleHeadingSix(editor, range),
},
{
commandKey: "to-do-list",
key: "to-do-list",
title: "To do",
description: "Track tasks with a to-do list.",
searchTerms: ["todo", "task", "list", "check", "checkbox"],
icon: <ListTodo className="size-3.5" />,
command: ({ editor, range }) => toggleTaskList(editor, range),
},
{
commandKey: "bulleted-list",
key: "bulleted-list",
title: "Bullet list",
description: "Create a simple bullet list.",
searchTerms: ["unordered", "point"],
icon: <List className="size-3.5" />,
command: ({ editor, range }) => toggleBulletList(editor, range),
},
{
commandKey: "numbered-list",
key: "numbered-list",
title: "Numbered list",
description: "Create a list with numbering.",
searchTerms: ["ordered"],
icon: <ListOrdered className="size-3.5" />,
command: ({ editor, range }) => toggleOrderedList(editor, range),
},
{
commandKey: "table",
key: "table",
title: "Table",
description: "Create a table",
searchTerms: ["table", "cell", "db", "data", "tabular"],
icon: <Table className="size-3.5" />,
command: ({ editor, range }) => insertTableCommand(editor, range),
},
{
commandKey: "quote",
key: "quote",
title: "Quote",
description: "Capture a quote.",
searchTerms: ["blockquote"],
icon: <Quote className="size-3.5" />,
command: ({ editor, range }) => toggleBlockquote(editor, range),
},
{
commandKey: "code",
key: "code",
title: "Code",
description: "Capture a code snippet.",
searchTerms: ["codeblock"],
icon: <Code2 className="size-3.5" />,
command: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleCodeBlock().run(),
},
{
commandKey: "image",
key: "image",
title: "Image",
icon: <ImageIcon className="size-3.5" />,
description: "Insert an image",
searchTerms: ["img", "photo", "picture", "media", "upload"],
command: ({ editor, range }: CommandProps) => insertImage({ editor, event: "insert", range }),
},
{
commandKey: "divider",
key: "divider",
title: "Divider",
description: "Visually divide blocks.",
searchTerms: ["line", "divider", "horizontal", "rule", "separate"],
icon: <MinusSquare className="size-3.5" />,
command: ({ editor, range }) => editor.chain().focus().deleteRange(range).setHorizontalRule().run(),
},
],
},
{
key: "text-color",
title: "Colors",
items: [
{
commandKey: "text-color",
key: "text-color-default",
title: "Default",
description: "Change text color",
searchTerms: ["color", "text", "default"],
icon: (
<ALargeSmall
className="size-3.5"
style={{
color: "rgba(var(--color-text-100))",
}}
/>
),
command: ({ editor, range }) => toggleTextColor(undefined, editor, range),
},
...COLORS_LIST.map(
(color) =>
({
commandKey: "text-color", commandKey: "text-color",
key: `text-color-${color.textColor}`, key: "text-color-default",
title: color.label, title: "Default",
description: "Change text color", description: "Change text color",
searchTerms: ["color", "text", color.label], searchTerms: ["color", "text", "default"],
icon: ( icon: (
<ALargeSmall <ALargeSmall
className="size-3.5" className="size-3.5"
style={{ style={{
color: color.textColor, color: "rgba(var(--color-text-100))",
}} }}
/> />
), ),
command: ({ editor, range }) => toggleTextColor(color.textColor, editor, range), command: ({ editor, range }) => toggleTextColor(undefined, editor, range),
}) as ISlashCommandItem },
), ...COLORS_LIST.map(
], (color) =>
}, ({
{ commandKey: "text-color",
key: "background-color", key: `text-color-${color.key}`,
title: "Background colors", title: color.label,
items: [ description: "Change text color",
{ searchTerms: ["color", "text", color.label],
commandKey: "background-color", icon: (
key: "background-color-default", <ALargeSmall
title: "Default background", className="size-3.5"
description: "Change background color", style={{
searchTerms: ["color", "bg", "background", "default"], color: color.textColor,
icon: <ALargeSmall className="size-3.5" />, }}
iconContainerStyle: { />
borderRadius: "4px", ),
backgroundColor: "rgba(var(--color-background-100))", command: ({ editor, range }) => toggleTextColor(color.key, editor, range),
border: "1px solid rgba(var(--color-border-300))", }) as ISlashCommandItem
}, ),
command: ({ editor, range }) => toggleTextColor(undefined, editor, range), ],
}, },
...COLORS_LIST.map( {
(color) => key: "background-color",
({ title: "Background colors",
items: [
{
commandKey: "background-color", commandKey: "background-color",
key: `background-color-${color.backgroundColor}`, key: "background-color-default",
title: `${color.label} background`, title: "Default background",
description: "Change background color", description: "Change background color",
searchTerms: ["color", "bg", "background", color.label], searchTerms: ["color", "bg", "background", "default"],
icon: <ALargeSmall className="size-3.5" />, icon: <ALargeSmall className="size-3.5" />,
iconContainerStyle: { iconContainerStyle: {
borderRadius: "4px", borderRadius: "4px",
backgroundColor: color.backgroundColor, backgroundColor: "rgba(var(--color-background-100))",
border: "1px solid rgba(var(--color-border-300))",
}, },
command: ({ editor, range }) => toggleBackgroundColor(color.backgroundColor, editor, range), command: ({ editor, range }) => toggleTextColor(undefined, editor, range),
}) as ISlashCommandItem },
), ...COLORS_LIST.map(
], (color) =>
}, ({
]; commandKey: "background-color",
key: `background-color-${color.key}`,
title: color.label,
description: "Change background color",
searchTerms: ["color", "bg", "background", color.label],
icon: <ALargeSmall className="size-3.5" />,
iconContainerStyle: {
borderRadius: "4px",
backgroundColor: color.backgroundColor,
},
command: ({ editor, range }) => toggleBackgroundColor(color.key, editor, range),
}) as ISlashCommandItem
),
],
},
];
export const getSlashCommandFilteredSections = additionalOptions?.map((item) => {
(additionalOptions?: ISlashCommandItem[]) => SLASH_COMMAND_SECTIONS?.[0]?.items.push(item);
({ query }: { query: string }): TSlashCommandSection[] => { });
if (additionalOptions) {
additionalOptions.map((item) => SLASH_COMMAND_SECTIONS?.[0]?.items.push(item));
}
const filteredSlashSections = SLASH_COMMAND_SECTIONS.map((section) => ({ const filteredSlashSections = SLASH_COMMAND_SECTIONS.map((section) => ({
...section, ...section,

View file

@ -6,8 +6,6 @@ import { CommandMenuItem } from "./command-menu-item";
type Props = { type Props = {
items: TSlashCommandSection[]; items: TSlashCommandSection[];
command: any; command: any;
editor: any;
range: any;
}; };
export const SlashCommandsMenu = (props: Props) => { export const SlashCommandsMenu = (props: Props) => {
@ -22,7 +20,7 @@ export const SlashCommandsMenu = (props: Props) => {
const selectItem = useCallback( const selectItem = useCallback(
(sectionIndex: number, itemIndex: number) => { (sectionIndex: number, itemIndex: number) => {
const item = sections[sectionIndex].items[itemIndex]; const item = sections[sectionIndex]?.items?.[itemIndex];
if (item) command(item); if (item) command(item);
}, },
[command, sections] [command, sections]

View file

@ -66,8 +66,6 @@ const renderItems = () => {
const tippyContainer = const tippyContainer =
document.querySelector(".active-editor") ?? document.querySelector('[id^="editor-container"]'); document.querySelector(".active-editor") ?? document.querySelector('[id^="editor-container"]');
// @ts-expect-error Tippy overloads are messed up
popup = tippy("body", { popup = tippy("body", {
getReferenceClientRect: props.clientRect, getReferenceClientRect: props.clientRect,
appendTo: tippyContainer, appendTo: tippyContainer,

View file

@ -157,39 +157,26 @@ export const setLinkEditor = (editor: Editor, url: string) => {
export const toggleTextColor = (color: string | undefined, editor: Editor, range?: Range) => { export const toggleTextColor = (color: string | undefined, editor: Editor, range?: Range) => {
if (color) { if (color) {
if (range) editor.chain().focus().deleteRange(range).setColor(color).run(); if (range) editor.chain().focus().deleteRange(range).setTextColor(color).run();
else editor.chain().focus().setColor(color).run(); else editor.chain().focus().setTextColor(color).run();
} else { } else {
if (range) editor.chain().focus().deleteRange(range).unsetColor().run(); if (range) editor.chain().focus().deleteRange(range).unsetTextColor().run();
else editor.chain().focus().unsetColor().run(); else editor.chain().focus().unsetTextColor().run();
} }
}; };
export const toggleBackgroundColor = (color: string | undefined, editor: Editor, range?: Range) => { export const toggleBackgroundColor = (color: string | undefined, editor: Editor, range?: Range) => {
if (color) { if (color) {
if (range) { if (range) {
editor editor.chain().focus().deleteRange(range).setBackgroundColor(color).run();
.chain()
.focus()
.deleteRange(range)
.setHighlight({
color,
})
.run();
} else { } else {
editor editor.chain().focus().setBackgroundColor(color).run();
.chain()
.focus()
.setHighlight({
color,
})
.run();
} }
} else { } else {
if (range) { if (range) {
editor.chain().focus().deleteRange(range).unsetHighlight().run(); editor.chain().focus().deleteRange(range).unsetBackgroundColor().run();
} else { } else {
editor.chain().focus().unsetHighlight().run(); editor.chain().focus().unsetBackgroundColor().run();
} }
} }
}; };

View file

@ -1,5 +1,6 @@
// styles // styles
// import "./styles/tailwind.css"; // import "./styles/tailwind.css";
import "src/styles/variables.css";
import "src/styles/editor.css"; import "src/styles/editor.css";
import "src/styles/table.css"; import "src/styles/table.css";
import "src/styles/github-dark.css"; import "src/styles/github-dark.css";

View file

@ -1,61 +1,3 @@
.editor-container {
&.large-font {
--font-size-h1: 1.75rem;
--font-size-h2: 1.5rem;
--font-size-h3: 1.375rem;
--font-size-h4: 1.25rem;
--font-size-h5: 1.125rem;
--font-size-h6: 1rem;
--font-size-regular: 1rem;
--font-size-list: var(--font-size-regular);
--font-size-code: var(--font-size-regular);
--line-height-h1: 2.25rem;
--line-height-h2: 2rem;
--line-height-h3: 1.75rem;
--line-height-h4: 1.5rem;
--line-height-h5: 1.5rem;
--line-height-h6: 1.5rem;
--line-height-regular: 1.5rem;
--line-height-list: var(--line-height-regular);
--line-height-code: var(--line-height-regular);
}
&.small-font {
--font-size-h1: 1.4rem;
--font-size-h2: 1.2rem;
--font-size-h3: 1.1rem;
--font-size-h4: 1rem;
--font-size-h5: 0.9rem;
--font-size-h6: 0.8rem;
--font-size-regular: 0.8rem;
--font-size-list: var(--font-size-regular);
--font-size-code: var(--font-size-regular);
--line-height-h1: 1.8rem;
--line-height-h2: 1.6rem;
--line-height-h3: 1.4rem;
--line-height-h4: 1.2rem;
--line-height-h5: 1.2rem;
--line-height-h6: 1.2rem;
--line-height-regular: 1.2rem;
--line-height-list: var(--line-height-regular);
--line-height-code: var(--line-height-regular);
}
&.sans-serif {
--font-style: "Inter", sans-serif;
}
&.serif {
--font-style: serif;
}
&.monospace {
--font-style: monospace;
}
}
.ProseMirror { .ProseMirror {
position: relative; position: relative;
word-wrap: break-word; word-wrap: break-word;
@ -439,3 +381,62 @@ ul[data-type="taskList"] ul[data-type="taskList"] {
margin-top: 0; margin-top: 0;
} }
/* end tailwind typography */ /* end tailwind typography */
/* text colors */
[data-text-color="gray"] {
color: var(--editor-colors-gray-text);
}
[data-text-color="peach"] {
color: var(--editor-colors-peach-text);
}
[data-text-color="pink"] {
color: var(--editor-colors-pink-text);
}
[data-text-color="orange"] {
color: var(--editor-colors-orange-text);
}
[data-text-color="green"] {
color: var(--editor-colors-green-text);
}
[data-text-color="light-blue"] {
color: var(--editor-colors-light-blue-text);
}
[data-text-color="dark-blue"] {
color: var(--editor-colors-dark-blue-text);
}
[data-text-color="purple"] {
color: var(--editor-colors-purple-text);
}
/* [data-text-color="pink-blue-gradient"] {
background-clip: text;
color: transparent;
background-image: linear-gradient(90deg, #a961cd 50%, #e75962 100%);
} */
/* end text colors */
/* background colors */
[data-background-color="gray"] {
background-color: var(--editor-colors-gray-background);
}
[data-background-color="peach"] {
background-color: var(--editor-colors-peach-background);
}
[data-background-color="pink"] {
background-color: var(--editor-colors-pink-background);
}
[data-background-color="orange"] {
background-color: var(--editor-colors-orange-background);
}
[data-background-color="green"] {
background-color: var(--editor-colors-green-background);
}
[data-background-color="light-blue"] {
background-color: var(--editor-colors-light-blue-background);
}
[data-background-color="dark-blue"] {
background-color: var(--editor-colors-dark-blue-background);
}
[data-background-color="purple"] {
background-color: var(--editor-colors-purple-background);
}
/* end background colors */

View file

@ -0,0 +1,96 @@
:root {
/* text colors */
--editor-colors-gray-text: #5c5e63;
--editor-colors-peach-text: #ff5b59;
--editor-colors-pink-text: #f65385;
--editor-colors-orange-text: #fd9038;
--editor-colors-green-text: #0fc27b;
--editor-colors-light-blue-text: #17bee9;
--editor-colors-dark-blue-text: #266df0;
--editor-colors-purple-text: #9162f9;
/* end text colors */
}
/* text background colors */
[data-theme="light"],
[data-theme="light-contrast"] {
--editor-colors-gray-background: #d6d6d8;
--editor-colors-peach-background: #ffd5d7;
--editor-colors-pink-background: #fdd4e3;
--editor-colors-orange-background: #ffe3cd;
--editor-colors-green-background: #c3f0de;
--editor-colors-light-blue-background: #c5eff9;
--editor-colors-dark-blue-background: #c9dafb;
--editor-colors-purple-background: #e3d8fd;
}
[data-theme="dark"],
[data-theme="dark-contrast"] {
--editor-colors-gray-background: #404144;
--editor-colors-peach-background: #593032;
--editor-colors-pink-background: #562e3d;
--editor-colors-orange-background: #583e2a;
--editor-colors-green-background: #1d4a3b;
--editor-colors-light-blue-background: #1f495c;
--editor-colors-dark-blue-background: #223558;
--editor-colors-purple-background: #3d325a;
}
/* end text background colors */
.editor-container {
/* font sizes and line heights */
&.large-font {
--font-size-h1: 1.75rem;
--font-size-h2: 1.5rem;
--font-size-h3: 1.375rem;
--font-size-h4: 1.25rem;
--font-size-h5: 1.125rem;
--font-size-h6: 1rem;
--font-size-regular: 1rem;
--font-size-list: var(--font-size-regular);
--font-size-code: var(--font-size-regular);
--line-height-h1: 2.25rem;
--line-height-h2: 2rem;
--line-height-h3: 1.75rem;
--line-height-h4: 1.5rem;
--line-height-h5: 1.5rem;
--line-height-h6: 1.5rem;
--line-height-regular: 1.5rem;
--line-height-list: var(--line-height-regular);
--line-height-code: var(--line-height-regular);
}
&.small-font {
--font-size-h1: 1.4rem;
--font-size-h2: 1.2rem;
--font-size-h3: 1.1rem;
--font-size-h4: 1rem;
--font-size-h5: 0.9rem;
--font-size-h6: 0.8rem;
--font-size-regular: 0.8rem;
--font-size-list: var(--font-size-regular);
--font-size-code: var(--font-size-regular);
--line-height-h1: 1.8rem;
--line-height-h2: 1.6rem;
--line-height-h3: 1.4rem;
--line-height-h4: 1.2rem;
--line-height-h5: 1.2rem;
--line-height-h6: 1.2rem;
--line-height-regular: 1.2rem;
--line-height-list: var(--line-height-regular);
--line-height-code: var(--line-height-regular);
}
/* end font sizes and line heights */
/* font styles */
&.sans-serif {
--font-style: "Inter", sans-serif;
}
&.serif {
--font-style: serif;
}
&.monospace {
--font-style: monospace;
}
/* end font styles */
}

View file

@ -16,8 +16,8 @@ type Props = {
export const ColorDropdown: React.FC<Props> = memo((props) => { export const ColorDropdown: React.FC<Props> = memo((props) => {
const { handleColorSelect, isColorActive } = props; const { handleColorSelect, isColorActive } = props;
const activeTextColor = COLORS_LIST.find((c) => isColorActive("text-color", c.textColor)); const activeTextColor = COLORS_LIST.find((c) => isColorActive("text-color", c.key));
const activeBackgroundColor = COLORS_LIST.find((c) => isColorActive("background-color", c.backgroundColor)); const activeBackgroundColor = COLORS_LIST.find((c) => isColorActive("background-color", c.key));
return ( return (
<Popover as="div" className="h-7 px-2"> <Popover as="div" className="h-7 px-2">
@ -47,25 +47,17 @@ export const ColorDropdown: React.FC<Props> = memo((props) => {
"bg-custom-background-100": !activeBackgroundColor, "bg-custom-background-100": !activeBackgroundColor,
} }
)} )}
style={ style={{
activeBackgroundColor backgroundColor: activeBackgroundColor ? activeBackgroundColor.backgroundColor : "transparent",
? { }}
backgroundColor: activeBackgroundColor.backgroundColor,
}
: {}
}
> >
<ALargeSmall <ALargeSmall
className={cn("size-3.5", { className={cn("size-3.5", {
"text-custom-text-100": !activeTextColor, "text-custom-text-100": !activeTextColor,
})} })}
style={ style={{
activeTextColor color: activeTextColor ? activeTextColor.textColor : "inherit",
? { }}
color: activeTextColor.textColor,
}
: {}
}
/> />
</span> </span>
</span> </span>
@ -80,13 +72,13 @@ export const ColorDropdown: React.FC<Props> = memo((props) => {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{COLORS_LIST.map((color) => ( {COLORS_LIST.map((color) => (
<button <button
key={color.textColor} key={color.key}
type="button" type="button"
className="flex-shrink-0 size-6 rounded border-[0.5px] border-custom-border-400 hover:opacity-60 transition-opacity" className="flex-shrink-0 size-6 rounded border-[0.5px] border-custom-border-400 hover:opacity-60 transition-opacity"
style={{ style={{
backgroundColor: color.textColor, backgroundColor: color.textColor,
}} }}
onClick={() => handleColorSelect("text-color", color.textColor)} onClick={() => handleColorSelect("text-color", color.key)}
/> />
))} ))}
<button <button
@ -103,13 +95,13 @@ export const ColorDropdown: React.FC<Props> = memo((props) => {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{COLORS_LIST.map((color) => ( {COLORS_LIST.map((color) => (
<button <button
key={color.backgroundColor} key={color.key}
type="button" type="button"
className="flex-shrink-0 size-6 rounded border-[0.5px] border-custom-border-400 hover:opacity-60 transition-opacity" className="flex-shrink-0 size-6 rounded border-[0.5px] border-custom-border-400 hover:opacity-60 transition-opacity"
style={{ style={{
backgroundColor: color.backgroundColor, backgroundColor: color.backgroundColor,
}} }}
onClick={() => handleColorSelect("background-color", color.backgroundColor)} onClick={() => handleColorSelect("background-color", color.key)}
/> />
))} ))}
<button <button

View file

@ -3636,11 +3636,6 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-collaboration/-/extension-collaboration-2.8.0.tgz#db7a1e600c80229ed24a9d004f290d9e8bd4d0f6" resolved "https://registry.yarnpkg.com/@tiptap/extension-collaboration/-/extension-collaboration-2.8.0.tgz#db7a1e600c80229ed24a9d004f290d9e8bd4d0f6"
integrity sha512-Ae5NZWj2aq8ZElsGxQiq3cAxxbb0cR7VHvmIG1mPA6USvrQL6/xtBVutersBqINFELmIuxh/jm8qVffBm2qXyg== integrity sha512-Ae5NZWj2aq8ZElsGxQiq3cAxxbb0cR7VHvmIG1mPA6USvrQL6/xtBVutersBqINFELmIuxh/jm8qVffBm2qXyg==
"@tiptap/extension-color@^2.7.1":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-color/-/extension-color-2.8.0.tgz#597e1ea2e675e3c01ba64933008eacd296913abd"
integrity sha512-b0ZIDaZKTDVdTb0PMgtOiPzgCkYhvDldjzdWyPLsjWup5x9/zPasH5X/2SfMuwtjt+cKj6YBPveJjF7w5ApK7w==
"@tiptap/extension-document@^2.8.0": "@tiptap/extension-document@^2.8.0":
version "2.8.0" version "2.8.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.8.0.tgz#7dc5d2622168ad5b81134a92fccf49d7be53f141" resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.8.0.tgz#7dc5d2622168ad5b81134a92fccf49d7be53f141"
@ -3673,11 +3668,6 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.8.0.tgz#1b7711860fe9f4336fb8933110a129150faa4e39" resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.8.0.tgz#1b7711860fe9f4336fb8933110a129150faa4e39"
integrity sha512-4inWgrTPiqlivPmEHFOM5ck2UsmOsbKKPtqga6bALvWPmCv24S6/EBwFp8Jz4YABabXDnkviihmGu0LpP9D69w== integrity sha512-4inWgrTPiqlivPmEHFOM5ck2UsmOsbKKPtqga6bALvWPmCv24S6/EBwFp8Jz4YABabXDnkviihmGu0LpP9D69w==
"@tiptap/extension-highlight@^2.7.1":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-highlight/-/extension-highlight-2.8.0.tgz#3970f42a5a116745fbb2b82cfc5055adb04158e9"
integrity sha512-vyqX7D449nuARhI0AyRqtIZReFg3sfc/U/q1p3JOjtUoW6z2jmDTzshiKRrSg+Jf7Hhzj1pqwU+6+CpelPPDpA==
"@tiptap/extension-history@^2.8.0": "@tiptap/extension-history@^2.8.0":
version "2.8.0" version "2.8.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.8.0.tgz#06505cbdaa29a9791911eddbee54304ee32b1d5c" resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.8.0.tgz#06505cbdaa29a9791911eddbee54304ee32b1d5c"