FEATURE: themes and components split
* FEATURE: themes and components split * two seperate methods to switch theme type * use strict equality operator
This commit is contained in:
parent
4a552fb967
commit
e0cc29d658
|
@ -0,0 +1,4 @@
|
|||
export default Ember.Component.extend({
|
||||
classNames: ["themes-list"],
|
||||
hasThemes: Ember.computed.gt("themes.length", 0)
|
||||
});
|
|
@ -53,11 +53,6 @@ export default Ember.Controller.extend({
|
|||
return this.shouldShow("mobile");
|
||||
},
|
||||
|
||||
@computed("onlyOverridden", "model.remote_theme")
|
||||
showSettings() {
|
||||
return false;
|
||||
},
|
||||
|
||||
@observes("onlyOverridden")
|
||||
onlyOverriddenChanged() {
|
||||
if (this.get("onlyOverridden")) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import showModal from "discourse/lib/show-modal";
|
|||
import ThemeSettings from "admin/models/theme-settings";
|
||||
|
||||
const THEME_UPLOAD_VAR = 2;
|
||||
const SETTINGS_TYPE_ID = 5;
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
editRouteName: "adminCustomizeThemes.edit",
|
||||
|
@ -24,8 +25,11 @@ export default Ember.Controller.extend({
|
|||
}
|
||||
},
|
||||
|
||||
@computed("model", "allThemes")
|
||||
@computed("model", "allThemes", "model.component")
|
||||
parentThemes(model, allThemes) {
|
||||
if (!model.get("component")) {
|
||||
return null;
|
||||
}
|
||||
let parents = allThemes.filter(theme =>
|
||||
_.contains(theme.get("childThemes"), model)
|
||||
);
|
||||
|
@ -34,7 +38,9 @@ export default Ember.Controller.extend({
|
|||
|
||||
@computed("model.theme_fields.@each")
|
||||
hasEditedFields(fields) {
|
||||
return fields.any(f => !Em.isBlank(f.value));
|
||||
return fields.any(
|
||||
f => !Em.isBlank(f.value) && f.type_id !== SETTINGS_TYPE_ID
|
||||
);
|
||||
},
|
||||
|
||||
@computed("model.theme_fields.@each")
|
||||
|
@ -72,36 +78,31 @@ export default Ember.Controller.extend({
|
|||
"model",
|
||||
"allowChildThemes"
|
||||
)
|
||||
selectableChildThemes(available, childThemes, model, allowChildThemes) {
|
||||
selectableChildThemes(available, childThemes, allowChildThemes) {
|
||||
if (!allowChildThemes && (!childThemes || childThemes.length === 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let themes = [];
|
||||
available.forEach(t => {
|
||||
if (
|
||||
(!childThemes || childThemes.indexOf(t) === -1) &&
|
||||
Em.isEmpty(t.get("childThemes")) &&
|
||||
!t.get("user_selectable") &&
|
||||
!t.get("default")
|
||||
) {
|
||||
if (!childThemes || childThemes.indexOf(t) === -1) {
|
||||
themes.push(t);
|
||||
}
|
||||
});
|
||||
return themes.length === 0 ? null : themes;
|
||||
},
|
||||
|
||||
@computed("allThemes", "allThemes.length", "model", "parentThemes")
|
||||
availableChildThemes(allThemes, count) {
|
||||
if (count === 1 || this.get("parentThemes")) {
|
||||
@computed("allThemes", "allThemes.length", "model.component", "model")
|
||||
availableChildThemes(allThemes, count, component) {
|
||||
if (count === 1 || component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let excludeIds = [this.get("model.id")];
|
||||
const themeId = this.get("model.id");
|
||||
|
||||
let themes = [];
|
||||
allThemes.forEach(theme => {
|
||||
if (excludeIds.indexOf(theme.get("id")) === -1) {
|
||||
if (themeId !== theme.get("id") && theme.get("component")) {
|
||||
themes.push(theme);
|
||||
}
|
||||
});
|
||||
|
@ -109,6 +110,12 @@ export default Ember.Controller.extend({
|
|||
return themes;
|
||||
},
|
||||
|
||||
@computed("model.component")
|
||||
switchKey(component) {
|
||||
const type = component ? "component" : "theme";
|
||||
return `admin.customize.theme.switch_${type}`;
|
||||
},
|
||||
|
||||
@computed("model.settings")
|
||||
settings(settings) {
|
||||
return settings.map(setting => ThemeSettings.create(setting));
|
||||
|
@ -254,6 +261,33 @@ export default Ember.Controller.extend({
|
|||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
switchType() {
|
||||
return bootbox.confirm(
|
||||
I18n.t(`${this.get("switchKey")}_alert`),
|
||||
I18n.t("no_value"),
|
||||
I18n.t("yes_value"),
|
||||
result => {
|
||||
if (result) {
|
||||
const model = this.get("model");
|
||||
model.set("component", !model.get("component"));
|
||||
model
|
||||
.saveChanges("component")
|
||||
.then(() => {
|
||||
this.set("colorSchemeId", null);
|
||||
model.setProperties({
|
||||
default: false,
|
||||
color_scheme_id: null,
|
||||
user_selectable: false,
|
||||
child_themes: [],
|
||||
childThemes: []
|
||||
});
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
import { default as computed } from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
@computed("model", "model.@each")
|
||||
sortedThemes(themes) {
|
||||
return _.sortBy(themes.content, t => {
|
||||
@computed("model", "model.@each", "model.@each.component")
|
||||
fullThemes(themes) {
|
||||
return _.sortBy(themes.filter(t => !t.get("component")), t => {
|
||||
return [
|
||||
!t.get("default"),
|
||||
!t.get("user_selectable"),
|
||||
t.get("name").toLowerCase()
|
||||
];
|
||||
});
|
||||
},
|
||||
|
||||
@computed("model", "model.@each", "model.@each.component")
|
||||
childThemes(themes) {
|
||||
return themes.filter(t => t.get("component"));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import { default as computed } from "ember-addons/ember-computed-decorators";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
|
||||
const COMPONENT = "component";
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
types: [
|
||||
{ name: I18n.t("admin.customize.theme.theme"), value: "theme" },
|
||||
{ name: I18n.t("admin.customize.theme.component"), value: COMPONENT }
|
||||
],
|
||||
selectedType: "theme",
|
||||
name: I18n.t("admin.customize.new_style"),
|
||||
themesController: Ember.inject.controller("adminCustomizeThemes"),
|
||||
loading: false,
|
||||
|
||||
@computed("selectedType")
|
||||
component(type) {
|
||||
return type === COMPONENT;
|
||||
},
|
||||
|
||||
actions: {
|
||||
createTheme() {
|
||||
this.set("loading", true);
|
||||
const theme = this.store.createRecord("theme");
|
||||
theme
|
||||
.save({ name: this.get("name"), component: this.get("component") })
|
||||
.then(() => {
|
||||
this.get("themesController").send("addTheme", theme);
|
||||
this.send("closeModal");
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
.finally(() => this.set("loading", false));
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,3 +1,5 @@
|
|||
import { scrollTop } from "discourse/mixins/scroll-top";
|
||||
|
||||
export default Ember.Route.extend({
|
||||
serialize(model) {
|
||||
return { theme_id: model.get("id") };
|
||||
|
@ -10,14 +12,37 @@ export default Ember.Route.extend({
|
|||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
this._super(...arguments);
|
||||
|
||||
controller.set("model", model);
|
||||
|
||||
const parentController = this.controllerFor("adminCustomizeThemes");
|
||||
parentController.set("editingTheme", false);
|
||||
controller.set("allThemes", parentController.get("model"));
|
||||
|
||||
this.handleHighlight(model);
|
||||
|
||||
controller.set(
|
||||
"colorSchemes",
|
||||
parentController.get("model.extras.color_schemes")
|
||||
);
|
||||
controller.set("colorSchemeId", model.get("color_scheme_id"));
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
this.handleHighlight();
|
||||
},
|
||||
|
||||
handleHighlight(theme) {
|
||||
this.get("controller.allThemes").forEach(t => t.set("active", false));
|
||||
if (theme) {
|
||||
theme.set("active", true);
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
didTransition() {
|
||||
scrollTop();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import showModal from "discourse/lib/show-modal";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
|
||||
export default Ember.Route.extend({
|
||||
model() {
|
||||
|
@ -22,16 +21,8 @@ export default Ember.Route.extend({
|
|||
this.transitionTo("adminCustomizeThemes.show", theme.get("id"));
|
||||
},
|
||||
|
||||
newTheme(obj) {
|
||||
obj = obj || { name: I18n.t("admin.customize.new_style") };
|
||||
const item = this.store.createRecord("theme");
|
||||
|
||||
item
|
||||
.save(obj)
|
||||
.then(() => {
|
||||
this.send("addTheme", item);
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
showCreateModal() {
|
||||
showModal("admin-create-theme", { admin: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<div class="themes-list-header">
|
||||
<b>{{I18n title}}</b>
|
||||
</div>
|
||||
|
||||
<div class="themes-list-container">
|
||||
{{#if hasThemes}}
|
||||
{{#each themes as |theme|}}
|
||||
<div class="themes-list-item {{if theme.active 'active' ''}}">
|
||||
{{#link-to 'adminCustomizeThemes.show' theme replace=true}}
|
||||
{{plugin-outlet name="admin-customize-themes-list-item" connectorTagName='span' args=(hash theme=theme)}}
|
||||
{{theme.name}}
|
||||
{{#if theme.user_selectable}}
|
||||
{{d-icon "user"}}
|
||||
{{/if}}
|
||||
{{#if theme.default}}
|
||||
{{d-icon "asterisk"}}
|
||||
{{/if}}
|
||||
{{/link-to}}
|
||||
</div>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<div class="themes-list-item">
|
||||
<span class="empty">{{I18n "admin.customize.theme.empty"}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -31,14 +31,6 @@
|
|||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if showSettings}}
|
||||
<li class='theme-settings'>
|
||||
{{#link-to 'adminCustomizeThemes.edit' model.id 'settings' fieldName replace=true}}
|
||||
{{i18n 'admin.customize.theme.settings'}}
|
||||
{{d-icon 'cog'}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
<div class='show-overidden'>
|
||||
<label>
|
||||
|
|
|
@ -22,32 +22,34 @@
|
|||
|
||||
|
||||
{{#if parentThemes}}
|
||||
<h3>{{i18n "admin.customize.theme.component_of"}}</h3>
|
||||
<ul>
|
||||
{{#each parentThemes as |theme|}}
|
||||
<li>{{#link-to 'adminCustomizeThemes.show' theme replace=true}}{{theme.name}}{{/link-to}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<p>
|
||||
{{inline-edit-checkbox action="applyDefault" labelKey="admin.customize.theme.is_default" checked=model.default}}
|
||||
{{inline-edit-checkbox action="applyUserSelectable" labelKey="admin.customize.theme.user_selectable" checked=model.user_selectable}}
|
||||
</p>
|
||||
|
||||
<h3>{{i18n "admin.customize.theme.color_scheme"}}</h3>
|
||||
<p>{{i18n "admin.customize.theme.color_scheme_select"}}</p>
|
||||
<p>{{combo-box content=colorSchemes
|
||||
filterable=true
|
||||
value=colorSchemeId
|
||||
icon="paint-brush"}}
|
||||
{{#if colorSchemeChanged}}
|
||||
{{d-button action="changeScheme" class="btn-primary btn-small submit-edit" icon="check"}}
|
||||
{{d-button action="cancelChangeScheme" class="btn-small cancel-edit" icon="times"}}
|
||||
{{/if}}
|
||||
</p>
|
||||
{{#link-to 'adminCustomize.colors' class="btn edit"}}{{i18n 'admin.customize.colors.edit'}}{{/link-to}}
|
||||
<h3>{{i18n "admin.customize.theme.component_of"}}</h3>
|
||||
<ul>
|
||||
{{#each parentThemes as |theme|}}
|
||||
<li>{{#link-to 'adminCustomizeThemes.show' theme replace=true}}{{theme.name}}{{/link-to}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
|
||||
{{#unless model.component}}
|
||||
<p>
|
||||
{{inline-edit-checkbox action="applyDefault" labelKey="admin.customize.theme.is_default" checked=model.default}}
|
||||
{{inline-edit-checkbox action="applyUserSelectable" labelKey="admin.customize.theme.user_selectable" checked=model.user_selectable}}
|
||||
</p>
|
||||
|
||||
<h3>{{i18n "admin.customize.theme.color_scheme"}}</h3>
|
||||
<p>{{i18n "admin.customize.theme.color_scheme_select"}}</p>
|
||||
<p>{{combo-box content=colorSchemes
|
||||
filterable=true
|
||||
value=colorSchemeId
|
||||
icon="paint-brush"}}
|
||||
{{#if colorSchemeChanged}}
|
||||
{{d-button action="changeScheme" class="btn-primary btn-small submit-edit" icon="check"}}
|
||||
{{d-button action="cancelChangeScheme" class="btn-small cancel-edit" icon="times"}}
|
||||
{{/if}}
|
||||
</p>
|
||||
{{#link-to 'adminCustomize.colors' class="btn edit"}}{{i18n 'admin.customize.colors.edit'}}{{/link-to}}
|
||||
{{/unless}}
|
||||
|
||||
<h3>{{i18n "admin.customize.theme.css_html"}}</h3>
|
||||
{{#if hasEditedFields}}
|
||||
<p>{{i18n "admin.customize.theme.custom_sections"}}</p>
|
||||
|
@ -61,15 +63,18 @@
|
|||
{{i18n "admin.customize.theme.edit_css_html_help"}}
|
||||
</p>
|
||||
{{/if}}
|
||||
|
||||
<p>
|
||||
{{#if model.remote_theme}}
|
||||
{{#if model.remote_theme.commits_behind}}
|
||||
{{#d-button action="updateToLatest" icon="download" class='btn-primary'}}{{i18n "admin.customize.theme.update_to_latest"}}{{/d-button}}
|
||||
{{#d-button action="updateToLatest" icon="download" class='btn-primary'}}{{i18n "admin.customize.theme.update_to_latest"}}{{/d-button}}
|
||||
{{else}}
|
||||
{{#d-button action="checkForThemeUpdates" icon="refresh"}}{{i18n "admin.customize.theme.check_for_updates"}}{{/d-button}}
|
||||
{{#d-button action="checkForThemeUpdates" icon="refresh"}}{{i18n "admin.customize.theme.check_for_updates"}}{{/d-button}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#d-button action="editTheme" class="btn edit"}}{{i18n 'admin.customize.theme.edit_css_html'}}{{/d-button}}
|
||||
|
||||
{{#if model.remote_theme}}
|
||||
<span class='status-message'>
|
||||
{{#if updatingRemote}}
|
||||
|
@ -146,5 +151,6 @@
|
|||
<a href='{{previewUrl}}' title="{{i18n 'admin.customize.explain_preview'}}" target='_blank' class='btn'>{{d-icon 'desktop'}}{{i18n 'admin.customize.theme.preview'}}</a>
|
||||
<a class="btn export" target="_blank" href={{downloadUrl}}>{{d-icon "download"}} {{i18n 'admin.export_json.button_text'}}</a>
|
||||
|
||||
{{d-button action="switchType" label=switchKey icon="arrows-h" class="btn-danger"}}
|
||||
{{d-button action="destroy" label="admin.customize.delete" icon="trash" class="btn-danger"}}
|
||||
</div>
|
||||
|
|
|
@ -1,25 +1,15 @@
|
|||
{{#unless editingTheme}}
|
||||
<div class='content-list'>
|
||||
<h3>{{i18n 'admin.customize.theme.long_title'}}</h3>
|
||||
<ul>
|
||||
{{#each sortedThemes as |theme|}}
|
||||
<li>
|
||||
{{#link-to 'adminCustomizeThemes.show' theme replace=true}}
|
||||
{{plugin-outlet name="admin-customize-themes-list-item" connectorTagName='span' args=(hash theme=theme)}}
|
||||
{{theme.name}}
|
||||
{{#if theme.user_selectable}}
|
||||
{{d-icon "user"}}
|
||||
{{/if}}
|
||||
{{#if theme.default}}
|
||||
{{d-icon "asterisk"}}
|
||||
{{/if}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
||||
{{d-button label="admin.customize.new" icon="plus" action="newTheme" class="btn-primary"}}
|
||||
{{d-button action="importModal" icon="upload" label="admin.customize.import"}}
|
||||
<div class="create-actions">
|
||||
{{d-button label="admin.customize.new" icon="plus" action="showCreateModal" class="btn-primary"}}
|
||||
{{d-button action="importModal" icon="upload" label="admin.customize.import"}}
|
||||
</div>
|
||||
|
||||
{{themes-list themes=fullThemes title="admin.customize.theme.title"}}
|
||||
{{themes-list themes=childThemes title="admin.customize.theme.components"}}
|
||||
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{outlet}}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
{{#d-modal-body class="create-theme-modal" title="admin.customize.theme.modal_title"}}
|
||||
<div class="input">
|
||||
<span class="label">
|
||||
{{I18n "admin.customize.theme.create_name"}}
|
||||
</span>
|
||||
<span class="control">
|
||||
{{input value=name}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="input">
|
||||
<span class="label">
|
||||
{{I18n "admin.customize.theme.create_type"}}
|
||||
</span>
|
||||
<span class="control">
|
||||
{{combo-box valueAttribute="value" content=types value=selectedType}}
|
||||
</span>
|
||||
</div>
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
{{d-button class="btn btn-primary" label="admin.customize.theme.create" action="createTheme" disabled=loading}}
|
||||
{{d-modal-cancel close=(action "closeModal")}}
|
||||
</div>
|
|
@ -44,6 +44,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
.create-theme-modal {
|
||||
div.input {
|
||||
margin-bottom: 12px;
|
||||
.label {
|
||||
width: 20%;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.admin-customize {
|
||||
h1 {
|
||||
margin-bottom: 10px;
|
||||
|
@ -101,6 +111,62 @@
|
|||
}
|
||||
}
|
||||
|
||||
.create-actions {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.themes-list {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.themes-list-header {
|
||||
font-size: $font-up-1;
|
||||
padding: 10px;
|
||||
background-color: $primary-low;
|
||||
}
|
||||
|
||||
.themes-list-container {
|
||||
max-height: 280px;
|
||||
overflow-y: scroll;
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
border-radius: 10px;
|
||||
background-color: $secondary;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
background-color: $primary-low-mid;
|
||||
}
|
||||
|
||||
.themes-list-item {
|
||||
color: $primary;
|
||||
border-bottom: 1px solid $primary-low;
|
||||
display: flex;
|
||||
|
||||
&:hover {
|
||||
background-color: $tertiary-low;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $secondary;
|
||||
font-weight: bold;
|
||||
background-color: $tertiary;
|
||||
}
|
||||
|
||||
a,
|
||||
span.empty {
|
||||
color: inherit;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme.settings {
|
||||
.theme-setting {
|
||||
padding-bottom: 0;
|
||||
|
|
|
@ -95,13 +95,13 @@ class Admin::ThemesController < Admin::AdminController
|
|||
end
|
||||
|
||||
def index
|
||||
@theme = Theme.order(:name).includes(:theme_fields, :remote_theme)
|
||||
@themes = Theme.order(:name).includes(:theme_fields, :remote_theme)
|
||||
@color_schemes = ColorScheme.all.to_a
|
||||
light = ColorScheme.new(name: I18n.t("color_schemes.light"))
|
||||
@color_schemes.unshift(light)
|
||||
|
||||
payload = {
|
||||
themes: ActiveModel::ArraySerializer.new(@theme, each_serializer: ThemeSerializer),
|
||||
themes: ActiveModel::ArraySerializer.new(@themes, each_serializer: ThemeSerializer),
|
||||
extras: {
|
||||
color_schemes: ActiveModel::ArraySerializer.new(@color_schemes, each_serializer: ColorSchemeSerializer)
|
||||
}
|
||||
|
@ -116,7 +116,8 @@ class Admin::ThemesController < Admin::AdminController
|
|||
@theme = Theme.new(name: theme_params[:name],
|
||||
user_id: current_user.id,
|
||||
user_selectable: theme_params[:user_selectable] || false,
|
||||
color_scheme_id: theme_params[:color_scheme_id])
|
||||
color_scheme_id: theme_params[:color_scheme_id],
|
||||
component: [true, "true"].include?(theme_params[:component]))
|
||||
set_fields
|
||||
|
||||
respond_to do |format|
|
||||
|
@ -155,11 +156,11 @@ class Admin::ThemesController < Admin::AdminController
|
|||
Theme.where(id: expected).each do |theme|
|
||||
@theme.add_child_theme!(theme)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
set_fields
|
||||
update_settings
|
||||
handle_switch
|
||||
|
||||
save_remote = false
|
||||
if params[:theme][:remote_check]
|
||||
|
@ -247,6 +248,7 @@ class Admin::ThemesController < Admin::AdminController
|
|||
:color_scheme_id,
|
||||
:default,
|
||||
:user_selectable,
|
||||
:component,
|
||||
settings: {},
|
||||
theme_fields: [:name, :target, :value, :upload_id, :type_id],
|
||||
child_theme_ids: []
|
||||
|
@ -280,4 +282,12 @@ class Admin::ThemesController < Admin::AdminController
|
|||
StaffActionLogger.new(current_user).log_theme_change(old_record, new_record)
|
||||
end
|
||||
|
||||
def handle_switch
|
||||
param = theme_params[:component]
|
||||
if param.to_s == "false" && @theme.component?
|
||||
@theme.switch_to_theme!
|
||||
elsif param.to_s == "true" && !@theme.component?
|
||||
@theme.switch_to_component!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,17 +7,12 @@ class ChildTheme < ActiveRecord::Base
|
|||
private
|
||||
|
||||
def child_validations
|
||||
if ChildTheme.exists?(["parent_theme_id = ? OR child_theme_id = ?", child_theme_id, parent_theme_id])
|
||||
if Theme.where(
|
||||
"(component IS true AND id = :parent) OR (component IS false AND id = :child)",
|
||||
parent: parent_theme_id, child: child_theme_id
|
||||
).exists?
|
||||
errors.add(:base, I18n.t("themes.errors.no_multilevels_components"))
|
||||
end
|
||||
|
||||
if Theme.exists?(id: child_theme_id, user_selectable: true)
|
||||
errors.add(:base, I18n.t("themes.errors.component_no_user_selectable"))
|
||||
end
|
||||
|
||||
if child_theme_id == SiteSetting.default_theme_id
|
||||
errors.add(:base, I18n.t("themes.errors.component_no_default"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -40,7 +40,8 @@ class RemoteTheme < ActiveRecord::Base
|
|||
importer.import!
|
||||
|
||||
theme_info = JSON.parse(importer["about.json"])
|
||||
theme = Theme.new(user_id: user&.id || -1, name: theme_info["name"])
|
||||
component = [true, "true"].include?(theme_info["component"])
|
||||
theme = Theme.new(user_id: user&.id || -1, name: theme_info["name"], component: component)
|
||||
|
||||
remote_theme = new
|
||||
theme.remote_theme = remote_theme
|
||||
|
@ -142,7 +143,7 @@ class RemoteTheme < ActiveRecord::Base
|
|||
self.commits_behind = 0
|
||||
end
|
||||
|
||||
update_theme_color_schemes(theme, theme_info["color_schemes"])
|
||||
update_theme_color_schemes(theme, theme_info["color_schemes"]) unless theme.component
|
||||
|
||||
self
|
||||
ensure
|
||||
|
|
|
@ -20,7 +20,7 @@ class Theme < ActiveRecord::Base
|
|||
has_many :color_schemes
|
||||
belongs_to :remote_theme
|
||||
|
||||
validate :user_selectable_validation
|
||||
validate :component_validations
|
||||
|
||||
scope :user_selectable, ->() {
|
||||
where('user_selectable OR id = ?', SiteSetting.default_theme_id)
|
||||
|
@ -128,7 +128,7 @@ class Theme < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def set_default!
|
||||
if component?
|
||||
if component
|
||||
raise Discourse::InvalidParameters.new(
|
||||
I18n.t("themes.errors.component_no_default")
|
||||
)
|
||||
|
@ -141,13 +141,36 @@ class Theme < ActiveRecord::Base
|
|||
SiteSetting.default_theme_id == id
|
||||
end
|
||||
|
||||
def component?
|
||||
ChildTheme.exists?(child_theme_id: id)
|
||||
def component_validations
|
||||
return unless component
|
||||
|
||||
errors.add(:base, I18n.t("themes.errors.component_no_color_scheme")) if color_scheme_id.present?
|
||||
errors.add(:base, I18n.t("themes.errors.component_no_user_selectable")) if user_selectable
|
||||
errors.add(:base, I18n.t("themes.errors.component_no_default")) if default?
|
||||
end
|
||||
|
||||
def user_selectable_validation
|
||||
if component? && user_selectable
|
||||
errors.add(:base, I18n.t("themes.errors.component_no_user_selectable"))
|
||||
def switch_to_component!
|
||||
return if component
|
||||
|
||||
Theme.transaction do
|
||||
self.component = true
|
||||
|
||||
self.color_scheme_id = nil
|
||||
self.user_selectable = false
|
||||
Theme.clear_default! if default?
|
||||
|
||||
ChildTheme.where("parent_theme_id = ?", id).destroy_all
|
||||
self.save!
|
||||
end
|
||||
end
|
||||
|
||||
def switch_to_theme!
|
||||
return unless component
|
||||
|
||||
Theme.transaction do
|
||||
self.component = false
|
||||
ChildTheme.where("child_theme_id = ?", id).destroy_all
|
||||
self.save!
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ class ThemeFieldSerializer < ApplicationSerializer
|
|||
end
|
||||
|
||||
class ChildThemeSerializer < ApplicationSerializer
|
||||
attributes :id, :name, :created_at, :updated_at, :default
|
||||
attributes :id, :name, :created_at, :updated_at, :default, :component
|
||||
|
||||
def include_default?
|
||||
object.id == SiteSetting.default_theme_id
|
||||
|
@ -76,6 +76,10 @@ class ThemeSerializer < ChildThemeSerializer
|
|||
def settings
|
||||
object.settings.map { |setting| ThemeSettingsSerializer.new(setting, root: false) }
|
||||
end
|
||||
|
||||
def include_child_themes?
|
||||
!object.component?
|
||||
end
|
||||
end
|
||||
|
||||
class ThemeFieldWithEmbeddedUploadsSerializer < ThemeFieldSerializer
|
||||
|
|
|
@ -3192,9 +3192,16 @@ en:
|
|||
revert_confirm: "Are you sure you want to revert your changes?"
|
||||
|
||||
theme:
|
||||
theme: "Theme"
|
||||
component: "Component"
|
||||
components: "Components"
|
||||
import_theme: "Import Theme"
|
||||
customize_desc: "Customize:"
|
||||
title: "Themes"
|
||||
modal_title: "Create Theme"
|
||||
create: "Create"
|
||||
create_type: "Type:"
|
||||
create_name: "Name:"
|
||||
long_title: "Amend colors, CSS and HTML contents of your site"
|
||||
edit: "Edit"
|
||||
edit_confirm: "This is a remote theme, if you edit CSS/HTML your changes will be erased next time you update the theme."
|
||||
|
@ -3209,6 +3216,10 @@ en:
|
|||
color_scheme_select: "Select colors to be used by theme"
|
||||
custom_sections: "Custom sections:"
|
||||
theme_components: "Theme Components"
|
||||
switch_component: "Make theme"
|
||||
switch_component_alert: "Are you sure you want to convert this component to theme? This will make it an independant theme and it will be removed as a child from all themes."
|
||||
switch_theme: "Make component"
|
||||
switch_theme_alert: "Are you sure you want to convert this theme to component? It will be removed as a parent from all components."
|
||||
uploads: "Uploads"
|
||||
no_uploads: "You can upload assets associated with your theme such as fonts and images"
|
||||
add_upload: "Add Upload"
|
||||
|
@ -3231,7 +3242,7 @@ en:
|
|||
public_key: "Grant the following public key access to the repo:"
|
||||
about_theme: "About Theme"
|
||||
license: "License"
|
||||
component_of: "Theme is a component of:"
|
||||
component_of: "Component of:"
|
||||
update_to_latest: "Update to Latest"
|
||||
check_for_updates: "Check for Updates"
|
||||
updating: "Updating..."
|
||||
|
@ -3239,6 +3250,7 @@ en:
|
|||
add: "Add"
|
||||
theme_settings: "Theme Settings"
|
||||
no_settings: "This theme has no settings."
|
||||
empty: "No items"
|
||||
commits_behind:
|
||||
one: "Theme is 1 commit behind!"
|
||||
other: "Theme is {{count}} commits behind!"
|
||||
|
|
|
@ -63,6 +63,7 @@ en:
|
|||
errors:
|
||||
component_no_user_selectable: "Theme components can't be user-selectable"
|
||||
component_no_default: "Theme components can't be default theme"
|
||||
component_no_color_scheme: "Theme components can't have color scheme"
|
||||
no_multilevels_components: "Themes with child themes can't be child themes themselves"
|
||||
settings_errors:
|
||||
invalid_yaml: "Provided YAML is invalid."
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
class AddComponentToThemes < ActiveRecord::Migration[5.2]
|
||||
def up
|
||||
add_column :themes, :component, :boolean, null: false, default: false
|
||||
|
||||
execute("
|
||||
UPDATE themes
|
||||
SET component = true, color_scheme_id = NULL, user_selectable = false
|
||||
WHERE id IN (SELECT child_theme_id FROM child_themes)
|
||||
")
|
||||
|
||||
execute("
|
||||
UPDATE site_settings
|
||||
SET value = -1
|
||||
WHERE name = 'default_theme_id' AND value::integer IN (SELECT id FROM themes WHERE component)
|
||||
")
|
||||
|
||||
execute("
|
||||
DELETE FROM child_themes
|
||||
WHERE parent_theme_id IN (SELECT id FROM themes WHERE component)
|
||||
")
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :themes, :component
|
||||
end
|
||||
end
|
|
@ -2609,7 +2609,7 @@ describe Guardian do
|
|||
theme2.update!(user_selectable: true)
|
||||
expect(user_guardian.allow_themes?([theme.id, theme2.id])).to eq(false)
|
||||
|
||||
theme2.update!(user_selectable: false)
|
||||
theme2.update!(user_selectable: false, component: true)
|
||||
theme.add_child_theme!(theme2)
|
||||
expect(user_guardian.allow_themes?([theme.id, theme2.id])).to eq(true)
|
||||
expect(user_guardian.allow_themes?([theme2.id])).to eq(false)
|
||||
|
|
|
@ -25,7 +25,7 @@ describe Stylesheet::Manager do
|
|||
|
||||
theme.save!
|
||||
|
||||
child_theme = Fabricate(:theme)
|
||||
child_theme = Fabricate(:theme, component: true)
|
||||
|
||||
child_theme.set_field(target: :common, name: "scss", value: ".child_common{.scss{color: red;}}")
|
||||
child_theme.set_field(target: :desktop, name: "scss", value: ".child_desktop{.scss{color: red;}}")
|
||||
|
|
|
@ -4,13 +4,13 @@ describe ChildTheme do
|
|||
describe "validations" do
|
||||
it "doesn't allow children to become parents or parents to become children" do
|
||||
theme = Fabricate(:theme)
|
||||
child = Fabricate(:theme)
|
||||
child = Fabricate(:theme, component: true)
|
||||
|
||||
child_theme = ChildTheme.new(parent_theme: theme, child_theme: child)
|
||||
expect(child_theme.valid?).to eq(true)
|
||||
child_theme.save!
|
||||
|
||||
grandchild = Fabricate(:theme)
|
||||
grandchild = Fabricate(:theme, component: true)
|
||||
child_theme = ChildTheme.new(parent_theme: child, child_theme: grandchild)
|
||||
expect(child_theme.valid?).to eq(false)
|
||||
expect(child_theme.errors.full_messages).to contain_exactly(I18n.t("themes.errors.no_multilevels_components"))
|
||||
|
@ -20,24 +20,5 @@ describe ChildTheme do
|
|||
expect(child_theme.valid?).to eq(false)
|
||||
expect(child_theme.errors.full_messages).to contain_exactly(I18n.t("themes.errors.no_multilevels_components"))
|
||||
end
|
||||
|
||||
it "doesn't allow a user selectable theme to be a child" do
|
||||
parent = Fabricate(:theme)
|
||||
selectable_theme = Fabricate(:theme, user_selectable: true)
|
||||
|
||||
child_theme = ChildTheme.new(parent_theme: parent, child_theme: selectable_theme)
|
||||
expect(child_theme.valid?).to eq(false)
|
||||
expect(child_theme.errors.full_messages).to contain_exactly(I18n.t("themes.errors.component_no_user_selectable"))
|
||||
end
|
||||
|
||||
it "doesn't allow a default theme to be child" do
|
||||
parent = Fabricate(:theme)
|
||||
default = Fabricate(:theme)
|
||||
default.set_default!
|
||||
|
||||
child_theme = ChildTheme.new(parent_theme: parent, child_theme: default)
|
||||
expect(child_theme.valid?).to eq(false)
|
||||
expect(child_theme.errors.full_messages).to contain_exactly(I18n.t("themes.errors.component_no_default"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,7 +23,7 @@ describe Theme do
|
|||
end
|
||||
|
||||
let(:theme) { Fabricate(:theme, user: user) }
|
||||
let(:child) { Fabricate(:theme, user: user) }
|
||||
let(:child) { Fabricate(:theme, user: user, component: true) }
|
||||
it 'can properly clean up color schemes' do
|
||||
scheme = ColorScheme.create!(theme_id: theme.id, name: 'test')
|
||||
scheme2 = ColorScheme.create!(theme_id: theme.id, name: 'test2')
|
||||
|
@ -76,7 +76,6 @@ describe Theme do
|
|||
grandchild = Fabricate(:theme, user: user)
|
||||
grandparent = Fabricate(:theme, user: user)
|
||||
|
||||
theme.add_child_theme!(child)
|
||||
expect do
|
||||
child.add_child_theme!(grandchild)
|
||||
end.to raise_error(Discourse::InvalidParameters, I18n.t("themes.errors.no_multilevels_components"))
|
||||
|
@ -87,18 +86,22 @@ describe Theme do
|
|||
end
|
||||
|
||||
it "doesn't allow a child to be user selectable" do
|
||||
theme.add_child_theme!(child)
|
||||
child.update(user_selectable: true)
|
||||
expect(child.errors.full_messages).to contain_exactly(I18n.t("themes.errors.component_no_user_selectable"))
|
||||
end
|
||||
|
||||
it "doesn't allow a child to be set as the default theme" do
|
||||
theme.add_child_theme!(child)
|
||||
expect do
|
||||
child.set_default!
|
||||
end.to raise_error(Discourse::InvalidParameters, I18n.t("themes.errors.component_no_default"))
|
||||
end
|
||||
|
||||
it "doesn't allow a component to have color scheme" do
|
||||
scheme = ColorScheme.create!(name: "test")
|
||||
child.update(color_scheme: scheme)
|
||||
expect(child.errors.full_messages).to contain_exactly(I18n.t("themes.errors.component_no_color_scheme"))
|
||||
end
|
||||
|
||||
it 'should correct bad html in body_tag_baked and head_tag_baked' do
|
||||
theme.set_field(target: :common, name: "head_tag", value: "<b>I am bold")
|
||||
theme.save!
|
||||
|
@ -146,14 +149,49 @@ HTML
|
|||
expect(field).to match(/<b>theme2test<\/b>/)
|
||||
end
|
||||
|
||||
describe ".transform_ids" do
|
||||
it "adds the child themes of the parent" do
|
||||
child = Fabricate(:theme)
|
||||
child2 = Fabricate(:theme)
|
||||
sorted = [child.id, child2.id].sort
|
||||
describe "#switch_to_component!" do
|
||||
it "correctly converts a theme to component" do
|
||||
theme.add_child_theme!(child)
|
||||
scheme = ColorScheme.create!(name: 'test')
|
||||
theme.update!(color_scheme_id: scheme.id, user_selectable: true)
|
||||
theme.set_default!
|
||||
|
||||
theme.switch_to_component!
|
||||
theme.reload
|
||||
|
||||
expect(theme.component).to eq(true)
|
||||
expect(theme.user_selectable).to eq(false)
|
||||
expect(theme.default?).to eq(false)
|
||||
expect(theme.color_scheme_id).to eq(nil)
|
||||
expect(ChildTheme.where(parent_theme: theme).exists?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#switch_to_theme!" do
|
||||
it "correctly converts a component to theme" do
|
||||
theme.add_child_theme!(child)
|
||||
|
||||
child.switch_to_theme!
|
||||
theme.reload
|
||||
child.reload
|
||||
|
||||
expect(child.component).to eq(false)
|
||||
expect(ChildTheme.where(child_theme: child).exists?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".transform_ids" do
|
||||
let!(:child) { Fabricate(:theme, component: true) }
|
||||
let!(:child2) { Fabricate(:theme, component: true) }
|
||||
|
||||
before do
|
||||
theme.add_child_theme!(child)
|
||||
theme.add_child_theme!(child2)
|
||||
end
|
||||
|
||||
it "adds the child themes of the parent" do
|
||||
sorted = [child.id, child2.id].sort
|
||||
|
||||
expect(Theme.transform_ids([theme.id])).to eq([theme.id, *sorted])
|
||||
|
||||
fake_id = [child.id, child2.id, theme.id].min - 5
|
||||
|
@ -164,12 +202,6 @@ HTML
|
|||
end
|
||||
|
||||
it "doesn't insert children when extend is false" do
|
||||
child = Fabricate(:theme)
|
||||
child2 = Fabricate(:theme)
|
||||
|
||||
theme.add_child_theme!(child)
|
||||
theme.add_child_theme!(child2)
|
||||
|
||||
fake_id = theme.id + 1
|
||||
fake_id2 = fake_id + 2
|
||||
fake_id3 = fake_id2 + 3
|
||||
|
@ -219,6 +251,7 @@ HTML
|
|||
theme.set_field(target: :common, name: :scss, value: 'body {color: $magic; }')
|
||||
theme.set_field(target: :common, name: :magic, value: 'red', type: :theme_var)
|
||||
theme.set_field(target: :common, name: :not_red, value: 'red', type: :theme_var)
|
||||
theme.component = true
|
||||
theme.save
|
||||
|
||||
parent_theme = Fabricate(:theme)
|
||||
|
|
|
@ -169,7 +169,7 @@ describe Admin::ThemesController do
|
|||
theme.set_field(target: :common, name: :scss, value: '.body{color: black;}')
|
||||
theme.save
|
||||
|
||||
child_theme = Fabricate(:theme)
|
||||
child_theme = Fabricate(:theme, component: true)
|
||||
|
||||
upload = Fabricate(:upload)
|
||||
|
||||
|
@ -200,8 +200,7 @@ describe Admin::ThemesController do
|
|||
end
|
||||
|
||||
it 'returns the right error message' do
|
||||
parent = Fabricate(:theme)
|
||||
parent.add_child_theme!(theme)
|
||||
theme.update!(component: true)
|
||||
|
||||
put "/admin/themes/#{theme.id}.json", params: {
|
||||
theme: { default: true }
|
||||
|
|
|
@ -122,7 +122,7 @@ RSpec.describe ApplicationController do
|
|||
expect(response.status).to eq(200)
|
||||
expect(controller.theme_ids).to eq([theme2.id])
|
||||
|
||||
theme2.update!(user_selectable: false)
|
||||
theme2.update!(user_selectable: false, component: true)
|
||||
theme.add_child_theme!(theme2)
|
||||
cookies['theme_ids'] = "#{theme.id},#{theme2.id}|#{user.user_option.theme_key_seq}"
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ describe UserUpdater do
|
|||
expect(user.user_option.theme_ids).to eq([])
|
||||
|
||||
theme = Fabricate(:theme)
|
||||
child = Fabricate(:theme)
|
||||
child = Fabricate(:theme, component: true)
|
||||
theme.add_child_theme!(child)
|
||||
theme.set_default!
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ moduleFor("controller:admin-customize-themes", {
|
|||
needs: ["controller:adminUser"]
|
||||
});
|
||||
|
||||
QUnit.test("can list sorted themes", function(assert) {
|
||||
QUnit.test("can list themes correctly", function(assert) {
|
||||
const defaultTheme = Theme.create({ id: 2, default: true, name: "default" });
|
||||
const userTheme = Theme.create({
|
||||
id: 3,
|
||||
|
@ -17,16 +17,25 @@ QUnit.test("can list sorted themes", function(assert) {
|
|||
});
|
||||
const strayTheme1 = Theme.create({ id: 4, name: "stray1" });
|
||||
const strayTheme2 = Theme.create({ id: 5, name: "stray2" });
|
||||
const componentTheme = Theme.create({
|
||||
id: 6,
|
||||
name: "component",
|
||||
component: true
|
||||
});
|
||||
|
||||
const controller = this.subject({
|
||||
model: {
|
||||
content: [strayTheme2, strayTheme1, userTheme, defaultTheme]
|
||||
}
|
||||
model: [strayTheme2, strayTheme1, userTheme, defaultTheme, componentTheme]
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
controller.get("sortedThemes").map(t => t.get("name")),
|
||||
controller.get("fullThemes").map(t => t.get("name")),
|
||||
[defaultTheme, userTheme, strayTheme1, strayTheme2].map(t => t.get("name")),
|
||||
"sorts themes correctly"
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
controller.get("childThemes").map(t => t.get("name")),
|
||||
[componentTheme].map(t => t.get("name")),
|
||||
"separate components from themes"
|
||||
);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue