Many Plugin upgrades.

This commit is contained in:
Robin Ward 2014-05-28 15:32:42 -04:00
parent a644947119
commit 3f9c4100ef
10 changed files with 194 additions and 116 deletions

View File

@ -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;

View File

@ -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')
});

View File

@ -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({

View File

@ -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);
},

View File

@ -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);
}
}

View File

@ -5,3 +5,4 @@
</div>
{{render "modal"}}
{{render "composer"}}

View File

@ -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>

View File

@ -48,6 +48,7 @@
{{/if}}
</h1>
{{/if}}
{{plugin-outlet "topic-title"}}
</div>
</div>
</div>

View File

@ -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);

View File

@ -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