diff --git a/src/shared/components/app/code-theme.tsx b/src/shared/components/app/code-theme.tsx
index 9cc016ef..c454ed56 100644
--- a/src/shared/components/app/code-theme.tsx
+++ b/src/shared/components/app/code-theme.tsx
@@ -1,18 +1,34 @@
import { dataBsTheme } from "@utils/browser";
import { Component } from "inferno";
import { Helmet } from "inferno-helmet";
-import { UserService } from "../../services";
interface CodeThemeProps {
- defaultTheme: string;
+ theme: string;
}
export class CodeTheme extends Component
{
render() {
- const user = UserService.Instance.myUserInfo;
- const userTheme = user?.local_user_view.local_user.theme;
- const theme =
- user && userTheme !== "browser" ? userTheme : this.props.defaultTheme;
+ const { theme } = this.props;
+ const hasTheme = theme !== "browser" && theme !== "browser-compact";
+
+ if (!hasTheme) {
+ return (
+
+
+
+
+ );
+ }
return (
diff --git a/src/shared/components/app/theme.tsx b/src/shared/components/app/theme.tsx
index 07c4f8c7..b577e435 100644
--- a/src/shared/components/app/theme.tsx
+++ b/src/shared/components/app/theme.tsx
@@ -1,76 +1,168 @@
import { Component } from "inferno";
import { Helmet } from "inferno-helmet";
import { UserService } from "../../services";
+import { dataBsTheme, isBrowser } from "@utils/browser";
+import { CodeTheme } from "./code-theme";
interface Props {
defaultTheme: string;
}
-export class Theme extends Component {
- render() {
- const user = UserService.Instance.myUserInfo;
- const hasTheme = user?.local_user_view.local_user.theme !== "browser";
+interface State {
+ themeOverride?: string;
+ graceTheme?: string;
+}
- if (user && hasTheme) {
+export class Theme extends Component {
+ private lightQuery?: MediaQueryList;
+ constructor(props, context) {
+ super(props, context);
+ if (isBrowser()) {
+ window.addEventListener("refresh-theme", this.eventListener);
+ window.addEventListener("set-theme-override", this.eventListener);
+ this.lightQuery = window.matchMedia("(prefers-color-scheme: light)");
+ this.lightQuery.addEventListener("change", this.eventListener);
+ }
+ }
+
+ private graceTimer;
+ private eventListener = e => {
+ if (e.type === "refresh-theme" || e.type === "change") {
+ this.forceUpdate();
+ } else if (e.type === "set-theme-override") {
+ if (e.detail?.theme) {
+ this.setState({
+ themeOverride: e.detail.theme,
+ graceTheme: this.state?.themeOverride ?? this.currentTheme(),
+ });
+ // Keep both themes enabled for one second. Avoids unstyled flashes.
+ clearTimeout(this.graceTimer);
+ this.graceTimer = setTimeout(() => {
+ this.setState({ graceTheme: undefined });
+ }, 1000);
+ } else {
+ this.setState({ themeOverride: undefined, graceTheme: undefined });
+ }
+ }
+ };
+
+ componentWillUnmount(): void {
+ if (isBrowser()) {
+ window.removeEventListener("refresh-theme", this.eventListener);
+ this.lightQuery?.removeEventListener("change", this.eventListener);
+ }
+ }
+
+ currentTheme(): string {
+ const user = UserService.Instance.myUserInfo;
+ const userTheme = user?.local_user_view.local_user.theme;
+ return userTheme ?? "browser";
+ }
+
+ render() {
+ if (this.state?.themeOverride) {
+ if (!this.state.graceTheme) {
+ return this.renderTheme(this.state.themeOverride);
+ }
+ // Render both themes to prevent rendering without theme.
+ return [
+ this.renderTheme(this.state.graceTheme ?? this.currentTheme()),
+ this.renderTheme(this.state.themeOverride),
+ ];
+ }
+
+ return this.renderTheme(this.currentTheme());
+ }
+
+ renderTheme(theme: string) {
+ const hasTheme = theme !== "browser" && theme !== "browser-compact";
+
+ const detectedBsTheme = {};
+ if (this.lightQuery) {
+ detectedBsTheme["data-bs-theme"] = this.lightQuery.matches
+ ? "light"
+ : "dark";
+ }
+
+ if (theme && hasTheme) {
return (
-
-
-
+ <>
+
+
+
+
+ >
);
} else if (
this.props.defaultTheme !== "browser" &&
this.props.defaultTheme !== "browser-compact"
) {
return (
-
-
-
+ <>
+
+
+
+
+ >
);
- } else if (this.props.defaultTheme === "browser-compact") {
+ } else if (
+ this.props.defaultTheme === "browser-compact" ||
+ theme === "browser-compact"
+ ) {
return (
-
-
-
-
+ <>
+
+
+
+
+
+ >
);
} else {
return (
-
-
-
-
+ <>
+
+
+
+
+
+ >
);
}
}
diff --git a/src/shared/components/home/admin-settings.tsx b/src/shared/components/home/admin-settings.tsx
index 1d2a69e2..48de62a2 100644
--- a/src/shared/components/home/admin-settings.tsx
+++ b/src/shared/components/home/admin-settings.tsx
@@ -102,6 +102,9 @@ export class AdminSettings extends Component {
async componentDidMount() {
if (!this.state.isIsomorphic) {
await this.fetchData();
+ } else {
+ const themeList = await fetchThemeList();
+ this.setState({ themeList });
}
}
diff --git a/src/shared/components/home/login.tsx b/src/shared/components/home/login.tsx
index 8beba844..d0c1becf 100644
--- a/src/shared/components/home/login.tsx
+++ b/src/shared/components/home/login.tsx
@@ -1,5 +1,5 @@
import { setIsoData } from "@utils/app";
-import { isBrowser, updateDataBsTheme } from "@utils/browser";
+import { isBrowser, refreshTheme } from "@utils/browser";
import { getQueryParams } from "@utils/helpers";
import { Component, linkEvent } from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route";
@@ -47,7 +47,7 @@ async function handleLoginSuccess(i: Login, loginRes: LoginResponse) {
if (site.state === "success") {
UserService.Instance.myUserInfo = site.data.my_user;
- updateDataBsTheme(site.data);
+ refreshTheme();
}
const { prev } = getLoginQueryParams();
diff --git a/src/shared/components/person/settings.tsx b/src/shared/components/person/settings.tsx
index 23ed4c8a..2760958a 100644
--- a/src/shared/components/person/settings.tsx
+++ b/src/shared/components/person/settings.tsx
@@ -7,7 +7,6 @@ import {
myAuth,
personToChoice,
setIsoData,
- setTheme,
showLocal,
updateCommunityBlock,
updateInstanceBlock,
@@ -67,7 +66,7 @@ import { PersonListing } from "./person-listing";
import { InitialFetchRequest } from "../../interfaces";
import TotpModal from "../common/totp-modal";
import { LoadingEllipses } from "../common/loading-ellipses";
-import { updateDataBsTheme } from "../../utils/browser";
+import { refreshTheme, setThemeOverride } from "../../utils/browser";
import { getHttpBaseInternal } from "../../utils/env";
type SettingsData = RouteDataResponse<{
@@ -342,6 +341,7 @@ export class Settings extends Component {
componentWillUnmount(): void {
// In case `interface_language` change wasn't saved.
loadUserLanguage();
+ setThemeOverride(undefined);
}
static async fetchInitialData({
@@ -1453,7 +1453,7 @@ export class Settings extends Component {
handleThemeChange(i: Settings, event: any) {
i.setState(s => ((s.saveUserSettingsForm.theme = event.target.value), s));
- setTheme(event.target.value, true);
+ setThemeOverride(event.target.value);
}
handleInterfaceLangChange(i: Settings, event: any) {
@@ -1571,6 +1571,7 @@ export class Settings extends Component {
window.scrollTo(0, 0);
}
+ setThemeOverride(undefined);
i.setState({ saveRes });
}
@@ -1665,7 +1666,7 @@ export class Settings extends Component {
} = siteRes.data.my_user!.local_user_view;
UserService.Instance.myUserInfo = siteRes.data.my_user;
- updateDataBsTheme(siteRes.data);
+ refreshTheme();
i.setState(prev => ({
...prev,
diff --git a/src/shared/utils/app/index.ts b/src/shared/utils/app/index.ts
index 870fa7fb..484bae02 100644
--- a/src/shared/utils/app/index.ts
+++ b/src/shared/utils/app/index.ts
@@ -44,7 +44,6 @@ import postToCommentSortType from "./post-to-comment-sort-type";
import searchCommentTree from "./search-comment-tree";
import selectableLanguages from "./selectable-languages";
import setIsoData from "./set-iso-data";
-import setTheme from "./set-theme";
import setupDateFns from "./setup-date-fns";
import showAvatars from "./show-avatars";
import showLocal from "./show-local";
@@ -103,7 +102,6 @@ export {
searchCommentTree,
selectableLanguages,
setIsoData,
- setTheme,
setupDateFns,
showAvatars,
showLocal,
diff --git a/src/shared/utils/app/initialize-site.ts b/src/shared/utils/app/initialize-site.ts
index b9fa1f09..d6c6511e 100644
--- a/src/shared/utils/app/initialize-site.ts
+++ b/src/shared/utils/app/initialize-site.ts
@@ -1,11 +1,9 @@
import { GetSiteResponse } from "lemmy-js-client";
import { setupEmojiDataModel, setupMarkdown } from "../../markdown";
import { UserService } from "../../services";
-import { updateDataBsTheme } from "@utils/browser";
export default function initializeSite(site?: GetSiteResponse) {
UserService.Instance.myUserInfo = site?.my_user;
- updateDataBsTheme(site);
if (site) {
setupEmojiDataModel(site.custom_emojis ?? []);
}
diff --git a/src/shared/utils/app/set-theme.ts b/src/shared/utils/app/set-theme.ts
deleted file mode 100644
index 348e0ba6..00000000
--- a/src/shared/utils/app/set-theme.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { fetchThemeList } from "@utils/app";
-import { dataBsTheme, isBrowser, loadCss } from "@utils/browser";
-
-export default async function setTheme(theme: string, forceReload = false) {
- if (!isBrowser()) {
- return;
- }
- if (theme === "browser" && !forceReload) {
- return;
- }
- // This is only run on a force reload
- if (theme === "browser") {
- theme = "darkly";
- }
-
- const themeList = await fetchThemeList();
-
- // Unload all the other themes
- for (var i = 0; i < themeList.length; i++) {
- const styleSheet = document.getElementById(themeList[i]);
- if (styleSheet) {
- styleSheet.setAttribute("disabled", "disabled");
- }
- }
-
- document
- .getElementById("default-light")
- ?.setAttribute("disabled", "disabled");
- document.getElementById("default-dark")?.setAttribute("disabled", "disabled");
-
- // Load the theme dynamically
- const cssLoc = `/css/themes/${theme}.css`;
-
- loadCss(theme, cssLoc);
- document
- .getElementById("app")
- ?.setAttribute("data-bs-theme", dataBsTheme(theme));
- document.getElementById(theme)?.removeAttribute("disabled");
-}
diff --git a/src/shared/utils/browser/index.ts b/src/shared/utils/browser/index.ts
index 8aac65aa..98c2bba8 100644
--- a/src/shared/utils/browser/index.ts
+++ b/src/shared/utils/browser/index.ts
@@ -3,13 +3,13 @@ import clearAuthCookie from "./clear-auth-cookie";
import dataBsTheme from "./data-bs-theme";
import isBrowser from "./is-browser";
import isDark from "./is-dark";
-import loadCss from "./load-css";
import platform from "./platform";
+import refreshTheme from "./refresh-theme";
import restoreScrollPosition from "./restore-scroll-position";
import saveScrollPosition from "./save-scroll-position";
import setAuthCookie from "./set-auth-cookie";
+import setThemeOverride from "./set-theme-override";
import share from "./share";
-import updateDataBsTheme from "./update-data-bs-theme";
export {
canShare,
@@ -17,11 +17,11 @@ export {
dataBsTheme,
isBrowser,
isDark,
- loadCss,
platform,
+ refreshTheme,
restoreScrollPosition,
saveScrollPosition,
setAuthCookie,
+ setThemeOverride,
share,
- updateDataBsTheme,
};
diff --git a/src/shared/utils/browser/load-css.ts b/src/shared/utils/browser/load-css.ts
deleted file mode 100644
index 4b4b86e3..00000000
--- a/src/shared/utils/browser/load-css.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export default function loadCss(id: string, loc: string) {
- if (!document.getElementById(id)) {
- var head = document.getElementsByTagName("head")[0];
- var link = document.createElement("link");
- link.id = id;
- link.rel = "stylesheet";
- link.type = "text/css";
- link.href = loc;
- link.media = "all";
- head.appendChild(link);
- }
-}
diff --git a/src/shared/utils/browser/refresh-theme.tsx b/src/shared/utils/browser/refresh-theme.tsx
new file mode 100644
index 00000000..b7ef2c18
--- /dev/null
+++ b/src/shared/utils/browser/refresh-theme.tsx
@@ -0,0 +1,7 @@
+import { isBrowser } from ".";
+
+export default function refreshTheme() {
+ if (isBrowser()) {
+ window.dispatchEvent(new CustomEvent("refresh-theme"));
+ }
+}
diff --git a/src/shared/utils/browser/set-theme-override.ts b/src/shared/utils/browser/set-theme-override.ts
new file mode 100644
index 00000000..a5f76fb0
--- /dev/null
+++ b/src/shared/utils/browser/set-theme-override.ts
@@ -0,0 +1,10 @@
+import { isBrowser } from "@utils/browser";
+
+export default async function setThemeOverride(theme?: string) {
+ if (!isBrowser()) {
+ return;
+ }
+ window.dispatchEvent(
+ new CustomEvent("set-theme-override", { detail: { theme } }),
+ );
+}
diff --git a/src/shared/utils/browser/update-data-bs-theme.ts b/src/shared/utils/browser/update-data-bs-theme.ts
deleted file mode 100644
index a277572b..00000000
--- a/src/shared/utils/browser/update-data-bs-theme.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { GetSiteResponse } from "lemmy-js-client";
-import isBrowser from "./is-browser";
-import dataBsTheme from "./data-bs-theme";
-
-export default function updateDataBsTheme(siteRes?: GetSiteResponse) {
- if (isBrowser()) {
- document
- .getElementById("app")
- ?.setAttribute("data-bs-theme", dataBsTheme(siteRes));
- }
-}