diff --git a/frontend/src/core/collections/contentCollection.js b/frontend/src/core/collections/contentCollection.js
new file mode 100644
index 0000000000..4aec220c22
--- /dev/null
+++ b/frontend/src/core/collections/contentCollection.js
@@ -0,0 +1,35 @@
+// LICENCE https://github.com/adaptlearning/adapt_authoring/blob/master/LICENSE
+define(function(require) {
+ var Backbone = require('backbone');
+ var Origin = require('core/origin');
+ var Helpers = require('core/helpers');
+
+ var ContentCollection = Backbone.Collection.extend({
+ initialize : function(models, options) {
+ this._type = options._type;
+ this.model = Helpers.contentModelMap(this._type);
+ this._courseId = options._courseId;
+ this._parentId = options._parentId;
+ this.url = options.url || 'api/content/' + options._type + this.buildQuery();
+
+ this.on('reset', this.loadedData, this);
+ },
+
+ buildQuery: function() {
+ var query = '';
+ if(this._courseId) {
+ query += '_courseId=' + this._courseId
+ }
+ if(this._parentId) {
+ query += '_parentId=' + this._parentId
+ }
+ return query ? '?' + query : '';
+ },
+
+ loadedData: function() {
+ Origin.trigger('contentCollection:dataLoaded', this._type);
+ }
+ });
+
+ return ContentCollection;
+});
diff --git a/frontend/src/core/helpers.js b/frontend/src/core/helpers.js
index b41b7bad8b..ec882aafb3 100644
--- a/frontend/src/core/helpers.js
+++ b/frontend/src/core/helpers.js
@@ -205,21 +205,6 @@ define(function(require){
}
},
- getAssetFromValue: function(url) {
- var urlSplit = url.split('/')
- var fileName = urlSplit[urlSplit.length - 1];
- // Get courseAsset model
- var courseAsset = Origin.editor.data.courseassets.findWhere({_fieldName: fileName});
-
- if (courseAsset) {
- var courseAssetId = courseAsset.get('_assetId');
-
- return '/api/asset/serve/' + courseAssetId;
- } else {
- return '';
- }
- },
-
ifImageIsCourseAsset: function(url, block) {
if (url.length !== 0 && url.indexOf('course/assets') == 0) {
return block.fn(this);
@@ -228,21 +213,6 @@ define(function(require){
}
},
- getThumbnailFromValue: function(url) {
-
- var urlSplit = url.split('/')
- var fileName = urlSplit[urlSplit.length - 1];
- // Get courseAsset model
- var courseAsset = Origin.editor.data.courseassets.findWhere({_fieldName: fileName});
- if (courseAsset) {
- var courseAssetId = courseAsset.get('_assetId');
- return '/api/asset/thumb/' + courseAssetId;
- } else {
- return '/api/asset/thumb/' + url;
- }
-
- },
-
ifAssetIsExternal: function(url, block) {
if(Handlebars.helpers.isAssetExternal(url)) {
return block.fn(this);
@@ -297,68 +267,38 @@ define(function(require){
return success;
},
-
- validateCourseContent: function(currentCourse) {
- // Let's do a standard check for at least one child object
- var containsAtLeastOneChild = true;
+ // checks for at least one child object
+ validateCourseContent: function(currentCourse, callback) {
+ var containsAtLeastOneChild = true;
var alerts = [];
-
- function iterateOverChildren(model) {
- // Return the function if no children - on components
- if(!model._children) return;
-
- var currentChildren = model.getChildren();
-
- // Do validate across each item
- if (currentChildren.length == 0) {
- containsAtLeastOneChild = false;
-
- var children = _.isArray(model._children) ? model._children.join('/') : model._children;
- alerts.push(
- "There seems to be a "
- + model.get('_type')
- + " with the title - '"
- + model.get('title')
- + "' with no "
- + children
- );
-
- return;
- } else {
- // Go over each child and call validation again
- currentChildren.each(function(childModel) {
- iterateOverChildren(childModel);
- });
+ var iterateOverChildren = function(model, index, doneIterator) {
+ if(!model._childTypes) {
+ return doneIterator();
}
-
- }
-
- iterateOverChildren(currentCourse);
-
- if(alerts.length > 0) {
+ model.fetchChildren(function(currentChildren) {
+ if (currentChildren.length > 0) {
+ return helpers.forParallelAsync(currentChildren, iterateOverChildren, doneIterator);
+ }
+ containsAtLeastOneChild = false;
+ var children = _.isArray(model._childTypes) ? model._childTypes.join('/') : model._childTypes;
+ alerts.push(model.get('_type') + " '" + model.get('title') + "' missing " + children);
+ doneIterator();
+ });
+ };
+ // start recursion
+ iterateOverChildren(currentCourse, null, function() {
var errorMessage = "";
- for(var i = 0, len = alerts.length; i < len; i++) {
- errorMessage += "
-
-
-
-
-
-
-{{/if}}
\ No newline at end of file
+{{/if}}
diff --git a/frontend/src/modules/scaffold/views/scaffoldAssetView.js b/frontend/src/modules/scaffold/views/scaffoldAssetView.js
index be0adaeee6..bd57e32132 100644
--- a/frontend/src/modules/scaffold/views/scaffoldAssetView.js
+++ b/frontend/src/modules/scaffold/views/scaffoldAssetView.js
@@ -1,343 +1,310 @@
// LICENCE https://github.com/adaptlearning/adapt_authoring/blob/master/LICENSE
define(function(require) {
-
- var Backbone = require('backbone');
- var BackboneForms = require('backbone-forms');
- var Origin = require('core/origin');
- var AssetManagementModalView = require('modules/assetManagement/views/assetManagementModalView');
- var AssetCollection = require('modules/assetManagement/collections/assetCollection');
- var CourseAssetModel = require('core/models/courseAssetModel');
-
- var ScaffoldAssetView = Backbone.Form.editors.Base.extend({
-
- tagName: 'div',
-
- events: {
- 'change input': function() {
- // The 'change' event should be triggered whenever something happens
- // that affects the result of `this.getValue()`.
- this.toggleFieldAvailibility();
- //this.checkValueHasChanged();
- this.trigger('change', this);
- },
- 'focus input': function() {
- // The 'focus' event should be triggered whenever an input within
- // this editor becomes the `document.activeElement`.
- this.trigger('focus', this);
- // This call automatically sets `this.hasFocus` to `true`.
- },
- 'blur input': function() {
- // The 'blur' event should be triggered whenever an input within
- // this editor stops being the `document.activeElement`.
- this.trigger('blur', this);
- // This call automatically sets `this.hasFocus` to `false`.
- },
- 'click .scaffold-asset-picker': 'onAssetButtonClicked',
- 'click .scaffold-asset-external': 'onExternalAssetButtonClicked',
- 'click .scaffold-asset-clear': 'onClearButtonClicked',
- 'click .scaffold-asset-external-input-save': 'onExternalAssetSaveClicked',
- 'click .scaffold-asset-external-input-cancel': 'onExternalAssetCancelClicked',
- 'click .scaffold-asset-clear-external': 'onExternalClearButtonClicked'
-
- },
-
- initialize: function(options) {
- this.listenTo(Origin, 'scaffold:assets:autofill', this.onAutofill);
- // Call parent constructor
- Backbone.Form.editors.Base.prototype.initialize.call(this, options);
-
- },
-
- onAutofill: function(courseAssetObject, value) {
- this.value = value;
- this.createCourseAsset(courseAssetObject);
- },
-
- render: function() {
- var assetType = this.schema.fieldType.replace('Asset:', '');
- this.$el.html(Handlebars.templates[this.constructor.template]({value: this.value, type: assetType}));
- this.setValue(this.value);
- // Should see if the field contains anything on render
- // if so disable it
- this.toggleFieldAvailibility();
-
- return this;
- },
-
- getValue: function() {
- return this.value || '';
- //return this.$('input').val();
- },
-
- setValue: function(value) {
- this.value = value;
- //this.$('input').val(value);
- },
-
- focus: function() {
- if (this.hasFocus) return;
-
- // This method call should result in an input within this edior
- // becoming the `document.activeElement`.
- // This, in turn, should result in this editor's `focus` event
- // being triggered, setting `this.hasFocus` to `true`.
- // See above for more detail.
- this.$('input').focus();
- },
-
- blur: function() {
- if (!this.hasFocus) return;
-
- this.$('input').blur();
- },
-
- toggleFieldAvailibility: function() {
- if (this.getValue().length === 0) {
- this.$('input').attr('disabled', false);
- //this.$('.scaffold-asset-clear').addClass('display-none');
- } else {
- //this.$('.scaffold-asset-clear').removeClass('display-none');
- this.$('input').attr('disabled', true);
- }
- },
-
- checkValueHasChanged: function() {
- if ('heroImage' === this.key){
- this.saveModel(false, {heroImage: this.getValue()});
- return;
- }
- var contentTypeId = Origin.scaffold.getCurrentModel().get('_id');
- var contentType = Origin.scaffold.getCurrentModel().get('_type');
- var fieldname = this.getValue() ? this.getValue().replace('course/assets/', '') : '';
- this.removeCourseAsset(contentTypeId, contentType, fieldname);
- },
-
- onExternalAssetButtonClicked: function(event) {
- event.preventDefault();
- this.$('.scaffold-asset-external-input').removeClass('display-none');
- this.$('.scaffold-asset-buttons').addClass('display-none');
- },
-
- onExternalAssetSaveClicked: function(event) {
- event.preventDefault();
- var inputValue = this.$('.scaffold-asset-external-input-field').val();
- // Check that there's actually some value
- if (inputValue.length > 0) {
- this.value = inputValue;
- this.saveModel(false);
- } else {
- // If nothing don't bother saving - instead revert to showing the buttons again
- this.$('.scaffold-asset-external-input').addClass('display-none');
- this.$('.scaffold-asset-buttons').removeClass('display-none');
- }
- },
-
- onExternalAssetCancelClicked: function(event) {
- event.preventDefault();
- this.$('.scaffold-asset-external-input').addClass('display-none');
- this.$('.scaffold-asset-buttons').removeClass('display-none');
- },
-
- onAssetButtonClicked: function(event) {
- event.preventDefault();
- Origin.trigger('modal:open', AssetManagementModalView, {
- collection: new AssetCollection,
- assetType: this.schema.fieldType,
- _shouldShowScrollbar: false,
- onUpdate: function(data) {
- if (data) {
-
- if ('heroImage' === this.key){
- this.setValue(data.assetId);
- this.saveModel(false, {heroImage: data.assetId});
- return;
- }
- // Setup courseasset
- var contentTypeId = Origin.scaffold.getCurrentModel().get('_id') || '';
- var contentType = Origin.scaffold.getCurrentModel().get('_type');
- var contentTypeParentId = Origin.scaffold.getCurrentModel().get('_parentId') || Origin.editor.data.course.get('_id');
- var fieldname = data.assetFilename;
- var assetId = data.assetId;
-
-
- var courseAssetObject = {
- contentTypeId: contentTypeId,
- contentType: contentType,
- contentTypeParentId: contentTypeParentId,
- fieldname: fieldname,
- assetId: assetId
- }
-
- // If the data is meant to autofill the rest of the graphic sizes
- // pass out an event instead - this is currently only used for the graphic component
- if (data._shouldAutofill) {
- Origin.trigger('scaffold:assets:autofill', courseAssetObject, data.assetLink);
- return;
- }
-
- this.value = data.assetLink;
-
- this.createCourseAsset(courseAssetObject);
-
- }
- },
- onCancel: function(data) {}
- }, this);
- },
-
- onClearButtonClicked: function(event) {
- event.preventDefault();
- this.checkValueHasChanged();
- this.setValue('');
- this.toggleFieldAvailibility();
- },
-
- onExternalClearButtonClicked: function(event) {
- event.preventDefault();
- this.setValue('');
- this.saveModel(false);
- this.toggleFieldAvailibility();
- },
-
- findAsset: function (contentTypeId, contentType, fieldname) {
- var searchCriteria = {
- _contentType: contentType,
- _fieldName: fieldname
- };
-
- if (contentTypeId) {
- searchCriteria._contentTypeId = contentTypeId;
- } else {
- searchCriteria._contentTypeParentId = Origin.editor.data.course.get('_id');
- }
- var asset = Origin.editor.data.courseassets.findWhere(searchCriteria);
-
- if (!asset) {
- // HACK - Try relaxing the search criteria for historic data
- asset = Origin.editor.data.courseassets.findWhere({_contentType: contentType, _fieldName: fieldname});
- }
-
- return asset ? asset : false;
- },
-
- createCourseAsset: function (courseAssetObject) {
- var self = this;
-
- var courseAsset = new CourseAssetModel();
- courseAsset.save({
- _courseId : Origin.editor.data.course.get('_id'),
- _contentType : courseAssetObject.contentType,
- _contentTypeId : courseAssetObject.contentTypeId,
- _fieldName : courseAssetObject.fieldname,
- _assetId : courseAssetObject.assetId,
- _contentTypeParentId: courseAssetObject.contentTypeParentId
- },{
- error: function(error) {
- Origin.Notify.alert({
- type: 'error',
- text: Origin.l10n.t('app.errorsaveasset')
- });
- },
- success: function() {
- self.saveModel(true);
- }
- });
-
- },
-
- removeCourseAsset: function (contentTypeId, contentType, fieldname) {
- var that = this;
- var courseAsset = this.findAsset(contentTypeId, contentType, fieldname);
- if (courseAsset) {
- courseAsset.destroy({
- success: function(success) {
- that.saveModel(true);
- },
- error: function(error) {
- }
- });
- } else {
- this.setValue('');
- this.saveModel(true);
- }
+ var Backbone = require('backbone');
+ var BackboneForms = require('backbone-forms');
+ var Origin = require('core/origin');
+ var Helpers = require('core/helpers');
+ var AssetManagementModalView = require('modules/assetManagement/views/assetManagementModalView');
+ var AssetCollection = require('modules/assetManagement/collections/assetCollection');
+ var ContentCollection = require('core/collections/contentCollection');
+ var CourseAssetModel = require('core/models/courseAssetModel');
+
+ var ScaffoldAssetView = Backbone.Form.editors.Base.extend({
+ tagName: 'div',
+ events: {
+ // triggered whenever something happens that affects the result of `this.getValue()`
+ 'change input': function() {
+ this.toggleFieldAvailibility();
+ this.trigger('change', this);
+ },
+ // triggered whenever an input in this editor becomes the `document.activeElement`
+ 'focus input': function() {
+ this.trigger('focus', this);
+ },
+ // triggered whenever an input in this editor stops being the `document.activeElement`
+ 'blur input': function() {
+ this.trigger('blur', this);
+ },
+ 'click .scaffold-asset-picker': 'onAssetButtonClicked',
+ 'click .scaffold-asset-external': 'onExternalAssetButtonClicked',
+ 'click .scaffold-asset-clear': 'onClearButtonClicked',
+ 'click .scaffold-asset-external-input-save': 'onExternalAssetSaveClicked',
+ 'click .scaffold-asset-external-input-cancel': 'onExternalAssetCancelClicked',
+ },
+
+ initialize: function(options) {
+ this.listenTo(Origin, 'scaffold:assets:autofill', this.onAutofill);
+ Backbone.Form.editors.Base.prototype.initialize.call(this, options);
+ },
+
+ render: function() {
+ var template = Handlebars.templates[this.constructor.template];
+ var templateData = {
+ value: this.value,
+ type: this.schema.fieldType.replace('Asset:', '')
+ };
+ // this delgate function is async, so does a re-render once the data is loaded
+ var _renderDelegate = _.bind(function(assetId) {
+ if(assetId) {
+ templateData.url = '/api/asset/serve/' + assetId;
+ templateData.thumbUrl = '/api/asset/thumb/' + assetId;
+ this.$el.html(template(templateData));
+ }
+ }, this);
+ if(Helpers.isAssetExternal(this.value)) {
+ // we know there won't be a courseasset record, so don't bother fetching
+ templateData.url = this.value;
+ templateData.thumbUrl = this.value;
+ } else {
+ // don't have asset ID, so query courseassets for matching URL && content ID
+ this.fetchCourseAsset({
+ _fieldName: this.value.split('/').pop(),
+ _contentTypeId: Origin.scaffold.getCurrentModel().get('_id')
+ }, function(error, collection) {
+ if(error) {
+ console.error(error);
+ return _renderDelegate();
+ }
+ if(collection.length === 0) {
+ return _renderDelegate();
+ }
+ _renderDelegate(collection.at(0).get('_assetId'));
+ });
+ }
+ // we do a first pass render here to satisfy code expecting us to return 'this'
+ this.setValue(this.value);
+ this.toggleFieldAvailibility();
+ this.$el.html(template(templateData));
+ return this;
+ },
+
+ getValue: function() {
+ return this.value || '';
+ },
+
+ setValue: function(value) {
+ this.value = value;
+ },
+
+ toggleFieldAvailibility: function() {
+ this.$('input').attr('disabled', this.getValue().length === 0);
+ },
+
+ checkValueHasChanged: function() {
+ var val = this.getValue();
+ if ('heroImage' === this.key) {
+ this.saveModel({ heroImage: val });
+ return;
+ }
+ if(Helpers.isAssetExternal(val)) {
+ this.saveModel();
+ return;
+ }
+ var contentTypeId = Origin.scaffold.getCurrentModel().get('_id');
+ var contentType = Origin.scaffold.getCurrentModel().get('_type');
+ var fieldname = val.replace('course/assets/', '');
+ this.removeCourseAsset(contentTypeId, contentType, fieldname);
+ },
+
+ createCourseAsset: function(courseAssetObject) {
+ (new CourseAssetModel()).save({
+ _courseId : Origin.editor.data.course.get('_id'),
+ _contentType : courseAssetObject.contentType,
+ _contentTypeId : courseAssetObject.contentTypeId,
+ _fieldName : courseAssetObject.fieldname,
+ _assetId : courseAssetObject.assetId,
+ _contentTypeParentId: courseAssetObject.contentTypeParentId
+ }, {
+ success: _.bind(function() {
+ this.saveModel();
+ }, this),
+ error: function(error) {
+ Origin.Notify.alert({
+ type: 'error',
+ text: Origin.l10n.t('app.errorsaveasset')
+ });
+ }
+ });
+ },
+
+ fetchCourseAsset: function(searchCriteria, cb) {
+ if(!searchCriteria._contentTypeId) {
+ searchCriteria._contentTypeParentId = Origin.editor.data.course.get('_id');
+ }
+ (new ContentCollection(null, { _type: 'courseasset' })).fetch({
+ data: searchCriteria,
+ success: function(collection) {
+ cb(null, collection);
},
-
- saveModel: function(shouldResetAssetCollection, attributesToSave) {
- var that = this;
- var isUsingAlternativeModel = false;
- var currentModel = Origin.scaffold.getCurrentModel()
- var alternativeModel = Origin.scaffold.getAlternativeModel();
- var alternativeAttribute = Origin.scaffold.getAlternativeAttribute();
- var isPatch = false;
-
- attributesToSave = typeof attributesToSave == 'undefined'
- ? []
- : attributesToSave;
-
- // Check if alternative model should be used
- if (alternativeModel) {
- currentModel = alternativeModel;
- isUsingAlternativeModel = true;
- }
-
- var currentForm = Origin.scaffold.getCurrentForm();
- var errors = currentForm.commit({validate: false});
-
- // Check if alternative attribute should be used
- if (alternativeAttribute) {
- attributesToSave[alternativeAttribute] = Origin.scaffold.getCurrentModel().attributes;
- }
-
- if (!attributesToSave && !attributesToSave.length) {
- currentModel.pruneAttributes();
- currentModel.unset('tags');
- } else {
- isPatch = true;
+ error: function(model, response) {
+ cb('Failed to fetch data for', model.get('filename') + ':', response.statusText);
+ }
+ });
+ },
+
+ removeCourseAsset: function(contentTypeId, contentType, fieldname) {
+ this.fetchCourseAsset({
+ _contentTypeId: contentTypeId,
+ _contentType: contentType,
+ _fieldName: fieldname
+ }, _.bind(function(error, courseassets) {
+ if(error) {
+ return console.error(error);
+ }
+ if(courseassets.length === 0) {
+ this.setValue('');
+ this.saveModel();
+ return;
+ }
+ // delete all matching courseassets and then saveModel
+ Helpers.forParallelAsync(courseassets, function(model, index, cb) {
+ model.destroy({
+ success: cb,
+ error: function(error) {
+ console.error('Failed to destroy courseasset record', courseasset.get('_id'));
+ cb();
}
-
- currentModel.save(attributesToSave, {
- patch: isPatch,
- error: function() {
- Origin.Notify.alert({
- type: 'error',
- text: Origin.l10n.t('app.errorsaveasset')
- });
- },
- success: function() {
-
- // Sometimes we don't need to reset the courseassets
- if (shouldResetAssetCollection) {
-
- Origin.editor.data.courseassets.fetch({
- reset:true,
- success: function() {
- that.render();
- that.trigger('change', that);
- }
- });
-
- } else {
- that.render();
- that.trigger('change', that);
- }
- }
- })
+ });
+ }, _.bind(this.saveModel, this));
+ }, this));
+ },
+
+ saveModel: function(attributesToSave) {
+ var isUsingAlternativeModel = false;
+ var currentModel = Origin.scaffold.getCurrentModel();
+ var alternativeModel = Origin.scaffold.getAlternativeModel();
+ var alternativeAttribute = Origin.scaffold.getAlternativeAttribute();
+ // Check if alternative model should be used
+ if (alternativeModel) {
+ currentModel = alternativeModel;
+ isUsingAlternativeModel = true;
+ }
+ // run schema validation
+ Origin.scaffold.getCurrentForm().commit({ validate: false });
+ // Check if alternative attribute should be used
+ if (alternativeAttribute) {
+ attributesToSave[alternativeAttribute] = Origin.scaffold.getCurrentModel().attributes;
+ }
+ if (!attributesToSave) {
+ currentModel.pruneAttributes();
+ currentModel.unset('tags');
+ }
+ currentModel.save(attributesToSave, {
+ patch: attributesToSave !== undefined,
+ success: _.bind(function() {
+ this.render();
+ this.trigger('change', this);
+ }, this),
+ error: function() {
+ Origin.Notify.alert({
+ type: 'error',
+ text: Origin.l10n.t('app.errorsaveasset')
+ });
}
-
- }, {
- template: "scaffoldAsset"
- });
-
- Origin.on('origin:dataReady', function() {
- // Add Image editor to the list of editors
- Origin.scaffold.addCustomField('Asset:image', ScaffoldAssetView);
- Origin.scaffold.addCustomField('Asset:audio', ScaffoldAssetView);
- Origin.scaffold.addCustomField('Asset:video', ScaffoldAssetView);
- Origin.scaffold.addCustomField('Asset:other', ScaffoldAssetView);
- Origin.scaffold.addCustomField('Asset', ScaffoldAssetView);
- })
-
-
- return ScaffoldAssetView;
-
-})
+ });
+ },
+
+ /**
+ * Event handling
+ */
+
+ focus: function() {
+ if (this.hasFocus) return;
+ /**
+ * makes input the `document.activeElement`, triggering this editor's
+ * focus` event, and setting `this.hasFocus` to `true`.
+ * See this.events above for more detail
+ */
+ this.$('input').focus();
+ },
+
+ blur: function() {
+ if(this.hasFocus) this.$('input').blur();
+ },
+
+ onAssetButtonClicked: function(event) {
+ event.preventDefault();
+ Origin.trigger('modal:open', AssetManagementModalView, {
+ collection: new AssetCollection,
+ assetType: this.schema.fieldType,
+ _shouldShowScrollbar: false,
+ onUpdate: function(data) {
+ if (!data) {
+ return;
+ }
+ if ('heroImage' === this.key) {
+ this.setValue(data.assetId);
+ this.saveModel({ heroImage: data.assetId });
+ return;
+ }
+ var courseAssetObject = {
+ contentTypeId: Origin.scaffold.getCurrentModel().get('_id') || '',
+ contentType: Origin.scaffold.getCurrentModel().get('_type'),
+ contentTypeParentId: Origin.scaffold.getCurrentModel().get('_parentId') || Origin.editor.data.course.get('_id'),
+ fieldname: data.assetFilename,
+ assetId: data.assetId
+ };
+ // all ScaffoldAssetViews listen to the autofill event, so we trigger
+ // that rather than call code directly
+ // FIXME only works with graphic components
+ if (data._shouldAutofill) {
+ Origin.trigger('scaffold:assets:autofill', courseAssetObject, data.assetLink);
+ return;
+ }
+ this.value = data.assetLink;
+ this.createCourseAsset(courseAssetObject);
+ }
+ }, this);
+ },
+
+ onClearButtonClicked: function(event) {
+ event.preventDefault();
+ this.checkValueHasChanged();
+ this.setValue('');
+ this.toggleFieldAvailibility();
+ },
+
+ onAutofill: function(courseAssetObject, value) {
+ this.value = value;
+ this.createCourseAsset(courseAssetObject);
+ },
+
+ onExternalAssetButtonClicked: function(event) {
+ event.preventDefault();
+ this.$('.scaffold-asset-external-input').removeClass('display-none');
+ this.$('.scaffold-asset-buttons').addClass('display-none');
+ },
+
+ onExternalAssetSaveClicked: function(event) {
+ event.preventDefault();
+ var inputValue = this.$('.scaffold-asset-external-input-field').val();
+
+ if (inputValue.length === 0) { // nothing to save
+ this.$('.scaffold-asset-external-input').addClass('display-none');
+ this.$('.scaffold-asset-buttons').removeClass('display-none');
+ return;
+ }
+ this.setValue(inputValue);
+ this.saveModel();
+ },
+
+ onExternalAssetCancelClicked: function(event) {
+ event.preventDefault();
+ this.$('.scaffold-asset-external-input').addClass('display-none');
+ this.$('.scaffold-asset-buttons').removeClass('display-none');
+ },
+ }, {
+ template: "scaffoldAsset"
+ });
+
+ Origin.on('origin:dataReady', function() {
+ // Add Image editor to the list of editors
+ Origin.scaffold.addCustomField('Asset:image', ScaffoldAssetView);
+ Origin.scaffold.addCustomField('Asset:audio', ScaffoldAssetView);
+ Origin.scaffold.addCustomField('Asset:video', ScaffoldAssetView);
+ Origin.scaffold.addCustomField('Asset:other', ScaffoldAssetView);
+ Origin.scaffold.addCustomField('Asset', ScaffoldAssetView);
+ });
+
+ return ScaffoldAssetView;
+});
diff --git a/lib/bowermanager.js b/lib/bowermanager.js
index a96fd1090a..f2bf976ce6 100644
--- a/lib/bowermanager.js
+++ b/lib/bowermanager.js
@@ -125,7 +125,7 @@ BowerManager.prototype.installPlugin = function(pluginName, pluginVersion, callb
if (err) {
return callback(err);
}
- installHelpers.getLatestFrameworkVersion(function(error, frameworkVersion) {
+ installHelpers.getInstalledFrameworkVersion(function(error, frameworkVersion) {
if (error) {
return callback(error);
}
diff --git a/lib/contentmanager.js b/lib/contentmanager.js
index 2c45fcad2a..8bedc960bc 100644
--- a/lib/contentmanager.js
+++ b/lib/contentmanager.js
@@ -1065,14 +1065,6 @@ ContentManager.prototype.setupRoutes = function () {
rest.post('/content/clipboard/copy', function(req, res, next) {
var user = app.usermanager.getCurrentUser();
var tenantId = user.tenant && user.tenant._id;
-
- var hierarchy = {
- 'contentObjects': 'contentobject',
- 'articles': 'article',
- 'blocks': 'block',
- 'components':'component'
- };
-
var id = req.body.objectId;
var referenceType = req.body.referenceType;
var courseId = req.body.courseId;
@@ -1100,9 +1092,7 @@ ContentManager.prototype.setupRoutes = function () {
},
// Retrieve the current object
function(callback) {
- var type = hierarchy[referenceType];
-
- db.retrieve(type, { _id: id }, function (error, results) {
+ db.retrieve(referenceType, { _id: id }, function (error, results) {
if (error) {
return callback(error);
}
@@ -1112,20 +1102,17 @@ ContentManager.prototype.setupRoutes = function () {
var item = results[0]._doc;
switch (referenceType) {
- case 'contentObjects':
+ case 'contentobject':
isMenu = (item._type == 'menu') ? true : false;
contentObjects.push(item);
break;
-
- case 'articles':
+ case 'article':
articles.push(item);
break;
-
- case 'blocks':
+ case 'block':
blocks.push(item);
break;
-
- case 'components':
+ case 'component':
components.push(item);
break;
}
@@ -1188,10 +1175,10 @@ ContentManager.prototype.setupRoutes = function () {
_tenantId: tenantId,
createdBy: user._id,
referenceType: referenceType,
- contentObjects: contentObjects,
- articles: articles,
- blocks: blocks,
- components: components
+ contentobject: contentObjects,
+ article: articles,
+ block: blocks,
+ component: components
};
db.create('clipboard', clipboard, function(error, newRecord) {
if (error) {
@@ -1221,22 +1208,18 @@ ContentManager.prototype.setupRoutes = function () {
var courseId = req.body.courseId;
var clipboard;
var parentObject;
-
+ var keyMap = {
+ ContentObject: 'contentobject',
+ Article: 'article',
+ Block: 'block',
+ Component: 'component'
+ };
var parentRelationship = {
- 'contentObjects': 'contentobject', // In case
- 'articles': 'contentobject',
- 'blocks': 'article',
- 'components': 'block'
+ 'contentobject': 'contentobject',
+ 'article': 'contentobject',
+ 'block': 'article',
+ 'component': 'block'
};
-
- var typeToCollection = {
- 'page': 'contentObjects',
- 'menu': 'contentObjects',
- 'article': 'articles',
- 'block': 'blocks',
- 'component': 'components'
- }
-
var map = {};
database.getDatabase(function (error, db) {
@@ -1256,9 +1239,9 @@ ContentManager.prototype.setupRoutes = function () {
clipboard = results[0]._doc;
- if (layout && clipboard.components.length == 1) {
+ if (layout && clipboard[keyMap.Component].length == 1) {
// Persist the component layout when there is only one
- clipboard.components[0]._layout = layout;
+ clipboard[keyMap.Component][0]._layout = layout;
}
// OK to proceed
async.series([
@@ -1272,8 +1255,8 @@ ContentManager.prototype.setupRoutes = function () {
parentObject = results[0]._doc;
return callback();
}
- if (clipboard.referenceType === 'contentObjects'
- && (clipboard.contentObjects[0]._courseId.toString() === clipboard._courseId.toString())) {
+ if (clipboard.referenceType === keyMap.ContentObject
+ && (clipboard[keyMap.ContentObject][0]._courseId.toString() === clipboard._courseId.toString())) {
// Handle if this is a root-level page
parentObject = { _id: clipboard._courseId };
callback();
@@ -1290,7 +1273,12 @@ ContentManager.prototype.setupRoutes = function () {
callback();
},
function(callback) {
- async.eachSeries(['contentObjects', 'articles', 'blocks', 'components'], function(level, cb) {
+ async.eachSeries([
+ keyMap.ContentObject,
+ keyMap.Article,
+ keyMap.Block,
+ keyMap.Component
+ ], function(level, cb) {
map[level] = {};
async.eachSeries(clipboard[level], function(item, cb2) {
map[level][item._id.toString()] = null;
@@ -1301,24 +1289,23 @@ ContentManager.prototype.setupRoutes = function () {
},
// Pasting contentObjects
function(callback) {
- if (clipboard['contentObjects'].length === 0) {
+ if (clipboard[keyMap.ContentObject].length === 0) {
return callback();
}
- async.eachSeries(clipboard['contentObjects'], function(item, cb) {
+ async.eachSeries(clipboard[keyMap.ContentObject], function(item, cb) {
var previousId = item._id.toString();
var previousParentId = item._parentId.toString();
- var newParentId = typeof(map['contentObjects'][previousParentId]) !== 'undefined' && map['contentObjects'][previousParentId] != null
- ? map['contentObjects'][previousParentId]
- : parentObject._id;
+ var mappedId = map[keyMap.ContentObject][previousParentId];
+ var newParentId = mappedId ? mappedId : parentObject._id;
delete item._id;
item._parentId = newParentId;
- that.create('contentobject', item, function (error, newItem) {
+ that.create(keyMap.ContentObject, item, function (error, newItem) {
if (error) {
return cb(error);
}
- map['contentObjects'][previousId] = newItem._id;
+ map[keyMap.ContentObject][previousId] = newItem._id;
// For each object being copied we should find out if there's any course assets
// associated with the object. Then create new ones based upon there new ids
copyCourseAssets(that, {
@@ -1332,14 +1319,14 @@ ContentManager.prototype.setupRoutes = function () {
},
// Pasting articles
function(callback) {
- if (clipboard['articles'].length === 0) {
+ if (clipboard[keyMap.Article].length === 0) {
return callback();
}
- async.eachSeries(clipboard['articles'], function(item, cb) {
+ async.eachSeries(clipboard[keyMap.Article], function(item, cb) {
var previousId = item._id.toString();
var previousParentId = item._parentId.toString();
- var newParentId = typeof(map['contentObjects'][previousParentId]) !== 'undefined' && map['contentObjects'][previousParentId] != null
- ? map['contentObjects'][previousParentId]
+ var newParentId = typeof(map[keyMap.ContentObject][previousParentId]) !== 'undefined' && map[keyMap.ContentObject][previousParentId] != null
+ ? map[keyMap.ContentObject][previousParentId]
: parentObject._id;
delete item._id;
@@ -1349,7 +1336,7 @@ ContentManager.prototype.setupRoutes = function () {
if (error) {
return cb(error);
}
- map['articles'][previousId] = newItem._id;
+ map[keyMap.Article][previousId] = newItem._id;
// For each object being copied we should find out if there's any course assets
// associated with the object. Then create new ones based upon there new ids
copyCourseAssets(that, {
@@ -1363,14 +1350,14 @@ ContentManager.prototype.setupRoutes = function () {
},
// Pasting blocks
function(callback) {
- if (clipboard['blocks'].length === 0) {
+ if (clipboard[keyMap.Block].length === 0) {
return callback();
}
- async.eachSeries(clipboard['blocks'], function(item, cb) {
+ async.eachSeries(clipboard[keyMap.Block], function(item, cb) {
var previousId = item._id.toString();
var previousParentId = item._parentId.toString();
- var newParentId = typeof(map['articles'][previousParentId]) !== 'undefined'
- ? map['articles'][previousParentId]
+ var newParentId = typeof(map[keyMap.Article][previousParentId]) !== 'undefined'
+ ? map[keyMap.Article][previousParentId]
: parentObject._id;
delete item._id;
@@ -1380,7 +1367,7 @@ ContentManager.prototype.setupRoutes = function () {
if (error) {
return cb(error);
}
- map['blocks'][previousId] = newItem._id;
+ map[keyMap.Block][previousId] = newItem._id;
// For each object being copied we should find out if there's any course assets
// associated with the object. Then create new ones based upon there new ids
copyCourseAssets(that, {
@@ -1394,14 +1381,14 @@ ContentManager.prototype.setupRoutes = function () {
},
// Pasting components
function(callback) {
- if (clipboard['components'].length === 0) {
+ if (clipboard[keyMap.Component].length === 0) {
return callback();
}
- async.eachSeries(clipboard['components'], function(item, cb) {
+ async.eachSeries(clipboard[keyMap.Component], function(item, cb) {
var previousId = item._id.toString();
var previousParentId = item._parentId.toString();
- var newParentId = typeof(map['blocks'][previousParentId]) !== 'undefined'
- ? map['blocks'][previousParentId]
+ var newParentId = typeof(map[keyMap.Block][previousParentId]) !== 'undefined'
+ ? map[keyMap.Block][previousParentId]
: parentObject._id;
delete item._id;
@@ -1411,7 +1398,7 @@ ContentManager.prototype.setupRoutes = function () {
if (error) {
return cb(error);
}
- map['components'][previousId] = newItem._id;
+ map[keyMap.Component][previousId] = newItem._id;
// For each object being copied we should find out if there's any course assets
// associated with the object. Then create new ones based upon there new ids
copyCourseAssets(that, {
@@ -1428,7 +1415,11 @@ ContentManager.prototype.setupRoutes = function () {
logger.log('error', err);
return res.status(500).json({ success: false, message: 'Error pasting clipboard data' });
}
- return res.status(200).json({ success: true, message: 'ok' });
+ // successful paste, remove entry
+ db.destroy('clipboard', { id: id }, function(error) {
+ var newId = _.values(map[_.find(Object.keys(map), function(key) { return !_.isEmpty(map[key]); })])[0];
+ return res.status(200).json({ _id: newId });
+ });
});
}, tenantId);
});
diff --git a/lib/installHelpers.js b/lib/installHelpers.js
index 71e7ef008a..cdcb9d43b5 100644
--- a/lib/installHelpers.js
+++ b/lib/installHelpers.js
@@ -166,10 +166,10 @@ function getUpdateData(callback) {
return callback(error);
}
var updateData = {};
- if(semver.lt(results[0].adapt_authoring, results[1].adapt_authoring)) {
+ if(results[1].adapt_authoring && semver.lt(results[0].adapt_authoring, results[1].adapt_authoring)) {
updateData.adapt_authoring = results[1].adapt_authoring;
}
- if(semver.lt(results[0].adapt_framework, results[1].adapt_framework)) {
+ if(results[1].adapt_framework && semver.lt(results[0].adapt_framework, results[1].adapt_framework)) {
updateData.adapt_framework = results[1].adapt_framework;
}
if(_.isEmpty(updateData)) {
@@ -205,17 +205,20 @@ function checkLatestAdaptRepoVersion(repoName, versionLimit, callback) {
}, done);
};
var _requestHandler = function(error, response, body) {
- // we've exceeded the API limit
- if(response.statusCode === 403 && response.headers['x-ratelimit-remaining'] === '0') {
- var reqsReset = new Date(response.headers['x-ratelimit-reset']*1000);
- error = `You have exceeded GitHub's request limit of ${response.headers['x-ratelimit-limit']} requests per hour. Please wait until at least ${reqsReset.toTimeString()} before trying again.`;
- }
- else if (response.statusCode !== 200) {
- error = 'GitubAPI did not respond with a 200 status code.';
+ if(response) {
+ // we've exceeded the API limit
+ if(response.statusCode === 403 && response.headers['x-ratelimit-remaining'] === '0') {
+ var reqsReset = new Date(response.headers['x-ratelimit-reset']*1000);
+ error = `You have exceeded GitHub's request limit of ${response.headers['x-ratelimit-limit']} requests per hour. Please wait until at least ${reqsReset.toTimeString()} before trying again.`;
+ }
+ else if(response.statusCode !== 200) {
+ error = 'GitubAPI did not respond with a 200 status code.';
+ }
}
-
+ // exit, but just log the error
if (error) {
- return callback(`Couldn't check latest version of ${repoName}\n${error}`);
+ log(`Couldn't check latest version of ${repoName}\n${error}`);
+ return callback();
}
nextPage = parseLinkHeader(response.headers.link).next;
try {
@@ -287,8 +290,8 @@ function installFramework(opts, callback) {
}
if(!opts.revision) {
return getLatestFrameworkVersion(function(error, version) {
- if(error) return callback(error);
- opts.revision = version;
+ // NOTE we default to the master branch
+ opts.revision = version || 'master';
installFramework(opts, callback);
});
}
diff --git a/plugins/content/clipboard/model.schema b/plugins/content/clipboard/model.schema
index 981d8b4bd1..d6667a7e75 100644
--- a/plugins/content/clipboard/model.schema
+++ b/plugins/content/clipboard/model.schema
@@ -19,9 +19,9 @@
"type" : "string",
"required" : true
},
- "contentObjects" : [{}],
- "articles" : [{}],
- "blocks" : [{}],
- "components" : [{}]
+ "contentobject" : [{}],
+ "article" : [{}],
+ "block" : [{}],
+ "component" : [{}]
}
}
diff --git a/routes/lang/en-application.json b/routes/lang/en-application.json
index 5730633337..8e4e227352 100644
--- a/routes/lang/en-application.json
+++ b/routes/lang/en-application.json
@@ -253,6 +253,7 @@
"app.errorsessionexpired": "Your session has expired, click OK to log on again",
"app.errorassetupdate": "Something went wrong while updating the asset.
Please try again.",
"app.errordeleteasset": "Couldn't delete this asset, %{message}",
+ "app.errordelete": "An error occurred while deleting, please try again",
"app.errorsave": "Something went wrong while saving your data.
Please try again.",
"app.errorgeneric": "Oops, something went wrong!",
"app.errorpreview": "Something went wrong while generating your preview.
Please contact an administrator for assistance.",
@@ -265,7 +266,7 @@
"app.errorgettingschemas": "An error occurred while getting schemas.",
"app.errorgeneratingpreview": "Error generating preview, please contact an administrator.",
"app.errorcopy": "Error during copy.",
- "app.errorpaste": "Error during paste",
+ "app.errorpaste": "An error occurred during paste. Please try again.",
"app.errorsaveasset": "An error occurred doing the save",
"app.errorusernoaccess": "Sorry, the current user doesn't have access to this course",
"app.errorpagenoaccesstitle": "Access Denied",
@@ -308,5 +309,6 @@
"app.page": "page",
"app.article": "article",
"app.block": "block",
- "app.component": "component"
+ "app.component": "component",
+ "app.errorloadconfig": "Failed to load configuration settings for %{course}"
}