diff --git a/.editorconfig b/.editorconfig index 65d44abc70..527c2c27d0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,4 +11,4 @@ indent_style = space indent_size = 2 [*.md] -trim_trailing_whitespace = true +trim_trailing_whitespace = false diff --git a/.mention-bot b/.mention-bot index 16c45ceb88..6508c7e867 100644 --- a/.mention-bot +++ b/.mention-bot @@ -1,5 +1,8 @@ { - "maxReviewers": 2, - "message": "Thanks @pullRequester for your pull request :+1:. By analyzing the blame information on this pull request, I identified @reviewers to be potential reviewers.", - "requiredOrgs": ["discourse"] + "maxReviewers": 2, + "message": "Thanks @pullRequester for your pull request :+1:. By analyzing the blame information on this pull request, I identified @reviewers to be potential reviewers.", + "requiredOrgs": ["discourse"], + "skipCollaboratorPR": true, + "delayed": true, + "delayedUntil": "6d" } diff --git a/.travis.yml b/.travis.yml index 6a2ca9143a..129da18869 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,9 +24,6 @@ matrix: fast_finish: true rvm: - - 2.0.0 - - 2.1 - - 2.2 - 2.3.1 services: diff --git a/Gemfile b/Gemfile index 8717d335be..dbea62d2a9 100644 --- a/Gemfile +++ b/Gemfile @@ -144,6 +144,7 @@ group :test, :development do gem 'spork-rails' gem 'pry-nav' gem 'byebug', require: ENV['RM_INFO'].nil? + gem 'bullet' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index ae4a85a5b8..aad2028d0a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -62,6 +62,9 @@ GEM binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) builder (3.2.2) + bullet (5.0.0) + activesupport (>= 3.0.0) + uniform_notifier (~> 1.9.0) byebug (8.2.1) certified (1.0.0) coderay (1.1.0) @@ -215,7 +218,7 @@ GEM omniauth-twitter (1.2.1) json (~> 1.3) omniauth-oauth (~> 1.1) - onebox (1.5.41) + onebox (1.5.42) htmlentities (~> 4.3.4) moneta (~> 0.8) multi_json (~> 1.11) @@ -395,6 +398,7 @@ GEM unicorn (5.1.0) kgio (~> 2.6) raindrops (~> 0.7) + uniform_notifier (1.9.0) PLATFORMS ruby @@ -407,6 +411,7 @@ DEPENDENCIES barber better_errors binding_of_caller + bullet byebug certified discourse-qunit-rails diff --git a/README.md b/README.md index f74dcb0be4..6cdf4c101b 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Browse [lots more notable Discourse instances](http://www.discourse.org/faq/cust 2. If you're familiar with how Rails works and are comfortable setting up your own environment, use our [**Discourse Advanced Developer Guide**](docs/DEVELOPER-ADVANCED.md). -Before you get started, ensure you have the following minimum versions: [Ruby 2.0.0+](http://www.ruby-lang.org/en/downloads/), [PostgreSQL 9.3+](http://www.postgresql.org/download/), [Redis 2.6+](http://redis.io/download). If you're having trouble, please see our [**TROUBLESHOOTING GUIDE**](docs/TROUBLESHOOTING.md) first! +Before you get started, ensure you have the following minimum versions: [Ruby 2.3+](http://www.ruby-lang.org/en/downloads/), [PostgreSQL 9.3+](http://www.postgresql.org/download/), [Redis 2.6+](http://redis.io/download). If you're having trouble, please see our [**TROUBLESHOOTING GUIDE**](docs/TROUBLESHOOTING.md) first! ## Setting up Discourse diff --git a/app/assets/javascripts/admin/templates/flags-list.hbs b/app/assets/javascripts/admin/templates/flags-list.hbs index d6211e700e..e315ce0b9e 100644 --- a/app/assets/javascripts/admin/templates/flags-list.hbs +++ b/app/assets/javascripts/admin/templates/flags-list.hbs @@ -1,150 +1,154 @@ {{#if model.length}} - {{#load-more tagName="table" className="admin-flags" selector="tbody tr" action="loadMore"}} - - - - - {{i18n 'admin.flags.flagged_by'}} - {{#if adminOldFlagsView}}{{i18n 'admin.flags.resolved_by'}}{{/if}} - - - - {{#each content as |flaggedPost|}} - + {{#load-more selector="tbody tr" action="loadMore"}} + + + + + + + + + + + {{#each content as |flaggedPost|}} + - + - + - - - - - - - {{#if flaggedPost.topicFlagged}} - - - - - {{/if}} - {{#each flaggedPost.conversations as |c|}} - - - + + + + {{#if flaggedPost.topicFlagged}} + + + + + {{/if}} + + {{#each flaggedPost.conversations as |c|}} + + + + + {{/each}} + + {{#unless adminOldFlagsView}} + + - + + + {{/unless}} + {{/each}} - - - - - {{/each}} - - + +
{{i18n 'admin.flags.flagged_by'}}{{#if adminOldFlagsView}}{{i18n 'admin.flags.resolved_by'}}{{/if}}
- {{#if flaggedPost.postAuthorFlagged}} - {{#if flaggedPost.user}} - {{#link-to 'adminUser' flaggedPost.user}}{{avatar flaggedPost.user imageSize="small"}}{{/link-to}} - {{#if flaggedPost.wasEdited}}{{/if}} + + {{#if flaggedPost.postAuthorFlagged}} + {{#if flaggedPost.user}} + {{#link-to 'adminUser' flaggedPost.user}}{{avatar flaggedPost.user imageSize="large"}}{{/link-to}} + {{#if flaggedPost.wasEdited}}{{/if}} + {{/if}} {{/if}} - {{/if}} - {{#if adminActiveFlagsView}} - {{#if flaggedPost.previous_flags_count}} - {{flaggedPost.previous_flags_count}} + {{#if adminActiveFlagsView}} + {{#if flaggedPost.previous_flags_count}} + {{flaggedPost.previous_flags_count}} + {{/if}} {{/if}} - {{/if}} - -

- {{#if flaggedPost.topic.isPrivateMessage}} - {{fa-icon "envelope"}} +

+

+ {{#if flaggedPost.topic.isPrivateMessage}} + {{fa-icon "envelope"}} + {{/if}} + {{topic-status topic=flaggedPost.topic}} + {{{unbound flaggedPost.topic.fancyTitle}}} +

+ {{#if flaggedPost.postAuthorFlagged}} +

{{{flaggedPost.excerpt}}}

{{/if}} - {{topic-status topic=flaggedPost.topic}} - {{{unbound flaggedPost.topic.fancyTitle}}} - - {{#if flaggedPost.postAuthorFlagged}} - {{{flaggedPost.excerpt}}} - {{/if}} -
- - - {{#each flaggedPost.flaggers as |flagger|}} - - - - - {{/each}} - -
- {{#link-to 'adminUser' flagger.user}} - {{avatar flagger.user imageSize="small"}} - {{/link-to}} - - {{#link-to 'adminUser' flagger.user}} - {{flagger.user.username}} - {{/link-to}} - {{format-age flagger.flaggedAt}} -
- {{flagger.flagType}} -
-
- {{#if adminOldFlagsView}} + {{#each flaggedPost.flaggers as |flagger|}} {{/each}}
- {{#link-to 'adminUser' flagger.disposedBy}} - {{avatar flagger.disposedBy imageSize="small"}} + {{#link-to 'adminUser' flagger.user}} + {{avatar flagger.user imageSize="medium"}} {{/link-to}} - {{format-age flagger.disposedAt}} - {{{flagger.dispositionIcon}}} - {{#if flagger.tookAction}} - - {{/if}} + {{#link-to 'adminUser' flagger.user}} + {{flagger.user.username}} + {{/link-to}} + {{format-age flagger.flaggedAt}} +
+ {{flagger.flagType}}
- {{/if}} -
-
- {{{i18n 'admin.flags.topic_flagged'}}} {{i18n 'admin.flags.visit_topic'}} -
-
- {{#if c.response}} -

- {{#link-to 'adminUser' c.response.user}}{{avatar c.response.user imageSize="small"}}{{/link-to}} {{{c.response.excerpt}}} -

- {{#if c.reply}} +
+ {{#if adminOldFlagsView}} + + + {{#each flaggedPost.flaggers as |flagger|}} + + + + + {{/each}} + +
+ {{#link-to 'adminUser' flagger.disposedBy}} + {{avatar flagger.disposedBy imageSize="medium"}} + {{/link-to}} + + {{format-age flagger.disposedAt}} + {{{flagger.dispositionIcon}}} + {{#if flagger.tookAction}} + + {{/if}} +
+ {{/if}} +
+
+ {{{i18n 'admin.flags.topic_flagged'}}} {{i18n 'admin.flags.visit_topic'}} +
+
+
+ {{#if c.response}}

- {{#link-to 'adminUser' c.reply.user}}{{avatar c.reply.user imageSize="small"}}{{/link-to}} {{{c.reply.excerpt}}} - {{#if c.hasMore}} - {{i18n 'admin.flags.more'}} - {{/if}} + {{#link-to 'adminUser' c.response.user}}{{avatar c.response.user imageSize="medium"}}{{/link-to}} {{{c.response.excerpt}}}

+ {{#if c.reply}} +

+ {{#link-to 'adminUser' c.reply.user}}{{avatar c.reply.user imageSize="medium"}}{{/link-to}} {{{c.reply.excerpt}}} + {{#if c.hasMore}} + {{i18n 'admin.flags.more'}} + {{/if}} +

+ {{/if}} + + + {{/if}} - - - +
+
+ {{#if adminActiveFlagsView}} + + {{#if flaggedPost.postHidden}} + + {{else}} + + {{/if}} + + {{/if}} - -
- {{#if adminActiveFlagsView}} - - {{#if flaggedPost.postHidden}} - - {{else}} - - {{/if}} - - - {{/if}} -
{{/load-more}} {{else}} diff --git a/app/assets/javascripts/discourse/components/composer-user-selector.js.es6 b/app/assets/javascripts/discourse/components/composer-user-selector.js.es6 new file mode 100644 index 0000000000..d977d6e309 --- /dev/null +++ b/app/assets/javascripts/discourse/components/composer-user-selector.js.es6 @@ -0,0 +1,74 @@ +import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; + +export default Ember.Component.extend({ + showSelector: true, + shouldHide: false, + defaultUsernameCount: 0, + + @observes('usernames') + _checkWidth() { + let width = 0; + const $acWrap = this.$().find('.ac-wrap'); + const limit = $acWrap.width(); + this.set('defaultUsernameCount', 0); + + $acWrap.find('.item').toArray().forEach(item => { + width += $(item).outerWidth(true); + const result = (width < limit); + + if (result) this.incrementProperty('defaultUsernameCount'); + return result; + }); + + if (width >= limit) { + this.set('shouldHide', true); + } else { + this.set('shouldHide', false); + }; + }, + + @observes('shouldHide') + _setFocus() { + const selector = '#reply-control #reply-title, #reply-control .d-editor-input'; + + if (this.get('shouldHide')) { + $(selector).on('focus.composer-user-selector', () => { + this.set('showSelector', false); + this.appEvents.trigger("composer:resize"); + }); + } else { + $(selector).off('focus.composer-user-selector'); + } + }, + + @computed('usernames') + splitUsernames(usernames) { + return usernames.split(','); + }, + + @computed('splitUsernames', 'defaultUsernameCount') + limitedUsernames(splitUsernames, count) { + return splitUsernames.slice(0, count).join(", "); + }, + + @computed('splitUsernames', 'defaultUsernameCount') + hiddenUsersCount(splitUsernames, count) { + return `${splitUsernames.length - count} ${I18n.t('more')}`; + }, + + actions: { + toggleSelector() { + this.set("showSelector", true); + + Ember.run.schedule('afterRender', () => { + this.$().find('input').focus(); + }); + }, + + triggerResize() { + this.appEvents.trigger("composer:resize"); + const $this = this.$().find('.ac-wrap'); + if ($this.height() >= 150) $this.scrollTop($this.height()); + }, + } +}); diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6 index 6ff564d54f..0c0b9c4331 100644 --- a/app/assets/javascripts/discourse/components/d-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/d-editor.js.es6 @@ -63,18 +63,7 @@ class Toolbar { perform: e => e.applySurround('> ', '', 'code_text') }); - this.addButton({ - id: 'code', - group: 'insertions', - shortcut: 'Shift+C', - perform(e) { - if (e.selected.value.indexOf("\n") !== -1) { - e.applySurround(' ', '', 'code_text'); - } else { - e.applySurround('`', '`', 'code_text'); - } - }, - }); + this.addButton({id: 'code', group: 'insertions', shortcut: 'Shift+C', action: 'formatCode'}); this.addButton({ id: 'bullet', @@ -530,6 +519,19 @@ export default Ember.Component.extend({ this.set('insertLinkHidden', false); }, + formatCode() { + const sel = this._getSelected(); + if (sel.value.indexOf("\n") !== -1) { + return (this.siteSettings.code_formatting_style === "4-spaces-indent") ? + this._applySurround(sel, ' ', '', 'code_text') : + this._addText(sel, '```\n' + sel.value + '\n```'); + } else { + return (this.siteSettings.code_formatting_style === "4-spaces-indent") ? + this._applySurround(sel, '`', '`', 'code_text') : + this._applySurround(sel, '```\n', '\n```', 'paste_code_text'); + } + }, + insertLink() { const origLink = this.get('linkUrl'); const linkUrl = (origLink.indexOf('://') === -1) ? `http://${origLink}` : origLink; diff --git a/app/assets/javascripts/discourse/components/user-selector.js.es6 b/app/assets/javascripts/discourse/components/user-selector.js.es6 index ab695e8b2b..862f0fae49 100644 --- a/app/assets/javascripts/discourse/components/user-selector.js.es6 +++ b/app/assets/javascripts/discourse/components/user-selector.js.es6 @@ -1,9 +1,11 @@ +import { observes } from 'ember-addons/ember-computed-decorators'; import TextField from 'discourse/components/text-field'; import userSearch from 'discourse/lib/user-search'; export default TextField.extend({ - _initializeAutocomplete: function() { + didInsertElement() { + this._super(); var self = this, selected = [], groups = [], @@ -63,6 +65,7 @@ export default TextField.extend({ self.set('hasGroups', hasGroups); selected = items; + if (self.get('onChangeCallback')) self.sendAction('onChangeCallback'); }, reverseTransform: function(i) { @@ -70,19 +73,21 @@ export default TextField.extend({ } }); - }.on('didInsertElement'), + }, - _removeAutocomplete: function() { + willDestroyElement() { + this._super(); this.$().autocomplete('destroy'); - }.on('willDestroyElement'), + }, // THIS IS A HUGE HACK TO SUPPORT CLEARING THE INPUT + @observes('usernames') _clearInput: function() { if (arguments.length > 1) { if (Em.isEmpty(this.get("usernames"))) { this.$().parent().find("a").click(); } } - }.observes("usernames") + } }); diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index 6438e6e879..4e2ddb29b6 100644 --- a/app/assets/javascripts/discourse/controllers/composer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/composer.js.es6 @@ -70,7 +70,7 @@ export default Ember.Controller.extend({ // iPhone 6 is 375, anything narrower and toolbar should // be default disabled. // That said we should remember the state - this._toolbarEnabled = $(window).width() > 370; + this._toolbarEnabled = $(window).width() > 370 && !this.capabilities.isAndroid; } return this._toolbarEnabled || storedVal === "true"; }, diff --git a/app/assets/javascripts/discourse/controllers/create-account.js.es6 b/app/assets/javascripts/discourse/controllers/create-account.js.es6 index 9ff65be628..45e7d7bc95 100644 --- a/app/assets/javascripts/discourse/controllers/create-account.js.es6 +++ b/app/assets/javascripts/discourse/controllers/create-account.js.es6 @@ -67,6 +67,10 @@ export default Ember.Controller.extend(ModalFunctionality, { usernameRequired: Ember.computed.not('authOptions.omit_username'), + fullnameRequired: function() { + return this.get('siteSettings.full_name_required') || this.get('siteSettings.enable_names'); + }.property(), + passwordRequired: function() { return Ember.isEmpty(this.get('authOptions.auth_provider')); }.property('authOptions.auth_provider'), diff --git a/app/assets/javascripts/discourse/controllers/preferences.js.es6 b/app/assets/javascripts/discourse/controllers/preferences.js.es6 index c9dd265c4c..d37c428d69 100644 --- a/app/assets/javascripts/discourse/controllers/preferences.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences.js.es6 @@ -2,6 +2,7 @@ import { setting } from 'discourse/lib/computed'; import CanCheckEmails from 'discourse/mixins/can-check-emails'; import { popupAjaxError } from 'discourse/lib/ajax-error'; import computed from "ember-addons/ember-computed-decorators"; +import { categoryBadgeHTML } from "discourse/helpers/category-link"; export default Ember.Controller.extend(CanCheckEmails, { @@ -134,6 +135,25 @@ export default Ember.Controller.extend(CanCheckEmails, { this.set('saved', false); const model = this.get('model'); + + + // watched status changes warn user + const changedWatch = model.changedCategoryNotifications("watched"); + + if (changedWatch.remove.length > 0 && !this.get("warnedRemoveWatch")) { + var categories = Discourse.Category.findByIds(changedWatch.remove).map((cat) => { + return categoryBadgeHTML(cat); + }).join(" "); + bootbox.confirm(I18n.t('user.warn_unwatch.message', {categories: categories}), + I18n.t('user.warn_unwatch.no_value', {count: changedWatch.remove.length}), I18n.t('user.warn_unwatch.yes_value'), + (yes)=>{ + this.set('unwatchCategoryTopics', yes ? changedWatch.remove : false); + this.send('save'); + }); + this.set("warnedRemoveWatch", true); + return; + } + const userFields = this.get('userFields'); // Update the user fields @@ -148,12 +168,19 @@ export default Ember.Controller.extend(CanCheckEmails, { // Cook the bio for preview model.set('name', this.get('newNameInput')); - return model.save().then(() => { + var options = {}; + if (this.get('warnedRemoveWatch') && this.get('unwatchCategoryTopics')) { + options["unwatchCategoryTopics"] = this.get("unwatchCategoryTopics"); + } + + return model.save(options).then(() => { if (Discourse.User.currentProp('id') === model.get('id')) { Discourse.User.currentProp('name', model.get('name')); } model.set('bio_cooked', Discourse.Markdown.cook(Discourse.Markdown.sanitize(model.get('bio_raw')))); this.set('saved', true); + this.set("unwatchTopics", false); + this.set('warnedRemoveWatch', false); }).catch(popupAjaxError); }, diff --git a/app/assets/javascripts/discourse/controllers/share.js.es6 b/app/assets/javascripts/discourse/controllers/share.js.es6 index b494cb1d73..97f1fb4d26 100644 --- a/app/assets/javascripts/discourse/controllers/share.js.es6 +++ b/app/assets/javascripts/discourse/controllers/share.js.es6 @@ -33,7 +33,8 @@ export default Ember.Controller.extend({ replyAsNewTopic() { const topicController = this.get("controllers.topic"); const postStream = topicController.get("model.postStream"); - const post = postStream.findLoadedPost(this.get("postId")); + const postId = this.get("postId") || postStream.findPostIdForPostNumber(1); + const post = postStream.findLoadedPost(postId); topicController.send("replyAsNewTopic", post); this.send("close"); }, diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index 0062c13bda..76d64427e8 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -758,7 +758,6 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { }, deleteTopic() { - this.unsubscribe(); this.get('content').destroy(Discourse.User.current()); }, diff --git a/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 b/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 index b61c7d45f6..8e860c2de5 100644 --- a/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 @@ -1,9 +1,12 @@ +import { observes } from 'ember-addons/ember-computed-decorators'; + export default Ember.ArrayController.extend({ needs: ['application'], - _showFooter: function() { + @observes('model.canLoadMore') + _showFooter() { this.set("controllers.application.showFooter", !this.get("model.canLoadMore")); - }.observes("model.canLoadMore"), + }, currentPath: Em.computed.alias('controllers.application.currentPath'), diff --git a/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 b/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 index b15685f0e6..cd7363d9fb 100644 --- a/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 +++ b/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 @@ -86,13 +86,12 @@ function buildConnectorCache() { }); findOutlets(Ember.TEMPLATES, function(outletName, resource, uniqueName) { - _connectorCache[outletName] = _connectorCache[outletName] || []; - const mixin = {templateName: resource.replace('javascripts/', '')}; let viewClass = uniqueViews[uniqueName]; if (viewClass) { // We are going to add it back with the proper template + _connectorCache[outletName] = _connectorCache[outletName] || []; _connectorCache[outletName].removeObject(viewClass); } else { if (!/\.raw$/.test(uniqueName)) { @@ -101,6 +100,7 @@ function buildConnectorCache() { } if (viewClass) { + _connectorCache[outletName] = _connectorCache[outletName] || []; _connectorCache[outletName].pushObject(viewClass.extend(mixin)); } else { // we have a raw template diff --git a/app/assets/javascripts/discourse/lib/autocomplete.js.es6 b/app/assets/javascripts/discourse/lib/autocomplete.js.es6 index a142947da6..a2edb6b886 100644 --- a/app/assets/javascripts/discourse/lib/autocomplete.js.es6 +++ b/app/assets/javascripts/discourse/lib/autocomplete.js.es6 @@ -101,13 +101,16 @@ export default function(options) { transformed = _.isArray(transformedItem) ? transformedItem : [transformedItem || item]; var divs = transformed.map(function(itm) { - var d = $("
" + itm + "
"); - var prev = me.parent().find('.item:last'); + let d = $(`
${itm}
`); + const $parent = me.parent(); + const prev = $parent.find('.item:last'); + if (prev.length === 0) { me.parent().prepend(d); } else { prev.after(d); } + inputSelectedItems.push(itm); return d[0]; }); diff --git a/app/assets/javascripts/discourse/lib/censored-words.js b/app/assets/javascripts/discourse/lib/censored-words.js index 246bc2a0b0..482b193483 100644 --- a/app/assets/javascripts/discourse/lib/censored-words.js +++ b/app/assets/javascripts/discourse/lib/censored-words.js @@ -7,14 +7,14 @@ Discourse.CensoredWords = { if (!censorRegexp) { var split = censored.split("|"); if (split && split.length) { - censorRegexp = new RegExp("\\b(?:" + split.map(function (t) { return "(" + t.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&') + ")"; }).join("|") + ")\\b", "ig"); + censorRegexp = new RegExp("(\\b(?:" + split.map(function (t) { return "(" + t.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&') + ")"; }).join("|") + ")\\b)(?![^\\(]*\\))", "ig"); } } if (censorRegexp) { var m = censorRegexp.exec(text); while (m && m[0]) { var replacement = new Array(m[0].length+1).join('■'); - text = text.replace(new RegExp("\\b" + m[0] + "\\b", "ig"), replacement); + text = text.replace(new RegExp("(\\b" + m[0] + "\\b)(?![^\\(]*\\))", "ig"), replacement); m = censorRegexp.exec(text); } diff --git a/app/assets/javascripts/discourse/lib/safari-hacks.js.es6 b/app/assets/javascripts/discourse/lib/safari-hacks.js.es6 index 888bf34fc6..1aa215f534 100644 --- a/app/assets/javascripts/discourse/lib/safari-hacks.js.es6 +++ b/app/assets/javascripts/discourse/lib/safari-hacks.js.es6 @@ -73,7 +73,8 @@ function positioningWorkaround($fixedElement) { fixedElement.style.top = '0px'; - fixedElement.style.height = parseInt(window.innerHeight*0.6) + "px"; + const height = Math.max(parseInt(window.innerHeight*0.6), 350); + fixedElement.style.height = height + "px"; // I used to do this, but it seems like we don't need to with position // fixed diff --git a/app/assets/javascripts/discourse/lib/transform-post.js.es6 b/app/assets/javascripts/discourse/lib/transform-post.js.es6 index 315a05f290..7723897970 100644 --- a/app/assets/javascripts/discourse/lib/transform-post.js.es6 +++ b/app/assets/javascripts/discourse/lib/transform-post.js.es6 @@ -19,7 +19,7 @@ export function includeAttributes(...attributes) { export function transformBasicPost(post) { // Note: it can be dangerous to not use `get` in Ember code, but this is significantly // faster and has tests to confirm it works. We only call `get` when the property is a CP - return { + const postAtts = { id: post.id, hidden: post.hidden, deleted: post.get('deleted'), @@ -73,6 +73,9 @@ export function transformBasicPost(post) { replyCount: post.reply_count, }; + _additionalAttributes.forEach(a => postAtts[a] = post[a]); + + return postAtts; } diff --git a/app/assets/javascripts/discourse/lib/utilities.js b/app/assets/javascripts/discourse/lib/utilities.js index 70b5b61e70..fe4417fe3b 100644 --- a/app/assets/javascripts/discourse/lib/utilities.js +++ b/app/assets/javascripts/discourse/lib/utilities.js @@ -251,7 +251,8 @@ Discourse.Utilities = { uploadLocation: function(url) { if (Discourse.CDN) { - return Discourse.CDN.startsWith('//') ? "http:" + Discourse.getURLWithCDN(url) : Discourse.getURLWithCDN(url); + url = Discourse.getURLWithCDN(url); + return url.startsWith('//') ? 'http:' + url : url; } else if (Discourse.SiteSettings.enable_s3_uploads) { return 'https:' + url; } else { diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6 index 666fc5614b..7b5c9e4790 100644 --- a/app/assets/javascripts/discourse/models/user.js.es6 +++ b/app/assets/javascripts/discourse/models/user.js.es6 @@ -141,7 +141,7 @@ const User = RestModel.extend({ return Discourse.User.create(this.getProperties(Object.keys(this))); }, - save() { + save(options) { const data = this.getProperties( 'bio_raw', 'website', @@ -177,8 +177,12 @@ const User = RestModel.extend({ data[s] = this.get(`user_option.${s}`); }); + var updatedState = {}; + ['muted','watched','tracked'].forEach(s => { let cats = this.get(s + 'Categories').map(c => c.get('id')); + updatedState[s + '_category_ids'] = cats; + // HACK: denote lack of categories if (cats.length === 0) { cats = [-1]; } data[s + '_category_ids'] = cats; @@ -188,6 +192,10 @@ const User = RestModel.extend({ data['edit_history_public'] = this.get('user_option.edit_history_public'); } + if (options && options.unwatchCategoryTopics) { + data.unwatch_category_topics = options.unwatchCategoryTopics; + } + // TODO: We can remove this when migrated fully to rest model. this.set('isSaving', true); return Discourse.ajax(`/users/${this.get('username_lower')}`, { @@ -197,6 +205,7 @@ const User = RestModel.extend({ this.set('bio_excerpt', result.user.bio_excerpt); const userProps = Em.getProperties(this.get('user_option'),'enable_quoting', 'external_links_in_new_tab', 'dynamic_favicon'); Discourse.User.current().setProperties(userProps); + this.setProperties(updatedState); }).finally(() => { this.set('isSaving', false); }); @@ -352,6 +361,16 @@ const User = RestModel.extend({ this.set("watchedCategories", Discourse.Category.findByIds(this.watched_category_ids)); }, + changedCategoryNotifications: function(type) { + const ids = this.get(type + "Categories").map(c => c.id); + const oldIds = this.get(type + "_category_ids"); + + return { + add: _.difference(ids, oldIds), + remove: _.difference(oldIds, ids), + }; + }, + @computed("can_delete_account", "reply_count", "topic_count") canDeleteAccount(canDeleteAccount, replyCount, topicCount) { return !Discourse.SiteSettings.enable_sso && canDeleteAccount && ((replyCount || 0) + (topicCount || 0)) <= 1; diff --git a/app/assets/javascripts/discourse/templates/components/composer-user-selector.hbs b/app/assets/javascripts/discourse/templates/components/composer-user-selector.hbs new file mode 100644 index 0000000000..71b5e8176d --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/composer-user-selector.hbs @@ -0,0 +1,17 @@ +{{#if showSelector}} + {{user-selector topicId=topicId + excludeCurrentUser='true' + onChangeCallback='triggerResize' + id="private-message-users" + includeMentionableGroups='true' + class="span8" + placeholderKey="composer.users_placeholder" + tabindex="1" + usernames=usernames + hasGroups=hasGroups}} +{{else}} +
+ {{limitedUsernames}} + {{hiddenUsersCount}} +
+{{/if}} diff --git a/app/assets/javascripts/discourse/templates/composer.hbs b/app/assets/javascripts/discourse/templates/composer.hbs index 4ad7703159..4471943e16 100644 --- a/app/assets/javascripts/discourse/templates/composer.hbs +++ b/app/assets/javascripts/discourse/templates/composer.hbs @@ -37,7 +37,7 @@ {{#if canEdit}} {{#if showEditReason}}
- {{text-field value=editReason tabindex="7" id="edit-reason" maxlength="255" placeholderKey="composer.edit_reason_placeholder"}} + {{text-field autofocus="true" value=editReason tabindex="7" id="edit-reason" maxlength="255" placeholderKey="composer.edit_reason_placeholder"}}
{{else}} {{i18n 'composer.show_edit_reason'}} @@ -48,13 +48,7 @@ {{#if model.canEditTitle}}
{{#if model.creatingPrivateMessage}} - {{user-selector topicId=topicModel.id - excludeCurrentUser="true" - id="private-message-users" - includeMentionableGroups="true" - class="span8" - placeholderKey="composer.users_placeholder" - tabindex="1" + {{composer-user-selector topicId=topicModel.id usernames=model.targetUsernames hasGroups=model.hasTargetGroups }} diff --git a/app/assets/javascripts/discourse/templates/modal/create-account.hbs b/app/assets/javascripts/discourse/templates/modal/create-account.hbs index 5e5d9b497b..ca5485a271 100644 --- a/app/assets/javascripts/discourse/templates/modal/create-account.hbs +++ b/app/assets/javascripts/discourse/templates/modal/create-account.hbs @@ -35,7 +35,7 @@ {{/if}} - {{#if siteSettings.enable_names}} + {{#if fullnameRequired}} diff --git a/app/assets/javascripts/discourse/templates/tags/show.hbs b/app/assets/javascripts/discourse/templates/tags/show.hbs index dcecef2c9c..db3ecce1e4 100644 --- a/app/assets/javascripts/discourse/templates/tags/show.hbs +++ b/app/assets/javascripts/discourse/templates/tags/show.hbs @@ -36,6 +36,8 @@
+{{plugin-outlet "discovery-list-container-top"}} +