render data with Handlebars template, support tags
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
b772c83c53
commit
1ca64bc925
@ -5,6 +5,7 @@
|
||||
"origins": [
|
||||
"http://localhost:3030"
|
||||
],
|
||||
"sessionSecret": "test",
|
||||
"paginate": {
|
||||
"default": 10,
|
||||
"max": 50
|
||||
|
@ -2,8 +2,7 @@
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>walias</title>
|
||||
<script src="index.js" async></script>
|
||||
<title>WildDuck Aliases</title>
|
||||
<meta name="description" content="Aliases for Wild Duck">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
@ -11,6 +10,8 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>
|
||||
<script src="index.js" async></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
@ -54,7 +55,7 @@
|
||||
<div class="wrapper">
|
||||
<div class="header">
|
||||
<img class="logo" src="logo.jpg">
|
||||
<h1>Wildduck aliases</h1>
|
||||
<h1>WildDuck Aliases</h1>
|
||||
</div>
|
||||
<div id="container"></div>
|
||||
</div>
|
||||
|
156
public/index.js
156
public/index.js
@ -1,107 +1,73 @@
|
||||
function renderAliases(aliases) {
|
||||
const dataContainer = document.getElementById('container')
|
||||
// Create table element ant populate with items
|
||||
const table = document.createElement('table')
|
||||
table.classList.add('table')
|
||||
const thead = document.createElement('thead')
|
||||
const tr = document.createElement('tr')
|
||||
function renderAliases(data) {
|
||||
const dataContainer = document.getElementById('container');
|
||||
const template = Handlebars.compile(okHandlebarsTemplate);
|
||||
const html = template({
|
||||
data: data.sort((a, b) => new Date(a.created) - new Date(b.created))
|
||||
});
|
||||
|
||||
for (const value of ['Address', 'Created']) {
|
||||
const th = document.createElement('th')
|
||||
th.setAttribute('scope', 'col')
|
||||
th.innerText = value
|
||||
tr.appendChild(th)
|
||||
}
|
||||
|
||||
thead.appendChild(tr)
|
||||
table.appendChild(thead)
|
||||
|
||||
const tbody = document.createElement('tbody')
|
||||
// sort by field created
|
||||
aliases
|
||||
.sort((a, b) => new Date(a.created) - new Date(b.created))
|
||||
.forEach(alias => {
|
||||
const tr = document.createElement('tr')
|
||||
const tdAddress = document.createElement('td')
|
||||
// On click copy to clipboard
|
||||
tdAddress.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(alias.address)
|
||||
})
|
||||
tdAddress.innerText = alias.address
|
||||
const tdCreated = document.createElement('td')
|
||||
tdCreated.innerText = alias.created
|
||||
const tdActions = document.createElement('td')
|
||||
|
||||
const deleteButton = createDeleteButton(alias)
|
||||
|
||||
tdActions.appendChild(deleteButton)
|
||||
|
||||
tr.appendChild(tdAddress)
|
||||
tr.appendChild(tdCreated)
|
||||
tr.appendChild(tdActions)
|
||||
tbody.appendChild(tr)
|
||||
})
|
||||
|
||||
table.appendChild(tbody)
|
||||
|
||||
dataContainer.innerHTML = ''
|
||||
dataContainer.appendChild(table)
|
||||
|
||||
renderCreateButton();
|
||||
dataContainer.innerHTML = html;
|
||||
}
|
||||
|
||||
function createDeleteButton(alias) {
|
||||
const deleteButton = document.createElement('a')
|
||||
deleteButton.classList.add('btn', 'btn-danger')
|
||||
function createAlias() {
|
||||
const tags = document.getElementById('tags').value?.split(',') || [];
|
||||
|
||||
if (!alias.id) {
|
||||
deleteButton.classList.add('disabled')
|
||||
} else {
|
||||
|
||||
deleteButton.addEventListener('click', async () => {
|
||||
const res = await fetch(`/aliases/${alias.id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
renderAliases(data)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deleteButton.innerText = 'Delete'
|
||||
|
||||
return deleteButton
|
||||
}
|
||||
|
||||
function renderCreateButton() {
|
||||
const dataContainer = document.getElementById('container')
|
||||
const button = document.createElement('a')
|
||||
button.classList.add('btn')
|
||||
button.classList.add('btn-primary')
|
||||
|
||||
button.addEventListener('click', async () => {
|
||||
button.innerText = 'Creating, please wait...'
|
||||
const res = await fetch('/aliases', {
|
||||
method: 'POST'
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
renderAliases(data)
|
||||
return
|
||||
fetch('/aliases', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ tags }),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
button.innerText = 'Create new alias'
|
||||
dataContainer.appendChild(button)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
renderAliases(data)
|
||||
})
|
||||
}
|
||||
|
||||
function deleteAlias(id) {
|
||||
fetch(`/aliases/${id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
renderAliases(data)
|
||||
})
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text)
|
||||
}
|
||||
|
||||
const okHandlebarsTemplate = `
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Address</th>
|
||||
<th scope="col">Tags</th>
|
||||
<th scope="col">Created</th>
|
||||
<th scope="col">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each data}}
|
||||
<tr>
|
||||
<td onclick="copyToClipboard('{{address}}')" style="cursor: pointer;">{{address}}</td>
|
||||
<td> {{#each tags}}{{this}} {{/each}}</td>
|
||||
<td>{{created}}</td>
|
||||
<td>
|
||||
<div onclick="deleteAlias('{{id}}')" class="btn btn-danger {{#unless id}}disabled{{/unless}}">Delete</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
<input type="text" id="tags" placeholder="Add tags (optional)">
|
||||
<div class="btn btn-primary" onclick="createAlias()">Create alias</div class="btn btn-primary">
|
||||
`
|
||||
|
||||
fetch('/aliases').then(async res => {
|
||||
const data = await res.json()
|
||||
const dataContainer = document.getElementById('container')
|
||||
const dataContainer = document.getElementById('container');
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
dataContainer.innerHTML = `
|
||||
|
@ -32,8 +32,7 @@ interface CreateWildDuckAddressResponse {
|
||||
success: boolean;
|
||||
id: string;
|
||||
}
|
||||
|
||||
type AliasesData = any;
|
||||
type AliasesData = { tags?: string[] }
|
||||
type AliasesPatch = any;
|
||||
type AliasesQuery = any;
|
||||
|
||||
@ -50,7 +49,7 @@ export interface AliasesParams extends Params<AliasesQuery> {
|
||||
export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
|
||||
implements ServiceInterface<AliasApiResponse, AliasesData, ServiceParams, AliasesPatch>
|
||||
{
|
||||
constructor(public options: AliasesServiceOptions) {}
|
||||
constructor(public options: AliasesServiceOptions) { }
|
||||
|
||||
async find(params: ServiceParams): Promise<AliasApiResponse[]> {
|
||||
const userId = await this.getUserIdByEmailAddress(params);
|
||||
@ -74,6 +73,7 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
|
||||
|
||||
const createResult = await wildDuckClient.post<CreateWildDuckAddressResponse>(`/users/${userId}/addresses`, {
|
||||
address: `${alias}@${emailDomain}`,
|
||||
tags: data.tags,
|
||||
});
|
||||
|
||||
if (!createResult.data.success) {
|
||||
|
Loading…
Reference in New Issue
Block a user