mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
feat: implement switchable task list node
This commit is contained in:
@ -44,10 +44,11 @@ import Text from "./Text";
|
||||
import UnorderedList from "./UnorderedList";
|
||||
|
||||
interface Props {
|
||||
index: string;
|
||||
node: Node;
|
||||
}
|
||||
|
||||
const Renderer: React.FC<Props> = ({ node }: Props) => {
|
||||
const Renderer: React.FC<Props> = ({ index, node }: Props) => {
|
||||
switch (node.type) {
|
||||
case NodeType.LINE_BREAK:
|
||||
return <LineBreak />;
|
||||
@ -66,7 +67,7 @@ const Renderer: React.FC<Props> = ({ node }: Props) => {
|
||||
case NodeType.UNORDERED_LIST:
|
||||
return <UnorderedList {...(node.unorderedListNode as UnorderedListNode)} />;
|
||||
case NodeType.TASK_LIST:
|
||||
return <TaskList {...(node.taskListNode as TaskListNode)} />;
|
||||
return <TaskList index={index} {...(node.taskListNode as TaskListNode)} />;
|
||||
case NodeType.MATH_BLOCK:
|
||||
return <Math {...(node.mathBlockNode as MathNode)} block={true} />;
|
||||
case NodeType.TEXT:
|
||||
|
@ -1,23 +1,51 @@
|
||||
import { Checkbox } from "@mui/joy";
|
||||
import { Node } from "@/types/proto/api/v2/markdown_service";
|
||||
import { useContext } from "react";
|
||||
import { useMemoStore } from "@/store/v1";
|
||||
import { Node, NodeType } from "@/types/proto/api/v2/markdown_service";
|
||||
import Renderer from "./Renderer";
|
||||
import { RendererContext } from "./types";
|
||||
|
||||
interface Props {
|
||||
index: string;
|
||||
symbol: string;
|
||||
complete: boolean;
|
||||
children: Node[];
|
||||
}
|
||||
|
||||
const TaskList: React.FC<Props> = ({ complete, children }: Props) => {
|
||||
const TaskList: React.FC<Props> = ({ index, complete, children }: Props) => {
|
||||
const context = useContext(RendererContext);
|
||||
const memoStore = useMemoStore();
|
||||
|
||||
const handleCheckboxChange = async (on: boolean) => {
|
||||
const nodeIndex = Number(index);
|
||||
if (isNaN(nodeIndex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = context.nodes[nodeIndex];
|
||||
if (node.type !== NodeType.TASK_LIST || !node.taskListNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.taskListNode!.complete = on;
|
||||
await memoStore.updateMemo(
|
||||
{
|
||||
id: context.memoId,
|
||||
nodes: context.nodes,
|
||||
},
|
||||
["nodes"]
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ul>
|
||||
<li className="grid grid-cols-[24px_1fr] gap-1">
|
||||
<div className="w-7 h-6 flex justify-center items-center">
|
||||
<Checkbox size="sm" checked={complete} readOnly />
|
||||
<Checkbox size="sm" checked={complete} disabled={context.readonly} onChange={(e) => handleCheckboxChange(e.target.checked)} />
|
||||
</div>
|
||||
<div>
|
||||
{children.map((child, index) => (
|
||||
<Renderer key={`${child.type}-${index}`} node={child} />
|
||||
{children.map((child, subIndex) => (
|
||||
<Renderer key={`${child.type}-${subIndex}`} index={`${index}-${subIndex}`} node={child} />
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
|
@ -1,16 +1,24 @@
|
||||
import { useRef } from "react";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import { useMemoStore } from "@/store/v1";
|
||||
import { Node } from "@/types/proto/api/v2/markdown_service";
|
||||
import Renderer from "./Renderer";
|
||||
import { RendererContext } from "./types";
|
||||
|
||||
interface Props {
|
||||
memoId: number;
|
||||
nodes: Node[];
|
||||
readonly?: boolean;
|
||||
className?: string;
|
||||
onMemoContentClick?: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
const MemoContent: React.FC<Props> = (props: Props) => {
|
||||
const { className, onMemoContentClick } = props;
|
||||
const { className, memoId, nodes, onMemoContentClick } = props;
|
||||
const currentUser = useCurrentUser();
|
||||
const memoStore = useMemoStore();
|
||||
const memoContentContainerRef = useRef<HTMLDivElement>(null);
|
||||
const allowEdit = currentUser?.id === memoStore.getMemoById(memoId)?.creatorId && !props.readonly;
|
||||
|
||||
const handleMemoContentClick = async (e: React.MouseEvent) => {
|
||||
if (onMemoContentClick) {
|
||||
@ -19,17 +27,25 @@ const MemoContent: React.FC<Props> = (props: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`w-full flex flex-col justify-start items-start text-gray-800 dark:text-gray-300 ${className || ""}`}>
|
||||
<div
|
||||
ref={memoContentContainerRef}
|
||||
className="w-full max-w-full word-break text-base leading-6 space-y-1"
|
||||
onClick={handleMemoContentClick}
|
||||
>
|
||||
{props.nodes.map((node, index) => (
|
||||
<Renderer key={`${node.type}-${index}`} node={node} />
|
||||
))}
|
||||
<RendererContext.Provider
|
||||
value={{
|
||||
memoId,
|
||||
nodes,
|
||||
readonly: !allowEdit,
|
||||
}}
|
||||
>
|
||||
<div className={`w-full flex flex-col justify-start items-start text-gray-800 dark:text-gray-300 ${className || ""}`}>
|
||||
<div
|
||||
ref={memoContentContainerRef}
|
||||
className="w-full max-w-full word-break text-base leading-6 space-y-1"
|
||||
onClick={handleMemoContentClick}
|
||||
>
|
||||
{nodes.map((node, index) => (
|
||||
<Renderer key={`${node.type}-${index}`} index={String(index)} node={node} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</RendererContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1 +1,14 @@
|
||||
export interface RendererContext {}
|
||||
import { createContext } from "react";
|
||||
import { UNKNOWN_ID } from "@/helpers/consts";
|
||||
import { Node } from "@/types/proto/api/v2/markdown_service";
|
||||
|
||||
interface Context {
|
||||
memoId: number;
|
||||
nodes: Node[];
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
export const RendererContext = createContext<Context>({
|
||||
memoId: UNKNOWN_ID,
|
||||
nodes: [],
|
||||
});
|
||||
|
@ -251,7 +251,7 @@ const MemoView: React.FC<Props> = (props: Props) => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<MemoContent nodes={memo.nodes} onMemoContentClick={handleMemoContentClick} />
|
||||
<MemoContent memoId={memo.id} nodes={memo.nodes} onMemoContentClick={handleMemoContentClick} />
|
||||
<MemoResourceListView resourceList={memo.resources} />
|
||||
<MemoRelationListView memo={memo} relationList={referenceRelations} />
|
||||
</div>
|
||||
|
@ -100,7 +100,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
|
||||
>
|
||||
<span className="w-full px-6 pt-5 pb-2 text-sm text-gray-500">{getDateTimeString(memo.displayTime)}</span>
|
||||
<div className="w-full px-6 text-base pb-4">
|
||||
<MemoContent nodes={memo.nodes} />
|
||||
<MemoContent memoId={memo.id} nodes={memo.nodes} readonly={true} />
|
||||
<MemoResourceListView resourceList={memo.resources} />
|
||||
</div>
|
||||
<div className="flex flex-row justify-between items-center w-full bg-gray-100 dark:bg-zinc-900 py-4 px-6">
|
||||
|
@ -18,7 +18,7 @@ const TimelineMemo = (props: Props) => {
|
||||
<div className="w-full flex flex-row justify-start items-center mt-0.5 mb-1 text-sm font-mono text-gray-500 dark:text-gray-400">
|
||||
<span className="opacity-80">{getTimeString(memo.displayTime)}</span>
|
||||
</div>
|
||||
<MemoContent nodes={memo.nodes} />
|
||||
<MemoContent memoId={memo.id} nodes={memo.nodes} />
|
||||
<MemoResourceListView resourceList={memo.resources} />
|
||||
<MemoRelationListView memo={memo} relationList={relations} />
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user