FEATURE: Import customizations from a JSON file
This commit is contained in:
parent
1e53c179a3
commit
291d9fc65e
|
@ -1,3 +1,5 @@
|
||||||
|
import showModal from 'discourse/lib/show-modal';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This controller supports interface for creating custom CSS skins in Discourse.
|
This controller supports interface for creating custom CSS skins in Discourse.
|
||||||
|
|
||||||
|
@ -21,6 +23,10 @@ export default Ember.ArrayController.extend({
|
||||||
this.set('selectedItem', item);
|
this.set('selectedItem', item);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
importModal: function() {
|
||||||
|
showModal('upload-customization');
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Select a given style
|
Select a given style
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,7 @@ Discourse.SiteCustomization = Discourse.Model.extend({
|
||||||
siteCustomization.set('savingStatus', I18n.t('saved'));
|
siteCustomization.set('savingStatus', I18n.t('saved'));
|
||||||
siteCustomization.set('saving',false);
|
siteCustomization.set('saving',false);
|
||||||
siteCustomization.startTrackingChanges();
|
siteCustomization.startTrackingChanges();
|
||||||
|
return siteCustomization;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
<button {{action "newCustomization"}} class='btn'>
|
<button {{action "newCustomization"}} class='btn'>
|
||||||
{{fa-icon "plus"}}{{i18n 'admin.customize.new'}}
|
{{fa-icon "plus"}}{{i18n 'admin.customize.new'}}
|
||||||
</button>
|
</button>
|
||||||
|
{{d-button action="importModal" icon="upload" label="admin.customize.import"}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if selectedItem}}
|
{{#if selectedItem}}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
|
||||||
|
export default Em.Component.extend({
|
||||||
|
fileInput: null,
|
||||||
|
loading: false,
|
||||||
|
expectedRootObjectName: null,
|
||||||
|
|
||||||
|
classNames: ['json-uploader'],
|
||||||
|
|
||||||
|
_initialize: function() {
|
||||||
|
const $this = this.$();
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
const $fileInput = $this.find('#js-file-input');
|
||||||
|
this.set('fileInput', $fileInput[0]);
|
||||||
|
|
||||||
|
$fileInput.on('change', function() {
|
||||||
|
self.fileSelected(this.files);
|
||||||
|
});
|
||||||
|
|
||||||
|
const $fileSelect = $this.find('.fileSelect');
|
||||||
|
|
||||||
|
$fileSelect.on('dragover dragenter', function(e) {
|
||||||
|
if (e.preventDefault) e.preventDefault();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
$fileSelect.on('drop', function(e) {
|
||||||
|
if (e.preventDefault) e.preventDefault();
|
||||||
|
|
||||||
|
self.fileSelected(e.dataTransfer.files);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
}.on('didInsertElement'),
|
||||||
|
|
||||||
|
setReady: function() {
|
||||||
|
let parsed;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(this.get('value'));
|
||||||
|
} catch (e) {
|
||||||
|
this.set('ready', false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootObject = parsed[this.get('expectedRootObjectName')];
|
||||||
|
|
||||||
|
if (rootObject !== null && rootObject !== undefined) {
|
||||||
|
this.set('ready', true);
|
||||||
|
} else {
|
||||||
|
this.set('ready', false);
|
||||||
|
}
|
||||||
|
}.observes('destination', 'expectedRootObjectName'),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
selectFile: function() {
|
||||||
|
const $fileInput = $(this.get('fileInput'));
|
||||||
|
$fileInput.click();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
fileSelected(fileList) {
|
||||||
|
const self = this;
|
||||||
|
const numFiles = fileList.length;
|
||||||
|
const firstFile = fileList[0];
|
||||||
|
|
||||||
|
this.set('loading', true);
|
||||||
|
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.onload = function(evt) {
|
||||||
|
self.set('value', evt.target.result);
|
||||||
|
self.set('loading', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(firstFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,52 @@
|
||||||
|
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",
|
||||||
|
|
||||||
|
ready: function() {
|
||||||
|
let parsed;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(this.get('customizationFile'));
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!parsed["site_customization"];
|
||||||
|
}.property('customizationFile'),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
createCustomization: function() {
|
||||||
|
const self = this;
|
||||||
|
const object = JSON.parse(this.get('customizationFile')).site_customization;
|
||||||
|
|
||||||
|
// Slight fixup before creating object
|
||||||
|
object.enabled = false;
|
||||||
|
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("<br>"));
|
||||||
|
} else {
|
||||||
|
bootbox.alert(I18n.t('generic_error'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,10 @@
|
||||||
|
<div class="jsfu-file">
|
||||||
|
<input id="js-file-input" type="file" style="display:none;" accept=".json,application/json">
|
||||||
|
{{d-button class="fileSelect" action="selectFile" class="" icon="upload" label="upload_selector.select_file"}}
|
||||||
|
{{conditional-loading-spinner condition=loading size="small"}}
|
||||||
|
</div>
|
||||||
|
<div class="jsfu-separator">{{i18n "alternation"}}</div>
|
||||||
|
<div class="jsfu-paste">
|
||||||
|
{{textarea value=value}}
|
||||||
|
</div>
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<form {{action "dummy" on="submit"}}>
|
||||||
|
<div class='modal-body'>
|
||||||
|
{{json-file-uploader value=customizationFile}}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
{{d-button class='btn-primary' action='createCustomization' type='submit' disabled=notReady icon="plus" label='admin.customize.import'}}
|
||||||
|
</div>
|
||||||
|
</form>
|
|
@ -0,0 +1,6 @@
|
||||||
|
import ModalBodyView from "discourse/views/modal-body";
|
||||||
|
|
||||||
|
export default ModalBodyView.extend({
|
||||||
|
templateName: 'modal/upload-customization',
|
||||||
|
title: I18n.t('admin.customize.import_title')
|
||||||
|
});
|
|
@ -138,6 +138,29 @@
|
||||||
.raw-email-textarea {
|
.raw-email-textarea {
|
||||||
height: 300px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
|
.json-uploader {
|
||||||
|
display: table-row;
|
||||||
|
.jsfu-file {
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
.jsfu-separator {
|
||||||
|
vertical-align: middle;
|
||||||
|
display: table-cell;
|
||||||
|
font-size: 36px;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
.jsfu-paste {
|
||||||
|
display: table-cell;
|
||||||
|
width: 100%;
|
||||||
|
textarea {
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.password-confirmation {
|
.password-confirmation {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
class SiteCustomizationSerializer < ApplicationSerializer
|
class SiteCustomizationSerializer < ApplicationSerializer
|
||||||
|
|
||||||
attributes :name, :enabled, :created_at, :updated_at,
|
attributes :id, :name, :key, :enabled, :created_at, :updated_at,
|
||||||
:stylesheet, :header, :footer, :top,
|
:stylesheet, :header, :footer, :top,
|
||||||
:mobile_stylesheet, :mobile_header, :mobile_footer, :mobile_top,
|
:mobile_stylesheet, :mobile_header, :mobile_footer, :mobile_top,
|
||||||
:head_tag, :body_tag
|
:head_tag, :body_tag
|
||||||
|
|
|
@ -143,6 +143,7 @@ en:
|
||||||
every_two_weeks: "every two weeks"
|
every_two_weeks: "every two weeks"
|
||||||
every_three_days: "every three days"
|
every_three_days: "every three days"
|
||||||
max_of_count: "max of {{count}}"
|
max_of_count: "max of {{count}}"
|
||||||
|
alternation: "or"
|
||||||
character_count:
|
character_count:
|
||||||
one: "{{count}} character"
|
one: "{{count}} character"
|
||||||
other: "{{count}} characters"
|
other: "{{count}} characters"
|
||||||
|
@ -849,6 +850,7 @@ en:
|
||||||
hint: "(you can also drag & drop into the editor to upload them)"
|
hint: "(you can also drag & drop into the editor to upload them)"
|
||||||
hint_for_supported_browsers: "(you can also drag and drop or paste images into the editor to upload them)"
|
hint_for_supported_browsers: "(you can also drag and drop or paste images into the editor to upload them)"
|
||||||
uploading: "Uploading"
|
uploading: "Uploading"
|
||||||
|
select_file: "Select File"
|
||||||
image_link: "link your image will point to"
|
image_link: "link your image will point to"
|
||||||
|
|
||||||
search:
|
search:
|
||||||
|
@ -1894,6 +1896,8 @@ en:
|
||||||
save: "Save"
|
save: "Save"
|
||||||
new: "New"
|
new: "New"
|
||||||
new_style: "New Style"
|
new_style: "New Style"
|
||||||
|
import: "Import"
|
||||||
|
import_title: "Select a file or paste text"
|
||||||
delete: "Delete"
|
delete: "Delete"
|
||||||
delete_confirm: "Delete this customization?"
|
delete_confirm: "Delete this customization?"
|
||||||
about: "Modify CSS stylesheets and HTML headers on the site. Add a customization to start."
|
about: "Modify CSS stylesheets and HTML headers on the site. Add a customization to start."
|
||||||
|
|
Loading…
Reference in New Issue