114 lines
2.2 KiB
Svelte
114 lines
2.2 KiB
Svelte
<script lang="ts">
|
|
import api from '$lib/api';
|
|
import IconButton from '../../utils/IconButton.svelte';
|
|
import Spinner from '../../utils/Spinner.svelte';
|
|
export let address: string;
|
|
|
|
let mode: 'preview' | 'full' | 'markdown' = 'preview';
|
|
|
|
$: textContent = (async () => {
|
|
const response = await api.fetchRaw(address, mode == 'preview');
|
|
const text = await response.text();
|
|
if (mode === 'markdown') {
|
|
const { marked } = await import('marked');
|
|
const DOMPurify = await import('dompurify');
|
|
return DOMPurify.default.sanitize(marked.parse(text));
|
|
} else {
|
|
return text;
|
|
}
|
|
})();
|
|
|
|
const tabs = [
|
|
['image', 'preview', 'Preview'],
|
|
['shape-circle', 'full', 'Full'],
|
|
['edit', 'markdown', 'Markdown']
|
|
] as [string, typeof mode, string][];
|
|
</script>
|
|
|
|
<div class="text-preview">
|
|
<header class="text-header">
|
|
{#each tabs as [icon, targetMode, label]}
|
|
<div
|
|
class="tab"
|
|
class:active={mode == targetMode}
|
|
on:click={() => (mode = targetMode)}
|
|
on:keydown={(ev) => {
|
|
if (ev.key === 'Enter') {
|
|
mode = targetMode;
|
|
}
|
|
}}
|
|
>
|
|
<IconButton name={icon} active={mode == targetMode} on:click={() => (mode = targetMode)} />
|
|
<div class="label">{label}</div>
|
|
</div>
|
|
{/each}
|
|
</header>
|
|
<div class="text" class:markdown={mode === 'markdown'}>
|
|
{#await textContent}
|
|
<Spinner centered />
|
|
{:then text}
|
|
{#if mode === 'markdown'}
|
|
{@html text}
|
|
{:else}
|
|
{text}{#if mode === 'preview'}…{/if}
|
|
{/if}
|
|
{/await}
|
|
</div>
|
|
</div>
|
|
|
|
<style lang="scss">
|
|
.text-preview {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.text {
|
|
background: var(--background);
|
|
padding: 0.5em;
|
|
height: 100%;
|
|
box-sizing: border-box;
|
|
|
|
overflow: auto;
|
|
|
|
border-radius: 4px;
|
|
border: 1px solid var(--foreground);
|
|
|
|
white-space: pre-wrap;
|
|
|
|
&.markdown {
|
|
white-space: unset;
|
|
|
|
:global(img) {
|
|
max-width: 75%;
|
|
}
|
|
}
|
|
}
|
|
|
|
header {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
|
|
.tab {
|
|
display: flex;
|
|
|
|
cursor: pointer;
|
|
|
|
border: 1px solid var(--foreground);
|
|
border-bottom: 0;
|
|
border-top-left-radius: 4px;
|
|
border-top-right-radius: 4px;
|
|
|
|
padding: 0.15em;
|
|
margin: 0 0.1em;
|
|
|
|
&.active {
|
|
background: var(--background);
|
|
}
|
|
|
|
.label {
|
|
margin-right: 0.5em;
|
|
}
|
|
}
|
|
}
|
|
</style>
|