feat: add task comments feature with CRUD operations

- Introduced a new `task_comments` table in the database schema.
- Implemented task comments API endpoints for listing, creating, and deleting comments.
- Enhanced the task detail dialog to display comments and allow users to add new comments.
- Updated task row component to handle click events for viewing task details.
- Added a theme provider to manage light/dark mode across the application.
- Refactored Milkdown editor to use Crepe for improved markdown editing experience.
- Updated global styles to accommodate new editor and theme changes.
- Enhanced task filtering and sorting functionality in the tasks page.
This commit is contained in:
Roberto Musso
2026-02-23 12:54:14 +01:00
parent 98acf6220e
commit c1aa6829c9
24 changed files with 996 additions and 234 deletions

View File

@@ -1,47 +1,49 @@
import { useRef } from 'react';
import { Editor, rootCtx, defaultValueCtx } from '@milkdown/kit/core';
import { commonmark } from '@milkdown/kit/preset/commonmark';
import { history } from '@milkdown/kit/plugin/history';
import { listener, listenerCtx } from '@milkdown/kit/plugin/listener';
import { Milkdown, MilkdownProvider, useEditor } from '@milkdown/react';
import { useEffect, useRef } from 'react';
import { Crepe, CrepeFeature } from '@milkdown/crepe';
import '@milkdown/kit/prose/view/style/prosemirror.css';
import '@milkdown/crepe/theme/common/style.css';
import '@milkdown/crepe/theme/nord.css';
interface MilkdownEditorProps {
initialContent: string;
onChange: (markdown: string) => void;
}
function MilkdownInner({ initialContent, onChange }: MilkdownEditorProps) {
export function MilkdownEditor({ initialContent, onChange }: MilkdownEditorProps) {
const containerRef = useRef<HTMLDivElement>(null);
const crepeRef = useRef<Crepe | null>(null);
const onChangeRef = useRef(onChange);
onChangeRef.current = onChange;
useEditor((root) =>
Editor.make()
.config((ctx) => {
ctx.set(rootCtx, root);
ctx.set(defaultValueCtx, initialContent);
})
.use(commonmark)
.use(history)
.use(listener)
.config((ctx) => {
ctx.get(listenerCtx).markdownUpdated((_ctx, markdown, prevMarkdown) => {
if (markdown !== prevMarkdown) {
onChangeRef.current(markdown);
}
});
}),
[]
);
useEffect(() => {
if (!containerRef.current) return;
return <Milkdown />;
}
const crepe = new Crepe({
root: containerRef.current,
defaultValue: initialContent,
featureConfigs: {
[CrepeFeature.Placeholder]: {
text: 'Start writing...',
},
},
});
export function MilkdownEditor(props: MilkdownEditorProps) {
return (
<MilkdownProvider>
<MilkdownInner {...props} />
</MilkdownProvider>
);
crepe.on((listener) => {
listener.markdownUpdated((_ctx, markdown, prevMarkdown) => {
if (markdown !== prevMarkdown) {
onChangeRef.current(markdown);
}
});
});
crepe.create();
crepeRef.current = crepe;
return () => {
crepe.destroy();
crepeRef.current = null;
};
}, []);
return <div ref={containerRef} className="milkdown-container" />;
}