feat: add file upload option and word wrap

This commit is contained in:
ShareVB 2024-09-15 21:18:11 +02:00
parent 78c4436b3f
commit 0c2dcbdf67
2 changed files with 93 additions and 12 deletions

View file

@ -7,7 +7,15 @@ import sqlHljs from 'highlight.js/lib/languages/sql';
import xmlHljs from 'highlight.js/lib/languages/xml';
import yamlHljs from 'highlight.js/lib/languages/yaml';
import iniHljs from 'highlight.js/lib/languages/ini';
import bashHljs from 'highlight.js/lib/languages/bash';
import markdownHljs from 'highlight.js/lib/languages/markdown';
import jsHljs from 'highlight.js/lib/languages/javascript';
import cssHljs from 'highlight.js/lib/languages/css';
import goHljs from 'highlight.js/lib/languages/go';
import csharpHljs from 'highlight.js/lib/languages/csharp';
import { Base64 } from 'js-base64';
import { useCopy } from '@/composable/copy';
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
const props = withDefaults(
defineProps<{
@ -16,12 +24,17 @@ const props = withDefaults(
language?: string
copyPlacement?: 'top-right' | 'bottom-right' | 'outside' | 'none'
copyMessage?: string
wordWrap?: boolean
downloadFileName?: string
downloadButtonText?: string
}>(),
{
followHeightOf: null,
language: 'txt',
copyPlacement: 'top-right',
copyMessage: 'Copy to clipboard',
downloadFileName: '',
downloadButtonText: 'Download',
},
);
hljs.registerLanguage('sql', sqlHljs);
@ -30,12 +43,25 @@ hljs.registerLanguage('html', xmlHljs);
hljs.registerLanguage('xml', xmlHljs);
hljs.registerLanguage('yaml', yamlHljs);
hljs.registerLanguage('toml', iniHljs);
hljs.registerLanguage('bash', bashHljs);
hljs.registerLanguage('markdown', markdownHljs);
hljs.registerLanguage('css', cssHljs);
hljs.registerLanguage('javascript', jsHljs);
hljs.registerLanguage('go', goHljs);
hljs.registerLanguage('csharp', csharpHljs);
const { value, language, followHeightOf, copyPlacement, copyMessage } = toRefs(props);
const { value, language, followHeightOf, copyPlacement, copyMessage, downloadFileName, downloadButtonText } = toRefs(props);
const { height } = followHeightOf.value ? useElementSize(followHeightOf) : { height: ref(null) };
const { copy, isJustCopied } = useCopy({ source: value, createToast: false });
const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.value);
const valueBase64 = computed(() => Base64.encode(value.value));
const { download } = useDownloadFileFromBase64(
{
source: valueBase64,
filename: downloadFileName,
});
</script>
<template>
@ -47,11 +73,16 @@ const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.
:style="height ? `min-height: ${height - 40 /* card padding */ + 10 /* negative margin compensation */}px` : ''"
>
<n-config-provider :hljs="hljs">
<n-code :code="value" :language="language" :trim="false" data-test-id="area-content" />
<n-code :code="value" :language="language" :word-wrap="wordWrap" :trim="false" data-test-id="area-content" />
</n-config-provider>
</n-scrollbar>
<div absolute right-10px top-10px>
<c-tooltip v-if="value" :tooltip="tooltipText" position="left">
<div
v-if="value && copyPlacement !== 'none'"
absolute right-10px
:top-10px="copyPlacement === 'top-right' ? '' : 'no'"
:bottom-10px="copyPlacement === 'bottom-right' ? '' : 'no'"
>
<c-tooltip v-if="value && copyPlacement !== 'outside'" :tooltip="tooltipText" position="left">
<c-button circle important:h-10 important:w-10 @click="copy()">
<n-icon size="22" :component="Copy" />
</c-button>
@ -63,6 +94,11 @@ const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.
{{ tooltipText }}
</c-button>
</div>
<div v-if="downloadFileName !== '' && value !== ''" mt-5 flex justify-center>
<c-button secondary @click="download">
{{ downloadButtonText }}
</c-button>
</div>
</div>
</template>

View file

@ -1,19 +1,31 @@
<script setup lang="ts">
import PostalMime from 'postal-mime';
const inputType = ref<'file' | 'content'>('file');
const emailContent = ref('');
const fileInput = ref() as Ref<File | null>;
const error = ref('');
const parsedEmail = computedAsync(async () => {
const emailContentValue = emailContent.value;
const file = fileInput.value;
error.value = '';
try {
return await PostalMime.parse(emailContentValue);
if (inputType.value === 'file' && file) {
return await PostalMime.parse(await file.arrayBuffer());
} else if (inputType.value === 'content' && emailContentValue) {
return await PostalMime.parse(emailContentValue);
} else {
return null;
}
}
catch (e: any) {
return e.toString();
error.value = e.toString();
return null;
}
});
function downloadFile(data: Uint8Array, fileName: string, fileType: string) {
function downloadFile(data: ArrayBuffer, fileName: string, fileType: string) {
const blob = new Blob([data], { type: fileType || 'application/octet-stream' });
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement('a');
@ -23,12 +35,38 @@ function downloadFile(data: Uint8Array, fileName: string, fileType: string) {
a.click();
URL.revokeObjectURL(downloadUrl);
}
function onUpload(file: File) {
if (file) {
fileInput.value = file;
}
}
</script>
<template>
<div style="max-width: 600px;">
<c-card title="Input" mb-2>
<n-radio-group v-model:value="inputType" name="radiogroup" mb-2 flex justify-center>
<n-space>
<n-radio
value="file"
label="File"
/>
<n-radio
value="content"
label="Content"
/>
</n-space>
</n-radio-group>
<c-file-upload
v-if="inputType === 'file'"
title="Drag and drop EML file here, or click to select a file"
@file-upload="onUpload"
/>
<c-input-text
v-if="inputType === 'content'"
v-model:value="emailContent"
label="Email Content"
multiline
@ -38,32 +76,39 @@ function downloadFile(data: Uint8Array, fileName: string, fileType: string) {
/>
</c-card>
<c-card v-if="parsedEmail && emailContent" title="Output">
<c-alert v-if="error">
{{ error }}
</c-alert>
<c-card v-if="!error && parsedEmail" title="Output">
<input-copyable v-if="fileInput?.name" label="File Name" :value="fileInput?.name" />
<input-copyable v-if="parsedEmail.date" label="Date" :value="parsedEmail.date" />
<input-copyable v-if="parsedEmail.from?.name" label="From (name)" :value="parsedEmail.from?.name" />
<input-copyable v-if="parsedEmail.from" label="From (address)" :value="parsedEmail.from?.address || parsedEmail.from" />
<input-copyable v-if="parsedEmail.to" label="To" :value="JSON.stringify(parsedEmail.to)" />
<input-copyable v-if="parsedEmail.cc" label="Cc" :value="JSON.stringify(parsedEmail.cc)" />
<input-copyable v-if="parsedEmail.bcc?.name" label="Bcc" :value="JSON.stringify(parsedEmail.bcc)" />
<input-copyable v-if="parsedEmail.bcc" label="Bcc" :value="JSON.stringify(parsedEmail.bcc)" />
<input-copyable v-if="parsedEmail.replyTo" label="Reply-To" :value="JSON.stringify(parsedEmail.replyTo)" />
<input-copyable v-if="parsedEmail.subject" label="Subject" :value="parsedEmail.subject" />
<c-card v-if="parsedEmail.text" title="Plain Content" mb-2>
<details>
<summary>See content</summary>
<textarea-copyable :value="parsedEmail.text" />
<textarea-copyable :value="parsedEmail.text" word-wrap />
</details>
</c-card>
<c-card v-if="parsedEmail.html" title="Html Content" mb-2>
<details>
<summary>See content</summary>
<textarea-copyable :value="parsedEmail.html" />
<textarea-copyable :value="parsedEmail.html" word-wrap />
</details>
</c-card>
<c-card v-if="parsedEmail?.attachments?.length" title="Attachments" mb-2>
<n-table>
<thead>
<tr>
<th scope="col">Attachment</th><th scope="col" />
<th scope="col">
Attachment
</th><th scope="col" />
</tr>
</thead>
<tbody>