All checks were successful
		
		
	
	Build Tilde Friends / Build-All (push) Successful in 18m1s
				
			
		
			
				
	
	
		
			288 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			288 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import {LitElement, html, unsafeHTML, repeat, until} from './lit-all.min.js';
 | 
						|
import * as tfrpc from '/static/tfrpc.js';
 | 
						|
import {styles, generate_theme} from './tf-styles.js';
 | 
						|
 | 
						|
class TfNewsElement extends LitElement {
 | 
						|
	static get properties() {
 | 
						|
		return {
 | 
						|
			whoami: {type: String},
 | 
						|
			users: {type: Object},
 | 
						|
			messages: {type: Array},
 | 
						|
			following: {type: Array},
 | 
						|
			drafts: {type: Object},
 | 
						|
			expanded: {type: Object},
 | 
						|
			channel: {type: String},
 | 
						|
			channel_unread: {type: Number},
 | 
						|
			recent_reactions: {type: Array},
 | 
						|
			hash: {type: String},
 | 
						|
		};
 | 
						|
	}
 | 
						|
 | 
						|
	static styles = styles;
 | 
						|
 | 
						|
	constructor() {
 | 
						|
		super();
 | 
						|
		let self = this;
 | 
						|
		this.whoami = null;
 | 
						|
		this.users = {};
 | 
						|
		this.messages = [];
 | 
						|
		this.following = [];
 | 
						|
		this.drafts = {};
 | 
						|
		this.expanded = {};
 | 
						|
		this.channel_unread = -1;
 | 
						|
		this.recent_reactions = [];
 | 
						|
	}
 | 
						|
 | 
						|
	process_messages(messages) {
 | 
						|
		let self = this;
 | 
						|
		let messages_by_id = {};
 | 
						|
 | 
						|
		console.log('processing', messages.length, 'messages');
 | 
						|
 | 
						|
		function ensure_message(id, rowid) {
 | 
						|
			let found = messages_by_id[id];
 | 
						|
			if (found) {
 | 
						|
				return found;
 | 
						|
			} else {
 | 
						|
				let added = {
 | 
						|
					rowid: rowid,
 | 
						|
					id: id,
 | 
						|
					placeholder: true,
 | 
						|
					content: '"placeholder"',
 | 
						|
					parent_message: undefined,
 | 
						|
					child_messages: [],
 | 
						|
					votes: [],
 | 
						|
				};
 | 
						|
				messages_by_id[id] = added;
 | 
						|
				return added;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		function link_message(message) {
 | 
						|
			if (message.content.type === 'vote') {
 | 
						|
				let parent = ensure_message(message.content.vote.link, message.rowid);
 | 
						|
				if (!parent.votes) {
 | 
						|
					parent.votes = [];
 | 
						|
				}
 | 
						|
				parent.votes.push(message);
 | 
						|
				message.parent_message = message.content.vote.link;
 | 
						|
			} else if (message.content.type == 'post') {
 | 
						|
				if (message.content.root) {
 | 
						|
					if (typeof message.content.root === 'string') {
 | 
						|
						let m = ensure_message(message.content.root, message.rowid);
 | 
						|
						if (!m.child_messages) {
 | 
						|
							m.child_messages = [];
 | 
						|
						}
 | 
						|
						m.child_messages.push(message);
 | 
						|
						message.parent_message = message.content.root;
 | 
						|
					} else {
 | 
						|
						let m = ensure_message(message.content.root[0], message.rowid);
 | 
						|
						if (!m.child_messages) {
 | 
						|
							m.child_messages = [];
 | 
						|
						}
 | 
						|
						m.child_messages.push(message);
 | 
						|
						message.parent_message = message.content.root[0];
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		for (let message of messages) {
 | 
						|
			message.votes = [];
 | 
						|
			message.parent_message = undefined;
 | 
						|
			message.child_messages = undefined;
 | 
						|
		}
 | 
						|
 | 
						|
		for (let message of messages) {
 | 
						|
			try {
 | 
						|
				message.content = JSON.parse(message.content);
 | 
						|
			} catch {}
 | 
						|
			if (!messages_by_id[message.id]) {
 | 
						|
				messages_by_id[message.id] = message;
 | 
						|
				link_message(message);
 | 
						|
			} else if (messages_by_id[message.id].placeholder) {
 | 
						|
				let placeholder = messages_by_id[message.id];
 | 
						|
				messages_by_id[message.id] = message;
 | 
						|
				message.parent_message = placeholder.parent_message;
 | 
						|
				message.child_messages = placeholder.child_messages;
 | 
						|
				message.votes = placeholder.votes;
 | 
						|
				if (
 | 
						|
					placeholder.parent_message &&
 | 
						|
					messages_by_id[placeholder.parent_message]
 | 
						|
				) {
 | 
						|
					let children =
 | 
						|
						messages_by_id[placeholder.parent_message].child_messages;
 | 
						|
					children.splice(children.indexOf(placeholder), 1);
 | 
						|
					children.push(message);
 | 
						|
				}
 | 
						|
				link_message(message);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return messages_by_id;
 | 
						|
	}
 | 
						|
 | 
						|
	update_latest_subtree_timestamp(messages) {
 | 
						|
		let latest = 0;
 | 
						|
		for (let message of messages || []) {
 | 
						|
			if (message.latest_subtree_timestamp === undefined) {
 | 
						|
				message.latest_subtree_timestamp = Math.max(
 | 
						|
					message.timestamp ?? 0,
 | 
						|
					this.update_latest_subtree_timestamp(message.child_messages)
 | 
						|
				);
 | 
						|
			}
 | 
						|
			latest = Math.max(latest, message.latest_subtree_timestamp);
 | 
						|
		}
 | 
						|
		return latest;
 | 
						|
	}
 | 
						|
 | 
						|
	finalize_messages(messages_by_id) {
 | 
						|
		function recursive_sort(messages, top) {
 | 
						|
			if (messages) {
 | 
						|
				if (top) {
 | 
						|
					messages.sort(
 | 
						|
						(a, b) => b.latest_subtree_timestamp - a.latest_subtree_timestamp
 | 
						|
					);
 | 
						|
				} else {
 | 
						|
					messages.sort((a, b) => a.timestamp - b.timestamp);
 | 
						|
				}
 | 
						|
				for (let message of messages) {
 | 
						|
					recursive_sort(message.child_messages, false);
 | 
						|
				}
 | 
						|
				return messages.map((x) => Object.assign({}, x));
 | 
						|
			} else {
 | 
						|
				return {};
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		let roots = Object.values(messages_by_id).filter((x) => !x.parent_message);
 | 
						|
		this.update_latest_subtree_timestamp(roots);
 | 
						|
		return recursive_sort(roots, true);
 | 
						|
	}
 | 
						|
 | 
						|
	group_messages(messages) {
 | 
						|
		let result = [];
 | 
						|
		let group = [];
 | 
						|
		let type = undefined;
 | 
						|
		for (let message of messages) {
 | 
						|
			if (
 | 
						|
				message?.content?.type === 'contact' ||
 | 
						|
				message?.content?.type === 'channel'
 | 
						|
			) {
 | 
						|
				if (type && message.content.type !== type) {
 | 
						|
					if (group.length == 1) {
 | 
						|
						result.push(group[0]);
 | 
						|
						group = [];
 | 
						|
					} else if (group.length > 1) {
 | 
						|
						result.push({
 | 
						|
							rowid: Math.max(...group.map((x) => x.rowid)),
 | 
						|
							type: `${type}_group`,
 | 
						|
							messages: group,
 | 
						|
						});
 | 
						|
						group = [];
 | 
						|
					}
 | 
						|
				}
 | 
						|
				type = message.content.type;
 | 
						|
				group.push(message);
 | 
						|
			} else {
 | 
						|
				if (group.length == 1) {
 | 
						|
					result.push(group[0]);
 | 
						|
					group = [];
 | 
						|
				} else if (group.length > 1) {
 | 
						|
					result.push({
 | 
						|
						rowid: Math.max(...group.map((x) => x.rowid)),
 | 
						|
						type: `${type}_group`,
 | 
						|
						messages: group,
 | 
						|
					});
 | 
						|
					group = [];
 | 
						|
				}
 | 
						|
				result.push(message);
 | 
						|
				type = undefined;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (group.length == 1) {
 | 
						|
			result.push(group[0]);
 | 
						|
			group = [];
 | 
						|
		} else if (group.length > 1) {
 | 
						|
			result.push({
 | 
						|
				rowid: Math.max(...group.map((x) => x.rowid)),
 | 
						|
				type: `${type}_group`,
 | 
						|
				messages: group,
 | 
						|
			});
 | 
						|
		}
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	unread_allowed() {
 | 
						|
		return !this.hash?.startsWith('#%') && !this.hash?.startsWith('#@');
 | 
						|
	}
 | 
						|
 | 
						|
	load_and_render(messages) {
 | 
						|
		let messages_by_id = this.process_messages(messages);
 | 
						|
		let final_messages = this.group_messages(
 | 
						|
			this.finalize_messages(messages_by_id)
 | 
						|
		);
 | 
						|
		let unread_rowid = -1;
 | 
						|
		if (this.unread_allowed()) {
 | 
						|
			for (let message of final_messages) {
 | 
						|
				if (message.rowid >= this.channel_unread) {
 | 
						|
					unread_rowid = message.rowid;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return html`
 | 
						|
			<style>
 | 
						|
				${generate_theme()}
 | 
						|
			</style>
 | 
						|
			<div>
 | 
						|
				${repeat(
 | 
						|
					final_messages,
 | 
						|
					(x) => x.id,
 | 
						|
					(x) => html`
 | 
						|
						<tf-message
 | 
						|
							.message=${x}
 | 
						|
							whoami=${this.whoami}
 | 
						|
							.users=${this.users}
 | 
						|
							.drafts=${this.drafts}
 | 
						|
							.expanded=${this.expanded}
 | 
						|
							collapsed="true"
 | 
						|
							channel=${this.channel}
 | 
						|
							channel_unread=${this.channel_unread}
 | 
						|
							.recent_reactions=${this.recent_reactions}
 | 
						|
						></tf-message>
 | 
						|
						${x.rowid == unread_rowid
 | 
						|
							? html`<div style="display: flex; flex-direction: row">
 | 
						|
									<div
 | 
						|
										style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
 | 
						|
									></div>
 | 
						|
									<button
 | 
						|
										style="color: #f00; padding: 8px"
 | 
						|
										class="w3-button"
 | 
						|
										@click=${() =>
 | 
						|
											this.dispatchEvent(
 | 
						|
												new Event('mark_all_read', {
 | 
						|
													bubbles: true,
 | 
						|
													composed: true,
 | 
						|
												})
 | 
						|
											)}
 | 
						|
									>
 | 
						|
										unread
 | 
						|
									</button>
 | 
						|
									<div
 | 
						|
										style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
 | 
						|
									></div>
 | 
						|
								</div>`
 | 
						|
							: undefined}
 | 
						|
					`
 | 
						|
				)}
 | 
						|
			</div>
 | 
						|
		`;
 | 
						|
	}
 | 
						|
 | 
						|
	render() {
 | 
						|
		return this.load_and_render(this.messages || []);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
customElements.define('tf-news', TfNewsElement);
 |