Skip to content

Commit 0c2dcbd

Browse files
committed
feat: add file upload option and word wrap
1 parent 78c4436 commit 0c2dcbd

File tree

2 files changed

+93
-12
lines changed

2 files changed

+93
-12
lines changed

src/components/TextareaCopyable.vue

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,15 @@ import sqlHljs from 'highlight.js/lib/languages/sql';
77
import xmlHljs from 'highlight.js/lib/languages/xml';
88
import yamlHljs from 'highlight.js/lib/languages/yaml';
99
import iniHljs from 'highlight.js/lib/languages/ini';
10+
import bashHljs from 'highlight.js/lib/languages/bash';
11+
import markdownHljs from 'highlight.js/lib/languages/markdown';
12+
import jsHljs from 'highlight.js/lib/languages/javascript';
13+
import cssHljs from 'highlight.js/lib/languages/css';
14+
import goHljs from 'highlight.js/lib/languages/go';
15+
import csharpHljs from 'highlight.js/lib/languages/csharp';
16+
import { Base64 } from 'js-base64';
1017
import { useCopy } from '@/composable/copy';
18+
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
1119
1220
const props = withDefaults(
1321
defineProps<{
@@ -16,12 +24,17 @@ const props = withDefaults(
1624
language?: string
1725
copyPlacement?: 'top-right' | 'bottom-right' | 'outside' | 'none'
1826
copyMessage?: string
27+
wordWrap?: boolean
28+
downloadFileName?: string
29+
downloadButtonText?: string
1930
}>(),
2031
{
2132
followHeightOf: null,
2233
language: 'txt',
2334
copyPlacement: 'top-right',
2435
copyMessage: 'Copy to clipboard',
36+
downloadFileName: '',
37+
downloadButtonText: 'Download',
2538
},
2639
);
2740
hljs.registerLanguage('sql', sqlHljs);
@@ -30,12 +43,25 @@ hljs.registerLanguage('html', xmlHljs);
3043
hljs.registerLanguage('xml', xmlHljs);
3144
hljs.registerLanguage('yaml', yamlHljs);
3245
hljs.registerLanguage('toml', iniHljs);
46+
hljs.registerLanguage('bash', bashHljs);
47+
hljs.registerLanguage('markdown', markdownHljs);
48+
hljs.registerLanguage('css', cssHljs);
49+
hljs.registerLanguage('javascript', jsHljs);
50+
hljs.registerLanguage('go', goHljs);
51+
hljs.registerLanguage('csharp', csharpHljs);
3352
34-
const { value, language, followHeightOf, copyPlacement, copyMessage } = toRefs(props);
53+
const { value, language, followHeightOf, copyPlacement, copyMessage, downloadFileName, downloadButtonText } = toRefs(props);
3554
const { height } = followHeightOf.value ? useElementSize(followHeightOf) : { height: ref(null) };
3655
3756
const { copy, isJustCopied } = useCopy({ source: value, createToast: false });
3857
const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.value);
58+
59+
const valueBase64 = computed(() => Base64.encode(value.value));
60+
const { download } = useDownloadFileFromBase64(
61+
{
62+
source: valueBase64,
63+
filename: downloadFileName,
64+
});
3965
</script>
4066

4167
<template>
@@ -47,11 +73,16 @@ const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.
4773
:style="height ? `min-height: ${height - 40 /* card padding */ + 10 /* negative margin compensation */}px` : ''"
4874
>
4975
<n-config-provider :hljs="hljs">
50-
<n-code :code="value" :language="language" :trim="false" data-test-id="area-content" />
76+
<n-code :code="value" :language="language" :word-wrap="wordWrap" :trim="false" data-test-id="area-content" />
5177
</n-config-provider>
5278
</n-scrollbar>
53-
<div absolute right-10px top-10px>
54-
<c-tooltip v-if="value" :tooltip="tooltipText" position="left">
79+
<div
80+
v-if="value && copyPlacement !== 'none'"
81+
absolute right-10px
82+
:top-10px="copyPlacement === 'top-right' ? '' : 'no'"
83+
:bottom-10px="copyPlacement === 'bottom-right' ? '' : 'no'"
84+
>
85+
<c-tooltip v-if="value && copyPlacement !== 'outside'" :tooltip="tooltipText" position="left">
5586
<c-button circle important:h-10 important:w-10 @click="copy()">
5687
<n-icon size="22" :component="Copy" />
5788
</c-button>
@@ -63,6 +94,11 @@ const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.
6394
{{ tooltipText }}
6495
</c-button>
6596
</div>
97+
<div v-if="downloadFileName !== '' && value !== ''" mt-5 flex justify-center>
98+
<c-button secondary @click="download">
99+
{{ downloadButtonText }}
100+
</c-button>
101+
</div>
66102
</div>
67103
</template>
68104

src/tools/email-parser/email-parser.vue

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,31 @@
11
<script setup lang="ts">
22
import PostalMime from 'postal-mime';
33
4+
const inputType = ref<'file' | 'content'>('file');
45
const emailContent = ref('');
6+
const fileInput = ref() as Ref<File | null>;
7+
const error = ref('');
58
69
const parsedEmail = computedAsync(async () => {
710
const emailContentValue = emailContent.value;
11+
const file = fileInput.value;
12+
error.value = '';
813
try {
9-
return await PostalMime.parse(emailContentValue);
14+
if (inputType.value === 'file' && file) {
15+
return await PostalMime.parse(await file.arrayBuffer());
16+
} else if (inputType.value === 'content' && emailContentValue) {
17+
return await PostalMime.parse(emailContentValue);
18+
} else {
19+
return null;
20+
}
1021
}
1122
catch (e: any) {
12-
return e.toString();
23+
error.value = e.toString();
24+
return null;
1325
}
1426
});
1527
16-
function downloadFile(data: Uint8Array, fileName: string, fileType: string) {
28+
function downloadFile(data: ArrayBuffer, fileName: string, fileType: string) {
1729
const blob = new Blob([data], { type: fileType || 'application/octet-stream' });
1830
const downloadUrl = URL.createObjectURL(blob);
1931
const a = document.createElement('a');
@@ -23,12 +35,38 @@ function downloadFile(data: Uint8Array, fileName: string, fileType: string) {
2335
a.click();
2436
URL.revokeObjectURL(downloadUrl);
2537
}
38+
39+
function onUpload(file: File) {
40+
if (file) {
41+
fileInput.value = file;
42+
}
43+
}
2644
</script>
2745

2846
<template>
2947
<div style="max-width: 600px;">
3048
<c-card title="Input" mb-2>
49+
<n-radio-group v-model:value="inputType" name="radiogroup" mb-2 flex justify-center>
50+
<n-space>
51+
<n-radio
52+
value="file"
53+
label="File"
54+
/>
55+
<n-radio
56+
value="content"
57+
label="Content"
58+
/>
59+
</n-space>
60+
</n-radio-group>
61+
62+
<c-file-upload
63+
v-if="inputType === 'file'"
64+
title="Drag and drop EML file here, or click to select a file"
65+
@file-upload="onUpload"
66+
/>
67+
3168
<c-input-text
69+
v-if="inputType === 'content'"
3270
v-model:value="emailContent"
3371
label="Email Content"
3472
multiline
@@ -38,32 +76,39 @@ function downloadFile(data: Uint8Array, fileName: string, fileType: string) {
3876
/>
3977
</c-card>
4078

41-
<c-card v-if="parsedEmail && emailContent" title="Output">
79+
<c-alert v-if="error">
80+
{{ error }}
81+
</c-alert>
82+
83+
<c-card v-if="!error && parsedEmail" title="Output">
84+
<input-copyable v-if="fileInput?.name" label="File Name" :value="fileInput?.name" />
4285
<input-copyable v-if="parsedEmail.date" label="Date" :value="parsedEmail.date" />
4386
<input-copyable v-if="parsedEmail.from?.name" label="From (name)" :value="parsedEmail.from?.name" />
4487
<input-copyable v-if="parsedEmail.from" label="From (address)" :value="parsedEmail.from?.address || parsedEmail.from" />
4588
<input-copyable v-if="parsedEmail.to" label="To" :value="JSON.stringify(parsedEmail.to)" />
4689
<input-copyable v-if="parsedEmail.cc" label="Cc" :value="JSON.stringify(parsedEmail.cc)" />
47-
<input-copyable v-if="parsedEmail.bcc?.name" label="Bcc" :value="JSON.stringify(parsedEmail.bcc)" />
90+
<input-copyable v-if="parsedEmail.bcc" label="Bcc" :value="JSON.stringify(parsedEmail.bcc)" />
4891
<input-copyable v-if="parsedEmail.replyTo" label="Reply-To" :value="JSON.stringify(parsedEmail.replyTo)" />
4992
<input-copyable v-if="parsedEmail.subject" label="Subject" :value="parsedEmail.subject" />
5093
<c-card v-if="parsedEmail.text" title="Plain Content" mb-2>
5194
<details>
5295
<summary>See content</summary>
53-
<textarea-copyable :value="parsedEmail.text" />
96+
<textarea-copyable :value="parsedEmail.text" word-wrap />
5497
</details>
5598
</c-card>
5699
<c-card v-if="parsedEmail.html" title="Html Content" mb-2>
57100
<details>
58101
<summary>See content</summary>
59-
<textarea-copyable :value="parsedEmail.html" />
102+
<textarea-copyable :value="parsedEmail.html" word-wrap />
60103
</details>
61104
</c-card>
62105
<c-card v-if="parsedEmail?.attachments?.length" title="Attachments" mb-2>
63106
<n-table>
64107
<thead>
65108
<tr>
66-
<th scope="col">Attachment</th><th scope="col" />
109+
<th scope="col">
110+
Attachment
111+
</th><th scope="col" />
67112
</tr>
68113
</thead>
69114
<tbody>

0 commit comments

Comments
 (0)