feat: add report content update endpoint and integrate Platejs editor for markdown editing

This commit is contained in:
Anish Sarkar 2026-02-16 00:11:34 +05:30
parent cb759b64fe
commit 1995fe9ec1
73 changed files with 7447 additions and 77 deletions

View file

@ -0,0 +1,250 @@
'use client';
import type { AutoformatBlockRule, AutoformatRule } from '@platejs/autoformat';
import type { SlateEditor } from 'platejs';
import {
autoformatArrow,
autoformatLegal,
autoformatLegalHtml,
autoformatMath,
AutoformatPlugin,
autoformatPunctuation,
autoformatSmartQuotes,
} from '@platejs/autoformat';
import { insertEmptyCodeBlock } from '@platejs/code-block';
import { toggleList, toggleTaskList, unwrapList } from '@platejs/list-classic';
import { openNextToggles } from '@platejs/toggle/react';
import { ElementApi, isType, KEYS } from 'platejs';
const preFormat: AutoformatBlockRule['preFormat'] = (editor) =>
unwrapList(editor);
const format = (editor: SlateEditor, customFormatting: any) => {
if (editor.selection) {
const parentEntry = editor.api.parent(editor.selection);
if (!parentEntry) return;
const [node] = parentEntry;
if (ElementApi.isElement(node) && !isType(editor, node, KEYS.codeBlock)) {
customFormatting();
}
}
};
const formatTaskList = (editor: SlateEditor, defaultChecked = false) => {
format(editor, () => toggleTaskList(editor, defaultChecked));
};
const formatList = (editor: SlateEditor, elementType: string) => {
format(editor, () =>
toggleList(editor, {
type: elementType,
})
);
};
const autoformatMarks: AutoformatRule[] = [
{
match: '***',
mode: 'mark',
type: [KEYS.bold, KEYS.italic],
},
{
match: '__*',
mode: 'mark',
type: [KEYS.underline, KEYS.italic],
},
{
match: '__**',
mode: 'mark',
type: [KEYS.underline, KEYS.bold],
},
{
match: '___***',
mode: 'mark',
type: [KEYS.underline, KEYS.bold, KEYS.italic],
},
{
match: '**',
mode: 'mark',
type: KEYS.bold,
},
{
match: '__',
mode: 'mark',
type: KEYS.underline,
},
{
match: '*',
mode: 'mark',
type: KEYS.italic,
},
{
match: '_',
mode: 'mark',
type: KEYS.italic,
},
{
match: '~~',
mode: 'mark',
type: KEYS.strikethrough,
},
{
match: '^',
mode: 'mark',
type: KEYS.sup,
},
{
match: '~',
mode: 'mark',
type: KEYS.sub,
},
{
match: '==',
mode: 'mark',
type: KEYS.highlight,
},
{
match: '≡',
mode: 'mark',
type: KEYS.highlight,
},
{
match: '`',
mode: 'mark',
type: KEYS.code,
},
];
const autoformatBlocks: AutoformatRule[] = [
{
match: '# ',
mode: 'block',
preFormat,
type: KEYS.h1,
},
{
match: '## ',
mode: 'block',
preFormat,
type: KEYS.h2,
},
{
match: '### ',
mode: 'block',
preFormat,
type: KEYS.h3,
},
{
match: '#### ',
mode: 'block',
preFormat,
type: KEYS.h4,
},
{
match: '##### ',
mode: 'block',
preFormat,
type: KEYS.h5,
},
{
match: '###### ',
mode: 'block',
preFormat,
type: KEYS.h6,
},
{
match: '> ',
mode: 'block',
preFormat,
type: KEYS.blockquote,
},
{
match: '```',
mode: 'block',
preFormat,
type: KEYS.codeBlock,
format: (editor) => {
insertEmptyCodeBlock(editor, {
defaultType: KEYS.p,
insertNodesOptions: { select: true },
});
},
},
{
match: '+ ',
mode: 'block',
preFormat: openNextToggles,
type: KEYS.toggle,
},
{
match: ['---', '—-', '___ '],
mode: 'block',
type: KEYS.hr,
format: (editor) => {
editor.tf.setNodes({ type: KEYS.hr });
editor.tf.insertNodes({
children: [{ text: '' }],
type: KEYS.p,
});
},
},
];
const autoformatLists: AutoformatRule[] = [
{
match: ['* ', '- '],
mode: 'block',
preFormat,
type: KEYS.li,
format: (editor) => formatList(editor, KEYS.ulClassic),
},
{
match: [String.raw`^\d+\.$ `, String.raw`^\d+\)$ `],
matchByRegex: true,
mode: 'block',
preFormat,
type: KEYS.li,
format: (editor) => formatList(editor, KEYS.olClassic),
},
{
match: '[] ',
mode: 'block',
type: KEYS.taskList,
format: (editor) => formatTaskList(editor, false),
},
{
match: '[x] ',
mode: 'block',
type: KEYS.taskList,
format: (editor) => formatTaskList(editor, true),
},
];
export const AutoformatKit = [
AutoformatPlugin.configure({
options: {
enableUndoOnDelete: true,
rules: [
...autoformatBlocks,
...autoformatMarks,
...autoformatSmartQuotes,
...autoformatPunctuation,
...autoformatLegal,
...autoformatLegalHtml,
...autoformatArrow,
...autoformatMath,
...autoformatLists,
].map((rule) => ({
...rule,
query: (editor) =>
!editor.api.some({
match: { type: editor.getType(KEYS.codeBlock) },
}),
})),
},
}),
];

View file

@ -0,0 +1,35 @@
import {
BaseBlockquotePlugin,
BaseH1Plugin,
BaseH2Plugin,
BaseH3Plugin,
BaseH4Plugin,
BaseH5Plugin,
BaseH6Plugin,
BaseHorizontalRulePlugin,
} from '@platejs/basic-nodes';
import { BaseParagraphPlugin } from 'platejs';
import { BlockquoteElementStatic } from '@/components/ui/blockquote-node-static';
import {
H1ElementStatic,
H2ElementStatic,
H3ElementStatic,
H4ElementStatic,
H5ElementStatic,
H6ElementStatic,
} from '@/components/ui/heading-node-static';
import { HrElementStatic } from '@/components/ui/hr-node-static';
import { ParagraphElementStatic } from '@/components/ui/paragraph-node-static';
export const BaseBasicBlocksKit = [
BaseParagraphPlugin.withComponent(ParagraphElementStatic),
BaseH1Plugin.withComponent(H1ElementStatic),
BaseH2Plugin.withComponent(H2ElementStatic),
BaseH3Plugin.withComponent(H3ElementStatic),
BaseH4Plugin.withComponent(H4ElementStatic),
BaseH5Plugin.withComponent(H5ElementStatic),
BaseH6Plugin.withComponent(H6ElementStatic),
BaseBlockquotePlugin.withComponent(BlockquoteElementStatic),
BaseHorizontalRulePlugin.withComponent(HrElementStatic),
];

View file

@ -0,0 +1,86 @@
'use client';
import {
BlockquotePlugin,
H1Plugin,
H2Plugin,
H3Plugin,
H4Plugin,
H5Plugin,
H6Plugin,
HorizontalRulePlugin,
} from '@platejs/basic-nodes/react';
import { ParagraphPlugin } from 'platejs/react';
import { BlockquoteElement } from '@/components/ui/blockquote-node';
import {
H1Element,
H2Element,
H3Element,
H4Element,
H5Element,
H6Element,
} from '@/components/ui/heading-node';
import { HrElement } from '@/components/ui/hr-node';
import { ParagraphElement } from '@/components/ui/paragraph-node';
export const BasicBlocksKit = [
ParagraphPlugin.withComponent(ParagraphElement),
H1Plugin.configure({
node: {
component: H1Element,
},
rules: {
break: { empty: 'reset' },
},
shortcuts: { toggle: { keys: 'mod+alt+1' } },
}),
H2Plugin.configure({
node: {
component: H2Element,
},
rules: {
break: { empty: 'reset' },
},
shortcuts: { toggle: { keys: 'mod+alt+2' } },
}),
H3Plugin.configure({
node: {
component: H3Element,
},
rules: {
break: { empty: 'reset' },
},
shortcuts: { toggle: { keys: 'mod+alt+3' } },
}),
H4Plugin.configure({
node: {
component: H4Element,
},
rules: {
break: { empty: 'reset' },
},
shortcuts: { toggle: { keys: 'mod+alt+4' } },
}),
H5Plugin.configure({
node: {
component: H5Element,
},
rules: {
break: { empty: 'reset' },
},
}),
H6Plugin.configure({
node: {
component: H6Element,
},
rules: {
break: { empty: 'reset' },
},
}),
BlockquotePlugin.configure({
node: { component: BlockquoteElement },
shortcuts: { toggle: { keys: 'mod+shift+period' } },
}),
HorizontalRulePlugin.withComponent(HrElement),
];

View file

@ -0,0 +1,27 @@
import {
BaseBoldPlugin,
BaseCodePlugin,
BaseHighlightPlugin,
BaseItalicPlugin,
BaseKbdPlugin,
BaseStrikethroughPlugin,
BaseSubscriptPlugin,
BaseSuperscriptPlugin,
BaseUnderlinePlugin,
} from '@platejs/basic-nodes';
import { CodeLeafStatic } from '@/components/ui/code-node-static';
import { HighlightLeafStatic } from '@/components/ui/highlight-node-static';
import { KbdLeafStatic } from '@/components/ui/kbd-node-static';
export const BaseBasicMarksKit = [
BaseBoldPlugin,
BaseItalicPlugin,
BaseUnderlinePlugin,
BaseCodePlugin.withComponent(CodeLeafStatic),
BaseStrikethroughPlugin,
BaseSubscriptPlugin,
BaseSuperscriptPlugin,
BaseHighlightPlugin.withComponent(HighlightLeafStatic),
BaseKbdPlugin.withComponent(KbdLeafStatic),
];

View file

@ -0,0 +1,41 @@
'use client';
import {
BoldPlugin,
CodePlugin,
HighlightPlugin,
ItalicPlugin,
KbdPlugin,
StrikethroughPlugin,
SubscriptPlugin,
SuperscriptPlugin,
UnderlinePlugin,
} from '@platejs/basic-nodes/react';
import { CodeLeaf } from '@/components/ui/code-node';
import { HighlightLeaf } from '@/components/ui/highlight-node';
import { KbdLeaf } from '@/components/ui/kbd-node';
export const BasicMarksKit = [
BoldPlugin,
ItalicPlugin,
UnderlinePlugin,
CodePlugin.configure({
node: { component: CodeLeaf },
shortcuts: { toggle: { keys: 'mod+e' } },
}),
StrikethroughPlugin.configure({
shortcuts: { toggle: { keys: 'mod+shift+x' } },
}),
SubscriptPlugin.configure({
shortcuts: { toggle: { keys: 'mod+comma' } },
}),
SuperscriptPlugin.configure({
shortcuts: { toggle: { keys: 'mod+period' } },
}),
HighlightPlugin.configure({
node: { component: HighlightLeaf },
shortcuts: { toggle: { keys: 'mod+shift+h' } },
}),
KbdPlugin.withComponent(KbdLeaf),
];

View file

@ -0,0 +1,6 @@
'use client';
import { BasicBlocksKit } from './basic-blocks-kit';
import { BasicMarksKit } from './basic-marks-kit';
export const BasicNodesKit = [...BasicBlocksKit, ...BasicMarksKit];

View file

@ -0,0 +1,8 @@
'use client';
import { CalloutPlugin } from '@platejs/callout/react';
import { CalloutElement } from '@/components/ui/callout-node';
export const CalloutKit = [CalloutPlugin.withComponent(CalloutElement)];

View file

@ -0,0 +1,23 @@
import {
BaseCodeBlockPlugin,
BaseCodeLinePlugin,
BaseCodeSyntaxPlugin,
} from '@platejs/code-block';
import { all, createLowlight } from 'lowlight';
import {
CodeBlockElementStatic,
CodeLineElementStatic,
CodeSyntaxLeafStatic,
} from '@/components/ui/code-block-node-static';
const lowlight = createLowlight(all);
export const BaseCodeBlockKit = [
BaseCodeBlockPlugin.configure({
node: { component: CodeBlockElementStatic },
options: { lowlight },
}),
BaseCodeLinePlugin.withComponent(CodeLineElementStatic),
BaseCodeSyntaxPlugin.withComponent(CodeSyntaxLeafStatic),
];

View file

@ -0,0 +1,26 @@
'use client';
import {
CodeBlockPlugin,
CodeLinePlugin,
CodeSyntaxPlugin,
} from '@platejs/code-block/react';
import { all, createLowlight } from 'lowlight';
import {
CodeBlockElement,
CodeLineElement,
CodeSyntaxLeaf,
} from '@/components/ui/code-block-node';
const lowlight = createLowlight(all);
export const CodeBlockKit = [
CodeBlockPlugin.configure({
node: { component: CodeBlockElement },
options: { lowlight },
shortcuts: { toggle: { keys: 'mod+alt+8' } },
}),
CodeLinePlugin.withComponent(CodeLineElement),
CodeSyntaxPlugin.withComponent(CodeSyntaxLeaf),
];

View file

@ -0,0 +1,19 @@
'use client';
import { createPlatePlugin } from 'platejs/react';
import { FloatingToolbar } from '@/components/ui/floating-toolbar';
import { FloatingToolbarButtons } from '@/components/ui/floating-toolbar-buttons';
export const FloatingToolbarKit = [
createPlatePlugin({
key: 'floating-toolbar',
render: {
afterEditable: () => (
<FloatingToolbar>
<FloatingToolbarButtons />
</FloatingToolbar>
),
},
}),
];

View file

@ -0,0 +1,19 @@
'use client';
import { IndentPlugin } from '@platejs/indent/react';
import { KEYS } from 'platejs';
export const IndentKit = [
IndentPlugin.configure({
inject: {
targetPlugins: [
...KEYS.heading,
KEYS.p,
KEYS.blockquote,
KEYS.codeBlock,
KEYS.toggle,
],
},
}),
];

View file

@ -0,0 +1,5 @@
import { BaseLinkPlugin } from '@platejs/link';
import { LinkElementStatic } from '@/components/ui/link-node-static';
export const BaseLinkKit = [BaseLinkPlugin.withComponent(LinkElementStatic)];

View file

@ -0,0 +1,15 @@
'use client';
import { LinkPlugin } from '@platejs/link/react';
import { LinkElement } from '@/components/ui/link-node';
import { LinkFloatingToolbar } from '@/components/ui/link-toolbar';
export const LinkKit = [
LinkPlugin.configure({
render: {
node: LinkElement,
afterEditable: () => <LinkFloatingToolbar />,
},
}),
];

View file

@ -0,0 +1,36 @@
'use client';
import {
BulletedListPlugin,
ListItemContentPlugin,
ListItemPlugin,
ListPlugin,
NumberedListPlugin,
TaskListPlugin,
} from '@platejs/list-classic/react';
import {
BulletedListElement,
ListItemElement,
NumberedListElement,
TaskListElement,
} from '@/components/ui/list-classic-node';
export const ListKit = [
ListPlugin,
ListItemPlugin,
ListItemContentPlugin,
BulletedListPlugin.configure({
node: { component: BulletedListElement },
shortcuts: { toggle: { keys: 'mod+alt+5' } },
}),
NumberedListPlugin.configure({
node: { component: NumberedListElement },
shortcuts: { toggle: { keys: 'mod+alt+6' } },
}),
TaskListPlugin.configure({
node: { component: TaskListElement },
shortcuts: { toggle: { keys: 'mod+alt+7' } },
}),
ListItemPlugin.withComponent(ListItemElement),
];

View file

@ -0,0 +1,11 @@
'use client';
import { EquationPlugin, InlineEquationPlugin } from '@platejs/math/react';
import { EquationElement, InlineEquationElement } from '@/components/ui/equation-node';
export const MathKit = [
EquationPlugin.withComponent(EquationElement),
InlineEquationPlugin.withComponent(InlineEquationElement),
];

View file

@ -0,0 +1,23 @@
'use client';
import { BlockSelectionPlugin } from '@platejs/selection/react';
import { BlockSelection } from '@/components/ui/block-selection';
export const SelectionKit = [
BlockSelectionPlugin.configure({
render: {
belowRootNodes: BlockSelection as any,
},
options: {
isSelectable: (element) => {
// Exclude specific block types from selection
if (['code_line', 'td', 'th'].includes(element.type as string)) {
return false;
}
return true;
},
},
}),
];

View file

@ -0,0 +1,21 @@
'use client';
import { SlashInputPlugin, SlashPlugin } from '@platejs/slash-command/react';
import { KEYS } from 'platejs';
import { SlashInputElement } from '@/components/ui/slash-node';
export const SlashCommandKit = [
SlashPlugin.configure({
options: {
trigger: '/',
triggerPreviousCharPattern: /^\s?$/,
triggerQuery: (editor) =>
!editor.api.some({
match: { type: editor.getType(KEYS.codeBlock) },
}),
},
}),
SlashInputPlugin.withComponent(SlashInputElement),
];

View file

@ -0,0 +1,20 @@
import {
BaseTableCellHeaderPlugin,
BaseTableCellPlugin,
BaseTablePlugin,
BaseTableRowPlugin,
} from '@platejs/table';
import {
TableCellElementStatic,
TableCellHeaderElementStatic,
TableElementStatic,
TableRowElementStatic,
} from '@/components/ui/table-node-static';
export const BaseTableKit = [
BaseTablePlugin.withComponent(TableElementStatic),
BaseTableRowPlugin.withComponent(TableRowElementStatic),
BaseTableCellPlugin.withComponent(TableCellElementStatic),
BaseTableCellHeaderPlugin.withComponent(TableCellHeaderElementStatic),
];

View file

@ -0,0 +1,22 @@
'use client';
import {
TableCellHeaderPlugin,
TableCellPlugin,
TablePlugin,
TableRowPlugin,
} from '@platejs/table/react';
import {
TableCellElement,
TableCellHeaderElement,
TableElement,
TableRowElement,
} from '@/components/ui/table-node';
export const TableKit = [
TablePlugin.withComponent(TableElement),
TableRowPlugin.withComponent(TableRowElement),
TableCellPlugin.withComponent(TableCellElement),
TableCellHeaderPlugin.withComponent(TableCellHeaderElement),
];

View file

@ -0,0 +1,13 @@
'use client';
import { TogglePlugin } from '@platejs/toggle/react';
import { ToggleElement } from '@/components/ui/toggle-node';
export const ToggleKit = [
TogglePlugin.configure({
node: { component: ToggleElement },
shortcuts: { toggle: { keys: 'mod+alt+9' } },
}),
];