diff --git a/spec.js b/spec.js
index d1429c2..4244cdf 100644
--- a/spec.js
+++ b/spec.js
@@ -54,6 +54,26 @@ module.exports = [
source: "/assets/icons/profile-event.svg",
target: "/static/profile-event.svg",
},
+ {
+ type: "file",
+ source: "/assets/icons/call-out.svg",
+ target: "/static/call-out.svg",
+ },
+ {
+ type: "file",
+ source: "/assets/icons/call-in.svg",
+ target: "/static/call-in.svg",
+ },
+ {
+ type: "file",
+ source: "/assets/icons/call-accepted.svg",
+ target: "/static/call-accepted.svg",
+ },
+ {
+ type: "file",
+ source: "/assets/icons/call-rejected.svg",
+ target: "/static/call-rejected.svg",
+ },
{
type: "sass",
source: "/sass/main.sass",
diff --git a/src/assets/icons/call-accepted.svg b/src/assets/icons/call-accepted.svg
new file mode 100644
index 0000000..fea7031
--- /dev/null
+++ b/src/assets/icons/call-accepted.svg
@@ -0,0 +1,94 @@
+
+
diff --git a/src/assets/icons/call-in.svg b/src/assets/icons/call-in.svg
new file mode 100644
index 0000000..484fe5c
--- /dev/null
+++ b/src/assets/icons/call-in.svg
@@ -0,0 +1,99 @@
+
+
diff --git a/src/assets/icons/call-out.svg b/src/assets/icons/call-out.svg
new file mode 100644
index 0000000..877bad4
--- /dev/null
+++ b/src/assets/icons/call-out.svg
@@ -0,0 +1,98 @@
+
+
diff --git a/src/assets/icons/call-rejected.svg b/src/assets/icons/call-rejected.svg
new file mode 100644
index 0000000..55b3994
--- /dev/null
+++ b/src/assets/icons/call-rejected.svg
@@ -0,0 +1,98 @@
+
+
diff --git a/src/js/events/call.js b/src/js/events/call.js
new file mode 100644
index 0000000..a918a48
--- /dev/null
+++ b/src/js/events/call.js
@@ -0,0 +1,67 @@
+const {UngroupableEvent} = require("./event")
+const {ejs} = require("../basic")
+const lsm = require("../lsm")
+const {extractDisplayName, resolveMxc, extractLocalpart} = require("../functions")
+
+class CallEvent extends UngroupableEvent {
+ constructor(data) {
+ super(data)
+ this.class("c-message-event")
+ this.senderName = extractLocalpart(this.data.sender)
+ this.render()
+ }
+
+ renderInner(iconURL, elements) {
+ this.clearChildren()
+ this.child(
+ ejs("div").class("c-message-event__inner").child(
+ iconURL ? ejs("img").class("c-message-event__icon").attribute("width", "20").attribute("height", "20").attribute("src", iconURL) : "",
+ ...elements
+ )
+ )
+ super.render()
+ }
+}
+
+class CallInviteEvent extends CallEvent {
+ static canRender(eventData) {
+ return eventData.type === "m.call.invite"
+ }
+
+ render() {
+ const icon = this.data.sender === lsm.get("mx_user_id") ? "static/call-out.svg" : "static/call-in.svg"
+ this.renderInner(icon, [
+ this.senderName,
+ " started a VOIP call, but Carbon doesn't support VOIP calls"
+ ])
+ }
+}
+
+class CallAnswerEvent extends CallEvent {
+ static canRender(eventData) {
+ return eventData.type === "m.call.answer"
+ }
+
+ render() {
+ this.renderInner("static/call-accepted.svg", [
+ this.senderName,
+ " answered the call"
+ ])
+ }
+}
+
+class CallHangupEvent extends CallEvent {
+ static canRender(eventData) {
+ return eventData.type === "m.call.hangup"
+ }
+
+ render() {
+ const reason = this.data.content.reason === "invite_timeout" ? "missed the call" : "hung up the call"
+ this.renderInner("static/call-rejected.svg", [
+ this.senderName,
+ " " + reason
+ ])
+ }
+}
+
+module.exports = [CallInviteEvent, CallAnswerEvent, CallHangupEvent]
diff --git a/src/js/events/image.js b/src/js/events/image.js
index 2c8a7d3..1a32bcf 100644
--- a/src/js/events/image.js
+++ b/src/js/events/image.js
@@ -24,7 +24,7 @@ class Image extends GroupableEvent {
wrapper.class("c-media--spoiler")
const wall = ejs("div").class("c-media__spoiler").text("Spoiler")
wrapper.child(wall)
- function toggle() {
+ const toggle = () => {
wrapper.element.classList.toggle("c-media--shown")
}
wrapper.on("click", toggle)
diff --git a/src/js/events/render-event.js b/src/js/events/render-event.js
index 9337a2d..627c60f 100644
--- a/src/js/events/render-event.js
+++ b/src/js/events/render-event.js
@@ -3,12 +3,14 @@ const messageEvent = require("./message")
const encryptedEvent = require("./encrypted")
const membershipEvent = require("./membership")
const unknownEvent = require("./unknown")
+const callEvent = require("./call")
const events = [
...imageEvent,
...messageEvent,
...encryptedEvent,
...membershipEvent,
+ ...callEvent,
...unknownEvent,
]
diff --git a/src/js/functions.js b/src/js/functions.js
index db43a16..3c346c4 100644
--- a/src/js/functions.js
+++ b/src/js/functions.js
@@ -1,7 +1,9 @@
const lsm = require("./lsm.js")
function resolveMxc(url, size, method) {
- let [server, id] = url.match(/^mxc:\/\/([^/]+)\/(.*)/).slice(1)
+ const match = url.match(/^mxc:\/\/([^/]+)\/(.*)/)
+ if (!match) return url
+ let [server, id] = match.slice(1)
id = id.replace(/#.*$/, "")
if (size && method) {
return `${lsm.get("domain")}/_matrix/media/r0/thumbnail/${server}/${id}?width=${size}&height=${size}&method=${method}`
diff --git a/src/js/timeline.js b/src/js/timeline.js
index 3ef646b..4a55406 100644
--- a/src/js/timeline.js
+++ b/src/js/timeline.js
@@ -259,7 +259,7 @@ class Timeline extends Subscribable {
this.map.get(id).update(eventData)
} else {
// skip displaying events that we don't know how to
- if (eventData.type === "m.reaction") {
+ if (["m.reaction", "m.call.candidates"].includes(eventData.type)) {
continue
}
// skip redacted events