Version bump
This commit is contained in:
commit
bcccec1ea6
@ -33,6 +33,9 @@
|
||||
"triggerEvent",
|
||||
"count",
|
||||
"exists",
|
||||
"visible",
|
||||
"invisible",
|
||||
"selectDropdown",
|
||||
"asyncTestDiscourse",
|
||||
"fixture",
|
||||
"find",
|
||||
|
||||
@ -8,6 +8,9 @@ env:
|
||||
- "RAILS_MASTER=1"
|
||||
- "RAILS_MASTER=0"
|
||||
|
||||
addons:
|
||||
postgresql: 9.3
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rvm: 2.0.0
|
||||
|
||||
11
Gemfile
11
Gemfile
@ -244,8 +244,15 @@ gem 'simple-rss', require: false
|
||||
# TODO mri_22 should be here, but bundler was real slow to pick it up
|
||||
# not even in production bundler yet, monkey patching it in feels bad
|
||||
gem 'gctools', require: false, platform: :mri_21
|
||||
gem 'stackprof', require: false, platform: :mri_21
|
||||
gem 'memory_profiler', require: false, platform: :mri_21
|
||||
|
||||
begin
|
||||
gem 'stackprof', require: false, platform: [:mri_21, :mri_22]
|
||||
gem 'memory_profiler', require: false, platform: [:mri_21, :mri_22]
|
||||
rescue Bundler::GemfileError
|
||||
STDERR.puts "You are running an old version of bundler, please upgrade bundler ASAP, if you are using Discourse docker, rebuild your container."
|
||||
gem 'stackprof', require: false, platform: [:mri_21]
|
||||
gem 'memory_profiler', require: false, platform: [:mri_21]
|
||||
end
|
||||
|
||||
gem 'rmmseg-cpp', require: false
|
||||
|
||||
|
||||
@ -207,7 +207,7 @@ GEM
|
||||
thor (~> 0.15)
|
||||
libv8 (3.16.14.7)
|
||||
listen (0.7.3)
|
||||
logster (0.1.8)
|
||||
logster (0.8.0)
|
||||
lru_redux (0.8.4)
|
||||
mail (2.5.4)
|
||||
mime-types (~> 1.16)
|
||||
@ -422,7 +422,7 @@ GEM
|
||||
sprockets (~> 2.8)
|
||||
stackprof (0.2.7)
|
||||
stringex (2.5.2)
|
||||
therubyracer (0.12.1)
|
||||
therubyracer (0.12.2)
|
||||
libv8 (~> 3.16.14.0)
|
||||
ref
|
||||
thin (1.6.3)
|
||||
|
||||
@ -83,7 +83,7 @@ The original Discourse code contributors can be found in [**AUTHORS.MD**](docs/A
|
||||
|
||||
## Copyright / License
|
||||
|
||||
Copyright 2014 Civilized Discourse Construction Kit, Inc.
|
||||
Copyright 2014 - 2015 Civilized Discourse Construction Kit, Inc.
|
||||
|
||||
Licensed under the GNU General Public License Version 2.0 (or later);
|
||||
you may not use this work except in compliance with the License.
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import Presence from 'discourse/mixins/presence';
|
||||
import { outputExportResult } from 'discourse/lib/export-result';
|
||||
|
||||
export default Ember.ArrayController.extend(Discourse.Presence, {
|
||||
export default Ember.ArrayController.extend(Presence, {
|
||||
loading: false,
|
||||
|
||||
actions: {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import Presence from 'discourse/mixins/presence';
|
||||
import { outputExportResult } from 'discourse/lib/export-result';
|
||||
|
||||
export default Ember.ArrayController.extend(Discourse.Presence, {
|
||||
export default Ember.ArrayController.extend(Presence, {
|
||||
loading: false,
|
||||
itemController: 'admin-log-screened-ip-address',
|
||||
filter: null,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import Presence from 'discourse/mixins/presence';
|
||||
import { outputExportResult } from 'discourse/lib/export-result';
|
||||
|
||||
export default Ember.ArrayController.extend(Discourse.Presence, {
|
||||
export default Ember.ArrayController.extend(Presence, {
|
||||
loading: false,
|
||||
|
||||
show() {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import Presence from 'discourse/mixins/presence';
|
||||
import { outputExportResult } from 'discourse/lib/export-result';
|
||||
|
||||
export default Ember.ArrayController.extend(Discourse.Presence, {
|
||||
export default Ember.ArrayController.extend(Presence, {
|
||||
loading: false,
|
||||
filters: null,
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export default Ember.ArrayController.extend(Discourse.Presence, {
|
||||
import Presence from 'discourse/mixins/presence';
|
||||
|
||||
export default Ember.ArrayController.extend(Presence, {
|
||||
filter: null,
|
||||
onlyOverridden: false,
|
||||
filtered: Ember.computed.notEmpty('filter'),
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
const AdminUser = Discourse.User.extend({
|
||||
|
||||
customGroups: Em.computed.filter("groups", (g) => !g.automatic && g.visible && Discourse.Group.create(g)),
|
||||
customGroups: Em.computed.filter("groups", (g) => !g.automatic && Discourse.Group.create(g)),
|
||||
automaticGroups: Em.computed.filter("groups", (g) => g.automatic && Discourse.Group.create(g)),
|
||||
|
||||
generateApiKey() {
|
||||
|
||||
@ -24,8 +24,8 @@ export default Ember.Route.extend({
|
||||
},
|
||||
|
||||
editGroupings() {
|
||||
const groupings = this.controllerFor('admin-badges').get('badgeGroupings');
|
||||
showModal('modals/admin-edit-badge-groupings', groupings);
|
||||
const model = this.controllerFor('admin-badges').get('badgeGroupings');
|
||||
showModal('modals/admin-edit-badge-groupings', { model });
|
||||
},
|
||||
|
||||
preview(badge, explain) {
|
||||
@ -38,9 +38,9 @@ export default Ember.Route.extend({
|
||||
trigger: badge.get('trigger'),
|
||||
explain
|
||||
}
|
||||
}).then(function(json) {
|
||||
}).then(function(model) {
|
||||
badge.set('preview_loading', false);
|
||||
showModal('modals/admin-badge-preview', json);
|
||||
showModal('modals/admin-badge-preview', { model });
|
||||
}).catch(function(error) {
|
||||
badge.set('preview_loading', false);
|
||||
Em.Logger.error(error);
|
||||
|
||||
@ -12,13 +12,13 @@ export default Discourse.Route.extend({
|
||||
},
|
||||
|
||||
actions: {
|
||||
showAgreeFlagModal(flaggedPost) {
|
||||
showModal('modals/admin-agree-flag', flaggedPost);
|
||||
showAgreeFlagModal(model) {
|
||||
showModal('modals/admin-agree-flag', { model });
|
||||
this.controllerFor('modal').set('modalClass', 'agree-flag-modal');
|
||||
},
|
||||
|
||||
showDeleteFlagModal(flaggedPost) {
|
||||
showModal('modals/admin-delete-flag', flaggedPost);
|
||||
showDeleteFlagModal(model) {
|
||||
showModal('modals/admin-delete-flag', { model });
|
||||
this.controllerFor('modal').set('modalClass', 'delete-flag-modal');
|
||||
}
|
||||
|
||||
|
||||
@ -12,14 +12,14 @@ export default Discourse.Route.extend({
|
||||
},
|
||||
|
||||
actions: {
|
||||
showDetailsModal(logRecord) {
|
||||
showModal('modals/admin-staff-action-log-details', logRecord);
|
||||
showDetailsModal(model) {
|
||||
showModal('modals/admin-staff-action-log-details', { model });
|
||||
this.controllerFor('modal').set('modalClass', 'log-details-modal');
|
||||
},
|
||||
|
||||
showCustomDetailsModal(logRecord) {
|
||||
const modalName = "modals/" + (logRecord.action_name + '_details').replace("_", "-");
|
||||
showModal(modalName, logRecord);
|
||||
showCustomDetailsModal(model) {
|
||||
const modalName = "modals/" + (model.action_name + '_details').replace("_", "-");
|
||||
showModal(modalName, { model });
|
||||
this.controllerFor('modal').set('modalClass', 'tabbed-modal log-details-modal');
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,8 +24,8 @@ export default Discourse.Route.extend({
|
||||
},
|
||||
|
||||
actions: {
|
||||
showSuspendModal(user) {
|
||||
showModal('modals/admin-suspend-user', user);
|
||||
showSuspendModal(model) {
|
||||
showModal('modals/admin-suspend-user', { model });
|
||||
this.controllerFor('modal').set('modalClass', 'suspend-user-modal');
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<div class='span13'>
|
||||
<div class='current-badge span13'>
|
||||
<p>{{i18n 'admin.badges.none_selected'}}</p>
|
||||
|
||||
<div>
|
||||
|
||||
@ -13,7 +13,9 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{{d-button action="sendInvites" title="admin.invite.button_title" icon="user-plus" label="admin.invite.button_text"}}
|
||||
{{#unless siteSettings.enable_sso}}
|
||||
{{d-button action="sendInvites" title="admin.invite.button_title" icon="user-plus" label="admin.invite.button_text"}}
|
||||
{{/unless}}
|
||||
{{d-button action="exportUsers" title="admin.export_csv.button_title.user" icon="download" label="admin.export_csv.button_text"}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
20
app/assets/javascripts/discourse/adapters/post.js.es6
Normal file
20
app/assets/javascripts/discourse/adapters/post.js.es6
Normal file
@ -0,0 +1,20 @@
|
||||
import RestAdapter from 'discourse/adapters/rest';
|
||||
import { Result } from 'discourse/adapters/rest';
|
||||
|
||||
export default RestAdapter.extend({
|
||||
|
||||
find(store, type, findArgs) {
|
||||
return this._super(store, type, findArgs).then(function(result) {
|
||||
return {post: result};
|
||||
});
|
||||
},
|
||||
|
||||
createRecord(store, type, args) {
|
||||
const typeField = Ember.String.underscore(type);
|
||||
args.nested_post = true;
|
||||
return Discourse.ajax(this.pathFor(store, type), { method: 'POST', data: args }).then(function (json) {
|
||||
return new Result(json[typeField], json);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@ -1,5 +1,21 @@
|
||||
const ADMIN_MODELS = ['plugin'];
|
||||
|
||||
export function Result(payload, responseJson) {
|
||||
this.payload = payload;
|
||||
this.responseJson = responseJson;
|
||||
this.target = null;
|
||||
}
|
||||
|
||||
const ajax = Discourse.ajax;
|
||||
|
||||
// We use this to make sure 404s are caught
|
||||
function rethrow(error) {
|
||||
if (error.status === 404) {
|
||||
throw "404: " + error.responseText;
|
||||
}
|
||||
throw(error);
|
||||
}
|
||||
|
||||
export default Ember.Object.extend({
|
||||
pathFor(store, type, findArgs) {
|
||||
let path = "/" + Ember.String.underscore(store.pluralize(type));
|
||||
@ -25,21 +41,33 @@ export default Ember.Object.extend({
|
||||
},
|
||||
|
||||
findAll(store, type) {
|
||||
return Discourse.ajax(this.pathFor(store, type));
|
||||
return ajax(this.pathFor(store, type)).catch(rethrow);
|
||||
},
|
||||
|
||||
|
||||
find(store, type, findArgs) {
|
||||
return Discourse.ajax(this.pathFor(store, type, findArgs));
|
||||
return ajax(this.pathFor(store, type, findArgs)).catch(rethrow);
|
||||
},
|
||||
|
||||
update(store, type, id, attrs) {
|
||||
const data = {};
|
||||
data[Ember.String.underscore(type)] = attrs;
|
||||
return Discourse.ajax(this.pathFor(store, type, id), { method: 'PUT', data });
|
||||
return ajax(this.pathFor(store, type, id), { method: 'PUT', data }).then(function(json) {
|
||||
return new Result(json[type], json);
|
||||
});
|
||||
},
|
||||
|
||||
createRecord(store, type, attrs) {
|
||||
const data = {};
|
||||
const typeField = Ember.String.underscore(type);
|
||||
data[typeField] = attrs;
|
||||
return ajax(this.pathFor(store, type), { method: 'POST', data }).then(function (json) {
|
||||
return new Result(json[typeField], json);
|
||||
});
|
||||
},
|
||||
|
||||
destroyRecord(store, type, record) {
|
||||
return Discourse.ajax(this.pathFor(store, type, record.get('id')), { method: 'DELETE' });
|
||||
return ajax(this.pathFor(store, type, record.get('id')), { method: 'DELETE' });
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
39
app/assets/javascripts/discourse/adapters/topic-list.js.es6
Normal file
39
app/assets/javascripts/discourse/adapters/topic-list.js.es6
Normal file
@ -0,0 +1,39 @@
|
||||
import RestAdapter from 'discourse/adapters/rest';
|
||||
|
||||
function finderFor(filter, params) {
|
||||
return function() {
|
||||
let url = Discourse.getURL("/") + filter + ".json";
|
||||
|
||||
if (params) {
|
||||
const keys = Object.keys(params),
|
||||
encoded = [];
|
||||
|
||||
keys.forEach(function(p) {
|
||||
const value = params[p];
|
||||
if (typeof value !== 'undefined') {
|
||||
encoded.push(p + "=" + value);
|
||||
}
|
||||
});
|
||||
|
||||
if (encoded.length > 0) {
|
||||
url += "?" + encoded.join('&');
|
||||
}
|
||||
}
|
||||
return Discourse.ajax(url);
|
||||
};
|
||||
}
|
||||
|
||||
export default RestAdapter.extend({
|
||||
|
||||
find(store, type, findArgs) {
|
||||
const filter = findArgs.filter;
|
||||
const params = findArgs.params;
|
||||
|
||||
return PreloadStore.getAndRemove("topic_list_" + filter, finderFor(filter, params)).then(function(result) {
|
||||
result.filter = filter;
|
||||
result.params = params;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -3,7 +3,7 @@ import showModal from 'discourse/lib/show-modal';
|
||||
export default Ember.Component.extend({
|
||||
actions: {
|
||||
showBulkActions() {
|
||||
const controller = showModal('topicBulkActions', this.get('selected'));
|
||||
const controller = showModal('topic-bulk-actions', { model: this.get('selected'), title: 'topics.bulk.actions' });
|
||||
controller.set('refreshTarget', this.get('refreshTarget'));
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,20 +10,20 @@ export default Em.Component.extend(StringBuffer, {
|
||||
rerenderTriggers: ['actionsHistory.@each', 'actionsHistory.users.length', 'post.deleted'],
|
||||
|
||||
// This was creating way too many bound ifs and subviews in the handlebars version.
|
||||
renderString: function(buffer) {
|
||||
renderString(buffer) {
|
||||
if (!this.get('emptyHistory')) {
|
||||
this.get('actionsHistory').forEach(function(c) {
|
||||
buffer.push("<div class='post-action'>");
|
||||
|
||||
var renderActionIf = function(property, dataAttribute, text) {
|
||||
const renderActionIf = function(property, dataAttribute, text) {
|
||||
if (!c.get(property)) { return; }
|
||||
buffer.push(" <span class='action-link " + dataAttribute +"-action'><a href='#' data-" + dataAttribute + "='" + c.get('id') + "'>" + text + "</a>.</span>");
|
||||
};
|
||||
|
||||
// TODO multi line expansion for flags
|
||||
var iconsHtml = "";
|
||||
let iconsHtml = "";
|
||||
if (c.get('usersExpanded')) {
|
||||
var postUrl;
|
||||
let postUrl;
|
||||
c.get('users').forEach(function(u) {
|
||||
iconsHtml += "<a href=\"" + Discourse.getURL("/users/") + u.get('username_lower') + "\" data-user-card=\"" + u.get('username_lower') + "\">";
|
||||
if (u.post_url) {
|
||||
@ -37,7 +37,7 @@ export default Em.Component.extend(StringBuffer, {
|
||||
iconsHtml += "</a>";
|
||||
});
|
||||
|
||||
var key = 'post.actions.people.' + c.get('actionType.name_key');
|
||||
let key = 'post.actions.people.' + c.get('actionType.name_key');
|
||||
if (postUrl) { key = key + "_with_url"; }
|
||||
|
||||
// TODO postUrl might be uninitialized? pick a good default
|
||||
@ -52,7 +52,7 @@ export default Em.Component.extend(StringBuffer, {
|
||||
});
|
||||
}
|
||||
|
||||
var post = this.get('post');
|
||||
const post = this.get('post');
|
||||
if (post.get('deleted')) {
|
||||
buffer.push("<div class='post-action'>" +
|
||||
"<i class='fa fa-trash-o'></i> " +
|
||||
@ -62,32 +62,34 @@ export default Em.Component.extend(StringBuffer, {
|
||||
}
|
||||
},
|
||||
|
||||
actionTypeById: function(actionTypeId) {
|
||||
actionTypeById(actionTypeId) {
|
||||
return this.get('actionsHistory').findProperty('id', actionTypeId);
|
||||
},
|
||||
|
||||
click: function(e) {
|
||||
var $target = $(e.target),
|
||||
actionTypeId;
|
||||
click(e) {
|
||||
const $target = $(e.target);
|
||||
let actionTypeId;
|
||||
|
||||
const post = this.get('post');
|
||||
|
||||
if (actionTypeId = $target.data('defer-flags')) {
|
||||
this.actionTypeById(actionTypeId).deferFlags();
|
||||
this.actionTypeById(actionTypeId).deferFlags(post);
|
||||
return false;
|
||||
}
|
||||
|
||||
// User wants to know who actioned it
|
||||
if (actionTypeId = $target.data('who-acted')) {
|
||||
this.actionTypeById(actionTypeId).loadUsers();
|
||||
this.actionTypeById(actionTypeId).loadUsers(post);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (actionTypeId = $target.data('act')) {
|
||||
this.get('actionsHistory').findProperty('id', actionTypeId).act();
|
||||
this.get('actionsHistory').findProperty('id', actionTypeId).act(post);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (actionTypeId = $target.data('undo')) {
|
||||
this.get('actionsHistory').findProperty('id', actionTypeId).undo();
|
||||
this.get('actionsHistory').findProperty('id', actionTypeId).undo(post);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'a',
|
||||
attributeBindings: ['href', 'data-user-card'],
|
||||
href: Ember.computed.alias('user.path'),
|
||||
'data-user-card': Ember.computed.alias('user.username_lower')
|
||||
});
|
||||
@ -7,7 +7,8 @@ export default TextField.extend({
|
||||
var self = this,
|
||||
selected = [],
|
||||
currentUser = this.currentUser,
|
||||
includeGroups = this.get('includeGroups') === 'true';
|
||||
includeGroups = this.get('includeGroups') === 'true',
|
||||
allowedUsers = this.get('allowedUsers') === 'true';
|
||||
|
||||
function excludedUsernames() {
|
||||
if (currentUser && self.get('excludeCurrentUser')) {
|
||||
@ -27,7 +28,8 @@ export default TextField.extend({
|
||||
term: term.replace(/[^a-zA-Z0-9_]/, ''),
|
||||
topicId: self.get('topicId'),
|
||||
exclude: excludedUsernames(),
|
||||
includeGroups: includeGroups
|
||||
includeGroups,
|
||||
allowedUsers
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -61,7 +61,8 @@ export default DiscourseController.extend({
|
||||
if (postId) {
|
||||
this.set('model.loading', true);
|
||||
const composer = this;
|
||||
return Discourse.Post.load(postId).then(function(post) {
|
||||
|
||||
return this.store.find('post', postId).then(function(post) {
|
||||
const quote = Discourse.Quote.build(post, post.get("raw"));
|
||||
composer.appendBlockAtCursor(quote);
|
||||
composer.set('model.loading', false);
|
||||
@ -82,6 +83,13 @@ export default DiscourseController.extend({
|
||||
},
|
||||
|
||||
hitEsc() {
|
||||
|
||||
const messages = this.get('controllers.composer-messages.model');
|
||||
if (messages.length) {
|
||||
messages.popObject();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.get('model.viewOpen')) {
|
||||
this.shrink();
|
||||
}
|
||||
@ -217,13 +225,20 @@ export default DiscourseController.extend({
|
||||
const promise = composer.save({
|
||||
imageSizes: this.get('view').imageSizes(),
|
||||
editReason: this.get("editReason")
|
||||
}).then(function(opts) {
|
||||
}).then(function(result) {
|
||||
|
||||
if (result.responseJson.action === "enqueued") {
|
||||
self.send('postWasEnqueued');
|
||||
self.destroyDraft();
|
||||
self.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
// If we replied as a new topic successfully, remove the draft.
|
||||
if (self.get('replyAsNewTopicDraft')) {
|
||||
self.destroyDraft();
|
||||
}
|
||||
|
||||
opts = opts || {};
|
||||
self.close();
|
||||
|
||||
const currentUser = Discourse.User.current();
|
||||
@ -236,16 +251,16 @@ export default DiscourseController.extend({
|
||||
// TODO disableJumpReply is super crude, it needs to provide some sort
|
||||
// of notification to the end user
|
||||
if (!composer.get('replyingToTopic') || !disableJumpReply) {
|
||||
if (opts.post && !staged) {
|
||||
Discourse.URL.routeTo(opts.post.get('url'));
|
||||
const post = result.target;
|
||||
if (post && !staged) {
|
||||
Discourse.URL.routeTo(post.get('url'));
|
||||
}
|
||||
}
|
||||
}).catch(function(error) {
|
||||
composer.set('disableDrafts', false);
|
||||
bootbox.alert(error);
|
||||
self.appEvents.one('composer:opened', () => bootbox.alert(error));
|
||||
});
|
||||
|
||||
|
||||
if (this.get('controllers.application.currentRouteName').split('.')[0] === 'topic' &&
|
||||
composer.get('topic.id') === this.get('controllers.topic.model.id')) {
|
||||
staged = composer.get('stagedPost');
|
||||
@ -412,7 +427,7 @@ export default DiscourseController.extend({
|
||||
composerModel.set('topic', opts.topic);
|
||||
}
|
||||
} else {
|
||||
composerModel = composerModel || Discourse.Composer.create();
|
||||
composerModel = composerModel || this.store.createRecord('composer');
|
||||
composerModel.open(opts);
|
||||
}
|
||||
|
||||
|
||||
@ -1 +1,3 @@
|
||||
export default Ember.Controller.extend(Discourse.Presence);
|
||||
import Presence from 'discourse/mixins/presence';
|
||||
|
||||
export default Ember.Controller.extend(Presence);
|
||||
|
||||
@ -48,7 +48,8 @@ var controllerOpts = {
|
||||
// router and ember throws an error due to missing `handlerInfos`.
|
||||
// Lesson learned: Don't call `loading` yourself.
|
||||
this.set('controllers.discovery.loading', true);
|
||||
Discourse.TopicList.find(filter).then(function(list) {
|
||||
|
||||
this.store.findFiltered('topicList', {filter}).then(function(list) {
|
||||
Discourse.TopicList.hideUniformCategory(list, self.get('category'));
|
||||
|
||||
self.setProperties({ model: list });
|
||||
|
||||
@ -81,7 +81,8 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||
if (opts) params = $.extend(params, opts);
|
||||
|
||||
this.send('hideModal');
|
||||
postAction.act(params).then(function() {
|
||||
|
||||
postAction.act(this.get('model'), params).then(function() {
|
||||
self.send('closeModal');
|
||||
}, function(errors) {
|
||||
self.send('closeModal');
|
||||
|
||||
@ -99,6 +99,7 @@ HeaderController.reopenClass({
|
||||
});
|
||||
|
||||
addFlagProperty('currentUser.site_flagged_posts_count');
|
||||
addFlagProperty('currentUser.post_queue_new_count');
|
||||
|
||||
export { addFlagProperty };
|
||||
export default HeaderController;
|
||||
|
||||
@ -15,11 +15,15 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||
disabled: function() {
|
||||
if (this.get('saving')) return true;
|
||||
if (this.blank('emailOrUsername')) return true;
|
||||
// when inviting to forum, email must be valid
|
||||
if (!this.get('invitingToTopic') && !Discourse.Utilities.emailValid(this.get('emailOrUsername'))) return true;
|
||||
// normal users (not admin) can't invite users to private topic via email
|
||||
if (!this.get('isAdmin') && this.get('isPrivateTopic') && Discourse.Utilities.emailValid(this.get('emailOrUsername'))) return true;
|
||||
// when invting to private topic via email, group name must be specified
|
||||
if (this.get('isPrivateTopic') && this.blank('groupNames') && Discourse.Utilities.emailValid(this.get('emailOrUsername'))) return true;
|
||||
if (this.get('model.details.can_invite_to')) return false;
|
||||
if (this.get('isPrivateTopic') && this.blank('groupNames')) return true;
|
||||
return false;
|
||||
}.property('emailOrUsername', 'invitingToTopic', 'isPrivateTopic', 'groupNames', 'saving'),
|
||||
}.property('isAdmin', 'emailOrUsername', 'invitingToTopic', 'isPrivateTopic', 'groupNames', 'saving'),
|
||||
|
||||
buttonTitle: function() {
|
||||
return this.get('saving') ? I18n.t('topic.inviting') : I18n.t('topic.invite_reply.action');
|
||||
@ -31,20 +35,23 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||
return this.get('model') !== Discourse.User.current();
|
||||
}.property('model'),
|
||||
|
||||
topicId: Ember.computed.alias('model.id'),
|
||||
|
||||
// Is Private Topic? (i.e. visible only to specific group members)
|
||||
isPrivateTopic: Em.computed.and('invitingToTopic', 'model.category.read_restricted'),
|
||||
|
||||
// Is Private Message?
|
||||
isMessage: Em.computed.equal('model.archetype', 'private_message'),
|
||||
|
||||
// Allow Existing Members? (username autocomplete)
|
||||
allowExistingMembers: function() {
|
||||
return this.get('invitingToTopic') && !this.get('isPrivateTopic');
|
||||
}.property('invitingToTopic', 'isPrivateTopic'),
|
||||
return this.get('invitingToTopic');
|
||||
}.property('invitingToTopic'),
|
||||
|
||||
// Show Groups? (add invited user to private group)
|
||||
showGroups: function() {
|
||||
return this.get('isAdmin') && (Discourse.Utilities.emailValid(this.get('emailOrUsername')) || this.get('isPrivateTopic') || !this.get('invitingToTopic')) && !Discourse.SiteSettings.enable_sso;
|
||||
}.property('isAdmin', 'emailOrUsername', 'isPrivateTopic', 'invitingToTopic'),
|
||||
return this.get('isAdmin') && (Discourse.Utilities.emailValid(this.get('emailOrUsername')) || this.get('isPrivateTopic') || !this.get('invitingToTopic')) && !Discourse.SiteSettings.enable_sso && !this.get('isMessage');
|
||||
}.property('isAdmin', 'emailOrUsername', 'isPrivateTopic', 'isMessage', 'invitingToTopic'),
|
||||
|
||||
// Instructional text for the modal.
|
||||
inviteInstructions: function() {
|
||||
@ -55,13 +62,19 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||
// inviting to a message
|
||||
return I18n.t('topic.invite_private.email_or_username');
|
||||
} else if (this.get('invitingToTopic')) {
|
||||
// when inviting to topic, display instructions based on provided entity
|
||||
if (this.blank('emailOrUsername')) {
|
||||
return I18n.t('topic.invite_reply.to_topic_blank');
|
||||
} else if (Discourse.Utilities.emailValid(this.get('emailOrUsername'))) {
|
||||
return I18n.t('topic.invite_reply.to_topic_email');
|
||||
// inviting to a private/public topic
|
||||
if (this.get('isPrivateTopic') && !this.get('isAdmin')) {
|
||||
// inviting to a private topic and is not admin
|
||||
return I18n.t('topic.invite_reply.to_username');
|
||||
} else {
|
||||
return I18n.t('topic.invite_reply.to_topic_username');
|
||||
// when inviting to a topic, display instructions based on provided entity
|
||||
if (this.blank('emailOrUsername')) {
|
||||
return I18n.t('topic.invite_reply.to_topic_blank');
|
||||
} else if (Discourse.Utilities.emailValid(this.get('emailOrUsername'))) {
|
||||
return I18n.t('topic.invite_reply.to_topic_email');
|
||||
} else {
|
||||
return I18n.t('topic.invite_reply.to_topic_username');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// inviting to forum
|
||||
|
||||
@ -20,7 +20,7 @@ export default ObjectController.extend({
|
||||
var badgeId = this.safe("data.badge_id");
|
||||
if (badgeId) {
|
||||
var badgeName = this.safe("data.badge_name");
|
||||
return '/badges/' + badgeId + '/' + badgeName.replace(/[^A-Za-z0-9_]+/g, '-').toLowerCase();
|
||||
return Discourse.getURL('/badges/' + badgeId + '/' + badgeName.replace(/[^A-Za-z0-9_]+/g, '-').toLowerCase());
|
||||
}
|
||||
|
||||
var topicId = this.safe('topic_id');
|
||||
@ -29,7 +29,7 @@ export default ObjectController.extend({
|
||||
}
|
||||
|
||||
if (this.get('notification_type') === INVITED_TYPE) {
|
||||
return '/my/invited';
|
||||
return Discourse.getURL('/my/invited');
|
||||
}
|
||||
}.property("data.{badge_id,badge_name}", "slug", "topic_id", "post_number"),
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export default Ember.ArrayController.extend({
|
||||
needs: ['header'],
|
||||
loadingNotifications: Em.computed.alias('controllers.header.loadingNotifications')
|
||||
loadingNotifications: Em.computed.alias('controllers.header.loadingNotifications'),
|
||||
|
||||
myNotificationsUrl: Discourse.computed.url('/my/notifications')
|
||||
});
|
||||
|
||||
@ -1 +1,3 @@
|
||||
export default Ember.ObjectController.extend(Discourse.Presence);
|
||||
import Presence from 'discourse/mixins/presence';
|
||||
|
||||
export default Ember.ObjectController.extend(Presence);
|
||||
|
||||
@ -67,7 +67,7 @@ export default ObjectController.extend(CanCheckEmails, {
|
||||
{ name: I18n.t('user.email_digests.every_two_weeks'), value: 14 }],
|
||||
|
||||
autoTrackDurations: [{ name: I18n.t('user.auto_track_options.never'), value: -1 },
|
||||
{ name: I18n.t('user.auto_track_options.always'), value: 0 },
|
||||
{ name: I18n.t('user.auto_track_options.immediately'), value: 0 },
|
||||
{ name: I18n.t('user.auto_track_options.after_n_seconds', { count: 30 }), value: 30000 },
|
||||
{ name: I18n.t('user.auto_track_options.after_n_minutes', { count: 1 }), value: 60000 },
|
||||
{ name: I18n.t('user.auto_track_options.after_n_minutes', { count: 2 }), value: 120000 },
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
import BufferedContent from 'discourse/mixins/buffered-content';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
function updateState(state) {
|
||||
return function() {
|
||||
const post = this.get('post');
|
||||
post.update({ state }).then(() => {
|
||||
this.get('controllers.queued-posts.model').removeObject(post);
|
||||
}).catch(popupAjaxError);
|
||||
};
|
||||
}
|
||||
|
||||
export default Ember.Controller.extend(BufferedContent, {
|
||||
needs: ['queued-posts'],
|
||||
post: Ember.computed.alias('model'),
|
||||
currentlyEditing: Ember.computed.alias('controllers.queued-posts.editing'),
|
||||
|
||||
editing: Discourse.computed.propertyEqual('model', 'currentlyEditing'),
|
||||
|
||||
actions: {
|
||||
approve: updateState('approved'),
|
||||
reject: updateState('rejected'),
|
||||
|
||||
edit() {
|
||||
// This is stupid but pagedown cannot be on the screen twice or it will break
|
||||
this.set('currentlyEditing', null);
|
||||
Ember.run.scheduleOnce('afterRender', () => this.set('currentlyEditing', this.get('model')));
|
||||
},
|
||||
|
||||
confirmEdit() {
|
||||
this.get('post').update({ raw: this.get('buffered.raw') }).then(() => {
|
||||
this.commitBuffer();
|
||||
this.set('currentlyEditing', null);
|
||||
});
|
||||
},
|
||||
|
||||
cancelEdit() {
|
||||
this.rollbackBuffer();
|
||||
this.set('currentlyEditing', null);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,8 +1,9 @@
|
||||
import Presence from 'discourse/mixins/presence';
|
||||
import searchForTerm from 'discourse/lib/search-for-term';
|
||||
|
||||
var _dontSearch = false;
|
||||
|
||||
export default Em.Controller.extend(Discourse.Presence, {
|
||||
export default Em.Controller.extend(Presence, {
|
||||
|
||||
contextType: function(key, value){
|
||||
if(arguments.length > 1) {
|
||||
|
||||
@ -8,7 +8,7 @@ export default Ember.ArrayController.extend({
|
||||
return Discourse.SiteSettings.faq_url ? Discourse.SiteSettings.faq_url : Discourse.getURL('/faq');
|
||||
}.property(),
|
||||
|
||||
badgesUrl: Discourse.getURL('/badges'),
|
||||
badgesUrl: Discourse.computed.url('/badges'),
|
||||
|
||||
showKeyboardShortcuts: function(){
|
||||
return !Discourse.Mobile.mobileView && !this.capabilities.touch;
|
||||
|
||||
@ -150,7 +150,7 @@ export default ObjectController.extend(Discourse.SelectedPostsCount, BufferedCon
|
||||
toggleLike(post) {
|
||||
const likeAction = post.get('actionByName.like');
|
||||
if (likeAction && likeAction.get('canToggle')) {
|
||||
likeAction.toggle();
|
||||
likeAction.toggle(post);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -19,10 +19,7 @@ export default ObjectController.extend(CanCheckEmails, {
|
||||
|
||||
linkWebsite: Em.computed.not('isBasic'),
|
||||
|
||||
canSeePrivateMessages: function() {
|
||||
return this.get('viewingSelf') || Discourse.User.currentProp('admin');
|
||||
}.property('viewingSelf'),
|
||||
|
||||
canSeePrivateMessages: Ember.computed.or('viewingSelf', 'currentUser.admin'),
|
||||
canSeeNotificationHistory: Em.computed.alias('canSeePrivateMessages'),
|
||||
|
||||
showBadges: function() {
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import registerUnbound from 'discourse/helpers/register-unbound';
|
||||
|
||||
registerUnbound('cook-text', function(text) {
|
||||
return new Handlebars.SafeString(Discourse.Markdown.cook(text));
|
||||
});
|
||||
|
||||
@ -27,10 +27,13 @@ export default {
|
||||
bus.callbackInterval = siteSettings.polling_interval;
|
||||
bus.enableLongPolling = true;
|
||||
|
||||
if (user.admin || user.moderator) {
|
||||
bus.subscribe('/flagged_counts', function(data) {
|
||||
if (user.get('staff')) {
|
||||
bus.subscribe('/flagged_counts', (data) => {
|
||||
user.set('site_flagged_posts_count', data.total);
|
||||
});
|
||||
bus.subscribe('/queue_counts', (data) => {
|
||||
user.set('post_queue_new_count', data.post_queue_new_count);
|
||||
});
|
||||
}
|
||||
bus.subscribe("/notification/" + user.get('id'), (function(data) {
|
||||
const oldUnread = user.get('unread_notifications');
|
||||
|
||||
@ -5,5 +5,6 @@ export default {
|
||||
// URL rewrites (usually due to refactoring)
|
||||
Discourse.URL.rewrite(/^\/category\//, "/c/");
|
||||
Discourse.URL.rewrite(/^\/group\//, "/groups/");
|
||||
Discourse.URL.rewrite(/\/private-messages\/$/, "/messages/");
|
||||
}
|
||||
};
|
||||
|
||||
@ -323,7 +323,13 @@
|
||||
// Adds a listener callback to a DOM element which is fired on a specified
|
||||
// event.
|
||||
util.addEvent = function (elem, event, listener) {
|
||||
elem.addEventListener(event, listener, false);
|
||||
var wrapped = function() {
|
||||
var wrappedArgs = Array.prototype.slice.call(arguments);
|
||||
Ember.run(function() {
|
||||
listener.apply(this, wrappedArgs);
|
||||
});
|
||||
};
|
||||
elem.addEventListener(event, wrapped, false);
|
||||
};
|
||||
|
||||
|
||||
@ -904,7 +910,7 @@
|
||||
// TODO allow us to inject this in (its our debouncer)
|
||||
var debounce = function(func,wait,trickle) {
|
||||
var timeout = null;
|
||||
return function(){
|
||||
return function() {
|
||||
var context = this;
|
||||
var args = arguments;
|
||||
|
||||
@ -924,8 +930,8 @@
|
||||
currentWait = wait;
|
||||
}
|
||||
|
||||
if (timeout) { clearTimeout(timeout); }
|
||||
timeout = setTimeout(later, currentWait);
|
||||
if (timeout) { Ember.run.cancel(timeout); }
|
||||
timeout = Ember.run.later(later, currentWait);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
38
app/assets/javascripts/discourse/lib/ajax-error.js.es6
Normal file
38
app/assets/javascripts/discourse/lib/ajax-error.js.es6
Normal file
@ -0,0 +1,38 @@
|
||||
function extractError(error) {
|
||||
if (error instanceof Error) {
|
||||
Ember.Logger.error(error.stack);
|
||||
}
|
||||
|
||||
if (typeof error === "string") {
|
||||
Ember.Logger.error(error);
|
||||
}
|
||||
|
||||
let parsedError;
|
||||
if (error.responseText) {
|
||||
try {
|
||||
const parsedJSON = $.parseJSON(error.responseText);
|
||||
if (parsedJSON.errors) {
|
||||
parsedError = parsedJSON.errors[0];
|
||||
} else if (parsedJSON.failed) {
|
||||
parsedError = parsedJSON.message;
|
||||
}
|
||||
} catch(ex) {
|
||||
// in case the JSON doesn't parse
|
||||
Ember.Logger.error(ex.stack);
|
||||
}
|
||||
}
|
||||
return parsedError || I18n.t('generic_error');
|
||||
}
|
||||
|
||||
export function throwAjaxError(undoCallback) {
|
||||
return function(error) {
|
||||
// If we provided an `undo` callback
|
||||
if (undoCallback) { undoCallback(error); }
|
||||
|
||||
throw extractError(error);
|
||||
};
|
||||
}
|
||||
|
||||
export function popupAjaxError(err) {
|
||||
bootbox.alert(extractError(err));
|
||||
}
|
||||
@ -51,7 +51,8 @@ Discourse.Onebox = {
|
||||
// Retrieve the onebox
|
||||
var promise = Discourse.ajax("/onebox", {
|
||||
dataType: 'html',
|
||||
data: { url: url, refresh: refresh }
|
||||
data: { url: url, refresh: refresh },
|
||||
cache: true
|
||||
});
|
||||
|
||||
// We can call this when loading is complete
|
||||
|
||||
@ -1,13 +1,38 @@
|
||||
export default (name, model) => {
|
||||
export default (name, opts) => {
|
||||
opts = opts || {};
|
||||
|
||||
if (opts.__type) {
|
||||
Ember.warn("showModal now takes `opts` as a second param instead of a model");
|
||||
opts = {model: opts};
|
||||
}
|
||||
|
||||
const container = Discourse.__container__;
|
||||
|
||||
// We use the container here because modals are like singletons
|
||||
// in Discourse. Only one can be shown with a particular state.
|
||||
const route = Discourse.__container__.lookup('route:application');
|
||||
const route = container.lookup('route:application');
|
||||
const modalController = route.controllerFor('modal');
|
||||
|
||||
route.controllerFor('modal').set('modalClass', null);
|
||||
route.render(name, { into: 'modal', outlet: 'modalBody' });
|
||||
modalController.set('modalClass', null);
|
||||
|
||||
const viewClass = container.lookupFactory('view:' + name);
|
||||
const controller = container.lookup('controller:' + name);
|
||||
if (viewClass) {
|
||||
route.render(name, { into: 'modal', outlet: 'modalBody' });
|
||||
} else {
|
||||
const templateName = Ember.String.dasherize(name);
|
||||
|
||||
const renderArgs = { into: 'modal', outlet: 'modalBody', view: 'modal-body'};
|
||||
if (controller) { renderArgs.controller = name; }
|
||||
|
||||
route.render('modal/' + templateName, renderArgs);
|
||||
if (opts.title) {
|
||||
modalController.set('title', I18n.t(opts.title));
|
||||
}
|
||||
}
|
||||
|
||||
const controller = route.controllerFor(name);
|
||||
if (controller) {
|
||||
const model = opts.model;
|
||||
if (model) { controller.set('model', model); }
|
||||
if (controller.onShow) { controller.onShow(); }
|
||||
controller.set('flashMessage', null);
|
||||
|
||||
@ -6,7 +6,7 @@ var cache = {},
|
||||
currentTerm,
|
||||
oldSearch;
|
||||
|
||||
function performSearch(term, topicId, includeGroups, resultsFn) {
|
||||
function performSearch(term, topicId, includeGroups, allowedUsers, resultsFn) {
|
||||
var cached = cache[term];
|
||||
if (cached) {
|
||||
resultsFn(cached);
|
||||
@ -17,7 +17,8 @@ function performSearch(term, topicId, includeGroups, resultsFn) {
|
||||
oldSearch = $.ajax(Discourse.getURL('/users/search/users'), {
|
||||
data: { term: term,
|
||||
topic_id: topicId,
|
||||
include_groups: includeGroups }
|
||||
include_groups: includeGroups,
|
||||
topic_allowed_users: allowedUsers }
|
||||
});
|
||||
|
||||
var returnVal = CANCELLED_STATUS;
|
||||
@ -75,6 +76,7 @@ function organizeResults(r, options) {
|
||||
export default function userSearch(options) {
|
||||
var term = options.term || "",
|
||||
includeGroups = options.includeGroups,
|
||||
allowedUsers = options.allowedUsers,
|
||||
topicId = options.topicId;
|
||||
|
||||
|
||||
@ -101,7 +103,7 @@ export default function userSearch(options) {
|
||||
resolve(CANCELLED_STATUS);
|
||||
}, 5000);
|
||||
|
||||
debouncedSearch(term, topicId, includeGroups, function(r) {
|
||||
debouncedSearch(term, topicId, includeGroups, allowedUsers, function(r) {
|
||||
clearTimeout(clearPromise);
|
||||
resolve(organizeResults(r, options));
|
||||
});
|
||||
|
||||
@ -51,7 +51,7 @@ Discourse.Utilities = {
|
||||
|
||||
var classes = "avatar" + (options.extraClasses ? " " + options.extraClasses : "");
|
||||
var title = (options.title) ? " title='" + Handlebars.Utils.escapeExpression(options.title || "") + "'" : "";
|
||||
return "<img width='" + size + "' height='" + size + "' src='" + url + "' class='" + classes + "'" + title + ">";
|
||||
return "<img alt='' width='" + size + "' height='" + size + "' src='" + url + "' class='" + classes + "'" + title + ">";
|
||||
},
|
||||
|
||||
tinyAvatar: function(avatarTemplate, options) {
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
/**
|
||||
This mixin provides `blank` and `present` to determine whether properties are
|
||||
there, accounting for more cases than just null and undefined.
|
||||
|
||||
@class Discourse.Presence
|
||||
@extends Ember.Mixin
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.Presence = Em.Mixin.create({
|
||||
|
||||
/**
|
||||
Returns whether a property is blank. It considers empty arrays, string, objects, undefined and null
|
||||
to be blank, otherwise true.
|
||||
|
||||
@method blank
|
||||
@param {String} name the name of the property we want to check
|
||||
@return {Boolean}
|
||||
*/
|
||||
blank: function(name) {
|
||||
return Ember.isEmpty(this[name] || this.get(name));
|
||||
},
|
||||
|
||||
/**
|
||||
Returns whether a property is present. A present property is the opposite of a `blank` one.
|
||||
|
||||
@method present
|
||||
@param {String} name the name of the property we want to check
|
||||
@return {Boolean}
|
||||
*/
|
||||
present: function(name) {
|
||||
return !this.blank(name);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
20
app/assets/javascripts/discourse/mixins/presence.js.es6
Normal file
20
app/assets/javascripts/discourse/mixins/presence.js.es6
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
This mixin provides `blank` and `present` to determine whether properties are
|
||||
there, accounting for more cases than just null and undefined.
|
||||
**/
|
||||
export default Ember.Mixin.create({
|
||||
|
||||
/**
|
||||
Returns whether a property is blank. It considers empty arrays, string, objects, undefined and null
|
||||
to be blank, otherwise true.
|
||||
*/
|
||||
blank(name) {
|
||||
return Ember.isEmpty(this[name] || this.get(name));
|
||||
},
|
||||
|
||||
// Returns whether a property is present. A present property is the opposite of a `blank` one.
|
||||
present(name) {
|
||||
return !this.blank(name);
|
||||
}
|
||||
|
||||
});
|
||||
@ -1,11 +1,3 @@
|
||||
/**
|
||||
A data model for summarizing actions a user has taken, for example liking a post.
|
||||
|
||||
@class ActionSummary
|
||||
@extends Discourse.Model
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.ActionSummary = Discourse.Model.extend({
|
||||
|
||||
// Description for the action
|
||||
@ -44,16 +36,16 @@ Discourse.ActionSummary = Discourse.Model.extend({
|
||||
}
|
||||
},
|
||||
|
||||
toggle: function() {
|
||||
toggle: function(post) {
|
||||
if (!this.get('acted')) {
|
||||
this.act();
|
||||
this.act(post);
|
||||
} else {
|
||||
this.undo();
|
||||
this.undo(post);
|
||||
}
|
||||
},
|
||||
|
||||
// Perform this action
|
||||
act: function(opts) {
|
||||
act: function(post, opts) {
|
||||
if (!opts) opts = {};
|
||||
|
||||
var action = this.get('actionType.name_key');
|
||||
@ -82,18 +74,14 @@ Discourse.ActionSummary = Discourse.Model.extend({
|
||||
return Discourse.ajax("/post_actions", {
|
||||
type: 'POST',
|
||||
data: {
|
||||
id: this.get('flagTopic') ? this.get('flagTopic.id') : this.get('post.id'),
|
||||
id: this.get('flagTopic') ? this.get('flagTopic.id') : post.get('id'),
|
||||
post_action_type_id: this.get('id'),
|
||||
message: opts.message,
|
||||
take_action: opts.takeAction,
|
||||
flag_topic: this.get('flagTopic') ? true : false
|
||||
}
|
||||
}).then(function(result) {
|
||||
var post = self.get('post');
|
||||
if (post && result && result.id === post.get('id')) {
|
||||
post.updateFromJson(result);
|
||||
}
|
||||
return post;
|
||||
return post.updateActionsSummary(result);
|
||||
}).catch(function (error) {
|
||||
self.removeAction();
|
||||
var message = $.parseJSON(error.responseText).errors;
|
||||
@ -102,43 +90,38 @@ Discourse.ActionSummary = Discourse.Model.extend({
|
||||
},
|
||||
|
||||
// Undo this action
|
||||
undo: function() {
|
||||
undo: function(post) {
|
||||
this.removeAction();
|
||||
|
||||
// Remove our post action
|
||||
var self = this;
|
||||
return Discourse.ajax("/post_actions/" + (this.get('post.id')), {
|
||||
return Discourse.ajax("/post_actions/" + post.get('id'), {
|
||||
type: 'DELETE',
|
||||
data: {
|
||||
post_action_type_id: this.get('id')
|
||||
}
|
||||
}).then(function(result) {
|
||||
var post = self.get('post');
|
||||
if (post && result && result.id === post.get('id')) {
|
||||
post.updateFromJson(result);
|
||||
}
|
||||
return post;
|
||||
return post.updateActionsSummary(result);
|
||||
});
|
||||
},
|
||||
|
||||
deferFlags: function() {
|
||||
deferFlags: function(post) {
|
||||
var self = this;
|
||||
return Discourse.ajax("/post_actions/defer_flags", {
|
||||
type: "POST",
|
||||
data: {
|
||||
post_action_type_id: this.get("id"),
|
||||
id: this.get("post.id")
|
||||
id: post.get('id')
|
||||
}
|
||||
}).then(function () {
|
||||
self.set("count", 0);
|
||||
});
|
||||
},
|
||||
|
||||
loadUsers: function() {
|
||||
loadUsers: function(post) {
|
||||
var self = this;
|
||||
Discourse.ajax("/post_actions/users", {
|
||||
data: {
|
||||
id: this.get('post.id'),
|
||||
id: post.get('id'),
|
||||
post_action_type_id: this.get('id')
|
||||
}
|
||||
}).then(function (result) {
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
import RestModel from 'discourse/models/rest';
|
||||
import Topic from 'discourse/models/topic';
|
||||
import { throwAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
const CLOSED = 'closed',
|
||||
SAVING = 'saving',
|
||||
OPEN = 'open',
|
||||
@ -26,10 +30,10 @@ const CLOSED = 'closed',
|
||||
categoryId: 'topic.category.id'
|
||||
};
|
||||
|
||||
const Composer = Discourse.Model.extend({
|
||||
const Composer = RestModel.extend({
|
||||
|
||||
archetypes: function() {
|
||||
return Discourse.Site.currentProp('archetypes');
|
||||
return this.site.get('archetypes');
|
||||
}.property(),
|
||||
|
||||
creatingTopic: Em.computed.equal('action', CREATE_TOPIC),
|
||||
@ -127,21 +131,16 @@ const Composer = Discourse.Model.extend({
|
||||
} else {
|
||||
// has a category? (when needed)
|
||||
return this.get('canCategorize') &&
|
||||
!Discourse.SiteSettings.allow_uncategorized_topics &&
|
||||
!this.siteSettings.allow_uncategorized_topics &&
|
||||
!this.get('categoryId') &&
|
||||
!Discourse.User.currentProp('staff');
|
||||
!this.user.get('staff');
|
||||
}
|
||||
}.property('loading', 'canEditTitle', 'titleLength', 'targetUsernames', 'replyLength', 'categoryId', 'missingReplyCharacters'),
|
||||
|
||||
/**
|
||||
Is the title's length valid?
|
||||
|
||||
@property titleLengthValid
|
||||
**/
|
||||
titleLengthValid: function() {
|
||||
if (Discourse.User.currentProp('admin') && this.get('post.static_doc') && this.get('titleLength') > 0) return true;
|
||||
if (this.user.get('admin') && this.get('post.static_doc') && this.get('titleLength') > 0) return true;
|
||||
if (this.get('titleLength') < this.get('minimumTitleLength')) return false;
|
||||
return (this.get('titleLength') <= Discourse.SiteSettings.max_topic_title_length);
|
||||
return (this.get('titleLength') <= this.siteSettings.max_topic_title_length);
|
||||
}.property('minimumTitleLength', 'titleLength', 'post.static_doc'),
|
||||
|
||||
// The icon for the save button
|
||||
@ -194,9 +193,9 @@ const Composer = Discourse.Model.extend({
|
||||
**/
|
||||
minimumTitleLength: function() {
|
||||
if (this.get('privateMessage')) {
|
||||
return Discourse.SiteSettings.min_private_message_title_length;
|
||||
return this.siteSettings.min_private_message_title_length;
|
||||
} else {
|
||||
return Discourse.SiteSettings.min_topic_title_length;
|
||||
return this.siteSettings.min_topic_title_length;
|
||||
}
|
||||
}.property('privateMessage'),
|
||||
|
||||
@ -216,12 +215,12 @@ const Composer = Discourse.Model.extend({
|
||||
**/
|
||||
minimumPostLength: function() {
|
||||
if( this.get('privateMessage') ) {
|
||||
return Discourse.SiteSettings.min_private_message_post_length;
|
||||
return this.siteSettings.min_private_message_post_length;
|
||||
} else if (this.get('topicFirstPost')) {
|
||||
// first post (topic body)
|
||||
return Discourse.SiteSettings.min_first_post_length;
|
||||
return this.siteSettings.min_first_post_length;
|
||||
} else {
|
||||
return Discourse.SiteSettings.min_post_length;
|
||||
return this.siteSettings.min_post_length;
|
||||
}
|
||||
}.property('privateMessage', 'topicFirstPost'),
|
||||
|
||||
@ -249,7 +248,7 @@ const Composer = Discourse.Model.extend({
|
||||
_setupComposer: function() {
|
||||
const val = (Discourse.Mobile.mobileView ? false : (Discourse.KeyValueStore.get('composer.showPreview') || 'true'));
|
||||
this.set('showPreview', val === 'true');
|
||||
this.set('archetypeId', Discourse.Site.currentProp('default_archetype'));
|
||||
this.set('archetypeId', this.site.get('default_archetype'));
|
||||
}.on('init'),
|
||||
|
||||
/**
|
||||
@ -349,15 +348,15 @@ const Composer = Discourse.Model.extend({
|
||||
|
||||
this.setProperties({
|
||||
categoryId: opts.categoryId || this.get('topic.category.id'),
|
||||
archetypeId: opts.archetypeId || Discourse.Site.currentProp('default_archetype'),
|
||||
archetypeId: opts.archetypeId || this.site.get('default_archetype'),
|
||||
metaData: opts.metaData ? Em.Object.create(opts.metaData) : null,
|
||||
reply: opts.reply || this.get("reply") || ""
|
||||
});
|
||||
|
||||
if (opts.postId) {
|
||||
this.set('loading', true);
|
||||
Discourse.Post.load(opts.postId).then(function(result) {
|
||||
composer.set('post', result);
|
||||
this.store.find('post', opts.postId).then(function(post) {
|
||||
composer.set('post', post);
|
||||
composer.set('loading', false);
|
||||
});
|
||||
}
|
||||
@ -370,10 +369,10 @@ const Composer = Discourse.Model.extend({
|
||||
|
||||
this.setProperties(topicProps);
|
||||
|
||||
Discourse.Post.load(opts.post.get('id')).then(function(result) {
|
||||
this.store.find('post', opts.post.get('id')).then(function(post) {
|
||||
composer.setProperties({
|
||||
reply: result.get('raw'),
|
||||
originalText: result.get('raw'),
|
||||
reply: post.get('raw'),
|
||||
originalText: post.get('raw'),
|
||||
loading: false
|
||||
});
|
||||
});
|
||||
@ -425,30 +424,29 @@ const Composer = Discourse.Model.extend({
|
||||
post.get('post_number') === 1 &&
|
||||
this.get('topic.details.can_edit')) {
|
||||
const topicProps = this.getProperties(Object.keys(_edit_topic_serializer));
|
||||
promise = Discourse.Topic.update(this.get('topic'), topicProps);
|
||||
|
||||
promise = Topic.update(this.get('topic'), topicProps);
|
||||
} else {
|
||||
promise = Ember.RSVP.resolve();
|
||||
}
|
||||
|
||||
post.setProperties({
|
||||
const props = {
|
||||
raw: this.get('reply'),
|
||||
editReason: opts.editReason,
|
||||
imageSizes: opts.imageSizes,
|
||||
edit_reason: opts.editReason,
|
||||
image_sizes: opts.imageSizes,
|
||||
cooked: this.getCookedHtml()
|
||||
});
|
||||
};
|
||||
|
||||
this.set('composeState', CLOSED);
|
||||
|
||||
return promise.then(function() {
|
||||
return post.save(function(result) {
|
||||
post.updateFromPost(result);
|
||||
return post.save(props).then(function(result) {
|
||||
self.clearState();
|
||||
}, function (error) {
|
||||
return result;
|
||||
}).catch(throwAjaxError(function() {
|
||||
post.set('cooked', oldCooked);
|
||||
self.set('composeState', OPEN);
|
||||
const response = $.parseJSON(error.responseText);
|
||||
throw response && response.errors ? response.errors[0] : I18n.t('generic_error');
|
||||
});
|
||||
}));
|
||||
});
|
||||
},
|
||||
|
||||
@ -467,30 +465,30 @@ const Composer = Discourse.Model.extend({
|
||||
createPost(opts) {
|
||||
const post = this.get('post'),
|
||||
topic = this.get('topic'),
|
||||
currentUser = Discourse.User.current(),
|
||||
user = this.user,
|
||||
postStream = this.get('topic.postStream');
|
||||
|
||||
let addedToStream = false;
|
||||
|
||||
// Build the post object
|
||||
const createdPost = Discourse.Post.create({
|
||||
const createdPost = this.store.createRecord('post', {
|
||||
imageSizes: opts.imageSizes,
|
||||
cooked: this.getCookedHtml(),
|
||||
reply_count: 0,
|
||||
name: currentUser.get('name'),
|
||||
display_username: currentUser.get('name'),
|
||||
username: currentUser.get('username'),
|
||||
user_id: currentUser.get('id'),
|
||||
user_title: currentUser.get('title'),
|
||||
uploaded_avatar_id: currentUser.get('uploaded_avatar_id'),
|
||||
user_custom_fields: currentUser.get('custom_fields'),
|
||||
post_type: Discourse.Site.currentProp('post_types.regular'),
|
||||
name: user.get('name'),
|
||||
display_username: user.get('name'),
|
||||
username: user.get('username'),
|
||||
user_id: user.get('id'),
|
||||
user_title: user.get('title'),
|
||||
uploaded_avatar_id: user.get('uploaded_avatar_id'),
|
||||
user_custom_fields: user.get('custom_fields'),
|
||||
post_type: this.site.get('post_types.regular'),
|
||||
actions_summary: [],
|
||||
moderator: currentUser.get('moderator'),
|
||||
admin: currentUser.get('admin'),
|
||||
moderator: user.get('moderator'),
|
||||
admin: user.get('admin'),
|
||||
yours: true,
|
||||
newPost: true,
|
||||
read: true
|
||||
read: true,
|
||||
wiki: false
|
||||
});
|
||||
|
||||
this.serialize(_create_serializer, createdPost);
|
||||
@ -520,78 +518,55 @@ const Composer = Discourse.Model.extend({
|
||||
// we would need to handle oneboxes and other bits that are not even in the
|
||||
// engine, staging will just cause a blank post to render
|
||||
if (!_.isEmpty(createdPost.get('cooked'))) {
|
||||
state = postStream.stagePost(createdPost, currentUser);
|
||||
|
||||
if(state === "alreadyStaging"){
|
||||
return;
|
||||
}
|
||||
|
||||
state = postStream.stagePost(createdPost, user);
|
||||
if (state === "alreadyStaging") { return; }
|
||||
}
|
||||
}
|
||||
|
||||
const composer = this,
|
||||
promise = new Ember.RSVP.Promise(function(resolve, reject) {
|
||||
composer.set('composeState', SAVING);
|
||||
|
||||
createdPost.save(function(result) {
|
||||
let saving = true;
|
||||
|
||||
createdPost.updateFromJson(result);
|
||||
|
||||
if (topic) {
|
||||
// It's no longer a new post
|
||||
createdPost.set('newPost', false);
|
||||
topic.set('draft_sequence', result.draft_sequence);
|
||||
postStream.commitPost(createdPost);
|
||||
addedToStream = true;
|
||||
} else {
|
||||
// We created a new topic, let's show it.
|
||||
composer.set('composeState', CLOSED);
|
||||
saving = false;
|
||||
|
||||
// Update topic_count for the category
|
||||
const category = Discourse.Site.currentProp('categories').find(function(x) { return x.get('id') === (parseInt(createdPost.get('category'),10) || 1); });
|
||||
if (category) category.incrementProperty('topic_count');
|
||||
Discourse.notifyPropertyChange('globalNotice');
|
||||
}
|
||||
|
||||
composer.clearState();
|
||||
composer.set('createdPost', createdPost);
|
||||
|
||||
if (addedToStream) {
|
||||
composer.set('composeState', CLOSED);
|
||||
} else if (saving) {
|
||||
composer.set('composeState', SAVING);
|
||||
}
|
||||
|
||||
return resolve({ post: result });
|
||||
}, function(error) {
|
||||
// If an error occurs
|
||||
if (postStream) {
|
||||
postStream.undoPost(createdPost);
|
||||
}
|
||||
composer.set('composeState', OPEN);
|
||||
|
||||
// TODO extract error handling code
|
||||
let parsedError;
|
||||
try {
|
||||
const parsedJSON = $.parseJSON(error.responseText);
|
||||
if (parsedJSON.errors) {
|
||||
parsedError = parsedJSON.errors[0];
|
||||
} else if (parsedJSON.failed) {
|
||||
parsedError = parsedJSON.message;
|
||||
}
|
||||
}
|
||||
catch(ex) {
|
||||
parsedError = "Unknown error saving post, try again. Error: " + error.status + " " + error.statusText;
|
||||
}
|
||||
reject(parsedError);
|
||||
});
|
||||
});
|
||||
|
||||
const composer = this;
|
||||
composer.set('composeState', SAVING);
|
||||
composer.set("stagedPost", state === "staged" && createdPost);
|
||||
|
||||
return promise;
|
||||
return createdPost.save().then(function(result) {
|
||||
let saving = true;
|
||||
|
||||
if (result.responseJson.action === "enqueued") {
|
||||
if (postStream) { postStream.undoPost(createdPost); }
|
||||
return result;
|
||||
}
|
||||
|
||||
if (topic) {
|
||||
// It's no longer a new post
|
||||
topic.set('draft_sequence', result.target.draft_sequence);
|
||||
postStream.commitPost(createdPost);
|
||||
addedToStream = true;
|
||||
} else {
|
||||
// We created a new topic, let's show it.
|
||||
composer.set('composeState', CLOSED);
|
||||
saving = false;
|
||||
|
||||
// Update topic_count for the category
|
||||
const category = composer.site.get('categories').find(function(x) { return x.get('id') === (parseInt(createdPost.get('category'),10) || 1); });
|
||||
if (category) category.incrementProperty('topic_count');
|
||||
Discourse.notifyPropertyChange('globalNotice');
|
||||
}
|
||||
|
||||
composer.clearState();
|
||||
composer.set('createdPost', createdPost);
|
||||
|
||||
if (addedToStream) {
|
||||
composer.set('composeState', CLOSED);
|
||||
} else if (saving) {
|
||||
composer.set('composeState', SAVING);
|
||||
}
|
||||
|
||||
return result;
|
||||
}).catch(throwAjaxError(function() {
|
||||
if (postStream) {
|
||||
postStream.undoPost(createdPost);
|
||||
}
|
||||
Ember.run.next(() => composer.set('composeState', OPEN));
|
||||
}));
|
||||
},
|
||||
|
||||
getCookedHtml() {
|
||||
@ -604,7 +579,7 @@ const Composer = Discourse.Model.extend({
|
||||
// Do not save when there is no reply
|
||||
if (!this.get('reply')) return;
|
||||
// Do not save when the reply's length is too small
|
||||
if (this.get('replyLength') < Discourse.SiteSettings.min_post_length) return;
|
||||
if (this.get('replyLength') < this.siteSettings.min_post_length) return;
|
||||
|
||||
const data = {
|
||||
reply: this.get('reply'),
|
||||
@ -673,6 +648,14 @@ Composer.reopenClass({
|
||||
}
|
||||
},
|
||||
|
||||
create(args) {
|
||||
args = args || {};
|
||||
args.user = args.user || Discourse.User.current();
|
||||
args.site = args.site || Discourse.Site.current();
|
||||
args.siteSettings = args.siteSettings || Discourse.SiteSettings;
|
||||
return this._super(args);
|
||||
},
|
||||
|
||||
serializeToTopic(fieldName, property) {
|
||||
if (!property) { property = fieldName; }
|
||||
_edit_topic_serializer[fieldName] = property;
|
||||
|
||||
@ -52,6 +52,12 @@ const Group = Discourse.Model.extend({
|
||||
}).then(function() {
|
||||
// reload member list
|
||||
self.findMembers();
|
||||
}).catch(function(error) {
|
||||
if (error && error.responseText) {
|
||||
bootbox.alert($.parseJSON(error.responseText).errors[0]);
|
||||
} else {
|
||||
bootbox.alert(I18n.t('generic_error'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
Discourse.Model = Ember.Object.extend(Discourse.Presence);
|
||||
import Presence from 'discourse/mixins/presence';
|
||||
|
||||
Discourse.Model.reopenClass({
|
||||
const Model = Ember.Object.extend(Presence);
|
||||
|
||||
Model.reopenClass({
|
||||
extractByKey: function(collection, klass) {
|
||||
var retval = {};
|
||||
if (Ember.isEmpty(collection)) { return retval; }
|
||||
@ -11,3 +13,5 @@ Discourse.Model.reopenClass({
|
||||
return retval;
|
||||
}
|
||||
});
|
||||
|
||||
export default Model;
|
||||
@ -1,4 +1,6 @@
|
||||
const PostStream = Ember.Object.extend({
|
||||
import RestModel from 'discourse/models/rest';
|
||||
|
||||
const PostStream = RestModel.extend({
|
||||
loading: Em.computed.or('loadingAbove', 'loadingBelow', 'loadingFilter', 'stagingPost'),
|
||||
notLoading: Em.computed.not('loading'),
|
||||
filteredPostsCount: Em.computed.alias("stream.length"),
|
||||
@ -148,12 +150,16 @@ const PostStream = Ember.Object.extend({
|
||||
opts = opts || {};
|
||||
opts.nearPost = parseInt(opts.nearPost, 10);
|
||||
|
||||
const topic = this.get('topic'),
|
||||
self = this;
|
||||
const topic = this.get('topic');
|
||||
const self = this;
|
||||
|
||||
// Do we already have the post in our list of posts? Jump there.
|
||||
const postWeWant = this.get('posts').findProperty('post_number', opts.nearPost);
|
||||
if (postWeWant) { return Ember.RSVP.resolve(); }
|
||||
if (opts.forceLoad) {
|
||||
this.set('loaded', false);
|
||||
} else {
|
||||
const postWeWant = this.get('posts').findProperty('post_number', opts.nearPost);
|
||||
if (postWeWant) { return Ember.RSVP.resolve(); }
|
||||
}
|
||||
|
||||
// TODO: if we have all the posts in the filter, don't go to the server for them.
|
||||
self.set('loadingFilter', true);
|
||||
@ -420,8 +426,9 @@ const PostStream = Ember.Object.extend({
|
||||
} else {
|
||||
// need to insert into stream
|
||||
const url = "/posts/" + postId;
|
||||
const store = this.store;
|
||||
Discourse.ajax(url).then(function(p){
|
||||
const post = Discourse.Post.create(p);
|
||||
const post = store.createRecord('post', p);
|
||||
const stream = self.get("stream");
|
||||
const posts = self.get("posts");
|
||||
self.storePost(post);
|
||||
@ -461,9 +468,10 @@ const PostStream = Ember.Object.extend({
|
||||
|
||||
if(existing){
|
||||
const url = "/posts/" + postId;
|
||||
const store = this.store;
|
||||
Discourse.ajax(url).then(
|
||||
function(p){
|
||||
self.storePost(Discourse.Post.create(p));
|
||||
self.storePost(store.createRecord('post', p));
|
||||
},
|
||||
function(){
|
||||
self.removePosts([existing]);
|
||||
@ -480,8 +488,9 @@ const PostStream = Ember.Object.extend({
|
||||
|
||||
if (existing && existing.updated_at !== updatedAt) {
|
||||
const url = "/posts/" + postId;
|
||||
const store = this.store;
|
||||
Discourse.ajax(url).then(function(p){
|
||||
self.storePost(Discourse.Post.create(p));
|
||||
self.storePost(store.createRecord('post', p));
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -491,9 +500,10 @@ const PostStream = Ember.Object.extend({
|
||||
const postStream = this,
|
||||
url = "/posts/" + post.get('id') + "/reply-history.json?max_replies=" + Discourse.SiteSettings.max_reply_history;
|
||||
|
||||
const store = this.store;
|
||||
return Discourse.ajax(url).then(function(result) {
|
||||
return result.map(function (p) {
|
||||
return postStream.storePost(Discourse.Post.create(p));
|
||||
return postStream.storePost(store.createRecord('post', p));
|
||||
});
|
||||
}).then(function (replyHistory) {
|
||||
post.set('replyHistory', replyHistory);
|
||||
@ -594,8 +604,9 @@ const PostStream = Ember.Object.extend({
|
||||
this.set('gaps', null);
|
||||
if (postStreamData) {
|
||||
// Load posts if present
|
||||
const store = this.store;
|
||||
postStreamData.posts.forEach(function(p) {
|
||||
postStream.appendPost(Discourse.Post.create(p));
|
||||
postStream.appendPost(store.createRecord('post', p));
|
||||
});
|
||||
delete postStreamData.posts;
|
||||
|
||||
@ -671,11 +682,12 @@ const PostStream = Ember.Object.extend({
|
||||
data = { post_ids: postIds },
|
||||
postStream = this;
|
||||
|
||||
const store = this.store;
|
||||
return Discourse.ajax(url, {data: data}).then(function(result) {
|
||||
const posts = Em.get(result, "post_stream.posts");
|
||||
if (posts) {
|
||||
posts.forEach(function (p) {
|
||||
postStream.storePost(Discourse.Post.create(p));
|
||||
postStream.storePost(store.createRecord('post', p));
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -751,6 +763,8 @@ PostStream.reopenClass({
|
||||
url += "/" + opts.nearPost;
|
||||
}
|
||||
delete opts.nearPost;
|
||||
delete opts.__type;
|
||||
delete opts.store;
|
||||
|
||||
return PreloadStore.getAndRemove("topic_" + topicId, function() {
|
||||
return Discourse.ajax(url + ".json", {data: opts});
|
||||
|
||||
@ -1,20 +1,15 @@
|
||||
/**
|
||||
A data model representing a post in a topic
|
||||
import RestModel from 'discourse/models/rest';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
@class Post
|
||||
@extends Discourse.Model
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.Post = Discourse.Model.extend({
|
||||
const Post = RestModel.extend({
|
||||
|
||||
init: function() {
|
||||
init() {
|
||||
this.set('replyHistory', []);
|
||||
},
|
||||
|
||||
shareUrl: function() {
|
||||
var user = Discourse.User.current();
|
||||
var userSuffix = user ? '?u=' + user.get('username_lower') : '';
|
||||
const user = Discourse.User.current();
|
||||
const userSuffix = user ? '?u=' + user.get('username_lower') : '';
|
||||
|
||||
if (this.get('firstPost')) {
|
||||
return this.get('topic.url') + userSuffix;
|
||||
@ -33,7 +28,7 @@ Discourse.Post = Discourse.Model.extend({
|
||||
userDeleted: Em.computed.empty('user_id'),
|
||||
|
||||
showName: function() {
|
||||
var name = this.get('name');
|
||||
const name = this.get('name');
|
||||
return name && (name !== this.get('username')) && Discourse.SiteSettings.display_name_on_posts;
|
||||
}.property('name', 'username'),
|
||||
|
||||
@ -69,29 +64,23 @@ Discourse.Post = Discourse.Model.extend({
|
||||
}.property("user_id"),
|
||||
|
||||
wikiChanged: function() {
|
||||
var data = { wiki: this.get("wiki") };
|
||||
const data = { wiki: this.get("wiki") };
|
||||
this._updatePost("wiki", data);
|
||||
}.observes('wiki'),
|
||||
|
||||
postTypeChanged: function () {
|
||||
var data = { post_type: this.get("post_type") };
|
||||
const data = { post_type: this.get("post_type") };
|
||||
this._updatePost("post_type", data);
|
||||
}.observes("post_type"),
|
||||
|
||||
_updatePost: function (field, data) {
|
||||
var self = this;
|
||||
_updatePost(field, data) {
|
||||
const self = this;
|
||||
Discourse.ajax("/posts/" + this.get("id") + "/" + field, {
|
||||
type: "PUT",
|
||||
data: data
|
||||
}).then(function () {
|
||||
self.incrementProperty("version");
|
||||
}, function (error) {
|
||||
if (error && error.responseText) {
|
||||
bootbox.alert($.parseJSON(error.responseText).errors[0]);
|
||||
} else {
|
||||
bootbox.alert(I18n.t("generic_error"));
|
||||
}
|
||||
});
|
||||
}).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
internalLinks: function() {
|
||||
@ -103,7 +92,7 @@ Discourse.Post = Discourse.Model.extend({
|
||||
editCount: function() { return this.get('version') - 1; }.property('version'),
|
||||
|
||||
flagsAvailable: function() {
|
||||
var post = this;
|
||||
const post = this;
|
||||
return Discourse.Site.currentProp('flagTypes').filter(function(item) {
|
||||
return post.get("actionByName." + item.get('name_key') + ".can_act");
|
||||
});
|
||||
@ -119,73 +108,46 @@ Discourse.Post = Discourse.Model.extend({
|
||||
});
|
||||
}.property('actions_summary.@each.users', 'actions_summary.@each.count'),
|
||||
|
||||
// Save a post and call the callback when done.
|
||||
save: function(complete, error) {
|
||||
var self = this;
|
||||
if (!this.get('newPost')) {
|
||||
// We're updating a post
|
||||
return Discourse.ajax("/posts/" + (this.get('id')), {
|
||||
type: 'PUT',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
post: { raw: this.get('raw'), edit_reason: this.get('editReason') },
|
||||
image_sizes: this.get('imageSizes')
|
||||
}
|
||||
}).then(function(result) {
|
||||
// If we received a category update, update it
|
||||
self.set('version', result.post.version);
|
||||
if (result.category) Discourse.Site.current().updateCategory(result.category);
|
||||
if (complete) complete(Discourse.Post.create(result.post));
|
||||
}).catch(function(result) {
|
||||
// Post failed to update
|
||||
if (error) error(result);
|
||||
});
|
||||
|
||||
} else {
|
||||
// We're saving a post
|
||||
var data = this.getProperties(Discourse.Composer.serializedFieldsForCreate());
|
||||
data.reply_to_post_number = this.get('reply_to_post_number');
|
||||
data.image_sizes = this.get('imageSizes');
|
||||
|
||||
var metaData = this.get('metaData');
|
||||
// Put the metaData into the request
|
||||
if (metaData) {
|
||||
data.meta_data = {};
|
||||
Ember.keys(metaData).forEach(function(key) { data.meta_data[key] = metaData.get(key); });
|
||||
}
|
||||
|
||||
return Discourse.ajax("/posts", {
|
||||
type: 'POST',
|
||||
data: data
|
||||
}).then(function(result) {
|
||||
// Post created
|
||||
if (complete) complete(Discourse.Post.create(result));
|
||||
}).catch(function(result) {
|
||||
// Failed to create a post
|
||||
if (error) error(result);
|
||||
});
|
||||
afterUpdate(res) {
|
||||
if (res.category) {
|
||||
Discourse.Site.current().updateCategory(res.category);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Expands the first post's content, if embedded and shortened.
|
||||
updateProperties() {
|
||||
return {
|
||||
post: { raw: this.get('raw'), edit_reason: this.get('editReason') },
|
||||
image_sizes: this.get('imageSizes')
|
||||
};
|
||||
},
|
||||
|
||||
@method expandFirstPost
|
||||
**/
|
||||
expand: function() {
|
||||
var self = this;
|
||||
createProperties() {
|
||||
const data = this.getProperties(Discourse.Composer.serializedFieldsForCreate());
|
||||
data.reply_to_post_number = this.get('reply_to_post_number');
|
||||
data.image_sizes = this.get('imageSizes');
|
||||
|
||||
const metaData = this.get('metaData');
|
||||
|
||||
// Put the metaData into the request
|
||||
if (metaData) {
|
||||
data.meta_data = {};
|
||||
Ember.keys(metaData).forEach(function(key) { data.meta_data[key] = metaData.get(key); });
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
// Expands the first post's content, if embedded and shortened.
|
||||
expand() {
|
||||
const self = this;
|
||||
return Discourse.ajax("/posts/" + this.get('id') + "/expand-embed").then(function(post) {
|
||||
self.set('cooked', "<section class='expanded-embed'>" + post.cooked + "</section>" );
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
Recover a deleted post
|
||||
|
||||
@method recover
|
||||
**/
|
||||
recover: function() {
|
||||
var post = this;
|
||||
// Recover a deleted post
|
||||
recover() {
|
||||
const post = this;
|
||||
post.setProperties({
|
||||
deleted_at: null,
|
||||
deleted_by: null,
|
||||
@ -207,11 +169,8 @@ Discourse.Post = Discourse.Model.extend({
|
||||
/**
|
||||
Changes the state of the post to be deleted. Does not call the server, that should be
|
||||
done elsewhere.
|
||||
|
||||
@method setDeletedState
|
||||
@param {Discourse.User} deletedBy The user deleting the post
|
||||
**/
|
||||
setDeletedState: function(deletedBy) {
|
||||
setDeletedState(deletedBy) {
|
||||
this.set('oldCooked', this.get('cooked'));
|
||||
|
||||
// Moderators can delete posts. Users can only trigger a deleted at message, unless delete_removed_posts_after is 0.
|
||||
@ -237,10 +196,8 @@ Discourse.Post = Discourse.Model.extend({
|
||||
Changes the state of the post to NOT be deleted. Does not call the server.
|
||||
This can only be called after setDeletedState was called, but the delete
|
||||
failed on the server.
|
||||
|
||||
@method undoDeletedState
|
||||
**/
|
||||
undoDeleteState: function() {
|
||||
undoDeleteState() {
|
||||
if (this.get('oldCooked')) {
|
||||
this.setProperties({
|
||||
deleted_at: null,
|
||||
@ -253,13 +210,7 @@ Discourse.Post = Discourse.Model.extend({
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Deletes a post
|
||||
|
||||
@method destroy
|
||||
@param {Discourse.User} deletedBy The user deleting the post
|
||||
**/
|
||||
destroy: function(deletedBy) {
|
||||
destroy(deletedBy) {
|
||||
this.setDeletedState(deletedBy);
|
||||
return Discourse.ajax("/posts/" + this.get('id'), {
|
||||
data: { context: window.location.pathname },
|
||||
@ -270,14 +221,11 @@ Discourse.Post = Discourse.Model.extend({
|
||||
/**
|
||||
Updates a post from another's attributes. This will normally happen when a post is loading but
|
||||
is already found in an identity map.
|
||||
|
||||
@method updateFromPost
|
||||
@param {Discourse.Post} otherPost The post we're updating from
|
||||
**/
|
||||
updateFromPost: function(otherPost) {
|
||||
var self = this;
|
||||
updateFromPost(otherPost) {
|
||||
const self = this;
|
||||
Object.keys(otherPost).forEach(function (key) {
|
||||
var value = otherPost[key],
|
||||
let value = otherPost[key],
|
||||
oldValue = self[key];
|
||||
|
||||
if (key === "replyHistory") {
|
||||
@ -287,7 +235,7 @@ Discourse.Post = Discourse.Model.extend({
|
||||
if (!value) { value = null; }
|
||||
if (!oldValue) { oldValue = null; }
|
||||
|
||||
var skip = false;
|
||||
let skip = false;
|
||||
if (typeof value !== "function" && oldValue !== value) {
|
||||
// wishing for an identity map
|
||||
if (key === "reply_to_user" && value && oldValue) {
|
||||
@ -301,56 +249,8 @@ Discourse.Post = Discourse.Model.extend({
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
Updates a post from a JSON packet. This is normally done after the post is saved to refresh any
|
||||
attributes.
|
||||
|
||||
@method updateFromJson
|
||||
@param {Object} obj The Json data to update with
|
||||
**/
|
||||
updateFromJson: function(obj) {
|
||||
if (!obj) return;
|
||||
|
||||
var skip, oldVal;
|
||||
|
||||
// Update all the properties
|
||||
var post = this;
|
||||
_.each(obj, function(val,key) {
|
||||
if (key !== 'actions_summary'){
|
||||
oldVal = post[key];
|
||||
skip = false;
|
||||
|
||||
if (val && val !== oldVal) {
|
||||
|
||||
if (key === "reply_to_user" && val && oldVal) {
|
||||
skip = val.username === oldVal.username || Em.get(val, "username") === Em.get(oldVal, "username");
|
||||
}
|
||||
|
||||
if(!skip) {
|
||||
post.set(key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Rebuild actions summary
|
||||
this.set('actions_summary', Em.A());
|
||||
if (obj.actions_summary) {
|
||||
var lookup = Em.Object.create();
|
||||
_.each(obj.actions_summary,function(a) {
|
||||
var actionSummary;
|
||||
a.post = post;
|
||||
a.actionType = Discourse.Site.current().postActionTypeById(a.id);
|
||||
actionSummary = Discourse.ActionSummary.create(a);
|
||||
post.get('actions_summary').pushObject(actionSummary);
|
||||
lookup.set(a.actionType.get('name_key'), actionSummary);
|
||||
});
|
||||
this.set('actionByName', lookup);
|
||||
}
|
||||
},
|
||||
|
||||
// Load replies to this post
|
||||
loadReplies: function() {
|
||||
loadReplies() {
|
||||
if(this.get('loadingReplies')){
|
||||
return;
|
||||
}
|
||||
@ -358,12 +258,12 @@ Discourse.Post = Discourse.Model.extend({
|
||||
this.set('loadingReplies', true);
|
||||
this.set('replies', []);
|
||||
|
||||
var self = this;
|
||||
const self = this;
|
||||
return Discourse.ajax("/posts/" + (this.get('id')) + "/replies")
|
||||
.then(function(loaded) {
|
||||
var replies = self.get('replies');
|
||||
const replies = self.get('replies');
|
||||
_.each(loaded,function(reply) {
|
||||
var post = Discourse.Post.create(reply);
|
||||
const post = Discourse.Post.create(reply);
|
||||
post.set('topic', self.get('topic'));
|
||||
replies.pushObject(post);
|
||||
});
|
||||
@ -375,7 +275,7 @@ Discourse.Post = Discourse.Model.extend({
|
||||
|
||||
// Whether to show replies directly below
|
||||
showRepliesBelow: function() {
|
||||
var replyCount = this.get('reply_count');
|
||||
const replyCount = this.get('reply_count');
|
||||
|
||||
// We don't show replies if there aren't any
|
||||
if (replyCount === 0) return false;
|
||||
@ -387,13 +287,13 @@ Discourse.Post = Discourse.Model.extend({
|
||||
if (replyCount > 1) return true;
|
||||
|
||||
// If we have *exactly* one reply, we have to consider if it's directly below us
|
||||
var topic = this.get('topic');
|
||||
const topic = this.get('topic');
|
||||
return !topic.isReplyDirectlyBelow(this);
|
||||
|
||||
}.property('reply_count'),
|
||||
|
||||
expandHidden: function() {
|
||||
var self = this;
|
||||
expandHidden() {
|
||||
const self = this;
|
||||
return Discourse.ajax("/posts/" + this.get('id') + "/cooked.json").then(function (result) {
|
||||
self.setProperties({
|
||||
cooked: result.cooked,
|
||||
@ -402,17 +302,17 @@ Discourse.Post = Discourse.Model.extend({
|
||||
});
|
||||
},
|
||||
|
||||
rebake: function () {
|
||||
rebake() {
|
||||
return Discourse.ajax("/posts/" + this.get("id") + "/rebake", { type: "PUT" });
|
||||
},
|
||||
|
||||
unhide: function () {
|
||||
unhide() {
|
||||
return Discourse.ajax("/posts/" + this.get("id") + "/unhide", { type: "PUT" });
|
||||
},
|
||||
|
||||
toggleBookmark: function() {
|
||||
var self = this,
|
||||
bookmarkedTopic;
|
||||
toggleBookmark() {
|
||||
const self = this;
|
||||
let bookmarkedTopic;
|
||||
|
||||
this.toggleProperty("bookmarked");
|
||||
|
||||
@ -432,43 +332,46 @@ Discourse.Post = Discourse.Model.extend({
|
||||
if (bookmarkedTopic) {self.set("topic.bookmarked", false); }
|
||||
throw e;
|
||||
});
|
||||
},
|
||||
|
||||
updateActionsSummary(json) {
|
||||
if (json && json.id === this.get('id')) {
|
||||
json = Post.munge(json);
|
||||
this.set('actions_summary', json.actions_summary);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Discourse.Post.reopenClass({
|
||||
Post.reopenClass({
|
||||
|
||||
createActionSummary: function(result) {
|
||||
if (result.actions_summary) {
|
||||
var lookup = Em.Object.create();
|
||||
munge(json) {
|
||||
if (json.actions_summary) {
|
||||
const lookup = Em.Object.create();
|
||||
// this area should be optimized, it is creating way too many objects per post
|
||||
result.actions_summary = result.actions_summary.map(function(a) {
|
||||
a.post = result;
|
||||
json.actions_summary = json.actions_summary.map(function(a) {
|
||||
a.actionType = Discourse.Site.current().postActionTypeById(a.id);
|
||||
var actionSummary = Discourse.ActionSummary.create(a);
|
||||
const actionSummary = Discourse.ActionSummary.create(a);
|
||||
lookup[a.actionType.name_key] = actionSummary;
|
||||
return actionSummary;
|
||||
});
|
||||
result.set('actionByName', lookup);
|
||||
json.actionByName = lookup;
|
||||
}
|
||||
|
||||
if (json && json.reply_to_user) {
|
||||
json.reply_to_user = Discourse.User.create(json.reply_to_user);
|
||||
}
|
||||
return json;
|
||||
},
|
||||
|
||||
create: function(obj) {
|
||||
var result = this._super.apply(this, arguments);
|
||||
this.createActionSummary(result);
|
||||
if (obj && obj.reply_to_user) {
|
||||
result.set('reply_to_user', Discourse.User.create(obj.reply_to_user));
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
updateBookmark: function(postId, bookmarked) {
|
||||
updateBookmark(postId, bookmarked) {
|
||||
return Discourse.ajax("/posts/" + postId + "/bookmark", {
|
||||
type: 'PUT',
|
||||
data: { bookmarked: bookmarked }
|
||||
});
|
||||
},
|
||||
|
||||
deleteMany: function(selectedPosts, selectedReplies) {
|
||||
deleteMany(selectedPosts, selectedReplies) {
|
||||
return Discourse.ajax("/posts/destroy_many", {
|
||||
type: 'DELETE',
|
||||
data: {
|
||||
@ -478,37 +381,33 @@ Discourse.Post.reopenClass({
|
||||
});
|
||||
},
|
||||
|
||||
loadRevision: function(postId, version) {
|
||||
loadRevision(postId, version) {
|
||||
return Discourse.ajax("/posts/" + postId + "/revisions/" + version + ".json").then(function (result) {
|
||||
return Ember.Object.create(result);
|
||||
});
|
||||
},
|
||||
|
||||
hideRevision: function(postId, version) {
|
||||
hideRevision(postId, version) {
|
||||
return Discourse.ajax("/posts/" + postId + "/revisions/" + version + "/hide", { type: 'PUT' });
|
||||
},
|
||||
|
||||
showRevision: function(postId, version) {
|
||||
showRevision(postId, version) {
|
||||
return Discourse.ajax("/posts/" + postId + "/revisions/" + version + "/show", { type: 'PUT' });
|
||||
},
|
||||
|
||||
loadQuote: function(postId) {
|
||||
loadQuote(postId) {
|
||||
return Discourse.ajax("/posts/" + postId + ".json").then(function (result) {
|
||||
var post = Discourse.Post.create(result);
|
||||
const post = Discourse.Post.create(result);
|
||||
return Discourse.Quote.build(post, post.get('raw'));
|
||||
});
|
||||
},
|
||||
|
||||
loadRawEmail: function(postId) {
|
||||
loadRawEmail(postId) {
|
||||
return Discourse.ajax("/posts/" + postId + "/raw-email").then(function (result) {
|
||||
return result.raw_email;
|
||||
});
|
||||
},
|
||||
|
||||
load: function(postId) {
|
||||
return Discourse.ajax("/posts/" + postId + ".json").then(function (result) {
|
||||
return Discourse.Post.create(result);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default Post;
|
||||
@ -1,16 +1,69 @@
|
||||
export default Ember.Object.extend({
|
||||
update(attrs) {
|
||||
const self = this,
|
||||
type = this.get('__type');
|
||||
return this.store.update(type, this.get('id'), attrs).then(function(result) {
|
||||
if (result && result[type]) {
|
||||
Object.keys(result).forEach(function(k) {
|
||||
attrs[k] = result[k];
|
||||
});
|
||||
import Presence from 'discourse/mixins/presence';
|
||||
|
||||
const RestModel = Ember.Object.extend(Presence, {
|
||||
isNew: Ember.computed.equal('__state', 'new'),
|
||||
isCreated: Ember.computed.equal('__state', 'created'),
|
||||
isSaving: false,
|
||||
|
||||
afterUpdate: Ember.K,
|
||||
|
||||
update(props) {
|
||||
if (this.get('isSaving')) { return Ember.RSVP.reject(); }
|
||||
|
||||
props = props || this.updateProperties();
|
||||
|
||||
const type = this.get('__type'),
|
||||
store = this.get('store');
|
||||
|
||||
const self = this;
|
||||
self.set('isSaving', true);
|
||||
return store.update(type, this.get('id'), props).then(function(res) {
|
||||
|
||||
const payload = self.__munge(res.payload || res.responseJson);
|
||||
|
||||
if (payload.success === "OK") {
|
||||
Ember.warn("An update call should return the updated attributes");
|
||||
res = props;
|
||||
}
|
||||
self.setProperties(attrs);
|
||||
return result;
|
||||
});
|
||||
|
||||
self.setProperties(payload);
|
||||
self.afterUpdate(res);
|
||||
return res;
|
||||
}).finally(() => this.set('isSaving', false));
|
||||
},
|
||||
|
||||
_saveNew(props) {
|
||||
if (this.get('isSaving')) { return Ember.RSVP.reject(); }
|
||||
|
||||
props = props || this.createProperties();
|
||||
|
||||
const type = this.get('__type'),
|
||||
store = this.get('store'),
|
||||
adapter = store.adapterFor(type);
|
||||
|
||||
const self = this;
|
||||
self.set('isSaving', true);
|
||||
return adapter.createRecord(store, type, props).then(function(res) {
|
||||
if (!res) { throw "Received no data back from createRecord"; }
|
||||
|
||||
// We can get a response back without properties, for example
|
||||
// when a post is queued.
|
||||
if (res.payload) {
|
||||
self.setProperties(self.__munge(res.payload));
|
||||
self.set('__state', 'created');
|
||||
}
|
||||
|
||||
res.target = self;
|
||||
return res;
|
||||
}).finally(() => this.set('isSaving', false));
|
||||
},
|
||||
|
||||
createProperties() {
|
||||
throw "You must overwrite `createProperties()` before saving a record";
|
||||
},
|
||||
|
||||
save(props) {
|
||||
return this.get('isNew') ? this._saveNew(props) : this.update(props);
|
||||
},
|
||||
|
||||
destroyRecord() {
|
||||
@ -18,3 +71,25 @@ export default Ember.Object.extend({
|
||||
return this.store.destroyRecord(type, this);
|
||||
}
|
||||
});
|
||||
|
||||
RestModel.reopenClass({
|
||||
|
||||
// Overwrite and JSON will be passed through here before `create` and `update`
|
||||
munge(json) {
|
||||
return json;
|
||||
},
|
||||
|
||||
create(args) {
|
||||
args = args || {};
|
||||
if (!args.store) {
|
||||
const container = Discourse.__container__;
|
||||
// Ember.warn('Use `store.createRecord` to create records instead of `.create()`');
|
||||
args.store = container.lookup('store:main');
|
||||
}
|
||||
|
||||
args.__munge = this.munge;
|
||||
return this._super(this.munge(args, args.store));
|
||||
}
|
||||
});
|
||||
|
||||
export default RestModel;
|
||||
|
||||
@ -1,7 +1,40 @@
|
||||
import RestModel from 'discourse/models/rest';
|
||||
import ResultSet from 'discourse/models/result-set';
|
||||
|
||||
const _identityMap = {};
|
||||
let _identityMap;
|
||||
|
||||
// You should only call this if you're a test scaffold
|
||||
function flushMap() {
|
||||
_identityMap = {};
|
||||
}
|
||||
|
||||
function storeMap(type, id, obj) {
|
||||
if (!id) { return; }
|
||||
|
||||
_identityMap[type] = _identityMap[type] || {};
|
||||
_identityMap[type][id] = obj;
|
||||
}
|
||||
|
||||
function fromMap(type, id) {
|
||||
const byType = _identityMap[type];
|
||||
if (byType) { return byType[id]; }
|
||||
}
|
||||
|
||||
function removeMap(type, id) {
|
||||
const byType = _identityMap[type];
|
||||
if (byType) { delete byType[id]; }
|
||||
}
|
||||
|
||||
function findAndRemoveMap(type, id) {
|
||||
const byType = _identityMap[type];
|
||||
if (byType) {
|
||||
const result = byType[id];
|
||||
delete byType[id];
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
flushMap();
|
||||
|
||||
export default Ember.Object.extend({
|
||||
pluralize(thing) {
|
||||
@ -9,21 +42,27 @@ export default Ember.Object.extend({
|
||||
},
|
||||
|
||||
findAll(type) {
|
||||
const adapter = this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
||||
const self = this;
|
||||
return adapter.findAll(this, type).then(function(result) {
|
||||
return this.adapterFor(type).findAll(this, type).then(function(result) {
|
||||
return self._resultSet(type, result);
|
||||
});
|
||||
},
|
||||
|
||||
find(type, findArgs) {
|
||||
const adapter = this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
||||
// Mostly for legacy, things like TopicList without ResultSets
|
||||
findFiltered(type, findArgs) {
|
||||
const self = this;
|
||||
return adapter.find(this, type, findArgs).then(function(result) {
|
||||
return this.adapterFor(type).find(this, type, findArgs).then(function(result) {
|
||||
return self._build(type, result);
|
||||
});
|
||||
},
|
||||
|
||||
find(type, findArgs) {
|
||||
const self = this;
|
||||
return this.adapterFor(type).find(this, type, findArgs).then(function(result) {
|
||||
if (typeof findArgs === "object") {
|
||||
return self._resultSet(type, result);
|
||||
} else {
|
||||
return self._hydrate(type, result[Ember.String.underscore(type)]);
|
||||
return self._hydrate(type, result[Ember.String.underscore(type)], result);
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -35,7 +74,7 @@ export default Ember.Object.extend({
|
||||
const typeName = Ember.String.underscore(self.pluralize(type)),
|
||||
totalRows = result["total_rows_" + typeName] || result.get('totalRows'),
|
||||
loadMoreUrl = result["load_more_" + typeName],
|
||||
content = result[typeName].map(obj => self._hydrate(type, obj));
|
||||
content = result[typeName].map(obj => self._hydrate(type, obj, result));
|
||||
|
||||
resultSet.setProperties({ totalRows, loadMoreUrl });
|
||||
resultSet.get('content').pushObjects(content);
|
||||
@ -48,58 +87,120 @@ export default Ember.Object.extend({
|
||||
},
|
||||
|
||||
update(type, id, attrs) {
|
||||
const adapter = this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
||||
return adapter.update(this, type, id, attrs, function(result) {
|
||||
return this.adapterFor(type).update(this, type, id, attrs, function(result) {
|
||||
if (result && result[type] && result[type].id) {
|
||||
const oldRecord = _identityMap[type][id];
|
||||
delete _identityMap[type][id];
|
||||
_identityMap[type][result[type].id] = oldRecord;
|
||||
const oldRecord = findAndRemoveMap(type, id);
|
||||
storeMap(type, result[type].id, oldRecord);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
},
|
||||
|
||||
createRecord(type, attrs) {
|
||||
return this._hydrate(type, attrs);
|
||||
attrs = attrs || {};
|
||||
return !!attrs.id ? this._hydrate(type, attrs) : this._build(type, attrs);
|
||||
},
|
||||
|
||||
destroyRecord(type, record) {
|
||||
const adapter = this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
||||
return adapter.destroyRecord(this, type, record).then(function(result) {
|
||||
const forType = _identityMap[type];
|
||||
if (forType) { delete forType[record.get('id')]; }
|
||||
return this.adapterFor(type).destroyRecord(this, type, record).then(function(result) {
|
||||
removeMap(type, record.get('id'));
|
||||
return result;
|
||||
});
|
||||
},
|
||||
|
||||
_resultSet(type, result) {
|
||||
const typeName = Ember.String.underscore(this.pluralize(type)),
|
||||
content = result[typeName].map(obj => this._hydrate(type, obj)),
|
||||
content = result[typeName].map(obj => this._hydrate(type, obj, result)),
|
||||
totalRows = result["total_rows_" + typeName] || content.length,
|
||||
loadMoreUrl = result["load_more_" + typeName];
|
||||
|
||||
return ResultSet.create({ content, totalRows, loadMoreUrl, store: this, __type: type });
|
||||
},
|
||||
|
||||
_hydrate(type, obj) {
|
||||
if (!obj) { throw "Can't hydrate " + type + " of `null`"; }
|
||||
if (!obj.id) { throw "Can't hydrate " + type + " without an `id`"; }
|
||||
|
||||
_identityMap[type] = _identityMap[type] || {};
|
||||
|
||||
const existing = _identityMap[type][obj.id];
|
||||
if (existing) {
|
||||
delete obj.id;
|
||||
existing.setProperties(obj);
|
||||
return existing;
|
||||
}
|
||||
|
||||
_build(type, obj) {
|
||||
obj.store = this;
|
||||
obj.__type = type;
|
||||
obj.__state = obj.id ? "created" : "new";
|
||||
|
||||
const klass = this.container.lookupFactory('model:' + type) || RestModel;
|
||||
const model = klass.create(obj);
|
||||
_identityMap[type][obj.id] = model;
|
||||
|
||||
storeMap(type, obj.id, model);
|
||||
return model;
|
||||
},
|
||||
|
||||
adapterFor(type) {
|
||||
return this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
||||
},
|
||||
|
||||
_lookupSubType(subType, id, root) {
|
||||
|
||||
// cheat: we know we already have categories in memory
|
||||
if (subType === 'category') {
|
||||
return Discourse.Category.findById(id);
|
||||
}
|
||||
|
||||
const pluralType = this.pluralize(subType);
|
||||
const collection = root[this.pluralize(subType)];
|
||||
if (collection) {
|
||||
const hashedProp = "__hashed_" + pluralType;
|
||||
let hashedCollection = root[hashedProp];
|
||||
if (!hashedCollection) {
|
||||
hashedCollection = {};
|
||||
collection.forEach(function(it) {
|
||||
hashedCollection[it.id] = it;
|
||||
});
|
||||
root[hashedProp] = hashedCollection;
|
||||
}
|
||||
|
||||
const found = hashedCollection[id];
|
||||
if (found) {
|
||||
const hydrated = this._hydrate(subType, found, root);
|
||||
hashedCollection[id] = hydrated;
|
||||
return hydrated;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_hydrateEmbedded(obj, root) {
|
||||
const self = this;
|
||||
Object.keys(obj).forEach(function(k) {
|
||||
const m = /(.+)\_id$/.exec(k);
|
||||
if (m) {
|
||||
const subType = m[1];
|
||||
const hydrated = self._lookupSubType(subType, obj[k], root);
|
||||
if (hydrated) {
|
||||
obj[subType] = hydrated;
|
||||
delete obj[k];
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_hydrate(type, obj, root) {
|
||||
if (!obj) { throw "Can't hydrate " + type + " of `null`"; }
|
||||
if (!obj.id) { throw "Can't hydrate " + type + " without an `id`"; }
|
||||
|
||||
root = root || obj;
|
||||
|
||||
// Experimental: If serialized with a certain option we'll wire up embedded objects
|
||||
// automatically.
|
||||
if (root.__rest_serializer === "1") {
|
||||
this._hydrateEmbedded(obj, root);
|
||||
}
|
||||
|
||||
const existing = fromMap(type, obj.id);
|
||||
if (existing === obj) { return existing; }
|
||||
|
||||
if (existing) {
|
||||
delete obj.id;
|
||||
const klass = this.container.lookupFactory('model:' + type) || RestModel;
|
||||
existing.setProperties(klass.munge(obj));
|
||||
return existing;
|
||||
}
|
||||
|
||||
return this._build(type, obj);
|
||||
}
|
||||
});
|
||||
|
||||
export { flushMap };
|
||||
|
||||
@ -2,7 +2,9 @@
|
||||
A model representing a Topic's details that aren't always present, such as a list of participants.
|
||||
When showing topics in lists and such this information should not be required.
|
||||
**/
|
||||
const TopicDetails = Discourse.Model.extend({
|
||||
import RestModel from 'discourse/models/rest';
|
||||
|
||||
const TopicDetails = RestModel.extend({
|
||||
loaded: false,
|
||||
|
||||
updateFromJson(details) {
|
||||
@ -15,8 +17,9 @@ const TopicDetails = Discourse.Model.extend({
|
||||
}
|
||||
|
||||
if (details.suggested_topics) {
|
||||
const store = this.store;
|
||||
details.suggested_topics = details.suggested_topics.map(function (st) {
|
||||
return Discourse.Topic.create(st);
|
||||
return store.createRecord('topic', st);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
168
app/assets/javascripts/discourse/models/topic-list.js.es6
Normal file
168
app/assets/javascripts/discourse/models/topic-list.js.es6
Normal file
@ -0,0 +1,168 @@
|
||||
import RestModel from 'discourse/models/rest';
|
||||
import Model from 'discourse/models/model';
|
||||
|
||||
|
||||
function topicsFrom(result, store) {
|
||||
if (!result) { return; }
|
||||
|
||||
// Stitch together our side loaded data
|
||||
const categories = Discourse.Category.list(),
|
||||
users = Model.extractByKey(result.users, Discourse.User);
|
||||
|
||||
return result.topic_list.topics.map(function (t) {
|
||||
t.category = categories.findBy('id', t.category_id);
|
||||
t.posters.forEach(function(p) {
|
||||
p.user = users[p.user_id];
|
||||
});
|
||||
if (t.participants) {
|
||||
t.participants.forEach(function(p) {
|
||||
p.user = users[p.user_id];
|
||||
});
|
||||
}
|
||||
return store.createRecord('topic', t);
|
||||
});
|
||||
}
|
||||
|
||||
const TopicList = RestModel.extend({
|
||||
canLoadMore: Em.computed.notEmpty("more_topics_url"),
|
||||
|
||||
forEachNew: function(topics, callback) {
|
||||
const topicIds = [];
|
||||
_.each(this.get('topics'),function(topic) {
|
||||
topicIds[topic.get('id')] = true;
|
||||
});
|
||||
|
||||
_.each(topics,function(topic) {
|
||||
if(!topicIds[topic.id]) {
|
||||
callback(topic);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
refreshSort: function(order, ascending) {
|
||||
const self = this,
|
||||
params = this.get('params');
|
||||
|
||||
params.order = order || params.order;
|
||||
|
||||
if (ascending === undefined) {
|
||||
params.ascending = ascending;
|
||||
} else {
|
||||
params.ascending = ascending;
|
||||
}
|
||||
|
||||
this.set('loaded', false);
|
||||
const store = this.store;
|
||||
store.findFiltered('topicList', {filter: this.get('filter'), params}).then(function(tl) {
|
||||
const newTopics = tl.get('topics'),
|
||||
topics = self.get('topics');
|
||||
|
||||
topics.clear();
|
||||
topics.pushObjects(newTopics);
|
||||
self.setProperties({ loaded: true, more_topics_url: newTopics.get('more_topics_url') });
|
||||
});
|
||||
},
|
||||
|
||||
loadMore: function() {
|
||||
if (this.get('loadingMore')) { return Ember.RSVP.resolve(); }
|
||||
|
||||
const moreUrl = this.get('more_topics_url');
|
||||
if (moreUrl) {
|
||||
const self = this;
|
||||
this.set('loadingMore', true);
|
||||
|
||||
const store = this.store;
|
||||
return Discourse.ajax({url: moreUrl}).then(function (result) {
|
||||
let topicsAdded = 0;
|
||||
|
||||
if (result) {
|
||||
// the new topics loaded from the server
|
||||
const newTopics = topicsFrom(result, store),
|
||||
topics = self.get("topics");
|
||||
|
||||
self.forEachNew(newTopics, function(t) {
|
||||
t.set('highlight', topicsAdded++ === 0);
|
||||
topics.pushObject(t);
|
||||
});
|
||||
|
||||
self.setProperties({
|
||||
loadingMore: false,
|
||||
more_topics_url: result.topic_list.more_topics_url
|
||||
});
|
||||
|
||||
Discourse.Session.currentProp('topicList', self);
|
||||
return self.get('more_topics_url');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Return a promise indicating no more results
|
||||
return Ember.RSVP.resolve();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// loads topics with these ids "before" the current topics
|
||||
loadBefore: function(topic_ids){
|
||||
const topicList = this,
|
||||
topics = this.get('topics');
|
||||
|
||||
// refresh dupes
|
||||
topics.removeObjects(topics.filter(function(topic){
|
||||
return topic_ids.indexOf(topic.get('id')) >= 0;
|
||||
}));
|
||||
|
||||
const url = Discourse.getURL("/") + this.get('filter') + "?topic_ids=" + topic_ids.join(",");
|
||||
|
||||
const store = this.store;
|
||||
return Discourse.ajax({ url }).then(function(result) {
|
||||
let i = 0;
|
||||
topicList.forEachNew(topicsFrom(result, store), function(t) {
|
||||
// highlight the first of the new topics so we can get a visual feedback
|
||||
t.set('highlight', true);
|
||||
topics.insertAt(i,t);
|
||||
i++;
|
||||
});
|
||||
Discourse.Session.currentProp('topicList', topicList);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
TopicList.reopenClass({
|
||||
|
||||
munge(json, store) {
|
||||
json.inserted = json.inserted || [];
|
||||
json.can_create_topic = json.topic_list.can_create_topic;
|
||||
json.more_topics_url = json.topic_list.more_topics_url;
|
||||
json.draft_key = json.topic_list.draft_key;
|
||||
json.draft_sequence = json.topic_list.draft_sequence;
|
||||
json.draft = json.topic_list.draft;
|
||||
json.for_period = json.topic_list.for_period;
|
||||
json.loaded = true;
|
||||
json.per_page = json.topic_list.per_page;
|
||||
json.topics = topicsFrom(json, store);
|
||||
|
||||
if (json.topic_list.filtered_category) {
|
||||
json.category = Discourse.Category.create(json.topic_list.filtered_category);
|
||||
}
|
||||
return json;
|
||||
},
|
||||
|
||||
find(filter, params) {
|
||||
const store = Discourse.__container__.lookup('store:main');
|
||||
return store.findFiltered('topicList', {filter, params});
|
||||
},
|
||||
|
||||
list(filter) {
|
||||
Ember.warn('`Discourse.TopicList.list` is deprecated. Use the store instead');
|
||||
return this.find(filter);
|
||||
},
|
||||
|
||||
// Sets `hideCategory` if all topics in the last have a particular category
|
||||
hideUniformCategory(list, category) {
|
||||
const hideCategory = !list.get('topics').any(function (t) { return t.get('category') !== category; });
|
||||
list.set('hideCategory', hideCategory);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default TopicList;
|
||||
@ -1,7 +1,6 @@
|
||||
import TopicDetails from 'discourse/models/topic-details';
|
||||
import PostStream from 'discourse/models/post-stream';
|
||||
import RestModel from 'discourse/models/rest';
|
||||
|
||||
const Topic = Discourse.Model.extend({
|
||||
const Topic = RestModel.extend({
|
||||
|
||||
// returns createdAt if there's no bumped date
|
||||
bumpedAt: function() {
|
||||
@ -23,7 +22,7 @@ const Topic = Discourse.Model.extend({
|
||||
}.property('created_at'),
|
||||
|
||||
postStream: function() {
|
||||
return PostStream.create({topic: this});
|
||||
return this.store.createRecord('postStream', {id: this.get('id'), topic: this});
|
||||
}.property(),
|
||||
|
||||
replyCount: function() {
|
||||
@ -31,7 +30,7 @@ const Topic = Discourse.Model.extend({
|
||||
}.property('posts_count'),
|
||||
|
||||
details: function() {
|
||||
return TopicDetails.create({topic: this});
|
||||
return this.store.createRecord('topicDetails', {id: this.get('id'), topic: this});
|
||||
}.property(),
|
||||
|
||||
invisible: Em.computed.not('visible'),
|
||||
@ -41,18 +40,18 @@ const Topic = Discourse.Model.extend({
|
||||
return ({ type: 'topic', id: this.get('id') });
|
||||
}.property('id'),
|
||||
|
||||
category: function() {
|
||||
const categoryId = this.get('category_id');
|
||||
if (categoryId) {
|
||||
return Discourse.Category.list().findProperty('id', categoryId);
|
||||
}
|
||||
_categoryIdChanged: function() {
|
||||
this.set('category', Discourse.Category.findById(this.get('category_id')));
|
||||
}.observes('category_id').on('init'),
|
||||
|
||||
_categoryNameChanged: function() {
|
||||
const categoryName = this.get('categoryName');
|
||||
let category;
|
||||
if (categoryName) {
|
||||
return Discourse.Category.list().findProperty('name', categoryName);
|
||||
category = Discourse.Category.list().findProperty('name', categoryName);
|
||||
}
|
||||
return null;
|
||||
}.property('category_id', 'categoryName'),
|
||||
this.set('category', category);
|
||||
}.observes('categoryName'),
|
||||
|
||||
categoryClass: function() {
|
||||
return 'category-' + this.get('category.fullSlug');
|
||||
@ -408,7 +407,6 @@ Topic.reopenClass({
|
||||
// The title can be cleaned up server side
|
||||
props.title = result.basic_topic.title;
|
||||
props.fancy_title = result.basic_topic.fancy_title;
|
||||
|
||||
topic.setProperties(props);
|
||||
});
|
||||
},
|
||||
|
||||
@ -1,272 +0,0 @@
|
||||
function finderFor(filter, params) {
|
||||
return function() {
|
||||
var url = Discourse.getURL("/") + filter + ".json";
|
||||
|
||||
if (params) {
|
||||
var keys = Object.keys(params),
|
||||
encoded = [];
|
||||
|
||||
keys.forEach(function(p) {
|
||||
var value = params[p];
|
||||
if (typeof value !== 'undefined') {
|
||||
encoded.push(p + "=" + value);
|
||||
}
|
||||
});
|
||||
|
||||
if (encoded.length > 0) {
|
||||
url += "?" + encoded.join('&');
|
||||
}
|
||||
}
|
||||
return Discourse.ajax(url);
|
||||
};
|
||||
}
|
||||
|
||||
Discourse.TopicList = Discourse.Model.extend({
|
||||
canLoadMore: Em.computed.notEmpty("more_topics_url"),
|
||||
|
||||
forEachNew: function(topics, callback) {
|
||||
var topicIds = [];
|
||||
_.each(this.get('topics'),function(topic) {
|
||||
topicIds[topic.get('id')] = true;
|
||||
});
|
||||
|
||||
_.each(topics,function(topic) {
|
||||
if(!topicIds[topic.id]) {
|
||||
callback(topic);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
refreshSort: function(order, ascending) {
|
||||
var self = this,
|
||||
params = this.get('params');
|
||||
|
||||
params.order = order || params.order;
|
||||
|
||||
if (ascending === undefined) {
|
||||
params.ascending = ascending;
|
||||
} else {
|
||||
params.ascending = ascending;
|
||||
}
|
||||
|
||||
this.set('loaded', false);
|
||||
var finder = finderFor(this.get('filter'), params);
|
||||
finder().then(function (result) {
|
||||
var newTopics = Discourse.TopicList.topicsFrom(result),
|
||||
topics = self.get('topics');
|
||||
|
||||
topics.clear();
|
||||
topics.pushObjects(newTopics);
|
||||
self.setProperties({ loaded: true, more_topics_url: result.topic_list.more_topics_url });
|
||||
});
|
||||
},
|
||||
|
||||
loadMore: function() {
|
||||
if (this.get('loadingMore')) { return Ember.RSVP.resolve(); }
|
||||
|
||||
var moreUrl = this.get('more_topics_url');
|
||||
if (moreUrl) {
|
||||
var self = this;
|
||||
this.set('loadingMore', true);
|
||||
|
||||
return Discourse.ajax({url: moreUrl}).then(function (result) {
|
||||
var topicsAdded = 0;
|
||||
if (result) {
|
||||
// the new topics loaded from the server
|
||||
var newTopics = Discourse.TopicList.topicsFrom(result),
|
||||
topics = self.get("topics");
|
||||
|
||||
self.forEachNew(newTopics, function(t) {
|
||||
t.set('highlight', topicsAdded++ === 0);
|
||||
topics.pushObject(t);
|
||||
});
|
||||
|
||||
self.setProperties({
|
||||
loadingMore: false,
|
||||
more_topics_url: result.topic_list.more_topics_url
|
||||
});
|
||||
|
||||
Discourse.Session.currentProp('topicList', self);
|
||||
return self.get('more_topics_url');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Return a promise indicating no more results
|
||||
return Ember.RSVP.resolve();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// loads topics with these ids "before" the current topics
|
||||
loadBefore: function(topic_ids){
|
||||
var topicList = this,
|
||||
topics = this.get('topics');
|
||||
|
||||
// refresh dupes
|
||||
topics.removeObjects(topics.filter(function(topic){
|
||||
return topic_ids.indexOf(topic.get('id')) >= 0;
|
||||
}));
|
||||
|
||||
Discourse.TopicList.loadTopics(topic_ids, this.get('filter'))
|
||||
.then(function(newTopics){
|
||||
var i = 0;
|
||||
topicList.forEachNew(newTopics, function(t) {
|
||||
// highlight the first of the new topics so we can get a visual feedback
|
||||
t.set('highlight', true);
|
||||
topics.insertAt(i,t);
|
||||
i++;
|
||||
});
|
||||
Discourse.Session.currentProp('topicList', topicList);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Discourse.TopicList.reopenClass({
|
||||
|
||||
loadTopics: function(topic_ids, filter) {
|
||||
return new Ember.RSVP.Promise(function(resolve, reject) {
|
||||
var url = Discourse.getURL("/") + filter + "?topic_ids=" + topic_ids.join(",");
|
||||
|
||||
Discourse.ajax({url: url}).then(function (result) {
|
||||
if (result) {
|
||||
// the new topics loaded from the server
|
||||
var newTopics = Discourse.TopicList.topicsFrom(result);
|
||||
resolve(newTopics);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
Stitch together side loaded topic data
|
||||
|
||||
@method topicsFrom
|
||||
@param {Object} result JSON object with topic data
|
||||
@returns {Array} the list of topics
|
||||
**/
|
||||
topicsFrom: function(result) {
|
||||
// Stitch together our side loaded data
|
||||
var categories = Discourse.Category.list(),
|
||||
users = this.extractByKey(result.users, Discourse.User);
|
||||
|
||||
return result.topic_list.topics.map(function (t) {
|
||||
t.category = categories.findBy('id', t.category_id);
|
||||
t.posters.forEach(function(p) {
|
||||
p.user = users[p.user_id];
|
||||
});
|
||||
if (t.participants) {
|
||||
t.participants.forEach(function(p) {
|
||||
p.user = users[p.user_id];
|
||||
});
|
||||
}
|
||||
return Discourse.Topic.create(t);
|
||||
});
|
||||
},
|
||||
|
||||
from: function(result, filter, params) {
|
||||
var topicList = Discourse.TopicList.create({
|
||||
inserted: [],
|
||||
filter: filter,
|
||||
params: params || {},
|
||||
topics: Discourse.TopicList.topicsFrom(result),
|
||||
can_create_topic: result.topic_list.can_create_topic,
|
||||
more_topics_url: result.topic_list.more_topics_url,
|
||||
draft_key: result.topic_list.draft_key,
|
||||
draft_sequence: result.topic_list.draft_sequence,
|
||||
draft: result.topic_list.draft,
|
||||
for_period: result.topic_list.for_period,
|
||||
loaded: true,
|
||||
per_page: result.topic_list.per_page
|
||||
});
|
||||
|
||||
if (result.topic_list.filtered_category) {
|
||||
topicList.set('category', Discourse.Category.create(result.topic_list.filtered_category));
|
||||
}
|
||||
|
||||
return topicList;
|
||||
},
|
||||
|
||||
/**
|
||||
Lists topics on a given menu item
|
||||
|
||||
@method list
|
||||
@param {Object} filter The menu item to filter to
|
||||
@param {Object} params Any additional params to pass to TopicList.find()
|
||||
@param {Object} extras Additional finding options, such as caching
|
||||
@returns {Promise} a promise that resolves to the list of topics
|
||||
**/
|
||||
list: function(filter, filterParams, extras) {
|
||||
var tracking = Discourse.TopicTrackingState.current();
|
||||
|
||||
extras = extras || {};
|
||||
return new Ember.RSVP.Promise(function(resolve) {
|
||||
var session = Discourse.Session.current();
|
||||
|
||||
if (extras.cached) {
|
||||
var cachedList = session.get('topicList');
|
||||
|
||||
// Try to use the cached version if it exists and is greater than the topics per page
|
||||
if (cachedList && (cachedList.get('filter') === filter) &&
|
||||
(cachedList.get('topics.length') || 0) > cachedList.get('per_page') &&
|
||||
_.isEqual(cachedList.get('listParams'), filterParams)) {
|
||||
cachedList.set('loaded', true);
|
||||
|
||||
if (tracking) {
|
||||
tracking.updateTopics(cachedList.get('topics'));
|
||||
}
|
||||
return resolve(cachedList);
|
||||
}
|
||||
session.set('topicList', null);
|
||||
} else {
|
||||
// Clear the cache
|
||||
session.setProperties({topicList: null, topicListScrollPosition: null});
|
||||
}
|
||||
|
||||
|
||||
// Clean up any string parameters that might slip through
|
||||
filterParams = filterParams || {};
|
||||
Ember.keys(filterParams).forEach(function(k) {
|
||||
var val = filterParams[k];
|
||||
if (val === "undefined" || val === "null" || val === 'false') {
|
||||
filterParams[k] = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
var findParams = {};
|
||||
Discourse.SiteSettings.top_menu.split('|').forEach(function (i) {
|
||||
if (i.indexOf(filter) === 0) {
|
||||
var exclude = i.split("-");
|
||||
if (exclude && exclude.length === 2) {
|
||||
findParams.exclude_category = exclude[1];
|
||||
}
|
||||
}
|
||||
});
|
||||
return resolve(Discourse.TopicList.find(filter, _.extend(findParams, filterParams || {})));
|
||||
|
||||
}).then(function(list) {
|
||||
list.set('listParams', filterParams);
|
||||
if (tracking) {
|
||||
tracking.sync(list, list.filter);
|
||||
tracking.trackIncoming(list.filter);
|
||||
}
|
||||
Discourse.Session.currentProp('topicList', list);
|
||||
return list;
|
||||
});
|
||||
},
|
||||
|
||||
find: function(filter, params) {
|
||||
return PreloadStore.getAndRemove("topic_list_" + filter, finderFor(filter, params)).then(function(result) {
|
||||
return Discourse.TopicList.from(result, filter, params);
|
||||
});
|
||||
},
|
||||
|
||||
// Sets `hideCategory` if all topics in the last have a particular category
|
||||
hideUniformCategory: function(list, category) {
|
||||
var hideCategory = !list.get('topics').any(function (t) { return t.get('category') !== category; });
|
||||
list.set('hideCategory', hideCategory);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import RestModel from 'discourse/models/rest';
|
||||
import avatarTemplate from 'discourse/lib/avatar-template';
|
||||
|
||||
const User = Discourse.Model.extend({
|
||||
const User = RestModel.extend({
|
||||
|
||||
hasPMs: Em.computed.gt("private_messages_stats.all", 0),
|
||||
hasStartedPMs: Em.computed.gt("private_messages_stats.mine", 0),
|
||||
|
||||
@ -1,12 +1,3 @@
|
||||
/**
|
||||
A data model representing actions users have taken
|
||||
|
||||
@class UserAction
|
||||
@extends Discourse.Model
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
|
||||
var UserActionTypes = {
|
||||
likes_given: 1,
|
||||
likes_received: 2,
|
||||
@ -18,7 +9,8 @@ var UserActionTypes = {
|
||||
quotes: 9,
|
||||
edits: 11,
|
||||
messages_sent: 12,
|
||||
messages_received: 13
|
||||
messages_received: 13,
|
||||
pending: 14
|
||||
},
|
||||
InvertedActionTypes = {};
|
||||
|
||||
|
||||
@ -66,7 +66,7 @@ export default function() {
|
||||
this.route('flaggedPosts', { path: '/flagged-posts' });
|
||||
this.route('deletedPosts', { path: '/deleted-posts' });
|
||||
|
||||
this.resource('userPrivateMessages', { path: '/private-messages' }, function() {
|
||||
this.resource('userPrivateMessages', { path: '/messages' }, function() {
|
||||
this.route('mine');
|
||||
this.route('unread');
|
||||
});
|
||||
@ -93,4 +93,6 @@ export default function() {
|
||||
this.resource('badges', function() {
|
||||
this.route('show', {path: '/:id/:slug'});
|
||||
});
|
||||
|
||||
this.resource('queued-posts', { path: '/queued-posts' });
|
||||
}
|
||||
|
||||
@ -37,6 +37,10 @@ const ApplicationRoute = Discourse.Route.extend({
|
||||
this.controllerFor('topic-entrance').send('show', data);
|
||||
},
|
||||
|
||||
postWasEnqueued() {
|
||||
showModal('post-enqueued', {title: 'queue.approval.title' });
|
||||
},
|
||||
|
||||
composePrivateMessage(user, post) {
|
||||
const self = this;
|
||||
this.transitionTo('userActivity', user).then(function () {
|
||||
@ -76,12 +80,12 @@ const ApplicationRoute = Discourse.Route.extend({
|
||||
showCreateAccount: unlessReadOnly('handleShowCreateAccount'),
|
||||
|
||||
showForgotPassword() {
|
||||
showModal('forgotPassword');
|
||||
showModal('forgotPassword', { title: 'forgot_password.title' });
|
||||
},
|
||||
|
||||
showNotActivated(props) {
|
||||
showModal('notActivated');
|
||||
this.controllerFor('notActivated').setProperties(props);
|
||||
const controller = showModal('not-activated', {title: 'log_in' });
|
||||
controller.setProperties(props);
|
||||
},
|
||||
|
||||
showUploadSelector(composerView) {
|
||||
@ -90,13 +94,13 @@ const ApplicationRoute = Discourse.Route.extend({
|
||||
},
|
||||
|
||||
showKeyboardShortcutsHelp() {
|
||||
showModal('keyboardShortcutsHelp');
|
||||
showModal('keyboard-shortcuts-help', { title: 'keyboard_shortcuts_help.title'});
|
||||
},
|
||||
|
||||
showSearchHelp() {
|
||||
// TODO: @EvitTrout how do we get a loading indicator here?
|
||||
Discourse.ajax("/static/search_help.html", { dataType: 'html' }).then(function(html){
|
||||
showModal('searchHelp', html);
|
||||
Discourse.ajax("/static/search_help.html", { dataType: 'html' }).then(function(model){
|
||||
showModal('searchHelp', { model });
|
||||
});
|
||||
},
|
||||
|
||||
@ -120,9 +124,9 @@ const ApplicationRoute = Discourse.Route.extend({
|
||||
|
||||
editCategory(category) {
|
||||
const self = this;
|
||||
Discourse.Category.reloadById(category.get('id')).then(function (c) {
|
||||
self.site.updateCategory(c);
|
||||
showModal('editCategory', c);
|
||||
Discourse.Category.reloadById(category.get('id')).then(function (model) {
|
||||
self.site.updateCategory(model);
|
||||
showModal('editCategory', { model });
|
||||
self.controllerFor('editCategory').set('selectedTab', 'general');
|
||||
});
|
||||
},
|
||||
@ -140,7 +144,7 @@ const ApplicationRoute = Discourse.Route.extend({
|
||||
const controllerName = w.replace('modal/', ''),
|
||||
factory = this.container.lookupFactory('controller:' + controllerName);
|
||||
|
||||
this.render(w, {into: 'topicBulkActions', outlet: 'bulkOutlet', controller: factory ? controllerName : 'topic-bulk-actions'});
|
||||
this.render(w, {into: 'modal/topic-bulk-actions', outlet: 'bulkOutlet', controller: factory ? controllerName : 'topic-bulk-actions'});
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { queryParams, filterQueryParams } from 'discourse/routes/build-topic-route';
|
||||
import { queryParams, filterQueryParams, findTopicList } from 'discourse/routes/build-topic-route';
|
||||
|
||||
// A helper function to create a category route with parameters
|
||||
export default function(filter, params) {
|
||||
@ -52,7 +52,7 @@ export default function(filter, params) {
|
||||
var findOpts = filterQueryParams(transition.queryParams, params),
|
||||
extras = { cached: this.isPoppedState(transition) };
|
||||
|
||||
return Discourse.TopicList.list(listFilter, findOpts, extras).then(function(list) {
|
||||
return findTopicList(this.store, listFilter, findOpts, extras).then(function(list) {
|
||||
Discourse.TopicList.hideUniformCategory(list, model);
|
||||
self.set('topics', list);
|
||||
});
|
||||
|
||||
@ -11,6 +11,65 @@ function filterQueryParams(params, defaultParams) {
|
||||
return findOpts;
|
||||
}
|
||||
|
||||
function findTopicList(store, filter, filterParams, extras) {
|
||||
const tracking = Discourse.TopicTrackingState.current();
|
||||
|
||||
extras = extras || {};
|
||||
return new Ember.RSVP.Promise(function(resolve) {
|
||||
const session = Discourse.Session.current();
|
||||
|
||||
if (extras.cached) {
|
||||
const cachedList = session.get('topicList');
|
||||
|
||||
// Try to use the cached version if it exists and is greater than the topics per page
|
||||
if (cachedList && (cachedList.get('filter') === filter) &&
|
||||
(cachedList.get('topics.length') || 0) > cachedList.get('per_page') &&
|
||||
_.isEqual(cachedList.get('listParams'), filterParams)) {
|
||||
cachedList.set('loaded', true);
|
||||
|
||||
if (tracking) {
|
||||
tracking.updateTopics(cachedList.get('topics'));
|
||||
}
|
||||
return resolve(cachedList);
|
||||
}
|
||||
session.set('topicList', null);
|
||||
} else {
|
||||
// Clear the cache
|
||||
session.setProperties({topicList: null, topicListScrollPosition: null});
|
||||
}
|
||||
|
||||
|
||||
// Clean up any string parameters that might slip through
|
||||
filterParams = filterParams || {};
|
||||
Ember.keys(filterParams).forEach(function(k) {
|
||||
const val = filterParams[k];
|
||||
if (val === "undefined" || val === "null" || val === 'false') {
|
||||
filterParams[k] = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
const findParams = {};
|
||||
Discourse.SiteSettings.top_menu.split('|').forEach(function (i) {
|
||||
if (i.indexOf(filter) === 0) {
|
||||
const exclude = i.split("-");
|
||||
if (exclude && exclude.length === 2) {
|
||||
findParams.exclude_category = exclude[1];
|
||||
}
|
||||
}
|
||||
});
|
||||
return resolve(store.findFiltered('topicList', { filter, params:_.extend(findParams, filterParams || {})}));
|
||||
|
||||
}).then(function(list) {
|
||||
list.set('listParams', filterParams);
|
||||
if (tracking) {
|
||||
tracking.sync(list, list.filter);
|
||||
tracking.trackIncoming(list.filter);
|
||||
}
|
||||
Discourse.Session.currentProp('topicList', list);
|
||||
return list;
|
||||
});
|
||||
}
|
||||
|
||||
export default function(filter, extras) {
|
||||
extras = extras || {};
|
||||
return Discourse.Route.extend({
|
||||
@ -28,7 +87,7 @@ export default function(filter, extras) {
|
||||
const findOpts = filterQueryParams(transition.queryParams),
|
||||
extras = { cached: this.isPoppedState(transition) };
|
||||
|
||||
return Discourse.TopicList.list(filter, findOpts, extras);
|
||||
return findTopicList(this.store, filter, findOpts, extras);
|
||||
},
|
||||
|
||||
titleToken() {
|
||||
@ -72,4 +131,4 @@ export default function(filter, extras) {
|
||||
}, extras);
|
||||
}
|
||||
|
||||
export { filterQueryParams };
|
||||
export { filterQueryParams, findTopicList };
|
||||
|
||||
@ -14,7 +14,7 @@ export default function (viewName, path) {
|
||||
},
|
||||
|
||||
model: function() {
|
||||
return Discourse.TopicList.find('topics/' + path + '/' + this.modelFor('user').get('username_lower'));
|
||||
return this.store.findFiltered('topicList', {filter: 'topics/' + path + '/' + this.modelFor('user').get('username_lower')});
|
||||
},
|
||||
|
||||
setupController: function() {
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import showModal from 'discourse/lib/show-modal';
|
||||
|
||||
const DiscourseRoute = Ember.Route.extend({
|
||||
|
||||
// Set to true to refresh a model without a transition if a query param
|
||||
@ -210,11 +208,6 @@ DiscourseRoute.reopenClass({
|
||||
|
||||
this.route('unknown', {path: '*path'});
|
||||
});
|
||||
},
|
||||
|
||||
showModal: function(route, name, model) {
|
||||
Ember.warn('DEPRECATED `Discourse.Route.showModal` - use `showModal` instead');
|
||||
showModal(name, model);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -46,11 +46,13 @@ Discourse.DiscoveryCategoriesRoute = Discourse.Route.extend(Discourse.OpenCompos
|
||||
const groups = this.site.groups,
|
||||
everyoneName = groups.findBy('id', 0).name;
|
||||
|
||||
showModal('editCategory', Discourse.Category.create({
|
||||
const model = Discourse.Category.create({
|
||||
color: 'AB9364', text_color: 'FFFFFF', group_permissions: [{group_name: everyoneName, permission_type: 1}],
|
||||
available_groups: groups.map(g => g.name),
|
||||
allow_badges: true
|
||||
}));
|
||||
});
|
||||
|
||||
showModal('editCategory', { model });
|
||||
this.controllerFor('editCategory').set('selectedTab', 'general');
|
||||
},
|
||||
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
import DiscourseRoute from 'discourse/routes/discourse';
|
||||
|
||||
export default DiscourseRoute.extend({
|
||||
model() {
|
||||
return this.store.find('queuedPost', {status: 'new'});
|
||||
}
|
||||
});
|
||||
@ -1,6 +1,9 @@
|
||||
// This route is used for retrieving a topic based on params
|
||||
export default Discourse.Route.extend({
|
||||
|
||||
// Avoid default model hook
|
||||
model: function(p) { return p; },
|
||||
|
||||
setupController: function(controller, params) {
|
||||
params = params || {};
|
||||
params.track_visit = true;
|
||||
@ -15,6 +18,7 @@ export default Discourse.Route.extend({
|
||||
if (params.nearPost === "last") { params.nearPost = 999999999; }
|
||||
|
||||
var self = this;
|
||||
params.forceLoad = true;
|
||||
postStream.refresh(params).then(function () {
|
||||
|
||||
// TODO we are seeing errors where closest post is null and this is exploding
|
||||
|
||||
@ -5,7 +5,6 @@ let isTransitioning = false,
|
||||
const SCROLL_DELAY = 500;
|
||||
|
||||
import ShowFooter from "discourse/mixins/show-footer";
|
||||
import Topic from 'discourse/models/topic';
|
||||
import showModal from 'discourse/lib/show-modal';
|
||||
|
||||
const TopicRoute = Discourse.Route.extend(ShowFooter, {
|
||||
@ -44,52 +43,52 @@ const TopicRoute = Discourse.Route.extend(ShowFooter, {
|
||||
this.controllerFor("topic-admin-menu").send("show");
|
||||
},
|
||||
|
||||
showFlags(post) {
|
||||
showModal('flag', post);
|
||||
showFlags(model) {
|
||||
showModal('flag', { model });
|
||||
this.controllerFor('flag').setProperties({ selected: null });
|
||||
},
|
||||
|
||||
showFlagTopic(topic) {
|
||||
showModal('flag', topic);
|
||||
showFlagTopic(model) {
|
||||
showModal('flag', { model });
|
||||
this.controllerFor('flag').setProperties({ selected: null, flagTopic: true });
|
||||
},
|
||||
|
||||
showAutoClose() {
|
||||
showModal('editTopicAutoClose', this.modelFor('topic'));
|
||||
showModal('edit-topic-auto-close', { model: this.modelFor('topic'), title: 'topic.auto_close_title' });
|
||||
this.controllerFor('modal').set('modalClass', 'edit-auto-close-modal');
|
||||
},
|
||||
|
||||
showFeatureTopic() {
|
||||
showModal('featureTopic', this.modelFor('topic'));
|
||||
showModal('featureTopic', { model: this.modelFor('topic'), title: 'topic.feature_topic.title' });
|
||||
this.controllerFor('modal').set('modalClass', 'feature-topic-modal');
|
||||
},
|
||||
|
||||
showInvite() {
|
||||
showModal('invite', this.modelFor('topic'));
|
||||
showModal('invite', { model: this.modelFor('topic') });
|
||||
this.controllerFor('invite').reset();
|
||||
},
|
||||
|
||||
showHistory(post) {
|
||||
showModal('history', post);
|
||||
this.controllerFor('history').refresh(post.get("id"), "latest");
|
||||
showHistory(model) {
|
||||
showModal('history', { model });
|
||||
this.controllerFor('history').refresh(model.get("id"), "latest");
|
||||
this.controllerFor('modal').set('modalClass', 'history-modal');
|
||||
},
|
||||
|
||||
showRawEmail(post) {
|
||||
showModal('raw-email', post);
|
||||
this.controllerFor('raw_email').loadRawEmail(post.get("id"));
|
||||
showRawEmail(model) {
|
||||
showModal('raw-email', { model });
|
||||
this.controllerFor('raw_email').loadRawEmail(model.get("id"));
|
||||
},
|
||||
|
||||
mergeTopic() {
|
||||
showModal('mergeTopic', this.modelFor('topic'));
|
||||
showModal('merge-topic', { model: this.modelFor('topic'), title: 'topic.merge_topic.title' });
|
||||
},
|
||||
|
||||
splitTopic() {
|
||||
showModal('split-topic', this.modelFor('topic'));
|
||||
showModal('split-topic', { model: this.modelFor('topic') });
|
||||
},
|
||||
|
||||
changeOwner() {
|
||||
showModal('changeOwner', this.modelFor('topic'));
|
||||
showModal('change-owner', { model: this.modelFor('topic'), title: 'topic.change_owner.title' });
|
||||
},
|
||||
|
||||
// Use replaceState to update the URL once it changes
|
||||
@ -153,7 +152,7 @@ const TopicRoute = Discourse.Route.extend(ShowFooter, {
|
||||
model(params, transition) {
|
||||
const queryParams = transition.queryParams;
|
||||
|
||||
const topic = this.modelFor('topic');
|
||||
let topic = this.modelFor('topic');
|
||||
if (topic && (topic.get('id') === parseInt(params.id, 10))) {
|
||||
this.setupParams(topic, queryParams);
|
||||
// If we have the existing model, refresh it
|
||||
@ -161,7 +160,8 @@ const TopicRoute = Discourse.Route.extend(ShowFooter, {
|
||||
return topic;
|
||||
});
|
||||
} else {
|
||||
return this.setupParams(Topic.create(_.omit(params, 'username_filters', 'filter')), queryParams);
|
||||
topic = this.store.createRecord('topic', _.omit(params, 'username_filters', 'filter'));
|
||||
return this.setupParams(topic, queryParams);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
import UserActivityStreamRoute from "discourse/routes/user-activity-stream";
|
||||
|
||||
export default UserActivityStreamRoute.extend({
|
||||
userActionType: Discourse.UserAction.TYPES.pending
|
||||
});
|
||||
@ -4,6 +4,6 @@ export default UserTopicListRoute.extend({
|
||||
userActionType: Discourse.UserAction.TYPES.topics,
|
||||
|
||||
model: function() {
|
||||
return Discourse.TopicList.find('topics/created-by/' + this.modelFor('user').get('username_lower'));
|
||||
return this.store.findFiltered('topicList', {filter: 'topics/created-by/' + this.modelFor('user').get('username_lower') });
|
||||
}
|
||||
});
|
||||
|
||||
@ -21,7 +21,7 @@ export default Discourse.Route.extend(ShowFooter, {
|
||||
|
||||
actions: {
|
||||
showInvite() {
|
||||
showModal('invite', Discourse.User.current());
|
||||
showModal('invite', { model: this.currentUser });
|
||||
this.controllerFor('invite').reset();
|
||||
},
|
||||
|
||||
|
||||
@ -15,33 +15,33 @@
|
||||
</li>
|
||||
<li>
|
||||
<a {{bind-attr href="topic.lastPostUrl"}}>
|
||||
<h4>{{i18n 'last_post_lowercase'}}</h4>
|
||||
<h4>{{i18n 'last_reply_lowercase'}}</h4>
|
||||
{{avatar details.last_poster imageSize="tiny"}}
|
||||
{{format-date topic.last_posted_at}}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{{number topic.posts_count}}
|
||||
<h4>{{i18n 'posts_lowercase'}}</h4>
|
||||
{{number topic.replyCount}}
|
||||
<h4>{{i18n 'replies_lowercase' count=topic.replyCount}}</h4>
|
||||
</li>
|
||||
<li class='secondary'>
|
||||
{{number topic.views class=topic.viewsHeat}}
|
||||
<h4>{{i18n 'views_lowercase'}}</h4>
|
||||
<h4>{{i18n 'views_lowercase' count=topic.views}}</h4>
|
||||
</li>
|
||||
<li class='secondary'>
|
||||
{{number topic.participant_count}}
|
||||
<h4>{{i18n 'users_lowercase'}}</h4>
|
||||
<h4>{{i18n 'users_lowercase' count=topic.participant_count}}</h4>
|
||||
</li>
|
||||
{{#if topic.like_count}}
|
||||
<li class='secondary'>
|
||||
{{number topic.like_count}}
|
||||
<h4>{{i18n 'likes_lowercase'}}</h4>
|
||||
<h4>{{i18n 'likes_lowercase' count=topic.like_count}}</h4>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if details.links.length}}
|
||||
<li class='secondary'>
|
||||
{{number details.links.length}}
|
||||
<h4>{{i18n 'links_lowercase'}}</h4>
|
||||
<h4>{{i18n 'links_lowercase' count=details.links.length}}</h4>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if showPosterAvatar}}
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
<div class="row">
|
||||
<div class="full-width">
|
||||
<div id='list-area'>
|
||||
{{plugin-outlet "discovery-list-container-top"}}
|
||||
{{outlet "list-container"}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -10,7 +10,11 @@
|
||||
{{else}}
|
||||
<label>{{inviteInstructions}}</label>
|
||||
{{#if allowExistingMembers}}
|
||||
{{user-selector single="true" allowAny=true excludeCurrentUser="true" usernames=emailOrUsername includeGroups="true" placeholderKey=placeholderKey}}
|
||||
{{#if isPrivateTopic}}
|
||||
{{user-selector single="true" allowAny=true excludeCurrentUser="true" usernames=emailOrUsername allowedUsers="true" topicId=topicId placeholderKey=placeholderKey}}
|
||||
{{else}}
|
||||
{{user-selector single="true" allowAny=true excludeCurrentUser="true" usernames=emailOrUsername placeholderKey=placeholderKey}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{text-field value=emailOrUsername placeholderKey="topic.invite_reply.email_placeholder"}}
|
||||
{{/if}}
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
<div class="modal-body">
|
||||
<p>{{i18n "queue.approval.description"}}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
{{d-button action="closeModal" class="btn-primary" label="queue.approval.ok"}}
|
||||
</div>
|
||||
@ -6,7 +6,7 @@
|
||||
{{notification-item notification=n scope=n.scope}}
|
||||
{{/each}}
|
||||
<li class="read last">
|
||||
<a href="/my/notifications">{{i18n 'notifications.more'}}…</a>
|
||||
<a href="{{unbound myNotificationsUrl}}">{{i18n 'notifications.more'}}…</a>
|
||||
</li>
|
||||
</ul>
|
||||
{{else}}
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
<span class="close"><i class="fa fa-times-circle"></i></span>
|
||||
{{view.validation.reason}}
|
||||
{{{view.validation.reason}}}
|
||||
|
||||
79
app/assets/javascripts/discourse/templates/queued-posts.hbs
Normal file
79
app/assets/javascripts/discourse/templates/queued-posts.hbs
Normal file
@ -0,0 +1,79 @@
|
||||
<div class='container'>
|
||||
<div class='queued-posts'>
|
||||
{{#each ctrl in model itemController='queued-post'}}
|
||||
<div class='queued-post'>
|
||||
<div class='poster'>
|
||||
{{#user-link user=ctrl.post.user}}
|
||||
{{avatar ctrl.post.user imageSize="large"}}
|
||||
{{/user-link}}
|
||||
|
||||
</div>
|
||||
<div class='cooked'>
|
||||
<div class='names'>
|
||||
<span class="username">
|
||||
{{#user-link user=ctrl.post.user}}
|
||||
{{ctrl.post.user.username}}
|
||||
{{/user-link}}
|
||||
</span>
|
||||
</div>
|
||||
<div class='post-info'>
|
||||
<span class='post-date'>{{age-with-tooltip ctrl.post.created_at}}</span>
|
||||
</div>
|
||||
<div class='clearfix'></div>
|
||||
|
||||
<span class='post-title'>
|
||||
{{i18n "queue.topic"}}
|
||||
{{#if ctrl.post.topic}}
|
||||
{{topic-link ctrl.post.topic}}
|
||||
{{else}}
|
||||
{{ctrl.post.post_options.title}}
|
||||
{{/if}}
|
||||
{{category-badge ctrl.post.category}}
|
||||
</span>
|
||||
|
||||
<div class='body'>
|
||||
{{#if ctrl.editing}}
|
||||
{{pagedown-editor value=ctrl.buffered.raw}}
|
||||
{{else}}
|
||||
{{{cook-text ctrl.post.raw}}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class='queue-controls'>
|
||||
{{#if ctrl.editing}}
|
||||
{{d-button action="confirmEdit"
|
||||
label="queue.confirm"
|
||||
disabled=ctrl.post.isSaving
|
||||
class="btn-primary confirm"}}
|
||||
{{d-button action="cancelEdit"
|
||||
label="queue.cancel"
|
||||
icon="times"
|
||||
disabled=ctrl.post.isSaving
|
||||
class="btn-danger cancel"}}
|
||||
|
||||
{{else}}
|
||||
{{d-button action="approve"
|
||||
disabled=ctrl.post.isSaving
|
||||
label="queue.approve"
|
||||
icon="check"
|
||||
class="btn-primary approve"}}
|
||||
{{d-button action="reject"
|
||||
disabled=ctrl.post.isSaving
|
||||
label="queue.reject"
|
||||
icon="times"
|
||||
class="btn-danger reject"}}
|
||||
{{d-button action="edit"
|
||||
disabled=ctrl.post.isSaving
|
||||
label="queue.edit"
|
||||
icon="pencil"
|
||||
class="edit"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class='clearfix'></div>
|
||||
</div>
|
||||
{{else}}
|
||||
<p>{{i18n "queue.none"}}</p>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
@ -2,19 +2,23 @@
|
||||
<ul class="location-links">
|
||||
{{#if showAdminLinks}}
|
||||
<li>
|
||||
<a href="/admin" class="admin-link"><i class='fa fa-wrench'></i> {{i18n 'admin_title'}}</a>
|
||||
{{#link-to "admin" class="admin-link"}}
|
||||
<i class='fa fa-wrench'></i> {{i18n 'admin_title'}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/flags/active" class="flagged-posts-link">
|
||||
{{#link-to "adminFlags" class="flagged-posts-link"}}
|
||||
{{fa-icon "flag"}} {{i18n 'flags_title'}}
|
||||
{{#if currentUser.site_flagged_posts_count}}
|
||||
<span title='{{i18n 'notifications.total_flagged'}}' class='badge-notification flagged-posts'>{{currentUser.site_flagged_posts_count}}</span>
|
||||
{{/if}}
|
||||
</a>
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
<li>
|
||||
<a href="/latest" title="{{i18n 'filters.latest.help'}}" class="latest-topics-link">{{i18n 'filters.latest.title'}}</a>
|
||||
{{#link-to "discovery.latest" class="latest-topics-link"}}
|
||||
{{i18n 'filters.latest.title'}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{#if showBadgesLink}}
|
||||
<li>
|
||||
@ -26,6 +30,17 @@
|
||||
<li>{{#link-to 'users'}}{{i18n "directory.title"}}{{/link-to}}</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if currentUser.show_queued_posts}}
|
||||
<li>
|
||||
{{#link-to 'queued-posts'}}
|
||||
{{i18n "queue.title"}}
|
||||
{{#if currentUser.post_queue_new_count}}
|
||||
<span class='badge-notification flagged-posts'>{{currentUser.post_queue_new_count}}</span>
|
||||
{{/if}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
{{plugin-outlet "site-map-links"}}
|
||||
|
||||
{{#if showKeyboardShortcuts}}
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
{{discourse-banner user=currentUser banner=site.banner overlay=view.hasScrolled hide=errorLoading}}
|
||||
</div>
|
||||
|
||||
{{plugin-outlet "topic-above-post-stream"}}
|
||||
|
||||
{{#if postStream.loaded}}
|
||||
{{#if postStream.firstPostPresent}}
|
||||
<div id='topic-title'>
|
||||
@ -22,8 +24,8 @@
|
||||
|
||||
{{plugin-outlet "edit-topic"}}
|
||||
|
||||
{{d-button action="finishedEditingTopic" class="btn-primary btn-small no-text" icon="check"}}
|
||||
{{d-button action="cancelEditingTopic" class="btn-small no-text" icon="times"}}
|
||||
{{d-button action="finishedEditingTopic" class="btn-primary btn-small no-text submit-edit" icon="check"}}
|
||||
{{d-button action="cancelEditingTopic" class="btn-small no-text cancel-edit" icon="times"}}
|
||||
{{else}}
|
||||
<h1>
|
||||
{{#unless is_warning}}
|
||||
@ -32,7 +34,7 @@
|
||||
|
||||
{{#if details.loaded}}
|
||||
{{topic-status topic=model}}
|
||||
<a href='{{unbound url}}' {{action "jumpTop"}}>
|
||||
<a href='{{unbound url}}' {{action "jumpTop"}} class='fancy-title'>
|
||||
{{{fancy_title}}}
|
||||
</a>
|
||||
{{/if}}
|
||||
@ -87,6 +89,8 @@
|
||||
{{view 'topic-closing' topic=model}}
|
||||
{{view 'topic-footer-buttons' topic=model}}
|
||||
|
||||
{{plugin-outlet "topic-above-suggested"}}
|
||||
|
||||
{{#if details.suggested_topics.length}}
|
||||
<div id='suggested-topics'>
|
||||
<h3>{{i18n 'suggested_topics.title'}}</h3>
|
||||
|
||||
@ -197,16 +197,17 @@
|
||||
|
||||
<div class="control-group other">
|
||||
<label class="control-label">{{i18n 'user.other_settings'}}</label>
|
||||
<div class="controls controls-dropdown">
|
||||
<label>{{i18n 'user.auto_track_topics'}}</label>
|
||||
{{combo-box valueAttribute="value" content=autoTrackDurations value=auto_track_topics_after_msecs}}
|
||||
</div>
|
||||
|
||||
<div class="controls controls-dropdown">
|
||||
<label>{{i18n 'user.new_topic_duration.label'}}</label>
|
||||
{{combo-box valueAttribute="value" content=considerNewTopicOptions value=new_topic_duration_minutes}}
|
||||
</div>
|
||||
|
||||
<div class="controls controls-dropdown">
|
||||
<label>{{i18n 'user.auto_track_topics'}}</label>
|
||||
{{combo-box valueAttribute="value" content=autoTrackDurations value=auto_track_topics_after_msecs}}
|
||||
</div>
|
||||
|
||||
{{preference-checkbox labelKey="user.external_links_in_new_tab" checked=external_links_in_new_tab}}
|
||||
{{preference-checkbox labelKey="user.enable_quoting" checked=enable_quoting}}
|
||||
{{preference-checkbox labelKey="user.dynamic_favicon" checked=dynamic_favicon}}
|
||||
|
||||
@ -168,7 +168,7 @@
|
||||
{{#if canSeeNotificationHistory}}
|
||||
{{#link-to 'user.notifications' tagName="li"}}
|
||||
{{#link-to 'user.notifications'}}
|
||||
<i class='glyph fa fa-comment'></i>
|
||||
{{fa-icon "comment" class="glyph"}}
|
||||
{{i18n 'user.notifications'}}
|
||||
<span class='count'>({{notification_count}})</span>
|
||||
{{/link-to}}
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/archetype_options',
|
||||
title: I18n.t('topic.options')
|
||||
});
|
||||
@ -1,6 +0,0 @@
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/change_owner',
|
||||
title: I18n.t('topic.change_owner.title')
|
||||
});
|
||||
@ -1,11 +1,3 @@
|
||||
/**
|
||||
Renders a popup messages on the composer
|
||||
|
||||
@class ComposerMessagesView
|
||||
@extends Discourse.View
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
export default Ember.CollectionView.extend({
|
||||
classNameBindings: [':composer-popup-container', 'hidden'],
|
||||
content: Em.computed.alias('controller.content'),
|
||||
@ -16,19 +8,18 @@ export default Ember.CollectionView.extend({
|
||||
classNames: ['composer-popup', 'hidden'],
|
||||
templateName: Em.computed.alias('content.templateName'),
|
||||
|
||||
init: function() {
|
||||
_setup: function() {
|
||||
this._super();
|
||||
this.set('context', this.get('content'));
|
||||
|
||||
if (this.get('content.extraClass')) {
|
||||
this.get('classNames').pushObject(this.get('content.extraClass'));
|
||||
}
|
||||
},
|
||||
}.on('init'),
|
||||
|
||||
didInsertElement: function() {
|
||||
_initCss: function() {
|
||||
var composerHeight = $('#reply-control').height() || 0;
|
||||
this.$().css('bottom', composerHeight + "px").show();
|
||||
}
|
||||
}.on('didInsertElement')
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@ -573,6 +573,10 @@ const ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
reason = I18n.t('composer.error.post_missing');
|
||||
} else if( missingChars > 0 ) {
|
||||
reason = I18n.t('composer.error.post_length', {min: this.get('model.minimumPostLength')});
|
||||
let tl = Discourse.User.currentProp("trust_level");
|
||||
if (tl === 0 || tl === 1) {
|
||||
reason += "<br/>" + I18n.t('composer.error.try_like');
|
||||
}
|
||||
}
|
||||
|
||||
if( reason ) {
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export default Ember.ContainerView.extend(Discourse.Presence, {
|
||||
import Presence from 'discourse/mixins/presence';
|
||||
|
||||
export default Ember.ContainerView.extend(Presence, {
|
||||
|
||||
attachViewWithArgs(viewArgs, viewClass) {
|
||||
if (!viewClass) { viewClass = Ember.View.extend(); }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user