| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | import {LitElement, html, unsafeHTML} from './lit-all.min.js'; | 
					
						
							|  |  |  | import * as tfrpc from '/static/tfrpc.js'; | 
					
						
							|  |  |  | import * as tfutils from './tf-utils.js'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const k_project = '%Hr+4xEVtjplidSKBlRWi4Aw/0Tfw7B+1OR9BzlDKmOI=.sha256'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TfComposeElement extends LitElement { | 
					
						
							|  |  |  | 	static get properties() { | 
					
						
							|  |  |  | 		return { | 
					
						
							|  |  |  | 			value: {type: String}, | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	input() { | 
					
						
							|  |  |  | 		let input = this.renderRoot.getElementById('input'); | 
					
						
							|  |  |  | 		let preview = this.renderRoot.getElementById('preview'); | 
					
						
							|  |  |  | 		if (input && preview) { | 
					
						
							|  |  |  | 			preview.innerHTML = tfutils.markdown(input.value); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	submit() { | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 		this.dispatchEvent( | 
					
						
							|  |  |  | 			new CustomEvent('tf-submit', { | 
					
						
							|  |  |  | 				bubbles: true, | 
					
						
							|  |  |  | 				composed: true, | 
					
						
							|  |  |  | 				detail: { | 
					
						
							|  |  |  | 					value: this.renderRoot.getElementById('input').value, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		); | 
					
						
							| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | 		this.renderRoot.getElementById('input').value = ''; | 
					
						
							|  |  |  | 		this.input(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	render() { | 
					
						
							|  |  |  | 		return html`
 | 
					
						
							|  |  |  | 			<div style="display: flex; flex-direction: row"> | 
					
						
							|  |  |  | 				<textarea id="input" @input=${this.input} style="flex: 1 1">${this.value}</textarea> | 
					
						
							|  |  |  | 				<div id="preview" style="flex: 1 1"></div> | 
					
						
							|  |  |  | 			</div> | 
					
						
							|  |  |  | 			<input type="submit" value="Submit" @click=${this.submit}></input> | 
					
						
							|  |  |  | 		`;
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | customElements.define('tf-compose', TfComposeElement); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TfIssuesAppElement extends LitElement { | 
					
						
							|  |  |  | 	static get properties() { | 
					
						
							|  |  |  | 		return { | 
					
						
							|  |  |  | 			issues: {type: Array}, | 
					
						
							|  |  |  | 			selected: {type: Object}, | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	constructor() { | 
					
						
							|  |  |  | 		super(); | 
					
						
							|  |  |  | 		this.issues = []; | 
					
						
							|  |  |  | 		this.load(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	async load() { | 
					
						
							|  |  |  | 		let issues = {}; | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 		let messages = await tfrpc.rpc.query( | 
					
						
							|  |  |  | 			`
 | 
					
						
							| 
									
										
										
										
											2024-04-14 13:47:28 +01:00
										 |  |  | 			WITH issues AS (SELECT messages.id, json(messages.content) AS content, messages.author, messages.timestamp FROM messages_refs JOIN messages ON | 
					
						
							| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | 				messages.id = messages_refs.message | 
					
						
							|  |  |  | 				WHERE messages_refs.ref = ? AND json_extract(messages.content, '$.type') = 'issue'), | 
					
						
							| 
									
										
										
										
											2024-04-14 13:47:28 +01:00
										 |  |  | 			edits AS (SELECT messages.id, json(messages.content) AS content, messages.author, messages.timestamp FROM issues JOIN messages_refs ON | 
					
						
							| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | 				issues.id = messages_refs.ref JOIN messages ON | 
					
						
							|  |  |  | 				messages.id = messages_refs.message | 
					
						
							|  |  |  | 				WHERE json_extract(messages.content, '$.type') IN ('issue-edit', 'post')) | 
					
						
							|  |  |  | 			SELECT * FROM issues | 
					
						
							|  |  |  | 			UNION | 
					
						
							|  |  |  | 			SELECT * FROM edits ORDER BY timestamp | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 		`,
 | 
					
						
							|  |  |  | 			[k_project] | 
					
						
							|  |  |  | 		); | 
					
						
							| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | 		for (let message of messages) { | 
					
						
							|  |  |  | 			let content = JSON.parse(message.content); | 
					
						
							|  |  |  | 			switch (content.type) { | 
					
						
							|  |  |  | 				case 'issue': | 
					
						
							|  |  |  | 					issues[message.id] = { | 
					
						
							|  |  |  | 						id: message.id, | 
					
						
							|  |  |  | 						author: message.author, | 
					
						
							|  |  |  | 						text: content.text, | 
					
						
							|  |  |  | 						updates: [], | 
					
						
							|  |  |  | 						created: message.timestamp, | 
					
						
							|  |  |  | 						open: true, | 
					
						
							|  |  |  | 					}; | 
					
						
							|  |  |  | 					break; | 
					
						
							|  |  |  | 				case 'issue-edit': | 
					
						
							|  |  |  | 				case 'post': | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 					for (let issue of content.issues || []) { | 
					
						
							| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | 						if (issues[issue.link]) { | 
					
						
							|  |  |  | 							if (issue.open !== undefined) { | 
					
						
							|  |  |  | 								issues[issue.link].open = issue.open; | 
					
						
							|  |  |  | 								message.open = issue.open; | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 							issues[issue.link].updates.push(message); | 
					
						
							|  |  |  | 							issues[issue.link].updated = message.timestamp; | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					break; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 		this.issues = Object.values(issues).sort( | 
					
						
							|  |  |  | 			(x, y) => y.open - x.open || y.created - x.created | 
					
						
							|  |  |  | 		); | 
					
						
							| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | 		if (this.selected) { | 
					
						
							|  |  |  | 			for (let issue of this.issues) { | 
					
						
							|  |  |  | 				if (issue.id == this.selected.id) { | 
					
						
							|  |  |  | 					this.selected = issue; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	render_issue_table_row(issue) { | 
					
						
							|  |  |  | 		return html`
 | 
					
						
							|  |  |  | 			<tr> | 
					
						
							| 
									
										
										
										
											2023-10-25 23:13:46 +00:00
										 |  |  | 				<td>${issue.open ? '☐ open' : '☑ closed'}</td> | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 				<td | 
					
						
							|  |  |  | 					style="max-width: 8em; overflow: hidden; white-space: nowrap; text-overflow: ellipsis" | 
					
						
							|  |  |  | 				> | 
					
						
							|  |  |  | 					${issue.author} | 
					
						
							|  |  |  | 				</td> | 
					
						
							|  |  |  | 				<td | 
					
						
							|  |  |  | 					style="max-width: 40em; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; cursor: pointer" | 
					
						
							|  |  |  | 					@click=${() => (this.selected = issue)} | 
					
						
							|  |  |  | 				> | 
					
						
							| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | 					${issue.text.split('\n')?.[0]} | 
					
						
							|  |  |  | 				</td> | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 				<td> | 
					
						
							|  |  |  | 					${new Date(issue.updated ?? issue.created).toLocaleDateString()} | 
					
						
							|  |  |  | 				</td> | 
					
						
							| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | 			</tr> | 
					
						
							|  |  |  | 		`;
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	render_update(update) { | 
					
						
							|  |  |  | 		let content = JSON.parse(update.content); | 
					
						
							|  |  |  | 		let message; | 
					
						
							|  |  |  | 		if (content.text) { | 
					
						
							|  |  |  | 			message = unsafeHTML(tfutils.markdown(content.text)); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return html`
 | 
					
						
							|  |  |  | 			<div style="border-left: 2px solid #fff; padding-left: 8px"> | 
					
						
							|  |  |  | 				<div>${new Date(update.timestamp).toLocaleString()}</div> | 
					
						
							|  |  |  | 				<div>${update.author}</div> | 
					
						
							|  |  |  | 				<div>${message}</div> | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 				<div> | 
					
						
							|  |  |  | 					${update.open !== undefined | 
					
						
							|  |  |  | 						? update.open | 
					
						
							|  |  |  | 							? 'issue opened' | 
					
						
							|  |  |  | 							: 'issue closed' | 
					
						
							|  |  |  | 						: undefined} | 
					
						
							|  |  |  | 				</div> | 
					
						
							| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | 			</div> | 
					
						
							|  |  |  | 		`;
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	async set_open(id, open) { | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 		if ( | 
					
						
							|  |  |  | 			confirm(`Are you sure you want to ${open ? 'open' : 'close'} this issue?`) | 
					
						
							|  |  |  | 		) { | 
					
						
							| 
									
										
										
										
											2024-04-14 13:47:28 +01:00
										 |  |  | 			let whoami = await tfrpc.rpc.getActiveIdentity(); | 
					
						
							| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | 			await tfrpc.rpc.appendMessage(whoami, { | 
					
						
							|  |  |  | 				type: 'issue-edit', | 
					
						
							|  |  |  | 				issues: [ | 
					
						
							|  |  |  | 					{ | 
					
						
							|  |  |  | 						link: id, | 
					
						
							|  |  |  | 						open: open, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 				], | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 			await this.load(); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	async create_issue(event) { | 
					
						
							| 
									
										
										
										
											2024-04-14 13:47:28 +01:00
										 |  |  | 		let whoami = await tfrpc.rpc.getActiveIdentity(); | 
					
						
							| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | 		await tfrpc.rpc.appendMessage(whoami, { | 
					
						
							|  |  |  | 			type: 'issue', | 
					
						
							|  |  |  | 			project: k_project, | 
					
						
							|  |  |  | 			text: event.detail.value, | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 		await this.load(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	async reply_to_issue(event) { | 
					
						
							| 
									
										
										
										
											2024-04-14 13:47:28 +01:00
										 |  |  | 		let whoami = await tfrpc.rpc.getActiveIdentity(); | 
					
						
							| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | 		await tfrpc.rpc.appendMessage(whoami, { | 
					
						
							|  |  |  | 			type: 'post', | 
					
						
							|  |  |  | 			text: event.detail.value, | 
					
						
							| 
									
										
										
										
											2023-09-04 17:12:32 +00:00
										 |  |  | 			root: this.selected.id, | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 			branch: this.selected.updates.length | 
					
						
							|  |  |  | 				? this.selected.updates[this.selected.updates.length - 1].id | 
					
						
							|  |  |  | 				: this.selected.id, | 
					
						
							| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | 			issues: [ | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					link: this.selected.id, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			], | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 		await this.load(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	render() { | 
					
						
							| 
									
										
										
										
											2024-04-17 20:56:33 -04:00
										 |  |  | 		let header = html` <h1>Tilde Friends Issues</h1> `; | 
					
						
							| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | 		if (this.selected) { | 
					
						
							|  |  |  | 			return html`
 | 
					
						
							|  |  |  | 				${header} | 
					
						
							|  |  |  | 				<div> | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 					<input type="button" value="Back" @click=${() => (this.selected = undefined)}></input> | 
					
						
							|  |  |  | 					${ | 
					
						
							|  |  |  | 						this.selected.open | 
					
						
							|  |  |  | 							? html`<input type="button" value="Close Issue" @click=${() => this.set_open(this.selected.id, false)}></input>` | 
					
						
							|  |  |  | 							: html`<input type="button" value="Reopen Issue" @click=${() => this.set_open(this.selected.id, true)}></input>` | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | 				</div> | 
					
						
							|  |  |  | 				<div>${new Date(this.selected.created).toLocaleString()}</div> | 
					
						
							|  |  |  | 				<div>${this.selected.author}</div> | 
					
						
							|  |  |  | 				<div>${this.selected.id}</div> | 
					
						
							|  |  |  | 				<div>${unsafeHTML(tfutils.markdown(this.selected.text))}</div> | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 				${this.selected.updates.map((x) => this.render_update(x))} | 
					
						
							| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | 				<tf-compose @tf-submit=${this.reply_to_issue}></tf-compose> | 
					
						
							|  |  |  | 			`;
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			return html`
 | 
					
						
							|  |  |  | 				${header} | 
					
						
							|  |  |  | 				<h2>New Issue</h2> | 
					
						
							|  |  |  | 				<tf-compose @tf-submit=${this.create_issue}></tf-compose> | 
					
						
							|  |  |  | 				<table> | 
					
						
							|  |  |  | 					<tr> | 
					
						
							|  |  |  | 						<th>Status</th> | 
					
						
							|  |  |  | 						<th>Author</th> | 
					
						
							|  |  |  | 						<th>Title</th> | 
					
						
							|  |  |  | 						<th>Date</th> | 
					
						
							|  |  |  | 					</tr> | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 					${this.issues.map((x) => this.render_issue_table_row(x))} | 
					
						
							| 
									
										
										
										
											2023-08-31 00:12:18 +00:00
										 |  |  | 				</table> | 
					
						
							|  |  |  | 			`;
 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | customElements.define('tf-issues-app', TfIssuesAppElement); |