From d06f490cc2e37f7c17123de601e74125556d59b0 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Sat, 4 Nov 2023 02:00:35 +0000 Subject: [PATCH] Work in progress wiki simplification, I hope. git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4603 ed5197a5-7fde-0310-b194-c3ffbd925b24 --- apps/wiki.json | 2 +- apps/wiki/app.js | 102 +++++++++++--- apps/wiki/tf-collection.js | 236 +++++++------------------------- apps/wiki/tf-collections-app.js | 113 ++++++++++----- 4 files changed, 212 insertions(+), 241 deletions(-) diff --git a/apps/wiki.json b/apps/wiki.json index 6575b3da..e46fc1af 100644 --- a/apps/wiki.json +++ b/apps/wiki.json @@ -1,5 +1,5 @@ { "type": "tildefriends-app", "emoji": "📝", - "previous": "&yXKDVsBMD1J3ZAqbLuG/z2sq/6lIIUu88dennvwuins=.sha256" + "previous": "&KrBCh2/8NVdDwJrOK0JB/Tszo/lAtjRI4XVdjZ2XNK8=.sha256" } \ No newline at end of file diff --git a/apps/wiki/app.js b/apps/wiki/app.js index 2546e7a7..b3663c96 100644 --- a/apps/wiki/app.js +++ b/apps/wiki/app.js @@ -1,6 +1,7 @@ import * as tfrpc from '/tfrpc.js'; let g_hash; +let g_collection_notifies = {}; tfrpc.register(async function getOwnerIdentities() { return ssb.getOwnerIdentities(); @@ -45,10 +46,21 @@ tfrpc.register(async function get_blob(id) { return utf8Decode(await ssb.blobGet(id)); }); -ssb.addEventListener('message', async function(id) { - let message; - await ssb.sqlAsync('SELECT * FROM messages WHERE id = ?', [id], function(row) { message = row; }); - await tfrpc.rpc.notify_new_message(message); +let g_new_message_resolve; +let g_new_message_promise = new Promise(function(resolve, reject) { + g_new_message_resolve = resolve; +}); + +function new_message() { + return g_new_message_promise; +} + +ssb.addEventListener('message', function(id) { + let resolve = g_new_message_resolve(); + g_new_message_promise = new Promise(function(resolve, reject) { + g_new_message_resolve = resolve; + }); + resolve(); }); core.register('message', async function message_handler(message) { @@ -76,22 +88,78 @@ tfrpc.register(async function encrypt(id, recipients, content) { return await ssb.privateMessageEncrypt(id, recipients, content); }); -async function get_collections(kind) { - let me = await ssb.getIdentities(); - let them = await ssb.following(me, 2); - let collections = {}; - print('querying'); - for (let row of await query(` - SELECT author, content, timestamp - FROM messages JOIN json_each(?) AS id ON messages.author = id.value - WHERE json_extract(content, '$.type') = 'collection' AND json_extract(content, '$.kind') = ? - `, [JSON.stringify(them), kind])) { - print(row); +async function process_message(whoami, collection, message, kind, parent) { + let content = JSON.parse(message.content); + if (typeof content == 'string') { + let x; + for (let id of whoami) { + x = await tfrpc.rpc.try_decrypt(whoami, content); + if (x) { + content = JSON.parse(x); + content.draft = true; + break; + } + } + if (!x) { + return; + } + if (content.type !== type || + (parent && content.parent !== parent)) { + return; + } + } else { + content.draft = false; } - print('done'); - return collections; + if (content?.key) { + if (content?.tombstone) { + delete collection[content.key]; + } else if (collection[content.key]) { + collection[content.key] = Object.assign(collection[content.key], content); + } + } else { + collection[message.id] = Object.assign(content, {id: message.id}); + } + return true; } +tfrpc.register(async function collection(ids, kind, parent, max_rowid, data) { + let whoami = await ssb.getIdentities(); + data = data ?? {}; + let rowid = 0; + await ssb.sqlAsync('SELECT MAX(rowid) AS rowid FROM messages', [], function(row) { + rowid = row.rowid; + }); + while (true) { + if (rowid == max_rowid) { + await new_message(); + await ssb.sqlAsync('SELECT MAX(rowid) AS rowid FROM messages', [], function(row) { + rowid = row.rowid; + }); + } + + let modified = false; + await ssb.sqlAsync(` + SELECT messages.id, author, content, timestamp + FROM messages + JOIN json_each(?1) AS id ON messages.author = id.value + WHERE + messages.rowid > ?2 AND + messages.rowid <= ?3 AND + ((json_extract(messages.content, '$.type') = ?4 AND + (?5 IS NULL OR json_extract(messages.content, '$.parent') = ?5)) OR + content LIKE '"%') + `, [JSON.stringify(ids), max_rowid ?? -1, rowid, kind, parent], function(row) { + if (process_message(whoami, data, row, kind, parent)) { + modified = true; + } + }); + if (modified) { + break; + } + } + return [rowid, data]; +}); + async function main() { await app.setDocument(utf8Decode(await getFile('index.html'))); } diff --git a/apps/wiki/tf-collection.js b/apps/wiki/tf-collection.js index d0e4fe2d..b08361f7 100644 --- a/apps/wiki/tf-collection.js +++ b/apps/wiki/tf-collection.js @@ -4,223 +4,79 @@ import * as tfrpc from '/static/tfrpc.js'; class TfCollectionElement extends LitElement { static get properties() { return { - whoami: {type: String}, - ids: {type: Array}, - collections: {type: Array}, - collections_loading: {type: Number}, - type: {type: String}, - parent: {type: String}, - selectname: {type: String}, - selectid: {type: String}, + collection: {type: Object}, + selected_id: {type: String}, is_creating: {type: Boolean}, is_renaming: {type: Boolean}, }; } - constructor() { - super(); - this.ids = []; - this.loaded = this.load(); - this.type = 'collection'; - this.collections_loading = 0; - } - - async process_message(message) { - let content = JSON.parse(message.content); - if (typeof content == 'string') { - let x = await tfrpc.rpc.try_decrypt(this.whoami, content); - if (x) { - content = JSON.parse(x); - content.draft = true; - } else { - return; - } - if (content.type !== this.type || - (this.parent && content.parent !== this.parent)) { - return; - } - } else { - content.draft = false; - } - if (content?.key) { - if (content?.tombstone) { - delete this.by_id[content.key]; - } else if (this.by_id[content.key]) { - this.by_id[content.key] = Object.assign(this.by_id[content.key], content); - } - } else { - this.by_id[message.id] = Object.assign(content, {id: message.id}); - } - } - - async load() { - try { - this.collections_loading++; - if (this.ids) { - let visible = this.ids; - this.visible = visible; - if (this.type) { - let collections = await tfrpc.rpc.query(` - SELECT messages.id, author, content, timestamp - FROM messages JOIN json_each(?1) AS id ON messages.author = id.value - WHERE - json_extract(content, '$.type') = ?2 AND - (?3 IS NULL OR json_extract(content, '$.parent') = ?3) OR - content LIKE '"%' - ORDER BY timestamp - `, [JSON.stringify(visible), this.type, this.parent]); - this.by_id = {}; - for (let collection of collections) { - await this.process_message(collection); - } - this.collections = Object.values(this.by_id); - } - } - } finally { - this.collections_loading--; - } - if (this.selectname) { - this.set_selected_by_name(this.selectname); - } - } - - async create(name, editors) { - let message = { - type: this.type, - name: name, - parent: this.parent, - editors: editors, - }; - print('will append', message); - await tfrpc.rpc.appendMessage(this.whoami, message); - } - - notify_new_message(message) { - if (this.visible && - this.visible.indexOf(message.author) != -1 && - JSON.parse(message.content).type == this.type) { - let self = this; - this.process_message(message).then(function() { - self.collections = [...Object.values(self.by_id)]; - }); - } - } - - async on_create(event) { + on_create(event) { let name = this.shadowRoot.getElementById('create_name').value; - if (name) { - await this.create(name, [this.whoami]); - } + this.dispatchEvent(new CustomEvent('create', { + bubbles: true, + detail: { + name: name, + }, + })); this.is_creating = false; } - async on_rename(id) { + on_rename(event) { + let id = this.shadowRoot.getElementById('select').value; let name = this.shadowRoot.getElementById('rename_name').value; - let message = { - type: this.type, - key: this.selectid, - parent: this.parent, - name: name, - }; - print(message); - await tfrpc.rpc.appendMessage(this.whoami, message); - this.is_renaming = false; - } - - async on_tombstone(event) { - if (confirm(`Are you sure you want to delete this ${this.type}?`)) { - let message = { - type: this.type, - key: this.selectid, - parent: this.parent, - tombstone: { - date: new Date().valueOf(), - reason: 'archived', - }, - }; - print(message); - await tfrpc.rpc.appendMessage(this.whoami, message); - } - } - - set_selected(id, value, by_user) { - this.selectid = id; - console.log('SEND', id, value?.name); - this.dispatchEvent(new CustomEvent('selected', { + this.dispatchEvent(new CustomEvent('rename', { bubbles: true, detail: { id: id, - value: value, - by_user: by_user, + value: this.collection[id], + name: name, + }, + })); + this.is_renaming = false; + } + + on_tombstone(event) { + let id = this.shadowRoot.getElementById('select').value; + if (confirm(`Are you sure you want to delete '${this.collection[id].name}'?`)) { + this.dispatchEvent(new CustomEvent('tombstone', { + bubbles: true, + detail: { + id: id, + value: this.collection[id], + }, + })); + } + } + + on_selected(event) { + let id = event.srcElement.value; + this.selected_id = id != '' ? id : undefined; + this.dispatchEvent(new CustomEvent('change', { + bubbles: true, + detail: { + id: id, + value: this.collection[id], }, })); } - set_selected_by_name(name) { - console.log('set selected by name', name); - if (this.collections) { - for (let collection of this.collections) { - if (collection.name === name) { - this.set_selected(collection.id, collection); - this._select_by_name = undefined; - return; - } - } - } - this._select_by_name = name; - } - - on_selected(event) { - if (this.collections) { - for (let collection of this.collections) { - if (collection.id === event.srcElement.value) { - this.set_selected(collection.id, collection, true); - break; - } - } - } - } - render() { let self = this; - let state = JSON.stringify([this.whoami, this.ids, this.parent]); - if (state !== this.loaded_for) { - this.loaded_for = state; - this.loaded = this.load(); - } - if (this.collections) { - if (this.selectname) { - for (let collection of this.collections) { - if (collection.name === this.selectname) { - this.set_selected(collection.id, collection); - break; - } - } - } else { - for (let collection of this.collections) { - if (collection.id === this.selectid) { - this.set_selected(collection.id, collection); - break; - } - } - } - } return html` - ${this.collections_loading > 0 ? html`
Loading...
` : html` - - `} + - - + + diff --git a/apps/wiki/tf-collections-app.js b/apps/wiki/tf-collections-app.js index f374119d..f23539b5 100644 --- a/apps/wiki/tf-collections-app.js +++ b/apps/wiki/tf-collections-app.js @@ -7,6 +7,10 @@ class TfCollectionsAppElement extends LitElement { ids: {type: Array}, owner_ids: {type: Array}, whoami: {type: String}, + + wikis: {type: Object}, + wiki_docs: {type: Object}, + wiki: {type: Object}, wiki_doc: {type: Object}, hash: {type: String}, @@ -19,32 +23,85 @@ class TfCollectionsAppElement extends LitElement { this.owner_ids = []; this.load(); let self = this; - tfrpc.register(function notify_new_message(message) { - self.notify_new_message(message); - }); tfrpc.register(function hash_changed(hash) { - self.hash = hash; + self.notify_hash_changed(hash); }); - tfrpc.rpc.get_hash().then(hash => self.hash = 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'); + + await this.read_wikis(); + await this.read_Wiki_docs(); } - notify_new_message(message) { - console.log('notify_new_message', message); - console.log('this', this); - console.log('qs', this.shadowRoot.querySelectorAll('tf-collection')); - for (let element of this.shadowRoot.querySelectorAll('tf-collection')) { - element.notify_new_message(message); + async read_wikis() { + let max_rowid; + let wikis; + while (true) + { + [max_rowid, wikis] = await tfrpc.rpc.collection(this.owner_ids, 'wiki', undefined, max_rowid, wikis); + this.wikis = wikis; + this.update_wiki(); } } - async notify_hash_changed(hash) { + async read_wiki_docs() { + console.log('read_wiki_docs', this.wiki?.id); + if (!this.wiki?.id) { + return; + } + let max_rowid; + let wiki_docs; + while (true) + { + [max_rowid, wiki_docs] = await tfrpc.rpc.collection(this.owner_ids, 'wiki-doc', this.wiki?.id, max_rowid, wiki_docs); + 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) { @@ -59,16 +116,14 @@ class TfCollectionsAppElement extends LitElement { async on_wiki_changed(event) { this.wiki = event.detail.value; - if (event.detail.by_user) { - this.update_hash(); - } + this.wiki_doc = undefined; + this.update_hash(); + this.read_wiki_docs(); } async on_wiki_doc_changed(event) { - this.wiki_doc = Object.assign({}, event.detail.value); - if (event.detail.by_user) { - this.update_hash(); - } + this.wiki_doc = event.detail.value; + this.update_hash(); } async on_wiki_publish(event) { @@ -91,27 +146,19 @@ class TfCollectionsAppElement extends LitElement { render() { let self = this; - console.log('render', this.wiki?.name, this.wiki_doc?.name, this.hash); return html`
+ .collection=${this.wikis} + selected_id=${this.wiki?.id} + @change=${this.on_wiki_changed}> + .collection=${this.wiki_docs} + selected_id=${this.wiki_doc?.id} + @change=${this.on_wiki_doc_changed}>
${this.wiki_doc && this.wiki_doc.parent === this.wiki?.id ? html`