diff --git a/Gemfile.lock b/Gemfile.lock index 593d89ab1f..725ea9068b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -145,7 +145,7 @@ GEM thor (~> 0.15) libv8 (3.16.14.7) listen (0.7.3) - logster (0.8.3) + logster (0.8.4.1.pre) lru_redux (1.1.0) mail (2.5.4) mime-types (~> 1.16) @@ -206,7 +206,7 @@ GEM omniauth-twitter (1.0.1) multi_json (~> 1.3) omniauth-oauth (~> 1.0) - onebox (1.5.22) + onebox (1.5.23) moneta (~> 0.8) multi_json (~> 1.11) mustache diff --git a/app/assets/javascripts/admin/models/report.js.es6 b/app/assets/javascripts/admin/models/report.js.es6 index ebb63486d2..1ce48a1448 100644 --- a/app/assets/javascripts/admin/models/report.js.es6 +++ b/app/assets/javascripts/admin/models/report.js.es6 @@ -1,14 +1,12 @@ import round from "discourse/lib/round"; const Report = Discourse.Model.extend({ - reportUrl: function() { - return("/admin/reports/" + this.get('type')); - }.property('type'), + reportUrl: Discourse.computed.fmt("type", "/admin/reports/%@"), valueAt(numDaysAgo) { if (this.data) { - var wantedDate = moment().subtract(numDaysAgo, 'days').format('YYYY-MM-DD'); - var item = this.data.find( function(d) { return d.x === wantedDate; } ); + const wantedDate = moment().subtract(numDaysAgo, "days").format("YYYY-MM-DD"); + const item = this.data.find(d => d.x === wantedDate); if (item) { return item.y; } @@ -16,128 +14,117 @@ const Report = Discourse.Model.extend({ return 0; }, - sumDays(startDaysAgo, endDaysAgo) { + valueFor(startDaysAgo, endDaysAgo) { if (this.data) { - var earliestDate = moment().subtract(endDaysAgo, 'days').startOf('day'); - var latestDate = moment().subtract(startDaysAgo, 'days').startOf('day'); - var d, sum = 0; - _.each(this.data,function(datum){ + const earliestDate = moment().subtract(endDaysAgo, "days").startOf("day"); + const latestDate = moment().subtract(startDaysAgo, "days").startOf("day"); + var d, sum = 0, count = 0; + _.each(this.data, datum => { d = moment(datum.x); - if(d >= earliestDate && d <= latestDate) { + if (d >= earliestDate && d <= latestDate) { sum += datum.y; + count++; } }); + if (this.get("method") === "average") { sum /= count; } return round(sum, -2); } }, - todayCount: function() { - return this.valueAt(0); - }.property('data'), + todayCount: function() { return this.valueAt(0); }.property("data"), + yesterdayCount: function() { return this.valueAt(1); }.property("data"), + sevenDaysAgoCount: function() { return this.valueAt(7); }.property("data"), + thirtyDaysAgoCount: function() { return this.valueAt(30); }.property("data"), - yesterdayCount: function() { - return this.valueAt(1); - }.property('data'), - - lastSevenDaysCount: function() { - return this.sumDays(1,7); - }.property('data'), - - lastThirtyDaysCount: function() { - return this.sumDays(1,30); - }.property('data'), - - sevenDaysAgoCount: function() { - return this.valueAt(7); - }.property('data'), - - thirtyDaysAgoCount: function() { - return this.valueAt(30); - }.property('data'), + lastSevenDaysCount: function() { return this.valueFor(1, 7); }.property("data"), + lastThirtyDaysCount: function() { return this.valueFor(1, 30); }.property("data"), yesterdayTrend: function() { - var yesterdayVal = this.valueAt(1); - var twoDaysAgoVal = this.valueAt(2); - if ( yesterdayVal > twoDaysAgoVal ) { - return 'trending-up'; - } else if ( yesterdayVal < twoDaysAgoVal ) { - return 'trending-down'; + const yesterdayVal = this.valueAt(1); + const twoDaysAgoVal = this.valueAt(2); + if (yesterdayVal > twoDaysAgoVal) { + return "trending-up"; + } else if (yesterdayVal < twoDaysAgoVal) { + return "trending-down"; } else { - return 'no-change'; + return "no-change"; } - }.property('data'), + }.property("data"), sevenDayTrend: function() { - var currentPeriod = this.sumDays(1,7); - var prevPeriod = this.sumDays(8,14); - if ( currentPeriod > prevPeriod ) { - return 'trending-up'; - } else if ( currentPeriod < prevPeriod ) { - return 'trending-down'; + const currentPeriod = this.valueFor(1, 7); + const prevPeriod = this.valueFor(8, 14); + if (currentPeriod > prevPeriod) { + return "trending-up"; + } else if (currentPeriod < prevPeriod) { + return "trending-down"; } else { - return 'no-change'; + return "no-change"; } - }.property('data'), + }.property("data"), thirtyDayTrend: function() { - if( this.get('prev30Days') ) { - var currentPeriod = this.sumDays(1,30); - if( currentPeriod > this.get('prev30Days') ) { - return 'trending-up'; - } else if ( currentPeriod < this.get('prev30Days') ) { - return 'trending-down'; + if (this.get("prev30Days")) { + const currentPeriod = this.valueFor(1, 30); + if (currentPeriod > this.get("prev30Days")) { + return "trending-up"; + } else if (currentPeriod < this.get("prev30Days")) { + return "trending-down"; } } - return 'no-change'; - }.property('data', 'prev30Days'), + return "no-change"; + }.property("data", "prev30Days"), icon: function() { - switch( this.get('type') ) { - case 'flags': - return 'flag'; - case 'likes': - return 'heart'; - default: - return null; + switch (this.get("type")) { + case "flags": return "flag"; + case "likes": return "heart"; + default: return null; } - }.property('type'), + }.property("type"), + + method: function() { + if (this.get("type") === "time_to_first_response") { + return "average"; + } else { + return "sum"; + } + }.property("type"), percentChangeString(val1, val2) { - var val = ((val1 - val2) / val2) * 100; - if( isNaN(val) || !isFinite(val) ) { + const val = ((val1 - val2) / val2) * 100; + if (isNaN(val) || !isFinite(val)) { return null; - } else if( val > 0 ) { - return '+' + val.toFixed(0) + '%'; + } else if (val > 0) { + return "+" + val.toFixed(0) + "%"; } else { - return val.toFixed(0) + '%'; + return val.toFixed(0) + "%"; } }, changeTitle(val1, val2, prevPeriodString) { - var title = ''; - var percentChange = this.percentChangeString(val1, val2); - if( percentChange ) { - title += percentChange + ' change. '; - } - title += 'Was ' + val2 + ' ' + prevPeriodString + '.'; + const percentChange = this.percentChangeString(val1, val2); + var title = ""; + if (percentChange) { title += percentChange + " change. "; } + title += "Was " + val2 + " " + prevPeriodString + "."; return title; }, yesterdayCountTitle: function() { - return this.changeTitle( this.valueAt(1), this.valueAt(2),'two days ago'); - }.property('data'), + return this.changeTitle(this.valueAt(1), this.valueAt(2), "two days ago"); + }.property("data"), sevenDayCountTitle: function() { - return this.changeTitle( this.sumDays(1,7), this.sumDays(8,14), 'two weeks ago'); - }.property('data'), + return this.changeTitle(this.valueFor(1, 7), this.valueFor(8, 14), "two weeks ago"); + }.property("data"), thirtyDayCountTitle: function() { - return this.changeTitle( this.sumDays(1,30), this.get('prev30Days'), 'in the previous 30 day period'); - }.property('data'), + return this.changeTitle(this.valueFor(1, 30), this.get("prev30Days"), "in the previous 30 day period"); + }.property("data"), dataReversed: function() { - return this.get('data').toArray().reverse(); - }.property('data') + return this.get("data").toArray().reverse(); + }.property("data") }); diff --git a/app/assets/javascripts/admin/templates/admin.hbs b/app/assets/javascripts/admin/templates/admin.hbs index d8e88712c4..c7746a6d4a 100644 --- a/app/assets/javascripts/admin/templates/admin.hbs +++ b/app/assets/javascripts/admin/templates/admin.hbs @@ -1,5 +1,4 @@
- {{global-notice}}
diff --git a/app/assets/javascripts/discourse/components/combo-box.js.es6 b/app/assets/javascripts/discourse/components/combo-box.js.es6 index d74410e89b..fdda11db1a 100644 --- a/app/assets/javascripts/discourse/components/combo-box.js.es6 +++ b/app/assets/javascripts/discourse/components/combo-box.js.es6 @@ -32,7 +32,8 @@ export default Ember.Component.extend({ if (this.get('content')) { const self = this; this.get('content').forEach(function(o) { - let val = o[self.get('valueAttribute')] || o; + let val = o[self.get('valueAttribute')]; + if (typeof val === "undefined") { val = o; } if (!Em.isNone(val)) { val = val.toString(); } const selectedText = (val === selected) ? "selected" : ""; diff --git a/app/assets/javascripts/discourse/components/desktop-notification-config.js.es6 b/app/assets/javascripts/discourse/components/desktop-notification-config.js.es6 new file mode 100644 index 0000000000..5a362280c3 --- /dev/null +++ b/app/assets/javascripts/discourse/components/desktop-notification-config.js.es6 @@ -0,0 +1,65 @@ +export default Ember.Component.extend({ + classNames: ['controls'], + + notificationsPermission: function() { + if (this.get('isNotSupported')) return ''; + + return Notification.permission; + }.property(), + + notificationsDisabled: function(_, value) { + if (arguments.length > 1) { + localStorage.setItem('notifications-disabled', value); + } + return localStorage.getItem('notifications-disabled'); + }.property(), + + + isNotSupported: function() { + return !window['Notification']; + }.property(), + + isDefaultPermission: function() { + if (this.get('isNotSupported')) return false; + + return Notification.permission === "default"; + }.property('isNotSupported', 'notificationsPermission'), + + isDeniedPermission: function() { + if (this.get('isNotSupported')) return false; + + return Notification.permission === "denied"; + }.property('isNotSupported', 'notificationsPermission'), + + isGrantedPermission: function() { + if (this.get('isNotSupported')) return false; + + return Notification.permission === "granted"; + }.property('isNotSupported', 'notificationsPermission'), + + isEnabled: function() { + if (!this.get('isGrantedPermission')) return false; + + return !this.get('notificationsDisabled'); + }.property('isGrantedPermission', 'notificationsDisabled'), + + actions: { + requestPermission() { + const self = this; + Notification.requestPermission(function() { + self.propertyDidChange('notificationsPermission'); + }); + }, + recheckPermission() { + this.propertyDidChange('notificationsPermission'); + }, + turnoff() { + this.set('notificationsDisabled', 'disabled'); + this.propertyDidChange('notificationsPermission'); + }, + turnon() { + this.set('notificationsDisabled', ''); + this.propertyDidChange('notificationsPermission'); + } + } +}); diff --git a/app/assets/javascripts/discourse/components/nav-item.js.es6 b/app/assets/javascripts/discourse/components/nav-item.js.es6 index c50741820e..87ab1007a8 100644 --- a/app/assets/javascripts/discourse/components/nav-item.js.es6 +++ b/app/assets/javascripts/discourse/components/nav-item.js.es6 @@ -1,3 +1,5 @@ +/* You might be looking for navigation-item. */ + export default Ember.Component.extend({ tagName: 'li', classNameBindings: ['active'], diff --git a/app/assets/javascripts/discourse/components/small-action.js.es6 b/app/assets/javascripts/discourse/components/small-action.js.es6 index 50e42f19ab..4457c6fb49 100644 --- a/app/assets/javascripts/discourse/components/small-action.js.es6 +++ b/app/assets/javascripts/discourse/components/small-action.js.es6 @@ -10,30 +10,26 @@ const icons = { 'pinned_globally.enabled': 'thumb-tack', 'pinned_globally.disabled': 'thumb-tack unpinned', 'visible.enabled': 'eye', - 'visible.disabled': 'eye-slash' + 'visible.disabled': 'eye-slash', + 'split_topic': 'sign-out' }; +export function actionDescription(actionCode, createdAt) { + return function() { + const ac = this.get(actionCode); + if (ac) { + const dt = new Date(this.get(createdAt)); + const when = Discourse.Formatter.relativeAge(dt, {format: 'medium-with-ago'}); + return I18n.t(`action_codes.${ac}`, {when}).htmlSafe(); + } + }.property(actionCode, createdAt); +} + export default Ember.Component.extend({ layoutName: 'components/small-action', // needed because `time-gap` inherits from this classNames: ['small-action'], - description: function() { - const actionCode = this.get('actionCode'); - if (actionCode) { - const dt = new Date(this.get('post.created_at')); - const when = Discourse.Formatter.relativeAge(dt, {format: 'medium-with-ago'}); - var result = I18n.t(`action_codes.${actionCode}`, {when}); - var cooked = this.get('post.cooked'); - - result = "

" + result + "

"; - - if (!Em.isEmpty(cooked)) { - result += "
" + cooked + "
"; - } - - return result; - } - }.property('actionCode', 'post.created_at', 'post.cooked'), + description: actionDescription('actionCode', 'post.created_at'), icon: function() { return icons[this.get('actionCode')] || 'exclamation'; diff --git a/app/assets/javascripts/discourse/components/stream-item.js.es6 b/app/assets/javascripts/discourse/components/stream-item.js.es6 new file mode 100644 index 0000000000..9a29d87438 --- /dev/null +++ b/app/assets/javascripts/discourse/components/stream-item.js.es6 @@ -0,0 +1,13 @@ +import { actionDescription } from "discourse/components/small-action"; + +export default Ember.Component.extend({ + classNameBindings: [":item", "item.hidden", "item.deleted", "moderatorAction"], + moderatorAction: Discourse.computed.propertyEqual("item.post_type", "site.post_types.moderator_action"), + actionDescription: actionDescription("item.action_code", "item.created_at"), + + actions: { + removeBookmark(userAction) { + this.sendAction("removeBookmark", userAction); + } + } +}); diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index 8ad012dd28..c4f1ac21b4 100644 --- a/app/assets/javascripts/discourse/controllers/composer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/composer.js.es6 @@ -413,7 +413,7 @@ export default Ember.ObjectController.extend(Presence, { } // we need a draft sequence for the composer to work - if (opts.draftSequence === void 0) { + if (opts.draftSequence === undefined) { return Discourse.Draft.get(opts.draftKey).then(function(data) { opts.draftSequence = data.draft_sequence; opts.draft = data.draft; diff --git a/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 b/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 index a763a94c3d..4f3c38b629 100644 --- a/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 +++ b/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 @@ -1,32 +1,39 @@ -import DiscourseController from 'discourse/controllers/controller'; -import { translateResults } from 'discourse/lib/search-for-term'; +import DiscourseController from "discourse/controllers/controller"; +import { translateResults } from "discourse/lib/search-for-term"; export default DiscourseController.extend({ - loading: Em.computed.not('model'), - queryParams: ['q'], + needs: ["application"], + + loading: Em.computed.not("model"), + queryParams: ["q"], q: null, - modelChanged: function(){ - if (this.get('searchTerm') !== this.get('q')) { - this.set('searchTerm', this.get('q')); - } - }.observes('model'), - qChanged: function(){ - var model = this.get('model'); - if (model && this.get('model.q') !== this.get('q')){ - this.set('searchTerm', this.get('q')); - this.send('search'); + modelChanged: function() { + if (this.get("searchTerm") !== this.get("q")) { + this.set("searchTerm", this.get("q")); } - }.observes('q'), + }.observes("model"), + + qChanged: function() { + const model = this.get("model"); + if (model && this.get("model.q") !== this.get("q")) { + this.set("searchTerm", this.get("q")); + this.send("search"); + } + }.observes("q"), + + _showFooter: function() { + this.set("controllers.application.showFooter", !this.get("loading")); + }.observes("loading"), + actions: { - search: function(){ - var self = this; - this.set('q', this.get('searchTerm')); - this.set('model', null); + search() { + this.set("q", this.get("searchTerm")); + this.set("model", null); - Discourse.ajax('/search', {data: {q: this.get('searchTerm')}}).then(function(results) { - self.set('model', translateResults(results) || {}); - self.set('model.q', self.get('q')); + Discourse.ajax("/search", { data: { q: this.get("searchTerm") } }).then(results => { + this.set("model", translateResults(results) || {}); + this.set("model.q", this.get("q")); }); } } diff --git a/app/assets/javascripts/discourse/controllers/merge-topic.js.es6 b/app/assets/javascripts/discourse/controllers/merge-topic.js.es6 index c923aaf2da..44f374ddbf 100644 --- a/app/assets/javascripts/discourse/controllers/merge-topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/merge-topic.js.es6 @@ -1,12 +1,15 @@ import Presence from 'discourse/mixins/presence'; import SelectedPostsCount from 'discourse/mixins/selected-posts-count'; import ModalFunctionality from 'discourse/mixins/modal-functionality'; -import ObjectController from 'discourse/controllers/object'; +import { movePosts, mergeTopic } from 'discourse/models/topic'; // Modal related to merging of topics -export default ObjectController.extend(SelectedPostsCount, ModalFunctionality, Presence, { +export default Ember.Controller.extend(SelectedPostsCount, ModalFunctionality, Presence, { needs: ['topic'], + saving: false, + selectedTopicId: null, + topicController: Em.computed.alias('controllers.topic'), selectedPosts: Em.computed.alias('topicController.selectedPosts'), selectedReplies: Em.computed.alias('topicController.selectedReplies'), @@ -22,38 +25,40 @@ export default ObjectController.extend(SelectedPostsCount, ModalFunctionality, P return I18n.t('topic.merge_topic.title'); }.property('saving'), - onShow: function() { + onShow() { this.set('controllers.modal.modalClass', 'split-modal'); }, actions: { - movePostsToExistingTopic: function() { + movePostsToExistingTopic() { + const topicId = this.get('model.id'); + this.set('saving', true); - var promise = null; + let promise = null; if (this.get('allPostsSelected')) { - promise = Discourse.Topic.mergeTopic(this.get('id'), this.get('selectedTopicId')); + promise = mergeTopic(topicId, this.get('selectedTopicId')); } else { - var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); }), - replyPostIds = this.get('selectedReplies').map(function(p) { return p.get('id'); }); + const postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); }); + const replyPostIds = this.get('selectedReplies').map(function(p) { return p.get('id'); }); - promise = Discourse.Topic.movePosts(this.get('id'), { + promise = movePosts(topicId, { destination_topic_id: this.get('selectedTopicId'), post_ids: postIds, reply_post_ids: replyPostIds }); } - var mergeTopicController = this; + const self = this; promise.then(function(result) { // Posts moved - mergeTopicController.send('closeModal'); - mergeTopicController.get('topicController').send('toggleMultiSelect'); + self.send('closeModal'); + self.get('topicController').send('toggleMultiSelect'); Em.run.next(function() { Discourse.URL.routeTo(result.url); }); - }, function() { - // Error moving posts - mergeTopicController.flash(I18n.t('topic.merge_topic.error')); - mergeTopicController.set('saving', false); + }).catch(function() { + self.flash(I18n.t('topic.merge_topic.error')); + }).finally(function() { + self.set('saving', false); }); return false; } diff --git a/app/assets/javascripts/discourse/controllers/split-topic.js.es6 b/app/assets/javascripts/discourse/controllers/split-topic.js.es6 index 98aed2dcce..0c75ae3503 100644 --- a/app/assets/javascripts/discourse/controllers/split-topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/split-topic.js.es6 @@ -1,15 +1,20 @@ import Presence from 'discourse/mixins/presence'; import SelectedPostsCount from 'discourse/mixins/selected-posts-count'; import ModalFunctionality from 'discourse/mixins/modal-functionality'; -import ObjectController from 'discourse/controllers/object'; +import { extractError } from 'discourse/lib/ajax-error'; +import { movePosts } from 'discourse/models/topic'; // Modal related to auto closing of topics -export default ObjectController.extend(SelectedPostsCount, ModalFunctionality, Presence, { +export default Ember.Controller.extend(SelectedPostsCount, ModalFunctionality, Presence, { needs: ['topic'], + topicName: null, + saving: false, + categoryId: null, topicController: Em.computed.alias('controllers.topic'), selectedPosts: Em.computed.alias('topicController.selectedPosts'), selectedReplies: Em.computed.alias('topicController.selectedReplies'), + allPostsSelected: Em.computed.alias('topicController.allPostsSelected'), buttonDisabled: function() { if (this.get('saving')) return true; @@ -21,7 +26,7 @@ export default ObjectController.extend(SelectedPostsCount, ModalFunctionality, P return I18n.t('topic.split_topic.action'); }.property('saving'), - onShow: function() { + onShow() { this.setProperties({ 'controllers.modal.modalClass': 'split-modal', saving: false, @@ -31,39 +36,29 @@ export default ObjectController.extend(SelectedPostsCount, ModalFunctionality, P }, actions: { - movePostsToNewTopic: function() { + movePostsToNewTopic() { this.set('saving', true); - var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); }), - replyPostIds = this.get('selectedReplies').map(function(p) { return p.get('id'); }), - self = this, - categoryId = this.get('categoryId'), - saveOpts = { - title: this.get('topicName'), - post_ids: postIds, - reply_post_ids: replyPostIds - }; + const postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); }), + replyPostIds = this.get('selectedReplies').map(function(p) { return p.get('id'); }), + self = this, + categoryId = this.get('categoryId'), + saveOpts = { + title: this.get('topicName'), + post_ids: postIds, + reply_post_ids: replyPostIds + }; if (!Ember.isNone(categoryId)) { saveOpts.category_id = categoryId; } - Discourse.Topic.movePosts(this.get('id'), saveOpts).then(function(result) { + movePosts(this.get('model.id'), saveOpts).then(function(result) { // Posts moved self.send('closeModal'); self.get('topicController').send('toggleMultiSelect'); - Em.run.next(function() { Discourse.URL.routeTo(result.url); }); + Ember.run.next(function() { Discourse.URL.routeTo(result.url); }); }).catch(function(xhr) { - - var error = I18n.t('topic.split_topic.error'); - - if (xhr) { - var json = xhr.responseJSON; - if (json && json.errors) { - error = json.errors[0]; - } - } - - // Error moving posts - self.flash(error); + self.flash(extractError(xhr, I18n.t('topic.split_topic.error'))); + }).finally(function() { self.set('saving', false); }); return false; diff --git a/app/assets/javascripts/discourse/controllers/static.js.es6 b/app/assets/javascripts/discourse/controllers/static.js.es6 index 2e9050b2d5..c22ce3c26d 100644 --- a/app/assets/javascripts/discourse/controllers/static.js.es6 +++ b/app/assets/javascripts/discourse/controllers/static.js.es6 @@ -1,8 +1,8 @@ export default Ember.Controller.extend({ - showLoginButton: Em.computed.equal('model.path', 'login'), + showLoginButton: Em.computed.equal("model.path", "login"), actions: { - markFaqRead: function() { + markFaqRead() { if (this.currentUser) { Discourse.ajax("/users/read-faq", { method: "POST" }); } diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index aace6f8e0f..7dbce6dbb4 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -725,7 +725,8 @@ export default ObjectController.extend(SelectedPostsCount, BufferedContent, { }, _showFooter: function() { - this.set("controllers.application.showFooter", this.get("model.postStream.loadedAllPosts")); - }.observes("model.postStream.loadedAllPosts") + const showFooter = this.get("model.postStream.loaded") && this.get("model.postStream.loadedAllPosts"); + this.set("controllers.application.showFooter", showFooter); + }.observes("model.postStream.{loaded,loadedAllPosts}") }); diff --git a/app/assets/javascripts/discourse/controllers/user-activity.js.es6 b/app/assets/javascripts/discourse/controllers/user-activity.js.es6 index 6113cedff3..7e4ec2db92 100644 --- a/app/assets/javascripts/discourse/controllers/user-activity.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-activity.js.es6 @@ -5,7 +5,7 @@ export default Ember.ObjectController.extend({ _showFooter: function() { var showFooter; if (this.get("userActionType")) { - var stat = _.find(this.get("model.stats"), { action_type: this.get("userActionType") }); + const stat = _.find(this.get("model.stats"), { action_type: this.get("userActionType") }); showFooter = stat && stat.count <= this.get("model.stream.itemsLoaded"); } else { showFooter = this.get("model.statsCountNonPM") <= this.get("model.stream.itemsLoaded"); diff --git a/app/assets/javascripts/discourse/controllers/users.js.es6 b/app/assets/javascripts/discourse/controllers/users.js.es6 index 8ff6e6e498..8705fb850b 100644 --- a/app/assets/javascripts/discourse/controllers/users.js.es6 +++ b/app/assets/javascripts/discourse/controllers/users.js.es6 @@ -1,19 +1,24 @@ export default Ember.Controller.extend({ - queryParams: ['period', 'order', 'asc', 'name'], - period: 'weekly', - order: 'likes_received', + needs: ["application"], + queryParams: ["period", "order", "asc", "name"], + period: "weekly", + order: "likes_received", asc: null, - name: '', + name: "", - showTimeRead: Ember.computed.equal('period', 'all'), + showTimeRead: Ember.computed.equal("period", "all"), _setName: Discourse.debounce(function() { - this.set('name', this.get('nameInput')); - }, 500).observes('nameInput'), + this.set("name", this.get("nameInput")); + }, 500).observes("nameInput"), + + _showFooter: function() { + this.set("controllers.application.showFooter", !this.get("model.canLoadMore")); + }.observes("model.canLoadMore"), actions: { loadMore() { - this.get('model').loadMore(); + this.get("model").loadMore(); } } }); diff --git a/app/assets/javascripts/discourse/dialects/dialect.js b/app/assets/javascripts/discourse/dialects/dialect.js index 62febca1a7..e80e5e9671 100644 --- a/app/assets/javascripts/discourse/dialects/dialect.js +++ b/app/assets/javascripts/discourse/dialects/dialect.js @@ -188,15 +188,8 @@ function hoistCodeBlocksAndSpans(text) { // /!\ the order is important /!\ - //
...
code blocks - text = text.replace(/(^\n*|\n\n)
([\s\S]*?)<\/pre>/ig, function(_, before, content) {
-    var hash = md5(content);
-    hoisted[hash] = escape(showBackslashEscapedCharacters(removeEmptyLines(content)));
-    return before + "
" + hash + "
"; - }); - // fenced code blocks (AKA GitHub code blocks) - text = text.replace(/(^\n*|\n\n)```([a-z0-9\-]*)\n([\s\S]*?)\n```/g, function(_, before, language, content) { + text = text.replace(/(^\n*|\n)```([a-z0-9\-]*)\n([\s\S]*?)\n```/g, function(_, before, language, content) { var hash = md5(content); hoisted[hash] = escape(showBackslashEscapedCharacters(removeEmptyLines(content))); return before + "```" + language + "\n" + hash + "\n```"; @@ -218,6 +211,13 @@ function hoistCodeBlocksAndSpans(text) { return before + " " + hash + "\n"; }); + //
...
code blocks + text = text.replace(/(\s|^)
([\s\S]*?)<\/pre>/ig, function(_, before, content) {
+    var hash = md5(content);
+    hoisted[hash] = escape(showBackslashEscapedCharacters(removeEmptyLines(content)));
+    return before + "
" + hash + "
"; + }); + // code spans (double & single `) ["``", "`"].forEach(function(delimiter) { var regexp = new RegExp("(^|[^`])" + delimiter + "([^`\\n]+?)" + delimiter + "([^`]|$)", "g"); @@ -277,11 +277,19 @@ Discourse.Dialect = { // If we hoisted out anything, put it back var keys = Object.keys(hoisted); if (keys.length) { - keys.forEach(function(key) { + var found = true; + + var unhoist = function(key) { result = result.replace(new RegExp(key, "g"), function() { + found = true; return hoisted[key]; }); - }); + }; + + while(found) { + found = false; + keys.forEach(unhoist); + } } return result.trim(); diff --git a/app/assets/javascripts/discourse/dialects/quote_dialect.js b/app/assets/javascripts/discourse/dialects/quote_dialect.js index 3c50598cfc..28f8de131c 100644 --- a/app/assets/javascripts/discourse/dialects/quote_dialect.js +++ b/app/assets/javascripts/discourse/dialects/quote_dialect.js @@ -26,7 +26,9 @@ Discourse.BBCode.register('quote', {noWrap: true, singlePara: true}, function(co if (options.lookupAvatarByPostNumber) { // client-side, we can retrieve the avatar from the post var postNumber = parseInt(params['data-post'], 10); - avatarImg = options.lookupAvatarByPostNumber(postNumber); + var topicId = parseInt(params['data-topic'], 10); + + avatarImg = options.lookupAvatarByPostNumber(postNumber, topicId); } else if (options.lookupAvatar) { // server-side, we need to lookup the avatar from the username avatarImg = options.lookupAvatar(username); diff --git a/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 b/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 index 1492a11bea..13acb784e9 100644 --- a/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 +++ b/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 @@ -47,7 +47,7 @@ **/ -let _connectorCache; +let _connectorCache, _rawCache; function findOutlets(collection, callback) { @@ -73,6 +73,7 @@ function findOutlets(collection, callback) { function buildConnectorCache() { _connectorCache = {}; + _rawCache = {}; const uniqueViews = {}; findOutlets(requirejs._eak_seen, function(outletName, resource, uniqueName) { @@ -93,10 +94,23 @@ function buildConnectorCache() { // We are going to add it back with the proper template _connectorCache[outletName].removeObject(viewClass); } else { - viewClass = Em.View.extend({ classNames: [outletName + '-outlet', uniqueName] }); + if (!/\.raw$/.test(uniqueName)) { + viewClass = Em.View.extend({ classNames: [outletName + '-outlet', uniqueName] }); + } + } + + if (viewClass) { + _connectorCache[outletName].pushObject(viewClass.extend(mixin)); + } else { + // we have a raw template + if (!_rawCache[outletName]) { + _rawCache[outletName] = []; + } + + _rawCache[outletName].push(Ember.TEMPLATES[resource]); } - _connectorCache[outletName].pushObject(viewClass.extend(mixin)); }); + } var _viewInjections; @@ -113,6 +127,24 @@ function viewInjections(container) { return _viewInjections; } +// unbound version of outlets, only has a template +Handlebars.registerHelper('plugin-outlet', function(name){ + + if (!_rawCache) { buildConnectorCache(); } + + const functions = _rawCache[name]; + if (functions) { + var output = []; + + for(var i=0; i" + title + ""); + var string = "" + title + ""; + + return new Handlebars.SafeString(string); }); diff --git a/app/assets/javascripts/discourse/initializers/show-footer.js.es6 b/app/assets/javascripts/discourse/initializers/show-footer.js.es6 new file mode 100644 index 0000000000..ce58457774 --- /dev/null +++ b/app/assets/javascripts/discourse/initializers/show-footer.js.es6 @@ -0,0 +1,15 @@ +export default { + name: "show-footer", + + initialize(container) { + const router = container.lookup("router:main"); + const application = container.lookup("controller:application"); + + // only take care of hiding the footer here + // controllers MUST take care of displaying it + router.on("willTransition", () => { + application.set("showFooter", false); + return true; + }); + } +}; diff --git a/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 b/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 index a45a5a3585..f833aa1c09 100644 --- a/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 +++ b/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 @@ -35,6 +35,9 @@ export default { }); bus.subscribe('/queue_counts', (data) => { user.set('post_queue_new_count', data.post_queue_new_count); + if (data.post_queue_new_count > 0) { + user.set('show_queued_posts', 1); + } }); } diff --git a/app/assets/javascripts/discourse/lib/ajax-error.js.es6 b/app/assets/javascripts/discourse/lib/ajax-error.js.es6 index 04d6d0dbc7..bb2a915727 100644 --- a/app/assets/javascripts/discourse/lib/ajax-error.js.es6 +++ b/app/assets/javascripts/discourse/lib/ajax-error.js.es6 @@ -1,4 +1,4 @@ -function extractError(error) { +export function extractError(error, defaultMessage) { if (error instanceof Error) { Ember.Logger.error(error.stack); } @@ -42,7 +42,7 @@ function extractError(error) { } } - return parsedError || I18n.t('generic_error'); + return parsedError || defaultMessage || I18n.t('generic_error'); } export function throwAjaxError(undoCallback) { diff --git a/app/assets/javascripts/discourse/lib/desktop-notifications.js.es6 b/app/assets/javascripts/discourse/lib/desktop-notifications.js.es6 index 5d7b18db0f..ce67ff9dfd 100644 --- a/app/assets/javascripts/discourse/lib/desktop-notifications.js.es6 +++ b/app/assets/javascripts/discourse/lib/desktop-notifications.js.es6 @@ -94,6 +94,7 @@ function onNotification(data) { if (!liveEnabled) { return; } if (!primaryTab) { return; } if (!isIdle()) { return; } + if (localStorage.getItem('notifications-disabled')) { return; } const notificationTitle = I18n.t(i18nKey(data.notification_type), { site_title: Discourse.SiteSettings.title, diff --git a/app/assets/javascripts/discourse/lib/emoji/emoji-toolbar.js.es6 b/app/assets/javascripts/discourse/lib/emoji/emoji-toolbar.js.es6 index 00287b21a5..c5b731da09 100644 --- a/app/assets/javascripts/discourse/lib/emoji/emoji-toolbar.js.es6 +++ b/app/assets/javascripts/discourse/lib/emoji/emoji-toolbar.js.es6 @@ -11,7 +11,7 @@ var groups = [ { name: "nature", fullname: "Nature", - tabicon: "leaves", + tabicon: "evergreen_tree", icons: ["seedling", "evergreen_tree", "deciduous_tree", "palm_tree", "cactus", "tulip", "cherry_blossom", "rose", "hibiscus", "sunflower", "blossom", "bouquet", "ear_of_rice", "herb", "four_leaf_clover", "maple_leaf", "fallen_leaf", "leaves", "mushroom", "chestnut", "rat", "mouse2", "mouse", "hamster", "ox", "water_buffalo", "cow2", "cow", "tiger2", "leopard", "tiger", "rabbit2", "rabbit", "cat2", "cat", "racehorse", "horse", "ram", "sheep", "goat", "rooster", "chicken", "baby_chick", "hatching_chick", "hatched_chick", "bird", "penguin", "elephant", "dromedary_camel", "camel", "boar", "pig2", "pig", "pig_nose", "dog2", "poodle", "dog", "wolf", "bear", "koala", "panda_face", "monkey_face", "see_no_evil", "hear_no_evil", "speak_no_evil", "monkey", "dragon", "dragon_face", "crocodile", "snake", "turtle", "frog", "whale2", "whale", "dolphin", "octopus", "fish", "tropical_fish", "blowfish", "shell", "snail", "bug", "ant", "bee", "beetle", "feet", "zap", "fire", "crescent_moon", "sunny", "partly_sunny", "cloud", "droplet", "sweat_drops", "umbrella", "dash", "snowflake", "star2", "star", "stars", "sunrise_over_mountains", "sunrise", "rainbow", "ocean", "volcano", "milky_way", "mount_fuji", "japan", "globe_with_meridians", "earth_africa", "earth_americas", "earth_asia", "new_moon", "waxing_crescent_moon", "first_quarter_moon", "moon", "full_moon", "waning_gibbous_moon", "last_quarter_moon", "waning_crescent_moon", "new_moon_with_face", "full_moon_with_face", "first_quarter_moon_with_face", "last_quarter_moon_with_face", "sun_with_face"] }, { @@ -144,7 +144,7 @@ var toolbar = function(selected){ var icon = g.tabicon; var title = g.fullname; if (g.name === "recent") { - icon = "star2"; + icon = "star"; title = "Recent"; } else if (g.name === "ungrouped") { icon = g.icons[0]; diff --git a/app/assets/javascripts/discourse/lib/static-route-builder.js.es6 b/app/assets/javascripts/discourse/lib/static-route-builder.js.es6 index 79365f4655..09ef6736c2 100644 --- a/app/assets/javascripts/discourse/lib/static-route-builder.js.es6 +++ b/app/assets/javascripts/discourse/lib/static-route-builder.js.es6 @@ -1,39 +1,42 @@ -import ShowFooter from "discourse/mixins/show-footer"; - -var configs = { - 'faq': 'faq_url', - 'tos': 'tos_url', - 'privacy': 'privacy_policy_url' +const configs = { + "faq": "faq_url", + "tos": "tos_url", + "privacy": "privacy_policy_url" }; -export default function(page) { - return Discourse.Route.extend(ShowFooter, { - renderTemplate: function() { - this.render('static'); +export default (page) => { + return Discourse.Route.extend({ + renderTemplate() { + this.render("static"); }, - beforeModel: function(transition) { - var configKey = configs[page]; + beforeModel(transition) { + const configKey = configs[page]; if (configKey && Discourse.SiteSettings[configKey].length > 0) { transition.abort(); Discourse.URL.redirectTo(Discourse.SiteSettings[configKey]); } }, - activate: function() { + activate() { this._super(); - // Scroll to an element if exists Discourse.URL.scrollToId(document.location.hash); }, - model: function() { + model() { return Discourse.StaticPage.find(page); }, - setupController: function(controller, model) { - this.controllerFor('static').set('model', model); + setupController(controller, model) { + this.controllerFor("static").set("model", model); + }, + + actions: { + didTransition() { + this.controllerFor("application").set("showFooter", true); + return true; + } } }); -} - +}; diff --git a/app/assets/javascripts/discourse/mixins/ajax.js b/app/assets/javascripts/discourse/mixins/ajax.js index a659ed57be..6817150d51 100644 --- a/app/assets/javascripts/discourse/mixins/ajax.js +++ b/app/assets/javascripts/discourse/mixins/ajax.js @@ -47,7 +47,8 @@ Discourse.Ajax = Em.Mixin.create({ if (_trackView && (!args.type || args.type === "GET")) { _trackView = false; - args.headers['Discourse-Track-View'] = true; + // DON'T CHANGE: rack is prepending "HTTP_" in the header's name + args.headers['DISCOURSE_TRACK_VIEW'] = true; } args.success = function(data, textStatus, xhr) { diff --git a/app/assets/javascripts/discourse/mixins/scrolling.js b/app/assets/javascripts/discourse/mixins/scrolling.js index 6178e46a33..c9473c155e 100644 --- a/app/assets/javascripts/discourse/mixins/scrolling.js +++ b/app/assets/javascripts/discourse/mixins/scrolling.js @@ -33,6 +33,7 @@ Discourse.Scrolling = Em.Mixin.create({ } Discourse.ScrollingDOMMethods.bindOnScroll(onScrollMethod, opts.name); + Em.run.scheduleOnce('afterRender', onScrollMethod); }, /** diff --git a/app/assets/javascripts/discourse/mixins/show-footer.js.es6 b/app/assets/javascripts/discourse/mixins/show-footer.js.es6 deleted file mode 100644 index b97014153a..0000000000 --- a/app/assets/javascripts/discourse/mixins/show-footer.js.es6 +++ /dev/null @@ -1,15 +0,0 @@ -export default Em.Mixin.create({ - actions: { - didTransition() { - Em.run.schedule("afterRender", () => { - this.controllerFor("application").set("showFooter", true); - }); - return true; - }, - - willTransition() { - this.controllerFor("application").set("showFooter", false); - return true; - } - } -}); diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6 index 49838fdf72..edde5df3c6 100644 --- a/app/assets/javascripts/discourse/models/composer.js.es6 +++ b/app/assets/javascripts/discourse/models/composer.js.es6 @@ -22,7 +22,9 @@ const CLOSED = 'closed', topic_id: 'topic.id', is_warning: 'isWarning', archetype: 'archetypeId', - target_usernames: 'targetUsernames' + target_usernames: 'targetUsernames', + typing_duration_msecs: 'typingTime', + composer_open_duration_msecs: 'composerTime' }, _edit_topic_serializer = { @@ -52,6 +54,31 @@ const Composer = RestModel.extend({ viewOpen: Em.computed.equal('composeState', OPEN), viewDraft: Em.computed.equal('composeState', DRAFT), + composeStateChanged: function() { + var oldOpen = this.get('composerOpened'); + + if (this.get('composeState') === OPEN) { + this.set('composerOpened', oldOpen || new Date()); + } else { + if (oldOpen) { + var oldTotal = this.get('composerTotalOpened') || 0; + this.set('composerTotalOpened', oldTotal + (new Date() - oldOpen)); + } + this.set('composerOpened', null); + } + }.observes('composeState'), + + composerTime: function() { + var total = this.get('composerTotalOpened') || 0; + + var oldOpen = this.get('composerOpened'); + if (oldOpen) { + total += (new Date() - oldOpen); + } + + return total; + }.property().volatile(), + archetype: function() { return this.get('archetypes').findProperty('id', this.get('archetypeId')); }.property('archetypeId'), @@ -60,6 +87,12 @@ const Composer = RestModel.extend({ return this.set('metaData', Em.Object.create()); }.observes('archetype'), + // view detected user is typing + typing: _.throttle(function(){ + var typingTime = this.get("typingTime") || 0; + this.set("typingTime", typingTime + 100); + }, 100, {leading: false, trailing: true}), + editingFirstPost: Em.computed.and('editingPost', 'post.firstPost'), canEditTitle: Em.computed.or('creatingTopic', 'creatingPrivateMessage', 'editingFirstPost'), canCategorize: Em.computed.and('canEditTitle', 'notCreatingPrivateMessage'), @@ -349,7 +382,9 @@ const Composer = RestModel.extend({ composeState: opts.composerState || OPEN, action: opts.action, topic: opts.topic, - targetUsernames: opts.usernames + targetUsernames: opts.usernames, + composerTotalOpened: opts.composerTime, + typingTime: opts.typingTime }); if (opts.post) { @@ -420,7 +455,10 @@ const Composer = RestModel.extend({ post: null, title: null, editReason: null, - stagedPost: false + stagedPost: false, + typingTime: 0, + composerOpened: null, + composerTotalOpened: 0 }); }, @@ -502,7 +540,9 @@ const Composer = RestModel.extend({ admin: user.get('admin'), yours: true, read: true, - wiki: false + wiki: false, + typingTime: this.get('typingTime'), + composerTime: this.get('composerTime') }); this.serialize(_create_serializer, createdPost); @@ -603,13 +643,20 @@ const Composer = RestModel.extend({ postId: this.get('post.id'), archetypeId: this.get('archetypeId'), metaData: this.get('metaData'), - usernames: this.get('targetUsernames') + usernames: this.get('targetUsernames'), + composerTime: this.get('composerTime'), + typingTime: this.get('typingTime') }; this.set('draftStatus', I18n.t('composer.saving_draft_tip')); const composer = this; + if (this._clearingStatus) { + Em.run.cancel(this._clearingStatus); + this._clearingStatus = null; + } + // try to save the draft return Discourse.Draft.save(this.get('draftKey'), this.get('draftSequence'), data) .then(function() { @@ -617,7 +664,20 @@ const Composer = RestModel.extend({ }).catch(function() { composer.set('draftStatus', I18n.t('composer.drafts_offline')); }); - } + }, + + dataChanged: function(){ + const draftStatus = this.get('draftStatus'); + const self = this; + + if (draftStatus && !this._clearingStatus) { + + this._clearingStatus = Em.run.later(this, function(){ + self.set('draftStatus', null); + self._clearingStatus = null; + }, 1000); + } + }.observes('title','reply') }); @@ -657,7 +717,9 @@ Composer.reopenClass({ metaData: draft.metaData, usernames: draft.usernames, draft: true, - composerState: DRAFT + composerState: DRAFT, + composerTime: draft.composerTime, + typingTime: draft.typingTime }); } }, diff --git a/app/assets/javascripts/discourse/models/nav_item.js b/app/assets/javascripts/discourse/models/nav-item.js.es6 similarity index 79% rename from app/assets/javascripts/discourse/models/nav_item.js rename to app/assets/javascripts/discourse/models/nav-item.js.es6 index 0ea27df11e..def6a3430a 100644 --- a/app/assets/javascripts/discourse/models/nav_item.js +++ b/app/assets/javascripts/discourse/models/nav-item.js.es6 @@ -7,7 +7,7 @@ @module Discourse **/ -Discourse.NavItem = Discourse.Model.extend({ +const NavItem = Discourse.Model.extend({ displayName: function() { var categoryName = this.get('categoryName'), @@ -25,7 +25,7 @@ Discourse.NavItem = Discourse.Model.extend({ extra.categoryName = Discourse.Formatter.toTitleCase(categoryName); } return I18n.t("filters." + name.replace("/", ".") + ".title", extra); - }.property('categoryName,name,count'), + }.property('categoryName', 'name', 'count'), topicTrackingState: function() { return Discourse.TopicTrackingState.current(); @@ -45,8 +45,13 @@ Discourse.NavItem = Discourse.Model.extend({ return null; }.property('name'), - // href from this item href: function() { + var customHref = null; + _.each(NavItem.customNavItemHrefs, function(cb) { + customHref = cb.call(this, this); + if (customHref) { return false; } + }, this); + if (customHref) { return customHref; } return Discourse.getURL("/") + this.get('filterMode'); }.property('filterMode'), @@ -79,10 +84,13 @@ Discourse.NavItem = Discourse.Model.extend({ }); -Discourse.NavItem.reopenClass({ +NavItem.reopenClass({ + + extraArgsCallbacks: [], + customNavItemHrefs: [], // create a nav item from the text, will return null if there is not valid nav item for this particular text - fromText: function(text, opts) { + fromText(text, opts) { var split = text.split(","), name = split[0], testName = name.split("/")[0], @@ -92,13 +100,17 @@ Discourse.NavItem.reopenClass({ if (!Discourse.Category.list() && testName === "categories") return null; if (!Discourse.Site.currentProp('top_menu_items').contains(testName)) return null; - var args = { name: name, hasIcon: name === "unread" }; + var args = { name: name, hasIcon: name === "unread" }, extra = null, self = this; if (opts.category) { args.category = opts.category; } if (opts.noSubcategories) { args.noSubcategories = true; } + _.each(NavItem.extraArgsCallbacks, function(cb) { + extra = cb.call(self, text, opts); + _.merge(args, extra); + }); return Discourse.NavItem.create(args); }, - buildList: function(category, args) { + buildList(category, args) { args = args || {}; if (category) { args.category = category } @@ -118,3 +130,11 @@ Discourse.NavItem.reopenClass({ } }); + +export default NavItem; +export function extraNavItemProperties(cb) { + NavItem.extraArgsCallbacks.push(cb); +} +export function customNavItemHref(cb) { + NavItem.customNavItemHrefs.push(cb); +} diff --git a/app/assets/javascripts/discourse/models/topic-list.js.es6 b/app/assets/javascripts/discourse/models/topic-list.js.es6 index d685b2f60c..782cb9fd06 100644 --- a/app/assets/javascripts/discourse/models/topic-list.js.es6 +++ b/app/assets/javascripts/discourse/models/topic-list.js.es6 @@ -1,7 +1,6 @@ import RestModel from 'discourse/models/rest'; import Model from 'discourse/models/model'; - function topicsFrom(result, store) { if (!result) { return; } diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index 0f06dc4847..2455087411 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -1,3 +1,4 @@ +import { flushMap } from 'discourse/models/store'; import RestModel from 'discourse/models/rest'; const Topic = RestModel.extend({ @@ -462,28 +463,6 @@ Topic.reopenClass({ return Discourse.ajax(url + ".json", {data: data}); }, - mergeTopic(topicId, destinationTopicId) { - const promise = Discourse.ajax("/t/" + topicId + "/merge-topic", { - type: 'POST', - data: {destination_topic_id: destinationTopicId} - }).then(function (result) { - if (result.success) return result; - promise.reject(new Error("error merging topic")); - }); - return promise; - }, - - movePosts(topicId, opts) { - const promise = Discourse.ajax("/t/" + topicId + "/move-posts", { - type: 'POST', - data: opts - }).then(function (result) { - if (result.success) return result; - promise.reject(new Error("error moving posts topic")); - }); - return promise; - }, - changeOwners(topicId, opts) { const promise = Discourse.ajax("/t/" + topicId + "/change-owner", { type: 'POST', @@ -523,4 +502,24 @@ Topic.reopenClass({ } }); +function moveResult(result) { + if (result.success) { + // We should be hesitant to flush the map but moving ids is one rare case + flushMap(); + return result; + } + throw "error moving posts topic"; +} + +export function movePosts(topicId, data) { + return Discourse.ajax("/t/" + topicId + "/move-posts", { type: 'POST', data }).then(moveResult); +} + +export function mergeTopic(topicId, destinationTopicId) { + return Discourse.ajax("/t/" + topicId + "/merge-topic", { + type: 'POST', + data: {destination_topic_id: destinationTopicId} + }).then(moveResult); +} + export default Topic; diff --git a/app/assets/javascripts/discourse/routes/about.js.es6 b/app/assets/javascripts/discourse/routes/about.js.es6 index 5258ea7fbd..f25d64387b 100644 --- a/app/assets/javascripts/discourse/routes/about.js.es6 +++ b/app/assets/javascripts/discourse/routes/about.js.es6 @@ -1,13 +1,16 @@ -import ShowFooter from "discourse/mixins/show-footer"; - -export default Discourse.Route.extend(ShowFooter, { - model: function() { - return Discourse.ajax("/about.json").then(function(result) { - return result.about; - }); +export default Discourse.Route.extend({ + model() { + return Discourse.ajax("/about.json").then(result => result.about); }, - titleToken: function() { + titleToken() { return I18n.t('about.simple_title'); + }, + + actions: { + didTransition() { + this.controllerFor("application").set("showFooter", true); + return true; + } } }); diff --git a/app/assets/javascripts/discourse/routes/application.js.es6 b/app/assets/javascripts/discourse/routes/application.js.es6 index b2c58f4fc8..f0af569b02 100644 --- a/app/assets/javascripts/discourse/routes/application.js.es6 +++ b/app/assets/javascripts/discourse/routes/application.js.es6 @@ -53,7 +53,7 @@ const ApplicationRoute = Discourse.Route.extend(OpenComposer, { error(err, transition) { if (err.status === 404) { // 404 - this.intermediateTransitionTo('unknown'); + this.transitionTo('unknown'); return; } @@ -74,7 +74,7 @@ const ApplicationRoute = Discourse.Route.extend(OpenComposer, { } exceptionController.setProperties({ lastTransition: transition, thrown: err }); - this.intermediateTransitionTo('exception'); + this.transitionTo('exception'); }, showLogin: unlessReadOnly('handleShowLogin'), diff --git a/app/assets/javascripts/discourse/routes/badges-index.js.es6 b/app/assets/javascripts/discourse/routes/badges-index.js.es6 index 67af4625f2..dfb207391f 100644 --- a/app/assets/javascripts/discourse/routes/badges-index.js.es6 +++ b/app/assets/javascripts/discourse/routes/badges-index.js.es6 @@ -1,17 +1,20 @@ -import ShowFooter from "discourse/mixins/show-footer"; - -export default Discourse.Route.extend(ShowFooter, { - model: function() { - if (PreloadStore.get('badges')) { - return PreloadStore.getAndRemove('badges').then(function(json) { - return Discourse.Badge.createFromJson(json); - }); +export default Discourse.Route.extend({ + model() { + if (PreloadStore.get("badges")) { + return PreloadStore.getAndRemove("badges").then(json => Discourse.Badge.createFromJson(json)); } else { - return Discourse.Badge.findAll({onlyListable: true}); + return Discourse.Badge.findAll({ onlyListable: true }); } }, - titleToken: function() { - return I18n.t('badges.title'); + titleToken() { + return I18n.t("badges.title"); + }, + + actions: { + didTransition() { + this.controllerFor("application").set("showFooter", true); + return true; + } } }); diff --git a/app/assets/javascripts/discourse/routes/badges-show.js.es6 b/app/assets/javascripts/discourse/routes/badges-show.js.es6 index 7b897f03bf..cc4cf0eaa9 100644 --- a/app/assets/javascripts/discourse/routes/badges-show.js.es6 +++ b/app/assets/javascripts/discourse/routes/badges-show.js.es6 @@ -1,43 +1,41 @@ -import ShowFooter from "discourse/mixins/show-footer"; - -export default Discourse.Route.extend(ShowFooter, { +export default Discourse.Route.extend({ actions: { - didTransition: function() { + didTransition() { this.controllerFor("badges/show")._showFooter(); return true; } }, - serialize: function(model) { - return {id: model.get('id'), slug: model.get('name').replace(/[^A-Za-z0-9_]+/g, '-').toLowerCase()}; + serialize(model) { + return { + id: model.get("id"), + slug: model.get("name").replace(/[^A-Za-z0-9_]+/g, "-").toLowerCase() + }; }, - model: function(params) { - if (PreloadStore.get('badge')) { - return PreloadStore.getAndRemove('badge').then(function(json) { - return Discourse.Badge.createFromJson(json); - }); + model(params) { + if (PreloadStore.get("badge")) { + return PreloadStore.getAndRemove("badge").then(json => Discourse.Badge.createFromJson(json)); } else { return Discourse.Badge.findById(params.id); } }, - afterModel: function(model) { - var self = this; - return Discourse.UserBadge.findByBadgeId(model.get('id')).then(function(userBadges) { - self.userBadges = userBadges; + afterModel(model) { + return Discourse.UserBadge.findByBadgeId(model.get("id")).then(userBadges => { + this.userBadges = userBadges; }); }, - titleToken: function() { - var model = this.modelFor('badges.show'); + titleToken() { + const model = this.modelFor("badges.show"); if (model) { - return model.get('displayName'); + return model.get("displayName"); } }, - setupController: function(controller, model) { - controller.set('model', model); - controller.set('userBadges', this.userBadges); + setupController(controller, model) { + controller.set("model", model); + controller.set("userBadges", this.userBadges); } }); diff --git a/app/assets/javascripts/discourse/routes/build-admin-user-posts-route.js.es6 b/app/assets/javascripts/discourse/routes/build-admin-user-posts-route.js.es6 index 6d79b3b487..1615cdaca8 100644 --- a/app/assets/javascripts/discourse/routes/build-admin-user-posts-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-admin-user-posts-route.js.es6 @@ -1,31 +1,29 @@ -import ShowFooter from "discourse/mixins/show-footer"; - export default function (filter) { - return Discourse.Route.extend(ShowFooter, { + return Discourse.Route.extend({ actions: { - didTransition: function() { - this.controllerFor('user').set('indexStream', true); + didTransition() { + this.controllerFor("user").set("indexStream", true); this.controllerFor("user-posts")._showFooter(); return true; } }, - model: function () { + model() { return this.modelFor("user").get("postsStream"); }, - afterModel: function () { + afterModel() { return this.modelFor("user").get("postsStream").filterBy(filter); }, - setupController: function(controller, model) { + setupController(controller, model) { // initialize "canLoadMore" model.set("canLoadMore", model.get("itemsLoaded") === 60); this.controllerFor("user-posts").set("model", model); }, - renderTemplate: function() { + renderTemplate() { this.render("user/posts", { into: "user" }); } }); diff --git a/app/assets/javascripts/discourse/routes/build-user-topic-list-route.js.es6 b/app/assets/javascripts/discourse/routes/build-user-topic-list-route.js.es6 index b5a83808d1..c14174fae7 100644 --- a/app/assets/javascripts/discourse/routes/build-user-topic-list-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-user-topic-list-route.js.es6 @@ -1,36 +1,35 @@ import UserTopicListRoute from "discourse/routes/user-topic-list"; -import ShowFooter from "discourse/mixins/show-footer"; // A helper to build a user topic list route -export default function (viewName, path) { - return UserTopicListRoute.extend(ShowFooter, { +export default (viewName, path) => { + return UserTopicListRoute.extend({ userActionType: Discourse.UserAction.TYPES.messages_received, actions: { - didTransition: function() { + didTransition() { this.controllerFor("user-topics-list")._showFooter(); return true; } }, - model: function() { - return this.store.findFiltered('topicList', {filter: 'topics/' + path + '/' + this.modelFor('user').get('username_lower')}); + model() { + return this.store.findFiltered("topicList", { filter: "topics/" + path + "/" + this.modelFor("user").get("username_lower") }); }, - setupController: function() { + setupController() { this._super.apply(this, arguments); - this.controllerFor('user-topics-list').setProperties({ + this.controllerFor("user-topics-list").setProperties({ hideCategory: true, showParticipants: true }); - this.controllerFor('user').set('pmView', viewName); - this.controllerFor('search').set('contextType', 'private_messages'); + this.controllerFor("user").set("pmView", viewName); + this.controllerFor("search").set("contextType", "private_messages"); }, - deactivate: function(){ - this.controllerFor('search').set('contextType', 'user'); + deactivate() { + this.controllerFor("search").set("contextType", "user"); } }); -} +}; diff --git a/app/assets/javascripts/discourse/routes/discourse.js.es6 b/app/assets/javascripts/discourse/routes/discourse.js.es6 index 2a26642805..a6bba2cb6a 100644 --- a/app/assets/javascripts/discourse/routes/discourse.js.es6 +++ b/app/assets/javascripts/discourse/routes/discourse.js.es6 @@ -4,7 +4,7 @@ const DiscourseRoute = Ember.Route.extend({ // changes resfreshQueryWithoutTransition: false, - refresh: function() { + refresh() { if (!this.refreshQueryWithoutTransition) { return this._super(); } if (!this.router.router.activeTransition) { @@ -17,13 +17,13 @@ const DiscourseRoute = Ember.Route.extend({ } }, - _refreshTitleOnce: function() { + _refreshTitleOnce() { this.send('_collectTitleTokens', []); }, actions: { - _collectTitleTokens: function(tokens) { + _collectTitleTokens(tokens) { // If there's a title token method, call it and get the token if (this.titleToken) { const t = this.titleToken(); @@ -40,19 +40,19 @@ const DiscourseRoute = Ember.Route.extend({ return true; }, - refreshTitle: function() { + refreshTitle() { Ember.run.once(this, this._refreshTitleOnce); } }, - redirectIfLoginRequired: function() { + redirectIfLoginRequired() { const app = this.controllerFor('application'); if (app.get('loginRequired')) { this.replaceWith('login'); } }, - openTopicDraft: function(model){ + openTopicDraft(model){ // If there's a draft, open the create topic composer if (model.draft) { const composer = this.controllerFor('composer'); @@ -67,7 +67,7 @@ const DiscourseRoute = Ember.Route.extend({ } }, - isPoppedState: function(transition) { + isPoppedState(transition) { return (!transition._discourse_intercepted) && (!!transition.intent.url); } }); diff --git a/app/assets/javascripts/discourse/routes/discovery-categories-route.js.es6 b/app/assets/javascripts/discourse/routes/discovery-categories-route.js.es6 index c46b9b8275..61fd853d10 100644 --- a/app/assets/javascripts/discourse/routes/discovery-categories-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/discovery-categories-route.js.es6 @@ -1,15 +1,14 @@ -import ShowFooter from 'discourse/mixins/show-footer'; -import showModal from 'discourse/lib/show-modal'; +import showModal from "discourse/lib/show-modal"; import OpenComposer from "discourse/mixins/open-composer"; -Discourse.DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, ShowFooter, { +Discourse.DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, { renderTemplate() { - this.render('navigation/categories', { outlet: 'navigation-bar' }); - this.render('discovery/categories', { outlet: 'list-container' }); + this.render("navigation/categories", { outlet: "navigation-bar" }); + this.render("discovery/categories", { outlet: "list-container" }); }, beforeModel() { - this.controllerFor('navigation/categories').set('filterMode', 'categories'); + this.controllerFor("navigation/categories").set("filterMode", "categories"); }, model() { @@ -17,11 +16,11 @@ Discourse.DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, ShowFo // if default page is categories PreloadStore.remove("topic_list"); - return Discourse.CategoryList.list('categories').then(function(list) { + return Discourse.CategoryList.list("categories").then(function(list) { const tracking = Discourse.TopicTrackingState.current(); if (tracking) { - tracking.sync(list, 'categories'); - tracking.trackIncoming('categories'); + tracking.sync(list, "categories"); + tracking.trackIncoming("categories"); } return list; }); @@ -29,15 +28,15 @@ Discourse.DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, ShowFo titleToken() { if (Discourse.Utilities.defaultHomepage() === "categories") { return; } - return I18n.t('filters.categories.title'); + return I18n.t("filters.categories.title"); }, setupController(controller, model) { - controller.set('model', model); + controller.set("model", model); // Only show either the Create Category or Create Topic button - this.controllerFor('navigation/categories').set('canCreateCategory', model.get('can_create_category')); - this.controllerFor('navigation/categories').set('canCreateTopic', model.get('can_create_topic') && !model.get('can_create_category')); + this.controllerFor("navigation/categories").set("canCreateCategory", model.get("can_create_category")); + this.controllerFor("navigation/categories").set("canCreateTopic", model.get("can_create_topic") && !model.get("can_create_category")); this.openTopicDraft(model); }, @@ -45,20 +44,25 @@ Discourse.DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, ShowFo actions: { createCategory() { const groups = this.site.groups, - everyoneName = groups.findBy('id', 0).name; + everyoneName = groups.findBy("id", 0).name; const model = Discourse.Category.create({ - color: 'AB9364', text_color: 'FFFFFF', group_permissions: [{group_name: everyoneName, permission_type: 1}], + 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'); + showModal("editCategory", { model }); + this.controllerFor("editCategory").set("selectedTab", "general"); }, createTopic() { - this.openComposer(this.controllerFor('discovery/categories')); + this.openComposer(this.controllerFor("discovery/categories")); + }, + + didTransition() { + this.controllerFor("application").set("showFooter", true); + return true; } } }); diff --git a/app/assets/javascripts/discourse/routes/discovery.js.es6 b/app/assets/javascripts/discourse/routes/discovery.js.es6 index e5be7da1e3..98d182c42a 100644 --- a/app/assets/javascripts/discourse/routes/discovery.js.es6 +++ b/app/assets/javascripts/discourse/routes/discovery.js.es6 @@ -2,14 +2,15 @@ The parent route for all discovery routes. Handles the logic for showing the loading spinners. **/ -import ShowFooter from "discourse/mixins/show-footer"; import OpenComposer from "discourse/mixins/open-composer"; -import { scrollTop } from 'discourse/mixins/scroll-top'; +import { scrollTop } from "discourse/mixins/scroll-top"; -const DiscoveryRoute = Discourse.Route.extend(OpenComposer, ShowFooter, { - redirect: function() { return this.redirectIfLoginRequired(); }, +const DiscoveryRoute = Discourse.Route.extend(OpenComposer, { + redirect() { + return this.redirectIfLoginRequired(); + }, - beforeModel: function(transition) { + beforeModel(transition) { if (transition.intent.url === "/" && transition.targetName.indexOf("discovery.top") === -1 && Discourse.User.currentProp("should_be_redirected_to_top")) { @@ -19,31 +20,31 @@ const DiscoveryRoute = Discourse.Route.extend(OpenComposer, ShowFooter, { }, actions: { - loading: function() { - this.controllerFor('discovery').set("loading", true); + loading() { + this.controllerFor("discovery").set("loading", true); return true; }, - loadingComplete: function() { - this.controllerFor('discovery').set('loading', false); - if (!this.session.get('topicListScrollPosition')) { + loadingComplete() { + this.controllerFor("discovery").set("loading", false); + if (!this.session.get("topicListScrollPosition")) { scrollTop(); } }, - didTransition: function() { + didTransition() { this.controllerFor("discovery")._showFooter(); - this.send('loadingComplete'); + this.send("loadingComplete"); return true; }, // clear a pinned topic - clearPin: function(topic) { + clearPin(topic) { topic.clearPin(); }, - createTopic: function() { - this.openComposer(this.controllerFor('discovery/topics')); + createTopic() { + this.openComposer(this.controllerFor("discovery/topics")); } } diff --git a/app/assets/javascripts/discourse/routes/exception.js.es6 b/app/assets/javascripts/discourse/routes/exception.js.es6 index a5c5fab878..2c911a989d 100644 --- a/app/assets/javascripts/discourse/routes/exception.js.es6 +++ b/app/assets/javascripts/discourse/routes/exception.js.es6 @@ -1,7 +1,10 @@ -import ShowFooter from "discourse/mixins/show-footer"; +export default Discourse.Route.extend({ + serialize() { return ""; }, -export default Discourse.Route.extend(ShowFooter, { - serialize: function() { - return ""; + actions: { + didTransition() { + this.controllerFor("application").set("showFooter", true); + return true; + } } }); diff --git a/app/assets/javascripts/discourse/routes/full-page-search.js.es6 b/app/assets/javascripts/discourse/routes/full-page-search.js.es6 index b092136ee5..482ebf1fc5 100644 --- a/app/assets/javascripts/discourse/routes/full-page-search.js.es6 +++ b/app/assets/javascripts/discourse/routes/full-page-search.js.es6 @@ -1,18 +1,23 @@ -import { translateResults } from 'discourse/lib/search-for-term'; +import { translateResults } from "discourse/lib/search-for-term"; export default Discourse.Route.extend({ - queryParams: { - q: { - } - }, - model: function(params) { + queryParams: { q: {} }, + + model(params) { return PreloadStore.getAndRemove("search", function() { - return Discourse.ajax('/search', {data: {q: params.q}}); - }).then(function(results){ - var model = translateResults(results) || {}; + return Discourse.ajax("/search", { data: { q: params.q } }); + }).then(results => { + const model = translateResults(results) || {}; model.q = params.q; return model; }); + }, + + actions: { + didTransition() { + this.controllerFor("full-page-search")._showFooter(); + return true; + } } }); diff --git a/app/assets/javascripts/discourse/routes/group-index.js.es6 b/app/assets/javascripts/discourse/routes/group-index.js.es6 index 63a1a23f76..89dec1edd2 100644 --- a/app/assets/javascripts/discourse/routes/group-index.js.es6 +++ b/app/assets/javascripts/discourse/routes/group-index.js.es6 @@ -1,18 +1,14 @@ -import ShowFooter from "discourse/mixins/show-footer"; - -export default Discourse.Route.extend(ShowFooter, { +export default Discourse.Route.extend({ actions: { - didTransition: function() { - return true; - } + didTransition() { return true; } }, - model: function() { - return this.modelFor('group').findPosts(); + model() { + return this.modelFor("group").findPosts(); }, - setupController: function(controller, model) { - controller.set('model', model); - this.controllerFor('group').set('showing', 'index'); + setupController(controller, model) { + controller.set("model", model); + this.controllerFor("group").set("showing", "index"); } }); diff --git a/app/assets/javascripts/discourse/routes/group-members.js.es6 b/app/assets/javascripts/discourse/routes/group-members.js.es6 index 4713495d8f..22e328cacf 100644 --- a/app/assets/javascripts/discourse/routes/group-members.js.es6 +++ b/app/assets/javascripts/discourse/routes/group-members.js.es6 @@ -1,12 +1,10 @@ -import ShowFooter from "discourse/mixins/show-footer"; - -export default Discourse.Route.extend(ShowFooter, { +export default Discourse.Route.extend({ model() { - return this.modelFor('group'); + return this.modelFor("group"); }, setupController(controller, model) { - this.controllerFor('group').set('showing', 'members'); + this.controllerFor("group").set("showing", "members"); controller.set("model", model); model.findMembers(); } diff --git a/app/assets/javascripts/discourse/routes/preferences.js.es6 b/app/assets/javascripts/discourse/routes/preferences.js.es6 index 67fd1b923e..d748689f40 100644 --- a/app/assets/javascripts/discourse/routes/preferences.js.es6 +++ b/app/assets/javascripts/discourse/routes/preferences.js.es6 @@ -1,8 +1,7 @@ -import ShowFooter from "discourse/mixins/show-footer"; import RestrictedUserRoute from "discourse/routes/restricted-user"; import showModal from 'discourse/lib/show-modal'; -export default RestrictedUserRoute.extend(ShowFooter, { +export default RestrictedUserRoute.extend({ model() { return this.modelFor('user'); }, diff --git a/app/assets/javascripts/discourse/routes/topic.js.es6 b/app/assets/javascripts/discourse/routes/topic.js.es6 index 63762c7b55..7d5f253341 100644 --- a/app/assets/javascripts/discourse/routes/topic.js.es6 +++ b/app/assets/javascripts/discourse/routes/topic.js.es6 @@ -4,10 +4,9 @@ let isTransitioning = false, const SCROLL_DELAY = 500; -import ShowFooter from "discourse/mixins/show-footer"; import showModal from 'discourse/lib/show-modal'; -const TopicRoute = Discourse.Route.extend(ShowFooter, { +const TopicRoute = Discourse.Route.extend({ redirect() { return this.redirectIfLoginRequired(); }, queryParams: { diff --git a/app/assets/javascripts/discourse/routes/user-activity-index.js.es6 b/app/assets/javascripts/discourse/routes/user-activity-index.js.es6 index 3f5ae1272a..926ba744e4 100644 --- a/app/assets/javascripts/discourse/routes/user-activity-index.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-activity-index.js.es6 @@ -4,9 +4,9 @@ export default UserActivityStreamRoute.extend({ userActionType: undefined, actions: { - didTransition: function() { + didTransition() { this._super(); - this.controllerFor('user').set('indexStream', true); + this.controllerFor("user").set("indexStream", true); return true; } } diff --git a/app/assets/javascripts/discourse/routes/user-activity-stream.js.es6 b/app/assets/javascripts/discourse/routes/user-activity-stream.js.es6 index bec825f849..7447d71217 100644 --- a/app/assets/javascripts/discourse/routes/user-activity-stream.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-activity-stream.js.es6 @@ -1,39 +1,38 @@ -import ShowFooter from "discourse/mixins/show-footer"; import ViewingActionType from "discourse/mixins/viewing-action-type"; -export default Discourse.Route.extend(ShowFooter, ViewingActionType, { - model: function() { - return this.modelFor('user').get('stream'); +export default Discourse.Route.extend(ViewingActionType, { + model() { + return this.modelFor("user").get("stream"); }, - afterModel: function() { - return this.modelFor('user').get('stream').filterBy(this.get('userActionType')); + afterModel() { + return this.modelFor("user").get("stream").filterBy(this.get("userActionType")); }, - renderTemplate: function() { - this.render('user_stream'); + renderTemplate() { + this.render("user_stream"); }, - setupController: function(controller, model) { - controller.set('model', model); - this.viewingActionType(this.get('userActionType')); + setupController(controller, model) { + controller.set("model", model); + this.viewingActionType(this.get("userActionType")); }, actions: { - didTransition: function() { + didTransition() { this.controllerFor("user-activity")._showFooter(); return true; }, - removeBookmark: function(userAction) { - var user = this.modelFor('user'); - Discourse.Post.updateBookmark(userAction.get('post_id'), false) + removeBookmark(userAction) { + var user = this.modelFor("user"); + Discourse.Post.updateBookmark(userAction.get("post_id"), false) .then(function() { // remove the user action from the stream - user.get('stream').remove(userAction); + user.get("stream").remove(userAction); // update the counts - user.get('stats').forEach(function (stat) { + user.get("stats").forEach(function (stat) { if (stat.get("action_type") === userAction.action_type) { stat.decrementProperty("count"); } diff --git a/app/assets/javascripts/discourse/routes/user-activity.js.es6 b/app/assets/javascripts/discourse/routes/user-activity.js.es6 index ca4bd71feb..e654ac9b2c 100644 --- a/app/assets/javascripts/discourse/routes/user-activity.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-activity.js.es6 @@ -1,20 +1,20 @@ export default Discourse.Route.extend({ - model: function() { - return this.modelFor('user'); + model() { + return this.modelFor("user"); }, - setupController: function(controller, user) { - this.controllerFor('user-activity').set('model', user); + setupController(controller, user) { + this.controllerFor("user-activity").set("model", user); // Bring up a draft - const composerController = this.controllerFor('composer'); - controller.set('model', user); + const composerController = this.controllerFor("composer"); + controller.set("model", user); if (this.currentUser) { - Discourse.Draft.get('new_private_message').then(function(data) { + Discourse.Draft.get("new_private_message").then(function(data) { if (data.draft) { composerController.open({ draft: data.draft, - draftKey: 'new_private_message', + draftKey: "new_private_message", ignoreIfChanged: true, draftSequence: data.draft_sequence }); diff --git a/app/assets/javascripts/discourse/routes/user-badges.js.es6 b/app/assets/javascripts/discourse/routes/user-badges.js.es6 index d3b39a3b93..fcd099d765 100644 --- a/app/assets/javascripts/discourse/routes/user-badges.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-badges.js.es6 @@ -1,17 +1,23 @@ -import ShowFooter from "discourse/mixins/show-footer"; import ViewingActionType from "discourse/mixins/viewing-action-type"; -export default Discourse.Route.extend(ShowFooter, ViewingActionType, { - model: function() { - return Discourse.UserBadge.findByUsername(this.modelFor('user').get('username_lower'), {grouped: true}); +export default Discourse.Route.extend(ViewingActionType, { + model() { + return Discourse.UserBadge.findByUsername(this.modelFor("user").get("username_lower"), { grouped: true }); }, - setupController: function(controller, model) { + setupController(controller, model) { this.viewingActionType(-1); - controller.set('model', model); + controller.set("model", model); }, - renderTemplate: function() { - this.render('user/badges', {into: 'user'}); + renderTemplate() { + this.render("user/badges", {into: "user"}); + }, + + actions: { + didTransition() { + this.controllerFor("application").set("showFooter", true); + return true; + } } }); diff --git a/app/assets/javascripts/discourse/routes/user-invited-show.js.es6 b/app/assets/javascripts/discourse/routes/user-invited-show.js.es6 index 6c42105dd6..21317f3090 100644 --- a/app/assets/javascripts/discourse/routes/user-invited-show.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-invited-show.js.es6 @@ -1,33 +1,32 @@ -import ShowFooter from 'discourse/mixins/show-footer'; -import showModal from 'discourse/lib/show-modal'; +import showModal from "discourse/lib/show-modal"; -export default Discourse.Route.extend(ShowFooter, { +export default Discourse.Route.extend({ - model: function(params) { + model(params) { this.inviteFilter = params.filter; - return Discourse.Invite.findInvitedBy(this.modelFor('user'), params.filter); + return Discourse.Invite.findInvitedBy(this.modelFor("user"), params.filter); }, - afterModel: function(model) { + afterModel(model) { if (!model.can_see_invite_details) { - this.replaceWith('userInvited.show', 'redeemed'); + this.replaceWith("userInvited.show", "redeemed"); } }, setupController(controller, model) { controller.setProperties({ model: model, - user: this.controllerFor('user').get('model'), + user: this.controllerFor("user").get("model"), filter: this.inviteFilter, - searchTerm: '', + searchTerm: "", totalInvites: model.invites.length }); }, actions: { showInvite() { - showModal('invite', { model: this.currentUser }); - this.controllerFor('invite').reset(); + showModal("invite", { model: this.currentUser }); + this.controllerFor("invite").reset(); }, uploadSuccess(filename) { diff --git a/app/assets/javascripts/discourse/routes/user-notifications.js.es6 b/app/assets/javascripts/discourse/routes/user-notifications.js.es6 index a90c131af2..7c2384db01 100644 --- a/app/assets/javascripts/discourse/routes/user-notifications.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-notifications.js.es6 @@ -1,7 +1,6 @@ -import ShowFooter from "discourse/mixins/show-footer"; import ViewingActionType from "discourse/mixins/viewing-action-type"; -export default Discourse.Route.extend(ShowFooter, ViewingActionType, { +export default Discourse.Route.extend(ViewingActionType, { actions: { didTransition() { this.controllerFor("user-notifications")._showFooter(); @@ -10,13 +9,12 @@ export default Discourse.Route.extend(ShowFooter, ViewingActionType, { }, model() { - var user = this.modelFor('user'); - return this.store.find('notification', {username: user.get('username')}); + return this.store.find("notification", { username: this.modelFor("user").get("username") }); }, setupController(controller, model) { - controller.set('model', model); - controller.set('user', this.modelFor('user')); + controller.set("model", model); + controller.set("user", this.modelFor("user")); this.viewingActionType(-1); } }); diff --git a/app/assets/javascripts/discourse/routes/users.js.es6 b/app/assets/javascripts/discourse/routes/users.js.es6 index e9d516543f..beb25276fc 100644 --- a/app/assets/javascripts/discourse/routes/users.js.es6 +++ b/app/assets/javascripts/discourse/routes/users.js.es6 @@ -9,16 +9,16 @@ export default Discourse.Route.extend({ refreshQueryWithoutTransition: true, titleToken() { - return I18n.t('directory.title'); + return I18n.t("directory.title"); }, resetController(controller, isExiting) { if (isExiting) { controller.setProperties({ - period: 'weekly', - order: 'likes_received', + period: "weekly", + order: "likes_received", asc: null, - name: '' + name: "" }); } }, @@ -26,11 +26,18 @@ export default Discourse.Route.extend({ model(params) { // If we refresh via `refreshModel` set the old model to loading this._params = params; - return this.store.find('directoryItem', params); + return this.store.find("directoryItem", params); }, setupController(controller, model) { const params = this._params; controller.setProperties({ model, period: params.period, nameInput: params.name }); + }, + + actions: { + didTransition() { + this.controllerFor("users")._showFooter(); + return true; + } } }); diff --git a/app/assets/javascripts/discourse/templates/application.hbs b/app/assets/javascripts/discourse/templates/application.hbs index 23822e0f40..7eb87f79e3 100644 --- a/app/assets/javascripts/discourse/templates/application.hbs +++ b/app/assets/javascripts/discourse/templates/application.hbs @@ -1,6 +1,10 @@ {{render "header"}} -
+
+
+ {{custom-html "top"}} + {{global-notice}} +
{{outlet}} {{render "user-card"}}
diff --git a/app/assets/javascripts/discourse/templates/components/badge-button.hbs b/app/assets/javascripts/discourse/templates/components/badge-button.hbs index 7c0469791e..4886147389 100644 --- a/app/assets/javascripts/discourse/templates/components/badge-button.hbs +++ b/app/assets/javascripts/discourse/templates/components/badge-button.hbs @@ -1,3 +1,3 @@ {{icon-or-image badge.icon}} -{{badge.displayName}} +{{badge.displayName}} {{yield}} diff --git a/app/assets/javascripts/discourse/templates/components/desktop-notification-config.hbs b/app/assets/javascripts/discourse/templates/components/desktop-notification-config.hbs new file mode 100644 index 0000000000..3bc0db1c51 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/desktop-notification-config.hbs @@ -0,0 +1,20 @@ + +{{#if isNotSupported}} + {{d-button icon="bell-slash" label="user.desktop_notifications.not_supported" disabled="true"}} +{{/if}} +{{#if isDefaultPermission}} + {{d-button icon="bell-slash" label="user.desktop_notifications.perm_default" action="requestPermission"}} +{{/if}} +{{#if isDeniedPermission}} + {{d-button icon="bell-slash" label="user.desktop_notifications.perm_denied_btn" action="recheckPermission"}} + {{i18n "user.desktop_notifications.perm_denied_expl"}} +{{/if}} +{{#if isGrantedPermission}} + {{#if isEnabled}} + {{d-button icon="bell-slash-o" label="user.desktop_notifications.disable" action="turnoff"}} + {{i18n "user.desktop_notifications.currently_enabled"}} + {{else}} + {{d-button icon="bell-o" label="user.desktop_notifications.enable" action="turnon"}} + {{i18n "user.desktop_notifications.currently_disabled"}} + {{/if}} +{{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/search-result-topic.hbs b/app/assets/javascripts/discourse/templates/components/search-result-topic.hbs index c092bcaa6e..01d100b718 100644 --- a/app/assets/javascripts/discourse/templates/components/search-result-topic.hbs +++ b/app/assets/javascripts/discourse/templates/components/search-result-topic.hbs @@ -2,7 +2,7 @@
  • - {{topic-status topic=result.topic disableActions=true}}{{unbound result.topic.title}}{{category-badge result.topic.category}} + {{topic-status topic=result.topic disableActions=true}}{{unbound result.topic.title}}{{category-badge result.topic.category}}{{plugin-outlet "search-category"}} {{#unless site.mobileView}} diff --git a/app/assets/javascripts/discourse/templates/components/small-action.hbs b/app/assets/javascripts/discourse/templates/components/small-action.hbs index b05c658088..091f0e07df 100644 --- a/app/assets/javascripts/discourse/templates/components/small-action.hbs +++ b/app/assets/javascripts/discourse/templates/components/small-action.hbs @@ -11,5 +11,8 @@ {{avatar post imageSize="small"}} {{/if}} - {{{description}}} +

    {{description}}

    + {{#if post.cooked}} +
    {{{post.cooked}}}
    + {{/if}}
  • diff --git a/app/assets/javascripts/discourse/templates/components/stream-item.hbs b/app/assets/javascripts/discourse/templates/components/stream-item.hbs new file mode 100644 index 0000000000..c84082519b --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/stream-item.hbs @@ -0,0 +1,31 @@ +
    +
    {{avatar item imageSize="large" extraClasses="actor" ignoreTitle="true"}}
    + {{format-date item.created_at}} + {{topic-status topic=item disableActions=true}} + + {{{item.title}}} + +
    {{category-link item.category}}
    +
    + +{{#if actionDescription}} +

    {{actionDescription}}

    +{{/if}} + +

    {{{item.excerpt}}}

    + +{{#each item.children as |child|}} +
    + + {{#each child.items as |grandChild|}} + {{#if grandChild.removableBookmark}} + + {{else}} +
    {{avatar grandChild imageSize="tiny" extraClasses="actor" ignoreTitle="true"}}
    + {{#if grandChild.edit_reason}} — {{grandChild.edit_reason}}{{/if}} + {{/if}} + {{/each}} +
    +{{/each}} diff --git a/app/assets/javascripts/discourse/templates/discovery.hbs b/app/assets/javascripts/discourse/templates/discovery.hbs index 195db18b2e..1e4548bbe2 100644 --- a/app/assets/javascripts/discourse/templates/discovery.hbs +++ b/app/assets/javascripts/discourse/templates/discovery.hbs @@ -1,10 +1,8 @@ -
    - {{custom-html "top"}} - {{global-notice}} +
    {{discourse-banner user=currentUser banner=site.banner}}
    -
    +
    {{outlet "navigation-bar"}}
    @@ -12,17 +10,17 @@ {{conditional-loading-spinner condition=loading}} -
    +
    -
    +
    {{outlet "header-list-container"}}
    -
    +
    {{plugin-outlet "discovery-list-container-top"}} {{outlet "list-container"}}
    diff --git a/app/assets/javascripts/discourse/templates/full-page-search.hbs b/app/assets/javascripts/discourse/templates/full-page-search.hbs index 848215c73e..b04d1e5937 100644 --- a/app/assets/javascripts/discourse/templates/full-page-search.hbs +++ b/app/assets/javascripts/discourse/templates/full-page-search.hbs @@ -1,45 +1,49 @@ {{#conditional-loading-spinner condition=loading}} -{{#unless model.posts}} -

    {{i18n "search.no_results"}} {{i18n "search.search_help"}} -

    -{{/unless}} + {{#unless model.posts}} +

    + {{i18n "search.no_results"}} {{i18n "search.search_help"}} +

    + {{/unless}} -{{#each model.posts as |result|}} -
    -
    - {{avatar result imageSize="tiny"}} - - {{topic-status topic=result.topic disableActions=true}}{{unbound result.topic.title}} - {{category-link result.topic.category}} -
    -
    - - {{format-age result.created_at}} - {{#if result.blurb}} - - - {{/if}} - - {{#if result.blurb}} - {{#highlight-text highlight=controller.q}} - {{{unbound result.blurb}}} - {{/highlight-text}} - {{/if}} -
    -
    -{{/each}} + {{#each model.posts as |result|}} +
    +
    + {{avatar result imageSize="tiny"}} + + {{topic-status topic=result.topic disableActions=true}}{{unbound result.topic.title}} + +
    + {{category-link result.topic.category}} + {{plugin-outlet "full-page-search-category"}} +
    +
    +
    + + {{format-age result.created_at}} + {{#if result.blurb}} + - + {{/if}} + + {{#if result.blurb}} + {{#highlight-text highlight=controller.q}} + {{{unbound result.blurb}}} + {{/highlight-text}} + {{/if}} +
    +
    + {{/each}} -{{#if model.posts}} - -{{/if}} + {{#if model.posts}} + + {{/if}} {{/conditional-loading-spinner}} - diff --git a/app/assets/javascripts/discourse/templates/list/topic-list-item.raw.hbs b/app/assets/javascripts/discourse/templates/list/topic-list-item.raw.hbs index 92f0bd68e9..26c958c4ec 100644 --- a/app/assets/javascripts/discourse/templates/list/topic-list-item.raw.hbs +++ b/app/assets/javascripts/discourse/templates/list/topic-list-item.raw.hbs @@ -10,6 +10,7 @@ {{#if controller.showTopicPostBadges}} {{raw "topic-post-badges" unread=topic.unread newPosts=topic.displayNewPosts unseen=topic.unseen url=topic.lastUnreadUrl}} {{/if}} + {{plugin-outlet "topic-list-tags"}} {{#if expandPinned}} {{raw "list/topic-excerpt" topic=topic}} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/modal/merge-topic.hbs b/app/assets/javascripts/discourse/templates/modal/merge-topic.hbs index a05ac14f22..e4bf364fc2 100644 --- a/app/assets/javascripts/discourse/templates/modal/merge-topic.hbs +++ b/app/assets/javascripts/discourse/templates/modal/merge-topic.hbs @@ -1,10 +1,4 @@ diff --git a/app/assets/javascripts/discourse/templates/modal/split_topic.hbs b/app/assets/javascripts/discourse/templates/modal/split-topic.hbs similarity index 59% rename from app/assets/javascripts/discourse/templates/modal/split_topic.hbs rename to app/assets/javascripts/discourse/templates/modal/split-topic.hbs index ff72aab89c..fe80f88174 100644 --- a/app/assets/javascripts/discourse/templates/modal/split_topic.hbs +++ b/app/assets/javascripts/discourse/templates/modal/split-topic.hbs @@ -1,10 +1,4 @@ diff --git a/app/assets/javascripts/discourse/templates/queued-posts.hbs b/app/assets/javascripts/discourse/templates/queued-posts.hbs index 400937caf5..5fc07e042b 100644 --- a/app/assets/javascripts/discourse/templates/queued-posts.hbs +++ b/app/assets/javascripts/discourse/templates/queued-posts.hbs @@ -6,7 +6,6 @@ {{#user-link user=ctrl.post.user}} {{avatar ctrl.post.user imageSize="large"}} {{/user-link}} -
    @@ -14,6 +13,9 @@ {{#user-link user=ctrl.post.user}} {{ctrl.post.user.username}} {{/user-link}} + {{#if ctrl.post.user.blocked}} + + {{/if}}