Support chat backgrounds

This commit is contained in:
N Pex 2023-10-18 15:05:50 +00:00
parent ea8522d8a3
commit 4e9aecc304
4 changed files with 97 additions and 56 deletions

View file

@ -59,5 +59,18 @@ The app loads runtime configutation from the server at "./config.json" and merge
* Insert the resulting config blob into the "shortCodeStickers" value of the config file (assets/config.json)
* Rearrange order of sticker packs by editing the config blob above.
### Chat backgrounds
Chat backgrounds can be set using the **chat_backgrounds** config value. It can be set per room type, "direct", "invite" and "public". If no background is set for the current room type, the app will also check if a default "all" value has any backgrounds specified.
Backgrounds are entered as an array. Which of the backgrounds in the array is used for a given room is calculated from the room ID, so that it is constant across the lifetime of the room.
```
chat_backgrounds: {
"direct": ["https://example.com/dm1.png", "data:image/png;base64,yadiyada..."],
"all": ["/default_background.png"]
}
```
### Attributions
Sounds from [Notification Sounds](https://notificationsounds.com)

View file

@ -8,7 +8,7 @@ $admin-fg: white;
body {
--v-app-background: $app-background;
--v-background-color: white;
--v-background-color: rgba(255, 255, 255, 0.8);
--v-foreground-color: black;
--v-secondary-color: #242424;
--v-divider-color: #eeeeee;
@ -233,20 +233,15 @@ body {
}
}
.input-area {
background-color: #e2e2e2;
margin: 0;
padding-left: $chat-standard-padding-s;
padding-right: $chat-standard-padding-s;
}
.input-area-outer {
position: relative;
background-color: #ffffff;
background-color: var(--v-background-color);
margin: 0;
margin-bottom: -10px;
padding-left: 2 * $chat-standard-padding-s;
padding-right: 2 * $chat-standard-padding-s;
padding-top: 0;
padding-bottom: 10px;
&.reply-to {
padding: 0;
@ -283,9 +278,9 @@ body {
background-color: white;
border: 1px solid #d4d4d4;
border-radius: 32px;
margin-bottom: 10px;
@media #{map-get($display-breakpoints, 'sm-and-down')} {
margin-bottom: 2px;
margin-bottom: 12px;
}
}
.input-area-button {
@ -423,7 +418,7 @@ body {
}
position: relative;
.bubble {
background-color: #ededed;
background-color: rgba(#ededed,0.8);
border-radius: 0px 10px 10px 10px;
[dir="rtl"] & {
border-radius: 10px 0px 10px 0px;
@ -437,7 +432,7 @@ body {
max-width: 70%;
}
&.from-admin .bubble {
background-color: $admin-bg;
background-color: rgba($admin-bg,0.8);
}
.audio-bubble {
width: 70%;
@ -516,7 +511,7 @@ body {
}
position: relative;
.bubble {
background-color: #e5e5e5;
background-color: rgba(#e5e5e5,0.8);
border-radius: 10px 10px 0 10px;
[dir="rtl"] & {
border-radius: 10px 10px 10px 0px;
@ -527,7 +522,7 @@ body {
max-width: 70%;
}
.audio-bubble {
background-color: #e5e5e5;
background-color: rgba(#e5e5e5,0.8);
border-radius: 10px 10px 0 10px;
[dir="rtl"] & {
border-radius: 10px 10px 10px 0px;
@ -539,13 +534,6 @@ body {
width: 70%;
height: 50px;
}
.video2-bubble {
background-color: #e5e5e5;
border-radius: 10px 10px 0 10px;
[dir="rtl"] & {
border-radius: 10px 10px 10px 0px;
}
}
.bubble.image-bubble {
padding: 0px;
display: inline-block;
@ -824,11 +812,8 @@ body {
.read-marker {
margin-left: 20px;
margin-right: 20px;
height: 1px;
width: calc(100% - 40px);
line-height: var(--v-theme-title-featured-line-height);
position: absolute;
bottom: 0;
font-family: sans-serif;
font-style: normal;
font-weight: normal;
@ -837,19 +822,18 @@ body {
/* identical to box height, or 14px */
letter-spacing: 0.29px;
color: #c0c0c0;
background-color: #c0c0c0;
text-align: center;
&::after {
position: absolute;
top: -4px;
background: white;
transform: translate(-50%, 0);
[dir="rtl"] & {
transform: translate(50%, 0);
display: flex;
align-items: center;
& div.text {
flex: 0 0 auto;
padding-left: 10px;
padding-right: 10px;
}
padding-left: 4px;
padding-right: 4px;
content: attr(title);
& div.line {
background: #c0c0c0;
height: 1px;
flex: 1 1 auto;
}
}
@ -858,7 +842,6 @@ body {
margin-right: 20px;
margin-top: 20px;
margin-bottom: 20px;
height: 1px;
line-height: var(--v-theme-title-featured-line-height);
font-family: sans-serif;
font-style: normal;
@ -868,20 +851,17 @@ body {
/* identical to box height, or 14px */
letter-spacing: 0.29px;
color: black;
background-color: black;
text-align: center;
position: relative;
&::after {
position: absolute;
top: -8px;
background: white;
transform: translate(-50%, 0);
[dir="rtl"] & {
transform: translate(50%, 0);
}
display: flex;
align-items: center;
& div.text {
flex: 0 0 auto;
padding-left: 10px;
padding-right: 10px;
content: attr(title);
}
& div.line {
background: black;
height: 1px;
flex: 1 1 auto;
}
}

View file

@ -1,5 +1,5 @@
<template>
<div class="chat-root fill-height d-flex flex-column">
<div class="chat-root fill-height d-flex flex-column" :style="chatContainerStyle">
<ChatHeaderPrivate class="chat-header flex-grow-0 flex-shrink-0"
v-on:header-click="onHeaderClick"
v-on:view-room-details="viewRoomDetails"
@ -62,7 +62,7 @@
<div v-for="(event, index) in filteredEvents" :key="event.getId()" :eventId="event.getId()">
<!-- DAY Marker, shown for every new day in the timeline -->
<div v-if="showDayMarkerBeforeEvent(event) && !!componentForEvent(event, isForExport = false)" class="day-marker" :title="dayForEvent(event)" />
<div v-if="showDayMarkerBeforeEvent(event) && !!componentForEvent(event, isForExport = false)" class="day-marker"><div class="line"></div><div class="text">{{ dayForEvent(event) }}</div><div class="line"></div></div>
<div v-if="!event.isRelation() && !event.isRedaction()" :ref="event.getId()">
<div class="message-wrapper" v-on:touchstart="
@ -82,8 +82,7 @@
/>
<!-- <div v-if="debugging" style="user-select:text">EventID: {{ event.getId() }}</div> -->
<!-- <div v-if="debugging" style="user-select:text">Event: {{ JSON.stringify(event) }}</div> -->
<div v-if="event.getId() == readMarker && index < filteredEvents.length - 1" class="read-marker"
:title="$t('message.unread_messages')" />
<div v-if="event.getId() == readMarker && index < filteredEvents.length - 1" class="read-marker"><div class="line"></div><div class="text">{{ $t('message.unread_messages') }}</div><div class="line"></div></div>
</div>
</div>
</div>
@ -727,7 +726,11 @@ export default {
},
isDirectRoom() {
return this.room.getJoinRule() == "invite" && this.joinedAndInvitedMembers.length == 2;
return this.room && this.room.getJoinRule() == "invite" && this.joinedAndInvitedMembers.length == 2;
},
isPublicRoom() {
return this.room && this.room.getJoinRule() == "public";
},
showCreatedRoomWelcomeHeader() {
@ -736,6 +739,51 @@ export default {
showDirectChatWelcomeHeader() {
return !this.hideDirectChatWelcomeHeader && this.roomCreatedByUsRecently && this.isDirectRoom;
},
chatContainerStyle() {
if (this.$config.chat_backgrounds && this.room && this.roomId) {
const roomType = this.isDirectRoom ? "direct" : this.isPublicRoom ? "public" : "invite";
let backgrounds = this.$config.chat_backgrounds[roomType] || this.$config.chat_backgrounds["all"];
if (backgrounds) {
const numBackgrounds = backgrounds.length;
// If we have several backgrounds set, use the room ID to calculate
// an int hash value, then take mod of that to select a background to use.
// That way, we always get the same one, since room IDs don't change.
// From: https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript
const hashCode = function (s) {
var hash = 0,
i, chr;
if (s.length === 0) return hash;
for (i = 0; i < s.length; i++) {
chr = s.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
}
// Adapted from: https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url
const validUrl = function (s) {
let url;
try {
url = new URL(s, window.location);
} catch (err) {
return false;
}
return url.protocol === "http:" || url.protocol === "https:" || url.protocol === "data:";
}
const index = Math.abs(hashCode(this.roomId)) % numBackgrounds;
const background = backgrounds[index];
if (background && validUrl(background)) {
return "background-image: url(" + background + ");background-repeat: repeat";
}
}
}
return "";
}
},

View file

@ -18,7 +18,7 @@
<div class="chat-content flex-grow-1 flex-shrink-1" ref="chatContainer">
<div v-for="(event, index) in events" :key="event.getId()" :eventId="event.getId()">
<!-- DAY Marker, shown for every new day in the timeline -->
<div v-if="showDayMarkerBeforeEvent(event)" class="day-marker" :title="dateForEvent(event)" />
<div v-if="showDayMarkerBeforeEvent(event)" class="day-marker"><div class="line"></div><div class="text">{{ dayForEvent(event) }}</div><div class="line"></div></div>
<div v-if="!event.isRelation() && !event.isRedacted() && !event.isRedaction()" :ref="event.getId()">
<div class="message-wrapper">