mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 09:42:07 +08:00
FEATURE: Dark mode alternative logos (#10441)
This commit is contained in:
parent
d5a4318ac1
commit
3745f2bb86
|
@ -82,6 +82,20 @@ export default {
|
||||||
Session.currentProp("safe_mode", setupData.safeMode);
|
Session.currentProp("safe_mode", setupData.safeMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Session.currentProp(
|
||||||
|
"darkModeAvailable",
|
||||||
|
document.head.querySelectorAll(
|
||||||
|
'link[media="(prefers-color-scheme: dark)"]'
|
||||||
|
).length > 0
|
||||||
|
);
|
||||||
|
|
||||||
|
Session.currentProp(
|
||||||
|
"darkColorScheme",
|
||||||
|
getComputedStyle(document.documentElement)
|
||||||
|
.getPropertyValue("--scheme-type")
|
||||||
|
.trim() === "dark"
|
||||||
|
);
|
||||||
|
|
||||||
app.HighlightJSPath = setupData.highlightJsPath;
|
app.HighlightJSPath = setupData.highlightJsPath;
|
||||||
Session.currentProp("svgSpritePath", setupData.svgSpritePath);
|
Session.currentProp("svgSpritePath", setupData.svgSpritePath);
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { h } from "virtual-dom";
|
||||||
import { iconNode } from "discourse-common/lib/icon-library";
|
import { iconNode } from "discourse-common/lib/icon-library";
|
||||||
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
||||||
import DiscourseURL from "discourse/lib/url";
|
import DiscourseURL from "discourse/lib/url";
|
||||||
|
import Session from "discourse/models/session";
|
||||||
|
|
||||||
export default createWidget("home-logo", {
|
export default createWidget("home-logo", {
|
||||||
tagName: "div.title",
|
tagName: "div.title",
|
||||||
|
@ -17,57 +18,110 @@ export default createWidget("home-logo", {
|
||||||
return typeof href === "function" ? href() : href;
|
return typeof href === "function" ? href() : href;
|
||||||
},
|
},
|
||||||
|
|
||||||
logoUrl() {
|
logoUrl(opts = {}) {
|
||||||
return this.siteSettings.site_logo_url || "";
|
return this.logoResolver("logo", opts);
|
||||||
},
|
},
|
||||||
|
|
||||||
mobileLogoUrl() {
|
mobileLogoUrl(opts = {}) {
|
||||||
return this.siteSettings.site_mobile_logo_url || "";
|
return this.logoResolver("mobile_logo", opts);
|
||||||
},
|
},
|
||||||
|
|
||||||
smallLogoUrl() {
|
smallLogoUrl(opts = {}) {
|
||||||
return this.siteSettings.site_logo_small_url || "";
|
return this.logoResolver("logo_small", opts);
|
||||||
},
|
},
|
||||||
|
|
||||||
logo() {
|
logo() {
|
||||||
const { siteSettings } = this;
|
const { siteSettings } = this,
|
||||||
const mobileView = this.site.mobileView;
|
mobileView = this.site.mobileView;
|
||||||
|
|
||||||
|
const darkModeOptions = Session.currentProp("darkModeAvailable")
|
||||||
|
? { dark: true }
|
||||||
|
: {};
|
||||||
|
|
||||||
|
const mobileLogoUrl = this.mobileLogoUrl(),
|
||||||
|
mobileLogoUrlDark = this.mobileLogoUrl(darkModeOptions);
|
||||||
|
|
||||||
const mobileLogoUrl = this.mobileLogoUrl();
|
|
||||||
const showMobileLogo = mobileView && mobileLogoUrl.length > 0;
|
const showMobileLogo = mobileView && mobileLogoUrl.length > 0;
|
||||||
|
|
||||||
const logoUrl = this.logoUrl();
|
const logoUrl = this.logoUrl(),
|
||||||
|
logoUrlDark = this.logoUrl(darkModeOptions);
|
||||||
const title = siteSettings.title;
|
const title = siteSettings.title;
|
||||||
|
|
||||||
if (this.attrs.minimized) {
|
if (this.attrs.minimized) {
|
||||||
const logoSmallUrl = this.smallLogoUrl();
|
const logoSmallUrl = this.smallLogoUrl(),
|
||||||
|
logoSmallUrlDark = this.smallLogoUrl(darkModeOptions);
|
||||||
if (logoSmallUrl.length) {
|
if (logoSmallUrl.length) {
|
||||||
return h("img#site-logo.logo-small", {
|
return this.logoElement(
|
||||||
key: "logo-small",
|
"logo-small",
|
||||||
attributes: {
|
logoSmallUrl,
|
||||||
src: getURL(logoSmallUrl),
|
title,
|
||||||
width: 36,
|
logoSmallUrlDark
|
||||||
alt: title
|
);
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
return iconNode("home");
|
return iconNode("home");
|
||||||
}
|
}
|
||||||
} else if (showMobileLogo) {
|
} else if (showMobileLogo) {
|
||||||
return h("img#site-logo.logo-big", {
|
return this.logoElement(
|
||||||
key: "logo-mobile",
|
"logo-mobile",
|
||||||
attributes: { src: getURL(mobileLogoUrl), alt: title }
|
mobileLogoUrl,
|
||||||
});
|
title,
|
||||||
|
mobileLogoUrlDark
|
||||||
|
);
|
||||||
} else if (logoUrl.length) {
|
} else if (logoUrl.length) {
|
||||||
return h("img#site-logo.logo-big", {
|
return this.logoElement("logo-big", logoUrl, title, logoUrlDark);
|
||||||
key: "logo-big",
|
|
||||||
attributes: { src: getURL(logoUrl), alt: title }
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
return h("h1#site-text-logo.text-logo", { key: "logo-text" }, title);
|
return h("h1#site-text-logo.text-logo", { key: "logo-text" }, title);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
logoResolver(name, opts = {}) {
|
||||||
|
const { siteSettings } = this;
|
||||||
|
|
||||||
|
// get alternative logos for browser dark dark mode switching
|
||||||
|
if (opts.dark) {
|
||||||
|
return siteSettings[`site_${name}_dark_url`];
|
||||||
|
}
|
||||||
|
|
||||||
|
// try dark logos first when color scheme is dark
|
||||||
|
// this is independent of browser dark mode
|
||||||
|
// hence the fallback to normal logos
|
||||||
|
if (Session.currentProp("darkColorScheme")) {
|
||||||
|
return (
|
||||||
|
siteSettings[`site_${name}_dark_url`] ||
|
||||||
|
siteSettings[`site_${name}_url`] ||
|
||||||
|
""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return siteSettings[`site_${name}_url`] || "";
|
||||||
|
},
|
||||||
|
|
||||||
|
logoElement(key, url, title, darkUrl = null) {
|
||||||
|
const attributes =
|
||||||
|
key === "logo-small"
|
||||||
|
? { src: getURL(url), width: 36, alt: title }
|
||||||
|
: { src: getURL(url), alt: title };
|
||||||
|
|
||||||
|
const imgElement = h(`img#site-logo.${key}`, {
|
||||||
|
key: key,
|
||||||
|
attributes
|
||||||
|
});
|
||||||
|
|
||||||
|
if (darkUrl && url !== darkUrl) {
|
||||||
|
return h("picture", [
|
||||||
|
h("source", {
|
||||||
|
attributes: {
|
||||||
|
srcset: getURL(darkUrl),
|
||||||
|
media: "(prefers-color-scheme: dark)"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
imgElement
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return imgElement;
|
||||||
|
},
|
||||||
|
|
||||||
html() {
|
html() {
|
||||||
return h(
|
return h(
|
||||||
"a",
|
"a",
|
||||||
|
|
|
@ -14,7 +14,16 @@
|
||||||
@return red($hex), green($hex), blue($hex);
|
@return red($hex), green($hex), blue($hex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@function schemeType() {
|
||||||
|
@if is-light-color-scheme() {
|
||||||
|
@return "light";
|
||||||
|
} @else {
|
||||||
|
@return "dark";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
--scheme-type: #{schemeType()};
|
||||||
--primary: #{$primary};
|
--primary: #{$primary};
|
||||||
--secondary: #{$secondary};
|
--secondary: #{$secondary};
|
||||||
--tertiary: #{$tertiary};
|
--tertiary: #{$tertiary};
|
||||||
|
|
|
@ -183,6 +183,9 @@ class SiteSetting < ActiveRecord::Base
|
||||||
site_logo_small_url
|
site_logo_small_url
|
||||||
site_mobile_logo_url
|
site_mobile_logo_url
|
||||||
site_favicon_url
|
site_favicon_url
|
||||||
|
site_logo_dark_url
|
||||||
|
site_logo_small_dark_url
|
||||||
|
site_mobile_logo_dark_url
|
||||||
}.each { |client_setting| client_settings << client_setting }
|
}.each { |client_setting| client_settings << client_setting }
|
||||||
|
|
||||||
%i{
|
%i{
|
||||||
|
@ -190,6 +193,9 @@ class SiteSetting < ActiveRecord::Base
|
||||||
logo_small
|
logo_small
|
||||||
digest_logo
|
digest_logo
|
||||||
mobile_logo
|
mobile_logo
|
||||||
|
logo_dark
|
||||||
|
logo_small_dark
|
||||||
|
mobile_logo_dark
|
||||||
large_icon
|
large_icon
|
||||||
manifest_icon
|
manifest_icon
|
||||||
favicon
|
favicon
|
||||||
|
|
|
@ -1481,6 +1481,9 @@ en:
|
||||||
logo_small: "The small logo image at the top left of your site, seen when scrolling down. Use a square 120 × 120 image. If left blank, a home glyph will be shown."
|
logo_small: "The small logo image at the top left of your site, seen when scrolling down. Use a square 120 × 120 image. If left blank, a home glyph will be shown."
|
||||||
digest_logo: "The alternate logo image used at the top of your site's email summary. Use a wide rectangle image. Don't use an SVG image. If left blank, the image from the `logo` setting will be used."
|
digest_logo: "The alternate logo image used at the top of your site's email summary. Use a wide rectangle image. Don't use an SVG image. If left blank, the image from the `logo` setting will be used."
|
||||||
mobile_logo: "The logo used on mobile version of your site. Use a wide rectangular image with a height of 120 and an aspect ratio greater than 3:1. If left blank, the image from the `logo` setting will be used."
|
mobile_logo: "The logo used on mobile version of your site. Use a wide rectangular image with a height of 120 and an aspect ratio greater than 3:1. If left blank, the image from the `logo` setting will be used."
|
||||||
|
logo_dark: "Dark scheme alternative for the 'logo' site setting."
|
||||||
|
logo_small_dark: "Dark scheme alternative for the 'logo small' site setting."
|
||||||
|
mobile_logo_dark: "Dark scheme alternative for the 'mobile logo' site setting."
|
||||||
large_icon: "Image used as the base for other metadata icons. Should ideally be larger than 512 x 512. If left blank, logo_small will be used."
|
large_icon: "Image used as the base for other metadata icons. Should ideally be larger than 512 x 512. If left blank, logo_small will be used."
|
||||||
manifest_icon: "Image used as logo/splash image on Android. Will be automatically resized to 512 × 512. If left blank, large_icon will be used."
|
manifest_icon: "Image used as logo/splash image on Android. Will be automatically resized to 512 × 512. If left blank, large_icon will be used."
|
||||||
favicon: "A favicon for your site, see <a href='https://en.wikipedia.org/wiki/Favicon' target='_blank'>https://en.wikipedia.org/wiki/Favicon</a>. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, large_icon will be used."
|
favicon: "A favicon for your site, see <a href='https://en.wikipedia.org/wiki/Favicon' target='_blank'>https://en.wikipedia.org/wiki/Favicon</a>. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, large_icon will be used."
|
||||||
|
|
|
@ -74,6 +74,18 @@ branding:
|
||||||
default: ""
|
default: ""
|
||||||
client: true
|
client: true
|
||||||
type: upload
|
type: upload
|
||||||
|
logo_dark:
|
||||||
|
default: ""
|
||||||
|
client: true
|
||||||
|
type: upload
|
||||||
|
logo_small_dark:
|
||||||
|
default: ""
|
||||||
|
client: true
|
||||||
|
type: upload
|
||||||
|
mobile_logo_dark:
|
||||||
|
default: ""
|
||||||
|
client: true
|
||||||
|
type: upload
|
||||||
large_icon:
|
large_icon:
|
||||||
default: ""
|
default: ""
|
||||||
client: true
|
client: true
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import { moduleForWidget, widgetTest } from "helpers/widget-test";
|
import { moduleForWidget, widgetTest } from "helpers/widget-test";
|
||||||
|
import Session from "discourse/models/session";
|
||||||
moduleForWidget("home-logo");
|
moduleForWidget("home-logo");
|
||||||
|
|
||||||
const bigLogo = "/images/d-logo-sketch.png?test";
|
const bigLogo = "/images/d-logo-sketch.png?test";
|
||||||
const smallLogo = "/images/d-logo-sketch-small.png?test";
|
const smallLogo = "/images/d-logo-sketch-small.png?test";
|
||||||
const mobileLogo = "/images/d-logo-sketch.png?mobile";
|
const mobileLogo = "/images/d-logo-sketch.png?mobile";
|
||||||
|
const darkLogo = "/images/d-logo-sketch.png?dark";
|
||||||
const title = "Cool Forum";
|
const title = "Cool Forum";
|
||||||
|
const prefersDark = "(prefers-color-scheme: dark)";
|
||||||
|
|
||||||
widgetTest("basics", {
|
widgetTest("basics", {
|
||||||
template: '{{mount-widget widget="home-logo" args=args}}',
|
template: '{{mount-widget widget="home-logo" args=args}}',
|
||||||
|
@ -38,6 +41,7 @@ widgetTest("basics - minimized", {
|
||||||
assert.ok(find("img.logo-small").length === 1);
|
assert.ok(find("img.logo-small").length === 1);
|
||||||
assert.equal(find("img.logo-small").attr("src"), smallLogo);
|
assert.equal(find("img.logo-small").attr("src"), smallLogo);
|
||||||
assert.equal(find("img.logo-small").attr("alt"), title);
|
assert.equal(find("img.logo-small").attr("alt"), title);
|
||||||
|
assert.equal(find("img.logo-small").attr("width"), 36);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -79,7 +83,7 @@ widgetTest("mobile logo", {
|
||||||
},
|
},
|
||||||
|
|
||||||
test(assert) {
|
test(assert) {
|
||||||
assert.ok(find("img#site-logo.logo-big").length === 1);
|
assert.ok(find("img#site-logo.logo-mobile").length === 1);
|
||||||
assert.equal(find("#site-logo").attr("src"), mobileLogo);
|
assert.equal(find("#site-logo").attr("src"), mobileLogo);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -96,3 +100,139 @@ widgetTest("mobile without logo", {
|
||||||
assert.equal(find("#site-logo").attr("src"), bigLogo);
|
assert.equal(find("#site-logo").attr("src"), bigLogo);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
widgetTest("logo with dark mode alternative", {
|
||||||
|
template: '{{mount-widget widget="home-logo" args=args}}',
|
||||||
|
beforeEach() {
|
||||||
|
this.siteSettings.site_logo_url = bigLogo;
|
||||||
|
this.siteSettings.site_logo_dark_url = darkLogo;
|
||||||
|
Session.currentProp("darkModeAvailable", true);
|
||||||
|
},
|
||||||
|
afterEach() {
|
||||||
|
Session.currentProp("darkModeAvailable", null);
|
||||||
|
},
|
||||||
|
|
||||||
|
test(assert) {
|
||||||
|
assert.ok(find("img#site-logo.logo-big").length === 1);
|
||||||
|
assert.equal(find("#site-logo").attr("src"), bigLogo);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find("picture source").attr("media"),
|
||||||
|
prefersDark,
|
||||||
|
"includes dark mode media attribute"
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
find("picture source").attr("srcset"),
|
||||||
|
darkLogo,
|
||||||
|
"includes dark mode alternative logo source"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
widgetTest("mobile logo with dark mode alternative", {
|
||||||
|
template: '{{mount-widget widget="home-logo" args=args}}',
|
||||||
|
beforeEach() {
|
||||||
|
this.siteSettings.site_logo_url = bigLogo;
|
||||||
|
this.siteSettings.site_mobile_logo_url = mobileLogo;
|
||||||
|
this.siteSettings.site_mobile_logo_dark_url = darkLogo;
|
||||||
|
Session.currentProp("darkModeAvailable", true);
|
||||||
|
|
||||||
|
this.site.mobileView = true;
|
||||||
|
},
|
||||||
|
afterEach() {
|
||||||
|
Session.currentProp("darkModeAvailable", null);
|
||||||
|
},
|
||||||
|
|
||||||
|
test(assert) {
|
||||||
|
assert.equal(find("#site-logo").attr("src"), mobileLogo);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find("picture source").attr("media"),
|
||||||
|
prefersDark,
|
||||||
|
"includes dark mode media attribute"
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
find("picture source").attr("srcset"),
|
||||||
|
darkLogo,
|
||||||
|
"includes dark mode alternative logo source"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
widgetTest("dark mode enabled but no dark logo set", {
|
||||||
|
template: '{{mount-widget widget="home-logo" args=args}}',
|
||||||
|
beforeEach() {
|
||||||
|
this.siteSettings.site_logo_url = bigLogo;
|
||||||
|
this.siteSettings.site_logo_dark_url = "";
|
||||||
|
Session.currentProp("darkModeAvailable", true);
|
||||||
|
},
|
||||||
|
afterEach() {
|
||||||
|
Session.currentProp("darkModeAvailable", null);
|
||||||
|
},
|
||||||
|
|
||||||
|
test(assert) {
|
||||||
|
assert.ok(find("img#site-logo.logo-big").length === 1);
|
||||||
|
assert.equal(find("#site-logo").attr("src"), bigLogo);
|
||||||
|
assert.ok(
|
||||||
|
find("picture").length === 0,
|
||||||
|
"does not include alternative logo"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
widgetTest("dark logo set but no dark mode", {
|
||||||
|
template: '{{mount-widget widget="home-logo" args=args}}',
|
||||||
|
beforeEach() {
|
||||||
|
this.siteSettings.site_logo_url = bigLogo;
|
||||||
|
this.siteSettings.site_logo_dark_url = darkLogo;
|
||||||
|
},
|
||||||
|
|
||||||
|
test(assert) {
|
||||||
|
assert.ok(find("img#site-logo.logo-big").length === 1);
|
||||||
|
assert.equal(find("#site-logo").attr("src"), bigLogo);
|
||||||
|
assert.ok(
|
||||||
|
find("picture").length === 0,
|
||||||
|
"does not include alternative logo"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
widgetTest("dark color scheme and dark logo set", {
|
||||||
|
template: '{{mount-widget widget="home-logo" args=args}}',
|
||||||
|
beforeEach() {
|
||||||
|
this.siteSettings.site_logo_url = bigLogo;
|
||||||
|
this.siteSettings.site_logo_dark_url = darkLogo;
|
||||||
|
Session.currentProp("darkColorScheme", true);
|
||||||
|
},
|
||||||
|
afterEach() {
|
||||||
|
Session.currentProp("darkColorScheme", null);
|
||||||
|
},
|
||||||
|
test(assert) {
|
||||||
|
assert.ok(find("img#site-logo.logo-big").length === 1);
|
||||||
|
assert.equal(find("#site-logo").attr("src"), darkLogo, "uses dark logo");
|
||||||
|
assert.ok(
|
||||||
|
find("picture").length === 0,
|
||||||
|
"does not add dark mode alternative"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
widgetTest("dark color scheme and dark logo not set", {
|
||||||
|
template: '{{mount-widget widget="home-logo" args=args}}',
|
||||||
|
beforeEach() {
|
||||||
|
this.siteSettings.site_logo_url = bigLogo;
|
||||||
|
this.siteSettings.site_logo_dark_url = "";
|
||||||
|
Session.currentProp("darkColorScheme", true);
|
||||||
|
},
|
||||||
|
afterEach() {
|
||||||
|
Session.currentProp("darkColorScheme", null);
|
||||||
|
},
|
||||||
|
test(assert) {
|
||||||
|
assert.ok(find("img#site-logo.logo-big").length === 1);
|
||||||
|
assert.equal(
|
||||||
|
find("#site-logo").attr("src"),
|
||||||
|
bigLogo,
|
||||||
|
"uses regular logo on dark scheme if no dark logo"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user