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("'", ''');
-};