2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2025-10-03 17:21:20 +08:00

DEV: Remove dual mode support for palettes and drop theme-owned palettes (#34467)

We're not going to finish/release dual-mode palettes and theme-owned
palettes at this time, so we're cleaning up the code that we've already
merged for these features.

Internal topic: t/161279.
This commit is contained in:
Osama Sayegh 2025-08-26 06:24:11 +03:00 committed by GitHub
parent 5cfda47b25
commit 7ee52c8f85
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 208 additions and 1107 deletions

View file

@ -186,33 +186,16 @@ export default class AdminConfigAreasColorPalette extends Component {
return;
}

const tags = document.querySelectorAll(`link[data-scheme-id="${id}"]`);
const tag = document.querySelector(`link[data-scheme-id="${id}"]`);

if (tags.length === 0) {
if (!tag) {
return;
}

let darkTag;
let lightTag;
for (const tag of tags) {
if (tag.classList.contains("dark-scheme")) {
darkTag = tag;
} else if (tag.classList.contains("light-scheme")) {
lightTag = tag;
}
}

try {
const data = await ajax(`/color-scheme-stylesheet/${id}.json`, {
data: {
include_dark_scheme: !!darkTag,
},
});
if (data?.new_href && lightTag) {
lightTag.href = data.new_href;
}
if (data?.new_dark_href && darkTag) {
darkTag.href = data.new_dark_href;
const data = await ajax(`/color-scheme-stylesheet/${id}.json`);
if (data?.new_href) {
tag.href = data.new_href;
}
} catch (error) {
// eslint-disable-next-line no-console

View file

@ -1,37 +0,0 @@
import { tracked } from "@glimmer/tracking";
import Controller from "@ember/controller";
import { TrackedSet } from "@ember-compat/tracked-built-ins";

class ChangeTracker {
@tracked dirtyLightColors = new TrackedSet();
@tracked dirtyDarkColors = new TrackedSet();

addDirtyLightColor(name) {
this.dirtyLightColors.add(name);
}

addDirtyDarkColor(name) {
this.dirtyDarkColors.add(name);
}

removeDirtyLightColor(name) {
this.dirtyLightColors.delete(name);
}

removeDirtyDarkColor(name) {
this.dirtyDarkColors.delete(name);
}

clear() {
this.dirtyLightColors.clear();
this.dirtyDarkColors.clear();
}

get dirtyColorsCount() {
return this.dirtyLightColors.size + this.dirtyDarkColors.size;
}
}

export default class AdminCustomizeThemesShowColorsController extends Controller {
colorPaletteChangeTracker = new ChangeTracker();
}

View file

@ -70,12 +70,7 @@ export async function applyColorScheme(scheme, options = {}) {

const apiUrl = `/color-scheme-stylesheet/${id}.json`;

const data = await ajax(apiUrl, {
data: {
include_dark_scheme: !!darkTag,
},
dataType: "json",
});
const data = await ajax(apiUrl);

if (data?.new_href && lightTag) {
lightTag.href = data.new_href;
@ -87,16 +82,6 @@ export async function applyColorScheme(scheme, options = {}) {
}
}

if (data?.new_dark_href && darkTag) {
darkTag.href = data.new_dark_href;

if (replace && id) {
darkTag.setAttribute("data-scheme-id", id);
} else if (replace && !id) {
darkTag.removeAttribute("data-scheme-id");
}
}

return data;
} catch (error) {
// eslint-disable-next-line no-console

View file

@ -7,10 +7,8 @@ import { i18n } from "discourse-i18n";

export default class ColorSchemeColor extends EmberObject {
@tracked hex;
@tracked dark_hex;

@tracked originalHex;
@tracked originalDarkHex;

// Whether the current value is different than Discourse's default color scheme.
@propertyNotEqual("hex", "default_hex") overridden;
@ -18,19 +16,16 @@ export default class ColorSchemeColor extends EmberObject {
init(object) {
super.init(...arguments);
this.originalHex = object.hex;
this.originalDarkHex = object.dark_hex;
}

discardColorChange() {
this.hex = this.originalHex;
this.dark_hex = this.originalDarkHex;
}

@on("init")
startTrackingChanges() {
this.set("originals", {
hex: this.hex || "FFFFFF",
darkHex: this.dark_hex,
});

// force changed property to be recalculated
@ -38,18 +33,14 @@ export default class ColorSchemeColor extends EmberObject {
}

// Whether value has changed since it was last saved.
@discourseComputed("hex", "dark_hex")
changed(hex, darkHex) {
@discourseComputed("hex")
changed(hex) {
if (!this.originals) {
return false;
}
if (hex !== this.originals.hex) {
return true;
}
if (darkHex !== this.originals.darkHex) {
return true;
}

return false;
}


View file

@ -11,15 +11,10 @@ import ColorSchemeColor from "admin/models/color-scheme-color";
class ColorSchemes extends ArrayProxy {}

export default class ColorScheme extends EmberObject {
static findAll({ excludeThemeOwned = false } = {}) {
static findAll() {
const colorSchemes = ColorSchemes.create({ content: [], loading: true });

const data = {};
if (excludeThemeOwned) {
data.exclude_theme_owned = true;
}

return ajax("/admin/color_schemes", { data }).then((all) => {
return ajax("/admin/color_schemes").then((all) => {
all.forEach((colorScheme) => {
colorSchemes.pushObject(
ColorScheme.create({
@ -89,14 +84,11 @@ export default class ColorScheme extends EmberObject {
}

schemeObject() {
const extractColors = (property) =>
Object.fromEntries(
this.colors.map((color) => [color.name, color[property]])
);
return {
name: this.name,
dark: extractColors("dark_hex"),
light: extractColors("hex"),
light: Object.fromEntries(
this.colors.map((color) => [color.name, color.hex])
),
};
}

@ -115,9 +107,7 @@ export default class ColorScheme extends EmberObject {
});
this.colors.forEach((c) => {
newScheme.colors.pushObject(
ColorSchemeColor.create(
c.getProperties("name", "hex", "default_hex", "dark_hex")
)
ColorSchemeColor.create(c.getProperties("name", "hex", "default_hex"))
);
});
return newScheme;
@ -172,7 +162,7 @@ export default class ColorScheme extends EmberObject {
data.colors = [];
this.colors.forEach((c) => {
if (!this.id || c.get("changed")) {
data.colors.pushObject(c.getProperties("name", "hex", "dark_hex"));
data.colors.pushObject(c.getProperties("name", "hex"));
}
});
}

View file

@ -1,13 +1,10 @@
import { tracked } from "@glimmer/tracking";
import { get } from "@ember/object";
import { gt, or } from "@ember/object/computed";
import { isBlank, isEmpty } from "@ember/utils";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import discourseComputed from "discourse/lib/decorators";
import RestModel from "discourse/models/rest";
import { i18n } from "discourse-i18n";
import ColorScheme from "admin/models/color-scheme";
import ThemeSettings from "admin/models/theme-settings";
import ThemeSiteSettings from "admin/models/theme-site-settings";

@ -34,17 +31,9 @@ class Theme extends RestModel {
);
}

const palette =
json.owned_color_palette || json.color_scheme || json.base_palette;
if (palette) {
json.colorPalette = ColorScheme.create(palette);
}

return json;
}

@tracked colorPalette;

@or("default", "user_selectable") isActive;
@gt("remote_theme.commits_behind", 0) isPendingUpdates;
@gt("editedFields.length", 0) hasEditedFields;
@ -192,34 +181,6 @@ class Theme extends RestModel {
return field ? field.error : "";
}

async changeColors() {
const colors = [];

for (const color of this.colorPalette.colors) {
const colorPayload = {
name: color.name,
hex: color.hex,
dark_hex: color.dark_hex,
};

colors.push(colorPayload);
}

const paletteData = await ajax(`/admin/themes/${this.id}/change-colors`, {
type: "PUT",
data: JSON.stringify({ colors }),
contentType: "application/json",
});
this.owned_color_palette = paletteData;
this.colorPalette = ColorScheme.create(paletteData);
}

discardColorChanges() {
for (const color of this.colorPalette.colors) {
color.discardColorChange();
}
}

getField(target, name) {
if (target === "common" && name === "js") {
target = "extra_js";

View file

@ -3,6 +3,6 @@ import ColorScheme from "admin/models/color-scheme";

export default class AdminConfigColorPalettesRoute extends DiscourseRoute {
model() {
return ColorScheme.findAll({ excludeThemeOwned: true });
return ColorScheme.findAll();
}
}

View file

@ -1,31 +0,0 @@
import { action } from "@ember/object";
import { service } from "@ember/service";
import DiscourseRoute from "discourse/routes/discourse";
import { i18n } from "discourse-i18n";

export default class AdminCustomizeThemesShowColorsRoute extends DiscourseRoute {
@service dialog;

@action
willTransition(transition) {
if (
this.controller.colorPaletteChangeTracker.dirtyColorsCount > 0 &&
transition.intent.name !== "adminCustomizeThemes.show.index"
) {
transition.abort();
this.dialog.yesNoConfirm({
message: i18n(
"admin.customize.theme.unsaved_colors_leave_route_confirmation"
),
didConfirm: () => {
this.controller.colorPaletteChangeTracker.clear();
transition.retry();
},
});
}
}

titleToken() {
return i18n("admin.customize.theme.colors_title");
}
}

View file

@ -69,7 +69,6 @@ export default function () {
function () {
this.route("show", { path: "/:theme_id" }, function () {
this.route("schema", { path: "schema/:setting_name" });
this.route("colors");
});
this.route("edit", { path: "/:theme_id/:target/:field_name/edit" });
}

View file

@ -1,61 +0,0 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import RouteTemplate from "ember-route-template";
import { gt } from "truth-helpers";
import { i18n } from "discourse-i18n";
import ChangesBanner from "admin/components/changes-banner";
import ColorPaletteEditor from "admin/components/color-palette-editor";

export default RouteTemplate(
class extends Component {
get pendingChangesBannerLabel() {
return i18n("admin.customize.theme.unsaved_colors", {
count: this.args.controller.colorPaletteChangeTracker.dirtyColorsCount,
});
}

@action
onColorChange(color, value) {
color.hex = value;
if (color.hex !== color.originalHex) {
this.args.controller.colorPaletteChangeTracker.addDirtyLightColor(
color.name
);
} else {
this.args.controller.colorPaletteChangeTracker.removeDirtyLightColor(
color.name
);
}
}

@action
async save() {
await this.args.model.changeColors();
this.args.controller.colorPaletteChangeTracker.clear();
}

@action
discard() {
this.args.model.discardColorChanges();
this.args.controller.colorPaletteChangeTracker.clear();
}

<template>
<ColorPaletteEditor
@colors={{@model.colorPalette.colors}}
@onColorChange={{this.onColorChange}}
@hideRevertButton={{true}}
@system={{@model.system}}
/>
{{#if (gt @controller.colorPaletteChangeTracker.dirtyColorsCount 0)}}
<ChangesBanner
@bannerLabel={{this.pendingChangesBannerLabel}}
@saveLabel={{i18n "admin.customize.theme.save_colors"}}
@discardLabel={{i18n "admin.customize.theme.discard_colors"}}
@save={{this.save}}
@discard={{this.discard}}
/>
{{/if}}
</template>
}
);

View file

@ -119,114 +119,112 @@ export default RouteTemplate(
{{/if}}

{{#unless @controller.model.component}}
{{#unless @controller.siteSettings.use_overhauled_theme_color_palette}}
<section
class="form-horizontal theme settings control-unit theme-settings__light-color-scheme"
>
<div class="row setting">
<div class="setting-label">
{{i18n "admin.customize.theme.color_scheme"}}
</div>
<section
class="form-horizontal theme settings control-unit theme-settings__light-color-scheme"
>
<div class="row setting">
<div class="setting-label">
{{i18n "admin.customize.theme.color_scheme"}}
</div>

<div class="setting-value">
<div class="color-palette-input-group">
<ColorPalettePicker
@content={{@controller.colorSchemes}}
@value={{@controller.colorSchemeId}}
@icon="paintbrush"
@options={{hash
filterable=true
translatedNone=(i18n
"admin.customize.theme.default_light_scheme"
)
}}
/>
</div>

<div class="desc">{{i18n
"admin.customize.theme.color_scheme_select"
<div class="setting-value">
<div class="color-palette-input-group">
<ColorPalettePicker
@content={{@controller.colorSchemes}}
@value={{@controller.colorSchemeId}}
@icon="paintbrush"
@options={{hash
filterable=true
translatedNone=(i18n
"admin.customize.theme.default_light_scheme"
)
}}

{{#if @controller.colorSchemeId}}
<LinkTo
@route="adminCustomize.colors-show"
@model={{@controller.colorSchemeId}}
>
{{i18n "admin.customize.theme.edit_colors"}}
</LinkTo>
{{/if}}
</div>
/>
</div>

<div class="setting-controls">
{{#if @controller.lightColorSchemeChanged}}
<DButton
@action={{@controller.changeLightScheme}}
@icon="check"
class="ok submit-light-edit"
/>
<DButton
@action={{@controller.cancelChangeLightScheme}}
@icon="xmark"
class="cancel cancel-light-edit"
/>
<div class="desc">{{i18n
"admin.customize.theme.color_scheme_select"
}}

{{#if @controller.colorSchemeId}}
<LinkTo
@route="adminCustomize.colors-show"
@model={{@controller.colorSchemeId}}
>
{{i18n "admin.customize.theme.edit_colors"}}
</LinkTo>
{{/if}}
</div>
</div>
</section>
<section
class="form-horizontal theme settings control-unit theme-settings__dark-color-scheme"
>
<div class="row setting">
<div class="setting-label">
{{i18n "admin.customize.theme.dark_color_scheme"}}

<div class="setting-controls">
{{#if @controller.lightColorSchemeChanged}}
<DButton
@action={{@controller.changeLightScheme}}
@icon="check"
class="ok submit-light-edit"
/>
<DButton
@action={{@controller.cancelChangeLightScheme}}
@icon="xmark"
class="cancel cancel-light-edit"
/>
{{/if}}
</div>
</div>
</section>
<section
class="form-horizontal theme settings control-unit theme-settings__dark-color-scheme"
>
<div class="row setting">
<div class="setting-label">
{{i18n "admin.customize.theme.dark_color_scheme"}}
</div>

<div class="setting-value">
<div class="color-palette-input-group">
<ColorPalettePicker
@content={{@controller.colorSchemes}}
@value={{@controller.darkColorSchemeId}}
@icon="paintbrush"
@options={{hash
filterable=true
translatedNone=(i18n
"admin.customize.theme.default_light_scheme"
)
}}
/>
</div>

<div class="setting-value">
<div class="color-palette-input-group">
<ColorPalettePicker
@content={{@controller.colorSchemes}}
@value={{@controller.darkColorSchemeId}}
@icon="paintbrush"
@options={{hash
filterable=true
translatedNone=(i18n
"admin.customize.theme.default_light_scheme"
)
}}
/>
</div>
<div class="desc">
{{i18n "admin.customize.theme.dark_color_scheme_select"}}

<div class="desc">
{{i18n "admin.customize.theme.dark_color_scheme_select"}}

{{#if @controller.darkColorSchemeId}}
<LinkTo
@route="adminCustomize.colors-show"
@model={{@controller.darkColorSchemeId}}
>
{{i18n "admin.customize.theme.edit_colors"}}
</LinkTo>
{{/if}}
</div>
</div>
<div class="setting-controls">
{{#if @controller.darkColorSchemeChanged}}
<DButton
@action={{@controller.changeDarkScheme}}
@icon="check"
class="ok submit-dark-edit"
/>
<DButton
@action={{@controller.cancelChangeDarkScheme}}
@icon="xmark"
class="cancel cancel-dark-edit"
/>
{{#if @controller.darkColorSchemeId}}
<LinkTo
@route="adminCustomize.colors-show"
@model={{@controller.darkColorSchemeId}}
>
{{i18n "admin.customize.theme.edit_colors"}}
</LinkTo>
{{/if}}
</div>
</div>
</section>
{{/unless}}
<div class="setting-controls">
{{#if @controller.darkColorSchemeChanged}}
<DButton
@action={{@controller.changeDarkScheme}}
@icon="check"
class="ok submit-dark-edit"
/>
<DButton
@action={{@controller.cancelChangeDarkScheme}}
@icon="xmark"
class="cancel cancel-dark-edit"
/>
{{/if}}
</div>
</div>
</section>
{{/unless}}

{{#if @controller.model.component}}

View file

@ -2,8 +2,6 @@ import { LinkTo } from "@ember/routing";
import { htmlSafe } from "@ember/template";
import RouteTemplate from "ember-route-template";
import DButton from "discourse/components/d-button";
import DPageHeader from "discourse/components/d-page-header";
import NavItem from "discourse/components/nav-item";
import PluginOutlet from "discourse/components/plugin-outlet";
import TextField from "discourse/components/text-field";
import UserLink from "discourse/components/user-link";
@ -250,27 +248,6 @@ export default RouteTemplate(
</div>
{{/if}}

{{#if @controller.siteSettings.use_overhauled_theme_color_palette}}
{{#unless @controller.model.component}}
<DPageHeader>
<:tabs>
<NavItem
class="admin-customize-theme-tabs__settings"
@route="adminCustomizeThemes.show.index"
@routeParam={{@controller.model.id}}
@label="admin.customize.theme.settings"
/>
<NavItem
class="admin-customize-theme-tabs__colors"
@route="adminCustomizeThemes.show.colors"
@routeParam={{@controller.model.id}}
@label="admin.customize.theme.colors"
/>
</:tabs>
</DPageHeader>
{{/unless}}
{{/if}}

{{outlet}}
{{/if}}
</div>

View file

@ -374,16 +374,6 @@ export default class AdminSidebarPanel extends BaseCustomSidebarPanel {
]);
}

if (siteSettings.use_overhauled_theme_color_palette) {
this.adminNavManager.overrideSectionLink(
"appearance",
"admin_color_palettes",
{
route: "adminConfig.colorPalettes",
}
);
}

for (const [sectionName, additionalLinks] of Object.entries(
additionalAdminSidebarSectionLinks
)) {

View file

@ -57,7 +57,6 @@ acceptance("Admin - Customize - Themes - Show", function (needs) {
"/uploads/default/original/1X/f8a61b9a0bfac672daec9e401787812f8c5e28df.png",
system: true,
color_scheme: null,
owned_color_palette: null,
user: {
id: -1,
username: "system",

View file

@ -13,328 +13,246 @@ const example_scheme = {
hex: "F0ECD7",
default_hex: "F0ECD7",
is_advanced: true,
dark_hex: "F0ECD7",
default_dark_hex: null,
},
{
name: "primary_low",
hex: "D6D8C7",
default_hex: "D6D8C7",
is_advanced: true,
dark_hex: "D6D8C7",
default_dark_hex: null,
},
{
name: "primary_low_mid",
hex: "A4AFA5",
default_hex: "A4AFA5",
is_advanced: true,
dark_hex: "A4AFA5",
default_dark_hex: null,
},
{
name: "primary_medium",
hex: "7E918C",
default_hex: "7E918C",
is_advanced: true,
dark_hex: "7E918C",
default_dark_hex: null,
},
{
name: "primary_high",
hex: "4C6869",
default_hex: "4C6869",
is_advanced: true,
dark_hex: "4C6869",
default_dark_hex: null,
},
{
name: "primary",
hex: "002B36",
default_hex: "002B36",
is_advanced: false,
dark_hex: "002B36",
default_dark_hex: null,
},
{
name: "primary-50",
hex: "F0EBDA",
default_hex: "F0EBDA",
is_advanced: true,
dark_hex: "F0EBDA",
default_dark_hex: null,
},
{
name: "primary-100",
hex: "DAD8CA",
default_hex: "DAD8CA",
is_advanced: true,
dark_hex: "DAD8CA",
default_dark_hex: null,
},
{
name: "primary-200",
hex: "B2B9B3",
default_hex: "B2B9B3",
is_advanced: true,
dark_hex: "B2B9B3",
default_dark_hex: null,
},
{
name: "primary-300",
hex: "839496",
default_hex: "839496",
is_advanced: true,
dark_hex: "839496",
default_dark_hex: null,
},
{
name: "primary-400",
hex: "76898C",
default_hex: "76898C",
is_advanced: true,
dark_hex: "76898C",
default_dark_hex: null,
},
{
name: "primary-500",
hex: "697F83",
default_hex: "697F83",
is_advanced: true,
dark_hex: "697F83",
default_dark_hex: null,
},
{
name: "primary-600",
hex: "627A7E",
default_hex: "627A7E",
is_advanced: true,
dark_hex: "627A7E",
default_dark_hex: null,
},
{
name: "primary-700",
hex: "556F74",
default_hex: "556F74",
is_advanced: true,
dark_hex: "556F74",
default_dark_hex: null,
},
{
name: "primary-800",
hex: "415F66",
default_hex: "415F66",
is_advanced: true,
dark_hex: "415F66",
default_dark_hex: null,
},
{
name: "primary-900",
hex: "21454E",
default_hex: "21454E",
is_advanced: true,
dark_hex: "21454E",
default_dark_hex: null,
},
{
name: "secondary_low",
hex: "325458",
default_hex: "325458",
is_advanced: true,
dark_hex: "325458",
default_dark_hex: null,
},
{
name: "secondary_medium",
hex: "6C8280",
default_hex: "6C8280",
is_advanced: true,
dark_hex: "6C8280",
default_dark_hex: null,
},
{
name: "secondary_high",
hex: "97A59D",
default_hex: "97A59D",
is_advanced: true,
dark_hex: "97A59D",
default_dark_hex: null,
},
{
name: "secondary_very_high",
hex: "E8E6D3",
default_hex: "E8E6D3",
is_advanced: true,
dark_hex: "E8E6D3",
default_dark_hex: null,
},
{
name: "secondary",
hex: "FCF6E1",
default_hex: "FCF6E1",
is_advanced: false,
dark_hex: "FCF6E1",
default_dark_hex: null,
},
{
name: "tertiary_low",
hex: "D6E6DE",
default_hex: "D6E6DE",
is_advanced: true,
dark_hex: "D6E6DE",
default_dark_hex: null,
},
{
name: "tertiary_medium",
hex: "7EBFD7",
default_hex: "7EBFD7",
is_advanced: true,
dark_hex: "7EBFD7",
default_dark_hex: null,
},
{
name: "tertiary",
hex: "0088cc",
default_hex: "0088cc",
is_advanced: false,
dark_hex: "0088cc",
default_dark_hex: null,
},
{
name: "tertiary_high",
hex: "329ED0",
default_hex: "329ED0",
is_advanced: true,
dark_hex: "329ED0",
default_dark_hex: null,
},
{
name: "quaternary",
hex: "e45735",
default_hex: "e45735",
is_advanced: false,
dark_hex: "e45735",
default_dark_hex: null,
},
{
name: "header_background",
hex: "FCF6E1",
default_hex: "FCF6E1",
is_advanced: false,
dark_hex: "FCF6E1",
default_dark_hex: null,
},
{
name: "header_primary",
hex: "002B36",
default_hex: "002B36",
is_advanced: false,
dark_hex: "002B36",
default_dark_hex: null,
},
{
name: "highlight_low",
hex: "FDF9AD",
default_hex: "FDF9AD",
is_advanced: true,
dark_hex: "FDF9AD",
default_dark_hex: null,
},
{
name: "highlight_medium",
hex: "E3D0A3",
default_hex: "E3D0A3",
is_advanced: true,
dark_hex: "E3D0A3",
default_dark_hex: null,
},
{
name: "highlight",
hex: "F2F481",
default_hex: "F2F481",
is_advanced: false,
dark_hex: "F2F481",
default_dark_hex: null,
},
{
name: "highlight_high",
hex: "BCAA7F",
default_hex: "BCAA7F",
is_advanced: true,
dark_hex: "BCAA7F",
default_dark_hex: null,
},
{
name: "selected",
hex: "E8E6D3",
default_hex: "E8E6D3",
is_advanced: false,
dark_hex: "E8E6D3",
default_dark_hex: null,
},
{
name: "hover",
hex: "F0EBDA",
default_hex: "F0EBDA",
is_advanced: false,
dark_hex: "F0EBDA",
default_dark_hex: null,
},
{
name: "danger_low",
hex: "F8D9C2",
default_hex: "F8D9C2",
is_advanced: true,
dark_hex: "F8D9C2",
default_dark_hex: null,
},
{
name: "danger",
hex: "e45735",
default_hex: "e45735",
is_advanced: false,
dark_hex: "e45735",
default_dark_hex: null,
},
{
name: "success_low",
hex: "CFE5B9",
default_hex: "CFE5B9",
is_advanced: true,
dark_hex: "CFE5B9",
default_dark_hex: null,
},
{
name: "success_medium",
hex: "4CB544",
default_hex: "4CB544",
is_advanced: true,
dark_hex: "4CB544",
default_dark_hex: null,
},
{
name: "success",
hex: "009900",
default_hex: "009900",
is_advanced: false,
dark_hex: "009900",
default_dark_hex: null,
},
{
name: "love_low",
hex: "FCDDD2",
default_hex: "FCDDD2",
is_advanced: true,
dark_hex: "FCDDD2",
default_dark_hex: null,
},
{
name: "love",
hex: "fa6c8d",
default_hex: "fa6c8d",
is_advanced: false,
dark_hex: "fa6c8d",
default_dark_hex: null,
},
],
is_dark: false,

View file

@ -17,24 +17,18 @@ const DEFAULT_CONTENT = [
hex: "1a1a1a",
default_hex: "222",
is_advanced: false,
dark_hex: "1a1a1a",
default_dark_hex: null,
},
{
name: "secondary",
hex: "ffffff",
default_hex: "fff",
is_advanced: false,
dark_hex: "ffffff",
default_dark_hex: null,
},
{
name: "tertiary",
hex: "595bca",
default_hex: "08c",
is_advanced: false,
dark_hex: "595bca",
default_dark_hex: null,
},
],
is_dark: false,
@ -49,24 +43,18 @@ const DEFAULT_CONTENT = [
hex: "dddddd",
default_hex: "dddddd",
is_advanced: false,
dark_hex: "dddddd",
default_dark_hex: null,
},
{
name: "secondary",
hex: "222222",
default_hex: "222222",
is_advanced: false,
dark_hex: "222222",
default_dark_hex: null,
},
{
name: "tertiary",
hex: "099dd7",
default_hex: "099dd7",
is_advanced: false,
dark_hex: "099dd7",
default_dark_hex: null,
},
],
is_dark: true,

View file

@ -4,10 +4,7 @@ class Admin::ColorSchemesController < Admin::AdminController
before_action :fetch_color_scheme, only: %i[update destroy]

def index
schemes =
ColorScheme.without_theme_owned_palettes.includes(:base_scheme).order("color_schemes.id ASC")

schemes = schemes.where(theme_id: nil) if params[:exclude_theme_owned]
schemes = ColorScheme.includes(:base_scheme).order("color_schemes.id ASC")

render_serialized(ColorScheme.base_color_schemes + schemes.to_a, ColorSchemeSerializer)
end
@ -38,13 +35,11 @@ class Admin::ColorSchemesController < Admin::AdminController
private

def fetch_color_scheme
@color_scheme = ColorScheme.without_theme_owned_palettes.find(params[:id])
@color_scheme = ColorScheme.find(params[:id])
end

def color_scheme_params
params.permit(
color_scheme: [:base_scheme_id, :name, :user_selectable, colors: %i[name hex dark_hex]],
)[
params.permit(color_scheme: [:base_scheme_id, :name, :user_selectable, colors: %i[name hex]])[
:color_scheme
]
end

View file

@ -2,7 +2,7 @@

class Admin::Config::ColorPalettesController < Admin::AdminController
def index
palettes = ColorScheme.without_theme_owned_palettes.to_a
palettes = ColorScheme.all.to_a
palettes.unshift(ColorScheme.base)
default_theme = Theme.find_default
default_light_palette = default_theme&.color_scheme_id
@ -55,10 +55,6 @@ class Admin::Config::ColorPalettesController < Admin::AdminController
end

def show
render_serialized(
ColorScheme.without_theme_owned_palettes.find(params[:id]),
ColorSchemeSerializer,
root: false,
)
render_serialized(ColorScheme.find(params[:id]), ColorSchemeSerializer, root: false)
end
end

View file

@ -176,7 +176,6 @@ class Admin::ThemesController < Admin::AdminController
ColorScheme
.strict_loading
.all
.without_theme_owned_palettes
.includes(
:theme,
:base_scheme,
@ -402,20 +401,6 @@ class Admin::ThemesController < Admin::AdminController
render_serialized(theme_setting, ThemeObjectsSettingMetadataSerializer, root: false)
end

def change_colors
raise Discourse::InvalidAccess if params[:id].to_i.negative?
theme = Theme.find_by(id: params[:id], component: false)
raise Discourse::NotFound if !theme

palette = theme.find_or_create_owned_color_palette

colors = params.permit(colors: %i[name hex dark_hex])

ColorSchemeRevisor.revise_existing_colors_only(palette, colors)

render_serialized(palette, ColorSchemeSerializer, root: false)
end

private

def ban_in_allowlist_mode!

View file

@ -23,15 +23,9 @@ class StylesheetsController < ApplicationController
def color_scheme
params.require("id")
params.permit("theme_id")
params.permit("include_dark_scheme")

manager = Stylesheet::Manager.new(theme_id: params[:theme_id])
stylesheet =
manager.color_scheme_stylesheet_details(
params[:id],
fallback_to_base: true,
include_dark_scheme: !!params[:include_dark_scheme],
)
stylesheet = manager.color_scheme_stylesheet_details(params[:id], fallback_to_base: true)

render json: stylesheet
end

View file

@ -575,10 +575,7 @@ module ApplicationHelper
return user_scheme_id if user_scheme_id
return if theme_id.blank?

if SiteSetting.use_overhauled_theme_color_palette
@scheme_id = ThemeColorScheme.where(theme_id: theme_id).pick(:color_scheme_id)
end
@scheme_id ||= Theme.where(id: theme_id).pick(:color_scheme_id)
@scheme_id = Theme.where(id: theme_id).pick(:color_scheme_id)
end

def user_dark_scheme_id
@ -588,12 +585,8 @@ module ApplicationHelper
end

def dark_scheme_id
if SiteSetting.use_overhauled_theme_color_palette
scheme_id
else
user_dark_scheme_id ||
(theme_id ? Theme.find_by_id(theme_id) : Theme.find_default)&.dark_color_scheme_id || -1
end
user_dark_scheme_id ||
(theme_id ? Theme.find_by_id(theme_id) : Theme.find_default)&.dark_color_scheme_id || -1
end

def current_homepage
@ -684,7 +677,6 @@ module ApplicationHelper
if dark_scheme_id != -1
result << stylesheet_manager.color_scheme_stylesheet_preload_tag(
dark_scheme_id,
dark: SiteSetting.use_overhauled_theme_color_palette,
fallback_to_base: false,
)
end
@ -702,7 +694,6 @@ module ApplicationHelper
dark_href =
stylesheet_manager.color_scheme_stylesheet_link_tag_href(
dark_scheme_id,
dark: SiteSetting.use_overhauled_theme_color_palette,
fallback_to_base: false,
)
end
@ -786,11 +777,7 @@ module ApplicationHelper
end

def dark_color_hex_for_name(name)
ColorScheme.hex_for_name(
name,
dark_scheme_id,
dark: SiteSetting.use_overhauled_theme_color_palette,
)
ColorScheme.hex_for_name(name, dark_scheme_id)
end

def dark_elements_media_query

View file

@ -346,12 +346,6 @@ class ColorScheme < ActiveRecord::Base
belongs_to :theme
belongs_to :base_scheme, class_name: "ColorScheme"

has_one :theme_color_scheme, dependent: :destroy
has_one :owning_theme, class_name: "Theme", through: :theme_color_scheme, source: :theme

scope :without_theme_owned_palettes,
-> { where("color_schemes.id NOT IN (SELECT color_scheme_id FROM theme_color_schemes)") }

validates_associated :color_scheme_colors

BASE_COLORS_FILE = "#{Rails.root}/app/assets/stylesheets/common/foundation/colors.scss"
@ -450,18 +444,17 @@ class ColorScheme < ActiveRecord::Base
new_color_scheme
end

def self.lookup_hex_for_name(name, scheme_id = nil, dark: false)
def self.lookup_hex_for_name(name, scheme_id = nil)
enabled_color_scheme = find_by(id: scheme_id) if scheme_id
enabled_color_scheme ||= Theme.where(id: SiteSetting.default_theme_id).first&.color_scheme
color_record = (enabled_color_scheme || base).colors.find { |c| c.name == name }
return if !color_record
dark ? color_record.dark_hex || color_record.hex : color_record.hex
color_record.hex
end

def self.hex_for_name(name, scheme_id = nil, dark: false)
def self.hex_for_name(name, scheme_id = nil)
cache_key = scheme_id ? "#{name}_#{scheme_id}" : name
cache_key += "_dark" if dark
hex_cache.defer_get_set(cache_key) { lookup_hex_for_name(name, scheme_id, dark:) }
hex_cache.defer_get_set(cache_key) { lookup_hex_for_name(name, scheme_id) }
end

def colors=(arr)
@ -501,16 +494,10 @@ class ColorScheme < ActiveRecord::Base
colors || (base_scheme_id ? {} : ColorScheme.base_colors)
end

def resolved_colors(dark: false)
def resolved_colors
from_base = ColorScheme.base_colors
from_custom_scheme = base_colors
from_db =
colors
.map do |c|
hex = dark ? (c.dark_hex || c.hex) : c.hex
[c.name, hex]
end
.to_h
from_db = colors.map { |c| [c.name, c.hex] }.to_h

resolved = from_base.merge(from_custom_scheme).except("hover", "selected").merge(from_db)


View file

@ -1,10 +1,13 @@
# frozen_string_literal: true

class ColorSchemeColor < ActiveRecord::Base
self.ignored_columns = [
"dark_hex", # TODO: Remove when 20250821155127_drop_dark_hex_from_color_scheme_color has been promoted to pre-deploy
]

belongs_to :color_scheme

validates :hex, format: { with: /\A([0-9a-fA-F]{3}|[0-9a-fA-F]{6})\z/ }
validates :dark_hex, format: { with: /\A([0-9a-fA-F]{3}|[0-9a-fA-F]{6})\z/ }, allow_nil: true

def hex_with_hash
"##{hex}"
@ -16,12 +19,11 @@ end
# Table name: color_scheme_colors
#
# id :integer not null, primary key
# name :string not null
# hex :string not null
# color_scheme_id :integer not null
# name :string not null
# created_at :datetime not null
# updated_at :datetime not null
# dark_hex :string(6)
# color_scheme_id :integer not null
#
# Indexes
#

View file

@ -2,12 +2,12 @@

class ColorSchemeSetting < EnumSiteSetting
def self.valid_value?(val)
val == -1 || ColorScheme.without_theme_owned_palettes.find_by_id(val)
val == -1 || ColorScheme.find_by_id(val)
end

def self.values
values = [{ name: I18n.t("site_settings.dark_mode_none"), value: -1 }]
ColorScheme.all.without_theme_owned_palettes.map { |c| values << { name: c.name, value: c.id } }
ColorScheme.all.map { |c| values << { name: c.name, value: c.id } }
values
end
end

View file

@ -66,13 +66,6 @@ class Theme < ActiveRecord::Base
-> { where(target_id: Theme.targets[:settings], name: "yaml") },
class_name: "ThemeField"
has_one :javascript_cache, dependent: :destroy
has_one :theme_color_scheme, dependent: :destroy
has_one :owned_color_scheme,
class_name: "ColorScheme",
through: :theme_color_scheme,
source: :color_scheme
alias_method :owned_color_palette, :owned_color_scheme
alias_method :owned_color_palette=, :owned_color_scheme=

has_many :locale_fields,
-> { filter_locale_fields(I18n.fallbacks[I18n.locale]) },
@ -129,7 +122,6 @@ class Theme < ActiveRecord::Base
:locale_fields,
:theme_translation_overrides,
color_scheme: %i[theme color_scheme_colors base_scheme],
owned_color_scheme: %i[theme color_scheme_colors base_scheme],
parent_themes: %i[color_scheme locale_fields theme_translation_overrides],
)
end
@ -1089,40 +1081,6 @@ class Theme < ActiveRecord::Base
ThemeSiteSettingResolver.new(theme: self).resolved_theme_site_settings
end

def find_or_create_owned_color_palette
Theme.transaction do
next self.owned_color_palette if self.owned_color_palette

palette = self.color_palette || ColorScheme.base

copy = palette.dup
copy.theme_id = self.id
copy.base_scheme_id = nil
copy.user_selectable = false
copy.via_wizard = false
copy.save!

result =
ThemeColorScheme.insert_all(
[{ theme_id: self.id, color_scheme_id: copy.id }],
unique_by: :index_theme_color_schemes_on_theme_id,
)

if result.rows.size == 0
# race condition, a palette has already been associated with this theme
copy.destroy!
self.reload.owned_color_palette
else
ColorSchemeColor.insert_all(
palette.colors.map do |color|
{ color_scheme_id: copy.id, name: color.name, hex: color.hex, dark_hex: color.dark_hex }
end,
)
copy.reload
end
end
end

private

attr_accessor :theme_setting_requests_refresh

View file

@ -1,22 +0,0 @@
# frozen_string_literal: true

class ThemeColorScheme < ActiveRecord::Base
belongs_to :theme
belongs_to :color_scheme, dependent: :destroy
end

# == Schema Information
#
# Table name: theme_color_schemes
#
# id :bigint not null, primary key
# theme_id :integer not null
# color_scheme_id :integer not null
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_theme_color_schemes_on_color_scheme_id (color_scheme_id) UNIQUE
# index_theme_color_schemes_on_theme_id (theme_id) UNIQUE
#

View file

@ -1,16 +1,12 @@
# frozen_string_literal: true

class ColorSchemeColorSerializer < ApplicationSerializer
attributes :name, :hex, :default_hex, :is_advanced, :dark_hex, :default_dark_hex
attributes :name, :hex, :default_hex, :is_advanced

def hex
object.hex
end

def dark_hex
object.dark_hex || object.hex
end

def default_hex
# return the hex value of the color when it is already a base color or no base_scheme is set
if !object.color_scheme || object.color_scheme.base_scheme_id == 0
@ -20,12 +16,6 @@ class ColorSchemeColorSerializer < ApplicationSerializer
end
end

def default_dark_hex
# TODO(osama) implement this when we add dark mode colors for built-in
# palettes
nil
end

def is_advanced
!ColorScheme.base_colors.keys.include?(object.name)
end

View file

@ -19,8 +19,6 @@ class ThemeSerializer < BasicThemeSerializer
:system

has_one :color_scheme, serializer: ColorSchemeSerializer, embed: :object
has_one :owned_color_palette, serializer: ColorSchemeSerializer, embed: :object
has_one :base_palette, serializer: ColorSchemeSerializer, embed: :object
has_one :user, serializer: UserNameSerializer, embed: :object
has_one :disabled_by, serializer: UserNameSerializer, embed: :object

@ -60,14 +58,6 @@ class ThemeSerializer < BasicThemeSerializer
object.parent_themes
end

def base_palette
ColorScheme.base
end

def include_base_palette?
object.color_scheme_id.blank? && object.owned_color_palette.blank?
end

def settings
object.settings.map do |_name, setting|
ThemeSettingsSerializer.new(setting, scope:, root: false)

View file

@ -28,11 +28,7 @@ class ColorSchemeRevisor
if existing = @color_scheme.colors_by_name[c[:name]]
existing.update(c)
elsif !update_existing_colors_only
@color_scheme.color_scheme_colors << ColorSchemeColor.new(
name: c[:name],
hex: c[:hex],
dark_hex: c[:dark_hex],
)
@color_scheme.color_scheme_colors << ColorSchemeColor.new(name: c[:name], hex: c[:hex])
end
end
@color_scheme.clear_colors_cache

View file

@ -7051,15 +7051,6 @@ en:
repo_unreachable: "Couldn't contact the Git repository of this theme. Error message:"
built_in_description: "This theme is preinstalled and can not be deleted or customized"
imported_from_archive: "This theme was imported from a .zip file"
settings: "Settings"
colors: "Colors"
unsaved_colors:
one: "You have %{count} unsaved color"
other: "You have %{count} unsaved colors"
unsaved_colors_leave_route_confirmation: "You haven't saved your color palette changes yet. Are you sure you want to leave?"
save_colors: "Save colors"
discard_colors: "Discard"
colors_title: "Colors"
scss:
text: "CSS"
title: "Custom CSS rules with support for SCSS syntax"

View file

@ -254,7 +254,6 @@ Discourse::Application.routes.draw do
put "setting" => "themes#update_single_setting"
put "site-setting" => "themes#update_theme_site_setting"
get "objects_setting_metadata/:setting_name" => "themes#objects_setting_metadata"
put "change-colors" => "themes#change_colors"
end

collection do
@ -275,7 +274,6 @@ Discourse::Application.routes.draw do
get "components/:id" => "themes#index"
get "components/:id/:target/:field_name/edit" => "themes#index"
get "themes/:id/export" => "themes#export"
get "themes/:id/colors" => "themes#index"
get "themes/:id/schema/:setting_name" => "themes#schema"
get "components/:id/schema/:setting_name" => "themes#schema"


View file

@ -4150,11 +4150,6 @@ experimental:
type: group_list
list_type: compact
area: "group_permissions|experimental"
use_overhauled_theme_color_palette:
default: false
hidden: true
client: true
area: "experimental"
reviewable_ui_refresh:
client: true
type: group_list

View file

@ -0,0 +1,11 @@
# frozen_string_literal: true

class RemoveUseOverhauledThemeColorPaletteSetting < ActiveRecord::Migration[8.0]
def up
execute "DELETE FROM site_settings WHERE name = 'use_overhauled_theme_color_palette'"
end

def down
raise ActiveRecord::IrreversibleMigration
end
end

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true

class DropDarkHexFromColorSchemeColor < ActiveRecord::Migration[8.0]
DROPPED_COLUMNS = { color_scheme_colors: %i[dark_hex] }

def up
DROPPED_COLUMNS.each { |table, columns| Migration::ColumnDropper.execute_drop(table, columns) }
end

def down
raise ActiveRecord::IrreversibleMigration
end
end

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true

class DropThemeColorScheme < ActiveRecord::Migration[8.0]
DROPPED_TABLES = %i[theme_color_schemes]

def up
delete_ids = DB.query_single("SELECT color_scheme_id FROM theme_color_schemes")

if delete_ids.length > 0
execute("DELETE FROM color_schemes WHERE id IN (#{delete_ids.join(",")})")
execute("DELETE FROM color_scheme_colors WHERE color_scheme_id IN (#{delete_ids.join(",")})")
end

DROPPED_TABLES.each { |table| Migration::TableDropper.execute_drop(table) }
end

def down
raise ActiveRecord::IrreversibleMigration
end
end

View file

@ -160,21 +160,19 @@ module Stylesheet
if @color_scheme_id
colors =
begin
ColorScheme.find(@color_scheme_id).resolved_colors(dark: @dark)
ColorScheme.find(@color_scheme_id).resolved_colors
rescue StandardError
ColorScheme.base_colors
end
elsif (@theme_id && !theme.component)
colors = theme&.color_scheme&.resolved_colors(dark: @dark) || ColorScheme.base_colors
colors = theme&.color_scheme&.resolved_colors || ColorScheme.base_colors
else
# this is a slightly ugly backwards compatibility fix,
# we shouldn't be using the default theme color scheme for components
# (most components use CSS custom properties which work fine without this)
colors =
Theme
.find_by_id(SiteSetting.default_theme_id)
&.color_scheme
&.resolved_colors(dark: @dark) || ColorScheme.base_colors
Theme.find_by_id(SiteSetting.default_theme_id)&.color_scheme&.resolved_colors ||
ColorScheme.base_colors
end

colors.each { |n, hex| contents << "$#{n}: ##{hex} !default; " }
@ -195,7 +193,6 @@ module Stylesheet
@theme = options[:theme]
@theme_id = options[:theme_id]
@color_scheme_id = options[:color_scheme_id]
@dark = options[:dark]

if @theme && !@theme_id
# make up an id so other stuff does not bail out

View file

@ -42,11 +42,10 @@ class Stylesheet::Manager
cache.clear_regex(/#{plugin}/)
end

def self.color_scheme_cache_key(color_scheme, theme_id = nil, dark: false)
def self.color_scheme_cache_key(color_scheme, theme_id = nil)
color_scheme_name = Slug.for(color_scheme.name) + color_scheme&.id.to_s
theme_string = theme_id ? "_theme#{theme_id}" : ""
dark_string = dark ? "_dark" : ""
"#{COLOR_SCHEME_STYLESHEET}_#{color_scheme_name}_#{theme_string}_#{Discourse.current_hostname}_#{GlobalSetting.relative_url_root}_#{dark_string}"
"#{COLOR_SCHEME_STYLESHEET}_#{color_scheme_name}_#{theme_string}_#{Discourse.current_hostname}_#{GlobalSetting.relative_url_root}"
end

def self.precompile_css
@ -117,17 +116,13 @@ class Stylesheet::Manager
theme_dark_color_scheme = ColorScheme.find_by_id(dark_color_scheme_id)
theme = manager.get_theme(theme_id)
[theme_color_scheme, theme_dark_color_scheme, *color_schemes].compact.uniq.each do |scheme|
[true, false].each do |dark|
mode = dark ? "dark" : "light"
$stderr.puts "precompile target: #{COLOR_SCHEME_STYLESHEET} #{theme.name} (#{scheme.name}) (#{mode})"
Stylesheet::Manager::Builder.new(
target: COLOR_SCHEME_STYLESHEET,
theme: theme,
color_scheme: scheme,
manager: manager,
dark:,
).compile(force: true)
end
$stderr.puts "precompile target: #{COLOR_SCHEME_STYLESHEET} #{theme.name} (#{scheme.name})"
Stylesheet::Manager::Builder.new(
target: COLOR_SCHEME_STYLESHEET,
theme: theme,
color_scheme: scheme,
manager: manager,
).compile(force: true)
end

clear_color_scheme_cache!
@ -344,12 +339,7 @@ class Stylesheet::Manager
end
end

def color_scheme_stylesheet_details(
color_scheme_id = nil,
dark: false,
fallback_to_base: true,
include_dark_scheme: false
)
def color_scheme_stylesheet_details(color_scheme_id = nil, fallback_to_base: true)
theme_id = @theme_id || SiteSetting.default_theme_id

color_scheme = ColorScheme.find_by(id: color_scheme_id)
@ -361,10 +351,10 @@ class Stylesheet::Manager

target = COLOR_SCHEME_STYLESHEET.to_sym
current_hostname = Discourse.current_hostname
cache_key = self.class.color_scheme_cache_key(color_scheme, theme_id, dark:)
cache_key = self.class.color_scheme_cache_key(color_scheme, theme_id)

cache.defer_get_set(cache_key) do
stylesheet = { color_scheme_id: color_scheme.id, dark: }
stylesheet = { color_scheme_id: color_scheme.id }

theme = get_theme(theme_id)

@ -374,7 +364,6 @@ class Stylesheet::Manager
theme: get_theme(theme_id),
color_scheme: color_scheme,
manager: self,
dark:,
)

builder.compile unless File.exist?(builder.stylesheet_fullpath)
@ -382,27 +371,12 @@ class Stylesheet::Manager
href = builder.stylesheet_absolute_url
stylesheet[:new_href] = href

if include_dark_scheme
dark_href =
self.color_scheme_stylesheet_link_tag_href(
color_scheme_id,
dark: true,
fallback_to_base: false,
)

stylesheet[:new_dark_href] = dark_href if dark_href
end

stylesheet.freeze
end
end

def color_scheme_stylesheet_preload_tag(
color_scheme_id = nil,
dark: false,
fallback_to_base: true
)
stylesheet = color_scheme_stylesheet_details(color_scheme_id, dark:, fallback_to_base:)
def color_scheme_stylesheet_preload_tag(color_scheme_id = nil, fallback_to_base: true)
stylesheet = color_scheme_stylesheet_details(color_scheme_id, fallback_to_base:)

return "" if !stylesheet

@ -411,12 +385,8 @@ class Stylesheet::Manager
%[<link href="#{href}" rel="preload" as="style"/>].html_safe
end

def color_scheme_stylesheet_link_tag_href(
color_scheme_id = nil,
dark: false,
fallback_to_base: true
)
stylesheet = color_scheme_stylesheet_details(color_scheme_id, dark:, fallback_to_base:)
def color_scheme_stylesheet_link_tag_href(color_scheme_id = nil, fallback_to_base: true)
stylesheet = color_scheme_stylesheet_details(color_scheme_id, fallback_to_base:)

return if !stylesheet


View file

@ -3,12 +3,11 @@
class Stylesheet::Manager::Builder
attr_reader :theme

def initialize(target: :desktop, theme: nil, color_scheme: nil, manager:, dark: false)
def initialize(target: :desktop, theme: nil, color_scheme: nil, manager:)
@target = target
@theme = theme
@color_scheme = color_scheme
@manager = manager
@dark = dark
end

def compile(opts = {})
@ -47,7 +46,6 @@ class Stylesheet::Manager::Builder
source_map_file: source_map_url_relative_from_stylesheet,
color_scheme_id: @color_scheme&.id,
load_paths: load_paths,
dark: @dark,
strict_deprecations: %i[desktop mobile admin wizard].include?(@target),
)
rescue SassC::SyntaxError, SassC::NotRenderedError, DiscourseJsProcessor::TranspileError => e
@ -122,14 +120,13 @@ class Stylesheet::Manager::Builder
end

def qualified_target
dark_string = @dark ? "_dark" : ""
if is_theme?
"#{@target}_#{theme&.id}"
elsif @color_scheme
"#{@target}_#{scheme_slug}_#{@color_scheme&.id}_#{@theme&.id}#{dark_string}"
"#{@target}_#{scheme_slug}_#{@color_scheme&.id}_#{@theme&.id}"
else
scheme_string = theme&.color_scheme ? "_#{theme.color_scheme.id}" : ""
"#{@target}#{scheme_string}#{dark_string}"
"#{@target}#{scheme_string}"
end
end

@ -250,9 +247,8 @@ class Stylesheet::Manager::Builder
digest_string = "#{current_hostname}-"
if cs
theme_color_defs = resolve_baked_field(:common, :color_definitions)
dark_string = @dark ? "-dark" : ""
digest_string +=
"#{RailsMultisite::ConnectionManagement.current_db}-#{cs&.id}-#{cs&.version}-#{theme_color_defs}-#{Stylesheet::Manager.fs_asset_cachebuster}-#{fonts}#{dark_string}"
"#{RailsMultisite::ConnectionManagement.current_db}-#{cs&.id}-#{cs&.version}-#{theme_color_defs}-#{Stylesheet::Manager.fs_asset_cachebuster}-#{fonts}"
else
digest_string += "defaults-#{Stylesheet::Manager.fs_asset_cachebuster}-#{fonts}"


View file

@ -956,20 +956,12 @@ RSpec.describe ApplicationHelper do
describe "#discourse_theme_color_meta_tags" do
before do
light = Fabricate(:color_scheme)
light.color_scheme_colors << ColorSchemeColor.new(
name: "header_background",
hex: "abcdef",
dark_hex: "fedcba",
)
light.color_scheme_colors << ColorSchemeColor.new(name: "header_background", hex: "abcdef")
light.save!
helper.request.cookies["color_scheme_id"] = light.id

dark = Fabricate(:color_scheme)
dark.color_scheme_colors << ColorSchemeColor.new(
name: "header_background",
hex: "defabc",
dark_hex: "cbafed",
)
dark.color_scheme_colors << ColorSchemeColor.new(name: "header_background", hex: "defabc")
dark.save!
helper.request.cookies["dark_scheme_id"] = dark.id
end
@ -989,17 +981,6 @@ RSpec.describe ApplicationHelper do
<meta name="theme-color" media="all" content="#abcdef">
HTML
end

context "when use_overhauled_theme_color_palette setting is true" do
before { SiteSetting.use_overhauled_theme_color_palette = true }

it "renders a light and dark theme-color meta tag using the light and dark palettes of the same color scheme record" do
expect(helper.discourse_theme_color_meta_tags).to eq(<<~HTML)
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#abcdef">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#fedcba">
HTML
end
end
end

describe "#discourse_color_scheme_meta_tag" do
@ -1055,15 +1036,6 @@ RSpec.describe ApplicationHelper do
it "returns the value set in the dark_scheme_id cookie" do
expect(helper.dark_scheme_id).to eq(dark_scheme.id)
end

context "when use_overhauled_theme_color_palette is true" do
before { SiteSetting.use_overhauled_theme_color_palette = true }

it "returns the same value as #scheme_id" do
expect(helper.dark_scheme_id).to eq(helper.scheme_id)
expect(helper.scheme_id).to eq(light_scheme.id)
end
end
end

describe "#forced_light_mode?" do

View file

@ -688,81 +688,6 @@ RSpec.describe Stylesheet::Manager do
expect(href).to include("/stylesheets/color_definitions_funky-bunch_#{cs.id}_")
end

it "generates the dark mode of a color scheme when the dark option is specified" do
scheme =
ColorScheme.create_from_base(
name: "Neutral",
base_scheme_id: ColorScheme::NAMES_TO_ID_MAP["Neutral"],
)
ColorSchemeRevisor.revise(
scheme,
colors: [{ name: "primary", hex: "CABFAF", dark_hex: "FAFCAB" }],
)
theme = Fabricate(:theme)
manager = manager(theme.id)

dark_stylesheet =
Stylesheet::Manager::Builder.new(
target: :color_definitions,
theme: theme,
color_scheme: scheme,
manager: manager,
dark: true,
).compile
light_stylesheet =
Stylesheet::Manager::Builder.new(
target: :color_definitions,
theme: theme,
color_scheme: scheme,
manager: manager,
).compile

expect(light_stylesheet).to include("--primary: #CABFAF;")
expect(light_stylesheet).to include("color_definitions_neutral_#{scheme.id}_#{theme.id}")
expect(light_stylesheet).not_to include(
"color_definitions_neutral_#{scheme.id}_#{theme.id}_dark",
)

expect(dark_stylesheet).to include("--primary: #FAFCAB;")
expect(dark_stylesheet).to include("color_definitions_neutral_#{scheme.id}_#{theme.id}_dark")
end

it "uses the light colors as fallback if the dark scheme doesn't define them" do
scheme =
ColorScheme.create_from_base(
name: "Neutral",
base_scheme_id: ColorScheme::NAMES_TO_ID_MAP["Neutral"],
)
ColorSchemeRevisor.revise(scheme, colors: [{ name: "primary", hex: "BACFAB", dark_hex: nil }])
theme = Fabricate(:theme)
manager = manager(theme.id)

dark_stylesheet =
Stylesheet::Manager::Builder.new(
target: :color_definitions,
theme: theme,
color_scheme: scheme,
manager: manager,
dark: true,
).compile
light_stylesheet =
Stylesheet::Manager::Builder.new(
target: :color_definitions,
theme: theme,
color_scheme: scheme,
manager: manager,
).compile

expect(light_stylesheet).to include("--primary: #BACFAB;")
expect(light_stylesheet).to include("color_definitions_neutral_#{scheme.id}_#{theme.id}")
expect(light_stylesheet).not_to include(
"color_definitions_neutral_#{scheme.id}_#{theme.id}_dark",
)

expect(dark_stylesheet).to include("--primary: #BACFAB;")
expect(dark_stylesheet).to include("color_definitions_neutral_#{scheme.id}_#{theme.id}_dark")
end

it "updates outputted colors when updating a color scheme" do
scheme =
ColorScheme.create_from_base(
@ -1004,7 +929,7 @@ RSpec.describe Stylesheet::Manager do

# Ensure we force compile each theme only once
expect(output.scan(/#{child_theme_with_css.name}/).length).to eq(2) # ltr/rtl
expect(StylesheetCache.count).to eq(10) # (2 theme with rtl/ltr) + 8 color schemes (2 themes * 2 color schemes (1 base light palette + 1 theme scheme) * 2 (light and dark mode per scheme))
expect(StylesheetCache.count).to eq(6) # (2 theme with rtl/ltr) + 4 color schemes (2 themes * 2 color schemes (1 base light palette + 1 theme scheme))
end

it "generates precompiled CSS - core and themes" do
@ -1012,7 +937,7 @@ RSpec.describe Stylesheet::Manager do
Stylesheet::Manager.precompile_theme_css

results = StylesheetCache.pluck(:target)
expect(results.size).to eq(20) # 10 core targets + 2 theme (ltr/rtl) + 8 color schemes (light and dark mode per scheme)
expect(results.size).to eq(16) # 10 core targets + 2 theme (ltr/rtl) + 4 color schemes

expect(results.count { |target| target =~ /^common_theme_/ }).to eq(2) # ltr/rtl
end
@ -1024,7 +949,7 @@ RSpec.describe Stylesheet::Manager do
Stylesheet::Manager.precompile_theme_css

results = StylesheetCache.pluck(:target)
expect(results.size).to eq(24) # 10 core targets + 2 theme rtl/ltr + 12 color schemes (light and dark mode per scheme)
expect(results.size).to eq(18) # 10 core targets + 2 theme rtl/ltr + 6 color schemes

expect(results).to include("color_definitions_#{scheme1.name}_#{scheme1.id}_#{user_theme.id}")
expect(results).to include(

View file

@ -1575,17 +1575,6 @@ HTML
end
end

describe "#owned_color_scheme" do
it "is destroyed when the theme is destroyed" do
scheme = Fabricate(:color_scheme, owning_theme: theme)

theme.destroy!

expect(ThemeColorScheme.exists?(color_scheme_id: scheme.id)).to eq(false)
expect(ColorScheme.unscoped.exists?(id: scheme.id)).to eq(false)
end
end

describe ".include_basic_relations" do
fab!(:parent_theme_1) do
Fabricate(
@ -1705,51 +1694,6 @@ HTML
end
end

describe "#find_or_create_owned_color_palette" do
it "correctly associates a theme with its owned color palette" do
palette = theme.find_or_create_owned_color_palette

expect(palette.owning_theme).to eq(theme)
expect(theme.reload.owned_color_palette).to eq(palette)
end

it "ensures owned color palette is not user selectable" do
palette = theme.find_or_create_owned_color_palette

expect(palette.user_selectable).to eq(false)
end

it "copies colors from base or theme color scheme" do
theme_without_scheme = Fabricate(:theme, color_scheme: nil)
base_palette = theme_without_scheme.find_or_create_owned_color_palette

expect(base_palette.colors.length).to be > 0
expect(base_palette.colors.map(&:name).sort).to eq(ColorScheme.base.colors.map(&:name).sort)

custom_palette =
Fabricate(
:color_scheme,
colors: [ColorSchemeColor.new(name: "custom", hex: "11ccff", dark_hex: "ee9955")],
)
theme_with_scheme = Fabricate(:theme, color_scheme: custom_palette)
custom_palette = theme_with_scheme.find_or_create_owned_color_palette

expect(custom_palette.colors.length).to be > 0
expect(custom_palette.colors.map(&:name).sort).to eq(custom_palette.colors.map(&:name).sort)
end

it "returns the existing palette if a race condition occurs and a theme-owned palette is created while it's executing" do
expect(theme.owned_color_palette).to eq(nil)

palette = Fabricate(:color_scheme)
ThemeColorScheme.create!(theme_id: theme.id, color_scheme_id: palette.id)

expect(theme.owned_color_palette).to eq(nil)
expect(theme.find_or_create_owned_color_palette.id).to eq(palette.id)
expect(theme.owned_color_palette).to eq(palette)
end
end

it "checks if fields can be updated for system themes" do
foundation_theme.update!(user_selectable: true)
expect(foundation_theme.user_selectable).to be true

View file

@ -45,45 +45,6 @@ RSpec.describe Admin::ColorSchemesController do
expect(scheme_colors[0]["name"]).to eq("primary")
expect(scheme_colors[0]["hex"]).to eq(scheme.resolved_colors["primary"])
end

it "doesn't list theme-owned color schemes" do
owned_scheme = Fabricate(:color_scheme, owning_theme: Fabricate(:theme))
scheme = Fabricate(:color_scheme)

get "/admin/color_schemes.json"
expect(response.status).to eq(200)

ids = response.parsed_body.map { |obj| obj["id"] }
expect(ids).to include(scheme.id)
expect(ids).not_to include(owned_scheme.id)
end

it "filters out theme-owned color schemes when exclude_theme_owned is true" do
theme = Fabricate(:theme)
theme_owned_scheme = Fabricate(:color_scheme, name: "Theme Scheme")

ThemeColorScheme.create!(theme: theme, color_scheme: theme_owned_scheme)

owned_scheme = Fabricate(:color_scheme, name: "Directly Owned", theme: theme)
regular_scheme = Fabricate(:color_scheme, name: "Regular Scheme")

get "/admin/color_schemes.json", params: { exclude_theme_owned: true }

expect(response.status).to eq(200)

scheme_names = response.parsed_body.map { |scheme| scheme["name"] }
expect(scheme_names).to include("Regular Scheme")
expect(scheme_names).not_to include("Theme Scheme")
expect(scheme_names).not_to include("Directly Owned")

get "/admin/color_schemes.json"

expect(response.status).to eq(200)
scheme_names = response.parsed_body.map { |scheme| scheme["name"] }
expect(scheme_names).to include("Regular Scheme")
expect(scheme_names).to include("Directly Owned")
expect(scheme_names).not_to include("Theme Scheme")
end
end

shared_examples "color schemes inaccessible" do
@ -187,15 +148,6 @@ RSpec.describe Admin::ColorSchemesController do
expect(response.status).to eq(422)
expect(response.parsed_body["errors"]).to be_present
end

it "doesn't allow editing theme-owned schemes" do
color_scheme = Fabricate(:color_scheme, owning_theme: Fabricate(:theme))

put "/admin/color_schemes/#{color_scheme.id}.json", params: valid_params
expect(response.status).to eq(404)
color_scheme.reload
expect(color_scheme.name).not_to eq(valid_params[:color_scheme][:name])
end
end

shared_examples "color scheme update not allowed" do
@ -232,14 +184,6 @@ RSpec.describe Admin::ColorSchemesController do
}.by(-1)
expect(response.status).to eq(200)
end

it "doesn't allow deleting theme-owned schemes" do
color_scheme = Fabricate(:color_scheme, owning_theme: Fabricate(:theme))

delete "/admin/color_schemes/#{color_scheme.id}.json"
expect(response.status).to eq(404)
expect(color_scheme.reload).to be_persisted
end
end

shared_examples "color scheme deletion not allowed" do

View file

@ -1690,98 +1690,6 @@ RSpec.describe Admin::ThemesController do
end
end

describe "#change_colors" do
fab!(:theme)

before { sign_in(admin) }

context "with valid parameters" do
it "creates a theme-owned color palette if one doesn't exist" do
expect(theme.owned_color_palette).to be_nil

put "/admin/themes/#{theme.id}/change-colors.json",
params: {
colors: [{ name: "primary", hex: "ff0000", dark_hex: "0000ff" }],
}

expect(response.status).to eq(200)

theme.reload
expect(theme.owned_color_palette.id).to eq(response.parsed_body["id"])

color = theme.owned_color_palette.colors.find_by(name: "primary")
expect(color.hex).to eq("ff0000")
expect(color.dark_hex).to eq("0000ff")
end

it "updates an existing theme-owned color palette" do
palette = theme.find_or_create_owned_color_palette
primary_color = palette.colors.find_by(name: "primary")
secondary_color = palette.colors.find_by(name: "secondary")

original_secondary_hex = secondary_color.hex
original_secondary_dark_hex = secondary_color.dark_hex

put "/admin/themes/#{theme.id}/change-colors.json",
params: {
colors: [{ name: "primary", hex: "aabbcc", dark_hex: "ccddee" }],
}

expect(response.status).to eq(200)

primary_color.reload
secondary_color.reload

expect(primary_color.hex).to eq("aabbcc")
expect(primary_color.dark_hex).to eq("ccddee")

expect(secondary_color.hex).to eq(original_secondary_hex)
expect(secondary_color.dark_hex).to eq(original_secondary_dark_hex)
end

it "returns the updated palette in the response" do
put "/admin/themes/#{theme.id}/change-colors.json",
params: {
colors: [{ name: "primary", hex: "abcdef", dark_hex: "fedcba" }],
}

expect(response.status).to eq(200)
json = response.parsed_body

expect(json["colors"]).to be_present
primary_color = json["colors"].find { |c| c["name"] == "primary" }
expect(primary_color["hex"]).to eq("abcdef")
expect(primary_color["dark_hex"]).to eq("fedcba")
end
end

context "with invalid parameters" do
it "returns 404 for non-existent theme" do
max_id = (Theme.maximum(:id) || 0) + 1

put "/admin/themes/#{max_id}/change-colors.json",
params: {
colors: [{ name: "primary", hex: "ff0000", dark_hex: "0000ff" }],
}

expect(response.status).to eq(404)
end
end

context "when system theme" do
before { theme.update_columns(id: -10) }

it "returns invalid access" do
put "/admin/themes/#{theme.id}/change-colors.json",
params: {
colors: [{ name: "primary", hex: "ff0000", dark_hex: "0000ff" }],
}

expect(response.status).to eq(403)
end
end
end

describe "#show" do
let(:theme) { Fabricate(:theme) }


View file

@ -189,8 +189,6 @@ describe "Admin Color Palette Config Area Page", type: :system do

color_scheme.colors.each do |color|
expect(color.hex).to eq(clipboard_scheme["light"][color.name])
next if color.dark_hex.nil?
expect(color.dark_hex).to eq(clipboard_scheme["dark"][color.name])
end
end
end

View file

@ -268,95 +268,6 @@ describe "Admin Customize Themes", type: :system do
end
end

describe "theme color palette editor" do
before { SiteSetting.use_overhauled_theme_color_palette = true }

it "allows editing colors of theme-owned palette" do
theme_page.visit(theme.id)
theme_page.colors_tab.click

expect(theme_page).to have_current_path("/admin/customize/themes/#{theme.id}/colors")

theme_page.color_palette_editor.change_color("primary", "#ff000e")

expect(theme_page.changes_banner).to be_visible
theme_page.changes_banner.click_save

page.refresh
expect(theme_page).to have_colors_tab_active

updated_color = theme_page.color_palette_editor.get_color_value("primary")
expect(updated_color).to eq("#ff000e")
end

it "allows discarding unsaved color changes" do
theme_page.visit(theme.id)
theme_page.colors_tab.click

original_hex = theme_page.color_palette_editor.get_color_value("primary")

theme_page.color_palette_editor.change_color("primary", "#10ff00")

theme_page.changes_banner.click_discard

expect(theme_page.changes_banner).to be_hidden

updated_color = theme_page.color_palette_editor.get_color_value("primary")
expect(updated_color).to eq(original_hex)
end

it "shows count of unsaved colors" do
theme_page.visit(theme.id)
theme_page.colors_tab.click

theme_page.color_palette_editor.change_color("primary", "#eeff80")

expect(theme_page.changes_banner).to have_label(
I18n.t("admin_js.admin.customize.theme.unsaved_colors", count: 1),
)

theme_page.color_palette_editor.change_color("secondary", "#ee30ab")
expect(theme_page.changes_banner).to have_label(
I18n.t("admin_js.admin.customize.theme.unsaved_colors", count: 2),
)
end

it "doesn't show colors tab or DPageHeader for components" do
component = Fabricate(:theme, component: true)
theme_page.visit(component.id)
expect(theme_page.header).to be_hidden

expect(theme_page).to have_no_color_scheme_selector
end

it "shows a confirmation dialog when leaving the page with unsaved changes" do
theme_page.visit(theme.id)
theme_page.colors_tab.click

theme_page.color_palette_editor.change_color("primary", "#eeff80")

expect(theme_page.changes_banner).to be_visible

find("#site-logo").click

expect(dialog).to be_open
expect(page).to have_content(
I18n.t("admin_js.admin.customize.theme.unsaved_colors_leave_route_confirmation"),
)

dialog.click_no

expect(dialog).to be_closed
expect(page).to have_current_path("/admin/customize/themes/#{theme.id}/colors")

find("#site-logo").click
expect(dialog).to be_open

dialog.click_yes
expect(page).to have_current_path("/")
end
end

describe "editing theme site settings" do
it "shows all themeable site settings and allows editing values" do
theme_page.visit(theme.id)