Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
f95c268
feat(wxt): init project
jurajhilje Nov 24, 2025
c1f87ae
feat(wxt): create store.ts
jurajhilje Nov 25, 2025
ffe2bd7
feat(wxt): create api.ts
jurajhilje Nov 25, 2025
af44df7
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Nov 25, 2025
2d1ee68
feat(wxt): update api.ts
jurajhilje Nov 25, 2025
a3d0cd5
feat(wxt): update wxt.config.ts
jurajhilje Nov 25, 2025
9d78c54
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Nov 26, 2025
8e53544
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Nov 26, 2025
112baab
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Nov 26, 2025
b319665
feat(wxt): create LoginScreen.vue
jurajhilje Nov 26, 2025
8f17055
feat(wxt): update index.html
jurajhilje Nov 26, 2025
e0d8471
feat(wxt): update LoginScreen.vue
jurajhilje Nov 27, 2025
0854e08
feat(wxt): create AliasesScreen.vue
jurajhilje Nov 27, 2025
9dc1c47
feat(wxt): update store.ts
jurajhilje Nov 27, 2025
a6bfb49
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Nov 29, 2025
19baa8e
feat(wxt): update store.ts
jurajhilje Nov 29, 2025
036bc82
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Nov 29, 2025
caaf1bd
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Dec 1, 2025
96da1f4
feat(wxt): update AliasesScreen.vue
jurajhilje Dec 1, 2025
f950c92
feat(wxt): update store.ts
jurajhilje Dec 1, 2025
92ab7e4
feat(api): update routes.go
jurajhilje Dec 1, 2025
a2eb56e
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Dec 2, 2025
8466c74
feat(wxt): update AliasesScreen.vue
jurajhilje Dec 2, 2025
df4fff6
feat(wxt): update package.json
jurajhilje Dec 2, 2025
5d2ca45
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Dec 2, 2025
3420fb7
feat(wxt): update api.ts
jurajhilje Dec 3, 2025
05377d4
feat(wxt): update style.css
jurajhilje Dec 3, 2025
f6aeeaf
feat(wxt): update package.json
jurajhilje Dec 3, 2025
498dddf
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Dec 3, 2025
7020780
feat(wxt): update types.ts
jurajhilje Dec 3, 2025
50e002a
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Dec 3, 2025
ed9f46d
feat(wxt): update LoginScreen.vue
jurajhilje Dec 3, 2025
f781612
feat(wxt): create AliasCreate.vue
jurajhilje Dec 4, 2025
b1a4437
feat(wxt): update types.ts
jurajhilje Dec 4, 2025
0e05fe2
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Dec 4, 2025
4ee00cd
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Dec 4, 2025
c3c8c81
feat(wxt): update App.vue
jurajhilje Dec 4, 2025
610eb59
feat(wxt): update AliasCreate.vue
jurajhilje Dec 4, 2025
cf5eb5f
feat(wxt): update AliasCreate.vue
jurajhilje Dec 4, 2025
f35f245
feat(wxt): update AliasesScreen.vue
jurajhilje Dec 5, 2025
34744ef
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Dec 5, 2025
e919bad
feat(wxt): update AliasesScreen.vue
jurajhilje Dec 5, 2025
bf1d907
feat(wxt): update App.vue
jurajhilje Dec 5, 2025
2216671
feat(wxt): update AliasesScreen.vue
jurajhilje Dec 5, 2025
4bec914
feat(wxt): update AliasesScreen.vue
jurajhilje Dec 5, 2025
ec6981c
feat(wxt): update App.vue
jurajhilje Dec 5, 2025
ed2717e
feat(wxt): update App.vue
jurajhilje Dec 5, 2025
aad92c2
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Dec 7, 2025
c7e81f0
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Dec 7, 2025
1865099
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Dec 8, 2025
abee955
feat(wxt): update App.vue
jurajhilje Dec 8, 2025
1570ccc
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Dec 8, 2025
8f429ec
feat(wxt): update Settings.vue
jurajhilje Dec 9, 2025
e99399d
feat(wxt): update Settings.vue
jurajhilje Dec 9, 2025
56515d5
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Dec 9, 2025
3320972
feat(wxt): update Settings.vue
jurajhilje Dec 9, 2025
4996fe2
feat(wxt): update App.vue
jurajhilje Dec 9, 2025
48eece7
feat(wxt): update Aliases.vue
jurajhilje Dec 9, 2025
7a45402
feat(wxt): create theme.ts
jurajhilje Dec 9, 2025
bca1f82
feat(wxt): update Settings.vue
jurajhilje Dec 9, 2025
4099a00
feat(wxt): update package.json
jurajhilje Dec 9, 2025
f0ea1d5
feat(wxt): update Login.vue
jurajhilje Dec 10, 2025
a7b0c96
feat(wxt): update App.vue
jurajhilje Dec 10, 2025
052fcbb
feat(wxt): update index.html
jurajhilje Dec 10, 2025
3b03782
feat(wxt): update style.css
jurajhilje Dec 10, 2025
38290f9
Merge branch 'feature/access-keys' into feature/wxt
jurajhilje Jan 5, 2026
0c16c94
Merge branch 'main' into feature/wxt
jurajhilje Jan 6, 2026
6d9807e
feat(wxt): update content.ts
jurajhilje Jan 7, 2026
d004613
feat(wxt): update wxt.config.ts
jurajhilje Jan 7, 2026
0c33d04
feat(wxt): update content.ts
jurajhilje Jan 8, 2026
77dd79d
feat(wxt): update background.ts
jurajhilje Jan 8, 2026
9706efd
feat(wxt): update content.ts
jurajhilje Jan 8, 2026
1be7135
feat(wxt): update content.ts
jurajhilje Jan 9, 2026
f87c371
feat(wxt): update content.ts
jurajhilje Jan 9, 2026
91ffc01
feat(wxt): update Settings.vue
jurajhilje Jan 9, 2026
acc6bbb
feat(wxt): update content.ts
jurajhilje Jan 9, 2026
7a6cdb1
feat(wxt): update content.ts
jurajhilje Jan 10, 2026
0b3aab5
feat(wxt): update content.ts
jurajhilje Jan 10, 2026
c024197
feat(wxt): update wxt.config.ts
jurajhilje Jan 12, 2026
db14fdb
feat(wxt): update wxt.config.ts
jurajhilje Jan 12, 2026
e2a38bf
Merge branch 'main' into feature/wxt
jurajhilje Jan 13, 2026
c231bfe
feat(wxt): update content.ts
jurajhilje Jan 14, 2026
f76772c
feat(wxt): update App.vue
jurajhilje Jan 15, 2026
bdf054f
feat(wxt): update Settings.vue
jurajhilje Jan 15, 2026
0aa4bb3
Merge branch 'main' into feature/wxt
jurajhilje Jan 15, 2026
20dcc28
feat(wxt): update content.ts
jurajhilje Jan 16, 2026
20bc660
feat(wxt): update package.json
jurajhilje Jan 16, 2026
b97c53a
feat(wxt): update Settings.vue
jurajhilje Jan 16, 2026
454935a
feat(wxt): update AliasCreate.vue
jurajhilje Jan 19, 2026
665aa76
feat(wxt): update AliasCreate.vue
jurajhilje Jan 19, 2026
2090e3d
feat(wxt): Aliases.vue
jurajhilje Jan 19, 2026
1977d92
feat(wxt): update package.json
jurajhilje Jan 19, 2026
a9123c7
Merge branch 'main' into feature/wxt
jurajhilje Jan 22, 2026
b43ad3e
feat(wxt): update content.ts
jurajhilje Jan 22, 2026
f1731d2
feat(wxt): update package.json
jurajhilje Jan 22, 2026
f990304
docs: update README.md
jurajhilje Jan 22, 2026
02c74e4
Merge branch 'main' into feature/wxt
jurajhilje Feb 4, 2026
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
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@

- [Docker Mailserver](https://github.com/docker-mailserver/docker-mailserver)

## Browser Extension

- [WXT](https://wxt.dev)

## Installation

### Prerequisites
Expand Down Expand Up @@ -160,6 +164,32 @@ docker compose down
docker compose up -d
```

### Browser Extension

#### Move to wxt directory
```bash
cd wxt
```

#### Run (development)
```bash
yarn dev
yarn dev:firefox
```

#### Build (beta, testing)
```bash
yarn build
yarn build:staging
yarn build:production
yarn build:firefox
```

#### Build (distribution)
```bash
yarn zip
yarn zip:firefox
```

## Add New Domain

Expand Down
10 changes: 10 additions & 0 deletions app/src/assets/icons/icon-copy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions app/src/style/components/form.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@

input[type=checkbox] {
@apply form-checkbox relative w-11 h-6 p-px bg-tertiary border-transparent text-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:ring-transparent disabled:opacity-50 disabled:pointer-events-none checked:bg-none checked:text-info checked:border-info focus:ring-offset-transparent before:inline-block before:size-5 before:bg-white checked:before:bg-white before:translate-x-0 checked:before:translate-x-full before:rounded-full before:shadow before:transform before:transition before:ease-in-out before:duration-200;

&.xs {
@apply w-9 h-5 before:size-4;
}
}

input[type=radio] {
Expand Down
4 changes: 4 additions & 0 deletions app/src/style/components/icon.css
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@
&.alert {
mask-image: url("../../assets/icons/icon-alert.svg");
}

&.copy {
mask-image: url("../../assets/icons/icon-copy.svg");
}
}

.logo {
Expand Down
27 changes: 27 additions & 0 deletions wxt/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
.output
stats.html
stats-*.json
.wxt
web-ext.config.ts
.env*

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
3 changes: 3 additions & 0 deletions wxt/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}
2 changes: 2 additions & 0 deletions wxt/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Readme

1 change: 1 addition & 0 deletions wxt/assets/vue.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
266 changes: 266 additions & 0 deletions wxt/components/popup/AliasCreate.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
<template>
<div>
<div id="modal-create-alias" class="hs-overlay hidden">
<div>
<div>
<header>
<button @click="close" class="close">
<i class="icon arrow-left-line icon-primary"></i>
</button>
<h4 class="uppercase">
New Alias
</h4>
</header>
<article>
<div>
<div class="pb-3">
<label for="alias_description">
Description (optional)
</label>
<input
v-model="alias.description"
id="alias_description"
type="text"
>
</div>
<div class="pb-3">
<label for="create-alias-recipient">
Recipient(s)
</label>
<select
id="create-alias-recipient"
v-model="selectRecipients"
:multiple="true"
data-hs-select='{
"placeholder": "Select recipient(s)",
"toggleTag": "<button type=\"button\" aria-expanded=\"false\"></button>",
"toggleClasses": "hs-select-disabled:pointer-events-none hs-select-disabled:opacity-50 relative py-2.5 ps-4 pe-9 flex gap-x-2 text-nowrap w-full cursor-pointer border border-primary text-secondary bg-secondary leading-tight focus:border-accent",
"dropdownClasses": "mt-2 z-50 w-full max-h-72 p-1 space-y-0.5 bg-primary border border-tertiary overflow-hidden overflow-y-auto",
"optionClasses": "py-2 px-4 w-full text-secondary cursor-pointer hover:bg-secondary bg-primary",
"optionTemplate": "<div class=\"flex justify-between items-center w-full\"><span data-title></span><span class=\"hidden hs-selected:block\"><svg class=\"shrink-0 size-3.5 text-accent \" xmlns=\"http:.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg></span></div>",
"extraMarkup": "<div class=\"absolute top-1/2 end-3 -translate-y-1/2\"><svg class=\"shrink-0 size-3.5 text-secondary \" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m7 15 5 5 5-5\"/><path d=\"m7 9 5-5 5 5\"/></svg></div>"
}' class="hidden">
<option v-for="(recipient, index) in defaults.recipients"
v-bind:value=recipient
:selected="recipient == defaults.recipient || (!defaults.recipient && index === 0)"
:key="recipient">
{{ recipient }}
</option>
</select>
<p v-if="errorRecipients" class="error pt-3">{{ errorRecipients }}</p>
</div>
<div class="hs-accordion-group">
<div class="hs-accordion" id="alias-accordion-one">
<button class="hs-accordion-toggle plain font-bold flex flex-row gap-2 mb-2 items-center" aria-expanded="false" aria-controls="alias-accordion-collapse-one">
<svg class="hs-accordion-active:hidden inline-flex size-3.5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14"></path>
<path d="M12 5v14"></path>
</svg>
<svg class="hs-accordion-active:inline-flex hidden size-3.5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14"></path>
</svg>
Custom settings
</button>
<div id="alias-accordion-collapse-one" class="hs-accordion-content hidden overflow-hidden transition-[height] duration-300" role="region" aria-labelledby="alias-accordion-one">
<div class="pb-3">
<label for="alias_format">
Format
</label>
<select id="alias_format">
<option v-for="(format, index) in formats" v-bind:value="format.value"
:selected="format.value == defaults.alias_format || index === 0" :key="format.value">
{{ format.name }}
</option>
</select>
</div>
<div class="pb-3">
<label for="alias_domain">
Domain
</label>
<select id="alias_domain" :disabled="!defaults.domains.length">
<option v-for="(domain, index) in defaults.domains" v-bind:domain
:selected="domain == defaults.domain || index === 0" :key="domain">
{{ domain }}
</option>
</select>
</div>
<div class="pb-3">
<label for="alias_from_name" class="flex gap-2">
From name
<span class="hs-tooltip [--strategy:absolute] flex">
<i class="icon info icon-primary hs-tooltip-toggle"></i>
<span class="hs-tooltip-content hs-tooltip-shown:opacity-100 hs-tooltip-shown:visible text-center" role="tooltip">
Leave blank to use<br>
alias email address or<br>
default From Name defined<br>
in Settings (if set)
</span>
</span>
</label>
<input
v-model="alias.from_name"
id="alias_from_name"
type="text"
>
</div>
</div>
</div>
</div>
</div>
</article>
<footer>
<nav>
<button
v-bind:disabled="errorRecipients.length > 0"
@click="postAlias"
class="cta">
Create and copy to clipboard
</button>
<button @click="close" class="cancel">
Cancel
</button>
</nav>
<p v-if="error" class="error px-5">Error: {{ error }}</p>
</footer>
</div>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import overlay from '@preline/overlay'
import select from '@preline/select'
import { api } from '@/lib/api'
import events from '@/lib/events'
import tooltip from '@preline/tooltip'
import accordion from '@preline/accordion'
import { Defaults, Alias } from '@/lib/types'

const props = defineProps<{
apiToken: string
defaults: Defaults
}>()
const alias = ref({} as Alias)
const selectRecipients = ref([] as string[])
const formats = ref([{
name: 'Words',
value: 'words'
}, {
name: 'Random',
value: 'random'
}, {
name: 'UUID',
value: 'uuid'
}])
const error = ref('')
const errorRecipients = ref('')
const loading = ref(false)

const postAlias = async () => {
if (loading.value) return

alias.value.domain = (document.getElementById('alias_domain') as HTMLInputElement).value
alias.value.recipients = selectRecipients.value.join(',')
alias.value.enabled = true

const formatElement = document.getElementById('alias_format') as HTMLInputElement;
if (formatElement) {
alias.value.format = formatElement.value;
}

if (!validate(alias.value.recipients)) return

try {
loading.value = true
const res = await api.createAlias(props.apiToken, alias.value)
console.log('Created alias:', res)
copyAlias(res.alias.name)
error.value = ''
events.emit('alias.create', { alias: res.alias})
close()
} catch (err) {
error.value = 'An unexpected error occurred'
console.error('Create alias error:', err)
} finally {
loading.value = false
}
}

const close = () => {
resetAlias()
error.value = ''

document.removeEventListener('keydown', handleKeydown)

const accordionInstance = accordion.getInstance('#alias-accordion-one', true);
if (accordionInstance && 'element' in accordionInstance) {
accordionInstance.element.hide()
}

const modal = document.querySelector('#modal-create-alias') as any
overlay.close(modal)

const multiselect = select.getInstance('#create-alias-recipient' as any, true) as any
multiselect.element.setValue([props.defaults.recipient])
}

const addEvents = () => {
const modal = overlay.getInstance('#modal-create-alias' as any, true) as any
modal.element.on('close', () => {
close()
})
modal.element.on('open', () => {
document.addEventListener('keydown', handleKeydown)
focusFirstInput()
})

const multiselect = select.getInstance('#create-alias-recipient' as any, true) as any
multiselect.element.on('change', (val: any) => {
errorRecipients.value = val.length === 0 ? 'Select one or more recipients' : ''
})
}

const handleKeydown = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault()
postAlias()
}
}

const focusFirstInput = () => {
const input = document.getElementById('alias_description')
input?.focus()
}

const validate = (rcps: string) => {
if (rcps.length === 0) {
error.value = 'Select one or more recipients'
return false
}

return true
}

const resetAlias = () => {
const multiselect = select.getInstance('#create-alias-recipient' as any, true) as any
multiselect.element.setValue([props.defaults.recipient])
selectRecipients.value = [props.defaults.recipient]
alias.value = {} as Alias
alias.value.domain = props.defaults.domain
}

const copyAlias = (alias: string) => {
navigator.clipboard.writeText(alias)
}

onMounted(async () => {
overlay.autoInit()
select.autoInit()
tooltip.autoInit()
accordion.autoInit()
addEvents()
resetAlias()
})
</script>
Loading
Loading