Support Element's groups and list sorting
This commit is contained in:
		
							parent
							
								
									4b0b5c4b39
								
							
						
					
					
						commit
						1f9462b89d
					
				
					 18 changed files with 272 additions and 145 deletions
				
			
		
							
								
								
									
										2
									
								
								build.js
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								build.js
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -49,7 +49,7 @@ function validate(filename, body, type) {
 | 
			
		|||
					let match
 | 
			
		||||
					if (match = message.message.match(/Property “([\w-]+)” doesn't exist.$/)) {
 | 
			
		||||
						// allow these properties specifically
 | 
			
		||||
						if (["scrollbar-width", "scrollbar-color"].includes(match[1])) {
 | 
			
		||||
						if (["scrollbar-width", "scrollbar-color", "overflow-anchor"].includes(match[1])) {
 | 
			
		||||
							continue
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,11 +2,11 @@
 | 
			
		|||
<html>
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="static/main.css?static=da499eb39c">
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="static/main.css?static=9aad8398d2">
 | 
			
		||||
    <script type="module" src="static/groups.js?static=2cc7f0daf8"></script>
 | 
			
		||||
    <script type="module" src="static/chat-input.js?static=e8b21037fa"></script>
 | 
			
		||||
    <script type="module" src="static/room-picker.js?static=1d38378110"></script>
 | 
			
		||||
    <script type="module" src="static/sync/sync.js?static=9e31c8a727"></script>
 | 
			
		||||
    <script type="module" src="static/room-picker.js?static=7bc94b38d3"></script>
 | 
			
		||||
    <script type="module" src="static/sync/sync.js?static=56e374b23d"></script>
 | 
			
		||||
    <script type="module" src="static/chat.js?static=8a04bee48d"></script>
 | 
			
		||||
    <title>Carbon</title>
 | 
			
		||||
  </head>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ class Anchor extends ElemJS {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	scroll() {
 | 
			
		||||
		console.log("anchor scrolled")
 | 
			
		||||
		// console.log("anchor scrolled")
 | 
			
		||||
		this.element.scrollIntoView({block: "start"})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,7 +57,7 @@ class EventGroup extends ElemJS {
 | 
			
		|||
			this.messages = ejs("div").class("c-message-group__messages").child(
 | 
			
		||||
				ejs("div").class("c-message-group__intro").child(
 | 
			
		||||
					ejs("div").class("c-message-group__name").text(this.data.sender),
 | 
			
		||||
					ejs("div").class("c-message-group__date").text("at 4:20 pm")
 | 
			
		||||
					ejs("div").class("c-message-group__date").text(this.data.origin_server_ts)
 | 
			
		||||
				),
 | 
			
		||||
				...this.list
 | 
			
		||||
			)
 | 
			
		||||
| 
						 | 
				
			
			@ -123,11 +123,13 @@ class Timeline extends Subscribable {
 | 
			
		|||
		this.list = []
 | 
			
		||||
		this.map = new Map()
 | 
			
		||||
		this.reactiveTimeline = new ReactiveTimeline([])
 | 
			
		||||
		this.latest = 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	updateEvents(events) {
 | 
			
		||||
		this.broadcast("beforeChange")
 | 
			
		||||
		for (const eventData of events) {
 | 
			
		||||
			this.latest = Math.max(this.latest, eventData.origin_server_ts)
 | 
			
		||||
			if (this.map.has(eventData.event_id)) {
 | 
			
		||||
				this.map.get(eventData.event_id).update(eventData)
 | 
			
		||||
			} else {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,7 +71,7 @@ body {
 | 
			
		|||
	position: relative;
 | 
			
		||||
	width: 80px;
 | 
			
		||||
	flex-shrink: 0;
 | 
			
		||||
	font-size: 24px;
 | 
			
		||||
	font-size: 22px;
 | 
			
		||||
	font-weight: 500;
 | 
			
		||||
}
 | 
			
		||||
.c-groups__display {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,6 +36,7 @@ class Group extends ElemJS {
 | 
			
		|||
		super("div")
 | 
			
		||||
 | 
			
		||||
		this.data = data
 | 
			
		||||
		this.order = this.data.order
 | 
			
		||||
 | 
			
		||||
		this.class("c-group")
 | 
			
		||||
		this.child(
 | 
			
		||||
| 
						 | 
				
			
			@ -68,6 +69,7 @@ class Room extends ElemJS {
 | 
			
		|||
		this.id = id
 | 
			
		||||
		this.data = data
 | 
			
		||||
		this.timeline = new Timeline()
 | 
			
		||||
		this.group = null
 | 
			
		||||
 | 
			
		||||
		this.class("c-room")
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -77,6 +79,25 @@ class Room extends ElemJS {
 | 
			
		|||
		this.render()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	get order() {
 | 
			
		||||
		if (this.group) {
 | 
			
		||||
			let chars = 36
 | 
			
		||||
			let total = 0
 | 
			
		||||
			const name = this.getName()
 | 
			
		||||
			for (let i = 0; i < name.length; i++) {
 | 
			
		||||
				const c = name[i]
 | 
			
		||||
				let d = 0
 | 
			
		||||
				if (c >= "A" && c <= "Z") d = c.charCodeAt(0) - 65 + 10
 | 
			
		||||
				else if (c >= "a" && c <= "z") d = c.charCodeAt(0) - 97 + 10
 | 
			
		||||
				else if (c >= "0" && c <= "9") d = +c
 | 
			
		||||
				total += d * chars ** (-i)
 | 
			
		||||
			}
 | 
			
		||||
			return total
 | 
			
		||||
		} else {
 | 
			
		||||
			return -this.timeline.latest
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getName() {
 | 
			
		||||
		let name = this.data.state.events.find(e => e.type === "m.room.name")
 | 
			
		||||
		if (name) {
 | 
			
		||||
| 
						 | 
				
			
			@ -102,8 +123,16 @@ class Room extends ElemJS {
 | 
			
		|||
		return store.directs.has(this.id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setGroup(id) {
 | 
			
		||||
		this.group = id
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getGroup() {
 | 
			
		||||
		return this.isDirect() ? store.groups.get("directs").value() : store.groups.get("channels").value()
 | 
			
		||||
		if (this.group) {
 | 
			
		||||
			return store.groups.get(this.group).value()
 | 
			
		||||
		} else {
 | 
			
		||||
			return this.isDirect() ? store.groups.get("directs").value() : store.groups.get("channels").value()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onClick() {
 | 
			
		||||
| 
						 | 
				
			
			@ -138,10 +167,16 @@ class Rooms extends ElemJS {
 | 
			
		|||
		// store.rooms.subscribe("changeItem", this.render.bind(this))
 | 
			
		||||
		store.activeGroup.subscribe("changeSelf", this.render.bind(this))
 | 
			
		||||
		store.directs.subscribe("changeItem", this.render.bind(this))
 | 
			
		||||
		store.newEvents.subscribe("changeSelf", this.sort.bind(this))
 | 
			
		||||
 | 
			
		||||
		this.render()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort() {
 | 
			
		||||
		store.rooms.sort()
 | 
			
		||||
		this.render()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	askAdd(event, {key, data}) {
 | 
			
		||||
		const room = new Room(key, data)
 | 
			
		||||
		store.rooms.addEnd(key, room)
 | 
			
		||||
| 
						 | 
				
			
			@ -181,74 +216,20 @@ class Groups extends ElemJS {
 | 
			
		|||
		super(q("#c-groups-list"))
 | 
			
		||||
 | 
			
		||||
		store.groups.subscribe("askAdd", this.askAdd.bind(this))
 | 
			
		||||
		store.groups.subscribe("addItem", this.addItem.bind(this))
 | 
			
		||||
		store.groups.subscribe("changeItem", this.render.bind(this))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	askAdd(event, {key, data}) {
 | 
			
		||||
		const group = new Group(key, data)
 | 
			
		||||
		store.groups.addEnd(key, group)
 | 
			
		||||
		store.groups.sort()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	addItem(event, key) {
 | 
			
		||||
		this.child(store.groups.get(key).value())
 | 
			
		||||
	render() {
 | 
			
		||||
		this.clearChildren()
 | 
			
		||||
		store.groups.forEach((key, item) => {
 | 
			
		||||
			this.child(item.value())
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
const groups = new Groups()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
;[
 | 
			
		||||
	{
 | 
			
		||||
		id: "directs",
 | 
			
		||||
		name: "Directs",
 | 
			
		||||
		icon: "/static/directs.svg"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "channels",
 | 
			
		||||
		name: "Channels",
 | 
			
		||||
		icon: "/static/channels.svg"
 | 
			
		||||
	}/*,
 | 
			
		||||
	{
 | 
			
		||||
		id: "123",
 | 
			
		||||
		name: "Fediverse Drama Museum"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "456",
 | 
			
		||||
		name: "Epicord"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "789",
 | 
			
		||||
		name: "Invidious"
 | 
			
		||||
	}*/
 | 
			
		||||
].forEach(data => store.groups.askAdd(data.id, data))
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
;[
 | 
			
		||||
	{id: "001", name: "riley", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "002", name: "BadAtNames", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "003", name: "lynxano", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "004", name: "quarky", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "005", name: "lepton", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "006", name: "ash", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "007", name: "mewmew", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "008", name: "Toniob", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "009", name: "cockandball", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "010", name: "Carbon brainstorming", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "011", name: "Bibliogram", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "012", name: "Monsters Inc Debate Hall", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "013", name: "DRB clan", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "014", name: "mettaton simp zone", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "015", name: "witches", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "016", name: "snouts", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "017", name: "monads", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "018", name: "radical", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "019", name: "blobcat", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "020", name: "main", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "021", name: "gaming", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "022", name: "inhalers", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "023", name: "minecraft", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "024", name: "osu", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "025", name: "covid", group: store.groups.get("456").value()}
 | 
			
		||||
].forEach(data => store.rooms.askAdd(data.id, data))
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
store.activeGroup.set(store.groups.get("directs").value())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,6 +53,16 @@ class SubscribeMapList extends Subscribable {
 | 
			
		|||
		this._add(key, value, false)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort() {
 | 
			
		||||
		console.log("sorting")
 | 
			
		||||
		this.list.sort((a, b) => {
 | 
			
		||||
			const orderA = this.map.get(a).value().order
 | 
			
		||||
			const orderB = this.map.get(b).value().order
 | 
			
		||||
			return orderA - orderB
 | 
			
		||||
		})
 | 
			
		||||
		this.broadcast("changeItem")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_add(key, value, start) {
 | 
			
		||||
		let s
 | 
			
		||||
		if (this.map.has(key)) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,4 @@
 | 
			
		|||
import {Subscribable} from "./Subscribable.js"
 | 
			
		||||
import {SubscribeMapList} from "./SubscribeMapList.js"
 | 
			
		||||
import {SubscribeSet} from "./SubscribeSet.js"
 | 
			
		||||
import {SubscribeValue} from "./SubscribeValue.js"
 | 
			
		||||
| 
						 | 
				
			
			@ -7,7 +8,8 @@ const store = {
 | 
			
		|||
	rooms: new SubscribeMapList(SubscribeValue),
 | 
			
		||||
	directs: new SubscribeSet(),
 | 
			
		||||
	activeGroup: new SubscribeValue(),
 | 
			
		||||
	activeRoom: new SubscribeValue()
 | 
			
		||||
	activeRoom: new SubscribeValue(),
 | 
			
		||||
	newEvents: new Subscribable()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
window.store = store
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,16 @@ import * as lsm from "../lsm.js"
 | 
			
		|||
 | 
			
		||||
let lastBatch = null
 | 
			
		||||
 | 
			
		||||
function resolveMxc(url, size, method) {
 | 
			
		||||
	const [server, id] = url.match(/^mxc:\/\/([^/]+)\/(.*)/).slice(1)
 | 
			
		||||
	if (size && method) {
 | 
			
		||||
		return `${lsm.get("domain")}/_matrix/media/r0/thumbnail/${server}/${id}?width=${size}&height=${size}&method=${method}`
 | 
			
		||||
	} else {
 | 
			
		||||
		return `${lsm.get("domain")}/_matrix/media/r0/download/${server}/${id}`
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function sync() {
 | 
			
		||||
	const url = new URL(`${lsm.get("domain")}/_matrix/client/r0/sync`)
 | 
			
		||||
	url.searchParams.append("access_token", lsm.get("access_token"))
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +45,8 @@ function sync() {
 | 
			
		|||
 | 
			
		||||
function manageSync(root) {
 | 
			
		||||
	try {
 | 
			
		||||
		let newEvents = false
 | 
			
		||||
 | 
			
		||||
		// set up directs
 | 
			
		||||
		const directs = root.account_data.events.find(e => e.type === "m.direct")
 | 
			
		||||
		if (directs) {
 | 
			
		||||
| 
						 | 
				
			
			@ -49,8 +61,45 @@ function manageSync(root) {
 | 
			
		|||
				store.rooms.askAdd(id, room)
 | 
			
		||||
			}
 | 
			
		||||
			const timeline = store.rooms.get(id).value().timeline
 | 
			
		||||
			if (room.timeline.events.length) newEvents = true
 | 
			
		||||
			timeline.updateEvents(room.timeline.events)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		// set up groups
 | 
			
		||||
		Promise.all(
 | 
			
		||||
			Object.keys(root.groups.join).map(id => {
 | 
			
		||||
				if (!store.groups.has(id)) {
 | 
			
		||||
					return Promise.all(["profile", "rooms"].map(path => {
 | 
			
		||||
						const url = new URL(`${lsm.get("domain")}/_matrix/client/r0/groups/${id}/${path}`)
 | 
			
		||||
						url.searchParams.append("access_token", lsm.get("access_token"))
 | 
			
		||||
						return fetch(url.toString()).then(res => res.json())
 | 
			
		||||
					})).then(([profile, rooms]) => {
 | 
			
		||||
						rooms = rooms.chunk
 | 
			
		||||
						let order = 999
 | 
			
		||||
						let orderEvent = root.account_data.events.find(e => e.type === "im.vector.web.tag_ordering")
 | 
			
		||||
						if (orderEvent) {
 | 
			
		||||
							if (orderEvent.content.tags.includes(id)) {
 | 
			
		||||
								order = orderEvent.content.tags.indexOf(id)
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						const data = {
 | 
			
		||||
							name: profile.name,
 | 
			
		||||
							icon: resolveMxc(profile.avatar_url, 96, "crop"),
 | 
			
		||||
							order
 | 
			
		||||
						}
 | 
			
		||||
						store.groups.askAdd(id, data)
 | 
			
		||||
						rooms.forEach(groupRoom => {
 | 
			
		||||
							if (store.rooms.has(groupRoom.room_id)) {
 | 
			
		||||
								store.rooms.get(groupRoom.room_id).value().setGroup(id)
 | 
			
		||||
							}
 | 
			
		||||
						})
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
		).then(() => {
 | 
			
		||||
			store.rooms.sort()
 | 
			
		||||
		})
 | 
			
		||||
		if (newEvents) store.newEvents.broadcast("changeSelf")
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
		console.error(root)
 | 
			
		||||
		throw e
 | 
			
		||||
| 
						 | 
				
			
			@ -61,4 +110,21 @@ function syncLoop() {
 | 
			
		|||
	return sync().then(manageSync).then(syncLoop)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
;[
 | 
			
		||||
	{
 | 
			
		||||
		id: "directs",
 | 
			
		||||
		name: "Directs",
 | 
			
		||||
		icon: "/static/directs.svg",
 | 
			
		||||
		order: -2
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "channels",
 | 
			
		||||
		name: "Channels",
 | 
			
		||||
		icon: "/static/channels.svg",
 | 
			
		||||
		order: -1
 | 
			
		||||
	}
 | 
			
		||||
].forEach(data => store.groups.askAdd(data.id, data))
 | 
			
		||||
 | 
			
		||||
store.activeGroup.set(store.groups.get("directs").value())
 | 
			
		||||
 | 
			
		||||
syncLoop()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										5
									
								
								spec.js
									
										
									
									
									
								
							
							
						
						
									
										5
									
								
								spec.js
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -69,6 +69,11 @@ module.exports = [
 | 
			
		|||
		source: "/js/Timeline.js",
 | 
			
		||||
		target: "/static/Timeline.js"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		type: "js",
 | 
			
		||||
		source: "/js/Anchor.js",
 | 
			
		||||
		target: "/static/Anchor.js"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		type: "js",
 | 
			
		||||
		source: "/js/chat.js",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ class Anchor extends ElemJS {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	scroll() {
 | 
			
		||||
		console.log("anchor scrolled")
 | 
			
		||||
		// console.log("anchor scrolled")
 | 
			
		||||
		this.element.scrollIntoView({block: "start"})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,7 +57,7 @@ class EventGroup extends ElemJS {
 | 
			
		|||
			this.messages = ejs("div").class("c-message-group__messages").child(
 | 
			
		||||
				ejs("div").class("c-message-group__intro").child(
 | 
			
		||||
					ejs("div").class("c-message-group__name").text(this.data.sender),
 | 
			
		||||
					ejs("div").class("c-message-group__date").text("at 4:20 pm")
 | 
			
		||||
					ejs("div").class("c-message-group__date").text(this.data.origin_server_ts)
 | 
			
		||||
				),
 | 
			
		||||
				...this.list
 | 
			
		||||
			)
 | 
			
		||||
| 
						 | 
				
			
			@ -123,11 +123,13 @@ class Timeline extends Subscribable {
 | 
			
		|||
		this.list = []
 | 
			
		||||
		this.map = new Map()
 | 
			
		||||
		this.reactiveTimeline = new ReactiveTimeline([])
 | 
			
		||||
		this.latest = 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	updateEvents(events) {
 | 
			
		||||
		this.broadcast("beforeChange")
 | 
			
		||||
		for (const eventData of events) {
 | 
			
		||||
			this.latest = Math.max(this.latest, eventData.origin_server_ts)
 | 
			
		||||
			if (this.map.has(eventData.event_id)) {
 | 
			
		||||
				this.map.get(eventData.event_id).update(eventData)
 | 
			
		||||
			} else {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,29 +1,30 @@
 | 
			
		|||
import {q} from "./basic.js"
 | 
			
		||||
import {store} from "./store/store.js"
 | 
			
		||||
import * as lsm from "./lsm.js"
 | 
			
		||||
import {chat} from "./chat.js"
 | 
			
		||||
 | 
			
		||||
let sentIndex = 0
 | 
			
		||||
 | 
			
		||||
const chat = q("#c-chat-textarea")
 | 
			
		||||
const input = q("#c-chat-textarea")
 | 
			
		||||
 | 
			
		||||
chat.addEventListener("keydown", event => {
 | 
			
		||||
input.addEventListener("keydown", event => {
 | 
			
		||||
	if (event.key === "Enter" && !event.shiftKey && !event.ctrlKey) {
 | 
			
		||||
		event.preventDefault()
 | 
			
		||||
		const body = chat.value
 | 
			
		||||
		send(chat.value)
 | 
			
		||||
		chat.value = ""
 | 
			
		||||
		const body = input.value
 | 
			
		||||
		send(input.value)
 | 
			
		||||
		input.value = ""
 | 
			
		||||
		fixHeight()
 | 
			
		||||
	}
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
chat.addEventListener("input", () => {
 | 
			
		||||
input.addEventListener("input", () => {
 | 
			
		||||
	fixHeight()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function fixHeight() {
 | 
			
		||||
	chat.style.height = "0px"
 | 
			
		||||
	console.log(chat.clientHeight, chat.scrollHeight)
 | 
			
		||||
	chat.style.height = (chat.scrollHeight + 1) + "px"
 | 
			
		||||
	input.style.height = "0px"
 | 
			
		||||
	console.log(input.clientHeight, input.scrollHeight)
 | 
			
		||||
	input.style.height = (input.scrollHeight + 1) + "px"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getTxnId() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,6 +36,7 @@ class Group extends ElemJS {
 | 
			
		|||
		super("div")
 | 
			
		||||
 | 
			
		||||
		this.data = data
 | 
			
		||||
		this.order = this.data.order
 | 
			
		||||
 | 
			
		||||
		this.class("c-group")
 | 
			
		||||
		this.child(
 | 
			
		||||
| 
						 | 
				
			
			@ -68,6 +69,7 @@ class Room extends ElemJS {
 | 
			
		|||
		this.id = id
 | 
			
		||||
		this.data = data
 | 
			
		||||
		this.timeline = new Timeline()
 | 
			
		||||
		this.group = null
 | 
			
		||||
 | 
			
		||||
		this.class("c-room")
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -77,6 +79,25 @@ class Room extends ElemJS {
 | 
			
		|||
		this.render()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	get order() {
 | 
			
		||||
		if (this.group) {
 | 
			
		||||
			let chars = 36
 | 
			
		||||
			let total = 0
 | 
			
		||||
			const name = this.getName()
 | 
			
		||||
			for (let i = 0; i < name.length; i++) {
 | 
			
		||||
				const c = name[i]
 | 
			
		||||
				let d = 0
 | 
			
		||||
				if (c >= "A" && c <= "Z") d = c.charCodeAt(0) - 65 + 10
 | 
			
		||||
				else if (c >= "a" && c <= "z") d = c.charCodeAt(0) - 97 + 10
 | 
			
		||||
				else if (c >= "0" && c <= "9") d = +c
 | 
			
		||||
				total += d * chars ** (-i)
 | 
			
		||||
			}
 | 
			
		||||
			return total
 | 
			
		||||
		} else {
 | 
			
		||||
			return -this.timeline.latest
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getName() {
 | 
			
		||||
		let name = this.data.state.events.find(e => e.type === "m.room.name")
 | 
			
		||||
		if (name) {
 | 
			
		||||
| 
						 | 
				
			
			@ -102,8 +123,16 @@ class Room extends ElemJS {
 | 
			
		|||
		return store.directs.has(this.id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setGroup(id) {
 | 
			
		||||
		this.group = id
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getGroup() {
 | 
			
		||||
		return this.isDirect() ? store.groups.get("directs").value() : store.groups.get("channels").value()
 | 
			
		||||
		if (this.group) {
 | 
			
		||||
			return store.groups.get(this.group).value()
 | 
			
		||||
		} else {
 | 
			
		||||
			return this.isDirect() ? store.groups.get("directs").value() : store.groups.get("channels").value()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onClick() {
 | 
			
		||||
| 
						 | 
				
			
			@ -138,10 +167,16 @@ class Rooms extends ElemJS {
 | 
			
		|||
		// store.rooms.subscribe("changeItem", this.render.bind(this))
 | 
			
		||||
		store.activeGroup.subscribe("changeSelf", this.render.bind(this))
 | 
			
		||||
		store.directs.subscribe("changeItem", this.render.bind(this))
 | 
			
		||||
		store.newEvents.subscribe("changeSelf", this.sort.bind(this))
 | 
			
		||||
 | 
			
		||||
		this.render()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort() {
 | 
			
		||||
		store.rooms.sort()
 | 
			
		||||
		this.render()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	askAdd(event, {key, data}) {
 | 
			
		||||
		const room = new Room(key, data)
 | 
			
		||||
		store.rooms.addEnd(key, room)
 | 
			
		||||
| 
						 | 
				
			
			@ -181,74 +216,20 @@ class Groups extends ElemJS {
 | 
			
		|||
		super(q("#c-groups-list"))
 | 
			
		||||
 | 
			
		||||
		store.groups.subscribe("askAdd", this.askAdd.bind(this))
 | 
			
		||||
		store.groups.subscribe("addItem", this.addItem.bind(this))
 | 
			
		||||
		store.groups.subscribe("changeItem", this.render.bind(this))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	askAdd(event, {key, data}) {
 | 
			
		||||
		const group = new Group(key, data)
 | 
			
		||||
		store.groups.addEnd(key, group)
 | 
			
		||||
		store.groups.sort()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	addItem(event, key) {
 | 
			
		||||
		this.child(store.groups.get(key).value())
 | 
			
		||||
	render() {
 | 
			
		||||
		this.clearChildren()
 | 
			
		||||
		store.groups.forEach((key, item) => {
 | 
			
		||||
			this.child(item.value())
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
const groups = new Groups()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
;[
 | 
			
		||||
	{
 | 
			
		||||
		id: "directs",
 | 
			
		||||
		name: "Directs",
 | 
			
		||||
		icon: "/static/directs.svg"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "channels",
 | 
			
		||||
		name: "Channels",
 | 
			
		||||
		icon: "/static/channels.svg"
 | 
			
		||||
	}/*,
 | 
			
		||||
	{
 | 
			
		||||
		id: "123",
 | 
			
		||||
		name: "Fediverse Drama Museum"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "456",
 | 
			
		||||
		name: "Epicord"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "789",
 | 
			
		||||
		name: "Invidious"
 | 
			
		||||
	}*/
 | 
			
		||||
].forEach(data => store.groups.askAdd(data.id, data))
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
;[
 | 
			
		||||
	{id: "001", name: "riley", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "002", name: "BadAtNames", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "003", name: "lynxano", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "004", name: "quarky", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "005", name: "lepton", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "006", name: "ash", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "007", name: "mewmew", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "008", name: "Toniob", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "009", name: "cockandball", group: store.groups.get("directs").value()},
 | 
			
		||||
	{id: "010", name: "Carbon brainstorming", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "011", name: "Bibliogram", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "012", name: "Monsters Inc Debate Hall", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "013", name: "DRB clan", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "014", name: "mettaton simp zone", group: store.groups.get("channels").value()},
 | 
			
		||||
	{id: "015", name: "witches", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "016", name: "snouts", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "017", name: "monads", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "018", name: "radical", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "019", name: "blobcat", group: store.groups.get("123").value()},
 | 
			
		||||
	{id: "020", name: "main", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "021", name: "gaming", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "022", name: "inhalers", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "023", name: "minecraft", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "024", name: "osu", group: store.groups.get("456").value()},
 | 
			
		||||
	{id: "025", name: "covid", group: store.groups.get("456").value()}
 | 
			
		||||
].forEach(data => store.rooms.askAdd(data.id, data))
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
store.activeGroup.set(store.groups.get("directs").value())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,6 +53,15 @@ class SubscribeMapList extends Subscribable {
 | 
			
		|||
		this._add(key, value, false)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort() {
 | 
			
		||||
		this.list.sort((a, b) => {
 | 
			
		||||
			const orderA = this.map.get(a).value().order
 | 
			
		||||
			const orderB = this.map.get(b).value().order
 | 
			
		||||
			return orderA - orderB
 | 
			
		||||
		})
 | 
			
		||||
		this.broadcast("changeItem")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_add(key, value, start) {
 | 
			
		||||
		let s
 | 
			
		||||
		if (this.map.has(key)) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,4 @@
 | 
			
		|||
import {Subscribable} from "./Subscribable.js"
 | 
			
		||||
import {SubscribeMapList} from "./SubscribeMapList.js"
 | 
			
		||||
import {SubscribeSet} from "./SubscribeSet.js"
 | 
			
		||||
import {SubscribeValue} from "./SubscribeValue.js"
 | 
			
		||||
| 
						 | 
				
			
			@ -7,7 +8,8 @@ const store = {
 | 
			
		|||
	rooms: new SubscribeMapList(SubscribeValue),
 | 
			
		||||
	directs: new SubscribeSet(),
 | 
			
		||||
	activeGroup: new SubscribeValue(),
 | 
			
		||||
	activeRoom: new SubscribeValue()
 | 
			
		||||
	activeRoom: new SubscribeValue(),
 | 
			
		||||
	newEvents: new Subscribable()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
window.store = store
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,16 @@ import * as lsm from "../lsm.js"
 | 
			
		|||
 | 
			
		||||
let lastBatch = null
 | 
			
		||||
 | 
			
		||||
function resolveMxc(url, size, method) {
 | 
			
		||||
	const [server, id] = url.match(/^mxc:\/\/([^/]+)\/(.*)/).slice(1)
 | 
			
		||||
	if (size && method) {
 | 
			
		||||
		return `${lsm.get("domain")}/_matrix/media/r0/thumbnail/${server}/${id}?width=${size}&height=${size}&method=${method}`
 | 
			
		||||
	} else {
 | 
			
		||||
		return `${lsm.get("domain")}/_matrix/media/r0/download/${server}/${id}`
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function sync() {
 | 
			
		||||
	const url = new URL(`${lsm.get("domain")}/_matrix/client/r0/sync`)
 | 
			
		||||
	url.searchParams.append("access_token", lsm.get("access_token"))
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +45,8 @@ function sync() {
 | 
			
		|||
 | 
			
		||||
function manageSync(root) {
 | 
			
		||||
	try {
 | 
			
		||||
		let newEvents = false
 | 
			
		||||
 | 
			
		||||
		// set up directs
 | 
			
		||||
		const directs = root.account_data.events.find(e => e.type === "m.direct")
 | 
			
		||||
		if (directs) {
 | 
			
		||||
| 
						 | 
				
			
			@ -49,8 +61,45 @@ function manageSync(root) {
 | 
			
		|||
				store.rooms.askAdd(id, room)
 | 
			
		||||
			}
 | 
			
		||||
			const timeline = store.rooms.get(id).value().timeline
 | 
			
		||||
			if (room.timeline.events.length) newEvents = true
 | 
			
		||||
			timeline.updateEvents(room.timeline.events)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		// set up groups
 | 
			
		||||
		Promise.all(
 | 
			
		||||
			Object.keys(root.groups.join).map(id => {
 | 
			
		||||
				if (!store.groups.has(id)) {
 | 
			
		||||
					return Promise.all(["profile", "rooms"].map(path => {
 | 
			
		||||
						const url = new URL(`${lsm.get("domain")}/_matrix/client/r0/groups/${id}/${path}`)
 | 
			
		||||
						url.searchParams.append("access_token", lsm.get("access_token"))
 | 
			
		||||
						return fetch(url.toString()).then(res => res.json())
 | 
			
		||||
					})).then(([profile, rooms]) => {
 | 
			
		||||
						rooms = rooms.chunk
 | 
			
		||||
						let order = 999
 | 
			
		||||
						let orderEvent = root.account_data.events.find(e => e.type === "im.vector.web.tag_ordering")
 | 
			
		||||
						if (orderEvent) {
 | 
			
		||||
							if (orderEvent.content.tags.includes(id)) {
 | 
			
		||||
								order = orderEvent.content.tags.indexOf(id)
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						const data = {
 | 
			
		||||
							name: profile.name,
 | 
			
		||||
							icon: resolveMxc(profile.avatar_url, 96, "crop"),
 | 
			
		||||
							order
 | 
			
		||||
						}
 | 
			
		||||
						store.groups.askAdd(id, data)
 | 
			
		||||
						rooms.forEach(groupRoom => {
 | 
			
		||||
							if (store.rooms.has(groupRoom.room_id)) {
 | 
			
		||||
								store.rooms.get(groupRoom.room_id).value().setGroup(id)
 | 
			
		||||
							}
 | 
			
		||||
						})
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
		).then(() => {
 | 
			
		||||
			store.rooms.sort()
 | 
			
		||||
		})
 | 
			
		||||
		if (newEvents) store.newEvents.broadcast("changeSelf")
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
		console.error(root)
 | 
			
		||||
		throw e
 | 
			
		||||
| 
						 | 
				
			
			@ -61,4 +110,21 @@ function syncLoop() {
 | 
			
		|||
	return sync().then(manageSync).then(syncLoop)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
;[
 | 
			
		||||
	{
 | 
			
		||||
		id: "directs",
 | 
			
		||||
		name: "Directs",
 | 
			
		||||
		icon: "/static/directs.svg",
 | 
			
		||||
		order: -2
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "channels",
 | 
			
		||||
		name: "Channels",
 | 
			
		||||
		icon: "/static/channels.svg",
 | 
			
		||||
		order: -1
 | 
			
		||||
	}
 | 
			
		||||
].forEach(data => store.groups.askAdd(data.id, data))
 | 
			
		||||
 | 
			
		||||
store.activeGroup.set(store.groups.get("directs").value())
 | 
			
		||||
 | 
			
		||||
syncLoop()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,7 @@ $out-width: $base-width + rooms.$list-width
 | 
			
		|||
  position: relative
 | 
			
		||||
  width: $base-width
 | 
			
		||||
  flex-shrink: 0
 | 
			
		||||
  font-size: 24px
 | 
			
		||||
  font-size: 22px
 | 
			
		||||
  font-weight: 500
 | 
			
		||||
 | 
			
		||||
  &__display
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue