forked from cadence/Carbon
		
	Actually delete build directory
This commit is contained in:
		
							parent
							
								
									4855b2c6a0
								
							
						
					
					
						commit
						25c3fe17b4
					
				
					 21 changed files with 0 additions and 1710 deletions
				
			
		| 
						 | 
					@ -1,35 +0,0 @@
 | 
				
			||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html>
 | 
					 | 
				
			||||||
  <head>
 | 
					 | 
				
			||||||
    <meta charset="utf-8">
 | 
					 | 
				
			||||||
    <!-- var static = !{JSON.stringify([...static.entries()].reduce((a, c) => (a[c[0]] = getRelative(c[1]), a), {}))}-->
 | 
					 | 
				
			||||||
    <script>var staticFiles = new Map([["/js/basic.js","static/basic.js?static=4212436742"],["/js/groups.js","static/groups.js?static=551ad1c247"],["/js/chat-input.js","static/chat-input.js?static=556a7b79a3"],["/js/room-picker.js","static/room-picker.js?static=d5e34a8051"],["/js/store/store.js","static/store/store.js?static=ce3066287b"],["/js/store/Subscribable.js","static/store/Subscribable.js?static=19b7e44aa6"],["/js/store/SubscribeValue.js","static/store/SubscribeValue.js?static=215b6a5099"],["/js/store/SubscribeMapList.js","static/store/SubscribeMapList.js?static=b2732c5460"],["/js/store/SubscribeSet.js","static/store/SubscribeSet.js?static=39a1c0a2a4"],["/js/sync/sync.js","static/sync/sync.js?static=fc5795a5b8"],["/js/lsm.js","static/lsm.js?static=aed2c7ca35"],["/js/Timeline.js","static/Timeline.js?static=1393b78916"],["/js/Anchor.js","static/Anchor.js?static=85efd1a836"],["/js/chat.js","static/chat.js?static=b01b3716ff"],["/js/functions.js","static/functions.js?static=e3784c70ce"],["/assets/fonts/whitney-500.woff","static/whitney-500.woff?static=ba33ed18fe"],["/assets/fonts/whitney-400.woff","static/whitney-400.woff?static=0f823bc4b5"],["/assets/icons/directs.svg","static/directs.svg?static=2555ccd447"],["/assets/icons/channels.svg","static/channels.svg?static=8389f20610"],["/assets/icons/join-event.svg","static/join-event.svg?static=761c23a0ef"],["/sass/main.sass","static/main.css?static=e9db1a3439"]])
 | 
					 | 
				
			||||||
    </script>
 | 
					 | 
				
			||||||
    <link rel="stylesheet" type="text/css" href="static/main.css?static=e9db1a3439">
 | 
					 | 
				
			||||||
    <script type="module" src="static/groups.js?static=551ad1c247"></script>
 | 
					 | 
				
			||||||
    <script type="module" src="static/chat-input.js?static=556a7b79a3"></script>
 | 
					 | 
				
			||||||
    <script type="module" src="static/room-picker.js?static=d5e34a8051"></script>
 | 
					 | 
				
			||||||
    <script type="module" src="static/sync/sync.js?static=fc5795a5b8"></script>
 | 
					 | 
				
			||||||
    <script type="module" src="static/chat.js?static=b01b3716ff"></script>
 | 
					 | 
				
			||||||
    <title>Carbon</title>
 | 
					 | 
				
			||||||
  </head>
 | 
					 | 
				
			||||||
  <body>
 | 
					 | 
				
			||||||
    <main class="main">
 | 
					 | 
				
			||||||
      <div class="c-groups">
 | 
					 | 
				
			||||||
        <div class="c-groups__display" id="c-groups-display">
 | 
					 | 
				
			||||||
          <div class="c-group-marker" id="c-group-marker"></div>
 | 
					 | 
				
			||||||
          <div class="c-groups__container" id="c-groups-list"></div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <div class="c-rooms" id="c-rooms"></div>
 | 
					 | 
				
			||||||
      <div class="c-chat">
 | 
					 | 
				
			||||||
        <div class="c-chat__messages" id="c-chat-messages">
 | 
					 | 
				
			||||||
          <div class="c-chat__inner" id="c-chat"></div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="c-chat-input">
 | 
					 | 
				
			||||||
          <textarea class="c-chat-input__textarea" placeholder="Send a message..." autocomplete="off" id="c-chat-textarea"></textarea>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </main>
 | 
					 | 
				
			||||||
  </body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,15 +0,0 @@
 | 
				
			||||||
import {ElemJS} from "../static/basic.js?static=4212436742"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Anchor extends ElemJS {
 | 
					 | 
				
			||||||
	constructor() {
 | 
					 | 
				
			||||||
		super("div")
 | 
					 | 
				
			||||||
		this.class("c-anchor")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	scroll() {
 | 
					 | 
				
			||||||
		// console.log("anchor scrolled")
 | 
					 | 
				
			||||||
		this.element.scrollIntoView({block: "start"})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {Anchor}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,282 +0,0 @@
 | 
				
			||||||
import {ElemJS, ejs} from "../static/basic.js?static=4212436742"
 | 
					 | 
				
			||||||
import {Subscribable} from "../static/store/Subscribable.js?static=19b7e44aa6"
 | 
					 | 
				
			||||||
import {store} from "../static/store/store.js?static=ce3066287b"
 | 
					 | 
				
			||||||
import {Anchor} from "../static/Anchor.js?static=85efd1a836"
 | 
					 | 
				
			||||||
import * as lsm from "../static/lsm.js?static=aed2c7ca35"
 | 
					 | 
				
			||||||
import {resolveMxc} from "../static/functions.js?static=e3784c70ce"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const dateFormatter = Intl.DateTimeFormat("default", {hour: "numeric", minute: "numeric", day: "numeric", month: "short", year: "numeric"})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let sentIndex = 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getTxnId() {
 | 
					 | 
				
			||||||
	return Date.now() + (sentIndex++)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function eventSearch(list, event, min = 0, max = -1) {
 | 
					 | 
				
			||||||
	if (list.length === 0) return {success: false, i: 0}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (max === -1) max = list.length - 1
 | 
					 | 
				
			||||||
	let mid = Math.floor((max + min) / 2)
 | 
					 | 
				
			||||||
	// success condition
 | 
					 | 
				
			||||||
	if (list[mid] && list[mid].data.event_id === event.data.event_id) return {success: true, i: mid}
 | 
					 | 
				
			||||||
	// failed condition
 | 
					 | 
				
			||||||
	if (min >= max) {
 | 
					 | 
				
			||||||
		while (mid !== -1 && (!list[mid] || list[mid].data.origin_server_ts > event.data.origin_server_ts)) mid--
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			success: false,
 | 
					 | 
				
			||||||
			i: mid + 1
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	// recurse (below)
 | 
					 | 
				
			||||||
	if (list[mid].data.origin_server_ts > event.data.origin_server_ts) return eventSearch(list, event, min, mid-1)
 | 
					 | 
				
			||||||
	// recurse (above)
 | 
					 | 
				
			||||||
	else return eventSearch(list, event, mid+1, max)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Event extends ElemJS {
 | 
					 | 
				
			||||||
	constructor(data) {
 | 
					 | 
				
			||||||
		super("div")
 | 
					 | 
				
			||||||
		this.class("c-message")
 | 
					 | 
				
			||||||
		this.data = null
 | 
					 | 
				
			||||||
		this.group = null
 | 
					 | 
				
			||||||
		this.update(data)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	setGroup(group) {
 | 
					 | 
				
			||||||
		this.group = group
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	update(data) {
 | 
					 | 
				
			||||||
		this.data = data
 | 
					 | 
				
			||||||
		this.render()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	removeEvent() {
 | 
					 | 
				
			||||||
		if (this.group) this.group.removeEvent(this)
 | 
					 | 
				
			||||||
		else this.remove()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	render() {
 | 
					 | 
				
			||||||
		this.element.classList[this.data.pending ? "add" : "remove"]("c-message--pending")
 | 
					 | 
				
			||||||
		this.text(this.data.content.body)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Sender {
 | 
					 | 
				
			||||||
	constructor(roomID, mxid) {
 | 
					 | 
				
			||||||
		this.sender = store.rooms.get(roomID).value().members.get(mxid)
 | 
					 | 
				
			||||||
		this.sender.subscribe("changeSelf", this.update.bind(this))
 | 
					 | 
				
			||||||
		this.name = new ElemJS("div").class("c-message-group__name")
 | 
					 | 
				
			||||||
		this.avatar = new ElemJS("div").class("c-message-group__avatar")
 | 
					 | 
				
			||||||
		this.update()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	update() {
 | 
					 | 
				
			||||||
		if (this.sender.exists()) {
 | 
					 | 
				
			||||||
			// name
 | 
					 | 
				
			||||||
			this.name.text(this.sender.value().content.displayname)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// avatar
 | 
					 | 
				
			||||||
			this.avatar.clearChildren()
 | 
					 | 
				
			||||||
			if (this.sender.value().content.avatar_url) {
 | 
					 | 
				
			||||||
				this.avatar.child(
 | 
					 | 
				
			||||||
					ejs("img").class("c-message-group__icon").attribute("src", resolveMxc(this.sender.value().content.avatar_url, 96, "crop"))
 | 
					 | 
				
			||||||
				)
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				this.avatar.child(
 | 
					 | 
				
			||||||
					ejs("div").class("c-message-group__icon", "c-message-group__icon--no-icon")
 | 
					 | 
				
			||||||
				)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class EventGroup extends ElemJS {
 | 
					 | 
				
			||||||
	constructor(reactive, list) {
 | 
					 | 
				
			||||||
		super("div")
 | 
					 | 
				
			||||||
		this.class("c-message-group")
 | 
					 | 
				
			||||||
		this.reactive = reactive
 | 
					 | 
				
			||||||
		this.list = list
 | 
					 | 
				
			||||||
		this.data = {
 | 
					 | 
				
			||||||
			sender: list[0].data.sender,
 | 
					 | 
				
			||||||
			origin_server_ts: list[0].data.origin_server_ts
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		this.sender = new Sender(this.reactive.id, this.data.sender)
 | 
					 | 
				
			||||||
		this.child(
 | 
					 | 
				
			||||||
			this.sender.avatar,
 | 
					 | 
				
			||||||
			this.messages = ejs("div").class("c-message-group__messages").child(
 | 
					 | 
				
			||||||
				ejs("div").class("c-message-group__intro").child(
 | 
					 | 
				
			||||||
					this.sender.name,
 | 
					 | 
				
			||||||
					ejs("div").class("c-message-group__date").text(dateFormatter.format(this.data.origin_server_ts))
 | 
					 | 
				
			||||||
				),
 | 
					 | 
				
			||||||
				...this.list
 | 
					 | 
				
			||||||
			)
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	addEvent(event) {
 | 
					 | 
				
			||||||
		const index = eventSearch(this.list, event).i
 | 
					 | 
				
			||||||
		event.setGroup(this)
 | 
					 | 
				
			||||||
		this.list.splice(index, 0, event)
 | 
					 | 
				
			||||||
		this.messages.childAt(index + 1, event)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	removeEvent(event) {
 | 
					 | 
				
			||||||
		const search = eventSearch(this.list, event)
 | 
					 | 
				
			||||||
		if (!search.success) throw new Error(`Event ${event.data.event_id} not found in this group`)
 | 
					 | 
				
			||||||
		const index = search.i
 | 
					 | 
				
			||||||
		// actually remove the event
 | 
					 | 
				
			||||||
		this.list.splice(index, 1)
 | 
					 | 
				
			||||||
		event.remove() // should get everything else
 | 
					 | 
				
			||||||
		if (this.list.length === 0) this.reactive.removeGroup(this)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ReactiveTimeline extends ElemJS {
 | 
					 | 
				
			||||||
	constructor(id, list) {
 | 
					 | 
				
			||||||
		super("div")
 | 
					 | 
				
			||||||
		this.class("c-event-groups")
 | 
					 | 
				
			||||||
		this.id = id
 | 
					 | 
				
			||||||
		this.list = list
 | 
					 | 
				
			||||||
		this.render()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	addEvent(event) {
 | 
					 | 
				
			||||||
		const search = eventSearch(this.list, event)
 | 
					 | 
				
			||||||
		// console.log(search, this.list.map(l => l.data.sender), event.data)
 | 
					 | 
				
			||||||
		if (!search.success && search.i >= 1) this.tryAddGroups(event, [search.i-1, search.i])
 | 
					 | 
				
			||||||
		else this.tryAddGroups(event, [search.i])
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	tryAddGroups(event, indices) {
 | 
					 | 
				
			||||||
		const success = indices.some(i => {
 | 
					 | 
				
			||||||
			if (!this.list[i]) {
 | 
					 | 
				
			||||||
				// if (printed++ < 100) console.log("tryadd success, created group")
 | 
					 | 
				
			||||||
				const group = new EventGroup(this, [event])
 | 
					 | 
				
			||||||
				this.list.splice(i, 0, group)
 | 
					 | 
				
			||||||
				this.childAt(i, group)
 | 
					 | 
				
			||||||
				return true
 | 
					 | 
				
			||||||
			} else if (this.list[i] && this.list[i].data.sender === event.data.sender) {
 | 
					 | 
				
			||||||
				// if (printed++ < 100) console.log("tryadd success, using existing group")
 | 
					 | 
				
			||||||
				this.list[i].addEvent(event)
 | 
					 | 
				
			||||||
				return true
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
		if (!success) console.log("tryadd failure", indices, this.list.map(l => l.data.sender), event.data)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	removeGroup(group) {
 | 
					 | 
				
			||||||
		const index = this.list.indexOf(group)
 | 
					 | 
				
			||||||
		this.list.splice(index, 1)
 | 
					 | 
				
			||||||
		group.remove() // should get everything else
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	render() {
 | 
					 | 
				
			||||||
		this.clearChildren()
 | 
					 | 
				
			||||||
		this.list.forEach(group => this.child(group))
 | 
					 | 
				
			||||||
		this.anchor = new Anchor()
 | 
					 | 
				
			||||||
		this.child(this.anchor)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Timeline extends Subscribable {
 | 
					 | 
				
			||||||
	constructor(id) {
 | 
					 | 
				
			||||||
		super()
 | 
					 | 
				
			||||||
		Object.assign(this.events, {
 | 
					 | 
				
			||||||
			beforeChange: [],
 | 
					 | 
				
			||||||
			afterChange: []
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
		Object.assign(this.eventDeps, {
 | 
					 | 
				
			||||||
			beforeChange: [],
 | 
					 | 
				
			||||||
			afterChange: []
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
		this.id = id
 | 
					 | 
				
			||||||
		this.list = []
 | 
					 | 
				
			||||||
		this.map = new Map()
 | 
					 | 
				
			||||||
		this.reactiveTimeline = new ReactiveTimeline(id, [])
 | 
					 | 
				
			||||||
		this.latest = 0
 | 
					 | 
				
			||||||
		this.pending = new Set()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	updateEvents(events) {
 | 
					 | 
				
			||||||
		this.broadcast("beforeChange")
 | 
					 | 
				
			||||||
		for (const eventData of events) {
 | 
					 | 
				
			||||||
			this.latest = Math.max(this.latest, eventData.origin_server_ts)
 | 
					 | 
				
			||||||
			let id = eventData.event_id
 | 
					 | 
				
			||||||
			if (eventData.sender === lsm.get("mx_user_id") && eventData.content && this.pending.has(eventData.content["chat.carbon.message.pending_id"])) {
 | 
					 | 
				
			||||||
				id = eventData.content["chat.carbon.message.pending_id"]
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if (this.map.has(id)) {
 | 
					 | 
				
			||||||
				this.map.get(id).update(eventData)
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				const event = new Event(eventData)
 | 
					 | 
				
			||||||
				this.map.set(id, event)
 | 
					 | 
				
			||||||
				this.reactiveTimeline.addEvent(event)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		this.broadcast("afterChange")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	removeEvent(id) {
 | 
					 | 
				
			||||||
		if (!this.map.has(id)) throw new Error(`Tried to delete event ID ${id} which does not exist`)
 | 
					 | 
				
			||||||
		this.map.get(id).removeEvent()
 | 
					 | 
				
			||||||
		this.map.delete(id)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	getTimeline() {
 | 
					 | 
				
			||||||
		return this.reactiveTimeline
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	send(body) {
 | 
					 | 
				
			||||||
		const tx = getTxnId()
 | 
					 | 
				
			||||||
		const id = `pending$${tx}`
 | 
					 | 
				
			||||||
		this.pending.add(id)
 | 
					 | 
				
			||||||
		const content = {
 | 
					 | 
				
			||||||
			msgtype: "m.text",
 | 
					 | 
				
			||||||
			body,
 | 
					 | 
				
			||||||
			"chat.carbon.message.pending_id": id
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		const fakeEvent = {
 | 
					 | 
				
			||||||
			origin_server_ts: Date.now(),
 | 
					 | 
				
			||||||
			event_id: id,
 | 
					 | 
				
			||||||
			sender: lsm.get("mx_user_id"),
 | 
					 | 
				
			||||||
			content,
 | 
					 | 
				
			||||||
			pending: true
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		this.updateEvents([fakeEvent])
 | 
					 | 
				
			||||||
		return fetch(`${lsm.get("domain")}/_matrix/client/r0/rooms/${this.id}/send/m.room.message/${tx}?access_token=${lsm.get("access_token")}`, {
 | 
					 | 
				
			||||||
			method: "PUT",
 | 
					 | 
				
			||||||
			body: JSON.stringify(content),
 | 
					 | 
				
			||||||
			headers: {
 | 
					 | 
				
			||||||
				"Content-Type": "application/json"
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		})/*.then(() => {
 | 
					 | 
				
			||||||
			const subscription = () => {
 | 
					 | 
				
			||||||
				this.removeEvent(id)
 | 
					 | 
				
			||||||
				this.unsubscribe("afterChange", subscription)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			this.subscribe("afterChange", subscription)
 | 
					 | 
				
			||||||
		})*/
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
	getGroupedEvents() {
 | 
					 | 
				
			||||||
		let currentSender = Symbol("N/A")
 | 
					 | 
				
			||||||
		let groups = []
 | 
					 | 
				
			||||||
		let currentGroup = []
 | 
					 | 
				
			||||||
		for (const event of this.list) {
 | 
					 | 
				
			||||||
			if (event.sender === currentSender) {
 | 
					 | 
				
			||||||
				currentGroup.push(event)
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				if (currentGroup.length) groups.push(currentGroup)
 | 
					 | 
				
			||||||
				currentGroup = [event]
 | 
					 | 
				
			||||||
				currentSender = event.sender
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if (currentGroup.length) groups.push(currentGroup)
 | 
					 | 
				
			||||||
		return groups
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	*/
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {Timeline}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,160 +0,0 @@
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Shortcut for querySelector.
 | 
					 | 
				
			||||||
 * @template {HTMLElement} T
 | 
					 | 
				
			||||||
 * @returns {T}
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
const q = s => document.querySelector(s);
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Shortcut for querySelectorAll.
 | 
					 | 
				
			||||||
 * @template {HTMLElement} T
 | 
					 | 
				
			||||||
 * @returns {T[]}
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
const qa = s => document.querySelectorAll(s);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * An easier, chainable, object-oriented way to create and update elements
 | 
					 | 
				
			||||||
 * and children according to related data. Subclass ElemJS to create useful,
 | 
					 | 
				
			||||||
 * advanced data managers, or just use it inline to quickly make a custom element.
 | 
					 | 
				
			||||||
 * Created by Cadence Ember in 2018.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
class ElemJS {
 | 
					 | 
				
			||||||
	constructor(type) {
 | 
					 | 
				
			||||||
		if (type instanceof HTMLElement) {
 | 
					 | 
				
			||||||
			// If passed an existing element, bind to it
 | 
					 | 
				
			||||||
			this.bind(type);
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			// Otherwise, create a new detached element to bind to
 | 
					 | 
				
			||||||
			this.bind(document.createElement(type));
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		this.children = [];
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Bind this construct to an existing element on the page. */
 | 
					 | 
				
			||||||
	bind(element) {
 | 
					 | 
				
			||||||
		this.element = element;
 | 
					 | 
				
			||||||
		this.element.js = this;
 | 
					 | 
				
			||||||
		return this;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Add a class. */
 | 
					 | 
				
			||||||
	class() {
 | 
					 | 
				
			||||||
		for (let name of arguments) if (name) this.element.classList.add(name);
 | 
					 | 
				
			||||||
		return this;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Remove a class. */
 | 
					 | 
				
			||||||
	removeClass() {
 | 
					 | 
				
			||||||
		for (let name of arguments) if (name) this.element.classList.remove(name);
 | 
					 | 
				
			||||||
		return this;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Set a JS property on the element. */
 | 
					 | 
				
			||||||
	direct(name, value) {
 | 
					 | 
				
			||||||
		if (name) this.element[name] = value;
 | 
					 | 
				
			||||||
		return this;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Set an attribute on the element. */
 | 
					 | 
				
			||||||
	attribute(name, value) {
 | 
					 | 
				
			||||||
		if (name) this.element.setAttribute(name, value != undefined ? value : "");
 | 
					 | 
				
			||||||
		return this;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Set a style on the element. */
 | 
					 | 
				
			||||||
	style(name, value) {
 | 
					 | 
				
			||||||
		if (name) this.element.style[name] = value;
 | 
					 | 
				
			||||||
		return this;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Set the element's ID. */
 | 
					 | 
				
			||||||
	id(name) {
 | 
					 | 
				
			||||||
		if (name) this.element.id = name;
 | 
					 | 
				
			||||||
		return this;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Attach a callback function to an event on the element. */
 | 
					 | 
				
			||||||
	on(name, callback) {
 | 
					 | 
				
			||||||
		this.element.addEventListener(name, callback);
 | 
					 | 
				
			||||||
		return this;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Set the element's text. */
 | 
					 | 
				
			||||||
	text(name) {
 | 
					 | 
				
			||||||
		this.element.innerText = name;
 | 
					 | 
				
			||||||
		return this;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Create a text node and add it to the element. */
 | 
					 | 
				
			||||||
	addText(name) {
 | 
					 | 
				
			||||||
		const node = document.createTextNode(name);
 | 
					 | 
				
			||||||
		this.element.appendChild(node);
 | 
					 | 
				
			||||||
		return this;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Set the element's HTML content. */
 | 
					 | 
				
			||||||
	html(name) {
 | 
					 | 
				
			||||||
		this.element.innerHTML = name;
 | 
					 | 
				
			||||||
		return this;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Add children to the element.
 | 
					 | 
				
			||||||
	 * Children can either be an instance of ElemJS, in
 | 
					 | 
				
			||||||
	 * which case the element will be appended as a child,
 | 
					 | 
				
			||||||
	 * or a string, in which case the string will be added as a text node.
 | 
					 | 
				
			||||||
	 * Each child should be a parameter to this method.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	child(...children) {
 | 
					 | 
				
			||||||
		for (const toAdd of children) {
 | 
					 | 
				
			||||||
			if (typeof toAdd === "object" && toAdd !== null) {
 | 
					 | 
				
			||||||
				// Should be an instance of ElemJS, so append as child
 | 
					 | 
				
			||||||
				toAdd.parent = this;
 | 
					 | 
				
			||||||
				this.element.appendChild(toAdd.element);
 | 
					 | 
				
			||||||
				this.children.push(toAdd);
 | 
					 | 
				
			||||||
			} else if (typeof toAdd === "string") {
 | 
					 | 
				
			||||||
				// Is a string, so add as text node
 | 
					 | 
				
			||||||
				this.addText(toAdd);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return this;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	childAt(index, toAdd) {
 | 
					 | 
				
			||||||
		if (typeof toAdd === "object" && toAdd !== null) {
 | 
					 | 
				
			||||||
			toAdd.parent = this;
 | 
					 | 
				
			||||||
			this.children.splice(index, 0, toAdd);
 | 
					 | 
				
			||||||
			if (index >= this.element.childNodes.length) {
 | 
					 | 
				
			||||||
				this.element.appendChild(toAdd.element)
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				this.element.childNodes[index].insertAdjacentElement("beforebegin", toAdd.element)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Remove all children from the element.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	clearChildren() {
 | 
					 | 
				
			||||||
		this.children.length = 0;
 | 
					 | 
				
			||||||
		while (this.element.lastChild) this.element.removeChild(this.element.lastChild);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Remove this element.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	remove() {
 | 
					 | 
				
			||||||
		let index;
 | 
					 | 
				
			||||||
		if (this.parent && (index = this.parent.children.indexOf(this)) !== -1) {
 | 
					 | 
				
			||||||
			this.parent.children.splice(index, 1);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		this.parent = null;
 | 
					 | 
				
			||||||
		this.element.remove();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** Shortcut for `new ElemJS`. */
 | 
					 | 
				
			||||||
function ejs(tag) {
 | 
					 | 
				
			||||||
	return new ElemJS(tag);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {q, qa, ElemJS, ejs}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,83 +0,0 @@
 | 
				
			||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
					 | 
				
			||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<svg
 | 
					 | 
				
			||||||
   xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
					 | 
				
			||||||
   xmlns:cc="http://creativecommons.org/ns#"
 | 
					 | 
				
			||||||
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
					 | 
				
			||||||
   xmlns:svg="http://www.w3.org/2000/svg"
 | 
					 | 
				
			||||||
   xmlns="http://www.w3.org/2000/svg"
 | 
					 | 
				
			||||||
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
					 | 
				
			||||||
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
					 | 
				
			||||||
   width="48.000004"
 | 
					 | 
				
			||||||
   height="48.000004"
 | 
					 | 
				
			||||||
   viewBox="0 0 12.700001 12.700001"
 | 
					 | 
				
			||||||
   version="1.1"
 | 
					 | 
				
			||||||
   id="svg27"
 | 
					 | 
				
			||||||
   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
 | 
					 | 
				
			||||||
   sodipodi:docname="channels.svg">
 | 
					 | 
				
			||||||
  <defs
 | 
					 | 
				
			||||||
     id="defs21" />
 | 
					 | 
				
			||||||
  <sodipodi:namedview
 | 
					 | 
				
			||||||
     id="base"
 | 
					 | 
				
			||||||
     pagecolor="#ffffff"
 | 
					 | 
				
			||||||
     bordercolor="#666666"
 | 
					 | 
				
			||||||
     borderopacity="1.0"
 | 
					 | 
				
			||||||
     inkscape:pageopacity="0.0"
 | 
					 | 
				
			||||||
     inkscape:pageshadow="2"
 | 
					 | 
				
			||||||
     inkscape:zoom="1"
 | 
					 | 
				
			||||||
     inkscape:cx="25.083567"
 | 
					 | 
				
			||||||
     inkscape:cy="22.080215"
 | 
					 | 
				
			||||||
     inkscape:document-units="px"
 | 
					 | 
				
			||||||
     inkscape:current-layer="layer1"
 | 
					 | 
				
			||||||
     showgrid="true"
 | 
					 | 
				
			||||||
     units="px"
 | 
					 | 
				
			||||||
     inkscape:window-width="1440"
 | 
					 | 
				
			||||||
     inkscape:window-height="879"
 | 
					 | 
				
			||||||
     inkscape:window-x="0"
 | 
					 | 
				
			||||||
     inkscape:window-y="0"
 | 
					 | 
				
			||||||
     inkscape:window-maximized="1"
 | 
					 | 
				
			||||||
     showborder="false">
 | 
					 | 
				
			||||||
    <inkscape:grid
 | 
					 | 
				
			||||||
       type="xygrid"
 | 
					 | 
				
			||||||
       id="grid824" />
 | 
					 | 
				
			||||||
  </sodipodi:namedview>
 | 
					 | 
				
			||||||
  <metadata
 | 
					 | 
				
			||||||
     id="metadata24">
 | 
					 | 
				
			||||||
    <rdf:RDF>
 | 
					 | 
				
			||||||
      <cc:Work
 | 
					 | 
				
			||||||
         rdf:about="">
 | 
					 | 
				
			||||||
        <dc:format>image/svg+xml</dc:format>
 | 
					 | 
				
			||||||
        <dc:type
 | 
					 | 
				
			||||||
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
 | 
					 | 
				
			||||||
        <dc:title></dc:title>
 | 
					 | 
				
			||||||
      </cc:Work>
 | 
					 | 
				
			||||||
    </rdf:RDF>
 | 
					 | 
				
			||||||
  </metadata>
 | 
					 | 
				
			||||||
  <g
 | 
					 | 
				
			||||||
     inkscape:label="Layer 1"
 | 
					 | 
				
			||||||
     inkscape:groupmode="layer"
 | 
					 | 
				
			||||||
     id="layer1"
 | 
					 | 
				
			||||||
     transform="translate(36.739286,-225.97828)">
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
       style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#e2e2e2;stroke-width:1.05833333;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke markers fill"
 | 
					 | 
				
			||||||
       d="m -31.976786,228.62411 v 7.40833"
 | 
					 | 
				
			||||||
       id="path864"
 | 
					 | 
				
			||||||
       inkscape:connector-curvature="0" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
       inkscape:connector-curvature="0"
 | 
					 | 
				
			||||||
       id="path866"
 | 
					 | 
				
			||||||
       d="m -28.801786,228.62412 v 7.40833"
 | 
					 | 
				
			||||||
       style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#e2e2e2;stroke-width:1.05833333;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke markers fill" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
       inkscape:connector-curvature="0"
 | 
					 | 
				
			||||||
       id="path905"
 | 
					 | 
				
			||||||
       d="m -26.685116,230.74078 h -7.40833"
 | 
					 | 
				
			||||||
       style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#e2e2e2;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke markers fill" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
       style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#e2e2e2;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke markers fill"
 | 
					 | 
				
			||||||
       d="m -26.685126,233.91578 h -7.40833"
 | 
					 | 
				
			||||||
       id="path907"
 | 
					 | 
				
			||||||
       inkscape:connector-curvature="0" />
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 3.2 KiB  | 
| 
						 | 
					@ -1,37 +0,0 @@
 | 
				
			||||||
import {q} from "../static/basic.js?static=4212436742"
 | 
					 | 
				
			||||||
import {store} from "../static/store/store.js?static=ce3066287b"
 | 
					 | 
				
			||||||
import * as lsm from "../static/lsm.js?static=aed2c7ca35"
 | 
					 | 
				
			||||||
import {chat} from "../static/chat.js?static=b01b3716ff"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const input = q("#c-chat-textarea")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
store.activeRoom.subscribe("changeSelf", () => {
 | 
					 | 
				
			||||||
	if (store.activeRoom.exists()) {
 | 
					 | 
				
			||||||
		input.focus()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
input.addEventListener("keydown", event => {
 | 
					 | 
				
			||||||
	if (event.key === "Enter" && !event.shiftKey && !event.ctrlKey) {
 | 
					 | 
				
			||||||
		event.preventDefault()
 | 
					 | 
				
			||||||
		const body = input.value
 | 
					 | 
				
			||||||
		send(input.value)
 | 
					 | 
				
			||||||
		input.value = ""
 | 
					 | 
				
			||||||
		fixHeight()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
input.addEventListener("input", () => {
 | 
					 | 
				
			||||||
	fixHeight()
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function fixHeight() {
 | 
					 | 
				
			||||||
	input.style.height = "0px"
 | 
					 | 
				
			||||||
	// console.log(input.clientHeight, input.scrollHeight)
 | 
					 | 
				
			||||||
	input.style.height = (input.scrollHeight + 1) + "px"
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function send(body) {
 | 
					 | 
				
			||||||
	if (!store.activeRoom.exists()) return
 | 
					 | 
				
			||||||
	return store.activeRoom.value().timeline.send(body)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,65 +0,0 @@
 | 
				
			||||||
import {ElemJS, q, ejs} from "../static/basic.js?static=4212436742"
 | 
					 | 
				
			||||||
import {store} from "../static/store/store.js?static=ce3066287b"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const chatMessages = q("#c-chat-messages")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Chat extends ElemJS {
 | 
					 | 
				
			||||||
	constructor() {
 | 
					 | 
				
			||||||
		super(q("#c-chat"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.removableSubscriptions = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		store.activeRoom.subscribe("changeSelf", this.changeRoom.bind(this))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.render()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	unsubscribe() {
 | 
					 | 
				
			||||||
		this.removableSubscriptions.forEach(({name, target, subscription}) => {
 | 
					 | 
				
			||||||
			target.unsubscribe(name, subscription)
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
		this.removableSubscriptions.length = 0
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	changeRoom() {
 | 
					 | 
				
			||||||
		// disconnect from the previous room
 | 
					 | 
				
			||||||
		this.unsubscribe()
 | 
					 | 
				
			||||||
		// connect to the new room's timeline updater
 | 
					 | 
				
			||||||
		if (store.activeRoom.exists()) {
 | 
					 | 
				
			||||||
			const timeline = store.activeRoom.value().timeline
 | 
					 | 
				
			||||||
			const subscription = () => {
 | 
					 | 
				
			||||||
				// scroll anchor does not work if the timeline is scrolled to the top.
 | 
					 | 
				
			||||||
				// at the start, when there are not enough messages for a full screen, this is the case.
 | 
					 | 
				
			||||||
				// once there are enough messages that scrolling is necessary, we initiate a scroll down to activate the scroll anchor.
 | 
					 | 
				
			||||||
				let oldDifference = chatMessages.scrollHeight - chatMessages.clientHeight
 | 
					 | 
				
			||||||
				setTimeout(() => {
 | 
					 | 
				
			||||||
					let newDifference = chatMessages.scrollHeight - chatMessages.clientHeight
 | 
					 | 
				
			||||||
					// console.log("height difference", oldDifference, newDifference)
 | 
					 | 
				
			||||||
					if (oldDifference < 24) { // this is jank
 | 
					 | 
				
			||||||
						this.element.parentElement.scrollBy(0, 1000)
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}, 0)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			const name = "beforeChange"
 | 
					 | 
				
			||||||
			this.removableSubscriptions.push({name, target: timeline, subscription})
 | 
					 | 
				
			||||||
			timeline.subscribe(name, subscription)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		this.render()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	render() {
 | 
					 | 
				
			||||||
		this.clearChildren()
 | 
					 | 
				
			||||||
		if (store.activeRoom.exists()) {
 | 
					 | 
				
			||||||
			const reactiveTimeline = store.activeRoom.value().timeline.getTimeline()
 | 
					 | 
				
			||||||
			this.child(reactiveTimeline)
 | 
					 | 
				
			||||||
			setTimeout(() => {
 | 
					 | 
				
			||||||
				this.element.parentElement.scrollBy(0, 1)
 | 
					 | 
				
			||||||
				reactiveTimeline.anchor.scroll()
 | 
					 | 
				
			||||||
			}, 0)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const chat = new Chat()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {chat}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,72 +0,0 @@
 | 
				
			||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
					 | 
				
			||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<svg
 | 
					 | 
				
			||||||
   xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
					 | 
				
			||||||
   xmlns:cc="http://creativecommons.org/ns#"
 | 
					 | 
				
			||||||
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
					 | 
				
			||||||
   xmlns:svg="http://www.w3.org/2000/svg"
 | 
					 | 
				
			||||||
   xmlns="http://www.w3.org/2000/svg"
 | 
					 | 
				
			||||||
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
					 | 
				
			||||||
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
					 | 
				
			||||||
   width="48.000004"
 | 
					 | 
				
			||||||
   height="48.000004"
 | 
					 | 
				
			||||||
   viewBox="0 0 12.700001 12.700001"
 | 
					 | 
				
			||||||
   version="1.1"
 | 
					 | 
				
			||||||
   id="svg27"
 | 
					 | 
				
			||||||
   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
 | 
					 | 
				
			||||||
   sodipodi:docname="directs.svg">
 | 
					 | 
				
			||||||
  <defs
 | 
					 | 
				
			||||||
     id="defs21" />
 | 
					 | 
				
			||||||
  <sodipodi:namedview
 | 
					 | 
				
			||||||
     id="base"
 | 
					 | 
				
			||||||
     pagecolor="#ffffff"
 | 
					 | 
				
			||||||
     bordercolor="#666666"
 | 
					 | 
				
			||||||
     borderopacity="1.0"
 | 
					 | 
				
			||||||
     inkscape:pageopacity="0.0"
 | 
					 | 
				
			||||||
     inkscape:pageshadow="2"
 | 
					 | 
				
			||||||
     inkscape:zoom="22.627417"
 | 
					 | 
				
			||||||
     inkscape:cx="27.665561"
 | 
					 | 
				
			||||||
     inkscape:cy="33.324951"
 | 
					 | 
				
			||||||
     inkscape:document-units="px"
 | 
					 | 
				
			||||||
     inkscape:current-layer="layer1"
 | 
					 | 
				
			||||||
     showgrid="true"
 | 
					 | 
				
			||||||
     units="px"
 | 
					 | 
				
			||||||
     inkscape:window-width="1440"
 | 
					 | 
				
			||||||
     inkscape:window-height="879"
 | 
					 | 
				
			||||||
     inkscape:window-x="0"
 | 
					 | 
				
			||||||
     inkscape:window-y="0"
 | 
					 | 
				
			||||||
     inkscape:window-maximized="1"
 | 
					 | 
				
			||||||
     showborder="true"
 | 
					 | 
				
			||||||
     inkscape:snap-bbox="true"
 | 
					 | 
				
			||||||
     inkscape:bbox-nodes="true"
 | 
					 | 
				
			||||||
     inkscape:snap-grids="true">
 | 
					 | 
				
			||||||
    <inkscape:grid
 | 
					 | 
				
			||||||
       type="xygrid"
 | 
					 | 
				
			||||||
       id="grid824" />
 | 
					 | 
				
			||||||
  </sodipodi:namedview>
 | 
					 | 
				
			||||||
  <metadata
 | 
					 | 
				
			||||||
     id="metadata24">
 | 
					 | 
				
			||||||
    <rdf:RDF>
 | 
					 | 
				
			||||||
      <cc:Work
 | 
					 | 
				
			||||||
         rdf:about="">
 | 
					 | 
				
			||||||
        <dc:format>image/svg+xml</dc:format>
 | 
					 | 
				
			||||||
        <dc:type
 | 
					 | 
				
			||||||
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
 | 
					 | 
				
			||||||
        <dc:title></dc:title>
 | 
					 | 
				
			||||||
      </cc:Work>
 | 
					 | 
				
			||||||
    </rdf:RDF>
 | 
					 | 
				
			||||||
  </metadata>
 | 
					 | 
				
			||||||
  <g
 | 
					 | 
				
			||||||
     inkscape:label="Layer 1"
 | 
					 | 
				
			||||||
     inkscape:groupmode="layer"
 | 
					 | 
				
			||||||
     id="layer1"
 | 
					 | 
				
			||||||
     transform="translate(36.739286,-225.97828)">
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
       style="opacity:1;fill:#e2e2e2;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke markers fill"
 | 
					 | 
				
			||||||
       d="m -33.035119,228.8887 h 5.067509 c 0.868277,0 1.567287,0.69901 1.567287,1.56728 v 2.66473 c 0,0.86828 -0.69901,1.56729 -1.567287,1.56729 h -2.157093 l -1.322916,1.86351 -1.322917,-1.86351 h -0.264583 c -0.868277,0 -1.567287,-0.69901 -1.567287,-1.56729 v -2.66473 c 0,-0.86827 0.69901,-1.56728 1.567287,-1.56728 z"
 | 
					 | 
				
			||||||
       id="rect820"
 | 
					 | 
				
			||||||
       inkscape:connector-curvature="0"
 | 
					 | 
				
			||||||
       sodipodi:nodetypes="ssssscccssss" />
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 2.5 KiB  | 
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
import {q} from "../static/basic.js?static=4212436742"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let state = "CLOSED"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const groups = q("#c-groups-display")
 | 
					 | 
				
			||||||
const rooms = q("#c-rooms")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
groups.addEventListener("click", () => {
 | 
					 | 
				
			||||||
	groups.classList.add("c-groups__display--closed")
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
rooms.addEventListener("mouseout", () => {
 | 
					 | 
				
			||||||
	groups.classList.remove("c-groups__display--closed")
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,80 +0,0 @@
 | 
				
			||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
					 | 
				
			||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<svg
 | 
					 | 
				
			||||||
   xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
					 | 
				
			||||||
   xmlns:cc="http://creativecommons.org/ns#"
 | 
					 | 
				
			||||||
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
					 | 
				
			||||||
   xmlns:svg="http://www.w3.org/2000/svg"
 | 
					 | 
				
			||||||
   xmlns="http://www.w3.org/2000/svg"
 | 
					 | 
				
			||||||
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
					 | 
				
			||||||
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
					 | 
				
			||||||
   width="20"
 | 
					 | 
				
			||||||
   height="20"
 | 
					 | 
				
			||||||
   viewBox="0 0 5.2916665 5.2916668"
 | 
					 | 
				
			||||||
   version="1.1"
 | 
					 | 
				
			||||||
   id="svg27"
 | 
					 | 
				
			||||||
   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
 | 
					 | 
				
			||||||
   sodipodi:docname="join-event.svg">
 | 
					 | 
				
			||||||
  <defs
 | 
					 | 
				
			||||||
     id="defs21" />
 | 
					 | 
				
			||||||
  <sodipodi:namedview
 | 
					 | 
				
			||||||
     id="base"
 | 
					 | 
				
			||||||
     pagecolor="#ffffff"
 | 
					 | 
				
			||||||
     bordercolor="#666666"
 | 
					 | 
				
			||||||
     borderopacity="1.0"
 | 
					 | 
				
			||||||
     inkscape:pageopacity="0.0"
 | 
					 | 
				
			||||||
     inkscape:pageshadow="2"
 | 
					 | 
				
			||||||
     inkscape:zoom="1"
 | 
					 | 
				
			||||||
     inkscape:cx="15.649008"
 | 
					 | 
				
			||||||
     inkscape:cy="8.3751893"
 | 
					 | 
				
			||||||
     inkscape:document-units="px"
 | 
					 | 
				
			||||||
     inkscape:current-layer="layer1"
 | 
					 | 
				
			||||||
     showgrid="true"
 | 
					 | 
				
			||||||
     units="px"
 | 
					 | 
				
			||||||
     inkscape:window-width="1440"
 | 
					 | 
				
			||||||
     inkscape:window-height="879"
 | 
					 | 
				
			||||||
     inkscape:window-x="0"
 | 
					 | 
				
			||||||
     inkscape:window-y="0"
 | 
					 | 
				
			||||||
     inkscape:window-maximized="1">
 | 
					 | 
				
			||||||
    <inkscape:grid
 | 
					 | 
				
			||||||
       type="xygrid"
 | 
					 | 
				
			||||||
       id="grid26" />
 | 
					 | 
				
			||||||
  </sodipodi:namedview>
 | 
					 | 
				
			||||||
  <metadata
 | 
					 | 
				
			||||||
     id="metadata24">
 | 
					 | 
				
			||||||
    <rdf:RDF>
 | 
					 | 
				
			||||||
      <cc:Work
 | 
					 | 
				
			||||||
         rdf:about="">
 | 
					 | 
				
			||||||
        <dc:format>image/svg+xml</dc:format>
 | 
					 | 
				
			||||||
        <dc:type
 | 
					 | 
				
			||||||
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
 | 
					 | 
				
			||||||
        <dc:title></dc:title>
 | 
					 | 
				
			||||||
      </cc:Work>
 | 
					 | 
				
			||||||
    </rdf:RDF>
 | 
					 | 
				
			||||||
  </metadata>
 | 
					 | 
				
			||||||
  <g
 | 
					 | 
				
			||||||
     inkscape:label="Layer 1"
 | 
					 | 
				
			||||||
     inkscape:groupmode="layer"
 | 
					 | 
				
			||||||
     id="layer1"
 | 
					 | 
				
			||||||
     transform="translate(0,-291.70832)">
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
       style="opacity:1;fill:#e2e2e2;fill-opacity:1;fill-rule:nonzero;stroke:#73d216;stroke-width:0.52916666;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke markers fill"
 | 
					 | 
				
			||||||
       d="M 0.79374997,294.35416 H 4.4979167"
 | 
					 | 
				
			||||||
       id="path28"
 | 
					 | 
				
			||||||
       inkscape:connector-curvature="0"
 | 
					 | 
				
			||||||
       sodipodi:nodetypes="cc" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
       inkscape:connector-curvature="0"
 | 
					 | 
				
			||||||
       id="path30"
 | 
					 | 
				
			||||||
       d="m 3.175,293.03124 1.3229167,1.32292"
 | 
					 | 
				
			||||||
       style="opacity:1;fill:#e2e2e2;fill-opacity:1;fill-rule:nonzero;stroke:#73d216;stroke-width:0.52916666;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke markers fill"
 | 
					 | 
				
			||||||
       sodipodi:nodetypes="cc" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
       sodipodi:nodetypes="cc"
 | 
					 | 
				
			||||||
       style="opacity:1;fill:#e2e2e2;fill-opacity:1;fill-rule:nonzero;stroke:#73d216;stroke-width:0.52916666;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke markers fill"
 | 
					 | 
				
			||||||
       d="M 4.4979167,294.35416 3.175,295.67707"
 | 
					 | 
				
			||||||
       id="path32"
 | 
					 | 
				
			||||||
       inkscape:connector-curvature="0" />
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 2.9 KiB  | 
| 
						 | 
					@ -1,11 +0,0 @@
 | 
				
			||||||
function get(name) {
 | 
					 | 
				
			||||||
	return localStorage.getItem(name)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function set(name, value) {
 | 
					 | 
				
			||||||
	return localStorage.setItem(name, value)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
window.lsm = {get, set}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {get, set}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,261 +0,0 @@
 | 
				
			||||||
@font-face {
 | 
					 | 
				
			||||||
	font-family: Whitney;
 | 
					 | 
				
			||||||
	font-weight: 400;
 | 
					 | 
				
			||||||
	src: url(/static/whitney-400.woff?static=0f823bc4b5) format("woff2");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@font-face {
 | 
					 | 
				
			||||||
	font-family: Whitney;
 | 
					 | 
				
			||||||
	font-weight: 500;
 | 
					 | 
				
			||||||
	src: url(/static/whitney-500.woff?static=ba33ed18fe) format("woff2");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
body {
 | 
					 | 
				
			||||||
	font-family: sans-serif;
 | 
					 | 
				
			||||||
	background-color: #36393e;
 | 
					 | 
				
			||||||
	color: #ddd;
 | 
					 | 
				
			||||||
	font-size: 16px;
 | 
					 | 
				
			||||||
	font-family: Whitney;
 | 
					 | 
				
			||||||
	line-height: 1.45;
 | 
					 | 
				
			||||||
	margin: 0;
 | 
					 | 
				
			||||||
	height: 100vh;
 | 
					 | 
				
			||||||
	font-weight: 400;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.main {
 | 
					 | 
				
			||||||
	height: 100vh;
 | 
					 | 
				
			||||||
	display: flex;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.c-rooms {
 | 
					 | 
				
			||||||
	background-color: #2f3135;
 | 
					 | 
				
			||||||
	padding: 8px;
 | 
					 | 
				
			||||||
	width: 240px;
 | 
					 | 
				
			||||||
	font-size: 18px;
 | 
					 | 
				
			||||||
	font-weight: 500;
 | 
					 | 
				
			||||||
	overflow-y: auto;
 | 
					 | 
				
			||||||
	scrollbar-width: thin;
 | 
					 | 
				
			||||||
	scrollbar-color: #202224 #2f3135;
 | 
					 | 
				
			||||||
	flex-shrink: 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.c-room {
 | 
					 | 
				
			||||||
	display: flex;
 | 
					 | 
				
			||||||
	align-items: center;
 | 
					 | 
				
			||||||
	padding: 6px 8px;
 | 
					 | 
				
			||||||
	margin: 2px 0;
 | 
					 | 
				
			||||||
	cursor: pointer;
 | 
					 | 
				
			||||||
	border-radius: 8px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-room:not(.c-room--active):hover {
 | 
					 | 
				
			||||||
	background-color: #393c42;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-room--active {
 | 
					 | 
				
			||||||
	background-color: #42454a;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-room__icon {
 | 
					 | 
				
			||||||
	width: 32px;
 | 
					 | 
				
			||||||
	height: 32px;
 | 
					 | 
				
			||||||
	margin-right: 8px;
 | 
					 | 
				
			||||||
	border-radius: 50%;
 | 
					 | 
				
			||||||
	flex-shrink: 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-room__icon--no-icon {
 | 
					 | 
				
			||||||
	background-color: #bbb;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-room__name {
 | 
					 | 
				
			||||||
	white-space: nowrap;
 | 
					 | 
				
			||||||
	overflow: hidden;
 | 
					 | 
				
			||||||
	text-overflow: ellipsis;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.c-groups {
 | 
					 | 
				
			||||||
	position: relative;
 | 
					 | 
				
			||||||
	width: 80px;
 | 
					 | 
				
			||||||
	flex-shrink: 0;
 | 
					 | 
				
			||||||
	font-size: 22px;
 | 
					 | 
				
			||||||
	font-weight: 500;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-groups__display {
 | 
					 | 
				
			||||||
	background-color: #202224;
 | 
					 | 
				
			||||||
	overflow: hidden;
 | 
					 | 
				
			||||||
	width: 80px;
 | 
					 | 
				
			||||||
	position: absolute;
 | 
					 | 
				
			||||||
	left: 0;
 | 
					 | 
				
			||||||
	top: 0;
 | 
					 | 
				
			||||||
	bottom: 0;
 | 
					 | 
				
			||||||
	right: 0;
 | 
					 | 
				
			||||||
	transition: width ease-out 0.12s;
 | 
					 | 
				
			||||||
	scrollbar-width: thin;
 | 
					 | 
				
			||||||
	scrollbar-color: #42454a #202224;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-groups__display:not(.c-groups__display--closed):hover {
 | 
					 | 
				
			||||||
	width: 320px;
 | 
					 | 
				
			||||||
	overflow-y: auto;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-groups__container {
 | 
					 | 
				
			||||||
	width: 320px;
 | 
					 | 
				
			||||||
	padding: 8px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.c-group {
 | 
					 | 
				
			||||||
	display: flex;
 | 
					 | 
				
			||||||
	align-items: center;
 | 
					 | 
				
			||||||
	padding: 4px 8px;
 | 
					 | 
				
			||||||
	cursor: pointer;
 | 
					 | 
				
			||||||
	border-radius: 8px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-group:hover {
 | 
					 | 
				
			||||||
	background-color: #2f3135;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-group__icon {
 | 
					 | 
				
			||||||
	width: 48px;
 | 
					 | 
				
			||||||
	height: 48px;
 | 
					 | 
				
			||||||
	background-color: #393c42;
 | 
					 | 
				
			||||||
	border-radius: 50%;
 | 
					 | 
				
			||||||
	margin-right: 16px;
 | 
					 | 
				
			||||||
	flex-shrink: 0;
 | 
					 | 
				
			||||||
	transition: border-radius ease-out 0.12s;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-group--active .c-group__icon {
 | 
					 | 
				
			||||||
	border-radius: 28%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-group__name {
 | 
					 | 
				
			||||||
	white-space: nowrap;
 | 
					 | 
				
			||||||
	overflow: hidden;
 | 
					 | 
				
			||||||
	text-overflow: ellipsis;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.c-group-marker {
 | 
					 | 
				
			||||||
	position: absolute;
 | 
					 | 
				
			||||||
	top: 5px;
 | 
					 | 
				
			||||||
	opacity: 0;
 | 
					 | 
				
			||||||
	transform: translateY(8px);
 | 
					 | 
				
			||||||
	transition: transform ease 0.12s, opacity ease-out 0.12s;
 | 
					 | 
				
			||||||
	height: 46px;
 | 
					 | 
				
			||||||
	width: 6px;
 | 
					 | 
				
			||||||
	background-color: #ccc;
 | 
					 | 
				
			||||||
	border-radius: 0px 6px 6px 0px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.c-event-groups * {
 | 
					 | 
				
			||||||
	overflow-anchor: none;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.c-message-group, .c-message-event {
 | 
					 | 
				
			||||||
	margin-top: 12px;
 | 
					 | 
				
			||||||
	padding-top: 12px;
 | 
					 | 
				
			||||||
	border-top: 1px solid #4b4e54;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.c-message-group {
 | 
					 | 
				
			||||||
	display: flex;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-message-group__avatar {
 | 
					 | 
				
			||||||
	flex-shrink: 0;
 | 
					 | 
				
			||||||
	margin-right: 16px;
 | 
					 | 
				
			||||||
	cursor: pointer;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-message-group__icon {
 | 
					 | 
				
			||||||
	width: 40px;
 | 
					 | 
				
			||||||
	height: 40px;
 | 
					 | 
				
			||||||
	border-radius: 50%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-message-group__icon--no-icon {
 | 
					 | 
				
			||||||
	background-color: #48d;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-message-group__intro {
 | 
					 | 
				
			||||||
	display: flex;
 | 
					 | 
				
			||||||
	align-items: baseline;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-message-group__name {
 | 
					 | 
				
			||||||
	color: #5bf;
 | 
					 | 
				
			||||||
	margin: -2px 0px -3px;
 | 
					 | 
				
			||||||
	font-size: 19px;
 | 
					 | 
				
			||||||
	font-weight: 500;
 | 
					 | 
				
			||||||
	cursor: pointer;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-message-group__name:hover {
 | 
					 | 
				
			||||||
	text-decoration: underline;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-message-group__date {
 | 
					 | 
				
			||||||
	font-size: 14px;
 | 
					 | 
				
			||||||
	margin-left: 9px;
 | 
					 | 
				
			||||||
	color: #999;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.c-message {
 | 
					 | 
				
			||||||
	margin-top: 4px;
 | 
					 | 
				
			||||||
	opacity: 1;
 | 
					 | 
				
			||||||
	transition: opacity 0.2s ease-out;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-message--pending {
 | 
					 | 
				
			||||||
	opacity: 0.5;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.c-message-event {
 | 
					 | 
				
			||||||
	padding-top: 10px;
 | 
					 | 
				
			||||||
	padding-left: 6px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-message-event__inner {
 | 
					 | 
				
			||||||
	display: flex;
 | 
					 | 
				
			||||||
	align-items: center;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-message-event__icon {
 | 
					 | 
				
			||||||
	margin-right: 8px;
 | 
					 | 
				
			||||||
	position: relative;
 | 
					 | 
				
			||||||
	top: 1px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.c-message-notice {
 | 
					 | 
				
			||||||
	padding: 12px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-message-notice__inner {
 | 
					 | 
				
			||||||
	text-align: center;
 | 
					 | 
				
			||||||
	padding: 12px;
 | 
					 | 
				
			||||||
	background-color: #42454a;
 | 
					 | 
				
			||||||
	border-radius: 8px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.c-chat {
 | 
					 | 
				
			||||||
	display: grid;
 | 
					 | 
				
			||||||
	grid-template-rows: 1fr 82px;
 | 
					 | 
				
			||||||
	align-items: end;
 | 
					 | 
				
			||||||
	flex: 1;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-chat__messages {
 | 
					 | 
				
			||||||
	height: 100%;
 | 
					 | 
				
			||||||
	overflow-y: scroll;
 | 
					 | 
				
			||||||
	scrollbar-color: #202224 #2f3135;
 | 
					 | 
				
			||||||
	display: grid;
 | 
					 | 
				
			||||||
	align-items: end;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-chat__inner {
 | 
					 | 
				
			||||||
	padding: 20px 20px 20px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.c-chat-input {
 | 
					 | 
				
			||||||
	width: 100%;
 | 
					 | 
				
			||||||
	border-top: 2px solid #4b4e54;
 | 
					 | 
				
			||||||
	background-color: #36393e;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.c-chat-input__textarea {
 | 
					 | 
				
			||||||
	width: calc(100% - 40px);
 | 
					 | 
				
			||||||
	height: 39.2px;
 | 
					 | 
				
			||||||
	box-sizing: border-box;
 | 
					 | 
				
			||||||
	margin: 20px;
 | 
					 | 
				
			||||||
	padding: 8px;
 | 
					 | 
				
			||||||
	font-family: inherit;
 | 
					 | 
				
			||||||
	font-size: inherit;
 | 
					 | 
				
			||||||
	background-color: #42454a;
 | 
					 | 
				
			||||||
	color: #fff;
 | 
					 | 
				
			||||||
	appearance: none;
 | 
					 | 
				
			||||||
	-moz-appearance: none;
 | 
					 | 
				
			||||||
	-webkit-appearance: none;
 | 
					 | 
				
			||||||
	border: none;
 | 
					 | 
				
			||||||
	border-radius: 8px;
 | 
					 | 
				
			||||||
	resize: none;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.c-anchor {
 | 
					 | 
				
			||||||
	overflow-anchor: auto;
 | 
					 | 
				
			||||||
	height: 1px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,230 +0,0 @@
 | 
				
			||||||
import {q, ElemJS, ejs} from "../static/basic.js?static=4212436742"
 | 
					 | 
				
			||||||
import {store} from "../static/store/store.js?static=ce3066287b"
 | 
					 | 
				
			||||||
import {SubscribeMapList} from "../static/store/SubscribeMapList.js?static=b2732c5460"
 | 
					 | 
				
			||||||
import {SubscribeValue} from "../static/store/SubscribeValue.js?static=215b6a5099"
 | 
					 | 
				
			||||||
import {Timeline} from "../static/Timeline.js?static=1393b78916"
 | 
					 | 
				
			||||||
import * as lsm from "../static/lsm.js?static=aed2c7ca35"
 | 
					 | 
				
			||||||
import {resolveMxc} from "../static/functions.js?static=e3784c70ce"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ActiveGroupMarker extends ElemJS {
 | 
					 | 
				
			||||||
	constructor() {
 | 
					 | 
				
			||||||
		super(q("#c-group-marker"))
 | 
					 | 
				
			||||||
		store.activeGroup.subscribe("changeSelf", this.render.bind(this))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	render() {
 | 
					 | 
				
			||||||
		if (store.activeGroup.exists()) {
 | 
					 | 
				
			||||||
			const group = store.activeGroup.value()
 | 
					 | 
				
			||||||
			this.style("opacity", 1)
 | 
					 | 
				
			||||||
			this.style("transform", `translateY(${group.element.offsetTop}px)`)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			this.style("opacity", 0)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const activeGroupMarker = new ActiveGroupMarker()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Group extends ElemJS {
 | 
					 | 
				
			||||||
	constructor(key, data) {
 | 
					 | 
				
			||||||
		super("div")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.data = data
 | 
					 | 
				
			||||||
		this.order = this.data.order
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.class("c-group")
 | 
					 | 
				
			||||||
		this.child(
 | 
					 | 
				
			||||||
			(this.data.icon
 | 
					 | 
				
			||||||
			 ? ejs("img").class("c-group__icon").attribute("src", this.data.icon)
 | 
					 | 
				
			||||||
			 : ejs("div").class("c-group__icon")
 | 
					 | 
				
			||||||
			),
 | 
					 | 
				
			||||||
			ejs("div").class("c-group__name").text(this.data.name)
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.on("click", this.onClick.bind(this))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		store.activeGroup.subscribe("changeSelf", this.render.bind(this))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	render() {
 | 
					 | 
				
			||||||
		const active = store.activeGroup.value() === this
 | 
					 | 
				
			||||||
		this.element.classList[active ? "add" : "remove"]("c-group--active")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	onClick() {
 | 
					 | 
				
			||||||
		store.activeGroup.set(this)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Room extends ElemJS {
 | 
					 | 
				
			||||||
	constructor(id, data) {
 | 
					 | 
				
			||||||
		super("div")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.id = id
 | 
					 | 
				
			||||||
		this.data = data
 | 
					 | 
				
			||||||
		this.timeline = new Timeline(this.id)
 | 
					 | 
				
			||||||
		this.group = null
 | 
					 | 
				
			||||||
		this.members = new SubscribeMapList(SubscribeValue)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.class("c-room")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.on("click", this.onClick.bind(this))
 | 
					 | 
				
			||||||
		store.activeRoom.subscribe("changeSelf", this.render.bind(this))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		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) {
 | 
					 | 
				
			||||||
			name = name.content.name
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			const users = this.data.summary["m.heroes"]
 | 
					 | 
				
			||||||
			const usernames = users.map(u => (u.match(/^@([^:]+):/) || [])[1] || u)
 | 
					 | 
				
			||||||
			name = usernames.join(", ")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return name
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	getIcon() {
 | 
					 | 
				
			||||||
		const avatar = this.data.state.events.find(e => e.type === "m.room.avatar")
 | 
					 | 
				
			||||||
		if (avatar) {
 | 
					 | 
				
			||||||
			return resolveMxc(avatar.content.url || avatar.content.avatar_url, 32, "crop")
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			return null
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	isDirect() {
 | 
					 | 
				
			||||||
		return store.directs.has(this.id)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	setGroup(id) {
 | 
					 | 
				
			||||||
		this.group = id
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	getGroup() {
 | 
					 | 
				
			||||||
		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() {
 | 
					 | 
				
			||||||
		store.activeRoom.set(this)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	render() {
 | 
					 | 
				
			||||||
		this.clearChildren()
 | 
					 | 
				
			||||||
		// data
 | 
					 | 
				
			||||||
		const icon = this.getIcon()
 | 
					 | 
				
			||||||
		if (icon) {
 | 
					 | 
				
			||||||
			this.child(ejs("img").class("c-room__icon").attribute("src", icon))
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			this.child(ejs("div").class("c-room__icon", "c-room__icon--no-icon"))
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		this.child(ejs("div").class("c-room__name").text(this.getName()))
 | 
					 | 
				
			||||||
		// active
 | 
					 | 
				
			||||||
		const active = store.activeRoom.value() === this
 | 
					 | 
				
			||||||
		this.element.classList[active ? "add" : "remove"]("c-room--active")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Rooms extends ElemJS {
 | 
					 | 
				
			||||||
	constructor() {
 | 
					 | 
				
			||||||
		super(q("#c-rooms"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.roomData = []
 | 
					 | 
				
			||||||
		this.rooms = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		store.rooms.subscribe("askAdd", this.askAdd.bind(this))
 | 
					 | 
				
			||||||
		store.rooms.subscribe("addItem", this.addItem.bind(this))
 | 
					 | 
				
			||||||
		// 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)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	addItem(event, key) {
 | 
					 | 
				
			||||||
		const room = store.rooms.get(key).value()
 | 
					 | 
				
			||||||
		if (room.getGroup() === store.activeGroup.value()) {
 | 
					 | 
				
			||||||
			this.child(room)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	render() {
 | 
					 | 
				
			||||||
		this.clearChildren()
 | 
					 | 
				
			||||||
		let first = null
 | 
					 | 
				
			||||||
		// set room list
 | 
					 | 
				
			||||||
		store.rooms.forEach((id, room) => {
 | 
					 | 
				
			||||||
			if (room.value().getGroup() === store.activeGroup.value()) {
 | 
					 | 
				
			||||||
				if (!first) first = room.value()
 | 
					 | 
				
			||||||
				this.child(room.value())
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
		// if needed, change the active room to be an item in the room list
 | 
					 | 
				
			||||||
		if (!store.activeRoom.exists() || store.activeRoom.value().getGroup() !== store.activeGroup.value()) {
 | 
					 | 
				
			||||||
			if (first) {
 | 
					 | 
				
			||||||
				store.activeRoom.set(first)
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				store.activeRoom.delete()
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
const rooms = new Rooms()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Groups extends ElemJS {
 | 
					 | 
				
			||||||
	constructor() {
 | 
					 | 
				
			||||||
		super(q("#c-groups-list"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		store.groups.subscribe("askAdd", this.askAdd.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()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	render() {
 | 
					 | 
				
			||||||
		this.clearChildren()
 | 
					 | 
				
			||||||
		store.groups.forEach((key, item) => {
 | 
					 | 
				
			||||||
			this.child(item.value())
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
const groups = new Groups()
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,38 +0,0 @@
 | 
				
			||||||
class Subscribable {
 | 
					 | 
				
			||||||
	constructor() {
 | 
					 | 
				
			||||||
		this.events = {
 | 
					 | 
				
			||||||
			addSelf: [],
 | 
					 | 
				
			||||||
			editSelf: [],
 | 
					 | 
				
			||||||
			removeSelf: [],
 | 
					 | 
				
			||||||
			changeSelf: []
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		this.eventDeps = {
 | 
					 | 
				
			||||||
			addSelf: ["changeSelf"],
 | 
					 | 
				
			||||||
			editSelf: ["changeSelf"],
 | 
					 | 
				
			||||||
			removeSelf: ["changeSelf"],
 | 
					 | 
				
			||||||
			changeSelf: []
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	subscribe(event, callback) {
 | 
					 | 
				
			||||||
		if (this.events[event]) {
 | 
					 | 
				
			||||||
			this.events[event].push(callback)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			throw new Error(`Cannot subscribe to non-existent event ${event}, available events are: ${Object.keys(this.events).join(", ")}`)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	unsubscribe(event, callback) {
 | 
					 | 
				
			||||||
		const index = this.events[event].indexOf(callback)
 | 
					 | 
				
			||||||
		if (index === -1) throw new Error(`Tried to remove a nonexisting subscription from event ${event}`)
 | 
					 | 
				
			||||||
		this.events[event].splice(index, 1)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	broadcast(event, data) {
 | 
					 | 
				
			||||||
		this.eventDeps[event].concat(event).forEach(eventName => {
 | 
					 | 
				
			||||||
			this.events[eventName].forEach(f => f(event, data))
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {Subscribable}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,86 +0,0 @@
 | 
				
			||||||
import {Subscribable} from "../../static/store/Subscribable.js?static=19b7e44aa6"
 | 
					 | 
				
			||||||
import {SubscribeValue} from "../../static/store/SubscribeValue.js?static=215b6a5099"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SubscribeMapList extends Subscribable {
 | 
					 | 
				
			||||||
	constructor(inner) {
 | 
					 | 
				
			||||||
		super()
 | 
					 | 
				
			||||||
		this.inner = inner
 | 
					 | 
				
			||||||
		Object.assign(this.events, {
 | 
					 | 
				
			||||||
			addItem: [],
 | 
					 | 
				
			||||||
			deleteItem: [],
 | 
					 | 
				
			||||||
			editItem: [],
 | 
					 | 
				
			||||||
			changeItem: [],
 | 
					 | 
				
			||||||
			askAdd: []
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
		Object.assign(this.eventDeps, {
 | 
					 | 
				
			||||||
			addItem: ["changeItem"],
 | 
					 | 
				
			||||||
			deleteItem: ["changeItem"],
 | 
					 | 
				
			||||||
			editItem: ["changeItem"],
 | 
					 | 
				
			||||||
			changeItem: [],
 | 
					 | 
				
			||||||
			askAdd: []
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
		this.map = new Map()
 | 
					 | 
				
			||||||
		this.list = []
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	has(key) {
 | 
					 | 
				
			||||||
		return this.map.has(key) && this.map.get(key).exists()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	get(key) {
 | 
					 | 
				
			||||||
		if (this.map.has(key)) {
 | 
					 | 
				
			||||||
			return this.map.get(key)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			const item = new this.inner()
 | 
					 | 
				
			||||||
			this.map.set(key, item)
 | 
					 | 
				
			||||||
			return item
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	forEach(f) {
 | 
					 | 
				
			||||||
		this.list.forEach(key => f(key, this.get(key)))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	askAdd(key, data) {
 | 
					 | 
				
			||||||
		this.broadcast("askAdd", {key, data})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	addStart(key, value) {
 | 
					 | 
				
			||||||
		this._add(key, value, true)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	addEnd(key, value) {
 | 
					 | 
				
			||||||
		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)) {
 | 
					 | 
				
			||||||
			const exists = this.map.get(key).exists()
 | 
					 | 
				
			||||||
			s = this.map.get(key).set(value)
 | 
					 | 
				
			||||||
			if (exists) {
 | 
					 | 
				
			||||||
				this.broadcast("editItem", key)
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				this.broadcast("addItem", key)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			s = new this.inner().set(value)
 | 
					 | 
				
			||||||
			this.map.set(key, s)
 | 
					 | 
				
			||||||
			if (start) this.list.unshift(key)
 | 
					 | 
				
			||||||
			else this.list.push(key)
 | 
					 | 
				
			||||||
			this.broadcast("addItem", key)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return s
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {SubscribeMapList}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,50 +0,0 @@
 | 
				
			||||||
import {Subscribable} from "../../static/store/Subscribable.js?static=19b7e44aa6"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SubscribeSet extends Subscribable {
 | 
					 | 
				
			||||||
	constructor() {
 | 
					 | 
				
			||||||
		super()
 | 
					 | 
				
			||||||
		Object.assign(this.events, {
 | 
					 | 
				
			||||||
			addItem: [],
 | 
					 | 
				
			||||||
			deleteItem: [],
 | 
					 | 
				
			||||||
			changeItem: [],
 | 
					 | 
				
			||||||
			askAdd: []
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
		Object.assign(this.eventDeps, {
 | 
					 | 
				
			||||||
			addItem: ["changeItem"],
 | 
					 | 
				
			||||||
			deleteItem: ["changeItem"],
 | 
					 | 
				
			||||||
			changeItem: [],
 | 
					 | 
				
			||||||
			askAdd: []
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
		this.set = new Set()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	has(key) {
 | 
					 | 
				
			||||||
		return this.set.has(key)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	forEach(f) {
 | 
					 | 
				
			||||||
		for (const key of this.set.keys()) {
 | 
					 | 
				
			||||||
			f(key)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	askAdd(key) {
 | 
					 | 
				
			||||||
		this.broadcast("askAdd", key)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	add(key) {
 | 
					 | 
				
			||||||
		if (!this.set.has(key)) {
 | 
					 | 
				
			||||||
			this.set.add(key)
 | 
					 | 
				
			||||||
			this.broadcast("addItem", key)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	delete(key) {
 | 
					 | 
				
			||||||
		if (this.set.has(key)) {
 | 
					 | 
				
			||||||
			this.set.delete(key)
 | 
					 | 
				
			||||||
			this.broadcast("deleteItem", key)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {SubscribeSet}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,47 +0,0 @@
 | 
				
			||||||
import {Subscribable} from "../../static/store/Subscribable.js?static=19b7e44aa6"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SubscribeValue extends Subscribable {
 | 
					 | 
				
			||||||
	constructor() {
 | 
					 | 
				
			||||||
		super()
 | 
					 | 
				
			||||||
		this.hasData = false
 | 
					 | 
				
			||||||
		this.data = null
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	exists() {
 | 
					 | 
				
			||||||
		return this.hasData
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	value() {
 | 
					 | 
				
			||||||
		if (this.hasData) return this.data
 | 
					 | 
				
			||||||
		else return null
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	set(data) {
 | 
					 | 
				
			||||||
		const exists = this.exists()
 | 
					 | 
				
			||||||
		this.data = data
 | 
					 | 
				
			||||||
		this.hasData = true
 | 
					 | 
				
			||||||
		if (exists) {
 | 
					 | 
				
			||||||
			this.broadcast("editSelf", this.data)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			this.broadcast("addSelf", this.data)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return this
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	edit(f) {
 | 
					 | 
				
			||||||
		if (this.exists()) {
 | 
					 | 
				
			||||||
			f(this.data)
 | 
					 | 
				
			||||||
			this.set(this.data)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			throw new Error("Tried to edit a SubscribeValue that had no value")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	delete() {
 | 
					 | 
				
			||||||
		this.hasData = false
 | 
					 | 
				
			||||||
		this.broadcast("removeSelf")
 | 
					 | 
				
			||||||
		return this
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {SubscribeValue}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,17 +0,0 @@
 | 
				
			||||||
import {Subscribable} from "../../static/store/Subscribable.js?static=19b7e44aa6"
 | 
					 | 
				
			||||||
import {SubscribeMapList} from "../../static/store/SubscribeMapList.js?static=b2732c5460"
 | 
					 | 
				
			||||||
import {SubscribeSet} from "../../static/store/SubscribeSet.js?static=39a1c0a2a4"
 | 
					 | 
				
			||||||
import {SubscribeValue} from "../../static/store/SubscribeValue.js?static=215b6a5099"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const store = {
 | 
					 | 
				
			||||||
	groups: new SubscribeMapList(SubscribeValue),
 | 
					 | 
				
			||||||
	rooms: new SubscribeMapList(SubscribeValue),
 | 
					 | 
				
			||||||
	directs: new SubscribeSet(),
 | 
					 | 
				
			||||||
	activeGroup: new SubscribeValue(),
 | 
					 | 
				
			||||||
	activeRoom: new SubscribeValue(),
 | 
					 | 
				
			||||||
	newEvents: new Subscribable()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
window.store = store
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {store}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,127 +0,0 @@
 | 
				
			||||||
import {store} from "../../static/store/store.js?static=ce3066287b"
 | 
					 | 
				
			||||||
import * as lsm from "../../static/lsm.js?static=aed2c7ca35"
 | 
					 | 
				
			||||||
import {resolveMxc} from "../../static/functions.js?static=e3784c70ce"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let lastBatch = null
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function sync() {
 | 
					 | 
				
			||||||
	const url = new URL(`${lsm.get("domain")}/_matrix/client/r0/sync`)
 | 
					 | 
				
			||||||
	url.searchParams.append("access_token", lsm.get("access_token"))
 | 
					 | 
				
			||||||
	const filter = {
 | 
					 | 
				
			||||||
		room: {
 | 
					 | 
				
			||||||
			// pulling more from the timeline massively increases download size
 | 
					 | 
				
			||||||
			timeline: {
 | 
					 | 
				
			||||||
				limit: 5
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			// members are not currently needed
 | 
					 | 
				
			||||||
			state: {
 | 
					 | 
				
			||||||
				lazy_load_members: true
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		presence: {
 | 
					 | 
				
			||||||
			// presence is not implemented, ignore it
 | 
					 | 
				
			||||||
			types: []
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	url.searchParams.append("filter", JSON.stringify(filter))
 | 
					 | 
				
			||||||
	url.searchParams.append("timeout", 20000)
 | 
					 | 
				
			||||||
	if (lastBatch) {
 | 
					 | 
				
			||||||
		url.searchParams.append("since", lastBatch)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return fetch(url.toString()).then(res => res.json()).then(root => {
 | 
					 | 
				
			||||||
		lastBatch = root.next_batch
 | 
					 | 
				
			||||||
		return root
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function manageSync(root) {
 | 
					 | 
				
			||||||
	try {
 | 
					 | 
				
			||||||
		let newEvents = false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// set up directs
 | 
					 | 
				
			||||||
		const directs = root.account_data.events.find(e => e.type === "m.direct")
 | 
					 | 
				
			||||||
		if (directs) {
 | 
					 | 
				
			||||||
			Object.values(directs.content).forEach(ids => {
 | 
					 | 
				
			||||||
				ids.forEach(id => store.directs.add(id))
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// set up rooms
 | 
					 | 
				
			||||||
		Object.entries(root.rooms.join).forEach(([id, room]) => {
 | 
					 | 
				
			||||||
			if (!store.rooms.has(id)) {
 | 
					 | 
				
			||||||
				store.rooms.askAdd(id, room)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			const storeRoom = store.rooms.get(id).value()
 | 
					 | 
				
			||||||
			room.state.events.forEach(event => {
 | 
					 | 
				
			||||||
				if (event.type === "m.room.member") {
 | 
					 | 
				
			||||||
					storeRoom.members.get(event.state_key).set(event)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
			const timeline = storeRoom.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
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function syncLoop() {
 | 
					 | 
				
			||||||
	return sync().then(manageSync).then(syncLoop)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
;[
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		id: "directs",
 | 
					 | 
				
			||||||
		name: "Directs",
 | 
					 | 
				
			||||||
		icon: staticFiles.get("/assets/icons/directs.svg"),
 | 
					 | 
				
			||||||
		order: -2
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		id: "channels",
 | 
					 | 
				
			||||||
		name: "Channels",
 | 
					 | 
				
			||||||
		icon: staticFiles.get("/assets/icons/channels.svg"),
 | 
					 | 
				
			||||||
		order: -1
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
].forEach(data => store.groups.askAdd(data.id, data))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
store.activeGroup.set(store.groups.get("directs").value())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
syncLoop()
 | 
					 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue