mirror of https://github.com/usememos/memos
refactor: simplify memo metadata components
parent
0e4d2d25ca
commit
f403f8c03c
@ -0,0 +1,9 @@
|
||||
import type { Location } from "@/types/proto/api/v1/memo_service_pb";
|
||||
|
||||
export const getLocationDisplayText = (location: Location): string => {
|
||||
return location.placeholder || `${location.latitude.toFixed(6)}, ${location.longitude.toFixed(6)}`;
|
||||
};
|
||||
|
||||
export const getLocationCoordinatesText = (location: Location, digits = 4): string => {
|
||||
return `${location.latitude.toFixed(digits)}°, ${location.longitude.toFixed(digits)}°`;
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import SectionHeader, { type SectionHeaderTab } from "./SectionHeader";
|
||||
|
||||
interface MetadataSectionProps extends PropsWithChildren {
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
count: number;
|
||||
tabs?: SectionHeaderTab[];
|
||||
className?: string;
|
||||
contentClassName?: string;
|
||||
}
|
||||
|
||||
const MetadataSection = ({ icon, title, count, tabs, className, contentClassName, children }: MetadataSectionProps) => {
|
||||
return (
|
||||
<div className={cn("w-full overflow-hidden rounded-lg border border-border bg-muted/20", className)}>
|
||||
<SectionHeader icon={icon} title={title} count={count} tabs={tabs} />
|
||||
<div className={contentClassName}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MetadataSection;
|
||||
@ -0,0 +1,42 @@
|
||||
import type { MemoRelation } from "@/types/proto/api/v1/memo_service_pb";
|
||||
import { MemoRelation_Type } from "@/types/proto/api/v1/memo_service_pb";
|
||||
|
||||
export type RelationDirection = "referencing" | "referenced";
|
||||
|
||||
export const isReferenceRelation = (relation: MemoRelation): boolean => relation.type === MemoRelation_Type.REFERENCE;
|
||||
|
||||
export const getEditorReferenceRelations = (relations: MemoRelation[], memoName?: string): MemoRelation[] => {
|
||||
return relations.filter(
|
||||
(relation) => isReferenceRelation(relation) && (!memoName || !relation.memo?.name || relation.memo.name === memoName),
|
||||
);
|
||||
};
|
||||
|
||||
export const getRelationBuckets = (relations: MemoRelation[], currentMemoName?: string) => {
|
||||
return relations.reduce(
|
||||
(groups, relation) => {
|
||||
if (!isReferenceRelation(relation)) {
|
||||
return groups;
|
||||
}
|
||||
|
||||
if (relation.memo?.name === currentMemoName && relation.relatedMemo?.name !== currentMemoName) {
|
||||
groups.referencing.push(relation);
|
||||
} else if (relation.memo?.name !== currentMemoName && relation.relatedMemo?.name === currentMemoName) {
|
||||
groups.referenced.push(relation);
|
||||
}
|
||||
|
||||
return groups;
|
||||
},
|
||||
{
|
||||
referencing: [] as MemoRelation[],
|
||||
referenced: [] as MemoRelation[],
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const getRelationMemo = (relation: MemoRelation, direction: RelationDirection) => {
|
||||
return direction === "referencing" ? relation.relatedMemo : relation.memo;
|
||||
};
|
||||
|
||||
export const getRelationMemoName = (relation: MemoRelation, direction: RelationDirection): string => {
|
||||
return getRelationMemo(relation, direction)?.name ?? "";
|
||||
};
|
||||
@ -0,0 +1,61 @@
|
||||
import { create } from "@bufbuild/protobuf";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { memoServiceClient } from "@/connect";
|
||||
import type { MemoRelation } from "@/types/proto/api/v1/memo_service_pb";
|
||||
import { MemoRelation_Memo, MemoRelation_MemoSchema } from "@/types/proto/api/v1/memo_service_pb";
|
||||
|
||||
export const useResolvedRelationMemos = (relations: MemoRelation[]) => {
|
||||
const [resolvedMemos, setResolvedMemos] = useState<Record<string, MemoRelation_Memo>>({});
|
||||
|
||||
const missingMemoNames = useMemo(() => {
|
||||
const names = new Set<string>();
|
||||
|
||||
for (const relation of relations) {
|
||||
const relatedMemo = relation.relatedMemo;
|
||||
if (relatedMemo?.name && !relatedMemo.snippet && !resolvedMemos[relatedMemo.name]) {
|
||||
names.add(relatedMemo.name);
|
||||
}
|
||||
}
|
||||
|
||||
return [...names];
|
||||
}, [relations, resolvedMemos]);
|
||||
|
||||
useEffect(() => {
|
||||
if (missingMemoNames.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
const memos = await Promise.all(
|
||||
missingMemoNames.map(async (name) => {
|
||||
const memo = await memoServiceClient.getMemo({ name });
|
||||
return create(MemoRelation_MemoSchema, { name: memo.name, snippet: memo.snippet });
|
||||
}),
|
||||
);
|
||||
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
setResolvedMemos((prev) => {
|
||||
const next = { ...prev };
|
||||
for (const memo of memos) {
|
||||
next[memo.name] = memo;
|
||||
}
|
||||
return next;
|
||||
});
|
||||
} catch {
|
||||
// Keep existing relation data when snippet hydration fails.
|
||||
}
|
||||
})();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [missingMemoNames]);
|
||||
|
||||
return resolvedMemos;
|
||||
};
|
||||
Loading…
Reference in New Issue