mirror of
https://github.com/usememos/memos.git
synced 2025-03-15 18:20:09 +01:00
chore: implement node renderer components
This commit is contained in:
parent
28c0549705
commit
5d677c3c57
@ -46,7 +46,6 @@ enum NodeType {
|
||||
ESCAPING_CHARACTER = 19;
|
||||
}
|
||||
|
||||
// Define the Node message.
|
||||
message Node {
|
||||
NodeType type = 1;
|
||||
oneof node {
|
||||
|
@ -1092,7 +1092,7 @@
|
||||
<a name="memos-api-v2-Node"></a>
|
||||
|
||||
### Node
|
||||
Define the Node message.
|
||||
|
||||
|
||||
|
||||
| Field | Type | Label | Description |
|
||||
|
@ -215,7 +215,6 @@ func (x *ParseMarkdownResponse) GetNodes() []*Node {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Define the Node message.
|
||||
type Node struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
18
web/src/components/MemoContentV1/Blockquote.tsx
Normal file
18
web/src/components/MemoContentV1/Blockquote.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { Node } from "@/types/proto/api/v2/markdown_service";
|
||||
import Renderer from "./Renderer";
|
||||
|
||||
interface Props {
|
||||
children: Node[];
|
||||
}
|
||||
|
||||
const Blockquote: React.FC<Props> = ({ children }: Props) => {
|
||||
return (
|
||||
<blockquote>
|
||||
{children.map((child, index) => (
|
||||
<Renderer key={`${child.type}-${index}`} node={child} />
|
||||
))}
|
||||
</blockquote>
|
||||
);
|
||||
};
|
||||
|
||||
export default Blockquote;
|
19
web/src/components/MemoContentV1/Bold.tsx
Normal file
19
web/src/components/MemoContentV1/Bold.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { Node } from "@/types/proto/api/v2/markdown_service";
|
||||
import Renderer from "./Renderer";
|
||||
|
||||
interface Props {
|
||||
symbol: string;
|
||||
children: Node[];
|
||||
}
|
||||
|
||||
const Bold: React.FC<Props> = ({ children }: Props) => {
|
||||
return (
|
||||
<strong>
|
||||
{children.map((child, index) => (
|
||||
<Renderer key={`${child.type}-${index}`} node={child} />
|
||||
))}
|
||||
</strong>
|
||||
);
|
||||
};
|
||||
|
||||
export default Bold;
|
14
web/src/components/MemoContentV1/BoldItalic.tsx
Normal file
14
web/src/components/MemoContentV1/BoldItalic.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
interface Props {
|
||||
symbol: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
const BoldItalic: React.FC<Props> = ({ content }: Props) => {
|
||||
return (
|
||||
<strong>
|
||||
<em>{content}</em>
|
||||
</strong>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoldItalic;
|
9
web/src/components/MemoContentV1/Code.tsx
Normal file
9
web/src/components/MemoContentV1/Code.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
interface Props {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const Code: React.FC<Props> = ({ content }: Props) => {
|
||||
return <code>{content}</code>;
|
||||
};
|
||||
|
||||
export default Code;
|
42
web/src/components/MemoContentV1/CodeBlock.tsx
Normal file
42
web/src/components/MemoContentV1/CodeBlock.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import classNames from "classnames";
|
||||
import copy from "copy-to-clipboard";
|
||||
import hljs from "highlight.js";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
interface Props {
|
||||
language: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
const CodeBlock: React.FC<Props> = ({ language, content }: Props) => {
|
||||
const formatedLanguage = language.toLowerCase() || "plaintext";
|
||||
let highlightedCode = hljs.highlightAuto(content).value;
|
||||
|
||||
try {
|
||||
const temp = hljs.highlight(content, {
|
||||
language: formatedLanguage,
|
||||
}).value;
|
||||
highlightedCode = temp;
|
||||
} catch (error) {
|
||||
// Skip error and use default highlighted code.
|
||||
}
|
||||
|
||||
const handleCopyButtonClick = () => {
|
||||
copy(content);
|
||||
toast.success("Copied to clipboard!");
|
||||
};
|
||||
|
||||
return (
|
||||
<pre className="group w-full my-1 p-3 rounded bg-gray-100 dark:bg-zinc-600 whitespace-pre-wrap relative">
|
||||
<button
|
||||
className="text-xs font-mono italic absolute top-0 right-0 px-2 leading-6 border btn-text rounded opacity-0 group-hover:opacity-60"
|
||||
onClick={handleCopyButtonClick}
|
||||
>
|
||||
copy
|
||||
</button>
|
||||
<code className={classNames(`language-${formatedLanguage}`, "block")} dangerouslySetInnerHTML={{ __html: highlightedCode }}></code>
|
||||
</pre>
|
||||
);
|
||||
};
|
||||
|
||||
export default CodeBlock;
|
9
web/src/components/MemoContentV1/EscapingCharacter.tsx
Normal file
9
web/src/components/MemoContentV1/EscapingCharacter.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
interface Props {
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
const EscapingCharacter: React.FC<Props> = ({ symbol }: Props) => {
|
||||
return <span>{symbol}</span>;
|
||||
};
|
||||
|
||||
export default EscapingCharacter;
|
20
web/src/components/MemoContentV1/Heading.tsx
Normal file
20
web/src/components/MemoContentV1/Heading.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { Node } from "@/types/proto/api/v2/markdown_service";
|
||||
import Renderer from "./Renderer";
|
||||
|
||||
interface Props {
|
||||
level: number;
|
||||
children: Node[];
|
||||
}
|
||||
|
||||
const Heading: React.FC<Props> = ({ level, children }: Props) => {
|
||||
const Head = `h${level}` as keyof JSX.IntrinsicElements;
|
||||
return (
|
||||
<Head>
|
||||
{children.map((child, index) => (
|
||||
<Renderer key={`${child.type}-${index}`} node={child} />
|
||||
))}
|
||||
</Head>
|
||||
);
|
||||
};
|
||||
|
||||
export default Heading;
|
9
web/src/components/MemoContentV1/HorizontalRule.tsx
Normal file
9
web/src/components/MemoContentV1/HorizontalRule.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
interface Props {
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
const HorizontalRule: React.FC<Props> = () => {
|
||||
return <hr />;
|
||||
};
|
||||
|
||||
export default HorizontalRule;
|
10
web/src/components/MemoContentV1/Image.tsx
Normal file
10
web/src/components/MemoContentV1/Image.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
interface Props {
|
||||
altText: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
const Image: React.FC<Props> = ({ altText, url }: Props) => {
|
||||
return <img alt={altText} src={url} />;
|
||||
};
|
||||
|
||||
export default Image;
|
10
web/src/components/MemoContentV1/Italic.tsx
Normal file
10
web/src/components/MemoContentV1/Italic.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
interface Props {
|
||||
symbol: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
const Italic: React.FC<Props> = ({ content }: Props) => {
|
||||
return <em>{content}</em>;
|
||||
};
|
||||
|
||||
export default Italic;
|
7
web/src/components/MemoContentV1/LineBreak.tsx
Normal file
7
web/src/components/MemoContentV1/LineBreak.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
interface Props {}
|
||||
|
||||
const LineBreak: React.FC<Props> = () => {
|
||||
return <br />;
|
||||
};
|
||||
|
||||
export default LineBreak;
|
14
web/src/components/MemoContentV1/Link.tsx
Normal file
14
web/src/components/MemoContentV1/Link.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
interface Props {
|
||||
text: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
const Link: React.FC<Props> = ({ text, url }: Props) => {
|
||||
return (
|
||||
<a className="text-blue-600 dark:text-blue-400 cursor-pointer underline break-all hover:opacity-80 decoration-1" href={url}>
|
||||
{text}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default Link;
|
26
web/src/components/MemoContentV1/OrderedList.tsx
Normal file
26
web/src/components/MemoContentV1/OrderedList.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { Node } from "@/types/proto/api/v2/markdown_service";
|
||||
import Renderer from "./Renderer";
|
||||
|
||||
interface Props {
|
||||
number: string;
|
||||
children: Node[];
|
||||
}
|
||||
|
||||
const OrderedList: React.FC<Props> = ({ number, children }: Props) => {
|
||||
return (
|
||||
<ol>
|
||||
<li className="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}`} node={child} />
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrderedList;
|
18
web/src/components/MemoContentV1/Paragraph.tsx
Normal file
18
web/src/components/MemoContentV1/Paragraph.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { Node } from "@/types/proto/api/v2/markdown_service";
|
||||
import Renderer from "./Renderer";
|
||||
|
||||
interface Props {
|
||||
children: Node[];
|
||||
}
|
||||
|
||||
const Paragraph: React.FC<Props> = ({ children }: Props) => {
|
||||
return (
|
||||
<p>
|
||||
{children.map((child, index) => (
|
||||
<Renderer key={`${child.type}-${index}`} node={child} />
|
||||
))}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export default Paragraph;
|
92
web/src/components/MemoContentV1/Renderer.tsx
Normal file
92
web/src/components/MemoContentV1/Renderer.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
import {
|
||||
BlockquoteNode,
|
||||
BoldItalicNode,
|
||||
BoldNode,
|
||||
CodeBlockNode,
|
||||
CodeNode,
|
||||
EscapingCharacterNode,
|
||||
HeadingNode,
|
||||
HorizontalRuleNode,
|
||||
ImageNode,
|
||||
ItalicNode,
|
||||
LinkNode,
|
||||
Node,
|
||||
NodeType,
|
||||
OrderedListNode,
|
||||
ParagraphNode,
|
||||
StrikethroughNode,
|
||||
TagNode,
|
||||
TaskListNode,
|
||||
TextNode,
|
||||
UnorderedListNode,
|
||||
} from "@/types/proto/api/v2/markdown_service";
|
||||
import Blockquote from "./Blockquote";
|
||||
import Bold from "./Bold";
|
||||
import BoldItalic from "./BoldItalic";
|
||||
import Code from "./Code";
|
||||
import CodeBlock from "./CodeBlock";
|
||||
import EscapingCharacter from "./EscapingCharacter";
|
||||
import Heading from "./Heading";
|
||||
import HorizontalRule from "./HorizontalRule";
|
||||
import Image from "./Image";
|
||||
import Italic from "./Italic";
|
||||
import LineBreak from "./LineBreak";
|
||||
import Link from "./Link";
|
||||
import OrderedList from "./OrderedList";
|
||||
import Paragraph from "./Paragraph";
|
||||
import Strikethrough from "./Strikethrough";
|
||||
import Tag from "./Tag";
|
||||
import TaskList from "./TaskList";
|
||||
import Text from "./Text";
|
||||
import UnorderedList from "./UnorderedList";
|
||||
|
||||
interface Props {
|
||||
node: Node;
|
||||
}
|
||||
|
||||
const Renderer: React.FC<Props> = ({ node }: Props) => {
|
||||
switch (node.type) {
|
||||
case NodeType.LINE_BREAK:
|
||||
return <LineBreak />;
|
||||
case NodeType.PARAGRAPH:
|
||||
return <Paragraph {...(node.paragraphNode as ParagraphNode)} />;
|
||||
case NodeType.CODE_BLOCK:
|
||||
return <CodeBlock {...(node.codeBlockNode as CodeBlockNode)} />;
|
||||
case NodeType.HEADING:
|
||||
return <Heading {...(node.headingNode as HeadingNode)} />;
|
||||
case NodeType.HORIZONTAL_RULE:
|
||||
return <HorizontalRule {...(node.horizontalRuleNode as HorizontalRuleNode)} />;
|
||||
case NodeType.BLOCKQUOTE:
|
||||
return <Blockquote {...(node.blockquoteNode as BlockquoteNode)} />;
|
||||
case NodeType.ORDERED_LIST:
|
||||
return <OrderedList {...(node.orderedListNode as OrderedListNode)} />;
|
||||
case NodeType.UNORDERED_LIST:
|
||||
return <UnorderedList {...(node.unorderedListNode as UnorderedListNode)} />;
|
||||
case NodeType.TASK_LIST:
|
||||
return <TaskList {...(node.taskListNode as TaskListNode)} />;
|
||||
case NodeType.TEXT:
|
||||
return <Text {...(node.textNode as TextNode)} />;
|
||||
case NodeType.BOLD:
|
||||
return <Bold {...(node.boldNode as BoldNode)} />;
|
||||
case NodeType.ITALIC:
|
||||
return <Italic {...(node.italicNode as ItalicNode)} />;
|
||||
case NodeType.BOLD_ITALIC:
|
||||
return <BoldItalic {...(node.boldItalicNode as BoldItalicNode)} />;
|
||||
case NodeType.CODE:
|
||||
return <Code {...(node.codeNode as CodeNode)} />;
|
||||
case NodeType.IMAGE:
|
||||
return <Image {...(node.imageNode as ImageNode)} />;
|
||||
case NodeType.LINK:
|
||||
return <Link {...(node.linkNode as LinkNode)} />;
|
||||
case NodeType.TAG:
|
||||
return <Tag {...(node.tagNode as TagNode)} />;
|
||||
case NodeType.STRIKETHROUGH:
|
||||
return <Strikethrough {...(node.strikethroughNode as StrikethroughNode)} />;
|
||||
case NodeType.ESCAPING_CHARACTER:
|
||||
return <EscapingCharacter {...(node.escapingCharacterNode as EscapingCharacterNode)} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default Renderer;
|
9
web/src/components/MemoContentV1/Strikethrough.tsx
Normal file
9
web/src/components/MemoContentV1/Strikethrough.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
interface Props {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const Strikethrough: React.FC<Props> = ({ content }: Props) => {
|
||||
return <del>{content}</del>;
|
||||
};
|
||||
|
||||
export default Strikethrough;
|
9
web/src/components/MemoContentV1/Tag.tsx
Normal file
9
web/src/components/MemoContentV1/Tag.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
interface Props {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const Tag: React.FC<Props> = ({ content }: Props) => {
|
||||
return <span className="inline-block w-auto text-blue-600 dark:text-blue-400">#{content}</span>;
|
||||
};
|
||||
|
||||
export default Tag;
|
28
web/src/components/MemoContentV1/TaskList.tsx
Normal file
28
web/src/components/MemoContentV1/TaskList.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import { Checkbox } from "@mui/joy";
|
||||
import { Node } from "@/types/proto/api/v2/markdown_service";
|
||||
import Renderer from "./Renderer";
|
||||
|
||||
interface Props {
|
||||
symbol: string;
|
||||
complete: boolean;
|
||||
children: Node[];
|
||||
}
|
||||
|
||||
const TaskList: React.FC<Props> = ({ complete, children }: Props) => {
|
||||
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 />
|
||||
</div>
|
||||
<div>
|
||||
{children.map((child, index) => (
|
||||
<Renderer key={`${child.type}-${index}`} node={child} />
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskList;
|
9
web/src/components/MemoContentV1/Text.tsx
Normal file
9
web/src/components/MemoContentV1/Text.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
interface Props {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const Text: React.FC<Props> = ({ content }: Props) => {
|
||||
return <span>{content}</span>;
|
||||
};
|
||||
|
||||
export default Text;
|
26
web/src/components/MemoContentV1/UnorderedList.tsx
Normal file
26
web/src/components/MemoContentV1/UnorderedList.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { Node } from "@/types/proto/api/v2/markdown_service";
|
||||
import Renderer from "./Renderer";
|
||||
|
||||
interface Props {
|
||||
symbol: string;
|
||||
children: Node[];
|
||||
}
|
||||
|
||||
const UnorderedList: React.FC<Props> = ({ children }: Props) => {
|
||||
return (
|
||||
<ul>
|
||||
<li className="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}`} node={child} />
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
export default UnorderedList;
|
48
web/src/components/MemoContentV1/index.tsx
Normal file
48
web/src/components/MemoContentV1/index.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { markdownServiceClient } from "@/grpcweb";
|
||||
import { Node } from "@/types/proto/api/v2/markdown_service";
|
||||
import Renderer from "./Renderer";
|
||||
|
||||
interface Props {
|
||||
content: string;
|
||||
className?: string;
|
||||
onMemoContentClick?: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
const MemoContentV1: React.FC<Props> = (props: Props) => {
|
||||
const { className, content, onMemoContentClick } = props;
|
||||
const [nodes, setNodes] = useState<Node[]>([]);
|
||||
const memoContentContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
markdownServiceClient
|
||||
.parseMarkdown({
|
||||
markdown: content,
|
||||
})
|
||||
.then(({ nodes }) => {
|
||||
setNodes(nodes);
|
||||
});
|
||||
}, [content]);
|
||||
|
||||
const handleMemoContentClick = async (e: React.MouseEvent) => {
|
||||
if (onMemoContentClick) {
|
||||
onMemoContentClick(e);
|
||||
}
|
||||
};
|
||||
|
||||
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}
|
||||
>
|
||||
{nodes.map((node, index) => (
|
||||
<Renderer key={`${node.type}-${index}`} node={node} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MemoContentV1;
|
1
web/src/components/MemoContentV1/types/context.ts
Normal file
1
web/src/components/MemoContentV1/types/context.ts
Normal file
@ -0,0 +1 @@
|
||||
export interface RendererContext {}
|
1
web/src/components/MemoContentV1/types/index.ts
Normal file
1
web/src/components/MemoContentV1/types/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./context";
|
@ -1,13 +1,12 @@
|
||||
import { Select, Option, Button, IconButton, Divider } from "@mui/joy";
|
||||
import { isNumber, last, uniq, uniqBy } from "lodash-es";
|
||||
import { isNumber, last, uniqBy } from "lodash-es";
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useLocalStorage from "react-use/lib/useLocalStorage";
|
||||
import { TAB_SPACE_WIDTH, UNKNOWN_ID, VISIBILITY_SELECTOR_ITEMS } from "@/helpers/consts";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import { getMatchedNodes } from "@/labs/marked";
|
||||
import { useGlobalStore, useMemoStore, useResourceStore, useTagStore } from "@/store/module";
|
||||
import { useGlobalStore, useMemoStore, useResourceStore } from "@/store/module";
|
||||
import { useUserV1Store } from "@/store/v1";
|
||||
import { Resource } from "@/types/proto/api/v2/resource_service";
|
||||
import { UserSetting, User_Role } from "@/types/proto/api/v2/user_service";
|
||||
@ -52,7 +51,6 @@ const MemoEditor = (props: Props) => {
|
||||
} = useGlobalStore();
|
||||
const userV1Store = useUserV1Store();
|
||||
const memoStore = useMemoStore();
|
||||
const tagStore = useTagStore();
|
||||
const resourceStore = useResourceStore();
|
||||
const currentUser = useCurrentUser();
|
||||
const [state, setState] = useState<State>({
|
||||
@ -335,13 +333,6 @@ const MemoEditor = (props: Props) => {
|
||||
};
|
||||
});
|
||||
|
||||
// Upsert tag with the content.
|
||||
const matchedNodes = getMatchedNodes(content);
|
||||
const tagNameList = uniq(matchedNodes.filter((node) => node.parserName === "tag").map((node) => node.matchedContent.slice(1)));
|
||||
for (const tagName of tagNameList) {
|
||||
await tagStore.upsertTag(tagName);
|
||||
}
|
||||
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
resourceList: [],
|
||||
|
@ -2,6 +2,7 @@ import { createChannel, createClientFactory, FetchTransport } from "nice-grpc-we
|
||||
import { ActivityServiceDefinition } from "./types/proto/api/v2/activity_service";
|
||||
import { AuthServiceDefinition } from "./types/proto/api/v2/auth_service";
|
||||
import { InboxServiceDefinition } from "./types/proto/api/v2/inbox_service";
|
||||
import { MarkdownServiceDefinition } from "./types/proto/api/v2/markdown_service";
|
||||
import { MemoServiceDefinition } from "./types/proto/api/v2/memo_service";
|
||||
import { ResourceServiceDefinition } from "./types/proto/api/v2/resource_service";
|
||||
import { SystemServiceDefinition } from "./types/proto/api/v2/system_service";
|
||||
@ -35,3 +36,5 @@ export const inboxServiceClient = clientFactory.create(InboxServiceDefinition, c
|
||||
export const activityServiceClient = clientFactory.create(ActivityServiceDefinition, channel);
|
||||
|
||||
export const webhookServiceClient = clientFactory.create(WebhookServiceDefinition, channel);
|
||||
|
||||
export const markdownServiceClient = clientFactory.create(MarkdownServiceDefinition, channel);
|
||||
|
@ -69,16 +69,6 @@
|
||||
code {
|
||||
@apply block;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.codeblock-copy-btn {
|
||||
@apply flex;
|
||||
}
|
||||
}
|
||||
|
||||
.codeblock-copy-btn {
|
||||
@apply btn-normal absolute hidden top-2 right-2 border-solid border-2;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
|
@ -6,7 +6,7 @@ import { Link, useParams } from "react-router-dom";
|
||||
import FloatingNavButton from "@/components/FloatingNavButton";
|
||||
import Icon from "@/components/Icon";
|
||||
import Memo from "@/components/Memo";
|
||||
import MemoContent from "@/components/MemoContent";
|
||||
import MemoContentV1 from "@/components/MemoContentV1";
|
||||
import MemoEditor from "@/components/MemoEditor";
|
||||
import showMemoEditorDialog from "@/components/MemoEditor/MemoEditorDialog";
|
||||
import MemoRelationListView from "@/components/MemoRelationListView";
|
||||
@ -133,7 +133,7 @@ const MemoDetail = () => {
|
||||
<div className="w-full mb-4 flex flex-row justify-start items-center mr-1">
|
||||
<span className="text-gray-400 select-none">{getDateTimeString(memo.displayTs)}</span>
|
||||
</div>
|
||||
<MemoContent content={memo.content} />
|
||||
<MemoContentV1 content={memo.content} />
|
||||
<MemoResourceListView resourceList={memo.resourceList} />
|
||||
<MemoRelationListView memo={memo} relationList={referenceRelations} />
|
||||
<div className="w-full mt-4 flex flex-col sm:flex-row justify-start sm:justify-between sm:items-center gap-2">
|
||||
|
Loading…
x
Reference in New Issue
Block a user