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() {
|
||||
|
||||
// Load any ES6 initializers
|
||||
Ember.keys(requirejs._eak_seen).filter(function(key) {
|
||||
return (/\/initializers\//).test(key);
|
||||
}).forEach(function(moduleName) {
|
||||
var module = require(moduleName, null, null, true);
|
||||
if (!module) { throw new Error(moduleName + ' must export an initializer.'); }
|
||||
Discourse.initializer(module.default);
|
||||
Ember.keys(requirejs._eak_seen).forEach(function(key) {
|
||||
if (/\/initializers\//.test(key)) {
|
||||
var module = require(key, null, null, true);
|
||||
if (!module) { throw new Error(key + ' must export an initializer.'); }
|
||||
Discourse.initializer(module.default);
|
||||
}
|
||||
});
|
||||
|
||||
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,
|
||||
editReason: null,
|
||||
|
||||
init: function() {
|
||||
this._super();
|
||||
this.set('similarTopics', Em.A());
|
||||
},
|
||||
_initializeSimilar: function() {
|
||||
this.set('similarTopics', []);
|
||||
}.on('init'),
|
||||
|
||||
actions: {
|
||||
// Toggle the reply view
|
||||
|
@ -45,7 +44,20 @@ export default Discourse.Controller.extend({
|
|||
|
||||
displayEditReason: function() {
|
||||
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() {
|
||||
|
@ -233,89 +245,74 @@ export default Discourse.Controller.extend({
|
|||
open: function(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) {
|
||||
alert("composer was opened without a 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 view = this.get('view');
|
||||
var self = this;
|
||||
if (!view) {
|
||||
var composerMessages = this.get('controllers.composerMessages'),
|
||||
self = this,
|
||||
composerModel = this.get('model');
|
||||
|
||||
// TODO: We should refactor how composer is inserted. It should probably use a
|
||||
// {{render}} and then the controller and view will be wired up automatically.
|
||||
var appView = Discourse.__container__.lookup('view:application');
|
||||
view = appView.createChildView(Discourse.ComposerView, {controller: this});
|
||||
view.appendTo($('#main'));
|
||||
this.set('view', view);
|
||||
this.setProperties({ showEditReason: false, editReason: null });
|
||||
composerMessages.reset();
|
||||
this.set('view', this.container.lookup('view:composer'));
|
||||
|
||||
// the next runloop is too soon, need to get the control rendered and then
|
||||
// we need to change stuff, otherwise css animations don't kick in
|
||||
Em.run.next(function() {
|
||||
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) {
|
||||
// If we want a different draft than the current composer, close it and clear our model.
|
||||
if (composerModel && opts.draftKey !== composerModel.draftKey &&
|
||||
composerModel.composeState === Discourse.Composer.DRAFT) {
|
||||
this.close();
|
||||
composer = null;
|
||||
composerModel = null;
|
||||
}
|
||||
|
||||
if (composer && !opts.tested && composer.get('replyDirty')) {
|
||||
if (composer.composeState === Discourse.Composer.DRAFT && composer.draftKey === opts.draftKey && composer.action === opts.action) {
|
||||
composer.set('composeState', Discourse.Composer.OPEN);
|
||||
promise.resolve();
|
||||
return promise;
|
||||
} else {
|
||||
opts.tested = true;
|
||||
if (!opts.ignoreIfChanged) {
|
||||
this.cancelComposer().then(function() { self.open(opts); }).catch(function() { return promise.reject(); });
|
||||
return new Ember.RSVP.Promise(function(resolve, reject) {
|
||||
if (composerModel && composerModel.get('replyDirty')) {
|
||||
if (composerModel.get('composeState') === Discourse.Composer.DRAFT &&
|
||||
composerModel.get('draftKey') === opts.draftKey &&
|
||||
composerModel.action === opts.action) {
|
||||
|
||||
// If it's the same draft, just open it up again.
|
||||
composerModel.set('composeState', Discourse.Composer.OPEN);
|
||||
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
|
||||
if (opts.draftSequence === void 0) {
|
||||
Discourse.Draft.get(opts.draftKey).then(function(data) {
|
||||
opts.draftSequence = data.draft_sequence;
|
||||
opts.draft = data.draft;
|
||||
return self.open(opts);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
// we need a draft sequence for the composer to work
|
||||
if (opts.draftSequence === void 0) {
|
||||
return Discourse.Draft.get(opts.draftKey).then(function(data) {
|
||||
opts.draftSequence = data.draft_sequence;
|
||||
opts.draft = data.draft;
|
||||
self._setModel(composerModel, opts);
|
||||
}).then(resolve, reject);
|
||||
}
|
||||
|
||||
self._setModel(composerModel, opts);
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
|
||||
// Given a potential instance and options, set the model for this composer.
|
||||
_setModel: function(composerModel, opts) {
|
||||
if (opts.draft) {
|
||||
composer = Discourse.Composer.loadDraft(opts.draftKey, opts.draftSequence, opts.draft);
|
||||
if (composer) {
|
||||
composer.set('topic', opts.topic);
|
||||
composerModel = Discourse.Composer.loadDraft(opts.draftKey, opts.draftSequence, opts.draft);
|
||||
if (composerModel) {
|
||||
composerModel.set('topic', opts.topic);
|
||||
}
|
||||
} else {
|
||||
composer = composer || Discourse.Composer.create();
|
||||
composer.open(opts);
|
||||
composerModel = composerModel || Discourse.Composer.create();
|
||||
composerModel.open(opts);
|
||||
}
|
||||
|
||||
this.set('model', composer);
|
||||
composer.set('composeState', Discourse.Composer.OPEN);
|
||||
composerMessages.queryFor(this.get('model'));
|
||||
promise.resolve();
|
||||
return promise;
|
||||
this.set('model', composerModel);
|
||||
composerModel.set('composeState', Discourse.Composer.OPEN);
|
||||
|
||||
var composerMessages = this.get('controllers.composerMessages');
|
||||
composerMessages.queryFor(composerModel);
|
||||
},
|
||||
|
||||
// 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() {
|
||||
if (this.get('model.replyDirty')) {
|
||||
|
@ -388,13 +380,6 @@ export default Discourse.Controller.extend({
|
|||
$('#wmd-input').autocomplete({ cancel: true });
|
||||
},
|
||||
|
||||
// ESC key hit
|
||||
hitEsc: function() {
|
||||
if (this.get('model.viewOpen')) {
|
||||
this.shrink();
|
||||
}
|
||||
},
|
||||
|
||||
showOptions: function() {
|
||||
var _ref;
|
||||
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);
|
||||
},
|
||||
|
||||
resolveHelper: function(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
resolveController: function(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>
|
||||
|
||||
{{render "modal"}}
|
||||
{{render "composer"}}
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
{{#if model.viewOpen}}
|
||||
<div class='control-row reply-area'>
|
||||
{{plugin-outlet "composer-open"}}
|
||||
|
||||
<div class='reply-to'>
|
||||
{{{model.actionTitle}}}:
|
||||
{{#if canEdit}}
|
||||
|
@ -57,7 +59,7 @@
|
|||
<div class='textarea-wrapper'>
|
||||
<div class='wmd-button-bar' id='wmd-button-bar'></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}}
|
||||
</div>
|
||||
<div class='preview-wrapper'>
|
||||
|
@ -75,7 +77,7 @@
|
|||
<a {{action showUploadSelector view}} class='mobile-file-upload'>{{i18n upload}}</a>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
<div id='draft-status'></div>
|
||||
<div id='draft-status'>{{model.draftStatus}}</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
{{/if}}
|
||||
</h1>
|
||||
{{/if}}
|
||||
{{plugin-outlet "topic-title"}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -26,15 +26,9 @@ Discourse.ComposerView = Discourse.View.extend(Ember.Evented, {
|
|||
content: Em.computed.alias('model'),
|
||||
|
||||
composeState: function() {
|
||||
var state = this.get('model.composeState');
|
||||
if (state) return state;
|
||||
return Discourse.Composer.CLOSED;
|
||||
return this.get('model.composeState') || Discourse.Composer.CLOSED;
|
||||
}.property('model.composeState'),
|
||||
|
||||
draftStatus: function() {
|
||||
$('#draft-status').text(this.get('model.draftStatus') || "");
|
||||
}.observes('model.draftStatus'),
|
||||
|
||||
// Disable fields when we're loading
|
||||
loadingChanged: function() {
|
||||
if (this.get('loading')) {
|
||||
|
@ -48,7 +42,6 @@ Discourse.ComposerView = Discourse.View.extend(Ember.Evented, {
|
|||
return this.present('controller.createdPost') ? 'created-post' : null;
|
||||
}.property('model.createdPost'),
|
||||
|
||||
|
||||
refreshPreview: Discourse.debounce(function() {
|
||||
if (this.editor) {
|
||||
this.editor.refreshPreview();
|
||||
|
@ -101,7 +94,7 @@ Discourse.ComposerView = Discourse.View.extend(Ember.Evented, {
|
|||
keyDown: function(e) {
|
||||
if (e.which === 27) {
|
||||
// ESC
|
||||
this.get('controller').hitEsc();
|
||||
this.get('controller').send('hitEsc');
|
||||
return false;
|
||||
} else if (e.which === 13 && (e.ctrlKey || e.metaKey)) {
|
||||
// CTRL+ENTER or CMD+ENTER
|
||||
|
@ -110,12 +103,12 @@ Discourse.ComposerView = Discourse.View.extend(Ember.Evented, {
|
|||
}
|
||||
},
|
||||
|
||||
didInsertElement: function() {
|
||||
_enableResizing: function() {
|
||||
var $replyControl = $('#reply-control');
|
||||
$replyControl.DivResizer({ resize: this.resize, onDrag: this.movePanels });
|
||||
Discourse.TransitionHelper.after($replyControl, this.resize);
|
||||
this.ensureMaximumDimensionForImagesInPreview();
|
||||
},
|
||||
}.on('didInsertElement'),
|
||||
|
||||
ensureMaximumDimensionForImagesInPreview: function() {
|
||||
// This enforce maximum dimensions of images in the preview according
|
||||
|
@ -132,7 +125,7 @@ Discourse.ComposerView = Discourse.View.extend(Ember.Evented, {
|
|||
},
|
||||
|
||||
click: function() {
|
||||
this.get('controller').openIfDraft();
|
||||
this.get('controller').send('openIfDraft');
|
||||
},
|
||||
|
||||
// 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);
|
||||
|
|
|
@ -23,6 +23,15 @@ class Plugin::Instance
|
|||
@metadata = metadata
|
||||
@path = path
|
||||
@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
|
||||
|
||||
def name
|
||||
|
|
Loading…
Reference in New Issue