forked from cory/tildefriends
		
	ssb: Merge in the new very work in progress channels interface.
This commit is contained in:
		| @@ -16,7 +16,9 @@ class TfElement extends LitElement { | ||||
| 			following: {type: Array}, | ||||
| 			users: {type: Object}, | ||||
| 			ids: {type: Array}, | ||||
| 			tags: {type: Array}, | ||||
| 			channels: {type: Array}, | ||||
| 			channels_unread: {type: Object}, | ||||
| 			channels_latest: {type: Object}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @@ -33,7 +35,9 @@ class TfElement extends LitElement { | ||||
| 		this.following = []; | ||||
| 		this.users = {}; | ||||
| 		this.loaded = false; | ||||
| 		this.tags = []; | ||||
| 		this.channels = []; | ||||
| 		this.channels_unread = {}; | ||||
| 		this.channels_latest = {}; | ||||
| 		tfrpc.rpc.getBroadcasts().then((b) => { | ||||
| 			self.broadcasts = b || []; | ||||
| 		}); | ||||
| @@ -64,6 +68,27 @@ class TfElement extends LitElement { | ||||
| 		let ids = (await tfrpc.rpc.getIdentities()) || []; | ||||
| 		this.whoami = whoami ?? (ids.length ? ids[0] : undefined); | ||||
| 		this.ids = ids; | ||||
|  | ||||
| 		let channels = await tfrpc.rpc.query(` | ||||
| 			SELECT | ||||
| 				content ->> 'channel' AS channel, | ||||
| 				content ->> 'subscribed' AS subscribed | ||||
| 			FROM | ||||
| 				messages | ||||
| 			WHERE | ||||
| 				author = ? AND | ||||
| 				content ->> 'type' = 'channel' | ||||
| 			ORDER BY sequence | ||||
| 		`, [this.whoami]); | ||||
| 		let channel_map = {}; | ||||
| 		for (let row of channels) { | ||||
| 			if (row.subscribed) { | ||||
| 				channel_map[row.channel] = true; | ||||
| 			} else { | ||||
| 				delete channel_map[row.channel]; | ||||
| 			} | ||||
| 		} | ||||
| 		this.channels = Object.keys(channel_map); | ||||
| 	} | ||||
|  | ||||
| 	set_hash(hash) { | ||||
| @@ -195,33 +220,9 @@ class TfElement extends LitElement { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	async load_recent_tags() { | ||||
| 		let start = new Date(); | ||||
| 		this.tags = await tfrpc.rpc.query( | ||||
| 			` | ||||
| 			WITH | ||||
| 				recent AS (SELECT id, json(content) AS content FROM messages | ||||
| 					WHERE messages.timestamp > ? AND json_extract(content, '$.type') = 'post' | ||||
| 					ORDER BY timestamp DESC LIMIT 1024), | ||||
| 				recent_channels AS (SELECT recent.id, '#' || json_extract(content, '$.channel') AS tag | ||||
| 					FROM recent | ||||
| 					WHERE json_extract(content, '$.channel') IS NOT NULL), | ||||
| 				recent_mentions AS (SELECT recent.id, json_extract(mention.value, '$.link') AS tag | ||||
| 					FROM recent, json_each(recent.content, '$.mentions') AS mention | ||||
| 					WHERE json_valid(mention.value) AND tag LIKE '#%'), | ||||
| 				combined AS (SELECT id, tag FROM recent_channels UNION ALL SELECT id, tag FROM recent_mentions), | ||||
| 				by_message AS (SELECT DISTINCT id, tag FROM combined) | ||||
| 			SELECT tag, COUNT(*) AS count FROM by_message GROUP BY tag ORDER BY count DESC LIMIT 10 | ||||
| 		`, | ||||
| 			[new Date() - 7 * 24 * 60 * 60 * 1000] | ||||
| 		); | ||||
| 		console.log('tags took', (new Date() - start) / 1000.0, 'seconds'); | ||||
| 	} | ||||
|  | ||||
| 	async load() { | ||||
| 		let whoami = this.whoami; | ||||
| 		let tags = this.load_recent_tags(); | ||||
| 		let following = await tfrpc.rpc.following([whoami], 3); | ||||
| 		let following = await tfrpc.rpc.following([whoami], 2); | ||||
| 		let users = {}; | ||||
| 		let by_count = []; | ||||
| 		for (let [id, v] of Object.entries(following)) { | ||||
| @@ -233,6 +234,17 @@ class TfElement extends LitElement { | ||||
| 			}; | ||||
| 			by_count.push({count: v.of, id: id}); | ||||
| 		} | ||||
| 		let channels = tfrpc.rpc.query(` | ||||
| 			SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages | ||||
| 			JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value | ||||
| 			JOIN json_each(?2) AS following ON messages.author = following.value | ||||
| 			WHERE messages.content ->> 'type' = 'post' AND messages.content ->> 'root' IS NULL | ||||
| 			GROUP by channel | ||||
| 			UNION | ||||
| 			SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages | ||||
| 			JOIN json_each(?2) AS following ON messages.author = following.value | ||||
| 		`, [JSON.stringify(this.channels), JSON.stringify(Object.keys(following))]); | ||||
| 		this.channels_unread = JSON.parse((await tfrpc.rpc.databaseGet('unread')) ?? '{}'); | ||||
| 		console.log(by_count.sort((x, y) => y.count - x.count).slice(0, 20)); | ||||
| 		let start_time = new Date(); | ||||
| 		users = await this.fetch_about(Object.keys(following).sort(), users); | ||||
| @@ -243,14 +255,24 @@ class TfElement extends LitElement { | ||||
| 			Object.keys(users).length, | ||||
| 			'users' | ||||
| 		); | ||||
| 		channels = await channels; | ||||
| 		this.channels_latest = Object.fromEntries(channels.map(x => [x.channel, x.rowid])); | ||||
| 		console.log('CHANNELS', channels); | ||||
| 		this.following = Object.keys(following); | ||||
| 		this.users = users; | ||||
| 		await tags; | ||||
| 		console.log(`load finished ${whoami} => ${this.whoami}`); | ||||
| 		this.whoami = whoami; | ||||
| 		this.loaded = whoami; | ||||
| 	} | ||||
|  | ||||
| 	channel_set_unread(event) { | ||||
| 		console.log(event.detail.channel ?? '', event.detail.unread); | ||||
| 		this.channels_unread[event.detail.channel ?? ''] = event.detail.unread; | ||||
| 		this.channels_unread = Object.assign({}, this.channels_unread); | ||||
| 		console.log(this.channels_unread); | ||||
| 		tfrpc.rpc.databaseSet('unread', JSON.stringify(this.channels_unread)); | ||||
| 	} | ||||
|  | ||||
| 	render_tab() { | ||||
| 		let following = this.following; | ||||
| 		let users = this.users; | ||||
| @@ -265,6 +287,10 @@ class TfElement extends LitElement { | ||||
| 					.unread=${this.unread} | ||||
| 					@refresh=${() => (this.unread = [])} | ||||
| 					?loading=${this.loading} | ||||
| 					.channels=${this.channels} | ||||
| 					.channels_latest=${this.channels_latest} | ||||
| 					.channels_unread=${this.channels_unread} | ||||
| 					@channelsetunread=${this.channel_set_unread} | ||||
| 				></tf-tab-news> | ||||
| 			`; | ||||
| 		} else if (this.tab === 'connections') { | ||||
| @@ -344,7 +370,7 @@ class TfElement extends LitElement { | ||||
| 		}; | ||||
|  | ||||
| 		let tabs = html` | ||||
| 			<div class="w3-bar w3-theme-l1"> | ||||
| 			<div class="w3-bar w3-theme-l1" style="position: sticky; top: 0"> | ||||
| 				<button | ||||
| 					class="w3-bar-item w3-button w3-circle w3-ripple" | ||||
| 					@click=${this.refresh} | ||||
| @@ -385,12 +411,7 @@ class TfElement extends LitElement { | ||||
| 				class="w3-theme-dark" | ||||
| 			> | ||||
| 				${tabs} | ||||
| 				<div style="padding: 8px"> | ||||
| 					${this.tags.map( | ||||
| 						(x) => html`<tf-tag tag=${x.tag} count=${x.count}></tf-tag>` | ||||
| 					)} | ||||
| 					${contents} | ||||
| 				</div> | ||||
| 				${contents} | ||||
| 			</div> | ||||
| 		`; | ||||
| 	} | ||||
|   | ||||
| @@ -14,6 +14,7 @@ class TfComposeElement extends LitElement { | ||||
| 			apps: {type: Object}, | ||||
| 			drafts: {type: Object}, | ||||
| 			author: {type: String}, | ||||
| 			channel: {type: String}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @@ -196,6 +197,7 @@ class TfComposeElement extends LitElement { | ||||
| 		let message = { | ||||
| 			type: 'post', | ||||
| 			text: edit.innerText, | ||||
| 			channel: this.channel, | ||||
| 		}; | ||||
| 		if (this.root || this.branch) { | ||||
| 			message.root = this.root; | ||||
| @@ -535,6 +537,9 @@ class TfComposeElement extends LitElement { | ||||
| 				class="w3-card-4 w3-theme-d4 w3-padding-small" | ||||
| 				style="box-sizing: border-box" | ||||
| 			> | ||||
| 				${this.channel !== undefined ? | ||||
| 					html`<p>To #${this.channel}:</p>` : | ||||
| 					undefined} | ||||
| 				${this.render_encrypt()} | ||||
| 				<div class="w3-container w3-padding-small"> | ||||
| 					<div class="w3-half"> | ||||
|   | ||||
| @@ -14,6 +14,8 @@ class TfMessageElement extends LitElement { | ||||
| 			format: {type: String}, | ||||
| 			blog_data: {type: String}, | ||||
| 			expanded: {type: Object}, | ||||
| 			channel: {type: String}, | ||||
| 			channel_unread: {type: Number}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @@ -28,6 +30,7 @@ class TfMessageElement extends LitElement { | ||||
| 		this.drafts = {}; | ||||
| 		this.format = 'message'; | ||||
| 		this.expanded = {}; | ||||
| 		this.channel_unread = -1; | ||||
| 	} | ||||
|  | ||||
| 	show_reply() { | ||||
| @@ -312,12 +315,25 @@ ${JSON.stringify(mention, null, 2)}</pre | ||||
| 								.users=${this.users} | ||||
| 								.drafts=${this.drafts} | ||||
| 								.expanded=${this.expanded} | ||||
| 								channel=${this.channel} | ||||
| 								channel_unread=${this.channel_unread} | ||||
| 							></tf-message>` | ||||
| 					)}`; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	mark_read() { | ||||
| 		this.dispatchEvent(new CustomEvent('channelsetunread', { | ||||
| 			bubbles: true, | ||||
| 			composed: true, | ||||
| 			detail: { | ||||
| 				channel: this.channel, | ||||
| 				unread: this.message.rowid + 1, | ||||
| 			}, | ||||
| 		})); | ||||
| 	} | ||||
|  | ||||
| 	render_channels() { | ||||
| 		let content = this.message?.content; | ||||
| 		if (this?.messsage?.decrypted?.type == 'post') { | ||||
| @@ -344,7 +360,7 @@ ${JSON.stringify(mention, null, 2)}</pre | ||||
| 		} | ||||
| 		let class_background = this.message?.decrypted | ||||
| 			? 'w3-pale-red' | ||||
| 			: 'w3-theme-d4'; | ||||
| 			: (this.message?.rowid >= this.channel_unread ? 'w3-theme-d2' : 'w3-theme-d4'); | ||||
| 		let self = this; | ||||
| 		let raw_button; | ||||
| 		switch (this.format) { | ||||
| @@ -423,6 +439,8 @@ ${JSON.stringify(mention, null, 2)}</pre | ||||
| 								.users=${self.users} | ||||
| 								.drafts=${self.drafts} | ||||
| 								.expanded=${self.expanded} | ||||
| 								channel=${this.channel} | ||||
| 								channel_unread=${this.channel_unread} | ||||
| 							></tf-message> | ||||
| 						` | ||||
| 					)} | ||||
| @@ -442,6 +460,8 @@ ${JSON.stringify(mention, null, 2)}</pre | ||||
| 							.users=${this.users} | ||||
| 							.drafts=${this.drafts} | ||||
| 							.expanded=${this.expanded} | ||||
| 							channel=${this.channel} | ||||
| 							channel_unread=${this.channel_unread} | ||||
| 						></tf-message>` | ||||
| 				)} | ||||
| 			</div>`; | ||||
| @@ -463,6 +483,8 @@ ${JSON.stringify(mention, null, 2)}</pre | ||||
| 							.users=${this.users} | ||||
| 							.drafts=${this.drafts} | ||||
| 							.expanded=${this.expanded} | ||||
| 							channel=${this.channel} | ||||
| 							channel_unread=${this.channel_unread} | ||||
| 						></tf-message> | ||||
| 					` | ||||
| 				)} | ||||
| @@ -618,6 +640,11 @@ ${JSON.stringify(content, null, 2)}</pre | ||||
| 							<button class="w3-button w3-theme-d1" @click=${this.react}> | ||||
| 								React | ||||
| 							</button> | ||||
| 							${!content.root ? | ||||
| 								html` | ||||
| 									<button class="w3-button w3-theme-d1" @click=${this.mark_read}>Set Read Here</button> | ||||
| 								` : | ||||
| 								undefined} | ||||
| 						</p> | ||||
| 						${this.render_children()} | ||||
| 					</div> | ||||
|   | ||||
| @@ -11,6 +11,8 @@ class TfNewsElement extends LitElement { | ||||
| 			following: {type: Array}, | ||||
| 			drafts: {type: Object}, | ||||
| 			expanded: {type: Object}, | ||||
| 			channel: {type: String}, | ||||
| 			channel_unread: {type: Number}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @@ -25,6 +27,7 @@ class TfNewsElement extends LitElement { | ||||
| 		this.following = []; | ||||
| 		this.drafts = {}; | ||||
| 		this.expanded = {}; | ||||
| 		this.channel_unread = -1; | ||||
| 	} | ||||
|  | ||||
| 	process_messages(messages) { | ||||
| @@ -179,7 +182,7 @@ class TfNewsElement extends LitElement { | ||||
| 			this.finalize_messages(messages_by_id) | ||||
| 		); | ||||
| 		return html` | ||||
| 			<div style="display: flex; flex-direction: column"> | ||||
| 			<div> | ||||
| 				${final_messages.map( | ||||
| 					(x) => | ||||
| 						html`<tf-message | ||||
| @@ -189,6 +192,8 @@ class TfNewsElement extends LitElement { | ||||
| 							.drafts=${this.drafts} | ||||
| 							.expanded=${this.expanded} | ||||
| 							collapsed="true" | ||||
| 							channel=${this.channel} | ||||
| 							channel_unread=${this.channel_unread} | ||||
| 						></tf-message>` | ||||
| 				)} | ||||
| 			</div> | ||||
|   | ||||
| @@ -12,6 +12,9 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 			messages: {type: Array}, | ||||
| 			drafts: {type: Object}, | ||||
| 			expanded: {type: Object}, | ||||
| 			channels_unread: {type: Object}, | ||||
| 			loading: {type: Number}, | ||||
| 			time_range: {type: Array}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @@ -26,19 +29,25 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 		this.following = []; | ||||
| 		this.drafts = {}; | ||||
| 		this.expanded = {}; | ||||
| 		this.start_time = new Date().valueOf() - 24 * 60 * 60 * 1000; | ||||
| 		this.channels_unread = {}; | ||||
| 		this.start_time = (new Date()).valueOf(); | ||||
| 		this.time_range = [0, 0]; | ||||
| 	} | ||||
|  | ||||
| 	async fetch_messages() { | ||||
| 	channel() { | ||||
| 		return this.hash.startsWith('##') ? this.hash.substring(2) : ''; | ||||
| 	} | ||||
|  | ||||
| 	async fetch_messages(start_time, end_time) { | ||||
| 		if (this.hash.startsWith('#@')) { | ||||
| 			let r = await tfrpc.rpc.query( | ||||
| 				` | ||||
| 					WITH mine AS (SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature | ||||
| 					WITH mine AS (SELECT rowid, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature | ||||
| 						FROM messages | ||||
| 						WHERE messages.author = ? | ||||
| 						ORDER BY sequence DESC | ||||
| 						LIMIT 20) | ||||
| 					SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 					SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 						FROM mine | ||||
| 						JOIN messages_refs ON mine.id = messages_refs.ref | ||||
| 						JOIN messages ON messages_refs.message = messages.id | ||||
| @@ -62,24 +71,27 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 				`, | ||||
| 				[this.hash.substring(1)] | ||||
| 			); | ||||
| 		} else { | ||||
| 		} else if (this.hash.startsWith('##')) { | ||||
| 			let promises = []; | ||||
| 			const k_following_limit = 256; | ||||
| 			for (let i = 0; i < this.following.length; i += k_following_limit) { | ||||
| 				promises.push( | ||||
| 					tfrpc.rpc.query( | ||||
| 						` | ||||
| 						WITH news AS (SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 						WITH news AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 						FROM messages | ||||
| 						JOIN json_each(?) AS following ON messages.author = following.value | ||||
| 						WHERE messages.timestamp > ? AND messages.timestamp < ? | ||||
| 						WHERE | ||||
| 							messages.timestamp > ? AND | ||||
| 							messages.timestamp < ? AND | ||||
| 							messages.content ->> 'channel' = ? | ||||
| 						ORDER BY messages.timestamp DESC) | ||||
| 						SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 						SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 							FROM news | ||||
| 							JOIN messages_refs ON news.id = messages_refs.ref | ||||
| 							JOIN messages ON messages_refs.message = messages.id | ||||
| 						UNION | ||||
| 						SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 						SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 							FROM news | ||||
| 							JOIN messages_refs ON news.id = messages_refs.message | ||||
| 							JOIN messages ON messages_refs.ref = messages.id | ||||
| @@ -88,12 +100,42 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 					`, | ||||
| 						[ | ||||
| 							JSON.stringify(this.following.slice(i, i + k_following_limit)), | ||||
| 							this.start_time, | ||||
| 							/* | ||||
| 							 ** Don't show messages more than a day into the future to prevent | ||||
| 							 ** messages with far-future timestamps from staying at the top forever. | ||||
| 							 */ | ||||
| 							new Date().valueOf() + 24 * 60 * 60 * 1000, | ||||
| 							start_time, | ||||
| 							end_time, | ||||
| 							this.hash.substring(2), | ||||
| 						] | ||||
| 					) | ||||
| 				); | ||||
| 			} | ||||
| 			return [].concat(...(await Promise.all(promises))); | ||||
| 		} else { | ||||
| 			let promises = []; | ||||
| 			const k_following_limit = 256; | ||||
| 			for (let i = 0; i < this.following.length; i += k_following_limit) { | ||||
| 				promises.push( | ||||
| 					tfrpc.rpc.query( | ||||
| 						` | ||||
| 						WITH news AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 						FROM messages | ||||
| 						JOIN json_each(?) AS following ON messages.author = following.value | ||||
| 						WHERE messages.timestamp > ? AND messages.timestamp < ? | ||||
| 						ORDER BY messages.timestamp DESC) | ||||
| 						SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 							FROM news | ||||
| 							JOIN messages_refs ON news.id = messages_refs.ref | ||||
| 							JOIN messages ON messages_refs.message = messages.id | ||||
| 						UNION | ||||
| 						SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 							FROM news | ||||
| 							JOIN messages_refs ON news.id = messages_refs.message | ||||
| 							JOIN messages ON messages_refs.ref = messages.id | ||||
| 						UNION | ||||
| 						SELECT news.* FROM news | ||||
| 					`, | ||||
| 						[ | ||||
| 							JSON.stringify(this.following.slice(i, i + k_following_limit)), | ||||
| 							start_time, | ||||
| 							end_time, | ||||
| 						] | ||||
| 					) | ||||
| 				); | ||||
| @@ -103,31 +145,19 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 	} | ||||
|  | ||||
| 	async load_more() { | ||||
| 		let last_start_time = this.start_time; | ||||
| 		this.start_time = last_start_time - 24 * 60 * 60 * 1000; | ||||
| 		let more = await tfrpc.rpc.query( | ||||
| 			` | ||||
| 				WITH news AS (SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 				FROM messages | ||||
| 				JOIN json_each(?) AS following ON messages.author = following.value | ||||
| 				WHERE messages.timestamp > ? | ||||
| 				AND messages.timestamp <= ? | ||||
| 				ORDER BY messages.timestamp DESC) | ||||
| 				SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 					FROM news | ||||
| 					JOIN messages_refs ON news.id = messages_refs.ref | ||||
| 					JOIN messages ON messages_refs.message = messages.id | ||||
| 				UNION | ||||
| 				SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 					FROM news | ||||
| 					JOIN messages_refs ON news.id = messages_refs.message | ||||
| 					JOIN messages ON messages_refs.ref = messages.id | ||||
| 				UNION | ||||
| 				SELECT news.* FROM news | ||||
| 			`, | ||||
| 			[JSON.stringify(this.following), this.start_time, last_start_time] | ||||
| 		); | ||||
| 		this.messages = await this.decrypt([...more, ...this.messages]); | ||||
| 		this.loading++; | ||||
| 		try { | ||||
| 			let more = []; | ||||
| 			while (!more.length) { | ||||
| 				let last_start_time = this.start_time; | ||||
| 				this.start_time = last_start_time - 7 * 24 * 60 * 60 * 1000; | ||||
| 				more = await this.fetch_messages(this.start_time, last_start_time); | ||||
| 				this.time_range = [this.start_time, this.time_range[1]]; | ||||
| 			} | ||||
| 			this.messages = await this.decrypt([...more, ...this.messages]); | ||||
| 		} finally { | ||||
| 			this.loading--; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	async decrypt(messages) { | ||||
| @@ -160,6 +190,51 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 		this.messages = await this.decrypt([...messages, ...this.messages]); | ||||
| 	} | ||||
|  | ||||
| 	async load_messages() { | ||||
| 		let self = this; | ||||
| 		this.loading = true; | ||||
| 		let messages = []; | ||||
| 		try { | ||||
| 			this.messages = []; | ||||
| 			this._messages_hash = this.hash; | ||||
| 			this._messages_following = this.following; | ||||
| 			let now = new Date().valueOf(); | ||||
| 			let start_time = now - 24 * 60 * 60 * 1000; | ||||
| 			this.start_time = start_time; | ||||
| 			this.time_range = [this.start_time, now + 24 * 60 * 60 * 1000]; | ||||
| 			messages = await this.fetch_messages(this.time_range[0], this.time_range[1]); | ||||
| 			messages = await this.decrypt(messages); | ||||
| 			if (!messages.length) { | ||||
| 				let more = []; | ||||
| 				while (!more.length && start_time >= 0) { | ||||
| 					let last_start_time = start_time; | ||||
| 					start_time = last_start_time - 7 * 24 * 60 * 60 * 1000; | ||||
| 					more = await this.fetch_messages(start_time, last_start_time); | ||||
| 				} | ||||
| 				this.time_range = [start_time, this.time_range[1]]; | ||||
| 				messages = await this.decrypt([...more, ...this.messages]); | ||||
| 			} | ||||
| 		} finally { | ||||
| 			this.loading = false; | ||||
| 		} | ||||
| 		this.messages = messages; | ||||
| 		console.log(`loading messages done for ${self.whoami}`); | ||||
| 	} | ||||
|  | ||||
| 	mark_all_read() { | ||||
| 		let newest = this.messages.reduce((accumulator, current) => Math.max(accumulator, current.rowid), -1); | ||||
| 		if (newest >= 0) { | ||||
| 			this.dispatchEvent(new CustomEvent('channelsetunread', { | ||||
| 				bubbles: true, | ||||
| 				composed: true, | ||||
| 				detail: { | ||||
| 					channel: this.channel(), | ||||
| 					unread: newest + 1, | ||||
| 				}, | ||||
| 			})); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	render() { | ||||
| 		if ( | ||||
| 			!this.messages || | ||||
| @@ -169,27 +244,17 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 			console.log( | ||||
| 				`loading messages for ${this.whoami} (following ${this.following.length})` | ||||
| 			); | ||||
| 			let self = this; | ||||
| 			this.messages = []; | ||||
| 			this._messages_hash = this.hash; | ||||
| 			this._messages_following = this.following; | ||||
| 			this.fetch_messages() | ||||
| 				.then(this.decrypt.bind(this)) | ||||
| 				.then(function (messages) { | ||||
| 					self.messages = messages; | ||||
| 					console.log(`loading mesages done for ${self.whoami}`); | ||||
| 				}) | ||||
| 				.catch(function (error) { | ||||
| 					alert(JSON.stringify(error, null, 2)); | ||||
| 				}); | ||||
| 			this.load_messages(); | ||||
| 		} | ||||
| 		let more; | ||||
| 		if (!this.hash.startsWith('#@') && !this.hash.startsWith('#%')) { | ||||
| 			more = html` | ||||
| 				<p> | ||||
| 					<button class="w3-button w3-theme-d1" @click=${this.load_more}> | ||||
| 					<button class="w3-button w3-theme-d1" @click=${this.mark_all_read}>Mark All Read</button> | ||||
| 					<button ?disabled=${this.loading} class="w3-button w3-theme-d1" @click=${this.load_more}> | ||||
| 						Load More | ||||
| 					</button> | ||||
| 					<span>Showing ${new Date(this.time_range[0]).toLocaleDateString()} - ${new Date(this.time_range[1]).toLocaleDateString()}.</span> | ||||
| 				</p> | ||||
| 			`; | ||||
| 		} | ||||
| @@ -202,6 +267,8 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 				.following=${this.following} | ||||
| 				.drafts=${this.drafts} | ||||
| 				.expanded=${this.expanded} | ||||
| 				channel=${this.channel()} | ||||
| 				channel_unread=${this.channels_unread?.[this.channel()]} | ||||
| 			></tf-news> | ||||
| 			${more} | ||||
| 		`; | ||||
|   | ||||
| @@ -13,6 +13,9 @@ class TfTabNewsElement extends LitElement { | ||||
| 			drafts: {type: Object}, | ||||
| 			expanded: {type: Object}, | ||||
| 			loading: {type: Boolean}, | ||||
| 			channels: {type: Array}, | ||||
| 			channels_unread: {type: Object}, | ||||
| 			channels_latest: {type: Object}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @@ -29,6 +32,9 @@ class TfTabNewsElement extends LitElement { | ||||
| 		this.cache = {}; | ||||
| 		this.drafts = {}; | ||||
| 		this.expanded = {}; | ||||
| 		this.channels_unread = {}; | ||||
| 		this.channels_latest = {}; | ||||
| 		this.channels = []; | ||||
| 		tfrpc.rpc.localStorageGet('drafts').then(function (d) { | ||||
| 			self.drafts = JSON.parse(d || '{}'); | ||||
| 		}); | ||||
| @@ -106,6 +112,47 @@ class TfTabNewsElement extends LitElement { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	unread_status(channel) { | ||||
| 		if (this.channels_latest[channel] && | ||||
| 			(this.channels_unread[channel] === undefined || | ||||
| 			this.channels_unread[channel] < this.channels_latest[channel])) { | ||||
| 			return '🔵'; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	show_sidebar() { | ||||
| 		this.renderRoot.getElementById('sidebar').style.display = 'block'; | ||||
| 		this.renderRoot.getElementById('main').style.marginLeft = '2in'; | ||||
| 		this.renderRoot.getElementById('show_sidebar').style.display = 'none'; | ||||
| 	} | ||||
|  | ||||
| 	hide_sidebar() { | ||||
| 		this.renderRoot.getElementById('sidebar').style.display = 'none'; | ||||
| 		this.renderRoot.getElementById('main').style.marginLeft = '0'; | ||||
| 		this.renderRoot.getElementById('show_sidebar').style.display = 'block'; | ||||
| 	} | ||||
|  | ||||
| 	async channel_toggle_subscribed() { | ||||
| 		let channel = this.hash.substring(2); | ||||
| 		let subscribed = this.channels.indexOf(channel) != -1; | ||||
| 		subscribed = !subscribed; | ||||
|  | ||||
| 		await tfrpc.rpc.appendMessage(this.whoami, { | ||||
| 			type: 'channel', | ||||
| 			channel: channel, | ||||
| 			subscribed: subscribed, | ||||
| 		}); | ||||
| 		if (subscribed) { | ||||
| 			this.channels = [].concat([channel], this.channels).sort(); | ||||
| 		} else { | ||||
| 			this.channels = this.channels.filter(x => x != channel); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	channel() { | ||||
| 		return this.hash.startsWith('##') ? this.hash.substring(2) : undefined; | ||||
| 	} | ||||
|  | ||||
| 	render() { | ||||
| 		let profile = this.hash.startsWith('#@') | ||||
| 			? html`<tf-profile | ||||
| @@ -129,39 +176,68 @@ class TfTabNewsElement extends LitElement { | ||||
| 			</div>`; | ||||
| 		} | ||||
| 		return html` | ||||
| 			<p class="w3-bar"> | ||||
| 				<button | ||||
| 					class="w3-bar-item w3-button w3-theme-d1" | ||||
| 					@click=${this.show_more} | ||||
| 				> | ||||
| 					${this.new_messages_text()} | ||||
| 				</button> | ||||
| 			</p> | ||||
| 			<div class="w3-bar"> | ||||
| 				Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>! | ||||
| 				${edit_profile} | ||||
| 			<div class="w3-sidebar w3-bar-block w3-theme-d1" style="width: 2in; left: 0" id="sidebar"> | ||||
| 				<div class="w3-right w3-button" @click=${this.hide_sidebar}>×</div> | ||||
| 				${this.hash.startsWith('##') && this.channels.indexOf(this.hash.substring(2)) == -1 ? | ||||
| 					html` | ||||
| 						<div class="w3-bar-item w3-theme-d2">Viewing</div> | ||||
| 						<a href="#" class="w3-bar-item w3-button" style="font-weight: bold">${this.hash.substring(2)}</a> | ||||
| 					` : | ||||
| 					undefined} | ||||
| 				<div class="w3-bar-item w3-theme-d2">Channels</div> | ||||
| 				<a href="#" class="w3-bar-item w3-button" style=${this.hash == '#' ? 'font-weight: bold' : undefined}>general ${this.unread_status('')}</a> | ||||
| 				${this.channels.map(x => html` | ||||
| 					<a | ||||
| 						href=${'#' + encodeURIComponent('#' + x)} | ||||
| 						class="w3-bar-item w3-button" | ||||
| 						style=${this.hash == '##' + x ? 'font-weight: bold' : undefined}>#${x} ${this.unread_status(x)}</a> | ||||
| 				`)} | ||||
| 			</div> | ||||
| 			<div> | ||||
| 				<tf-compose | ||||
| 					id="tf-compose" | ||||
| 			<div style="margin-left: 2in; padding: 8px" id="main"> | ||||
| 				<div id="show_sidebar" class="w3-left w3-button" style="display: none" @click=${this.show_sidebar}>☰</div> | ||||
| 				<p> | ||||
| 					<button | ||||
| 						class="w3-button w3-theme-d1" | ||||
| 						@click=${this.show_more} | ||||
| 					> | ||||
| 						${this.new_messages_text()} | ||||
| 					</button> | ||||
| 					${this.hash.startsWith('##') ? | ||||
| 						html` | ||||
| 							<button class="w3-button w3-theme-d1" @click=${this.channel_toggle_subscribed}> | ||||
| 								${this.channels.indexOf(this.hash.substring(2)) != -1 ? 'Unsubscribe from #' : 'Subscribe to #'}${this.hash.substring(2)} | ||||
| 							</button> | ||||
| 						` : | ||||
| 						undefined} | ||||
| 				</p> | ||||
| 				<div class="w3-bar"> | ||||
| 					Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>! | ||||
| 					${edit_profile} | ||||
| 				</div> | ||||
| 				<div> | ||||
| 					<tf-compose | ||||
| 						id="tf-compose" | ||||
| 						whoami=${this.whoami} | ||||
| 						.users=${this.users} | ||||
| 						.drafts=${this.drafts} | ||||
| 						@tf-draft=${this.draft} | ||||
| 						.channel=${this.channel()} | ||||
| 					></tf-compose> | ||||
| 				</div> | ||||
| 				${profile} | ||||
| 				<tf-tab-news-feed | ||||
| 					id="news" | ||||
| 					whoami=${this.whoami} | ||||
| 					.users=${this.users} | ||||
| 					.following=${this.following} | ||||
| 					hash=${this.hash} | ||||
| 					.drafts=${this.drafts} | ||||
| 					.expanded=${this.expanded} | ||||
| 					@tf-draft=${this.draft} | ||||
| 				></tf-compose> | ||||
| 					@tf-expand=${this.on_expand} | ||||
| 					.channels_unread=${this.channels_unread} | ||||
| 				></tf-tab-news-feed> | ||||
| 			</div> | ||||
| 			${profile} | ||||
| 			<tf-tab-news-feed | ||||
| 				id="news" | ||||
| 				whoami=${this.whoami} | ||||
| 				.users=${this.users} | ||||
| 				.following=${this.following} | ||||
| 				hash=${this.hash} | ||||
| 				.drafts=${this.drafts} | ||||
| 				.expanded=${this.expanded} | ||||
| 				@tf-draft=${this.draft} | ||||
| 				@tf-expand=${this.on_expand} | ||||
| 			></tf-tab-news-feed> | ||||
| 		`; | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user