Commit fbe0251e authored by Steven's avatar Steven

feat: impl list renderer

parent 759f7c61
This diff is collapsed.
......@@ -25,7 +25,7 @@ require (
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
github.com/usememos/gomark v0.0.0-20240714122951-35ed01b21822
github.com/usememos/gomark v0.0.0-20240917110103-3ccacc410d19
golang.org/x/crypto v0.25.0
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8
golang.org/x/mod v0.20.0
......
......@@ -439,8 +439,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/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/usememos/gomark v0.0.0-20240714122951-35ed01b21822 h1:4DQs0DJGaXLq+1eP6QfMkvcunyTVJ5OR7LK1cOm2imE=
github.com/usememos/gomark v0.0.0-20240714122951-35ed01b21822/go.mod h1:7CZRoYFQyyljzplOTeyODFR26O+wr0BbnpTWVLGfKJA=
github.com/usememos/gomark v0.0.0-20240917110103-3ccacc410d19 h1:LFNLNBuUxqyYqh8yZrPyuk0n72rRGHqGWfoGBmoysMk=
github.com/usememos/gomark v0.0.0-20240917110103-3ccacc410d19/go.mod h1:7CZRoYFQyyljzplOTeyODFR26O+wr0BbnpTWVLGfKJA=
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/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
......
......@@ -70,71 +70,81 @@ message LinkMetadata {
enum NodeType {
NODE_UNSPECIFIED = 0;
// Block nodes.
LINE_BREAK = 1;
PARAGRAPH = 2;
CODE_BLOCK = 3;
HEADING = 4;
HORIZONTAL_RULE = 5;
BLOCKQUOTE = 6;
ORDERED_LIST = 7;
UNORDERED_LIST = 8;
TASK_LIST = 9;
MATH_BLOCK = 10;
TABLE = 11;
EMBEDDED_CONTENT = 12;
TEXT = 13;
BOLD = 14;
ITALIC = 15;
BOLD_ITALIC = 16;
CODE = 17;
IMAGE = 18;
LINK = 19;
AUTO_LINK = 20;
TAG = 21;
STRIKETHROUGH = 22;
ESCAPING_CHARACTER = 23;
MATH = 24;
HIGHLIGHT = 25;
SUBSCRIPT = 26;
SUPERSCRIPT = 27;
REFERENCED_CONTENT = 28;
SPOILER = 29;
HTML_ELEMENT = 30;
LIST = 7;
ORDERED_LIST_ITEM = 8;
UNORDERED_LIST_ITEM = 9;
TASK_LIST_ITEM = 10;
MATH_BLOCK = 11;
TABLE = 12;
EMBEDDED_CONTENT = 13;
// Inline nodes.
TEXT = 51;
BOLD = 52;
ITALIC = 53;
BOLD_ITALIC = 54;
CODE = 55;
IMAGE = 56;
LINK = 57;
AUTO_LINK = 58;
TAG = 59;
STRIKETHROUGH = 60;
ESCAPING_CHARACTER = 61;
MATH = 62;
HIGHLIGHT = 63;
SUBSCRIPT = 64;
SUPERSCRIPT = 65;
REFERENCED_CONTENT = 66;
SPOILER = 67;
HTML_ELEMENT = 68;
}
message Node {
NodeType type = 1;
oneof node {
LineBreakNode line_break_node = 2;
ParagraphNode paragraph_node = 3;
CodeBlockNode code_block_node = 4;
HeadingNode heading_node = 5;
HorizontalRuleNode horizontal_rule_node = 6;
BlockquoteNode blockquote_node = 7;
OrderedListNode ordered_list_node = 8;
UnorderedListNode unordered_list_node = 9;
TaskListNode task_list_node = 10;
MathBlockNode math_block_node = 11;
TableNode table_node = 12;
EmbeddedContentNode embedded_content_node = 13;
TextNode text_node = 14;
BoldNode bold_node = 15;
ItalicNode italic_node = 16;
BoldItalicNode bold_italic_node = 17;
CodeNode code_node = 18;
ImageNode image_node = 19;
LinkNode link_node = 20;
AutoLinkNode auto_link_node = 21;
TagNode tag_node = 22;
StrikethroughNode strikethrough_node = 23;
EscapingCharacterNode escaping_character_node = 24;
MathNode math_node = 25;
HighlightNode highlight_node = 26;
SubscriptNode subscript_node = 27;
SuperscriptNode superscript_node = 28;
ReferencedContentNode referenced_content_node = 29;
SpoilerNode spoiler_node = 30;
HTMLElementNode html_element_node = 31;
// Block nodes.
LineBreakNode line_break_node = 11;
ParagraphNode paragraph_node = 12;
CodeBlockNode code_block_node = 13;
HeadingNode heading_node = 14;
HorizontalRuleNode horizontal_rule_node = 15;
BlockquoteNode blockquote_node = 16;
ListNode list_node = 17;
OrderedListItemNode ordered_list_item_node = 18;
UnorderedListItemNode unordered_list_item_node = 19;
TaskListItemNode task_list_item_node = 20;
MathBlockNode math_block_node = 21;
TableNode table_node = 22;
EmbeddedContentNode embedded_content_node = 23;
// Inline nodes.
TextNode text_node = 51;
BoldNode bold_node = 52;
ItalicNode italic_node = 53;
BoldItalicNode bold_italic_node = 54;
CodeNode code_node = 55;
ImageNode image_node = 56;
LinkNode link_node = 57;
AutoLinkNode auto_link_node = 58;
TagNode tag_node = 59;
StrikethroughNode strikethrough_node = 60;
EscapingCharacterNode escaping_character_node = 61;
MathNode math_node = 62;
HighlightNode highlight_node = 63;
SubscriptNode subscript_node = 64;
SuperscriptNode superscript_node = 65;
ReferencedContentNode referenced_content_node = 66;
SpoilerNode spoiler_node = 67;
HTMLElementNode html_element_node = 68;
}
}
......@@ -162,19 +172,23 @@ message BlockquoteNode {
repeated Node children = 1;
}
message OrderedListNode {
message ListNode {
repeated Node children = 1;
}
message OrderedListItemNode {
string number = 1;
int32 indent = 2;
repeated Node children = 3;
}
message UnorderedListNode {
message UnorderedListItemNode {
string symbol = 1;
int32 indent = 2;
repeated Node children = 3;
}
message TaskListNode {
message TaskListItemNode {
string symbol = 1;
int32 indent = 2;
bool complete = 3;
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -75,15 +75,18 @@ func convertFromASTNode(rawNode ast.Node) *v1pb.Node {
case *ast.Blockquote:
children := convertFromASTNodes(n.Children)
node.Node = &v1pb.Node_BlockquoteNode{BlockquoteNode: &v1pb.BlockquoteNode{Children: children}}
case *ast.OrderedList:
case *ast.List:
children := convertFromASTNodes(n.Children)
node.Node = &v1pb.Node_OrderedListNode{OrderedListNode: &v1pb.OrderedListNode{Number: n.Number, Indent: int32(n.Indent), Children: children}}
case *ast.UnorderedList:
node.Node = &v1pb.Node_ListNode{ListNode: &v1pb.ListNode{Children: children}}
case *ast.OrderedListItem:
children := convertFromASTNodes(n.Children)
node.Node = &v1pb.Node_UnorderedListNode{UnorderedListNode: &v1pb.UnorderedListNode{Symbol: n.Symbol, Indent: int32(n.Indent), Children: children}}
case *ast.TaskList:
node.Node = &v1pb.Node_OrderedListItemNode{OrderedListItemNode: &v1pb.OrderedListItemNode{Number: n.Number, Indent: int32(n.Indent), Children: children}}
case *ast.UnorderedListItem:
children := convertFromASTNodes(n.Children)
node.Node = &v1pb.Node_TaskListNode{TaskListNode: &v1pb.TaskListNode{Symbol: n.Symbol, Indent: int32(n.Indent), Complete: n.Complete, Children: children}}
node.Node = &v1pb.Node_UnorderedListItemNode{UnorderedListItemNode: &v1pb.UnorderedListItemNode{Symbol: n.Symbol, Indent: int32(n.Indent), Children: children}}
case *ast.TaskListItem:
children := convertFromASTNodes(n.Children)
node.Node = &v1pb.Node_TaskListItemNode{TaskListItemNode: &v1pb.TaskListItemNode{Symbol: n.Symbol, Indent: int32(n.Indent), Complete: n.Complete, Children: children}}
case *ast.MathBlock:
node.Node = &v1pb.Node_MathBlockNode{MathBlockNode: &v1pb.MathBlockNode{Content: n.Content}}
case *ast.Table:
......@@ -170,15 +173,18 @@ func convertToASTNode(node *v1pb.Node) ast.Node {
case *v1pb.Node_BlockquoteNode:
children := convertToASTNodes(n.BlockquoteNode.Children)
return &ast.Blockquote{Children: children}
case *v1pb.Node_OrderedListNode:
children := convertToASTNodes(n.OrderedListNode.Children)
return &ast.OrderedList{Number: n.OrderedListNode.Number, Indent: int(n.OrderedListNode.Indent), Children: children}
case *v1pb.Node_UnorderedListNode:
children := convertToASTNodes(n.UnorderedListNode.Children)
return &ast.UnorderedList{Symbol: n.UnorderedListNode.Symbol, Indent: int(n.UnorderedListNode.Indent), Children: children}
case *v1pb.Node_TaskListNode:
children := convertToASTNodes(n.TaskListNode.Children)
return &ast.TaskList{Symbol: n.TaskListNode.Symbol, Indent: int(n.TaskListNode.Indent), Complete: n.TaskListNode.Complete, Children: children}
case *v1pb.Node_ListNode:
children := convertToASTNodes(n.ListNode.Children)
return &ast.List{Children: children}
case *v1pb.Node_OrderedListItemNode:
children := convertToASTNodes(n.OrderedListItemNode.Children)
return &ast.OrderedListItem{Number: n.OrderedListItemNode.Number, Indent: int(n.OrderedListItemNode.Indent), Children: children}
case *v1pb.Node_UnorderedListItemNode:
children := convertToASTNodes(n.UnorderedListItemNode.Children)
return &ast.UnorderedListItem{Symbol: n.UnorderedListItemNode.Symbol, Indent: int(n.UnorderedListItemNode.Indent), Children: children}
case *v1pb.Node_TaskListItemNode:
children := convertToASTNodes(n.TaskListItemNode.Children)
return &ast.TaskListItem{Symbol: n.TaskListItemNode.Symbol, Indent: int(n.TaskListItemNode.Indent), Complete: n.TaskListItemNode.Complete, Children: children}
case *v1pb.Node_MathBlockNode:
return &ast.MathBlock{Content: n.MathBlockNode.Content}
case *v1pb.Node_TableNode:
......
......@@ -86,7 +86,7 @@ func GetMemoPropertyFromContent(content string) (*storepb.MemoPayload_Property,
}
case *ast.Link, *ast.AutoLink:
property.HasLink = true
case *ast.TaskList:
case *ast.TaskListItem:
property.HasTaskList = true
if !n.Complete {
property.HasIncompleteTasks = true
......@@ -108,11 +108,13 @@ func TraverseASTNodes(nodes []ast.Node, fn func(ast.Node)) {
TraverseASTNodes(n.Children, fn)
case *ast.Blockquote:
TraverseASTNodes(n.Children, fn)
case *ast.OrderedList:
case *ast.List:
TraverseASTNodes(n.Children, fn)
case *ast.UnorderedList:
case *ast.OrderedListItem:
TraverseASTNodes(n.Children, fn)
case *ast.TaskList:
case *ast.UnorderedListItem:
TraverseASTNodes(n.Children, fn)
case *ast.TaskListItem:
TraverseASTNodes(n.Children, fn)
case *ast.Bold:
TraverseASTNodes(n.Children, fn)
......
import { Node, NodeType } from "@/types/proto/api/v1/markdown_service";
import Renderer from "./Renderer";
interface Props {
index: string;
children: Node[];
}
const List: React.FC<Props> = ({ children }: Props) => {
let prevNode: Node | null = null;
let skipNextLineBreakFlag = false;
return (
<dl>
{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} />;
})}
</dl>
);
};
export default List;
import { repeat } from "lodash-es";
import { Node } from "@/types/proto/api/v1/markdown_service";
import Renderer from "./Renderer";
import { BaseProps } from "./types";
interface Props extends BaseProps {
number: string;
indent: number;
children: Node[];
}
const OrderedList: React.FC<Props> = ({ number, indent, children }: Props) => {
return (
<ol>
<li className="w-full flex flex-row">
{indent > 0 && (
<div className="block font-mono shrink-0">
<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>
</ol>
);
};
export default OrderedList;
import { repeat } from "lodash-es";
import { Node } from "@/types/proto/api/v1/markdown_service";
import Renderer from "./Renderer";
import { BaseProps } from "./types";
interface Props extends BaseProps {
number: string;
indent: number;
children: Node[];
}
const OrderedListItem: React.FC<Props> = ({ number, indent, children }: Props) => {
return (
<li className="w-full flex flex-row">
{indent > 0 && (
<div className="block font-mono shrink-0">
<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>
);
};
export default OrderedListItem;
......@@ -14,11 +14,12 @@ import {
ImageNode,
ItalicNode,
LinkNode,
ListNode,
MathBlockNode,
MathNode,
Node,
NodeType,
OrderedListNode,
OrderedListItemNode,
ParagraphNode,
ReferencedContentNode,
SpoilerNode,
......@@ -27,9 +28,9 @@ import {
SuperscriptNode,
TableNode,
TagNode,
TaskListNode,
TaskListItemNode,
TextNode,
UnorderedListNode,
UnorderedListItemNode,
} from "@/types/proto/api/v1/markdown_service";
import Blockquote from "./Blockquote";
import Bold from "./Bold";
......@@ -46,8 +47,9 @@ import Image from "./Image";
import Italic from "./Italic";
import LineBreak from "./LineBreak";
import Link from "./Link";
import List from "./List";
import Math from "./Math";
import OrderedList from "./OrderedList";
import OrderedListItem from "./OrderedListItem";
import Paragraph from "./Paragraph";
import ReferencedContent from "./ReferencedContent";
import Spoiler from "./Spoiler";
......@@ -56,9 +58,9 @@ import Subscript from "./Subscript";
import Superscript from "./Superscript";
import Table from "./Table";
import Tag from "./Tag";
import TaskList from "./TaskList";
import TaskListItem from "./TaskListItem";
import Text from "./Text";
import UnorderedList from "./UnorderedList";
import UnorderedListItem from "./UnorderedListItem";
interface Props {
index: string;
......@@ -79,12 +81,14 @@ const Renderer: React.FC<Props> = ({ index, node }: Props) => {
return <HorizontalRule index={index} {...(node.horizontalRuleNode as HorizontalRuleNode)} />;
case NodeType.BLOCKQUOTE:
return <Blockquote index={index} {...(node.blockquoteNode as BlockquoteNode)} />;
case NodeType.ORDERED_LIST:
return <OrderedList index={index} {...(node.orderedListNode as OrderedListNode)} />;
case NodeType.UNORDERED_LIST:
return <UnorderedList {...(node.unorderedListNode as UnorderedListNode)} />;
case NodeType.TASK_LIST:
return <TaskList index={index} {...(node.taskListNode as TaskListNode)} />;
case NodeType.LIST:
return <List index={index} {...(node.listNode as ListNode)} />;
case NodeType.ORDERED_LIST_ITEM:
return <OrderedListItem index={index} {...(node.orderedListItemNode as OrderedListItemNode)} />;
case NodeType.UNORDERED_LIST_ITEM:
return <UnorderedListItem {...(node.unorderedListItemNode as UnorderedListItemNode)} />;
case NodeType.TASK_LIST_ITEM:
return <TaskListItem index={index} node={node} {...(node.taskListItemNode as TaskListItemNode)} />;
case NodeType.MATH_BLOCK:
return <Math {...(node.mathBlockNode as MathBlockNode)} block={true} />;
case NodeType.TABLE:
......
......@@ -4,11 +4,12 @@ import { repeat } from "lodash-es";
import { useContext, useState } from "react";
import { markdownServiceClient } from "@/grpcweb";
import { useMemoStore } from "@/store/v1";
import { Node, NodeType, TaskListNode } from "@/types/proto/api/v1/markdown_service";
import { Node, TaskListItemNode } from "@/types/proto/api/v1/markdown_service";
import Renderer from "./Renderer";
import { RendererContext } from "./types";
interface Props {
node: Node;
index: string;
symbol: string;
indent: number;
......@@ -16,7 +17,7 @@ interface Props {
children: Node[];
}
const TaskList: React.FC<Props> = ({ index, indent, complete, children }: Props) => {
const TaskListItem: React.FC<Props> = ({ node, indent, complete, children }: Props) => {
const context = useContext(RendererContext);
const memoStore = useMemoStore();
const [checked] = useState(complete);
......@@ -26,17 +27,7 @@ const TaskList: React.FC<Props> = ({ index, indent, complete, children }: Props)
return;
}
const nodeIndex = Number(index);
if (isNaN(nodeIndex)) {
return;
}
const node = context.nodes[nodeIndex];
if (node.type !== NodeType.TASK_LIST) {
return;
}
(node.taskListNode as TaskListNode)!.complete = on;
(node.taskListItemNode as TaskListItemNode)!.complete = on;
const { markdown } = await markdownServiceClient.restoreMarkdownNodes({ nodes: context.nodes });
await memoStore.updateMemo(
{
......@@ -48,26 +39,24 @@ const TaskList: React.FC<Props> = ({ index, indent, complete, children }: Props)
};
return (
<ul>
<li className="w-full flex flex-row">
{indent > 0 && (
<div className="block font-mono shrink-0">
<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">
<Checkbox size="sm" checked={checked} disabled={context.readonly} onChange={(e) => handleCheckboxChange(e.target.checked)} />
</div>
<div className={clsx(complete && "line-through opacity-80")}>
{children.map((child, index) => (
<Renderer key={`${child.type}-${index}`} index={String(index)} node={child} />
))}
</div>
<li className="w-full flex flex-row">
{indent > 0 && (
<div className="block font-mono shrink-0">
<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">
<Checkbox size="sm" checked={checked} disabled={context.readonly} onChange={(e) => handleCheckboxChange(e.target.checked)} />
</div>
<div className={clsx(complete && "line-through opacity-80")}>
{children.map((child, index) => (
<Renderer key={`${child.type}-${index}`} index={String(index)} node={child} />
))}
</div>
</li>
</ul>
</div>
</li>
);
};
export default TaskList;
export default TaskListItem;
import { repeat } from "lodash-es";
import { Node } from "@/types/proto/api/v1/markdown_service";
import Renderer from "./Renderer";
interface Props {
symbol: string;
indent: number;
children: Node[];
}
const UnorderedList: React.FC<Props> = ({ indent, children }: Props) => {
return (
<ul>
<li className="w-full flex flex-row">
{indent > 0 && (
<div className="block font-mono shrink-0">
<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>
</ul>
);
};
export default UnorderedList;
import { repeat } from "lodash-es";
import { Node } from "@/types/proto/api/v1/markdown_service";
import Renderer from "./Renderer";
interface Props {
symbol: string;
indent: number;
children: Node[];
}
const UnorderedListItem: React.FC<Props> = ({ indent, children }: Props) => {
return (
<li className="w-full flex flex-row">
{indent > 0 && (
<div className="block font-mono shrink-0">
<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>
);
};
export default UnorderedListItem;
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