diff --git a/app/assets/javascripts/admin/components/customize-link.js.es6 b/app/assets/javascripts/admin/components/customize-link.js.es6 new file mode 100644 index 00000000000..79d8cc26f4a --- /dev/null +++ b/app/assets/javascripts/admin/components/customize-link.js.es6 @@ -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') +}); diff --git a/app/assets/javascripts/admin/controllers/admin-customize-colors.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-colors.js.es6 index 636905c009a..4a6a58021bb 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-colors.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-colors.js.es6 @@ -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({ - onlyOverridden: false, baseColorScheme: function() { diff --git a/app/assets/javascripts/admin/controllers/admin-customize-css-html-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-css-html-show.js.es6 new file mode 100644 index 00000000000..01dc0da3259 --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-customize-css-html-show.js.es6 @@ -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); + } + } + +}); diff --git a/app/assets/javascripts/admin/controllers/admin-customize-css-html.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-css-html.js.es6 deleted file mode 100644 index d71267589d4..00000000000 --- a/app/assets/javascripts/admin/controllers/admin-customize-css-html.js.es6 +++ /dev/null @@ -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); - } - }); - } - - } - -}); diff --git a/app/assets/javascripts/admin/models/site-customization.js.es6 b/app/assets/javascripts/admin/models/site-customization.js.es6 new file mode 100644 index 00000000000..655d15bf726 --- /dev/null +++ b/app/assets/javascripts/admin/models/site-customization.js.es6 @@ -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; diff --git a/app/assets/javascripts/admin/models/site_customization.js b/app/assets/javascripts/admin/models/site_customization.js deleted file mode 100644 index 19fcbad266e..00000000000 --- a/app/assets/javascripts/admin/models/site_customization.js +++ /dev/null @@ -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 }); - }); - } -}); diff --git a/app/assets/javascripts/admin/routes/admin-customize-css-html-show.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-css-html-show.js.es6 new file mode 100644 index 00000000000..dc38f3c51fb --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-customize-css-html-show.js.es6 @@ -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); + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-customize-css-html.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-css-html.js.es6 index c76c7e9a43c..8bbd22a4565 100644 --- a/app/assets/javascripts/admin/routes/admin-customize-css-html.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-customize-css-html.js.es6 @@ -1,5 +1,26 @@ +import showModal from 'discourse/lib/show-modal'; +import { popupAjaxError } from 'discourse/lib/ajax-error'; + export default Ember.Route.extend({ 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); + } } }); diff --git a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 index daf3b1ced6f..66d9ea4c795 100644 --- a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 @@ -16,7 +16,11 @@ export default { this.resource('adminCustomize', { path: '/customize' } ,function() { 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.route('edit', {path: '/:text_type'}); }); diff --git a/app/assets/javascripts/admin/templates/components/customize-link.hbs b/app/assets/javascripts/admin/templates/components/customize-link.hbs new file mode 100644 index 00000000000..dd3c4104c75 --- /dev/null +++ b/app/assets/javascripts/admin/templates/components/customize-link.hbs @@ -0,0 +1,5 @@ +
{{i18n 'admin.customize.about'}}
diff --git a/app/assets/javascripts/admin/templates/customize-css-html-show.hbs b/app/assets/javascripts/admin/templates/customize-css-html-show.hbs new file mode 100644 index 00000000000..4d93f5ee890 --- /dev/null +++ b/app/assets/javascripts/admin/templates/customize-css-html-show.hbs @@ -0,0 +1,74 @@ +{{i18n 'admin.customize.about'}}
-{{/if}} diff --git a/app/assets/javascripts/admin/views/admin-customize.js.es6 b/app/assets/javascripts/admin/views/admin-customize.js.es6 new file mode 100644 index 00000000000..faca47026f6 --- /dev/null +++ b/app/assets/javascripts/admin/views/admin-customize.js.es6 @@ -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") + +}); diff --git a/app/assets/javascripts/admin/views/admin_customize_view.js b/app/assets/javascripts/admin/views/admin_customize_view.js deleted file mode 100644 index 37ecfaceb1d..00000000000 --- a/app/assets/javascripts/admin/views/admin_customize_view.js +++ /dev/null @@ -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") - -}); diff --git a/app/assets/javascripts/discourse/adapters/rest.js.es6 b/app/assets/javascripts/discourse/adapters/rest.js.es6 index 5a64730aba1..5e427a86589 100644 --- a/app/assets/javascripts/discourse/adapters/rest.js.es6 +++ b/app/assets/javascripts/discourse/adapters/rest.js.es6 @@ -1,4 +1,4 @@ -const ADMIN_MODELS = ['plugin']; +const ADMIN_MODELS = ['plugin', 'site-customization']; export function Result(payload, responseJson) { this.payload = payload; diff --git a/app/assets/javascripts/discourse/controllers/upload-customization.js.es6 b/app/assets/javascripts/discourse/controllers/upload-customization.js.es6 index 09158f53752..72bf3aee6ee 100644 --- a/app/assets/javascripts/discourse/controllers/upload-customization.js.es6 +++ b/app/assets/javascripts/discourse/controllers/upload-customization.js.es6 @@ -2,20 +2,15 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality'; export default Ember.Controller.extend(ModalFunctionality, { notReady: Em.computed.not('ready'), - - needs: ['admin-customize-css-html'], - - title: "hi", + needs: ['adminCustomizeCssHtml'], ready: function() { - let parsed; try { - parsed = JSON.parse(this.get('customizationFile')); + const parsed = JSON.parse(this.get('customizationFile')); + return !!parsed["site_customization"]; } catch (e) { return false; } - - return !!parsed["site_customization"]; }.property('customizationFile'), actions: { @@ -28,24 +23,8 @@ export default Ember.Controller.extend(ModalFunctionality, { delete object.id; delete object.key; - const customization = Discourse.SiteCustomization.create(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("