tildefriends/apps/wiki/tf-wiki-app.js

370 lines
9.0 KiB
JavaScript

import {LitElement, html, keyed} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
class TfCollectionsAppElement extends LitElement {
static get properties() {
return {
ids: {type: Array},
owner_ids: {type: Array},
whoami: {type: String},
following: {type: Array},
wikis: {type: Object},
wiki_docs: {type: Object},
wiki: {type: Object},
wiki_doc: {type: Object},
hash: {type: String},
adding_editor: {type: Boolean},
expand_editors: {type: Boolean},
};
}
constructor() {
super();
this.ids = [];
this.owner_ids = [];
this.following = [];
this.load();
let self = this;
tfrpc.register(function hash_changed(hash) {
self.notify_hash_changed(hash);
});
tfrpc.rpc.get_hash().then((hash) => self.notify_hash_changed(hash));
}
async load() {
this.ids = await tfrpc.rpc.getIdentities();
this.owner_ids = await tfrpc.rpc.getOwnerIdentities();
this.whoami = await tfrpc.rpc.localStorageGet('collections_whoami');
let ids = [...new Set([...this.owner_ids, this.whoami])].sort();
this.following = Object.keys(await tfrpc.rpc.following(ids, 1)).sort();
await this.read_wikis();
await this.read_Wiki_docs();
}
async read_wikis() {
let max_rowid;
let wikis;
let start_whoami = this.whoami;
while (true) {
console.log('read_wikis', this.whoami);
[max_rowid, wikis] = await tfrpc.rpc.collection(
this.following,
'wiki',
undefined,
max_rowid,
wikis,
false
);
console.log('read ->', wikis);
if (this.whoami !== start_whoami) {
break;
}
console.log('wikis =>', wikis);
this.wikis = wikis;
this.update_wiki();
}
}
async read_wiki_docs() {
if (!this.wiki?.id) {
return;
}
let start_id = this.wiki.id;
let max_rowid;
let wiki_docs;
while (true) {
[max_rowid, wiki_docs] = await tfrpc.rpc.collection(
this.wiki?.editors,
'wiki-doc',
this.wiki?.id,
max_rowid,
wiki_docs
);
if (this.wiki?.id !== start_id) {
break;
}
this.wiki_docs = wiki_docs;
this.update_wiki_doc();
}
}
hash_wiki() {
let hash = this.hash ?? '';
hash = hash.charAt(0) == '#' ? hash.substring(1) : hash;
let parts = hash.split('/');
return parts[0];
}
hash_wiki_doc() {
let hash = this.hash ?? '';
hash = hash.charAt(0) == '#' ? hash.substring(1) : hash;
let slash = hash.indexOf('/');
return slash != -1 ? hash.substring(slash + 1) : undefined;
}
update_wiki() {
let want_wiki = this.hash_wiki();
for (let wiki of Object.values(this.wikis ?? {})) {
if (wiki.name === want_wiki) {
this.wiki = wiki;
this.read_wiki_docs();
break;
}
}
}
update_wiki_doc() {
let want_wiki_doc = this.hash_wiki_doc();
for (let wiki_doc of Object.values(this.wiki_docs ?? {})) {
if (wiki_doc.name === want_wiki_doc) {
this.wiki_doc = wiki_doc;
}
}
}
notify_hash_changed(hash) {
this.hash = hash;
this.update_wiki();
this.update_wiki_doc();
}
async on_whoami_changed(event) {
let new_id = event.srcElement.selected;
await tfrpc.rpc.localStorageSet('collections_whoami', new_id);
this.whoami = new_id;
}
update_hash() {
tfrpc.rpc.set_hash(
this.wiki_doc
? `${this.wiki.name}/${this.wiki_doc.name}`
: `${this.wiki.name}`
);
}
async on_wiki_changed(event) {
this.wiki = event.detail.value;
this.wiki_doc = undefined;
this.wiki_docs = undefined;
this.adding_editor = false;
this.update_hash();
this.read_wiki_docs();
}
async on_wiki_create(event) {
await tfrpc.rpc.appendMessage(this.whoami, {
type: 'wiki',
name: event.detail.name,
});
}
async on_wiki_rename(event) {
await tfrpc.rpc.appendMessage(this.whoami, {
type: 'wiki',
key: event.detail.id,
name: event.detail.name,
});
}
async on_add_editor(event) {
let id = this.shadowRoot.getElementById('add_editor').value;
let editors = [...this.wiki.editors];
if (editors.indexOf(id) == -1) {
editors.push(id);
editors.sort();
}
await tfrpc.rpc.appendMessage(this.whoami, {
type: 'wiki',
key: this.wiki.id,
editors: editors,
});
this.adding_editor = false;
}
async on_remove_editor(id) {
if (confirm(`Are you sure you want to remove ${id} as an editor?`)) {
let editors = [...this.wiki.editors];
if (editors.indexOf(id) != -1) {
editors = editors.filter((x) => x !== id);
}
await tfrpc.rpc.appendMessage(this.whoami, {
type: 'wiki',
key: this.wiki.id,
editors: editors,
});
}
}
async on_wiki_tombstone(event) {
await tfrpc.rpc.appendMessage(this.whoami, {
type: 'wiki',
key: event.detail.id,
tombstone: {
date: new Date().valueOf(),
reason: 'tombstoned by user',
},
});
}
async on_wiki_doc_create(event) {
await tfrpc.rpc.appendMessage(this.whoami, {
type: 'wiki-doc',
parent: this.wiki.id,
name: event.detail.name,
});
}
async on_wiki_doc_rename(event) {
await tfrpc.rpc.appendMessage(this.whoami, {
type: 'wiki-doc',
parent: this.wiki.id,
key: event.detail.id,
name: event.detail.name,
});
}
async on_wiki_doc_tombstone(event) {
await tfrpc.rpc.appendMessage(this.whoami, {
type: 'wiki-doc',
parent: this.wiki.id,
key: event.detail.id,
tombstone: {
date: new Date().valueOf(),
reason: 'tombstoned by user',
},
});
}
async on_wiki_doc_changed(event) {
this.wiki_doc = event.detail.value;
this.update_hash();
}
updated(changed_properties) {
if (changed_properties.has('whoami')) {
this.wikis = {};
this.wiki_docs = {};
this.read_wikis();
}
}
render() {
let self = this;
return html`
<style>
.toc:hover {
background-color: #0cc;
}
.toc.selected {
background-color: #088;
}
</style>
<div>
<tf-id-picker .ids=${this.ids} selected=${this.whoami} @change=${this.on_whoami_changed} ?hidden=${!this.ids?.length}></tf-id-picker>
</div>
<div>
${keyed(
this.whoami,
html`<tf-collection
.collection=${this.wikis}
whoami=${this.whoami}
selected_id=${this.wiki?.id}
@create=${this.on_wiki_create}
@rename=${this.on_wiki_rename}
@tombstone=${this.on_wiki_tombstone}
@change=${this.on_wiki_changed}
></tf-collection>`
)}
${keyed(
this.wiki_doc?.id,
html`<tf-collection
.collection=${this.wiki_docs}
whoami=${this.whoami}
selected_id=${this.wiki_doc &&
this.wiki_doc?.parent == this.wiki?.id
? this.wiki_doc?.id
: ''}
@create=${this.on_wiki_doc_create}
@rename=${this.on_wiki_doc_rename}
@tombstone=${this.on_wiki_doc_tombstone}
@change=${this.on_wiki_doc_changed}
></tf-collection>`
)}
<button @click=${() => (self.expand_editors = !self.expand_editors)}>${this.wiki?.editors?.length} editor${this.wiki?.editors?.length > 1 ? 's' : ''}</button>
<div ?hidden=${!this.wiki?.editors || !this.expand_editors}>
<div>
<ul>
${this.wiki?.editors.map((id) => html`<li><button ?hidden=${id == this.whoami} @click=${() => self.on_remove_editor(id)}>x</button> ${id}</li>`)}
<li>
<button @click=${() => (self.adding_editor = true)} ?hidden=${this.wiki?.editors?.indexOf(this.whoami) == -1 || this.adding_editor}>+</button>
<div ?hidden=${!this.adding_editor}>
<label for="add_editor">Add Editor:</label>
<input type="text" id="add_editor"></input>
<button @click=${this.on_add_editor}>Add Editor</button>
<button @click=${() => (self.adding_editor = false)}>x</button>
</div>
</li>
</ul>
</div>
</div>
</div>
<div style="display: flex; flex-direction: row">
<div style="flex: 0 0">
${Object.values(this.wikis || {})
.sort((x, y) => x.name.localeCompare(y.name))
.map(
(wiki) => html`
<div
class="toc ${self.wiki?.id === wiki.id ? 'selected' : ''}"
style="white-space: nowrap; cursor: pointer"
@click=${() => self.on_wiki_changed({detail: {value: wiki}})}
>
${wiki.name}
</div>
<ul>
${Object.values(self.wiki_docs || {})
.filter((doc) => doc.parent === wiki?.id)
.sort((x, y) => x.name.localeCompare(y.name))
.map(
(doc) => html`
<li
class="toc ${self.wiki_doc?.id === doc.id
? 'selected'
: ''}"
style="white-space: nowrap; cursor: pointer; list-style: none; text-indent: -1rem"
@click=${() =>
self.on_wiki_doc_changed({
detail: {value: doc},
})}
>
${doc?.private ? '🔒' : '📄'} ${doc.name}
</li>
`
)}
</ul>
`
)}
</div>
${
this.wiki_doc && this.wiki_doc.parent === this.wiki?.id
? html`
<tf-wiki-doc
style="width: 100%"
whoami=${this.whoami}
.wiki=${this.wiki}
.value=${this.wiki_doc}
></tf-wiki-doc>
`
: undefined
}
</div>
`;
}
}
customElements.define('tf-collections-app', TfCollectionsAppElement);