refactor: introduce dedicated identifier types for improved clarity and type safety

This commit is contained in:
2025-11-24 23:47:36 +01:00
parent 22f0c040a2
commit 70e7bda87e
79 changed files with 1567 additions and 1572 deletions
+1 -2
View File
@@ -11,8 +11,7 @@ import { DocumentsHeaderBreadcrumb } from '../documents/panel/DocumentsPanelHead
import { SidebarProvider, useSidebarContext } from '../sidebar/SidebarContext';
import { PanelManagerProvider, usePanelManager } from './PanelManagerContext';
import Sidebar from '../sidebar/Sidebar';
type Identifier = string | number;
import type { Identifier } from '../types/identifiers';
type EnsureAssetUrl = (
docId: Identifier,
+2 -2
View File
@@ -28,7 +28,7 @@ interface StatusMessage {
}
interface TenantOption {
id?: string | number | null;
id?: string | null;
name?: string | null;
}
@@ -321,7 +321,7 @@ const LoginRoute: React.FC = () => {
const payload: {
magic_token: string;
username?: string;
preferred_tenant_id?: string | number;
preferred_tenant_id?: string;
} = {
magic_token: magicToken,
};
+3 -3
View File
@@ -15,12 +15,12 @@ import PanelHeader from '../ui/PanelHeader';
type UploadStatus = 'pending' | 'uploading' | 'success' | 'duplicate' | 'error' | (string & {});
interface UploadQueueItem {
id: string | number;
id: string;
name: string;
status: UploadStatus;
error?: string | null;
document?: { id?: string | number; title?: string };
conflictDocumentId?: string | number;
document?: { id?: string; title?: string };
conflictDocumentId?: string;
}
interface UploadQueueOverlayProps {
+4 -2
View File
@@ -1,13 +1,15 @@
import type { DocumentId, FolderId } from '../types/identifiers';
// Entry key utilities for workspace selection
// Entry keys are strings in the format "document:id" or "folder:id"
const ENTRY_KEY_SEPARATOR = ':';
// Create entry key strings
export const createDocumentEntryKey = (documentId: string | number): string =>
export const createDocumentEntryKey = (documentId: DocumentId): string =>
`document${ENTRY_KEY_SEPARATOR}${documentId}`;
export const createFolderEntryKey = (folderId: string | number): string =>
export const createFolderEntryKey = (folderId: FolderId): string =>
`folder${ENTRY_KEY_SEPARATOR}${folderId}`;
// Type guards for entry key strings
+5 -5
View File
@@ -1,19 +1,19 @@
import { useCallback, useEffect, useRef, useState } from 'react';
export interface DetailDocument {
id?: string | number;
id?: string;
[key: string]: unknown;
}
interface UseDetailPanelOptions {
documentLookup: Map<string | number, DetailDocument>;
documentLookup: Map<string, DetailDocument>;
orderedSelectedDocuments: DetailDocument[];
}
interface OpenDetailPanelArgs {
documentId?: string | number;
documentId?: string;
document?: DetailDocument | null;
documentIds?: Array<string | number>;
documentIds?: Array<string>;
documents?: DetailDocument[];
}
@@ -22,7 +22,7 @@ export const useDetailPanel = ({
orderedSelectedDocuments,
}: UseDetailPanelOptions) => {
const [detailPanelOpen, setDetailPanelOpen] = useState(false);
const [detailPanelDocId, setDetailPanelDocId] = useState<string | number | null>(null);
const [detailPanelDocId, setDetailPanelDocId] = useState<string | null>(null);
const [detailPanelDocument, setDetailPanelDocument] = useState<DetailDocument | null>(null);
const latestOrderedDocsRef = useRef<DetailDocument[]>([]);
+1 -1
View File
@@ -5,8 +5,8 @@ import type {
SetStateAction,
} from 'react';
import { fetchDocument } from '../lib/apiClient';
import type { DocumentId } from '../types/identifiers';
type DocumentId = string | number;
type FolderId = DocumentId | 'root';
type DocumentLike = {
+6 -7
View File
@@ -6,8 +6,7 @@ import {
isFolderEntry,
getEntryId,
} from './entryKey';
type DocumentId = string | number;
import type { DocumentId } from '../types/identifiers';
interface SelectionEventLike {
shiftKey?: boolean;
@@ -21,7 +20,7 @@ interface UseDocumentSelectionOptions {
}
interface ApplySelectionOptions {
anchor?: string;
anchor: string | null;
interactedKeys?: string[];
}
@@ -35,8 +34,8 @@ export const useDocumentSelection = ({
const selectionOrderRef = useRef<string[]>(initialEntries);
const selectionAnchorRef = useRef<string | null>(null);
const selectionInitializedRef = useRef(false);
const [focusedDocumentId, setFocusedDocumentId] = useState<DocumentId | undefined>(undefined);
const [focusedRowKey, setFocusedRowKey] = useState<string | undefined>(undefined);
const [focusedDocumentId, setFocusedDocumentId] = useState<DocumentId | null>(null);
const [focusedRowKey, setFocusedRowKey] = useState<string | null>(null);
const visibleRowKeySetRef = useRef<Set<string>>(new Set());
const navigableRowKeysRef = useRef<string[]>([]);
@@ -90,7 +89,7 @@ export const useDocumentSelection = ({
const applySelection = useCallback(
(
rowKeys: Array<string | null>,
{ anchor, interactedKeys = [] }: ApplySelectionOptions = {},
{ anchor = null, interactedKeys = [] }: ApplySelectionOptions = { anchor: null, interactedKeys: [] },
) => {
const visibleRowKeySet = visibleRowKeySetRef.current;
const unique: string[] = [];
@@ -117,7 +116,7 @@ export const useDocumentSelection = ({
}
});
let resolvedAnchor = anchor;
let resolvedAnchor: string | null = anchor ?? null;
if (resolvedAnchor && !unique.includes(resolvedAnchor)) {
resolvedAnchor = null;
}
+1 -2
View File
@@ -2,8 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import type { Dispatch, SetStateAction } from 'react';
import { TAG_FILTER_UNTAGGED } from './workspaceUtils';
import { listDocuments } from '../lib/apiClient';
type Identifier = string | number;
import type { Identifier } from '../types/identifiers';
type DocumentLike = { id?: Identifier } & Record<string, unknown>;
+2 -2
View File
@@ -9,13 +9,13 @@ const TAGS_MODAL = 'tags';
const CORRESPONDENTS_MODAL = 'correspondents';
interface TagRecord {
id?: string | number;
id?: string;
label?: string;
[key: string]: unknown;
}
interface CorrespondentRecord {
id?: string | number;
id?: string;
name?: string;
[key: string]: unknown;
}
+4 -4
View File
@@ -10,8 +10,8 @@ interface SelectionEntry {
}
interface WorkspaceSelectionOptions {
onDocumentActivate?: (id: string | number) => void;
onInspectFolder?: (id: string | number) => void;
onDocumentActivate?: (id: string) => void;
onInspectFolder?: (id: string) => void;
}
const identity = <T,>(value: T) => value;
@@ -71,7 +71,7 @@ export const useWorkspaceSelection = ({
);
const inspectDocument = useCallback(
(documentId?: string | number) => {
(documentId?: string) => {
if (!documentId) return;
onDocumentActivate(documentId);
},
@@ -79,7 +79,7 @@ export const useWorkspaceSelection = ({
);
const inspectFolder = useCallback(
(folderId?: string | number) => {
(folderId?: string) => {
if (!folderId) return;
onInspectFolder(folderId);
},
+1 -2
View File
@@ -5,8 +5,7 @@ import DocumentsPanel from '../documents/panel/DocumentsPanel';
import DocumentViewerPanel from '../preview/DocumentViewerPanel';
import { usePanelManager } from './PanelManagerContext';
import { FolderManagerProvider } from '../folders/FolderManagerContext';
type Identifier = string | number;
import type { Identifier } from '../types/identifiers';
type EnsureAssetUrl = (docId: Identifier, asset: unknown, options?: Record<string, unknown>) => Promise<unknown> | void;
type EnsurePreviewData = (docId: Identifier, options?: Record<string, unknown>) => Promise<unknown>;
+1 -1
View File
@@ -1,4 +1,4 @@
export type Identifier = string | number;
import type { Identifier } from './types/identifiers';
type Nullable<T> = T | null;
@@ -7,7 +7,7 @@ interface CorrespondentUsage {
}
export interface CorrespondentEntry {
id?: string | number;
id?: string;
name?: string;
usage?: CorrespondentUsage;
[key: string]: unknown;
@@ -17,8 +17,8 @@ export interface CorrespondentsPanelProps {
correspondents?: CorrespondentEntry[];
onRefresh?: () => void | Promise<void>;
onCreate: (payload: { name: string }) => Promise<CorrespondentEntry | void>;
onUpdate: (id: string | number, payload: { name: string }) => Promise<void>;
onDelete: (id: string | number) => Promise<void>;
onUpdate: (id: string, payload: { name: string }) => Promise<void>;
onDelete: (id: string) => Promise<void>;
onNotify?: (message: string, variant?: string) => void;
}
@@ -30,12 +30,12 @@ function CorrespondentsPanel({
onDelete,
onNotify,
}: CorrespondentsPanelProps) {
const [editingId, setEditingId] = useState<string | number | null>(null);
const [editingId, setEditingId] = useState<string | null>(null);
const [draftName, setDraftName] = useState('');
const [createName, setCreateName] = useState('');
const [saving, setSaving] = useState(false);
const [creating, setCreating] = useState(false);
const [deletingId, setDeletingId] = useState<string | number | null>(null);
const [deletingId, setDeletingId] = useState<string | null>(null);
const startEdit = useCallback((correspondent: CorrespondentEntry) => {
setEditingId(correspondent.id);
+9 -8
View File
@@ -3,17 +3,18 @@ import DesktopPreviewCard from './DesktopPreviewCard';
import { resolveCorrespondents } from '../documents/correspondents';
import { getTagColorStyle } from '../utils/colors';
import { preventAll } from './events';
import type { DocumentId } from '../types/identifiers';
type DocumentLike = {
id?: string | number;
id?: string;
title?: string;
tags?: Array<{ id?: string | number; label?: string; color?: string | null }>;
tags?: Array<{ id?: string; label?: string; color?: string | null }>;
[key: string]: unknown;
};
interface PendingRemovalTag {
docId?: string | number;
tagId?: string | number;
docId?: string;
tagId?: string;
}
interface DesktopDocumentCardProps {
@@ -30,10 +31,10 @@ interface DesktopDocumentCardProps {
getDocumentAsset?: (...args: any[]) => unknown;
handleNavigatorSnapshot?: (...args: any[]) => void;
cardPointerHandlers?: React.HTMLAttributes<HTMLDivElement>;
onDocumentActivate?: (id: string | number) => void;
onTagDragEnter?: (event: React.DragEvent<HTMLDivElement>, docId: string | number) => void;
onTagDragOver?: (event: React.DragEvent<HTMLDivElement>, docId: string | number) => void;
onTagDragLeave?: (event: React.DragEvent<HTMLDivElement>, docId: string | number) => void;
onDocumentActivate?: (id: string) => void;
onTagDragEnter?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void;
onTagDragOver?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void;
onTagDragLeave?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void;
onTagDrop?: (event: React.DragEvent<HTMLDivElement>, doc: DocumentLike) => void;
onDocTagPointerDown?: (event: React.PointerEvent<HTMLElement>, doc: DocumentLike, tag: any) => void;
onDocTagDragStart?: (event: React.DragEvent<HTMLElement>, doc: DocumentLike, tag: any) => void;
+2 -3
View File
@@ -1,8 +1,7 @@
import { useEffect } from 'react';
import type { JSX } from 'react';
import { useAssetNavigator } from '../hooks/useAssetNavigator';
type Identifier = string | number;
import type { Identifier } from '../types/identifiers';
interface DocumentLike {
id?: Identifier;
@@ -21,7 +20,7 @@ interface AssetLike {
type EnsureAssetUrl = (
documentId: Identifier,
asset: AssetLike,
options?: { force?: boolean; [key: string]: unknown },
options?: { force?: boolean;[key: string]: unknown },
) => Promise<unknown>;
type GetDocumentAsset = (document: DocumentLike | null, assetType: string) => AssetLike | null;
+4 -5
View File
@@ -29,8 +29,7 @@ import '../styles/workspace/workspace-layout.css';
import '../styles/workspace/workspace-items.css';
import '../styles/workspace/workspace-cards.css';
import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext';
type Identifier = string | number;
import type { DocumentId, Identifier } from '../types/identifiers';
type TagLike = { id?: Identifier | null; label?: string; color?: string | null } | null;
type DocumentLinkLike = { url?: string | null; mimeType?: string | null };
@@ -79,7 +78,7 @@ interface DocumentSizeInfo {
}
interface PreviewMetadataEntry {
docId: string;
docId: DocumentId;
width: number;
height: number;
}
@@ -225,7 +224,7 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
const syntheticEvent = event || ({
metaKey: true,
ctrlKey: true,
preventDefault: () => {},
preventDefault: () => { },
} as unknown as PointerEvent);
docIds.forEach((id) => {
const key = getDocRowKey(id);
@@ -354,7 +353,7 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
engine.recalcVisibleDocIds();
}, [engine]);
const setDraggingId = useCallback((value: string | number | null) => {
const setDraggingId = useCallback((value: string | null) => {
engine.setDraggingId(value);
}, [engine]);
+30 -6
View File
@@ -1,3 +1,6 @@
import type { DocumentId } from '../types/identifiers';
type TenantId = import('../types/identifiers').TenantId;
const DB_NAME = 'papercrate_desk';
const DB_VERSION = 1;
const LAYOUT_STORE = 'layouts';
@@ -107,9 +110,9 @@ const withStore = async <T>(mode: TransactionMode, handler: (store: IDBObjectSto
};
interface LayoutRecord {
tenantId: string | number;
viewId: string | number;
documentId: string | number;
tenantId: TenantId;
viewId: string;
documentId: DocumentId;
centerX?: number;
centerY?: number;
rotation?: number;
@@ -117,7 +120,13 @@ interface LayoutRecord {
updatedAt?: number;
}
export const fetchLayoutRecords = async ({ tenantId, viewId }: { tenantId?: string | number; viewId?: string | number }): Promise<LayoutRecord[]> => {
export const fetchLayoutRecords = async ({
tenantId,
viewId,
}: {
tenantId?: TenantId;
viewId?: string;
}): Promise<LayoutRecord[]> => {
if (!tenantId || !viewId) {
return [];
}
@@ -133,7 +142,22 @@ export const fetchLayoutRecords = async ({ tenantId, viewId }: { tenantId?: stri
}
};
export const upsertLayoutRecords = async ({ tenantId, viewId, entries }: { tenantId?: string | number; viewId?: string | number; entries?: Array<{ documentId?: string | number; centerX?: number; centerY?: number; rotation?: number; zIndex?: number; updatedAt?: number }> }) => {
export const upsertLayoutRecords = async ({
tenantId,
viewId,
entries,
}: {
tenantId?: TenantId;
viewId?: string;
entries?: Array<{
documentId?: DocumentId;
centerX?: number;
centerY?: number;
rotation?: number;
zIndex?: number;
updatedAt?: number;
}>;
}) => {
if (!tenantId || !viewId || !Array.isArray(entries) || !entries.length) {
return;
}
@@ -162,7 +186,7 @@ export const upsertLayoutRecords = async ({ tenantId, viewId, entries }: { tenan
}
};
export const deleteTenantLayouts = async (tenantId?: string | number) => {
export const deleteTenantLayouts = async (tenantId?: TenantId) => {
if (!tenantId) {
return;
}
@@ -1,23 +1,24 @@
import { useEffect, useState } from 'react';
import type { DocumentId } from '../../types/identifiers';
interface DocumentLike {
id?: string | number;
id?: string;
current_version?: unknown;
tags?: unknown;
}
interface AssetLike {
id?: string | number;
id?: string;
[key: string]: unknown;
}
interface PreviewMetadataEntry {
docId: string;
docId: DocumentId;
width: number;
height: number;
}
type GetDocumentAsset = (doc: DocumentLike, type: string) => AssetLike | null;
type EnsureAssetUrl = (docId: string | number, asset: AssetLike, options?: { force?: boolean }) => Promise<AssetLike | null>;
type EnsureAssetUrl = (docId: DocumentId, asset: AssetLike, options?: { force?: boolean }) => Promise<AssetLike | null>;
const usePreviewMetadata = (
documents: DocumentLike[] | null,
+4 -3
View File
@@ -1,4 +1,5 @@
import { safeInvoke } from '../events';
import type { DocumentId } from '../../types/identifiers';
export const CLICK_ACTIONS = {
selectSingle: 'selectSingle',
@@ -25,9 +26,9 @@ export const LONG_PRESS_DURATION_MS = 450;
export const withinThreshold = (dx: number, dy: number, thresholdSquared: number): boolean => (dx * dx + dy * dy) <= thresholdSquared;
interface PointerIntentArgs {
doc: { id: string | number };
doc: { id: string };
entryDescriptor: unknown;
selectedDocumentIds: Array<string | number>;
selectedDocumentIds: Array<string>;
metaKey: boolean;
pointerButton?: number;
pointerType?: string;
@@ -35,7 +36,7 @@ interface PointerIntentArgs {
}
export interface PointerIntent {
docId: string | number;
docId: DocumentId;
entryDescriptor: unknown;
pointerType?: string;
pointerButton?: number;
+4 -5
View File
@@ -19,8 +19,7 @@ import {
applyDomTransform,
type WorkspaceEngine,
} from './workspaceEngine';
type Identifier = string | number;
import type { DocumentId, Identifier } from '../types/identifiers';
interface DocumentLike {
id?: Identifier | null;
@@ -121,7 +120,7 @@ interface UseDocumentDragOptions {
type EngineInertiaState = Parameters<WorkspaceEngine['startInertiaAnimation']>[1];
interface DragStateInternal extends EngineDragState {
docId: string;
docId: DocumentId;
docKey: string;
pointerId: number;
originCenterX: number;
@@ -216,7 +215,7 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
onDocumentStackSelect,
selectedDocumentIds = [],
markLayoutDirty,
} = options;
} = options;
const fallbackContainerRef = useRef<HTMLElement | null>(null);
const containerRef = providedContainerRef ?? fallbackContainerRef;
@@ -237,7 +236,7 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
const tapHandler = usePointerTap<DragTapMetadata>({
delay: 220,
onSingle: () => {},
onSingle: () => { },
onDouble: ({ data, event }) => {
if (!data?.docId) {
return;
+18 -47
View File
@@ -1,7 +1,7 @@
import { clamp, formatTransform } from '../utils/math';
import { fetchLayoutRecords, upsertLayoutRecords } from './db';
type DocumentId = string;
import type { DocumentId } from '../types/identifiers';
type TenantId = import('../types/identifiers').TenantId;
interface Point {
x: number;
@@ -63,7 +63,7 @@ interface BaseMetrics {
}
interface DragGroupItem {
docId?: string | number | null;
docId?: string | null;
width: number;
height: number;
currentCenterX?: number;
@@ -80,7 +80,7 @@ interface DragState {
}
interface InertiaSimulationState {
docId: string;
docId: DocumentId;
restRotation: number;
rotation: number;
dynamicRotation: number;
@@ -106,7 +106,7 @@ interface WorkspaceSnapshot {
type WorkspaceSubscriber = () => void;
type DeskDocument = { id?: string | number | null } & Record<string, unknown>;
type DeskDocument = { id?: string | null } & Record<string, unknown>;
type EnsureDocumentSize = (doc: DeskDocument) => DocumentSize | null;
@@ -226,7 +226,7 @@ export const clampCardDimensions = (width: number, height: number): CardDimensio
};
};
export const computeFallbackCardSize = (docId: string | number): CardDimensions | null => {
export const computeFallbackCardSize = (docId: DocumentId): CardDimensions | null => {
const baseSeed = seededRandom(`${docId}:fallback-size`);
const aspectSeed = seededRandom(`${docId}:fallback-aspect`);
@@ -259,7 +259,7 @@ function randomRangeFromSeed(seedKey: string, min: number, max: number): number
return min + seed * span;
}
function buildKey(docId: string | number, suffix: string): string {
function buildKey(docId: DocumentId, suffix: string): string {
return `${docId}::${suffix}`;
}
@@ -525,63 +525,34 @@ const generateInitialLayout = (
export class WorkspaceEngine {
allowLayoutPersistence: boolean;
tenantId: string | null;
tenantId: TenantId | null;
viewId: string | null;
layout: Map<DocumentId, LayoutEntry>;
layoutSnapshot: Map<DocumentId, LayoutEntry>;
persistedLayout: Map<DocumentId, LayoutEntry>;
layoutDirty: boolean;
zCounter: number;
canvasSize: { width: number; height: number };
visibleDocIds: Set<DocumentId>;
draggingId: string | null;
tagDropTargetId: string | null;
pendingTagDocId: string | null;
pendingRemovalTag: unknown;
dragInProgress: boolean;
activeDragDocIds: Set<DocumentId>;
pendingSnapshotSync: boolean;
pendingPersistSync: boolean;
persistDebounceId: number | null;
items: DeskDocument[];
documentLookup: Map<string, DeskDocument>;
ensureDocumentSize: EnsureDocumentSize;
resolveBaseMetrics: ResolveBaseMetrics;
snapshotCache: WorkspaceSnapshot;
subscribers: Set<WorkspaceSubscriber>;
loadingPersisted: boolean;
pendingPersistence: unknown;
itemRefs: ItemRefs;
inertiaAnimations: Map<string, InertiaSimulationState>;
initialLoadDone: boolean;
constructor({
@@ -716,7 +687,7 @@ export class WorkspaceEngine {
this.emit();
}
setDraggingId(docId: string | number | null): void {
setDraggingId(docId: DocumentId | null): void {
const normalized = docId != null ? String(docId) : null;
if (this.draggingId === normalized) {
return;
@@ -725,7 +696,7 @@ export class WorkspaceEngine {
this.emit();
}
beginDrag(docIds: Array<string | number | null> = []): void {
beginDrag(docIds: Array<string | null> = []): void {
this.dragInProgress = true;
if (Array.isArray(docIds)) {
this.activeDragDocIds = new Set(docIds.map((id) => (id != null ? String(id) : null)).filter(Boolean));
@@ -749,7 +720,7 @@ export class WorkspaceEngine {
}
}
setTagDropTargetId(docId: string | number | null): void {
setTagDropTargetId(docId: DocumentId | null): void {
const normalized = docId != null ? String(docId) : null;
if (this.tagDropTargetId === normalized) {
return;
@@ -758,7 +729,7 @@ export class WorkspaceEngine {
this.emit();
}
setPendingTagDocId(docId: string | number | null): void {
setPendingTagDocId(docId: DocumentId | null): void {
const normalized = docId != null ? String(docId) : null;
if (this.pendingTagDocId === normalized) {
return;
@@ -779,7 +750,7 @@ export class WorkspaceEngine {
this.layoutDirty = true;
}
getLayout(docId: string | number | null): LayoutEntry | null {
getLayout(docId: DocumentId | null): LayoutEntry | null {
if (docId == null) {
return null;
}
@@ -788,7 +759,7 @@ export class WorkspaceEngine {
}
updateLayoutEntry(
docId: string | number | null,
docId: DocumentId | null,
updater: (previous: LayoutEntry | null) => LayoutEntry | null,
): void {
if (docId == null) {
@@ -807,7 +778,7 @@ export class WorkspaceEngine {
this.persistLayoutSnapshot();
}
bringToFront(docId: string | number | null): void {
bringToFront(docId: DocumentId | null): void {
if (docId == null) {
return;
}
@@ -825,7 +796,7 @@ export class WorkspaceEngine {
}
applyTransform(
docId: string | number | null,
docId: DocumentId | null,
centerX: number,
centerY: number,
width: number,
@@ -896,7 +867,7 @@ export class WorkspaceEngine {
this.persistLayoutSnapshot();
}
cancelInertiaAnimation(docId: string | number | null): void {
cancelInertiaAnimation(docId: DocumentId | null): void {
const key = docId != null ? String(docId) : null;
if (!key) {
return;
@@ -995,7 +966,7 @@ export class WorkspaceEngine {
return isSettled;
}
startInertiaAnimation(docId: string | number | null, baseState: InertiaSimulationState): void {
startInertiaAnimation(docId: DocumentId | null, baseState: InertiaSimulationState): void {
const raf = window.requestAnimationFrame;
if (!raf) {
return;
+2 -2
View File
@@ -4,7 +4,7 @@ import { clamp } from '../utils/math';
import PdfViewer from '../preview/PdfViewer';
type DocumentLike = {
id?: string | number;
id?: string;
title?: string;
mime_type?: string | null;
[key: string]: unknown;
@@ -43,7 +43,7 @@ const determineDisplayKind = (entry?: DocumentLink | null): DisplayKind => {
return 'image';
};
const noop = () => {};
const noop = () => { };
const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
open = false,
+1 -2
View File
@@ -6,8 +6,7 @@ import { DEFAULT_FOLDER_NAME } from '../app/workspaceUtils';
import { getEntryId, isDocumentEntry } from '../app/entryKey';
import type { DocumentInfoPanelProps } from '../documents/DocumentInfoPanel';
import type { EnsureAssetUrl, GetDocumentAsset } from '../utils/ocr';
type Identifier = string | number;
import type { Identifier } from '../types/identifiers';
interface DocumentLike {
id?: Identifier;
@@ -3,15 +3,15 @@ import React from 'react';
const NBSP = String.fromCharCode(160);
export interface CorrespondentLinkEntry {
id?: string | number | null;
id?: string | null;
name?: string | null;
key?: string;
}
interface CorrespondentLinksProps {
correspondents?: CorrespondentLinkEntry[];
activeCorrespondentIdSet?: Set<string | number>;
onCorrespondentClick?: (id: string | number) => void;
activeCorrespondentIdSet?: Set<string>;
onCorrespondentClick?: (id: string) => void;
}
const CorrespondentLinks: React.FC<CorrespondentLinksProps> = ({
@@ -23,7 +23,7 @@ const CorrespondentLinks: React.FC<CorrespondentLinksProps> = ({
return null;
}
const activeSet = activeCorrespondentIdSet || new Set<string | number>();
const activeSet = activeCorrespondentIdSet || new Set<string>();
const handleClick = (event: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLButtonElement>, correspondent: CorrespondentLinkEntry) => {
if (!onCorrespondentClick || correspondent.id == null) {
return;
+1 -1
View File
@@ -36,7 +36,7 @@ export interface DocumentInfoPanelProps {
activeTab?: string;
onTabChange?: (tabId: string) => void;
defaultTabId?: string;
resetKey?: string | number | null;
resetKey?: string | null;
classNamePrefix?: string;
hideTabNavWhenSingle?: boolean;
summaryPlacement?: 'inline' | 'tabs';
@@ -14,11 +14,10 @@ import {
import { describeDocumentSummary, type DocumentSummaryRow } from './documentSummary';
import { useFolderManager } from '../folders/FolderManagerContext';
type Identifier = string | number;
import type { FolderId, Identifier, TagId } from '../types/identifiers';
interface TagEntry {
id?: Identifier;
id?: TagId;
label?: string;
color?: string | null;
}
@@ -33,7 +32,7 @@ interface DocumentLike {
id?: Identifier;
title?: string;
issued_at?: string | null;
folder_id?: string | null;
folder_id?: FolderId | null;
current_version?: { version_number?: number } | null;
tags?: TagEntry[];
correspondents?: CorrespondentEntry[];
@@ -64,17 +63,17 @@ interface CorrespondentSectionProps {
export interface DocumentSummarySectionProps {
document?: DocumentLike | null;
tagLookupById?: Map<Identifier, TagEntry>;
tagLookupById?: Map<TagId, TagEntry>;
tagOptions?: SelectionAssignmentMenuItem[];
onTagAdd?: (doc: DocumentLike, value: string, context?: { option?: unknown }) => void;
onTagRemove?: (docId: Identifier | undefined, tagId: Identifier | undefined) => void;
onTagRemove?: (docId: Identifier | undefined, tagId: TagId | undefined) => void;
correspondents?: CorrespondentEntry[];
correspondentOptions?: SelectionAssignmentMenuItem[];
onCorrespondentAdd?: (payload: { document: DocumentLike; name: string; option?: unknown }) => void;
onCorrespondentRemove?: (payload: { documentId: Identifier | undefined; correspondentId: Identifier | undefined }) => void;
onUpdateTitle?: (docId: Identifier | undefined, title: string) => Promise<boolean> | boolean;
onUpdateIssued?: (docId: Identifier | undefined, timestamp: number | null) => Promise<boolean> | boolean;
onFolderNavigate?: (folderId: string | null) => void;
onFolderNavigate?: (folderId: FolderId | null) => void;
layout?: 'default' | 'compact';
}
@@ -17,7 +17,7 @@ const DEFAULT_THUMBNAIL_SIZE = 48;
// Detect when an element becomes visible within a scroll container so we can delay loading.
const useLazyVisibility = (
rootRef: MutableRefObject<Element | null> | null,
resetKey?: string | number | null,
resetKey?: string | null,
) => {
const targetRef = useRef<HTMLDivElement | null>(null);
const [isVisible, setIsVisible] = useState(false);
+1 -2
View File
@@ -8,8 +8,7 @@ import { resolveCorrespondents } from './correspondents';
import { writeTagTransferData } from './tagTransfer';
import useInlineRename from './useInlineRename';
import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext';
export type Identifier = string | number;
import type { Identifier } from '../types/identifiers';
export interface FolderLike {
id?: Identifier | 'root';
+1 -2
View File
@@ -9,8 +9,7 @@ import { resolveCorrespondents } from './correspondents';
import { writeTagTransferData } from './tagTransfer';
import useInlineRename from './useInlineRename';
import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext';
export type Identifier = string | number;
import type { Identifier } from '../types/identifiers';
export interface FolderLike {
id?: Identifier | 'root';
+1 -2
View File
@@ -1,6 +1,5 @@
import { shallowEqual } from 'react-redux';
type DocumentId = string | number;
import type { DocumentId } from '../types/identifiers';
export type ManagedDocument = { id?: DocumentId | null } & Record<string, unknown>;
@@ -5,18 +5,18 @@ import { CheckIcon, CircleDashedCheckIcon, PlusIcon } from '../ui/icons';
export type AssignmentState = 'all' | 'partial' | 'none';
export interface SelectionAssignmentMenuItem {
id?: string | number;
id?: string;
label?: string;
state?: AssignmentState;
count?: number | null;
total?: number | null;
color?: string | null;
value?: string | number;
value?: string;
payload?: unknown;
}
export interface NormalizedSelectionAssignmentItem {
id: string | number;
id: string;
label: string;
state: AssignmentState;
count: number | null;
@@ -105,7 +105,7 @@ const SelectionAssignmentMenu: React.FC<SelectionAssignmentMenuProps> = ({
const inputRef = useRef<HTMLInputElement | null>(null);
const [query, setQuery] = useState('');
const [pending, setPending] = useState(false);
const [sortSnapshot, setSortSnapshot] = useState<Array<string | number> | null>(null);
const [sortSnapshot, setSortSnapshot] = useState<Array<string> | null>(null);
const {
isOpen,
@@ -175,10 +175,10 @@ const SelectionAssignmentMenu: React.FC<SelectionAssignmentMenuProps> = ({
const orderedItems = useMemo(() => {
if (freezeSortOnOpen && sortSnapshot && sortByState) {
const itemMap = new Map<string | number, NormalizedSelectionAssignmentItem>(
const itemMap = new Map<string, NormalizedSelectionAssignmentItem>(
sortedByStateItems.map((item) => [item.id, item]),
);
const seen = new Set<string | number>();
const seen = new Set<string>();
const fromSnapshot = sortSnapshot
.map((id) => {
const entry = itemMap.get(id);
@@ -12,10 +12,10 @@ import SelectionAssignmentMenu, { SelectionAssignmentMenuItem } from './Selectio
import SelectionSummary from './SelectionSummary';
import { useAppState } from '../app/appState';
import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext';
import type { DocumentId } from '../types/identifiers';
const ROOT_FOLDER_LABEL = 'Documents';
type DocumentId = string | number;
type NullableDocumentId = DocumentId | null;
type SelectedIdList = NullableDocumentId[] | null;
@@ -145,7 +145,7 @@ const buildTagAssignments = (
return [];
}
const map = new Map<string | number, {
const map = new Map<string, {
id?: DocumentId;
label: string;
color: string | null;
@@ -209,7 +209,7 @@ const buildCorrespondentAssignments = (
return [];
}
const map = new Map<string | number, {
const map = new Map<string, {
id?: DocumentId;
label: string;
count: number;
@@ -491,7 +491,7 @@ const SelectionFloatingActions: React.FC<SelectionFloatingActionsProps> = ({
const value = isRecord(candidate)
? (candidate?.id ?? candidate?.value ?? null)
: candidate;
if (!value && value !== 0) {
if (!value) {
return;
}
await onMoveDocumentsToFolder(documentIdList, value as DocumentId);
@@ -1,10 +1,9 @@
import React, { createContext, useContext } from 'react';
type Identifier = string | number;
import type { Identifier } from '../../types/identifiers';
export interface DocumentsFilterValue {
query: string;
searchResultIds: Array<string | number> | null;
searchResultIds: Array<string> | null;
searchLoading: boolean;
includeDescendants: boolean;
activeTagIds: Identifier[];
+4 -4
View File
@@ -1,5 +1,5 @@
export interface CorrespondentReference {
id?: string | number | null;
id?: string | null;
name?: string | null;
key?: string;
}
@@ -9,9 +9,9 @@ export interface DocumentLike {
}
export interface ResolvedCorrespondent {
id?: string | number | null;
id?: string | null;
name: string;
key: string | number;
key: string;
}
export const resolveCorrespondents = (doc?: DocumentLike | null): ResolvedCorrespondent[] => {
@@ -19,7 +19,7 @@ export const resolveCorrespondents = (doc?: DocumentLike | null): ResolvedCorres
return [];
}
const seen = new Set<string | number>();
const seen = new Set<string>();
const results: ResolvedCorrespondent[] = [];
doc.correspondents.forEach((entry = {}, index) => {
@@ -1,7 +1,6 @@
import { useCallback } from 'react';
import { assignCorrespondentsBulk } from '../../lib/apiClient';
export type Identifier = string | number;
import type { Identifier } from '../../types/identifiers';
type BulkAssignmentResponse = {
assigned?: number;
@@ -116,7 +115,7 @@ const useBulkDocumentActions = ({
setStatusMessage,
updateDocumentCaches,
],
);
);
const handleBulkCorrespondentRemove = useCallback(
async ({ assignments = [], documentIds }: { assignments?: CorrespondentAssignment[]; documentIds?: Identifier[] }) => {
@@ -174,7 +173,7 @@ const useBulkDocumentActions = ({
}
},
[resolveTargetDocumentIds, setStatusMessage, updateDocumentCaches],
);
);
const handleDeleteSelection = useCallback(async () => {
const docIds = Array.isArray(selectedDocumentIds) ? selectedDocumentIds : [];
@@ -1,7 +1,6 @@
import { useMemo } from 'react';
import type { WorkspaceSelectionValue } from '../../app/WorkspaceSelectionContext';
type Identifier = string | number;
import type { Identifier } from '../../types/identifiers';
interface DocumentLinkLike {
url?: string | null;
@@ -1,20 +1,21 @@
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { createDocumentEntryKey, createFolderEntryKey, isFolderEntry } from '../../app/entryKey';
import type { DocumentId, FolderId } from '../../types/identifiers';
interface FolderEntry {
id: string | number;
id: FolderId;
[key: string]: unknown;
}
interface DocumentEntry {
id: string | number;
id: DocumentId;
[key: string]: unknown;
}
interface NavigableRow {
key: string;
type: 'folder' | 'document';
id: string | number;
id: FolderId | DocumentId;
}
interface UseDocumentsSelectionOptions {
@@ -25,19 +26,19 @@ interface UseDocumentsSelectionOptions {
visibleRowKeySet: Set<string>;
selectedEntries: string[];
selectionAnchorRef: { current: string | null };
promoteSelectionOrderRaw: (id: string | number) => void;
setFocusedDocumentId: (id: string | number | null) => void;
setActivePreviewId: (id: string | number | null) => void;
promoteSelectionOrderRaw: (id: DocumentId) => void;
setFocusedDocumentId: (id: DocumentId | null) => void;
setActivePreviewId: (id: DocumentId | null) => void;
clearSelection: () => void;
focusedDocumentId: string | number | null;
focusedDocumentId: DocumentId | null;
setFocusedRowKey: (value: string | null | ((current: string | null) => string | null)) => void;
focusedRowKey: string | null;
}
const useDocumentsSelection = ({
showingSearchResults,
currentSubfolders,
visibleDocuments,
currentSubfolders = [],
visibleDocuments = [],
configureSelectionEnvironment,
visibleRowKeySet,
selectedEntries,
@@ -82,7 +83,7 @@ const useDocumentsSelection = ({
}, [configureSelectionEnvironment, visibleRowKeySet, navigableRowKeys]);
const promoteSelectionOrder = useCallback(
(docId: string | number | null) => {
(docId: DocumentId | null) => {
if (!docId) return;
promoteSelectionOrderRaw(docId);
const rowKey = createDocumentEntryKey(docId);
@@ -99,7 +100,7 @@ const useDocumentsSelection = ({
clearSelection();
}, [clearSelection]);
const prevFocusedDocIdRef = useRef<string | number | null>(focusedDocumentId);
const prevFocusedDocIdRef = useRef<DocumentId | null>(focusedDocumentId);
useEffect(() => {
const previous = prevFocusedDocIdRef.current;
if (previous === focusedDocumentId) {
@@ -109,7 +110,7 @@ const useDocumentsSelection = ({
if (focusedDocumentId) {
setFocusedRowKey(createDocumentEntryKey(focusedDocumentId));
} else {
setFocusedRowKey((current) => (isFolderEntry(current) ? current : null));
setFocusedRowKey((current) => (current && isFolderEntry(current) ? current : null));
}
}, [focusedDocumentId, setFocusedRowKey]);
@@ -18,6 +18,7 @@ import DocumentsPanelHeader, {
import { SelectionFloatingPanel } from '../SelectionFloatingActions';
import { createDocumentsTableHeaderActions } from './DocumentsToolbar';
import { useDocumentsFilter } from '../context/DocumentsFilterContext';
import type { Identifier } from '../../types/identifiers';
const DEFAULT_GRID_ICON_SIZE = 144;
@@ -73,7 +74,7 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
documentLinks,
ensureDownloadUrl,
deskWorkspaceProps = null,
onRefresh = () => {},
onRefresh = () => { },
sortField,
sortDirection,
onSortFieldChange,
@@ -256,8 +257,7 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
const isGridView = viewMode === 'grid';
const isDeskView = viewMode === 'desk';
type Identifier = string | number;
type ZoomSource = { url: string; alt?: string | null; mimeType?: string | null };
type ZoomSource = { url: string; alt?: string | null; mimeType?: string | null };
const [previewDocId, setPreviewDocId] = useState<Identifier | null>(null);
@@ -499,7 +499,7 @@ type ZoomSource = { url: string; alt?: string | null; mimeType?: string | null }
setFocusedRowKey(targetRow.key);
handleEntrySelection(targetRow.key, {
shiftKey,
preventDefault: () => {},
preventDefault: () => { },
});
},
[
@@ -2,8 +2,7 @@ import React from 'react';
import type { ReactNode } from 'react';
import PanelHeader from '../../ui/PanelHeader';
import BreadcrumbTrail from '../../ui/BreadcrumbTrail';
type Identifier = string | number;
import type { Identifier } from '../../types/identifiers';
export interface DocumentsHeaderBreadcrumb {
id?: Identifier;
+14 -5
View File
@@ -1,14 +1,16 @@
import type { DocumentId, TagId } from '../types/identifiers';
const TAG_MIME_TYPES = ['application/x-papercrate-tag', 'text/papercrate-tag'];
const TAG_TEXT_MIME_TYPE = 'text/plain';
interface TagPayload {
id: string | number;
id: TagId;
label: string;
sourceDocId: string | number | null;
sourceDocId: DocumentId | null;
}
interface TagLike {
id?: string | number;
id?: TagId;
label?: string | null;
}
@@ -21,7 +23,10 @@ const serializePayload = (payload: TagPayload): string | null => {
}
};
export const createTagTransferPayload = (tag?: TagLike | null, sourceDocId: string | number | null = null): TagPayload | null => {
export const createTagTransferPayload = (
tag?: TagLike | null,
sourceDocId: DocumentId | null = null,
): TagPayload | null => {
if (!tag || tag.id == null) {
return null;
}
@@ -33,7 +38,11 @@ export const createTagTransferPayload = (tag?: TagLike | null, sourceDocId: stri
};
};
export const writeTagTransferData = (dataTransfer: DataTransfer | null, tag: TagLike, sourceDocId: string | number | null = null): void => {
export const writeTagTransferData = (
dataTransfer: DataTransfer | null,
tag: TagLike,
sourceDocId: DocumentId | null = null,
): void => {
if (!dataTransfer) {
return;
}
+3 -3
View File
@@ -20,7 +20,7 @@ export const isPrimaryPointerEvent = (event?: PointerEventLike | null): boolean
export type EntryType = 'document' | 'folder';
export interface WorkspaceEntry {
id: string | number;
id: string;
key?: string;
type: EntryType;
[key: string]: unknown;
@@ -28,7 +28,7 @@ export interface WorkspaceEntry {
interface UseEntryPointerOptions {
onSelectEntry?: (entry: WorkspaceEntry, event?: PointerEventLike | null, metadata?: EntryPointerMetadata) => void;
onDocumentActivate?: (id: string | number, metadata?: EntryPointerMetadata) => void;
onDocumentActivate?: (id: string, metadata?: EntryPointerMetadata) => void;
}
export interface EntryPointerMetadata {
@@ -36,7 +36,7 @@ export interface EntryPointerMetadata {
primaryClick: boolean;
rowKey: string;
type: EntryType;
id: string | number;
id: string;
}
export const useEntryPointer = ({
+8 -8
View File
@@ -13,22 +13,22 @@ type FocusableInput = (HTMLInputElement | HTMLTextAreaElement) & {
type InlineRenameOptions<TEntity> = {
getCurrentValue?: (entity: TEntity) => string | null;
getEntityId?: (entity: TEntity) => string | number | null;
getEntityId?: (entity: TEntity) => string | null;
};
type InlineRenameHandler = (
id: string | number,
id: string,
value: string,
) => boolean | void | Promise<boolean | void>;
type InlineRenameReturn<TEntity> = {
editingId: string | number | null;
editingId: string | null;
draftValue: string;
setDraftValue: Dispatch<SetStateAction<string>>;
beginEditing: (entity?: TEntity | null, event?: SyntheticEvent | Event) => void;
cancelEditing: (event?: SyntheticEvent | Event) => void;
submitEditing: (entity?: TEntity | null) => Promise<boolean>;
savingId: string | number | null;
savingId: string | null;
attachInputRef: (node: FocusableInput | null) => void;
};
@@ -51,18 +51,18 @@ const focusInput = (node: FocusableInput | null) => {
const identity = (value: unknown) => value as string;
const defaultGetEntityId = <T,>(entity?: T | null) =>
(entity as { id?: string | number } | null)?.id ?? null;
(entity as { id?: string } | null)?.id ?? null;
const useInlineRename = <TEntity,>(
onRename?: InlineRenameHandler,
{
getCurrentValue = identity as (entity: TEntity) => string | null,
getEntityId = defaultGetEntityId as (entity: TEntity) => string | number | null,
getEntityId = defaultGetEntityId as (entity: TEntity) => string | null,
}: InlineRenameOptions<TEntity> = {},
): InlineRenameReturn<TEntity> => {
const [editingId, setEditingId] = useState<string | number | null>(null);
const [editingId, setEditingId] = useState<string | null>(null);
const [draftValue, setDraftValue] = useState('');
const [savingId, setSavingId] = useState<string | number | null>(null);
const [savingId, setSavingId] = useState<string | null>(null);
const inputRef = useRef<FocusableInput | null>(null);
const resetState = useCallback(() => {
@@ -17,7 +17,7 @@ const FolderManagerContext = createContext<FolderManager>(defaultManager);
interface FolderManagerProviderProps {
folderNodes?: Map<string | 'root', { name?: string | null }>;
ensureFolderData?: (folderId: string | 'root', options?: { force?: boolean; includeDocuments?: boolean }) => Promise<void>;
ensureFolderData?: (folderId: FolderId | 'root', options?: { force?: boolean; includeDocuments?: boolean }) => Promise<void>;
children: ReactNode;
}
@@ -8,7 +8,7 @@ type ApiClient = {
};
interface CorrespondentEntry {
id?: string | number;
id?: string;
name?: string;
[key: string]: unknown;
}
@@ -17,7 +17,7 @@ interface UseCorrespondentsOptions {
apiClient: ApiClient;
notifyApiError: (error: unknown, fallback: string) => void;
setStatusMessage: (message: string, variant?: string) => void;
tenantIdRef: MutableRefObject<string | number | null>;
tenantIdRef: MutableRefObject<string | null>;
mapDocumentCaches?: (mapper: (doc: any) => any) => void;
}
@@ -47,7 +47,7 @@ const useCorrespondents = ({
}, [apiClient, notifyApiError, tenantIdRef]);
const handleCorrespondentUpdate = useCallback(
async (correspondentId: string | number, changes: { name?: string }) => {
async (correspondentId: string, changes: { name?: string }) => {
if (correspondentId == null) {
throw new Error('Missing correspondent identifier.');
}
@@ -100,7 +100,7 @@ const useCorrespondents = ({
);
const handleCorrespondentDelete = useCallback(
async (correspondentId: string | number) => {
async (correspondentId: string) => {
if (correspondentId == null) {
throw new Error('Missing correspondent identifier.');
}
@@ -1,4 +1,5 @@
import { useCallback, useMemo } from 'react';
import type { Identifier } from '../../types/identifiers';
type ApiClient = {
@@ -7,13 +8,11 @@ type ApiClient = {
};
interface CorrespondentOption {
id?: string | number;
id?: string;
name?: string;
[key: string]: unknown;
}
type Identifier = string | number;
interface UseDocumentCorrespondentActionsArgs {
apiClient: ApiClient;
correspondents: CorrespondentOption[];
@@ -135,7 +134,7 @@ const useDocumentCorrespondentActions = ({
};
const handleCorrespondentAdd = useCallback(
async ({ document, name, input = null, option = null }: { document?: { id?: string | number }; name?: string; input?: HTMLInputElement | null; option?: CorrespondentOption | string | null }) => {
async ({ document, name, input = null, option = null }: { document?: { id?: string }; name?: string; input?: HTMLInputElement | null; option?: CorrespondentOption | string | null }) => {
if (!document?.id) {
throw new Error('Missing document for correspondent assignment.');
}
@@ -1,9 +1,9 @@
import { useCallback, useEffect, useMemo, useRef } from 'react';
import type { DragEvent } from 'react';
import { createDocumentEntryKey, createFolderEntryKey } from '../../app/entryKey';
import type { FolderId, Identifier } from '../../types/identifiers';
type Identifier = string | number;
type FolderIdentifier = string | 'root';
type FolderIdentifier = FolderId | 'root';
type FolderInput = FolderIdentifier | number;
interface DocumentLike {
@@ -14,7 +14,7 @@ interface DocumentLike {
type ApplySelectionFn = (
keys: string[],
options?: { anchor?: string | null; interactedKeys?: string[] },
options?: { anchor: string | null; interactedKeys?: string[] },
) => void;
type HandleEntrySelectionFn = (
@@ -13,9 +13,9 @@ import {
trashDocument,
updateDocument,
} from '../../lib/apiClient';
import type { DocumentId, FolderId as FolderIdentifier } from '../../types/identifiers';
type DocumentId = string | number;
type FolderId = DocumentId | 'root';
type FolderId = FolderIdentifier | 'root';
type NullableFolderId = FolderId | null;
type StatusLevel = 'success' | 'error' | 'info' | string;
@@ -1,6 +1,6 @@
import { useCallback } from 'react';
type Identifier = string | number;
import type { Identifier } from '../../types/identifiers';
interface TagRecord {
id?: Identifier;
@@ -205,8 +205,7 @@ const useDocumentTagging = ({
if (result?.ok) {
const { tagCount, docsCount } = result;
setStatusMessage(
`Assigned ${tagCount} tag${tagCount === 1 ? '' : 's'} to ${docsCount} document${
docsCount === 1 ? '' : 's'
`Assigned ${tagCount} tag${tagCount === 1 ? '' : 's'} to ${docsCount} document${docsCount === 1 ? '' : 's'
}.`,
'success',
);
@@ -3,8 +3,8 @@ import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
import useFileDrop from './useFileDrop';
import { DEFAULT_FOLDER_NAME, hasFiles } from '../../app/workspaceUtils';
import { fetchDocument } from '../../lib/apiClient';
import type { Identifier } from '../../types/identifiers';
type Identifier = string | number;
type FolderId = Identifier | 'root' | null;
type FileEntry = {
+1 -2
View File
@@ -7,8 +7,7 @@ import {
useState,
} from 'react';
import DocumentsManager from '../../documents/DocumentsManager';
type DocumentId = string | number;
import type { DocumentId } from '../../types/identifiers';
interface DocumentLike {
id?: DocumentId;
@@ -52,6 +52,7 @@ import useWorkspaceTaxonomies from './useWorkspaceTaxonomies';
import useWorkspaceBreadcrumbs from './useWorkspaceBreadcrumbs';
import useWorkspaceDeskProps from './useWorkspaceDeskProps';
import useWorkspaceSelectionSync from './useWorkspaceSelectionSync';
import type { DocumentId, FolderId as FolderIdentifier, Identifier } from '../../types/identifiers';
const EntryType = Object.freeze({
document: 'document',
@@ -60,9 +61,7 @@ const EntryType = Object.freeze({
const noop = () => { };
type Identifier = string | number;
type DocumentId = Identifier;
type FolderId = Identifier | 'root';
type FolderId = FolderIdentifier | 'root';
interface DocumentLike {
id?: DocumentId | null;
+1 -1
View File
@@ -1,6 +1,6 @@
import { MutableRefObject, useEffect } from 'react';
type FolderId = string | number | 'root' | null;
type FolderId = string | 'root' | null;
interface DropOverlayState {
active: boolean;
@@ -8,9 +8,9 @@ import {
createDocumentEntryKey,
createFolderEntryKey,
} from '../../app/entryKey';
import type { FolderId as FolderIdentifier, Identifier } from '../../types/identifiers';
type Identifier = string | number;
type FolderId = Identifier | 'root';
type FolderId = FolderIdentifier | 'root';
interface DocumentLike {
id?: Identifier | null;
@@ -126,8 +126,8 @@ const useFolderTree = ({
.filter(Boolean),
);
let nextDocKeys = [];
let mergedSelection = [];
let nextDocKeys: string[] = [];
let mergedSelection: string[] = [];
setSelectedEntries((previous) => {
const previousFolderKeys = previous
@@ -140,10 +140,12 @@ const useFolderTree = ({
});
const nextFocus = (() => {
if (focusedDocumentId) {
const currentFocusedKey = createDocumentEntryKey(focusedDocumentId);
if (currentFocusedKey && availableDocKeySet.has(currentFocusedKey)) {
return focusedDocumentId;
}
}
if (nextDocKeys.length) {
const lastDocKey = nextDocKeys[nextDocKeys.length - 1];
return getEntryId(lastDocKey) || null;
@@ -172,7 +174,7 @@ const useFolderTree = ({
if (!targetId || targetId === 'root') {
setFolderNodes((prev: Map<FolderId, FolderTreeNode>) => {
const root = prev.get('root');
if (root?.expanded) return prev;
if (!root || root.expanded) return prev;
const next = new Map<FolderId, FolderTreeNode>(prev);
next.set('root', { ...root, expanded: true });
return next;
@@ -7,8 +7,8 @@ import {
moveFolder as moveFolderRequest,
renameFolder as renameFolderRequest,
} from '../../lib/apiClient';
import type { FolderId } from '../../types/identifiers';
type FolderId = string | number;
type FolderKey = FolderId | 'root';
interface FolderNode {
+6 -5
View File
@@ -1,4 +1,5 @@
import { MutableRefObject, useCallback, useState } from 'react';
import type { TagId, TenantId } from '../../types/identifiers';
type ApiClient = {
get: (path: string) => Promise<{ data: unknown }>
@@ -12,7 +13,7 @@ interface TagManagerInterface {
}
interface TagEntry {
id?: string | number;
id?: TagId;
label?: string;
color?: string | null;
[key: string]: unknown;
@@ -23,8 +24,8 @@ interface UseTagsOptions {
notifyApiError: (error: unknown, fallback: string) => void;
setStatusMessage: (message: string, variant?: string) => void;
tagManager: TagManagerInterface;
tenantIdRef: MutableRefObject<string | number | null>;
setActiveTagFilters: (updater: (prev: Array<string | number>) => Array<string | number>) => void;
tenantIdRef: MutableRefObject<TenantId | null>;
setActiveTagFilters: (updater: (prev: Array<TagId>) => Array<TagId>) => void;
mapDocumentCaches?: (mapper: (doc: any) => any) => void;
}
@@ -56,7 +57,7 @@ const useTags = ({
}, [apiClient, notifyApiError, tenantIdRef]);
const handleTagUpdate = useCallback(
async (tagId: string | number, changes: { label?: string; color?: string | null }) => {
async (tagId: TagId, changes: { label?: string; color?: string | null }) => {
if (tagId == null) {
throw new Error('Missing tag identifier.');
}
@@ -104,7 +105,7 @@ const useTags = ({
);
const handleTagDelete = useCallback(
async (tagId: string | number) => {
async (tagId: TagId) => {
if (tagId == null) {
throw new Error('Missing tag identifier.');
}
@@ -1,5 +1,6 @@
import { MutableRefObject, useCallback } from 'react';
import type { NavigateFunction } from 'react-router-dom';
import type { FolderId, TenantId } from '../../types/identifiers';
interface ApiClient {
get: (path: string) => Promise<{ data: unknown }>;
@@ -8,24 +9,24 @@ interface ApiClient {
}
interface TenantOption {
id?: string | number;
id?: TenantId;
name?: string;
}
interface UseTenantManagerOptions {
apiClient: ApiClient;
appDispatch: (action: any) => void;
currentTenantId: string | number | null;
currentTenantId: TenantId | null;
resetWorkspaceState: () => void;
setStatusMessage: (message: string, variant?: string) => void;
notifyApiError: (error: unknown, message: string) => void;
refreshTags: () => Promise<void>;
refreshCorrespondents: () => Promise<void>;
loadFolder: (folderId: string, options?: { preserveSearch?: boolean }) => Promise<void>;
loadFolder: (folderId: FolderId, options?: { preserveSearch?: boolean }) => Promise<void>;
handleDocumentsViewModeChange: (mode: string) => void;
navigate: NavigateFunction;
tokenRef?: MutableRefObject<string | null>;
tenantIdRef?: MutableRefObject<string | number | null>;
tenantIdRef?: MutableRefObject<TenantId | null>;
}
const useTenantManager = ({
@@ -1,8 +1,8 @@
import React, { useEffect, useMemo } from 'react';
import { DEFAULT_FOLDER_NAME } from '../../app/workspaceUtils';
import type { FolderId as FolderIdentifier } from '../../types/identifiers';
type Identifier = string | number;
type FolderId = Identifier | 'root';
type FolderId = FolderIdentifier | 'root';
interface UseWorkspaceBreadcrumbsArgs {
selectedFolder: FolderId | null;
@@ -1,11 +1,10 @@
import { useCallback, useMemo } from 'react';
import type { MutableRefObject } from 'react';
import { createDocumentEntryKey } from '../../app/entryKey';
type Identifier = string | number;
import type { Identifier } from '../../types/identifiers';
interface ApplySelectionFn {
(keys: string[], options?: { anchor?: string | null; interactedKeys?: string[] }): unknown;
(keys: string[], options?: { anchor: string | null; interactedKeys?: string[] }): unknown;
}
interface UseWorkspaceDeskPropsArgs {
@@ -17,8 +16,8 @@ interface UseWorkspaceDeskPropsArgs {
applySelection: ApplySelectionFn;
showingSearchResults: boolean;
searchQuery: string;
activeTagFilters: Array<string | number>;
activeCorrespondentFilters: Array<string | number>;
activeTagFilters: Array<string>;
activeCorrespondentFilters: Array<string>;
selectedFolder: Identifier | 'root' | null;
promoteSelectionOrder: () => void;
handleDocumentTagDrop: (docId: Identifier, tagId: Identifier) => Promise<void> | void;
@@ -1,14 +1,13 @@
import { useEffect } from 'react';
import type { MutableRefObject } from 'react';
type Identifier = string | number;
import type { Identifier } from '../../types/identifiers';
interface UseWorkspaceSelectionSyncArgs {
showingSearchResults: boolean;
searchQuery: string;
setSelectedEntries: (entries: Array<string | number>) => void;
setSelectionOrder: (order: Array<string | number>) => void;
selectionOrderRef: MutableRefObject<Array<string | number>>;
setSelectedEntries: (entries: Array<string>) => void;
setSelectionOrder: (order: Array<string>) => void;
selectionOrderRef: MutableRefObject<Array<string>>;
selectionAnchorRef: MutableRefObject<Identifier | string | null>;
setFocusedDocumentId: (id: Identifier | null) => void;
selectedDocumentIds: Identifier[];
@@ -5,8 +5,7 @@ import TagManager from '../../tag_manager';
import useCorrespondents from './useCorrespondents';
import useDocumentCorrespondentActions from './useDocumentCorrespondentActions';
import useTags from './useTags';
type Identifier = string | number;
import type { Identifier } from '../../types/identifiers';
interface UseWorkspaceTaxonomiesArgs {
apiClient: any;
+3 -4
View File
@@ -1,7 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import { resolveAssetUrl } from '../asset_manager';
type Identifier = string | number;
import type { Identifier } from '../types/identifiers';
type DocumentLike = {
id?: Identifier;
@@ -27,7 +26,7 @@ type AssetLike = {
type EnsureAssetUrl = (
documentId: Identifier,
asset: AssetLike,
options?: { force?: boolean; [key: string]: unknown },
options?: { force?: boolean;[key: string]: unknown },
) => Promise<unknown>;
type GetAsset = (document: DocumentLike, assetType: string) => AssetLike | null;
@@ -92,7 +91,7 @@ export const useAssetNavigator = ({
let cancelled = false;
setIsLoading(true);
ensureAssetUrl(documentId, asset, { force: true })
.catch(() => {})
.catch(() => { })
.finally(() => {
if (!cancelled) {
setIsLoading(false);
+2 -1
View File
@@ -1,6 +1,7 @@
// Types aligned with OpenAPI schemas for common endpoints.
import type { Identifier } from '../types/identifiers';
export type Identifier = string | number;
export type { Identifier };
export interface DownloadLink {
url: string;
+2 -2
View File
@@ -12,7 +12,7 @@ const StatusBanner: React.FC<StatusBannerProps> = ({ status }) => {
};
interface TenantOption {
id?: string | number;
id?: string;
name?: string;
}
@@ -25,7 +25,7 @@ interface LoginViewProps {
tenantSelection?: TenantSelectionState | null;
onSelectTenant?: (tenant: TenantOption) => void;
onCancelSelection?: () => void;
selectingTenantId?: string | number | null;
selectingTenantId?: string | null;
onPasskeyLogin?: (username: string) => void;
onSignup?: (username: string) => void;
passkeySupported?: boolean;
@@ -5,7 +5,7 @@ import { DownloadIcon } from '../ui/icons';
import PdfViewer from './PdfViewer';
interface DocumentLike {
id?: string | number;
id?: string;
title?: string;
mime_type?: string;
filename?: string;
@@ -40,7 +40,7 @@ interface DocumentViewerLayoutProps {
summaryProps?: Record<string, unknown>;
metadataPayload?: unknown;
contentTabConfig?: ContentTabConfig | null;
resetKey?: string | number | null;
resetKey?: string | null;
classNamePrefix?: string;
defaultTabId?: string;
infoPanelProps?: Record<string, unknown>;
+10 -9
View File
@@ -28,14 +28,15 @@ import BreadcrumbTrail from '../ui/BreadcrumbTrail';
import DocumentViewerLayout from './DocumentViewerLayout';
import useViewerLayoutMode from './useViewerLayoutMode';
import { usePanelResizeBindings } from '../app/PanelManagerContext';
import type { DocumentId, FolderId } from '../types/identifiers';
interface DocumentLike {
id?: string | number;
id?: DocumentId;
title?: string;
mime_type?: string | null;
issued_at?: string | null;
folder_id?: string | null;
correspondents?: Array<{ id?: string | number; name?: string }>;
folder_id?: FolderId | null;
correspondents?: Array<{ id?: string; name?: string }>;
current_version?: {
version_number?: number;
download?: { url?: string | null; expires_at?: number } | null;
@@ -51,7 +52,7 @@ interface DocumentLike {
}
interface AssetLike {
id?: string | number;
id?: string;
url?: string | null;
metadata?: Record<string, unknown> | null;
[key: string]: unknown;
@@ -59,16 +60,16 @@ interface AssetLike {
interface DocumentViewerPanelProps extends DocumentSummarySectionProps {
document: DocumentLike | null;
ensureAssetUrl?: (docId: string | number, asset: AssetLike, options?: { force?: boolean }) => Promise<unknown>;
ensureAssetUrl?: (docId: DocumentId, asset: AssetLike, options?: { force?: boolean }) => Promise<unknown>;
getDocumentAsset?: (doc: DocumentLike | null, type: string) => AssetLike | null;
ensurePreviewData?: (docId: string | number, options?: { signal?: AbortSignal }) => Promise<DocumentLike | null>;
ensurePreviewData?: (docId: DocumentId, options?: { signal?: AbortSignal }) => Promise<DocumentLike | null>;
notifyApiError?: (error: unknown, fallbackMessage?: string) => void;
sidebarToggle?: ReactNode;
onClosePanel?: () => void;
resolveFolderPath?: (doc: DocumentLike | null) => Array<{ id?: string | number; name?: string }>;
resolveFolderPath?: (doc: DocumentLike | null) => Array<{ id?: string; name?: string }>;
variant?: 'viewer' | 'sidebar';
onCollapsePanel?: () => void;
onMaximizePanel?: (args: { documentIds: Array<string | number> }) => void;
onMaximizePanel?: (args: { documentIds: Array<string> }) => void;
}
@@ -163,7 +164,7 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
}, [document, getDocumentAsset]);
const navigateToFolder = useCallback(
(folderId) => {
(folderId: FolderId | null) => {
const target = folderId == null
? '/documents'
: `/documents/folder/${folderId}`;
+1 -2
View File
@@ -2,8 +2,7 @@ import React, { useEffect } from 'react';
import { Navigate, useNavigate, useParams } from 'react-router-dom';
import { useAppShell } from '../appShellContext';
type Identifier = string | number;
import type { Identifier } from '../types/identifiers';
interface DocumentViewerRouteContext {
previewWorkspaceDocument?: { id?: Identifier } | null;
@@ -7,9 +7,7 @@ import {
} from 'react';
import type { JSX } from 'react';
import { CheckIcon, ChevronDownIcon } from '../../ui/icons';
type CapabilityValue = string | number;
import type { CapabilityValue } from '../../types/identifiers';
export interface CapabilityDropdownOption {
value?: CapabilityValue | null;
@@ -1,7 +1,6 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import type { ChangeEvent, FormEvent } from 'react';
type Identifier = string | number;
import type { Identifier } from '../../types/identifiers';
interface ApiTokenEntry {
id?: Identifier;
@@ -8,10 +8,7 @@ import React, {
import type { SettingsSectionConfig } from '../SettingsModal';
import { IconX } from '../../ui/icons';
import CapabilityDropdown, { CapabilityDropdownOption } from '../components/CapabilityDropdown';
type CapabilityValue = string | number;
type CapabilitySetId = string | number;
import type { CapabilitySetId, CapabilityValue } from '../../types/identifiers';
interface CapabilitySet {
id: CapabilitySetId;
@@ -18,10 +18,10 @@ interface PasskeysSectionProps {
passkeysSupported?: boolean | null;
passkeysLoading?: boolean;
registeringPasskey?: boolean;
revokingPasskeyId?: string | number | null;
revokingPasskeyId?: string | null;
onRefreshPasskeys?: () => void | Promise<void>;
onRegisterPasskey?: (args: { nickname?: string }) => Promise<RegisterPasskeyResult | undefined>;
onRevokePasskey?: (id: string | number, reason?: string) => Promise<void>;
onRevokePasskey?: (id: string, reason?: string) => Promise<void>;
}
const PasskeysSection = ({
+11 -10
View File
@@ -6,6 +6,7 @@ import {
regenerateApiToken,
type ApiTokenRecord,
} from '../lib/apiClient';
import type { ApiTokenId, CapabilitySetId } from '../types/identifiers';
interface ApiTokensResponse {
token_info?: ApiTokenRecord;
@@ -15,7 +16,7 @@ interface ApiTokensResponse {
interface CreateTokenArgs {
label?: string;
expires_at?: string;
capability_set_id?: string | number;
capability_set_id?: CapabilitySetId;
}
interface UseApiTokensArgs {
@@ -28,13 +29,13 @@ interface UseApiTokensResult {
tokens: ApiTokenRecord[];
loading: boolean;
creating: boolean;
deletingId: string | number | null;
regeneratingId: string | number | null;
deletingId: ApiTokenId | null;
regeneratingId: ApiTokenId | null;
createdSecret: string | null;
refresh: () => Promise<void>;
create: (args?: CreateTokenArgs) => Promise<ApiTokensResponse | false>;
revoke: (tokenId?: string | number | null) => Promise<boolean>;
regenerate: (tokenId?: string | number | null) => Promise<boolean>;
revoke: (tokenId?: ApiTokenId | null) => Promise<boolean>;
regenerate: (tokenId?: ApiTokenId | null) => Promise<boolean>;
dismissSecret: () => void;
}
@@ -42,8 +43,8 @@ const useApiTokens = ({ notifyApiError, setStatusMessage, token }: UseApiTokensA
const [tokens, setTokens] = useState<ApiTokenRecord[]>([]);
const [loading] = useState(false);
const [creating, setCreating] = useState(false);
const [deletingId, setDeletingId] = useState<string | number | null>(null);
const [regeneratingId, setRegeneratingId] = useState<string | number | null>(null);
const [deletingId, setDeletingId] = useState<ApiTokenId | null>(null);
const [regeneratingId, setRegeneratingId] = useState<ApiTokenId | null>(null);
const [createdSecret, setCreatedSecret] = useState<string | null>(null);
const refresh = useCallback(async () => {
@@ -65,7 +66,7 @@ const useApiTokens = ({ notifyApiError, setStatusMessage, token }: UseApiTokensA
}
setCreating(true);
try {
const payload: { capability_set_id: string | number; label?: string; expires_at?: string } = { capability_set_id };
const payload: { capability_set_id: CapabilitySetId; label?: string; expires_at?: string } = { capability_set_id };
if (label) {
payload.label = label;
}
@@ -100,7 +101,7 @@ const useApiTokens = ({ notifyApiError, setStatusMessage, token }: UseApiTokensA
);
const revoke = useCallback(
async (tokenId?: string | number | null) => {
async (tokenId?: string | null) => {
if (!tokenId) {
return false;
}
@@ -121,7 +122,7 @@ const useApiTokens = ({ notifyApiError, setStatusMessage, token }: UseApiTokensA
);
const regenerate = useCallback(
async (tokenId?: string | number | null) => {
async (tokenId?: string | null) => {
if (!tokenId) {
return false;
}
+1 -2
View File
@@ -5,8 +5,7 @@ import {
listCapabilitySets,
updateCapabilitySet as updateCapabilitySetRequest,
} from '../lib/apiClient';
type Identifier = string | number;
import type { Identifier } from '../types/identifiers';
interface CapabilitySet {
id?: Identifier;
+6 -5
View File
@@ -12,6 +12,7 @@ import {
listPasskeys,
startPasskeyRegistration,
} from '../lib/apiClient';
import type { PasskeyId } from '../types/identifiers';
type StatusMessageFn = (message: string, variant?: string) => void;
type NotifyApiErrorFn = (error: unknown, message: string) => void;
@@ -27,7 +28,7 @@ type ApiError = {
};
export interface PasskeyRecord {
id?: string | number;
id?: PasskeyId;
nickname?: string;
created_at?: string;
createdAt?: string;
@@ -79,11 +80,11 @@ interface UsePasskeysResult {
passkeysSupported: boolean | null;
passkeysLoading: boolean;
registeringPasskey: boolean;
revokingPasskeyId: string | number | null;
revokingPasskeyId: PasskeyId | null;
refreshPasskeys: () => Promise<void>;
registerPasskey: (options?: { nickname?: string }) => Promise<RegisterPasskeyResult>;
revokePasskey: (
passkeyId: string | number,
passkeyId: PasskeyId,
reason?: string,
) => Promise<RevokePasskeyResult>;
}
@@ -93,7 +94,7 @@ const usePasskeys = ({ notifyApiError, setStatusMessage, token }: UsePasskeysArg
const [passkeysSupported, setPasskeysSupported] = useState<boolean | null>(null);
const [passkeysLoading, setPasskeysLoading] = useState(false);
const [registeringPasskey, setRegisteringPasskey] = useState(false);
const [revokingPasskeyId, setRevokingPasskeyId] = useState<string | number | null>(null);
const [revokingPasskeyId, setRevokingPasskeyId] = useState<PasskeyId | null>(null);
const refreshPasskeys = useCallback(async (): Promise<void> => {
if (!token) {
@@ -191,7 +192,7 @@ const usePasskeys = ({ notifyApiError, setStatusMessage, token }: UsePasskeysArg
const revokePasskey = useCallback(
async (
passkeyId: string | number,
passkeyId: PasskeyId,
reason?: string,
): Promise<RevokePasskeyResult> => {
if (passkeyId == null) {
+2 -3
View File
@@ -31,6 +31,7 @@ import { getTagColorStyle } from '../utils/colors';
import { useSidebarContext } from './SidebarContext';
import { usePanelManager, usePanelResizeBindings } from '../app/PanelManagerContext';
import type { StatusMessage } from '../hooks/documents/store/useDocumentsStore';
import type { Identifier } from '../types/identifiers';
interface CommunityLink {
label: string;
@@ -60,7 +61,6 @@ const COMMUNITY_LINKS: CommunityLink[] = [
},
];
type Identifier = string | number;
type FolderIdentifier = Identifier | 'root';
interface FolderTreeNode {
@@ -925,8 +925,7 @@ const Sidebar: React.FC<SidebarProps> = ({
</div>
</div>
<div
className={`sidebar-tag-cloud${
activeTagSet.size ? ' sidebar-tag-cloud--has-active' : ''
className={`sidebar-tag-cloud${activeTagSet.size ? ' sidebar-tag-cloud--has-active' : ''
}`}
role="list"
>
+1 -2
View File
@@ -2,8 +2,7 @@ import { useMemo } from 'react';
import type { DragEvent } from 'react';
import { TAG_FILTER_UNTAGGED } from '../app/workspaceUtils';
import type { StatusMessage } from '../hooks/documents/store/useDocumentsStore';
type Identifier = string | number;
import type { Identifier } from '../types/identifiers';
interface FolderTreeNode {
id: Identifier;
+11
View File
@@ -0,0 +1,11 @@
// Common string-based identifiers used across the app.
export type Identifier = string;
export type DocumentId = Identifier;
export type FolderId = Identifier;
export type CapabilitySetId = Identifier;
export type CapabilityValue = Identifier;
export type TenantId = Identifier;
export type TagId = Identifier;
export type ApiTokenId = Identifier;
export type PasskeyId = Identifier;
+3 -3
View File
@@ -4,10 +4,10 @@ import { PlusIcon } from './icons';
import useFloatingMenu from './useFloatingMenu';
type QuickAddOption = string | number | { id?: string | number; label?: string; name?: string;[key: string]: unknown };
type QuickAddOption = string | { id?: string; label?: string; name?: string;[key: string]: unknown };
interface NormalizedOption {
id?: string | number;
id?: string;
label: string;
original: QuickAddOption;
index: number;
@@ -47,7 +47,7 @@ interface FloatingMenuState {
updatePosition: () => void;
}
type CSSVarStyle = CSSProperties & Record<string, string | number>;
type CSSVarStyle = CSSProperties & Record<string, string>;
export interface QuickAddMenuProps {
onSelectOption?: (value: QuickAddOption, normalized: NormalizedOption) => Promise<void> | void;
+6 -6
View File
@@ -1,4 +1,4 @@
const ensureDate = (value: string | number | Date | null): Date | null => {
const ensureDate = (value: string | Date | null): Date | null => {
if (!value) {
return null;
}
@@ -12,7 +12,7 @@ interface FormatOptions {
options?: Intl.DateTimeFormatOptions;
}
export const formatDate = (value: string | number | Date | null, { fallback = '—', locale, options }: FormatOptions = {}): string => {
export const formatDate = (value: string | Date | null, { fallback = '—', locale, options }: FormatOptions = {}): string => {
const date = ensureDate(value);
if (!date) {
return fallback;
@@ -20,7 +20,7 @@ export const formatDate = (value: string | number | Date | null, { fallback = '
return date.toLocaleDateString(locale, options);
};
export const formatDateTime = (value: string | number | Date | null, { fallback = '—', locale, options }: FormatOptions = {}): string => {
export const formatDateTime = (value: string | Date | null, { fallback = '—', locale, options }: FormatOptions = {}): string => {
const date = ensureDate(value);
if (!date) {
return fallback;
@@ -28,7 +28,7 @@ export const formatDateTime = (value: string | number | Date | null, { fallback
return date.toLocaleString(locale, options);
};
export const toDateInputValue = (value: string | number | Date | null): string => {
export const toDateInputValue = (value: string | Date | null): string => {
const date = ensureDate(value);
if (!date) {
return '';
@@ -38,7 +38,7 @@ export const toDateInputValue = (value: string | number | Date | null): string =
return localDate.toISOString().slice(0, 10);
};
export const toIssuedTimestamp = (dateString: string | null, fallback: string | number | Date | null): string | null => {
export const toIssuedTimestamp = (dateString: string | null, fallback: string | Date | null): string | null => {
if (!dateString) {
return null;
}
@@ -52,7 +52,7 @@ export const toIssuedTimestamp = (dateString: string | null, fallback: string |
return Number.isNaN(candidate.getTime()) ? null : candidate.toISOString();
};
export const parseDateValue = (value: string | number | Date | null): Date | null => ensureDate(value);
export const parseDateValue = (value: string | Date | null): Date | null => ensureDate(value);
export default {
formatDate,
+2 -2
View File
@@ -12,9 +12,9 @@ export interface DocumentLike extends AssetManagerDocumentLike {
export type AssetLike = AssetManagerAssetLike;
export type EnsurePreviewData = (id: string | number) => Promise<DocumentLike | null>;
export type EnsurePreviewData = (id: string) => Promise<DocumentLike | null>;
export type EnsureAssetUrl = (
id: string | number,
id: string,
asset: AssetLike,
options?: { force?: boolean },
) => Promise<AssetLike | null>;