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:
		| @@ -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) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user