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 TfIdPickerElement extends LitElement { static get properties() { return { ids: {type: Array}, selected: {type: String}, }; } constructor() { super(); this.load(); } async load() { this.selected = await tfrpc.rpc.localStorageGet('whoami'); this.ids = (await tfrpc.rpc.getIdentities()) || []; } changed(event) { this.selected = event.srcElement.value; tfrpc.rpc.localStorageSet('whoami', this.selected); } render() { if (this.ids) { return html` `; } else { return html`
Loading...
`; } } } customElements.define('tf-id-picker', TfIdPickerElement); 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() { this.dispatchEvent(new CustomEvent('tf-submit', { bubbles: true, composed: true, detail: { value: this.renderRoot.getElementById('input').value, }, })); this.renderRoot.getElementById('input').value = ''; this.input(); } render() { return html`
`; } } 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 = {}; let messages = await tfrpc.rpc.query(` WITH issues AS (SELECT messages.* FROM messages_refs JOIN messages ON messages.id = messages_refs.message WHERE messages_refs.ref = ? AND json_extract(messages.content, '$.type') = 'issue'), edits AS (SELECT messages.* FROM issues JOIN messages_refs ON 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 `, [k_project]); 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': for (let issue of (content.issues || [])) { 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; } } this.issues = Object.values(issues).sort((x, y) => (y.open - x.open) || (y.created - x.created)); 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` ${issue.open ? '☐ open' : '☑ closed'} ${issue.author} this.selected = issue}> ${issue.text.split('\n')?.[0]} ${new Date(issue.updated ?? issue.created).toLocaleDateString()} `; } render_update(update) { let content = JSON.parse(update.content); let message; if (content.text) { message = unsafeHTML(tfutils.markdown(content.text)); } return html`
${new Date(update.timestamp).toLocaleString()}
${update.author}
${message}
${update.open !== undefined ? (update.open ? 'issue opened' : 'issue closed') : undefined}
`; } async set_open(id, open) { if (confirm(`Are you sure you want to ${open ? 'open' : 'close'} this issue?`)) { let whoami = this.shadowRoot.getElementById('picker').selected; await tfrpc.rpc.appendMessage(whoami, { type: 'issue-edit', issues: [ { link: id, open: open, }, ], }); await this.load(); } } async create_issue(event) { let whoami = this.shadowRoot.getElementById('picker').selected; await tfrpc.rpc.appendMessage(whoami, { type: 'issue', project: k_project, text: event.detail.value, }); await this.load(); } async reply_to_issue(event) { let whoami = this.shadowRoot.getElementById('picker').selected; await tfrpc.rpc.appendMessage(whoami, { type: 'post', text: event.detail.value, root: this.selected.id, branch: this.selected.updates.length ? this.selected.updates[this.selected.updates.length - 1].id : this.selected.id, issues: [ { link: this.selected.id, }, ], }); await this.load(); } render() { let header = html`

Tilde Friends Issues

`; if (this.selected) { return html` ${header}
this.selected = undefined}> ${this.selected.open ? html` this.set_open(this.selected.id, false)}>` : html` this.set_open(this.selected.id, true)}>`}
${new Date(this.selected.created).toLocaleString()}
${this.selected.author}
${this.selected.id}
${unsafeHTML(tfutils.markdown(this.selected.text))}
${this.selected.updates.map(x => this.render_update(x))} `; } else { return html` ${header}

New Issue

${this.issues.map(x => this.render_issue_table_row(x))}
Status Author Title Date
`; } } } customElements.define('tf-issues-app', TfIssuesAppElement);