All checks were successful
		
		
	
	Build Tilde Friends / Build-All (push) Successful in 18m35s
				
			
		
			
				
	
	
		
			577 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			577 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import {LitElement, cache, html, unsafeHTML, until} from './lit-all.min.js';
 | 
						|
import * as tfrpc from '/static/tfrpc.js';
 | 
						|
import {styles, generate_theme} from './tf-styles.js';
 | 
						|
 | 
						|
class TfTabNewsFeedElement extends LitElement {
 | 
						|
	static get properties() {
 | 
						|
		return {
 | 
						|
			whoami: {type: String},
 | 
						|
			users: {type: Object},
 | 
						|
			hash: {type: String},
 | 
						|
			following: {type: Array},
 | 
						|
			messages: {type: Array},
 | 
						|
			drafts: {type: Object},
 | 
						|
			expanded: {type: Object},
 | 
						|
			channels_unread: {type: Object},
 | 
						|
			channels_latest: {type: Object},
 | 
						|
			loading: {type: Number},
 | 
						|
			time_range: {type: Array},
 | 
						|
			time_loading: {type: Array},
 | 
						|
			private_messages: {type: Array},
 | 
						|
			grouped_private_messages: {type: Object},
 | 
						|
			recent_reactions: {type: Array},
 | 
						|
		};
 | 
						|
	}
 | 
						|
 | 
						|
	static styles = styles;
 | 
						|
 | 
						|
	constructor() {
 | 
						|
		super();
 | 
						|
		let self = this;
 | 
						|
		this.whoami = null;
 | 
						|
		this.users = {};
 | 
						|
		this.hash = '#';
 | 
						|
		this.following = [];
 | 
						|
		this.drafts = {};
 | 
						|
		this.expanded = {};
 | 
						|
		this.channels_unread = {};
 | 
						|
		this.channels_latest = {};
 | 
						|
		this.start_time = new Date().valueOf();
 | 
						|
		this.time_range = [0, 0];
 | 
						|
		this.time_loading = undefined;
 | 
						|
		this.recent_reactions = [];
 | 
						|
		this.loading = 0;
 | 
						|
	}
 | 
						|
 | 
						|
	channel() {
 | 
						|
		return this.hash.startsWith('##')
 | 
						|
			? this.hash.substring(2)
 | 
						|
			: this.hash.substring(1);
 | 
						|
	}
 | 
						|
 | 
						|
	async _fetch_related_messages(messages) {
 | 
						|
		let refs = await tfrpc.rpc.query(
 | 
						|
			`
 | 
						|
				WITH
 | 
						|
					news AS (
 | 
						|
						SELECT value AS id FROM json_each(?)
 | 
						|
					)
 | 
						|
				SELECT refs_out.ref AS ref FROM messages_refs refs_out JOIN news ON refs_out.message = news.id
 | 
						|
				UNION
 | 
						|
				SELECT refs_in.message AS ref FROM messages_refs refs_in JOIN news ON refs_in.ref = news.id
 | 
						|
			`,
 | 
						|
			[JSON.stringify(messages.map((x) => x.id))]
 | 
						|
		);
 | 
						|
		let related_messages = await tfrpc.rpc.query(
 | 
						|
			`
 | 
						|
				SELECT FALSE AS is_primary, 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(?2) refs ON messages.id = refs.value
 | 
						|
				JOIN json_each(?1) AS following ON messages.author = following.value
 | 
						|
			`,
 | 
						|
			[JSON.stringify(this.following), JSON.stringify(refs.map((x) => x.ref))]
 | 
						|
		);
 | 
						|
		let combined = [].concat(messages, related_messages);
 | 
						|
		let refs2 = await tfrpc.rpc.query(
 | 
						|
			`
 | 
						|
				WITH
 | 
						|
					news AS (
 | 
						|
						SELECT value AS id FROM json_each(?)
 | 
						|
					)
 | 
						|
				SELECT refs_out.ref AS ref FROM messages_refs refs_out JOIN news ON refs_out.message = news.id
 | 
						|
				UNION
 | 
						|
				SELECT refs_in.message AS ref FROM messages_refs refs_in JOIN news ON refs_in.ref = news.id
 | 
						|
			`,
 | 
						|
			[JSON.stringify(combined.map((x) => x.id))]
 | 
						|
		);
 | 
						|
		let t0 = new Date();
 | 
						|
		let result = [].concat(
 | 
						|
			combined,
 | 
						|
			await tfrpc.rpc.query(
 | 
						|
				`
 | 
						|
				SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
 | 
						|
				FROM json_each(?2) refs
 | 
						|
				JOIN messages ON messages.id = refs.value
 | 
						|
				JOIN json_each(?1) following ON messages.author = following.value
 | 
						|
				WHERE messages.content ->> 'type' != 'post'
 | 
						|
			`,
 | 
						|
				[
 | 
						|
					JSON.stringify(this.following),
 | 
						|
					JSON.stringify(refs2.map((x) => x.ref)),
 | 
						|
				]
 | 
						|
			)
 | 
						|
		);
 | 
						|
		let t1 = new Date();
 | 
						|
		console.log((t1 - t0) / 1000);
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	async fetch_messages(start_time, end_time) {
 | 
						|
		this.dispatchEvent(
 | 
						|
			new CustomEvent('loadmessages', {
 | 
						|
				bubbles: true,
 | 
						|
				composed: true,
 | 
						|
			})
 | 
						|
		);
 | 
						|
		this.time_loading = [start_time, end_time];
 | 
						|
		let result;
 | 
						|
		const k_max_results = 64;
 | 
						|
		if (this.hash == '#@') {
 | 
						|
			result = await tfrpc.rpc.query(
 | 
						|
				`
 | 
						|
					WITH mentions 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_fts(?1)
 | 
						|
						JOIN messages ON messages.rowid = messages_fts.rowid
 | 
						|
						JOIN json_each(?2) AS following ON messages.author = following.value
 | 
						|
						WHERE
 | 
						|
							messages.author != ?1 AND
 | 
						|
							(?3 IS NULL OR messages.timestamp >= ?3) AND messages.timestamp < ?4
 | 
						|
						ORDER BY timestamp DESC limit ?5)
 | 
						|
					SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
 | 
						|
						FROM mentions
 | 
						|
						JOIN messages_refs ON mentions.id = messages_refs.ref
 | 
						|
						JOIN messages ON messages_refs.message = messages.id
 | 
						|
					UNION
 | 
						|
					SELECT TRUE AS is_primary, * FROM mentions
 | 
						|
				`,
 | 
						|
				[
 | 
						|
					'"' + this.whoami.replace('"', '""') + '"',
 | 
						|
					JSON.stringify(this.following),
 | 
						|
					start_time,
 | 
						|
					end_time,
 | 
						|
					k_max_results,
 | 
						|
				]
 | 
						|
			);
 | 
						|
		} else if (this.hash.startsWith('#@')) {
 | 
						|
			result = await tfrpc.rpc.query(
 | 
						|
				`
 | 
						|
					WITH
 | 
						|
						selected AS (SELECT rowid, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
 | 
						|
							FROM messages
 | 
						|
							WHERE messages.author = ?1 AND (?2 IS NULL OR messages.timestamp >= 2) AND messages.timestamp < ?3
 | 
						|
							ORDER BY sequence DESC LIMIT ?4
 | 
						|
						)
 | 
						|
					SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
 | 
						|
						FROM selected
 | 
						|
						JOIN messages_refs ON selected.id = messages_refs.ref
 | 
						|
						JOIN messages ON messages_refs.message = messages.id
 | 
						|
					UNION
 | 
						|
					SELECT TRUE AS is_primary, * FROM selected
 | 
						|
				`,
 | 
						|
				[this.hash.substring(1), start_time, end_time, k_max_results]
 | 
						|
			);
 | 
						|
		} else if (this.hash.startsWith('#%')) {
 | 
						|
			result = await tfrpc.rpc.query(
 | 
						|
				`
 | 
						|
					SELECT TRUE AS is_primary, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
 | 
						|
					FROM messages
 | 
						|
					WHERE messages.id = ?1
 | 
						|
					UNION
 | 
						|
					SELECT FALSE AS is_primary, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
 | 
						|
					FROM messages JOIN messages_refs
 | 
						|
					ON messages.id = messages_refs.message
 | 
						|
					WHERE messages_refs.ref = ?1
 | 
						|
				`,
 | 
						|
				[this.hash.substring(1)]
 | 
						|
			);
 | 
						|
		} else if (this.hash.startsWith('##')) {
 | 
						|
			let t0 = new Date();
 | 
						|
			let initial_messages = await tfrpc.rpc.query(
 | 
						|
				`
 | 
						|
					WITH
 | 
						|
						all_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.content ->> 'channel' = ?4 AND messages.content ->> 'type' != 'vote'
 | 
						|
							UNION
 | 
						|
							SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
 | 
						|
								FROM messages_refs
 | 
						|
								JOIN messages ON messages.id = messages_refs.message
 | 
						|
								JOIN json_each(?1) AS following ON messages.author = following.value
 | 
						|
								WHERE messages_refs.ref = '#' || ?4 AND messages.content ->> 'type' != 'vote'
 | 
						|
							)
 | 
						|
					SELECT TRUE AS is_primary, all_news.* FROM all_news
 | 
						|
						WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
 | 
						|
						ORDER BY all_news.timestamp DESC LIMIT ?5
 | 
						|
				`,
 | 
						|
				[
 | 
						|
					JSON.stringify(this.following),
 | 
						|
					start_time,
 | 
						|
					end_time,
 | 
						|
					this.hash.substring(2),
 | 
						|
					k_max_results,
 | 
						|
				]
 | 
						|
			);
 | 
						|
			let t1 = new Date();
 | 
						|
			result = await this._fetch_related_messages(initial_messages);
 | 
						|
			let t2 = new Date();
 | 
						|
			console.log(
 | 
						|
				`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}`
 | 
						|
			);
 | 
						|
		} else if (this.hash.startsWith('#🔐')) {
 | 
						|
			let ids =
 | 
						|
				this.hash == '#🔐' ? [] : this.hash.substring('#🔐'.length).split(',');
 | 
						|
			result = await tfrpc.rpc.query(
 | 
						|
				`
 | 
						|
					SELECT TRUE AS is_primary, messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
 | 
						|
					FROM messages
 | 
						|
					JOIN json_each(?1) AS private_messages ON messages.id = private_messages.value
 | 
						|
					WHERE
 | 
						|
						(?2 IS NULL OR (messages.timestamp >= ?2)) AND messages.timestamp < ?3 AND
 | 
						|
						json(messages.content) LIKE '"%'
 | 
						|
					ORDER BY messages.rowid DESC LIMIT ?4
 | 
						|
				`,
 | 
						|
				[
 | 
						|
					JSON.stringify(
 | 
						|
						this.grouped_private_messages?.[JSON.stringify(ids)]?.map(
 | 
						|
							(x) => x.id
 | 
						|
						) ?? []
 | 
						|
					),
 | 
						|
					start_time,
 | 
						|
					end_time,
 | 
						|
					k_max_results,
 | 
						|
				]
 | 
						|
			);
 | 
						|
			result = (await this.decrypt(result)).filter((x) => x.decrypted);
 | 
						|
		} else if (this.hash == '#👍') {
 | 
						|
			result = await tfrpc.rpc.query(
 | 
						|
				`
 | 
						|
					WITH votes 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(?1) AS following ON messages.author = following.value
 | 
						|
						WHERE
 | 
						|
							messages.content ->> 'type' = 'vote' AND
 | 
						|
							(?2 IS NULL OR messages.timestamp >= ?2) AND messages.timestamp < ?3
 | 
						|
						ORDER BY timestamp DESC limit ?4)
 | 
						|
					SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
 | 
						|
						FROM votes
 | 
						|
						JOIN messages ON messages.id = votes.content ->> '$.vote.link'
 | 
						|
					UNION
 | 
						|
					SELECT TRUE AS is_primary, * FROM votes
 | 
						|
				`,
 | 
						|
				[JSON.stringify(this.following), start_time, end_time, k_max_results]
 | 
						|
			);
 | 
						|
		} else {
 | 
						|
			let t0 = new Date();
 | 
						|
			let initial_messages = await tfrpc.rpc.query(
 | 
						|
				`
 | 
						|
					WITH
 | 
						|
						channels AS (SELECT '#' || value AS value FROM json_each(?5))
 | 
						|
					SELECT TRUE AS is_primary, 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(?1) AS following ON messages.author = following.value
 | 
						|
							WHERE messages.timestamp < ?3 AND (?2 IS NULL OR messages.timestamp >= ?2) AND
 | 
						|
								messages.content ->> 'type' != 'vote' AND
 | 
						|
								(messages.content ->> 'root' IS NULL OR (
 | 
						|
									NOT EXISTS (SELECT * FROM messages root JOIN channels ON ('#' || (root.content ->> 'channel')) = channels.value WHERE root.id = messages.content ->> 'root') AND
 | 
						|
									NOT EXISTS (SELECT * FROM messages root JOIN messages_refs ON root.id = messages.content ->> 'root' JOIN channels ON messages_refs.message = root.id AND messages_refs.ref = channels.value)
 | 
						|
								)) AND
 | 
						|
								(messages.content ->> 'channel' IS NULL OR ('#' || (messages.content ->> 'channel')) NOT IN (SELECT * FROM channels)) AND
 | 
						|
								NOT EXISTS (SELECT * FROM messages_refs JOIN channels ON messages_refs.message = messages.id AND messages_refs.ref = channels.value)
 | 
						|
					ORDER BY timestamp DESC LIMIT ?4
 | 
						|
				`,
 | 
						|
				[
 | 
						|
					JSON.stringify(this.following),
 | 
						|
					start_time,
 | 
						|
					end_time,
 | 
						|
					k_max_results,
 | 
						|
					JSON.stringify(Object.keys(this.channels_latest)),
 | 
						|
				]
 | 
						|
			);
 | 
						|
			let t1 = new Date();
 | 
						|
			result = await this._fetch_related_messages(initial_messages);
 | 
						|
			let t2 = new Date();
 | 
						|
			console.log(
 | 
						|
				`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}`
 | 
						|
			);
 | 
						|
		}
 | 
						|
		this.time_loading = undefined;
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	update_time_range_from_messages(messages) {
 | 
						|
		let only_primary = messages.filter((x) => x.is_primary);
 | 
						|
		this.time_range = [
 | 
						|
			only_primary.reduce(
 | 
						|
				(accumulator, current) => Math.min(accumulator, current.timestamp),
 | 
						|
				this.time_range[0]
 | 
						|
			),
 | 
						|
			only_primary.reduce(
 | 
						|
				(accumulator, current) => Math.max(accumulator, current.timestamp),
 | 
						|
				this.time_range[1]
 | 
						|
			),
 | 
						|
		];
 | 
						|
	}
 | 
						|
 | 
						|
	unread_allowed() {
 | 
						|
		return (
 | 
						|
			this.hash == '#@' ||
 | 
						|
			(!this.hash.startsWith('#%') && !this.hash.startsWith('#@'))
 | 
						|
		);
 | 
						|
	}
 | 
						|
 | 
						|
	async load_more() {
 | 
						|
		this.loading++;
 | 
						|
		this.loading_canceled = false;
 | 
						|
		try {
 | 
						|
			let more = [];
 | 
						|
			let last_start_time = this.time_range[0];
 | 
						|
			try {
 | 
						|
				more = await this.fetch_messages(null, last_start_time);
 | 
						|
			} catch (e) {
 | 
						|
				console.log(e);
 | 
						|
			}
 | 
						|
			this.update_time_range_from_messages(
 | 
						|
				more.filter((x) => x.timestamp < last_start_time)
 | 
						|
			);
 | 
						|
			this.messages = await this.decrypt([...more, ...this.messages]);
 | 
						|
		} finally {
 | 
						|
			this.loading--;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	cancel_load() {
 | 
						|
		this.loading_canceled = true;
 | 
						|
	}
 | 
						|
 | 
						|
	async decrypt(messages) {
 | 
						|
		let result = [];
 | 
						|
		for (let message of messages) {
 | 
						|
			let content;
 | 
						|
			try {
 | 
						|
				content = JSON.parse(message?.content);
 | 
						|
			} catch {}
 | 
						|
			if (typeof content === 'string') {
 | 
						|
				let decrypted;
 | 
						|
				try {
 | 
						|
					decrypted = await tfrpc.rpc.try_decrypt(this.whoami, content);
 | 
						|
				} catch {}
 | 
						|
				if (decrypted) {
 | 
						|
					try {
 | 
						|
						message.decrypted = JSON.parse(decrypted);
 | 
						|
					} catch {
 | 
						|
						message.decrypted = decrypted;
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
			result.push(message);
 | 
						|
		}
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	merge_messages(old_messages, new_messages) {
 | 
						|
		let old_by_id = Object.fromEntries(old_messages.map((x) => [x.id, x]));
 | 
						|
		return new_messages.map((x) => (old_by_id[x.id] ? old_by_id[x.id] : x));
 | 
						|
	}
 | 
						|
 | 
						|
	async load_latest() {
 | 
						|
		this.loading++;
 | 
						|
		let now = new Date().valueOf();
 | 
						|
		let end_time = now + 24 * 60 * 60 * 1000;
 | 
						|
		let messages = [];
 | 
						|
		try {
 | 
						|
			messages = await this.fetch_messages(this.time_range[0], end_time);
 | 
						|
			messages = await this.decrypt(messages);
 | 
						|
			this.update_time_range_from_messages(
 | 
						|
				messages.filter(
 | 
						|
					(x) => x.timestamp >= this.time_range[0] && x.timestamp < end_time
 | 
						|
				)
 | 
						|
			);
 | 
						|
		} finally {
 | 
						|
			this.loading--;
 | 
						|
		}
 | 
						|
		this.messages = this.merge_messages(
 | 
						|
			this.messages,
 | 
						|
			Object.values(
 | 
						|
				Object.fromEntries(
 | 
						|
					[...this.messages, ...messages]
 | 
						|
						.sort((x, y) => x.timestamp - y.timestamp)
 | 
						|
						.slice(-1024)
 | 
						|
						.map((x) => [x.id, x])
 | 
						|
				)
 | 
						|
			)
 | 
						|
		);
 | 
						|
		console.log('done loading latest messages.');
 | 
						|
	}
 | 
						|
 | 
						|
	async load_messages() {
 | 
						|
		let start_time = new Date();
 | 
						|
		let self = this;
 | 
						|
		this.loading++;
 | 
						|
		let messages = [];
 | 
						|
		let original_hash = this.hash;
 | 
						|
		try {
 | 
						|
			if (this._messages_hash !== this.hash) {
 | 
						|
				this.messages = [];
 | 
						|
				this._messages_hash = this.hash;
 | 
						|
			}
 | 
						|
			this._messages_following = JSON.stringify(this.following);
 | 
						|
			this._private_messages =
 | 
						|
				JSON.stringify(this.private_messages) +
 | 
						|
				JSON.stringify(this.grouped_private_messages);
 | 
						|
			this._channels_latest = JSON.stringify(
 | 
						|
				Object.keys(this.channels_latest ?? {})
 | 
						|
			);
 | 
						|
			let now = new Date().valueOf();
 | 
						|
			let start_time = now - 24 * 60 * 60 * 1000;
 | 
						|
			this.start_time = start_time;
 | 
						|
			this.time_range = [now + 24 * 60 * 60 * 1000, now + 24 * 60 * 60 * 1000];
 | 
						|
			messages = await this.fetch_messages(null, this.time_range[1]);
 | 
						|
			this.update_time_range_from_messages(
 | 
						|
				messages.filter((x) => x.timestamp < this.time_range[1])
 | 
						|
			);
 | 
						|
			messages = await this.decrypt(messages);
 | 
						|
		} finally {
 | 
						|
			this.loading--;
 | 
						|
		}
 | 
						|
		if (this.hash == original_hash) {
 | 
						|
			this.messages = this.merge_messages(this.messages, messages);
 | 
						|
		}
 | 
						|
		this.time_loading = undefined;
 | 
						|
		console.log(
 | 
						|
			`loading ${messages.length} messages done for ${self.whoami} in ${(new Date() - start_time) / 1000}s`
 | 
						|
		);
 | 
						|
	}
 | 
						|
 | 
						|
	mark_all_read() {
 | 
						|
		let newest = this.messages.reduce(
 | 
						|
			(accumulator, current) => Math.max(accumulator, current.rowid),
 | 
						|
			this.channels_latest[this.channel()] ?? -1
 | 
						|
		);
 | 
						|
		if (newest >= 0) {
 | 
						|
			this.dispatchEvent(
 | 
						|
				new CustomEvent('channelsetunread', {
 | 
						|
					bubbles: true,
 | 
						|
					composed: true,
 | 
						|
					detail: {
 | 
						|
						channel: this.channel(),
 | 
						|
						unread: newest + 1,
 | 
						|
					},
 | 
						|
				})
 | 
						|
			);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	close_private_chat() {
 | 
						|
		this.mark_all_read();
 | 
						|
		this.dispatchEvent(
 | 
						|
			new CustomEvent('closeprivatechat', {
 | 
						|
				bubbles: true,
 | 
						|
				composed: true,
 | 
						|
				detail: {
 | 
						|
					key: JSON.stringify(
 | 
						|
						this.hash == '#🔐'
 | 
						|
							? []
 | 
						|
							: this.hash.substring('#🔐'.length).split(',')
 | 
						|
					),
 | 
						|
				},
 | 
						|
			})
 | 
						|
		);
 | 
						|
		tfrpc.rpc.setHash('#');
 | 
						|
	}
 | 
						|
 | 
						|
	render_close_chat_button() {
 | 
						|
		if (this.hash.startsWith('#🔐')) {
 | 
						|
			return html`
 | 
						|
				<button class="w3-button w3-theme-d1" @click=${this.close_private_chat}>
 | 
						|
					Close Chat
 | 
						|
				</button>
 | 
						|
			`;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	render() {
 | 
						|
		if (
 | 
						|
			!this.messages ||
 | 
						|
			this._messages_hash !== this.hash ||
 | 
						|
			this._messages_following !== JSON.stringify(this.following) ||
 | 
						|
			this._private_messages !==
 | 
						|
				JSON.stringify(this.private_messages) +
 | 
						|
					JSON.stringify(this.grouped_private_messages) ||
 | 
						|
			this._channels_latest !==
 | 
						|
				JSON.stringify(Object.keys(this.channels_latest))
 | 
						|
		) {
 | 
						|
			console.log(
 | 
						|
				`loading messages for ${this.whoami} (following ${this.following.length})`
 | 
						|
			);
 | 
						|
			this.load_messages();
 | 
						|
		}
 | 
						|
		let more;
 | 
						|
		if (!this.hash.startsWith('#%')) {
 | 
						|
			more = html`
 | 
						|
				<p>
 | 
						|
					${this.unread_allowed()
 | 
						|
						? html`
 | 
						|
								<button
 | 
						|
									class="w3-button w3-theme-d1"
 | 
						|
									@click=${this.mark_all_read}
 | 
						|
								>
 | 
						|
									Mark All Read
 | 
						|
								</button>
 | 
						|
							`
 | 
						|
						: undefined}
 | 
						|
					<button
 | 
						|
						?disabled=${this.loading}
 | 
						|
						class="w3-button w3-theme-d1"
 | 
						|
						@click=${this.load_more}
 | 
						|
					>
 | 
						|
						Load More
 | 
						|
					</button>
 | 
						|
					<button
 | 
						|
						class=${'w3-button w3-theme-d1' + (this.loading ? '' : ' w3-hide')}
 | 
						|
						@click=${this.cancel_load}
 | 
						|
					>
 | 
						|
						Cancel
 | 
						|
					</button>
 | 
						|
					<span
 | 
						|
						>Showing
 | 
						|
						${new Date(
 | 
						|
							this.time_loading
 | 
						|
								? Math.min(this.time_loading[0], this.time_range[0])
 | 
						|
								: this.time_range[0]
 | 
						|
						).toLocaleDateString()}
 | 
						|
						-
 | 
						|
						${new Date(
 | 
						|
							this.time_loading
 | 
						|
								? Math.max(this.time_loading[1], this.time_range[1])
 | 
						|
								: this.time_range[1]
 | 
						|
						).toLocaleDateString()}.</span
 | 
						|
					>
 | 
						|
				</p>
 | 
						|
			`;
 | 
						|
		}
 | 
						|
		return cache(html`
 | 
						|
			<style>
 | 
						|
				${generate_theme()}
 | 
						|
			</style>
 | 
						|
			${this.unread_allowed()
 | 
						|
				? html`<button
 | 
						|
						class="w3-button w3-theme-d1"
 | 
						|
						@click=${this.mark_all_read}
 | 
						|
					>
 | 
						|
						Mark All Read
 | 
						|
					</button>`
 | 
						|
				: undefined}
 | 
						|
			${this.render_close_chat_button()}
 | 
						|
			<tf-news
 | 
						|
				id="news"
 | 
						|
				whoami=${this.whoami}
 | 
						|
				.users=${this.users}
 | 
						|
				.messages=${this.messages}
 | 
						|
				.following=${this.following}
 | 
						|
				.drafts=${this.drafts}
 | 
						|
				.expanded=${this.expanded}
 | 
						|
				hash=${this.hash}
 | 
						|
				channel=${this.channel()}
 | 
						|
				channel_unread=${this.channels_unread?.[this.channel()]}
 | 
						|
				.recent_reactions=${this.recent_reactions}
 | 
						|
				@mark_all_read=${this.mark_all_read}
 | 
						|
			></tf-news>
 | 
						|
			${more}
 | 
						|
		`);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
customElements.define('tf-tab-news-feed', TfTabNewsFeedElement);
 |