Commit ca213437 authored by Steven's avatar Steven

feat: implement nesting lists

parent 7a4d54bb
This diff is collapsed.
...@@ -23,7 +23,7 @@ require ( ...@@ -23,7 +23,7 @@ require (
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0 github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
github.com/usememos/gomark v0.0.0-20240920000613-9640de1955cd github.com/usememos/gomark v0.0.0-20240921115339-c9d3461efcd8
golang.org/x/crypto v0.27.0 golang.org/x/crypto v0.27.0
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
golang.org/x/mod v0.21.0 golang.org/x/mod v0.21.0
......
...@@ -438,8 +438,8 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM ...@@ -438,8 +438,8 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/usememos/gomark v0.0.0-20240920000613-9640de1955cd h1:tqBJ00an5/MWAu1pIFkyE9TCSIe1JYAhShJlVeveYgs= github.com/usememos/gomark v0.0.0-20240921115339-c9d3461efcd8 h1:kutNP+XD8oiBzoJJER35fbVaphCeRhU1dKDjZ5eKT0M=
github.com/usememos/gomark v0.0.0-20240920000613-9640de1955cd/go.mod h1:7CZRoYFQyyljzplOTeyODFR26O+wr0BbnpTWVLGfKJA= github.com/usememos/gomark v0.0.0-20240921115339-c9d3461efcd8/go.mod h1:7CZRoYFQyyljzplOTeyODFR26O+wr0BbnpTWVLGfKJA=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
......
...@@ -173,7 +173,15 @@ message BlockquoteNode { ...@@ -173,7 +173,15 @@ message BlockquoteNode {
} }
message ListNode { message ListNode {
repeated Node children = 1; enum Kind {
KIND_UNSPECIFIED = 0;
ORDERED = 1;
UNORDERED = 2;
DESCRIPTION = 3;
}
Kind kind = 1;
int32 indent = 2;
repeated Node children = 3;
} }
message OrderedListItemNode { message OrderedListItemNode {
......
This diff is collapsed.
...@@ -77,7 +77,7 @@ func convertFromASTNode(rawNode ast.Node) *v1pb.Node { ...@@ -77,7 +77,7 @@ func convertFromASTNode(rawNode ast.Node) *v1pb.Node {
node.Node = &v1pb.Node_BlockquoteNode{BlockquoteNode: &v1pb.BlockquoteNode{Children: children}} node.Node = &v1pb.Node_BlockquoteNode{BlockquoteNode: &v1pb.BlockquoteNode{Children: children}}
case *ast.List: case *ast.List:
children := convertFromASTNodes(n.Children) children := convertFromASTNodes(n.Children)
node.Node = &v1pb.Node_ListNode{ListNode: &v1pb.ListNode{Children: children}} node.Node = &v1pb.Node_ListNode{ListNode: &v1pb.ListNode{Kind: convertListKindFromASTNode(n.Kind), Indent: int32(n.Indent), Children: children}}
case *ast.OrderedListItem: case *ast.OrderedListItem:
children := convertFromASTNodes(n.Children) children := convertFromASTNodes(n.Children)
node.Node = &v1pb.Node_OrderedListItemNode{OrderedListItemNode: &v1pb.OrderedListItemNode{Number: n.Number, Indent: int32(n.Indent), Children: children}} node.Node = &v1pb.Node_OrderedListItemNode{OrderedListItemNode: &v1pb.OrderedListItemNode{Number: n.Number, Indent: int32(n.Indent), Children: children}}
...@@ -156,6 +156,19 @@ func convertTableFromASTNode(node *ast.Table) *v1pb.TableNode { ...@@ -156,6 +156,19 @@ func convertTableFromASTNode(node *ast.Table) *v1pb.TableNode {
return table return table
} }
func convertListKindFromASTNode(node ast.ListKind) v1pb.ListNode_Kind {
switch node {
case ast.OrderedList:
return v1pb.ListNode_ORDERED
case ast.UnorderedList:
return v1pb.ListNode_UNORDERED
case ast.DescrpitionList:
return v1pb.ListNode_DESCRIPTION
default:
return v1pb.ListNode_KIND_UNSPECIFIED
}
}
func convertToASTNode(node *v1pb.Node) ast.Node { func convertToASTNode(node *v1pb.Node) ast.Node {
switch n := node.Node.(type) { switch n := node.Node.(type) {
case *v1pb.Node_LineBreakNode: case *v1pb.Node_LineBreakNode:
...@@ -175,7 +188,7 @@ func convertToASTNode(node *v1pb.Node) ast.Node { ...@@ -175,7 +188,7 @@ func convertToASTNode(node *v1pb.Node) ast.Node {
return &ast.Blockquote{Children: children} return &ast.Blockquote{Children: children}
case *v1pb.Node_ListNode: case *v1pb.Node_ListNode:
children := convertToASTNodes(n.ListNode.Children) children := convertToASTNodes(n.ListNode.Children)
return &ast.List{Children: children} return &ast.List{Kind: convertListKindToASTNode(n.ListNode.Kind), Indent: int(n.ListNode.Indent), Children: children}
case *v1pb.Node_OrderedListItemNode: case *v1pb.Node_OrderedListItemNode:
children := convertToASTNodes(n.OrderedListItemNode.Children) children := convertToASTNodes(n.OrderedListItemNode.Children)
return &ast.OrderedListItem{Number: n.OrderedListItemNode.Number, Indent: int(n.OrderedListItemNode.Indent), Children: children} return &ast.OrderedListItem{Number: n.OrderedListItemNode.Number, Indent: int(n.OrderedListItemNode.Indent), Children: children}
...@@ -252,3 +265,17 @@ func convertTableToASTNode(node *v1pb.TableNode) *ast.Table { ...@@ -252,3 +265,17 @@ func convertTableToASTNode(node *v1pb.TableNode) *ast.Table {
} }
return table return table
} }
func convertListKindToASTNode(kind v1pb.ListNode_Kind) ast.ListKind {
switch kind {
case v1pb.ListNode_ORDERED:
return ast.OrderedList
case v1pb.ListNode_UNORDERED:
return ast.UnorderedList
case v1pb.ListNode_DESCRIPTION:
return ast.DescrpitionList
default:
// Default to description list.
return ast.DescrpitionList
}
}
import { Node, NodeType } from "@/types/proto/api/v1/markdown_service"; import clsx from "clsx";
import React from "react";
import { ListNode_Kind, Node, NodeType } from "@/types/proto/api/v1/markdown_service";
import Renderer from "./Renderer"; import Renderer from "./Renderer";
interface Props { interface Props {
index: string; index: string;
kind: ListNode_Kind;
indent: number;
children: Node[]; children: Node[];
} }
const List: React.FC<Props> = ({ children }: Props) => { const List: React.FC<Props> = ({ kind, indent, children }: Props) => {
let prevNode: Node | null = null; let prevNode: Node | null = null;
let skipNextLineBreakFlag = false; let skipNextLineBreakFlag = false;
return ( const getListContainer = (kind: ListNode_Kind) => {
<dl> switch (kind) {
{children.map((child, index) => { case ListNode_Kind.ORDERED:
if (prevNode?.type !== NodeType.LINE_BREAK && child.type === NodeType.LINE_BREAK && skipNextLineBreakFlag) { return "ol";
skipNextLineBreakFlag = false; case ListNode_Kind.UNORDERED:
return null; return "ul";
} case ListNode_Kind.DESCRIPTION:
return "dl";
default:
return "div";
}
};
prevNode = child; return React.createElement(
skipNextLineBreakFlag = true; getListContainer(kind),
return <Renderer key={`${child.type}-${index}`} index={String(index)} node={child} />; {
})} className: clsx(
</dl> `list-inside ${kind === ListNode_Kind.ORDERED ? "list-decimal" : kind === ListNode_Kind.UNORDERED ? "list-disc" : "list-none"}`,
indent > 0 ? `pl-${2 * indent}` : "",
),
},
children.map((child, index) => {
if (prevNode?.type !== NodeType.LINE_BREAK && child.type === NodeType.LINE_BREAK && skipNextLineBreakFlag) {
skipNextLineBreakFlag = false;
return null;
}
prevNode = child;
skipNextLineBreakFlag = true;
return <Renderer key={`${child.type}-${index}`} index={String(index)} node={child} />;
}),
); );
}; };
......
import { repeat } from "lodash-es";
import { Node } from "@/types/proto/api/v1/markdown_service"; import { Node } from "@/types/proto/api/v1/markdown_service";
import Renderer from "./Renderer"; import Renderer from "./Renderer";
import { BaseProps } from "./types"; import { BaseProps } from "./types";
...@@ -9,24 +8,12 @@ interface Props extends BaseProps { ...@@ -9,24 +8,12 @@ interface Props extends BaseProps {
children: Node[]; children: Node[];
} }
const OrderedListItem: React.FC<Props> = ({ number, indent, children }: Props) => { const OrderedListItem: React.FC<Props> = ({ children }: Props) => {
return ( return (
<li className="w-full flex flex-row"> <li>
{indent > 0 && ( {children.map((child, index) => (
<div className="block font-mono shrink-0"> <Renderer key={`${child.type}-${index}`} index={String(index)} node={child} />
<span>{repeat(" ", indent)}</span> ))}
</div>
)}
<div className="w-auto grid grid-cols-[24px_1fr] gap-1">
<div className="w-7 h-6 flex justify-center items-center">
<span className="opacity-80">{number}.</span>
</div>
<div>
{children.map((child, index) => (
<Renderer key={`${child.type}-${index}`} index={String(index)} node={child} />
))}
</div>
</div>
</li> </li>
); );
}; };
......
import { Checkbox } from "@mui/joy"; import { Checkbox } from "@mui/joy";
import clsx from "clsx"; import clsx from "clsx";
import { repeat } from "lodash-es";
import { useContext, useState } from "react"; import { useContext, useState } from "react";
import { markdownServiceClient } from "@/grpcweb"; import { markdownServiceClient } from "@/grpcweb";
import { useMemoStore } from "@/store/v1"; import { useMemoStore } from "@/store/v1";
...@@ -17,7 +16,7 @@ interface Props { ...@@ -17,7 +16,7 @@ interface Props {
children: Node[]; children: Node[];
} }
const TaskListItem: React.FC<Props> = ({ node, indent, complete, children }: Props) => { const TaskListItem: React.FC<Props> = ({ node, complete, children }: Props) => {
const context = useContext(RendererContext); const context = useContext(RendererContext);
const memoStore = useMemoStore(); const memoStore = useMemoStore();
const [checked] = useState(complete); const [checked] = useState(complete);
...@@ -39,22 +38,15 @@ const TaskListItem: React.FC<Props> = ({ node, indent, complete, children }: Pro ...@@ -39,22 +38,15 @@ const TaskListItem: React.FC<Props> = ({ node, indent, complete, children }: Pro
}; };
return ( return (
<li className="w-full flex flex-row"> <li className={clsx("w-full grid grid-cols-[24px_1fr]")}>
{indent > 0 && ( <span className="w-6 h-6 flex justify-start items-center">
<div className="block font-mono shrink-0"> <Checkbox size="sm" checked={checked} disabled={context.readonly} onChange={(e) => handleCheckboxChange(e.target.checked)} />
<span>{repeat(" ", indent)}</span> </span>
</div> <p className={clsx(complete && "line-through opacity-80")}>
)} {children.map((child, index) => (
<div className="w-auto grid grid-cols-[24px_1fr] gap-1"> <Renderer key={`${child.type}-${index}`} index={String(index)} node={child} />
<div className="w-7 h-6 flex justify-center items-center"> ))}
<Checkbox size="sm" checked={checked} disabled={context.readonly} onChange={(e) => handleCheckboxChange(e.target.checked)} /> </p>
</div>
<div className={clsx(complete && "line-through opacity-80")}>
{children.map((child, index) => (
<Renderer key={`${child.type}-${index}`} index={String(index)} node={child} />
))}
</div>
</div>
</li> </li>
); );
}; };
......
import { repeat } from "lodash-es";
import { Node } from "@/types/proto/api/v1/markdown_service"; import { Node } from "@/types/proto/api/v1/markdown_service";
import Renderer from "./Renderer"; import Renderer from "./Renderer";
...@@ -8,24 +7,12 @@ interface Props { ...@@ -8,24 +7,12 @@ interface Props {
children: Node[]; children: Node[];
} }
const UnorderedListItem: React.FC<Props> = ({ indent, children }: Props) => { const UnorderedListItem: React.FC<Props> = ({ children }: Props) => {
return ( return (
<li className="w-full flex flex-row"> <li>
{indent > 0 && ( {children.map((child, index) => (
<div className="block font-mono shrink-0"> <Renderer key={`${child.type}-${index}`} index={String(index)} node={child} />
<span>{repeat(" ", indent)}</span> ))}
</div>
)}
<div className="w-auto grid grid-cols-[24px_1fr] gap-1">
<div className="w-7 h-6 flex justify-center items-center">
<span className="opacity-80"></span>
</div>
<div>
{children.map((child, index) => (
<Renderer key={`${child.type}-${index}`} index={String(index)} node={child} />
))}
</div>
</div>
</li> </li>
); );
}; };
......
...@@ -443,7 +443,7 @@ const MemoEditor = (props: Props) => { ...@@ -443,7 +443,7 @@ const MemoEditor = (props: Props) => {
<AddMemoRelationPopover editorRef={editorRef} /> <AddMemoRelationPopover editorRef={editorRef} />
</div> </div>
</div> </div>
<Divider className="!mt-2" /> <Divider className="!mt-2 opacity-40" />
<div className="w-full flex flex-row justify-between items-center py-3 dark:border-t-zinc-500"> <div className="w-full flex flex-row justify-between items-center py-3 dark:border-t-zinc-500">
<div className="relative flex flex-row justify-start items-center" onFocus={(e) => e.stopPropagation()}> <div className="relative flex flex-row justify-start items-center" onFocus={(e) => e.stopPropagation()}>
<Select <Select
......
...@@ -23,8 +23,8 @@ const UserBanner = (props: Props) => { ...@@ -23,8 +23,8 @@ const UserBanner = (props: Props) => {
const workspaceSettingStore = useWorkspaceSettingStore(); const workspaceSettingStore = useWorkspaceSettingStore();
const workspaceGeneralSetting = const workspaceGeneralSetting =
workspaceSettingStore.getWorkspaceSettingByKey(WorkspaceSettingKey.GENERAL).generalSetting || WorkspaceGeneralSetting.fromPartial({}); workspaceSettingStore.getWorkspaceSettingByKey(WorkspaceSettingKey.GENERAL).generalSetting || WorkspaceGeneralSetting.fromPartial({});
const title = user ? user.nickname || user.username : workspaceGeneralSetting.customProfile?.title; const title = (user ? user.nickname || user.username : workspaceGeneralSetting.customProfile?.title) || "Memos";
const avatarUrl = user ? user.avatarUrl : workspaceGeneralSetting.customProfile?.logoUrl; const avatarUrl = (user ? user.avatarUrl : workspaceGeneralSetting.customProfile?.logoUrl) || "/logo.webp";
const handleSignOut = async () => { const handleSignOut = async () => {
await authServiceClient.signOut({}); await authServiceClient.signOut({});
......
...@@ -81,20 +81,6 @@ module.exports = { ...@@ -81,20 +81,6 @@ module.exports = {
md: "calc(var(--radius) - 2px)", md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)", sm: "calc(var(--radius) - 4px)",
}, },
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
}, },
}, },
plugins: [require("tailwindcss-animate")], plugins: [require("tailwindcss-animate")],
......
{
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.html"
}
]
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment