render data with Handlebars template, support tags
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
Sergo 2023-12-04 00:07:01 +02:00
parent b772c83c53
commit 1ca64bc925
4 changed files with 69 additions and 101 deletions

View File

@ -5,6 +5,7 @@
"origins": [ "origins": [
"http://localhost:3030" "http://localhost:3030"
], ],
"sessionSecret": "test",
"paginate": { "paginate": {
"default": 10, "default": 10,
"max": 50 "max": 50

View File

@ -2,8 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>walias</title> <title>WildDuck Aliases</title>
<script src="index.js" async></script>
<meta name="description" content="Aliases for Wild Duck"> <meta name="description" content="Aliases for Wild Duck">
<meta name="viewport" content="width=device-width, initial-scale=1"> <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" <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" <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
crossorigin="anonymous"></script> crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>
<script src="index.js" async></script>
<style> <style>
* { * {
margin: 0; margin: 0;
@ -54,7 +55,7 @@
<div class="wrapper"> <div class="wrapper">
<div class="header"> <div class="header">
<img class="logo" src="logo.jpg"> <img class="logo" src="logo.jpg">
<h1>Wildduck aliases</h1> <h1>WildDuck Aliases</h1>
</div> </div>
<div id="container"></div> <div id="container"></div>
</div> </div>

View File

@ -1,107 +1,73 @@
function renderAliases(aliases) { function renderAliases(data) {
const dataContainer = document.getElementById('container') const dataContainer = document.getElementById('container');
// Create table element ant populate with items const template = Handlebars.compile(okHandlebarsTemplate);
const table = document.createElement('table') const html = template({
table.classList.add('table') data: data.sort((a, b) => new Date(a.created) - new Date(b.created))
const thead = document.createElement('thead') });
const tr = document.createElement('tr')
for (const value of ['Address', 'Created']) { dataContainer.innerHTML = html;
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();
} }
function createDeleteButton(alias) { function createAlias() {
const deleteButton = document.createElement('a') const tags = document.getElementById('tags').value?.split(',') || [];
deleteButton.classList.add('btn', 'btn-danger')
if (!alias.id) { fetch('/aliases', {
deleteButton.classList.add('disabled') method: 'POST',
} else { body: JSON.stringify({ tags }),
headers: {
deleteButton.addEventListener('click', async () => { 'Content-Type': 'application/json'
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
} }
}) })
.then(res => res.json())
button.innerText = 'Create new alias' .then(data => {
dataContainer.appendChild(button) 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 => { 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) { if (!res.ok) {
dataContainer.innerHTML = ` dataContainer.innerHTML = `

View File

@ -32,8 +32,7 @@ interface CreateWildDuckAddressResponse {
success: boolean; success: boolean;
id: string; id: string;
} }
type AliasesData = { tags?: string[] }
type AliasesData = any;
type AliasesPatch = any; type AliasesPatch = any;
type AliasesQuery = any; type AliasesQuery = any;
@ -50,7 +49,7 @@ export interface AliasesParams extends Params<AliasesQuery> {
export class AliasesService<ServiceParams extends AliasesParams = AliasesParams> export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
implements ServiceInterface<AliasApiResponse, AliasesData, ServiceParams, AliasesPatch> implements ServiceInterface<AliasApiResponse, AliasesData, ServiceParams, AliasesPatch>
{ {
constructor(public options: AliasesServiceOptions) {} constructor(public options: AliasesServiceOptions) { }
async find(params: ServiceParams): Promise<AliasApiResponse[]> { async find(params: ServiceParams): Promise<AliasApiResponse[]> {
const userId = await this.getUserIdByEmailAddress(params); 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`, { const createResult = await wildDuckClient.post<CreateWildDuckAddressResponse>(`/users/${userId}/addresses`, {
address: `${alias}@${emailDomain}`, address: `${alias}@${emailDomain}`,
tags: data.tags,
}); });
if (!createResult.data.success) { if (!createResult.data.success) {