forked from cadence/Carbon
Now technically a chat app
This commit is contained in:
parent
ac6320c12c
commit
33e4a7d7cb
19 changed files with 434 additions and 13 deletions
|
@ -2,11 +2,12 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" type="text/css" href="static/main.css?static=b0aba41b0b">
|
<link rel="stylesheet" type="text/css" href="static/main.css?static=0c50689ce5">
|
||||||
<script type="module" src="static/groups.js?static=2cc7f0daf8"></script>
|
<script type="module" src="static/groups.js?static=2cc7f0daf8"></script>
|
||||||
<script type="module" src="static/chat-input.js?static=a90499fdac"></script>
|
<script type="module" src="static/chat-input.js?static=e8b21037fa"></script>
|
||||||
<script type="module" src="static/room-picker.js?static=d92adfaaca"></script>
|
<script type="module" src="static/room-picker.js?static=1d38378110"></script>
|
||||||
<script type="module" src="static/sync/sync.js?static=232a64285c"></script>
|
<script type="module" src="static/sync/sync.js?static=9e31c8a727"></script>
|
||||||
|
<script type="module" src="static/chat.js?static=d4a415f1ae"></script>
|
||||||
<title>Carbon</title>
|
<title>Carbon</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -20,7 +21,7 @@
|
||||||
<div class="c-rooms" id="c-rooms"></div>
|
<div class="c-rooms" id="c-rooms"></div>
|
||||||
<div class="c-chat">
|
<div class="c-chat">
|
||||||
<div class="c-chat__messages">
|
<div class="c-chat__messages">
|
||||||
<div class="c-chat__inner">
|
<div class="c-chat__inner" id="c-chat">
|
||||||
<div class="c-message-notice">
|
<div class="c-message-notice">
|
||||||
<div class="c-message-notice__inner">You've reached the start of the conversation.</div>
|
<div class="c-message-notice__inner">You've reached the start of the conversation.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
95
build/static/Timeline.js
Normal file
95
build/static/Timeline.js
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import {ElemJS} from "./basic.js"
|
||||||
|
import {Subscribable} from "./store/Subscribable.js"
|
||||||
|
|
||||||
|
class Event extends ElemJS {
|
||||||
|
constructor(data) {
|
||||||
|
super("div")
|
||||||
|
this.class("c-message")
|
||||||
|
this.data = null
|
||||||
|
this.update(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
update(data) {
|
||||||
|
this.data = data
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.child(this.data.content.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Timeline extends Subscribable {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
Object.assign(this.events, {
|
||||||
|
addItem: [],
|
||||||
|
removeItem: []
|
||||||
|
})
|
||||||
|
Object.assign(this.eventDeps, {
|
||||||
|
addItem: [],
|
||||||
|
removeItem: []
|
||||||
|
})
|
||||||
|
this.list = []
|
||||||
|
this.map = new Map()
|
||||||
|
this.elementsMap = new Map()
|
||||||
|
this.elementsList = []
|
||||||
|
}
|
||||||
|
|
||||||
|
_binarySearch(event, min = 0, max = -1) {
|
||||||
|
if (this.list.length === 0) return {success: false, i: 0}
|
||||||
|
|
||||||
|
if (max === -1) max = this.list.length - 1
|
||||||
|
let mid = Math.floor((max + min) / 2)
|
||||||
|
// success condition
|
||||||
|
if (this.list[mid] && this.list[mid].event_id === event.event_id) return {success: true, i: mid}
|
||||||
|
// failed condition
|
||||||
|
if (min >= max) {
|
||||||
|
while (mid !== -1 && (!this.list[mid] || this.list[mid].origin_server_ts > event.origin_server_ts)) mid--
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
i: mid + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// recurse (below)
|
||||||
|
if (this.list[mid].origin_server_ts > event.origin_server_ts) return this._binarySearch(event, min, mid-1)
|
||||||
|
// recurse (above)
|
||||||
|
else return this._binarySearch(event, mid+1, max)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateEvents(events) {
|
||||||
|
for (const event of events) {
|
||||||
|
if (this.map.has(event.event_id)) {
|
||||||
|
this.map.set(event.event_id, event)
|
||||||
|
this.elementsMap.get(event.event_id).update(this.map.get(event.event_id))
|
||||||
|
} else {
|
||||||
|
const index = this._binarySearch(event).i
|
||||||
|
this.list.splice(index, 0, event)
|
||||||
|
this.map.set(event.event_id, event)
|
||||||
|
const e = new Event(event)
|
||||||
|
this.elementsList.splice(index, 0, e)
|
||||||
|
this.elementsMap.set(event.event_id, e)
|
||||||
|
this.broadcast("addItem", {index, element: e})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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}
|
|
@ -119,6 +119,18 @@ class ElemJS {
|
||||||
return this;
|
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.
|
* Remove all children from the element.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,17 +1,46 @@
|
||||||
import {q} from "./basic.js"
|
import {q} from "./basic.js"
|
||||||
|
import {store} from "./store/store.js"
|
||||||
|
import * as lsm from "./lsm.js"
|
||||||
|
|
||||||
|
let sentIndex = 0
|
||||||
|
|
||||||
const chat = q("#c-chat-textarea")
|
const chat = q("#c-chat-textarea")
|
||||||
|
|
||||||
chat.addEventListener("keydown", event => {
|
chat.addEventListener("keydown", event => {
|
||||||
if (event.key === "Enter" && !event.shiftKey && !event.ctrlKey) {
|
if (event.key === "Enter" && !event.shiftKey && !event.ctrlKey) {
|
||||||
chat.value = ""
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
const body = chat.value
|
||||||
|
send(chat.value)
|
||||||
|
chat.value = ""
|
||||||
|
fixHeight()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
chat.addEventListener("input", () => {
|
chat.addEventListener("input", () => {
|
||||||
|
fixHeight()
|
||||||
|
})
|
||||||
|
|
||||||
|
function fixHeight() {
|
||||||
chat.style.height = "0px"
|
chat.style.height = "0px"
|
||||||
console.log(chat.clientHeight, chat.scrollHeight)
|
console.log(chat.clientHeight, chat.scrollHeight)
|
||||||
chat.style.height = (chat.scrollHeight + 1) + "px"
|
chat.style.height = (chat.scrollHeight + 1) + "px"
|
||||||
})
|
}
|
||||||
|
|
||||||
|
function getTxnId() {
|
||||||
|
return Date.now() + (sentIndex++)
|
||||||
|
}
|
||||||
|
|
||||||
|
function send(body) {
|
||||||
|
if (!store.activeRoom.exists()) return
|
||||||
|
const id = store.activeRoom.value().id
|
||||||
|
return fetch(`${lsm.get("domain")}/_matrix/client/r0/rooms/${id}/send/m.room.message/${getTxnId()}?access_token=${lsm.get("access_token")}`, {
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify({
|
||||||
|
msgtype: "m.text",
|
||||||
|
body
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
62
build/static/chat.js
Normal file
62
build/static/chat.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import {ElemJS, q, ejs} from "./basic.js"
|
||||||
|
import {store} from "./store/store.js"
|
||||||
|
|
||||||
|
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 = (_, {element, index}) => {
|
||||||
|
this.childAt(index, element)
|
||||||
|
}
|
||||||
|
const name = "addItem"
|
||||||
|
this.removableSubscriptions.push({name, target: timeline, subscription})
|
||||||
|
timeline.subscribe(name, subscription)
|
||||||
|
}
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.clearChildren()
|
||||||
|
if (store.activeRoom.exists()) {
|
||||||
|
const timeline = store.activeRoom.value().timeline
|
||||||
|
for (const group of timeline.getGroupedEvents()) {
|
||||||
|
const first = group[0]
|
||||||
|
this.child(
|
||||||
|
ejs("div").class("c-message-group").child(
|
||||||
|
ejs("div").class("c-message-group__avatar").child(
|
||||||
|
ejs("div").class("c-message-group__icon")
|
||||||
|
),
|
||||||
|
ejs("div").class("c-message-group__messages").child(
|
||||||
|
ejs("div").class("c-message-group__intro").child(
|
||||||
|
ejs("div").class("c-message-group__name").text(first.sender)
|
||||||
|
),
|
||||||
|
...group.map(event => timeline.elementsMap.get(event.event_id))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new Chat()
|
|
@ -208,6 +208,7 @@ body {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 1fr auto;
|
grid-template-rows: 1fr auto;
|
||||||
align-items: end;
|
align-items: end;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
.c-chat__messages {
|
.c-chat__messages {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {q, ElemJS, ejs} from "./basic.js"
|
import {q, ElemJS, ejs} from "./basic.js"
|
||||||
import {store} from "./store/store.js"
|
import {store} from "./store/store.js"
|
||||||
|
import {Timeline} from "./Timeline.js"
|
||||||
import * as lsm from "./lsm.js"
|
import * as lsm from "./lsm.js"
|
||||||
|
|
||||||
function resolveMxc(url, size, method) {
|
function resolveMxc(url, size, method) {
|
||||||
|
@ -66,6 +67,7 @@ class Room extends ElemJS {
|
||||||
|
|
||||||
this.id = id
|
this.id = id
|
||||||
this.data = data
|
this.data = data
|
||||||
|
this.timeline = new Timeline()
|
||||||
|
|
||||||
this.class("c-room")
|
this.class("c-room")
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,9 @@ class Subscribable {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsubscribe(event, callback) {
|
unsubscribe(event, callback) {
|
||||||
this.events[event].push(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) {
|
broadcast(event, data) {
|
||||||
|
|
|
@ -48,6 +48,8 @@ function manageSync(root) {
|
||||||
if (!store.rooms.has(id)) {
|
if (!store.rooms.has(id)) {
|
||||||
store.rooms.askAdd(id, room)
|
store.rooms.askAdd(id, room)
|
||||||
}
|
}
|
||||||
|
const timeline = store.rooms.get(id).value().timeline
|
||||||
|
timeline.updateEvents(room.timeline.events)
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(root)
|
console.error(root)
|
||||||
|
|
10
spec.js
10
spec.js
|
@ -64,6 +64,16 @@ module.exports = [
|
||||||
source: "/js/lsm.js",
|
source: "/js/lsm.js",
|
||||||
target: "/static/lsm.js"
|
target: "/static/lsm.js"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "js",
|
||||||
|
source: "/js/Timeline.js",
|
||||||
|
target: "/static/Timeline.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "js",
|
||||||
|
source: "/js/chat.js",
|
||||||
|
target: "/static/chat.js"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: "file",
|
type: "file",
|
||||||
source: "/assets/fonts/whitney-500.woff",
|
source: "/assets/fonts/whitney-500.woff",
|
||||||
|
|
|
@ -38,6 +38,7 @@ html
|
||||||
script(type="module" src=getStatic("/js/chat-input.js"))
|
script(type="module" src=getStatic("/js/chat-input.js"))
|
||||||
script(type="module" src=getStatic("/js/room-picker.js"))
|
script(type="module" src=getStatic("/js/room-picker.js"))
|
||||||
script(type="module" src=getStatic("/js/sync/sync.js"))
|
script(type="module" src=getStatic("/js/sync/sync.js"))
|
||||||
|
script(type="module" src=getStatic("/js/chat.js"))
|
||||||
title Carbon
|
title Carbon
|
||||||
body
|
body
|
||||||
main.main
|
main.main
|
||||||
|
@ -48,7 +49,7 @@ html
|
||||||
.c-rooms#c-rooms
|
.c-rooms#c-rooms
|
||||||
.c-chat
|
.c-chat
|
||||||
.c-chat__messages
|
.c-chat__messages
|
||||||
.c-chat__inner
|
.c-chat__inner#c-chat
|
||||||
+message-notice("You've reached the start of the conversation.")
|
+message-notice("You've reached the start of the conversation.")
|
||||||
+message("Cadence", [
|
+message("Cadence", [
|
||||||
`the second button is for rooms (gonna call them "channels" to make discord users happy) that are not in a group (which will be most rooms - few people set up groups because they're so annoying, and many communities of people only need a single chatroom)`,
|
`the second button is for rooms (gonna call them "channels" to make discord users happy) that are not in a group (which will be most rooms - few people set up groups because they're so annoying, and many communities of people only need a single chatroom)`,
|
||||||
|
|
95
src/js/Timeline.js
Normal file
95
src/js/Timeline.js
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import {ElemJS} from "./basic.js"
|
||||||
|
import {Subscribable} from "./store/Subscribable.js"
|
||||||
|
|
||||||
|
class Event extends ElemJS {
|
||||||
|
constructor(data) {
|
||||||
|
super("div")
|
||||||
|
this.class("c-message")
|
||||||
|
this.data = null
|
||||||
|
this.update(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
update(data) {
|
||||||
|
this.data = data
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.child(this.data.content.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Timeline extends Subscribable {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
Object.assign(this.events, {
|
||||||
|
addItem: [],
|
||||||
|
removeItem: []
|
||||||
|
})
|
||||||
|
Object.assign(this.eventDeps, {
|
||||||
|
addItem: [],
|
||||||
|
removeItem: []
|
||||||
|
})
|
||||||
|
this.list = []
|
||||||
|
this.map = new Map()
|
||||||
|
this.elementsMap = new Map()
|
||||||
|
this.elementsList = []
|
||||||
|
}
|
||||||
|
|
||||||
|
_binarySearch(event, min = 0, max = -1) {
|
||||||
|
if (this.list.length === 0) return {success: false, i: 0}
|
||||||
|
|
||||||
|
if (max === -1) max = this.list.length - 1
|
||||||
|
let mid = Math.floor((max + min) / 2)
|
||||||
|
// success condition
|
||||||
|
if (this.list[mid] && this.list[mid].event_id === event.event_id) return {success: true, i: mid}
|
||||||
|
// failed condition
|
||||||
|
if (min >= max) {
|
||||||
|
while (mid !== -1 && (!this.list[mid] || this.list[mid].origin_server_ts > event.origin_server_ts)) mid--
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
i: mid + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// recurse (below)
|
||||||
|
if (this.list[mid].origin_server_ts > event.origin_server_ts) return this._binarySearch(event, min, mid-1)
|
||||||
|
// recurse (above)
|
||||||
|
else return this._binarySearch(event, mid+1, max)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateEvents(events) {
|
||||||
|
for (const event of events) {
|
||||||
|
if (this.map.has(event.event_id)) {
|
||||||
|
this.map.set(event.event_id, event)
|
||||||
|
this.elementsMap.get(event.event_id).update(this.map.get(event.event_id))
|
||||||
|
} else {
|
||||||
|
const index = this._binarySearch(event).i
|
||||||
|
this.list.splice(index, 0, event)
|
||||||
|
this.map.set(event.event_id, event)
|
||||||
|
const e = new Event(event)
|
||||||
|
this.elementsList.splice(index, 0, e)
|
||||||
|
this.elementsMap.set(event.event_id, e)
|
||||||
|
this.broadcast("addItem", {index, element: e})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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}
|
|
@ -119,6 +119,18 @@ class ElemJS {
|
||||||
return this;
|
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.
|
* Remove all children from the element.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,17 +1,46 @@
|
||||||
import {q} from "./basic.js"
|
import {q} from "./basic.js"
|
||||||
|
import {store} from "./store/store.js"
|
||||||
|
import * as lsm from "./lsm.js"
|
||||||
|
|
||||||
|
let sentIndex = 0
|
||||||
|
|
||||||
const chat = q("#c-chat-textarea")
|
const chat = q("#c-chat-textarea")
|
||||||
|
|
||||||
chat.addEventListener("keydown", event => {
|
chat.addEventListener("keydown", event => {
|
||||||
if (event.key === "Enter" && !event.shiftKey && !event.ctrlKey) {
|
if (event.key === "Enter" && !event.shiftKey && !event.ctrlKey) {
|
||||||
chat.value = ""
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
const body = chat.value
|
||||||
|
send(chat.value)
|
||||||
|
chat.value = ""
|
||||||
|
fixHeight()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
chat.addEventListener("input", () => {
|
chat.addEventListener("input", () => {
|
||||||
|
fixHeight()
|
||||||
|
})
|
||||||
|
|
||||||
|
function fixHeight() {
|
||||||
chat.style.height = "0px"
|
chat.style.height = "0px"
|
||||||
console.log(chat.clientHeight, chat.scrollHeight)
|
console.log(chat.clientHeight, chat.scrollHeight)
|
||||||
chat.style.height = (chat.scrollHeight + 1) + "px"
|
chat.style.height = (chat.scrollHeight + 1) + "px"
|
||||||
})
|
}
|
||||||
|
|
||||||
|
function getTxnId() {
|
||||||
|
return Date.now() + (sentIndex++)
|
||||||
|
}
|
||||||
|
|
||||||
|
function send(body) {
|
||||||
|
if (!store.activeRoom.exists()) return
|
||||||
|
const id = store.activeRoom.value().id
|
||||||
|
return fetch(`${lsm.get("domain")}/_matrix/client/r0/rooms/${id}/send/m.room.message/${getTxnId()}?access_token=${lsm.get("access_token")}`, {
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify({
|
||||||
|
msgtype: "m.text",
|
||||||
|
body
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
62
src/js/chat.js
Normal file
62
src/js/chat.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import {ElemJS, q, ejs} from "./basic.js"
|
||||||
|
import {store} from "./store/store.js"
|
||||||
|
|
||||||
|
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 = (_, {element, index}) => {
|
||||||
|
this.childAt(index, element)
|
||||||
|
}
|
||||||
|
const name = "addItem"
|
||||||
|
this.removableSubscriptions.push({name, target: timeline, subscription})
|
||||||
|
timeline.subscribe(name, subscription)
|
||||||
|
}
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.clearChildren()
|
||||||
|
if (store.activeRoom.exists()) {
|
||||||
|
const timeline = store.activeRoom.value().timeline
|
||||||
|
for (const group of timeline.getGroupedEvents()) {
|
||||||
|
const first = group[0]
|
||||||
|
this.child(
|
||||||
|
ejs("div").class("c-message-group").child(
|
||||||
|
ejs("div").class("c-message-group__avatar").child(
|
||||||
|
ejs("div").class("c-message-group__icon")
|
||||||
|
),
|
||||||
|
ejs("div").class("c-message-group__messages").child(
|
||||||
|
ejs("div").class("c-message-group__intro").child(
|
||||||
|
ejs("div").class("c-message-group__name").text(first.sender)
|
||||||
|
),
|
||||||
|
...group.map(event => timeline.elementsMap.get(event.event_id))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new Chat()
|
|
@ -1,5 +1,6 @@
|
||||||
import {q, ElemJS, ejs} from "./basic.js"
|
import {q, ElemJS, ejs} from "./basic.js"
|
||||||
import {store} from "./store/store.js"
|
import {store} from "./store/store.js"
|
||||||
|
import {Timeline} from "./Timeline.js"
|
||||||
import * as lsm from "./lsm.js"
|
import * as lsm from "./lsm.js"
|
||||||
|
|
||||||
function resolveMxc(url, size, method) {
|
function resolveMxc(url, size, method) {
|
||||||
|
@ -66,6 +67,7 @@ class Room extends ElemJS {
|
||||||
|
|
||||||
this.id = id
|
this.id = id
|
||||||
this.data = data
|
this.data = data
|
||||||
|
this.timeline = new Timeline()
|
||||||
|
|
||||||
this.class("c-room")
|
this.class("c-room")
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,9 @@ class Subscribable {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsubscribe(event, callback) {
|
unsubscribe(event, callback) {
|
||||||
this.events[event].push(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) {
|
broadcast(event, data) {
|
||||||
|
|
|
@ -48,6 +48,8 @@ function manageSync(root) {
|
||||||
if (!store.rooms.has(id)) {
|
if (!store.rooms.has(id)) {
|
||||||
store.rooms.askAdd(id, room)
|
store.rooms.askAdd(id, room)
|
||||||
}
|
}
|
||||||
|
const timeline = store.rooms.get(id).value().timeline
|
||||||
|
timeline.updateEvents(room.timeline.events)
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(root)
|
console.error(root)
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
display: grid
|
display: grid
|
||||||
grid-template-rows: 1fr auto
|
grid-template-rows: 1fr auto
|
||||||
align-items: end
|
align-items: end
|
||||||
// height: 100%
|
flex: 1
|
||||||
|
|
||||||
&__messages
|
&__messages
|
||||||
height: 100%
|
height: 100%
|
||||||
|
|
Loading…
Reference in a new issue