Many Plugin upgrades.
This commit is contained in:
parent
a644947119
commit
3f9c4100ef
|
@ -117,12 +117,12 @@ window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
|
||||||
start: function() {
|
start: function() {
|
||||||
|
|
||||||
// Load any ES6 initializers
|
// Load any ES6 initializers
|
||||||
Ember.keys(requirejs._eak_seen).filter(function(key) {
|
Ember.keys(requirejs._eak_seen).forEach(function(key) {
|
||||||
return (/\/initializers\//).test(key);
|
if (/\/initializers\//.test(key)) {
|
||||||
}).forEach(function(moduleName) {
|
var module = require(key, null, null, true);
|
||||||
var module = require(moduleName, null, null, true);
|
if (!module) { throw new Error(key + ' must export an initializer.'); }
|
||||||
if (!module) { throw new Error(moduleName + ' must export an initializer.'); }
|
Discourse.initializer(module.default);
|
||||||
Discourse.initializer(module.default);
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var initializers = this.initializers;
|
var initializers = this.initializers;
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
export default Ember.TextArea.extend({
|
||||||
|
placeholder: function() {
|
||||||
|
return I18n.t(this.get('placeholderKey'));
|
||||||
|
}.property('placeholderKey'),
|
||||||
|
|
||||||
|
_signalParentInsert: function() {
|
||||||
|
return this.get('parentView').childDidInsertElement(this);
|
||||||
|
}.on('didInsertElement'),
|
||||||
|
|
||||||
|
_signalParentDestroy: function() {
|
||||||
|
return this.get('parentView').childWillDestroyElement(this);
|
||||||
|
}.on('willDestroyElement')
|
||||||
|
});
|
||||||
|
|
|
@ -15,10 +15,9 @@ export default Discourse.Controller.extend({
|
||||||
showEditReason: false,
|
showEditReason: false,
|
||||||
editReason: null,
|
editReason: null,
|
||||||
|
|
||||||
init: function() {
|
_initializeSimilar: function() {
|
||||||
this._super();
|
this.set('similarTopics', []);
|
||||||
this.set('similarTopics', Em.A());
|
}.on('init'),
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
// Toggle the reply view
|
// Toggle the reply view
|
||||||
|
@ -45,7 +44,20 @@ export default Discourse.Controller.extend({
|
||||||
|
|
||||||
displayEditReason: function() {
|
displayEditReason: function() {
|
||||||
this.set("showEditReason", true);
|
this.set("showEditReason", true);
|
||||||
}
|
},
|
||||||
|
|
||||||
|
hitEsc: function() {
|
||||||
|
if (this.get('model.viewOpen')) {
|
||||||
|
this.shrink();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
openIfDraft: function() {
|
||||||
|
if (this.get('model.viewDraft')) {
|
||||||
|
this.set('model.composeState', Discourse.Composer.OPEN);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateDraftStatus: function() {
|
updateDraftStatus: function() {
|
||||||
|
@ -233,89 +245,74 @@ export default Discourse.Controller.extend({
|
||||||
open: function(opts) {
|
open: function(opts) {
|
||||||
if (!opts) opts = {};
|
if (!opts) opts = {};
|
||||||
|
|
||||||
this.setProperties({
|
|
||||||
showEditReason: false,
|
|
||||||
editReason: null
|
|
||||||
});
|
|
||||||
|
|
||||||
var composerMessages = this.get('controllers.composerMessages');
|
|
||||||
composerMessages.reset();
|
|
||||||
|
|
||||||
var promise = opts.promise || Ember.Deferred.create();
|
|
||||||
opts.promise = promise;
|
|
||||||
|
|
||||||
if (!opts.draftKey) {
|
if (!opts.draftKey) {
|
||||||
alert("composer was opened without a draft key");
|
alert("composer was opened without a draft key");
|
||||||
throw "composer opened without a proper draft key";
|
throw "composer opened without a proper draft key";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure we have a view now, without it transitions are going to be messed
|
var composerMessages = this.get('controllers.composerMessages'),
|
||||||
var view = this.get('view');
|
self = this,
|
||||||
var self = this;
|
composerModel = this.get('model');
|
||||||
if (!view) {
|
|
||||||
|
|
||||||
// TODO: We should refactor how composer is inserted. It should probably use a
|
this.setProperties({ showEditReason: false, editReason: null });
|
||||||
// {{render}} and then the controller and view will be wired up automatically.
|
composerMessages.reset();
|
||||||
var appView = Discourse.__container__.lookup('view:application');
|
this.set('view', this.container.lookup('view:composer'));
|
||||||
view = appView.createChildView(Discourse.ComposerView, {controller: this});
|
|
||||||
view.appendTo($('#main'));
|
|
||||||
this.set('view', view);
|
|
||||||
|
|
||||||
// the next runloop is too soon, need to get the control rendered and then
|
// If we want a different draft than the current composer, close it and clear our model.
|
||||||
// we need to change stuff, otherwise css animations don't kick in
|
if (composerModel && opts.draftKey !== composerModel.draftKey &&
|
||||||
Em.run.next(function() {
|
composerModel.composeState === Discourse.Composer.DRAFT) {
|
||||||
Em.run.next(function() {
|
|
||||||
self.open(opts);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
var composer = this.get('model');
|
|
||||||
if (composer && opts.draftKey !== composer.draftKey && composer.composeState === Discourse.Composer.DRAFT) {
|
|
||||||
this.close();
|
this.close();
|
||||||
composer = null;
|
composerModel = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (composer && !opts.tested && composer.get('replyDirty')) {
|
return new Ember.RSVP.Promise(function(resolve, reject) {
|
||||||
if (composer.composeState === Discourse.Composer.DRAFT && composer.draftKey === opts.draftKey && composer.action === opts.action) {
|
if (composerModel && composerModel.get('replyDirty')) {
|
||||||
composer.set('composeState', Discourse.Composer.OPEN);
|
if (composerModel.get('composeState') === Discourse.Composer.DRAFT &&
|
||||||
promise.resolve();
|
composerModel.get('draftKey') === opts.draftKey &&
|
||||||
return promise;
|
composerModel.action === opts.action) {
|
||||||
} else {
|
|
||||||
opts.tested = true;
|
// If it's the same draft, just open it up again.
|
||||||
if (!opts.ignoreIfChanged) {
|
composerModel.set('composeState', Discourse.Composer.OPEN);
|
||||||
this.cancelComposer().then(function() { self.open(opts); }).catch(function() { return promise.reject(); });
|
return resolve();
|
||||||
|
} else {
|
||||||
|
// If it's a different draft, cancel it and try opening again.
|
||||||
|
return self.cancelComposer().then(function() {
|
||||||
|
return self.open(opts);
|
||||||
|
}).then(resolve, reject);
|
||||||
}
|
}
|
||||||
return promise;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// we need a draft sequence, without it drafts are bust
|
// we need a draft sequence for the composer to work
|
||||||
if (opts.draftSequence === void 0) {
|
if (opts.draftSequence === void 0) {
|
||||||
Discourse.Draft.get(opts.draftKey).then(function(data) {
|
return Discourse.Draft.get(opts.draftKey).then(function(data) {
|
||||||
opts.draftSequence = data.draft_sequence;
|
opts.draftSequence = data.draft_sequence;
|
||||||
opts.draft = data.draft;
|
opts.draft = data.draft;
|
||||||
return self.open(opts);
|
self._setModel(composerModel, opts);
|
||||||
});
|
}).then(resolve, reject);
|
||||||
return promise;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
self._setModel(composerModel, opts);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Given a potential instance and options, set the model for this composer.
|
||||||
|
_setModel: function(composerModel, opts) {
|
||||||
if (opts.draft) {
|
if (opts.draft) {
|
||||||
composer = Discourse.Composer.loadDraft(opts.draftKey, opts.draftSequence, opts.draft);
|
composerModel = Discourse.Composer.loadDraft(opts.draftKey, opts.draftSequence, opts.draft);
|
||||||
if (composer) {
|
if (composerModel) {
|
||||||
composer.set('topic', opts.topic);
|
composerModel.set('topic', opts.topic);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
composer = composer || Discourse.Composer.create();
|
composerModel = composerModel || Discourse.Composer.create();
|
||||||
composer.open(opts);
|
composerModel.open(opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.set('model', composer);
|
this.set('model', composerModel);
|
||||||
composer.set('composeState', Discourse.Composer.OPEN);
|
composerModel.set('composeState', Discourse.Composer.OPEN);
|
||||||
composerMessages.queryFor(this.get('model'));
|
|
||||||
promise.resolve();
|
var composerMessages = this.get('controllers.composerMessages');
|
||||||
return promise;
|
composerMessages.queryFor(composerModel);
|
||||||
},
|
},
|
||||||
|
|
||||||
// View a new reply we've made
|
// View a new reply we've made
|
||||||
|
@ -356,11 +353,6 @@ export default Discourse.Controller.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
openIfDraft: function() {
|
|
||||||
if (this.get('model.viewDraft')) {
|
|
||||||
this.set('model.composeState', Discourse.Composer.OPEN);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
shrink: function() {
|
shrink: function() {
|
||||||
if (this.get('model.replyDirty')) {
|
if (this.get('model.replyDirty')) {
|
||||||
|
@ -388,13 +380,6 @@ export default Discourse.Controller.extend({
|
||||||
$('#wmd-input').autocomplete({ cancel: true });
|
$('#wmd-input').autocomplete({ cancel: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
// ESC key hit
|
|
||||||
hitEsc: function() {
|
|
||||||
if (this.get('model.viewOpen')) {
|
|
||||||
this.shrink();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
showOptions: function() {
|
showOptions: function() {
|
||||||
var _ref;
|
var _ref;
|
||||||
return (_ref = this.get('controllers.modal')) ? _ref.show(Discourse.ArchetypeOptionsModalView.create({
|
return (_ref = this.get('controllers.modal')) ? _ref.show(Discourse.ArchetypeOptionsModalView.create({
|
||||||
|
|
|
@ -65,6 +65,10 @@ Discourse.Resolver = Ember.DefaultResolver.extend({
|
||||||
return this.customResolve(parsedName) || this._super(parsedName);
|
return this.customResolve(parsedName) || this._super(parsedName);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
resolveHelper: function(parsedName) {
|
||||||
|
return this.customResolve(parsedName) || this._super(parsedName);
|
||||||
|
},
|
||||||
|
|
||||||
resolveController: function(parsedName) {
|
resolveController: function(parsedName) {
|
||||||
return this.customResolve(parsedName) || this._super(parsedName);
|
return this.customResolve(parsedName) || this._super(parsedName);
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
/**
|
||||||
|
A plugin outlet is an extension point for templates where other templates can
|
||||||
|
be inserted by plugins.
|
||||||
|
|
||||||
|
If you handlebars template has:
|
||||||
|
|
||||||
|
```handlebars
|
||||||
|
{{plugin-outlet "evil-trout"}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then any handlebars files you create in the `connectors/evil-trout` directory
|
||||||
|
will automatically be appended. For example:
|
||||||
|
|
||||||
|
plugins/hello/assets/javascripts/discourse/templates/connectors/evil-trout/hello.handlebars
|
||||||
|
|
||||||
|
With the contents:
|
||||||
|
|
||||||
|
```handlebars
|
||||||
|
<b>Hello World</b>
|
||||||
|
```
|
||||||
|
|
||||||
|
Will insert <b>Hello World</b> at that point in the template.
|
||||||
|
|
||||||
|
Optionally you can also define a view class for the outlet as:
|
||||||
|
|
||||||
|
plugins/hello/assets/javascripts/discourse/views/connectors/evil-trout/hello.js.es6
|
||||||
|
|
||||||
|
And it will be wired up automatically.
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
var _connectorCache;
|
||||||
|
|
||||||
|
function findOutlets(collection, callback) {
|
||||||
|
Ember.keys(collection).forEach(function(i) {
|
||||||
|
if (i.indexOf("/connectors/") !== -1) {
|
||||||
|
var segments = i.split("/"),
|
||||||
|
outletName = segments[segments.length-2],
|
||||||
|
uniqueName = segments[segments.length-1];
|
||||||
|
|
||||||
|
callback(outletName, i, uniqueName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildConnectorCache() {
|
||||||
|
_connectorCache = {};
|
||||||
|
|
||||||
|
var uniqueViews = {};
|
||||||
|
findOutlets(requirejs._eak_seen, function(outletName, idx, uniqueName) {
|
||||||
|
_connectorCache[outletName] = _connectorCache[outletName] || [];
|
||||||
|
|
||||||
|
var viewClass = require(idx, null, null, true).default;
|
||||||
|
uniqueViews[uniqueName] = viewClass;
|
||||||
|
_connectorCache[outletName].pushObject(viewClass);
|
||||||
|
});
|
||||||
|
|
||||||
|
findOutlets(Ember.TEMPLATES, function(outletName, idx, uniqueName) {
|
||||||
|
_connectorCache[outletName] = _connectorCache[outletName] || [];
|
||||||
|
|
||||||
|
var mixin = {templateName: idx.replace('javascripts/', '')},
|
||||||
|
viewClass = uniqueViews[uniqueName];
|
||||||
|
|
||||||
|
if (viewClass) {
|
||||||
|
// We are going to add it back with the proper template
|
||||||
|
_connectorCache[outletName].removeObject(viewClass);
|
||||||
|
} else {
|
||||||
|
viewClass = Em.View;
|
||||||
|
}
|
||||||
|
_connectorCache[outletName].pushObject(viewClass.extend(mixin));
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function(connectionName, options) {
|
||||||
|
if (!_connectorCache) { buildConnectorCache(); }
|
||||||
|
|
||||||
|
if (_connectorCache[connectionName]) {
|
||||||
|
var CustomContainerView = Ember.ContainerView.extend({
|
||||||
|
childViews: _connectorCache[connectionName].map(function(vc) { return vc.create(); })
|
||||||
|
});
|
||||||
|
return Ember.Handlebars.helpers.view.call(this, CustomContainerView, options);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,3 +5,4 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{render "modal"}}
|
{{render "modal"}}
|
||||||
|
{{render "composer"}}
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
|
|
||||||
{{#if model.viewOpen}}
|
{{#if model.viewOpen}}
|
||||||
<div class='control-row reply-area'>
|
<div class='control-row reply-area'>
|
||||||
|
{{plugin-outlet "composer-open"}}
|
||||||
|
|
||||||
<div class='reply-to'>
|
<div class='reply-to'>
|
||||||
{{{model.actionTitle}}}:
|
{{{model.actionTitle}}}:
|
||||||
{{#if canEdit}}
|
{{#if canEdit}}
|
||||||
|
@ -57,7 +59,7 @@
|
||||||
<div class='textarea-wrapper'>
|
<div class='textarea-wrapper'>
|
||||||
<div class='wmd-button-bar' id='wmd-button-bar'></div>
|
<div class='wmd-button-bar' id='wmd-button-bar'></div>
|
||||||
<div id='wmd-preview-scroller'></div>
|
<div id='wmd-preview-scroller'></div>
|
||||||
{{view Discourse.NotifyingTextArea parentBinding="view" tabindex="3" valueBinding="model.reply" id="wmd-input" placeholderKey="composer.reply_placeholder"}}
|
{{composer-text-area tabindex="3" value=model.reply id="wmd-input" placeholderKey="composer.reply_placeholder"}}
|
||||||
{{popupInputTip validation=view.replyValidation shownAt=view.showReplyTip}}
|
{{popupInputTip validation=view.replyValidation shownAt=view.showReplyTip}}
|
||||||
</div>
|
</div>
|
||||||
<div class='preview-wrapper'>
|
<div class='preview-wrapper'>
|
||||||
|
@ -75,7 +77,7 @@
|
||||||
<a {{action showUploadSelector view}} class='mobile-file-upload'>{{i18n upload}}</a>
|
<a {{action showUploadSelector view}} class='mobile-file-upload'>{{i18n upload}}</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div id='draft-status'></div>
|
<div id='draft-status'>{{model.draftStatus}}</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</h1>
|
</h1>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{plugin-outlet "topic-title"}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -26,15 +26,9 @@ Discourse.ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||||
content: Em.computed.alias('model'),
|
content: Em.computed.alias('model'),
|
||||||
|
|
||||||
composeState: function() {
|
composeState: function() {
|
||||||
var state = this.get('model.composeState');
|
return this.get('model.composeState') || Discourse.Composer.CLOSED;
|
||||||
if (state) return state;
|
|
||||||
return Discourse.Composer.CLOSED;
|
|
||||||
}.property('model.composeState'),
|
}.property('model.composeState'),
|
||||||
|
|
||||||
draftStatus: function() {
|
|
||||||
$('#draft-status').text(this.get('model.draftStatus') || "");
|
|
||||||
}.observes('model.draftStatus'),
|
|
||||||
|
|
||||||
// Disable fields when we're loading
|
// Disable fields when we're loading
|
||||||
loadingChanged: function() {
|
loadingChanged: function() {
|
||||||
if (this.get('loading')) {
|
if (this.get('loading')) {
|
||||||
|
@ -48,7 +42,6 @@ Discourse.ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||||
return this.present('controller.createdPost') ? 'created-post' : null;
|
return this.present('controller.createdPost') ? 'created-post' : null;
|
||||||
}.property('model.createdPost'),
|
}.property('model.createdPost'),
|
||||||
|
|
||||||
|
|
||||||
refreshPreview: Discourse.debounce(function() {
|
refreshPreview: Discourse.debounce(function() {
|
||||||
if (this.editor) {
|
if (this.editor) {
|
||||||
this.editor.refreshPreview();
|
this.editor.refreshPreview();
|
||||||
|
@ -101,7 +94,7 @@ Discourse.ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||||
keyDown: function(e) {
|
keyDown: function(e) {
|
||||||
if (e.which === 27) {
|
if (e.which === 27) {
|
||||||
// ESC
|
// ESC
|
||||||
this.get('controller').hitEsc();
|
this.get('controller').send('hitEsc');
|
||||||
return false;
|
return false;
|
||||||
} else if (e.which === 13 && (e.ctrlKey || e.metaKey)) {
|
} else if (e.which === 13 && (e.ctrlKey || e.metaKey)) {
|
||||||
// CTRL+ENTER or CMD+ENTER
|
// CTRL+ENTER or CMD+ENTER
|
||||||
|
@ -110,12 +103,12 @@ Discourse.ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
didInsertElement: function() {
|
_enableResizing: function() {
|
||||||
var $replyControl = $('#reply-control');
|
var $replyControl = $('#reply-control');
|
||||||
$replyControl.DivResizer({ resize: this.resize, onDrag: this.movePanels });
|
$replyControl.DivResizer({ resize: this.resize, onDrag: this.movePanels });
|
||||||
Discourse.TransitionHelper.after($replyControl, this.resize);
|
Discourse.TransitionHelper.after($replyControl, this.resize);
|
||||||
this.ensureMaximumDimensionForImagesInPreview();
|
this.ensureMaximumDimensionForImagesInPreview();
|
||||||
},
|
}.on('didInsertElement'),
|
||||||
|
|
||||||
ensureMaximumDimensionForImagesInPreview: function() {
|
ensureMaximumDimensionForImagesInPreview: function() {
|
||||||
// This enforce maximum dimensions of images in the preview according
|
// This enforce maximum dimensions of images in the preview according
|
||||||
|
@ -132,7 +125,7 @@ Discourse.ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||||
},
|
},
|
||||||
|
|
||||||
click: function() {
|
click: function() {
|
||||||
this.get('controller').openIfDraft();
|
this.get('controller').send('openIfDraft');
|
||||||
},
|
},
|
||||||
|
|
||||||
// Called after the preview renders. Debounced for performance
|
// Called after the preview renders. Debounced for performance
|
||||||
|
@ -487,19 +480,4 @@ Discourse.ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// not sure if this is the right way, keeping here for now, we could use a mixin perhaps
|
|
||||||
Discourse.NotifyingTextArea = Ember.TextArea.extend({
|
|
||||||
placeholder: function() {
|
|
||||||
return I18n.t(this.get('placeholderKey'));
|
|
||||||
}.property('placeholderKey'),
|
|
||||||
|
|
||||||
didInsertElement: function() {
|
|
||||||
return this.get('parent').childDidInsertElement(this);
|
|
||||||
},
|
|
||||||
|
|
||||||
willDestroyElement: function() {
|
|
||||||
return this.get('parent').childWillDestroyElement(this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
RSVP.EventTarget.mixin(Discourse.ComposerView);
|
RSVP.EventTarget.mixin(Discourse.ComposerView);
|
||||||
|
|
|
@ -23,6 +23,15 @@ class Plugin::Instance
|
||||||
@metadata = metadata
|
@metadata = metadata
|
||||||
@path = path
|
@path = path
|
||||||
@assets = []
|
@assets = []
|
||||||
|
|
||||||
|
# Automatically include all ES6 JS files
|
||||||
|
if @path
|
||||||
|
dir = File.dirname(@path)
|
||||||
|
Dir.glob("#{dir}/assets/javascripts/**/*.js.es6") do |f|
|
||||||
|
relative = f.sub("#{dir}/assets/", "")
|
||||||
|
register_asset(relative)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def name
|
def name
|
||||||
|
|
Loading…
Reference in New Issue