diff --git a/.tx/config b/.tx/config index 9dfdf375f1..718e97b54c 100644 --- a/.tx/config +++ b/.tx/config @@ -1,6 +1,6 @@ [main] host = https://www.transifex.com -lang_map = es_ES: es, fr_FR: fr, ko_KR: ko, pt_PT: pt, sk_SK: sk +lang_map = es_ES: es, fr_FR: fr, ko_KR: ko, pt_PT: pt, sk_SK: sk, vi_VN: vi [discourse-org.clientenyml] file_filter = config/locales/client..yml diff --git a/Gemfile b/Gemfile index fae8666355..3f5a46fb76 100644 --- a/Gemfile +++ b/Gemfile @@ -64,7 +64,7 @@ gem 'aws-sdk', require: false gem 'excon', require: false gem 'unf', require: false -gem 'email_reply_trimmer', '0.0.5' +gem 'email_reply_trimmer', '0.0.8' # note: for image_optim to correctly work you need to follow # https://github.com/toy/image_optim diff --git a/Gemfile.lock b/Gemfile.lock index 89e65bafb8..2bc7adb15d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -76,7 +76,7 @@ GEM docile (1.1.5) domain_name (0.5.25) unf (>= 0.0.5, < 1.0.0) - email_reply_trimmer (0.0.5) + email_reply_trimmer (0.0.8) ember-data-source (1.0.0.beta.16.1) ember-source (~> 1.8) ember-handlebars-template (0.1.5) @@ -149,7 +149,7 @@ GEM thor (~> 0.15) libv8 (3.16.14.13) listen (0.7.3) - logster (1.0.1) + logster (1.1.1) loofah (2.0.3) nokogiri (>= 1.5.9) lru_redux (1.1.0) @@ -211,7 +211,7 @@ GEM omniauth-twitter (1.2.1) json (~> 1.3) omniauth-oauth (~> 1.1) - onebox (1.5.34) + onebox (1.5.35) moneta (~> 0.8) multi_json (~> 1.11) mustache @@ -411,7 +411,7 @@ DEPENDENCIES byebug certified discourse-qunit-rails - email_reply_trimmer (= 0.0.5) + email_reply_trimmer (= 0.0.8) ember-rails ember-source (= 1.12.2) excon diff --git a/app/assets/javascripts/admin/controllers/modals/admin-incoming-email.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-incoming-email.js.es6 new file mode 100644 index 0000000000..bc9cd2edd8 --- /dev/null +++ b/app/assets/javascripts/admin/controllers/modals/admin-incoming-email.js.es6 @@ -0,0 +1,17 @@ +import ModalFunctionality from 'discourse/mixins/modal-functionality'; +import IncomingEmail from 'admin/models/incoming-email'; +import computed from 'ember-addons/ember-computed-decorators'; +import { longDate } from 'discourse/lib/formatter'; + +export default Ember.Controller.extend(ModalFunctionality, { + + @computed("model.date") + date(d) { + return longDate(d); + }, + + load(id) { + return IncomingEmail.find(id).then(result => this.set("model", result)); + } + +}); diff --git a/app/assets/javascripts/admin/models/admin-user.js.es6 b/app/assets/javascripts/admin/models/admin-user.js.es6 index 267d951e56..14b7e3edc3 100644 --- a/app/assets/javascripts/admin/models/admin-user.js.es6 +++ b/app/assets/javascripts/admin/models/admin-user.js.es6 @@ -402,7 +402,7 @@ const AdminUser = Discourse.User.extend({ } } }).catch(function() { - AdminUser.find( user.get('username') ).then(function(u){ user.setProperties(u); }); + AdminUser.find(user.get('id')).then(u => user.setProperties(u)); bootbox.alert(I18n.t("admin.user.delete_failed")); }); }; @@ -475,7 +475,7 @@ const AdminUser = Discourse.User.extend({ if (user.get('loadedDetails')) { return Ember.RSVP.resolve(user); } - return AdminUser.find(user.get('username_lower')).then(function (result) { + return AdminUser.find(user.get('id')).then(result => { user.setProperties(result); user.set('loadedDetails', true); }); @@ -533,8 +533,8 @@ AdminUser.reopenClass({ }); }, - find(username) { - return Discourse.ajax("/admin/users/" + username + ".json").then(function (result) { + find(user_id) { + return Discourse.ajax("/admin/users/" + user_id + ".json").then(result => { result.loadedDetails = true; return AdminUser.create(result); }); diff --git a/app/assets/javascripts/admin/models/incoming-email.js.es6 b/app/assets/javascripts/admin/models/incoming-email.js.es6 index d0e5b43c59..82534c5c43 100644 --- a/app/assets/javascripts/admin/models/incoming-email.js.es6 +++ b/app/assets/javascripts/admin/models/incoming-email.js.es6 @@ -14,6 +14,10 @@ IncomingEmail.reopenClass({ return this._super(attrs); }, + find(id) { + return Discourse.ajax(`/admin/email/incoming/${id}.json`); + }, + findAll(filter, offset) { filter = filter || {}; offset = offset || 0; diff --git a/app/assets/javascripts/admin/routes/admin-email-rejected.js.es6 b/app/assets/javascripts/admin/routes/admin-email-rejected.js.es6 index 4d96868d29..a7819b31a6 100644 --- a/app/assets/javascripts/admin/routes/admin-email-rejected.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-email-rejected.js.es6 @@ -5,9 +5,9 @@ export default AdminEmailIncomings.extend({ status: "rejected", actions: { - showRawEmail(incomingEmailId) { - showModal('raw-email'); - this.controllerFor('raw_email').loadIncomingRawEmail(incomingEmailId); + showIncomingEmail(id) { + showModal('modals/admin-incoming-email'); + this.controllerFor("modals/admin-incoming-email").load(id); } } diff --git a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 index 3626ea48d3..64a8e393a0 100644 --- a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 @@ -62,7 +62,7 @@ export default { }); this.resource('adminUsers', { path: '/users' }, function() { - this.resource('adminUser', { path: '/:username' }, function() { + this.resource('adminUser', { path: '/:user_id/:username' }, function() { this.route('badges'); this.route('tl3Requirements', { path: '/tl3_requirements' }); }); diff --git a/app/assets/javascripts/admin/routes/admin-user.js.es6 b/app/assets/javascripts/admin/routes/admin-user.js.es6 index af3171a857..35a105a105 100644 --- a/app/assets/javascripts/admin/routes/admin-user.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-user.js.es6 @@ -2,11 +2,11 @@ import AdminUser from 'admin/models/admin-user'; export default Discourse.Route.extend({ serialize(model) { - return { username: model.get('username').toLowerCase() }; + return { user_id: model.get('id'), username: model.get('username').toLowerCase() }; }, model(params) { - return AdminUser.find(Em.get(params, 'username').toLowerCase()); + return AdminUser.find(Em.get(params, 'user_id')); }, renderTemplate() { diff --git a/app/assets/javascripts/admin/templates/email-rejected.hbs b/app/assets/javascripts/admin/templates/email-rejected.hbs index ea5c22112d..3c5f3cba8e 100644 --- a/app/assets/javascripts/admin/templates/email-rejected.hbs +++ b/app/assets/javascripts/admin/templates/email-rejected.hbs @@ -42,7 +42,7 @@ {{email.subject}} - {{email.error}} + {{email.error}} {{else}} diff --git a/app/assets/javascripts/admin/templates/email-sent.hbs b/app/assets/javascripts/admin/templates/email-sent.hbs index 4e4f5dbf77..2f8785c6f5 100644 --- a/app/assets/javascripts/admin/templates/email-sent.hbs +++ b/app/assets/javascripts/admin/templates/email-sent.hbs @@ -30,7 +30,13 @@ {{l.to_address}} {{l.email_type}} - {{l.reply_key}} + + {{#if l.post_url}} + {{l.reply_key}} + {{else}} + {{l.reply_key}} + {{/if}} + {{else}} {{i18n 'admin.email.logs.none'}} diff --git a/app/assets/javascripts/admin/templates/email-skipped.hbs b/app/assets/javascripts/admin/templates/email-skipped.hbs index d983b09378..6ef4872566 100644 --- a/app/assets/javascripts/admin/templates/email-skipped.hbs +++ b/app/assets/javascripts/admin/templates/email-skipped.hbs @@ -30,7 +30,13 @@ {{l.to_address}} {{l.email_type}} - {{l.skipped_reason}} + + {{#if l.post_url}} + {{l.skipped_reason}} + {{else}} + {{l.skipped_reason}} + {{/if}} + {{else}} {{i18n 'admin.email.logs.none'}} diff --git a/app/assets/javascripts/admin/templates/groups_type.hbs b/app/assets/javascripts/admin/templates/groups_type.hbs index 980ca08c8f..3c4fc3e506 100644 --- a/app/assets/javascripts/admin/templates/groups_type.hbs +++ b/app/assets/javascripts/admin/templates/groups_type.hbs @@ -4,7 +4,11 @@ diff --git a/app/assets/javascripts/admin/templates/modal/admin_incoming_email.hbs b/app/assets/javascripts/admin/templates/modal/admin_incoming_email.hbs new file mode 100644 index 0000000000..d4907a7e32 --- /dev/null +++ b/app/assets/javascripts/admin/templates/modal/admin_incoming_email.hbs @@ -0,0 +1,98 @@ +
+ +
+

{{model.error}}

+ {{#if model.error_description}} +

{{model.error_description}}

+ {{/if}} +
+
+ +
+ +{{#if model.return_path}} +
+ +
+ {{model.return_path}} +
+
+{{/if}} + +
+ +
+ {{model.message_id}} +
+
+ +{{#if model.references}} +
+ +
+
    + {{#each reference in model.references}} +
  • {{reference}}
  • + {{/each}} +
+
+
+{{/if}} + +{{#if model.in_reply_to}} +
+ +
+ {{model.in_reply_to}} +
+
+{{/if}} + +
+ +
+ {{date}} +
+
+ +
+ +
+ {{model.from}} +
+
+ +
+ +
+
    + {{#each to in model.to}} +
  • {{to}}
  • + {{/each}} +
+
+
+ +{{#if model.cc}} +
+ +
+ {{model.cc}} +
+
+{{/if}} + +
+ +
+ {{model.subject}} +
+
+ +
+ +
+ {{textarea value=model.body}} +
+
+ diff --git a/app/assets/javascripts/admin/views/modals/admin-incoming-email.js.es6 b/app/assets/javascripts/admin/views/modals/admin-incoming-email.js.es6 new file mode 100644 index 0000000000..576feff433 --- /dev/null +++ b/app/assets/javascripts/admin/views/modals/admin-incoming-email.js.es6 @@ -0,0 +1,7 @@ +import ModalBodyView from "discourse/views/modal-body"; + +export default ModalBodyView.extend({ + templateName: 'admin/templates/modal/admin_incoming_email', + classNames: ['incoming-emails'], + title: I18n.t('admin.email.incoming_emails.modal.title') +}); diff --git a/app/assets/javascripts/discourse/adapters/notification.js.es6 b/app/assets/javascripts/discourse/adapters/notification.js.es6 index f8298a4307..ddee883f62 100644 --- a/app/assets/javascripts/discourse/adapters/notification.js.es6 +++ b/app/assets/javascripts/discourse/adapters/notification.js.es6 @@ -1,4 +1,3 @@ import RestAdapter from 'discourse/adapters/rest'; -import StaleLocalStorage from 'discourse/mixins/stale-local-storage'; -export default RestAdapter.extend(StaleLocalStorage); +export default RestAdapter.extend({cache: true}); diff --git a/app/assets/javascripts/discourse/adapters/rest.js.es6 b/app/assets/javascripts/discourse/adapters/rest.js.es6 index 1404e76d50..5faaf6d0bf 100644 --- a/app/assets/javascripts/discourse/adapters/rest.js.es6 +++ b/app/assets/javascripts/discourse/adapters/rest.js.es6 @@ -1,6 +1,8 @@ -import StaleResult from 'discourse/lib/stale-result'; +import { hashString } from 'discourse/lib/hash'; + const ADMIN_MODELS = ['plugin', 'site-customization', 'embeddable-host']; + export function Result(payload, responseJson) { this.payload = payload; this.responseJson = responseJson; @@ -19,6 +21,15 @@ function rethrow(error) { export default Ember.Object.extend({ + + storageKey(type, findArgs, options) { + if (options && options.cacheKey) { + return options.cacheKey; + } + const hashedArgs = Math.abs(hashString(JSON.stringify(findArgs))); + return `${type}_${hashedArgs}`; + }, + basePath(store, type) { if (ADMIN_MODELS.indexOf(type.replace('_', '-')) !== -1) { return "/admin/"; } return "/"; @@ -56,8 +67,15 @@ export default Ember.Object.extend({ return ajax(this.pathFor(store, type, findArgs)).catch(rethrow); }, - findStale() { - return new StaleResult(); + findStale(store, type, findArgs, options) { + if (this.cached) { + return this.cached[this.storageKey(type, findArgs, options)]; + } + }, + + cacheFind(store, type, findArgs, opts, hydrated) { + this.cached = this.cached || {}; + this.cached[this.storageKey(type,findArgs,opts)] = hydrated; }, update(store, type, id, attrs) { diff --git a/app/assets/javascripts/discourse/components/composer-editor.js.es6 b/app/assets/javascripts/discourse/components/composer-editor.js.es6 index 9dbd7f25d1..ddc86134cf 100644 --- a/app/assets/javascripts/discourse/components/composer-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-editor.js.es6 @@ -5,7 +5,7 @@ import { linkSeenCategoryHashtags, fetchUnseenCategoryHashtags } from 'discourse export default Ember.Component.extend({ classNames: ['wmd-controls'], - classNameBindings: [':wmd-controls', 'showPreview', 'showPreview::hide-preview'], + classNameBindings: ['showToolbar:toolbar-visible', ':wmd-controls', 'showPreview', 'showPreview::hide-preview'], uploadProgress: 0, showPreview: true, @@ -343,12 +343,34 @@ export default Ember.Component.extend({ }, showOptions() { + // long term we want some smart positioning algorithm in popup-menu + // the problem is that positioning in a fixed panel is a nightmare + // cause offsetParent can end up returning a fixed element and then + // using offset() is not going to work, so you end up needing special logic + // especially since we allow for negative .top, provided there is room on screen const myPos = this.$().position(); const buttonPos = this.$('.options').position(); + const popupHeight = $('#reply-control .popup-menu').height(); + const popupWidth = $('#reply-control .popup-menu').width(); + + var top = myPos.top + buttonPos.top - 15; + var left = myPos.left + buttonPos.left - (popupWidth/2); + + const composerPos = $('#reply-control').position(); + + if (composerPos.top + top - popupHeight < 0) { + top = top + popupHeight + this.$('.options').height() + 50; + } + + var replyWidth = $('#reply-control').width(); + if (left + popupWidth > replyWidth) { + left = replyWidth - popupWidth - 40; + } + this.sendAction('showOptions', { position: "absolute", - left: myPos.left + buttonPos.left, - top: myPos.top + buttonPos.top }); + left: left, + top: top }); }, showUploadModal(toolbarEvent) { diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6 index 5ebd6ed92b..e81f14c212 100644 --- a/app/assets/javascripts/discourse/components/d-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/d-editor.js.es6 @@ -31,7 +31,7 @@ function Toolbar() { this.groups = [ {group: 'fontStyles', buttons: []}, {group: 'insertions', buttons: []}, - {group: 'extras', buttons: [], lastGroup: true} + {group: 'extras', buttons: []} ]; this.addButton({ @@ -105,6 +105,20 @@ function Toolbar() { title: 'composer.hr_title', perform: e => e.addText("\n\n----------\n") }); + + if (Discourse.Mobile.mobileView) { + this.groups.push({group: 'mobileExtras', buttons: []}); + + this.addButton({ + id: 'preview', + group: 'mobileExtras', + icon: 'television', + title: 'composer.hr_preview', + perform: e => e.preview() + }); + } + + this.groups[this.groups.length-1].lastGroup = true; }; Toolbar.prototype.addButton = function(button) { @@ -166,6 +180,7 @@ export function onToolbarCreate(func) { export default Ember.Component.extend({ classNames: ['d-editor'], ready: false, + forcePreview: false, insertLinkHidden: true, link: '', lastSel: null, @@ -446,6 +461,10 @@ export default Ember.Component.extend({ Ember.run.scheduleOnce("afterRender", () => this.$("textarea.d-editor-input").focus()); }, + _togglePreview() { + this.toggleProperty('forcePreview'); + }, + actions: { toolbarButton(button) { const selected = this._getSelected(); @@ -453,7 +472,8 @@ export default Ember.Component.extend({ selected, applySurround: (head, tail, exampleKey) => this._applySurround(selected, head, tail, exampleKey), applyList: (head, exampleKey) => this._applyList(selected, head, exampleKey), - addText: text => this._addText(selected, text) + addText: text => this._addText(selected, text), + preview: () => this._togglePreview() }; if (button.sendAction) { @@ -463,6 +483,10 @@ export default Ember.Component.extend({ } }, + hidePreview() { + this.set('forcePreview', false); + }, + showLinkModal() { this._lastSel = this._getSelected(); this.set('insertLinkHidden', false); diff --git a/app/assets/javascripts/discourse/components/date-picker.js.es6 b/app/assets/javascripts/discourse/components/date-picker.js.es6 index d1c8e4fcef..621b0d1fd8 100644 --- a/app/assets/javascripts/discourse/components/date-picker.js.es6 +++ b/app/assets/javascripts/discourse/components/date-picker.js.es6 @@ -17,6 +17,14 @@ export default Em.Component.extend({ format: "YYYY-MM-DD", defaultDate: moment().add(1, "day").toDate(), minDate: new Date(), + firstDay: moment.localeData().firstDayOfWeek(), + i18n: { + previousMonth: I18n.t('dates.previous_month'), + nextMonth: I18n.t('dates.next_month'), + months: moment.months(), + weekdays: moment.weekdays(), + weekdaysShort: moment.weekdaysShort() + }, onSelect: date => this.set("value", moment(date).format("YYYY-MM-DD")) }; diff --git a/app/assets/javascripts/discourse/components/mobile-nav.js.es6 b/app/assets/javascripts/discourse/components/mobile-nav.js.es6 new file mode 100644 index 0000000000..cee8a2111a --- /dev/null +++ b/app/assets/javascripts/discourse/components/mobile-nav.js.es6 @@ -0,0 +1,54 @@ +import { on, observes } from 'ember-addons/ember-computed-decorators'; + +export default Ember.Component.extend({ + + @on('init') + _init() { + if (!this.get('site.mobileView')) { + var classes = this.get('desktopClass'); + if (classes) { + classes = classes.split(' '); + this.set('classNames', classes); + } + } + }, + + tagName: 'ul', + + classNames: ['mobile-nav'], + + @observes('currentPath') + currentPathChanged() { + this.set('expanded', false); + Em.run.next(() => this._updateSelectedHtml()); + }, + + _updateSelectedHtml(){ + const active = this.$('.active'); + if (active && active.html) { + this.set('selectedHtml', active.html()); + } + }, + + didInsertElement() { + this._updateSelectedHtml(); + }, + + @on('didInsertElement') + _bindClick() { + this.$().on("click.mobile-nav", 'ul li', () => { + this.set('expanded', false); + }); + }, + + @on('willDestroyElement') + _unbindClick() { + this.$().off("click.mobile-nav", 'ul li'); + }, + + actions: { + toggleExpanded(){ + this.toggleProperty('expanded'); + } + } +}); diff --git a/app/assets/javascripts/discourse/components/notification-item.js.es6 b/app/assets/javascripts/discourse/components/notification-item.js.es6 index 6818ba5a3d..844bf83edf 100644 --- a/app/assets/javascripts/discourse/components/notification-item.js.es6 +++ b/app/assets/javascripts/discourse/components/notification-item.js.es6 @@ -61,12 +61,18 @@ export default Ember.Component.extend({ _markRead: function(){ this.$('a').click(() => { this.set('notification.read', true); + Discourse.setTransientHeader("Discourse-Clear-Notifications", this.get('notification.id')); + if (document && document.cookie) { + document.cookie = `cn=${this.get('notification.id')}; expires=Fri, 31 Dec 9999 23:59:59 GMT`; + } return true; }); }.on('didInsertElement'), render(buffer) { const notification = this.get('notification'); + // since we are reusing views now sometimes this can be unset + if (!notification) { return; } const description = this.get('description'); const username = notification.get('data.display_username'); var text; diff --git a/app/assets/javascripts/discourse/components/user-menu.js.es6 b/app/assets/javascripts/discourse/components/user-menu.js.es6 index 506fc98d78..d864f23ba4 100644 --- a/app/assets/javascripts/discourse/components/user-menu.js.es6 +++ b/app/assets/javascripts/discourse/components/user-menu.js.es6 @@ -54,7 +54,7 @@ export default Ember.Component.extend({ // TODO: It's a bit odd to use the store in a component, but this one really // wants to reach out and grab notifications const store = this.container.lookup('store:main'); - const stale = store.findStale('notification', {recent: true, limit }, {storageKey: 'recent-notifications'}); + const stale = store.findStale('notification', {recent: true, limit }, {cacheKey: 'recent-notifications'}); if (stale.hasResults) { const results = stale.results; diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index 1c7384c254..88e6c532be 100644 --- a/app/assets/javascripts/discourse/controllers/composer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/composer.js.es6 @@ -54,12 +54,10 @@ export default Ember.Controller.extend({ similarTopicsMessage: null, lastSimilaritySearch: null, optionsVisible: false, - lastValidatedAt: null, - isUploading: false, - topic: null, + showToolbar: false, _initializeSimilar: function() { this.set('similarTopics', []); @@ -90,6 +88,10 @@ export default Ember.Controller.extend({ this.toggleProperty('model.whisper'); }, + toggleToolbar() { + this.toggleProperty('showToolbar'); + }, + showOptions(loc) { this.appEvents.trigger('popup-menu:open', loc); this.set('optionsVisible', true); diff --git a/app/assets/javascripts/discourse/controllers/flag.js.es6 b/app/assets/javascripts/discourse/controllers/flag.js.es6 index 07db5951b1..ea3bc834e2 100644 --- a/app/assets/javascripts/discourse/controllers/flag.js.es6 +++ b/app/assets/javascripts/discourse/controllers/flag.js.es6 @@ -141,8 +141,7 @@ export default Ember.Controller.extend(ModalFunctionality, { fetchUserDetails() { if (Discourse.User.currentProp('staff') && this.get('model.username')) { const AdminUser = require('admin/models/admin-user').default; - AdminUser.find(this.get('model.username').toLowerCase()) - .then(user => this.set('userDetails', user)); + AdminUser.find(this.get('model.id')).then(user => this.set('userDetails', user)); } } diff --git a/app/assets/javascripts/discourse/controllers/header.js.es6 b/app/assets/javascripts/discourse/controllers/header.js.es6 index 07addf2c1c..57401135df 100644 --- a/app/assets/javascripts/discourse/controllers/header.js.es6 +++ b/app/assets/javascripts/discourse/controllers/header.js.es6 @@ -21,13 +21,7 @@ const HeaderController = Ember.Controller.extend({ actions: { toggleSearch() { - // there may be a cleaner way, but this is so trivial code wise - const $fullpageSearch = $('input.full-page-search'); - if ($fullpageSearch.length === 1) { - $fullpageSearch.focus().select(); - } else { - this.toggleProperty('searchVisible'); - } + this.toggleProperty('searchVisible'); }, showUserMenu() { if (!this.get('userMenuVisible')) { diff --git a/app/assets/javascripts/discourse/controllers/login.js.es6 b/app/assets/javascripts/discourse/controllers/login.js.es6 index b2eed5ceed..cd81f119c0 100644 --- a/app/assets/javascripts/discourse/controllers/login.js.es6 +++ b/app/assets/javascripts/discourse/controllers/login.js.es6 @@ -69,6 +69,9 @@ export default Ember.Controller.extend(ModalFunctionality, { sentTo: result.sent_to_email, currentEmail: result.current_email }); + } else if (result.reason === 'suspended' ) { + self.send("closeModal"); + bootbox.alert(result.error); } else { self.flash(result.error, 'error'); } diff --git a/app/assets/javascripts/discourse/controllers/preferences.js.es6 b/app/assets/javascripts/discourse/controllers/preferences.js.es6 index 1efdaff99b..5231e4fa43 100644 --- a/app/assets/javascripts/discourse/controllers/preferences.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences.js.es6 @@ -62,6 +62,12 @@ export default Ember.Controller.extend(CanCheckEmails, { return this.siteSettings.available_locales.split('|').map(s => ({ name: s, value: s })); }, + previousRepliesOptions: [ + {name: I18n.t('user.email_previous_replies.always'), value: 0}, + {name: I18n.t('user.email_previous_replies.unless_emailed'), value: 1}, + {name: I18n.t('user.email_previous_replies.never'), value: 2} + ], + digestFrequencies: [{ name: I18n.t('user.email_digests.daily'), value: 1 }, { name: I18n.t('user.email_digests.every_three_days'), value: 3 }, { name: I18n.t('user.email_digests.weekly'), value: 7 }, diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index e5f5d19d33..2fe9e6ee7b 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -632,6 +632,14 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { } return; } + case "move_to_inbox": { + topic.set("message_archived",false); + return; + } + case "archived": { + topic.set("message_archived",true); + return; + } default: { Em.Logger.warn("unknown topic bus message type", data); } diff --git a/app/assets/javascripts/discourse/controllers/user-activity.js.es6 b/app/assets/javascripts/discourse/controllers/user-activity.js.es6 index 59c6b6dd81..55ab7846ca 100644 --- a/app/assets/javascripts/discourse/controllers/user-activity.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-activity.js.es6 @@ -3,7 +3,7 @@ import { exportUserArchive } from 'discourse/lib/export-csv'; export default Ember.Controller.extend({ userActionType: null, needs: ["application", "user"], - + currentPath: Em.computed.alias('controllers.application.currentPath'), viewingSelf: Em.computed.alias("controllers.user.viewingSelf"), showBookmarks: Em.computed.alias("controllers.user.showBookmarks"), diff --git a/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 b/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 index 85865c16e5..c06137a9e1 100644 --- a/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 @@ -7,6 +7,8 @@ export default Ember.ArrayController.extend({ showDismissButton: Ember.computed.gt('user.total_unread_notifications', 0), + currentPath: Em.computed.alias('controllers.application.currentPath'), + actions: { resetNew: function() { Discourse.ajax('/notifications/mark-read', { method: 'PUT' }).then(() => { diff --git a/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 b/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 index eb8d13c721..73c8ff293a 100644 --- a/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 @@ -3,11 +3,11 @@ import Topic from 'discourse/models/topic'; export default Ember.Controller.extend({ - needs: ["user-topics-list", "user"], + needs: ["application", "user-topics-list", "user"], pmView: false, - viewingSelf: Em.computed.alias("controllers.user.viewingSelf"), + viewingSelf: Em.computed.alias('controllers.user.viewingSelf'), isGroup: Em.computed.equal('pmView', 'groups'), - + currentPath: Em.computed.alias('controllers.application.currentPath'), selected: Em.computed.alias('controllers.user-topics-list.selected'), bulkSelectEnabled: Em.computed.alias('controllers.user-topics-list.bulkSelectEnabled'), diff --git a/app/assets/javascripts/discourse/controllers/user.js.es6 b/app/assets/javascripts/discourse/controllers/user.js.es6 index 3971558f88..24b26957da 100644 --- a/app/assets/javascripts/discourse/controllers/user.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user.js.es6 @@ -6,7 +6,8 @@ import User from 'discourse/models/user'; export default Ember.Controller.extend(CanCheckEmails, { indexStream: false, userActionType: null, - needs: ['user-notifications', 'user-topics-list'], + needs: ['application','user-notifications', 'user-topics-list'], + currentPath: Em.computed.alias('controllers.application.currentPath'), @computed("content.username") viewingSelf(username) { @@ -84,8 +85,7 @@ export default Ember.Controller.extend(CanCheckEmails, { adminDelete() { // I really want this deferred, don't want to bring in all this code till used const AdminUser = require('admin/models/admin-user').default; - AdminUser.find(this.get('model.username').toLowerCase()) - .then(user => user.destroy({deletePosts: true})); + AdminUser.find(this.get('model.id')).then(user => user.destroy({deletePosts: 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 79157a123a..fd820d8202 100644 --- a/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 +++ b/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 @@ -9,10 +9,11 @@ export default { site = container.lookup('site:main'), siteSettings = container.lookup('site-settings:main'), bus = container.lookup('message-bus:main'), - keyValueStore = container.lookup('key-value-store:main'); + keyValueStore = container.lookup('key-value-store:main'), + store = container.lookup('store:main'); - // clear old cached notifications - // they could be a week old for all we know + // clear old cached notifications, we used to store in local storage + // TODO 2017 delete this line keyValueStore.remove('recent-notifications'); if (user) { @@ -40,29 +41,43 @@ export default { user.set('lastNotificationChange', new Date()); } - var stale = keyValueStore.getObject('recent-notifications'); + const stale = store.findStale('notification', {}, {cacheKey: 'recent-notifications'}); const lastNotification = data.last_notification && data.last_notification.notification; - if (stale && stale.notifications && lastNotification) { + if (stale && stale.hasResults && lastNotification) { - const oldNotifications = stale.notifications; + const oldNotifications = stale.results.get('content'); const staleIndex = _.findIndex(oldNotifications, {id: lastNotification.id}); - if (staleIndex > -1) { - oldNotifications.splice(staleIndex, 1); + if (staleIndex === -1) { + // this gets a bit tricky, uread pms are bumped to front + var insertPosition = 0; + if (lastNotification.notification_type !== 6) { + insertPosition = _.findIndex(oldNotifications, function(n){ + return n.notification_type !== 6 || n.read; + }); + insertPosition = insertPosition === -1 ? oldNotifications.length - 1 : insertPosition; + } + + oldNotifications.insertAt(insertPosition, Em.Object.create(lastNotification)); } - // this gets a bit tricky, uread pms are bumped to front - var insertPosition = 0; - if (lastNotification.notification_type !== 6) { - insertPosition = _.findIndex(oldNotifications, function(n){ - return n.notification_type !== 6 || n.read; - }); - insertPosition = insertPosition === -1 ? oldNotifications.length - 1 : insertPosition; - } + for (var idx=0; idx < data.recent.length; idx++) { + var old; + while(old = oldNotifications[idx]) { + var info = data.recent[idx]; - oldNotifications.splice(insertPosition, 0, lastNotification); - keyValueStore.setItem('recent-notifications', JSON.stringify(stale)); + if (old.get('id') !== info[0]) { + oldNotifications.removeAt(idx); + } else { + if (old.get('read') !== info[1]) { + old.set('read', info[1]); + } + break; + } + } + if ( !old ) { break; } + } } }, user.notification_channel_position); diff --git a/app/assets/javascripts/discourse/lib/autocomplete.js.es6 b/app/assets/javascripts/discourse/lib/autocomplete.js.es6 index 5aa01e495b..5da0fb6b02 100644 --- a/app/assets/javascripts/discourse/lib/autocomplete.js.es6 +++ b/app/assets/javascripts/discourse/lib/autocomplete.js.es6 @@ -47,7 +47,8 @@ export default function(options) { $(this).off('keypress.autocomplete') .off('keydown.autocomplete') - .off('paste.autocomplete'); + .off('paste.autocomplete') + .off('click.autocomplete'); return; } @@ -241,6 +242,15 @@ export default function(options) { }); }; + const dataSource = (term, opts) => { + if (term.length !== 0 && term.trim().length === 0) { + closeAutocomplete(); + return null; + } else { + return opts.dataSource(term); + } + }; + var updateAutoComplete = function(r) { if (completeStart === null) return; @@ -276,6 +286,10 @@ export default function(options) { closeAutocomplete(); }); + $(this).on('click.autocomplete', function() { + closeAutocomplete(); + }); + $(this).on('paste.autocomplete', function() { _.delay(function(){ me.trigger("keydown"); @@ -299,13 +313,13 @@ export default function(options) { var prevChar = me.val().charAt(caretPosition - 1); if (checkTriggerRule() && (!prevChar || allowedLettersRegex.test(prevChar))) { completeStart = completeEnd = caretPosition; - updateAutoComplete(options.dataSource("")); + updateAutoComplete(dataSource("", options)); } } else if ((completeStart !== null) && (e.charCode !== 0)) { caretPosition = Discourse.Utilities.caretPosition(me[0]); term = me.val().substring(completeStart + (options.key ? 1 : 0), caretPosition); term += String.fromCharCode(e.charCode); - updateAutoComplete(options.dataSource(term)); + updateAutoComplete(dataSource(term, options)); } }); @@ -355,7 +369,7 @@ export default function(options) { completeStart = c; caretPosition = completeEnd = initial; term = me[0].value.substring(c + 1, initial); - updateAutoComplete(options.dataSource(term)); + updateAutoComplete(dataSource(term, options)); return true; } } @@ -375,16 +389,21 @@ export default function(options) { if (completeStart !== null) { caretPosition = Discourse.Utilities.caretPosition(me[0]); + // allow people to right arrow out of completion + if (e.which === keys.rightArrow && me[0].value[caretPosition] === ' ') { + closeAutocomplete(); + return true; + } + // If we've backspaced past the beginning, cancel unless no key if (caretPosition <= completeStart && options.key) { closeAutocomplete(); - return false; + return true; } // Keyboard codes! So 80's. switch (e.which) { case keys.enter: - case keys.rightArrow: case keys.tab: if (!autocompleteOptions) return true; if (selectedOption >= 0 && (userToComplete = autocompleteOptions[selectedOption])) { @@ -430,7 +449,11 @@ export default function(options) { term = me.val().substring(completeStart + (options.key ? 1 : 0), caretPosition); - updateAutoComplete(options.dataSource(term)); + if ((completeStart === caretPosition) && (term === options.key)) { + closeAutocomplete(); + } + + updateAutoComplete(dataSource(term, options)); return true; default: completeEnd = caretPosition; diff --git a/app/assets/javascripts/discourse/lib/safari-hacks.js.es6 b/app/assets/javascripts/discourse/lib/safari-hacks.js.es6 index c14aa64b63..93e584f239 100644 --- a/app/assets/javascripts/discourse/lib/safari-hacks.js.es6 +++ b/app/assets/javascripts/discourse/lib/safari-hacks.js.es6 @@ -89,11 +89,15 @@ function positioningWorkaround($fixedElement) { } const checkForInputs = _.debounce(function(){ - $fixedElement.find('button,a:not(.mobile-file-upload)').each(function(idx, elem){ + $fixedElement.find('button:not(.hide-preview),a:not(.mobile-file-upload):not(.toggle-toolbar)').each(function(idx, elem){ if ($(elem).parents('.autocomplete').length > 0) { return; } + if ($(elem).parents('.d-editor-button-bar').length > 0) { + return; + } + attachTouchStart(this, function(evt){ done = true; $(document.activeElement).blur(); diff --git a/app/assets/javascripts/discourse/lib/stale-result.js.es6 b/app/assets/javascripts/discourse/lib/stale-result.js.es6 deleted file mode 100644 index 1ae29e5832..0000000000 --- a/app/assets/javascripts/discourse/lib/stale-result.js.es6 +++ /dev/null @@ -1,12 +0,0 @@ -const StaleResult = function() { - this.hasResults = false; -}; - -StaleResult.prototype.setResults = function(results) { - if (results) { - this.results = results; - this.hasResults = true; - } -}; - -export default StaleResult; diff --git a/app/assets/javascripts/discourse/mixins/ajax.js b/app/assets/javascripts/discourse/mixins/ajax.js index fdd58cf2fe..36e42f5122 100644 --- a/app/assets/javascripts/discourse/mixins/ajax.js +++ b/app/assets/javascripts/discourse/mixins/ajax.js @@ -3,9 +3,14 @@ respect Discourse paths and the run loop. **/ var _trackView = false; +var _transientHeader = null; Discourse.Ajax = Em.Mixin.create({ + setTransientHeader: function(k, v) { + _transientHeader = {key: k, value: v}; + }, + viewTrackingRequired: function() { _trackView = true; }, @@ -43,6 +48,11 @@ Discourse.Ajax = Em.Mixin.create({ args.headers = args.headers || {}; + if (_transientHeader) { + args.headers[_transientHeader.key] = _transientHeader.value; + _transientHeader = null; + } + if (_trackView && (!args.type || args.type === "GET")) { _trackView = false; // DON'T CHANGE: rack is prepending "HTTP_" in the header's name diff --git a/app/assets/javascripts/discourse/mixins/stale-local-storage.js.es6 b/app/assets/javascripts/discourse/mixins/stale-local-storage.js.es6 deleted file mode 100644 index 19d8495162..0000000000 --- a/app/assets/javascripts/discourse/mixins/stale-local-storage.js.es6 +++ /dev/null @@ -1,34 +0,0 @@ -import StaleResult from 'discourse/lib/stale-result'; -import { hashString } from 'discourse/lib/hash'; - -// Mix this in to an adapter to provide stale caching in our key value store -export default { - storageKey(type, findArgs) { - const hashedArgs = Math.abs(hashString(JSON.stringify(findArgs))); - return `${type}_${hashedArgs}`; - }, - - findStale(store, type, findArgs, opts) { - const staleResult = new StaleResult(); - const key = (opts && opts.storageKey) || this.storageKey(type, findArgs); - try { - const stored = this.keyValueStore.getItem(key); - if (stored) { - const parsed = JSON.parse(stored); - staleResult.setResults(parsed); - } - } catch(e) { - // JSON parsing error - } - return staleResult; - }, - - find(store, type, findArgs, opts) { - const key = (opts && opts.storageKey) || this.storageKey(type, findArgs); - - return this._super(store, type, findArgs).then((results) => { - this.keyValueStore.setItem(key, JSON.stringify(results)); - return results; - }); - } -}; diff --git a/app/assets/javascripts/discourse/models/group.js.es6 b/app/assets/javascripts/discourse/models/group.js.es6 index d0048cc957..a34c6dccae 100644 --- a/app/assets/javascripts/discourse/models/group.js.es6 +++ b/app/assets/javascripts/discourse/models/group.js.es6 @@ -17,11 +17,11 @@ const Group = Discourse.Model.extend({ return this.get("automatic") ? "automatic" : "custom"; }.property("automatic"), - userCountDisplay: function(){ - var c = this.get('user_count'); + @computed('user_count') + userCountDisplay(userCount) { // don't display zero its ugly - if (c > 0) { return c; } - }.property('user_count'), + if (userCount > 0) { return userCount; } + }, findMembers() { if (Em.isEmpty(this.get('name'))) { return ; } diff --git a/app/assets/javascripts/discourse/models/store.js.es6 b/app/assets/javascripts/discourse/models/store.js.es6 index a7f4edcf9b..9c66da96dd 100644 --- a/app/assets/javascripts/discourse/models/store.js.es6 +++ b/app/assets/javascripts/discourse/models/store.js.es6 @@ -73,19 +73,43 @@ export default Ember.Object.extend({ // refresh it in the background. findStale(type, findArgs, opts) { const stale = this.adapterFor(type).findStale(this, type, findArgs, opts); - if (stale.hasResults) { - stale.results = this._hydrateFindResults(stale.results, type, findArgs); - } - stale.refresh = () => this.find(type, findArgs, opts); - return stale; + return { + hasResults: (stale !== undefined), + results: stale, + refresh: () => this.find(type, findArgs, opts) + }; }, find(type, findArgs, opts) { - return this.adapterFor(type).find(this, type, findArgs, opts).then(result => { - return this._hydrateFindResults(result, type, findArgs, opts); + var adapter = this.adapterFor(type); + return adapter.find(this, type, findArgs, opts).then(result => { + var hydrated = this._hydrateFindResults(result, type, findArgs, opts); + if (adapter.cache) { + const stale = adapter.findStale(this, type, findArgs, opts); + hydrated = this._updateStale(stale, hydrated); + adapter.cacheFind(this, type, findArgs, opts, hydrated); + } + return hydrated; }); }, + _updateStale(stale, hydrated) { + if (!stale) { + return hydrated; + } + + hydrated.set('content', hydrated.get('content').map((item) => { + var staleItem = stale.content.findBy('id', item.get('id')); + if (staleItem) { + staleItem.setProperties(item); + } else { + staleItem = item; + } + return staleItem; + })); + return hydrated; + }, + refreshResults(resultSet, type, url) { const self = this; return Discourse.ajax(url).then(result => { diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6 index 442899731e..9eeb9f87b3 100644 --- a/app/assets/javascripts/discourse/models/user.js.es6 +++ b/app/assets/javascripts/discourse/models/user.js.es6 @@ -90,7 +90,7 @@ const User = RestModel.extend({ }, - adminPath: url('username_lower', "/admin/users/%@"), + adminPath: url('id', 'username_lower', "/admin/users/%@1/%@2"), mutedTopicsPath: url('/latest?state=muted'), @@ -141,31 +141,36 @@ const User = RestModel.extend({ save() { const data = this.getProperties( - 'auto_track_topics_after_msecs', 'bio_raw', 'website', 'location', 'name', 'locale', - 'email_digests', - 'email_direct', - 'email_always', - 'email_private_messages', - 'dynamic_favicon', - 'digest_after_days', - 'new_topic_duration_minutes', - 'external_links_in_new_tab', - 'mailing_list_mode', - 'enable_quoting', - 'disable_jump_reply', 'custom_fields', 'user_fields', 'muted_usernames', 'profile_background', - 'card_background', - 'automatically_unpin_topics' + 'card_background' ); + [ 'email_always', + 'mailing_list_mode', + 'external_links_in_new_tab', + 'email_digests', + 'email_direct', + 'email_private_messages', + 'email_previous_replies', + 'dynamic_favicon', + 'enable_quoting', + 'disable_jump_reply', + 'automatically_unpin_topics', + 'digest_after_days', + 'new_topic_duration_minutes', + 'auto_track_topics_after_msecs' + ].forEach(s => { + data[s] = this.get(`user_option.${s}`); + }); + ['muted','watched','tracked'].forEach(s => { let cats = this.get(s + 'Categories').map(c => c.get('id')); // HACK: denote lack of categories @@ -174,7 +179,7 @@ const User = RestModel.extend({ }); if (!Discourse.SiteSettings.edit_history_visible_to_public) { - data['edit_history_public'] = this.get('edit_history_public'); + data['edit_history_public'] = this.get('user_option.edit_history_public'); } // TODO: We can remove this when migrated fully to rest model. @@ -184,7 +189,7 @@ const User = RestModel.extend({ type: 'PUT' }).then(result => { this.set('bio_excerpt', result.user.bio_excerpt); - const userProps = this.getProperties('enable_quoting', 'external_links_in_new_tab', 'dynamic_favicon'); + const userProps = Em.getProperties(this.get('user_option'),'enable_quoting', 'external_links_in_new_tab', 'dynamic_favicon'); Discourse.User.current().setProperties(userProps); }).finally(() => { this.set('isSaving', false); diff --git a/app/assets/javascripts/discourse/routes/application.js.es6 b/app/assets/javascripts/discourse/routes/application.js.es6 index 8c1637b7af..0ace34b5bb 100644 --- a/app/assets/javascripts/discourse/routes/application.js.es6 +++ b/app/assets/javascripts/discourse/routes/application.js.es6 @@ -53,9 +53,18 @@ const ApplicationRoute = Discourse.Route.extend(OpenComposer, { }, composePrivateMessage(user, post) { - const self = this; - this.transitionTo('userActivity', user).then(function () { - self.controllerFor('user-activity').send('composePrivateMessage', user, post); + + const recipient = user ? user.get('username') : '', + reply = post ? window.location.protocol + "//" + window.location.host + post.get("url") : null; + + // used only once, one less dependency + const Composer = require('discourse/models/composer').default; + return this.controllerFor('composer').open({ + action: Composer.PRIVATE_MESSAGE, + usernames: recipient, + archetypeId: 'private_message', + draftKey: 'new_private_message', + reply: reply }); }, 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 3cda47e9a7..dc895f4307 100644 --- a/app/assets/javascripts/discourse/routes/full-page-search.js.es6 +++ b/app/assets/javascripts/discourse/routes/full-page-search.js.es6 @@ -1,4 +1,5 @@ import { translateResults, getSearchKey, isValidSearchTerm } from "discourse/lib/search"; +import Composer from 'discourse/models/composer'; export default Discourse.Route.extend({ queryParams: { q: {}, context_id: {}, context: {}, skip_context: {} }, @@ -39,6 +40,17 @@ export default Discourse.Route.extend({ didTransition() { this.controllerFor("full-page-search")._showFooter(); return true; + }, + + createTopic(searchTerm) { + let category; + if (searchTerm.indexOf("category:")) { + const match = searchTerm.match(/category:(\S*)/); + if (match && match[1]) { + category = match[1]; + } + } + this.container.lookup('controller:composer').open({action: Composer.CREATE_TOPIC, draftKey: Composer.CREATE_TOPIC, topicCategory: category}); } } diff --git a/app/assets/javascripts/discourse/routes/user.js.es6 b/app/assets/javascripts/discourse/routes/user.js.es6 index 71e803300d..271dfad479 100644 --- a/app/assets/javascripts/discourse/routes/user.js.es6 +++ b/app/assets/javascripts/discourse/routes/user.js.es6 @@ -11,21 +11,6 @@ export default Discourse.Route.extend({ }, actions: { - composePrivateMessage(user, post) { - const recipient = user ? user.get('username') : '', - reply = post ? window.location.protocol + "//" + window.location.host + post.get("url") : null; - - // used only once, one less dependency - const Composer = require('discourse/models/composer').default; - return this.controllerFor('composer').open({ - action: Composer.PRIVATE_MESSAGE, - usernames: recipient, - archetypeId: 'private_message', - draftKey: 'new_private_message', - reply: reply - }); - }, - willTransition(transition) { // will reset the indexStream when transitioning to routes that aren't "indexStream" // otherwise the "header" will jump diff --git a/app/assets/javascripts/discourse/templates/components/d-editor.hbs b/app/assets/javascripts/discourse/templates/components/d-editor.hbs index 64ea890986..e9226920d1 100644 --- a/app/assets/javascripts/discourse/templates/components/d-editor.hbs +++ b/app/assets/javascripts/discourse/templates/components/d-editor.hbs @@ -25,9 +25,12 @@ {{popup-input-tip validation=validation}} -
+
{{{preview}}}
+ {{#if site.mobileView}} + {{d-button action='hidePreview' class='hide-preview' label='composer.hide_preview'}} + {{/if}}
diff --git a/app/assets/javascripts/discourse/templates/components/mobile-nav.hbs b/app/assets/javascripts/discourse/templates/components/mobile-nav.hbs new file mode 100644 index 0000000000..889d9eeadc --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/mobile-nav.hbs @@ -0,0 +1 @@ +{{yield}} diff --git a/app/assets/javascripts/discourse/templates/components/toggle-summary.hbs b/app/assets/javascripts/discourse/templates/components/toggle-summary.hbs index 6eed02a1cd..671b85d945 100644 --- a/app/assets/javascripts/discourse/templates/components/toggle-summary.hbs +++ b/app/assets/javascripts/discourse/templates/components/toggle-summary.hbs @@ -3,9 +3,9 @@ {{else}} {{#if topic.estimatedReadingTime}} -

{{{i18n 'summary.description_time' count=topic.posts_count readingTime=topic.estimatedReadingTime}}}

+

{{{i18n 'summary.description_time' replyCount=topic.replyCount readingTime=topic.estimatedReadingTime}}}

{{else}} -

{{{i18n 'summary.description' count=topic.posts_count}}}

+

{{{i18n 'summary.description' replyCount=topic.replyCount}}}

{{/if}} diff --git a/app/assets/javascripts/discourse/templates/composer.hbs b/app/assets/javascripts/discourse/templates/composer.hbs index bc3fa3270f..06ebfdfbef 100644 --- a/app/assets/javascripts/discourse/templates/composer.hbs +++ b/app/assets/javascripts/discourse/templates/composer.hbs @@ -11,6 +11,10 @@ {{render "composer-messages"}}
+ + {{#if site.mobileView}} + + {{/if}} {{#if model.viewOpen}} @@ -20,9 +24,11 @@
{{{model.actionTitle}}} + {{#unless site.mobileView}} {{#if model.whisper}} ({{i18n "composer.whisper"}}) {{/if}} + {{/unless}} {{#if canEdit}} {{#if showEditReason}} @@ -85,6 +91,7 @@ groupsMentioned="groupsMentioned" importQuote="importQuote" showOptions="showOptions" + showToolbar=showToolbar showUploadSelector="showUploadSelector"}} {{#if currentUser}} @@ -92,6 +99,12 @@ {{plugin-outlet "composer-fields-below"}} {{i18n 'cancel'}} + + {{#if site.mobileView}} + {{#if model.whisper}} + + {{/if}} + {{/if}}
{{/if}}
diff --git a/app/assets/javascripts/discourse/templates/full-page-search.hbs b/app/assets/javascripts/discourse/templates/full-page-search.hbs index 344c28985b..e216f7ab3f 100644 --- a/app/assets/javascripts/discourse/templates/full-page-search.hbs +++ b/app/assets/javascripts/discourse/templates/full-page-search.hbs @@ -1,6 +1,11 @@