refactor: Introduce dedicated Document and Asset types and migrate codebase from DocumentLike.

This commit is contained in:
2025-11-26 00:38:01 +01:00
parent 93dcde471f
commit 342714528a
31 changed files with 282 additions and 442 deletions
@@ -6,11 +6,7 @@ import type { FolderId, Identifier } from '../../types/identifiers';
type FolderIdentifier = FolderId | 'root';
type FolderInput = FolderIdentifier | number;
interface DocumentLike {
id?: Identifier | null;
title?: string;
[key: string]: unknown;
}
import type { Document } from '../../types/documents';
type ApplySelectionFn = (
keys: string[],
@@ -28,7 +24,7 @@ interface UseDocumentDragHandlersOptions {
selectedFolderIds: FolderInput[];
applySelection: ApplySelectionFn;
handleEntrySelection: HandleEntrySelectionFn;
documentLookup: Map<Identifier, DocumentLike>;
documentLookup: Map<Identifier, Document>;
setDraggedDocumentIds: (ids: Identifier[] | []) => void;
setDraggedFolderId: (id: FolderIdentifier | null) => void;
documentsViewMode: string;
@@ -62,7 +58,7 @@ const useDocumentDragHandlers = ({
useEffect(() => destroyDragPreview, [destroyDragPreview]);
const createDragPreview = useCallback(
({ documents = [], folders = [] }: { documents?: DocumentLike[]; folders?: FolderIdentifier[] } = {}) => {
({ documents = [], folders = [] }: { documents?: Document[]; folders?: FolderIdentifier[] } = {}) => {
destroyDragPreview();
const docEntries = (documents || []).filter(Boolean);
@@ -202,9 +198,9 @@ const useDocumentDragHandlers = ({
);
const handleDocumentDragStart = useCallback(
(event: DragEvent<HTMLElement>, documentOrId: DocumentLike | Identifier | null) => {
(event: DragEvent<HTMLElement>, documentOrId: Document | Identifier | null) => {
const documentId: Identifier | null = Object(documentOrId) === documentOrId
? (documentOrId as DocumentLike)?.id ?? null
? (documentOrId as Document)?.id ?? null
: (documentOrId as Identifier | null);
if (!documentId) {
return;
@@ -14,6 +14,7 @@ import {
updateDocument,
} from '../../lib/apiClient';
import type { DocumentId, FolderId as FolderIdentifier } from '../../types/identifiers';
import type { Document } from '../../types/documents';
type FolderId = FolderIdentifier | 'root';
type NullableFolderId = FolderId | null;
@@ -21,8 +22,8 @@ type NullableFolderId = FolderId | null;
type StatusLevel = 'success' | 'error' | 'info' | string;
type DocumentCacheMapper = (
doc: DocumentLike | null,
) => DocumentLike | null;
doc: Document | null,
) => Document | null;
type MapDocumentCaches = (mapper: DocumentCacheMapper) => void;
@@ -53,19 +54,8 @@ interface Tag {
[key: string]: unknown;
}
interface DocumentLike {
id?: DocumentId;
folder_id?: NullableFolderId;
folder_path?: string | null;
folder_name?: string | null;
issued_at?: number | null;
title?: string;
tags?: Tag[];
[key: string]: unknown;
}
interface FolderContents {
documents?: DocumentLike[];
documents?: Document[];
subfolders?: Array<{ id?: FolderId;[key: string]: unknown }>;
[key: string]: unknown;
}
@@ -109,12 +99,12 @@ interface FolderDeleteOptions {
interface UseDocumentMutationsArgs {
token?: string | null;
documentLookup: Map<DocumentId, DocumentLike>;
documentLookup: Map<DocumentId, Document>;
folderLabelMap: Map<FolderId, string>;
ensureFolderData: EnsureFolderData;
selectedFolder: FolderId;
setSelectedFolder: Dispatch<SetStateAction<FolderId>>;
setDocuments: Dispatch<SetStateAction<DocumentLike[]>>;
setDocuments: Dispatch<SetStateAction<Document[]>>;
setFolderContents: Dispatch<SetStateAction<Map<FolderId, FolderContents>>>;
setSearchResultIds: Dispatch<SetStateAction<DocumentId[] | null>>;
setSelectedEntries: Dispatch<SetStateAction<string[]>>;
@@ -140,13 +130,13 @@ interface UseDocumentMutationsArgs {
tags: Tag[];
refreshTags: () => Promise<void>;
tagManager: TagManager;
extractDocumentFromResponse?: (payload: unknown) => DocumentLike | null;
ingestDocuments?: (docs: unknown[]) => { canonical: DocumentLike[]; changed: boolean };
extractDocumentFromResponse?: (payload: unknown) => Document | null;
ingestDocuments?: (docs: unknown[]) => { canonical: Document[]; changed: boolean };
}
interface UseDocumentMutationsResult {
moveDocumentsToFolder: (
documentIds: Array<DocumentId | DocumentLike>,
documentIds: Array<DocumentId | Document>,
targetFolderId?: NullableFolderId,
) => Promise<void>;
handleThumbnailRegeneration: (documentId: DocumentId) => Promise<void>;
@@ -155,7 +145,7 @@ interface UseDocumentMutationsResult {
options?: DeleteOptions,
) => Promise<boolean>;
handleDocumentTagAdd: (
document: DocumentLike,
document: Document,
label: string,
extras?: DocumentTagExtras | null,
) => Promise<void>;
@@ -218,7 +208,7 @@ const useDocumentMutations = ({
ingestDocuments,
}: UseDocumentMutationsArgs): UseDocumentMutationsResult => {
const moveDocumentsToFolder = useCallback(
async (documentIds: Array<DocumentId | DocumentLike>, targetFolderId?: NullableFolderId) => {
async (documentIds: Array<DocumentId | Document>, targetFolderId?: NullableFolderId) => {
const uniqueIds = Array.from(
new Set((documentIds || []).map((value) => normalizeDocumentId(value)).filter(Boolean) as DocumentId[]),
);
@@ -241,9 +231,9 @@ const useDocumentMutations = ({
document: doc,
};
})
.filter(Boolean) as Array<{ id: DocumentId; sourceFolderId: NullableFolderId; document: DocumentLike }>;
.filter(Boolean) as Array<{ id: DocumentId; sourceFolderId: NullableFolderId; document: Document }>;
const updatedDocsMap = new Map<DocumentId, DocumentLike>();
const updatedDocsMap = new Map<DocumentId, Document>();
const resolveTargetName = () => {
if (!targetLabel) {
return null;
@@ -257,7 +247,7 @@ const useDocumentMutations = ({
if (!document) {
return;
}
const updated: DocumentLike = {
const updated: Document = {
...document,
folder_id: target,
};
@@ -572,7 +562,7 @@ const useDocumentMutations = ({
);
const handleDocumentTagAdd = useCallback(
async (document: DocumentLike, label: string, extras: DocumentTagExtras | null = null) => {
async (document: Document, label: string, extras: DocumentTagExtras | null = null) => {
const normalizedLabel = tagManager.normalizeLabel(label);
const optionCandidate = extras?.option ?? null;
const input = extras?.input ?? null;
+9 -13
View File
@@ -8,20 +8,16 @@ import {
} from 'react';
import DocumentsManager from '../../documents/DocumentsManager';
import type { DocumentId } from '../../types/identifiers';
interface DocumentLike {
id?: DocumentId;
[key: string]: unknown;
}
import type { Document } from '../../types/documents';
interface FolderContentsEntry {
documents?: DocumentLike[];
documents?: Document[];
[key: string]: unknown;
}
interface UseDocumentsOptions {
setFolderContents: Dispatch<SetStateAction<Map<string, FolderContentsEntry>>>;
fetchDocumentById?: (id: DocumentId) => Promise<DocumentLike | null>;
fetchDocumentById?: (id: DocumentId) => Promise<Document | null>;
}
const useDocuments = ({
@@ -29,16 +25,16 @@ const useDocuments = ({
fetchDocumentById,
}: UseDocumentsOptions) => {
const managerRef = useRef(
new DocumentsManager<DocumentLike>(fetchDocumentById),
new DocumentsManager<Document>(fetchDocumentById),
);
const [documents, setDocumentsState] = useState<DocumentLike[]>([]);
const [documents, setDocumentsState] = useState<Document[]>([]);
useEffect(() => {
managerRef.current.setFetcher(fetchDocumentById);
}, [fetchDocumentById]);
const setDocuments = useCallback(
(value: DocumentLike[] | ((prev: DocumentLike[]) => DocumentLike[])) => {
(value: Document[] | ((prev: Document[]) => Document[])) => {
setDocumentsState((prev) => {
const resolved = typeof value === 'function' ? value(prev) : value;
if (!Array.isArray(resolved)) {
@@ -52,7 +48,7 @@ const useDocuments = ({
);
const mapDocumentCaches = useCallback(
(mapper: (doc: DocumentLike) => DocumentLike | undefined) => {
(mapper: (doc: Document) => Document | undefined) => {
managerRef.current.map(mapper);
const lookupSnapshot = managerRef.current.getSnapshot();
@@ -64,7 +60,7 @@ const useDocuments = ({
const next = prev.map((doc) => {
const id = doc?.id;
if (id != null && lookupSnapshot.has(id as DocumentId)) {
const canonical = lookupSnapshot.get(id as DocumentId) as DocumentLike;
const canonical = lookupSnapshot.get(id as DocumentId) as Document;
if (canonical !== doc) {
changed = true;
}
@@ -95,7 +91,7 @@ const useDocuments = ({
const updatedDocs = docs.map((doc) => {
const id = doc?.id;
if (id != null && lookupSnapshot.has(id as DocumentId)) {
const canonical = lookupSnapshot.get(id as DocumentId) as DocumentLike;
const canonical = lookupSnapshot.get(id as DocumentId) as Document;
if (canonical !== doc) {
docsChanged = true;
}
@@ -62,15 +62,11 @@ const noop = () => { };
type FolderId = FolderIdentifier | 'root';
interface DocumentLike {
id?: DocumentId | null;
title?: string | null;
[key: string]: unknown;
}
import type { Document } from '../../types/documents';
interface FolderContentsEntry {
folder?: { id?: FolderId; name?: string | null } | null;
documents?: DocumentLike[];
documents?: Document[];
subfolders?: Array<{ id?: FolderId; name?: string | null;[key: string]: unknown }>;
__includesDocuments?: boolean;
__sortField?: string | null;
@@ -397,7 +393,7 @@ const useDocumentsWorkspace = ({
() =>
visibleDocumentIds
.map((id) => documentLookup.get(id) || null)
.filter((doc): doc is DocumentLike => Boolean(doc)),
.filter((doc): doc is Document => Boolean(doc)),
[visibleDocumentIds, documentLookup],
);
@@ -9,14 +9,10 @@ import {
createFolderEntryKey,
} from '../../app/entryKey';
import type { FolderId as FolderIdentifier, Identifier } from '../../types/identifiers';
import type { Document } from '../../types/documents';
type FolderId = FolderIdentifier | 'root';
interface DocumentLike {
id?: Identifier | null;
[key: string]: unknown;
}
interface FolderSummary {
id?: FolderId;
name?: string;
@@ -31,7 +27,7 @@ interface FolderSummary {
interface FolderContentsEntry {
folder?: FolderSummary | null;
documents?: DocumentLike[];
documents?: Document[];
subfolders?: FolderSummary[];
__includesDocuments?: boolean;
__sortField?: string | null;
@@ -67,7 +63,7 @@ interface UseFolderTreeOptions {
documentsSortFieldRef: MutableRefObject<string>;
documentsSortDirectionRef: MutableRefObject<string>;
selectionHelpers: SelectionHelpers;
setDocuments: Dispatch<SetStateAction<DocumentLike[]>>;
setDocuments: Dispatch<SetStateAction<Document[]>>;
setFolderContents: Dispatch<SetStateAction<Map<FolderId, FolderContentsEntry>>>;
folderContentsRef: MutableRefObject<Map<FolderId, FolderContentsEntry>>;
}
+5 -8
View File
@@ -1,11 +1,8 @@
import { useEffect, useMemo, useState } from 'react';
import { resolveAssetUrl } from '../asset_manager';
import type { Identifier } from '../types/identifiers';
type DocumentLike = {
id?: Identifier;
[key: string]: unknown;
};
import type { Document } from '../types/documents';
type AssetObject = {
url?: string | null;
@@ -29,7 +26,7 @@ type EnsureAssetUrl = (
options?: { force?: boolean;[key: string]: unknown },
) => Promise<unknown>;
type GetAsset = (document: DocumentLike, assetType: string) => AssetLike | null;
type GetAsset = (document: Document, assetType: string) => AssetLike | null;
type AssetViewLike = {
url: string | null;
@@ -37,14 +34,14 @@ type AssetViewLike = {
};
interface UseAssetNavigatorOptions {
document?: DocumentLike | null;
document?: Document | null;
assetType: string;
ensureAssetUrl?: EnsureAssetUrl | null;
getAsset?: GetAsset;
}
interface AssetNavigatorReturn {
document: DocumentLike | null;
document: Document | null;
documentId: Identifier | null;
asset: AssetLike | null;
assetType: string;