Greatly improved membership event display
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Cadence Ember 2020-11-27 02:48:30 +13:00
parent 70cae25aa7
commit e6fc1de276
Signed by: cadence
GPG key ID: BC1C2C61CF521B17
13 changed files with 384 additions and 44 deletions

15
spec.js
View file

@ -39,6 +39,21 @@ module.exports = [
source: "/assets/icons/join-event.svg", source: "/assets/icons/join-event.svg",
target: "/static/join-event.svg", target: "/static/join-event.svg",
}, },
{
type: "file",
source: "/assets/icons/leave-event.svg",
target: "/static/leave-event.svg",
},
{
type: "file",
source: "/assets/icons/invite-event.svg",
target: "/static/invite-event.svg",
},
{
type: "file",
source: "/assets/icons/profile-event.svg",
target: "/static/profile-event.svg",
},
{ {
type: "sass", type: "sass",
source: "/sass/main.sass", source: "/sass/main.sass",

View file

@ -0,0 +1,81 @@
<?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="invite-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="16"
inkscape:cx="12.866591"
inkscape:cy="7.092849"
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:snap-global="false"
inkscape:snap-bbox="true"
inkscape:bbox-nodes="true">
<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
sodipodi:type="star"
style="opacity:1;fill:#fce94f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.50955456;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill"
id="path1026"
sodipodi:sides="4"
sodipodi:cx="2.5607762"
sodipodi:cy="294.50937"
sodipodi:r1="2.1649818"
sodipodi:r2="0.86599272"
sodipodi:arg1="0.78539816"
sodipodi:arg2="1.5707963"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 4.0916496,296.04024 -1.5308733,-0.66488 -1.5308734,0.66488 0.6648806,-1.53087 -0.6648806,-1.53087 1.5308733,0.66488 1.5308734,-0.66488 -0.6648806,1.53087 z"
transform="matrix(0.73526681,0.7333768,-0.7333768,0.73526681,216.88378,75.810398)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -25,9 +25,9 @@
borderopacity="1.0" borderopacity="1.0"
inkscape:pageopacity="0.0" inkscape:pageopacity="0.0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:zoom="1" inkscape:zoom="11.313708"
inkscape:cx="15.649008" inkscape:cx="-4.2728481"
inkscape:cy="8.3751893" inkscape:cy="-2.1951295"
inkscape:document-units="px" inkscape:document-units="px"
inkscape:current-layer="layer1" inkscape:current-layer="layer1"
showgrid="true" showgrid="true"

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,80 @@
<?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="leave-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="11.313708"
inkscape:cx="-4.2728481"
inkscape:cy="-2.1951295"
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:#f43f3f;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 4.4979167,294.35416 H 0.79374997"
id="path28"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
inkscape:connector-curvature="0"
id="path30"
d="m 2.1166667,293.03124 -1.32291673,1.32292"
style="opacity:1;fill:#e2e2e2;fill-opacity:1;fill-rule:nonzero;stroke:#f43f3f;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"
sodipodi:nodetypes="cc" />
<path
sodipodi:nodetypes="cc"
style="opacity:1;fill:#e2e2e2;fill-opacity:1;fill-rule:nonzero;stroke:#f43f3f;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 0.79374997,294.35416 1.32291673,1.32291"
id="path32"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,83 @@
<?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="profile-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="16"
inkscape:cx="8.674554"
inkscape:cy="12.76461"
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:snap-global="true"
inkscape:snap-bbox="true"
inkscape:bbox-nodes="true">
<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:none;fill-opacity:1;fill-rule:nonzero;stroke:#729fcf;stroke-width:0.52916664;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill"
d="m 2.5761745,295.93007 c -0.6374174,0.0773 -1.2586148,-0.23706 -1.5739225,-0.79639 -0.31531561,-0.55933 -0.26263464,-1.25353 0.1334534,-1.75888 0.3960878,-0.50535 1.057602,-0.72235 1.6760616,-0.54979 0.6184669,0.17255 1.0674318,0.73341 1.3352208,1.26428"
id="path941"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csscc" />
<path
style="opacity:1;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#729fcf;stroke-width:0.52916664;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill"
d="M 4.1469878,294.08929 3.0515179,293.89345"
id="path943"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path948"
d="m 4.2827903,292.91012 -0.1358025,1.17917"
style="opacity:1;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#729fcf;stroke-width:0.52916664;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -1,7 +1,7 @@
const {MatrixEvent} = require("./event") const {GroupableEvent} = require("./event")
const {ejs} = require("../basic") const {ejs} = require("../basic")
class EncryptedMessage extends MatrixEvent { class EncryptedMessage extends GroupableEvent {
render() { render() {
this.clearChildren() this.clearChildren()
this.child( this.child(
@ -13,10 +13,6 @@ class EncryptedMessage extends MatrixEvent {
static canRender(eventData) { static canRender(eventData) {
return eventData.type === "m.room.encrypted" return eventData.type === "m.room.encrypted"
} }
canGroup() {
return true
}
} }
module.exports = [EncryptedMessage] module.exports = [EncryptedMessage]

View file

@ -5,7 +5,6 @@ const {SubscribeSet} = require("../store/subscribe_set.js")
class MatrixEvent extends ElemJS { class MatrixEvent extends ElemJS {
constructor(data) { constructor(data) {
super("div") super("div")
this.class("c-message")
this.data = null this.data = null
this.group = null this.group = null
this.editedAt = null this.editedAt = null
@ -53,4 +52,21 @@ class MatrixEvent extends ElemJS {
} }
} }
module.exports = {MatrixEvent} class GroupableEvent extends MatrixEvent {
constructor(data) {
super(data)
this.class("c-message")
}
canGroup() {
return true
}
}
class UngroupableEvent extends MatrixEvent {
}
module.exports = {
GroupableEvent,
UngroupableEvent
}

View file

@ -1,8 +1,8 @@
const {ejs, ElemJS} = require("../basic") const {ejs, ElemJS} = require("../basic")
const {resolveMxc} = require("../functions") const {resolveMxc} = require("../functions")
const {MatrixEvent} = require("./event") const {GroupableEvent} = require("./event")
class Image extends MatrixEvent { class Image extends GroupableEvent {
render() { render() {
this.clearChildren() this.clearChildren()
this.class("c-message--media") this.class("c-message--media")

View file

@ -1,15 +1,35 @@
const {MatrixEvent} = require("./event") const {UngroupableEvent} = require("./event")
const {ejs} = require("../basic") const {ejs} = require("../basic")
const {extractDisplayName, resolveMxc, extractLocalpart} = require("../functions")
class MembershipEvent extends UngroupableEvent {
constructor(data) {
super(data)
this.class("c-message-event")
this.senderName = extractDisplayName(data)
if (data.content.avatar_url) {
this.smallAvatar = ejs("img")
.attribute("width", "32")
.attribute("height", "32")
.attribute("src", resolveMxc(data.content.avatar_url, 32, "crop"))
.class("c-message-event__avatar")
} else {
this.smallAvatar = ""
}
this.render()
}
class MembershipEvent extends MatrixEvent {
static canRender(eventData) { static canRender(eventData) {
return eventData.type === "m.room.member" return eventData.type === "m.room.member"
} }
renderText(text) { renderInner(iconURL, elements) {
this.clearChildren() this.clearChildren()
this.child( this.child(
ejs("i").text(text) 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() super.render()
} }
@ -22,7 +42,30 @@ class JoinedEvent extends MembershipEvent {
} }
render() { render() {
this.renderText("joined the room") const changes = []
const prev = this.data.unsigned.prev_content
if (prev && prev.membership === "join") {
if (prev.avatar_url !== this.data.content.avatar_url) {
changes.push("changed their avatar")
}
if (prev.displayname !== this.data.content.displayname) {
changes.push(`changed their display name (was ${this.data.unsigned.prev_content.displayname})`)
}
}
let message
let iconURL
if (changes.length) {
message = " " + changes.join(", ")
iconURL = "static/profile-event.svg"
} else {
message = " joined the room"
iconURL = "static/join-event.svg"
}
this.renderInner(iconURL, [
this.smallAvatar,
this.senderName,
message
])
} }
} }
@ -32,7 +75,10 @@ class InvitedEvent extends MembershipEvent {
} }
render() { render() {
this.renderText(`invited ${this.data.content.displayname}`) this.renderInner("static/invite-event.svg", [
this.smallAvatar,
`${extractLocalpart(this.data.sender)} invited ${this.data.state_key}` // full mxid for clarity
])
} }
} }
@ -42,13 +88,21 @@ class LeaveEvent extends MembershipEvent {
} }
render() { render() {
this.renderText("left the room") this.renderInner("static/leave-event.svg", [
this.smallAvatar,
this.senderName,
" left the room"
])
} }
} }
class UnknownMembership extends MembershipEvent { class UnknownMembership extends MembershipEvent {
render() { render() {
this.renderText("unknown membership event") this.renderInner("", [
this.smallAvatar,
this.senderName,
ejs("i").text(" unknown membership event")
])
} }
} }

View file

@ -2,7 +2,7 @@ const {ejs, ElemJS} = require("../basic")
const {HighlightedCode} = require("./components") const {HighlightedCode} = require("./components")
const DOMPurify = require("dompurify") const DOMPurify = require("dompurify")
const {resolveMxc} = require("../functions") const {resolveMxc} = require("../functions")
const {MatrixEvent} = require("./event") const {GroupableEvent} = require("./event")
const purifier = DOMPurify() const purifier = DOMPurify()
@ -88,7 +88,7 @@ function postProcessElements(element) {
} }
class HTMLMessage extends MatrixEvent { class HTMLMessage extends GroupableEvent {
render() { render() {
this.clearChildren() this.clearChildren()
@ -111,10 +111,6 @@ class HTMLMessage extends MatrixEvent {
&& content.formatted_body && content.formatted_body
) )
} }
canGroup() {
return true
}
} }
function autoLinkText(text) { function autoLinkText(text) {
@ -139,7 +135,7 @@ function autoLinkText(text) {
return fragment return fragment
} }
class TextMessage extends MatrixEvent { class TextMessage extends GroupableEvent {
render() { render() {
this.clearChildren() this.clearChildren()
this.class("c-message--plain") this.class("c-message--plain")
@ -151,10 +147,6 @@ class TextMessage extends MatrixEvent {
static canRender(event) { static canRender(event) {
return event.type === "m.room.message" return event.type === "m.room.message"
} }
canGroup() {
return true
}
} }
module.exports = [HTMLMessage, TextMessage] module.exports = [HTMLMessage, TextMessage]

View file

@ -1,7 +1,7 @@
const {MatrixEvent} = require("./event") const {GroupableEvent} = require("./event")
const {ejs} = require("../basic") const {ejs} = require("../basic")
class UnknownEvent extends MatrixEvent { class UnknownEvent extends GroupableEvent {
static canRender() { static canRender() {
return true return true
} }

View file

@ -65,6 +65,11 @@ class EventGroup extends ElemJS {
) )
} }
canGroup() {
if (this.list.length) return this.list[0].canGroup()
else return true
}
addEvent(event) { addEvent(event) {
const index = eventSearch(this.list, event).i const index = eventSearch(this.list, event).i
event.setGroup(this) event.setGroup(this)
@ -142,16 +147,21 @@ class ReactiveTimeline extends ElemJS {
const success = indices.some(i => { const success = indices.some(i => {
if (!this.list[i]) { if (!this.list[i]) {
// if (printed++ < 100) console.log("tryadd success, created group") // if (printed++ < 100) console.log("tryadd success, created group")
const group = new EventGroup(this, [event])
if (i === -1) { if (i === -1) {
// here, -1 means at the start, before the first group // here, -1 means at the start, before the first group
i = 0 // jank but it does the trick i = 0 // jank but it does the trick
} }
this.list.splice(i, 0, group) if (event.canGroup()) {
this.childAt(i, group) const group = new EventGroup(this, [event])
event.setGroup(group) this.list.splice(i, 0, group)
this.childAt(i, group)
event.setGroup(group)
} else {
this.list.splice(i, 0, event)
this.childAt(i, event)
}
return true return true
} else if (this.list[i] && this.list[i].data.sender === event.data.sender) { } else if (event.canGroup() && this.list[i] && this.list[i].canGroup() && this.list[i].data.sender === event.data.sender) {
// if (printed++ < 100) console.log("tryadd success, using existing group") // if (printed++ < 100) console.log("tryadd success, using existing group")
this.list[i].addEvent(event) this.list[i].addEvent(event)
return true return true

View file

@ -116,17 +116,30 @@
margin-bottom: 0px margin-bottom: 0px
.c-message-event .c-message-event
padding-top: 10px // closer spacing than normal messages
padding-top: 2px
padding-left: 6px padding-left: 6px
margin-bottom: -4px
line-height: 1.2
&__inner &__inner
display: flex text-indent: -36px
align-items: center margin-left: 36px
img
// let me know if there's a smarter way to line this shit up
position: relative
top: -5px
transform: translateY(50%)
&__icon &__icon
margin-right: 8px margin-right: 8px
position: relative
top: 1px &__avatar
width: 16px
height: 16px
border-radius: 50%
margin: 0px 6px
.c-message-notice .c-message-notice
padding: 12px padding: 12px