mirror of
https://github.com/tulir/gomuks.git
synced 2025-04-20 18:43:41 -05:00
web/timeline: add colors for user displaynames
This commit is contained in:
parent
7e793ec0ba
commit
9b73e755e8
5 changed files with 42 additions and 20 deletions
|
@ -28,12 +28,18 @@ export const getEncryptedMediaURL = (mxc?: string): string | undefined => {
|
|||
return getMediaURL(mxc, true)
|
||||
}
|
||||
|
||||
// This should be synced with the sender colors in ui/timeline/TimelineEvent.css
|
||||
const fallbackColors = [
|
||||
"#a4041d", "#9b2200", "#803f00", "#005f00",
|
||||
"#005c45", "#00548c", "#064ab1", "#5d26cd",
|
||||
"#822198", "#9f0850",
|
||||
]
|
||||
|
||||
export const getUserColorIndex = (userID: UserID) =>
|
||||
userID.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0) % fallbackColors.length
|
||||
|
||||
export const getUserColor = (userID: UserID) => fallbackColors[getUserColorIndex(userID)]
|
||||
|
||||
// note: this should stay in sync with fallbackAvatarTemplate in cmd/gomuks.media.go
|
||||
function makeFallbackAvatar(backgroundColor: string, fallbackCharacter: string): string {
|
||||
return "data:image/svg+xml," + encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000">
|
||||
|
@ -55,8 +61,7 @@ function escapeHTMLChar(char: string): string {
|
|||
|
||||
export const getAvatarURL = (userID: UserID, content?: Partial<MemberEventContent>): string | undefined => {
|
||||
const fallbackCharacter = (content?.displayname?.[0]?.toUpperCase() ?? userID[1].toUpperCase()).toWellFormed()
|
||||
const charCodeSum = userID.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0)
|
||||
const backgroundColor = fallbackColors[charCodeSum % fallbackColors.length]
|
||||
const backgroundColor = getUserColor(userID)
|
||||
const [server, mediaID] = parseMXC(content?.avatar_url)
|
||||
if (!mediaID) {
|
||||
return makeFallbackAvatar(backgroundColor, fallbackCharacter)
|
||||
|
|
|
@ -49,11 +49,6 @@ blockquote.reply-body {
|
|||
margin-right: .25rem;
|
||||
}
|
||||
|
||||
> span.event-sender {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
> button.close-reply {
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import { use } from "react"
|
||||
import { getAvatarURL } from "@/api/media.ts"
|
||||
import { getAvatarURL, getUserColorIndex } from "@/api/media.ts"
|
||||
import { RoomStateStore, useRoomEvent, useRoomState } from "@/api/statestore"
|
||||
import type { EventID, MemDBEvent, MemberEventContent } from "@/api/types"
|
||||
import ClientContext from "../ClientContext.ts"
|
||||
|
@ -89,7 +89,9 @@ export const ReplyBody = ({ room, event, onClose, isThread, isEditing }: ReplyBo
|
|||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<span className="event-sender">{memberEvtContent?.displayname || event.sender}</span>
|
||||
<span className={`event-sender sender-color-${getUserColorIndex(event.sender)}`}>
|
||||
{memberEvtContent?.displayname || event.sender}
|
||||
</span>
|
||||
{onClose && <button className="close-reply" onClick={onClose}><CloseButton/></button>}
|
||||
</div>
|
||||
<ContentErrorBoundary>
|
||||
|
|
|
@ -51,8 +51,6 @@ div.timeline-event {
|
|||
|
||||
> span.event-sender {
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
> span.event-time, > span.event-edited {
|
||||
|
@ -148,6 +146,23 @@ div.timeline-event {
|
|||
}
|
||||
}
|
||||
|
||||
span.event-sender {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
/* These should be synced with the avatar colors in api/media.ts */
|
||||
&.sender-color-0 { color: #a4041d; }
|
||||
&.sender-color-1 { color: #9b2200; }
|
||||
&.sender-color-2 { color: #803f00; }
|
||||
&.sender-color-3 { color: #005f00; }
|
||||
&.sender-color-4 { color: #005c45; }
|
||||
&.sender-color-5 { color: #00548c; }
|
||||
&.sender-color-6 { color: #064ab1; }
|
||||
&.sender-color-7 { color: #5d26cd; }
|
||||
&.sender-color-8 { color: #822198; }
|
||||
&.sender-color-9 { color: #9f0850; }
|
||||
}
|
||||
|
||||
div.event-content > div.event-reactions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import React, { use, useState } from "react"
|
||||
import { getAvatarURL, getMediaURL } from "@/api/media.ts"
|
||||
import { getAvatarURL, getMediaURL, getUserColorIndex } from "@/api/media.ts"
|
||||
import { useRoomState } from "@/api/statestore"
|
||||
import { MemDBEvent, MemberEventContent, UnreadType } from "@/api/types"
|
||||
import { isEventID } from "@/util/validation.ts"
|
||||
|
@ -86,14 +86,18 @@ const TimelineEvent = ({ evt, prevEvt, disableMenu }: TimelineEventProps) => {
|
|||
wrapperClassNames.push("highlight")
|
||||
}
|
||||
let smallAvatar = false
|
||||
let renderAvatar = true
|
||||
let eventTimeOnly = false
|
||||
if (isSmallEvent(BodyType)) {
|
||||
wrapperClassNames.push("hidden-event")
|
||||
smallAvatar = true
|
||||
eventTimeOnly = true
|
||||
} else if (prevEvt?.sender === evt.sender &&
|
||||
prevEvt.timestamp + 15 * 60 * 1000 > evt.timestamp &&
|
||||
!isSmallEvent(getBodyType(prevEvt))) {
|
||||
wrapperClassNames.push("same-sender")
|
||||
smallAvatar = true
|
||||
eventTimeOnly = true
|
||||
renderAvatar = false
|
||||
}
|
||||
const fullTime = fullTimeFormatter.format(eventTS)
|
||||
const shortTime = formatShortTime(eventTS)
|
||||
|
@ -104,7 +108,7 @@ const TimelineEvent = ({ evt, prevEvt, disableMenu }: TimelineEventProps) => {
|
|||
{!disableMenu && <div className={`context-menu-container ${forceContextMenuOpen ? "force-open" : ""}`}>
|
||||
<EventMenu evt={evt} setForceOpen={setForceContextMenuOpen}/>
|
||||
</div>}
|
||||
<div className="sender-avatar" title={evt.sender}>
|
||||
{renderAvatar && <div className="sender-avatar" title={evt.sender}>
|
||||
<img
|
||||
className={`${smallAvatar ? "small" : ""} avatar`}
|
||||
loading="lazy"
|
||||
|
@ -112,17 +116,18 @@ const TimelineEvent = ({ evt, prevEvt, disableMenu }: TimelineEventProps) => {
|
|||
onClick={use(LightboxContext)!}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div className="event-sender-and-time">
|
||||
<span className="event-sender">{memberEvtContent?.displayname || evt.sender}</span>
|
||||
</div>}
|
||||
{!eventTimeOnly ? <div className="event-sender-and-time">
|
||||
<span className={`event-sender sender-color-${getUserColorIndex(evt.sender)}`}>
|
||||
{memberEvtContent?.displayname || evt.sender}
|
||||
</span>
|
||||
<span className="event-time" title={fullTime}>{shortTime}</span>
|
||||
{(editEventTS && editTime) ? <span className="event-edited" title={editTime}>
|
||||
(edited at {formatShortTime(editEventTS)})
|
||||
</span> : null}
|
||||
</div>
|
||||
<div className="event-time-only">
|
||||
</div> : <div className="event-time-only">
|
||||
<span className="event-time" title={editTime ? `${fullTime} - ${editTime}` : fullTime}>{shortTime}</span>
|
||||
</div>
|
||||
</div>}
|
||||
<div className="event-content">
|
||||
{isEventID(replyTo) && BodyType !== HiddenEvent ? <ReplyIDBody
|
||||
room={roomCtx.store}
|
||||
|
|
Loading…
Add table
Reference in a new issue