Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions unime/src/lib/components/CollapsibleRenderer.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<script lang="ts">
import markdownit from 'markdown-it';
import { slide } from 'svelte/transition';

import { createAccordion, melt } from '@melt-ui/svelte';

import { CaretDownBoldIcon } from '$lib/icons';

const md = markdownit();

const {
elements: { content, item, trigger, root },
helpers: { isSelected },
} = createAccordion({ defaultValue: 'description' });

type Item = {
id: string;
title: string;
description: string;
};

export let items: Item[] = [];
</script>

<div class="mx-auto flex max-w-full flex-col space-y-4" {...$root}>
{#each items as { id, title, description }}
<div
use:melt={$item(id)}
class="overflow-hidden rounded-xl transition-colors focus-within:relative focus-within:z-10 focus-within:ring-3 focus-within:ring-primary dark:border-slate-600"
>
<h2 class="flex">
<button
use:melt={$trigger(id)}
class="hover:bg-opacity-95 flex min-h-12 flex-1 cursor-pointer items-center justify-between text-base leading-none font-medium text-slate-800 transition-colors focus:ring-0! dark:bg-background dark:text-grey"
>
<div class="flex w-full items-center justify-between py-2">
<p class="text-left text-xl font-bold">{title}</p>
<CaretDownBoldIcon
class="size-4 transition-transform"
style={`transform: ${$isSelected(id) ? 'rotate(180deg)' : 'rotate(0deg)'};`}
/>
</div>
</button>
</h2>

{#if $isSelected(id)}
<div
use:melt={$content(id)}
class="overflow-hidden text-sm text-slate-500 dark:bg-background dark:text-slate-300"
transition:slide
>
<div class="x-5 prose prose-sm max-w-none dark:prose-invert">
{@html md.render(description)}

Check warning on line 53 in unime/src/lib/components/CollapsibleRenderer.svelte

View workflow job for this annotation

GitHub Actions / unime_frontend

`{@html}` can lead to XSS attack
</div>
</div>
{/if}
</div>
{/each}
</div>
115 changes: 87 additions & 28 deletions unime/src/routes/credentials/[id]/OpenBadgeRenderer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,46 @@

import type { DisplayCredential } from '@bindings/credentials/DisplayCredential';

import CollapsibleRenderer from '$lib/components/CollapsibleRenderer.svelte';

import TextFieldRenderer from './TextFieldRenderer.svelte';

export let credential: DisplayCredential;

const md = markdownit();

Check failure on line 13 in unime/src/routes/credentials/[id]/OpenBadgeRenderer.svelte

View workflow job for this annotation

GitHub Actions / unime_frontend

'md' is assigned a value but never used
</script>

<div class="flex flex-col gap-4">
<!-- Achievement -->
{#if credential.data.credentialSubject?.achievement?.description}
<div class="prose prose-sm rounded-xl bg-background p-4 dark:prose-invert">
<h2>{$LL.CREDENTIAL.DETAILS.DESCRIPTION()}</h2>
<!-- TODO: Review marked vs. markdown-it and security risks. -->
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html md.render(credential.data.credentialSubject.achievement.description)}
<div class="rounded-xl bg-background p-3">
<CollapsibleRenderer
items={[
{
id: 'description',
title: $LL.CREDENTIAL.DETAILS.DESCRIPTION(),
description: credential.data.credentialSubject.achievement.description,
},
]}
/>
</div>
<!-- TODO: Review marked vs. markdown-it and security risks. -->
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{/if}

{#if credential.data.credentialSubject?.achievement?.criteria?.narrative}
<div class="prose prose-sm rounded-xl bg-background p-4 dark:prose-invert">
<h2>{$LL.CREDENTIAL.DETAILS.OPEN_BADGES.CRITERIA()}</h2>
<div class="rounded-xl bg-background p-3">
<CollapsibleRenderer
items={[
{
id: 'criteria',
title: $LL.CREDENTIAL.DETAILS.OPEN_BADGES.CRITERIA(),
description: credential.data.credentialSubject.achievement.criteria.narrative,
},
]}
/>
<!-- TODO: Review marked vs. markdown-it and security risks. -->
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html md.render(credential.data.credentialSubject.achievement.criteria.narrative)}
</div>
{/if}

Expand All @@ -38,21 +54,64 @@
/>
{/if}

{#if credential.data.credentialSubject?.achievement?.alignment?.length > 0}
<!-- {#if credential.data.credentialSubject?.achievement?.alignment?.length > 0}
<div class="prose prose-sm rounded-xl bg-background p-4 dark:prose-invert">
<h2>{$LL.CREDENTIAL.DETAILS.OPEN_BADGES.ALIGNMENT()}</h2>
{#each credential.data.credentialSubject.achievement.alignment as alignmentItem}
<h4>{alignmentItem.targetName}</h4>
{#if alignmentItem.targetDescription}
<!-- TODO Review marked vs. markdown-it and security risks. -->
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html md.render(alignmentItem.targetDescription)}
{#if alignmentItem.targetDescription} -->
<!-- TODO Review marked vs. markdown-it and security risks. -->
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
<!-- {@html md.render(alignmentItem.targetDescription)}
{/if}
{/each}
</div>
{/if} -->

{#if credential.data.credentialSubject?.achievement?.alignment?.length > 0}
<div class="rounded-xl bg-background p-3">
<CollapsibleRenderer
items={credential.data.credentialSubject.achievement.alignment.map((item, index) => ({
id: `alignment-${index}`,
title: $LL.CREDENTIAL.DETAILS.OPEN_BADGES.ALIGNMENT(),
description: `#### ${item.targetName}\n\n${item.targetDescription ?? ''}`,
}))}
/>
</div>
{/if}

<!-- Result -->
{#if credential.data.credentialSubject?.result?.length > 0}
<div class="rounded-xl bg-background p-3">
<CollapsibleRenderer
items={credential.data.credentialSubject.result.map((resultItem, index) => {
let description = '';

if (resultItem.alignment?.length) {
description += resultItem.alignment
.map((a) => `#### ${a.targetName}\n\n${a.targetDescription ?? ''}`)
.join('\n\n');
}

if (resultItem.value) {
description += `\n\n#### ${$LL.CREDENTIAL.DETAILS.OPEN_BADGES.VALUE()}\n\n${resultItem.value}`;
}

if (resultItem.resultDescription) {
description += `\n\n${resultItem.resultDescription}`;
}

return {
id: `result-${index}`,
title: $LL.CREDENTIAL.DETAILS.OPEN_BADGES.RESULT(),
description,
};
})}
/>
</div>
{/if}

<!-- Result
{#if credential.data.credentialSubject?.result?.length > 0}
<div class="prose prose-sm rounded-xl bg-background p-4 dark:prose-invert">
<h2>{$LL.CREDENTIAL.DETAILS.OPEN_BADGES.RESULT()}</h2>
Expand All @@ -63,37 +122,37 @@
{#if resultItem.alignment?.length > 0}
{#each resultItem.alignment as resultAlignment}
<h4>{resultAlignment.targetName}</h4>
{#if resultAlignment.targetDescription}
<!-- TODO: Review marked vs. markdown-it and security risks. -->
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html md.render(resultAlignment.targetDescription)}
{#if resultAlignment.targetDescription} -->
<!-- TODO: Review marked vs. markdown-it and security risks. -->
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
<!-- {@html md.render(resultAlignment.targetDescription)}
{/if}
{/each}
{/if}

{#if resultItem.value}
<div class="flex h-16 items-center justify-between">
<h4 class="mt-2">{$LL.CREDENTIAL.DETAILS.OPEN_BADGES.VALUE()}</h4>
<div class="text-2xl font-bold">
<!-- TODO: Review marked vs. markdown-it and security risks. -->
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html md.render(resultItem.value)}
<div class="text-2xl font-bold"> -->
<!-- TODO: Review marked vs. markdown-it and security risks. -->
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
<!-- {@html md.render(resultItem.value)}
</div>
</div>
{/if}

{#if resultItem.resultDescription}
<div class="text-[12px]/[14px] text-text-alt">
<!-- TODO: Review marked vs. markdown-it and security risks. -->
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html md.render(resultItem.resultDescription)}
<div class="text-[12px]/[14px] text-text-alt"> -->
<!-- TODO: Review marked vs. markdown-it and security risks. -->
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
<!-- {@html md.render(resultItem.resultDescription)}
</div>
{/if}
</div>
</div> -
{/each}
</div>
</div>
</div>
{/if}
{/if} -->

<!-- "validFrom" is defined as REQUIRED in JSON Schema: https://purl.imsglobal.org/spec/ob/v3p0/schema/json/ob_v3p0_achievementcredential_schema.json -->
{#if credential.data.validFrom}
Expand Down
Loading