From e234a7821d9b7c429f4dde49268c1cad1da7e94e Mon Sep 17 00:00:00 2001 From: ElTipejoLoco Date: Fri, 17 Mar 2017 08:05:21 -0500 Subject: [PATCH 001/124] Add "Show tracked topics" to User Preferences Does what it says on the tin. Minor clean-up. --- app/assets/javascripts/discourse/models/user.js.es6 | 5 +++++ app/assets/javascripts/discourse/templates/preferences.hbs | 4 +++- config/locales/client.en.yml | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6 index 5086c54cbc..7fecf9eb30 100644 --- a/app/assets/javascripts/discourse/models/user.js.es6 +++ b/app/assets/javascripts/discourse/models/user.js.es6 @@ -146,6 +146,11 @@ const User = RestModel.extend({ return defaultHomepage() === "latest" ? Discourse.getURL('/?state=watching') : Discourse.getURL('/latest?state=watching'); }, + @computed() + trackingTopicsPath() { + return defaultHomepage() === "latest" ? Discourse.getURL('/?state=tracking') : Discourse.getURL('/latest?state=tracking'); + }, + @computed("username") username_lower(username) { return username.toLowerCase(); diff --git a/app/assets/javascripts/discourse/templates/preferences.hbs b/app/assets/javascripts/discourse/templates/preferences.hbs index 4be554bdf6..6fea4f7f6f 100644 --- a/app/assets/javascripts/discourse/templates/preferences.hbs +++ b/app/assets/javascripts/discourse/templates/preferences.hbs @@ -263,12 +263,14 @@
{{i18n 'user.watched_topics_link'}}
-
{{category-selector categories=model.trackedCategories blacklist=selectedCategories}}
{{i18n 'user.tracked_categories_instructions'}}
+
+ {{i18n 'user.tracked_topics_link'}} +
{{category-selector categories=model.watchedFirstPostCategories}} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index cd3f3d15b1..6c500a5984 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -627,6 +627,7 @@ en: muted_users_instructions: "Suppress all notifications from these users." muted_topics_link: "Show muted topics" watched_topics_link: "Show watched topics" + tracked_topics_link: "Show tracked topics" automatically_unpin_topics: "Automatically unpin topics when I reach the bottom." apps: "Apps" revoke_access: "Revoke Access" From d073f582eb242d086af0970940281816985f9691 Mon Sep 17 00:00:00 2001 From: James Kiesel Date: Fri, 24 Mar 2017 09:19:25 +1300 Subject: [PATCH 002/124] Allow plugins to add panels to header more easily --- app/assets/javascripts/discourse/widgets/header.js.es6 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/assets/javascripts/discourse/widgets/header.js.es6 b/app/assets/javascripts/discourse/widgets/header.js.es6 index 1f66b5eec6..78017055b8 100644 --- a/app/assets/javascripts/discourse/widgets/header.js.es6 +++ b/app/assets/javascripts/discourse/widgets/header.js.es6 @@ -214,6 +214,8 @@ export default createWidget('header', { panels.push(this.attach('user-menu')); } + this.additionalPanels(attrs, state).map(function(panel) { panels.push(panel) }) + const contents = [ this.attach('home-logo', { minimized: !!attrs.topic }), h('div.panel.clearfix', panels) ]; @@ -224,6 +226,11 @@ export default createWidget('header', { return h('div.wrap', h('div.contents.clearfix', contents)); }, + // override to allow plugins to append additional panels + additionalPanels(attrs, state) { + return [] + }, + updateHighlight() { if (!this.state.searchVisible) { const service = this.register.lookup('search-service:main'); From 569785c55529c22a9046ad0ba14929024d6b5293 Mon Sep 17 00:00:00 2001 From: James Kiesel Date: Fri, 24 Mar 2017 09:48:51 +1300 Subject: [PATCH 003/124] Add semicolon --- app/assets/javascripts/discourse/widgets/header.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/widgets/header.js.es6 b/app/assets/javascripts/discourse/widgets/header.js.es6 index 78017055b8..8f5a9bfde4 100644 --- a/app/assets/javascripts/discourse/widgets/header.js.es6 +++ b/app/assets/javascripts/discourse/widgets/header.js.es6 @@ -214,7 +214,7 @@ export default createWidget('header', { panels.push(this.attach('user-menu')); } - this.additionalPanels(attrs, state).map(function(panel) { panels.push(panel) }) + this.additionalPanels(attrs, state).map(function(panel) { panels.push(panel) }); const contents = [ this.attach('home-logo', { minimized: !!attrs.topic }), h('div.panel.clearfix', panels) ]; From 9f969b402e0b7df8f05965bd10623a1a8405bc73 Mon Sep 17 00:00:00 2001 From: James Kiesel Date: Fri, 24 Mar 2017 09:54:53 +1300 Subject: [PATCH 004/124] More stylistic fixes --- app/assets/javascripts/discourse/widgets/header.js.es6 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/widgets/header.js.es6 b/app/assets/javascripts/discourse/widgets/header.js.es6 index 8f5a9bfde4..a2a4ce3979 100644 --- a/app/assets/javascripts/discourse/widgets/header.js.es6 +++ b/app/assets/javascripts/discourse/widgets/header.js.es6 @@ -214,7 +214,7 @@ export default createWidget('header', { panels.push(this.attach('user-menu')); } - this.additionalPanels(attrs, state).map(function(panel) { panels.push(panel) }); + this.additionalPanels(attrs, state).map(function(panel) { panels.push(panel); }); const contents = [ this.attach('home-logo', { minimized: !!attrs.topic }), h('div.panel.clearfix', panels) ]; @@ -227,8 +227,8 @@ export default createWidget('header', { }, // override to allow plugins to append additional panels - additionalPanels(attrs, state) { - return [] + additionalPanels() { + return []; }, updateHighlight() { From a4127a8f71dcf91e749dbc0b7cee9dfdd0c9bc22 Mon Sep 17 00:00:00 2001 From: James Kiesel Date: Sat, 25 Mar 2017 21:33:55 +1300 Subject: [PATCH 005/124] Add addAdditionalHeader widget to pluginApi --- .../discourse/lib/plugin-api.js.es6 | 19 ++++++++++++++++++- .../discourse/widgets/header.js.es6 | 16 ++++++++++------ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 index 0b3e85739b..ca1d1ce289 100644 --- a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 +++ b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 @@ -18,9 +18,10 @@ import { addTagsHtmlCallback } from 'discourse/lib/render-tags'; import { addUserMenuGlyph } from 'discourse/widgets/user-menu'; import { addPostClassesCallback } from 'discourse/widgets/post'; import { addPostTransformCallback } from 'discourse/widgets/post-stream'; +import { queryRegistry } from 'discourse/widgets/widget'; // If you add any methods to the API ensure you bump up this number -const PLUGIN_API_VERSION = '0.8.5'; +const PLUGIN_API_VERSION = '0.8.6'; class PluginApi { constructor(version, container) { @@ -333,6 +334,22 @@ class PluginApi { return addFlagProperty(property); } + /** + * Adds a panel to the header + * + * takes a widget name, a value to toggle on, and a function which returns the attrs for the widget + * Example: + * ```javascript + * api.addHeaderPanel('widget-name', 'widgetVisible', function(attrs, state) { return {}; }); + * ``` + * note that 'toggle' is an attribute on the state of the header widget, + * and the attrFn receives the current attrs and state of the header as arguments, + * and returns a hash of attrs to pass to the widget + **/ + addHeaderPanel(name, toggle, attrFn) { + queryRegistry('header').prototype.attachAdditionalPanel(name, toggle, attrFn) + } + /** * Adds a pluralization to the store * diff --git a/app/assets/javascripts/discourse/widgets/header.js.es6 b/app/assets/javascripts/discourse/widgets/header.js.es6 index a2a4ce3979..842e0674f2 100644 --- a/app/assets/javascripts/discourse/widgets/header.js.es6 +++ b/app/assets/javascripts/discourse/widgets/header.js.es6 @@ -169,6 +169,7 @@ const forceContextEnabled = ['category', 'user', 'private_messages']; export default createWidget('header', { tagName: 'header.d-header.clearfix', buildKey: () => `header`, + additionalPanels: [], defaultState() { let states = { @@ -185,6 +186,10 @@ export default createWidget('header', { return states; }, + attachAdditionalPanel(name, toggle, attrFn) { + this.additionalPanels.push({ name, toggle, attrFn }) + }, + html(attrs, state) { const panels = [this.attach('header-buttons', attrs), this.attach('header-icons', { hamburgerVisible: state.hamburgerVisible, @@ -214,7 +219,11 @@ export default createWidget('header', { panels.push(this.attach('user-menu')); } - this.additionalPanels(attrs, state).map(function(panel) { panels.push(panel); }); + this.additionalPanels.map((panel) => { + if (this.state[panel.toggle]) { + panels.push(this.attach(panel.name, panel.attrFn.call(this, attrs, state))); + } + }); const contents = [ this.attach('home-logo', { minimized: !!attrs.topic }), h('div.panel.clearfix', panels) ]; @@ -226,11 +235,6 @@ export default createWidget('header', { return h('div.wrap', h('div.contents.clearfix', contents)); }, - // override to allow plugins to append additional panels - additionalPanels() { - return []; - }, - updateHighlight() { if (!this.state.searchVisible) { const service = this.register.lookup('search-service:main'); From c99883cf656fcfdbe186aa2a446f98f5cf6d4aa2 Mon Sep 17 00:00:00 2001 From: James Kiesel Date: Sat, 25 Mar 2017 21:44:28 +1300 Subject: [PATCH 006/124] SEMICOLONS ;.; --- app/assets/javascripts/discourse/lib/plugin-api.js.es6 | 2 +- app/assets/javascripts/discourse/widgets/header.js.es6 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 index ca1d1ce289..91603a7f0c 100644 --- a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 +++ b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 @@ -347,7 +347,7 @@ class PluginApi { * and returns a hash of attrs to pass to the widget **/ addHeaderPanel(name, toggle, attrFn) { - queryRegistry('header').prototype.attachAdditionalPanel(name, toggle, attrFn) + queryRegistry('header').prototype.attachAdditionalPanel(name, toggle, attrFn); } /** diff --git a/app/assets/javascripts/discourse/widgets/header.js.es6 b/app/assets/javascripts/discourse/widgets/header.js.es6 index 842e0674f2..ec93b0f230 100644 --- a/app/assets/javascripts/discourse/widgets/header.js.es6 +++ b/app/assets/javascripts/discourse/widgets/header.js.es6 @@ -187,7 +187,7 @@ export default createWidget('header', { }, attachAdditionalPanel(name, toggle, attrFn) { - this.additionalPanels.push({ name, toggle, attrFn }) + this.additionalPanels.push({ name, toggle, attrFn }); }, html(attrs, state) { From 322ed7124ee96ea6f2f42e737b1aff95e4e7c6e8 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Tue, 28 Mar 2017 23:42:26 +0530 Subject: [PATCH 007/124] UX: add a link to bulk invite howto --- app/assets/javascripts/discourse/templates/user-invited-show.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/discourse/templates/user-invited-show.hbs b/app/assets/javascripts/discourse/templates/user-invited-show.hbs index fc8657b0d4..0a78d6e64f 100644 --- a/app/assets/javascripts/discourse/templates/user-invited-show.hbs +++ b/app/assets/javascripts/discourse/templates/user-invited-show.hbs @@ -17,6 +17,7 @@ {{d-button icon="plus" action="showInvite" label="user.invited.create" class="btn"}} {{#if canBulkInvite}} {{csv-uploader uploading=uploading}} + {{fa-icon "question-circle"}} {{/if}} {{#if showReinviteAllButton}} {{#if reinvitedAll}} From f3540332a880f1a92842e6b1563360892fd42389 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 28 Mar 2017 16:28:26 -0400 Subject: [PATCH 008/124] FIX: Topic title wasn't showing properly when entering lower in topics --- .../discourse/components/discourse-topic.js.es6 | 5 ++++- .../discourse/components/topic-entrance.js.es6 | 8 ++++++-- .../javascripts/discourse/models/post-stream.js.es6 | 4 ++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/components/discourse-topic.js.es6 b/app/assets/javascripts/discourse/components/discourse-topic.js.es6 index 5a07d9516d..4c717b3049 100644 --- a/app/assets/javascripts/discourse/components/discourse-topic.js.es6 +++ b/app/assets/javascripts/discourse/components/discourse-topic.js.es6 @@ -142,7 +142,10 @@ export default Ember.Component.extend(AddArchetypeClass, Scrolling, { this.appEvents.trigger('header:show-topic', topic); } else { if (!DiscourseURL.isJumpScheduled()) { - this.appEvents.trigger('header:hide-topic'); + const loadingNear = topic.get('postStream.loadingNearPost') || 1; + if (loadingNear === 1) { + this.appEvents.trigger('header:hide-topic'); + } } } } diff --git a/app/assets/javascripts/discourse/components/topic-entrance.js.es6 b/app/assets/javascripts/discourse/components/topic-entrance.js.es6 index d3b5983ce4..6e4ba25d14 100644 --- a/app/assets/javascripts/discourse/components/topic-entrance.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-entrance.js.es6 @@ -94,11 +94,15 @@ export default Ember.Component.extend(CleansUp, { actions: { enterTop() { - DiscourseURL.routeTo(this.get('topic.url')); + const topic = this.get('topic'); + this.appEvents.trigger('header:update-topic', topic); + DiscourseURL.routeTo(topic.get('url')); }, enterBottom() { - DiscourseURL.routeTo(this.get('topic.lastPostUrl')); + const topic = this.get('topic'); + this.appEvents.trigger('header:update-topic', topic); + DiscourseURL.routeTo(topic.get('lastPostUrl')); } } }); diff --git a/app/assets/javascripts/discourse/models/post-stream.js.es6 b/app/assets/javascripts/discourse/models/post-stream.js.es6 index 32cfa6427f..0e9a586303 100644 --- a/app/assets/javascripts/discourse/models/post-stream.js.es6 +++ b/app/assets/javascripts/discourse/models/post-stream.js.es6 @@ -15,6 +15,7 @@ export default RestModel.extend({ loadingAbove: null, loadingBelow: null, loadingFilter: null, + loadingNearPost: null, stagingPost: null, postsWithPlaceholders: null, timelineLookup: null, @@ -206,6 +207,7 @@ export default RestModel.extend({ // TODO: if we have all the posts in the filter, don't go to the server for them. this.set('loadingFilter', true); + this.set('loadingNearPost', opts.nearPost); opts = _.merge(opts, this.get('streamFilters')); @@ -216,6 +218,8 @@ export default RestModel.extend({ }).catch(result => { this.errorLoading(result); throw result; + }).finally(() => { + this.set('loadingNearPost', null); }); }, From 86f11955cf931f536a3f646ffd1a755c8a8b7629 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 28 Mar 2017 17:05:07 -0400 Subject: [PATCH 009/124] FIX: Clicking on the title was doubling up the title --- app/assets/javascripts/discourse/lib/url.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/url.js.es6 b/app/assets/javascripts/discourse/lib/url.js.es6 index 203126a6c8..e4dbd44f03 100644 --- a/app/assets/javascripts/discourse/lib/url.js.es6 +++ b/app/assets/javascripts/discourse/lib/url.js.es6 @@ -47,8 +47,8 @@ const DiscourseURL = Ember.Object.extend({ opts = opts || {}; const holderId = `#post_${postNumber}`; - _transitioning = true; - Em.run.schedule('afterRender', () => { + _transitioning = postNumber > 1; + Ember.run.schedule('afterRender', () => { let elementId; let holder; From 3b55ceffb39b6d1bc27effb31aa7167e8f526df4 Mon Sep 17 00:00:00 2001 From: James Kiesel Date: Wed, 29 Mar 2017 08:52:50 +0900 Subject: [PATCH 010/124] Use local var for additionalPanels --- .../discourse/lib/plugin-api.js.es6 | 19 ++++++++++++------- .../discourse/widgets/header.js.es6 | 13 +++++++------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 index 91603a7f0c..1bc7638c83 100644 --- a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 +++ b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 @@ -18,7 +18,8 @@ import { addTagsHtmlCallback } from 'discourse/lib/render-tags'; import { addUserMenuGlyph } from 'discourse/widgets/user-menu'; import { addPostClassesCallback } from 'discourse/widgets/post'; import { addPostTransformCallback } from 'discourse/widgets/post-stream'; -import { queryRegistry } from 'discourse/widgets/widget'; +import { attachAdditionalPanel } from 'discourse/widgets/header'; + // If you add any methods to the API ensure you bump up this number const PLUGIN_API_VERSION = '0.8.6'; @@ -340,14 +341,18 @@ class PluginApi { * takes a widget name, a value to toggle on, and a function which returns the attrs for the widget * Example: * ```javascript - * api.addHeaderPanel('widget-name', 'widgetVisible', function(attrs, state) { return {}; }); + * api.addHeaderPanel('widget-name', 'widgetVisible', function(attrs, state) { + * return { name: attrs.name, description: state.description }; + * }); * ``` - * note that 'toggle' is an attribute on the state of the header widget, - * and the attrFn receives the current attrs and state of the header as arguments, - * and returns a hash of attrs to pass to the widget + * 'toggle' is an attribute on the state of the header widget, + * + * 'transformAttrs' is a function which is passed the current attrs and state of the widget, + * and returns a hash of values to pass to attach + * **/ - addHeaderPanel(name, toggle, attrFn) { - queryRegistry('header').prototype.attachAdditionalPanel(name, toggle, attrFn); + addHeaderPanel(name, toggle, transformAttrs) { + attachAdditionalPanel(name, toggle, transformAttrs); } /** diff --git a/app/assets/javascripts/discourse/widgets/header.js.es6 b/app/assets/javascripts/discourse/widgets/header.js.es6 index ec93b0f230..c90954978f 100644 --- a/app/assets/javascripts/discourse/widgets/header.js.es6 +++ b/app/assets/javascripts/discourse/widgets/header.js.es6 @@ -166,6 +166,11 @@ createWidget('header-buttons', { const forceContextEnabled = ['category', 'user', 'private_messages']; +let additionalPanels = [] +export function attachAdditionalPanel(name, toggle, transformAttrs) { + additionalPanels.push({ name, toggle, transformAttrs }); +} + export default createWidget('header', { tagName: 'header.d-header.clearfix', buildKey: () => `header`, @@ -186,10 +191,6 @@ export default createWidget('header', { return states; }, - attachAdditionalPanel(name, toggle, attrFn) { - this.additionalPanels.push({ name, toggle, attrFn }); - }, - html(attrs, state) { const panels = [this.attach('header-buttons', attrs), this.attach('header-icons', { hamburgerVisible: state.hamburgerVisible, @@ -219,9 +220,9 @@ export default createWidget('header', { panels.push(this.attach('user-menu')); } - this.additionalPanels.map((panel) => { + additionalPanels.map((panel) => { if (this.state[panel.toggle]) { - panels.push(this.attach(panel.name, panel.attrFn.call(this, attrs, state))); + panels.push(this.attach(panel.name, panel.transformAttrs.call(this, attrs, state))); } }); From 7cf0f39066ef76444795299aacf03a7a318536f0 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Wed, 29 Mar 2017 11:10:25 +0800 Subject: [PATCH 011/124] Require `Sidekiq::Testing` in rails helper. --- spec/integration/topic_auto_close_spec.rb | 1 - spec/jobs/jobs_spec.rb | 2 -- spec/models/discourse_single_sign_on_spec.rb | 1 - spec/models/web_hook_spec.rb | 1 - spec/rails_helper.rb | 1 + 5 files changed, 1 insertion(+), 5 deletions(-) diff --git a/spec/integration/topic_auto_close_spec.rb b/spec/integration/topic_auto_close_spec.rb index c69d710832..c57c505c65 100644 --- a/spec/integration/topic_auto_close_spec.rb +++ b/spec/integration/topic_auto_close_spec.rb @@ -1,7 +1,6 @@ # encoding: UTF-8 require 'rails_helper' -require 'sidekiq/testing' describe Topic do diff --git a/spec/jobs/jobs_spec.rb b/spec/jobs/jobs_spec.rb index 64de988b71..a5e6203ac3 100644 --- a/spec/jobs/jobs_spec.rb +++ b/spec/jobs/jobs_spec.rb @@ -1,4 +1,3 @@ -require "sidekiq/testing" require 'rails_helper' require_dependency 'jobs/base' @@ -123,4 +122,3 @@ describe Jobs do end end - diff --git a/spec/models/discourse_single_sign_on_spec.rb b/spec/models/discourse_single_sign_on_spec.rb index 534cfb857f..fedab67aba 100644 --- a/spec/models/discourse_single_sign_on_spec.rb +++ b/spec/models/discourse_single_sign_on_spec.rb @@ -1,5 +1,4 @@ require "rails_helper" -require 'sidekiq/testing' describe DiscourseSingleSignOn do before do diff --git a/spec/models/web_hook_spec.rb b/spec/models/web_hook_spec.rb index f71ac30e11..d65641ea7e 100644 --- a/spec/models/web_hook_spec.rb +++ b/spec/models/web_hook_spec.rb @@ -1,5 +1,4 @@ require 'rails_helper' -require 'sidekiq/testing' describe WebHook do it { is_expected.to validate_presence_of :payload_url } diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index d6a66b9ee7..cc2ac9b91b 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -25,6 +25,7 @@ Spork.prefork do require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'shoulda' + require 'sidekiq/testing' # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. From bb0fa5abbc6da2ad32ec52264796ee78d1cf3a45 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Wed, 29 Mar 2017 18:19:28 +0530 Subject: [PATCH 012/124] FIX: suggested username should not be more than setting max_username_length --- lib/user_name_suggester.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/user_name_suggester.rb b/lib/user_name_suggester.rb index f26b3f36d1..2069113cfd 100644 --- a/lib/user_name_suggester.rb +++ b/lib/user_name_suggester.rb @@ -28,7 +28,7 @@ module UserNameSuggester i += 1 end until attempt == allow_username || User.username_available?(attempt) || i > 200 - attempt = SecureRandom.hex[0..SiteSetting.max_username_length] + attempt = SecureRandom.hex[1..SiteSetting.max_username_length] i += 1 end attempt From 3914f746b882bc3e78c72089098c615cb4974e66 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 29 Mar 2017 13:03:35 -0400 Subject: [PATCH 013/124] FIX: Missing semicolon --- app/assets/javascripts/discourse/widgets/header.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/widgets/header.js.es6 b/app/assets/javascripts/discourse/widgets/header.js.es6 index c90954978f..f0d522bc00 100644 --- a/app/assets/javascripts/discourse/widgets/header.js.es6 +++ b/app/assets/javascripts/discourse/widgets/header.js.es6 @@ -166,7 +166,7 @@ createWidget('header-buttons', { const forceContextEnabled = ['category', 'user', 'private_messages']; -let additionalPanels = [] +let additionalPanels = []; export function attachAdditionalPanel(name, toggle, transformAttrs) { additionalPanels.push({ name, toggle, transformAttrs }); } From cd2d2f16e5c2164a5f32bf2683e42f9316775f7a Mon Sep 17 00:00:00 2001 From: Yana Agun Siswanto Date: Thu, 30 Mar 2017 00:33:23 +0700 Subject: [PATCH 014/124] Allow to order search results by the topic creation date based on: https://meta.discourse.org/t/allow-to-order-search-results-by-the-topic-creation-date/38544 --- .../controllers/full-page-search.js.es6 | 6 +++++- config/locales/client.en.yml | 1 + lib/search.rb | 9 ++++++++ spec/components/search_spec.rb | 21 +++++++++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) 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 91e5b907d2..c6379ba1ab 100644 --- a/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 +++ b/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 @@ -11,6 +11,8 @@ const SortOrders = [ {name: I18n.t('search.latest_post'), id: 1, term: 'order:latest'}, {name: I18n.t('search.most_liked'), id: 2, term: 'order:likes'}, {name: I18n.t('search.most_viewed'), id: 3, term: 'order:views'}, + {name: I18n.t('search.latest_topic'), id: 4, term: 'order:latest_topic'}, + ]; export default Ember.Controller.extend({ @@ -90,7 +92,9 @@ export default Ember.Controller.extend({ this._searchOnSortChange = false; if (term) { SortOrders.forEach(order => { - if (term.indexOf(order.term) > -1){ + let term_start = term.indexOf(order.term) + let char_after_term = term[term_start + (order.term || '').length] + if (term_start > -1 && (!char_after_term || char_after_term.match(/\s/))){ this.set('sortOrder', order.id); term = term.replace(order.term, ""); term = term.trim(); diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 85ad37d345..20c77e4535 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1276,6 +1276,7 @@ en: sort_by: "Sort by" relevance: "Relevance" latest_post: "Latest Post" + latest_topic: "Latest Topic" most_viewed: "Most Viewed" most_liked: "Most Liked" select_all: "Select All" diff --git a/lib/search.rb b/lib/search.rb index 7c055e0a40..81c924df44 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -477,6 +477,9 @@ class Search if word == 'order:latest' @order = :latest nil + elsif word == 'order:latest_topic' + @order = :latest_topic + nil elsif word =~ /topic:(\d+)/ topic_id = $1.to_i if topic_id > 1 @@ -680,6 +683,12 @@ class Search else posts = posts.order("posts.created_at DESC") end + elsif @order == :latest_topic + if opts[:aggregate_search] + posts = posts.order("MAX(topics.created_at) DESC") + else + posts = posts.order("topics.created_at DESC") + end elsif @order == :views if opts[:aggregate_search] posts = posts.order("MAX(topics.views) DESC") diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb index 0cf29c59aa..892e5b3dd9 100644 --- a/spec/components/search_spec.rb +++ b/spec/components/search_spec.rb @@ -613,7 +613,28 @@ describe Search do expect(Search.execute('sam').posts.map(&:id)).to eq([post1.id, post2.id]) expect(Search.execute('sam order:latest').posts.map(&:id)).to eq([post2.id, post1.id]) + end + it 'can order by topic creation' do + today = Date.today + yesterday = 1.day.ago + two_days_ago = 2.days.ago + + old_topic = Fabricate(:topic, + title: 'First Topic, testing the created_at sort', + created_at: two_days_ago) + latest_topic = Fabricate(:topic, + title: 'Second Topic, testing the created_at sort', + created_at: yesterday) + + old_relevant_topic_post = Fabricate(:post, topic: old_topic, created_at: yesterday, raw: 'Relevant Topic') + latest_irelevant_topic_post = Fabricate(:post, topic: latest_topic, created_at: today, raw: 'Not Relevant') + + # Expecting the default results + expect(Search.execute('Topic').posts.map(&:id)).to eq([old_relevant_topic_post.id, latest_irelevant_topic_post.id]) + + # Expecting the ordered by topic creation results + expect(Search.execute('Topic order:latest_topic').posts.map(&:id)).to eq([latest_irelevant_topic_post.id, old_relevant_topic_post.id]) end it 'can tokenize dots' do From 47c1cc95dd532dee4bebb7caaafcf2bf4927a63f Mon Sep 17 00:00:00 2001 From: Yana Agun Siswanto Date: Thu, 30 Mar 2017 01:49:54 +0700 Subject: [PATCH 015/124] Refactor `setSearchTerm`, to be reused on `noSortQ` --- .../controllers/full-page-search.js.es6 | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) 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 c6379ba1ab..a6751b639e 100644 --- a/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 +++ b/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 @@ -75,14 +75,7 @@ export default Ember.Controller.extend({ @computed('q') noSortQ(q) { - if (q) { - SortOrders.forEach((order) => { - if (q.indexOf(order.term) > -1){ - q = q.replace(order.term, ""); - q = q.trim(); - } - }); - } + q = this.cleanTerm(q); return escapeExpression(q); }, @@ -90,19 +83,23 @@ export default Ember.Controller.extend({ setSearchTerm(term) { this._searchOnSortChange = false; + term = this.cleanTerm(term); + this._searchOnSortChange = true; + this.set('searchTerm', term); + }, + + cleanTerm(term) { if (term) { SortOrders.forEach(order => { - let term_start = term.indexOf(order.term) - let char_after_term = term[term_start + (order.term || '').length] - if (term_start > -1 && (!char_after_term || char_after_term.match(/\s/))){ + let matches = term.match(new RegExp(`${order.term}\\b`)); + if (matches) { this.set('sortOrder', order.id); - term = term.replace(order.term, ""); + term = term.replace(new RegExp(`${order.term}\\b`, 'g'), ""); term = term.trim(); } }); } - this._searchOnSortChange = true; - this.set('searchTerm', term); + return term; }, @observes('sortOrder') From 8742f5176f5bc72d4f1514e5423135712e219e87 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Wed, 29 Mar 2017 16:48:53 -0400 Subject: [PATCH 016/124] FIX: tags and topic links rendered outside of page header --- app/assets/stylesheets/common/base/tagging.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/assets/stylesheets/common/base/tagging.scss b/app/assets/stylesheets/common/base/tagging.scss index aa13934ae5..7fb5f4813e 100644 --- a/app/assets/stylesheets/common/base/tagging.scss +++ b/app/assets/stylesheets/common/base/tagging.scss @@ -93,6 +93,12 @@ $tag-color: scale-color($primary, $lightness: 40%); } } +.d-header .topic-header-extra { + display: inline-block; + .discourse-tags { display: inline-block; } + .topic-featured-link { margin-left: 8px; } +} + .select2-container-multi .select2-choices .select2-search-choice.discourse-tag-select2 { padding-top: 5px; -webkit-box-shadow: none; From 47182d34412c6358ca3dc725b288c9f8a1164112 Mon Sep 17 00:00:00 2001 From: James Kiesel Date: Thu, 30 Mar 2017 08:16:00 +0900 Subject: [PATCH 017/124] Remove stray reference to additionalPanels --- app/assets/javascripts/discourse/widgets/header.js.es6 | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/discourse/widgets/header.js.es6 b/app/assets/javascripts/discourse/widgets/header.js.es6 index f0d522bc00..5b67b1bda3 100644 --- a/app/assets/javascripts/discourse/widgets/header.js.es6 +++ b/app/assets/javascripts/discourse/widgets/header.js.es6 @@ -174,7 +174,6 @@ export function attachAdditionalPanel(name, toggle, transformAttrs) { export default createWidget('header', { tagName: 'header.d-header.clearfix', buildKey: () => `header`, - additionalPanels: [], defaultState() { let states = { From a818fa9831a85430231661171a4787e3847e3416 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Thu, 30 Mar 2017 09:46:39 +0800 Subject: [PATCH 018/124] FIX: Show stats of the last 30 days be default for admin reports. * `1.month.ago + 1.month` uses the calendar month for calculations such that `1.month.ago` from the 30th of March 2017 will give us the 28th of February 2017. Adding one month ahead from 28th February 2017 will be 28th of March 2017. --- app/controllers/admin/reports_controller.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index f24ee3f528..8a47b8a9d1 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -7,12 +7,9 @@ class Admin::ReportsController < Admin::AdminController raise Discourse::NotFound unless report_type =~ /^[a-z0-9\_]+$/ - start_date = 1.month.ago - start_date = Time.parse(params[:start_date]) if params[:start_date].present? - - end_date = start_date + 1.month - end_date = Time.parse(params[:end_date]) if params[:end_date].present? - + start_date = params[:start_date].present? ? Time.parse(params[:start_date]) : 30.days.ago + end_date = params[:end_date].present? ? Time.parse(params[:end_date]) : start_date + 30.days + if params.has_key?(:category_id) && params[:category_id].to_i > 0 category_id = params[:category_id].to_i else From 99abbc2e2d8a5a9050688c346b02d1e21b3c221d Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Thu, 30 Mar 2017 13:24:18 +0800 Subject: [PATCH 019/124] UX: Order custom emojis by name. --- app/models/emoji.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/emoji.rb b/app/models/emoji.rb index e098719b0a..54cac0c4b2 100644 --- a/app/models/emoji.rb +++ b/app/models/emoji.rb @@ -92,7 +92,7 @@ class Emoji def self.load_custom result = [] - CustomEmoji.all.each do |emoji| + CustomEmoji.order(:name).all.each do |emoji| result << Emoji.new.tap do |e| e.name = emoji.name e.url = emoji.upload.url From 6b976433c9b918f31a646534140af99b7dc2f92a Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Mon, 27 Mar 2017 15:34:54 -0400 Subject: [PATCH 020/124] Support for both `/users/` and `/u/` paths --- .../initializers/url-redirects.js.es6 | 6 +- .../discourse/lib/discourse-location.js.es6 | 2 - .../javascripts/discourse/lib/url.js.es6 | 20 +++- .../discourse/mapping-router.js.es6 | 4 + .../discourse/routes/app-route-map.js.es6 | 6 +- config/routes.rb | 107 ++++++++++++++---- spec/controllers/email_controller_spec.rb | 2 +- spec/controllers/users_controller_spec.rb | 2 +- .../acceptance/user-anonymous-test.js.es6 | 2 +- test/javascripts/helpers/qunit-helpers.js.es6 | 2 + 10 files changed, 119 insertions(+), 34 deletions(-) diff --git a/app/assets/javascripts/discourse/initializers/url-redirects.js.es6 b/app/assets/javascripts/discourse/initializers/url-redirects.js.es6 index b0e9c79c10..e183bda742 100644 --- a/app/assets/javascripts/discourse/initializers/url-redirects.js.es6 +++ b/app/assets/javascripts/discourse/initializers/url-redirects.js.es6 @@ -12,12 +12,14 @@ export default { DiscourseURL.rewrite(/^\/category\//, "/c/"); DiscourseURL.rewrite(/^\/group\//, "/groups/"); DiscourseURL.rewrite(/\/private-messages\/$/, "/messages/"); + DiscourseURL.rewrite(/^\/users$/, "/u"); + DiscourseURL.rewrite(/^\/users\//, "/u/"); if (currentUser) { const username = currentUser.get('username'); - DiscourseURL.rewrite(new RegExp(`^/users/${username}/?$`, "i"), `/users/${username}/activity`); + DiscourseURL.rewrite(new RegExp(`^/u/${username}/?$`, "i"), `/u/${username}/activity`); } - DiscourseURL.rewrite(/^\/users\/([^\/]+)\/?$/, "/users/$1/activity"); + DiscourseURL.rewrite(/^\/u\/([^\/]+)\/?$/, "/u/$1/summary"); } }; diff --git a/app/assets/javascripts/discourse/lib/discourse-location.js.es6 b/app/assets/javascripts/discourse/lib/discourse-location.js.es6 index 43b74ab1ef..c2e4fe0437 100644 --- a/app/assets/javascripts/discourse/lib/discourse-location.js.es6 +++ b/app/assets/javascripts/discourse/lib/discourse-location.js.es6 @@ -66,12 +66,10 @@ const DiscourseLocation = Ember.Object.extend({ getURL() { const location = get(this, 'location'); let url = location.pathname; - url = url.replace(Discourse.BaseUri, ''); const search = location.search || ''; url += search; - return url; }, diff --git a/app/assets/javascripts/discourse/lib/url.js.es6 b/app/assets/javascripts/discourse/lib/url.js.es6 index e4dbd44f03..f4858447db 100644 --- a/app/assets/javascripts/discourse/lib/url.js.es6 +++ b/app/assets/javascripts/discourse/lib/url.js.es6 @@ -18,6 +18,23 @@ const SERVER_SIDE_ONLY = [ /\.json$/, ]; +export function rewritePath(path) { + const params = path.split("?"); + + let result = params[0]; + rewrites.forEach(rw => result = result.replace(rw.regexp, rw.replacement)); + + if (params.length > 1) { + result += `?${params[1]}`; + } + + return result; +} + +export function clearRewrites() { + rewrites.length = 0; +} + let _jumpScheduled = false; export function jumpToElement(elementId) { if (_jumpScheduled || Ember.isEmpty(elementId)) { return; } @@ -180,8 +197,7 @@ const DiscourseURL = Ember.Object.extend({ } } - rewrites.forEach(rw => path = path.replace(rw.regexp, rw.replacement)); - + path = rewritePath(path); if (this.navigatedToPost(oldPath, path, opts)) { return; } if (oldPath === path) { diff --git a/app/assets/javascripts/discourse/mapping-router.js.es6 b/app/assets/javascripts/discourse/mapping-router.js.es6 index e88b7f746d..e1428039d9 100644 --- a/app/assets/javascripts/discourse/mapping-router.js.es6 +++ b/app/assets/javascripts/discourse/mapping-router.js.es6 @@ -1,4 +1,6 @@ import { defaultHomepage } from 'discourse/lib/utilities'; +import { rewritePath } from 'discourse/lib/url'; + const rootURL = Discourse.BaseUri; const BareRouter = Ember.Router.extend({ @@ -6,6 +8,7 @@ const BareRouter = Ember.Router.extend({ location: Ember.testing ? 'none': 'discourse-location', handleURL(url) { + url = rewritePath(url); const params = url.split('?'); if (params[0] === "/") { @@ -14,6 +17,7 @@ const BareRouter = Ember.Router.extend({ url = `${url}?${params[1]}`; } } + console.log(url); return this._super(url); } }); diff --git a/app/assets/javascripts/discourse/routes/app-route-map.js.es6 b/app/assets/javascripts/discourse/routes/app-route-map.js.es6 index 99a2aa0886..cb0bdb8d5d 100644 --- a/app/assets/javascripts/discourse/routes/app-route-map.js.es6 +++ b/app/assets/javascripts/discourse/routes/app-route-map.js.es6 @@ -62,9 +62,9 @@ export default function() { }); // User routes - this.route('users', { resetNamespace: true }); - this.route('password-reset', { path: '/users/password-reset/:token' }); - this.route('user', { path: '/users/:username', resetNamespace: true }, function() { + this.route('users', { resetNamespace: true, path: '/u' }); + this.route('password-reset', { path: '/u/password-reset/:token' }); + this.route('user', { path: '/u/:username', resetNamespace: true }, function() { this.route('summary'); this.route('userActivity', { path: '/activity', resetNamespace: true }, function() { this.route('topics'); diff --git a/config/routes.rb b/config/routes.rb index 292824ec6e..2a239f1526 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -286,13 +286,6 @@ Discourse::Application.routes.draw do get "session/csrf" => "session#csrf" get "composer_messages" => "composer_messages#index" - resources :users, except: [:show, :update, :destroy] do - collection do - get "check_username" - get "is_local_username" - end - end - resources :static post "login" => "static#enter", constraints: { format: /(json|html)/ } get "login" => "static#show", id: "login", constraints: { format: /(json|html)/ } @@ -304,10 +297,90 @@ Discourse::Application.routes.draw do get "signup" => "static#show", id: "signup", constraints: { format: /(json|html)/ } get "login-preferences" => "static#show", id: "login", constraints: { format: /(json|html)/ } + get "my/*path", to: 'users#my_redirect' + get "user_preferences" => "users#user_preferences_redirect" + + # New /u/ routes + get 'u' => 'users#index' + get 'u/check_username' => 'users#check_username' + get 'u/is_local_username' => 'users#is_local_username' + post 'u' => 'users#create' + + get "u/admin-login" => "users#admin_login" + put "u/admin-login" => "users#admin_login" + get "u/admin-login/:token" => "users#admin_login" + + post "u/toggle-anon" => "users#toggle_anon" + post "u/read-faq" => "users#read_faq" + get "u/search/users" => "users#search_users" + get "u/account-created/" => "users#account_created" + get "u/password-reset/:token" => "users#password_reset" + get "u/confirm-email-token/:token" => "users#confirm_email_token", constraints: { format: 'json' } + put "u/password-reset/:token" => "users#password_reset" + get "u/activate-account/:token" => "users#activate_account" + put "u/activate-account/:token" => "users#perform_account_activation", as: 'perform_activate_account' + get "u/authorize-email/:token" => "users_email#confirm" + get "u/hp" => "users#get_honeypot_value" + + get "u/:username/private-messages" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/private-messages/:filter" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/messages" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/messages/:filter" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/messages/group/:group_name" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT, group_name: USERNAME_ROUTE_FORMAT} + + get "u/:username/messages/group/:group_name/archive" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT, group_name: USERNAME_ROUTE_FORMAT} + + get "u/:username.json" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}, defaults: {format: :json} + get "u/:username" => "users#show", as: 'user', constraints: {username: USERNAME_ROUTE_FORMAT, format: /(json|html)/} + put "u/:username" => "users#update", constraints: {username: USERNAME_ROUTE_FORMAT}, defaults: { format: :json } + get "u/:username/emails" => "users#check_emails", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/preferences" => "users#preferences", constraints: {username: USERNAME_ROUTE_FORMAT}, as: :email_preferences + get "u/:username/preferences/email" => "users_email#index", constraints: {username: USERNAME_ROUTE_FORMAT} + put "u/:username/preferences/email" => "users_email#update", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/preferences/about-me" => "users#preferences", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/preferences/badge_title" => "users#preferences", constraints: {username: USERNAME_ROUTE_FORMAT} + put "u/:username/preferences/badge_title" => "users#badge_title", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/preferences/username" => "users#preferences", constraints: {username: USERNAME_ROUTE_FORMAT} + put "u/:username/preferences/username" => "users#username", constraints: {username: USERNAME_ROUTE_FORMAT} + delete "u/:username/preferences/user_image" => "users#destroy_user_image", constraints: {username: USERNAME_ROUTE_FORMAT} + put "u/:username/preferences/avatar/pick" => "users#pick_avatar", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/preferences/card-badge" => "users#card_badge", constraints: {username: USERNAME_ROUTE_FORMAT} + put "u/:username/preferences/card-badge" => "users#update_card_badge", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/staff-info" => "users#staff_info", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/summary" => "users#summary", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/invited" => "users#invited", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/invited_count" => "users#invited_count", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/invited/:filter" => "users#invited", constraints: {username: USERNAME_ROUTE_FORMAT} + post "u/action/send_activation_email" => "users#send_activation_email" + get "u/:username/summary" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} + + # user activity RSS feed + get "u/:username/activity/topics.rss" => "list#user_topics_feed", format: :rss, constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/activity.rss" => "posts#user_posts_feed", format: :rss, constraints: {username: USERNAME_ROUTE_FORMAT} + + get "u/:username/activity" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/activity/:filter" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/badges" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/notifications" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/notifications/:filter" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/activity/pending" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} + delete "u/:username" => "users#destroy", constraints: {username: USERNAME_ROUTE_FORMAT} + # The external_id constraint is to allow periods to be used in the value without becoming part of the format. ie: foo.bar.json + get "u/by-external/:external_id" => "users#show", constraints: {external_id: /[^\/]+/} + get "u/:username/flagged-posts" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} + get "u/:username/deleted-posts" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} + + get "u/:username/topic-tracking-state" => "users#topic_tracking_state", constraints: {username: USERNAME_ROUTE_FORMAT} + + # Old User routes, will be removed when /u/ is everywhere + get 'users' => 'users#index' + get 'users/check_username' => 'users#check_username' + get 'users/is_local_username' => 'users#is_local_username' + post 'users' => 'users#create' + get "users/admin-login" => "users#admin_login" put "users/admin-login" => "users#admin_login" get "users/admin-login/:token" => "users#admin_login" - post "users/toggle-anon" => "users#toggle_anon" post "users/read-faq" => "users#read_faq" get "users/search/users" => "users#search_users" @@ -316,25 +389,19 @@ Discourse::Application.routes.draw do get "users/confirm-email-token/:token" => "users#confirm_email_token", constraints: { format: 'json' } put "users/password-reset/:token" => "users#password_reset" get "users/activate-account/:token" => "users#activate_account" - put "users/activate-account/:token" => "users#perform_account_activation", as: 'perform_activate_account' + put "users/activate-account/:token" => "users#perform_account_activation" get "users/authorize-email/:token" => "users_email#confirm" - get "users/hp" => "users#get_honeypot_value" - get "my/*path", to: 'users#my_redirect' - - get "user_preferences" => "users#user_preferences_redirect" get "users/:username/private-messages" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/private-messages/:filter" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/messages" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/messages/:filter" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/messages/group/:group_name" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT, group_name: USERNAME_ROUTE_FORMAT} - get "users/:username/messages/group/:group_name/archive" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT, group_name: USERNAME_ROUTE_FORMAT} - get "users/:username.json" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}, defaults: {format: :json} - get "users/:username" => "users#show", as: 'user', constraints: {username: USERNAME_ROUTE_FORMAT, format: /(json|html)/} + get "users/:username" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT, format: /(json|html)/} put "users/:username" => "users#update", constraints: {username: USERNAME_ROUTE_FORMAT}, defaults: { format: :json } get "users/:username/emails" => "users#check_emails", constraints: {username: USERNAME_ROUTE_FORMAT} - get "users/:username/preferences" => "users#preferences", constraints: {username: USERNAME_ROUTE_FORMAT}, as: :email_preferences + get "users/:username/preferences" => "users#preferences", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/preferences/email" => "users_email#index", constraints: {username: USERNAME_ROUTE_FORMAT} put "users/:username/preferences/email" => "users_email#update", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/preferences/about-me" => "users#preferences", constraints: {username: USERNAME_ROUTE_FORMAT} @@ -353,11 +420,8 @@ Discourse::Application.routes.draw do get "users/:username/invited/:filter" => "users#invited", constraints: {username: USERNAME_ROUTE_FORMAT} post "users/action/send_activation_email" => "users#send_activation_email" get "users/:username/summary" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} - - # user activity RSS feed get "users/:username/activity/topics.rss" => "list#user_topics_feed", format: :rss, constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/activity.rss" => "posts#user_posts_feed", format: :rss, constraints: {username: USERNAME_ROUTE_FORMAT} - get "users/:username/activity" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/activity/:filter" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/badges" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} @@ -365,12 +429,11 @@ Discourse::Application.routes.draw do get "users/:username/notifications/:filter" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/activity/pending" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} delete "users/:username" => "users#destroy", constraints: {username: USERNAME_ROUTE_FORMAT} - # The external_id constraint is to allow periods to be used in the value without becoming part of the format. ie: foo.bar.json get "users/by-external/:external_id" => "users#show", constraints: {external_id: /[^\/]+/} get "users/:username/flagged-posts" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/deleted-posts" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} - get "users/:username/topic-tracking-state" => "users#topic_tracking_state", constraints: {username: USERNAME_ROUTE_FORMAT} + # -- end of old users paths get "user-badges/:username.json" => "user_badges#username", constraints: {username: USERNAME_ROUTE_FORMAT}, defaults: {format: :json} get "user-badges/:username" => "user_badges#username", constraints: {username: USERNAME_ROUTE_FORMAT} diff --git a/spec/controllers/email_controller_spec.rb b/spec/controllers/email_controller_spec.rb index 03c0df3c3e..63fd6865ad 100644 --- a/spec/controllers/email_controller_spec.rb +++ b/spec/controllers/email_controller_spec.rb @@ -13,7 +13,7 @@ describe EmailController do it 'redirects to your user preferences' do get :preferences_redirect - expect(response).to redirect_to("/users/#{user.username}/preferences") + expect(response).to redirect_to("/u/#{user.username}/preferences") end end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index d69165c35f..119859d3f5 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -100,7 +100,7 @@ describe UsersController do it "redirects to their profile when logged in" do user = log_in get :user_preferences_redirect - expect(response).to redirect_to("/users/#{user.username_lower}/preferences") + expect(response).to redirect_to("/u/#{user.username_lower}/preferences") end end diff --git a/test/javascripts/acceptance/user-anonymous-test.js.es6 b/test/javascripts/acceptance/user-anonymous-test.js.es6 index 62e458cea5..2d93a45c3b 100644 --- a/test/javascripts/acceptance/user-anonymous-test.js.es6 +++ b/test/javascripts/acceptance/user-anonymous-test.js.es6 @@ -49,6 +49,6 @@ test("Restricted Routes", () => { visit("/users/eviltrout/preferences"); andThen(() => { - equal(currentURL(), '/users/eviltrout/activity', "it redirects from preferences"); + equal(currentURL(), '/u/eviltrout/activity', "it redirects from preferences"); }); }); diff --git a/test/javascripts/helpers/qunit-helpers.js.es6 b/test/javascripts/helpers/qunit-helpers.js.es6 index 1fdc796b69..32747f9c5a 100644 --- a/test/javascripts/helpers/qunit-helpers.js.es6 +++ b/test/javascripts/helpers/qunit-helpers.js.es6 @@ -8,6 +8,7 @@ import { resetPluginApi } from 'discourse/lib/plugin-api'; import { clearCache as clearOutletCache, resetExtraClasses } from 'discourse/lib/plugin-connectors'; import { clearHTMLCache } from 'discourse/helpers/custom-html'; import { flushMap } from 'discourse/models/store'; +import { clearRewrites } from 'discourse/lib/url'; function currentUser() { @@ -88,6 +89,7 @@ function acceptance(name, options) { clearOutletCache(); clearHTMLCache(); resetPluginApi(); + clearRewrites(); Discourse.reset(); } }); From 45a257815a0181059de91101fa4f9c6f8aa9b3c7 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 28 Mar 2017 12:16:58 -0400 Subject: [PATCH 021/124] Convert front end paths from `/users/` to `/u/` --- .../admin/controllers/admin-user-index.js.es6 | 3 +- .../discourse/components/small-action.js.es6 | 3 +- .../components/user-card-contents.js.es6 | 3 +- .../discourse/components/user-info.js.es6 | 8 +++-- .../controllers/create-account.js.es6 | 5 +-- .../controllers/discovery/topics.js.es6 | 5 +-- .../controllers/not-activated.js.es6 | 3 +- .../controllers/password-reset.js.es6 | 3 +- .../controllers/preferences/username.js.es6 | 3 +- .../discourse/controllers/static.js.es6 | 3 +- .../discourse/controllers/topic.js.es6 | 3 +- .../discourse/lib/link-mentions.js.es6 | 5 +-- .../javascripts/discourse/lib/search.js.es6 | 3 +- .../discourse/lib/transform-post.js.es6 | 4 ++- .../javascripts/discourse/lib/url.js.es6 | 6 +++- .../discourse/lib/user-search.js.es6 | 3 +- .../discourse/lib/utilities.js.es6 | 4 --- .../discourse/mapping-router.js.es6 | 1 - .../discourse/models/invite.js.es6 | 5 +-- .../javascripts/discourse/models/post.js.es6 | 8 +++-- .../javascripts/discourse/models/topic.js.es6 | 8 +++-- .../discourse/models/user-action.js.es6 | 13 +++++-- .../javascripts/discourse/models/user.js.es6 | 34 +++++++++---------- .../discourse/routes/application.js.es6 | 3 +- .../discourse/routes/password-reset.js.es6 | 3 +- .../discourse/widgets/actions-summary.js.es6 | 3 +- .../discourse/widgets/hamburger-menu.js.es6 | 3 +- .../widgets/notification-item.js.es6 | 5 +-- .../discourse-markdown/mentions.js.es6 | 2 +- app/models/post_analyzer.rb | 3 +- app/views/users/activate_account.html.erb | 2 +- config/routes.rb | 3 +- spec/models/post_spec.rb | 2 +- .../acceptance/password-reset-test.js.es6 | 4 +-- .../acceptance/search-full-test.js.es6 | 2 +- .../javascripts/fixtures/user_fixtures.js.es6 | 2 +- .../helpers/create-pretender.js.es6 | 24 ++++++------- test/javascripts/lib/pretty-text-test.js.es6 | 6 ++-- test/javascripts/lib/url-test.js.es6 | 8 ++++- test/javascripts/lib/user-search-test.js.es6 | 2 +- 40 files changed, 129 insertions(+), 84 deletions(-) diff --git a/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 index 14bc031c0a..91717cf8f0 100644 --- a/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 @@ -1,6 +1,7 @@ import { ajax } from 'discourse/lib/ajax'; import CanCheckEmails from 'discourse/mixins/can-check-emails'; import { propertyNotEqual, setting } from 'discourse/lib/computed'; +import { userPath } from 'discourse/lib/url'; export default Ember.Controller.extend(CanCheckEmails, { editingTitle: false, @@ -62,7 +63,7 @@ export default Ember.Controller.extend(CanCheckEmails, { saveTitle() { const self = this; - return ajax(`/users/${this.get('model.username').toLowerCase()}.json`, { + return ajax(userPath(`${this.get('model.username').toLowerCase()}.json`), { data: {title: this.get('userTitleValue')}, type: 'PUT' }).catch(function(e) { diff --git a/app/assets/javascripts/discourse/components/small-action.js.es6 b/app/assets/javascripts/discourse/components/small-action.js.es6 index 8a17c41031..114d35801c 100644 --- a/app/assets/javascripts/discourse/components/small-action.js.es6 +++ b/app/assets/javascripts/discourse/components/small-action.js.es6 @@ -1,4 +1,5 @@ import { autoUpdatingRelativeAge } from 'discourse/lib/formatter'; +import { userPath } from 'discourse/lib/url'; export function actionDescriptionHtml(actionCode, createdAt, username) { const dt = new Date(createdAt); @@ -9,7 +10,7 @@ export function actionDescriptionHtml(actionCode, createdAt, username) { if (actionCode === "invited_group" || actionCode === "removed_group") { who = `@${username}`; } else { - who = `@${username}`; + who = `@${username}`; } } return I18n.t(`action_codes.${actionCode}`, { who, when }).htmlSafe(); diff --git a/app/assets/javascripts/discourse/components/user-card-contents.js.es6 b/app/assets/javascripts/discourse/components/user-card-contents.js.es6 index 6aefcaa2f2..969a6a931c 100644 --- a/app/assets/javascripts/discourse/components/user-card-contents.js.es6 +++ b/app/assets/javascripts/discourse/components/user-card-contents.js.es6 @@ -5,6 +5,7 @@ import afterTransition from 'discourse/lib/after-transition'; import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; import DiscourseURL from 'discourse/lib/url'; import User from 'discourse/models/user'; +import { userPath } from 'discourse/lib/url'; const clickOutsideEventName = "mousedown.outside-user-card"; const clickDataExpand = "click.discourse-user-card"; @@ -92,7 +93,7 @@ export default Ember.Component.extend(CleansUp, { // Don't show on mobile if (this.site.mobileView) { - DiscourseURL.routeTo(`/users/${username}`); + DiscourseURL.routeTo(userPath(username)); return false; } diff --git a/app/assets/javascripts/discourse/components/user-info.js.es6 b/app/assets/javascripts/discourse/components/user-info.js.es6 index 05dde9fffe..3293e4d46e 100644 --- a/app/assets/javascripts/discourse/components/user-info.js.es6 +++ b/app/assets/javascripts/discourse/components/user-info.js.es6 @@ -1,5 +1,5 @@ -import { url } from 'discourse/lib/computed'; import computed from 'ember-addons/ember-computed-decorators'; +import { userPath } from 'discourse/lib/url'; function normalize(name) { return name.replace(/[\-\_ \.]/g, '').toLowerCase(); @@ -8,7 +8,11 @@ function normalize(name) { export default Ember.Component.extend({ classNameBindings: [':user-info', 'size'], size: 'small', - userPath: url('user.username', '/users/%@'), + + @computed('user.username') + userPath(username) { + return userPath(username); + }, // TODO: In later ember releases `hasBlock` works without this hasBlock: Ember.computed.alias('template'), diff --git a/app/assets/javascripts/discourse/controllers/create-account.js.es6 b/app/assets/javascripts/discourse/controllers/create-account.js.es6 index d04a03821c..d77a4db489 100644 --- a/app/assets/javascripts/discourse/controllers/create-account.js.es6 +++ b/app/assets/javascripts/discourse/controllers/create-account.js.es6 @@ -6,6 +6,7 @@ import { emailValid } from 'discourse/lib/utilities'; import InputValidation from 'discourse/models/input-validation'; import PasswordValidation from "discourse/mixins/password-validation"; import UsernameValidation from "discourse/mixins/username-validation"; +import { userPath } from 'discourse/lib/url'; export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, UsernameValidation, { login: Ember.inject.controller(), @@ -164,7 +165,7 @@ export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, U @on('init') fetchConfirmationValue() { - return ajax('/users/hp.json').then(json => { + return ajax(userPath('hp.json')).then(json => { this.set('accountPasswordConfirm', json.value); this.set('accountChallenge', json.challenge.split("").reverse().join("")); }); @@ -196,7 +197,7 @@ export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, U const $hidden_login_form = $('#hidden-login-form'); $hidden_login_form.find('input[name=username]').val(attrs.accountUsername); $hidden_login_form.find('input[name=password]').val(attrs.accountPassword); - $hidden_login_form.find('input[name=redirect]').val(Discourse.getURL('/users/account-created')); + $hidden_login_form.find('input[name=redirect]').val(userPath('account-created')); $hidden_login_form.submit(); } else { self.flash(result.message || I18n.t('create_account.failed'), 'error'); diff --git a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 index 516c53daf4..706ee73241 100644 --- a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 @@ -3,6 +3,7 @@ import { queryParams } from 'discourse/controllers/discovery-sortable'; import BulkTopicSelection from 'discourse/mixins/bulk-topic-selection'; import { endWith } from 'discourse/lib/computed'; import showModal from 'discourse/lib/show-modal'; +import { userPath } from 'discourse/lib/url'; const controllerOpts = { discovery: Ember.inject.controller(), @@ -133,14 +134,14 @@ const controllerOpts = { }.property('allLoaded', 'model.topics.length'), footerEducation: function() { - if (!this.get('allLoaded') || this.get('model.topics.length') > 0 || !Discourse.User.current()) { return; } + if (!this.get('allLoaded') || this.get('model.topics.length') > 0 || !this.currentUser) { return; } const split = (this.get('model.filter') || '').split('/'); if (split[0] !== 'new' && split[0] !== 'unread') { return; } return I18n.t("topics.none.educate." + split[0], { - userPrefsUrl: Discourse.getURL("/users/") + (Discourse.User.currentProp("username_lower")) + "/preferences" + userPrefsUrl: userPath(`${this.currentUser.get('username_lower')}/preferences`) }); }.property('allLoaded', 'model.topics.length') diff --git a/app/assets/javascripts/discourse/controllers/not-activated.js.es6 b/app/assets/javascripts/discourse/controllers/not-activated.js.es6 index 86afe2ddd7..cf0164b0e6 100644 --- a/app/assets/javascripts/discourse/controllers/not-activated.js.es6 +++ b/app/assets/javascripts/discourse/controllers/not-activated.js.es6 @@ -1,6 +1,7 @@ import { ajax } from 'discourse/lib/ajax'; import { popupAjaxError } from 'discourse/lib/ajax-error'; import ModalFunctionality from 'discourse/mixins/modal-functionality'; +import { userPath } from 'discourse/lib/url'; export default Ember.Controller.extend(ModalFunctionality, { emailSent: false, @@ -11,7 +12,7 @@ export default Ember.Controller.extend(ModalFunctionality, { actions: { sendActivationEmail() { - ajax('/users/action/send_activation_email', { + ajax(userPath('action/send_activation_email'), { data: { username: this.get('username') }, type: 'POST' }).then(() => { diff --git a/app/assets/javascripts/discourse/controllers/password-reset.js.es6 b/app/assets/javascripts/discourse/controllers/password-reset.js.es6 index 34b911c23d..e7694a2301 100644 --- a/app/assets/javascripts/discourse/controllers/password-reset.js.es6 +++ b/app/assets/javascripts/discourse/controllers/password-reset.js.es6 @@ -3,6 +3,7 @@ import getUrl from 'discourse-common/lib/get-url'; import DiscourseURL from 'discourse/lib/url'; import { ajax } from 'discourse/lib/ajax'; import PasswordValidation from "discourse/mixins/password-validation"; +import { userPath } from 'discourse/lib/url'; export default Ember.Controller.extend(PasswordValidation, { isDeveloper: Ember.computed.alias('model.is_developer'), @@ -27,7 +28,7 @@ export default Ember.Controller.extend(PasswordValidation, { actions: { submit() { ajax({ - url: `/users/password-reset/${this.get('model.token')}.json`, + url: userPath(`password-reset/${this.get('model.token')}.json`), type: 'PUT', data: { password: this.get('accountPassword') diff --git a/app/assets/javascripts/discourse/controllers/preferences/username.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/username.js.es6 index a597df810a..f6cfa7be70 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/username.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/username.js.es6 @@ -1,5 +1,6 @@ import { setting, propertyEqual } from 'discourse/lib/computed'; import DiscourseURL from 'discourse/lib/url'; +import { userPath } from 'discourse/lib/url'; export default Ember.Controller.extend({ taken: false, @@ -48,7 +49,7 @@ export default Ember.Controller.extend({ if (result) { this.set('saving', true); this.get('content').changeUsername(this.get('newUsername')).then(() => { - DiscourseURL.redirectTo("/users/" + this.get('newUsername').toLowerCase() + "/preferences"); + DiscourseURL.redirectTo(userPath(this.get('newUsername').toLowerCase() + "/preferences")); }) .catch(() => this.set('error', true)) .finally(() => this.set('saving', false)); diff --git a/app/assets/javascripts/discourse/controllers/static.js.es6 b/app/assets/javascripts/discourse/controllers/static.js.es6 index 166a0676a7..369c6bddf5 100644 --- a/app/assets/javascripts/discourse/controllers/static.js.es6 +++ b/app/assets/javascripts/discourse/controllers/static.js.es6 @@ -1,5 +1,6 @@ import { ajax } from 'discourse/lib/ajax'; import computed from 'ember-addons/ember-computed-decorators'; +import { userPath } from 'discourse/lib/url'; export default Ember.Controller.extend({ application: Ember.inject.controller(), @@ -18,7 +19,7 @@ export default Ember.Controller.extend({ markFaqRead() { const currentUser = this.currentUser; if (currentUser) { - ajax("/users/read-faq", { method: "POST" }).then(() => { + ajax(userPath("read-faq"), { method: "POST" }).then(() => { currentUser.set('read_faq', true); }); } diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index 32b824fc25..7d1c98142b 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -12,6 +12,7 @@ import Post from 'discourse/models/post'; import debounce from 'discourse/lib/debounce'; import isElementInViewport from "discourse/lib/is-element-in-viewport"; import QuoteState from 'discourse/lib/quote-state'; +import { userPath } from 'discourse/lib/url'; export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { composer: Ember.inject.controller(), @@ -126,7 +127,7 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { showCategoryChooser: Ember.computed.not("model.isPrivateMessage"), gotoInbox(name) { - var url = '/users/' + this.get('currentUser.username_lower') + '/messages'; + let url = userPath(this.get('currentUser.username_lower') + '/messages'); if (name) { url = url + '/group/' + name; } diff --git a/app/assets/javascripts/discourse/lib/link-mentions.js.es6 b/app/assets/javascripts/discourse/lib/link-mentions.js.es6 index 20876bd72e..d8cd970299 100644 --- a/app/assets/javascripts/discourse/lib/link-mentions.js.es6 +++ b/app/assets/javascripts/discourse/lib/link-mentions.js.es6 @@ -1,4 +1,5 @@ import { ajax } from 'discourse/lib/ajax'; +import { userPath } from 'discourse/lib/url'; function replaceSpan($e, username, opts) { let extra = ""; @@ -15,7 +16,7 @@ function replaceSpan($e, username, opts) { extra = `data-name='${username}'`; extraClass = "cannot-see"; } - $e.replaceWith(`@${username}`); + $e.replaceWith(`@${username}`); } } @@ -54,7 +55,7 @@ export function linkSeenMentions($elem, siteSettings) { // 'Create a New Topic' scenario is not supported (per conversation with codinghorror) // https://meta.discourse.org/t/taking-another-1-7-release-task/51986/7 export function fetchUnseenMentions(usernames, topic_id) { - return ajax("/users/is_local_username", { data: { usernames, topic_id } }).then(r => { + return ajax(userPath("is_local_username"), { data: { usernames, topic_id } }).then(r => { r.valid.forEach(v => found[v] = true); r.valid_groups.forEach(vg => foundGroups[vg] = true); r.mentionable_groups.forEach(mg => mentionableGroups[mg.name] = mg); diff --git a/app/assets/javascripts/discourse/lib/search.js.es6 b/app/assets/javascripts/discourse/lib/search.js.es6 index 0824f8edca..30ab32629f 100644 --- a/app/assets/javascripts/discourse/lib/search.js.es6 +++ b/app/assets/javascripts/discourse/lib/search.js.es6 @@ -5,6 +5,7 @@ import { SEPARATOR } from 'discourse/lib/category-hashtags'; import Category from 'discourse/models/category'; import { search as searchCategoryTag } from 'discourse/lib/category-tag-search'; import userSearch from 'discourse/lib/user-search'; +import { userPath } from 'discourse/lib/url'; export function translateResults(results, opts) { @@ -29,7 +30,7 @@ export function translateResults(results, opts) { results.posts = results.posts.map(post => { if (post.username) { - post.userPath = Discourse.getURL(`/users/${post.username.toLowerCase()}`); + post.userPath = userPath(post.username.toLowerCase()); } post = Post.create(post); post.set('topic', topicMap[post.topic_id]); diff --git a/app/assets/javascripts/discourse/lib/transform-post.js.es6 b/app/assets/javascripts/discourse/lib/transform-post.js.es6 index d9742501d6..7df3954b30 100644 --- a/app/assets/javascripts/discourse/lib/transform-post.js.es6 +++ b/app/assets/javascripts/discourse/lib/transform-post.js.es6 @@ -1,3 +1,5 @@ +import { userPath } from 'discourse/lib/url'; + function actionDescription(action, acted, count) { if (acted) { if (count <= 1) { @@ -39,7 +41,7 @@ export function transformBasicPost(post) { via_email: post.via_email, isAutoGenerated: post.is_auto_generated, user_id: post.user_id, - usernameUrl: Discourse.getURL(`/users/${post.username}`), + usernameUrl: userPath(post.username), username: post.username, avatar_template: post.avatar_template, bookmarked: post.bookmarked, diff --git a/app/assets/javascripts/discourse/lib/url.js.es6 b/app/assets/javascripts/discourse/lib/url.js.es6 index f4858447db..80dbdef6d2 100644 --- a/app/assets/javascripts/discourse/lib/url.js.es6 +++ b/app/assets/javascripts/discourse/lib/url.js.es6 @@ -35,6 +35,10 @@ export function clearRewrites() { rewrites.length = 0; } +export function userPath(subPath) { + return Discourse.getURL(subPath ? `/u/${subPath}` : '/u'); +} + let _jumpScheduled = false; export function jumpToElement(elementId) { if (_jumpScheduled || Ember.isEmpty(elementId)) { return; } @@ -190,7 +194,7 @@ const DiscourseURL = Ember.Object.extend({ if (path.indexOf('/my/') === 0) { const currentUser = Discourse.User.current(); if (currentUser) { - path = path.replace('/my/', '/users/' + currentUser.get('username_lower') + "/"); + path = path.replace('/my/', userPath(currentUser.get('username_lower') + "/")); } else { document.location.href = "/404"; return; diff --git a/app/assets/javascripts/discourse/lib/user-search.js.es6 b/app/assets/javascripts/discourse/lib/user-search.js.es6 index 6f99fa0699..36612631f5 100644 --- a/app/assets/javascripts/discourse/lib/user-search.js.es6 +++ b/app/assets/javascripts/discourse/lib/user-search.js.es6 @@ -1,4 +1,5 @@ import { CANCELLED_STATUS } from 'discourse/lib/autocomplete'; +import { userPath } from 'discourse/lib/url'; var cache = {}, cacheTopicId, @@ -14,7 +15,7 @@ function performSearch(term, topicId, includeGroups, includeMentionableGroups, a } // need to be able to cancel this - oldSearch = $.ajax(Discourse.getURL('/users/search/users'), { + oldSearch = $.ajax(userPath('search/users'), { data: { term: term, topic_id: topicId, include_groups: includeGroups, diff --git a/app/assets/javascripts/discourse/lib/utilities.js.es6 b/app/assets/javascripts/discourse/lib/utilities.js.es6 index 67f795bf3d..d80b84cabc 100644 --- a/app/assets/javascripts/discourse/lib/utilities.js.es6 +++ b/app/assets/javascripts/discourse/lib/utilities.js.es6 @@ -65,10 +65,6 @@ export function postUrl(slug, topicId, postNumber) { return url; } -export function userUrl(username) { - return Discourse.getURL("/users/" + username.toLowerCase()); -} - export function emailValid(email) { // see: http://stackoverflow.com/questions/46155/validate-email-address-in-javascript const re = /^[a-zA-Z0-9!#$%&'*+\/=?\^_`{|}~\-]+(?:\.[a-zA-Z0-9!#$%&'\*+\/=?\^_`{|}~\-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?$/; diff --git a/app/assets/javascripts/discourse/mapping-router.js.es6 b/app/assets/javascripts/discourse/mapping-router.js.es6 index e1428039d9..6d30be749c 100644 --- a/app/assets/javascripts/discourse/mapping-router.js.es6 +++ b/app/assets/javascripts/discourse/mapping-router.js.es6 @@ -17,7 +17,6 @@ const BareRouter = Ember.Router.extend({ url = `${url}?${params[1]}`; } } - console.log(url); return this._super(url); } }); diff --git a/app/assets/javascripts/discourse/models/invite.js.es6 b/app/assets/javascripts/discourse/models/invite.js.es6 index 8f89cfc684..1425e63b25 100644 --- a/app/assets/javascripts/discourse/models/invite.js.es6 +++ b/app/assets/javascripts/discourse/models/invite.js.es6 @@ -1,5 +1,6 @@ import { ajax } from 'discourse/lib/ajax'; import { popupAjaxError } from 'discourse/lib/ajax-error'; +import { userPath } from 'discourse/lib/url'; const Invite = Discourse.Model.extend({ @@ -41,7 +42,7 @@ Invite.reopenClass({ if (!Em.isNone(search)) { data.search = search; } data.offset = offset || 0; - return ajax("/users/" + user.get('username_lower') + "/invited.json", {data}).then(function (result) { + return ajax(userPath(user.get('username_lower') + "/invited.json"), {data}).then(function (result) { result.invites = result.invites.map(function (i) { return Invite.create(i); }); @@ -52,7 +53,7 @@ Invite.reopenClass({ findInvitedCount(user) { if (!user) { return Em.RSVP.resolve(); } - return ajax("/users/" + user.get('username_lower') + "/invited_count.json").then(result => Em.Object.create(result.counts)); + return ajax(userPath(user.get('username_lower') + "/invited_count.json")).then(result => Em.Object.create(result.counts)); }, reinviteAll() { diff --git a/app/assets/javascripts/discourse/models/post.js.es6 b/app/assets/javascripts/discourse/models/post.js.es6 index 127b81f861..7800d3be38 100644 --- a/app/assets/javascripts/discourse/models/post.js.es6 +++ b/app/assets/javascripts/discourse/models/post.js.es6 @@ -2,11 +2,12 @@ import { ajax } from 'discourse/lib/ajax'; import RestModel from 'discourse/models/rest'; import { popupAjaxError } from 'discourse/lib/ajax-error'; import ActionSummary from 'discourse/models/action-summary'; -import { url, propertyEqual } from 'discourse/lib/computed'; +import { propertyEqual } from 'discourse/lib/computed'; import Quote from 'discourse/lib/quote'; import computed from 'ember-addons/ember-computed-decorators'; import { postUrl } from 'discourse/lib/utilities'; import { cook } from 'discourse/lib/text'; +import { userPath } from 'discourse/lib/url'; const Post = RestModel.extend({ @@ -60,7 +61,10 @@ const Post = RestModel.extend({ return postNumber === 1 ? baseUrl + "/1" : baseUrl; }, - usernameUrl: url('username', '/users/%@'), + @computed('username') + usernameUrl(username) { + return userPath(username); + }, topicOwner: propertyEqual('topic.details.created_by.id', 'user_id'), diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index 139b2ddcf7..84545aa9ea 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -9,6 +9,7 @@ import { popupAjaxError } from 'discourse/lib/ajax-error'; import { censor } from 'pretty-text/censored-words'; import { emojiUnescape } from 'discourse/lib/text'; import PreloadStore from 'preload-store'; +import { userPath } from 'discourse/lib/url'; export function loadTopicView(topic, args) { const topicId = topic.get('id'); @@ -182,9 +183,10 @@ const Topic = RestModel.extend({ return this.urlForPostNumber(1) + (this.get('has_summary') ? "?filter=summary" : ""); }.property('url'), - lastPosterUrl: function() { - return Discourse.getURL("/users/") + this.get("last_poster.username"); - }.property('last_poster'), + @computed('last_poster.username') + lastPosterUrl(username) { + return userPath(username); + }, // The amount of new posts to display. It might be different than what the server // tells us if we are still asynchronously flushing our "recently read" data. diff --git a/app/assets/javascripts/discourse/models/user-action.js.es6 b/app/assets/javascripts/discourse/models/user-action.js.es6 index ebc4be7381..c4d5b7a161 100644 --- a/app/assets/javascripts/discourse/models/user-action.js.es6 +++ b/app/assets/javascripts/discourse/models/user-action.js.es6 @@ -1,9 +1,9 @@ import RestModel from 'discourse/models/rest'; -import { url } from 'discourse/lib/computed'; import { on } from 'ember-addons/ember-computed-decorators'; import computed from 'ember-addons/ember-computed-decorators'; import UserActionGroup from 'discourse/models/user-action-group'; import { postUrl } from 'discourse/lib/utilities'; +import { userPath } from 'discourse/lib/url'; const UserActionTypes = { likes_given: 1, @@ -79,14 +79,21 @@ const UserAction = RestModel.extend({ presentName: Ember.computed.or('name', 'username'), targetDisplayName: Ember.computed.or('target_name', 'target_username'), actingDisplayName: Ember.computed.or('acting_name', 'acting_username'), - targetUserUrl: url('target_username', '/users/%@'), + + @computed('target_username') + targetUserUrl(username) { + return userPath(username); + }, @computed("username") usernameLower(username) { return username.toLowerCase(); }, - userUrl: url('usernameLower', '/users/%@'), + @computed('usernameLower') + userUrl(usernameLower) { + return userPath(usernameLower); + }, @computed() postUrl() { diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6 index 5086c54cbc..e2589b8e47 100644 --- a/app/assets/javascripts/discourse/models/user.js.es6 +++ b/app/assets/javascripts/discourse/models/user.js.es6 @@ -15,6 +15,7 @@ import Topic from 'discourse/models/topic'; import { emojiUnescape } from 'discourse/lib/text'; import PreloadStore from 'preload-store'; import { defaultHomepage } from 'discourse/lib/utilities'; +import { userPath } from 'discourse/lib/url'; const User = RestModel.extend({ @@ -71,7 +72,7 @@ const User = RestModel.extend({ @computed() path() { // no need to observe, requires a hard refresh to update - return Discourse.getURL(`/users/${this.get('username_lower')}`); + return userPath(this.get('username_lower')); }, @computed() @@ -124,11 +125,10 @@ const User = RestModel.extend({ // directly targetted so go to inbox if (!groups || (allowedUsers && allowedUsers.findBy("id", userId))) { - return Discourse.getURL(`/users/${username}/messages`); + return userPath(`${username}/messages`); } else { - if (groups && groups[0]) - { - return Discourse.getURL(`/users/${username}/messages/group/${groups[0].name}`); + if (groups && groups[0]) { + return userPath(`${username}/messages/group/${groups[0].name}`); } } @@ -179,14 +179,14 @@ const User = RestModel.extend({ }, changeUsername(new_username) { - return ajax(`/users/${this.get('username_lower')}/preferences/username`, { + return ajax(userPath(`${this.get('username_lower')}/preferences/username`), { type: 'PUT', data: { new_username } }); }, changeEmail(email) { - return ajax(`/users/${this.get('username_lower')}/preferences/email`, { + return ajax(userPath(`${this.get('username_lower')}/preferences/email`), { type: 'PUT', data: { email } }); @@ -254,7 +254,7 @@ const User = RestModel.extend({ // TODO: We can remove this when migrated fully to rest model. this.set('isSaving', true); - return ajax(`/users/${this.get('username_lower')}.json`, { + return ajax(userPath(`${this.get('username_lower')}.json`), { data: data, type: 'PUT' }).then(result => { @@ -330,7 +330,7 @@ const User = RestModel.extend({ const user = this; return PreloadStore.getAndRemove(`user_${user.get('username')}`, () => { - return ajax(`/users/${user.get('username')}.json`, { data: options }); + return ajax(userPath(`${user.get('username')}.json`), { data: options }); }).then(json => { if (!Em.isEmpty(json.user.stats)) { @@ -375,13 +375,13 @@ const User = RestModel.extend({ findStaffInfo() { if (!Discourse.User.currentProp("staff")) { return Ember.RSVP.resolve(null); } - return ajax(`/users/${this.get("username_lower")}/staff-info.json`).then(info => { + return ajax(userPath(`${this.get("username_lower")}/staff-info.json`)).then(info => { this.setProperties(info); }); }, pickAvatar(upload_id, type, avatar_template) { - return ajax(`/users/${this.get("username_lower")}/preferences/avatar/pick`, { + return ajax(userPath(`${this.get("username_lower")}/preferences/avatar/pick`), { type: 'PUT', data: { upload_id, type } }).then(() => this.setProperties({ @@ -437,7 +437,7 @@ const User = RestModel.extend({ "delete": function() { if (this.get('can_delete_account')) { - return ajax("/users/" + this.get('username'), { + return ajax(userPath(this.get('username')), { type: 'DELETE', data: {context: window.location.pathname} }); @@ -448,14 +448,14 @@ const User = RestModel.extend({ dismissBanner(bannerKey) { this.set("dismissed_banner_key", bannerKey); - ajax(`/users/${this.get('username')}`, { + ajax(userPath(this.get('username')), { type: 'PUT', data: { dismissed_banner_key: bannerKey } }); }, checkEmail() { - return ajax(`/users/${this.get("username_lower")}/emails.json`, { + return ajax(userPath(`${this.get("username_lower")}/emails.json`), { data: { context: window.location.pathname } }).then(result => { if (result) { @@ -468,7 +468,7 @@ const User = RestModel.extend({ }, summary() { - return ajax(`/users/${this.get("username_lower")}/summary.json`) + return ajax(userPath(`${this.get("username_lower")}/summary.json`)) .then(json => { const summary = json["user_summary"]; const topicMap = {}; @@ -526,7 +526,7 @@ User.reopenClass(Singleton, { }, checkUsername(username, email, for_user_id) { - return ajax('/users/check_username', { + return ajax(userPath('check_username'), { data: { username, email, for_user_id } }); }, @@ -557,7 +557,7 @@ User.reopenClass(Singleton, { }, createAccount(attrs) { - return ajax("/users", { + return ajax(userPath(), { data: { name: attrs.accountName, email: attrs.accountEmail, diff --git a/app/assets/javascripts/discourse/routes/application.js.es6 b/app/assets/javascripts/discourse/routes/application.js.es6 index 1c3fed9a6b..2c800ff199 100644 --- a/app/assets/javascripts/discourse/routes/application.js.es6 +++ b/app/assets/javascripts/discourse/routes/application.js.es6 @@ -7,6 +7,7 @@ import Category from 'discourse/models/category'; import mobile from 'discourse/lib/mobile'; import { findAll } from 'discourse/models/login-method'; import { getOwner } from 'discourse-common/lib/get-owner'; +import { userPath } from 'discourse/lib/url'; function unlessReadOnly(method, message) { return function() { @@ -23,7 +24,7 @@ const ApplicationRoute = Discourse.Route.extend(OpenComposer, { actions: { toggleAnonymous() { - ajax("/users/toggle-anon", {method: 'POST'}).then(() => { + ajax(userPath("toggle-anon"), {method: 'POST'}).then(() => { window.location.reload(); }); }, diff --git a/app/assets/javascripts/discourse/routes/password-reset.js.es6 b/app/assets/javascripts/discourse/routes/password-reset.js.es6 index 1f4cf2102c..ff5fde0fad 100644 --- a/app/assets/javascripts/discourse/routes/password-reset.js.es6 +++ b/app/assets/javascripts/discourse/routes/password-reset.js.es6 @@ -1,5 +1,6 @@ import PreloadStore from 'preload-store'; import { ajax } from 'discourse/lib/ajax'; +import { userPath } from 'discourse/lib/url'; export default Discourse.Route.extend({ titleToken() { @@ -15,7 +16,7 @@ export default Discourse.Route.extend({ afterModel(model) { // confirm token here so email clients who crawl URLs don't invalidate the link if (model) { - return ajax({ url: `/users/confirm-email-token/${model.token}.json`, dataType: 'json' }); + return ajax({ url: userPath(`confirm-email-token/${model.token}.json`), dataType: 'json' }); } } }); diff --git a/app/assets/javascripts/discourse/widgets/actions-summary.js.es6 b/app/assets/javascripts/discourse/widgets/actions-summary.js.es6 index bf09613c73..df852621b9 100644 --- a/app/assets/javascripts/discourse/widgets/actions-summary.js.es6 +++ b/app/assets/javascripts/discourse/widgets/actions-summary.js.es6 @@ -3,12 +3,13 @@ import { avatarFor } from 'discourse/widgets/post'; import { iconNode } from 'discourse/helpers/fa-icon-node'; import { h } from 'virtual-dom'; import { dateNode } from 'discourse/helpers/node'; +import { userPath } from 'discourse/lib/url'; export function avatarAtts(user) { return { template: user.avatar_template, username: user.username, post_url: user.post_url, - url: Discourse.getURL('/users/') + user.username_lower }; + url: userPath(user.username_lower) }; } createWidget('small-user-list', { diff --git a/app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6 b/app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6 index 7b95c36328..3e9979cad1 100644 --- a/app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6 +++ b/app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6 @@ -2,6 +2,7 @@ import { createWidget, applyDecorators } from 'discourse/widgets/widget'; import { h } from 'virtual-dom'; import DiscourseURL from 'discourse/lib/url'; import { ajax } from 'discourse/lib/ajax'; +import { userPath } from 'discourse/lib/url'; const flatten = array => [].concat.apply([], array); @@ -19,7 +20,7 @@ createWidget('priority-faq-link', { click(e) { e.preventDefault(); if (this.siteSettings.faq_url === this.attrs.href) { - ajax("/users/read-faq", { method: "POST" }).then(() => { + ajax(userPath("read-faq"), { method: "POST" }).then(() => { this.currentUser.set('read_faq', true); DiscourseURL.routeToTag($(e.target).closest('a')[0]); }); diff --git a/app/assets/javascripts/discourse/widgets/notification-item.js.es6 b/app/assets/javascripts/discourse/widgets/notification-item.js.es6 index 7cb5ce028a..187a090ce0 100644 --- a/app/assets/javascripts/discourse/widgets/notification-item.js.es6 +++ b/app/assets/javascripts/discourse/widgets/notification-item.js.es6 @@ -6,6 +6,7 @@ import { h } from 'virtual-dom'; import { emojiUnescape } from 'discourse/lib/text'; import { postUrl, escapeExpression } from 'discourse/lib/utilities'; import { setTransientHeader } from 'discourse/lib/ajax'; +import { userPath } from 'discourse/lib/url'; const LIKED_TYPE = 5; const INVITED_TYPE = 8; @@ -45,11 +46,11 @@ createWidget('notification-item', { } if (attrs.notification_type === INVITED_TYPE) { - return Discourse.getURL('/users/' + data.display_username); + return userPath(data.display_username); } if (data.group_id) { - return Discourse.getURL('/users/' + data.username + '/messages/group/' + data.group_name); + return userPath(data.username + '/messages/group/' + data.group_name); } }, diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/mentions.js.es6 b/app/assets/javascripts/pretty-text/engines/discourse-markdown/mentions.js.es6 index 177c322cca..115f58997d 100644 --- a/app/assets/javascripts/pretty-text/engines/discourse-markdown/mentions.js.es6 +++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown/mentions.js.es6 @@ -38,7 +38,7 @@ export function setup(helper) { const type = mentionLookup && mentionLookup(name); if (type === "user") { - return ['a', {'class': 'mention', href: opts.getURL("/users/") + name.toLowerCase()}, mention]; + return ['a', {'class': 'mention', href: opts.getURL("/u/") + name.toLowerCase()}, mention]; } else if (type === "group") { return ['a', {'class': 'mention-group', href: opts.getURL("/groups/") + name}, mention]; } else { diff --git a/app/models/post_analyzer.rb b/app/models/post_analyzer.rb index 09f7cd9932..53296c8e59 100644 --- a/app/models/post_analyzer.rb +++ b/app/models/post_analyzer.rb @@ -124,7 +124,8 @@ class PostAnalyzer def link_is_a_mention?(l) html_class = l['class'] return false if html_class.blank? - html_class.to_s['mention'] && l['href'].to_s[/^\/users\//] + href = l['href'].to_s + html_class.to_s['mention'] && href[/^\/u\//] || href[/^\/users\//] end end diff --git a/app/views/users/activate_account.html.erb b/app/views/users/activate_account.html.erb index 6723d66ed5..97cf7faecf 100644 --- a/app/views/users/activate_account.html.erb +++ b/app/views/users/activate_account.html.erb @@ -19,7 +19,7 @@ (function() { function activateAccount() { $('#activate-account-button').prop('disabled', true); - $.ajax("<%= path "/users/hp" %>").then(function(hp) { + $.ajax("<%= path "/u/hp" %>").then(function(hp) { $('#password_confirmation').val(hp.value); $('#challenge').val(hp.challenge.split("").reverse().join("")); $('#activate-account-form').submit(); diff --git a/config/routes.rb b/config/routes.rb index 2a239f1526..a20d2cd576 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -305,6 +305,7 @@ Discourse::Application.routes.draw do get 'u/check_username' => 'users#check_username' get 'u/is_local_username' => 'users#is_local_username' post 'u' => 'users#create' + get "u/hp" => "users#get_honeypot_value" get "u/admin-login" => "users#admin_login" put "u/admin-login" => "users#admin_login" @@ -320,7 +321,6 @@ Discourse::Application.routes.draw do get "u/activate-account/:token" => "users#activate_account" put "u/activate-account/:token" => "users#perform_account_activation", as: 'perform_activate_account' get "u/authorize-email/:token" => "users_email#confirm" - get "u/hp" => "users#get_honeypot_value" get "u/:username/private-messages" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT} get "u/:username/private-messages/:filter" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT} @@ -377,6 +377,7 @@ Discourse::Application.routes.draw do get 'users/check_username' => 'users#check_username' get 'users/is_local_username' => 'users#is_local_username' post 'users' => 'users#create' + get "users/hp" => "users#get_honeypot_value" get "users/admin-login" => "users#admin_login" put "users/admin-login" => "users#admin_login" diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb index 73c18249eb..208414e1a9 100644 --- a/spec/models/post_spec.rb +++ b/spec/models/post_spec.rb @@ -952,7 +952,7 @@ describe Post do it "will unhide the post but will keep the topic invisible/unlisted" do hidden_topic = Fabricate(:topic, visible: false) - first_post = create_post(topic: hidden_topic) + _first_post = create_post(topic: hidden_topic) second_post = create_post(topic: hidden_topic) second_post.update_columns(hidden: true, hidden_at: Time.now, hidden_reason_id: 1) diff --git a/test/javascripts/acceptance/password-reset-test.js.es6 b/test/javascripts/acceptance/password-reset-test.js.es6 index 8bfe33d512..7c627069c2 100644 --- a/test/javascripts/acceptance/password-reset-test.js.es6 +++ b/test/javascripts/acceptance/password-reset-test.js.es6 @@ -12,11 +12,11 @@ acceptance("Password Reset", { ]; }; - server.get('/users/confirm-email-token/myvalidtoken.json', () => { //eslint-disable-line + server.get('/u/confirm-email-token/myvalidtoken.json', () => { //eslint-disable-line return response({success: "OK"}); }); - server.put('/users/password-reset/myvalidtoken.json', request => { //eslint-disable-line + server.put('/u/password-reset/myvalidtoken.json', request => { //eslint-disable-line const body = parsePostData(request.requestBody); if (body.password === "jonesyAlienSlayer") { return response({success: false, errors: {password: ["is the name of your cat"]}}); diff --git a/test/javascripts/acceptance/search-full-test.js.es6 b/test/javascripts/acceptance/search-full-test.js.es6 index 886dd4b13d..d8050d72f4 100644 --- a/test/javascripts/acceptance/search-full-test.js.es6 +++ b/test/javascripts/acceptance/search-full-test.js.es6 @@ -14,7 +14,7 @@ acceptance("Search - Full Page", { return response({results: [{text: 'monkey', count: 1}]}); }); - server.get('/users/search/users', () => { //eslint-disable-line + server.get('/u/search/users', () => { //eslint-disable-line return response({users: [{username: "admin", name: "admin", avatar_template: "/images/avatar.png"}]}); }); diff --git a/test/javascripts/fixtures/user_fixtures.js.es6 b/test/javascripts/fixtures/user_fixtures.js.es6 index abfb5e2fd1..28e5a9cf04 100644 --- a/test/javascripts/fixtures/user_fixtures.js.es6 +++ b/test/javascripts/fixtures/user_fixtures.js.es6 @@ -1,6 +1,6 @@ /*jshint maxlen:10000000 */ export default { -"/users/eviltrout.json": {"user_badges":[{"id":5870,"granted_at":"2014-05-16T02:39:38.388Z","badge_id":4,"user_id":19,"granted_by_id":-1},{"id":40673,"granted_at":"2014-03-31T14:23:18.060Z","post_id":7241,"post_number":19,"badge_id":23,"user_id":19,"granted_by_id":-1,"topic_id":3153},{"id":5868,"granted_at":"2014-05-16T02:39:38.380Z","badge_id":3,"user_id":19,"granted_by_id":-1}],"badges":[{"id":4,"name":"Leader","description":null,"grant_count":7,"allow_title":true,"multiple_grant":false,"icon":"fa-user","image":null,"listable":true,"enabled":true,"badge_grouping_id":4,"system":true,"badge_type_id":1},{"id":23,"name":"Great Share","description":null,"grant_count":14,"allow_title":false,"multiple_grant":true,"icon":"fa-certificate","image":null,"listable":true,"enabled":true,"badge_grouping_id":2,"system":true,"badge_type_id":1},{"id":3,"name":"Regular","description":null,"grant_count":30,"allow_title":true,"multiple_grant":false,"icon":"fa-user","image":null,"listable":true,"enabled":true,"badge_grouping_id":4,"system":true,"badge_type_id":2}],"badge_types":[{"id":1,"name":"Gold","sort_order":9},{"id":2,"name":"Silver","sort_order":8},{"id":3,"name":"Bronze","sort_order":7}],"users":[{"id":19,"username":"eviltrout","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/eviltrout/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png"},{"id":-1,"username":"system","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/system/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png"}],"topics":[{"id":3153,"title":"Is it better for Discourse to use JavaScript or CoffeeScript?","fancy_title":"Is it better for Discourse to use JavaScript or CoffeeScript?","slug":"is-it-better-for-discourse-to-use-javascript-or-coffeescript","posts_count":56}],"user":{"user_option":{},"id":19,"username":"eviltrout","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/eviltrout/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png","name":"Robin Ward","email":"robin.ward@example.com","last_posted_at":"2015-05-07T15:23:35.074Z","last_seen_at":"2015-05-13T14:34:23.188Z","bio_raw":"Co-founder of Discourse. Previously, I created Forumwarz. Follow me on Twitter.","bio_cooked":"

Co-founder of Discourse. Previously, I created Forumwarz. Follow me on Twitter.

","created_at":"2013-02-03T15:19:22.704Z","website":"http://eviltrout.com","location":"Toronto","can_edit":false,"can_edit_username":true,"can_edit_email":true,"can_edit_name":true,"stats":[{"action_type":13,"count":342,"id":null},{"action_type":12,"count":109,"id":null},{"action_type":4,"count":27,"id":null},{"action_type":5,"count":1607,"id":null},{"action_type":6,"count":771,"id":null},{"action_type":1,"count":333,"id":null},{"action_type":2,"count":2671,"id":null},{"action_type":7,"count":949,"id":null},{"action_type":9,"count":42,"id":null},{"action_type":3,"count":8,"id":null},{"action_type":11,"count":20,"id":null}],"can_send_private_messages":true,"can_send_private_message_to_user":false,"bio_excerpt":"Co-founder of Discourse. Previously, I created Forumwarz. Follow me on Twitter.","trust_level":4,"moderator":true,"admin":true,"title":"co-founder","badge_count":23,"notification_count":3244,"has_title_badges":true,"custom_fields":{},"user_fields":{"1":"33"},"pending_count":0,"post_count":1987,"can_be_deleted":false,"can_delete_all_posts":false,"locale":"","email_digests":true,"email_private_messages":true,"email_direct":true,"email_always":true,"digest_after_minutes":10080,"mailing_list_mode":false,"auto_track_topics_after_msecs":60000,"new_topic_duration_minutes":1440,"external_links_in_new_tab":false,"dynamic_favicon":true,"enable_quoting":true,"muted_category_ids":[],"tracked_category_ids":[],"watched_category_ids":[3],"private_messages_stats":{"all":101,"mine":13,"unread":3},"disable_jump_reply":false,"gravatar_avatar_upload_id":5275,"custom_avatar_upload_id":1573,"card_image_badge":"/images/avatar.png","card_image_badge_id":120,"muted_usernames":[],"invited_by":{"id":1,"username":"sam","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/sam/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png"},"custom_groups":[{"id":44,"automatic":false,"name":"ubuntu","user_count":11,"alias_level":0,"visible":true,"automatic_membership_email_domains":null,"automatic_membership_retroactive":false,"primary_group":false,"title":null},{"id":47,"automatic":false,"name":"discourse","user_count":7,"alias_level":0,"visible":true,"automatic_membership_email_domains":null,"automatic_membership_retroactive":false,"primary_group":false,"title":null}],"featured_user_badge_ids":[5870,40673,5868],"card_badge":{"id":120,"name":"Garbage Man","description":"This Discourse developer successfully called something \"garbage!\"","grant_count":3,"allow_title":false,"multiple_grant":false,"icon":"/images/avatar.png","image":"/images/avatar.png","listable":false,"enabled":false,"badge_grouping_id":8,"system":false,"badge_type_id":3}}}, +"/u/eviltrout.json": {"user_badges":[{"id":5870,"granted_at":"2014-05-16T02:39:38.388Z","badge_id":4,"user_id":19,"granted_by_id":-1},{"id":40673,"granted_at":"2014-03-31T14:23:18.060Z","post_id":7241,"post_number":19,"badge_id":23,"user_id":19,"granted_by_id":-1,"topic_id":3153},{"id":5868,"granted_at":"2014-05-16T02:39:38.380Z","badge_id":3,"user_id":19,"granted_by_id":-1}],"badges":[{"id":4,"name":"Leader","description":null,"grant_count":7,"allow_title":true,"multiple_grant":false,"icon":"fa-user","image":null,"listable":true,"enabled":true,"badge_grouping_id":4,"system":true,"badge_type_id":1},{"id":23,"name":"Great Share","description":null,"grant_count":14,"allow_title":false,"multiple_grant":true,"icon":"fa-certificate","image":null,"listable":true,"enabled":true,"badge_grouping_id":2,"system":true,"badge_type_id":1},{"id":3,"name":"Regular","description":null,"grant_count":30,"allow_title":true,"multiple_grant":false,"icon":"fa-user","image":null,"listable":true,"enabled":true,"badge_grouping_id":4,"system":true,"badge_type_id":2}],"badge_types":[{"id":1,"name":"Gold","sort_order":9},{"id":2,"name":"Silver","sort_order":8},{"id":3,"name":"Bronze","sort_order":7}],"users":[{"id":19,"username":"eviltrout","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/eviltrout/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png"},{"id":-1,"username":"system","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/system/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png"}],"topics":[{"id":3153,"title":"Is it better for Discourse to use JavaScript or CoffeeScript?","fancy_title":"Is it better for Discourse to use JavaScript or CoffeeScript?","slug":"is-it-better-for-discourse-to-use-javascript-or-coffeescript","posts_count":56}],"user":{"user_option":{},"id":19,"username":"eviltrout","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/eviltrout/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png","name":"Robin Ward","email":"robin.ward@example.com","last_posted_at":"2015-05-07T15:23:35.074Z","last_seen_at":"2015-05-13T14:34:23.188Z","bio_raw":"Co-founder of Discourse. Previously, I created Forumwarz. Follow me on Twitter.","bio_cooked":"

Co-founder of Discourse. Previously, I created Forumwarz. Follow me on Twitter.

","created_at":"2013-02-03T15:19:22.704Z","website":"http://eviltrout.com","location":"Toronto","can_edit":false,"can_edit_username":true,"can_edit_email":true,"can_edit_name":true,"stats":[{"action_type":13,"count":342,"id":null},{"action_type":12,"count":109,"id":null},{"action_type":4,"count":27,"id":null},{"action_type":5,"count":1607,"id":null},{"action_type":6,"count":771,"id":null},{"action_type":1,"count":333,"id":null},{"action_type":2,"count":2671,"id":null},{"action_type":7,"count":949,"id":null},{"action_type":9,"count":42,"id":null},{"action_type":3,"count":8,"id":null},{"action_type":11,"count":20,"id":null}],"can_send_private_messages":true,"can_send_private_message_to_user":false,"bio_excerpt":"Co-founder of Discourse. Previously, I created Forumwarz. Follow me on Twitter.","trust_level":4,"moderator":true,"admin":true,"title":"co-founder","badge_count":23,"notification_count":3244,"has_title_badges":true,"custom_fields":{},"user_fields":{"1":"33"},"pending_count":0,"post_count":1987,"can_be_deleted":false,"can_delete_all_posts":false,"locale":"","email_digests":true,"email_private_messages":true,"email_direct":true,"email_always":true,"digest_after_minutes":10080,"mailing_list_mode":false,"auto_track_topics_after_msecs":60000,"new_topic_duration_minutes":1440,"external_links_in_new_tab":false,"dynamic_favicon":true,"enable_quoting":true,"muted_category_ids":[],"tracked_category_ids":[],"watched_category_ids":[3],"private_messages_stats":{"all":101,"mine":13,"unread":3},"disable_jump_reply":false,"gravatar_avatar_upload_id":5275,"custom_avatar_upload_id":1573,"card_image_badge":"/images/avatar.png","card_image_badge_id":120,"muted_usernames":[],"invited_by":{"id":1,"username":"sam","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/sam/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png"},"custom_groups":[{"id":44,"automatic":false,"name":"ubuntu","user_count":11,"alias_level":0,"visible":true,"automatic_membership_email_domains":null,"automatic_membership_retroactive":false,"primary_group":false,"title":null},{"id":47,"automatic":false,"name":"discourse","user_count":7,"alias_level":0,"visible":true,"automatic_membership_email_domains":null,"automatic_membership_retroactive":false,"primary_group":false,"title":null}],"featured_user_badge_ids":[5870,40673,5868],"card_badge":{"id":120,"name":"Garbage Man","description":"This Discourse developer successfully called something \"garbage!\"","grant_count":3,"allow_title":false,"multiple_grant":false,"icon":"/images/avatar.png","image":"/images/avatar.png","listable":false,"enabled":false,"badge_grouping_id":8,"system":false,"badge_type_id":3}}}, "/user_actions.json": {"user_actions":[{"action_type":7,"created_at":"2014-01-16T14:13:05Z","excerpt":"So again, \n\nWhat is the problem?\n\nI need to check user_trust_level , i get the 'username' from a form via ajax, i need to check what level he is on discourse \n\nAlso, if possible, i would like to get other details as well, like email address etc. \n\nI took a look at : https://github.com/discourse/dis…","avatar_template":"//www.gravatar.com/avatar/bdab7e61b3191e483492fd680f563fed.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/bdab7e61b3191e483492fd680f563fed.png?s={size}&r=pg&d=identicon","slug":"how-to-check-the-user-level-via-ajax","topic_id":11993,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":1,"reply_to_post_number":null,"username":"Abhishek_Gupta","name":"Abhishek Gupta","user_id":8021,"acting_username":"Abhishek_Gupta","acting_name":"Abhishek Gupta","acting_user_id":8021,"title":"How to check the user level via ajax?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-15T16:53:49Z","excerpt":"A good fix would be to have the ERB template do an if statement. We'd happily accept a PR that did this if you feel up to it: \n\n <% if SiteSetting.logo_url.present? %>\n display logo html\n<% else %>\n display title html\n<% end %>","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"users-activate-account-pulling-blank-logo-instead-of-defaulting-to-h2","topic_id":10911,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"/users/activate-account pulling blank logo instead of defaulting to h2","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-15T15:21:37Z","excerpt":"A good fix would be to have the ERB template do an if statement. We'd happily accept a PR that did this if you feel up to it: \n\n <% if SiteSetting.logo_url.present? %>\n display logo html\n<% else %>\n display title html\n<% end %>","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"users-activate-account-pulling-blank-logo-instead-of-defaulting-to-h2","topic_id":10911,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"/users/activate-account pulling blank logo instead of defaulting to h2","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-15T12:22:12Z","excerpt":"OK - i see what you mean. From the piwik code I should add: \n\n_paq.push(["setDocumentTitle", document.domain + "/" + document.title]);\n\n? \n\nUnfortunately I have had to give up on Piwik for now because I have switched the forum to SSL on a free cert and have used up the free subdomain for the forum. …","avatar_template":"//localhost:3000/uploads/default/avatars/2a8/a3c/8fddcac642/{size}.jpg","acting_avatar_template":"//localhost:3000/uploads/default/avatars/2a8/a3c/8fddcac642/{size}.jpg","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":26,"reply_to_post_number":25,"username":"citkane","name":"Michael Jonker","user_id":7604,"acting_username":"citkane","acting_name":"Michael Jonker","acting_user_id":7604,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-15T11:16:36Z","excerpt":"@eviltrout recently added support for multiple API keys [wink] \n\n[]","avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"allow-for-multiple-api-keys","topic_id":7444,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":null,"username":"zogstrip","name":"Régis Hanol","user_id":1995,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Allow for multiple API Keys","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-15T10:58:46Z","excerpt":"@eviltrout added a tooltip when you click on the user's avatar which allows you to show the posts made by that user \n\n[image]","avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"to-group-posts-by-a-user","topic_id":7412,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":4,"reply_to_post_number":3,"username":"zogstrip","name":"Régis Hanol","user_id":1995,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"To group posts by a user","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-15T10:36:15Z","excerpt":"@eviltrout implemented per-user API key a while ago [wink] \n\n [image]\nTopics_-_Discourse_Meta-5.png884x339 29.6 KB\n","avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"auth-using-rest-api","topic_id":5937,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"zogstrip","name":"Régis Hanol","user_id":1995,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Auth using REST API?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-15T09:55:17Z","excerpt":"@eviltrout has recently introduced this feature and has even blogged about it: \n\n \n \n \n \n eviltrout.com\n \n \n \n \n \n Hiding Offscreen Content in Ember.js - Evil Trout's Blog","avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"infinite-scrolling-reusing-dom-nodes","topic_id":5186,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":null,"username":"zogstrip","name":"Régis Hanol","user_id":1995,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Infinite scrolling: Reusing DOM nodes","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-15T00:54:32Z","excerpt":"You can retrieve a user's JSON by making a call to /users/username.json but that assumes you know the user's username. If that's impossible, I would be happy to accept a PR that would return the current user JSON from /session/current-user or something like that. \n\nAdditionally, if you're looking to…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/035d12bad251759d8fbc9fb10574d1f6.png?s={size}&r=pg&d=identicon","slug":"get-current-user-information-via-json","topic_id":11959,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"watchmanmonitor","acting_name":"Watchman Monitoring","acting_user_id":8085,"title":"Get current user information via JSON","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-14T21:59:51Z","excerpt":"You can retrieve a user's JSON by making a call to /users/username.json but that assumes you know the user's username. If that's impossible, I would be happy to accept a PR that would return the current user JSON from /session/current-user or something like that. \n\nAdditionally, if you're looking to…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/9cfd2536afac32d209335b092094c12c.png?s={size}&r=pg&d=identicon","slug":"get-current-user-information-via-json","topic_id":11959,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"znation","acting_name":"znation","acting_user_id":8163,"title":"Get current user information via JSON","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-14T21:46:50Z","excerpt":"Okay I've fixed the https [point_right] http links on the server side and in the Javascript click tracking as @BhaelOchon pointed out. \n\nLet me know if you find anything else broken.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"broken-links-possibly-related-to-https","topic_id":11831,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":18,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Broken links, possibly related to HTTPS","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-14T21:43:28Z","excerpt":"Thanks for your help @eviltrout! I will consider making that change and sending a pull request. I may not get to it for a while. \n\nI am embedding Discourse on another site and it is mostly going well. I have indeed been using your blog for inspiration.","avatar_template":"//www.gravatar.com/avatar/9cfd2536afac32d209335b092094c12c.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/9cfd2536afac32d209335b092094c12c.png?s={size}&r=pg&d=identicon","slug":"get-current-user-information-via-json","topic_id":11959,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"znation","name":"znation","user_id":8163,"acting_username":"znation","acting_name":"znation","acting_user_id":8163,"title":"Get current user information via JSON","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-14T21:21:52Z","excerpt":"Okay I've fixed the https [point_right] http links on the server side and in the Javascript click tracking as @BhaelOchon pointed out. \n\nLet me know if you find anything else broken.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"broken-links-possibly-related-to-https","topic_id":11831,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":18,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Broken links, possibly related to HTTPS","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T21:03:07Z","excerpt":"Okay I've fixed the https [point_right] http links on the server side and in the Javascript click tracking as @BhaelOchon pointed out. \n\nLet me know if you find anything else broken.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"broken-links-possibly-related-to-https","topic_id":11831,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":18,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Broken links, possibly related to HTTPS","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-14T20:42:51Z","excerpt":"You can retrieve a user's JSON by making a call to /users/username.json but that assumes you know the user's username. If that's impossible, I would be happy to accept a PR that would return the current user JSON from /session/current-user or something like that. \n\nAdditionally, if you're looking to…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"get-current-user-information-via-json","topic_id":11959,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Get current user information via JSON","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T20:29:23Z","excerpt":"You can retrieve a user's JSON by making a call to /users/username.json but that assumes you know the user's username. If that's impossible, I would be happy to accept a PR that would return the current user JSON from /session/current-user or something like that. \n\nAdditionally, if you're looking to…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"get-current-user-information-via-json","topic_id":11959,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Get current user information via JSON","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T19:20:28Z","excerpt":"Perhaps the ['trackpageView'] is not the correct API call? We can probably send more information across such as the URL.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":25,"reply_to_post_number":24,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T19:19:46Z","excerpt":"Nope but I bet you can find one!","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"how-far-to-take-user-documentation","topic_id":11943,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":4,"reply_to_post_number":3,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"How far to take user documentation?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-14T18:37:05Z","excerpt":"I'd be glad to write a pull request to take use there. Is there a specific part of their documentation you have in mind?","avatar_template":"//www.gravatar.com/avatar/035d12bad251759d8fbc9fb10574d1f6.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/035d12bad251759d8fbc9fb10574d1f6.png?s={size}&r=pg&d=identicon","slug":"how-far-to-take-user-documentation","topic_id":11943,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"watchmanmonitor","name":"Watchman Monitoring","user_id":8085,"acting_username":"watchmanmonitor","acting_name":"Watchman Monitoring","acting_user_id":8085,"title":"How far to take user documentation?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-14T16:04:28Z","excerpt":"Thanks @eviltrout , the code in the 'bottom of pages' now reads: \n\n<script type="text/javascript">\nDiscourse.PageTracker.current().on('change', function() {\n console.log('tracked!')\n _paq.push(['trackPageView']);\n});\n</script>\n\nThe console is logging 'tracked!' and piwik is logging for each page c…","avatar_template":"//localhost:3000/uploads/default/avatars/2a8/a3c/8fddcac642/{size}.jpg","acting_avatar_template":"//localhost:3000/uploads/default/avatars/2a8/a3c/8fddcac642/{size}.jpg","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":23,"reply_to_post_number":22,"username":"citkane","name":"Michael Jonker","user_id":7604,"acting_username":"citkane","acting_name":"Michael Jonker","acting_user_id":7604,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T15:58:27Z","excerpt":"This topic is now archived. It is frozen and cannot be changed in any way.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"regression-cannot-sort-topic-list","topic_id":11944,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":4,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Regression: Cannot sort topic list","deleted":false,"hidden":false,"moderator_action":true,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T15:26:57Z","excerpt":"I do think that leading them into the official rails documentation at that point is not a bad idea. Like "congratulations, everything is ready but now you'll need to understand the platform we built it in to be productive."","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"how-far-to-take-user-documentation","topic_id":11943,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"How far to take user documentation?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-14T08:28:00Z","excerpt":"I've just added the ability to list reply counts on your blog index and archive pages as you can see here. \n\nIt works with a similar API to embedding comments: \n\n <script type="text/javascript">\n var discourseUrl = "http://fishtank.eviltrout.com/";\n\n (function() {\n var d = document.createEleme…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"discourse-plugin-for-static-site-generators-like-jekyll-or-octopress","topic_id":7965,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":98,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Discourse plugin for static site generators like Jekyll or Octopress","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-14T00:21:26Z","excerpt":"In pull request 1821, @eviltrout asked: \n\n "About rails s: I wouldn't be against adding it but at what point do we stop holding their hand and expect them to know how rails works? I'm sure rails documentation could do a better job than us. Actually maybe we should just link to that? \n\nWhat point to …","avatar_template":"//www.gravatar.com/avatar/035d12bad251759d8fbc9fb10574d1f6.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/035d12bad251759d8fbc9fb10574d1f6.png?s={size}&r=pg&d=identicon","slug":"how-far-to-take-user-documentation","topic_id":11943,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":1,"reply_to_post_number":null,"username":"watchmanmonitor","name":"Watchman Monitoring","user_id":8085,"acting_username":"watchmanmonitor","acting_name":"Watchman Monitoring","acting_user_id":8085,"title":"How far to take user documentation?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-13T21:58:28Z","excerpt":"It looks uneeded, but you need to review a fair amount of code to confirm it is not needed. \n\nI am going to keep it for now cause its safer under some weird edge conditions.","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"ruby-question-about-use-of-klass-self-in-the-site-customization-rb","topic_id":11889,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Ruby question about use of klass=self in the site_customization.rb","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T21:11:32Z","excerpt":"I had to fix an issue with Google analytics so I added a new API hook that can be used. \n\nIf you add the following it should work: \n\n Discourse.PageTracker.current().on('change', function() {\n _paq.push(['trackPageView']);\n});","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-13T21:10:57Z","excerpt":"Having a look, the fix is a bit scary imho, we should fix the root issue.","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"error-after-update-to-0-9-8-1","topic_id":11903,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":11,"reply_to_post_number":10,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Error after update to 0.9.8.1","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T20:50:34Z","excerpt":"I've just added the ability to list reply counts on your blog index and archive pages as you can see here. \n\nIt works with a similar API to embedding comments: \n\n <script type="text/javascript">\n var discourseUrl = "http://fishtank.eviltrout.com/";\n\n (function() {\n var d = document.createEleme…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//localhost:3000/uploads/default/avatars/527/614/d16e1504d9/{size}.jpg","slug":"discourse-plugin-for-static-site-generators-like-jekyll-or-octopress","topic_id":7965,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":98,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"trident","acting_name":"Ben T","acting_user_id":5707,"title":"Discourse plugin for static site generators like Jekyll or Octopress","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T20:44:56Z","excerpt":"I had to fix an issue with Google analytics so I added a new API hook that can be used. \n\nIf you add the following it should work: \n\n Discourse.PageTracker.current().on('change', function() {\n _paq.push(['trackPageView']);\n});","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T20:40:21Z","excerpt":"I had to fix an issue with Google analytics so I added a new API hook that can be used. \n\nIf you add the following it should work: \n\n Discourse.PageTracker.current().on('change', function() {\n _paq.push(['trackPageView']);\n});","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T19:52:04Z","excerpt":"@Sam do you have any idea why only some people are getting this issue? I dont' mind the proposed fix but I'd prefer to know why it happens in the first place.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"error-after-update-to-0-9-8-1","topic_id":11903,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":10,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Error after update to 0.9.8.1","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T19:01:19Z","excerpt":"I've just added the ability to list reply counts on your blog index and archive pages as you can see here. \n\nIt works with a similar API to embedding comments: \n\n <script type="text/javascript">\n var discourseUrl = "http://fishtank.eviltrout.com/";\n\n (function() {\n var d = document.createEleme…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"discourse-plugin-for-static-site-generators-like-jekyll-or-octopress","topic_id":7965,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":98,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Discourse plugin for static site generators like Jekyll or Octopress","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T18:50:14Z","excerpt":"I've just added the ability to list reply counts on your blog index and archive pages as you can see here. \n\nIt works with a similar API to embedding comments: \n\n <script type="text/javascript">\n var discourseUrl = "http://fishtank.eviltrout.com/";\n\n (function() {\n var d = document.createEleme…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"discourse-plugin-for-static-site-generators-like-jekyll-or-octopress","topic_id":7965,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":98,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Discourse plugin for static site generators like Jekyll or Octopress","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T18:47:33Z","excerpt":"I am pretty sure that the denizens of SO are correct and the variable is unneeded. @sam can confirm but it seems like it was once needed for something that has since been removed and the variable declaration was left intact.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"ruby-question-about-use-of-klass-self-in-the-site-customization-rb","topic_id":11889,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Ruby question about use of klass=self in the site_customization.rb","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T18:45:41Z","excerpt":"I've just added the ability to list reply counts on your blog index and archive pages as you can see here. \n\nIt works with a similar API to embedding comments: \n\n <script type="text/javascript">\n var discourseUrl = "http://fishtank.eviltrout.com/";\n\n (function() {\n var d = document.createEleme…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"discourse-plugin-for-static-site-generators-like-jekyll-or-octopress","topic_id":7965,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":98,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Discourse plugin for static site generators like Jekyll or Octopress","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T17:19:08Z","excerpt":"@Sam do you have any idea why only some people are getting this issue? I dont' mind the proposed fix but I'd prefer to know why it happens in the first place.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/5120fc4e345db0d1a964888272073819.png?s={size}&r=pg&d=identicon","slug":"error-after-update-to-0-9-8-1","topic_id":11903,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":10,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"riking","acting_name":"Kane York","acting_user_id":6626,"title":"Error after update to 0.9.8.1","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-13T16:41:31Z","excerpt":"I'd love to see API support. @sam and @eviltrout, I can facilitate an intro to the piwik guys if you want—I've written about them before and they're typically super-responsive. Because I know you guys are totally hunting for new stuff to do [wink]","avatar_template":"//localhost:3000/uploads/default/avatars/95a/06d/c337428568/{size}.png","acting_avatar_template":"//localhost:3000/uploads/default/avatars/95a/06d/c337428568/{size}.png","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":20,"reply_to_post_number":null,"username":"Lee_Ars","name":"Lee_Ars","user_id":4457,"acting_username":"Lee_Ars","acting_name":"Lee_Ars","acting_user_id":4457,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T16:15:51Z","excerpt":"The code looks okay but it's hard to debug this way. \n\nOne thing you could do is add a: console.log('tracked!') just before line 8. Then open a developer console and see if the javascript is running properly.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":18,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T15:10:41Z","excerpt":"This is really interesting. I'd like to hear your findings.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"focus-events-track-which-window-is-the-last-active-instance-of-a-forum-edit","topic_id":11872,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":9,"reply_to_post_number":8,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Focus events: Track which window is the last active instance of a forum Edit","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T15:02:45Z","excerpt":"The code looks okay but it's hard to debug this way. \n\nOne thing you could do is add a: console.log('tracked!') just before line 8. Then open a developer console and see if the javascript is running properly.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":18,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T14:53:13Z","excerpt":"@Sam do you have any idea why only some people are getting this issue? I dont' mind the proposed fix but I'd prefer to know why it happens in the first place.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"error-after-update-to-0-9-8-1","topic_id":11903,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":10,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Error after update to 0.9.8.1","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-13T06:27:26Z","excerpt":"Can this be archived @eviltrout?","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"search-not-working-for-staff-users","topic_id":11371,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":13,"reply_to_post_number":null,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Search not working for Staff users","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-13T05:32:46Z","excerpt":"When you navigate to another topic using the "suggested topics" area we are not registering a page view with Google. \n\n@eviltrout perhaps we should do this from discourse location instead of application controller?","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"google-analytics-is-not-registering-page-views","topic_id":11914,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":1,"reply_to_post_number":null,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Google analytics is not registering page views","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-13T02:50:25Z","excerpt":"@eviltrout any ideas here, the code seems correct","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":17,"reply_to_post_number":16,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-12T22:31:35Z","excerpt":"This is an interesting approach an an interesting feature. @eviltrout your thoughts. Essentially allows us to have notifications cross tabs.","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"focus-events-track-which-window-is-the-last-active-instance-of-a-forum-edit","topic_id":11872,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":4,"reply_to_post_number":1,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Focus events: Track which window is the last active instance of a forum Edit","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-12T18:01:04Z","excerpt":"This was the link \n\nmetric_fu \n\n[metric_fu](https://github.com/metricfu/metric_fu/blob/b1bf8feb921916fc265f041efa3157a6a6530a9b/lib/metric_fu/logging/mf_debugger.rb#L24)\n\nSeems to work fine now that @eviltrout worked so hard to get us MDTest 1.1 compliant.","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"underscores-in-linked-text-can-cause-markdown-bug","topic_id":10848,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":null,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Underscores in linked text can cause markdown bug","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-12T04:14:06Z","excerpt":"Awesome plugin, but doesn't seem to work out of the box with images \n\nhttps://github.com/discourse/discourse-spoiler-alert/issues/2","avatar_template":"//localhost:3000/uploads/default/avatars/276/f19/3826efe463/{size}.jpg","acting_avatar_template":"//localhost:3000/uploads/default/avatars/276/f19/3826efe463/{size}.jpg","slug":"brand-new-plugin-interface","topic_id":8793,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":64,"reply_to_post_number":44,"username":"xrvk","name":"Eero Heikkinen","user_id":8068,"acting_username":"xrvk","acting_name":"Eero Heikkinen","acting_user_id":8068,"title":"Brand new plugin interface","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-11T23:36:11Z","excerpt":"A few things, \n\n@eviltrout myself and many others have discourse_docker hosted on DigitalOcean, my user cpu is usually around 2% I have plenty of capacity. \n\nI know that stonehearth and other larger scale discourse work on DigitalOcean fine. Officially we strongly recommend a 2GB instance, thoug…","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"performance-issue-on-digital-ocean-with-discourse-docker","topic_id":11895,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Performance issue on DigitalOcean with discourse_docker","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-11T00:58:23Z","excerpt":"Confirmed on try.discourse.org, this is still an issue. \n\n@eviltrout can you add that to your list -- unless you are a staff member you should not be able to delete (your own) posts from an archived topic.","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"archived-discussions-still-allow-posts-to-be-deleted","topic_id":6479,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":null,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Archived discussions still allow posts to be deleted","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-11T00:35:38Z","excerpt":"Agree, @eviltrout can you make sure the usercard is using the same logic as the user page in displaying profile info?","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"usercard-does-not-resize-for-obnoxiously-large-images","topic_id":11007,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":5,"reply_to_post_number":4,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Usercard does not resize for obnoxiously large images","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-11T00:34:06Z","excerpt":"@eviltrout can you make sure the "import post" button is suppressed on the user page when editing "about me"? \n\n(I agree it is like a "lose all my work" button on that page if you happen to press it..) \n\nThen I can archive this.","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"quote-post-button-should-be-disabled-or-raise-an-error-when-creating-a-new-topic","topic_id":834,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":5,"reply_to_post_number":4,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"\"Quote Post\" button should be disabled or raise an error when creating a new topic","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-10T21:00:11Z","excerpt":">\n\nLooks good now. Thanks for these fixes @eviltrout, we (and markdown-js) are now MDTest 1.1 compliant!","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"text-editor-issue-with-the-code-block","topic_id":10050,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":5,"reply_to_post_number":null,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Text Editor issue with the code block","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":1,"created_at":"2014-01-10T20:07:46Z","excerpt":"We can't repro that one, also seems a bit obscure. But thank you very much for all the reports, whenever I see a bug entry from YOU I always know it is going to be a good one based on experience here and elsewhere. [trophy]","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"security-error-on-console-noticed-on-meta","topic_id":11825,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":12,"reply_to_post_number":11,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Security Error on console (noticed on meta)","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T19:48:08Z","excerpt":"Thanks for letting us know. It turns out that by using minutely(5) instead of minutely causes ice_cube to peg a core at 100% usage. I've pushed out a fix in master.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T19:47:17Z","excerpt":"Thanks for letting us know. It turns out that by using minutely(5) instead of minutely causes ice_cube to peg a core at 100% usage. I've pushed out a fix in master.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/42776c4982dff1fa45ee8248532f8ad0.png?s={size}&r=pg&d=identicon","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"neil","acting_name":"Neil","acting_user_id":2,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T17:39:24Z","excerpt":"We should consider doing what Google Drive does: they intercept cmd-f and pop up a box that allows you to dynamically search.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/5120fc4e345db0d1a964888272073819.png?s={size}&r=pg&d=identicon","slug":"ctrl-f-search-is-interrupted-by-quotation-popup","topic_id":7114,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":12,"reply_to_post_number":11,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"riking","acting_name":"Kane York","acting_user_id":6626,"title":"Ctrl+F search is interrupted by quotation popup","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T17:29:15Z","excerpt":"Thanks for letting us know. It turns out that by using minutely(5) instead of minutely causes ice_cube to peg a core at 100% usage. I've pushed out a fix in master.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/5120fc4e345db0d1a964888272073819.png?s={size}&r=pg&d=identicon","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"riking","acting_name":"Kane York","acting_user_id":6626,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T17:24:37Z","excerpt":"Thanks for letting us know. It turns out that by using minutely(5) instead of minutely causes ice_cube to peg a core at 100% usage. I've pushed out a fix in master.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-10T17:02:35Z","excerpt":"Fixed [smile] \n\ntop - 12:02:00 up 12 days, 2:16, 1 user, load average: 0.28, 0.92, 0.97\nTasks: 115 total, 1 running, 114 sleeping, 0 stopped, 0 zombie\nCpu0 : 0.7%us, 0.3%sy, 0.0%ni, 99.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st\nCpu1 : 0.7%us, 0.3%sy, 0.0%ni, 99.0%id, 0.0%wa, 0.0%hi,…","avatar_template":"//localhost:3000/uploads/default/avatars/886/ea8/e533d87fd9/{size}.png","acting_avatar_template":"//localhost:3000/uploads/default/avatars/886/ea8/e533d87fd9/{size}.png","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":23,"reply_to_post_number":22,"username":"michaeld","name":"Michael","user_id":6548,"acting_username":"michaeld","acting_name":"Michael","acting_user_id":6548,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T16:58:12Z","excerpt":"Thanks for letting us know. It turns out that by using minutely(5) instead of minutely causes ice_cube to peg a core at 100% usage. I've pushed out a fix in master.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//localhost:3000/uploads/default/avatars/527/614/d16e1504d9/{size}.jpg","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"trident","acting_name":"Ben T","acting_user_id":5707,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null}]}, "/topics/created-by/eviltrout.json": {"users":[{"id":19,"username":"eviltrout","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon"},{"id":5460,"username":"ned","avatar_template":"//localhost:3000/uploads/default/avatars/06b/90d/3b3ea7e56b/{size}.png"},{"id":402,"username":"thebrianbarlow","avatar_template":"//www.gravatar.com/avatar/5ddf2459e8edd6cf52dfff6cb41ca70d.png?s={size}&r=pg&d=identicon"},{"id":5707,"username":"trident","avatar_template":"//localhost:3000/uploads/default/avatars/527/614/d16e1504d9/{size}.jpg"},{"id":32,"username":"codinghorror","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon"},{"id":1995,"username":"zogstrip","avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon"},{"id":2702,"username":"ryanflorence","avatar_template":"//www.gravatar.com/avatar/749001c9fe6927c4b069a45c2a3d68f7.png?s={size}&r=pg&d=identicon"},{"id":9,"username":"tms","avatar_template":"//www.gravatar.com/avatar/3981cd271c302f5cba628c6b6d2b32ee.png?s={size}&r=pg&d=identicon"},{"id":1,"username":"sam","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon"},{"id":2636,"username":"lonnon","avatar_template":"//www.gravatar.com/avatar/9489ef302fbff6c19bba507d09f8cd1d.png?s={size}&r=pg&d=identicon"}],"topic_list":{"can_create_topic":false,"draft":null,"draft_key":"new_topic","draft_sequence":null,"topics":[{"id":7764,"title":"New: Reply via Email Support!","fancy_title":"New: Reply via Email Support!","slug":"new-reply-via-email-support","posts_count":32,"reply_count":24,"highest_post_number":35,"image_url":"/uploads/meta_discourse/1227/8f4e5818dfaa56c7.png","created_at":"2013-06-25T11:58:39.000-04:00","last_posted_at":"2014-01-09T18:53:06.000-05:00","bumped":true,"bumped_at":"2014-01-09T17:09:40.000-05:00","unseen":false,"pinned":false,"visible":true,"closed":false,"archived":false,"views":2201,"like_count":46,"has_summary":false,"archetype":"regular","last_poster_username":"codinghorror","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":19},{"extras":null,"description":"Most Posts","user_id":5460},{"extras":null,"description":"Frequent Poster","user_id":402},{"extras":null,"description":"Frequent Poster","user_id":5707},{"extras":"latest","description":"Most Recent Poster","user_id":32}]},{"id":9318,"title":"Discourse has a new Markdown Parser!","fancy_title":"Discourse has a new Markdown Parser!","slug":"discourse-has-a-new-markdown-parser","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-08-24T14:08:06.000-04:00","last_posted_at":"2013-08-24T14:08:06.000-04:00","bumped":true,"bumped_at":"2013-08-24T14:13:25.000-04:00","unseen":false,"pinned":false,"visible":true,"closed":false,"archived":false,"views":812,"like_count":13,"has_summary":false,"archetype":"regular","last_poster_username":"eviltrout","category_id":7,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":19}]},{"id":7019,"title":"Discourse Ember Refactorings","fancy_title":"Discourse Ember Refactorings","slug":"discourse-ember-refactorings","posts_count":5,"reply_count":3,"highest_post_number":5,"image_url":null,"created_at":"2013-05-30T11:16:36.000-04:00","last_posted_at":"2013-06-02T11:22:58.000-04:00","bumped":true,"bumped_at":"2013-06-02T11:22:58.000-04:00","unseen":false,"pinned":false,"visible":true,"closed":false,"archived":false,"views":1075,"like_count":15,"has_summary":false,"archetype":"regular","last_poster_username":"eviltrout","category_id":7,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":19},{"extras":null,"description":"Most Posts","user_id":1995},{"extras":null,"description":"Frequent Poster","user_id":2702}]},{"id":4650,"title":"Migrating off Active Record Observers","fancy_title":"Migrating off Active Record Observers","slug":"migrating-off-active-record-observers","posts_count":8,"reply_count":7,"highest_post_number":8,"image_url":null,"created_at":"2013-03-11T11:26:13.000-04:00","last_posted_at":"2013-05-14T18:40:16.000-04:00","bumped":true,"bumped_at":"2013-05-14T18:40:16.000-04:00","unseen":false,"pinned":false,"visible":true,"closed":false,"archived":false,"views":377,"like_count":3,"has_summary":false,"archetype":"regular","last_poster_username":"sam","category_id":7,"posters":[{"extras":null,"description":"Original Poster","user_id":19},{"extras":null,"description":"Most Posts","user_id":9},{"extras":null,"description":"Frequent Poster","user_id":1995},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":4960,"title":"Vagrant Updates!","fancy_title":"Vagrant Updates!","slug":"vagrant-updates","posts_count":5,"reply_count":3,"highest_post_number":5,"image_url":"/plugins/emoji/images/fish.png","created_at":"2013-03-20T22:29:22.000-04:00","last_posted_at":"2013-03-21T19:06:40.000-04:00","bumped":true,"bumped_at":"2013-03-21T19:06:40.000-04:00","unseen":false,"pinned":false,"visible":true,"closed":false,"archived":false,"views":500,"like_count":4,"has_summary":false,"archetype":"regular","last_poster_username":"zogstrip","category_id":7,"posters":[{"extras":null,"description":"Original Poster","user_id":19},{"extras":null,"description":"Most Posts","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":"latest","description":"Most Recent Poster","user_id":1995}]},{"id":2918,"title":"New: Updated Docs","fancy_title":"New: Updated Docs","slug":"new-updated-docs","posts_count":3,"reply_count":2,"highest_post_number":3,"image_url":null,"created_at":"2013-02-12T12:13:02.000-05:00","last_posted_at":"2013-02-15T17:57:19.000-05:00","bumped":true,"bumped_at":"2013-02-15T17:57:19.000-05:00","unseen":false,"pinned":false,"visible":true,"closed":false,"archived":false,"views":457,"like_count":10,"has_summary":false,"archetype":"regular","last_poster_username":"eviltrout","category_id":10,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":19},{"extras":null,"description":"Most Posts","user_id":2636}]}]}} }; diff --git a/test/javascripts/helpers/create-pretender.js.es6 b/test/javascripts/helpers/create-pretender.js.es6 index 8ed14ff788..a704196dce 100644 --- a/test/javascripts/helpers/create-pretender.js.es6 +++ b/test/javascripts/helpers/create-pretender.js.es6 @@ -63,17 +63,17 @@ export default function() { }] }); }); - this.get(`/users/eviltrout/emails.json`, () => { + this.get(`/u/eviltrout/emails.json`, () => { return response({ email: 'eviltrout@example.com' }); }); - this.get('/users/eviltrout.json', () => { - const json = fixturesByUrl['/users/eviltrout.json']; + this.get('/u/eviltrout.json', () => { + const json = fixturesByUrl['/u/eviltrout.json']; json.user.can_edit = loggedIn(); return response(json); }); - this.get('/users/eviltrout/summary.json', () => { + this.get('/u/eviltrout/summary.json', () => { return response({ user_summary: { topics: [], @@ -85,13 +85,13 @@ export default function() { }); }); - this.get('/users/eviltrout/invited_count.json', () => { + this.get('/u/eviltrout/invited_count.json', () => { return response({ "counts": { "pending": 1, "redeemed": 0, "total": 0 } }); }); - this.get('/users/eviltrout/invited.json', () => { + this.get('/u/eviltrout/invited.json', () => { return response({ "invites": [ {id: 1} ] }); }); @@ -113,7 +113,7 @@ export default function() { return response({}); }); - this.put('/users/eviltrout.json', () => response({ user: {} })); + this.put('/u/eviltrout.json', () => response({ user: {} })); this.get("/t/280.json", () => response(fixturesByUrl['/t/280/1.json'])); this.get("/t/28830.json", () => response(fixturesByUrl['/t/28830/1.json'])); @@ -134,7 +134,7 @@ export default function() { this.delete('/draft.json', success); this.post('/draft.json', success); - this.get('/users/:username/staff-info.json', () => response({})); + this.get('/u/:username/staff-info.json', () => response({})); this.get('/post_action_users', () => { return response({ @@ -198,9 +198,9 @@ export default function() { return response(400, {error: 'invalid login'}); }); - this.post('/users/action/send_activation_email', success); + this.post('/u/action/send_activation_email', success); - this.get('/users/hp.json', function() { + this.get('/u/hp.json', function() { return response({"value":"32faff1b1ef1ac3","challenge":"61a3de0ccf086fb9604b76e884d75801"}); }); @@ -208,14 +208,14 @@ export default function() { return response({"csrf":"mgk906YLagHo2gOgM1ddYjAN4hQolBdJCqlY6jYzAYs="}); }); - this.get('/users/check_username', function(request) { + this.get('/u/check_username', function(request) { if (request.queryParams.username === 'taken') { return response({available: false, suggestion: 'nottaken'}); } return response({available: true}); }); - this.post('/users', () => response({success: true})); + this.post('/u', () => response({success: true})); this.get('/login.html', () => [200, {}, 'LOGIN PAGE']); diff --git a/test/javascripts/lib/pretty-text-test.js.es6 b/test/javascripts/lib/pretty-text-test.js.es6 index 5ec6d6dead..6b95c6c5f3 100644 --- a/test/javascripts/lib/pretty-text-test.js.es6 +++ b/test/javascripts/lib/pretty-text-test.js.es6 @@ -227,7 +227,7 @@ test("Mentions", function() { const alwaysTrue = { mentionLookup: (function() { return "user"; }) }; cookedOptions("Hello @sam", alwaysTrue, - "

Hello @sam

", + "

Hello @sam

", "translates mentions to links"); cooked("[@codinghorror](https://twitter.com/codinghorror)", @@ -303,11 +303,11 @@ test("Mentions", function() { "handles mentions separated by a slash."); cookedOptions("@eviltrout", alwaysTrue, - "

@eviltrout

", + "

@eviltrout

", "it doesn't onebox mentions"); cookedOptions("a @sam c", alwaysTrue, - "

a @sam c

", + "

a @sam c

", "it allows mentions within HTML tags"); }); diff --git a/test/javascripts/lib/url-test.js.es6 b/test/javascripts/lib/url-test.js.es6 index 7c420b58cc..d64adb6e38 100644 --- a/test/javascripts/lib/url-test.js.es6 +++ b/test/javascripts/lib/url-test.js.es6 @@ -1,4 +1,4 @@ -import DiscourseURL from 'discourse/lib/url'; +import { default as DiscourseURL, userPath } from 'discourse/lib/url'; module("lib:url"); @@ -25,3 +25,9 @@ test("isInternal on subfolder install", function() { not(DiscourseURL.isInternal("http://eviltrout.com/tophat"), "a url on the same host but on a different folder is not internal"); ok(DiscourseURL.isInternal("http://eviltrout.com/forum/moustache"), "a url on the same host and on the same folder is internal"); }); + +test("userPath", assert => { + assert.equal(userPath(), '/u'); + assert.equal(userPath('eviltrout'), '/u/eviltrout'); + assert.equal(userPath('hp.json'), '/u/hp.json'); +}); diff --git a/test/javascripts/lib/user-search-test.js.es6 b/test/javascripts/lib/user-search-test.js.es6 index 2b72910bff..662aa6d626 100644 --- a/test/javascripts/lib/user-search-test.js.es6 +++ b/test/javascripts/lib/user-search-test.js.es6 @@ -10,7 +10,7 @@ module("lib:user-search", { ]; }; - server.get('/users/search/users', () => { //eslint-disable-line + server.get('/u/search/users', () => { //eslint-disable-line return response( { users: [ From 14410b71fbfab6f2980f9d7d532df4a95dbcc8e9 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 28 Mar 2017 14:27:54 -0400 Subject: [PATCH 022/124] Convert server side paths to use `/u/` --- .../admin/models/admin-user.js.es6 | 3 ++- .../javascripts/discourse/routes/user.js.es6 | 4 ++-- app/controllers/admin/users_controller.rb | 4 +++- app/controllers/list_controller.rb | 4 ++-- app/controllers/posts_controller.rb | 2 +- app/controllers/users_controller.rb | 2 +- app/models/user_action.rb | 2 +- app/views/email/_post.html.erb | 4 ++-- app/views/embed/comments.html.erb | 4 ++-- app/views/list/list.rss.erb | 2 +- app/views/robots_txt/index.erb | 1 + app/views/topics/show.html.erb | 2 +- .../user_notifications/mailing_list.html.erb | 2 +- config/locales/client.en.yml | 2 +- config/locales/server.en.yml | 24 +++++++++---------- config/routes.rb | 2 +- lib/composer_messages_finder.rb | 2 +- lib/system_message.rb | 2 +- lib/tasks/integration.rake | 2 +- script/bench.rb | 2 +- script/import_scripts/mylittleforum.rb | 4 +--- spec/components/post_creator_spec.rb | 6 ++--- spec/components/pretty_text_spec.rb | 4 ++-- spec/integration/users_spec.rb | 8 +++---- spec/models/topic_link_spec.rb | 2 +- .../acceptance/password-reset-test.js.es6 | 2 +- .../plugin-outlet-connector-class-test.js.es6 | 2 +- .../plugin-outlet-multi-template-test.js.es6 | 2 +- .../plugin-outlet-single-template-test.js.es6 | 2 +- .../acceptance/preferences-test.js.es6 | 10 ++++---- .../javascripts/acceptance/search-test.js.es6 | 2 +- .../acceptance/user-anonymous-test.js.es6 | 12 +++++----- test/javascripts/acceptance/user-test.js.es6 | 8 +++---- .../fixtures/group-fixtures.js.es6 | 8 +++---- test/javascripts/lib/computed-test.js.es6 | 6 ++--- test/javascripts/lib/discourse-test.js.es6 | 4 ++-- test/javascripts/lib/pretty-text-test.js.es6 | 4 ++-- .../widgets/poster-name-test.js.es6 | 4 ++-- 38 files changed, 82 insertions(+), 80 deletions(-) diff --git a/app/assets/javascripts/admin/models/admin-user.js.es6 b/app/assets/javascripts/admin/models/admin-user.js.es6 index 48764b671d..4c952d953f 100644 --- a/app/assets/javascripts/admin/models/admin-user.js.es6 +++ b/app/assets/javascripts/admin/models/admin-user.js.es6 @@ -5,6 +5,7 @@ import { popupAjaxError } from 'discourse/lib/ajax-error'; import ApiKey from 'admin/models/api-key'; import Group from 'discourse/models/group'; import TL3Requirements from 'admin/models/tl3-requirements'; +import { userPath } from 'discourse/lib/url'; const AdminUser = Discourse.User.extend({ @@ -346,7 +347,7 @@ const AdminUser = Discourse.User.extend({ }, sendActivationEmail() { - return ajax('/users/action/send_activation_email', { + return ajax(userPath('action/send_activation_email'), { type: 'POST', data: { username: this.get('username') } }).then(function() { diff --git a/app/assets/javascripts/discourse/routes/user.js.es6 b/app/assets/javascripts/discourse/routes/user.js.es6 index 936ebadfcc..2ab3296925 100644 --- a/app/assets/javascripts/discourse/routes/user.js.es6 +++ b/app/assets/javascripts/discourse/routes/user.js.es6 @@ -83,14 +83,14 @@ export default Discourse.Route.extend({ activate() { this._super(); const user = this.modelFor('user'); - this.messageBus.subscribe("/users/" + user.get('username_lower'), function(data) { + this.messageBus.subscribe("/u/" + user.get('username_lower'), function(data) { user.loadUserAction(data); }); }, deactivate() { this._super(); - this.messageBus.unsubscribe("/users/" + this.modelFor('user').get('username_lower')); + this.messageBus.unsubscribe("/u/" + this.modelFor('user').get('username_lower')); // Remove the search context this.searchService.set('searchContext', null); diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 8fdd4b9536..c0e9791116 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -348,7 +348,9 @@ class Admin::UsersController < Admin::AdminController email_token: email_token.token) end - render json: success_json.merge!(password_url: "#{Discourse.base_url}/users/password-reset/#{email_token.token}") + render json: success_json.merge!( + password_url: "#{Discourse.base_url}#{password_reset_token_path(token: email_token.token)}" + ) end diff --git a/app/controllers/list_controller.rb b/app/controllers/list_controller.rb index 69391459d8..b0f9efcf31 100644 --- a/app/controllers/list_controller.rb +++ b/app/controllers/list_controller.rb @@ -192,8 +192,8 @@ class ListController < ApplicationController target_user = fetch_user_from_params @title = "#{SiteSetting.title} - #{I18n.t("rss_description.user_topics", username: target_user.username)}" - @link = "#{Discourse.base_url}/users/#{target_user.username}/activity/topics" - @atom_link = "#{Discourse.base_url}/users/#{target_user.username}/activity/topics.rss" + @link = "#{Discourse.base_url}/u/#{target_user.username}/activity/topics" + @atom_link = "#{Discourse.base_url}/u/#{target_user.username}/activity/topics.rss" @description = I18n.t("rss_description.user_topics", username: target_user.username) @topic_list = TopicQuery.new(nil, order: 'created').send("list_topics_by", target_user) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 225b01f1d6..8531c42e92 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -103,7 +103,7 @@ class PostsController < ApplicationController @posts = posts @title = "#{SiteSetting.title} - #{I18n.t("rss_description.user_posts", username: user.username)}" - @link = "#{Discourse.base_url}/users/#{user.username}/activity" + @link = "#{Discourse.base_url}/u/#{user.username}/activity" @description = I18n.t("rss_description.user_posts", username: user.username) render 'posts/latest', formats: [:rss] end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 6874fff56d..13eddab750 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -189,7 +189,7 @@ class UsersController < ApplicationController cookies[:destination_url] = "/my/#{params[:path]}" redirect_to "/login-preferences" else - redirect_to(path("/users/#{current_user.username}/#{params[:path]}")) + redirect_to(path("/u/#{current_user.username}/#{params[:path]}")) end end diff --git a/app/models/user_action.rb b/app/models/user_action.rb index 13e4dea91a..5e3599a381 100644 --- a/app/models/user_action.rb +++ b/app/models/user_action.rb @@ -260,7 +260,7 @@ SQL end if action.user - MessageBus.publish("/users/#{action.user.username.downcase}", action.id, user_ids: [user_id], group_ids: group_ids) + MessageBus.publish("/u/#{action.user.username.downcase}", action.id, user_ids: [user_id], group_ids: group_ids) end action diff --git a/app/views/email/_post.html.erb b/app/views/email/_post.html.erb index 3f0d21580f..569a12e058 100644 --- a/app/views/email/_post.html.erb +++ b/app/views/email/_post.html.erb @@ -9,10 +9,10 @@ <%- if show_username_on_post(post) %> - <%= post.user.username %> + <%= post.user.username %> <% end %> <%- if show_name_on_post(post) %> - <%= post.user.name %> + <%= post.user.name %> <% end %> <%- if post.user.title.present? %> <%= post.user.title %> diff --git a/app/views/embed/comments.html.erb b/app/views/embed/comments.html.erb index 29ab277232..23f69584fe 100644 --- a/app/views/embed/comments.html.erb +++ b/app/views/embed/comments.html.erb @@ -16,11 +16,11 @@ <%- end %>
- +

- <%= post.user.username %> + <%= post.user.username %> <%- if post.user.title.present? %> <%= post.user.title %> <%- end %> diff --git a/app/views/list/list.rss.erb b/app/views/list/list.rss.erb index d0f6c14957..69799bc593 100644 --- a/app/views/list/list.rss.erb +++ b/app/views/list/list.rss.erb @@ -20,7 +20,7 @@ <%= topic.category.name %> -

<%= t('author_wrote', author: link_to("@#{username}", "#{Discourse.base_url}/users/#{topic.user.username_lower}")).html_safe %>

+

<%= t('author_wrote', author: link_to("@#{username}", "#{Discourse.base_url}/u/#{topic.user.username_lower}")).html_safe %>

<% end %>
<%= topic.posts.first.cooked.html_safe %> diff --git a/app/views/robots_txt/index.erb b/app/views/robots_txt/index.erb index 8ae193da42..e4c6a446bc 100644 --- a/app/views/robots_txt/index.erb +++ b/app/views/robots_txt/index.erb @@ -10,6 +10,7 @@ Disallow: /auth/github/callback Disallow: /auth/cas/callback Disallow: /assets/browser-update*.js Disallow: /users/ +Disallow: /u/ Disallow: /badges/ Disallow: /search Disallow: /search/ diff --git a/app/views/topics/show.html.erb b/app/views/topics/show.html.erb index 73ef1724b6..e24626f203 100644 --- a/app/views/topics/show.html.erb +++ b/app/views/topics/show.html.erb @@ -46,7 +46,7 @@ <% if (u = post.user) %>
- + <%= "(#{u.name})" if (SiteSetting.display_name_on_posts && SiteSetting.enable_names? && !u.name.blank?) %>
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 044a99738a..749f98cdba 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1481,8 +1481,12 @@ en: all: units: "" examples: 'Enter number of hours (24), absolute time (17:30) or timestamp (2013-11-22 14:00).' + temp_open: + title: "Open Temporarily" auto_reopen: title: "Auto-open Topic" + temp_close: + title: "Close Temporarily" auto_close: title: "Auto-Close Topic" label: "Auto-close topic time:" From ed577fbff8fbfdc81379421d9bfb694ee5e2d1d8 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Fri, 31 Mar 2017 14:35:05 +0800 Subject: [PATCH 031/124] FEATURE: Pause a topic instead of permanently closing when flag threshold is reached. --- app/models/post_action.rb | 14 ++++++++++++-- config/locales/server.en.yml | 5 ++++- config/site_settings.yml | 3 +++ spec/models/post_action_spec.rb | 15 ++++++++++----- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/app/models/post_action.rb b/app/models/post_action.rb index e90767a087..39d36c2242 100644 --- a/app/models/post_action.rb +++ b/app/models/post_action.rb @@ -497,8 +497,18 @@ SQL return if flags.sum { |f| f[1] } < SiteSetting.num_flags_to_close_topic # the threshold has been reached, we will close the topic waiting for intervention - message = I18n.t("temporarily_closed_due_to_flags") - topic.update_status("closed", true, Discourse.system_user, message: message) + topic.update_status("closed", true, Discourse.system_user, + message: I18n.t( + "temporarily_closed_due_to_flags", + count: SiteSetting.num_hours_to_close_topic + ) + ) + + topic.set_or_create_status_update( + TopicStatusUpdate.types[:open], + SiteSetting.num_hours_to_close_topic, + by_user: Discourse.system_user + ) end def self.auto_hide_if_needed(acting_user, post, post_action_type) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 12ee021f74..9108c34887 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1286,6 +1286,7 @@ en: num_flaggers_to_close_topic: "Minimum number of unique flaggers that is required to automatically pause a topic for intervention" num_flags_to_close_topic: "Minimum number of active flags that is required to automatically pause a topic for intervention" + num_hours_to_close_topic: "Number of hours to pause a topic for intervention." auto_respond_to_flag_actions: "Enable automatic reply when disposing a flag." min_first_post_typing_time: "Minimum amount of time in milliseconds a user must type during first post, if threshold is not met post will automatically enter the needs approval queue. Set to 0 to disable (not recommended)" @@ -1912,7 +1913,9 @@ en: deferred: "Thanks for letting us know. We're looking into it." deferred_and_deleted: "Thanks for letting us know. We've removed the post." - temporarily_closed_due_to_flags: "This topic is temporarily closed due to a large number of community flags." + temporarily_closed_due_to_flags: + one: "This topic is temporarily closed for 1 hour due to a large number of community flags." + other: "This topic is temporarily closed for %{count} hours due to a large number of community flags." system_messages: post_hidden: diff --git a/config/site_settings.yml b/config/site_settings.yml index 7302d23e90..eb36c891b4 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -944,6 +944,9 @@ spam: max_age_unmatched_ips: 365 num_flaggers_to_close_topic: 5 num_flags_to_close_topic: 12 + num_hours_to_close_topic: + default: 4 + min: 1 auto_respond_to_flag_actions: true min_first_post_typing_time: 3000 auto_block_fast_typers_on_first_post: true diff --git a/spec/models/post_action_spec.rb b/spec/models/post_action_spec.rb index aa7daba215..9ed0e70499 100644 --- a/spec/models/post_action_spec.rb +++ b/spec/models/post_action_spec.rb @@ -447,11 +447,11 @@ describe PostAction do expect(post.hidden).to eq(false) end - it "will automatically close a topic due to large community flagging" do - SiteSetting.stubs(:flags_required_to_hide_post).returns(0) - - SiteSetting.stubs(:num_flags_to_close_topic).returns(3) - SiteSetting.stubs(:num_flaggers_to_close_topic).returns(2) + it "will automatically pause a topic due to large community flagging" do + SiteSetting.flags_required_to_hide_post = 0 + SiteSetting.num_flags_to_close_topic = 3 + SiteSetting.num_flaggers_to_close_topic = 2 + SiteSetting.num_hours_to_close_topic = 1 topic = Fabricate(:topic) post1 = create_post(topic: topic) @@ -490,6 +490,11 @@ describe PostAction do expect(topic.reload.closed).to eq(true) + topic_status_update = TopicStatusUpdate.last + + expect(topic_status_update.topic).to eq(topic) + expect(topic_status_update.execute_at).to be_within(1.second).of(1.hour.from_now) + expect(topic_status_update.status_type).to eq(TopicStatusUpdate.types[:open]) end end From b6e9871b4b48d2e5b176accf921014f7b447b721 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Fri, 31 Mar 2017 15:02:36 +0800 Subject: [PATCH 032/124] Update `Topic#closed` client side when closing/opening a topic temporarily. --- .../discourse/controllers/edit-topic-status-update.js.es6 | 1 + app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 | 1 - app/controllers/topics_controller.rb | 3 ++- spec/integration/managing_topic_status_spec.rb | 2 ++ 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/edit-topic-status-update.js.es6 b/app/assets/javascripts/discourse/controllers/edit-topic-status-update.js.es6 index b07fcd2f3e..27c3a13c16 100644 --- a/app/assets/javascripts/discourse/controllers/edit-topic-status-update.js.es6 +++ b/app/assets/javascripts/discourse/controllers/edit-topic-status-update.js.es6 @@ -71,6 +71,7 @@ export default Ember.Controller.extend(ModalFunctionality, { this.send('closeModal'); this.set('topicStatusUpdate.execute_at', result.execute_at); this.set('topicStatusUpdate.duration', result.duration); + this.set('model.closed', result.closed); } else { this.set('topicStatusUpdate', Ember.Object.create({})); this.set('selection', null); diff --git a/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 b/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 index ca925d343d..6b927c05fc 100644 --- a/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 +++ b/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 @@ -347,7 +347,6 @@ export default createWidget('topic-timeline', { result.push(h('div.title', elems)); } - if (!attrs.fullScreen && currentUser && currentUser.get('canManageTopic')) { result.push(h('div.timeline-controls', this.attach('topic-admin-menu-button', { topic }))); } diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index 257744a359..42d76310f3 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -310,7 +310,8 @@ class TopicsController < ApplicationController render json: success_json.merge!({ execute_at: topic_status_update&.execute_at, duration: topic_status_update&.duration, - based_on_last_post: topic_status_update&.based_on_last_post + based_on_last_post: topic_status_update&.based_on_last_post, + closed: topic.closed }) else render_json_error(topic) diff --git a/spec/integration/managing_topic_status_spec.rb b/spec/integration/managing_topic_status_spec.rb index 4e59f9c3df..a70f2141d0 100644 --- a/spec/integration/managing_topic_status_spec.rb +++ b/spec/integration/managing_topic_status_spec.rb @@ -56,6 +56,7 @@ RSpec.describe "Managing a topic's status update", type: :request do .to be_within(1.seconds).of(DateTime.parse(topic_status_update.execute_at.to_s)) expect(json['duration']).to eq(topic_status_update.duration) + expect(json['closed']).to eq(topic.reload.closed) end it 'should be able to delete a topic status update' do @@ -72,6 +73,7 @@ RSpec.describe "Managing a topic's status update", type: :request do expect(json['execute_at']).to eq(nil) expect(json['duration']).to eq(nil) + expect(json['closed']).to eq(topic.closed) end describe 'invalid status type' do From 2fd1c49b88dcae814eef6244c01f3cca6f75cd49 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 31 Mar 2017 00:12:07 -0700 Subject: [PATCH 033/124] we don't need this IE 10 tag any more, see http://stackoverflow.com/questions/26346917/why-use-x-ua-compatible-ie-edge-anymore --- config/nginx.sample.conf | 7 ------- 1 file changed, 7 deletions(-) diff --git a/config/nginx.sample.conf b/config/nginx.sample.conf index 46a865e334..f36d290f4e 100644 --- a/config/nginx.sample.conf +++ b/config/nginx.sample.conf @@ -63,13 +63,6 @@ server { # path to discourse's public directory set $public /var/www/discourse/public; - # Prevent Internet Explorer 10 "compatibility mode", which breaks Discourse. - # If other subdomains under your domain are supposed to use Internet Explorer Compatibility mode, - # it may be used for this one too, unless you explicitly tell IE not to use it. Alternatively, - # some people have reported having compatibility mode "stuck" on for some reason. - # (This will also prevent compatibility mode in IE 8 and 9, but those browsers aren't supported anyway. - add_header X-UA-Compatible "IE=edge"; - # without weak etags we get zero benefit from etags on dynamically compressed content # further more etags are based on the file in nginx not sha of data # use dates, it solves the problem fine even cross server From 0bbad5040a209a2ca04289aa7e8176aa7cc25184 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Fri, 31 Mar 2017 15:56:09 +0800 Subject: [PATCH 034/124] `topic-status-info` component wasn't updated when topic is closed/opened. --- .../components/topic-status-info.js.es6 | 11 ++++++----- .../discourse/controllers/topic.js.es6 | 6 +++++- .../javascripts/discourse/models/topic.js.es6 | 2 +- app/controllers/topics_controller.rb | 7 ++++++- spec/controllers/topics_controller_spec.rb | 19 ++++++++++++------- 5 files changed, 30 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/discourse/components/topic-status-info.js.es6 b/app/assets/javascripts/discourse/components/topic-status-info.js.es6 index 9545a022a0..86bf080069 100644 --- a/app/assets/javascripts/discourse/components/topic-status-info.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-status-info.js.es6 @@ -4,13 +4,14 @@ export default Ember.Component.extend(bufferedRender({ elementId: 'topic-status-info', delayedRerender: null, - rerenderTriggers: ['topic.closed', - 'topic.topic_status_update.execute_at', - 'topic.topic_status_update.based_on_last_post', - 'topic.topic_status_update.duration'], + rerenderTriggers: [ + 'topic.topic_status_update', + 'topic.topic_status_update.execute_at', + 'topic.topic_status_update.based_on_last_post', + 'topic.topic_status_update.duration' + ], buildBuffer(buffer) { - if (Ember.isEmpty(this.get('topic.topic_status_update.execute_at'))) return; if (!this.get('topic.topic_status_update')) return; let statusUpdateAt = moment(this.get('topic.topic_status_update.execute_at')); diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index a774cb8ad0..28c929189d 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -588,7 +588,11 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { }, toggleClosed() { - this.get('content').toggleStatus('closed'); + const topic = this.get('content'); + + this.get('content').toggleStatus('closed').then(result => { + topic.set('topic_status_update', result.topic_status_update); + }); }, recoverTopic() { diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index a40aecc3f7..67c6a9b40b 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -223,7 +223,7 @@ const Topic = RestModel.extend({ toggleStatus(property) { this.toggleProperty(property); - this.saveStatus(property, !!this.get(property)); + return this.saveStatus(property, !!this.get(property)); }, saveStatus(property, value, until) { diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index 42d76310f3..e7ca1a34c1 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -273,7 +273,12 @@ class TopicsController < ApplicationController @topic = Topic.find_by(id: topic_id) guardian.ensure_can_moderate!(@topic) @topic.update_status(status, enabled, current_user, until: params[:until]) - render nothing: true + + render json: success_json.merge!( + topic_status_update: TopicStatusUpdateSerializer.new( + TopicStatusUpdate.find_by(topic: @topic), root: false + ) + ) end def mute diff --git a/spec/controllers/topics_controller_spec.rb b/spec/controllers/topics_controller_spec.rb index 6377403e32..4aad6443d7 100644 --- a/spec/controllers/topics_controller_spec.rb +++ b/spec/controllers/topics_controller_spec.rb @@ -412,14 +412,19 @@ describe TopicsController do expect { xhr :put, :status, topic_id: @topic.id, status: 'title', enabled: 'true' }.to raise_error(Discourse::InvalidParameters) end - it 'calls update_status on the forum topic with false' do - Topic.any_instance.expects(:update_status).with('closed', false, @user, until: nil) - xhr :put, :status, topic_id: @topic.id, status: 'closed', enabled: 'false' - end + it 'should update the status of the topic correctly' do + @topic = Fabricate(:topic, user: @user, closed: true, topic_status_updates: [ + Fabricate(:topic_status_update, status_type: TopicStatusUpdate.types[:open]) + ]) - it 'calls update_status on the forum topic with true' do - Topic.any_instance.expects(:update_status).with('closed', true, @user, until: nil) - xhr :put, :status, topic_id: @topic.id, status: 'closed', enabled: 'true' + xhr :put, :status, topic_id: @topic.id, status: 'closed', enabled: 'false' + + expect(response).to be_success + expect(@topic.reload.closed).to eq(false) + + body = JSON.parse(response.body) + + expect(body['topic_status_update']).to eq(nil) end end From 78d87a79eb5a2497d008f2550568e4a8e794a188 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Fri, 31 Mar 2017 16:47:49 +0800 Subject: [PATCH 035/124] UX: Improve `.modal-header` style on mobile. --- .../javascripts/discourse/templates/modal.hbs | 7 +++++-- app/assets/stylesheets/desktop/modal.scss | 9 +++++++-- app/assets/stylesheets/mobile/modal.scss | 17 +++++++++-------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/modal.hbs b/app/assets/javascripts/discourse/templates/modal.hbs index 3d778a0476..5783f544aa 100644 --- a/app/assets/javascripts/discourse/templates/modal.hbs +++ b/app/assets/javascripts/discourse/templates/modal.hbs @@ -3,10 +3,13 @@