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": [
|
"origins": [
|
||||||
"http://localhost:3030"
|
"http://localhost:3030"
|
||||||
],
|
],
|
||||||
|
"sessionSecret": "test",
|
||||||
"paginate": {
|
"paginate": {
|
||||||
"default": 10,
|
"default": 10,
|
||||||
"max": 50
|
"max": 50
|
||||||
|
@ -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>
|
||||||
|
156
public/index.js
156
public/index.js
@ -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 = `
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user