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) function createAlias() {
table.appendChild(thead) const tags = document.getElementById('tags').value?.split(',') || [];
const tbody = document.createElement('tbody') fetch('/aliases', {
// sort by field created method: 'POST',
aliases body: JSON.stringify({ tags }),
.sort((a, b) => new Date(a.created) - new Date(b.created)) headers: {
.forEach(alias => { 'Content-Type': 'application/json'
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 .then(res => res.json())
const tdCreated = document.createElement('td') .then(data => {
tdCreated.innerText = alias.created renderAliases(data)
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 deleteAlias(id) {
const deleteButton = document.createElement('a') fetch(`/aliases/${id}`, {
deleteButton.classList.add('btn', 'btn-danger')
if (!alias.id) {
deleteButton.classList.add('disabled')
} else {
deleteButton.addEventListener('click', async () => {
const res = await fetch(`/aliases/${alias.id}`, {
method: 'DELETE' method: 'DELETE'
}) })
.then(res => res.json())
if (res.ok) { .then(data => {
const data = await res.json()
renderAliases(data) renderAliases(data)
return
}
}) })
} }
deleteButton.innerText = 'Delete' function copyToClipboard(text) {
navigator.clipboard.writeText(text)
return deleteButton
} }
function renderCreateButton() { const okHandlebarsTemplate = `
const dataContainer = document.getElementById('container') <table class="table">
const button = document.createElement('a') <thead>
button.classList.add('btn') <tr>
button.classList.add('btn-primary') <th scope="col">Address</th>
<th scope="col">Tags</th>
button.addEventListener('click', async () => { <th scope="col">Created</th>
button.innerText = 'Creating, please wait...' <th scope="col">Action</th>
const res = await fetch('/aliases', { </tr>
method: 'POST' </thead>
}) <tbody>
{{#each data}}
if (res.ok) { <tr>
const data = await res.json() <td onclick="copyToClipboard('{{address}}')" style="cursor: pointer;">{{address}}</td>
renderAliases(data) <td> {{#each tags}}{{this}} {{/each}}</td>
return <td>{{created}}</td>
} <td>
}) <div onclick="deleteAlias('{{id}}')" class="btn btn-danger {{#unless id}}disabled{{/unless}}">Delete</div>
</td>
button.innerText = 'Create new alias' </tr>
dataContainer.appendChild(button) {{/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;
@ -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) {