diff --git a/frontend/src/app/LoginRoute.jsx b/frontend/src/app/LoginRoute.jsx index fcf7370..7bf9ab3 100644 --- a/frontend/src/app/LoginRoute.jsx +++ b/frontend/src/app/LoginRoute.jsx @@ -37,26 +37,24 @@ const LoginRoute = () => { let combined = extract(location.search); - if (typeof window !== 'undefined') { - const hash = window.location.hash || ''; - const queryIndex = hash.indexOf('?'); - if (queryIndex !== -1) { - const hashQuery = hash.slice(queryIndex + 1); - const hashParams = extract(`?${hashQuery}`); - combined = { - token: combined.token || hashParams.token, - username: combined.username || hashParams.username, - preferredTenantId: combined.preferredTenantId || hashParams.preferredTenantId, - }; - } - if (!combined.token) { - const searchParams = extract(window.location.search); - combined = { - token: combined.token || searchParams.token, - username: combined.username || searchParams.username, - preferredTenantId: combined.preferredTenantId || searchParams.preferredTenantId, - }; - } + const hash = window.location.hash || ''; + const queryIndex = hash.indexOf('?'); + if (queryIndex !== -1) { + const hashQuery = hash.slice(queryIndex + 1); + const hashParams = extract(`?${hashQuery}`); + combined = { + token: combined.token || hashParams.token, + username: combined.username || hashParams.username, + preferredTenantId: combined.preferredTenantId || hashParams.preferredTenantId, + }; + } + if (!combined.token) { + const searchParams = extract(window.location.search); + combined = { + token: combined.token || searchParams.token, + username: combined.username || searchParams.username, + preferredTenantId: combined.preferredTenantId || searchParams.preferredTenantId, + }; } return combined; @@ -96,10 +94,6 @@ const LoginRoute = () => { ); const clearMagicParamsFromUrl = useCallback(() => { - if (typeof window === 'undefined') { - return; - } - const removableKeys = ['magic_token', 'username', 'preferred_tenant_id']; const currentSearch = new URLSearchParams(window.location.search); let searchChanged = false; @@ -184,7 +178,7 @@ const LoginRoute = () => { const handlePasskeyLogin = useCallback( async (rawUsername) => { - const username = typeof rawUsername === 'string' ? rawUsername.trim() : ''; + const username = rawUsername?.trim?.() || ''; if (!username) { setStatusMessage('Enter your username before using a passkey.', 'error'); return; @@ -381,7 +375,7 @@ const LoginRoute = () => { const handleSignup = useCallback( async (rawUsername) => { - const username = typeof rawUsername === 'string' ? rawUsername.trim() : ''; + const username = rawUsername?.trim?.() || ''; if (!username) { setStatusMessage('Choose a username to create your account.', 'error'); return; @@ -459,8 +453,8 @@ const LoginRoute = () => { ); const redirectTarget = useMemo(() => { - const target = location.state?.from; - if (typeof target === 'string' && target.startsWith('/')) { + const target = String(location.state?.from ?? ''); + if (target.startsWith('/')) { return target; } return '/documents'; diff --git a/frontend/src/app/PanelManagerContext.js b/frontend/src/app/PanelManagerContext.js index 91fa6da..f0b38a1 100644 --- a/frontend/src/app/PanelManagerContext.js +++ b/frontend/src/app/PanelManagerContext.js @@ -12,8 +12,16 @@ import { useSidebarContext } from '../sidebar/SidebarContext'; const PanelManagerContext = createContext(null); const PANEL_LIMITS = { - sidebar: { minRatio: 1 / 6, maxRatio: 1 / 4 }, - detail: { minRatio: 1 / 4, maxRatio: 3 / 4 }, + sidebar: { + minRatio: 1 / 6, + maxRatio: 1 / 4, + minPx: 240, + }, + detail: { + minRatio: 1 / 4, + maxRatio: 3 / 4, + minPx: 320, + }, }; const STORAGE_KEYS = { @@ -30,8 +38,10 @@ const clampPanelWidth = (panel, value) => { return numeric; } const viewport = window.innerWidth; - const minLimit = Math.max(0, Math.round(viewport * limits.minRatio)); - const rawMax = Math.max(minLimit, Math.round(viewport * limits.maxRatio)); + const ratioMin = Math.round(viewport * limits.minRatio); + const ratioMax = Math.round(viewport * limits.maxRatio); + const minLimit = Math.max(0, Math.max(ratioMin, limits.minPx)); + const rawMax = Math.max(minLimit, ratioMax); const maxAllowed = Math.min(rawMax, viewport - 160); const targetMax = Math.max(minLimit, maxAllowed); return Math.min(Math.max(numeric, minLimit), targetMax); diff --git a/frontend/src/app/appLayoutUtils.js b/frontend/src/app/appLayoutUtils.js index 1896b6a..041b2a8 100644 --- a/frontend/src/app/appLayoutUtils.js +++ b/frontend/src/app/appLayoutUtils.js @@ -16,10 +16,12 @@ export const resolveApiPath = (path = '') => path; const makeRowKey = (type, id) => id ? `${type}${ROW_KEY_SEPARATOR}${id}` : `${type}${ROW_KEY_SEPARATOR}`; -const getRowType = (key) => (typeof key === 'string' ? key.split(ROW_KEY_SEPARATOR, 1)[0] : ''); +const getRowType = (key) => ( + typeof key?.split === 'function' ? key.split(ROW_KEY_SEPARATOR, 1)[0] : '' +); export const getRowId = (key) => { - if (typeof key !== 'string') return ''; + if (typeof key?.indexOf !== 'function' || typeof key?.slice !== 'function') return ''; const separatorIndex = key.indexOf(ROW_KEY_SEPARATOR); if (separatorIndex === -1) return key; return key.slice(separatorIndex + 1); diff --git a/frontend/src/app/appState.js b/frontend/src/app/appState.js index 8a63e46..cc7f6dd 100644 --- a/frontend/src/app/appState.js +++ b/frontend/src/app/appState.js @@ -1,7 +1,7 @@ import React, { useContext, useEffect, useMemo, useReducer } from 'react'; import api from '../lib/api'; -const storage = typeof window !== 'undefined' ? window.sessionStorage : undefined; +const storage = window.sessionStorage; const STORED_TOKEN = storage?.getItem('papercrate_token') ?? ''; let STORED_TENANT = null; diff --git a/frontend/src/app/useDocumentsPreferences.js b/frontend/src/app/useDocumentsPreferences.js index c02468f..5317616 100644 --- a/frontend/src/app/useDocumentsPreferences.js +++ b/frontend/src/app/useDocumentsPreferences.js @@ -11,9 +11,6 @@ const SORT_DIRECTION_STORAGE_KEY = 'papercrate_sort_direction'; const INCLUDE_DESCENDANTS_STORAGE_KEY = 'papercrate_include_descendants'; const readSessionStorage = (key) => { - if (typeof window === 'undefined') { - return null; - } try { return window.sessionStorage.getItem(key); } catch (error) { @@ -23,9 +20,6 @@ const readSessionStorage = (key) => { }; const writeSessionStorage = (key, value) => { - if (typeof window === 'undefined') { - return; - } try { window.sessionStorage.setItem(key, value); } catch (error) { diff --git a/frontend/src/app/useWorkspaceSelection.js b/frontend/src/app/useWorkspaceSelection.js index db31f4b..72d00ff 100644 --- a/frontend/src/app/useWorkspaceSelection.js +++ b/frontend/src/app/useWorkspaceSelection.js @@ -59,7 +59,7 @@ export const useWorkspaceSelection = ({ const selectEntry = useCallback( (entry, event) => { - const rowKey = typeof entry === 'string' ? entry : entry?.rowKey; + const rowKey = entry?.rowKey ?? (typeof entry?.split === 'function' ? entry : null); if (!rowKey) return; handleEntrySelection(rowKey, event); }, diff --git a/frontend/src/desktop/DesktopWorkspace.jsx b/frontend/src/desktop/DesktopWorkspace.jsx index 7b94936..a884121 100644 --- a/frontend/src/desktop/DesktopWorkspace.jsx +++ b/frontend/src/desktop/DesktopWorkspace.jsx @@ -251,7 +251,7 @@ const DesktopWorkspace = ({ commitSize(); - if (typeof ResizeObserver === 'undefined') { + if (!('ResizeObserver' in window)) { window.addEventListener('resize', commitSize); return () => { window.removeEventListener('resize', commitSize); @@ -292,7 +292,7 @@ const DesktopWorkspace = ({ const requestCanvasFocus = useCallback(() => { const canvas = containerRef.current; - if (!canvas || typeof canvas.focus !== 'function') { + if (!canvas?.focus) { return; } @@ -306,20 +306,14 @@ const DesktopWorkspace = ({ } }; - if (typeof window === 'undefined') { - focusTarget(); + const raf = window.requestAnimationFrame; + if (raf) { + raf(() => focusTarget()); return; } - - if (typeof window.requestAnimationFrame === 'function') { - window.requestAnimationFrame(() => { - focusTarget(); - }); - } else { - setTimeout(() => { - focusTarget(); - }, 0); - } + setTimeout(() => { + focusTarget(); + }, 0); }, []); const tagInteractions = useDeskTagInteractions({ @@ -756,8 +750,8 @@ const DesktopWorkspaceView = ({ return ( <>
{ - if (event.target === event.currentTarget && typeof onClearSelection === 'function') { - onClearSelection(); + if (event.target === event.currentTarget) { + onClearSelection?.(); } focusShell(); }} @@ -771,8 +765,8 @@ const DesktopWorkspaceView = ({ onDragLeave={handleCanvasDragLeave} onDrop={handleCanvasDrop} onPointerDown={(event) => { - if (event.target === event.currentTarget && typeof onClearSelection === 'function') { - onClearSelection(); + if (event.target === event.currentTarget) { + onClearSelection?.(); } focusShell(); }} diff --git a/frontend/src/desktop/db.js b/frontend/src/desktop/db.js index 2e571ce..e6a541e 100644 --- a/frontend/src/desktop/db.js +++ b/frontend/src/desktop/db.js @@ -10,12 +10,13 @@ const openDatabase = () => { } currentDbPromise.value = new Promise((resolve, reject) => { - if (typeof indexedDB === 'undefined') { + const dbApi = window.indexedDB; + if (!dbApi) { reject(new Error('IndexedDB not available')); return; } - const request = indexedDB.open(DB_NAME, DB_VERSION); + const request = dbApi.open(DB_NAME, DB_VERSION); request.onupgradeneeded = () => { const db = request.result; diff --git a/frontend/src/desktop/events.js b/frontend/src/desktop/events.js index e803064..6ff187e 100644 --- a/frontend/src/desktop/events.js +++ b/frontend/src/desktop/events.js @@ -14,7 +14,7 @@ export const preventAll = (event) => { } }; -export const safeInvoke = (fn, ...args) => (typeof fn === 'function' ? fn(...args) : undefined); +export const safeInvoke = (fn, ...args) => fn?.(...args); export const getPointerPosition = (event, { fallbackToPage = true } = {}) => { if (!event) { diff --git a/frontend/src/desktop/hooks/usePreviewMetadata.js b/frontend/src/desktop/hooks/usePreviewMetadata.js index 6078c21..2fa9fd7 100644 --- a/frontend/src/desktop/hooks/usePreviewMetadata.js +++ b/frontend/src/desktop/hooks/usePreviewMetadata.js @@ -20,8 +20,7 @@ const usePreviewMetadata = (documents, getDocumentAsset, ensureAssetUrl) => { } const docId = String(doc.id); - const resolveAsset = (type) => - (typeof getDocumentAsset === 'function' ? getDocumentAsset(doc, type) : null); + const resolveAsset = (type) => getDocumentAsset?.(doc, type) ?? null; let asset = resolveAsset('preview') || resolveAsset('thumbnail'); let view = createAssetView(asset); diff --git a/frontend/src/desktop/pointer/useDeskPointer.js b/frontend/src/desktop/pointer/useDeskPointer.js index 855f955..c932bf2 100644 --- a/frontend/src/desktop/pointer/useDeskPointer.js +++ b/frontend/src/desktop/pointer/useDeskPointer.js @@ -183,9 +183,6 @@ export const useDeskPointer = ({ } longPressActiveRef.current = true; - if (typeof window === 'undefined') { - return; - } longPressTimerRef.current = window.setTimeout(() => { if (!longPressActiveRef.current || pointerMovedRef.current) { @@ -229,8 +226,8 @@ export const useDeskPointer = ({ pointerMovedRef.current = false; resetLongPressState(); - const pointerButton = typeof event.button === 'number' ? event.button : 0; - const pointerType = typeof event.pointerType === 'string' ? event.pointerType : ''; + const pointerButton = Number.isFinite(event?.button) ? event.button : 0; + const pointerType = String(event?.pointerType ?? ''); const metaKey = (event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey; const modifierActive = Boolean(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey); @@ -322,12 +319,10 @@ export const useDeskPointer = ({ && !pointerState.longPressTriggered && pointerState.docId === doc.id ) { - const expectedButton = typeof pointerState.pointerButton === 'number' + const expectedButton = Number.isFinite(pointerState?.pointerButton) ? pointerState.pointerButton : 0; - const releasedButton = typeof event.button === 'number' - ? event.button - : expectedButton; + const releasedButton = Number.isFinite(event?.button) ? event.button : expectedButton; const isPrimaryRelease = expectedButton === 0 && releasedButton === 0; const stillSelected = Array.isArray(selectedDocumentIds) && selectedDocumentIds.includes(doc.id); @@ -401,11 +396,7 @@ export const useDeskPointer = ({ } } - if ( - typeof openOverlayForDoc === 'function' - && Array.isArray(selectedDocumentIds) - && selectedDocumentIds.length > 0 - ) { + if (openOverlayForDoc && Array.isArray(selectedDocumentIds) && selectedDocumentIds.length > 0) { event.preventDefault(); const targetId = selectedDocumentIds[selectedDocumentIds.length - 1]; if (targetId) { @@ -427,9 +418,7 @@ export const useDeskPointer = ({ handleShellKeyDown, focusShell: () => { const shell = containerRef.current; - if (shell && typeof shell.focus === 'function') { - shell.focus({ preventScroll: true }); - } + shell?.focus?.({ preventScroll: true }); }, }; }; diff --git a/frontend/src/desktop/tags/useDeskTagInteractions.js b/frontend/src/desktop/tags/useDeskTagInteractions.js index b1c4e7b..cc21235 100644 --- a/frontend/src/desktop/tags/useDeskTagInteractions.js +++ b/frontend/src/desktop/tags/useDeskTagInteractions.js @@ -51,9 +51,6 @@ export const useDeskTagInteractions = ({ const removalCursorActiveRef = useRef(false); const updateRemovalCursor = useCallback((active) => { - if (typeof document === 'undefined') { - return; - } if (removalCursorActiveRef.current === active) { return; } @@ -100,8 +97,9 @@ export const useDeskTagInteractions = ({ } }; const scheduleShowNode = () => { - if (typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function') { - window.requestAnimationFrame(showNode); + const raf = window.requestAnimationFrame; + if (raf) { + raf(showNode); } else { setTimeout(showNode, 0); } diff --git a/frontend/src/desktop/useDocumentDrag.js b/frontend/src/desktop/useDocumentDrag.js index 354e20c..db4dacd 100644 --- a/frontend/src/desktop/useDocumentDrag.js +++ b/frontend/src/desktop/useDocumentDrag.js @@ -9,11 +9,15 @@ const DRAG_HYSTERESIS_SQUARED = DRAG_HYSTERESIS_PX * DRAG_HYSTERESIS_PX; const EDGE_COLLISION_THRESHOLD = 0.5; const getEventTargetElement = (event) => { - if (typeof Element === 'undefined' || !event) { + if (!event) { + return null; + } + const ElementCtor = window.Element; + if (!ElementCtor) { return null; } const candidate = event.target || (event.nativeEvent ? event.nativeEvent.target : null); - return candidate instanceof Element ? candidate : null; + return candidate instanceof ElementCtor ? candidate : null; }; const useDocumentDrag = (options = {}) => { @@ -63,9 +67,7 @@ const useDocumentDrag = (options = {}) => { openOverlayForDoc(data.docId, data.originInfo); return; } - if (typeof onInspectDocument === 'function') { - onInspectDocument(data.docId, event); - } + onInspectDocument?.(data.docId, event); }, }); const dragStateRef = useRef(null); @@ -83,7 +85,7 @@ const useDocumentDrag = (options = {}) => { const clearDragTransforms = useCallback(() => { const map = dragTransformsRef?.current; - if (!map || typeof map.clear !== 'function') { + if (!map?.clear) { return; } map.clear(); @@ -118,7 +120,7 @@ const useDocumentDrag = (options = {}) => { const state = dragStateRef.current; if (state && state.pointerId === pointerId) { const capturedTarget = state.capturedTarget; - if (capturedTarget && typeof capturedTarget.releasePointerCapture === 'function') { + if (capturedTarget?.releasePointerCapture) { try { capturedTarget.releasePointerCapture(pointerId); } catch (error) { @@ -141,7 +143,7 @@ const useDocumentDrag = (options = {}) => { const handlePointerDown = useCallback( (event, docIdInput, options = {}) => { const targetElement = getEventTargetElement(event); - if (targetElement && typeof targetElement.closest === 'function' && targetElement.closest('[data-desk-tag-chip="true"]')) { + if (targetElement?.closest && targetElement.closest('[data-desk-tag-chip="true"]')) { return; } preventAll(event); @@ -167,9 +169,8 @@ const useDocumentDrag = (options = {}) => { : null; const stackSelectionAppliedInitial = Boolean(options?.stackSelectionApplied); const wasSelectedAtPointerDown = Boolean(options?.wasSelected); - const pointerModifierActive = typeof options?.modifierActive === 'boolean' - ? options.modifierActive - : Boolean(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey); + const pointerModifierActive = + options?.modifierActive ?? Boolean(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey); const stackReplace = Boolean(options?.stackReplace); let selectionIds = Array.isArray(selectedDocumentIds) @@ -227,8 +228,8 @@ const useDocumentDrag = (options = {}) => { const entry = layoutRef.current.get(docKey) || null; const defaultCenterX = canvasPadding + docWidth / 2; const defaultCenterY = canvasPadding + docHeight / 2; - const centerX = typeof entry?.centerX === 'number' ? entry.centerX : defaultCenterX; - const centerY = typeof entry?.centerY === 'number' ? entry.centerY : defaultCenterY; + const centerX = Number.isFinite(entry?.centerX) ? entry.centerX : defaultCenterX; + const centerY = Number.isFinite(entry?.centerY) ? entry.centerY : defaultCenterY; const modifierPressed = pointerModifierActive; if (!modifierPressed) { @@ -255,7 +256,7 @@ const useDocumentDrag = (options = {}) => { } const capturedTarget = event.currentTarget instanceof HTMLElement ? event.currentTarget : null; - if (capturedTarget && typeof capturedTarget.setPointerCapture === 'function') { + if (capturedTarget?.setPointerCapture) { try { capturedTarget.setPointerCapture(event.pointerId); } catch (error) { @@ -286,9 +287,9 @@ const useDocumentDrag = (options = {}) => { const itemHeight = itemSize.height || docHeight; const itemEntry = layoutRef.current.get(id) || null; const itemCenterX = - typeof itemEntry?.centerX === 'number' ? itemEntry.centerX : canvasPadding + itemWidth / 2; + Number.isFinite(itemEntry?.centerX) ? itemEntry.centerX : canvasPadding + itemWidth / 2; const itemCenterY = - typeof itemEntry?.centerY === 'number' ? itemEntry.centerY : canvasPadding + itemHeight / 2; + Number.isFinite(itemEntry?.centerY) ? itemEntry.centerY : canvasPadding + itemHeight / 2; const baseOffsetX = itemCenterX - centerX; const baseOffsetY = itemCenterY - centerY; const initialRotation = itemEntry?.rotation ?? 0; @@ -310,9 +311,9 @@ const useDocumentDrag = (options = {}) => { }); const eventTimestamp = - typeof event.timeStamp === 'number' && Number.isFinite(event.timeStamp) + (Number.isFinite(event?.timeStamp)) ? event.timeStamp - : typeof performance !== 'undefined' && typeof performance.now === 'function' + : performance?.now ? performance.now() : Date.now(); @@ -540,9 +541,9 @@ const useDocumentDrag = (options = {}) => { state.lastClientX = event.clientX; state.lastClientY = event.clientY; state.lastTimestamp = - typeof event.timeStamp === 'number' && Number.isFinite(event.timeStamp) + (Number.isFinite(event?.timeStamp)) ? event.timeStamp - : typeof performance !== 'undefined' && typeof performance.now === 'function' + : performance?.now ? performance.now() : Date.now(); @@ -663,9 +664,9 @@ const useDocumentDrag = (options = {}) => { Math.abs(pointerLocalX) <= halfWidth && Math.abs(pointerLocalY) <= halfHeight; const currentTimestamp = - typeof event.timeStamp === 'number' && Number.isFinite(event.timeStamp) + (Number.isFinite(event?.timeStamp)) ? event.timeStamp - : typeof performance !== 'undefined' && typeof performance.now === 'function' + : performance?.now ? performance.now() : Date.now(); const previousTimestamp = state.lastTimestamp ?? currentTimestamp; diff --git a/frontend/src/desktop/workspaceEngine.js b/frontend/src/desktop/workspaceEngine.js index 08e5a38..fb9ffe1 100644 --- a/frontend/src/desktop/workspaceEngine.js +++ b/frontend/src/desktop/workspaceEngine.js @@ -688,25 +688,19 @@ export class WorkspaceEngine { if (!key) { return; } - if (typeof window === 'undefined') { - this.inertiaAnimations.delete(key); - return; - } const existing = this.inertiaAnimations.get(key); - if (existing && typeof window.cancelAnimationFrame === 'function') { + if (existing?.frameId != null) { window.cancelAnimationFrame(existing.frameId); } this.inertiaAnimations.delete(key); } disposeInertiaAnimations() { - if (typeof window !== 'undefined' && typeof window.cancelAnimationFrame === 'function') { - this.inertiaAnimations.forEach((animation) => { - if (animation?.frameId != null) { - window.cancelAnimationFrame(animation.frameId); - } - }); - } + this.inertiaAnimations.forEach((animation) => { + if (animation?.frameId != null) { + window.cancelAnimationFrame(animation.frameId); + } + }); this.inertiaAnimations.clear(); } @@ -768,7 +762,8 @@ export class WorkspaceEngine { } startInertiaAnimation(docId, baseState) { - if (typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function') { + const raf = window.requestAnimationFrame; + if (!raf) { return; } const key = docId != null ? String(docId) : null; @@ -778,10 +773,7 @@ export class WorkspaceEngine { this.cancelInertiaAnimation(key); - const now = - typeof performance !== 'undefined' && typeof performance.now === 'function' - ? performance.now() - : Date.now(); + const now = performance?.now ? performance.now() : Date.now(); const simulationState = { ...baseState, @@ -807,10 +799,10 @@ export class WorkspaceEngine { this.persistLayoutSnapshot(); return; } - simulationState.frameId = window.requestAnimationFrame(step); + simulationState.frameId = raf(step); }; - simulationState.frameId = window.requestAnimationFrame(step); + simulationState.frameId = raf(step); this.inertiaAnimations.set(key, simulationState); } @@ -885,14 +877,10 @@ export class WorkspaceEngine { } }; - if (typeof window !== 'undefined' && typeof window.setTimeout === 'function') { - this.persistDebounceId = window.setTimeout(() => { - this.persistDebounceId = null; - void persistTask(); - }, 100); - } else { - await persistTask(); - } + this.persistDebounceId = window.setTimeout(() => { + this.persistDebounceId = null; + void persistTask(); + }, 100); } ensureLayoutForItems() { @@ -1223,8 +1211,9 @@ export const useWorkspaceSnapshot = (engine, useSyncExternalStoreHook) => { }; /* istanbul ignore next */ -if (typeof module !== 'undefined' && module && module.exports) { - module.exports = { +const commonJsModule = globalThis?.module; +if (commonJsModule?.exports) { + commonJsModule.exports = { WorkspaceEngine, DESK_CANVAS_PADDING, DESK_ROTATION_RANGE, diff --git a/frontend/src/detail/PreviewZoomOverlay.jsx b/frontend/src/detail/PreviewZoomOverlay.jsx index 2426f2c..cd151c0 100644 --- a/frontend/src/detail/PreviewZoomOverlay.jsx +++ b/frontend/src/detail/PreviewZoomOverlay.jsx @@ -5,19 +5,12 @@ import { clamp } from '../utils/math'; const noop = () => {}; -const ensureDocumentRoot = () => { - if (typeof document === 'undefined') { - return null; - } - return document.body; -}; - const PreviewZoomOverlay = ({ open = false, display = null, onClose = noop, }) => { - const portalTarget = ensureDocumentRoot(); + const portalTarget = document.body; const [isNativeScale, setIsNativeScale] = useState(false); const [naturalSize, setNaturalSize] = useState({ width: null, height: null }); const [renderBackdrop, setRenderBackdrop] = useState(false); @@ -125,21 +118,13 @@ const PreviewZoomOverlay = ({ useEffect(() => { if (!open) { - if (previouslyFocusedRef.current && typeof previouslyFocusedRef.current.focus === 'function') { - previouslyFocusedRef.current.focus(); - } + previouslyFocusedRef.current?.focus?.(); previouslyFocusedRef.current = null; return; } - if (typeof document !== 'undefined') { - const active = document.activeElement; - if (active && typeof active.focus === 'function') { - previouslyFocusedRef.current = active; - } else { - previouslyFocusedRef.current = null; - } - } + const active = document.activeElement; + previouslyFocusedRef.current = active?.focus ? active : null; }, [open]); const activeDisplay = open && display?.url ? display : displaySnapshot; @@ -163,10 +148,7 @@ const PreviewZoomOverlay = ({ } const frame = requestAnimationFrame(() => { - const scrollEl = scrollRef.current; - if (scrollEl && typeof scrollEl.focus === 'function') { - scrollEl.focus({ preventScroll: true }); - } + scrollRef.current?.focus?.({ preventScroll: true }); }); return () => cancelAnimationFrame(frame); diff --git a/frontend/src/documents/DocumentSummarySection.jsx b/frontend/src/documents/DocumentSummarySection.jsx index 1d1319b..4c700a5 100644 --- a/frontend/src/documents/DocumentSummarySection.jsx +++ b/frontend/src/documents/DocumentSummarySection.jsx @@ -18,7 +18,7 @@ export const sortCorrespondents = (entries = []) => export const buildCorrespondentOptions = (entries = []) => { const seen = new Set(); return entries.reduce((options, entry) => { - const name = typeof entry?.name === 'string' ? entry.name.trim() : ''; + const name = entry?.name?.trim?.() || ''; if (!name) { return options; } @@ -38,20 +38,14 @@ const normalizeQuickAddOption = (option) => { if (option == null) { return null; } - if (typeof option === 'string') { - const label = option.trim(); - return label ? { id: label, label, original: option } : null; - } - const label = typeof option.label === 'string' - ? option.label.trim() - : typeof option.name === 'string' - ? option.name.trim() - : ''; + const isObject = typeof option === 'object'; + const labelSource = isObject ? option.label ?? option.name ?? '' : option; + const label = labelSource?.trim?.() || ''; if (!label) { return null; } return { - id: option.id ?? label, + id: isObject && option.id ? option.id : label, label, original: option, }; @@ -75,9 +69,10 @@ export const TagSection = ({ const handleSelect = useCallback( (option) => { if (!onAdd) return; - const label = - (option && typeof option === 'object' && option.label) || - (typeof option === 'string' ? option : ''); + const labelSource = option && typeof option === 'object' + ? option.label ?? option.name ?? '' + : option; + const label = labelSource?.trim?.() || ''; if (!label) { return; } @@ -117,7 +112,7 @@ export const TagSection = ({ }); tags.forEach((tag) => { - const label = typeof tag?.label === 'string' ? tag.label.trim() : ''; + const label = tag?.label?.trim?.() || ''; if (!label) { return; } @@ -244,7 +239,7 @@ export const CorrespondentSection = ({ }); entries.forEach((entry) => { - const label = typeof entry?.name === 'string' ? entry.name.trim() : ''; + const label = entry?.name?.trim?.() || ''; if (!label) { return; } @@ -274,9 +269,10 @@ export const CorrespondentSection = ({ } const source = item.payload ?? item; const resolvedName = - (source && typeof source.name === 'string' && source.name.trim()) - || (typeof source === 'string' ? source.trim() : '') - || (source && typeof source.label === 'string' ? source.label.trim() : ''); + source?.name?.trim?.() + || source?.label?.trim?.() + || source?.trim?.() + || ''; if (!resolvedName) { return; } diff --git a/frontend/src/documents/DocumentThumbnailImage.jsx b/frontend/src/documents/DocumentThumbnailImage.jsx index aeb038a..a1eef99 100644 --- a/frontend/src/documents/DocumentThumbnailImage.jsx +++ b/frontend/src/documents/DocumentThumbnailImage.jsx @@ -24,7 +24,7 @@ const useLazyVisibility = (rootRef, resetKey) => { return undefined; } - if (typeof window === 'undefined' || typeof IntersectionObserver === 'undefined') { + if (!('IntersectionObserver' in window)) { setIsVisible(true); return undefined; } diff --git a/frontend/src/documents/SelectionAssignmentMenu.jsx b/frontend/src/documents/SelectionAssignmentMenu.jsx index ca75ed8..746731a 100644 --- a/frontend/src/documents/SelectionAssignmentMenu.jsx +++ b/frontend/src/documents/SelectionAssignmentMenu.jsx @@ -10,15 +10,24 @@ const STATE_ORDER = { const normalizeItems = (items) => (Array.isArray(items) ? items : []) - .filter((item) => item && typeof item.label === 'string' && item.label.trim().length > 0) - .map((item) => ({ - id: item.id ?? item.label, - label: item.label.trim(), - state: item.state === 'all' ? 'all' : item.state === 'partial' ? 'partial' : 'none', - count: typeof item.count === 'number' ? item.count : null, - total: typeof item.total === 'number' ? item.total : null, - payload: item.payload ?? item, - })); + .map((item) => { + if (!item) { + return null; + } + const trimmedLabel = item.label?.trim?.() || ''; + if (!trimmedLabel) { + return null; + } + return { + id: item.id ?? trimmedLabel, + label: trimmedLabel, + state: item.state === 'all' ? 'all' : item.state === 'partial' ? 'partial' : 'none', + count: typeof item.count === 'number' ? item.count : null, + total: typeof item.total === 'number' ? item.total : null, + payload: item.payload ?? item, + }; + }) + .filter(Boolean); const SelectionAssignmentMenu = ({ label, diff --git a/frontend/src/documents/SelectionFloatingActions.jsx b/frontend/src/documents/SelectionFloatingActions.jsx index 04dd260..e326f97 100644 --- a/frontend/src/documents/SelectionFloatingActions.jsx +++ b/frontend/src/documents/SelectionFloatingActions.jsx @@ -34,9 +34,8 @@ const buildFolderTreeOptions = (tree) => { if (!node || !node.id) { return; } - const name = typeof node.name === 'string' && node.name.trim().length - ? node.name.trim() - : 'Folder'; + const trimmedName = node?.name?.trim?.(); + const name = trimmedName?.length ? trimmedName : 'Folder'; const nextSegments = parentSegments.concat([name]); const label = nextSegments.join('/'); entries.push({ id: node.id, label }); diff --git a/frontend/src/documents/correspondents.js b/frontend/src/documents/correspondents.js index f13c694..a2e990e 100644 --- a/frontend/src/documents/correspondents.js +++ b/frontend/src/documents/correspondents.js @@ -8,11 +8,7 @@ export const resolveCorrespondents = (doc) => { doc.correspondents.forEach((entry = {}, index) => { const { id, name } = entry; - if (typeof name !== 'string') { - return; - } - - const trimmedName = name.trim(); + const trimmedName = name?.trim?.(); if (!trimmedName) { return; } diff --git a/frontend/src/documents/hooks/useBulkDocumentActions.js b/frontend/src/documents/hooks/useBulkDocumentActions.js index 64bdea9..798163c 100644 --- a/frontend/src/documents/hooks/useBulkDocumentActions.js +++ b/frontend/src/documents/hooks/useBulkDocumentActions.js @@ -16,7 +16,7 @@ const useBulkDocumentActions = ({ }) => { const handleBulkCorrespondentAdd = useCallback( async ({ name, input, documentIds }) => { - const trimmed = typeof name === 'string' ? name.trim() : ''; + const trimmed = name?.trim?.() || ''; if (!trimmed) { setStatusMessage('Correspondent name is required.', 'error'); return; diff --git a/frontend/src/documents/useEntryPointer.js b/frontend/src/documents/useEntryPointer.js index 4d5cb13..88cca78 100644 --- a/frontend/src/documents/useEntryPointer.js +++ b/frontend/src/documents/useEntryPointer.js @@ -10,7 +10,7 @@ export const isPrimaryPointerEvent = (event) => { if (typeof event.button === 'number' && event.button !== 0) { return false; } - const type = typeof event.type === 'string' ? event.type.toLowerCase() : ''; + const type = event?.type?.toLowerCase?.() ?? ''; return type === 'click' || type === 'pointerdown' || type === 'pointerup'; }; diff --git a/frontend/src/documents/useInlineRename.js b/frontend/src/documents/useInlineRename.js index 0961336..55e6589 100644 --- a/frontend/src/documents/useInlineRename.js +++ b/frontend/src/documents/useInlineRename.js @@ -6,15 +6,14 @@ const focusInput = (node) => { } const applyFocus = () => { node.focus(); - if (typeof node.select === 'function') { - node.select(); - } + node.select?.(); }; - if (typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function') { - window.requestAnimationFrame(applyFocus); - } else { - applyFocus(); + const raf = window.requestAnimationFrame; + if (raf) { + raf(applyFocus); + return; } + applyFocus(); }; const identity = (value) => value; diff --git a/frontend/src/hooks/documents/useAuthManager.js b/frontend/src/hooks/documents/useAuthManager.js index 63b5f19..c25684a 100644 --- a/frontend/src/hooks/documents/useAuthManager.js +++ b/frontend/src/hooks/documents/useAuthManager.js @@ -68,7 +68,7 @@ const useAuthManager = ({ } const status = response.status; - const url = typeof config.url === 'string' ? config.url : ''; + const url = String(config?.url ?? ''); const isAuthRoute = url.includes('/auth/login') || url.includes('/auth/refresh'); if (status === 401 && !config._retry && !isAuthRoute) { diff --git a/frontend/src/hooks/documents/useCorrespondents.js b/frontend/src/hooks/documents/useCorrespondents.js index cc6c281..501a09b 100644 --- a/frontend/src/hooks/documents/useCorrespondents.js +++ b/frontend/src/hooks/documents/useCorrespondents.js @@ -32,7 +32,7 @@ const useCorrespondents = ({ } const payload = {}; - if (typeof changes.name === 'string') { + if (typeof changes?.name?.trim === 'function') { const trimmed = changes.name.trim(); if (!trimmed) { throw new Error('Correspondent name cannot be empty.'); @@ -60,7 +60,7 @@ const useCorrespondents = ({ const handleCorrespondentCreate = useCallback( async ({ name }) => { - const trimmed = typeof name === 'string' ? name.trim() : ''; + const trimmed = name?.trim?.() || ''; if (!trimmed) { throw new Error('Correspondent name is required.'); } diff --git a/frontend/src/hooks/documents/useDocumentCorrespondentActions.js b/frontend/src/hooks/documents/useDocumentCorrespondentActions.js index b618d47..93c7476 100644 --- a/frontend/src/hooks/documents/useDocumentCorrespondentActions.js +++ b/frontend/src/hooks/documents/useDocumentCorrespondentActions.js @@ -72,7 +72,7 @@ const useDocumentCorrespondentActions = ({ if (!document?.id) { throw new Error('Missing document for correspondent assignment.'); } - const trimmed = typeof name === 'string' ? name.trim() : ''; + const trimmed = name?.trim?.() || ''; if (!trimmed) { setStatusMessage('Correspondent name is required.', 'error'); return; diff --git a/frontend/src/hooks/documents/useDocumentDragHandlers.js b/frontend/src/hooks/documents/useDocumentDragHandlers.js index b131e6d..6d0c2c9 100644 --- a/frontend/src/hooks/documents/useDocumentDragHandlers.js +++ b/frontend/src/hooks/documents/useDocumentDragHandlers.js @@ -29,10 +29,6 @@ const useDocumentDragHandlers = ({ ({ documents = [], folders = [] } = {}) => { destroyDragPreview(); - if (typeof document === 'undefined') { - return null; - } - const docEntries = (documents || []).filter(Boolean); const folderEntries = (folders || []).filter(Boolean); const totalCount = docEntries.length + folderEntries.length; @@ -108,7 +104,7 @@ const useDocumentDragHandlers = ({ } } else { const payload = item.payload; - const folderId = typeof payload === 'string' ? payload : payload?.id; + const folderId = payload?.id ?? (typeof payload?.trim === 'function' ? payload : null); const rowEl = folderId ? document.getElementById(`folder-row-${folderId}`) || document.getElementById(`folder-card-${folderId}`) @@ -160,7 +156,7 @@ const useDocumentDragHandlers = ({ const handleDocumentDragStart = useCallback( (event, documentOrId) => { - const documentId = typeof documentOrId === 'string' ? documentOrId : documentOrId?.id; + const documentId = documentOrId?.id ?? (typeof documentOrId?.trim === 'function' ? documentOrId : null); if (!documentId) { return; } diff --git a/frontend/src/hooks/documents/useDocumentMutations.js b/frontend/src/hooks/documents/useDocumentMutations.js index 9725cd1..cb8309b 100644 --- a/frontend/src/hooks/documents/useDocumentMutations.js +++ b/frontend/src/hooks/documents/useDocumentMutations.js @@ -313,7 +313,7 @@ const useDocumentMutations = ({ const handleDocumentTitleUpdate = useCallback( async (documentId, nextTitle) => { - const trimmed = typeof nextTitle === 'string' ? nextTitle.trim() : ''; + const trimmed = nextTitle?.trim?.() || ''; if (!trimmed) { setStatusMessage('Document title cannot be empty.', 'error'); return false; @@ -418,7 +418,7 @@ const useDocumentMutations = ({ const resolveTagForCache = () => { const lookupTag = tagLookupById.get(tagId); const source = lookupTag ?? tagData; - if (!source || source.id == null || typeof source.label !== 'string') { + if (!source || source.id == null || typeof source.label?.trim !== 'function') { return null; } return { diff --git a/frontend/src/hooks/documents/useDocumentTagging.js b/frontend/src/hooks/documents/useDocumentTagging.js index 6347ec3..b8982f1 100644 --- a/frontend/src/hooks/documents/useDocumentTagging.js +++ b/frontend/src/hooks/documents/useDocumentTagging.js @@ -98,7 +98,7 @@ const useDocumentTagging = ({ const handleBulkTagAddFromDetail = useCallback( async ({ label, input, documentIds }) => { - const trimmed = typeof label === 'string' ? label.trim() : ''; + const trimmed = label?.trim?.() || ''; if (!trimmed) { setStatusMessage('Enter a tag label.', 'error'); return; @@ -131,7 +131,7 @@ const useDocumentTagging = ({ const handleBulkTagRemoveFromDetail = useCallback( async ({ label, input, documentIds }) => { - const trimmed = typeof label === 'string' ? label.trim() : ''; + const trimmed = label?.trim?.() || ''; if (!trimmed) { setStatusMessage('Enter a tag label to remove.', 'error'); return; @@ -201,4 +201,3 @@ const useDocumentTagging = ({ }; export default useDocumentTagging; - diff --git a/frontend/src/hooks/documents/useDocumentUploads.js b/frontend/src/hooks/documents/useDocumentUploads.js index 26dab19..cb3c2a1 100644 --- a/frontend/src/hooks/documents/useDocumentUploads.js +++ b/frontend/src/hooks/documents/useDocumentUploads.js @@ -10,8 +10,7 @@ const mapFilesToEntries = (filesInput) => { return files .filter(Boolean) .map((file) => { - const relativePath = - typeof file.webkitRelativePath === 'string' ? file.webkitRelativePath : ''; + const relativePath = file?.webkitRelativePath ?? ''; const segments = relativePath ? relativePath .split('/') @@ -213,10 +212,7 @@ const useDocumentUploads = ({ const fileFromItem = typeof item.getAsFile === 'function' ? item.getAsFile() : null; if (fileFromItem) { - const relativePath = - typeof fileFromItem.webkitRelativePath === 'string' - ? fileFromItem.webkitRelativePath - : ''; + const relativePath = fileFromItem?.webkitRelativePath ?? ''; const segments = relativePath ? relativePath .split('/') @@ -246,8 +242,7 @@ const useDocumentUploads = ({ Array.from(dataTransfer.files || []).forEach((file) => { if (!file) return; - const relativePath = - typeof file.webkitRelativePath === 'string' ? file.webkitRelativePath : ''; + const relativePath = file?.webkitRelativePath ?? ''; const segments = relativePath ? relativePath .split('/') diff --git a/frontend/src/hooks/documents/useDocumentsWorkspace.js b/frontend/src/hooks/documents/useDocumentsWorkspace.js index b376d81..6fcb5e0 100644 --- a/frontend/src/hooks/documents/useDocumentsWorkspace.js +++ b/frontend/src/hooks/documents/useDocumentsWorkspace.js @@ -135,9 +135,6 @@ const useDocumentsWorkspace = ({ const tenantIdRef = useRef(currentTenantId); const detailPanelControlRef = useRef({ open: () => {}, close: () => {} }); const setTagRemovalCursor = useCallback((active) => { - if (typeof document === 'undefined') { - return; - } if (tagRemovalCursorActiveRef.current === active) { return; } diff --git a/frontend/src/hooks/documents/useFolderTreeActions.js b/frontend/src/hooks/documents/useFolderTreeActions.js index 29da7b1..2c53bb9 100644 --- a/frontend/src/hooks/documents/useFolderTreeActions.js +++ b/frontend/src/hooks/documents/useFolderTreeActions.js @@ -201,7 +201,7 @@ const useFolderTreeActions = ({ setStatusMessage('Log in to rename folders.', 'error'); return false; } - const trimmed = typeof nextName === 'string' ? nextName.trim() : ''; + const trimmed = nextName?.trim?.() || ''; if (!trimmed) { setStatusMessage('Folder name cannot be empty.', 'error'); return false; diff --git a/frontend/src/hooks/documents/useTags.js b/frontend/src/hooks/documents/useTags.js index 0f54b42..604bcc9 100644 --- a/frontend/src/hooks/documents/useTags.js +++ b/frontend/src/hooks/documents/useTags.js @@ -34,7 +34,7 @@ const useTags = ({ } const payload = {}; - if (typeof changes.label === 'string') { + if (typeof changes?.label?.trim === 'function') { payload.label = changes.label; } if (Object.prototype.hasOwnProperty.call(changes, 'color')) { diff --git a/frontend/src/hooks/useApiError.js b/frontend/src/hooks/useApiError.js index 3da486e..6ec26bb 100644 --- a/frontend/src/hooks/useApiError.js +++ b/frontend/src/hooks/useApiError.js @@ -4,7 +4,7 @@ const noop = () => {}; const normalizeMessage = (error) => { if (!error) return 'Something went wrong.'; - if (typeof error === 'string') { + if (typeof error?.trim === 'function') { return error; } const { response, message } = error; diff --git a/frontend/src/preview/useViewerLayoutMode.js b/frontend/src/preview/useViewerLayoutMode.js index 1579b9e..8698899 100644 --- a/frontend/src/preview/useViewerLayoutMode.js +++ b/frontend/src/preview/useViewerLayoutMode.js @@ -4,10 +4,6 @@ export const DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO = 0.4; const PORTRAIT_RATIO_STYLE_ID = 'document-viewer-portrait-ratio-style'; const ensurePortraitRatioStyle = () => { - if (typeof document === 'undefined') { - return; - } - const cssValue = String(DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO); const cssText = `:root { --document-viewer-portrait-height-ratio: ${cssValue}; }`; @@ -23,12 +19,7 @@ const ensurePortraitRatioStyle = () => { } }; -const computeStackedLayoutBreakpoint = () => { - if (typeof window === 'undefined') { - return 900; - } - return window.innerWidth / 2; -}; +const computeStackedLayoutBreakpoint = () => window.innerWidth / 2; export const useViewerLayoutMode = (ref, dependency) => { const [isStacked, setIsStacked] = useState(false); @@ -61,7 +52,7 @@ export const useViewerLayoutMode = (ref, dependency) => { measure(); - if (typeof ResizeObserver === 'undefined') { + if (!('ResizeObserver' in window)) { window.addEventListener('resize', measure); return () => { if (frame) { diff --git a/frontend/src/settings/sections/ApiTokensSection.jsx b/frontend/src/settings/sections/ApiTokensSection.jsx index afe743a..39af8f2 100644 --- a/frontend/src/settings/sections/ApiTokensSection.jsx +++ b/frontend/src/settings/sections/ApiTokensSection.jsx @@ -39,8 +39,7 @@ const ApiTokensSection = ({ const [newTokenExpires, setNewTokenExpires] = useState(''); const [newTokenCapabilitySetId, setNewTokenCapabilitySetId] = useState(''); const [formError, setFormError] = useState(null); - const supportsClipboardWrite = typeof navigator !== 'undefined' - && Boolean(navigator?.clipboard?.writeText); + const supportsClipboardWrite = Boolean(navigator?.clipboard?.writeText); const [canCopyToken, setCanCopyToken] = useState(supportsClipboardWrite); const [copyFeedback, setCopyFeedback] = useState(null); @@ -61,7 +60,7 @@ const ApiTokensSection = ({ const capabilitySelectionOptions = useMemo(() => ( Array.isArray(capabilities) ? capabilities.map((capability) => { - if (typeof capability !== 'string') { + if (typeof capability?.split !== 'function') { return { value: capability, label: String(capability) }; } const [namespace, action] = capability.split(':'); @@ -147,9 +146,7 @@ const ApiTokensSection = ({ useEffect(() => { setCopyFeedback(null); - if (typeof navigator !== 'undefined') { - setCanCopyToken(Boolean(navigator?.clipboard?.writeText)); - } + setCanCopyToken(Boolean(navigator?.clipboard?.writeText)); }, [createdToken]); const handleNewCapabilitySetChange = useCallback((event) => { diff --git a/frontend/src/settings/sections/CapabilitySetsSection.jsx b/frontend/src/settings/sections/CapabilitySetsSection.jsx index 27ee405..9e59c55 100644 --- a/frontend/src/settings/sections/CapabilitySetsSection.jsx +++ b/frontend/src/settings/sections/CapabilitySetsSection.jsx @@ -37,7 +37,7 @@ const CapabilitySetsSection = ({ const capabilitySelectionOptions = useMemo(() => ( Array.isArray(capabilities) ? capabilities.map((capability) => { - if (typeof capability !== 'string') { + if (typeof capability?.split !== 'function') { return { value: capability, label: String(capability) }; } const [namespace, action] = capability.split(':'); diff --git a/frontend/src/sidebar/Sidebar.jsx b/frontend/src/sidebar/Sidebar.jsx index d738677..13d99de 100644 --- a/frontend/src/sidebar/Sidebar.jsx +++ b/frontend/src/sidebar/Sidebar.jsx @@ -377,7 +377,7 @@ const Sidebar = ({ }, [cycleThemeMode]); const themeMenuSection = - typeof neutralHue === 'number' || typeof neutralHue === 'string' + neutralHue != null ? (
@@ -604,9 +604,7 @@ const Sidebar = ({ size={16} /> - {tenantMenuOpen - && tenantMenuStyle - && typeof document !== 'undefined' + {tenantMenuOpen && tenantMenuStyle ? createPortal(
{ mode: DEFAULT_THEME_MODE, }; - if (typeof window === 'undefined') { - if (typeof document !== 'undefined') { - const root = document.documentElement; - const current = root.style.getPropertyValue('--neutral-hue'); - const parsed = Number.parseInt(current, 10); - return { - neutralHue: Number.isNaN(parsed) ? defaults.neutralHue : parsed, - neutralChroma: defaults.neutralChroma, - neutralContrast: defaults.neutralContrast, - mode: defaults.mode, - }; - } - return defaults; - } - const root = document.documentElement; const readNumberVar = (name, fallback) => { const inlineValue = root.style.getPropertyValue(name); @@ -48,12 +33,10 @@ const loadInitialThemeSettings = () => { if (!Number.isNaN(inlineParsed)) { return inlineParsed; } - if (typeof window !== 'undefined' && window.getComputedStyle) { - const computedValue = window.getComputedStyle(root).getPropertyValue(name); - const computedParsed = Number.parseFloat(computedValue); - if (!Number.isNaN(computedParsed)) { - return computedParsed; - } + const computedValue = window.getComputedStyle(root).getPropertyValue(name); + const computedParsed = Number.parseFloat(computedValue); + if (!Number.isNaN(computedParsed)) { + return computedParsed; } return fallback; }; @@ -120,9 +103,6 @@ const loadInitialThemeSettings = () => { }; const loadInitialCollapsedState = (defaultValue) => { - if (typeof window === 'undefined') { - return Boolean(defaultValue); - } try { const stored = window.sessionStorage.getItem(SIDEBAR_COLLAPSE_STORAGE_KEY); if (stored === '1' || stored === 'true') { @@ -146,30 +126,18 @@ export const SidebarProvider = ({ initialCollapsed = false, children }) => { const [themeMode, setThemeModeState] = useState(initialTheme.mode); useEffect(() => { - if (typeof document === 'undefined') { - return; - } document.documentElement.style.setProperty('--neutral-hue', `${neutralHue}deg`); }, [neutralHue]); useEffect(() => { - if (typeof document === 'undefined') { - return; - } document.documentElement.style.setProperty('--neutral-chroma', String(neutralChroma)); }, [neutralChroma]); useEffect(() => { - if (typeof document === 'undefined') { - return; - } document.documentElement.style.setProperty('--neutral-contrast', String(neutralContrast)); }, [neutralContrast]); useEffect(() => { - if (typeof document === 'undefined') { - return; - } const root = document.documentElement; if (themeMode === 'system') { root.removeAttribute('data-theme'); @@ -179,9 +147,6 @@ export const SidebarProvider = ({ initialCollapsed = false, children }) => { }, [themeMode]); useEffect(() => { - if (typeof window === 'undefined') { - return; - } try { const payload = JSON.stringify({ neutralHue, @@ -199,7 +164,7 @@ export const SidebarProvider = ({ initialCollapsed = false, children }) => { const setNeutralHue = useCallback((value) => { setNeutralHueState((prev) => { - if (value === '' || value === null || typeof value === 'undefined') { + if (value === '' || value === null || value === undefined) { return DEFAULT_NEUTRAL_HUE; } const parsed = Number(value); @@ -213,7 +178,7 @@ export const SidebarProvider = ({ initialCollapsed = false, children }) => { const setNeutralChroma = useCallback((value) => { setNeutralChromaState((prev) => { - if (value === '' || value === null || typeof value === 'undefined') { + if (value === '' || value === null || value === undefined) { return DEFAULT_NEUTRAL_CHROMA; } const parsed = Number(value); @@ -231,7 +196,7 @@ export const SidebarProvider = ({ initialCollapsed = false, children }) => { const setNeutralContrast = useCallback((value) => { setNeutralContrastState((prev) => { - if (value === '' || value === null || typeof value === 'undefined') { + if (value === '' || value === null || value === undefined) { return DEFAULT_NEUTRAL_CONTRAST; } const parsed = Number(value); @@ -265,9 +230,6 @@ export const SidebarProvider = ({ initialCollapsed = false, children }) => { }, [themeMode]); useEffect(() => { - if (typeof window === 'undefined') { - return; - } try { window.sessionStorage.setItem(SIDEBAR_COLLAPSE_STORAGE_KEY, collapsed ? '1' : '0'); } catch (error) { diff --git a/frontend/src/tag_manager.js b/frontend/src/tag_manager.js index beb430c..8437d5b 100644 --- a/frontend/src/tag_manager.js +++ b/frontend/src/tag_manager.js @@ -6,10 +6,7 @@ class TagManager { } normalizeLabel(label) { - if (typeof label !== 'string') { - return ''; - } - return label.trim(); + return label?.trim?.() || ''; } buildPayload({ label, color } = {}) { @@ -18,7 +15,7 @@ class TagManager { throw new Error('Tag label is required.'); } const payload = { label: normalizedLabel }; - const trimmedColor = typeof color === 'string' && color.trim().length ? color.trim() : null; + const trimmedColor = color?.trim?.() || null; payload.color = trimmedColor || this.colorGenerator(); return payload; } diff --git a/frontend/src/ui/BreadcrumbTrail.jsx b/frontend/src/ui/BreadcrumbTrail.jsx index a6fed37..9d374b0 100644 --- a/frontend/src/ui/BreadcrumbTrail.jsx +++ b/frontend/src/ui/BreadcrumbTrail.jsx @@ -80,14 +80,15 @@ const BreadcrumbTrail = ({ }; const scheduleMeasure = () => { - if (typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function') { + const raf = window.requestAnimationFrame; + if (!raf) { measure(); return; } if (measureRafRef.current) { cancelAnimationFrame(measureRafRef.current); } - measureRafRef.current = window.requestAnimationFrame(() => { + measureRafRef.current = raf(() => { measureRafRef.current = null; measure(); }); @@ -95,7 +96,7 @@ const BreadcrumbTrail = ({ scheduleMeasure(); - if (typeof ResizeObserver === 'undefined') { + if (!('ResizeObserver' in window)) { return () => { if (measureRafRef.current) { cancelAnimationFrame(measureRafRef.current); @@ -364,7 +365,6 @@ const BreadcrumbTrail = ({ {ellipsisMenuOpen && hasHiddenEntries && ellipsisMenuStyle - && typeof document !== 'undefined' ? createPortal(
{ if (option == null) { return null; } - if (typeof option === 'string') { + if (option && typeof option === 'object') { + const label = option.label ?? option.name; + if (label == null) { + return null; + } return { - id: option, - label: option, + id: option.id ?? label, + label: String(label), original: option, index, }; } - const label = option.label ?? option.name; - if (!label) { - return null; - } + const label = String(option); return { - id: option.id ?? label, + id: label, label, original: option, index, @@ -90,7 +91,7 @@ const QuickAddMenu = ({ () => options .map((option, index) => normalizeOption(option, index)) - .filter((option) => option && typeof option.label === 'string'), + .filter(Boolean), [options], ); diff --git a/frontend/src/ui/useFloatingMenu.js b/frontend/src/ui/useFloatingMenu.js index e46e113..1396833 100644 --- a/frontend/src/ui/useFloatingMenu.js +++ b/frontend/src/ui/useFloatingMenu.js @@ -3,15 +3,7 @@ import { clamp } from '../utils/math'; const DEFAULT_VIEWPORT_MARGIN = 8; -const resolveViewportWidth = () => { - if (typeof window !== 'undefined' && typeof window.innerWidth === 'number') { - return window.innerWidth; - } - if (typeof document !== 'undefined' && document.documentElement) { - return document.documentElement.clientWidth; - } - return 0; -}; +const resolveViewportWidth = () => window.innerWidth || document.documentElement.clientWidth || 0; const computeWidth = (anchorWidth, minWidth, matchAnchorWidth) => { if (matchAnchorWidth) { @@ -29,7 +21,7 @@ const formatStyle = (metrics) => { top: metrics.top, left: metrics.left, }; - if (typeof metrics.minWidth === 'number') { + if (Number.isFinite(metrics.minWidth)) { style['--floating-min-width'] = `${Math.max(metrics.minWidth, 0)}px`; } if (metrics.width) { @@ -59,7 +51,7 @@ const useFloatingMenu = ({ const updatePosition = useCallback(() => { const anchor = anchorRef?.current; - if (!anchor || typeof window === 'undefined') { + if (!anchor) { return false; } @@ -109,7 +101,7 @@ const useFloatingMenu = ({ } const viewportWidth = resolveViewportWidth(); - const viewportHeight = typeof window !== 'undefined' ? window.innerHeight : 0; + const viewportHeight = window.innerHeight || 0; const safeMargin = viewportMargin ?? DEFAULT_VIEWPORT_MARGIN; const menuHeight = menu?.offsetHeight ?? 0; @@ -200,8 +192,9 @@ const useFloatingMenu = ({ } let ignoreFocusEvents = true; - const rafId = typeof window !== 'undefined' - ? window.requestAnimationFrame(() => { + const raf = window.requestAnimationFrame; + const rafId = raf + ? raf(() => { ignoreFocusEvents = false; }) : null; diff --git a/frontend/src/utils/webauthn.js b/frontend/src/utils/webauthn.js index a803110..30b1698 100644 --- a/frontend/src/utils/webauthn.js +++ b/frontend/src/utils/webauthn.js @@ -12,10 +12,10 @@ const base64ToBase64url = (value = '') => value.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); const decodeBase64 = (value) => { - if (typeof window !== 'undefined' && typeof window.atob === 'function') { + if (window.atob) { return window.atob(value); } - const bufferCtor = typeof globalThis !== 'undefined' ? globalThis.Buffer : undefined; + const bufferCtor = globalThis?.Buffer; if (bufferCtor) { return bufferCtor.from(value, 'base64').toString('binary'); } @@ -23,10 +23,10 @@ const decodeBase64 = (value) => { }; const encodeBase64 = (binary) => { - if (typeof window !== 'undefined' && typeof window.btoa === 'function') { + if (window.btoa) { return window.btoa(binary); } - const bufferCtor = typeof globalThis !== 'undefined' ? globalThis.Buffer : undefined; + const bufferCtor = globalThis?.Buffer; if (bufferCtor) { return bufferCtor.from(binary, 'binary').toString('base64'); } @@ -54,11 +54,7 @@ export const arrayBufferToBase64url = (buffer) => { }; export const isWebAuthnAvailable = () => - typeof window !== 'undefined' - && typeof navigator !== 'undefined' - && navigator.credentials - && typeof navigator.credentials.create === 'function' - && typeof navigator.credentials.get === 'function'; + Boolean(navigator?.credentials?.create && navigator.credentials.get); export const preparePublicKeyCreationOptions = (challengeResponse) => { if (!challengeResponse || !challengeResponse.publicKey) { @@ -118,10 +114,7 @@ export const serializeRegistrationCredential = (credential) => { return null; } - const transports = - typeof credential?.response?.getTransports === 'function' - ? credential.response.getTransports() - : undefined; + const transports = credential?.response?.getTransports?.(); return { id: credential.id, @@ -132,10 +125,7 @@ export const serializeRegistrationCredential = (credential) => { attestationObject: arrayBufferToBase64url(credential.response.attestationObject), transports: transports && transports.length ? Array.from(transports) : undefined, }, - clientExtensionResults: - typeof credential.getClientExtensionResults === 'function' - ? credential.getClientExtensionResults() - : {}, + clientExtensionResults: credential.getClientExtensionResults?.() || {}, }; }; @@ -156,9 +146,6 @@ export const serializeAuthenticationCredential = (credential) => { ? arrayBufferToBase64url(credential.response.userHandle) : undefined, }, - clientExtensionResults: - typeof credential.getClientExtensionResults === 'function' - ? credential.getClientExtensionResults() - : {}, + clientExtensionResults: credential.getClientExtensionResults?.() || {}, }; };