, R
export const TableCell = ({ children, className, node: _node, ...props }: TableCellProps) => {
return (
|
- {children}
+ {children}
|
);
};
diff --git a/web/src/components/MemoContent/markdown/Blockquote.tsx b/web/src/components/MemoContent/markdown/Blockquote.tsx
index c8ed5fc3..531e61f7 100644
--- a/web/src/components/MemoContent/markdown/Blockquote.tsx
+++ b/web/src/components/MemoContent/markdown/Blockquote.tsx
@@ -1,4 +1,5 @@
import { cn } from "@/lib/utils";
+import { NestedMarkdownRenderContext } from "../MarkdownRenderContext";
import type { ReactMarkdownProps } from "./types";
interface BlockquoteProps extends React.BlockquoteHTMLAttributes, ReactMarkdownProps {
@@ -11,7 +12,7 @@ interface BlockquoteProps extends React.BlockquoteHTMLAttributes {
return (
- {children}
+ {children}
);
};
diff --git a/web/src/components/MemoContent/markdown/List.tsx b/web/src/components/MemoContent/markdown/List.tsx
index c34e6a07..3e3d2e95 100644
--- a/web/src/components/MemoContent/markdown/List.tsx
+++ b/web/src/components/MemoContent/markdown/List.tsx
@@ -1,5 +1,6 @@
import { cn } from "@/lib/utils";
import { TASK_LIST_CLASS, TASK_LIST_ITEM_CLASS } from "../constants";
+import { NestedMarkdownRenderContext } from "../MarkdownRenderContext";
import type { ReactMarkdownProps } from "./types";
interface ListProps extends React.HTMLAttributes, ReactMarkdownProps {
@@ -55,14 +56,14 @@ export const ListItem = ({ children, className, node: _node, ...domProps }: List
)}
{...domProps}
>
- {children}
+ {children}
);
}
return (
- {children}
+ {children}
);
};
diff --git a/web/src/components/MemoContent/markdown/Paragraph.tsx b/web/src/components/MemoContent/markdown/Paragraph.tsx
index ecf5e67e..00331598 100644
--- a/web/src/components/MemoContent/markdown/Paragraph.tsx
+++ b/web/src/components/MemoContent/markdown/Paragraph.tsx
@@ -1,17 +1,47 @@
+import type { Element } from "hast";
import { cn } from "@/lib/utils";
+import LinkMetadataCard from "../LinkMetadataCard";
+import { useMarkdownRenderContext } from "../MarkdownRenderContext";
import type { ReactMarkdownProps } from "./types";
interface ParagraphProps extends React.HTMLAttributes, ReactMarkdownProps {
children: React.ReactNode;
}
-/**
- * Paragraph component with compact spacing
- */
-export const Paragraph = ({ children, className, node: _node, ...props }: ParagraphProps) => {
- return (
+function getSingleLinkHref(node?: Element): string | undefined {
+ if (!node || node.tagName !== "p") {
+ return undefined;
+ }
+
+ const meaningfulChildren = node.children.filter((child) => {
+ return !(child.type === "text" && child.value.trim() === "");
+ });
+
+ if (meaningfulChildren.length !== 1) {
+ return undefined;
+ }
+
+ const onlyChild = meaningfulChildren[0];
+ if (onlyChild.type !== "element" || onlyChild.tagName !== "a") {
+ return undefined;
+ }
+
+ const href = onlyChild.properties?.href;
+ return typeof href === "string" ? href : undefined;
+}
+
+export const Paragraph = ({ children, className, node, ...props }: ParagraphProps) => {
+ const { blockDepth } = useMarkdownRenderContext();
+ const href = blockDepth === 0 ? getSingleLinkHref(node) : undefined;
+ const paragraph = (
{children}
);
+
+ if (href) {
+ return ;
+ }
+
+ return paragraph;
};
diff --git a/web/src/hooks/useMemoQueries.ts b/web/src/hooks/useMemoQueries.ts
index 9fda043f..e9299867 100644
--- a/web/src/hooks/useMemoQueries.ts
+++ b/web/src/hooks/useMemoQueries.ts
@@ -15,6 +15,7 @@ export const memoKeys = {
details: () => [...memoKeys.all, "detail"] as const,
detail: (name: string) => [...memoKeys.details(), name] as const,
comments: (name: string) => [...memoKeys.all, "comments", name] as const,
+ linkMetadata: (url: string) => [...memoKeys.all, "linkMetadata", url] as const,
};
type MemoPatch = Partial & Pick;
@@ -149,6 +150,30 @@ export function useMemo(name: string, options?: { enabled?: boolean }) {
});
}
+function isHTTPURL(url: string): boolean {
+ try {
+ const parsed = new URL(url);
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
+ } catch {
+ return false;
+ }
+}
+
+export function useLinkMetadata(url: string, options?: { enabled?: boolean }) {
+ const trimmedUrl = url.trim();
+
+ return useQuery({
+ queryKey: memoKeys.linkMetadata(trimmedUrl),
+ queryFn: async () => {
+ const metadata = await memoServiceClient.getLinkMetadata({ url: trimmedUrl });
+ return metadata;
+ },
+ enabled: (options?.enabled ?? true) && isHTTPURL(trimmedUrl),
+ staleTime: 1000 * 60 * 60 * 24,
+ gcTime: 1000 * 60 * 60 * 24,
+ });
+}
+
export function useCreateMemo() {
const queryClient = useQueryClient();