Support chat backgrounds
This commit is contained in:
parent
ea8522d8a3
commit
4e9aecc304
4 changed files with 97 additions and 56 deletions
13
README.md
13
README.md
|
|
@ -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)
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 "";
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue