diff --git a/web/src/lib/components/i18n/format-bold-message.svelte b/web/src/lib/components/i18n/format-bold-message.svelte new file mode 100644 index 0000000000..6a449e8808 --- /dev/null +++ b/web/src/lib/components/i18n/format-bold-message.svelte @@ -0,0 +1,13 @@ + + + + {#if tag === 'b'} + {message} + {/if} + diff --git a/web/src/lib/components/i18n/format-message.svelte b/web/src/lib/components/i18n/format-message.svelte index 9b91d1b7f4..d6ff09ed1c 100644 --- a/web/src/lib/components/i18n/format-message.svelte +++ b/web/src/lib/components/i18n/format-message.svelte @@ -1,5 +1,10 @@ + + + +Notification message with link diff --git a/web/src/lib/components/shared-components/notification/notification-card.svelte b/web/src/lib/components/shared-components/notification/notification-card.svelte index 0919bca035..aac0823bf5 100644 --- a/web/src/lib/components/shared-components/notification/notification-card.svelte +++ b/web/src/lib/components/shared-components/notification/notification-card.svelte @@ -2,16 +2,18 @@ import { fade } from 'svelte/transition'; import Icon from '$lib/components/elements/icon.svelte'; import { - type Notification, + isComponentNotification, notificationController, NotificationType, + type ComponentNotification, + type Notification, } from '$lib/components/shared-components/notification/notification'; import { onMount } from 'svelte'; import { mdiCloseCircleOutline, mdiInformationOutline, mdiWindowClose } from '@mdi/js'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import { t } from 'svelte-i18n'; - export let notification: Notification; + export let notification: Notification | ComponentNotification; $: icon = notification.type === NotificationType.Error ? mdiCloseCircleOutline : mdiInformationOutline; $: hoverStyle = notification.action.type === 'discard' ? 'hover:cursor-pointer' : ''; @@ -93,9 +95,8 @@

- {#if notification.html} - - {@html notification.message} + {#if isComponentNotification(notification)} + {:else} {notification.message} {/if} diff --git a/web/src/lib/components/shared-components/notification/notification.ts b/web/src/lib/components/shared-components/notification/notification.ts index dfe4e1f923..9cafcd9eaf 100644 --- a/web/src/lib/components/shared-components/notification/notification.ts +++ b/web/src/lib/components/shared-components/notification/notification.ts @@ -1,3 +1,4 @@ +import type { ComponentProps, ComponentType, SvelteComponent } from 'svelte'; import { writable } from 'svelte/store'; export enum NotificationType { @@ -15,11 +16,6 @@ export type Notification = { id: number; type: NotificationType; message: string; - /** - * Allow HTML to be inserted within the message. Make sure to verify/encode - * variables that may be interpoalted into 'message' - */ - html?: boolean; /** The action to take when the notification is clicked */ action: NotificationAction; button?: NotificationButton; @@ -32,13 +28,37 @@ type NoopAction = { type: 'noop' }; export type NotificationAction = DiscardAction | NoopAction; -export type NotificationOptions = Partial> & { message: string }; +type Component = { + type: T; + props: ComponentProps>; +}; + +type BaseNotificationOptions = Partial> & Pick; + +export type NotificationOptions = BaseNotificationOptions; +export type ComponentNotificationOptions = BaseNotificationOptions< + ComponentNotification, + 'component' +>; + +export type ComponentNotification> = Omit< + Notification, + 'message' +> & { + component: Component; +}; + +export const isComponentNotification = ( + notification: Notification | ComponentNotification, +): notification is ComponentNotification => { + return 'component' in notification; +}; function createNotificationList() { - const notificationList = writable([]); + const notificationList = writable<(Notification | ComponentNotification)[]>([]); let count = 1; - const show = (options: NotificationOptions) => { + const show = (options: T extends ComponentType ? ComponentNotificationOptions : NotificationOptions) => { notificationList.update((currentList) => { currentList.push({ id: count++, diff --git a/web/src/lib/i18n/en.json b/web/src/lib/i18n/en.json index 549322ed98..c122941656 100644 --- a/web/src/lib/i18n/en.json +++ b/web/src/lib/i18n/en.json @@ -378,7 +378,7 @@ "assets": "Assets", "assets_added_count": "Added {count, plural, one {# asset} other {# assets}}", "assets_added_to_album_count": "Added {count, plural, one {# asset} other {# assets}} to the album", - "assets_added_to_name_count": "Added {count, plural, one {# asset} other {# assets}} to {name}", + "assets_added_to_name_count": "Added {count, plural, one {# asset} other {# assets}} to {hasName, select, true {{name}} other {new album}}", "assets_count": "{count, plural, one {# asset} other {# assets}}", "assets_moved_to_trash_count": "Moved {count, plural, one {# asset} other {# assets}} to trash", "assets_permanently_deleted_count": "Permanently deleted {count, plural, one {# asset} other {# assets}}", diff --git a/web/src/lib/utils/asset-utils.ts b/web/src/lib/utils/asset-utils.ts index 6d8279a165..476d910523 100644 --- a/web/src/lib/utils/asset-utils.ts +++ b/web/src/lib/utils/asset-utils.ts @@ -1,4 +1,5 @@ import { goto } from '$app/navigation'; +import FormatBoldMessage from '$lib/components/i18n/format-bold-message.svelte'; import { NotificationType, notificationController } from '$lib/components/shared-components/notification/notification'; import { AppRoute } from '$lib/constants'; import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store'; @@ -9,7 +10,6 @@ import { preferences } from '$lib/stores/user.store'; import { downloadRequest, getKey, withError } from '$lib/utils'; import { createAlbum } from '$lib/utils/album-utils'; import { getByteUnitString } from '$lib/utils/byte-units'; -import { encodeHTMLSpecialChars } from '$lib/utils/string-utils'; import { addAssetsToAlbum as addAssets, getAssetInfo, @@ -63,13 +63,17 @@ export const addAssetsToNewAlbum = async (albumName: string, assetIds: string[]) if (!album) { return; } - const displayName = albumName ? `${encodeHTMLSpecialChars(albumName)}` : 'new album'; const $t = get(t); notificationController.show({ type: NotificationType.Info, timeout: 5000, - message: $t('assets_added_to_name_count', { values: { count: assetIds.length, name: displayName } }), - html: true, + component: { + type: FormatBoldMessage, + props: { + key: 'assets_added_to_name_count', + values: { count: assetIds.length, name: albumName, hasName: !!albumName }, + }, + }, button: { text: $t('view_album'), onClick() { diff --git a/web/src/lib/utils/string-utils.ts b/web/src/lib/utils/string-utils.ts index b58f859f62..0170c34737 100644 --- a/web/src/lib/utils/string-utils.ts +++ b/web/src/lib/utils/string-utils.ts @@ -5,12 +5,3 @@ export const removeAccents = (str: string) => { export const normalizeSearchString = (str: string) => { return removeAccents(str.toLocaleLowerCase()); }; - -export const encodeHTMLSpecialChars = (str: string) => { - return str - .replaceAll('&', '&') - .replaceAll('<', '<') - .replaceAll('>', '>') - .replaceAll('"', '"') - .replaceAll("'", '''); -};