mirror of
https://github.com/unraid/api.git
synced 2025-12-31 13:39:52 -06:00
feat(web): support markdown in notification messages
This commit is contained in:
@@ -1,39 +1,47 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
ArchiveBoxIcon,
|
ArchiveBoxIcon,
|
||||||
ShieldExclamationIcon,
|
|
||||||
CheckBadgeIcon,
|
CheckBadgeIcon,
|
||||||
ExclamationTriangleIcon,
|
ExclamationTriangleIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
|
ShieldExclamationIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
} from "@heroicons/vue/24/solid";
|
} from '@heroicons/vue/24/solid';
|
||||||
import { useMutation } from "@vue/apollo-composable";
|
import { useMutation } from '@vue/apollo-composable';
|
||||||
import type { NotificationFragmentFragment } from "~/composables/gql/graphql";
|
import type { NotificationFragmentFragment } from '~/composables/gql/graphql';
|
||||||
|
import { NotificationType } from '~/composables/gql/graphql';
|
||||||
import { NotificationType } from "~/composables/gql/graphql";
|
import { safeParseMarkdown } from '~/helpers/markdown';
|
||||||
import {
|
import {
|
||||||
archiveNotification as archiveMutation,
|
archiveNotification as archiveMutation,
|
||||||
deleteNotification as deleteMutation,
|
deleteNotification as deleteMutation,
|
||||||
} from "./graphql/notification.query";
|
} from './graphql/notification.query';
|
||||||
|
|
||||||
const props = defineProps<NotificationFragmentFragment>();
|
const props = defineProps<NotificationFragmentFragment>();
|
||||||
|
|
||||||
|
const descriptionMarkup = computedAsync(async () => {
|
||||||
|
try {
|
||||||
|
return await safeParseMarkdown(props.description);
|
||||||
|
} catch (e) {
|
||||||
|
return props.description;
|
||||||
|
}
|
||||||
|
}, '');
|
||||||
|
|
||||||
const icon = computed<{ component: Component; color: string } | null>(() => {
|
const icon = computed<{ component: Component; color: string } | null>(() => {
|
||||||
switch (props.importance) {
|
switch (props.importance) {
|
||||||
case "INFO":
|
case 'INFO':
|
||||||
return {
|
return {
|
||||||
component: CheckBadgeIcon,
|
component: CheckBadgeIcon,
|
||||||
color: "text-green-500",
|
color: 'text-green-500',
|
||||||
};
|
};
|
||||||
case "WARNING":
|
case 'WARNING':
|
||||||
return {
|
return {
|
||||||
component: ExclamationTriangleIcon,
|
component: ExclamationTriangleIcon,
|
||||||
color: "text-yellow-500",
|
color: 'text-yellow-500',
|
||||||
};
|
};
|
||||||
case "ALERT":
|
case 'ALERT':
|
||||||
return {
|
return {
|
||||||
component: ShieldExclamationIcon,
|
component: ShieldExclamationIcon,
|
||||||
color: "text-red-500",
|
color: 'text-red-500',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -86,7 +94,7 @@ const mutationError = computed(() => {
|
|||||||
<div
|
<div
|
||||||
class="w-full flex flex-row items-center justify-between gap-2 opacity-75 group-hover/item:opacity-100 group-focus/item:opacity-100"
|
class="w-full flex flex-row items-center justify-between gap-2 opacity-75 group-hover/item:opacity-100 group-focus/item:opacity-100"
|
||||||
>
|
>
|
||||||
<p class="text-secondary-foreground">{{ description }}</p>
|
<div class="text-secondary-foreground" v-html="descriptionMarkup" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="mutationError" class="text-red-600">Error: {{ mutationError }}</p>
|
<p v-if="mutationError" class="text-red-600">Error: {{ mutationError }}</p>
|
||||||
|
|||||||
13
web/helpers/markdown.ts
Normal file
13
web/helpers/markdown.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
import { marked } from 'marked';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses arbitrary markdown content as sanitized html. May throw if parsing fails.
|
||||||
|
*
|
||||||
|
* @param markdownContent string of markdown content
|
||||||
|
* @returns safe, sanitized html content
|
||||||
|
*/
|
||||||
|
export async function safeParseMarkdown(markdownContent: string) {
|
||||||
|
const parsed = await marked.parse(markdownContent);
|
||||||
|
return DOMPurify.sanitize(parsed);
|
||||||
|
}
|
||||||
7
web/package-lock.json
generated
7
web/package-lock.json
generated
@@ -23,6 +23,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.11",
|
"dayjs": "^1.11.11",
|
||||||
|
"dompurify": "^3.2.0",
|
||||||
"focus-trap": "^7.5.4",
|
"focus-trap": "^7.5.4",
|
||||||
"graphql": "^16.8.1",
|
"graphql": "^16.8.1",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
@@ -8329,6 +8330,12 @@
|
|||||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dompurify": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-AMdOzK44oFWqHEi0wpOqix/fUNY707OmoeFDnbi3Q5I8uOpy21ufUA5cDJPr0bosxrflOVD/H2DMSvuGKJGfmQ==",
|
||||||
|
"license": "(MPL-2.0 OR Apache-2.0)"
|
||||||
|
},
|
||||||
"node_modules/domutils": {
|
"node_modules/domutils": {
|
||||||
"version": "2.8.0",
|
"version": "2.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.11",
|
"dayjs": "^1.11.11",
|
||||||
|
"dompurify": "^3.2.0",
|
||||||
"focus-trap": "^7.5.4",
|
"focus-trap": "^7.5.4",
|
||||||
"graphql": "^16.8.1",
|
"graphql": "^16.8.1",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
|
|||||||
Reference in New Issue
Block a user