| 
									
										
										
										
											2022-09-11 17:42:41 +00:00
										 |  |  |  | import {LitElement, html, css, guard, until} from './lit-all.min.js'; | 
					
						
							| 
									
										
										
										
											2022-09-06 23:26:43 +00:00
										 |  |  |  | import * as tfrpc from '/static/tfrpc.js'; | 
					
						
							| 
									
										
										
										
											2025-10-22 19:39:20 -04:00
										 |  |  |  | import {styles, generate_theme} from './tf-styles.js'; | 
					
						
							| 
									
										
										
										
											2022-09-06 23:26:43 +00:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | class TfElement extends LitElement { | 
					
						
							|  |  |  |  | 	static get properties() { | 
					
						
							|  |  |  |  | 		return { | 
					
						
							|  |  |  |  | 			whoami: {type: String}, | 
					
						
							|  |  |  |  | 			hash: {type: String}, | 
					
						
							| 
									
										
										
										
											2022-09-10 02:56:15 +00:00
										 |  |  |  | 			tab: {type: String}, | 
					
						
							|  |  |  |  | 			broadcasts: {type: Array}, | 
					
						
							|  |  |  |  | 			connections: {type: Array}, | 
					
						
							| 
									
										
										
										
											2022-09-14 23:33:57 +00:00
										 |  |  |  | 			loading: {type: Boolean}, | 
					
						
							| 
									
										
										
										
											2025-05-22 10:44:41 -04:00
										 |  |  |  | 			loading_about: {type: Number}, | 
					
						
							| 
									
										
										
										
											2022-09-14 23:33:57 +00:00
										 |  |  |  | 			loaded: {type: Boolean}, | 
					
						
							|  |  |  |  | 			following: {type: Array}, | 
					
						
							|  |  |  |  | 			users: {type: Object}, | 
					
						
							| 
									
										
										
										
											2022-11-10 00:03:39 +00:00
										 |  |  |  | 			ids: {type: Array}, | 
					
						
							| 
									
										
										
										
											2024-11-30 15:05:14 -05:00
										 |  |  |  | 			channels: {type: Array}, | 
					
						
							|  |  |  |  | 			channels_unread: {type: Object}, | 
					
						
							|  |  |  |  | 			channels_latest: {type: Object}, | 
					
						
							| 
									
										
										
										
											2025-01-05 12:52:52 -05:00
										 |  |  |  | 			guest: {type: Boolean}, | 
					
						
							|  |  |  |  | 			url: {type: String}, | 
					
						
							| 
									
										
										
										
											2025-08-20 19:08:07 -04:00
										 |  |  |  | 			private_closed: {type: Object}, | 
					
						
							| 
									
										
										
										
											2025-02-05 18:41:37 -05:00
										 |  |  |  | 			private_messages: {type: Array}, | 
					
						
							| 
									
										
										
										
											2025-08-13 19:16:34 -04:00
										 |  |  |  | 			grouped_private_messages: {type: Object}, | 
					
						
							| 
									
										
										
										
											2025-04-29 20:48:47 -04:00
										 |  |  |  | 			recent_reactions: {type: Array}, | 
					
						
							| 
									
										
										
										
											2025-06-28 13:47:58 -04:00
										 |  |  |  | 			is_administrator: {type: Boolean}, | 
					
						
							|  |  |  |  | 			stay_connected: {type: Boolean}, | 
					
						
							| 
									
										
										
										
											2025-07-30 19:49:08 -04:00
										 |  |  |  | 			progress: {type: Number}, | 
					
						
							| 
									
										
										
										
											2022-09-06 23:26:43 +00:00
										 |  |  |  | 		}; | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-10 02:56:15 +00:00
										 |  |  |  | 	static styles = styles; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-06 23:26:43 +00:00
										 |  |  |  | 	constructor() { | 
					
						
							|  |  |  |  | 		super(); | 
					
						
							|  |  |  |  | 		let self = this; | 
					
						
							|  |  |  |  | 		this.hash = '#'; | 
					
						
							| 
									
										
										
										
											2022-09-10 02:56:15 +00:00
										 |  |  |  | 		this.tab = 'news'; | 
					
						
							|  |  |  |  | 		this.broadcasts = []; | 
					
						
							|  |  |  |  | 		this.connections = []; | 
					
						
							| 
									
										
										
										
											2022-09-14 23:33:57 +00:00
										 |  |  |  | 		this.following = []; | 
					
						
							|  |  |  |  | 		this.users = {}; | 
					
						
							|  |  |  |  | 		this.loaded = false; | 
					
						
							| 
									
										
										
										
											2025-05-22 10:44:41 -04:00
										 |  |  |  | 		this.loading_about = 0; | 
					
						
							| 
									
										
										
										
											2024-11-30 15:05:14 -05:00
										 |  |  |  | 		this.channels = []; | 
					
						
							|  |  |  |  | 		this.channels_unread = {}; | 
					
						
							|  |  |  |  | 		this.channels_latest = {}; | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 		this.loading_latest = 0; | 
					
						
							|  |  |  |  | 		this.loading_latest_scheduled = 0; | 
					
						
							| 
									
										
										
										
											2025-04-29 20:48:47 -04:00
										 |  |  |  | 		this.recent_reactions = []; | 
					
						
							| 
									
										
										
										
											2025-08-20 19:08:07 -04:00
										 |  |  |  | 		this.private_closed = {}; | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  |  | 		tfrpc.rpc.getBroadcasts().then((b) => { | 
					
						
							|  |  |  |  | 			self.broadcasts = b || []; | 
					
						
							|  |  |  |  | 		}); | 
					
						
							|  |  |  |  | 		tfrpc.rpc.getConnections().then((c) => { | 
					
						
							|  |  |  |  | 			self.connections = c || []; | 
					
						
							|  |  |  |  | 		}); | 
					
						
							|  |  |  |  | 		tfrpc.rpc.getHash().then((hash) => self.set_hash(hash)); | 
					
						
							| 
									
										
										
										
											2022-09-06 23:26:43 +00:00
										 |  |  |  | 		tfrpc.register(function hashChanged(hash) { | 
					
						
							| 
									
										
										
										
											2022-09-15 00:16:37 +00:00
										 |  |  |  | 			self.set_hash(hash); | 
					
						
							| 
									
										
										
										
											2025-07-31 12:48:45 -04:00
										 |  |  |  | 			self.reset_progress(); | 
					
						
							| 
									
										
										
										
											2022-09-06 23:26:43 +00:00
										 |  |  |  | 		}); | 
					
						
							|  |  |  |  | 		tfrpc.register(async function notifyNewMessage(id) { | 
					
						
							|  |  |  |  | 			await self.fetch_new_message(id); | 
					
						
							|  |  |  |  | 		}); | 
					
						
							| 
									
										
										
										
											2025-09-24 11:34:34 -04:00
										 |  |  |  | 		tfrpc.register(async function notifyNewBlob(id) { | 
					
						
							|  |  |  |  | 			window.dispatchEvent( | 
					
						
							|  |  |  |  | 				new CustomEvent('blob-stored', { | 
					
						
							|  |  |  |  | 					bubbles: true, | 
					
						
							|  |  |  |  | 					composed: true, | 
					
						
							|  |  |  |  | 					detail: { | 
					
						
							|  |  |  |  | 						id: id, | 
					
						
							|  |  |  |  | 					}, | 
					
						
							|  |  |  |  | 				}) | 
					
						
							|  |  |  |  | 			); | 
					
						
							|  |  |  |  | 		}); | 
					
						
							| 
									
										
										
										
											2022-09-10 02:56:15 +00:00
										 |  |  |  | 		tfrpc.register(function set(name, value) { | 
					
						
							|  |  |  |  | 			if (name === 'broadcasts') { | 
					
						
							|  |  |  |  | 				self.broadcasts = value; | 
					
						
							|  |  |  |  | 			} else if (name === 'connections') { | 
					
						
							|  |  |  |  | 				self.connections = value; | 
					
						
							| 
									
										
										
										
											2024-04-13 19:52:40 -04:00
										 |  |  |  | 			} else if (name === 'identity') { | 
					
						
							|  |  |  |  | 				self.whoami = value; | 
					
						
							| 
									
										
										
										
											2022-09-10 02:56:15 +00:00
										 |  |  |  | 			} | 
					
						
							|  |  |  |  | 		}); | 
					
						
							| 
									
										
										
										
											2022-11-17 00:30:58 +00:00
										 |  |  |  | 		this.initial_load(); | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	async initial_load() { | 
					
						
							| 
									
										
										
										
											2024-04-13 19:52:40 -04:00
										 |  |  |  | 		let whoami = await tfrpc.rpc.getActiveIdentity(); | 
					
						
							| 
									
										
										
										
											2022-11-17 00:30:58 +00:00
										 |  |  |  | 		let ids = (await tfrpc.rpc.getIdentities()) || []; | 
					
						
							| 
									
										
										
										
											2025-06-28 13:47:58 -04:00
										 |  |  |  | 		this.is_administrator = await tfrpc.rpc.isAdministrator(); | 
					
						
							|  |  |  |  | 		this.stay_connected = | 
					
						
							|  |  |  |  | 			this.is_administrator && | 
					
						
							|  |  |  |  | 			(await tfrpc.rpc.globalSettingsGet('stay_connected')); | 
					
						
							| 
									
										
										
										
											2025-01-05 12:52:52 -05:00
										 |  |  |  | 		this.url = await tfrpc.rpc.url(); | 
					
						
							| 
									
										
										
										
											2022-11-17 00:30:58 +00:00
										 |  |  |  | 		this.whoami = whoami ?? (ids.length ? ids[0] : undefined); | 
					
						
							| 
									
										
										
										
											2025-01-05 12:52:52 -05:00
										 |  |  |  | 		this.guest = !this.whoami?.length; | 
					
						
							| 
									
										
										
										
											2022-11-17 00:30:58 +00:00
										 |  |  |  | 		this.ids = ids; | 
					
						
							| 
									
										
										
										
											2025-08-20 19:08:07 -04:00
										 |  |  |  | 		let private_closed = | 
					
						
							|  |  |  |  | 			(await tfrpc.rpc.databaseGet('private_closed')) ?? '{}'; | 
					
						
							|  |  |  |  | 		this.private_closed = JSON.parse(private_closed); | 
					
						
							| 
									
										
										
										
											2024-11-30 17:49:27 -05:00
										 |  |  |  | 		await this.load_channels(); | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-11-30 15:05:14 -05:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-20 19:08:07 -04:00
										 |  |  |  | 	async close_private_chat(event) { | 
					
						
							|  |  |  |  | 		let update = {}; | 
					
						
							|  |  |  |  | 		update[event.detail.key] = true; | 
					
						
							|  |  |  |  | 		this.private_closed = Object.assign(update, this.private_closed); | 
					
						
							|  |  |  |  | 		await tfrpc.rpc.databaseSet( | 
					
						
							|  |  |  |  | 			'private_closed', | 
					
						
							|  |  |  |  | 			JSON.stringify(this.private_closed) | 
					
						
							|  |  |  |  | 		); | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-30 17:49:27 -05:00
										 |  |  |  | 	async load_channels() { | 
					
						
							| 
									
										
										
										
											2024-12-05 20:47:02 -05:00
										 |  |  |  | 		let channels = await tfrpc.rpc.query( | 
					
						
							|  |  |  |  | 			`
 | 
					
						
							| 
									
										
										
										
											2024-11-30 15:05:14 -05:00
										 |  |  |  | 			SELECT | 
					
						
							|  |  |  |  | 				content ->> 'channel' AS channel, | 
					
						
							|  |  |  |  | 				content ->> 'subscribed' AS subscribed | 
					
						
							|  |  |  |  | 			FROM | 
					
						
							|  |  |  |  | 				messages | 
					
						
							|  |  |  |  | 			WHERE | 
					
						
							|  |  |  |  | 				author = ? AND | 
					
						
							|  |  |  |  | 				content ->> 'type' = 'channel' | 
					
						
							|  |  |  |  | 			ORDER BY sequence | 
					
						
							| 
									
										
										
										
											2024-12-05 20:47:02 -05:00
										 |  |  |  | 		`,
 | 
					
						
							|  |  |  |  | 			[this.whoami] | 
					
						
							|  |  |  |  | 		); | 
					
						
							| 
									
										
										
										
											2024-11-30 15:05:14 -05:00
										 |  |  |  | 		let channel_map = {}; | 
					
						
							|  |  |  |  | 		for (let row of channels) { | 
					
						
							|  |  |  |  | 			if (row.subscribed) { | 
					
						
							|  |  |  |  | 				channel_map[row.channel] = true; | 
					
						
							|  |  |  |  | 			} else { | 
					
						
							|  |  |  |  | 				delete channel_map[row.channel]; | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-12-04 20:50:46 -05:00
										 |  |  |  | 		this.channels = Object.keys(channel_map).sort(); | 
					
						
							| 
									
										
										
										
											2022-09-06 23:26:43 +00:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-11 12:53:04 -05:00
										 |  |  |  | 	connectedCallback() { | 
					
						
							|  |  |  |  | 		super.connectedCallback(); | 
					
						
							|  |  |  |  | 		this._keydown = this.keydown.bind(this); | 
					
						
							|  |  |  |  | 		window.addEventListener('keydown', this._keydown); | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	disconnectedCallback() { | 
					
						
							|  |  |  |  | 		super.disconnectedCallback(); | 
					
						
							|  |  |  |  | 		window.removeEventListener('keydown', this._keydown); | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	keydown(event) { | 
					
						
							|  |  |  |  | 		if (event.altKey && event.key == 'ArrowUp') { | 
					
						
							| 
									
										
										
										
											2024-12-23 13:18:30 -05:00
										 |  |  |  | 			this.next_channel(-1); | 
					
						
							| 
									
										
										
										
											2024-12-11 12:53:04 -05:00
										 |  |  |  | 			event.preventDefault(); | 
					
						
							|  |  |  |  | 		} else if (event.altKey && event.key == 'ArrowDown') { | 
					
						
							| 
									
										
										
										
											2024-12-23 13:18:30 -05:00
										 |  |  |  | 			this.next_channel(1); | 
					
						
							| 
									
										
										
										
											2024-12-11 12:53:04 -05:00
										 |  |  |  | 			event.preventDefault(); | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-20 19:08:07 -04:00
										 |  |  |  | 	visible_private() { | 
					
						
							|  |  |  |  | 		if (!this.grouped_private_messages || !this.private_closed) { | 
					
						
							|  |  |  |  | 			return []; | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		let self = this; | 
					
						
							|  |  |  |  | 		return Object.fromEntries( | 
					
						
							|  |  |  |  | 			Object.entries(this.grouped_private_messages).filter(([key, value]) => { | 
					
						
							|  |  |  |  | 				let channel = '🔐' + [...new Set(JSON.parse(key))].sort().join(','); | 
					
						
							|  |  |  |  | 				let grouped_latest = Math.max(...value.map((x) => x.rowid)); | 
					
						
							|  |  |  |  | 				return ( | 
					
						
							|  |  |  |  | 					!self.private_closed[key] || | 
					
						
							|  |  |  |  | 					self.channels_unread[channel] === undefined || | 
					
						
							|  |  |  |  | 					grouped_latest > self.channels_unread[channel] | 
					
						
							|  |  |  |  | 				); | 
					
						
							|  |  |  |  | 			}) | 
					
						
							|  |  |  |  | 		); | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-11 12:53:04 -05:00
										 |  |  |  | 	next_channel(delta) { | 
					
						
							| 
									
										
										
										
											2025-06-04 18:01:39 -04:00
										 |  |  |  | 		let channel_names = [ | 
					
						
							|  |  |  |  | 			'', | 
					
						
							|  |  |  |  | 			'@', | 
					
						
							|  |  |  |  | 			'👍', | 
					
						
							| 
									
										
										
										
											2025-08-20 19:08:07 -04:00
										 |  |  |  | 			...Object.keys(this.visible_private()) | 
					
						
							| 
									
										
										
										
											2025-08-13 19:24:25 -04:00
										 |  |  |  | 				.sort() | 
					
						
							|  |  |  |  | 				.map((x) => '🔐' + JSON.parse(x).join(',')), | 
					
						
							| 
									
										
										
										
											2025-06-04 18:01:39 -04:00
										 |  |  |  | 			...this.channels.map((x) => '#' + x), | 
					
						
							|  |  |  |  | 		]; | 
					
						
							| 
									
										
										
										
											2024-12-11 12:53:04 -05:00
										 |  |  |  | 		let index = channel_names.indexOf(this.hash.substring(1)); | 
					
						
							| 
									
										
										
										
											2024-12-23 13:18:30 -05:00
										 |  |  |  | 		index = index != -1 ? index + delta : 0; | 
					
						
							|  |  |  |  | 		tfrpc.rpc.setHash( | 
					
						
							|  |  |  |  | 			'#' + | 
					
						
							|  |  |  |  | 				encodeURIComponent( | 
					
						
							|  |  |  |  | 					channel_names[(index + channel_names.length) % channel_names.length] | 
					
						
							|  |  |  |  | 				) | 
					
						
							|  |  |  |  | 		); | 
					
						
							| 
									
										
										
										
											2024-12-11 12:53:04 -05:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-15 00:16:37 +00:00
										 |  |  |  | 	set_hash(hash) { | 
					
						
							| 
									
										
										
										
											2024-11-09 18:04:58 -05:00
										 |  |  |  | 		this.hash = decodeURIComponent(hash || '#'); | 
					
						
							| 
									
										
										
										
											2022-09-15 00:16:37 +00:00
										 |  |  |  | 		if (this.hash.startsWith('#q=')) { | 
					
						
							|  |  |  |  | 			this.tab = 'search'; | 
					
						
							| 
									
										
										
										
											2022-09-25 12:33:54 +00:00
										 |  |  |  | 		} else if (this.hash === '#connections') { | 
					
						
							|  |  |  |  | 			this.tab = 'connections'; | 
					
						
							| 
									
										
										
										
											2022-09-15 00:16:37 +00:00
										 |  |  |  | 		} else { | 
					
						
							|  |  |  |  | 			this.tab = 'news'; | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 	async fetch_about(following, users) { | 
					
						
							| 
									
										
										
										
											2025-05-22 10:44:41 -04:00
										 |  |  |  | 		this.loading_about++; | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 		let ids = Object.keys(following).sort(); | 
					
						
							| 
									
										
										
										
											2025-05-17 20:10:15 -04:00
										 |  |  |  | 		const k_cache_version = 3; | 
					
						
							| 
									
										
										
										
											2025-05-09 13:25:05 -04:00
										 |  |  |  | 		let cache = await tfrpc.rpc.databaseGet('about'); | 
					
						
							|  |  |  |  | 		let original_cache = cache; | 
					
						
							|  |  |  |  | 		cache = cache ? JSON.parse(cache) : {}; | 
					
						
							|  |  |  |  | 		if (cache.version !== k_cache_version) { | 
					
						
							|  |  |  |  | 			cache = { | 
					
						
							|  |  |  |  | 				version: k_cache_version, | 
					
						
							|  |  |  |  | 				about: {}, | 
					
						
							|  |  |  |  | 			}; | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-17 20:10:15 -04:00
										 |  |  |  | 		let ids_out_of_date = ids.filter( | 
					
						
							|  |  |  |  | 			(x) => | 
					
						
							|  |  |  |  | 				(users[x]?.seq && !cache.about[x]?.seq) || | 
					
						
							|  |  |  |  | 				(users[x]?.seq && users[x]?.seq > cache.about[x].seq) | 
					
						
							|  |  |  |  | 		); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-09 13:25:05 -04:00
										 |  |  |  | 		for (let id of Object.keys(cache.about)) { | 
					
						
							|  |  |  |  | 			if (ids.indexOf(id) == -1) { | 
					
						
							|  |  |  |  | 				delete cache.about[id]; | 
					
						
							|  |  |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2025-05-14 20:56:13 -04:00
										 |  |  |  | 				users[id] = Object.assign(cache.about[id], users[id] || {}); | 
					
						
							| 
									
										
										
										
											2025-05-09 13:25:05 -04:00
										 |  |  |  | 			} | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-11 21:54:53 -04:00
										 |  |  |  | 		console.log( | 
					
						
							|  |  |  |  | 			'loading about for', | 
					
						
							|  |  |  |  | 			ids.length, | 
					
						
							|  |  |  |  | 			'accounts', | 
					
						
							|  |  |  |  | 			ids_out_of_date.length, | 
					
						
							|  |  |  |  | 			'out of date' | 
					
						
							|  |  |  |  | 		); | 
					
						
							| 
									
										
										
										
											2025-05-09 13:25:05 -04:00
										 |  |  |  | 		if (ids_out_of_date.length) { | 
					
						
							|  |  |  |  | 			try { | 
					
						
							|  |  |  |  | 				let rows = await tfrpc.rpc.query( | 
					
						
							|  |  |  |  | 					`
 | 
					
						
							|  |  |  |  | 						SELECT all_abouts.author, json(json_group_object(all_abouts.key, all_abouts.value)) AS about | 
					
						
							|  |  |  |  | 						FROM ( | 
					
						
							|  |  |  |  | 							SELECT | 
					
						
							|  |  |  |  | 								messages.author, | 
					
						
							|  |  |  |  | 								fields.key, | 
					
						
							|  |  |  |  | 								RANK() OVER (PARTITION BY messages.author, fields.key ORDER BY messages.sequence DESC) AS rank, | 
					
						
							|  |  |  |  | 								fields.value | 
					
						
							|  |  |  |  | 							FROM messages JOIN json_each(messages.content) AS fields | 
					
						
							|  |  |  |  | 							WHERE | 
					
						
							|  |  |  |  | 								messages.content ->> '$.type' = 'about' AND | 
					
						
							|  |  |  |  | 								messages.content ->> '$.about' = messages.author AND | 
					
						
							|  |  |  |  | 								NOT fields.key IN ('about', 'type')) all_abouts | 
					
						
							|  |  |  |  | 						JOIN json_each(?) AS following ON all_abouts.author = following.value | 
					
						
							|  |  |  |  | 						WHERE rank = 1 | 
					
						
							|  |  |  |  | 						GROUP BY all_abouts.author | 
					
						
							|  |  |  |  | 					`,
 | 
					
						
							|  |  |  |  | 					[JSON.stringify(ids_out_of_date)] | 
					
						
							| 
									
										
										
										
											2025-04-09 22:15:51 -04:00
										 |  |  |  | 				); | 
					
						
							| 
									
										
										
										
											2025-05-09 13:25:05 -04:00
										 |  |  |  | 				users = users || {}; | 
					
						
							|  |  |  |  | 				for (let row of rows) { | 
					
						
							|  |  |  |  | 					users[row.author] = Object.assign( | 
					
						
							|  |  |  |  | 						users[row.author] || {}, | 
					
						
							|  |  |  |  | 						JSON.parse(row.about) | 
					
						
							|  |  |  |  | 					); | 
					
						
							|  |  |  |  | 					cache.about[row.author] = Object.assign( | 
					
						
							|  |  |  |  | 						{seq: users[row.author].seq}, | 
					
						
							|  |  |  |  | 						JSON.parse(row.about) | 
					
						
							|  |  |  |  | 					); | 
					
						
							|  |  |  |  | 				} | 
					
						
							|  |  |  |  | 			} catch (e) { | 
					
						
							|  |  |  |  | 				console.log(e); | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 		for (let id of ids_out_of_date) { | 
					
						
							|  |  |  |  | 			if (!cache.about[id]?.seq) { | 
					
						
							| 
									
										
										
										
											2025-05-21 17:44:00 -04:00
										 |  |  |  | 				cache.about[id] = Object.assign(cache.about[id] ?? {}, { | 
					
						
							|  |  |  |  | 					seq: users[id]?.seq ?? 0, | 
					
						
							|  |  |  |  | 				}); | 
					
						
							| 
									
										
										
										
											2022-09-06 23:26:43 +00:00
										 |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2025-05-11 21:54:53 -04:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2025-05-09 13:25:05 -04:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-22 10:44:41 -04:00
										 |  |  |  | 		this.loading_about--; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-09 13:25:05 -04:00
										 |  |  |  | 		let new_cache = JSON.stringify(cache); | 
					
						
							|  |  |  |  | 		if (new_cache != original_cache) { | 
					
						
							|  |  |  |  | 			let start_time = new Date(); | 
					
						
							| 
									
										
										
										
											2025-05-11 21:54:53 -04:00
										 |  |  |  | 			tfrpc.rpc.databaseSet('about', new_cache).then(function () { | 
					
						
							| 
									
										
										
										
											2025-05-09 13:25:05 -04:00
										 |  |  |  | 				console.log('saving about took', (new Date() - start_time) / 1000); | 
					
						
							|  |  |  |  | 			}); | 
					
						
							| 
									
										
										
										
											2022-09-06 23:26:43 +00:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2025-05-09 13:25:05 -04:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-11 17:42:41 +00:00
										 |  |  |  | 		return Object.assign({}, users); | 
					
						
							| 
									
										
										
										
											2022-09-06 23:26:43 +00:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	async fetch_new_message(id) { | 
					
						
							|  |  |  |  | 		let messages = await tfrpc.rpc.query( | 
					
						
							|  |  |  |  | 			`
 | 
					
						
							| 
									
										
										
										
											2024-02-28 20:41:27 -05:00
										 |  |  |  | 				SELECT messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature | 
					
						
							| 
									
										
										
										
											2022-09-06 23:26:43 +00:00
										 |  |  |  | 				FROM messages | 
					
						
							|  |  |  |  | 				JOIN json_each(?) AS following ON messages.author = following.value | 
					
						
							|  |  |  |  | 				WHERE messages.id = ? | 
					
						
							|  |  |  |  | 			`,
 | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  |  | 			[JSON.stringify(this.following), id] | 
					
						
							|  |  |  |  | 		); | 
					
						
							| 
									
										
										
										
											2024-11-30 17:49:27 -05:00
										 |  |  |  | 		for (let message of messages) { | 
					
						
							| 
									
										
										
										
											2024-12-29 13:32:37 -05:00
										 |  |  |  | 			if ( | 
					
						
							|  |  |  |  | 				message.author == this.whoami && | 
					
						
							|  |  |  |  | 				JSON.parse(message.content)?.type == 'channel' | 
					
						
							|  |  |  |  | 			) { | 
					
						
							|  |  |  |  | 				this.load_channels(); | 
					
						
							| 
									
										
										
										
											2024-11-30 17:49:27 -05:00
										 |  |  |  | 			} | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 		this.schedule_load_latest(); | 
					
						
							| 
									
										
										
										
											2022-09-06 23:26:43 +00:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-17 00:30:58 +00:00
										 |  |  |  | 	async _handle_whoami_changed(event) { | 
					
						
							|  |  |  |  | 		let old_id = this.whoami; | 
					
						
							|  |  |  |  | 		let new_id = event.srcElement.selected; | 
					
						
							|  |  |  |  | 		console.log('received', new_id); | 
					
						
							|  |  |  |  | 		if (this.whoami !== new_id) { | 
					
						
							|  |  |  |  | 			console.log(event); | 
					
						
							|  |  |  |  | 			this.whoami = new_id; | 
					
						
							|  |  |  |  | 			console.log(`whoami ${old_id} => ${new_id}`); | 
					
						
							|  |  |  |  | 			await tfrpc.rpc.localStorageSet('whoami', new_id); | 
					
						
							| 
									
										
										
										
											2022-09-06 23:26:43 +00:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-18 20:03:53 -05:00
										 |  |  |  | 	async get_latest_private(following) { | 
					
						
							| 
									
										
										
										
											2025-01-03 15:53:19 -05:00
										 |  |  |  | 		const k_version = 1; | 
					
						
							|  |  |  |  | 		// { "version": 1, "range": [1234, 5678], messages: [ "%1.sha256", "%2.sha256", ... ], latest: rowid }
 | 
					
						
							| 
									
										
										
										
											2025-01-04 12:41:04 -05:00
										 |  |  |  | 		let cache = JSON.parse( | 
					
						
							|  |  |  |  | 			(await tfrpc.rpc.databaseGet(`private:${this.whoami}`)) ?? '{}' | 
					
						
							|  |  |  |  | 		); | 
					
						
							| 
									
										
										
										
											2025-01-03 15:53:19 -05:00
										 |  |  |  | 		if (cache.version !== k_version) { | 
					
						
							|  |  |  |  | 			cache = { | 
					
						
							|  |  |  |  | 				version: k_version, | 
					
						
							|  |  |  |  | 				messages: [], | 
					
						
							|  |  |  |  | 				range: [], | 
					
						
							|  |  |  |  | 			}; | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-12-23 11:08:27 -05:00
										 |  |  |  | 		let latest = ( | 
					
						
							|  |  |  |  | 			await tfrpc.rpc.query('SELECT MAX(rowid) AS latest FROM messages') | 
					
						
							|  |  |  |  | 		)[0].latest; | 
					
						
							| 
									
										
										
										
											2025-01-03 15:53:19 -05:00
										 |  |  |  | 		let ranges = []; | 
					
						
							|  |  |  |  | 		const k_chunk_size = 512; | 
					
						
							|  |  |  |  | 		if (cache.range.length) { | 
					
						
							|  |  |  |  | 			for (let i = cache.range[1]; i < latest; i += k_chunk_size) { | 
					
						
							| 
									
										
										
										
											2025-06-10 12:44:56 -04:00
										 |  |  |  | 				ranges.push([i, Math.min(i + k_chunk_size, latest), true]); | 
					
						
							| 
									
										
										
										
											2025-01-03 15:53:19 -05:00
										 |  |  |  | 			} | 
					
						
							|  |  |  |  | 			for (let i = cache.range[0]; i >= 0; i -= k_chunk_size) { | 
					
						
							| 
									
										
										
										
											2025-06-10 12:44:56 -04:00
										 |  |  |  | 				ranges.push([Math.max(i - k_chunk_size, 0), i, false]); | 
					
						
							| 
									
										
										
										
											2025-01-03 15:53:19 -05:00
										 |  |  |  | 			} | 
					
						
							|  |  |  |  | 		} else { | 
					
						
							|  |  |  |  | 			for (let i = 0; i < latest; i += k_chunk_size) { | 
					
						
							|  |  |  |  | 				ranges.push([i, Math.min(i + k_chunk_size, latest), true]); | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		for (let range of ranges) { | 
					
						
							| 
									
										
										
										
											2024-12-23 11:08:27 -05:00
										 |  |  |  | 			let messages = await tfrpc.rpc.query( | 
					
						
							|  |  |  |  | 				`
 | 
					
						
							| 
									
										
										
										
											2025-01-03 15:53:19 -05:00
										 |  |  |  | 					SELECT messages.rowid, messages.id, json(content) AS content | 
					
						
							| 
									
										
										
										
											2024-12-18 20:03:53 -05:00
										 |  |  |  | 						FROM messages | 
					
						
							|  |  |  |  | 						WHERE | 
					
						
							| 
									
										
										
										
											2025-01-03 15:53:19 -05:00
										 |  |  |  | 							messages.rowid > ?1 AND | 
					
						
							|  |  |  |  | 							messages.rowid <= ?2 AND | 
					
						
							| 
									
										
										
										
											2024-12-18 20:03:53 -05:00
										 |  |  |  | 							json(messages.content) LIKE '"%' | 
					
						
							| 
									
										
										
										
											2025-06-07 15:10:37 -04:00
										 |  |  |  | 						ORDER BY messages.rowid DESC | 
					
						
							| 
									
										
										
										
											2024-12-18 20:03:53 -05:00
										 |  |  |  | 					`,
 | 
					
						
							| 
									
										
										
										
											2025-01-04 12:41:04 -05:00
										 |  |  |  | 				[range[0], range[1]] | 
					
						
							| 
									
										
										
										
											2024-12-23 11:08:27 -05:00
										 |  |  |  | 			); | 
					
						
							|  |  |  |  | 			messages = (await this.decrypt(messages)).filter((x) => x.decrypted); | 
					
						
							| 
									
										
										
										
											2024-12-18 20:03:53 -05:00
										 |  |  |  | 			if (messages.length) { | 
					
						
							| 
									
										
										
										
											2025-01-04 12:41:04 -05:00
										 |  |  |  | 				cache.latest = Math.max( | 
					
						
							|  |  |  |  | 					cache.latest ?? 0, | 
					
						
							|  |  |  |  | 					...messages.map((x) => x.rowid) | 
					
						
							|  |  |  |  | 				); | 
					
						
							| 
									
										
										
										
											2025-01-03 15:53:19 -05:00
										 |  |  |  | 				if (range[2]) { | 
					
						
							| 
									
										
										
										
											2025-01-04 12:41:04 -05:00
										 |  |  |  | 					cache.messages = [...cache.messages, ...messages.map((x) => x.id)]; | 
					
						
							| 
									
										
										
										
											2025-01-03 15:53:19 -05:00
										 |  |  |  | 				} else { | 
					
						
							| 
									
										
										
										
											2025-01-04 12:41:04 -05:00
										 |  |  |  | 					cache.messages = [...messages.map((x) => x.id), ...cache.messages]; | 
					
						
							| 
									
										
										
										
											2025-01-03 15:53:19 -05:00
										 |  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2024-12-18 20:03:53 -05:00
										 |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2025-01-03 15:53:19 -05:00
										 |  |  |  | 			cache.range[0] = Math.min(cache.range[0] ?? range[0], range[0]); | 
					
						
							|  |  |  |  | 			cache.range[1] = Math.max(cache.range[1] ?? range[1], range[1]); | 
					
						
							| 
									
										
										
										
											2025-01-04 12:41:04 -05:00
										 |  |  |  | 			await tfrpc.rpc.databaseSet( | 
					
						
							|  |  |  |  | 				`private:${this.whoami}`, | 
					
						
							|  |  |  |  | 				JSON.stringify(cache) | 
					
						
							|  |  |  |  | 			); | 
					
						
							| 
									
										
										
										
											2024-12-23 11:08:27 -05:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2025-02-05 18:41:37 -05:00
										 |  |  |  | 		return [cache.latest, cache.messages]; | 
					
						
							| 
									
										
										
										
											2024-12-18 20:03:53 -05:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-18 12:36:59 -04:00
										 |  |  |  | 	async query_timed(sql, args) { | 
					
						
							|  |  |  |  | 		let start = new Date(); | 
					
						
							|  |  |  |  | 		let result = await tfrpc.rpc.query(sql, args); | 
					
						
							|  |  |  |  | 		let end = new Date(); | 
					
						
							| 
									
										
										
										
											2025-07-02 18:21:40 -04:00
										 |  |  |  | 		console.log((end - start) / 1000, sql.replaceAll(/\s+/g, ' ').trim()); | 
					
						
							| 
									
										
										
										
											2025-06-18 12:36:59 -04:00
										 |  |  |  | 		return result; | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-13 19:16:34 -04:00
										 |  |  |  | 	async group_private_messages(messages) { | 
					
						
							|  |  |  |  | 		let groups = {}; | 
					
						
							|  |  |  |  | 		let result = await this.decrypt( | 
					
						
							|  |  |  |  | 			await tfrpc.rpc.query( | 
					
						
							|  |  |  |  | 				`
 | 
					
						
							| 
									
										
										
										
											2025-08-14 12:40:58 -04:00
										 |  |  |  | 				SELECT messages.rowid, messages.id, author, timestamp, json(content) AS content | 
					
						
							| 
									
										
										
										
											2025-08-13 19:16:34 -04:00
										 |  |  |  | 				FROM messages | 
					
						
							|  |  |  |  | 				JOIN json_each(?) AS ids | 
					
						
							|  |  |  |  | 				WHERE messages.id = ids.value | 
					
						
							|  |  |  |  | 				ORDER BY timestamp DESC | 
					
						
							|  |  |  |  | 			`,
 | 
					
						
							|  |  |  |  | 				[JSON.stringify(messages)] | 
					
						
							|  |  |  |  | 			) | 
					
						
							|  |  |  |  | 		); | 
					
						
							|  |  |  |  | 		for (let message of result) { | 
					
						
							|  |  |  |  | 			let key = JSON.stringify( | 
					
						
							| 
									
										
										
										
											2025-08-20 19:08:07 -04:00
										 |  |  |  | 				[ | 
					
						
							|  |  |  |  | 					...new Set( | 
					
						
							|  |  |  |  | 						message?.decrypted?.recps?.filter((x) => x != this.whoami) | 
					
						
							|  |  |  |  | 					), | 
					
						
							|  |  |  |  | 				].sort() ?? [] | 
					
						
							| 
									
										
										
										
											2025-08-13 19:16:34 -04:00
										 |  |  |  | 			); | 
					
						
							|  |  |  |  | 			if (!groups[key]) { | 
					
						
							|  |  |  |  | 				groups[key] = []; | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 			groups[key].push(message); | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		return groups; | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-22 13:16:56 -05:00
										 |  |  |  | 	async load_channels_latest(following) { | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 		let start_time = new Date(); | 
					
						
							|  |  |  |  | 		let latest_private = this.get_latest_private(following); | 
					
						
							| 
									
										
										
										
											2025-06-18 12:36:59 -04:00
										 |  |  |  | 		const k_args = [ | 
					
						
							|  |  |  |  | 			JSON.stringify(this.channels), | 
					
						
							|  |  |  |  | 			JSON.stringify(following), | 
					
						
							|  |  |  |  | 			'"' + this.whoami.replace('"', '""') + '"', | 
					
						
							|  |  |  |  | 			this.whoami, | 
					
						
							|  |  |  |  | 		]; | 
					
						
							| 
									
										
										
										
											2025-06-18 12:37:41 -04:00
										 |  |  |  | 		let channels = ( | 
					
						
							|  |  |  |  | 			await Promise.all([ | 
					
						
							|  |  |  |  | 				this.query_timed( | 
					
						
							|  |  |  |  | 					`
 | 
					
						
							| 
									
										
										
										
											2025-06-18 12:36:59 -04:00
										 |  |  |  | 					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 AND | 
					
						
							|  |  |  |  | 						messages.author != ?4 | 
					
						
							|  |  |  |  | 					GROUP by channel | 
					
						
							|  |  |  |  | 				`,
 | 
					
						
							| 
									
										
										
										
											2025-06-18 12:37:41 -04:00
										 |  |  |  | 					k_args | 
					
						
							|  |  |  |  | 				), | 
					
						
							|  |  |  |  | 				this.query_timed( | 
					
						
							|  |  |  |  | 					`
 | 
					
						
							| 
									
										
										
										
											2025-06-18 12:36:59 -04:00
										 |  |  |  | 					SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages | 
					
						
							|  |  |  |  | 					JOIN messages_refs ON messages.id = messages_refs.message | 
					
						
							|  |  |  |  | 					JOIN json_each(?1) AS channels ON messages_refs.ref = '#' || channels.value | 
					
						
							|  |  |  |  | 					JOIN json_each(?2) AS following ON messages.author = following.value | 
					
						
							|  |  |  |  | 					WHERE | 
					
						
							|  |  |  |  | 						messages.content ->> 'type' = 'post' AND | 
					
						
							|  |  |  |  | 						messages.content ->> 'root' IS NULL AND | 
					
						
							|  |  |  |  | 						messages.author != ?4 | 
					
						
							|  |  |  |  | 					GROUP by channel | 
					
						
							|  |  |  |  | 				`,
 | 
					
						
							| 
									
										
										
										
											2025-06-18 12:37:41 -04:00
										 |  |  |  | 					k_args | 
					
						
							|  |  |  |  | 				), | 
					
						
							|  |  |  |  | 				this.query_timed( | 
					
						
							|  |  |  |  | 					`
 | 
					
						
							| 
									
										
										
										
											2025-06-18 12:36:59 -04:00
										 |  |  |  | 					SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages | 
					
						
							|  |  |  |  | 					JOIN json_each(?2) AS following ON messages.author = following.value | 
					
						
							|  |  |  |  | 					WHERE | 
					
						
							|  |  |  |  | 						messages.content ->> 'type' = 'post' AND | 
					
						
							|  |  |  |  | 						messages.content ->> 'root' IS NULL AND | 
					
						
							|  |  |  |  | 						messages.author != ?4 | 
					
						
							|  |  |  |  | 				`,
 | 
					
						
							| 
									
										
										
										
											2025-06-18 12:37:41 -04:00
										 |  |  |  | 					k_args | 
					
						
							|  |  |  |  | 				), | 
					
						
							|  |  |  |  | 				this.query_timed( | 
					
						
							|  |  |  |  | 					`
 | 
					
						
							| 
									
										
										
										
											2025-06-18 12:36:59 -04:00
										 |  |  |  | 					SELECT '@' AS channel, MAX(messages.rowid) AS rowid FROM messages_fts(?3) | 
					
						
							|  |  |  |  | 					JOIN messages ON messages.rowid = messages_fts.rowid | 
					
						
							|  |  |  |  | 					JOIN json_each(?2) AS following ON messages.author = following.value | 
					
						
							|  |  |  |  | 					WHERE messages.author != ?4 | 
					
						
							|  |  |  |  | 				`,
 | 
					
						
							| 
									
										
										
										
											2025-06-18 12:37:41 -04:00
										 |  |  |  | 					k_args | 
					
						
							|  |  |  |  | 				), | 
					
						
							|  |  |  |  | 			]) | 
					
						
							|  |  |  |  | 		).flat(); | 
					
						
							| 
									
										
										
										
											2025-05-31 17:55:49 -04:00
										 |  |  |  | 		let latest = {}; | 
					
						
							|  |  |  |  | 		for (let row of channels) { | 
					
						
							|  |  |  |  | 			if (!latest[row.channel]) { | 
					
						
							|  |  |  |  | 				latest[row.channel] = row.rowid; | 
					
						
							|  |  |  |  | 			} else { | 
					
						
							|  |  |  |  | 				latest[row.channel] = Math.max(row.rowid, latest[row.channel]); | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		this.channels_latest = latest; | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 		console.log('channels took', (new Date() - start_time) / 1000.0); | 
					
						
							|  |  |  |  | 		let self = this; | 
					
						
							| 
									
										
										
										
											2025-01-14 20:15:18 -05:00
										 |  |  |  | 		start_time = new Date(); | 
					
						
							| 
									
										
										
										
											2025-08-13 19:16:34 -04:00
										 |  |  |  | 		latest_private.then(async function (latest) { | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 			self.channels_latest = Object.assign({}, self.channels_latest, { | 
					
						
							| 
									
										
										
										
											2025-02-05 18:41:37 -05:00
										 |  |  |  | 				'🔐': latest[0], | 
					
						
							| 
									
										
										
										
											2024-12-22 13:16:56 -05:00
										 |  |  |  | 			}); | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 			console.log('private took', (new Date() - start_time) / 1000.0); | 
					
						
							| 
									
										
										
										
											2025-02-05 18:41:37 -05:00
										 |  |  |  | 			self.private_messages = latest[1]; | 
					
						
							| 
									
										
										
										
											2025-08-13 19:16:34 -04:00
										 |  |  |  | 			self.grouped_private_messages = await self.group_private_messages( | 
					
						
							|  |  |  |  | 				latest[1] | 
					
						
							|  |  |  |  | 			); | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 		}); | 
					
						
							| 
									
										
										
										
											2024-12-22 13:16:56 -05:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 	_schedule_load_latest_timer() { | 
					
						
							|  |  |  |  | 		--this.loading_latest_scheduled; | 
					
						
							|  |  |  |  | 		this.schedule_load_latest(); | 
					
						
							| 
									
										
										
										
											2024-12-22 13:16:56 -05:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 19:49:08 -04:00
										 |  |  |  | 	reset_progress() { | 
					
						
							|  |  |  |  | 		if (this.progress === undefined) { | 
					
						
							| 
									
										
										
										
											2025-07-30 20:04:34 -04:00
										 |  |  |  | 			this._progress_start = new Date(); | 
					
						
							| 
									
										
										
										
											2025-07-30 19:49:08 -04:00
										 |  |  |  | 			requestAnimationFrame(this.update_progress.bind(this)); | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	update_progress() { | 
					
						
							| 
									
										
										
										
											2025-08-02 12:09:08 -04:00
										 |  |  |  | 		if ( | 
					
						
							|  |  |  |  | 			!this.loading_latest && | 
					
						
							| 
									
										
										
										
											2025-07-31 12:48:45 -04:00
										 |  |  |  | 			!this.loading_latest_scheduled && | 
					
						
							| 
									
										
										
										
											2025-08-02 12:09:08 -04:00
										 |  |  |  | 			!this.shadowRoot.getElementById('tf-tab-news')?.is_loading() | 
					
						
							|  |  |  |  | 		) { | 
					
						
							| 
									
										
										
										
											2025-07-30 19:49:08 -04:00
										 |  |  |  | 			this.progress = undefined; | 
					
						
							|  |  |  |  | 			return; | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2025-07-30 20:04:34 -04:00
										 |  |  |  | 		this.progress = (new Date() - this._progress_start).valueOf(); | 
					
						
							| 
									
										
										
										
											2025-07-30 19:49:08 -04:00
										 |  |  |  | 		requestAnimationFrame(this.update_progress.bind(this)); | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 	schedule_load_latest() { | 
					
						
							| 
									
										
										
										
											2025-07-30 19:49:08 -04:00
										 |  |  |  | 		this.reset_progress(); | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 		if (!this.loading_latest) { | 
					
						
							| 
									
										
										
										
											2024-12-29 13:32:37 -05:00
										 |  |  |  | 			this.shadowRoot.getElementById('tf-tab-news')?.load_latest(); | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 			this.load(); | 
					
						
							|  |  |  |  | 		} else if (!this.loading_latest_scheduled) { | 
					
						
							|  |  |  |  | 			this.loading_latest_scheduled++; | 
					
						
							|  |  |  |  | 			setTimeout(this._schedule_load_latest_timer.bind(this), 5000); | 
					
						
							| 
									
										
										
										
											2024-12-22 13:16:56 -05:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-14 20:15:18 -05:00
										 |  |  |  | 	async fetch_user_info(users) { | 
					
						
							|  |  |  |  | 		let info = await tfrpc.rpc.query( | 
					
						
							|  |  |  |  | 			`
 | 
					
						
							| 
									
										
										
										
											2025-01-22 19:19:50 -05:00
										 |  |  |  | 				SELECT messages_stats.author, messages_stats.max_sequence, messages_stats.max_timestamp AS max_ts FROM messages_stats | 
					
						
							| 
									
										
										
										
											2025-01-14 20:15:18 -05:00
										 |  |  |  | 				JOIN json_each(?) AS following | 
					
						
							| 
									
										
										
										
											2025-01-22 19:19:50 -05:00
										 |  |  |  | 				ON messages_stats.author = following.value | 
					
						
							| 
									
										
										
										
											2025-01-14 20:15:18 -05:00
										 |  |  |  | 			`,
 | 
					
						
							| 
									
										
										
										
											2025-01-14 21:37:11 -05:00
										 |  |  |  | 			[JSON.stringify(Object.keys(users))] | 
					
						
							|  |  |  |  | 		); | 
					
						
							| 
									
										
										
										
											2025-01-14 20:15:18 -05:00
										 |  |  |  | 		for (let row of info) { | 
					
						
							| 
									
										
										
										
											2025-05-22 10:56:30 -04:00
										 |  |  |  | 			users[row.author] = Object.assign(users[row.author], { | 
					
						
							|  |  |  |  | 				seq: row.max_sequence, | 
					
						
							|  |  |  |  | 				ts: row.max_ts, | 
					
						
							|  |  |  |  | 			}); | 
					
						
							| 
									
										
										
										
											2025-01-14 20:15:18 -05:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 		return users; | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-29 20:48:47 -04:00
										 |  |  |  | 	async load_recent_reactions() { | 
					
						
							| 
									
										
										
										
											2025-05-07 18:15:33 -04:00
										 |  |  |  | 		this.recent_reactions = ( | 
					
						
							|  |  |  |  | 			await tfrpc.rpc.query( | 
					
						
							|  |  |  |  | 				`
 | 
					
						
							| 
									
										
										
										
											2025-04-29 20:48:47 -04:00
										 |  |  |  | 			SELECT DISTINCT content ->> '$.vote.expression' AS value | 
					
						
							|  |  |  |  | 			FROM messages | 
					
						
							|  |  |  |  | 			WHERE author = ? AND | 
					
						
							|  |  |  |  | 			content ->> '$.type' = 'vote' | 
					
						
							|  |  |  |  | 			ORDER BY timestamp DESC LIMIT 10 | 
					
						
							|  |  |  |  | 		`,
 | 
					
						
							| 
									
										
										
										
											2025-05-07 18:15:33 -04:00
										 |  |  |  | 				[this.whoami] | 
					
						
							|  |  |  |  | 			) | 
					
						
							|  |  |  |  | 		).map((x) => x.value); | 
					
						
							| 
									
										
										
										
											2025-04-29 20:48:47 -04:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-14 23:33:57 +00:00
										 |  |  |  | 	async load() { | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 		this.loading_latest = true; | 
					
						
							| 
									
										
										
										
											2025-07-30 19:49:08 -04:00
										 |  |  |  | 		this.reset_progress(); | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 		try { | 
					
						
							|  |  |  |  | 			let start_time = new Date(); | 
					
						
							|  |  |  |  | 			let whoami = this.whoami; | 
					
						
							|  |  |  |  | 			let following = await tfrpc.rpc.following([whoami], 2); | 
					
						
							| 
									
										
										
										
											2025-05-09 07:35:52 -04:00
										 |  |  |  | 			let old_users = this.users ?? {}; | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 			let users = {}; | 
					
						
							|  |  |  |  | 			let by_count = []; | 
					
						
							|  |  |  |  | 			for (let [id, v] of Object.entries(following)) { | 
					
						
							| 
									
										
										
										
											2025-05-09 07:35:52 -04:00
										 |  |  |  | 				users[id] = Object.assign( | 
					
						
							|  |  |  |  | 					{ | 
					
						
							|  |  |  |  | 						following: v.of, | 
					
						
							|  |  |  |  | 						blocking: v.ob, | 
					
						
							|  |  |  |  | 						followed: v.if, | 
					
						
							|  |  |  |  | 						blocked: v.ib, | 
					
						
							|  |  |  |  | 						follow_depth: following[id]?.d, | 
					
						
							|  |  |  |  | 					}, | 
					
						
							|  |  |  |  | 					old_users[id] | 
					
						
							|  |  |  |  | 				); | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 				by_count.push({count: v.of, id: id}); | 
					
						
							|  |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2025-04-29 20:48:47 -04:00
										 |  |  |  | 			let reactions = this.load_recent_reactions(); | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 			this.load_channels_latest(Object.keys(following)); | 
					
						
							|  |  |  |  | 			this.channels_unread = JSON.parse( | 
					
						
							|  |  |  |  | 				(await tfrpc.rpc.databaseGet('unread')) ?? '{}' | 
					
						
							|  |  |  |  | 			); | 
					
						
							|  |  |  |  | 			this.following = Object.keys(following); | 
					
						
							| 
									
										
										
										
											2025-01-25 18:00:06 -05:00
										 |  |  |  | 			let about_start_time = new Date(); | 
					
						
							| 
									
										
										
										
											2025-01-14 20:15:18 -05:00
										 |  |  |  | 			start_time = new Date(); | 
					
						
							|  |  |  |  | 			users = await this.fetch_user_info(users); | 
					
						
							| 
									
										
										
										
											2025-01-14 21:37:11 -05:00
										 |  |  |  | 			console.log( | 
					
						
							|  |  |  |  | 				'user info took', | 
					
						
							|  |  |  |  | 				(new Date() - start_time) / 1000.0, | 
					
						
							|  |  |  |  | 				'seconds' | 
					
						
							|  |  |  |  | 			); | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 			this.users = users; | 
					
						
							| 
									
										
										
										
											2025-05-09 07:27:35 -04:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 			let self = this; | 
					
						
							| 
									
										
										
										
											2025-05-09 07:35:52 -04:00
										 |  |  |  | 			this.fetch_about(following, users).then(function (result) { | 
					
						
							| 
									
										
										
										
											2025-05-09 07:27:35 -04:00
										 |  |  |  | 				self.users = result; | 
					
						
							|  |  |  |  | 				console.log( | 
					
						
							|  |  |  |  | 					'about took', | 
					
						
							|  |  |  |  | 					(new Date() - about_start_time) / 1000.0, | 
					
						
							|  |  |  |  | 					'seconds for', | 
					
						
							|  |  |  |  | 					Object.keys(users).length, | 
					
						
							|  |  |  |  | 					'users' | 
					
						
							|  |  |  |  | 				); | 
					
						
							|  |  |  |  | 			}); | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 			console.log( | 
					
						
							|  |  |  |  | 				`load finished ${whoami} => ${this.whoami} in ${(new Date() - start_time) / 1000}` | 
					
						
							|  |  |  |  | 			); | 
					
						
							| 
									
										
										
										
											2025-04-29 20:48:47 -04:00
										 |  |  |  | 			await reactions; | 
					
						
							| 
									
										
										
										
											2025-01-11 13:48:06 -05:00
										 |  |  |  | 			this.whoami = whoami; | 
					
						
							|  |  |  |  | 			this.loaded = whoami; | 
					
						
							|  |  |  |  | 		} finally { | 
					
						
							|  |  |  |  | 			this.loading_latest = false; | 
					
						
							| 
									
										
										
										
											2023-11-03 00:45:30 +00:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-09-14 23:33:57 +00:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-30 15:05:14 -05:00
										 |  |  |  | 	channel_set_unread(event) { | 
					
						
							|  |  |  |  | 		this.channels_unread[event.detail.channel ?? ''] = event.detail.unread; | 
					
						
							|  |  |  |  | 		this.channels_unread = Object.assign({}, this.channels_unread); | 
					
						
							|  |  |  |  | 		tfrpc.rpc.databaseSet('unread', JSON.stringify(this.channels_unread)); | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-18 20:03:53 -05:00
										 |  |  |  | 	async decrypt(messages) { | 
					
						
							|  |  |  |  | 		let whoami = this.whoami; | 
					
						
							| 
									
										
										
										
											2024-12-23 11:08:27 -05:00
										 |  |  |  | 		return Promise.all( | 
					
						
							|  |  |  |  | 			messages.map(async function (message) { | 
					
						
							|  |  |  |  | 				let content; | 
					
						
							| 
									
										
										
										
											2024-12-18 20:03:53 -05:00
										 |  |  |  | 				try { | 
					
						
							| 
									
										
										
										
											2024-12-23 11:08:27 -05:00
										 |  |  |  | 					content = JSON.parse(message?.content); | 
					
						
							| 
									
										
										
										
											2024-12-18 20:03:53 -05:00
										 |  |  |  | 				} catch {} | 
					
						
							| 
									
										
										
										
											2024-12-23 11:08:27 -05:00
										 |  |  |  | 				if (typeof content === 'string') { | 
					
						
							|  |  |  |  | 					let decrypted; | 
					
						
							| 
									
										
										
										
											2024-12-18 20:03:53 -05:00
										 |  |  |  | 					try { | 
					
						
							| 
									
										
										
										
											2024-12-23 11:08:27 -05:00
										 |  |  |  | 						decrypted = await tfrpc.rpc.try_decrypt(whoami, content); | 
					
						
							|  |  |  |  | 					} catch {} | 
					
						
							|  |  |  |  | 					if (decrypted) { | 
					
						
							|  |  |  |  | 						try { | 
					
						
							|  |  |  |  | 							message.decrypted = JSON.parse(decrypted); | 
					
						
							|  |  |  |  | 						} catch { | 
					
						
							|  |  |  |  | 							message.decrypted = decrypted; | 
					
						
							|  |  |  |  | 						} | 
					
						
							| 
									
										
										
										
											2024-12-18 20:03:53 -05:00
										 |  |  |  | 					} | 
					
						
							|  |  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2024-12-23 11:08:27 -05:00
										 |  |  |  | 				return message; | 
					
						
							|  |  |  |  | 			}) | 
					
						
							|  |  |  |  | 		); | 
					
						
							| 
									
										
										
										
											2024-12-18 20:03:53 -05:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-14 23:33:57 +00:00
										 |  |  |  | 	render_tab() { | 
					
						
							|  |  |  |  | 		let following = this.following; | 
					
						
							|  |  |  |  | 		let users = this.users; | 
					
						
							| 
									
										
										
										
											2022-09-11 17:42:41 +00:00
										 |  |  |  | 		if (this.tab === 'news') { | 
					
						
							|  |  |  |  | 			return html`
 | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  |  | 				<tf-tab-news | 
					
						
							|  |  |  |  | 					id="tf-tab-news" | 
					
						
							|  |  |  |  | 					.following=${this.following} | 
					
						
							|  |  |  |  | 					whoami=${this.whoami} | 
					
						
							|  |  |  |  | 					.users=${this.users} | 
					
						
							|  |  |  |  | 					hash=${this.hash} | 
					
						
							| 
									
										
										
										
											2025-05-22 10:44:41 -04:00
										 |  |  |  | 					?loading=${this.loading || this.loading_about != 0} | 
					
						
							| 
									
										
										
										
											2024-11-30 15:05:14 -05:00
										 |  |  |  | 					.channels=${this.channels} | 
					
						
							|  |  |  |  | 					.channels_latest=${this.channels_latest} | 
					
						
							|  |  |  |  | 					.channels_unread=${this.channels_unread} | 
					
						
							|  |  |  |  | 					@channelsetunread=${this.channel_set_unread} | 
					
						
							| 
									
										
										
										
											2025-07-02 12:41:03 -04:00
										 |  |  |  | 					@refresh=${this.refresh} | 
					
						
							|  |  |  |  | 					@toggle_stay_connected=${this.toggle_stay_connected} | 
					
						
							| 
									
										
										
										
											2025-07-31 12:48:45 -04:00
										 |  |  |  | 					@loadmessages=${this.reset_progress} | 
					
						
							| 
									
										
										
										
											2025-08-20 19:08:07 -04:00
										 |  |  |  | 					@closeprivatechat=${this.close_private_chat} | 
					
						
							| 
									
										
										
										
											2024-12-29 14:54:29 -05:00
										 |  |  |  | 					.connections=${this.connections} | 
					
						
							| 
									
										
										
										
											2025-02-05 18:41:37 -05:00
										 |  |  |  | 					.private_messages=${this.private_messages} | 
					
						
							| 
									
										
										
										
											2025-08-20 19:08:07 -04:00
										 |  |  |  | 					.grouped_private_messages=${this.visible_private()} | 
					
						
							| 
									
										
										
										
											2025-04-29 20:48:47 -04:00
										 |  |  |  | 					.recent_reactions=${this.recent_reactions} | 
					
						
							| 
									
										
										
										
											2025-07-02 12:41:03 -04:00
										 |  |  |  | 					?is_administrator=${this.is_administrator} | 
					
						
							|  |  |  |  | 					?stay_connected=${this.stay_connected} | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  |  | 				></tf-tab-news> | 
					
						
							| 
									
										
										
										
											2022-09-11 17:42:41 +00:00
										 |  |  |  | 			`;
 | 
					
						
							|  |  |  |  | 		} else if (this.tab === 'connections') { | 
					
						
							|  |  |  |  | 			return html`
 | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  |  | 				<tf-tab-connections | 
					
						
							|  |  |  |  | 					.users=${this.users} | 
					
						
							|  |  |  |  | 					.connections=${this.connections} | 
					
						
							|  |  |  |  | 					.broadcasts=${this.broadcasts} | 
					
						
							|  |  |  |  | 				></tf-tab-connections> | 
					
						
							| 
									
										
										
										
											2022-09-11 17:42:41 +00:00
										 |  |  |  | 			`;
 | 
					
						
							|  |  |  |  | 		} else if (this.tab === 'search') { | 
					
						
							|  |  |  |  | 			return html`
 | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  |  | 				<tf-tab-search | 
					
						
							|  |  |  |  | 					.following=${this.following} | 
					
						
							|  |  |  |  | 					whoami=${this.whoami} | 
					
						
							|  |  |  |  | 					.users=${this.users} | 
					
						
							|  |  |  |  | 					query=${this.hash?.startsWith('#q=') | 
					
						
							|  |  |  |  | 						? decodeURIComponent(this.hash.substring(3)) | 
					
						
							|  |  |  |  | 						: null} | 
					
						
							|  |  |  |  | 				></tf-tab-search> | 
					
						
							| 
									
										
										
										
											2022-09-11 17:42:41 +00:00
										 |  |  |  | 			`;
 | 
					
						
							| 
									
										
										
										
											2022-09-10 18:23:58 +00:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-25 12:33:54 +00:00
										 |  |  |  | 	async set_tab(tab) { | 
					
						
							|  |  |  |  | 		this.tab = tab; | 
					
						
							|  |  |  |  | 		if (tab === 'news') { | 
					
						
							| 
									
										
										
										
											2025-07-30 19:49:08 -04:00
										 |  |  |  | 			this.schedule_load_latest(); | 
					
						
							| 
									
										
										
										
											2022-09-25 12:33:54 +00:00
										 |  |  |  | 			await tfrpc.rpc.setHash('#'); | 
					
						
							|  |  |  |  | 		} else if (tab === 'connections') { | 
					
						
							|  |  |  |  | 			await tfrpc.rpc.setHash('#connections'); | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-23 16:49:33 -05:00
										 |  |  |  | 	refresh() { | 
					
						
							|  |  |  |  | 		tfrpc.rpc.sync(); | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-28 13:47:58 -04:00
										 |  |  |  | 	async toggle_stay_connected() { | 
					
						
							|  |  |  |  | 		let stay_connected = await tfrpc.rpc.globalSettingsGet('stay_connected'); | 
					
						
							|  |  |  |  | 		let new_stay_connected = !this.stay_connected; | 
					
						
							|  |  |  |  | 		try { | 
					
						
							|  |  |  |  | 			if (new_stay_connected != stay_connected) { | 
					
						
							|  |  |  |  | 				await tfrpc.rpc.globalSettingsSet('stay_connected', new_stay_connected); | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 		} finally { | 
					
						
							|  |  |  |  | 			this.stay_connected = await tfrpc.rpc.globalSettingsGet('stay_connected'); | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-22 19:39:20 -04:00
										 |  |  |  | 	async pick_color() { | 
					
						
							|  |  |  |  | 		let input = document.createElement('input'); | 
					
						
							|  |  |  |  | 		input.type = 'color'; | 
					
						
							|  |  |  |  | 		input.value = (await tfrpc.rpc.localStorageGet('color')) ?? '#ff0000'; | 
					
						
							|  |  |  |  | 		input.addEventListener('change', async function () { | 
					
						
							|  |  |  |  | 			await tfrpc.rpc.localStorageSet('color', input.value); | 
					
						
							|  |  |  |  | 			window.location.reload(); | 
					
						
							|  |  |  |  | 		}); | 
					
						
							|  |  |  |  | 		input.click(); | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-06 23:26:43 +00:00
										 |  |  |  | 	render() { | 
					
						
							| 
									
										
										
										
											2022-09-10 02:56:15 +00:00
										 |  |  |  | 		let self = this; | 
					
						
							| 
									
										
										
										
											2022-09-14 23:33:57 +00:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 		if (!this.loading && this.whoami && this.loaded !== this.whoami) { | 
					
						
							|  |  |  |  | 			this.loading = true; | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  |  | 			this.load().finally(function () { | 
					
						
							| 
									
										
										
										
											2022-09-14 23:33:57 +00:00
										 |  |  |  | 				self.loading = false; | 
					
						
							|  |  |  |  | 			}); | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 04:23:31 +00:00
										 |  |  |  | 		const k_tabs = { | 
					
						
							|  |  |  |  | 			'📰': 'news', | 
					
						
							|  |  |  |  | 			'📡': 'connections', | 
					
						
							|  |  |  |  | 			'🔍': 'search', | 
					
						
							|  |  |  |  | 		}; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-10 02:56:15 +00:00
										 |  |  |  | 		let tabs = html`
 | 
					
						
							| 
									
										
										
										
											2024-12-05 20:47:02 -05:00
										 |  |  |  | 			<div | 
					
						
							|  |  |  |  | 				class="w3-bar w3-theme-l1" | 
					
						
							| 
									
										
										
										
											2024-12-29 12:44:42 -05:00
										 |  |  |  | 				style="position: static; top: 0; z-index: 10" | 
					
						
							| 
									
										
										
										
											2024-12-05 20:47:02 -05:00
										 |  |  |  | 			> | 
					
						
							| 
									
										
										
										
											2025-09-24 13:43:33 -04:00
										 |  |  |  | 				${this.is_administrator | 
					
						
							| 
									
										
										
										
											2025-06-28 13:47:58 -04:00
										 |  |  |  | 					? html`
 | 
					
						
							|  |  |  |  | 							<button | 
					
						
							|  |  |  |  | 								class=${'w3-bar-item w3-button w3-circle w3-ripple' + | 
					
						
							|  |  |  |  | 								(this.connections?.some((x) => x.flags.one_shot) | 
					
						
							|  |  |  |  | 									? ' w3-spin' | 
					
						
							|  |  |  |  | 									: '')} | 
					
						
							|  |  |  |  | 								@click=${this.refresh} | 
					
						
							|  |  |  |  | 							> | 
					
						
							|  |  |  |  | 								↻ | 
					
						
							|  |  |  |  | 							</button> | 
					
						
							|  |  |  |  | 							<button | 
					
						
							|  |  |  |  | 								class="w3-bar-item w3-button w3-ripple" | 
					
						
							|  |  |  |  | 								@click=${this.toggle_stay_connected} | 
					
						
							|  |  |  |  | 							> | 
					
						
							|  |  |  |  | 								${this.stay_connected ? '🔗' : '⛓️💥'} | 
					
						
							|  |  |  |  | 							</button> | 
					
						
							|  |  |  |  | 						`
 | 
					
						
							|  |  |  |  | 					: undefined} | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  |  | 				${Object.entries(k_tabs).map( | 
					
						
							|  |  |  |  | 					([k, v]) => html`
 | 
					
						
							|  |  |  |  | 						<button | 
					
						
							|  |  |  |  | 							title=${v} | 
					
						
							| 
									
										
										
										
											2024-05-12 07:48:34 -04:00
										 |  |  |  | 							class="w3-bar-item w3-padding w3-hover-theme tab ${self.tab == v | 
					
						
							| 
									
										
										
										
											2024-04-04 20:35:09 -04:00
										 |  |  |  | 								? 'w3-theme-l2' | 
					
						
							|  |  |  |  | 								: 'w3-theme-l1'}" | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  |  | 							@click=${() => self.set_tab(v)} | 
					
						
							|  |  |  |  | 						> | 
					
						
							|  |  |  |  | 							${k} | 
					
						
							| 
									
										
										
										
											2024-05-12 07:48:34 -04:00
										 |  |  |  | 							<span class=${self.tab == v ? '' : 'w3-hide-small'} | 
					
						
							|  |  |  |  | 								>${v.charAt(0).toUpperCase() + v.substring(1)}</span | 
					
						
							|  |  |  |  | 							> | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  |  | 						</button> | 
					
						
							|  |  |  |  | 					`
 | 
					
						
							|  |  |  |  | 				)} | 
					
						
							| 
									
										
										
										
											2025-10-22 19:39:20 -04:00
										 |  |  |  | 				<button | 
					
						
							|  |  |  |  | 					class="w3-bar-item w3-button w3-right" | 
					
						
							|  |  |  |  | 					@click=${this.pick_color} | 
					
						
							|  |  |  |  | 				> | 
					
						
							|  |  |  |  | 					🎨<span class="w3-hide-small">Color</span> | 
					
						
							|  |  |  |  | 				</button> | 
					
						
							| 
									
										
										
										
											2022-09-10 02:56:15 +00:00
										 |  |  |  | 			</div> | 
					
						
							|  |  |  |  | 		`;
 | 
					
						
							| 
									
										
										
										
											2025-01-05 14:52:27 -05:00
										 |  |  |  | 		let contents = this.guest | 
					
						
							|  |  |  |  | 			? html`<div
 | 
					
						
							|  |  |  |  | 					class="w3-display-middle w3-panel w3-theme-l5 w3-card-4 w3-padding-large w3-round-xlarge w3-xlarge w3-container" | 
					
						
							|  |  |  |  | 				> | 
					
						
							|  |  |  |  | 					<p>⚠️🦀 Must be logged in to Tilde Friends to scuttle here. 🦀⚠️</p> | 
					
						
							|  |  |  |  | 					<footer class="w3-center"> | 
					
						
							|  |  |  |  | 						<a | 
					
						
							|  |  |  |  | 							class="w3-button w3-theme-d1" | 
					
						
							|  |  |  |  | 							href=${`/login?return=${encodeURIComponent(this.url)}`} | 
					
						
							|  |  |  |  | 							>Login</a | 
					
						
							|  |  |  |  | 						> | 
					
						
							|  |  |  |  | 					</footer> | 
					
						
							|  |  |  |  | 				</div>` | 
					
						
							| 
									
										
										
										
											2025-01-05 12:52:52 -05:00
										 |  |  |  | 			: !this.loaded || this.loading | 
					
						
							| 
									
										
										
										
											2024-12-24 14:22:24 -05:00
										 |  |  |  | 				? html`<div
 | 
					
						
							| 
									
										
										
										
											2024-12-24 12:45:25 -05:00
										 |  |  |  | 						class="w3-display-middle w3-panel w3-theme-l5 w3-card-4 w3-padding-large w3-round-xlarge w3-xlarge" | 
					
						
							|  |  |  |  | 					> | 
					
						
							| 
									
										
										
										
											2024-12-24 14:22:24 -05:00
										 |  |  |  | 						<span class="w3-spin" style="display: inline-block">🦀</span> | 
					
						
							|  |  |  |  | 						Loading... | 
					
						
							| 
									
										
										
										
											2024-12-24 12:45:25 -05:00
										 |  |  |  | 					</div>` | 
					
						
							| 
									
										
										
										
											2024-12-24 14:22:24 -05:00
										 |  |  |  | 				: this.render_tab(); | 
					
						
							| 
									
										
										
										
											2025-07-30 20:04:34 -04:00
										 |  |  |  | 		let progress = | 
					
						
							|  |  |  |  | 			this.progress !== undefined | 
					
						
							|  |  |  |  | 				? html`
 | 
					
						
							|  |  |  |  | 						<div style="position: absolute; width: 100%" id="progress"> | 
					
						
							|  |  |  |  | 							<div | 
					
						
							| 
									
										
										
										
											2025-08-02 12:16:07 -04:00
										 |  |  |  | 								class="w3-theme-l3" | 
					
						
							| 
									
										
										
										
											2025-07-30 20:04:34 -04:00
										 |  |  |  | 								style=${`height: 4px; position: absolute; right: ${Math.cos(this.progress / 250) > 0 ? 'auto' : '0'}; width: ${50 * Math.sin(this.progress / 250) + 50}%`} | 
					
						
							|  |  |  |  | 							></div> | 
					
						
							|  |  |  |  | 						</div> | 
					
						
							|  |  |  |  | 					`
 | 
					
						
							|  |  |  |  | 				: undefined; | 
					
						
							| 
									
										
										
										
											2022-09-11 17:42:41 +00:00
										 |  |  |  | 		return html`
 | 
					
						
							| 
									
										
										
										
											2025-10-22 19:39:20 -04:00
										 |  |  |  | 			<style> | 
					
						
							|  |  |  |  | 				${generate_theme()} | 
					
						
							|  |  |  |  | 			</style> | 
					
						
							| 
									
										
										
										
											2024-04-11 18:36:31 -04:00
										 |  |  |  | 			<div | 
					
						
							| 
									
										
										
										
											2024-12-29 12:44:42 -05:00
										 |  |  |  | 				style="width: 100vw; min-height: 100vh; height: 100vh; display: flex; flex-direction: column" | 
					
						
							| 
									
										
										
										
											2024-04-11 18:36:31 -04:00
										 |  |  |  | 				class="w3-theme-dark" | 
					
						
							|  |  |  |  | 			> | 
					
						
							| 
									
										
										
										
											2025-07-30 19:49:08 -04:00
										 |  |  |  | 				${progress} | 
					
						
							| 
									
										
										
										
											2024-12-29 12:44:42 -05:00
										 |  |  |  | 				<div style="flex: 0 0">${tabs}</div> | 
					
						
							| 
									
										
										
										
											2025-01-02 08:58:10 -05:00
										 |  |  |  | 				<div style="flex: 1 1; overflow: auto; contain: layout"> | 
					
						
							| 
									
										
										
										
											2024-12-29 13:32:37 -05:00
										 |  |  |  | 					${contents} | 
					
						
							|  |  |  |  | 				</div> | 
					
						
							| 
									
										
										
										
											2024-04-04 20:35:09 -04:00
										 |  |  |  | 			</div> | 
					
						
							| 
									
										
										
										
											2022-09-06 23:26:43 +00:00
										 |  |  |  | 		`;
 | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-03 00:30:48 +00:00
										 |  |  |  | customElements.define('tf-app', TfElement); |