mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-23 15:48:43 -05:00
Refactor Customizations to have deeper URLs
This commit is contained in:
parent
92b2d8c247
commit
0932e82508
25 changed files with 294 additions and 378 deletions
|
@ -0,0 +1,10 @@
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
router: function() {
|
||||||
|
return this.container.lookup('router:main');
|
||||||
|
}.property(),
|
||||||
|
|
||||||
|
active: function() {
|
||||||
|
const id = this.get('customization.id');
|
||||||
|
return this.get('router.url').indexOf(`/customize/css_html/${id}/css`) !== -1;
|
||||||
|
}.property('router.url', 'customization.id')
|
||||||
|
});
|
|
@ -1,13 +1,4 @@
|
||||||
/**
|
|
||||||
This controller supports interface for creating custom CSS skins in Discourse.
|
|
||||||
|
|
||||||
@class AdminCustomizeColorsController
|
|
||||||
@extends Ember.Controller
|
|
||||||
@namespace Discourse
|
|
||||||
@module Discourse
|
|
||||||
**/
|
|
||||||
export default Ember.ArrayController.extend({
|
export default Ember.ArrayController.extend({
|
||||||
|
|
||||||
onlyOverridden: false,
|
onlyOverridden: false,
|
||||||
|
|
||||||
baseColorScheme: function() {
|
baseColorScheme: function() {
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
const sections = ['css', 'header', 'top', 'footer', 'head-tag', 'body-tag',
|
||||||
|
'mobile-css', 'mobile-header', 'mobile-top', 'mobile-footer' ];
|
||||||
|
|
||||||
|
const activeSections = {};
|
||||||
|
sections.forEach(function(s) {
|
||||||
|
activeSections[Ember.String.camelize(s) + "Active"] = Ember.computed.equal('section', s);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export default Ember.Controller.extend(activeSections, {
|
||||||
|
maximized: false,
|
||||||
|
section: null,
|
||||||
|
|
||||||
|
previewUrl: Discourse.computed.url("model.key", "/?preview-style=%@"),
|
||||||
|
downloadUrl: Discourse.computed.url('model.id', '/admin/size_customizations/%@'),
|
||||||
|
|
||||||
|
mobile: function() {
|
||||||
|
return this.get('section').startsWith('mobile-');
|
||||||
|
}.property('section'),
|
||||||
|
|
||||||
|
maximizeIcon: function() {
|
||||||
|
return this.get('maximized') ? 'compress' : 'expand';
|
||||||
|
}.property('maximized'),
|
||||||
|
|
||||||
|
saveButtonText: function() {
|
||||||
|
return this.get('model.isSaving') ? I18n.t('saving') : I18n.t('admin.customize.save');
|
||||||
|
}.property('model.isSaving'),
|
||||||
|
|
||||||
|
saveDisabled: function() {
|
||||||
|
return !this.get('model.changed') || this.get('model.isSaving');
|
||||||
|
}.property('model.changed', 'model.isSaving'),
|
||||||
|
|
||||||
|
needs: ['adminCustomizeCssHtml'],
|
||||||
|
|
||||||
|
undoPreviewUrl: Discourse.computed.url('/?preview-style='),
|
||||||
|
defaultStyleUrl: Discourse.computed.url('/?preview-style=default'),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
save() {
|
||||||
|
this.get('model').saveChanges();
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
const self = this;
|
||||||
|
return bootbox.confirm(I18n.t("admin.customize.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
|
||||||
|
if (result) {
|
||||||
|
const model = self.get('model');
|
||||||
|
model.destroyRecord().then(function() {
|
||||||
|
self.get('controllers.adminCustomizeCssHtml').get('model').removeObject(model);
|
||||||
|
self.transitionToRoute('adminCustomizeCssHtml');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleMaximize: function() {
|
||||||
|
this.toggleProperty('maximized');
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleMobile: function() {
|
||||||
|
const section = this.get('section');
|
||||||
|
|
||||||
|
// Try to send to the same tab as before
|
||||||
|
let dest;
|
||||||
|
if (this.get('mobile')) {
|
||||||
|
dest = section.replace('mobile-', '');
|
||||||
|
if (sections.indexOf(dest) === -1) { dest = 'css'; }
|
||||||
|
} else {
|
||||||
|
dest = 'mobile-' + section;
|
||||||
|
if (sections.indexOf(dest) === -1) { dest = 'mobile-css'; }
|
||||||
|
}
|
||||||
|
this.replaceWith('adminCustomizeCssHtml.show', this.get('model.id'), dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -1,69 +0,0 @@
|
||||||
import showModal from 'discourse/lib/show-modal';
|
|
||||||
|
|
||||||
export default Ember.ArrayController.extend({
|
|
||||||
|
|
||||||
undoPreviewUrl: function() {
|
|
||||||
return Discourse.getURL("/?preview-style=");
|
|
||||||
}.property(),
|
|
||||||
|
|
||||||
defaultStyleUrl: function() {
|
|
||||||
return Discourse.getURL("/?preview-style=default");
|
|
||||||
}.property(),
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
|
|
||||||
/**
|
|
||||||
Create a new customization style
|
|
||||||
|
|
||||||
@method newCustomization
|
|
||||||
**/
|
|
||||||
newCustomization: function() {
|
|
||||||
var item = Discourse.SiteCustomization.create({name: I18n.t("admin.customize.new_style")});
|
|
||||||
this.pushObject(item);
|
|
||||||
this.set('selectedItem', item);
|
|
||||||
},
|
|
||||||
|
|
||||||
importModal: function() {
|
|
||||||
showModal('upload-customization');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
Select a given style
|
|
||||||
|
|
||||||
@method selectStyle
|
|
||||||
@param {Discourse.SiteCustomization} style The style we are selecting
|
|
||||||
**/
|
|
||||||
selectStyle: function(style) {
|
|
||||||
this.set('selectedItem', style);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
Save the current customization
|
|
||||||
|
|
||||||
@method save
|
|
||||||
**/
|
|
||||||
save: function() {
|
|
||||||
this.get('selectedItem').save();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
Destroy the current customization
|
|
||||||
|
|
||||||
@method destroy
|
|
||||||
**/
|
|
||||||
destroy: function() {
|
|
||||||
var _this = this;
|
|
||||||
return bootbox.confirm(I18n.t("admin.customize.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
|
|
||||||
var selected;
|
|
||||||
if (result) {
|
|
||||||
selected = _this.get('selectedItem');
|
|
||||||
selected.destroy();
|
|
||||||
_this.set('selectedItem', null);
|
|
||||||
return _this.removeObject(selected);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import RestModel from 'discourse/models/rest';
|
||||||
|
|
||||||
|
const trackedProperties = [
|
||||||
|
'enabled', 'name', 'stylesheet', 'header', 'top', 'footer', 'mobile_stylesheet',
|
||||||
|
'mobile_header', 'mobile_top', 'mobile_footer', 'head_tag', 'body_tag'
|
||||||
|
];
|
||||||
|
|
||||||
|
function changed() {
|
||||||
|
const originals = this.get('originals');
|
||||||
|
if (!originals) { return false; }
|
||||||
|
return _.some(trackedProperties, (p) => originals[p] !== this.get(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
const SiteCustomization = RestModel.extend({
|
||||||
|
description: function() {
|
||||||
|
return "" + this.name + (this.enabled ? ' (*)' : '');
|
||||||
|
}.property('selected', 'name', 'enabled'),
|
||||||
|
|
||||||
|
changed: changed.property.apply(changed, trackedProperties.concat('originals')),
|
||||||
|
|
||||||
|
startTrackingChanges: function() {
|
||||||
|
this.set('originals', this.getProperties(trackedProperties));
|
||||||
|
}.on('init'),
|
||||||
|
|
||||||
|
saveChanges() {
|
||||||
|
return this.save(this.getProperties(trackedProperties)).then(() => this.startTrackingChanges());
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export default SiteCustomization;
|
|
@ -1,116 +0,0 @@
|
||||||
/**
|
|
||||||
Our data model for interacting with site customizations.
|
|
||||||
|
|
||||||
@class SiteCustomization
|
|
||||||
@extends Discourse.Model
|
|
||||||
@namespace Discourse
|
|
||||||
@module Discourse
|
|
||||||
**/
|
|
||||||
Discourse.SiteCustomization = Discourse.Model.extend({
|
|
||||||
trackedProperties: [
|
|
||||||
'enabled', 'name',
|
|
||||||
'stylesheet', 'header', 'top', 'footer',
|
|
||||||
'mobile_stylesheet', 'mobile_header', 'mobile_top', 'mobile_footer',
|
|
||||||
'head_tag', 'body_tag'
|
|
||||||
],
|
|
||||||
|
|
||||||
description: function() {
|
|
||||||
return "" + this.name + (this.enabled ? ' (*)' : '');
|
|
||||||
}.property('selected', 'name', 'enabled'),
|
|
||||||
|
|
||||||
changed: function() {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (!this.originals) { return false; }
|
|
||||||
|
|
||||||
var changed = _.some(this.trackedProperties, function (p) {
|
|
||||||
return self.originals[p] !== self.get(p);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (changed) { this.set('savingStatus', ''); }
|
|
||||||
|
|
||||||
return changed;
|
|
||||||
}.property('enabled', 'name', 'originals',
|
|
||||||
'stylesheet', 'header', 'top', 'footer',
|
|
||||||
'mobile_stylesheet', 'mobile_header', 'mobile_top', 'mobile_footer',
|
|
||||||
'head_tag', 'body_tag'),
|
|
||||||
|
|
||||||
startTrackingChanges: function() {
|
|
||||||
var self = this;
|
|
||||||
var originals = {};
|
|
||||||
_.each(this.trackedProperties, function (prop) {
|
|
||||||
originals[prop] = self.get(prop);
|
|
||||||
});
|
|
||||||
this.set('originals', originals);
|
|
||||||
}.on('init'),
|
|
||||||
|
|
||||||
previewUrl: function() { return Discourse.getURL("/?preview-style=" + this.get('key')); }.property('key'),
|
|
||||||
disableSave: function() { return !this.get('changed') || this.get('saving'); }.property('changed'),
|
|
||||||
|
|
||||||
save: function() {
|
|
||||||
this.set('savingStatus', I18n.t('saving'));
|
|
||||||
this.set('saving',true);
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
name: this.name,
|
|
||||||
enabled: this.enabled,
|
|
||||||
stylesheet: this.stylesheet,
|
|
||||||
header: this.header,
|
|
||||||
top: this.top,
|
|
||||||
footer: this.footer,
|
|
||||||
mobile_stylesheet: this.mobile_stylesheet,
|
|
||||||
mobile_header: this.mobile_header,
|
|
||||||
mobile_top: this.mobile_top,
|
|
||||||
mobile_footer: this.mobile_footer,
|
|
||||||
head_tag: this.head_tag,
|
|
||||||
body_tag: this.body_tag
|
|
||||||
};
|
|
||||||
|
|
||||||
var siteCustomization = this;
|
|
||||||
return Discourse.ajax("/admin/site_customizations" + (this.id ? '/' + this.id : ''), {
|
|
||||||
data: { site_customization: data },
|
|
||||||
type: this.id ? 'PUT' : 'POST'
|
|
||||||
}).then(function (result) {
|
|
||||||
if (!siteCustomization.id) {
|
|
||||||
siteCustomization.set('id', result.id);
|
|
||||||
siteCustomization.set('key', result.key);
|
|
||||||
}
|
|
||||||
siteCustomization.set('savingStatus', I18n.t('saved'));
|
|
||||||
siteCustomization.set('saving',false);
|
|
||||||
siteCustomization.startTrackingChanges();
|
|
||||||
return siteCustomization;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
destroy: function() {
|
|
||||||
if (!this.id) return;
|
|
||||||
return Discourse.ajax("/admin/site_customizations/" + this.id, { type: 'DELETE' });
|
|
||||||
},
|
|
||||||
|
|
||||||
download_url: function() {
|
|
||||||
return Discourse.getURL('/admin/site_customizations/' + this.id);
|
|
||||||
}.property('id')
|
|
||||||
});
|
|
||||||
|
|
||||||
var SiteCustomizations = Ember.ArrayProxy.extend({
|
|
||||||
selectedItemChanged: function() {
|
|
||||||
var selected = this.get('selectedItem');
|
|
||||||
_.each(this.get('content'), function (i) {
|
|
||||||
i.set('selected', selected === i);
|
|
||||||
});
|
|
||||||
}.observes('selectedItem')
|
|
||||||
});
|
|
||||||
|
|
||||||
Discourse.SiteCustomization.reopenClass({
|
|
||||||
findAll: function() {
|
|
||||||
return Discourse.ajax("/admin/site_customizations").then(function (data) {
|
|
||||||
var content = [];
|
|
||||||
if (data) {
|
|
||||||
content = data.site_customizations.map(function(c) {
|
|
||||||
return Discourse.SiteCustomization.create(c);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return SiteCustomizations.create({ content: content });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
export default Ember.Route.extend({
|
||||||
|
model(params) {
|
||||||
|
const all = this.modelFor('adminCustomizeCssHtml');
|
||||||
|
const model = all.findProperty('id', parseInt(params.site_customization_id));
|
||||||
|
return model ? { model, section: params.section } : this.replaceWith('adminCustomizeCssHtml.index');
|
||||||
|
},
|
||||||
|
|
||||||
|
setupController(controller, hash) {
|
||||||
|
controller.setProperties(hash);
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,5 +1,26 @@
|
||||||
|
import showModal from 'discourse/lib/show-modal';
|
||||||
|
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||||
|
|
||||||
export default Ember.Route.extend({
|
export default Ember.Route.extend({
|
||||||
model() {
|
model() {
|
||||||
return Discourse.SiteCustomization.findAll();
|
return this.store.findAll('site-customization');
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
importModal() {
|
||||||
|
showModal('upload-customization');
|
||||||
|
},
|
||||||
|
|
||||||
|
newCustomization(obj) {
|
||||||
|
obj = obj || {name: I18n.t("admin.customize.new_style")};
|
||||||
|
const item = this.store.createRecord('site-customization', obj);
|
||||||
|
|
||||||
|
const all = this.modelFor('adminCustomizeCssHtml');
|
||||||
|
const self = this;
|
||||||
|
item.save().then(function() {
|
||||||
|
all.pushObject(item);
|
||||||
|
self.transitionTo('adminCustomizeCssHtml.show', item.get('id'), 'css');
|
||||||
|
}).catch(popupAjaxError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,7 +16,11 @@ export default {
|
||||||
|
|
||||||
this.resource('adminCustomize', { path: '/customize' } ,function() {
|
this.resource('adminCustomize', { path: '/customize' } ,function() {
|
||||||
this.route('colors');
|
this.route('colors');
|
||||||
this.route('css_html');
|
|
||||||
|
this.resource('adminCustomizeCssHtml', { path: 'css_html' }, function() {
|
||||||
|
this.route('show', {path: '/:site_customization_id/:section'});
|
||||||
|
});
|
||||||
|
|
||||||
this.resource('adminSiteText', { path: '/site_text' }, function() {
|
this.resource('adminSiteText', { path: '/site_text' }, function() {
|
||||||
this.route('edit', {path: '/:text_type'});
|
this.route('edit', {path: '/:text_type'});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<li>
|
||||||
|
<a href="/admin/customize/css_html/{{customization.id}}/css" class="{{if active 'active'}}">
|
||||||
|
{{customization.description}}
|
||||||
|
</a>
|
||||||
|
</li>
|
|
@ -0,0 +1 @@
|
||||||
|
<p class="about">{{i18n 'admin.customize.about'}}</p>
|
|
@ -0,0 +1,74 @@
|
||||||
|
<div class="current-style {{if maximized 'maximized'}}">
|
||||||
|
<div class='wrapper'>
|
||||||
|
{{text-field class="style-name" value=model.name}}
|
||||||
|
<a class="btn export" download target="_blank" href={{downloadUrl}}>{{fa-icon "download"}} {{i18n 'admin.export_json.button_text'}}</a>
|
||||||
|
|
||||||
|
<div class='admin-controls'>
|
||||||
|
<ul class="nav nav-pills">
|
||||||
|
{{#if mobile}}
|
||||||
|
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'mobile-css' replace=true}}{{i18n "admin.customize.css"}}{{/link-to}}</li>
|
||||||
|
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'mobile-header' replace=true}}{{i18n "admin.customize.header"}}{{/link-to}}</li>
|
||||||
|
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'mobile-top' replace=true}}{{i18n "admin.customize.top"}}{{/link-to}}</li>
|
||||||
|
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'mobile-footer' replace=true}}{{i18n "admin.customize.footer"}}{{/link-to}}</li>
|
||||||
|
{{else}}
|
||||||
|
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'css' replace=true}}{{i18n "admin.customize.css"}}{{/link-to}}</li>
|
||||||
|
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'header' replace=true}}{{i18n "admin.customize.header"}}{{/link-to}}</li>
|
||||||
|
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'top' replace=true}}{{i18n "admin.customize.top"}}{{/link-to}}</li>
|
||||||
|
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'footer' replace=true}}{{i18n "admin.customize.footer"}}{{/link-to}}</li>
|
||||||
|
<li>
|
||||||
|
{{#link-to 'adminCustomizeCssHtml.show' model.id 'head-tag'}}
|
||||||
|
{{fa-icon "file-text-o"}} {{i18n 'admin.customize.head_tag.text'}}
|
||||||
|
{{/link-to}}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{{#link-to 'adminCustomizeCssHtml.show' model.id 'body-tag'}}
|
||||||
|
{{fa-icon "file-text-o"}} {{i18n 'admin.customize.body_tag.text'}}
|
||||||
|
{{/link-to}}
|
||||||
|
</li>
|
||||||
|
{{/if}}
|
||||||
|
<li class='toggle-mobile'>
|
||||||
|
<a {{bind-attr class="mobile:active"}} {{action "toggleMobile"}}>{{fa-icon "mobile"}}</a>
|
||||||
|
</li>
|
||||||
|
<li class='toggle-maximize'>
|
||||||
|
<a {{action "toggleMaximize"}}>
|
||||||
|
{{fa-icon-bound maximizeIcon}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="admin-container">
|
||||||
|
{{#if cssActive}}{{ace-editor content=model.stylesheet mode="scss"}}{{/if}}
|
||||||
|
{{#if headerActive}}{{ace-editor content=model.header mode="html"}}{{/if}}
|
||||||
|
{{#if topActive}}{{ace-editor content=model.top mode="html"}}{{/if}}
|
||||||
|
{{#if footerActive}}{{ace-editor content=model.footer mode="html"}}{{/if}}
|
||||||
|
{{#if headTagActive}}{{ace-editor content=model.head_tag mode="html"}}{{/if}}
|
||||||
|
{{#if bodyTagActive}}{{ace-editor content=model.body_tag mode="html"}}{{/if}}
|
||||||
|
{{#if mobileCssActive}}{{ace-editor content=model.mobile_stylesheet mode="scss"}}{{/if}}
|
||||||
|
{{#if mobileHeaderActive}}{{ace-editor content=model.mobile_header mode="html"}}{{/if}}
|
||||||
|
{{#if mobileTopActive}}{{ace-editor content=model.mobile_top mode="html"}}{{/if}}
|
||||||
|
{{#if mobileFooterActive}}{{ace-editor content=model.mobile_footer mode="html"}}{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='admin-footer'>
|
||||||
|
<div class='status-actions'>
|
||||||
|
<span>{{i18n 'admin.customize.enabled'}} {{input type="checkbox" checked=model.enabled}}</span>
|
||||||
|
{{#unless model.changed}}
|
||||||
|
<a class='preview-link' href={{previewUrl}} target='_blank' title="{{i18n 'admin.customize.explain_preview'}}">{{i18n 'admin.customize.preview'}}</a>
|
||||||
|
|
|
||||||
|
<a href={{undoPreviewUrl}} target='_blank' title="{{i18n 'admin.customize.explain_undo_preview'}}">{{i18n 'admin.customize.undo_preview'}}</a>
|
||||||
|
|
|
||||||
|
<a href={{defaultStyleUrl}} target='_blank' title="{{i18n 'admin.customize.explain_rescue_preview'}}">{{i18n 'admin.customize.rescue_preview'}}</a><br>
|
||||||
|
{{/unless}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='buttons'>
|
||||||
|
{{#d-button action="save" disabled=saveDisabled class='btn-primary'}}
|
||||||
|
{{saveButtonText}}
|
||||||
|
{{/d-button}}
|
||||||
|
{{d-button action="destroy" label="admin.customize.delete" icon="trash" class="btn-danger"}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<div class='content-list span6'>
|
||||||
|
<h3>{{i18n 'admin.customize.css_html.long_title'}}</h3>
|
||||||
|
<ul>
|
||||||
|
{{#each model as |c|}}
|
||||||
|
{{customize-link customization=c}}
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{{d-button label="admin.customize.new" icon="plus" action="newCustomization" class="btn-primary"}}
|
||||||
|
{{d-button action="importModal" icon="upload" label="admin.customize.import"}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{outlet}}
|
|
@ -1,6 +1,6 @@
|
||||||
{{#admin-nav}}
|
{{#admin-nav}}
|
||||||
{{nav-item route='adminCustomize.colors' label='admin.customize.colors.title'}}
|
{{nav-item route='adminCustomize.colors' label='admin.customize.colors.title'}}
|
||||||
{{nav-item route='adminCustomize.css_html' label='admin.customize.css_html.title'}}
|
{{nav-item route='adminCustomizeCssHtml.index' label='admin.customize.css_html.title'}}
|
||||||
{{nav-item route='adminSiteText' label='admin.site_text.title'}}
|
{{nav-item route='adminSiteText' label='admin.site_text.title'}}
|
||||||
{{nav-item route='adminUserFields' label='admin.user_fields.title'}}
|
{{nav-item route='adminUserFields' label='admin.user_fields.title'}}
|
||||||
{{nav-item route='adminEmojis' label='admin.emoji.title'}}
|
{{nav-item route='adminEmojis' label='admin.emoji.title'}}
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
<div class='content-list span6'>
|
|
||||||
<h3>{{i18n 'admin.customize.css_html.long_title'}}</h3>
|
|
||||||
<ul>
|
|
||||||
{{#each style in model}}
|
|
||||||
<li><a {{action "selectStyle" style}} {{bind-attr class="style.selected:active"}}>{{style.description}}</a></li>
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
<button {{action "newCustomization"}} class='btn'>
|
|
||||||
{{fa-icon "plus"}}{{i18n 'admin.customize.new'}}
|
|
||||||
</button>
|
|
||||||
{{d-button action="importModal" icon="upload" label="admin.customize.import"}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if selectedItem}}
|
|
||||||
<div {{bind-attr class=":current-style view.maximized:maximized"}}>
|
|
||||||
<div class='wrapper'>
|
|
||||||
{{text-field class="style-name" value=selectedItem.name}}
|
|
||||||
<a class="btn export" download target="_blank" href={{selectedItem.download_url}}>{{fa-icon "download"}} {{i18n 'admin.export_json.button_text'}}</a>
|
|
||||||
|
|
||||||
<div class='admin-controls'>
|
|
||||||
<ul class="nav nav-pills">
|
|
||||||
{{#if view.mobile}}
|
|
||||||
<li><a {{bind-attr class="view.mobileStylesheetActive:active"}} {{action "select" "mobile_stylesheet" target="view"}}>{{fa-icon "mobile"}} {{i18n 'admin.customize.css'}}</a></li>
|
|
||||||
<li><a {{bind-attr class="view.mobileHeaderActive:active"}} {{action "select" "mobile_header" target="view"}}>{{fa-icon "mobile"}} {{i18n 'admin.customize.header'}}</a></li>
|
|
||||||
<li><a {{bind-attr class="view.mobileTopActive:active"}} {{action "select" "mobile_top" target="view"}}>{{fa-icon "mobile"}} {{i18n 'admin.customize.top'}}</a></li>
|
|
||||||
<li><a {{bind-attr class="view.mobileFooterActive:active"}} {{action "select" "mobile_footer" target="view"}}>{{fa-icon "mobile"}} {{i18n 'admin.customize.footer'}}</a></li>
|
|
||||||
{{else}}
|
|
||||||
<li><a {{bind-attr class="view.stylesheetActive:active"}} {{action "select" "stylesheet" target="view"}}>{{i18n 'admin.customize.css'}}</a></li>
|
|
||||||
<li><a {{bind-attr class="view.headerActive:active"}} {{action "select" "header" target="view"}}>{{i18n 'admin.customize.header'}}</a></li>
|
|
||||||
<li><a {{bind-attr class="view.topActive:active"}} {{action "select" "top" target="view"}}>{{i18n 'admin.customize.top'}}</a></li>
|
|
||||||
<li><a {{bind-attr class="view.footerActive:active"}} {{action "select" "footer" target="view"}}>{{i18n 'admin.customize.footer'}}</a></li>
|
|
||||||
<li><a {{bind-attr class="view.headTagActive:active"}} {{action "select" "head_tag" target="view"}} title="{{i18n 'admin.customize.head_tag.title'}}">{{fa-icon "file-text-o"}} {{i18n 'admin.customize.head_tag.text'}}</a></li>
|
|
||||||
<li><a {{bind-attr class="view.bodyTagActive:active"}} {{action "select" "body_tag" target="view"}} title="{{i18n 'admin.customize.body_tag.title'}}">{{fa-icon "file-text-o"}} {{i18n 'admin.customize.body_tag.text'}}</a></li>
|
|
||||||
{{/if}}
|
|
||||||
<li class='toggle-mobile'>
|
|
||||||
<a {{bind-attr class="view.mobile:active"}} {{action "toggleMobile" target="view"}}>{{fa-icon "mobile"}}</a>
|
|
||||||
</li>
|
|
||||||
<li class='toggle-maximize'>
|
|
||||||
<a {{action "toggleMaximize" target="view"}}>
|
|
||||||
{{#if view.maximized}}
|
|
||||||
{{fa-icon "compress"}}
|
|
||||||
{{else}}
|
|
||||||
{{fa-icon "expand"}}
|
|
||||||
{{/if}}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="admin-container">
|
|
||||||
{{#if view.stylesheetActive}}{{ace-editor content=selectedItem.stylesheet mode="scss"}}{{/if}}
|
|
||||||
{{#if view.headerActive}}{{ace-editor content=selectedItem.header mode="html"}}{{/if}}
|
|
||||||
{{#if view.topActive}}{{ace-editor content=selectedItem.top mode="html"}}{{/if}}
|
|
||||||
{{#if view.footerActive}}{{ace-editor content=selectedItem.footer mode="html"}}{{/if}}
|
|
||||||
{{#if view.headTagActive}}{{ace-editor content=selectedItem.head_tag mode="html"}}{{/if}}
|
|
||||||
{{#if view.bodyTagActive}}{{ace-editor content=selectedItem.body_tag mode="html"}}{{/if}}
|
|
||||||
{{#if view.mobileStylesheetActive}}{{ace-editor content=selectedItem.mobile_stylesheet mode="scss"}}{{/if}}
|
|
||||||
{{#if view.mobileHeaderActive}}{{ace-editor content=selectedItem.mobile_header mode="html"}}{{/if}}
|
|
||||||
{{#if view.mobileTopActive}}{{ace-editor content=selectedItem.mobile_top mode="html"}}{{/if}}
|
|
||||||
{{#if view.mobileFooterActive}}{{ace-editor content=selectedItem.mobile_footer mode="html"}}{{/if}}
|
|
||||||
</div>
|
|
||||||
<div class='admin-footer'>
|
|
||||||
<div class='status-actions'>
|
|
||||||
<span>{{i18n 'admin.customize.enabled'}} {{input type="checkbox" checked=selectedItem.enabled}}</span>
|
|
||||||
{{#unless selectedItem.changed}}
|
|
||||||
<a class='preview-link' {{bind-attr href="selectedItem.previewUrl"}} target='_blank' title="{{i18n 'admin.customize.explain_preview'}}">{{i18n 'admin.customize.preview'}}</a>
|
|
||||||
|
|
|
||||||
<a href="{{undoPreviewUrl}}" target='_blank' title="{{i18n 'admin.customize.explain_undo_preview'}}">{{i18n 'admin.customize.undo_preview'}}</a>
|
|
||||||
|
|
|
||||||
<a href="{{defaultStyleUrl}}" target='_blank' title="{{i18n 'admin.customize.explain_rescue_preview'}}">{{i18n 'admin.customize.rescue_preview'}}</a><br>
|
|
||||||
{{/unless}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='buttons'>
|
|
||||||
<button {{action "save"}} {{bind-attr disabled="selectedItem.disableSave"}} class='btn'>{{i18n 'admin.customize.save'}}</button>
|
|
||||||
<span class='saving'>{{selectedItem.savingStatus}}</span>
|
|
||||||
<a {{action "destroy"}} class='delete-link'>{{i18n 'admin.customize.delete'}}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<p class="about">{{i18n 'admin.customize.about'}}</p>
|
|
||||||
{{/if}}
|
|
18
app/assets/javascripts/admin/views/admin-customize.js.es6
Normal file
18
app/assets/javascripts/admin/views/admin-customize.js.es6
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/*global Mousetrap:true */
|
||||||
|
|
||||||
|
export default Ember.View.extend({
|
||||||
|
classNames: ['customize'],
|
||||||
|
|
||||||
|
_init: function() {
|
||||||
|
var controller = this.get('controller');
|
||||||
|
Mousetrap.bindGlobal('mod+s', function() {
|
||||||
|
controller.send("save");
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}.on("didInsertElement"),
|
||||||
|
|
||||||
|
_cleanUp: function() {
|
||||||
|
Mousetrap.unbindGlobal('mod+s');
|
||||||
|
}.on("willDestroyElement")
|
||||||
|
|
||||||
|
});
|
|
@ -1,68 +0,0 @@
|
||||||
/*global Mousetrap:true */
|
|
||||||
|
|
||||||
/**
|
|
||||||
A view to handle site customizations
|
|
||||||
|
|
||||||
@class AdminCustomizeView
|
|
||||||
@extends Discourse.View
|
|
||||||
@namespace Discourse
|
|
||||||
@module Discourse
|
|
||||||
**/
|
|
||||||
Discourse.AdminCustomizeView = Discourse.View.extend({
|
|
||||||
templateName: 'admin/templates/customize',
|
|
||||||
classNames: ['customize'],
|
|
||||||
selected: 'stylesheet',
|
|
||||||
mobile: false,
|
|
||||||
|
|
||||||
stylesheetActive: Em.computed.equal('selected', 'stylesheet'),
|
|
||||||
headerActive: Em.computed.equal('selected', 'header'),
|
|
||||||
topActive: Em.computed.equal('selected', 'top'),
|
|
||||||
footerActive: Em.computed.equal('selected', 'footer'),
|
|
||||||
headTagActive: Em.computed.equal('selected', 'head_tag'),
|
|
||||||
bodyTagActive: Em.computed.equal('selected', 'body_tag'),
|
|
||||||
|
|
||||||
mobileStylesheetActive: Em.computed.equal('selected', 'mobile_stylesheet'),
|
|
||||||
mobileHeaderActive: Em.computed.equal('selected', 'mobile_header'),
|
|
||||||
mobileTopActive: Em.computed.equal('selected', 'mobile_top'),
|
|
||||||
mobileFooterActive: Em.computed.equal('selected', 'mobile_footer'),
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
toggleMobile: function() {
|
|
||||||
// auto-select best tab
|
|
||||||
var tab = this.get("selected");
|
|
||||||
if (/_tag$/.test(tab)) { tab = "stylesheet"; }
|
|
||||||
if (this.get("mobile")) { tab = tab.replace("mobile_", ""); }
|
|
||||||
else { tab = "mobile_" + tab; }
|
|
||||||
this.set("selected", tab);
|
|
||||||
// toggle mobile
|
|
||||||
this.toggleProperty("mobile");
|
|
||||||
},
|
|
||||||
|
|
||||||
select: function(tab) {
|
|
||||||
this.set('selected', tab);
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleMaximize: function() {
|
|
||||||
this.set("maximized", !this.get("maximized"));
|
|
||||||
|
|
||||||
Em.run.scheduleOnce('afterRender', this, function(){
|
|
||||||
$('.ace-wrapper').each(function(){
|
|
||||||
$(this).data("editor").resize();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
_init: function() {
|
|
||||||
var controller = this.get('controller');
|
|
||||||
Mousetrap.bindGlobal('mod+s', function() {
|
|
||||||
controller.send("save");
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}.on("didInsertElement"),
|
|
||||||
|
|
||||||
_cleanUp: function() {
|
|
||||||
Mousetrap.unbindGlobal('mod+s');
|
|
||||||
}.on("willDestroyElement")
|
|
||||||
|
|
||||||
});
|
|
|
@ -1,4 +1,4 @@
|
||||||
const ADMIN_MODELS = ['plugin'];
|
const ADMIN_MODELS = ['plugin', 'site-customization'];
|
||||||
|
|
||||||
export function Result(payload, responseJson) {
|
export function Result(payload, responseJson) {
|
||||||
this.payload = payload;
|
this.payload = payload;
|
||||||
|
|
|
@ -2,20 +2,15 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||||
|
|
||||||
export default Ember.Controller.extend(ModalFunctionality, {
|
export default Ember.Controller.extend(ModalFunctionality, {
|
||||||
notReady: Em.computed.not('ready'),
|
notReady: Em.computed.not('ready'),
|
||||||
|
needs: ['adminCustomizeCssHtml'],
|
||||||
needs: ['admin-customize-css-html'],
|
|
||||||
|
|
||||||
title: "hi",
|
|
||||||
|
|
||||||
ready: function() {
|
ready: function() {
|
||||||
let parsed;
|
|
||||||
try {
|
try {
|
||||||
parsed = JSON.parse(this.get('customizationFile'));
|
const parsed = JSON.parse(this.get('customizationFile'));
|
||||||
|
return !!parsed["site_customization"];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!parsed["site_customization"];
|
|
||||||
}.property('customizationFile'),
|
}.property('customizationFile'),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -28,24 +23,8 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||||
delete object.id;
|
delete object.id;
|
||||||
delete object.key;
|
delete object.key;
|
||||||
|
|
||||||
const customization = Discourse.SiteCustomization.create(object);
|
const controller = this.get('controllers.adminCustomizeCssHtml');
|
||||||
|
controller.send('newCustomization', object);
|
||||||
this.set('loading', true);
|
|
||||||
customization.save().then(function(customization) {
|
|
||||||
self.send('closeModal');
|
|
||||||
self.set('loading', false);
|
|
||||||
|
|
||||||
const parentController = self.get('controllers.admin-customize-css-html');
|
|
||||||
parentController.pushObject(customization);
|
|
||||||
parentController.set('selectedItem', customization);
|
|
||||||
}).catch(function(xhr) {
|
|
||||||
self.set('loading', false);
|
|
||||||
if (xhr.responseJSON) {
|
|
||||||
bootbox.alert(xhr.responseJSON.errors.join("<br>"));
|
|
||||||
} else {
|
|
||||||
bootbox.alert(I18n.t('generic_error'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,9 @@ function iconHTML(icon, params) {
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ember.Handlebars.helper('fa-icon-bound', function(value, options) {
|
||||||
|
return new Handlebars.SafeString(iconHTML(value, options));
|
||||||
|
});
|
||||||
|
|
||||||
registerUnbound('fa-icon', function(icon, params) {
|
registerUnbound('fa-icon', function(icon, params) {
|
||||||
return new Handlebars.SafeString(iconHTML(icon, params));
|
return new Handlebars.SafeString(iconHTML(icon, params));
|
||||||
|
|
|
@ -116,6 +116,12 @@ export default Ember.Object.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
destroyRecord(type, record) {
|
destroyRecord(type, record) {
|
||||||
|
// If the record is new, don't perform an Ajax call
|
||||||
|
if (record.get('isNew')) {
|
||||||
|
removeMap(type, record.get('id'));
|
||||||
|
return Ember.RSVP.Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
return this.adapterFor(type).destroyRecord(this, type, record).then(function(result) {
|
return this.adapterFor(type).destroyRecord(this, type, record).then(function(result) {
|
||||||
removeMap(type, record.get('id'));
|
removeMap(type, record.get('id'));
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<form {{action "dummy" on="submit"}}>
|
<form>
|
||||||
<div class='modal-body'>
|
<div class='modal-body'>
|
||||||
{{json-file-uploader value=customizationFile extension=".dcstyle.json"}}
|
{{json-file-uploader value=customizationFile extension=".dcstyle.json"}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -32,7 +32,7 @@ class Admin::SiteCustomizationsController < Admin::AdminController
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
if @site_customization.update_attributes(site_customization_params)
|
if @site_customization.update_attributes(site_customization_params)
|
||||||
format.json { head :no_content }
|
format.json { render json: @site_customization, status: :created}
|
||||||
else
|
else
|
||||||
log_record.destroy if log_record
|
log_record.destroy if log_record
|
||||||
format.json { render json: @site_customization.errors, status: :unprocessable_entity }
|
format.json { render json: @site_customization.errors, status: :unprocessable_entity }
|
||||||
|
|
|
@ -132,6 +132,7 @@ Discourse::Application.routes.draw do
|
||||||
|
|
||||||
get "customize" => "color_schemes#index", constraints: AdminConstraint.new
|
get "customize" => "color_schemes#index", constraints: AdminConstraint.new
|
||||||
get "customize/css_html" => "site_customizations#index", constraints: AdminConstraint.new
|
get "customize/css_html" => "site_customizations#index", constraints: AdminConstraint.new
|
||||||
|
get "customize/css_html/:id/:section" => "site_customizations#index", constraints: AdminConstraint.new
|
||||||
get "customize/colors" => "color_schemes#index", constraints: AdminConstraint.new
|
get "customize/colors" => "color_schemes#index", constraints: AdminConstraint.new
|
||||||
get "customize/permalinks" => "permalinks#index", constraints: AdminConstraint.new
|
get "customize/permalinks" => "permalinks#index", constraints: AdminConstraint.new
|
||||||
get "flags" => "flags#index"
|
get "flags" => "flags#index"
|
||||||
|
|
|
@ -97,6 +97,15 @@ test('destroyRecord', function(assert) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('destroyRecord when new', function(assert) {
|
||||||
|
const store = createStore();
|
||||||
|
const w = store.createRecord('widget', {name: 'hello'});
|
||||||
|
store.destroyRecord('widget', w).then(function(result) {
|
||||||
|
assert.ok(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
test('find embedded', function() {
|
test('find embedded', function() {
|
||||||
const store = createStore();
|
const store = createStore();
|
||||||
return store.find('fruit', 1).then(function(f) {
|
return store.find('fruit', 1).then(function(f) {
|
||||||
|
|
Loading…
Reference in a new issue