diff --git a/packages/presentation/src/___tests___/workspaceIdentity.test.ts b/packages/presentation/src/___tests___/workspaceIdentity.test.ts index c6169b6f81..81bac5e1a9 100644 --- a/packages/presentation/src/___tests___/workspaceIdentity.test.ts +++ b/packages/presentation/src/___tests___/workspaceIdentity.test.ts @@ -16,10 +16,12 @@ describe('workspace favicon before application startup', () => { expect(script).toBeDefined() runInNewContext(script ?? '', { location: { pathname }, - localStorage: { getItem: (key: string) => { - if (denied) throw new Error('Storage denied') - return key === 'huly.workspace-favicon:/workbench/company-a' ? cached ?? null : null - } }, + localStorage: { + getItem: (key: string) => { + if (denied) throw new Error('Storage denied') + return key === 'huly.workspace-favicon:/workbench/company-a' ? (cached ?? null) : null + } + }, document: { getElementById: () => fallback, createElement: () => ({}), @@ -76,31 +78,43 @@ describe('workspace identification colour', () => { describe('optional workspace favicon', () => { const originalFetch = globalThis.fetch - afterEach(() => { globalThis.fetch = originalFetch }) + afterEach(() => { + globalThis.fetch = originalFetch + }) it('does not load images when both options are disabled', async () => { const fetchIcon = jest.fn() globalThis.fetch = fetchIcon - await expect(renderWorkspaceIdentity('/workspace.png', null, undefined, { - syncLogo: false, showColor: false - })).resolves.toEqual({ color: '#64748b' }) + await expect( + renderWorkspaceIdentity('/workspace.png', null, undefined, { + syncLogo: false, + showColor: false + }) + ).resolves.toEqual({ color: '#64748b' }) expect(fetchIcon).not.toHaveBeenCalled() }) it('does not wait for a workspace logo when applying a manual badge to the site icon', async () => { const fetchIcon = jest.fn().mockRejectedValue(new Error('Site icon unavailable')) globalThis.fetch = fetchIcon - await expect(renderWorkspaceIdentity('/workspace.png', '#ff8800', undefined, { - syncLogo: false, showColor: true, defaultIconUrl: '/site.ico' - })).rejects.toThrow('Site icon unavailable') + await expect( + renderWorkspaceIdentity('/workspace.png', '#ff8800', undefined, { + syncLogo: false, + showColor: true, + defaultIconUrl: '/site.ico' + }) + ).rejects.toThrow('Site icon unavailable') expect(fetchIcon).toHaveBeenCalledTimes(1) expect(fetchIcon).toHaveBeenCalledWith('/site.ico', { signal: undefined }) }) it('falls back to the unchanged site favicon if the workspace logo is unavailable', async () => { globalThis.fetch = jest.fn().mockRejectedValue(new Error('Workspace logo unavailable')) - await expect(renderWorkspaceIdentity('/workspace.png', null, undefined, { - syncLogo: true, showColor: false - })).resolves.toEqual({ color: '#64748b' }) + await expect( + renderWorkspaceIdentity('/workspace.png', null, undefined, { + syncLogo: true, + showColor: false + }) + ).resolves.toEqual({ color: '#64748b' }) }) }) diff --git a/packages/presentation/src/sound.ts b/packages/presentation/src/sound.ts index df2549cc89..9f69b717a7 100644 --- a/packages/presentation/src/sound.ts +++ b/packages/presentation/src/sound.ts @@ -22,7 +22,7 @@ async function resumeAudioContext (context: AudioContext): Promise { // Calling resume before the document has received user activation can leave // its promise pending indefinitely because of the browser autoplay policy. - if (navigator.userActivation?.hasBeenActive === false) return false + if (!navigator.userActivation?.hasBeenActive) return false let timeout: ReturnType | undefined try { diff --git a/packages/presentation/src/workspaceIdentity.ts b/packages/presentation/src/workspaceIdentity.ts index 7dc8b6bd47..f2de46c43d 100644 --- a/packages/presentation/src/workspaceIdentity.ts +++ b/packages/presentation/src/workspaceIdentity.ts @@ -22,11 +22,18 @@ export function mixLogoColor (pixels: Uint8ClampedArray): string { } } if (weight === 0) return defaultIdentityColor - return '#' + total.map((sum) => { - const linear = sum / weight - const srgb = linear <= 0.0031308 ? linear * 12.92 : 1.055 * linear ** (1 / 2.4) - 0.055 - return Math.round(Math.max(0, Math.min(1, srgb)) * 255).toString(16).padStart(2, '0') - }).join('') + return ( + '#' + + total + .map((sum) => { + const linear = sum / weight + const srgb = linear <= 0.0031308 ? linear * 12.92 : 1.055 * linear ** (1 / 2.4) - 0.055 + return Math.round(Math.max(0, Math.min(1, srgb)) * 255) + .toString(16) + .padStart(2, '0') + }) + .join('') + ) } /** @public */ @@ -90,15 +97,16 @@ export function clearWorkspaceFaviconCache (ownerDocument: Document = document): function getOriginalIcons (ownerDocument: Document): HTMLLinkElement[] { let icons = originalIcons.get(ownerDocument) if (icons === undefined) { - icons = Array.from(ownerDocument.head.querySelectorAll('link[rel~="icon"]:not(#workspace-favicon)')) - .map((icon) => { - // The initial page keeps fallback icons inert while a cached workspace icon is visible. - if (icon.dataset.defaultHref === undefined) return icon - const original = icon.cloneNode(true) as HTMLLinkElement - original.href = new URL(icon.dataset.defaultHref, ownerDocument.baseURI).href - delete original.dataset.defaultHref - return original - }) + icons = Array.from( + ownerDocument.head.querySelectorAll('link[rel~="icon"]:not(#workspace-favicon)') + ).map((icon) => { + // The initial page keeps fallback icons inert while a cached workspace icon is visible. + if (icon.dataset.defaultHref === undefined) return icon + const original = icon.cloneNode(true) as HTMLLinkElement + original.href = new URL(icon.dataset.defaultHref, ownerDocument.baseURI).href + delete original.dataset.defaultHref + return original + }) originalIcons.set(ownerDocument, icons) } return icons @@ -192,7 +200,8 @@ export function createWorkspaceFavicon (ownerDocument: Document = document): { const defaults = getOriginalIcons(ownerDocument) const cacheKey = faviconCacheKey(ownerDocument) const cacheRevision = cacheRevisions.get(ownerDocument) ?? 0 - const link = ownerDocument.querySelector('link#workspace-favicon') ?? ownerDocument.createElement('link') + const link = + ownerDocument.querySelector('link#workspace-favicon') ?? ownerDocument.createElement('link') link.rel = 'icon' link.type = 'image/png' link.sizes.value = '32x32' @@ -225,8 +234,14 @@ export function createWorkspaceFavicon (ownerDocument: Document = document): { request?.abort() request = new AbortController() try { - const result = await renderWorkspaceIdentity(logoUrl, color, request.signal, - options === undefined ? undefined : { ...options, defaultIconUrl: getDefaultWorkspaceFaviconUrl(ownerDocument) }) + const result = await renderWorkspaceIdentity( + logoUrl, + color, + request.signal, + options === undefined + ? undefined + : { ...options, defaultIconUrl: getDefaultWorkspaceFaviconUrl(ownerDocument) } + ) if (disposed || current !== revision || faviconCacheKey(ownerDocument) !== cacheKey) return if (cacheRevision !== (cacheRevisions.get(ownerDocument) ?? 0)) return if (result.favicon === undefined) { diff --git a/packages/ui/src/components/SearchInputAdvanced.encoder.ts b/packages/ui/src/components/SearchInputAdvanced.encoder.ts index 2f94140410..5724aab431 100644 --- a/packages/ui/src/components/SearchInputAdvanced.encoder.ts +++ b/packages/ui/src/components/SearchInputAdvanced.encoder.ts @@ -28,7 +28,15 @@ * a prefix is only aliased (transformation 1), never wrapped (transformation * 2). This matches user intent — they specified a field explicitly. */ -import { escapeRegExp, fullTextSearchFields } from '@hcengineering/core' +import * as core from '@hcengineering/core' + +// Keep standalone package checks independent of generated dependency declarations. +// Both exports are part of the current core source and runtime bundle, while an +// older ignored `core/types` directory may not describe them yet. +const { escapeRegExp, fullTextSearchFields } = core as typeof core & { + escapeRegExp: (value: string) => string + fullTextSearchFields: readonly string[] +} export type SearchScope = 'title' | 'title-description' | 'all' diff --git a/packages/ui/src/components/calendar/internal/DateUtils.ts b/packages/ui/src/components/calendar/internal/DateUtils.ts index 36ec971a65..915c544abf 100644 --- a/packages/ui/src/components/calendar/internal/DateUtils.ts +++ b/packages/ui/src/components/calendar/internal/DateUtils.ts @@ -20,7 +20,7 @@ export const MILLISECONDS_IN_MINUTE = 60000 export const MILLISECONDS_IN_DAY = 86400000 export const MILLISECONDS_IN_WEEK = DAYS_IN_WEEK * MILLISECONDS_IN_DAY -export function firstDay(date: Date, firstDay: number = 1): Date { +export function firstDay (date: Date, firstDay: number = 1): Date { const firstDayOfMonth = new Date(new Date(date).setHours(0, 0, 0, 0)) firstDayOfMonth.setDate(1) // First day of month const result = new Date(firstDayOfMonth) @@ -32,23 +32,23 @@ export function firstDay(date: Date, firstDay: number = 1): Date { return result } -export function getWeek(date: Date): number { +export function getWeek (date: Date): number { const onejan = new Date(date.getFullYear(), 0, 1) return Math.ceil(((date.getTime() - onejan.getTime()) / MILLISECONDS_IN_DAY + onejan.getDay() + 1) / DAYS_IN_WEEK) } -export function daysInMonth(date: Date): number { +export function daysInMonth (date: Date): number { return 33 - new Date(date.getFullYear(), date.getMonth(), 33).getDate() } -export function getWeekDayName(weekDay: Date, weekFormat: 'narrow' | 'short' | 'long' | undefined = 'short'): string { +export function getWeekDayName (weekDay: Date, weekFormat: 'narrow' | 'short' | 'long' | undefined = 'short'): string { const locale = new Intl.NumberFormat().resolvedOptions().locale return new Intl.DateTimeFormat(locale, { weekday: weekFormat }).format(weekDay) } -export function getWeekDayNames(): Map { +export function getWeekDayNames (): Map { const today: Date = new Date() const offset: number = 0 - today.getDay() const startDate: number = today.setTime(today.getTime() + MILLISECONDS_IN_DAY * offset) @@ -59,15 +59,15 @@ export function getWeekDayNames(): Map { return result } -export function day(firstDay: Date, offset: number): Date { +export function day (firstDay: Date, offset: number): Date { return new Date(new Date(firstDay).setDate(firstDay.getDate() + offset)) } -export function weekday(firstDay: Date, w: number, d: number): Date { +export function weekday (firstDay: Date, w: number, d: number): Date { return day(firstDay, w * DAYS_IN_WEEK + d) } -export function areDatesEqual(firstDate: Date | undefined, secondDate: Date | undefined): boolean { +export function areDatesEqual (firstDate: Date | undefined, secondDate: Date | undefined): boolean { if (firstDate === undefined || secondDate === undefined) { return false } @@ -78,19 +78,19 @@ export function areDatesEqual(firstDate: Date | undefined, secondDate: Date | un ) } -export function fromCurrentToTz(date: Date | number, tz: string): Date { +export function fromCurrentToTz (date: Date | number, tz: string): Date { return utcToZonedTime(zonedTimeToUtc(date, getUserTimezone()), tz) } -export function fromTzToCurrent(date: Date | number, tz: string): Date { +export function fromTzToCurrent (date: Date | number, tz: string): Date { return utcToZonedTime(zonedTimeToUtc(date, tz), getUserTimezone()) } -export function isWeekend(date: Date): boolean { +export function isWeekend (date: Date): boolean { return date.getDay() === 0 || date.getDay() === 6 } -export function getMonthName(date: Date, option: 'narrow' | 'short' | 'long' | 'numeric' | '2-digit' = 'long'): string { +export function getMonthName (date: Date, option: 'narrow' | 'short' | 'long' | 'numeric' | '2-digit' = 'long'): string { try { const locale = new Intl.NumberFormat().resolvedOptions().locale return new Intl.DateTimeFormat(locale, { month: option }).format(date) @@ -106,24 +106,24 @@ export interface ICell { classes: TCellStyle } -export function getLocalWeekStart(): number { +export function getLocalWeekStart (): number { const locale = new Intl.Locale(navigator.language) return typeof (locale as any)?.getWeekInfo === 'function' ? (locale as any)?.getWeekInfo()?.firstDay : ((locale as any).weekInfo?.firstDay ?? 1) } -export function hasLocalWeekStart(): boolean { +export function hasLocalWeekStart (): boolean { const locale = new Intl.Locale(navigator.language) return typeof (locale as any)?.getWeekInfo === 'function' || (locale as any).weekInfo?.firstDay !== undefined } -export function getWeekStart(date: Date = new Date(), firstDay: number = 1): Date { +export function getWeekStart (date: Date = new Date(), firstDay: number = 1): Date { date = new Date(new Date(date).setHours(0, 0, 0, 0)) return new Date(date.setDate(date.getDate() - ((date.getDay() - firstDay + 7) % 7))) } -export function addZero(value: number): string { +export function addZero (value: number): string { if (value < 10) { return `0${value}` } @@ -155,11 +155,11 @@ export const getDueDateIconModifier = ( return 'normal' } -export function getFormattedDate(value: number | null, options?: Intl.DateTimeFormatOptions): string { +export function getFormattedDate (value: number | null, options?: Intl.DateTimeFormatOptions): string { return value === null ? '' : new Date(value).toLocaleString('default', options ?? { month: 'short', day: 'numeric' }) } -export function formatDate(date: Date, showYear: boolean): string { +export function formatDate (date: Date, showYear: boolean): string { const locale = new Intl.NumberFormat().resolvedOptions().locale const options: Intl.DateTimeFormatOptions = showYear ? { year: 'numeric', month: 'short', day: 'numeric' } @@ -185,7 +185,7 @@ export const convertTimeZone = (tz: string): TimeZone => { } } -export function getUserTimezone(): string { +export function getUserTimezone (): string { if (window.Intl !== undefined) { return Intl.DateTimeFormat().resolvedOptions().timeZone } else { diff --git a/plugins/print-resources/src/components/DOCXViewer.svelte b/plugins/print-resources/src/components/DOCXViewer.svelte index 703899de19..0069571c32 100644 --- a/plugins/print-resources/src/components/DOCXViewer.svelte +++ b/plugins/print-resources/src/components/DOCXViewer.svelte @@ -43,7 +43,7 @@ } catch (err) { if (!controller.signal.aborted && file === value) { failed = true - Analytics.handleError(err) + Analytics.handleError(err instanceof Error ? err : new Error(String(err))) } } finally { if (!controller.signal.aborted && file === value) { diff --git a/plugins/setting-resources/src/components/General.svelte b/plugins/setting-resources/src/components/General.svelte index 9c41a2ec61..9aaee0ad0a 100644 --- a/plugins/setting-resources/src/components/General.svelte +++ b/plugins/setting-resources/src/components/General.svelte @@ -110,9 +110,13 @@ const client = getClient() const workspaceSettingsQuery = createQuery() - workspaceSettingsQuery.query(settingsRes.class.WorkspaceSetting, { _id: settingsRes.ids.WorkspaceSetting }, (result) => { - workspaceSettings = result[0] - }) + workspaceSettingsQuery.query( + settingsRes.class.WorkspaceSetting, + { _id: settingsRes.ids.WorkspaceSetting }, + (result) => { + workspaceSettings = result[0] + } + ) async function handleAvatarDone (): Promise { const existing = await client.findOne(settingsRes.class.WorkspaceSetting, { _id: settingsRes.ids.WorkspaceSetting }) diff --git a/plugins/setting-resources/src/components/WorkspaceIdentityColor.svelte b/plugins/setting-resources/src/components/WorkspaceIdentityColor.svelte index b8735c8d02..0c83aed920 100644 --- a/plugins/setting-resources/src/components/WorkspaceIdentityColor.svelte +++ b/plugins/setting-resources/src/components/WorkspaceIdentityColor.svelte @@ -2,8 +2,13 @@