diff --git a/components.d.ts b/components.d.ts index 3e65c3cc..d1171f10 100644 --- a/components.d.ts +++ b/components.d.ts @@ -20,6 +20,7 @@ declare module '@vue/runtime-core' { Bcrypt: typeof import('./src/tools/bcrypt/bcrypt.vue')['default'] BenchmarkBuilder: typeof import('./src/tools/benchmark-builder/benchmark-builder.vue')['default'] Bip39Generator: typeof import('./src/tools/bip39-generator/bip39-generator.vue')['default'] + BounceParser: typeof import('./src/tools/bounce-parser/bounce-parser.vue')['default'] CAlert: typeof import('./src/ui/c-alert/c-alert.vue')['default'] 'CAlert.demo': typeof import('./src/ui/c-alert/c-alert.demo.vue')['default'] CameraRecorder: typeof import('./src/tools/camera-recorder/camera-recorder.vue')['default'] @@ -130,10 +131,9 @@ declare module '@vue/runtime-core' { MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default'] MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] - NCheckbox: typeof import('naive-ui')['NCheckbox'] + NCode: typeof import('naive-ui')['NCode'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] NConfigProvider: typeof import('naive-ui')['NConfigProvider'] - NDivider: typeof import('naive-ui')['NDivider'] NEllipsis: typeof import('naive-ui')['NEllipsis'] NH1: typeof import('naive-ui')['NH1'] NH3: typeof import('naive-ui')['NH3'] @@ -141,7 +141,8 @@ declare module '@vue/runtime-core' { NLayout: typeof import('naive-ui')['NLayout'] NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NMenu: typeof import('naive-ui')['NMenu'] - NSpace: typeof import('naive-ui')['NSpace'] + NP: typeof import('naive-ui')['NP'] + NScrollbar: typeof import('naive-ui')['NScrollbar'] NTable: typeof import('naive-ui')['NTable'] NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] diff --git a/package.json b/package.json index 5c991cff..344a8234 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "crypto-js": "^4.1.1", "date-fns": "^2.29.3", "dompurify": "^3.0.6", + "email-bounce-parser-browser": "^1.1", "email-normalizer": "^1.0.0", "emojilib": "^3.0.10", "figlet": "^1.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3798ae17..44ed2495 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,6 +80,9 @@ dependencies: dompurify: specifier: ^3.0.6 version: 3.0.6 + email-bounce-parser-browser: + specifier: ^1.1 + version: 1.1.0 email-normalizer: specifier: ^1.0.0 version: 1.0.0 @@ -3412,7 +3415,7 @@ packages: dependencies: '@unhead/dom': 0.5.1 '@unhead/schema': 0.5.1 - '@vueuse/shared': 11.0.3(vue@3.3.4) + '@vueuse/shared': 11.1.0(vue@3.3.4) unhead: 0.5.1 vue: 3.3.4 transitivePeerDependencies: @@ -4054,8 +4057,8 @@ packages: - vue dev: false - /@vueuse/shared@11.0.3(vue@3.3.4): - resolution: {integrity: sha512-0rY2m6HS5t27n/Vp5cTDsKTlNnimCqsbh/fmT2LgE+aaU42EMfXo8+bNX91W9I7DDmxfuACXMmrd7d79JxkqWA==} + /@vueuse/shared@11.1.0(vue@3.3.4): + resolution: {integrity: sha512-YUtIpY122q7osj+zsNMFAfMTubGz0sn5QzE5gPzAIiCmtt2ha3uQUY1+JPyL4gRCTsLPX82Y9brNbo/aqlA91w==} dependencies: vue-demi: 0.14.10(vue@3.3.4) transitivePeerDependencies: @@ -5036,6 +5039,10 @@ packages: /electron-to-chromium@1.4.572: resolution: {integrity: sha512-RlFobl4D3ieetbnR+2EpxdzFl9h0RAJkPK3pfiwMug2nhBin2ZCsGIAJWdpNniLz43sgXam/CgipOmvTA+rUiA==} + /email-bounce-parser-browser@1.1.0: + resolution: {integrity: sha512-aPMJKpvAPTFTqDI3/bMxcEHK3Wk2yy/tNK/nVWHl5zqyoxJFFgO5a2jvVno4V3e+kyLjZx2M5KJNgNmYf0ZkuQ==} + dev: false + /email-normalizer@1.0.0: resolution: {integrity: sha512-wZYuuMtL4kUOmg/TPtCrf9hAZjbFq+FcjWA85Z5nr2lGllRnWJPxCJw3gy4Cx+adMoyVw4VJfGGvt/OHgIW+qg==} dependencies: diff --git a/public/re2.wasm b/public/re2.wasm new file mode 100755 index 00000000..515fbca9 Binary files /dev/null and b/public/re2.wasm differ diff --git a/src/components/TextareaCopyable.vue b/src/components/TextareaCopyable.vue index 9585177d..a0961f1b 100644 --- a/src/components/TextareaCopyable.vue +++ b/src/components/TextareaCopyable.vue @@ -7,8 +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<{ @@ -17,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); @@ -31,13 +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, + }); diff --git a/src/composable/downloadBase64.ts b/src/composable/downloadBase64.ts index 3bc20226..773541e2 100644 --- a/src/composable/downloadBase64.ts +++ b/src/composable/downloadBase64.ts @@ -1,6 +1,7 @@ import { extension as getExtensionFromMimeType, extension as getMimeTypeFromExtension } from 'mime-types'; -import type { Ref } from 'vue'; +import type { MaybeRef, Ref } from 'vue'; import _ from 'lodash'; +import { get } from '@vueuse/core'; export { getMimeTypeFromBase64, @@ -75,21 +76,11 @@ function downloadFromBase64({ sourceValue, filename, extension, fileMimeType }: } function useDownloadFileFromBase64( - { source, filename, extension, fileMimeType }: - { source: Ref; filename?: string; extension?: string; fileMimeType?: string }) { - return { - download() { - downloadFromBase64({ sourceValue: source.value, filename, extension, fileMimeType }); - }, - }; -} - -function useDownloadFileFromBase64Refs( { source, filename, extension }: - { source: Ref; filename?: Ref; extension?: Ref }) { + { source: MaybeRef; filename?: MaybeRef; extension?: MaybeRef }) { return { download() { - downloadFromBase64({ sourceValue: source.value, filename: filename?.value, extension: extension?.value }); + downloadFromBase64({ sourceValue: get(source), filename: get(filename), extension: get(extension) }); }, }; } @@ -116,3 +107,13 @@ function previewImageFromBase64(base64String: string): HTMLImageElement { return img; } + +function useDownloadFileFromBase64Refs( + { source, filename, extension }: + { source: Ref; filename?: Ref; extension?: Ref }) { + return { + download() { + downloadFromBase64({ sourceValue: source.value, filename: filename?.value, extension: extension?.value }); + }, + }; +} diff --git a/src/tools/bounce-parser/bounce-parser.vue b/src/tools/bounce-parser/bounce-parser.vue new file mode 100644 index 00000000..5ad048cb --- /dev/null +++ b/src/tools/bounce-parser/bounce-parser.vue @@ -0,0 +1,66 @@ + + + diff --git a/src/tools/bounce-parser/email-bounce-parser-browser.d.ts b/src/tools/bounce-parser/email-bounce-parser-browser.d.ts new file mode 100644 index 00000000..e526d8af --- /dev/null +++ b/src/tools/bounce-parser/email-bounce-parser-browser.d.ts @@ -0,0 +1,36 @@ +declare module "email-bounce-parser-browser" { + export default class EmailBounceParse { + read(emailContent: string): { + bounce: boolean + email?: { + body?: string + intro?: string + error?: string + } + data?: { + error?: { + code?: { + basic?: string + enhanced?:string + } + label?: string + type?: string + temporary?: boolean + permanent?: boolean + data?: { + type?: string + blocked?: boolean + spam?: boolean + } + } + recipient?:string + server?: { + hostname?: string + ip?: string + port?: string + } + command?: string + } + } + } +} \ No newline at end of file diff --git a/src/tools/bounce-parser/index.ts b/src/tools/bounce-parser/index.ts new file mode 100644 index 00000000..7822c349 --- /dev/null +++ b/src/tools/bounce-parser/index.ts @@ -0,0 +1,12 @@ +import { Mailbox } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'Bounce Email Parser', + path: '/bounce-parser', + description: 'Parse SMTP Bounce Emails', + keywords: ['bounce', 'email', 'smtp', 'parser'], + component: () => import('./bounce-parser.vue'), + icon: Mailbox, + createdAt: new Date('2024-08-15'), +}); diff --git a/src/tools/index.ts b/src/tools/index.ts index 388cfaf4..f3916768 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -2,6 +2,7 @@ import { tool as base64FileConverter } from './base64-file-converter'; import { tool as base64StringConverter } from './base64-string-converter'; import { tool as basicAuthGenerator } from './basic-auth-generator'; import { tool as emailNormalizer } from './email-normalizer'; +import { tool as bounceParser } from './bounce-parser'; import { tool as asciiTextDrawer } from './ascii-text-drawer'; @@ -137,6 +138,7 @@ export const toolsByCategory: ToolCategory[] = [ httpStatusCodes, jsonDiff, safelinkDecoder, + bounceParser, ], }, { diff --git a/vite.config.ts b/vite.config.ts index 42a2cb29..87a35f7c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -113,4 +113,7 @@ export default defineConfig({ build: { target: 'esnext', }, + optimizeDeps: { + include: ['re2-wasm-embedded'], // optionally specify dependency name + }, });