mirror of
https://github.com/docmost/docmost.git
synced 2025-11-15 19:41:11 +10:00
editor improvements
* add callout, youtube embed, image, video, table, detail, math * fix attachments module * other fixes
This commit is contained in:
@ -1,155 +1,277 @@
|
||||
import {
|
||||
IconBlockquote,
|
||||
IconCheckbox, IconCode,
|
||||
IconCaretRightFilled,
|
||||
IconCheckbox,
|
||||
IconCode,
|
||||
IconH1,
|
||||
IconH2,
|
||||
IconH3,
|
||||
IconInfoCircle,
|
||||
IconList,
|
||||
IconListNumbers, IconPhoto,
|
||||
IconListNumbers,
|
||||
IconMath,
|
||||
IconMathFunction,
|
||||
IconMovie,
|
||||
IconPhoto,
|
||||
IconTable,
|
||||
IconTypography,
|
||||
} from '@tabler/icons-react';
|
||||
import { CommandProps, SlashMenuGroupedItemsType } from '@/features/editor/components/slash-menu/types';
|
||||
} from "@tabler/icons-react";
|
||||
import {
|
||||
CommandProps,
|
||||
SlashMenuGroupedItemsType,
|
||||
} from "@/features/editor/components/slash-menu/types";
|
||||
import { uploadImageAction } from "@/features/editor/components/image/upload-image-action.tsx";
|
||||
import { uploadVideoAction } from "@/features/editor/components/video/upload-video-action.tsx";
|
||||
|
||||
const CommandGroups: SlashMenuGroupedItemsType = {
|
||||
basic: [
|
||||
{
|
||||
title: 'Text',
|
||||
description: 'Just start typing with plain text.',
|
||||
searchTerms: ['p', 'paragraph'],
|
||||
title: "Text",
|
||||
description: "Just start typing with plain text.",
|
||||
searchTerms: ["p", "paragraph"],
|
||||
icon: IconTypography,
|
||||
command: ({ editor, range }: CommandProps) => {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.toggleNode('paragraph', 'paragraph')
|
||||
.toggleNode("paragraph", "paragraph")
|
||||
.run();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'To-do List',
|
||||
description: 'Track tasks with a to-do list.',
|
||||
searchTerms: ['todo', 'task', 'list', 'check', 'checkbox'],
|
||||
title: "To-do list",
|
||||
description: "Track tasks with a to-do list.",
|
||||
searchTerms: ["todo", "task", "list", "check", "checkbox"],
|
||||
icon: IconCheckbox,
|
||||
command: ({ editor, range }: CommandProps) => {
|
||||
editor.chain().focus().deleteRange(range).toggleTaskList().run();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Heading 1',
|
||||
description: 'Big section heading.',
|
||||
searchTerms: ['title', 'big', 'large'],
|
||||
title: "Heading 1",
|
||||
description: "Big section heading.",
|
||||
searchTerms: ["title", "big", "large"],
|
||||
icon: IconH1,
|
||||
command: ({ editor, range }: CommandProps) => {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.setNode('heading', { level: 1 })
|
||||
.setNode("heading", { level: 1 })
|
||||
.run();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Heading 2',
|
||||
description: 'Medium section heading.',
|
||||
searchTerms: ['subtitle', 'medium'],
|
||||
title: "Heading 2",
|
||||
description: "Medium section heading.",
|
||||
searchTerms: ["subtitle", "medium"],
|
||||
icon: IconH2,
|
||||
command: ({ editor, range }: CommandProps) => {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.setNode('heading', { level: 2 })
|
||||
.setNode("heading", { level: 2 })
|
||||
.run();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Heading 3',
|
||||
description: 'Small section heading.',
|
||||
searchTerms: ['subtitle', 'small'],
|
||||
title: "Heading 3",
|
||||
description: "Small section heading.",
|
||||
searchTerms: ["subtitle", "small"],
|
||||
icon: IconH3,
|
||||
command: ({ editor, range }: CommandProps) => {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.setNode('heading', { level: 3 })
|
||||
.setNode("heading", { level: 3 })
|
||||
.run();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Bullet List',
|
||||
description: 'Create a simple bullet list.',
|
||||
searchTerms: ['unordered', 'point'],
|
||||
title: "Bullet list",
|
||||
description: "Create a simple bullet list.",
|
||||
searchTerms: ["unordered", "point", "list"],
|
||||
icon: IconList,
|
||||
command: ({ editor, range }: CommandProps) => {
|
||||
editor.chain().focus().deleteRange(range).toggleBulletList().run();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Numbered List',
|
||||
description: 'Create a list with numbering.',
|
||||
searchTerms: ['ordered'],
|
||||
title: "Numbered list",
|
||||
description: "Create a list with numbering.",
|
||||
searchTerms: ["numbered", "ordered", "list"],
|
||||
icon: IconListNumbers,
|
||||
command: ({ editor, range }: CommandProps) => {
|
||||
editor.chain().focus().deleteRange(range).toggleOrderedList().run();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Quote',
|
||||
description: 'Capture a quote.',
|
||||
searchTerms: ['blockquote', 'quotes'],
|
||||
title: "Quote",
|
||||
description: "Create block quote.",
|
||||
searchTerms: ["blockquote", "quotes"],
|
||||
icon: IconBlockquote,
|
||||
command: ({ editor, range }: CommandProps) =>
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.toggleNode('paragraph', 'paragraph')
|
||||
.toggleBlockquote()
|
||||
.run(),
|
||||
editor.chain().focus().deleteRange(range).toggleBlockquote().run(),
|
||||
},
|
||||
{
|
||||
title: 'Code',
|
||||
description: 'Capture a code snippet.',
|
||||
searchTerms: ['codeblock'],
|
||||
title: "Code",
|
||||
description: "Capture a code snippet.",
|
||||
searchTerms: ["codeblock"],
|
||||
icon: IconCode,
|
||||
command: ({ editor, range }: CommandProps) =>
|
||||
editor.chain().focus().deleteRange(range).toggleCodeBlock().run(),
|
||||
},
|
||||
{
|
||||
title: 'Image',
|
||||
description: 'Upload an image from your computer.',
|
||||
searchTerms: ['photo', 'picture', 'media'],
|
||||
title: "Image",
|
||||
description: "Upload an image from your computer.",
|
||||
searchTerms: ["photo", "picture", "media"],
|
||||
icon: IconPhoto,
|
||||
command: ({ editor, range }: CommandProps) => {
|
||||
command: ({ editor, range }) => {
|
||||
editor.chain().focus().deleteRange(range).run();
|
||||
|
||||
const pageId = editor.storage?.pageId;
|
||||
if (!pageId) return;
|
||||
|
||||
// upload image
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = 'image/*';
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = "image/*";
|
||||
input.onchange = async () => {
|
||||
if (input.files?.length) {
|
||||
const file = input.files[0];
|
||||
const pos = editor.view.state.selection.from;
|
||||
//startImageUpload(file, editor.view, pos);
|
||||
uploadImageAction(file, editor.view, pos, pageId);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Video",
|
||||
description: "Upload an video from your computer.",
|
||||
searchTerms: ["video", "mp4", "media"],
|
||||
icon: IconMovie,
|
||||
command: ({ editor, range }) => {
|
||||
editor.chain().focus().deleteRange(range).run();
|
||||
|
||||
const pageId = editor.storage?.pageId;
|
||||
if (!pageId) return;
|
||||
|
||||
// upload video
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = "video/*";
|
||||
input.onchange = async () => {
|
||||
if (input.files?.length) {
|
||||
const file = input.files[0];
|
||||
const pos = editor.view.state.selection.from;
|
||||
uploadVideoAction(file, editor.view, pos, pageId);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Table",
|
||||
description: "Insert a table.",
|
||||
searchTerms: ["table", "rows", "columns"],
|
||||
icon: IconTable,
|
||||
command: ({ editor, range }: CommandProps) =>
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.insertTable({ rows: 3, cols: 3, withHeaderRow: false })
|
||||
.run(),
|
||||
},
|
||||
{
|
||||
title: "Toggle block",
|
||||
description: "Insert collapsible block.",
|
||||
searchTerms: ["collapsible", "block", "toggle", "details", "expand"],
|
||||
icon: IconCaretRightFilled,
|
||||
command: ({ editor, range }: CommandProps) =>
|
||||
editor.chain().focus().deleteRange(range).toggleDetails().run(),
|
||||
},
|
||||
{
|
||||
title: "Callout",
|
||||
description: "Insert callout notice.",
|
||||
searchTerms: [
|
||||
"callout",
|
||||
"notice",
|
||||
"panel",
|
||||
"info",
|
||||
"warning",
|
||||
"success",
|
||||
"error",
|
||||
"danger",
|
||||
],
|
||||
icon: IconInfoCircle,
|
||||
command: ({ editor, range }: CommandProps) =>
|
||||
editor.chain().focus().deleteRange(range).toggleCallout().run(),
|
||||
},
|
||||
{
|
||||
title: "Math inline",
|
||||
description: "Insert inline math equation.",
|
||||
searchTerms: [
|
||||
"math",
|
||||
"inline",
|
||||
"mathinline",
|
||||
"inlinemath",
|
||||
"inline math",
|
||||
"equation",
|
||||
"katex",
|
||||
"latex",
|
||||
"tex",
|
||||
],
|
||||
icon: IconMathFunction,
|
||||
command: ({ editor, range }: CommandProps) =>
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.setMathInline()
|
||||
.setNodeSelection(range.from)
|
||||
.run(),
|
||||
},
|
||||
{
|
||||
title: "Math block",
|
||||
description: "Insert math equation",
|
||||
searchTerms: [
|
||||
"math",
|
||||
"block",
|
||||
"mathblock",
|
||||
"block math",
|
||||
"equation",
|
||||
"katex",
|
||||
"latex",
|
||||
"tex",
|
||||
],
|
||||
icon: IconMath,
|
||||
command: ({ editor, range }: CommandProps) =>
|
||||
editor.chain().focus().deleteRange(range).setMathBlock().run(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const getSuggestionItems = ({ query }: { query: string }): SlashMenuGroupedItemsType => {
|
||||
export const getSuggestionItems = ({
|
||||
query,
|
||||
}: {
|
||||
query: string;
|
||||
}): SlashMenuGroupedItemsType => {
|
||||
const search = query.toLowerCase();
|
||||
const filteredGroups: SlashMenuGroupedItemsType = {};
|
||||
|
||||
for (const [group, items] of Object.entries(CommandGroups)) {
|
||||
const filteredItems = items.filter((item) => {
|
||||
return item.title.toLowerCase().includes(search)
|
||||
|| item.description.toLowerCase().includes(search)
|
||||
|| (item.searchTerms && item.searchTerms.some((term: string) => term.includes(search)));
|
||||
return (
|
||||
item.title.toLowerCase().includes(search) ||
|
||||
item.description.toLowerCase().includes(search) ||
|
||||
(item.searchTerms &&
|
||||
item.searchTerms.some((term: string) => term.includes(search)))
|
||||
);
|
||||
});
|
||||
|
||||
if (filteredItems.length) {
|
||||
|
||||
Reference in New Issue
Block a user