Merge pull request 'Seperate themes and layouts into seperate settings' (#63) from better-themes into main
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful

Reviewed-on: #63
Reviewed-by: SauceyRed <saucey@saucey.red>
This commit is contained in:
Twig 2025-08-05 21:47:19 +00:00
commit 6df0545f41
27 changed files with 360 additions and 141 deletions

View file

@ -9,14 +9,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import ContextMenu from '~/components/UserInterface/ContextMenu.vue'; import ContextMenu from '~/components/UserInterface/ContextMenu.vue';
import type { ContextMenuInterface } from './types/interfaces'; import type { ContextMenuInterface } from './types/interfaces';
import loadPreferredTheme from '~/utils/loadPreferredTheme';
const banner = useState("banner", () => false); const banner = useState("banner", () => false);
const contextMenu = useState<ContextMenuInterface>("contextMenu"); const contextMenu = useState<ContextMenuInterface>("contextMenu");
onMounted(() => { onMounted(() => {
loadPreferredTheme() loadPreferredThemes()
document.removeEventListener("contextmenu", contextMenuHandler); document.removeEventListener("contextmenu", contextMenuHandler);
document.addEventListener("contextmenu", (e) => { document.addEventListener("contextmenu", (e) => {

View file

@ -27,7 +27,7 @@ const props = defineProps<{ options: DropdownOption[] }>();
} }
.dropdown-option { .dropdown-option {
border: .09rem solid rgb(70, 70, 70); border: .09rem solid var(--padding-color);
} }
.dropdown-button { .dropdown-button {

View file

@ -1,7 +1,9 @@
<template> <template>
<div class="member-item" @click.prevent="showModalPopup" tabindex="0"> <div class="member-item" @click.prevent="showModalPopup" tabindex="0">
<Avatar :profile="props.member" class="member-avatar"/> <Avatar :profile="props.member" class="member-avatar"/>
<span class="member-display-name">{{ getDisplayName(props.member) }}</span> <span class="member-display-name" :style="`color: ${generateIrcColor(props.member.user.uuid)}`">
{{ getDisplayName(props.member) }}
</span>
</div> </div>
<ModalProfilePopup v-if="modalPopupVisible" :profile="props.member" <ModalProfilePopup v-if="modalPopupVisible" :profile="props.member"
:onFinish="hideModalPopup" :keepalive="false"/> :onFinish="hideModalPopup" :keepalive="false"/>

View file

@ -270,11 +270,11 @@ function getDayDifference(date1: Date, date2: Date) {
*/ */
.mentioned { .mentioned {
background-color: rgba(0, 255, 166, 0.123); background-color: var(--chat-important-background-color);
} }
.mentioned:hover { .mentioned:hover {
background-color: rgba(90, 255, 200, 0.233); background-color: var(--chat-important-highlighted-background-color);
} }
.message-reply-svg { .message-reply-svg {
@ -299,7 +299,7 @@ function getDayDifference(date1: Date, date2: Date) {
<style> <style>
.replying-to { .replying-to {
background-color: var(--primary-highlighted-color); background-color: var(--chat-featured-message-color);
} }
</style> </style>

View file

@ -7,7 +7,7 @@
:margin-bottom="(messages[i + 1] && messagesType[messages[i + 1].uuid] == 'normal') ?? false" :margin-bottom="(messages[i + 1] && messagesType[messages[i + 1].uuid] == 'normal') ?? false"
:last="i == messages.length - 1" :message-id="message.uuid" :author="message.member" :me="me" :last="i == messages.length - 1" :message-id="message.uuid" :author="message.member" :me="me"
:message="message" :is-reply="message.reply_to" :message="message" :is-reply="message.reply_to"
:author-color="`${generateIrcColor(message.member.uuid)}`" :author-color="`${generateIrcColor(message.member.user.uuid)}`"
:reply-message="message.reply_to ? getReplyMessage(message.reply_to) : undefined" /> :reply-message="message.reply_to ? getReplyMessage(message.reply_to) : undefined" />
</div> </div>
<div id="message-box" class="rounded-corners"> <div id="message-box" class="rounded-corners">

View file

@ -2,20 +2,44 @@
<div> <div>
<h1>Appearance</h1> <h1>Appearance</h1>
<p class="subtitle">THEMES</p> <h2>Themes</h2>
<div class="themes"> <div class="themes">
<div v-for="theme of themes" class="theme-preview-container"> <p class="subtitle">STYLES</p>
<span class="theme-preview" <div class="styles">
:title="theme.displayName" <div v-for="style of styles" class="theme-preview-container">
:style="{background:`linear-gradient(${theme.previewGradient})`}" <span class="theme-instance"
@click="changeTheme(theme.id, theme.themeUrl)" :title="style.displayName"
> @click="changeTheme(StyleLayout.Style, style)">
<span class="theme-title" :style="{color:`${theme.complementaryColor}`}"> <div class="theme-content-container">
{{ theme.displayName }} <span class="style-background"
:style="{background:`linear-gradient(${style.previewGradient})`}"
></span>
<span class="theme-title" :style="{color:`${style.complementaryColor}`}">
{{ style.displayName }}
</span> </span>
</div>
</span> </span>
</div> </div>
</div> </div>
<p class="subtitle">LAYOUTS</p>
<div class="layouts">
<div v-for="layout of layouts" class="theme-preview-container">
<div class="theme-instance"
:title="layout.displayName"
@click="changeTheme(StyleLayout.Layout, layout)">
<div class="theme-content-container">
<span class="layout-background"
:style="{backgroundImage:`url(${layout.previewImageUrl})`}"
></span>
<span class="theme-title" :style="{color:`${layout.complementaryColor}`}">
{{ layout.displayName }}
</span>
<NuxtImg class="layout-preview" :src="layout.previewImageUrl"></NuxtImg>
</div>
</div>
</div>
</div>
</div>
<!-- <p class="subtitle">Icons</p> <!-- <p class="subtitle">Icons</p>
<div class="icons"> <div class="icons">
@ -32,39 +56,119 @@
<script lang="ts" setup> <script lang="ts" setup>
import RadioButtons from '~/components/UserInterface/RadioButtons.vue'; import RadioButtons from '~/components/UserInterface/RadioButtons.vue';
import type { TimeFormat } from '~/types/settings'; import type { TimeFormat } from '~/types/settings';
import loadPreferredTheme from '~/utils/loadPreferredTheme'; import { settingSave, settingsLoad } from '#imports';
import settingSave from '~/utils/settingSave';
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
const defaultThemes = runtimeConfig.public.defaultThemes
const baseURL = runtimeConfig.app.baseURL; const baseURL = runtimeConfig.app.baseURL;
const styleFolder = `${baseURL}themes/style`
const layoutFolder = `${baseURL}themes/layout`
const timeFormatTextStrings = ["Auto", "12-Hour", "24-Hour"] const timeFormatTextStrings = ["Auto", "12-Hour", "24-Hour"]
const themes: Array<Theme> = [] enum StyleLayout {
Style,
Layout
}
interface Theme { interface Theme {
id: string
displayName: string displayName: string
previewGradient: string
complementaryColor: string complementaryColor: string
cssData: string
themeUrl: string themeUrl: string
previewGradient?: string
previewImageUrl?: string
} }
function changeTheme(id: string, url: string) { async function parseTheme(url: string): Promise<Theme | void> {
settingSave("selectedThemeId", id) const styleData: any = await $fetch(url)
loadPreferredTheme()
}
async function fetchThemes() { if (typeof styleData != "string") {
for (const theme of defaultThemes) { return
const themeConfig = await $fetch(`${baseURL}themes/${theme}.json`) as Theme }
themeConfig.id = theme
themes.push(themeConfig) const metadataMatch = styleData.match(/\/\*([\s\S]*?)\*\//);
if (!metadataMatch) {
alert(`Failed to fetch metadata for a theme, panicking`)
return
}
const commentContent = metadataMatch[0].trim().split("\n");
const cssData = styleData.substring(metadataMatch[0].length).trim();
let displayName: string | undefined
let complementaryColor: string | undefined
let previewGradient: string | undefined
let previewImageUrl: string | undefined
for (const line of commentContent) {
const lineArray = line.split("=")
if (lineArray.length === 2) {
switch (lineArray[0].trim()) {
case "displayName":
displayName = lineArray[1].trim()
break
case "complementaryColor":
complementaryColor = lineArray[1].trim()
break
case "previewGradient":
previewGradient = lineArray[1].trim()
break
case "previewImageUrl":
previewImageUrl = `${layoutFolder}/${lineArray[1].trim()}`
break
}
}
}
console.log(displayName, complementaryColor, previewGradient, previewImageUrl, cssData)
if (!(displayName && complementaryColor && cssData && (previewGradient || previewImageUrl))) {
return
}
return {
displayName,
complementaryColor,
cssData,
themeUrl: url,
previewGradient,
previewImageUrl,
} }
} }
await fetchThemes() async function parseThemeLayout(
folder: string,
incomingThemeList: string[],
outputThemeList: Theme[]) {
for (const theme of incomingThemeList) {
const parsedThemeData = await parseTheme(`${folder}/${theme}`)
if (parsedThemeData) {
outputThemeList.push(parsedThemeData)
}
}
}
const styles: Theme[] = [];
const layouts: Theme[] = [];
const styleList = await $fetch(`${styleFolder}/styles.json`)
const layoutList = await $fetch(`${layoutFolder}/layouts.json`)
if (Array.isArray(styleList)) {
await parseThemeLayout(styleFolder, styleList, styles)
}
if (Array.isArray(layoutList)) {
await parseThemeLayout(layoutFolder, layoutList, layouts)
}
function changeTheme(themeType: StyleLayout, theme: Theme) {
if (themeType == StyleLayout.Style) {
settingSave("selectedThemeStyle", theme.themeUrl)
} else {
settingSave("selectedThemeLayout", theme.themeUrl)
}
loadPreferredThemes()
}
async function onTimeFormatClicked(index: number) { async function onTimeFormatClicked(index: number) {
let format: "auto" | "12" | "24" = "auto" let format: "auto" | "12" | "24" = "auto"
@ -84,29 +188,89 @@ async function onTimeFormatClicked(index: number) {
<style scoped> <style scoped>
.themes { .themes {
--instance-size: 5em;
}
.styles, .layouts {
display: flex; display: flex;
} }
.theme-preview-container { .theme-preview-container {
margin: .5em; margin: .5em;
width: 5em; width: var(--instance-size);
height: 5em; height: var(--instance-size);
} }
.theme-preview { .theme-instance {
width: 5em; width: var(--instance-size);
height: 5em; height: var(--instance-size);
border-radius: 100%; border-radius: 100%;
border: .1em solid var(--primary-color); border: .1em solid var(--primary-color);
display: inline-block; display: inline-block;
text-align: center;
align-content: center;
cursor: pointer; cursor: pointer;
} }
.theme-content-container {
position: relative;
text-align: center;
align-content: center;
}
.style-background, .layout-background {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: var(--instance-size);
height: var(--instance-size);
border-radius: 100%;
}
.layout-background {
background-size: cover;
background-repeat: no-repeat;
filter: brightness(35%);
}
.layout-preview {
position: absolute;
pointer-events: none;
border: 0 solid var(--primary-color);
transform: translate(0, calc(var(--instance-size) / 2));
transition: all 250ms;
height: 0;
width: calc((height / 9) * 16);
max-height: 40dvh;
}
.theme-instance:hover .layout-preview {
border: .1em solid var(--primary-color);
filter: drop-shadow(0 0 .2em var(--secondary-color));
transform: translate(3.5em, -4em);
height: 40dvw;
}
.theme-title { .theme-title {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
font-size: .8em; font-size: .8em;
line-height: 5em; /* same height as the parent to centre it vertically */ /* i CANNOT explain this line height calculation, but it works for a font size of .8em no matter what size the instances are */
line-height: calc(var(--instance-size) * 1.25);
} }
</style> </style>

View file

@ -2,7 +2,9 @@
<NuxtLink class="user-item" :href="`/me/${user.uuid}`" tabindex="0"> <NuxtLink class="user-item" :href="`/me/${user.uuid}`" tabindex="0">
<Avatar :profile="props.user" class="user-avatar"/> <Avatar :profile="props.user" class="user-avatar"/>
<span class="user-display-name">{{ getDisplayName(props.user) }}</span> <span class="user-display-name" :style="`color: ${generateIrcColor(props.user.uuid)}`">
{{ getDisplayName(props.user) }}
</span>
</NuxtLink> </NuxtLink>
</template> </template>

View file

@ -50,7 +50,7 @@ function runCallback(item: ContextMenuItem) {
height: 2rem; height: 2rem;
width: 100%; width: 100%;
color: var(--text-color); color: var(--text-color);
background-color: var(--sidebar-highlighted-background-color); background-color: var(--popup-background-color);
border: none; border: none;
text-align: left; text-align: left;
padding-left: 1rem; padding-left: 1rem;
@ -58,7 +58,7 @@ function runCallback(item: ContextMenuItem) {
} }
.context-menu-item:hover { .context-menu-item:hover {
background-color: rgb(50, 50, 50); background-color: var(--popup-highlighted-background-color);
} }
.context-menu-item-danger { .context-menu-item-danger {

View file

@ -58,7 +58,7 @@ function scrollToReply(e: MouseEvent) {
console.log("scrolling into view"); console.log("scrolling into view");
reply.scrollIntoView({ behavior: "smooth", block: "center" }); reply.scrollIntoView({ behavior: "smooth", block: "center" });
reply.style.transition = "background-color .3s"; reply.style.transition = "background-color .3s";
reply.style.backgroundColor = "var(--primary-highlighted-color)"; reply.style.backgroundColor = "var(--chat-featured-message-color)";
setTimeout(() => { setTimeout(() => {
reply.style.backgroundColor = ""; reply.style.backgroundColor = "";
}, 1000); }, 1000);

View file

@ -30,9 +30,6 @@ export default defineNuxtConfig({
messageGroupingMaxDifference: 300000, messageGroupingMaxDifference: 300000,
buildTimeString: new Date().toISOString(), buildTimeString: new Date().toISOString(),
gitHash: process.env.GIT_SHORT_REV || "N/A", gitHash: process.env.GIT_SHORT_REV || "N/A",
defaultThemes: [
"light", "ash", "dark", "rainbow-capitalism"
]
} }
}, },
/* nitro: { /* nitro: {

View file

@ -1,6 +0,0 @@
{
"displayName": "Ash",
"previewGradient": "45deg, #2f2e2d, #46423b",
"complementaryColor": "white",
"themeUrl": "ash.css"
}

View file

@ -1,6 +0,0 @@
{
"displayName": "Dark",
"previewGradient": "45deg, #1f1e1d, #36322b",
"complementaryColor": "white",
"themeUrl": "dark.css"
}

View file

@ -0,0 +1,17 @@
/*
displayName = Gorb
previewImageUrl = gorb.jpg
complementaryColor = white
*/
:root {
--sidebar-icon-width: 2.5em;
--sidebar-icon-gap: .25em;
--sidebar-margin: .5em;
--standard-radius: .5em;
--button-radius: .6em;
--guild-icon-radius: 15%;
--pfp-radius: 50%;
--preferred-font: Arial;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

View file

@ -0,0 +1,3 @@
[
"gorb.css"
]

View file

@ -1,6 +0,0 @@
{
"displayName": "Light",
"previewGradient": "45deg, #f0ebe8, #d4d0ca",
"complementaryColor": "black",
"themeUrl": "light.css"
}

View file

@ -1,6 +0,0 @@
{
"displayName": "Woke",
"previewGradient": "45deg, #ed2224, #ed2224, #f35b22, #f99621, #f5c11e, #f1eb1b 27%, #f1eb1b, #f1eb1b 33%, #63c720, #0c9b49, #21878d, #3954a5, #61379b, #93288e, #93288e",
"complementaryColor": "white",
"themeUrl": "rainbow-capitalism.css"
}

View file

@ -1,3 +1,9 @@
/*
displayName = Ash
previewGradient = 45deg, #2f2e2d, #46423b
complementaryColor = white
*/
:root { :root {
--text-color: #f0e5e0; --text-color: #f0e5e0;
--secondary-text-color: #e8e0db; --secondary-text-color: #e8e0db;
@ -6,12 +12,18 @@
--chat-background-color: #2f2e2d; --chat-background-color: #2f2e2d;
--chat-highlighted-background-color: #3f3b38; --chat-highlighted-background-color: #3f3b38;
--chat-important-background-color: #ffcf5f38;
--chat-important-highlighted-background-color: #ffa86f4f;
--chat-featured-message-color: #4f3f2f60;
--popup-background-color: #2f2828;
--popup-highlighted-background-color: #382f2f;
--sidebar-background-color: #3e3a37; --sidebar-background-color: #3e3a37;
--sidebar-highlighted-background-color: #46423b; --sidebar-highlighted-background-color: #46423b;
--topbar-background-color: #3a3733; --topbar-background-color: #3a3733;
--chatbox-background-color: #3a3733; --chatbox-background-color: #3a3733;
--padding-color: #e0e0e0; --padding-color: #4f4f4f;
--primary-color: #f07028; --primary-color: #f07028;
--primary-highlighted-color: #f28f4b; --primary-highlighted-color: #f28f4b;
@ -19,11 +31,4 @@
--secondary-highlighted-color: #885830; --secondary-highlighted-color: #885830;
--accent-color: #a04b24; --accent-color: #a04b24;
--accent-highlighted-color: #b86038; --accent-highlighted-color: #b86038;
--sidebar-width: 2.5em;
--standard-radius: .5em;
--button-radius: .6em;
--guild-icon-radius: 20%;
--pfp-radius: 50%;
--preferred-font: Arial;
} }

View file

@ -1,3 +1,9 @@
/*
displayName = Dark
previewGradient = 45deg, #1f1e1d, #36322b
complementaryColor = white
*/
:root { :root {
--text-color: #f7eee8; --text-color: #f7eee8;
--secondary-text-color: #f0e8e4; --secondary-text-color: #f0e8e4;
@ -6,6 +12,12 @@
--chat-background-color: #1f1e1d; --chat-background-color: #1f1e1d;
--chat-highlighted-background-color: #2f2b28; --chat-highlighted-background-color: #2f2b28;
--chat-important-background-color: #ffc44f2f;
--chat-important-highlighted-background-color: #ffa45f4a;
--chat-featured-message-color: #4f2f1f58;
--popup-background-color: #2f1f1f;
--popup-highlighted-background-color: #3f2f2f;
--sidebar-background-color: #2e2a27; --sidebar-background-color: #2e2a27;
--sidebar-highlighted-background-color: #36322b; --sidebar-highlighted-background-color: #36322b;
--topbar-background-color: #2a2723; --topbar-background-color: #2a2723;
@ -19,14 +31,4 @@
--secondary-highlighted-color: #8f5b2c; --secondary-highlighted-color: #8f5b2c;
--accent-color: #b35719; --accent-color: #b35719;
--accent-highlighted-color: #c76a2e; --accent-highlighted-color: #c76a2e;
--sidebar-icon-width: 2.5em;
--sidebar-icon-gap: .25em;
--sidebar-margin: .5em;
--standard-radius: .5em;
--button-radius: .6em;
--guild-icon-radius: 15%;
--pfp-radius: 50%;
--preferred-font: Arial;
} }

View file

@ -1,4 +1,11 @@
/*
displayName = Description
previewGradient = 45deg, #ff8f8f, #8f8fff
complementaryColor = black
*/
/* this is not a real theme, but rather a template for themes */ /* this is not a real theme, but rather a template for themes */
:root { :root {
--text-color: #161518; --text-color: #161518;
--secondary-text-color: #2b2930; --secondary-text-color: #2b2930;
@ -6,6 +13,12 @@
--chat-background-color: #80808000; --chat-background-color: #80808000;
--chat-highlighted-background-color: #ffffff20; --chat-highlighted-background-color: #ffffff20;
--chat-important-background-color: #ffc44f2f;
--chat-important-highlighted-background-color: #ffa45f4a;
--chat-featured-message-color: #4f2f1f58;
--popup-background-color: #2f1f1f;
--popup-highlighted-background-color: #3f2f2f;
--sidebar-background-color: #80808000; --sidebar-background-color: #80808000;
--sidebar-highlighted-background-color: #ffffff20; --sidebar-highlighted-background-color: #ffffff20;
--topbar-background-color: #80808000; --topbar-background-color: #80808000;
@ -20,12 +33,6 @@
--accent-color: #ff218c80; --accent-color: #ff218c80;
--accent-highlighted-color: #df1b6f80; --accent-highlighted-color: #df1b6f80;
--sidebar-width: 2.5em;
--standard-radius: .5em;
--button-radius: .6em;
--pfp-radius: 50%;
--preferred-font: Arial;
--optional-body-background: ; /* background element for the body */ --optional-body-background: ; /* background element for the body */
--optional-chat-background: ; /* background element for the chat box */ --optional-chat-background: ; /* background element for the chat box */
--optional-topbar-background: ; /* background element for the topbar */ --optional-topbar-background: ; /* background element for the topbar */

View file

@ -1,3 +1,9 @@
/*
displayName = Light
previewGradient = 45deg, #f0ebe8, #d4d0ca
complementaryColor = black
*/
:root { :root {
--text-color: #170f08; --text-color: #170f08;
--secondary-text-color: #2f2b28; --secondary-text-color: #2f2b28;
@ -6,6 +12,12 @@
--chat-background-color: #f0ebe8; --chat-background-color: #f0ebe8;
--chat-highlighted-background-color: #e8e4e0; --chat-highlighted-background-color: #e8e4e0;
--chat-important-background-color: #df5f0b26;
--chat-important-hightlighted-background-color: #df5f0b3d;
--chat-featured-message-color: #e8ac841f;
--popup-background-color: #e8e4e0;
--popup-highlighted-background-color: #dfdbd6;
--sidebar-background-color: #dbd8d4; --sidebar-background-color: #dbd8d4;
--sidebar-highlighted-background-color: #d4d0ca; --sidebar-highlighted-background-color: #d4d0ca;
--topbar-background-color: #dfdbd6; --topbar-background-color: #dfdbd6;
@ -19,10 +31,4 @@
--secondary-highlighted-color: #f8b68a; --secondary-highlighted-color: #f8b68a;
--accent-color: #e68b4e; --accent-color: #e68b4e;
--accent-highlighted-color: #f69254; --accent-highlighted-color: #f69254;
--sidebar-width: 2.5em;
--standard-radius: .5em;
--button-radius: .6em;
--pfp-radius: 50%;
--preferred-font: Arial;
} }

View file

@ -1,11 +1,23 @@
/*
displayName = Woke
previewGradient = 45deg, #ed2224, #ed2224, #f35b22, #f99621, #f5c11e, #f1eb1b 27%, #f1eb1b, #f1eb1b 33%, #63c720, #0c9b49, #21878d, #3954a5, #61379b, #93288e, #93288e
complementaryColor = white
*/
:root { :root {
--text-color: #161518; --text-color: #000000;
--secondary-text-color: #2b2930; --secondary-text-color: #1f1f1f;
--reply-text-color: #969696; --reply-text-color: #969696;
--danger-text-color: #ff0000; --danger-text-color: #ff0000;
--chat-background-color: #80808000; --chat-background-color: #b0b0b040;
--chat-highlighted-background-color: #ffffff20; --chat-highlighted-background-color: #ffffff20;
--chat-important-background-color: #ff4f4f80;
--chat-important-highlighted-background-color: #ff6f6fa0;
--chat-featured-message-color: #4f8f4f80;
--popup-background-color: #80808080;
--popup-highlighted-background-color: #9f9f9f9f;
--sidebar-background-color: #80808000; --sidebar-background-color: #80808000;
--sidebar-highlighted-background-color: #ffffff20; --sidebar-highlighted-background-color: #ffffff20;
--topbar-background-color: #80808000; --topbar-background-color: #80808000;
@ -20,12 +32,6 @@
--accent-color: #ff218c80; --accent-color: #ff218c80;
--accent-highlighted-color: #df1b6f80; --accent-highlighted-color: #df1b6f80;
--sidebar-width: 2.5em;
--standard-radius: .5em;
--button-radius: .6em;
--pfp-radius: 50%;
--preferred-font: Arial;
/* --optional-body-background: background */ /* --optional-body-background: background */
--optional-body-background: linear-gradient(45deg, #ed222480, #ed222480, #ed222480, #ed222480, #ed222480, #ed222480, #f35b2280, #f9962180, #f5c11e80, #f1eb1b80, #f1eb1b80, #f1eb1b80, #63c72080, #0c9b4980, #21878d80, #3954a580, #61379b80, #93288e80); --optional-body-background: linear-gradient(45deg, #ed222480, #ed222480, #ed222480, #ed222480, #ed222480, #ed222480, #f35b2280, #f9962180, #f5c11e80, #f1eb1b80, #f1eb1b80, #f1eb1b80, #63c72080, #0c9b4980, #21878d80, #3954a580, #61379b80, #93288e80);
--optional-topbar-background: linear-gradient(-12.5deg, cyan, pink, white, pink, cyan); --optional-topbar-background: linear-gradient(-12.5deg, cyan, pink, white, pink, cyan);

View file

@ -0,0 +1,6 @@
[
"ash.css",
"dark.css",
"light.css",
"rainbow-capitalism.css"
]

View file

@ -1,6 +1,7 @@
export interface ClientSettings { export interface ClientSettings {
selectedThemeId?: string, // the ID of the theme, not the URL, for example "dark"
timeFormat?: TimeFormat timeFormat?: TimeFormat
selectedThemeStyle?: string // URL
selectedThemeLayout?: string // URL
} }
export interface TimeFormat { export interface TimeFormat {

View file

@ -1,28 +0,0 @@
let themeLinkElement: HTMLLinkElement | null;
export default function loadPreferredTheme() {
const currentTheme = settingsLoad().selectedThemeId ?? "dark"
if (themeLinkElement) {
themeLinkElement.href = getThemeUrl(currentTheme);
} else {
// create the theme link if one doesn't already exist
useHead({
link: [{
id: "main-theme",
rel: "stylesheet",
href: getThemeUrl(currentTheme)
}]
})
themeLinkElement = document.getElementById('main-theme') as HTMLLinkElement;
}
}
function getThemeUrl(id: string): string {
const runtimeConfig = useRuntimeConfig()
const baseURL = runtimeConfig.app.baseURL;
// this should preferrably use version hash, but that's not implemented yet
return `${baseURL}themes/${id}.css?v=${runtimeConfig.public.buildTimeString}`
}

View file

@ -0,0 +1,52 @@
let styleLinkElement: HTMLLinkElement | null;
let layoutLinkElement: HTMLLinkElement | null;
export default () => {
const runtimeConfig = useRuntimeConfig()
const baseURL = runtimeConfig.app.baseURL;
let currentStyle = settingsLoad().selectedThemeStyle ?? undefined
let currentLayout = settingsLoad().selectedThemeLayout ?? `${baseURL}themes/layout/gorb.css`
if (!currentStyle) {
if (prefersLight()) {
currentStyle = `${baseURL}themes/style/light.css`
} else {
currentStyle = `${baseURL}themes/style/dark.css`
}
}
if (styleLinkElement) {
styleLinkElement.href = currentStyle;
} else {
createStyleHead("style-theme", currentStyle)
styleLinkElement = document.getElementById('style-theme') as HTMLLinkElement;
}
if (layoutLinkElement) {
layoutLinkElement.href = currentLayout;
} else {
createStyleHead("style-layout", currentLayout)
layoutLinkElement = document.getElementById('style-layout') as HTMLLinkElement;
}
}
// create a new theme link if one doesn't already exist
function createStyleHead(id: string, themeUrl: string) {
useHead({
link: [{
id: id,
rel: "stylesheet",
href: themeUrl
}]
})
}
function prefersLight(): boolean {
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
return true
}
return false
}

View file

@ -2,6 +2,8 @@ import { render } from "vue";
import MessageReply from "~/components/UserInterface/MessageReply.vue"; import MessageReply from "~/components/UserInterface/MessageReply.vue";
import type { MessageProps } from "~/types/props"; import type { MessageProps } from "~/types/props";
const { getDisplayName } = useProfile()
export default (element: HTMLDivElement, props: MessageProps) => { export default (element: HTMLDivElement, props: MessageProps) => {
console.log("element:", element); console.log("element:", element);
const messageBox = document.getElementById("message-box") as HTMLDivElement; const messageBox = document.getElementById("message-box") as HTMLDivElement;