diff --git a/app/assets/javascripts/admin/components/ip-lookup.js.es6 b/app/assets/javascripts/admin/components/ip-lookup.js.es6 index 63f61dcc9..0c9b73d54 100644 --- a/app/assets/javascripts/admin/components/ip-lookup.js.es6 +++ b/app/assets/javascripts/admin/components/ip-lookup.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; export default Ember.Component.extend({ classNames: ["ip-lookup"], @@ -23,7 +24,7 @@ export default Ember.Component.extend({ this.set("show", true); if (!this.get("location")) { - Discourse.ajax("/admin/users/ip-info", { + ajax("/admin/users/ip-info", { data: { ip: this.get("ip") } }).then(function (location) { self.set("location", Em.Object.create(location)); @@ -39,7 +40,7 @@ export default Ember.Component.extend({ "order": "trust_level DESC" }; - Discourse.ajax("/admin/users/total-others-with-same-ip", { data }).then(function (result) { + ajax("/admin/users/total-others-with-same-ip", { data }).then(function (result) { self.set("totalOthersWithSameIP", result.total); }); @@ -67,7 +68,7 @@ export default Ember.Component.extend({ totalOthersWithSameIP: null }); - Discourse.ajax("/admin/users/delete-others-with-same-ip.json", { + ajax("/admin/users/delete-others-with-same-ip.json", { type: "DELETE", data: { "ip": self.get("ip"), diff --git a/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 index 1617e6133..98f76405a 100644 --- a/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; export default Ember.ArrayController.extend({ needs: ["adminBackups"], status: Ember.computed.alias("controllers.adminBackups"), @@ -39,7 +40,7 @@ export default Ember.ArrayController.extend({ _toggleReadOnlyMode(enable) { var site = this.site; - Discourse.ajax("/admin/backups/readonly", { + ajax("/admin/backups/readonly", { type: "PUT", data: { enable: enable } }).then(function() { diff --git a/app/assets/javascripts/admin/controllers/admin-email-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-index.js.es6 index b4006391b..fce0cb891 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-index.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; export default Ember.Controller.extend({ /** @@ -29,7 +30,7 @@ export default Ember.Controller.extend({ }); var self = this; - Discourse.ajax("/admin/email/test", { + ajax("/admin/email/test", { type: 'POST', data: { email_address: this.get('testEmailAddress') } }).then(function () { diff --git a/app/assets/javascripts/admin/controllers/admin-emojis.js.es6 b/app/assets/javascripts/admin/controllers/admin-emojis.js.es6 index 5277f2feb..b111a5952 100644 --- a/app/assets/javascripts/admin/controllers/admin-emojis.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-emojis.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; export default Ember.ArrayController.extend({ sortProperties: ["name"], @@ -15,7 +16,7 @@ export default Ember.ArrayController.extend({ I18n.t("yes_value"), function(destroy) { if (destroy) { - return Discourse.ajax("/admin/customize/emojis/" + emoji.get("name"), { type: "DELETE" }).then(function() { + return ajax("/admin/customize/emojis/" + emoji.get("name"), { type: "DELETE" }).then(function() { self.removeObject(emoji); }); } diff --git a/app/assets/javascripts/admin/controllers/admin-groups-bulk.js.es6 b/app/assets/javascripts/admin/controllers/admin-groups-bulk.js.es6 index 6628a6fa7..bf60519c5 100644 --- a/app/assets/javascripts/admin/controllers/admin-groups-bulk.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-groups-bulk.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import computed from 'ember-addons/ember-computed-decorators'; import { popupAjaxError } from 'discourse/lib/ajax-error'; @@ -20,7 +21,7 @@ export default Ember.Controller.extend({ .reject(x => x.length === 0); this.set('saving', true); - Discourse.ajax('/admin/groups/bulk', { + ajax('/admin/groups/bulk', { data: { users, group_id: this.get('groupId') }, method: 'PUT' }).then(() => { diff --git a/app/assets/javascripts/admin/controllers/admin-groups-type.js.es6 b/app/assets/javascripts/admin/controllers/admin-groups-type.js.es6 index eba51de4e..9a5962ccc 100644 --- a/app/assets/javascripts/admin/controllers/admin-groups-type.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-groups-type.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; export default Ember.ArrayController.extend({ sortProperties: ['name'], refreshingAutoGroups: false, @@ -9,7 +10,7 @@ export default Ember.ArrayController.extend({ refreshAutoGroups: function(){ var self = this; this.set('refreshingAutoGroups', true); - Discourse.ajax('/admin/groups/refresh_automatic_groups', {type: 'POST'}).then(function() { + ajax('/admin/groups/refresh_automatic_groups', {type: 'POST'}).then(function() { self.transitionToRoute("adminGroupsType", "automatic").then(function() { self.set('refreshingAutoGroups', false); }); 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 a5e34fc25..0c1cb7abb 100644 --- a/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import CanCheckEmails from 'discourse/mixins/can-check-emails'; import { propertyNotEqual, setting } from 'discourse/lib/computed'; @@ -38,7 +39,7 @@ export default Ember.Controller.extend(CanCheckEmails, { saveTitle() { const self = this; - return Discourse.ajax("/users/" + this.get('model.username').toLowerCase(), { + return ajax("/users/" + this.get('model.username').toLowerCase(), { data: {title: this.get('userTitleValue')}, type: 'PUT' }).catch(function(e) { @@ -68,7 +69,7 @@ export default Ember.Controller.extend(CanCheckEmails, { savePrimaryGroup() { const self = this; - return Discourse.ajax("/admin/users/" + this.get('model.id') + "/primary_group", { + return ajax("/admin/users/" + this.get('model.id') + "/primary_group", { type: 'PUT', data: {primary_group_id: this.get('model.primary_group_id')} }).then(function () { diff --git a/app/assets/javascripts/admin/controllers/modals/admin-edit-badge-groupings.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-edit-badge-groupings.js.es6 index 6c0f6b9cc..086080c17 100644 --- a/app/assets/javascripts/admin/controllers/modals/admin-edit-badge-groupings.js.es6 +++ b/app/assets/javascripts/admin/controllers/modals/admin-edit-badge-groupings.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; export default Ember.Controller.extend({ needs: ['modal'], @@ -57,7 +58,7 @@ export default Ember.Controller.extend({ const groupIds = items.map(function(i){return i.get("id") || -1;}); const names = items.map(function(i){return i.get("name");}); - Discourse.ajax('/admin/badges/badge_groupings',{ + ajax('/admin/badges/badge_groupings',{ data: {ids: groupIds, names: names}, method: 'POST' }).then(function(data){ diff --git a/app/assets/javascripts/admin/models/admin-dashboard.js.es6 b/app/assets/javascripts/admin/models/admin-dashboard.js.es6 index 866012b95..ac44a7677 100644 --- a/app/assets/javascripts/admin/models/admin-dashboard.js.es6 +++ b/app/assets/javascripts/admin/models/admin-dashboard.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; const AdminDashboard = Discourse.Model.extend({}); @@ -11,7 +12,7 @@ AdminDashboard.reopenClass({ @return {jqXHR} a jQuery Promise object **/ find: function() { - return Discourse.ajax("/admin/dashboard.json").then(function(json) { + return ajax("/admin/dashboard.json").then(function(json) { var model = AdminDashboard.create(json); model.set('loaded', true); return model; @@ -26,7 +27,7 @@ AdminDashboard.reopenClass({ @return {jqXHR} a jQuery Promise object **/ fetchProblems: function() { - return Discourse.ajax("/admin/dashboard/problems.json", { + return ajax("/admin/dashboard/problems.json", { type: 'GET', dataType: 'json' }).then(function(json) { diff --git a/app/assets/javascripts/admin/models/admin-user.js.es6 b/app/assets/javascripts/admin/models/admin-user.js.es6 index c97f5ea91..d80489477 100644 --- a/app/assets/javascripts/admin/models/admin-user.js.es6 +++ b/app/assets/javascripts/admin/models/admin-user.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import computed from 'ember-addons/ember-computed-decorators'; import { propertyNotEqual } from 'discourse/lib/computed'; import { popupAjaxError } from 'discourse/lib/ajax-error'; @@ -40,7 +41,7 @@ const AdminUser = Discourse.User.extend({ canResetBounceScore: Ember.computed.gt("bounce_score", 0), resetBounceScore() { - return Discourse.ajax(`/admin/users/${this.get("id")}/reset_bounce_score`, { + return ajax(`/admin/users/${this.get("id")}/reset_bounce_score`, { type: 'POST' }).then(() => this.setProperties({ "bounce_score": 0, @@ -50,7 +51,7 @@ const AdminUser = Discourse.User.extend({ generateApiKey() { const self = this; - return Discourse.ajax("/admin/users/" + this.get('id') + "/generate_api_key", { + return ajax("/admin/users/" + this.get('id') + "/generate_api_key", { type: 'POST' }).then(function (result) { const apiKey = ApiKey.create(result.api_key); @@ -60,20 +61,20 @@ const AdminUser = Discourse.User.extend({ }, groupAdded(added) { - return Discourse.ajax("/admin/users/" + this.get('id') + "/groups", { + return ajax("/admin/users/" + this.get('id') + "/groups", { type: 'POST', data: { group_id: added.id } }).then(() => this.get('groups').pushObject(added)); }, groupRemoved(groupId) { - return Discourse.ajax("/admin/users/" + this.get('id') + "/groups/" + groupId, { + return ajax("/admin/users/" + this.get('id') + "/groups/" + groupId, { type: 'DELETE' }).then(() => this.set('groups.[]', this.get('groups').rejectBy("id", groupId))); }, revokeApiKey() { - return Discourse.ajax("/admin/users/" + this.get('id') + "/revoke_api_key", { + return ajax("/admin/users/" + this.get('id') + "/revoke_api_key", { type: 'DELETE' }).then(() => this.set('api_key', null)); }, @@ -104,7 +105,7 @@ const AdminUser = Discourse.User.extend({ "label": ' ' + I18n.t("admin.user.delete_all_posts"), "class": "btn btn-danger", "callback": function() { - Discourse.ajax("/admin/users/" + user.get('id') + "/delete_all_posts", { + ajax("/admin/users/" + user.get('id') + "/delete_all_posts", { type: 'PUT' }).then(() => user.set('post_count', 0)); } @@ -114,7 +115,7 @@ const AdminUser = Discourse.User.extend({ revokeAdmin() { const self = this; - return Discourse.ajax("/admin/users/" + this.get('id') + "/revoke_admin", { + return ajax("/admin/users/" + this.get('id') + "/revoke_admin", { type: 'PUT' }).then(function() { self.setProperties({ @@ -127,7 +128,7 @@ const AdminUser = Discourse.User.extend({ grantAdmin() { const self = this; - return Discourse.ajax("/admin/users/" + this.get('id') + "/grant_admin", { + return ajax("/admin/users/" + this.get('id') + "/grant_admin", { type: 'PUT' }).then(function() { self.setProperties({ @@ -140,7 +141,7 @@ const AdminUser = Discourse.User.extend({ revokeModeration() { const self = this; - return Discourse.ajax("/admin/users/" + this.get('id') + "/revoke_moderation", { + return ajax("/admin/users/" + this.get('id') + "/revoke_moderation", { type: 'PUT' }).then(function() { self.setProperties({ @@ -153,7 +154,7 @@ const AdminUser = Discourse.User.extend({ grantModeration() { const self = this; - return Discourse.ajax("/admin/users/" + this.get('id') + "/grant_moderation", { + return ajax("/admin/users/" + this.get('id') + "/grant_moderation", { type: 'PUT' }).then(function() { self.setProperties({ @@ -165,14 +166,14 @@ const AdminUser = Discourse.User.extend({ }, refreshBrowsers() { - return Discourse.ajax("/admin/users/" + this.get('id') + "/refresh_browsers", { + return ajax("/admin/users/" + this.get('id') + "/refresh_browsers", { type: 'POST' }).finally(() => bootbox.alert(I18n.t("admin.user.refresh_browsers_message"))); }, approve() { const self = this; - return Discourse.ajax("/admin/users/" + this.get('id') + "/approve", { + return ajax("/admin/users/" + this.get('id') + "/approve", { type: 'PUT' }).then(function() { self.setProperties({ @@ -190,7 +191,7 @@ const AdminUser = Discourse.User.extend({ dirty: propertyNotEqual('originalTrustLevel', 'trustLevel.id'), saveTrustLevel() { - return Discourse.ajax("/admin/users/" + this.id + "/trust_level", { + return ajax("/admin/users/" + this.id + "/trust_level", { type: 'PUT', data: { level: this.get('trustLevel.id') } }).then(function() { @@ -210,7 +211,7 @@ const AdminUser = Discourse.User.extend({ }, lockTrustLevel(locked) { - return Discourse.ajax("/admin/users/" + this.id + "/trust_level_lock", { + return ajax("/admin/users/" + this.id + "/trust_level_lock", { type: 'PUT', data: { locked: !!locked } }).then(function() { @@ -239,14 +240,14 @@ const AdminUser = Discourse.User.extend({ }.property('suspended_till', 'suspended_at'), suspend(duration, reason) { - return Discourse.ajax("/admin/users/" + this.id + "/suspend", { + return ajax("/admin/users/" + this.id + "/suspend", { type: 'PUT', data: { duration: duration, reason: reason } }); }, unsuspend() { - return Discourse.ajax("/admin/users/" + this.id + "/unsuspend", { + return ajax("/admin/users/" + this.id + "/unsuspend", { type: 'PUT' }).then(function() { window.location.reload(); @@ -257,7 +258,7 @@ const AdminUser = Discourse.User.extend({ }, log_out() { - return Discourse.ajax("/admin/users/" + this.id + "/log_out", { + return ajax("/admin/users/" + this.id + "/log_out", { type: 'POST', data: { username_or_email: this.get('username') } }).then(function() { @@ -266,7 +267,7 @@ const AdminUser = Discourse.User.extend({ }, impersonate() { - return Discourse.ajax("/admin/impersonate", { + return ajax("/admin/impersonate", { type: 'POST', data: { username_or_email: this.get('username') } }).then(function() { @@ -281,7 +282,7 @@ const AdminUser = Discourse.User.extend({ }, activate() { - return Discourse.ajax('/admin/users/' + this.id + '/activate', { + return ajax('/admin/users/' + this.id + '/activate', { type: 'PUT' }).then(function() { window.location.reload(); @@ -292,7 +293,7 @@ const AdminUser = Discourse.User.extend({ }, deactivate() { - return Discourse.ajax('/admin/users/' + this.id + '/deactivate', { + return ajax('/admin/users/' + this.id + '/deactivate', { type: 'PUT' }).then(function() { window.location.reload(); @@ -304,7 +305,7 @@ const AdminUser = Discourse.User.extend({ unblock() { this.set('blockingUser', true); - return Discourse.ajax('/admin/users/' + this.id + '/unblock', { + return ajax('/admin/users/' + this.id + '/unblock', { type: 'PUT' }).then(function() { window.location.reload(); @@ -320,7 +321,7 @@ const AdminUser = Discourse.User.extend({ const performBlock = function() { user.set('blockingUser', true); - return Discourse.ajax('/admin/users/' + user.id + '/block', { + return ajax('/admin/users/' + user.id + '/block', { type: 'PUT' }).then(function() { window.location.reload(); @@ -345,7 +346,7 @@ const AdminUser = Discourse.User.extend({ }, sendActivationEmail() { - return Discourse.ajax('/users/action/send_activation_email', { + return ajax('/users/action/send_activation_email', { type: 'POST', data: { username: this.get('username') } }).then(function() { @@ -360,7 +361,7 @@ const AdminUser = Discourse.User.extend({ message = I18n.t("admin.user.anonymize_confirm"); const performAnonymize = function() { - return Discourse.ajax("/admin/users/" + user.get('id') + '/anonymize.json', { + return ajax("/admin/users/" + user.get('id') + '/anonymize.json', { type: 'PUT' }).then(function(data) { if (data.success) { @@ -422,7 +423,7 @@ const AdminUser = Discourse.User.extend({ if (opts && opts.deletePosts) { formData["delete_posts"] = true; } - return Discourse.ajax("/admin/users/" + user.get('id') + '.json', { + return ajax("/admin/users/" + user.get('id') + '.json', { type: 'DELETE', data: formData }).then(function(data) { @@ -481,7 +482,7 @@ const AdminUser = Discourse.User.extend({ "label": ' ' + I18n.t("flagging.yes_delete_spammer"), "class": "btn btn-danger", "callback": function() { - return Discourse.ajax("/admin/users/" + user.get('id') + '.json', { + return ajax("/admin/users/" + user.get('id') + '.json', { type: 'DELETE', data: { delete_posts: true, @@ -549,7 +550,7 @@ AdminUser.reopenClass({ }); }); - return Discourse.ajax("/admin/users/approve-bulk", { + return ajax("/admin/users/approve-bulk", { type: 'PUT', data: { users: users.map((u) => u.id) } }).finally(() => bootbox.alert(I18n.t("admin.user.approve_bulk_success"))); @@ -561,7 +562,7 @@ AdminUser.reopenClass({ user.set('selected', false); }); - return Discourse.ajax("/admin/users/reject-bulk", { + return ajax("/admin/users/reject-bulk", { type: 'DELETE', data: { users: users.map((u) => u.id), @@ -571,14 +572,14 @@ AdminUser.reopenClass({ }, find(user_id) { - return Discourse.ajax("/admin/users/" + user_id + ".json").then(result => { + return ajax("/admin/users/" + user_id + ".json").then(result => { result.loadedDetails = true; return AdminUser.create(result); }); }, findAll(query, filter) { - return Discourse.ajax("/admin/users/list/" + query + ".json", { + return ajax("/admin/users/list/" + query + ".json", { data: filter }).then(function(users) { return users.map((u) => AdminUser.create(u)); diff --git a/app/assets/javascripts/admin/models/api-key.js.es6 b/app/assets/javascripts/admin/models/api-key.js.es6 index 7ef771954..aa05ce834 100644 --- a/app/assets/javascripts/admin/models/api-key.js.es6 +++ b/app/assets/javascripts/admin/models/api-key.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; const ApiKey = Discourse.Model.extend({ /** @@ -8,7 +9,7 @@ const ApiKey = Discourse.Model.extend({ **/ regenerate: function() { var self = this; - return Discourse.ajax('/admin/api/key', {type: 'PUT', data: {id: this.get('id')}}).then(function (result) { + return ajax('/admin/api/key', {type: 'PUT', data: {id: this.get('id')}}).then(function (result) { self.set('key', result.api_key.key); return self; }); @@ -21,7 +22,7 @@ const ApiKey = Discourse.Model.extend({ @returns {Promise} a promise that resolves when the key has been revoked **/ revoke: function() { - return Discourse.ajax('/admin/api/key', {type: 'DELETE', data: {id: this.get('id')}}); + return ajax('/admin/api/key', {type: 'DELETE', data: {id: this.get('id')}}); } }); @@ -51,7 +52,7 @@ ApiKey.reopenClass({ @returns {Promise} a promise that resolves to the array of `ApiKey` instances **/ find: function() { - return Discourse.ajax("/admin/api").then(function(keys) { + return ajax("/admin/api").then(function(keys) { return keys.map(function (key) { return ApiKey.create(key); }); @@ -65,7 +66,7 @@ ApiKey.reopenClass({ @returns {Promise} a promise that resolves to a master `ApiKey` **/ generateMasterKey: function() { - return Discourse.ajax("/admin/api/key", {type: 'POST'}).then(function (result) { + return ajax("/admin/api/key", {type: 'POST'}).then(function (result) { return ApiKey.create(result.api_key); }); } diff --git a/app/assets/javascripts/admin/models/backup.js.es6 b/app/assets/javascripts/admin/models/backup.js.es6 index 8b4991b72..c38f74ec8 100644 --- a/app/assets/javascripts/admin/models/backup.js.es6 +++ b/app/assets/javascripts/admin/models/backup.js.es6 @@ -1,11 +1,12 @@ +import { ajax } from 'discourse/lib/ajax'; const Backup = Discourse.Model.extend({ destroy() { - return Discourse.ajax("/admin/backups/" + this.get("filename"), { type: "DELETE" }); + return ajax("/admin/backups/" + this.get("filename"), { type: "DELETE" }); }, restore() { - return Discourse.ajax("/admin/backups/" + this.get("filename") + "/restore", { + return ajax("/admin/backups/" + this.get("filename") + "/restore", { type: "POST", data: { client_id: window.MessageBus.clientId } }); @@ -16,13 +17,13 @@ const Backup = Discourse.Model.extend({ Backup.reopenClass({ find() { - return PreloadStore.getAndRemove("backups", () => Discourse.ajax("/admin/backups.json")) + return PreloadStore.getAndRemove("backups", () => ajax("/admin/backups.json")) .then(backups => backups.map(backup => Backup.create(backup))); }, start(withUploads) { if (withUploads === undefined) { withUploads = true; } - return Discourse.ajax("/admin/backups", { + return ajax("/admin/backups", { type: "POST", data: { with_uploads: withUploads, @@ -34,14 +35,14 @@ Backup.reopenClass({ }, cancel() { - return Discourse.ajax("/admin/backups/cancel.json") + return ajax("/admin/backups/cancel.json") .then(result => { if (!result.success) { bootbox.alert(result.message); } }); }, rollback() { - return Discourse.ajax("/admin/backups/rollback.json") + return ajax("/admin/backups/rollback.json") .then(result => { if (!result.success) { bootbox.alert(result.message); diff --git a/app/assets/javascripts/admin/models/color-scheme.js.es6 b/app/assets/javascripts/admin/models/color-scheme.js.es6 index 512672230..743c779d6 100644 --- a/app/assets/javascripts/admin/models/color-scheme.js.es6 +++ b/app/assets/javascripts/admin/models/color-scheme.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import ColorSchemeColor from 'admin/models/color-scheme-color'; const ColorScheme = Discourse.Model.extend(Ember.Copyable, { @@ -65,7 +66,7 @@ const ColorScheme = Discourse.Model.extend(Ember.Copyable, { }); } - return Discourse.ajax("/admin/color_schemes" + (this.id ? '/' + this.id : '') + '.json', { + return ajax("/admin/color_schemes" + (this.id ? '/' + this.id : '') + '.json', { data: JSON.stringify({"color_scheme": data}), type: this.id ? 'PUT' : 'POST', dataType: 'json', @@ -88,7 +89,7 @@ const ColorScheme = Discourse.Model.extend(Ember.Copyable, { destroy: function() { if (this.id) { - return Discourse.ajax("/admin/color_schemes/" + this.id, { type: 'DELETE' }); + return ajax("/admin/color_schemes/" + this.id, { type: 'DELETE' }); } } @@ -106,7 +107,7 @@ var ColorSchemes = Ember.ArrayProxy.extend({ ColorScheme.reopenClass({ findAll: function() { var colorSchemes = ColorSchemes.create({ content: [], loading: true }); - Discourse.ajax('/admin/color_schemes').then(function(all) { + ajax('/admin/color_schemes').then(function(all) { _.each(all, function(colorScheme){ colorSchemes.pushObject(ColorScheme.create({ id: colorScheme.id, diff --git a/app/assets/javascripts/admin/models/email-log.js.es6 b/app/assets/javascripts/admin/models/email-log.js.es6 index 2b19eeff4..dd6948bd4 100644 --- a/app/assets/javascripts/admin/models/email-log.js.es6 +++ b/app/assets/javascripts/admin/models/email-log.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import AdminUser from 'admin/models/admin-user'; const EmailLog = Discourse.Model.extend({}); @@ -21,7 +22,7 @@ EmailLog.reopenClass({ const status = filter.status || "sent"; filter = _.omit(filter, "status"); - return Discourse.ajax(`/admin/email/${status}.json?offset=${offset}`, { data: filter }) + return ajax(`/admin/email/${status}.json?offset=${offset}`, { data: filter }) .then(logs => _.map(logs, log => EmailLog.create(log))); } }); diff --git a/app/assets/javascripts/admin/models/email-preview.js.es6 b/app/assets/javascripts/admin/models/email-preview.js.es6 index 12826f98e..f992bf250 100644 --- a/app/assets/javascripts/admin/models/email-preview.js.es6 +++ b/app/assets/javascripts/admin/models/email-preview.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; const EmailPreview = Discourse.Model.extend({}); EmailPreview.reopenClass({ @@ -11,7 +12,7 @@ EmailPreview.reopenClass({ username = Discourse.User.current().username; } - return Discourse.ajax("/admin/email/preview-digest.json", { + return ajax("/admin/email/preview-digest.json", { data: { last_seen_at: lastSeenAt, username: username } }).then(function (result) { return EmailPreview.create(result); diff --git a/app/assets/javascripts/admin/models/email-settings.js.es6 b/app/assets/javascripts/admin/models/email-settings.js.es6 index 1b8f791f2..ed5f000d5 100644 --- a/app/assets/javascripts/admin/models/email-settings.js.es6 +++ b/app/assets/javascripts/admin/models/email-settings.js.es6 @@ -1,8 +1,9 @@ +import { ajax } from 'discourse/lib/ajax'; const EmailSettings = Discourse.Model.extend({}); EmailSettings.reopenClass({ find: function() { - return Discourse.ajax("/admin/email.json").then(function (settings) { + return ajax("/admin/email.json").then(function (settings) { return EmailSettings.create(settings); }); } diff --git a/app/assets/javascripts/admin/models/email-template.js.es6 b/app/assets/javascripts/admin/models/email-template.js.es6 index 7e8e6579a..81d15c06c 100644 --- a/app/assets/javascripts/admin/models/email-template.js.es6 +++ b/app/assets/javascripts/admin/models/email-template.js.es6 @@ -1,9 +1,10 @@ +import { ajax } from 'discourse/lib/ajax'; import RestModel from 'discourse/models/rest'; const { getProperties } = Ember; export default RestModel.extend({ revert() { - return Discourse.ajax(`/admin/customize/email_templates/${this.get('id')}`, { + return ajax(`/admin/customize/email_templates/${this.get('id')}`, { method: 'DELETE' }).then(result => getProperties(result.email_template, 'subject', 'body', 'can_revert')); } diff --git a/app/assets/javascripts/admin/models/flagged-post.js.es6 b/app/assets/javascripts/admin/models/flagged-post.js.es6 index 8492c4f8b..c7a654608 100644 --- a/app/assets/javascripts/admin/models/flagged-post.js.es6 +++ b/app/assets/javascripts/admin/models/flagged-post.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import AdminUser from 'admin/models/admin-user'; import Topic from 'discourse/models/topic'; import Post from 'discourse/models/post'; @@ -106,22 +107,22 @@ const FlaggedPost = Post.extend({ deletePost: function() { if (this.get('post_number') === 1) { - return Discourse.ajax('/t/' + this.topic_id, { type: 'DELETE', cache: false }); + return ajax('/t/' + this.topic_id, { type: 'DELETE', cache: false }); } else { - return Discourse.ajax('/posts/' + this.id, { type: 'DELETE', cache: false }); + return ajax('/posts/' + this.id, { type: 'DELETE', cache: false }); } }, disagreeFlags: function () { - return Discourse.ajax('/admin/flags/disagree/' + this.id, { type: 'POST', cache: false }); + return ajax('/admin/flags/disagree/' + this.id, { type: 'POST', cache: false }); }, deferFlags: function (deletePost) { - return Discourse.ajax('/admin/flags/defer/' + this.id, { type: 'POST', cache: false, data: { delete_post: deletePost } }); + return ajax('/admin/flags/defer/' + this.id, { type: 'POST', cache: false, data: { delete_post: deletePost } }); }, agreeFlags: function (actionOnPost) { - return Discourse.ajax('/admin/flags/agree/' + this.id, { type: 'POST', cache: false, data: { action_on_post: actionOnPost } }); + return ajax('/admin/flags/agree/' + this.id, { type: 'POST', cache: false, data: { action_on_post: actionOnPost } }); }, postHidden: Em.computed.alias('hidden'), @@ -144,7 +145,7 @@ FlaggedPost.reopenClass({ var result = Em.A(); result.set('loading', true); - return Discourse.ajax('/admin/flags/' + filter + '.json?offset=' + offset).then(function (data) { + return ajax('/admin/flags/' + filter + '.json?offset=' + offset).then(function (data) { // users var userLookup = {}; _.each(data.users, function (user) { diff --git a/app/assets/javascripts/admin/models/incoming-email.js.es6 b/app/assets/javascripts/admin/models/incoming-email.js.es6 index 82534c5c4..d0386b2bc 100644 --- a/app/assets/javascripts/admin/models/incoming-email.js.es6 +++ b/app/assets/javascripts/admin/models/incoming-email.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import AdminUser from 'admin/models/admin-user'; const IncomingEmail = Discourse.Model.extend({}); @@ -15,7 +16,7 @@ IncomingEmail.reopenClass({ }, find(id) { - return Discourse.ajax(`/admin/email/incoming/${id}.json`); + return ajax(`/admin/email/incoming/${id}.json`); }, findAll(filter, offset) { @@ -25,12 +26,12 @@ IncomingEmail.reopenClass({ const status = filter.status || "received"; filter = _.omit(filter, "status"); - return Discourse.ajax(`/admin/email/${status}.json?offset=${offset}`, { data: filter }) + return ajax(`/admin/email/${status}.json?offset=${offset}`, { data: filter }) .then(incomings => _.map(incomings, incoming => IncomingEmail.create(incoming))); }, loadRawEmail(id) { - return Discourse.ajax(`/admin/email/incoming/${id}/raw.json`); + return ajax(`/admin/email/incoming/${id}/raw.json`); } }); diff --git a/app/assets/javascripts/admin/models/permalink.js.es6 b/app/assets/javascripts/admin/models/permalink.js.es6 index eb867adb3..966e3f10e 100644 --- a/app/assets/javascripts/admin/models/permalink.js.es6 +++ b/app/assets/javascripts/admin/models/permalink.js.es6 @@ -1,19 +1,20 @@ +import { ajax } from 'discourse/lib/ajax'; const Permalink = Discourse.Model.extend({ save: function() { - return Discourse.ajax("/admin/permalinks.json", { + return ajax("/admin/permalinks.json", { type: 'POST', data: {url: this.get('url'), permalink_type: this.get('permalink_type'), permalink_type_value: this.get('permalink_type_value')} }); }, destroy: function() { - return Discourse.ajax("/admin/permalinks/" + this.get('id') + ".json", {type: 'DELETE'}); + return ajax("/admin/permalinks/" + this.get('id') + ".json", {type: 'DELETE'}); } }); Permalink.reopenClass({ findAll: function(filter) { - return Discourse.ajax("/admin/permalinks.json", { data: { filter: filter } }).then(function(permalinks) { + return ajax("/admin/permalinks.json", { data: { filter: filter } }).then(function(permalinks) { return permalinks.map(p => Permalink.create(p)); }); } diff --git a/app/assets/javascripts/admin/models/report.js.es6 b/app/assets/javascripts/admin/models/report.js.es6 index 1891984b4..3342323b4 100644 --- a/app/assets/javascripts/admin/models/report.js.es6 +++ b/app/assets/javascripts/admin/models/report.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import round from "discourse/lib/round"; import { fmt } from 'discourse/lib/computed'; @@ -132,7 +133,7 @@ const Report = Discourse.Model.extend({ Report.reopenClass({ find(type, startDate, endDate, categoryId, groupId) { - return Discourse.ajax("/admin/reports/" + type, { + return ajax("/admin/reports/" + type, { data: { start_date: startDate, end_date: endDate, diff --git a/app/assets/javascripts/admin/models/screened-email.js.es6 b/app/assets/javascripts/admin/models/screened-email.js.es6 index 71c74d0ad..a07b9a14b 100644 --- a/app/assets/javascripts/admin/models/screened-email.js.es6 +++ b/app/assets/javascripts/admin/models/screened-email.js.es6 @@ -1,16 +1,17 @@ +import { ajax } from 'discourse/lib/ajax'; const ScreenedEmail = Discourse.Model.extend({ actionName: function() { return I18n.t("admin.logs.screened_actions." + this.get('action')); }.property('action'), clearBlock: function() { - return Discourse.ajax('/admin/logs/screened_emails/' + this.get('id'), {method: 'DELETE'}); + return ajax('/admin/logs/screened_emails/' + this.get('id'), {method: 'DELETE'}); } }); ScreenedEmail.reopenClass({ findAll: function() { - return Discourse.ajax("/admin/logs/screened_emails.json").then(function(screened_emails) { + return ajax("/admin/logs/screened_emails.json").then(function(screened_emails) { return screened_emails.map(function(b) { return ScreenedEmail.create(b); }); diff --git a/app/assets/javascripts/admin/models/screened-ip-address.js.es6 b/app/assets/javascripts/admin/models/screened-ip-address.js.es6 index 635fddf02..4bde08707 100644 --- a/app/assets/javascripts/admin/models/screened-ip-address.js.es6 +++ b/app/assets/javascripts/admin/models/screened-ip-address.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import computed from 'ember-addons/ember-computed-decorators'; const ScreenedIpAddress = Discourse.Model.extend({ @@ -14,25 +15,25 @@ const ScreenedIpAddress = Discourse.Model.extend({ }, save() { - return Discourse.ajax("/admin/logs/screened_ip_addresses" + (this.id ? '/' + this.id : '') + ".json", { + return ajax("/admin/logs/screened_ip_addresses" + (this.id ? '/' + this.id : '') + ".json", { type: this.id ? 'PUT' : 'POST', data: {ip_address: this.get('ip_address'), action_name: this.get('action_name')} }); }, destroy() { - return Discourse.ajax("/admin/logs/screened_ip_addresses/" + this.get('id') + ".json", {type: 'DELETE'}); + return ajax("/admin/logs/screened_ip_addresses/" + this.get('id') + ".json", {type: 'DELETE'}); } }); ScreenedIpAddress.reopenClass({ findAll(filter) { - return Discourse.ajax("/admin/logs/screened_ip_addresses.json", { data: { filter: filter } }) + return ajax("/admin/logs/screened_ip_addresses.json", { data: { filter: filter } }) .then(screened_ips => screened_ips.map(b => ScreenedIpAddress.create(b))); }, rollUp() { - return Discourse.ajax("/admin/logs/screened_ip_addresses/roll_up", { type: "POST" }); + return ajax("/admin/logs/screened_ip_addresses/roll_up", { type: "POST" }); } }); diff --git a/app/assets/javascripts/admin/models/screened-url.js.es6 b/app/assets/javascripts/admin/models/screened-url.js.es6 index 9b16c7fae..282bc2fde 100644 --- a/app/assets/javascripts/admin/models/screened-url.js.es6 +++ b/app/assets/javascripts/admin/models/screened-url.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; const ScreenedUrl = Discourse.Model.extend({ actionName: function() { return I18n.t("admin.logs.screened_actions." + this.get('action')); @@ -6,7 +7,7 @@ const ScreenedUrl = Discourse.Model.extend({ ScreenedUrl.reopenClass({ findAll: function() { - return Discourse.ajax("/admin/logs/screened_urls.json").then(function(screened_urls) { + return ajax("/admin/logs/screened_urls.json").then(function(screened_urls) { return screened_urls.map(function(b) { return ScreenedUrl.create(b); }); diff --git a/app/assets/javascripts/admin/models/site-setting.js.es6 b/app/assets/javascripts/admin/models/site-setting.js.es6 index 98b3ab896..510376266 100644 --- a/app/assets/javascripts/admin/models/site-setting.js.es6 +++ b/app/assets/javascripts/admin/models/site-setting.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; const SiteSetting = Discourse.Model.extend({ overridden: function() { let val = this.get('value'), @@ -28,7 +29,7 @@ const SiteSetting = Discourse.Model.extend({ SiteSetting.reopenClass({ findAll() { - return Discourse.ajax("/admin/site_settings").then(function (settings) { + return ajax("/admin/site_settings").then(function (settings) { // Group the results by category const categories = {}; settings.site_settings.forEach(function(s) { @@ -47,7 +48,7 @@ SiteSetting.reopenClass({ update(key, value) { const data = {}; data[key] = value; - return Discourse.ajax("/admin/site_settings/" + key, { type: 'PUT', data }); + return ajax("/admin/site_settings/" + key, { type: 'PUT', data }); } }); diff --git a/app/assets/javascripts/admin/models/site-text.js.es6 b/app/assets/javascripts/admin/models/site-text.js.es6 index 0d18ad7ea..8e187dede 100644 --- a/app/assets/javascripts/admin/models/site-text.js.es6 +++ b/app/assets/javascripts/admin/models/site-text.js.es6 @@ -1,9 +1,10 @@ +import { ajax } from 'discourse/lib/ajax'; import RestModel from 'discourse/models/rest'; const { getProperties } = Ember; export default RestModel.extend({ revert() { - return Discourse.ajax(`/admin/customize/site_texts/${this.get('id')}`, { + return ajax(`/admin/customize/site_texts/${this.get('id')}`, { method: 'DELETE' }).then(result => getProperties(result.site_text, 'value', 'can_revert')); } diff --git a/app/assets/javascripts/admin/models/staff-action-log.js.es6 b/app/assets/javascripts/admin/models/staff-action-log.js.es6 index b6e764ce3..24ec4fbcc 100644 --- a/app/assets/javascripts/admin/models/staff-action-log.js.es6 +++ b/app/assets/javascripts/admin/models/staff-action-log.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import AdminUser from 'admin/models/admin-user'; import { escapeExpression } from 'discourse/lib/utilities'; @@ -56,7 +57,7 @@ StaffActionLog.reopenClass({ }, findAll: function(filters) { - return Discourse.ajax("/admin/logs/staff_action_logs.json", { data: filters }).then(function(staff_actions) { + return ajax("/admin/logs/staff_action_logs.json", { data: filters }).then(function(staff_actions) { return staff_actions.map(function(s) { return StaffActionLog.create(s); }); diff --git a/app/assets/javascripts/admin/models/version-check.js.es6 b/app/assets/javascripts/admin/models/version-check.js.es6 index 4f9b2c4c6..fc8c1bf5e 100644 --- a/app/assets/javascripts/admin/models/version-check.js.es6 +++ b/app/assets/javascripts/admin/models/version-check.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; const VersionCheck = Discourse.Model.extend({ noCheckPerformed: function() { @@ -33,7 +34,7 @@ const VersionCheck = Discourse.Model.extend({ VersionCheck.reopenClass({ find: function() { - return Discourse.ajax('/admin/version_check').then(function(json) { + return ajax('/admin/version_check').then(function(json) { return VersionCheck.create(json); }); } diff --git a/app/assets/javascripts/admin/routes/admin-backups.js.es6 b/app/assets/javascripts/admin/routes/admin-backups.js.es6 index f97d86f93..1a944314e 100644 --- a/app/assets/javascripts/admin/routes/admin-backups.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-backups.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import showModal from 'discourse/lib/show-modal'; import BackupStatus from 'admin/models/backup-status'; import Backup from 'admin/models/backup'; @@ -31,7 +32,7 @@ export default Discourse.Route.extend({ model() { return PreloadStore.getAndRemove("operations_status", function() { - return Discourse.ajax("/admin/backups/status.json"); + return ajax("/admin/backups/status.json"); }).then(status => { return BackupStatus.create({ isOperationRunning: status.is_operation_running, diff --git a/app/assets/javascripts/admin/routes/admin-badges-show.js.es6 b/app/assets/javascripts/admin/routes/admin-badges-show.js.es6 index c283ff737..03d091d22 100644 --- a/app/assets/javascripts/admin/routes/admin-badges-show.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-badges-show.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import Badge from 'discourse/models/badge'; import showModal from 'discourse/lib/show-modal'; @@ -31,7 +32,7 @@ export default Ember.Route.extend({ preview(badge, explain) { badge.set('preview_loading', true); - Discourse.ajax('/admin/badges/preview.json', { + ajax('/admin/badges/preview.json', { method: 'post', data: { sql: badge.get('query'), diff --git a/app/assets/javascripts/admin/routes/admin-badges.js.es6 b/app/assets/javascripts/admin/routes/admin-badges.js.es6 index 5efa86491..68da5edb6 100644 --- a/app/assets/javascripts/admin/routes/admin-badges.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-badges.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import Badge from 'discourse/models/badge'; import BadgeGrouping from 'discourse/models/badge-grouping'; @@ -6,7 +7,7 @@ export default Discourse.Route.extend({ model: function() { var self = this; - return Discourse.ajax('/admin/badges.json').then(function(json) { + return ajax('/admin/badges.json').then(function(json) { self._json = json; return Badge.createFromJson(json); }); diff --git a/app/assets/javascripts/admin/routes/admin-emojis.js.es6 b/app/assets/javascripts/admin/routes/admin-emojis.js.es6 index a33ade011..520f7a0d5 100644 --- a/app/assets/javascripts/admin/routes/admin-emojis.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-emojis.js.es6 @@ -1,6 +1,7 @@ +import { ajax } from 'discourse/lib/ajax'; export default Discourse.Route.extend({ model: function() { - return Discourse.ajax("/admin/customize/emojis.json").then(function(emojis) { + return ajax("/admin/customize/emojis.json").then(function(emojis) { return emojis.map(function (emoji) { return Ember.Object.create(emoji); }); }); } diff --git a/app/assets/javascripts/discourse.js b/app/assets/javascripts/discourse.js index 926cc59fb..fb983d943 100644 --- a/app/assets/javascripts/discourse.js +++ b/app/assets/javascripts/discourse.js @@ -8,7 +8,7 @@ define('ember', ['exports'], function(__exports__) { var _pluginCallbacks = []; -window.Discourse = Ember.Application.extend(Discourse.Ajax, { +window.Discourse = Ember.Application.extend({ rootElement: '#main', _docTitle: document.title, __TAGS_INCLUDED__: true, @@ -179,6 +179,12 @@ window.Discourse = Ember.Application.extend(Discourse.Ajax, { }) }).create(); +Discourse.ajax = function() { + var ajax = require('discourse/lib/ajax').ajax; + Ember.warn("Discourse.ajax is deprecated. Import the module and use it instead"); + return ajax.apply(this, arguments); +}; + Discourse.Markdown = { whiteListTag: Ember.K, whiteListIframe: Ember.K diff --git a/app/assets/javascripts/discourse/adapters/post-reply-history.js.es6 b/app/assets/javascripts/discourse/adapters/post-reply-history.js.es6 index 335c22b6b..549736f79 100644 --- a/app/assets/javascripts/discourse/adapters/post-reply-history.js.es6 +++ b/app/assets/javascripts/discourse/adapters/post-reply-history.js.es6 @@ -1,9 +1,10 @@ +import { ajax } from 'discourse/lib/ajax'; import RestAdapter from 'discourse/adapters/rest'; export default RestAdapter.extend({ find(store, type, findArgs) { const maxReplies = Discourse.SiteSettings.max_reply_history; - return Discourse.ajax(`/posts/${findArgs.postId}/reply-history?max_replies=${maxReplies}`).then(replies => { + return ajax(`/posts/${findArgs.postId}/reply-history?max_replies=${maxReplies}`).then(replies => { return { post_reply_histories: replies }; }); }, diff --git a/app/assets/javascripts/discourse/adapters/post-reply.js.es6 b/app/assets/javascripts/discourse/adapters/post-reply.js.es6 index f36299d00..99112ddff 100644 --- a/app/assets/javascripts/discourse/adapters/post-reply.js.es6 +++ b/app/assets/javascripts/discourse/adapters/post-reply.js.es6 @@ -1,8 +1,9 @@ +import { ajax } from 'discourse/lib/ajax'; import RestAdapter from 'discourse/adapters/rest'; export default RestAdapter.extend({ find(store, type, findArgs) { - return Discourse.ajax(`/posts/${findArgs.postId}/replies`).then(replies => { + return ajax(`/posts/${findArgs.postId}/replies`).then(replies => { return { post_replies: replies }; }); }, diff --git a/app/assets/javascripts/discourse/adapters/post.js.es6 b/app/assets/javascripts/discourse/adapters/post.js.es6 index 1d0a375b5..3c7532d74 100644 --- a/app/assets/javascripts/discourse/adapters/post.js.es6 +++ b/app/assets/javascripts/discourse/adapters/post.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import RestAdapter from 'discourse/adapters/rest'; import { Result } from 'discourse/adapters/rest'; @@ -12,7 +13,7 @@ export default RestAdapter.extend({ createRecord(store, type, args) { const typeField = Ember.String.underscore(type); args.nested_post = true; - return Discourse.ajax(this.pathFor(store, type), { method: 'POST', data: args }).then(function (json) { + return ajax(this.pathFor(store, type), { method: 'POST', data: args }).then(function (json) { return new Result(json[typeField], json); }); } diff --git a/app/assets/javascripts/discourse/adapters/rest.js.es6 b/app/assets/javascripts/discourse/adapters/rest.js.es6 index 5faaf6d0b..75366ca80 100644 --- a/app/assets/javascripts/discourse/adapters/rest.js.es6 +++ b/app/assets/javascripts/discourse/adapters/rest.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import { hashString } from 'discourse/lib/hash'; const ADMIN_MODELS = ['plugin', 'site-customization', 'embeddable-host']; @@ -9,8 +10,6 @@ export function Result(payload, responseJson) { this.target = null; } -const ajax = Discourse.ajax; - // We use this to make sure 404s are caught function rethrow(error) { if (error.status === 404) { diff --git a/app/assets/javascripts/discourse/adapters/topic-list.js.es6 b/app/assets/javascripts/discourse/adapters/topic-list.js.es6 index bb7375597..c0311c011 100644 --- a/app/assets/javascripts/discourse/adapters/topic-list.js.es6 +++ b/app/assets/javascripts/discourse/adapters/topic-list.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import RestAdapter from 'discourse/adapters/rest'; export function finderFor(filter, params) { @@ -19,7 +20,7 @@ export function finderFor(filter, params) { url += "?" + encoded.join('&'); } } - return Discourse.ajax(url); + return ajax(url); }; } diff --git a/app/assets/javascripts/discourse/adapters/topic.js.es6 b/app/assets/javascripts/discourse/adapters/topic.js.es6 index 290dfff4b..c8a6120d2 100644 --- a/app/assets/javascripts/discourse/adapters/topic.js.es6 +++ b/app/assets/javascripts/discourse/adapters/topic.js.es6 @@ -1,9 +1,10 @@ +import { ajax } from 'discourse/lib/ajax'; import RestAdapter from 'discourse/adapters/rest'; export default RestAdapter.extend({ find(store, type, findArgs) { if (findArgs.similar) { - return Discourse.ajax("/topics/similar_to", { data: findArgs.similar }); + return ajax("/topics/similar_to", { data: findArgs.similar }); } else { return this._super(store, type, findArgs); } diff --git a/app/assets/javascripts/discourse/components/composer-editor.js.es6 b/app/assets/javascripts/discourse/components/composer-editor.js.es6 index 362165eda..71c660807 100644 --- a/app/assets/javascripts/discourse/components/composer-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-editor.js.es6 @@ -4,6 +4,7 @@ import { linkSeenMentions, fetchUnseenMentions } from 'discourse/lib/link-mentio import { linkSeenCategoryHashtags, fetchUnseenCategoryHashtags } from 'discourse/lib/link-category-hashtags'; import { fetchUnseenTagHashtags, linkSeenTagHashtags } from 'discourse/lib/link-tag-hashtag'; import { load } from 'pretty-text/oneboxer'; +import { ajax } from 'discourse/lib/ajax'; import InputValidation from 'discourse/models/input-validation'; import { tinyAvatar, @@ -499,7 +500,7 @@ export default Ember.Component.extend({ } // Paint oneboxes - $('a.onebox', $preview).each((i, e) => load(e, refresh)); + $('a.onebox', $preview).each((i, e) => load(e, refresh, ajax)); this.trigger('previewRefreshed', $preview); this.sendAction('afterRefresh', $preview); }, diff --git a/app/assets/javascripts/discourse/controllers/create-account.js.es6 b/app/assets/javascripts/discourse/controllers/create-account.js.es6 index 985fa42dd..9bed6142a 100644 --- a/app/assets/javascripts/discourse/controllers/create-account.js.es6 +++ b/app/assets/javascripts/discourse/controllers/create-account.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import debounce from 'discourse/lib/debounce'; import ModalFunctionality from 'discourse/mixins/modal-functionality'; import { setting } from 'discourse/lib/computed'; @@ -336,7 +337,7 @@ export default Ember.Controller.extend(ModalFunctionality, { @on('init') fetchConfirmationValue() { - return Discourse.ajax('/users/hp.json').then(json => { + return ajax('/users/hp.json').then(json => { this.set('accountPasswordConfirm', json.value); this.set('accountChallenge', json.challenge.split("").reverse().join("")); }); diff --git a/app/assets/javascripts/discourse/controllers/edit-topic-auto-close.js.es6 b/app/assets/javascripts/discourse/controllers/edit-topic-auto-close.js.es6 index 9cd0e00e7..f0f4301f3 100644 --- a/app/assets/javascripts/discourse/controllers/edit-topic-auto-close.js.es6 +++ b/app/assets/javascripts/discourse/controllers/edit-topic-auto-close.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import { observes } from "ember-addons/ember-computed-decorators"; import ModalFunctionality from 'discourse/mixins/modal-functionality'; @@ -32,7 +33,7 @@ export default Ember.Controller.extend(ModalFunctionality, { setAutoClose(time) { const self = this; this.set('loading', true); - Discourse.ajax({ + ajax({ url: `/t/${this.get('model.id')}/autoclose`, type: 'PUT', dataType: 'json', diff --git a/app/assets/javascripts/discourse/controllers/feature-topic.js.es6 b/app/assets/javascripts/discourse/controllers/feature-topic.js.es6 index f83e50c62..bb195b636 100644 --- a/app/assets/javascripts/discourse/controllers/feature-topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/feature-topic.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import ModalFunctionality from 'discourse/mixins/modal-functionality'; import { categoryLinkHTML } from 'discourse/helpers/category-link'; import computed from 'ember-addons/ember-computed-decorators'; @@ -91,7 +92,7 @@ export default Ember.Controller.extend(ModalFunctionality, { onShow() { this.set("loading", true); - return Discourse.ajax("/topics/feature_stats.json", { + return ajax("/topics/feature_stats.json", { data: { category_id: this.get("model.category.id") } }).then(result => { if (result) { diff --git a/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 b/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 index 242bfc731..fd98f7910 100644 --- a/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 +++ b/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import ModalFunctionality from 'discourse/mixins/modal-functionality'; import { escapeExpression } from 'discourse/lib/utilities'; @@ -49,7 +50,7 @@ export default Ember.Controller.extend(ModalFunctionality, { self.flash(e.responseJSON.errors[0], 'error'); }; - Discourse.ajax('/session/forgot_password', { + ajax('/session/forgot_password', { data: { login: this.get('accountEmailOrUsername').trim() }, type: 'POST' }).then(success, fail).finally(function(){ 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 65ff5da4c..27ee3f030 100644 --- a/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 +++ b/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import { translateResults, searchContextDescription, getSearchKey, isValidSearchTerm } from "discourse/lib/search"; import showModal from 'discourse/lib/show-modal'; import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; @@ -157,7 +158,7 @@ export default Ember.Controller.extend({ const searchKey = getSearchKey(args); - Discourse.ajax("/search", { data: args }).then(results => { + ajax("/search", { data: args }).then(results => { const model = translateResults(results) || {}; router.transientCache('lastSearch', { searchKey, model }, 5); this.set("model", model); @@ -194,7 +195,7 @@ export default Ember.Controller.extend({ showSearchHelp() { // TODO: dupe code should be centralized - Discourse.ajax("/static/search_help.html", { dataType: 'html' }).then((model) => { + ajax("/static/search_help.html", { dataType: 'html' }).then((model) => { showModal('searchHelp', { model }); }); }, diff --git a/app/assets/javascripts/discourse/controllers/login.js.es6 b/app/assets/javascripts/discourse/controllers/login.js.es6 index fd884e391..4303de475 100644 --- a/app/assets/javascripts/discourse/controllers/login.js.es6 +++ b/app/assets/javascripts/discourse/controllers/login.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import ModalFunctionality from 'discourse/mixins/modal-functionality'; import showModal from 'discourse/lib/show-modal'; import { setting } from 'discourse/lib/computed'; @@ -55,7 +56,7 @@ export default Ember.Controller.extend(ModalFunctionality, { this.set('loggingIn', true); - Discourse.ajax("/session", { + ajax("/session", { data: { login: this.get('loginName'), password: this.get('loginPassword') }, type: 'POST' }).then(function (result) { diff --git a/app/assets/javascripts/discourse/controllers/not-activated.js.es6 b/app/assets/javascripts/discourse/controllers/not-activated.js.es6 index 2c2e2b075..1c38cc58e 100644 --- a/app/assets/javascripts/discourse/controllers/not-activated.js.es6 +++ b/app/assets/javascripts/discourse/controllers/not-activated.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import ModalFunctionality from 'discourse/mixins/modal-functionality'; export default Ember.Controller.extend(ModalFunctionality, { @@ -9,7 +10,7 @@ export default Ember.Controller.extend(ModalFunctionality, { actions: { sendActivationEmail: function() { - Discourse.ajax('/users/action/send_activation_email', {data: {username: this.get('username')}, type: 'POST'}); + ajax('/users/action/send_activation_email', {data: {username: this.get('username')}, type: 'POST'}); this.set('emailSent', true); } } diff --git a/app/assets/javascripts/discourse/controllers/preferences/badge-title.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/badge-title.js.es6 index a48dae7fc..b71660d17 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/badge-title.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/badge-title.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import BadgeSelectController from "discourse/mixins/badge-select-controller"; export default Ember.ArrayController.extend(BadgeSelectController, { @@ -11,7 +12,7 @@ export default Ember.ArrayController.extend(BadgeSelectController, { this.setProperties({ saved: false, saving: true }); var self = this; - Discourse.ajax(this.get('user.path') + "/preferences/badge_title", { + ajax(this.get('user.path') + "/preferences/badge_title", { type: "PUT", data: { user_badge_id: self.get('selectedUserBadgeId') } }).then(function() { diff --git a/app/assets/javascripts/discourse/controllers/preferences/card-badge.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/card-badge.js.es6 index 4b0dcaacc..d0c42fb55 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/card-badge.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/card-badge.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import BadgeSelectController from "discourse/mixins/badge-select-controller"; export default Ember.ArrayController.extend(BadgeSelectController, { @@ -12,7 +13,7 @@ export default Ember.ArrayController.extend(BadgeSelectController, { this.setProperties({ saved: false, saving: true }); var self = this; - Discourse.ajax(this.get('user.path') + "/preferences/card-badge", { + ajax(this.get('user.path') + "/preferences/card-badge", { type: "PUT", data: { user_badge_id: self.get('selectedUserBadgeId') } }).then(function() { diff --git a/app/assets/javascripts/discourse/controllers/reorder-categories.js.es6 b/app/assets/javascripts/discourse/controllers/reorder-categories.js.es6 index 62a313b9a..18a000a38 100644 --- a/app/assets/javascripts/discourse/controllers/reorder-categories.js.es6 +++ b/app/assets/javascripts/discourse/controllers/reorder-categories.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import ModalFunctionality from 'discourse/mixins/modal-functionality'; const BufferedProxy = window.BufferedProxy; // import BufferedProxy from 'ember-buffered-proxy/proxy'; import { popupAjaxError } from 'discourse/lib/ajax-error'; @@ -90,7 +91,7 @@ export default Ember.Controller.extend(ModalFunctionality, Ember.Evented, { this.get('categoriesBuffered').forEach((cat) => { data[cat.get('id')] = cat.get('position'); }); - Discourse.ajax('/categories/reorder', + ajax('/categories/reorder', {type: 'POST', data: {mapping: JSON.stringify(data)}}). then(() => this.send("closeModal")). catch(popupAjaxError); diff --git a/app/assets/javascripts/discourse/controllers/static.js.es6 b/app/assets/javascripts/discourse/controllers/static.js.es6 index a1c4ab9dd..c656b99df 100644 --- a/app/assets/javascripts/discourse/controllers/static.js.es6 +++ b/app/assets/javascripts/discourse/controllers/static.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import computed from 'ember-addons/ember-computed-decorators'; export default Ember.Controller.extend({ @@ -15,7 +16,7 @@ export default Ember.Controller.extend({ markFaqRead() { const currentUser = this.currentUser; if (currentUser) { - Discourse.ajax("/users/read-faq", { method: "POST" }).then(() => { + ajax("/users/read-faq", { method: "POST" }).then(() => { currentUser.set('read_faq', true); }); } diff --git a/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 b/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 index 503933018..2d308dffd 100644 --- a/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 @@ -1,3 +1,5 @@ +import { ajax } from 'discourse/lib/ajax'; +import { observes } from 'ember-addons/ember-computed-decorators'; import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; export default Ember.ArrayController.extend({ @@ -17,7 +19,7 @@ export default Ember.ArrayController.extend({ actions: { resetNew() { - Discourse.ajax('/notifications/mark-read', { method: 'PUT' }).then(() => { + ajax('/notifications/mark-read', { method: 'PUT' }).then(() => { this.setEach('read', true); }); }, diff --git a/app/assets/javascripts/discourse/initializers/page-tracking.js.es6 b/app/assets/javascripts/discourse/initializers/page-tracking.js.es6 index 34fed0c97..7f680580e 100644 --- a/app/assets/javascripts/discourse/initializers/page-tracking.js.es6 +++ b/app/assets/javascripts/discourse/initializers/page-tracking.js.es6 @@ -1,5 +1,6 @@ import { cleanDOM } from 'discourse/routes/discourse'; import { startPageTracking, onPageChange } from 'discourse/lib/page-tracker'; +import { viewTrackingRequired } from 'discourse/lib/ajax'; export default { name: "page-tracking", @@ -11,9 +12,7 @@ export default { // Tell our AJAX system to track a page transition const router = container.lookup('router:main'); - router.on('willTransition', function() { - Discourse.viewTrackingRequired(); - }); + router.on('willTransition', viewTrackingRequired); router.on('didTransition', function() { Em.run.scheduleOnce('afterRender', Ember.Route, cleanDOM); diff --git a/app/assets/javascripts/discourse/lib/ajax.js.es6 b/app/assets/javascripts/discourse/lib/ajax.js.es6 new file mode 100644 index 000000000..48f259fd4 --- /dev/null +++ b/app/assets/javascripts/discourse/lib/ajax.js.es6 @@ -0,0 +1,122 @@ +let _trackView = false; +let _transientHeader = null; + +export function setTransientHeader(key, value) { + _transientHeader = {key, value}; +} + +export function viewTrackingRequired() { + _trackView = true; +} + +/** + Our own $.ajax method. Makes sure the .then method executes in an Ember runloop + for performance reasons. Also automatically adjusts the URL to support installs + in subfolders. +**/ +export function ajax() { + let url, args; + let ajaxObj; + + if (arguments.length === 1) { + if (typeof arguments[0] === "string") { + url = arguments[0]; + args = {}; + } else { + args = arguments[0]; + url = args.url; + delete args.url; + } + } else if (arguments.length === 2) { + url = arguments[0]; + args = arguments[1]; + } + + function performAjax(resolve, reject) { + + args.headers = args.headers || {}; + + if (_transientHeader) { + args.headers[_transientHeader.key] = _transientHeader.value; + _transientHeader = null; + } + + if (_trackView && (!args.type || args.type === "GET")) { + _trackView = false; + // DON'T CHANGE: rack is prepending "HTTP_" in the header's name + args.headers['Discourse-Track-View'] = "true"; + } + + args.success = (data, textStatus, xhr) => { + if (xhr.getResponseHeader('Discourse-Readonly')) { + Ember.run(() => Discourse.Site.currentProp('isReadOnly', true)); + } + + if (args.returnXHR) { + data = { result: data, xhr: xhr }; + } + + Ember.run(null, resolve, data); + }; + + args.error = (xhr, textStatus, errorThrown) => { + // note: for bad CSRF we don't loop an extra request right away. + // this allows us to eliminate the possibility of having a loop. + if (xhr.status === 403 && xhr.responseText === "['BAD CSRF']") { + Discourse.Session.current().set('csrfToken', null); + } + + // If it's a parsererror, don't reject + if (xhr.status === 200) return args.success(xhr); + + // Fill in some extra info + xhr.jqTextStatus = textStatus; + xhr.requestedUrl = url; + + Ember.run(null, reject, { + jqXHR: xhr, + textStatus: textStatus, + errorThrown: errorThrown + }); + }; + + // We default to JSON on GET. If we don't, sometimes if the server doesn't return the proper header + // it will not be parsed as an object. + if (!args.type) args.type = 'GET'; + if (!args.dataType && args.type.toUpperCase() === 'GET') args.dataType = 'json'; + + if (args.dataType === "script") { + args.headers['Discourse-Script'] = true; + } + + if (args.type === 'GET' && args.cache !== true) { + args.cache = false; + } + + ajaxObj = $.ajax(Discourse.getURL(url), args); + }; + + let promise; + + // For cached pages we strip out CSRF tokens, need to round trip to server prior to sending the + // request (bypass for GET, not needed) + if(args.type && args.type.toUpperCase() !== 'GET' && !Discourse.Session.currentProp('csrfToken')){ + promise = new Ember.RSVP.Promise((resolve, reject) => { + ajaxObj = $.ajax(Discourse.getURL('/session/csrf'), {cache: false}) + .success(result => { + Discourse.Session.currentProp('csrfToken', result.csrf); + performAjax(resolve, reject); + }); + }); + } else { + promise = new Ember.RSVP.Promise(performAjax); + } + + promise.abort = () => { + if (ajaxObj) { + ajaxObj.abort(); + } + }; + + return promise; +} diff --git a/app/assets/javascripts/discourse/lib/click-track.js.es6 b/app/assets/javascripts/discourse/lib/click-track.js.es6 index 5ea80bce8..f5f9119be 100644 --- a/app/assets/javascripts/discourse/lib/click-track.js.es6 +++ b/app/assets/javascripts/discourse/lib/click-track.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import DiscourseURL from 'discourse/lib/url'; import { wantsNewWindow } from 'discourse/lib/intercept-click'; import { selectedText } from 'discourse/lib/utilities'; @@ -64,7 +65,7 @@ export default { // if they want to open in a new tab, do an AJAX request if (wantsNewWindow(e)) { - Discourse.ajax("/clicks/track", { + ajax("/clicks/track", { data: { url: href, post_id: postId, @@ -105,7 +106,7 @@ export default { // If we're on the same site, use the router and track via AJAX if (DiscourseURL.isInternal(href) && !$link.hasClass('attachment')) { - Discourse.ajax("/clicks/track", { + ajax("/clicks/track", { data: { url: href, post_id: postId, diff --git a/app/assets/javascripts/discourse/lib/export-csv.js.es6 b/app/assets/javascripts/discourse/lib/export-csv.js.es6 index c4b74a08f..a88558b3d 100644 --- a/app/assets/javascripts/discourse/lib/export-csv.js.es6 +++ b/app/assets/javascripts/discourse/lib/export-csv.js.es6 @@ -1,5 +1,6 @@ +import { ajax } from 'discourse/lib/ajax'; function exportEntityByType(type, entity, args) { - return Discourse.ajax("/export_csv/export_entity.json", { + return ajax("/export_csv/export_entity.json", { method: 'POST', data: {entity_type: type, entity, args} }); diff --git a/app/assets/javascripts/discourse/lib/link-category-hashtags.js.es6 b/app/assets/javascripts/discourse/lib/link-category-hashtags.js.es6 index 53b3a8c2a..05a71cb34 100644 --- a/app/assets/javascripts/discourse/lib/link-category-hashtags.js.es6 +++ b/app/assets/javascripts/discourse/lib/link-category-hashtags.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import { replaceSpan } from 'discourse/lib/category-hashtags'; const validCategoryHashtags = {}; @@ -41,7 +42,7 @@ export function linkSeenCategoryHashtags($elem) { }; export function fetchUnseenCategoryHashtags(categorySlugs) { - return Discourse.ajax("/category_hashtags/check", { data: { category_slugs: categorySlugs } }) + return ajax("/category_hashtags/check", { data: { category_slugs: categorySlugs } }) .then((response) => { response.valid.forEach((category) => { validCategoryHashtags[category.slug] = category.url; diff --git a/app/assets/javascripts/discourse/lib/link-mentions.js.es6 b/app/assets/javascripts/discourse/lib/link-mentions.js.es6 index 476ae64fc..149b15161 100644 --- a/app/assets/javascripts/discourse/lib/link-mentions.js.es6 +++ b/app/assets/javascripts/discourse/lib/link-mentions.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; function replaceSpan($e, username, opts) { if (opts && opts.group) { var extra = "", extraClass = ""; @@ -52,7 +53,7 @@ export function linkSeenMentions($elem, siteSettings) { } export function fetchUnseenMentions($elem, usernames) { - return Discourse.ajax("/users/is_local_username", { data: { usernames } }).then(function(r) { + return ajax("/users/is_local_username", { data: { usernames } }).then(function(r) { found.push.apply(found, r.valid); foundGroups.push.apply(foundGroups, r.valid_groups); mentionableGroups.push.apply(mentionableGroups, r.mentionable_groups); diff --git a/app/assets/javascripts/discourse/lib/link-tag-hashtag.js.es6 b/app/assets/javascripts/discourse/lib/link-tag-hashtag.js.es6 index c37610ff0..e82de3eb0 100644 --- a/app/assets/javascripts/discourse/lib/link-tag-hashtag.js.es6 +++ b/app/assets/javascripts/discourse/lib/link-tag-hashtag.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import { replaceSpan } from 'discourse/lib/category-hashtags'; import { TAG_HASHTAG_POSTFIX } from 'discourse/lib/tag-hashtags'; @@ -42,7 +43,7 @@ export function linkSeenTagHashtags($elem) { }; export function fetchUnseenTagHashtags(tagValues) { - return Discourse.ajax("/tags/check", { data: { tag_values: tagValues } }) + return ajax("/tags/check", { data: { tag_values: tagValues } }) .then((response) => { response.valid.forEach((tag) => { validTagHashtags[tag.value] = tag.url; diff --git a/app/assets/javascripts/discourse/lib/load-script.js.es6 b/app/assets/javascripts/discourse/lib/load-script.js.es6 index b870bf1d7..b562a1154 100644 --- a/app/assets/javascripts/discourse/lib/load-script.js.es6 +++ b/app/assets/javascripts/discourse/lib/load-script.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; const _loaded = {}; const _loading = {}; @@ -58,7 +59,7 @@ export default function loadScript(url, opts) { if (opts.scriptTag) { loadWithTag(cdnUrl, cb); } else { - Discourse.ajax({url: cdnUrl, dataType: "script", cache: true}).then(cb); + ajax({url: cdnUrl, dataType: "script", cache: true}).then(cb); } }); } diff --git a/app/assets/javascripts/discourse/lib/screen-track.js.es6 b/app/assets/javascripts/discourse/lib/screen-track.js.es6 index fa52eab6f..af4c46a04 100644 --- a/app/assets/javascripts/discourse/lib/screen-track.js.es6 +++ b/app/assets/javascripts/discourse/lib/screen-track.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; // We use this class to track how long posts in a topic are on the screen. const PAUSE_UNLESS_SCROLLED = 1000 * 60 * 3; const MAX_TRACKING_TIME = 1000 * 60 * 6; @@ -107,7 +108,7 @@ export default class { if (!$.isEmptyObject(newTimings)) { if (this.currentUser) { - Discourse.ajax('/topics/timings', { + ajax('/topics/timings', { data: { timings: newTimings, topic_time: this._topicTime, diff --git a/app/assets/javascripts/discourse/lib/search.js.es6 b/app/assets/javascripts/discourse/lib/search.js.es6 index 2675f1031..b0929ca60 100644 --- a/app/assets/javascripts/discourse/lib/search.js.es6 +++ b/app/assets/javascripts/discourse/lib/search.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; export function translateResults(results, opts) { @@ -82,7 +83,7 @@ function searchForTerm(term, opts) { }; } - var promise = Discourse.ajax('/search/query', { data: data }); + var promise = ajax('/search/query', { data: data }); promise.then(function(results){ return translateResults(results, opts); diff --git a/app/assets/javascripts/discourse/mixins/ajax.js b/app/assets/javascripts/discourse/mixins/ajax.js deleted file mode 100644 index 9c1d2d4df..000000000 --- a/app/assets/javascripts/discourse/mixins/ajax.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - This mixin provides an 'ajax' method that can be used to perform ajax requests that - respect Discourse paths and the run loop. -**/ -var _trackView = false; -var _transientHeader = null; - -Discourse.Ajax = Em.Mixin.create({ - - setTransientHeader: function(k, v) { - _transientHeader = {key: k, value: v}; - }, - - viewTrackingRequired: function() { - _trackView = true; - }, - - /** - Our own $.ajax method. Makes sure the .then method executes in an Ember runloop - for performance reasons. Also automatically adjusts the URL to support installs - in subfolders. - - @method ajax - **/ - ajax: function() { - var url, args; - var ajax; - - if (arguments.length === 1) { - if (typeof arguments[0] === "string") { - url = arguments[0]; - args = {}; - } else { - args = arguments[0]; - url = args.url; - delete args.url; - } - } else if (arguments.length === 2) { - url = arguments[0]; - args = arguments[1]; - } - - if (args.success || args.error) { - throw "Discourse.ajax should use promises"; - } - - var performAjax = function(resolve, reject) { - - args.headers = args.headers || {}; - - if (_transientHeader) { - args.headers[_transientHeader.key] = _transientHeader.value; - _transientHeader = null; - } - - if (_trackView && (!args.type || args.type === "GET")) { - _trackView = false; - // DON'T CHANGE: rack is prepending "HTTP_" in the header's name - args.headers['Discourse-Track-View'] = "true"; - } - - args.success = function(data, textStatus, xhr) { - if (xhr.getResponseHeader('Discourse-Readonly')) { - Ember.run(function() { - Discourse.Site.currentProp('isReadOnly', true); - }); - } - - if (args.returnXHR) { - data = { result: data, xhr: xhr }; - } - - Ember.run(null, resolve, data); - }; - - args.error = function(xhr, textStatus, errorThrown) { - // note: for bad CSRF we don't loop an extra request right away. - // this allows us to eliminate the possibility of having a loop. - if (xhr.status === 403 && xhr.responseText === "['BAD CSRF']") { - Discourse.Session.current().set('csrfToken', null); - } - - // If it's a parsererror, don't reject - if (xhr.status === 200) return args.success(xhr); - - // Fill in some extra info - xhr.jqTextStatus = textStatus; - xhr.requestedUrl = url; - - Ember.run(null, reject, { - jqXHR: xhr, - textStatus: textStatus, - errorThrown: errorThrown - }); - }; - - // We default to JSON on GET. If we don't, sometimes if the server doesn't return the proper header - // it will not be parsed as an object. - if (!args.type) args.type = 'GET'; - if (!args.dataType && args.type.toUpperCase() === 'GET') args.dataType = 'json'; - - if (args.dataType === "script") { - args.headers['Discourse-Script'] = true; - } - - if (args.type === 'GET' && args.cache !== true) { - args.cache = false; - } - - ajax = $.ajax(Discourse.getURL(url), args); - }; - - var promise; - - // For cached pages we strip out CSRF tokens, need to round trip to server prior to sending the - // request (bypass for GET, not needed) - if(args.type && args.type.toUpperCase() !== 'GET' && !Discourse.Session.currentProp('csrfToken')){ - promise = new Ember.RSVP.Promise(function(resolve, reject){ - ajax = $.ajax(Discourse.getURL('/session/csrf'), {cache: false}) - .success(function(result){ - Discourse.Session.currentProp('csrfToken', result.csrf); - performAjax(resolve, reject); - }); - }); - } else { - promise = new Ember.RSVP.Promise(performAjax); - } - - promise.abort = function(){ - if (ajax) { - ajax.abort(); - } - }; - - return promise; - } - -}); diff --git a/app/assets/javascripts/discourse/models/action-summary.js.es6 b/app/assets/javascripts/discourse/models/action-summary.js.es6 index a4f50f52d..e21a13e0e 100644 --- a/app/assets/javascripts/discourse/models/action-summary.js.es6 +++ b/app/assets/javascripts/discourse/models/action-summary.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import RestModel from 'discourse/models/rest'; import { popupAjaxError } from 'discourse/lib/ajax-error'; @@ -53,7 +54,7 @@ export default RestModel.extend({ // Create our post action const self = this; - return Discourse.ajax("/post_actions", { + return ajax("/post_actions", { type: 'POST', data: { id: this.get('flagTopic') ? this.get('flagTopic.id') : post.get('id'), @@ -82,7 +83,7 @@ export default RestModel.extend({ this.removeAction(post); // Remove our post action - return Discourse.ajax("/post_actions/" + post.get('id'), { + return ajax("/post_actions/" + post.get('id'), { type: 'DELETE', data: { post_action_type_id: this.get('id') } }).then(result => { @@ -92,7 +93,7 @@ export default RestModel.extend({ }, deferFlags(post) { - return Discourse.ajax("/post_actions/defer_flags", { + return ajax("/post_actions/defer_flags", { type: "POST", data: { post_action_type_id: this.get("id"), id: post.get('id') } }).then(() => this.set('count', 0)); diff --git a/app/assets/javascripts/discourse/models/badge.js.es6 b/app/assets/javascripts/discourse/models/badge.js.es6 index 00cf97f34..333b40c57 100644 --- a/app/assets/javascripts/discourse/models/badge.js.es6 +++ b/app/assets/javascripts/discourse/models/badge.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import BadgeGrouping from 'discourse/models/badge-grouping'; import RestModel from 'discourse/models/rest'; @@ -53,7 +54,7 @@ const Badge = RestModel.extend({ requestType = "PUT"; } - return Discourse.ajax(url, { + return ajax(url, { type: requestType, data: data }).then(function(json) { @@ -72,7 +73,7 @@ const Badge = RestModel.extend({ **/ destroy: function() { if (this.get('newBadge')) return Ember.RSVP.resolve(); - return Discourse.ajax("/admin/badges/" + this.get('id'), { + return ajax("/admin/badges/" + this.get('id'), { type: "DELETE" }); } @@ -134,7 +135,7 @@ Badge.reopenClass({ if(opts && opts.onlyListable){ listable = "?only_listable=true"; } - return Discourse.ajax('/badges.json' + listable).then(function(badgesJson) { + return ajax('/badges.json' + listable).then(function(badgesJson) { return Badge.createFromJson(badgesJson); }); }, @@ -147,7 +148,7 @@ Badge.reopenClass({ @returns {Promise} a promise that resolves to a `Badge` **/ findById: function(id) { - return Discourse.ajax("/badges/" + id).then(function(badgeJson) { + return ajax("/badges/" + id).then(function(badgeJson) { return Badge.createFromJson(badgeJson); }); } diff --git a/app/assets/javascripts/discourse/models/category-list.js.es6 b/app/assets/javascripts/discourse/models/category-list.js.es6 index 3a0b9e47f..b0d6c2c45 100644 --- a/app/assets/javascripts/discourse/models/category-list.js.es6 +++ b/app/assets/javascripts/discourse/models/category-list.js.es6 @@ -1,3 +1,5 @@ +import { ajax } from 'discourse/lib/ajax'; + const CategoryList = Ember.ArrayProxy.extend({ init() { this.set('content', []); @@ -34,7 +36,7 @@ CategoryList.reopenClass({ }, listForParent(store, category) { - return Discourse.ajax(`/categories.json?parent_category_id=${category.get("id")}`).then(result => { + return ajax(`/categories.json?parent_category_id=${category.get("id")}`).then(result => { return CategoryList.create({ categories: this.categoriesFrom(store, result), parentCategory: category @@ -43,7 +45,7 @@ CategoryList.reopenClass({ }, list(store) { - const getCategories = () => Discourse.ajax("/categories.json"); + const getCategories = () => ajax("/categories.json"); return PreloadStore.getAndRemove("categories_list", getCategories).then(result => { return CategoryList.create({ categories: this.categoriesFrom(store, result), diff --git a/app/assets/javascripts/discourse/models/category.js.es6 b/app/assets/javascripts/discourse/models/category.js.es6 index 8d1eb8ba8..000aeaecc 100644 --- a/app/assets/javascripts/discourse/models/category.js.es6 +++ b/app/assets/javascripts/discourse/models/category.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import RestModel from 'discourse/models/rest'; import { on } from 'ember-addons/ember-computed-decorators'; import PermissionType from 'discourse/models/permission-type'; @@ -67,7 +68,7 @@ const Category = RestModel.extend({ url = "/categories/" + this.get('id'); } - return Discourse.ajax(url, { + return ajax(url, { data: { name: this.get('name'), slug: this.get('slug'), @@ -103,7 +104,7 @@ const Category = RestModel.extend({ }.property("permissions"), destroy: function() { - return Discourse.ajax("/categories/" + (this.get('id') || this.get('slug')), { type: 'DELETE' }); + return ajax("/categories/" + (this.get('id') || this.get('slug')), { type: 'DELETE' }); }, addPermission: function(permission){ @@ -170,7 +171,7 @@ const Category = RestModel.extend({ setNotification: function(notification_level) { var url = "/category/" + this.get('id')+"/notifications"; this.set('notification_level', notification_level); - return Discourse.ajax(url, { + return ajax(url, { data: { notification_level: notification_level }, @@ -285,11 +286,11 @@ Category.reopenClass({ }, reloadById(id) { - return Discourse.ajax(`/c/${id}/show.json`); + return ajax(`/c/${id}/show.json`); }, reloadBySlug(slug, parentSlug) { - return parentSlug ? Discourse.ajax(`/c/${parentSlug}/${slug}/find_by_slug.json`) : Discourse.ajax(`/c/${slug}/find_by_slug.json`); + return parentSlug ? ajax(`/c/${parentSlug}/${slug}/find_by_slug.json`) : ajax(`/c/${slug}/find_by_slug.json`); }, search(term, opts) { diff --git a/app/assets/javascripts/discourse/models/draft.js.es6 b/app/assets/javascripts/discourse/models/draft.js.es6 index faf16a3ae..2959f7fce 100644 --- a/app/assets/javascripts/discourse/models/draft.js.es6 +++ b/app/assets/javascripts/discourse/models/draft.js.es6 @@ -1,9 +1,10 @@ +import { ajax } from 'discourse/lib/ajax'; const Draft = Discourse.Model.extend(); Draft.reopenClass({ clear(key, sequence) { - return Discourse.ajax("/draft.json", { + return ajax("/draft.json", { type: 'DELETE', data: { draft_key: key, @@ -13,7 +14,7 @@ Draft.reopenClass({ }, get(key) { - return Discourse.ajax('/draft.json', { + return ajax('/draft.json', { data: { draft_key: key }, dataType: 'json' }); @@ -26,7 +27,7 @@ Draft.reopenClass({ save(key, sequence, data) { data = typeof data === "string" ? data : JSON.stringify(data); - return Discourse.ajax("/draft.json", { + return ajax("/draft.json", { type: 'POST', data: { draft_key: key, diff --git a/app/assets/javascripts/discourse/models/group.js.es6 b/app/assets/javascripts/discourse/models/group.js.es6 index c2369960d..85c8ba5a5 100644 --- a/app/assets/javascripts/discourse/models/group.js.es6 +++ b/app/assets/javascripts/discourse/models/group.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import computed from 'ember-addons/ember-computed-decorators'; const Group = Discourse.Model.extend({ @@ -49,7 +50,7 @@ const Group = Discourse.Model.extend({ removeOwner(member) { var self = this; - return Discourse.ajax('/admin/groups/' + this.get('id') + '/owners.json', { + return ajax('/admin/groups/' + this.get('id') + '/owners.json', { type: "DELETE", data: { user_id: member.get("id") } }).then(function() { @@ -60,7 +61,7 @@ const Group = Discourse.Model.extend({ removeMember(member) { var self = this; - return Discourse.ajax('/groups/' + this.get('id') + '/members.json', { + return ajax('/groups/' + this.get('id') + '/members.json', { type: "DELETE", data: { user_id: member.get("id") } }).then(function() { @@ -71,7 +72,7 @@ const Group = Discourse.Model.extend({ addMembers(usernames) { var self = this; - return Discourse.ajax('/groups/' + this.get('id') + '/members.json', { + return ajax('/groups/' + this.get('id') + '/members.json', { type: "PUT", data: { usernames: usernames } }).then(function() { @@ -81,7 +82,7 @@ const Group = Discourse.Model.extend({ addOwners(usernames) { var self = this; - return Discourse.ajax('/admin/groups/' + this.get('id') + '/owners.json', { + return ajax('/admin/groups/' + this.get('id') + '/owners.json', { type: "PUT", data: { usernames: usernames } }).then(function() { @@ -105,18 +106,18 @@ const Group = Discourse.Model.extend({ create() { var self = this; - return Discourse.ajax("/admin/groups", { type: "POST", data: this.asJSON() }).then(function(resp) { + return ajax("/admin/groups", { type: "POST", data: this.asJSON() }).then(function(resp) { self.set('id', resp.basic_group.id); }); }, save() { - return Discourse.ajax("/admin/groups/" + this.get('id'), { type: "PUT", data: this.asJSON() }); + return ajax("/admin/groups/" + this.get('id'), { type: "PUT", data: this.asJSON() }); }, destroy() { if (!this.get('id')) { return; } - return Discourse.ajax("/admin/groups/" + this.get('id'), { type: "DELETE" }); + return ajax("/admin/groups/" + this.get('id'), { type: "DELETE" }); }, findPosts(opts) { @@ -127,7 +128,7 @@ const Group = Discourse.Model.extend({ var data = {}; if (opts.beforePostId) { data.before_post_id = opts.beforePostId; } - return Discourse.ajax(`/groups/${this.get('name')}/${type}.json`, { data: data }).then(posts => { + return ajax(`/groups/${this.get('name')}/${type}.json`, { data: data }).then(posts => { return posts.map(p => { p.user = Discourse.User.create(p.user); p.topic = Discourse.Topic.create(p.topic); @@ -138,7 +139,7 @@ const Group = Discourse.Model.extend({ setNotification(notification_level) { this.set("notification_level", notification_level); - return Discourse.ajax(`/groups/${this.get("name")}/notifications`, { + return ajax(`/groups/${this.get("name")}/notifications`, { data: { notification_level }, type: "POST" }); @@ -147,21 +148,21 @@ const Group = Discourse.Model.extend({ Group.reopenClass({ findAll(opts) { - return Discourse.ajax("/admin/groups.json", { data: opts }).then(function (groups){ + return ajax("/admin/groups.json", { data: opts }).then(function (groups){ return groups.map(g => Group.create(g)); }); }, findGroupCounts(name) { - return Discourse.ajax("/groups/" + name + "/counts.json").then(result => Em.Object.create(result.counts)); + return ajax("/groups/" + name + "/counts.json").then(result => Em.Object.create(result.counts)); }, find(name) { - return Discourse.ajax("/groups/" + name + ".json").then(result => Group.create(result.basic_group)); + return ajax("/groups/" + name + ".json").then(result => Group.create(result.basic_group)); }, loadMembers(name, offset, limit) { - return Discourse.ajax('/groups/' + name + '/members.json', { + return ajax('/groups/' + name + '/members.json', { data: { limit: limit || 50, offset: offset || 0 diff --git a/app/assets/javascripts/discourse/models/invite.js.es6 b/app/assets/javascripts/discourse/models/invite.js.es6 index 2d5197de0..8f89cfc68 100644 --- a/app/assets/javascripts/discourse/models/invite.js.es6 +++ b/app/assets/javascripts/discourse/models/invite.js.es6 @@ -1,9 +1,10 @@ +import { ajax } from 'discourse/lib/ajax'; import { popupAjaxError } from 'discourse/lib/ajax-error'; const Invite = Discourse.Model.extend({ rescind() { - Discourse.ajax('/invites', { + ajax('/invites', { type: 'DELETE', data: { email: this.get('email') } }); @@ -12,7 +13,7 @@ const Invite = Discourse.Model.extend({ reinvite() { const self = this; - return Discourse.ajax('/invites/reinvite', { + return ajax('/invites/reinvite', { type: 'POST', data: { email: this.get('email') } }).then(function() { @@ -40,7 +41,7 @@ Invite.reopenClass({ if (!Em.isNone(search)) { data.search = search; } data.offset = offset || 0; - return Discourse.ajax("/users/" + user.get('username_lower') + "/invited.json", {data}).then(function (result) { + return ajax("/users/" + user.get('username_lower') + "/invited.json", {data}).then(function (result) { result.invites = result.invites.map(function (i) { return Invite.create(i); }); @@ -51,11 +52,11 @@ Invite.reopenClass({ findInvitedCount(user) { if (!user) { return Em.RSVP.resolve(); } - return Discourse.ajax("/users/" + user.get('username_lower') + "/invited_count.json").then(result => Em.Object.create(result.counts)); + return ajax("/users/" + user.get('username_lower') + "/invited_count.json").then(result => Em.Object.create(result.counts)); }, reinviteAll() { - return Discourse.ajax('/invites/reinvite-all', { type: 'POST' }); + return ajax('/invites/reinvite-all', { type: 'POST' }); } }); diff --git a/app/assets/javascripts/discourse/models/live-post-counts.es6 b/app/assets/javascripts/discourse/models/live-post-counts.es6 index 678aecbf5..1ecd225d5 100644 --- a/app/assets/javascripts/discourse/models/live-post-counts.es6 +++ b/app/assets/javascripts/discourse/models/live-post-counts.es6 @@ -1,8 +1,9 @@ +import { ajax } from 'discourse/lib/ajax'; const LivePostCounts = Discourse.Model.extend({}); LivePostCounts.reopenClass({ find() { - return Discourse.ajax("/about/live_post_counts.json").then(result => LivePostCounts.create(result)); + return ajax("/about/live_post_counts.json").then(result => LivePostCounts.create(result)); } }); diff --git a/app/assets/javascripts/discourse/models/post-stream.js.es6 b/app/assets/javascripts/discourse/models/post-stream.js.es6 index 309674a9a..a96f0e024 100644 --- a/app/assets/javascripts/discourse/models/post-stream.js.es6 +++ b/app/assets/javascripts/discourse/models/post-stream.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import DiscourseURL from 'discourse/lib/url'; import RestModel from 'discourse/models/rest'; import PostsWithPlaceholders from 'discourse/lib/posts-with-placeholders'; @@ -455,7 +456,7 @@ export default RestModel.extend({ const url = "/posts/" + postId; const store = this.store; - return Discourse.ajax(url).then(p => this.storePost(store.createRecord('post', p))); + return ajax(url).then(p => this.storePost(store.createRecord('post', p))); }, /** @@ -497,7 +498,7 @@ export default RestModel.extend({ // need to insert into stream const url = "/posts/" + postId; const store = this.store; - return Discourse.ajax(url).then(p => { + return ajax(url).then(p => { const post = store.createRecord('post', p); const stream = this.get("stream"); const posts = this.get("posts"); @@ -538,7 +539,7 @@ export default RestModel.extend({ const url = "/posts/" + postId; const store = this.store; - return Discourse.ajax(url).then(p => { + return ajax(url).then(p => { this.storePost(store.createRecord('post', p)); }).catch(() => { this.removePosts([existing]); @@ -555,7 +556,7 @@ export default RestModel.extend({ if (existing && existing.updated_at !== updatedAt) { const url = "/posts/" + postId; const store = this.store; - return Discourse.ajax(url).then(p => this.storePost(store.createRecord('post', p))); + return ajax(url).then(p => this.storePost(store.createRecord('post', p))); } return resolved; }, @@ -727,7 +728,7 @@ export default RestModel.extend({ const url = "/t/" + this.get('topic.id') + "/posts.json"; const data = { post_ids: postIds }; const store = this.store; - return Discourse.ajax(url, {data}).then(result => { + return ajax(url, {data}).then(result => { const posts = Ember.get(result, "post_stream.posts"); if (posts) { posts.forEach(p => this.storePost(store.createRecord('post', p))); diff --git a/app/assets/javascripts/discourse/models/post.js.es6 b/app/assets/javascripts/discourse/models/post.js.es6 index a3891e3e7..a9ca011ce 100644 --- a/app/assets/javascripts/discourse/models/post.js.es6 +++ b/app/assets/javascripts/discourse/models/post.js.es6 @@ -1,3 +1,4 @@ +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'; @@ -67,7 +68,7 @@ const Post = RestModel.extend({ const data = {}; data[field] = value; - return Discourse.ajax(`/posts/${this.get('id')}/${field}`, { type: 'PUT', data }).then(() => { + return ajax(`/posts/${this.get('id')}/${field}`, { type: 'PUT', data }).then(() => { this.set(field, value); this.incrementProperty("version"); }).catch(popupAjaxError); @@ -119,7 +120,7 @@ const Post = RestModel.extend({ // Expands the first post's content, if embedded and shortened. expand() { const self = this; - return Discourse.ajax("/posts/" + this.get('id') + "/expand-embed").then(function(post) { + return ajax("/posts/" + this.get('id') + "/expand-embed").then(function(post) { self.set('cooked', "
" + post.cooked + "
" ); }); }, @@ -136,7 +137,7 @@ const Post = RestModel.extend({ can_delete: false }); - return Discourse.ajax("/posts/" + (this.get('id')) + "/recover", { type: 'PUT', cache: false }).then(function(data){ + return ajax("/posts/" + (this.get('id')) + "/recover", { type: 'PUT', cache: false }).then(function(data){ post.setProperties({ cooked: data.cooked, raw: data.raw, @@ -198,7 +199,7 @@ const Post = RestModel.extend({ destroy(deletedBy) { this.setDeletedState(deletedBy); - return Discourse.ajax("/posts/" + this.get('id'), { + return ajax("/posts/" + this.get('id'), { data: { context: window.location.pathname }, type: 'DELETE' }); @@ -232,17 +233,17 @@ const Post = RestModel.extend({ }, expandHidden() { - return Discourse.ajax("/posts/" + this.get('id') + "/cooked.json").then(result => { + return ajax("/posts/" + this.get('id') + "/cooked.json").then(result => { this.setProperties({ cooked: result.cooked, cooked_hidden: false }); }); }, rebake() { - return Discourse.ajax("/posts/" + this.get("id") + "/rebake", { type: "PUT" }); + return ajax("/posts/" + this.get("id") + "/rebake", { type: "PUT" }); }, unhide() { - return Discourse.ajax("/posts/" + this.get("id") + "/unhide", { type: "PUT" }); + return ajax("/posts/" + this.get("id") + "/unhide", { type: "PUT" }); }, toggleBookmark() { @@ -277,7 +278,7 @@ const Post = RestModel.extend({ }, revertToRevision(version) { - return Discourse.ajax(`/posts/${this.get('id')}/revisions/${version}/revert`, { type: 'PUT' }); + return ajax(`/posts/${this.get('id')}/revisions/${version}/revert`, { type: 'PUT' }); } }); @@ -311,14 +312,14 @@ Post.reopenClass({ }, updateBookmark(postId, bookmarked) { - return Discourse.ajax("/posts/" + postId + "/bookmark", { + return ajax("/posts/" + postId + "/bookmark", { type: 'PUT', data: { bookmarked: bookmarked } }); }, deleteMany(selectedPosts, selectedReplies) { - return Discourse.ajax("/posts/destroy_many", { + return ajax("/posts/destroy_many", { type: 'DELETE', data: { post_ids: selectedPosts.map(function(p) { return p.get('id'); }), @@ -328,27 +329,27 @@ Post.reopenClass({ }, loadRevision(postId, version) { - return Discourse.ajax("/posts/" + postId + "/revisions/" + version + ".json") + return ajax("/posts/" + postId + "/revisions/" + version + ".json") .then(result => Ember.Object.create(result)); }, hideRevision(postId, version) { - return Discourse.ajax("/posts/" + postId + "/revisions/" + version + "/hide", { type: 'PUT' }); + return ajax("/posts/" + postId + "/revisions/" + version + "/hide", { type: 'PUT' }); }, showRevision(postId, version) { - return Discourse.ajax("/posts/" + postId + "/revisions/" + version + "/show", { type: 'PUT' }); + return ajax("/posts/" + postId + "/revisions/" + version + "/show", { type: 'PUT' }); }, loadQuote(postId) { - return Discourse.ajax("/posts/" + postId + ".json").then(result => { + return ajax("/posts/" + postId + ".json").then(result => { const post = Discourse.Post.create(result); return Quote.build(post, post.get('raw'), {raw: true, full: true}); }); }, loadRawEmail(postId) { - return Discourse.ajax(`/posts/${postId}/raw-email.json`); + return ajax(`/posts/${postId}/raw-email.json`); } }); diff --git a/app/assets/javascripts/discourse/models/static-page.js.es6 b/app/assets/javascripts/discourse/models/static-page.js.es6 index a935b0edc..6bab9df1c 100644 --- a/app/assets/javascripts/discourse/models/static-page.js.es6 +++ b/app/assets/javascripts/discourse/models/static-page.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; const StaticPage = Ember.Object.extend(); StaticPage.reopenClass({ @@ -11,7 +12,7 @@ StaticPage.reopenClass({ text = text.match(/((?:.|[\n\r])*)/)[1]; resolve(StaticPage.create({path: path, html: text})); } else { - Discourse.ajax(path + ".html", {dataType: 'html'}).then(function (result) { + ajax(path + ".html", {dataType: 'html'}).then(function (result) { resolve(StaticPage.create({path: path, html: result})); }); } diff --git a/app/assets/javascripts/discourse/models/store.js.es6 b/app/assets/javascripts/discourse/models/store.js.es6 index 512d41a82..1dd6ee2a1 100644 --- a/app/assets/javascripts/discourse/models/store.js.es6 +++ b/app/assets/javascripts/discourse/models/store.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import RestModel from 'discourse/models/rest'; import ResultSet from 'discourse/models/result-set'; @@ -114,7 +115,7 @@ export default Ember.Object.extend({ refreshResults(resultSet, type, url) { const self = this; - return Discourse.ajax(url).then(result => { + return ajax(url).then(result => { const typeName = Ember.String.underscore(self.pluralize(type)); const content = result[typeName].map(obj => self._hydrate(type, obj, result)); resultSet.set('content', content); @@ -124,7 +125,7 @@ export default Ember.Object.extend({ appendResults(resultSet, type, url) { const self = this; - return Discourse.ajax(url).then(function(result) { + return ajax(url).then(function(result) { const typeName = Ember.String.underscore(self.pluralize(type)), totalRows = result["total_rows_" + typeName] || result.get('totalRows'), loadMoreUrl = result["load_more_" + typeName], diff --git a/app/assets/javascripts/discourse/models/tag-group.js.es6 b/app/assets/javascripts/discourse/models/tag-group.js.es6 index 67ee9b63e..f27adad26 100644 --- a/app/assets/javascripts/discourse/models/tag-group.js.es6 +++ b/app/assets/javascripts/discourse/models/tag-group.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import RestModel from 'discourse/models/rest'; import computed from 'ember-addons/ember-computed-decorators'; @@ -17,7 +18,7 @@ const TagGroup = RestModel.extend({ this.set('savingStatus', I18n.t('saving')); this.set('saving', true); - return Discourse.ajax(url, { + return ajax(url, { data: { name: this.get('name'), tag_names: this.get('tag_names'), @@ -33,7 +34,7 @@ const TagGroup = RestModel.extend({ }, destroy() { - return Discourse.ajax("/tag_groups/" + this.get('id'), {type: "DELETE"}); + return ajax("/tag_groups/" + this.get('id'), {type: "DELETE"}); } }); diff --git a/app/assets/javascripts/discourse/models/topic-details.js.es6 b/app/assets/javascripts/discourse/models/topic-details.js.es6 index 833af1e19..25714b0c4 100644 --- a/app/assets/javascripts/discourse/models/topic-details.js.es6 +++ b/app/assets/javascripts/discourse/models/topic-details.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; /** A model representing a Topic's details that aren't always present, such as a list of participants. When showing topics in lists and such this information should not be required. @@ -57,7 +58,7 @@ const TopicDetails = RestModel.extend({ updateNotifications(v) { this.set('notification_level', v); this.set('notifications_reason_id', null); - return Discourse.ajax("/t/" + (this.get('topic.id')) + "/notifications", { + return ajax("/t/" + (this.get('topic.id')) + "/notifications", { type: 'POST', data: { notification_level: v } }); @@ -67,7 +68,7 @@ const TopicDetails = RestModel.extend({ const groups = this.get('allowed_groups'); const name = group.name; - return Discourse.ajax("/t/" + this.get('topic.id') + "/remove-allowed-group", { + return ajax("/t/" + this.get('topic.id') + "/remove-allowed-group", { type: 'PUT', data: { name: name } }).then(() => { @@ -79,7 +80,7 @@ const TopicDetails = RestModel.extend({ const users = this.get('allowed_users'); const username = user.get('username'); - return Discourse.ajax("/t/" + this.get('topic.id') + "/remove-allowed-user", { + return ajax("/t/" + this.get('topic.id') + "/remove-allowed-user", { type: 'PUT', data: { username: username } }).then(() => { diff --git a/app/assets/javascripts/discourse/models/topic-list.js.es6 b/app/assets/javascripts/discourse/models/topic-list.js.es6 index 15eff2635..4e547619d 100644 --- a/app/assets/javascripts/discourse/models/topic-list.js.es6 +++ b/app/assets/javascripts/discourse/models/topic-list.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import RestModel from 'discourse/models/rest'; import Model from 'discourse/models/model'; @@ -60,7 +61,7 @@ const TopicList = RestModel.extend({ this.set('loadingMore', true); const store = this.store; - return Discourse.ajax({url: moreUrl}).then(function (result) { + return ajax({url: moreUrl}).then(function (result) { let topicsAdded = 0; if (result) { @@ -100,7 +101,7 @@ const TopicList = RestModel.extend({ const url = `${Discourse.getURL("/")}${this.get('filter')}?topic_ids=${topic_ids.join(",")}`; const store = this.store; - return Discourse.ajax({ url }).then(result => { + return ajax({ url }).then(result => { let i = 0; topicList.forEachNew(topicsFrom(result, store), function(t) { // highlight the first of the new topics so we can get a visual feedback diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index 3f11614a1..5054634ca 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import { flushMap } from 'discourse/models/store'; import RestModel from 'discourse/models/rest'; import { propertyEqual } from 'discourse/lib/computed'; @@ -19,7 +20,7 @@ export function loadTopicView(topic, args) { delete data.store; return PreloadStore.getAndRemove(`topic_${topicId}`, () => { - return Discourse.ajax(jsonUrl, {data}); + return ajax(jsonUrl, {data}); }).then(json => { topic.updateFromJson(json); return json; @@ -225,7 +226,7 @@ const Topic = RestModel.extend({ this.set('details.auto_close_at', null); } } - return Discourse.ajax(this.get('url') + "/status", { + return ajax(this.get('url') + "/status", { type: 'PUT', data: { status: property, @@ -237,13 +238,13 @@ const Topic = RestModel.extend({ makeBanner() { const self = this; - return Discourse.ajax('/t/' + this.get('id') + '/make-banner', { type: 'PUT' }) + return ajax('/t/' + this.get('id') + '/make-banner', { type: 'PUT' }) .then(function () { self.set('archetype', 'banner'); }); }, removeBanner() { const self = this; - return Discourse.ajax('/t/' + this.get('id') + '/remove-banner', { type: 'PUT' }) + return ajax('/t/' + this.get('id') + '/remove-banner', { type: 'PUT' }) .then(function () { self.set('archetype', 'regular'); }); }, @@ -258,7 +259,7 @@ const Topic = RestModel.extend({ const path = bookmark ? '/bookmark' : '/remove_bookmarks'; const toggleBookmarkOnServer = () => { - return Discourse.ajax(`/t/${this.get('id')}${path}`, { type: 'PUT' }).then(() => { + return ajax(`/t/${this.get('id')}${path}`, { type: 'PUT' }).then(() => { this.toggleProperty('bookmarked'); if (bookmark && firstPost) { firstPost.set('bookmarked', true); @@ -314,21 +315,21 @@ const Topic = RestModel.extend({ }, createGroupInvite(group) { - return Discourse.ajax("/t/" + this.get('id') + "/invite-group", { + return ajax("/t/" + this.get('id') + "/invite-group", { type: 'POST', data: { group } }); }, createInvite(user, group_names, custom_message) { - return Discourse.ajax("/t/" + this.get('id') + "/invite", { + return ajax("/t/" + this.get('id') + "/invite", { type: 'POST', data: { user, group_names, custom_message } }); }, generateInviteLink: function(email, groupNames, topicId) { - return Discourse.ajax('/invites/link', { + return ajax('/invites/link', { type: 'POST', data: {email: email, group_names: groupNames, topic_id: topicId} }); @@ -342,7 +343,7 @@ const Topic = RestModel.extend({ 'details.can_delete': false, 'details.can_recover': true }); - return Discourse.ajax("/t/" + this.get('id'), { + return ajax("/t/" + this.get('id'), { data: { context: window.location.pathname }, type: 'DELETE' }); @@ -356,7 +357,7 @@ const Topic = RestModel.extend({ 'details.can_delete': true, 'details.can_recover': false }); - return Discourse.ajax("/t/" + this.get('id') + "/recover", { type: 'PUT' }); + return ajax("/t/" + this.get('id') + "/recover", { type: 'PUT' }); }, // Update our attributes from a JSON result @@ -372,7 +373,7 @@ const Topic = RestModel.extend({ reload() { const self = this; - return Discourse.ajax('/t/' + this.get('id'), { type: 'GET' }).then(function(topic_json) { + return ajax('/t/' + this.get('id'), { type: 'GET' }).then(function(topic_json) { self.updateFromJson(topic_json); }); }, @@ -388,7 +389,7 @@ const Topic = RestModel.extend({ topic.set('pinned', false); topic.set('unpinned', true); - Discourse.ajax("/t/" + this.get('id') + "/clear-pin", { + ajax("/t/" + this.get('id') + "/clear-pin", { type: 'PUT' }).then(null, function() { // On error, put the pin back @@ -412,7 +413,7 @@ const Topic = RestModel.extend({ topic.set('pinned', true); topic.set('unpinned', false); - Discourse.ajax("/t/" + this.get('id') + "/re-pin", { + ajax("/t/" + this.get('id') + "/re-pin", { type: 'PUT' }).then(null, function() { // On error, put the pin back @@ -434,7 +435,7 @@ const Topic = RestModel.extend({ archiveMessage() { this.set("archiving", true); - var promise = Discourse.ajax(`/t/${this.get('id')}/archive-message`, {type: 'PUT'}); + var promise = ajax(`/t/${this.get('id')}/archive-message`, {type: 'PUT'}); promise.then((msg)=> { this.set('message_archived', true); @@ -448,7 +449,7 @@ const Topic = RestModel.extend({ moveToInbox() { this.set("archiving", true); - var promise = Discourse.ajax(`/t/${this.get('id')}/move-to-inbox`, {type: 'PUT'}); + var promise = ajax(`/t/${this.get('id')}/move-to-inbox`, {type: 'PUT'}); promise.then((msg)=> { this.set('message_archived', false); @@ -461,7 +462,7 @@ const Topic = RestModel.extend({ }, convertTopic(type) { - return Discourse.ajax(`/t/${this.get('id')}/convert-topic/${type}`, {type: 'PUT'}).then(() => { + return ajax(`/t/${this.get('id')}/convert-topic/${type}`, {type: 'PUT'}).then(() => { window.location.reload(); }).catch(popupAjaxError); } @@ -511,7 +512,7 @@ Topic.reopenClass({ } }); - return Discourse.ajax(topic.get('url'), { type: 'PUT', data: props }).then(function(result) { + return ajax(topic.get('url'), { type: 'PUT', data: props }).then(function(result) { // The title can be cleaned up server side props.title = result.basic_topic.title; props.fancy_title = result.basic_topic.fancy_title; @@ -558,11 +559,11 @@ Topic.reopenClass({ } // Check the preload store. If not, load it via JSON - return Discourse.ajax(url + ".json", {data: data}); + return ajax(url + ".json", {data: data}); }, changeOwners(topicId, opts) { - const promise = Discourse.ajax("/t/" + topicId + "/change-owner", { + const promise = ajax("/t/" + topicId + "/change-owner", { type: 'POST', data: opts }).then(function (result) { @@ -573,7 +574,7 @@ Topic.reopenClass({ }, changeTimestamp(topicId, timestamp) { - const promise = Discourse.ajax("/t/" + topicId + '/change-timestamp', { + const promise = ajax("/t/" + topicId + '/change-timestamp', { type: 'PUT', data: { timestamp: timestamp }, }).then(function(result) { @@ -584,7 +585,7 @@ Topic.reopenClass({ }, bulkOperation(topics, operation) { - return Discourse.ajax("/topics/bulk", { + return ajax("/topics/bulk", { type: 'PUT', data: { topic_ids: topics.map(function(t) { return t.get('id'); }), @@ -596,18 +597,18 @@ Topic.reopenClass({ bulkOperationByFilter(filter, operation, categoryId) { const data = { filter: filter, operation: operation }; if (categoryId) data['category_id'] = categoryId; - return Discourse.ajax("/topics/bulk", { + return ajax("/topics/bulk", { type: 'PUT', data: data }); }, resetNew() { - return Discourse.ajax("/topics/reset-new", {type: 'PUT'}); + return ajax("/topics/reset-new", {type: 'PUT'}); }, idForSlug(slug) { - return Discourse.ajax("/t/id_for/" + slug); + return ajax("/t/id_for/" + slug); } }); @@ -621,11 +622,11 @@ function moveResult(result) { } export function movePosts(topicId, data) { - return Discourse.ajax("/t/" + topicId + "/move-posts", { type: 'POST', data }).then(moveResult); + return ajax("/t/" + topicId + "/move-posts", { type: 'POST', data }).then(moveResult); } export function mergeTopic(topicId, destinationTopicId) { - return Discourse.ajax("/t/" + topicId + "/merge-topic", { + return ajax("/t/" + topicId + "/merge-topic", { type: 'POST', data: {destination_topic_id: destinationTopicId} }).then(moveResult); diff --git a/app/assets/javascripts/discourse/models/user-badge.js.es6 b/app/assets/javascripts/discourse/models/user-badge.js.es6 index bc257aafc..e3a687243 100644 --- a/app/assets/javascripts/discourse/models/user-badge.js.es6 +++ b/app/assets/javascripts/discourse/models/user-badge.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import Badge from 'discourse/models/badge'; const UserBadge = Discourse.Model.extend({ @@ -8,7 +9,7 @@ const UserBadge = Discourse.Model.extend({ }.property(), // avoid the extra bindings for now revoke() { - return Discourse.ajax("/user_badges/" + this.get('id'), { + return ajax("/user_badges/" + this.get('id'), { type: "DELETE" }); } @@ -89,7 +90,7 @@ UserBadge.reopenClass({ if (options && options.grouped) { url += "?grouped=true"; } - return Discourse.ajax(url).then(function(json) { + return ajax(url).then(function(json) { return UserBadge.createFromJson(json); }); }, @@ -105,7 +106,7 @@ UserBadge.reopenClass({ if (!options) { options = {}; } options.badge_id = badgeId; - return Discourse.ajax("/user_badges.json", { + return ajax("/user_badges.json", { data: options }).then(function(json) { return UserBadge.createFromJson(json); @@ -121,7 +122,7 @@ UserBadge.reopenClass({ @returns {Promise} a promise that resolves to an instance of `UserBadge`. **/ grant: function(badgeId, username, reason) { - return Discourse.ajax("/user_badges", { + return ajax("/user_badges", { type: "POST", data: { username: username, diff --git a/app/assets/javascripts/discourse/models/user-posts-stream.js.es6 b/app/assets/javascripts/discourse/models/user-posts-stream.js.es6 index 8d7a750d8..cd552d35d 100644 --- a/app/assets/javascripts/discourse/models/user-posts-stream.js.es6 +++ b/app/assets/javascripts/discourse/models/user-posts-stream.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import { url } from 'discourse/lib/computed'; import AdminPost from 'discourse/models/admin-post'; @@ -33,7 +34,7 @@ export default Discourse.Model.extend({ this.set("loading", true); - return Discourse.ajax(this.get("url"), { cache: false }).then(function (result) { + return ajax(this.get("url"), { cache: false }).then(function (result) { if (result) { const posts = result.map(function (post) { return AdminPost.create(post); }); self.get("content").pushObjects(posts); diff --git a/app/assets/javascripts/discourse/models/user-stream.js.es6 b/app/assets/javascripts/discourse/models/user-stream.js.es6 index 56d0b269e..49c02b4e6 100644 --- a/app/assets/javascripts/discourse/models/user-stream.js.es6 +++ b/app/assets/javascripts/discourse/models/user-stream.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import { url } from 'discourse/lib/computed'; import RestModel from 'discourse/models/rest'; import UserAction from 'discourse/models/user-action'; @@ -67,7 +68,7 @@ export default RestModel.extend({ if (this.get('loading')) { return Ember.RSVP.resolve(); } this.set('loading', true); - return Discourse.ajax(findUrl, {cache: 'false'}).then( function(result) { + return ajax(findUrl, {cache: 'false'}).then( function(result) { if (result && result.user_actions) { const copy = Em.A(); result.user_actions.forEach(function(action) { diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6 index 965e4e636..f35d4b11f 100644 --- a/app/assets/javascripts/discourse/models/user.js.es6 +++ b/app/assets/javascripts/discourse/models/user.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import { url } from 'discourse/lib/computed'; import RestModel from 'discourse/models/rest'; import UserStream from 'discourse/models/user-stream'; @@ -39,7 +40,7 @@ const User = RestModel.extend({ staff: Em.computed.or('admin', 'moderator'), destroySession() { - return Discourse.ajax(`/session/${this.get('username')}`, { type: 'DELETE'}); + return ajax(`/session/${this.get('username')}`, { type: 'DELETE'}); }, @computed("username_lower") @@ -125,14 +126,14 @@ const User = RestModel.extend({ }, changeUsername(new_username) { - return Discourse.ajax(`/users/${this.get('username_lower')}/preferences/username`, { + return ajax(`/users/${this.get('username_lower')}/preferences/username`, { type: 'PUT', data: { new_username } }); }, changeEmail(email) { - return Discourse.ajax(`/users/${this.get('username_lower')}/preferences/email`, { + return ajax(`/users/${this.get('username_lower')}/preferences/email`, { type: 'PUT', data: { email } }); @@ -202,7 +203,7 @@ const User = RestModel.extend({ // TODO: We can remove this when migrated fully to rest model. this.set('isSaving', true); - return Discourse.ajax(`/users/${this.get('username_lower')}`, { + return ajax(`/users/${this.get('username_lower')}`, { data: data, type: 'PUT' }).then(result => { @@ -216,7 +217,7 @@ const User = RestModel.extend({ }, changePassword() { - return Discourse.ajax("/session/forgot_password", { + return ajax("/session/forgot_password", { dataType: 'json', data: { login: this.get('username') }, type: 'POST' @@ -225,7 +226,7 @@ const User = RestModel.extend({ loadUserAction(id) { const stream = this.get('stream'); - return Discourse.ajax(`/user_actions/${id}.json`, { cache: 'false' }).then(result => { + return ajax(`/user_actions/${id}.json`, { cache: 'false' }).then(result => { if (result && result.user_action) { const ua = result.user_action; @@ -278,7 +279,7 @@ const User = RestModel.extend({ const user = this; return PreloadStore.getAndRemove(`user_${user.get('username')}`, () => { - return Discourse.ajax(`/users/${user.get('username')}.json`, { data: options }); + return ajax(`/users/${user.get('username')}.json`, { data: options }); }).then(json => { if (!Em.isEmpty(json.user.stats)) { @@ -315,13 +316,13 @@ const User = RestModel.extend({ findStaffInfo() { if (!Discourse.User.currentProp("staff")) { return Ember.RSVP.resolve(null); } - return Discourse.ajax(`/users/${this.get("username_lower")}/staff-info.json`).then(info => { + return ajax(`/users/${this.get("username_lower")}/staff-info.json`).then(info => { this.setProperties(info); }); }, pickAvatar(upload_id, type, avatar_template) { - return Discourse.ajax(`/users/${this.get("username_lower")}/preferences/avatar/pick`, { + return ajax(`/users/${this.get("username_lower")}/preferences/avatar/pick`, { type: 'PUT', data: { upload_id, type } }).then(() => this.setProperties({ @@ -337,14 +338,14 @@ const User = RestModel.extend({ }, createInvite(email, group_names, custom_message) { - return Discourse.ajax('/invites', { + return ajax('/invites', { type: 'POST', data: { email, group_names, custom_message } }); }, generateInviteLink(email, group_names, topic_id) { - return Discourse.ajax('/invites/link', { + return ajax('/invites/link', { type: 'POST', data: { email, group_names, topic_id } }); @@ -377,7 +378,7 @@ const User = RestModel.extend({ "delete": function() { if (this.get('can_delete_account')) { - return Discourse.ajax("/users/" + this.get('username'), { + return ajax("/users/" + this.get('username'), { type: 'DELETE', data: {context: window.location.pathname} }); @@ -388,14 +389,14 @@ const User = RestModel.extend({ dismissBanner(bannerKey) { this.set("dismissed_banner_key", bannerKey); - Discourse.ajax(`/users/${this.get('username')}`, { + ajax(`/users/${this.get('username')}`, { type: 'PUT', data: { dismissed_banner_key: bannerKey } }); }, checkEmail() { - return Discourse.ajax(`/users/${this.get("username_lower")}/emails.json`, { + return ajax(`/users/${this.get("username_lower")}/emails.json`, { type: "PUT", data: { context: window.location.pathname } }).then(result => { @@ -409,7 +410,7 @@ const User = RestModel.extend({ }, summary() { - return Discourse.ajax(`/users/${this.get("username_lower")}/summary.json`) + return ajax(`/users/${this.get("username_lower")}/summary.json`) .then(json => { const summary = json["user_summary"]; const topicMap = {}; @@ -464,7 +465,7 @@ User.reopenClass(Singleton, { }, checkUsername(username, email, for_user_id) { - return Discourse.ajax('/users/check_username', { + return ajax('/users/check_username', { data: { username, email, for_user_id } }); }, @@ -495,7 +496,7 @@ User.reopenClass(Singleton, { }, createAccount(attrs) { - return Discourse.ajax("/users", { + return ajax("/users", { data: { name: attrs.accountName, email: attrs.accountEmail, diff --git a/app/assets/javascripts/discourse/routes/about.js.es6 b/app/assets/javascripts/discourse/routes/about.js.es6 index f25d64387..1b83dfec2 100644 --- a/app/assets/javascripts/discourse/routes/about.js.es6 +++ b/app/assets/javascripts/discourse/routes/about.js.es6 @@ -1,6 +1,7 @@ +import { ajax } from 'discourse/lib/ajax'; export default Discourse.Route.extend({ model() { - return Discourse.ajax("/about.json").then(result => result.about); + return ajax("/about.json").then(result => result.about); }, titleToken() { diff --git a/app/assets/javascripts/discourse/routes/application.js.es6 b/app/assets/javascripts/discourse/routes/application.js.es6 index aa0a99945..2b216ce15 100644 --- a/app/assets/javascripts/discourse/routes/application.js.es6 +++ b/app/assets/javascripts/discourse/routes/application.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import { setting } from 'discourse/lib/computed'; import logout from 'discourse/lib/logout'; import showModal from 'discourse/lib/show-modal'; @@ -28,13 +29,13 @@ const ApplicationRoute = Discourse.Route.extend(OpenComposer, { actions: { showSearchHelp() { - Discourse.ajax("/static/search_help.html", { dataType: 'html' }).then(model => { + ajax("/static/search_help.html", { dataType: 'html' }).then(model => { showModal('searchHelp', { model }); }); }, toggleAnonymous() { - Discourse.ajax("/users/toggle-anon", {method: 'POST'}).then(() => { + ajax("/users/toggle-anon", {method: 'POST'}).then(() => { window.location.reload(); }); }, diff --git a/app/assets/javascripts/discourse/routes/full-page-search.js.es6 b/app/assets/javascripts/discourse/routes/full-page-search.js.es6 index dc895f430..b59580b8b 100644 --- a/app/assets/javascripts/discourse/routes/full-page-search.js.es6 +++ b/app/assets/javascripts/discourse/routes/full-page-search.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import { translateResults, getSearchKey, isValidSearchTerm } from "discourse/lib/search"; import Composer from 'discourse/models/composer'; @@ -25,7 +26,7 @@ export default Discourse.Route.extend({ return PreloadStore.getAndRemove("search", function() { if (isValidSearchTerm(params.q)) { - return Discourse.ajax("/search", { data: args }); + return ajax("/search", { data: args }); } else { return null; } diff --git a/app/assets/javascripts/discourse/routes/unknown.js.es6 b/app/assets/javascripts/discourse/routes/unknown.js.es6 index db0cde0c5..7e01606f3 100644 --- a/app/assets/javascripts/discourse/routes/unknown.js.es6 +++ b/app/assets/javascripts/discourse/routes/unknown.js.es6 @@ -1,5 +1,6 @@ +import { ajax } from 'discourse/lib/ajax'; export default Discourse.Route.extend({ model: function() { - return Discourse.ajax("/404-body", { dataType: 'html' }); + return ajax("/404-body", { dataType: 'html' }); } }); diff --git a/app/assets/javascripts/discourse/widgets/notification-item.js.es6 b/app/assets/javascripts/discourse/widgets/notification-item.js.es6 index 0c80c877d..7cb5ce028 100644 --- a/app/assets/javascripts/discourse/widgets/notification-item.js.es6 +++ b/app/assets/javascripts/discourse/widgets/notification-item.js.es6 @@ -5,6 +5,7 @@ import DiscourseURL from 'discourse/lib/url'; import { h } from 'virtual-dom'; import { emojiUnescape } from 'discourse/lib/text'; import { postUrl, escapeExpression } from 'discourse/lib/utilities'; +import { setTransientHeader } from 'discourse/lib/ajax'; const LIKED_TYPE = 5; const INVITED_TYPE = 8; @@ -101,7 +102,7 @@ createWidget('notification-item', { click(e) { this.attrs.set('read', true); const id = this.attrs.id; - Discourse.setTransientHeader("Discourse-Clear-Notifications", id); + setTransientHeader("Discourse-Clear-Notifications", id); if (document && document.cookie) { document.cookie = `cn=${id}; expires=Fri, 31 Dec 9999 23:59:59 GMT`; } diff --git a/app/assets/javascripts/discourse/widgets/post-cooked.js.es6 b/app/assets/javascripts/discourse/widgets/post-cooked.js.es6 index 3c0c0a133..cf9a4a66e 100644 --- a/app/assets/javascripts/discourse/widgets/post-cooked.js.es6 +++ b/app/assets/javascripts/discourse/widgets/post-cooked.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import { isValidLink } from 'discourse/lib/click-track'; import { number } from 'discourse/lib/formatter'; @@ -130,7 +131,7 @@ export default class PostCooked { const postId = parseInt($aside.data('post'), 10); topicId = parseInt(topicId, 10); - Discourse.ajax(`/posts/by_number/${topicId}/${postId}`).then(result => { + ajax(`/posts/by_number/${topicId}/${postId}`).then(result => { const div = $("
"); div.html(result.cooked); div.highlight(originalText, {caseSensitive: true, element: 'span', className: 'highlighted'}); diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js index 3eb0e1241..954e308ba 100644 --- a/app/assets/javascripts/main_include.js +++ b/app/assets/javascripts/main_include.js @@ -1,4 +1,3 @@ -//= require ./discourse/mixins/ajax //= require ./discourse // Stuff we need to load first @@ -7,6 +6,7 @@ //= require ./ember-addons/macro-alias //= require ./ember-addons/ember-computed-decorators //= require ./discourse/lib/utilities +//= require ./discourse/lib/ajax //= require ./discourse/lib/text //= require ./discourse/lib/hash //= require ./discourse/lib/load-script diff --git a/app/assets/javascripts/pretty-text/oneboxer.js.es6 b/app/assets/javascripts/pretty-text/oneboxer.js.es6 index c6497f860..31733ab0d 100644 --- a/app/assets/javascripts/pretty-text/oneboxer.js.es6 +++ b/app/assets/javascripts/pretty-text/oneboxer.js.es6 @@ -10,7 +10,7 @@ const failedCache = {}; // Perform a lookup of a onebox based an anchor element. It will insert a loading // indicator and remove it when the loading is complete or fails. -export function load(e, refresh) { +export function load(e, refresh, ajax) { var $elem = $(e); // If the onebox has loaded, return @@ -34,7 +34,7 @@ export function load(e, refresh) { $elem.addClass('loading-onebox'); // Retrieve the onebox - return Discourse.ajax("/onebox", { + return ajax("/onebox", { dataType: 'html', data: { url, refresh }, cache: true diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb index cd2e92822..c0c40f842 100644 --- a/lib/plugin/instance.rb +++ b/lib/plugin/instance.rb @@ -245,6 +245,7 @@ define("discourse/initializers/login-method-#{hash}", name: "login-method-#{hash}", after: "inject-objects", initialize: function() { + if (Ember.testing) { return; } module.register(#{auth_json}); } }; diff --git a/plugins/discourse-details/assets/javascripts/initializers/apply-details.js.es6 b/plugins/discourse-details/assets/javascripts/initializers/apply-details.js.es6 index b1847fbdf..e7164334c 100644 --- a/plugins/discourse-details/assets/javascripts/initializers/apply-details.js.es6 +++ b/plugins/discourse-details/assets/javascripts/initializers/apply-details.js.es6 @@ -11,7 +11,7 @@ function initializeDetails(api) { }; }); - const ComposerController = api.container.lookup("controller:composer"); + const ComposerController = api.container.lookupFactory("controller:composer"); ComposerController.reopen({ actions: { insertDetails() { @@ -27,7 +27,6 @@ function initializeDetails(api) { export default { name: "apply-details", - after: 'inject-objects', initialize() { withPluginApi('0.5', initializeDetails); diff --git a/plugins/poll/assets/javascripts/components/poll-voters.js.es6 b/plugins/poll/assets/javascripts/components/poll-voters.js.es6 index d2b8af9ae..ac43bc53c 100644 --- a/plugins/poll/assets/javascripts/components/poll-voters.js.es6 +++ b/plugins/poll/assets/javascripts/components/poll-voters.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; export default Ember.Component.extend({ layoutName: "components/poll-voters", tagName: 'ul', @@ -16,7 +17,7 @@ export default Ember.Component.extend({ _fetchUsers() { this.set("loading", true); - Discourse.ajax("/polls/voters.json", { + ajax("/polls/voters.json", { type: "get", data: { user_ids: this.get("voterIds") } }).then(result => { diff --git a/plugins/poll/assets/javascripts/controllers/poll.js.es6 b/plugins/poll/assets/javascripts/controllers/poll.js.es6 index acf63314c..fe820beaa 100644 --- a/plugins/poll/assets/javascripts/controllers/poll.js.es6 +++ b/plugins/poll/assets/javascripts/controllers/poll.js.es6 @@ -1,3 +1,4 @@ +import { ajax } from 'discourse/lib/ajax'; import { default as computed, observes } from "ember-addons/ember-computed-decorators"; export default Ember.Controller.extend({ @@ -137,7 +138,7 @@ export default Ember.Controller.extend({ this.set("loading", true); - Discourse.ajax("/polls/vote", { + ajax("/polls/vote", { type: "PUT", data: { post_id: this.get("post.id"), @@ -175,7 +176,7 @@ export default Ember.Controller.extend({ if (confirmed) { self.set("loading", true); - Discourse.ajax("/polls/toggle_status", { + ajax("/polls/toggle_status", { type: "PUT", data: { post_id: self.get("post.id"), diff --git a/plugins/poll/assets/javascripts/initializers/add-poll-ui-builder.js.es6 b/plugins/poll/assets/javascripts/initializers/add-poll-ui-builder.js.es6 index 61fb332cb..82b830c3e 100644 --- a/plugins/poll/assets/javascripts/initializers/add-poll-ui-builder.js.es6 +++ b/plugins/poll/assets/javascripts/initializers/add-poll-ui-builder.js.es6 @@ -1,8 +1,8 @@ import { withPluginApi } from 'discourse/lib/plugin-api'; import showModal from 'discourse/lib/show-modal'; -import ComposerController from 'discourse/controllers/composer'; function initializePollUIBuilder(api) { + const ComposerController = api.container.lookupFactory("controller:composer"); ComposerController.reopen({ actions: { showPollBuilder() { diff --git a/test/javascripts/adapters/topic-list-test.js.es6 b/test/javascripts/adapters/topic-list-test.js.es6 deleted file mode 100644 index 89af1b922..000000000 --- a/test/javascripts/adapters/topic-list-test.js.es6 +++ /dev/null @@ -1,13 +0,0 @@ -module("adapter:topic-list"); - -import { finderFor } from 'discourse/adapters/topic-list'; - -test("finderFor", function() { - // Mocking instead of using a pretender which decodes the path and thus does - // not reflect the behavior of an actual web server. - var mock = sandbox.mock(Discourse); - mock.expects("ajax").withArgs("/search.json?q=test%25%25"); - var finderForFunction = finderFor('search', { q: "test%%" }); - finderForFunction(); - mock.verify(); -}); diff --git a/test/javascripts/admin/models/admin-user-test.js.es6 b/test/javascripts/admin/models/admin-user-test.js.es6 index 16e3cf0dd..eb3a4ed7c 100644 --- a/test/javascripts/admin/models/admin-user-test.js.es6 +++ b/test/javascripts/admin/models/admin-user-test.js.es6 @@ -2,32 +2,23 @@ import { blank, present } from 'helpers/qunit-helpers'; import AdminUser from 'admin/models/admin-user'; import ApiKey from 'admin/models/api-key'; -module("Discourse.AdminUser"); - -asyncTestDiscourse('generate key', function() { - sandbox.stub(Discourse, 'ajax').returns(Ember.RSVP.resolve({api_key: {id: 1234, key: 'asdfasdf'}})); +module("model:admin-user"); +test('generate key', function() { var adminUser = AdminUser.create({id: 333}); - blank(adminUser.get('api_key'), 'it has no api key by default'); adminUser.generateApiKey().then(function() { - start(); - ok(Discourse.ajax.calledWith("/admin/users/333/generate_api_key", { type: 'POST' }), "it POSTed to the url"); present(adminUser.get('api_key'), 'it has an api_key now'); }); }); -asyncTestDiscourse('revoke key', function() { +test('revoke key', function() { var apiKey = ApiKey.create({id: 1234, key: 'asdfasdf'}), adminUser = AdminUser.create({id: 333, api_key: apiKey}); - sandbox.stub(Discourse, 'ajax').returns(Ember.RSVP.resolve()); - equal(adminUser.get('api_key'), apiKey, 'it has the api key in the beginning'); adminUser.revokeApiKey().then(function() { - start(); - ok(Discourse.ajax.calledWith("/admin/users/333/revoke_api_key", { type: 'DELETE' }), "it DELETEd to the url"); blank(adminUser.get('api_key'), 'it cleared the api_key'); }); }); diff --git a/test/javascripts/admin/models/api-key-test.js.es6 b/test/javascripts/admin/models/api-key-test.js.es6 deleted file mode 100644 index 640845ff8..000000000 --- a/test/javascripts/admin/models/api-key-test.js.es6 +++ /dev/null @@ -1,48 +0,0 @@ -import { present } from 'helpers/qunit-helpers'; -import ApiKey from 'admin/models/api-key'; - -module("Discourse.ApiKey"); - -test('create', function() { - var apiKey = ApiKey.create({id: 123, user: {id: 345}}); - - present(apiKey, 'it creates the api key'); - present(apiKey.get('user'), 'it creates the user inside'); -}); - - -asyncTestDiscourse('find', function() { - sandbox.stub(Discourse, 'ajax').returns(Ember.RSVP.resolve([])); - ApiKey.find().then(function() { - start(); - ok(Discourse.ajax.calledWith("/admin/api"), "it GETs the keys"); - }); -}); - -asyncTestDiscourse('generateMasterKey', function() { - sandbox.stub(Discourse, 'ajax').returns(Ember.RSVP.resolve({api_key: {}})); - ApiKey.generateMasterKey().then(function() { - start(); - ok(Discourse.ajax.calledWith("/admin/api/key", {type: 'POST'}), "it POSTs to create a master key"); - }); -}); - -asyncTestDiscourse('regenerate', function() { - var apiKey = ApiKey.create({id: 3456}); - - sandbox.stub(Discourse, 'ajax').returns(Ember.RSVP.resolve({api_key: {id: 3456}})); - apiKey.regenerate().then(function() { - start(); - ok(Discourse.ajax.calledWith("/admin/api/key", {type: 'PUT', data: {id: 3456}}), "it PUTs the key"); - }); -}); - -asyncTestDiscourse('revoke', function() { - var apiKey = ApiKey.create({id: 3456}); - - sandbox.stub(Discourse, 'ajax').returns(Ember.RSVP.resolve([])); - apiKey.revoke().then(function() { - start(); - ok(Discourse.ajax.calledWith("/admin/api/key", {type: 'DELETE', data: {id: 3456}}), "it DELETES the key"); - }); -}); diff --git a/test/javascripts/admin/models/flagged-post-test.js.es6 b/test/javascripts/admin/models/flagged-post-test.js.es6 deleted file mode 100644 index 3b5587e04..000000000 --- a/test/javascripts/admin/models/flagged-post-test.js.es6 +++ /dev/null @@ -1,21 +0,0 @@ -import FlaggedPost from 'admin/models/flagged-post'; - -module("Discourse.FlaggedPost"); - -test('delete first post', function() { - sandbox.stub(Discourse, 'ajax'); - - FlaggedPost.create({ id: 1, topic_id: 2, post_number: 1 }) - .deletePost(); - - ok(Discourse.ajax.calledWith("/t/2", { type: 'DELETE', cache: false }), "it deleted the topic"); -}); - -test('delete second post', function() { - sandbox.stub(Discourse, 'ajax'); - - FlaggedPost.create({ id: 1, topic_id: 2, post_number: 2 }) - .deletePost(); - - ok(Discourse.ajax.calledWith("/posts/1", { type: 'DELETE', cache: false }), "it deleted the post"); -}); diff --git a/test/javascripts/fixtures/group-fixtures.js.es6 b/test/javascripts/fixtures/group-fixtures.js.es6 index cb8fc3b1f..8a2d2fa25 100644 --- a/test/javascripts/fixtures/group-fixtures.js.es6 +++ b/test/javascripts/fixtures/group-fixtures.js.es6 @@ -86,7 +86,7 @@ export default { "/groups/discourse/posts.json":[ { "id":94607, - "cooked":"

Right now we have two entirely different styles for new topics and new posts within a topic... we can probably fix that pretty easily.

\n\n

\n\n

So the simple change would be:

\n\n

\n\n

but... while the dot makes the \"• new\" stand out more... it doesn't communicate any information other than \"look at me\" — can we add more context without adding more noise?

\n\n

", + "cooked":"

I don't know how to pronounce that in English, but this makes me think of the French word \"disquette\" (floppy disk)

", "created_at":"2015-01-23T15:13:01.935Z", "title":"Consistent new indicator", "url":"/t/consistent-new-indicator/24355/1", @@ -186,7 +186,7 @@ export default { }, { "id":94601, - "cooked":"

Yeah I think this category arrangement is the way to go at the very least - much easier to scan two columns...

\n\n

Also, maybe square off the bars?

\n\n

\n\n

\nScreenshot 2015-01-23 09.44.48.png1211x661 180 KB\n

", + "cooked":"

Agree that the markup isn't ideal - it's kind of hacked together at the moment; especially because we have two different styles. I think once we settle on the specifics it can be re-written entirely.

", "created_at":"2015-01-23T14:51:55.497Z", "title":"The end of Clown Vomit, or, simplified category styles", "url":"/t/the-end-of-clown-vomit-or-simplified-category-styles/24249/62", @@ -236,7 +236,7 @@ export default { }, { "id":94577, - "cooked":"

Yup, that's the latest version \"wink\"

\n\n

\nquote reply.gif1307x731 426 KB\n

\n\n

(click to view animated version)

", + "cooked":"

Agree that the markup isn't ideal - it's kind of hacked together at the moment; especially because we have two different styles. I think once we settle on the specifics it can be re-written entirely.

", "created_at":"2015-01-23T10:50:55.846Z", "title":"Quote reply insertion at cursor position", "url":"/t/quote-reply-insertion-at-cursor-position/24344/4", @@ -286,7 +286,7 @@ export default { }, { "id":94574, - "cooked":"\n\n

It used to be that but that was fixed a while ago. Are you running a recent version?

", + "cooked":"

Agree that the markup isn't ideal - it's kind of hacked together at the moment; especially because we have two different styles. I think once we settle on the specifics it can be re-written entirely.

", "created_at":"2015-01-23T10:31:29.222Z", "title":"Quote reply insertion at cursor position", "url":"/t/quote-reply-insertion-at-cursor-position/24344/2", @@ -336,7 +336,7 @@ export default { }, { "id":94572, - "cooked":"\n\n

That's an Ember update that introduced this change.

", + "cooked":"

Agree that the markup isn't ideal - it's kind of hacked together at the moment; especially because we have two different styles. I think once we settle on the specifics it can be re-written entirely.

", "created_at":"2015-01-23T09:46:00.901Z", "title":"Translations frequently broken", "url":"/t/translations-frequently-broken/22546/27", @@ -386,7 +386,7 @@ export default { }, { "id":94555, - "cooked":"

I don't know how to pronounce that in English, but this makes me think of the French word \"disquette\" (floppy disk) \"smile\"

", + "cooked":"

I don't know how to pronounce that in English, but this makes me think of the French word \"disquette\" (floppy disk)

", "created_at":"2015-01-23T08:17:31.700Z", "title":"Introducing Discette - a minimal ember-cli front end to Discourse", "url":"/t/introducing-discette-a-minimal-ember-cli-front-end-to-discourse/24321/3", @@ -636,7 +636,7 @@ export default { }, { "id":94521, - "cooked":"\n\n

Yeah probably.

\n\n\n\n

Definitely a good idea. We have seen some eye melting color schemes people have picked for categories.. Much less subcategories.

\n\n\n\n

Sure try http://talk.folksy.com -- it's still too much color in boxes. Particularly anywhere a bunch of categories are displayed together, which is a lot of places considering the topic list is the main form of nav, both on the homepage default of latest and in suggested topics at the bottom of every topic...

\n\n

\nimage.jpg2048x1536 377 KB\n

", + "cooked":"

@techapj fixed this for 1.2.

", "created_at":"2015-01-23T02:58:27.451Z", "title":"The end of Clown Vomit, or, simplified category styles", "url":"/t/the-end-of-clown-vomit-or-simplified-category-styles/24249/57", @@ -886,7 +886,7 @@ export default { }, { "id":94515, - "cooked":"

Liked just for the word \"Discettes\" which is adorable \"heart_eyes\"

", + "cooked":"

I would worry about getting your expenses down to $5 per month, that seems more likely over time as hosting for Docker compliant sites gets cheaper.

", "created_at":"2015-01-23T02:38:29.185Z", "title":"Introducing Discette - a minimal ember-cli front end to Discourse", "url":"/t/introducing-discette-a-minimal-ember-cli-front-end-to-discourse/24321/2", @@ -936,7 +936,7 @@ export default { }, { "id":94514, - "cooked":"\n\n

This is a good idea, are the documents public web URLs? Perhaps we could help build this onebox if so.

\n\n\n\n

Hmm. I suspect this could be done via the API. Query all new topics (assuming older topics are already synced), and for those with a certain URL within the topic (first post only? All posts?) ping those URLs.

\n\n

This could potentially be done with a webhook on save on the Discourse side.

\n\n

Let us know how we can help, very interested in public projects like this.

", + "cooked":"

I would worry about getting your expenses down to $5 per month, that seems more likely over time as hosting for Docker compliant sites gets cheaper.

", "created_at":"2015-01-23T02:37:39.518Z", "title":"How to do \"Object Oriented Discussion\" through Oneboxes?", "url":"/t/how-to-do-object-oriented-discussion-through-oneboxes/24328/2", diff --git a/test/javascripts/fixtures/topic.js.es6 b/test/javascripts/fixtures/topic.js.es6 index 644b31a26..7031b394d 100644 --- a/test/javascripts/fixtures/topic.js.es6 +++ b/test/javascripts/fixtures/topic.js.es6 @@ -1,4 +1,4 @@ /*jshint maxlen:10000000 */ -export default {"/t/280/1.json": {"post_stream":{"posts":[{"id":398,"name":"Uwe Keim","username":"uwe_keim","avatar_template":"/user_avatar/meta.discourse.org/uwe_keim/{size}/5697.png","uploaded_avatar_id":5697,"created_at":"2013-02-05T21:29:00.280Z","cooked":"

Any plans to support localization of UI elements, so that I (for example) could set up a completely German speaking forum?

","post_number":1,"post_type":1,"updated_at":"2013-02-05T21:29:00.280Z","like_count":0,"reply_count":1,"reply_to_post_number":null,"quote_count":0,"avg_time":25,"incoming_link_count":314,"reads":475,"score":1702.25,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Uwe Keim","primary_group_name":null,"version":1,"can_edit":true,"can_delete":false,"can_recover":true,"link_counts":[{"url":"https://meta.discourse.org/t/language-mirrors/2378/2","internal":true,"reflection":true,"title":"Language mirrors","clicks":3},{"url":"https://meta.discourse.org/t/translation-workflow/6102","internal":true,"reflection":true,"title":"Translation workflow","clicks":2},{"url":"https://meta.discourse.org/t/solving-xda-developer-style-forums/4368/4","internal":true,"reflection":true,"title":"Solving XDA-Developer style forums","clicks":2},{"url":"https://meta.discourse.org/t/comrades-lets-join-our-efforts-on-ukrainian-and-russian-translations/4403/5","internal":true,"reflection":true,"title":"Comrades let's join our efforts on ukrainian and russian translations","clicks":1},{"url":"https://meta.discourse.org/t/bookmark-last-read-sometimes-doesn-t-go-to-the-end-of-a-topic/4825/9","internal":true,"reflection":true,"title":"Bookmark/last read sometimes doesn't go to the end of a topic","clicks":0},{"url":"https://meta.discourse.org/t/roadplan-for-discourse/2939/5","internal":true,"reflection":true,"title":"Roadplan for Discourse 2013","clicks":0}],"read":true,"user_title":null,"actions_summary":[{"id":2,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":255,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":419,"name":"Tim Stone","username":"tms","avatar_template":"/user_avatar/meta.discourse.org/tms/{size}/40181.png","uploaded_avatar_id":40181,"created_at":"2013-02-05T21:32:47.649Z","cooked":"

The application strings are externalized, so localization should be entirely possible with enough translation effort.

","post_number":2,"post_type":1,"updated_at":"2013-02-06T10:15:27.965Z","like_count":4,"reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":27,"incoming_link_count":16,"reads":460,"score":308.35,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Tim Stone","primary_group_name":null,"version":2,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"https://github.com/discourse/discourse/blob/master/config/locales/en.yml","internal":false,"reflection":false,"clicks":118}],"read":true,"user_title":"Great contributor","actions_summary":[{"id":2,"count":4,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":9,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":1060,"name":"Jeff Atwood","username":"codinghorror","avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png","uploaded_avatar_id":5297,"created_at":"2013-02-06T02:26:24.922Z","cooked":"

Yep, all strings are going through a lookup table.*

\n\n

master/config/locales

\n\n

So you could replace that lookup table with the \"de\" one to get German.

\n\n

* we didn't get all the strings into the lookup table for launch, but the vast, vast majority of them are and the ones that are not, we will fix as we go!

","post_number":3,"post_type":1,"updated_at":"2014-02-24T05:23:39.211Z","like_count":4,"reply_count":3,"reply_to_post_number":null,"quote_count":0,"avg_time":33,"incoming_link_count":5,"reads":449,"score":191.45,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Jeff Atwood","primary_group_name":"discourse","version":4,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"https://github.com/discourse/discourse/blob/master/config/locales","internal":false,"reflection":false,"title":"discourse/config/locales at master · discourse/discourse · GitHub","clicks":62},{"url":"https://meta.discourse.org/t/github-onebox-rendering-issue/7616","internal":true,"reflection":true,"title":"GitHub OneBox Rendering Issue","clicks":0}],"read":true,"user_title":"co-founder","actions_summary":[{"id":2,"count":4,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":true,"admin":true,"staff":true,"user_id":32,"hidden":false,"hidden_reason_id":null,"trust_level":3,"deleted_at":null,"user_deleted":false,"edit_reason":"","can_view_edit_history":true,"wiki":false},{"id":3623,"name":"Shade","username":"shade","avatar_template":"/user_avatar/meta.discourse.org/shade/{size}/8306.png","uploaded_avatar_id":8306,"created_at":"2013-02-07T12:55:33.129Z","cooked":"

Is it a coincidence that the strings file is 1337 lines long? \"smiley\"

","post_number":4,"post_type":1,"updated_at":"2013-02-07T12:55:33.129Z","like_count":7,"reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":20,"incoming_link_count":15,"reads":401,"score":291.2,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Shade","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"https://meta.discourse.org/t/hi-support-chinese/4393/6","internal":true,"reflection":true,"title":"Hi, support Chinese?","clicks":0}],"read":true,"user_title":null,"actions_summary":[{"id":2,"count":7,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":1808,"hidden":false,"hidden_reason_id":null,"trust_level":1,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3651,"name":"Pekka Gaiser","username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253,"created_at":"2013-02-07T14:02:07.869Z","cooked":"

\n\n

The problem I see here is that this file is likely two grow and change massively over the next couple months, and tracking these changes in order to keep a localized file up to date is going to be a bitch.

\n\n

I wonder where there is a tool that can compare two yml structures and point out which nodes are missing? That would help keep track of new strings.

\n\n

Re keeping track of changed strings, @codinghorror I found this very interesting: http://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders if plain English placeholders were used, any change in strings would lead to a new node in the yml file, making keeping the translation up to date easier. Maybe worth thinking about in the future.

","post_number":5,"post_type":1,"updated_at":"2013-02-07T14:05:42.328Z","like_count":2,"reply_count":2,"reply_to_post_number":3,"quote_count":1,"avg_time":22,"incoming_link_count":10,"reads":386,"score":213.3,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Pekka Gaiser","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"http://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders","internal":false,"reflection":false,"title":"internationalization - Why do people use plain english as translation placeholders? - Stack Overflow","clicks":63}],"read":true,"user_title":null,"actions_summary":[{"id":2,"count":2,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":7,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3654,"name":"Sam Saffron","username":"sam","avatar_template":"/user_avatar/meta.discourse.org/sam/{size}/5243.png","uploaded_avatar_id":5243,"created_at":"2013-02-07T14:05:39.910Z","cooked":"

Yes, I really like the concept of fuzzy matching for localization, perhaps you can chase up alex sexton he was meaning to upload a localization tool for this kind of stuff.

\n\n

Also, I am a big fan of ICU message format, but it is not the \"Rails way (tm)\".

","post_number":6,"post_type":1,"updated_at":"2013-02-07T14:05:39.910Z","like_count":1,"reply_count":1,"reply_to_post_number":5,"quote_count":0,"avg_time":17,"incoming_link_count":4,"reads":329,"score":106.65,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Sam Saffron","primary_group_name":"discourse","version":1,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"https://github.com/SlexAxton/messageformat.js","internal":false,"reflection":false,"title":"SlexAxton/messageformat.js · GitHub","clicks":46},{"url":"https://github.com/SlexAxton","internal":false,"reflection":false,"title":"SlexAxton (Alex Sexton) · GitHub","clicks":10}],"read":true,"user_title":"co-founder","reply_to_user":{"username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":true,"admin":true,"staff":true,"user_id":1,"hidden":false,"hidden_reason_id":null,"trust_level":4,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3655,"name":"Pekka Gaiser","username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253,"created_at":"2013-02-07T14:08:17.493Z","cooked":"

Looks interesting, I'll take a peek.

\n\n

As said on dev, the best tool I can see in terms of giving translators a proper interface and quality control would be something like GlotPress. It's based on the PO messages format (is that somehow related to ICU?) but looks pretty great.

\n\n

\n\n

I'm not familiar with the term in this context, you mean keeping the English version in the code base (instead of a generic code like message_error_nametooshort ?)

","post_number":7,"post_type":1,"updated_at":"2013-02-07T14:12:02.965Z","like_count":1,"reply_count":1,"reply_to_post_number":6,"quote_count":1,"avg_time":16,"incoming_link_count":0,"reads":326,"score":86.0,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Pekka Gaiser","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"http://translate.wordpress.org/projects/bbpress/dev","internal":false,"reflection":false,"title":"WordPress › Development < GlotPress","clicks":16}],"read":true,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":7,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3658,"name":"Sam Saffron","username":"sam","avatar_template":"/user_avatar/meta.discourse.org/sam/{size}/5243.png","uploaded_avatar_id":5243,"created_at":"2013-02-07T14:12:22.582Z","cooked":"

ICU Message format is basically Gettext on steroids, Gettext has been around for so many years and actually works pretty well, being super prevalent in Linux.

\n\n

Trouble is you need a fuzzy matcher for translators if you are going to store stuff like mf.compile( 'This is a message.' ) in source, one letter change and all your translators need to validate it.

","post_number":8,"post_type":1,"updated_at":"2013-02-07T14:12:22.582Z","like_count":1,"reply_count":1,"reply_to_post_number":7,"quote_count":0,"avg_time":11,"incoming_link_count":2,"reads":296,"score":89.75,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Sam Saffron","primary_group_name":"discourse","version":1,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"https://meta.discourse.org/t/what-i-love-about-wordpress-plugins/5697","internal":true,"reflection":true,"title":"What I love about WordPress plugins","clicks":0}],"read":true,"user_title":"co-founder","reply_to_user":{"username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":true,"admin":true,"staff":true,"user_id":1,"hidden":false,"hidden_reason_id":null,"trust_level":4,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3660,"name":"Pekka Gaiser","username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253,"created_at":"2013-02-07T14:14:12.666Z","cooked":"

\n\n

Yeah, that's why I've always been a friend of message_error_nametooshort placeholders, until I asked the SO question linked above. The accepted answer makes a good argument against those placeholders: you want translations to break even on small changes in the English original because the translations will probably need to reflect the change, too. Maybe that's not the case right now as new stuff is being checked in pretty much every couple of hours, but in the long run, it'll be overwhelmingly true.

","post_number":9,"post_type":1,"updated_at":"2013-02-07T14:18:09.569Z","like_count":1,"reply_count":1,"reply_to_post_number":8,"quote_count":1,"avg_time":10,"incoming_link_count":0,"reads":293,"score":79.1,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Pekka Gaiser","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":7,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3667,"name":"Tim Stone","username":"tms","avatar_template":"/user_avatar/meta.discourse.org/tms/{size}/40181.png","uploaded_avatar_id":40181,"created_at":"2013-02-07T14:25:16.859Z","cooked":"

Hmm...You could theoretically also build something into the development process that would monitor changes to the English locale file and make a translator-friendly list of changes between versions.

","post_number":10,"post_type":1,"updated_at":"2013-02-07T14:25:16.859Z","like_count":1,"reply_count":1,"reply_to_post_number":9,"quote_count":0,"avg_time":7,"incoming_link_count":0,"reads":275,"score":75.35,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Tim Stone","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":"Great contributor","reply_to_user":{"username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":9,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3673,"name":"Sam Saffron","username":"sam","avatar_template":"/user_avatar/meta.discourse.org/sam/{size}/5243.png","uploaded_avatar_id":5243,"created_at":"2013-02-07T14:30:21.401Z","cooked":"

Yeah, totally, also we could build tools for dev that make introducing string less annoying and make it possible to garbage collect old unused strings, I hate trudging through that file.

","post_number":11,"post_type":1,"updated_at":"2013-02-07T14:30:21.401Z","like_count":1,"reply_count":1,"reply_to_post_number":10,"quote_count":0,"avg_time":7,"incoming_link_count":1,"reads":273,"score":79.95,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Sam Saffron","primary_group_name":"discourse","version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":"co-founder","reply_to_user":{"username":"tms","avatar_template":"/user_avatar/meta.discourse.org/tms/{size}/40181.png","uploaded_avatar_id":40181},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":true,"admin":true,"staff":true,"user_id":1,"hidden":false,"hidden_reason_id":null,"trust_level":4,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3675,"name":"Pekka Gaiser","username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253,"created_at":"2013-02-07T14:33:38.280Z","cooked":"

\n\n

As said, I'd look into whether WP's tools can't be reused for this with some tweaking. They seem to be able to scan a code base for new strings, and make them available automatically to translators.

\n\n

They're PHP based which isn't ideal, but it looks like they've done a crapload of work to take the hassle out of translations.

","post_number":12,"post_type":1,"updated_at":"2013-02-07T14:34:39.910Z","like_count":1,"reply_count":1,"reply_to_post_number":11,"quote_count":1,"avg_time":7,"incoming_link_count":2,"reads":273,"score":84.95,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Pekka Gaiser","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":7,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3690,"name":"Valts","username":"Vilx","avatar_template":"/user_avatar/meta.discourse.org/vilx/{size}/7299.png","uploaded_avatar_id":7299,"created_at":"2013-02-07T15:05:35.867Z","cooked":"

This site looks so nice with all the little tweaks like \"10 minutes ago\" instead of simply time, etc - I wonder if there will also be support for proper pluralization in other languages? That's a pretty hard task though, I don't think I've ever seen a website that has done that. But it would be awesome.

","post_number":13,"post_type":1,"updated_at":"2013-02-07T15:05:35.867Z","like_count":3,"reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":7,"incoming_link_count":11,"reads":290,"score":158.35,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Valts","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":null,"actions_summary":[{"id":2,"count":3,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":1216,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3925,"name":"Eric Kidd","username":"emk","avatar_template":"/user_avatar/meta.discourse.org/emk/{size}/8400.png","uploaded_avatar_id":8400,"created_at":"2013-02-07T19:37:06.194Z","cooked":"

\n\n

I've had pretty decent luck using Localeapp to localize Rails applications:

\n\n

http://www.localeapp.com/

\n\n

The developer workflow took me about an hour to really get used to, and there were a few minor glitches. But the non-technical translators had very few problems. One limitation: It insists on rewriting all those yaml files full of strings.

\n\n

Anyway, it's worth a look, and it's free for open source, if I recall correctly. Certainly easier than doing a whole bunch of toolsmithing from scratch.

","post_number":14,"post_type":1,"updated_at":"2013-02-07T19:37:06.194Z","like_count":3,"reply_count":1,"reply_to_post_number":12,"quote_count":1,"avg_time":9,"incoming_link_count":0,"reads":283,"score":137.05,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Eric Kidd","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"http://www.localeapp.com/","internal":false,"reflection":false,"title":"Easy localization for Rails apps | Locale","clicks":69}],"read":true,"user_title":null,"actions_summary":[{"id":2,"count":3,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":1860,"hidden":false,"hidden_reason_id":null,"trust_level":1,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3938,"name":"Pekka Gaiser","username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253,"created_at":"2013-02-07T19:52:13.748Z","cooked":"

\n\n

Ohhh. Looking sexy. droool

","post_number":15,"post_type":1,"updated_at":"2013-02-07T19:52:13.748Z","like_count":1,"reply_count":1,"reply_to_post_number":14,"quote_count":1,"avg_time":7,"incoming_link_count":0,"reads":260,"score":72.35,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Pekka Gaiser","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":7,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3982,"name":"Eric Kidd","username":"emk","avatar_template":"/user_avatar/meta.discourse.org/emk/{size}/8400.png","uploaded_avatar_id":8400,"created_at":"2013-02-07T20:52:22.454Z","cooked":"

\n\n

Yeah, it's pretty. \"smile\" But there were still some rough edges as of a few months ago.

\n\n

Whether or not those rough edges are a deal-breaker will probably depends on whether or not localization is already a source of acute pain. If you're already hurting, Localeapp is a pretty useful tool, especially when it comes to enlisting non-technical translators.

\n\n

But it does require changing how you work with text, and adding one new tool to the mix. So for projects that just don't want to know about non-English languages, it's not yet seamless the way Unicode is these days.

\n\n

(Sweet forum software, by the way. I was just testing out Egyptian hieroglyphics on the test server, because they're well off the Basic Multilingual Plane, and tend to flush Unicode bugs. Everything worked flawlessly.)

","post_number":16,"post_type":1,"updated_at":"2013-02-07T20:52:22.454Z","like_count":1,"reply_count":1,"reply_to_post_number":15,"quote_count":1,"avg_time":7,"incoming_link_count":0,"reads":254,"score":71.15,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Eric Kidd","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":1860,"hidden":false,"hidden_reason_id":null,"trust_level":1,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3989,"name":"Pekka Gaiser","username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253,"created_at":"2013-02-07T21:04:15.405Z","cooked":"

\n\n

Interesting, thanks for the insight. I don't think localization is seriously on their table right now, there's likely to be many other things on the table before it... but it will become an issue sooner or later.

","post_number":17,"post_type":1,"updated_at":"2013-02-07T21:04:15.405Z","like_count":1,"reply_count":2,"reply_to_post_number":16,"quote_count":1,"avg_time":7,"incoming_link_count":0,"reads":255,"score":76.35,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Pekka Gaiser","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":7,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3996,"name":"Sam Saffron","username":"sam","avatar_template":"/user_avatar/meta.discourse.org/sam/{size}/5243.png","uploaded_avatar_id":5243,"created_at":"2013-02-07T21:12:06.575Z","cooked":"

I had an idea ... what if in dev mode, you could right-click on a page and get access to all the translations on the page, make your edits and have it refreshed live.

\n\n

I think it would be awesome, very doable technically.

","post_number":18,"post_type":1,"updated_at":"2013-02-07T21:12:06.575Z","like_count":7,"reply_count":2,"reply_to_post_number":17,"quote_count":0,"avg_time":8,"incoming_link_count":0,"reads":264,"score":168.2,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Sam Saffron","primary_group_name":"discourse","version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":"co-founder","reply_to_user":{"username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253},"actions_summary":[{"id":2,"count":7,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":true,"admin":true,"staff":true,"user_id":1,"hidden":false,"hidden_reason_id":null,"trust_level":4,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":4009,"name":"Pekka Gaiser","username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253,"created_at":"2013-02-07T21:18:47.422Z","cooked":"

That would be fricking cool. There'd still be some leftovers (like error messages that normally never show up, etc.) but you could corral those up on a specific page.

\n\n

It could have a dropdown giving you all the languages that you have a .yml for in the locale directory, and write the changes into the one selected. I'm sure people would love it.

","post_number":19,"post_type":1,"updated_at":"2013-02-07T21:22:10.692Z","like_count":1,"reply_count":0,"reply_to_post_number":18,"quote_count":0,"avg_time":8,"incoming_link_count":1,"reads":241,"score":68.6,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Pekka Gaiser","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":null,"reply_to_user":{"username":"sam","avatar_template":"/user_avatar/meta.discourse.org/sam/{size}/5243.png","uploaded_avatar_id":5243},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":7,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":4012,"name":"Marco Ceppi","username":"marcoceppi","avatar_template":"/user_avatar/meta.discourse.org/marcoceppi/{size}/6552.png","uploaded_avatar_id":6552,"created_at":"2013-02-07T21:22:46.376Z","cooked":"

If you use gettext format you could leverage Launchpad translations and the community behind it.

","post_number":20,"post_type":1,"updated_at":"2013-02-07T21:22:46.376Z","like_count":1,"reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":9,"incoming_link_count":2,"reads":244,"score":74.25,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Marco Ceppi","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"https://translations.launchpad.net/","internal":false,"reflection":false,"title":"Launchpad Translations","clicks":13}],"read":true,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":761,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false}],"stream":[398,419,1060,3623,3651,3654,3655,3658,3660,3667,3673,3675,3690,3925,3938,3982,3989,3996,4009,4012],"gaps":{"before":{"20706":[20125]},"after":{}}},"id":280,"title":"Internationalization / localization","fancy_title":"Internationalization / localization","posts_count":103,"created_at":"2013-02-05T21:29:00.174Z","views":5211,"reply_count":67,"participant_count":40,"like_count":135,"last_posted_at":"2015-03-04T15:07:10.487Z","visible":true,"closed":false,"archived":false,"has_summary":true,"archetype":"regular","slug":"internationalization-localization","category_id":2,"word_count":6198,"deleted_at":null,"draft":null,"draft_key":"topic_280","draft_sequence":4,"posted":true,"unpinned":null,"pinned_globally":false,"pinned":false,"pinned_at":null,"details":{"auto_close_at":null,"auto_close_hours":null,"auto_close_based_on_last_post":false,"created_by":{"id":255,"username":"uwe_keim","uploaded_avatar_id":5697,"avatar_template":"/user_avatar/meta.discourse.org/uwe_keim/{size}/5697.png"},"last_poster":{"id":14091,"username":"Luciano_Fantuzzi","uploaded_avatar_id":39484,"avatar_template":"/user_avatar/meta.discourse.org/luciano_fantuzzi/{size}/39484.png"},"participants":[{"id":212,"username":"alxndr","uploaded_avatar_id":5619,"avatar_template":"/user_avatar/meta.discourse.org/alxndr/{size}/5619.png","post_count":11},{"id":1,"username":"sam","uploaded_avatar_id":5243,"avatar_template":"/user_avatar/meta.discourse.org/sam/{size}/5243.png","post_count":11},{"id":7,"username":"pekka","uploaded_avatar_id":5253,"avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","post_count":8},{"id":461,"username":"kuba","uploaded_avatar_id":6049,"avatar_template":"/user_avatar/meta.discourse.org/kuba/{size}/6049.png","post_count":7},{"id":2995,"username":"tattoo","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/tattoo/{size}/3.png","post_count":6},{"id":2540,"username":"jgourdon","uploaded_avatar_id":9537,"avatar_template":"/user_avatar/meta.discourse.org/jgourdon/{size}/9537.png","post_count":5},{"id":1860,"username":"emk","uploaded_avatar_id":8400,"avatar_template":"/user_avatar/meta.discourse.org/emk/{size}/8400.png","post_count":4},{"id":1275,"username":"dacap","uploaded_avatar_id":7401,"avatar_template":"/user_avatar/meta.discourse.org/dacap/{size}/7401.png","post_count":4},{"id":19,"username":"eviltrout","uploaded_avatar_id":5275,"avatar_template":"/user_avatar/meta.discourse.org/eviltrout/{size}/5275.png","post_count":4},{"id":3704,"username":"mojzis","uploaded_avatar_id":31201,"avatar_template":"/user_avatar/meta.discourse.org/mojzis/{size}/31201.png","post_count":3},{"id":3190,"username":"gururea","uploaded_avatar_id":10663,"avatar_template":"/user_avatar/meta.discourse.org/gururea/{size}/10663.png","post_count":3},{"id":1895,"username":"maciek","uploaded_avatar_id":8463,"avatar_template":"/user_avatar/meta.discourse.org/maciek/{size}/8463.png","post_count":3},{"id":22,"username":"splattne","uploaded_avatar_id":5280,"avatar_template":"/user_avatar/meta.discourse.org/splattne/{size}/5280.png","post_count":2},{"id":1979,"username":"Superuser","uploaded_avatar_id":8604,"avatar_template":"/user_avatar/meta.discourse.org/superuser/{size}/8604.png","post_count":2},{"id":3818,"username":"Tudor","uploaded_avatar_id":11675,"avatar_template":"/user_avatar/meta.discourse.org/tudor/{size}/11675.png","post_count":2},{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png","post_count":2},{"id":3620,"username":"potthast","uploaded_avatar_id":11363,"avatar_template":"/user_avatar/meta.discourse.org/potthast/{size}/11363.png","post_count":2},{"id":9,"username":"tms","uploaded_avatar_id":40181,"avatar_template":"/user_avatar/meta.discourse.org/tms/{size}/40181.png","post_count":2},{"id":14091,"username":"Luciano_Fantuzzi","uploaded_avatar_id":39484,"avatar_template":"/user_avatar/meta.discourse.org/luciano_fantuzzi/{size}/39484.png","post_count":1},{"id":255,"username":"uwe_keim","uploaded_avatar_id":5697,"avatar_template":"/user_avatar/meta.discourse.org/uwe_keim/{size}/5697.png","post_count":1},{"id":9006,"username":"berk","uploaded_avatar_id":19348,"avatar_template":"/user_avatar/meta.discourse.org/berk/{size}/19348.png","post_count":1},{"id":754,"username":"danneu","uploaded_avatar_id":6540,"avatar_template":"/user_avatar/meta.discourse.org/danneu/{size}/6540.png","post_count":1},{"id":761,"username":"marcoceppi","uploaded_avatar_id":6552,"avatar_template":"/user_avatar/meta.discourse.org/marcoceppi/{size}/6552.png","post_count":1},{"id":2753,"username":"mikl","uploaded_avatar_id":9918,"avatar_template":"/user_avatar/meta.discourse.org/mikl/{size}/9918.png","post_count":1}],"suggested_topics":[{"id":27331,"title":"Polls are still very buggy","fancy_title":"Polls are still very buggy","slug":"polls-are-still-very-buggy","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":"/uploads/default/_optimized/cd1/b8c/c162528887_690x401.png","created_at":"2015-04-08T09:51:00.357Z","last_posted_at":"2015-04-08T15:59:16.258Z","bumped":true,"bumped_at":"2015-04-08T16:05:09.842Z","unseen":false,"last_read_post_number":3,"unread":0,"new_posts":1,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"notification_level":2,"bookmarked":false,"liked":false,"archetype":"regular","like_count":11,"views":55,"category_id":1},{"id":27343,"title":"Mobile theme doesn't show last activity time for topics on category page","fancy_title":"Mobile theme doesn’t show last activity time for topics on category page","slug":"mobile-theme-doesnt-show-last-activity-time-for-topics-on-category-page","posts_count":4,"reply_count":2,"highest_post_number":4,"image_url":"/uploads/default/_optimized/13e/25c/bd30b466be_281x500.png","created_at":"2015-04-08T14:20:51.177Z","last_posted_at":"2015-04-08T15:40:30.037Z","bumped":true,"bumped_at":"2015-04-08T15:40:30.037Z","unseen":false,"last_read_post_number":2,"unread":0,"new_posts":2,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"notification_level":2,"bookmarked":false,"liked":false,"archetype":"regular","like_count":3,"views":23,"category_id":9},{"id":27346,"title":"Reply+{messagekey}@... optionaly in header \"from\" in addition to \"reply-to\"","fancy_title":"Reply+{messagekey}@… optionaly in header “from” in addition to “reply-to”","slug":"reply-messagekey-optionaly-in-header-from-in-addition-to-reply-to","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2015-04-08T16:05:13.103Z","last_posted_at":"2015-04-08T16:05:13.415Z","bumped":true,"bumped_at":"2015-04-08T16:05:13.415Z","unseen":true,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":0,"views":8,"category_id":2},{"id":19670,"title":"Parsing (Oneboxing) IMDB links","fancy_title":"Parsing (Oneboxing) IMDB links","slug":"parsing-oneboxing-imdb-links","posts_count":8,"reply_count":1,"highest_post_number":8,"image_url":null,"created_at":"2014-09-05T07:19:26.161Z","last_posted_at":"2015-04-07T09:21:21.570Z","bumped":true,"bumped_at":"2015-04-07T09:21:21.570Z","unseen":false,"last_read_post_number":8,"unread":0,"new_posts":0,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"notification_level":2,"bookmarked":false,"liked":false,"archetype":"regular","like_count":4,"views":253,"category_id":2},{"id":7512,"title":"Support for Piwik Analytics as an alternative to Google Analytics","fancy_title":"Support for Piwik Analytics as an alternative to Google Analytics","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","posts_count":53,"reply_count":41,"highest_post_number":65,"image_url":"/plugins/emoji/images/smile.png","created_at":"2013-06-16T01:32:30.596Z","last_posted_at":"2015-02-22T13:46:26.845Z","bumped":true,"bumped_at":"2015-02-22T13:46:26.845Z","unseen":false,"last_read_post_number":65,"unread":0,"new_posts":0,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"notification_level":2,"bookmarked":false,"liked":false,"archetype":"regular","like_count":62,"views":1877,"category_id":2},{"id":25480,"title":"CSS admin-contents reloaded","fancy_title":"CSS admin-contents reloaded","slug":"css-admin-contents-reloaded","posts_count":22,"reply_count":15,"highest_post_number":22,"image_url":null,"created_at":"2015-02-21T12:15:57.707Z","last_posted_at":"2015-03-02T23:24:18.899Z","bumped":true,"bumped_at":"2015-03-02T23:24:18.899Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":21,"views":185,"category_id":2},{"id":26576,"title":"Badge timestamp should be the time the badge was granted?","fancy_title":"Badge timestamp should be the time the badge was granted?","slug":"badge-timestamp-should-be-the-time-the-badge-was-granted","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2015-03-20T13:22:08.266Z","last_posted_at":"2015-03-21T00:33:52.243Z","bumped":true,"bumped_at":"2015-03-21T00:33:52.243Z","unseen":false,"last_read_post_number":1,"unread":0,"new_posts":0,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"notification_level":1,"bookmarked":false,"liked":false,"archetype":"regular","like_count":9,"views":87,"category_id":2}],"links":[{"url":"https://github.com/discourse/discourse/blob/master/config/locales/en.yml","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":118,"user_id":9,"domain":"github.com"},{"url":"http://www.localeapp.com/","title":"Easy localization for Rails apps | Locale","fancy_title":null,"internal":false,"reflection":false,"clicks":69,"user_id":1860,"domain":"www.localeapp.com"},{"url":"http://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders","title":"internationalization - Why do people use plain english as translation placeholders? - Stack Overflow","fancy_title":null,"internal":false,"reflection":false,"clicks":63,"user_id":7,"domain":"stackoverflow.com"},{"url":"https://github.com/discourse/discourse/blob/master/config/locales","title":"discourse/config/locales at master · discourse/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":62,"user_id":32,"domain":"github.com"},{"url":"https://github.com/SlexAxton/messageformat.js","title":"SlexAxton/messageformat.js · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":46,"user_id":1,"domain":"github.com"},{"url":"http://www.localeapp.com/projects/1537/translations?utf8=%E2%9C%93&search=source_code","title":"langforums | Locale","fancy_title":null,"internal":false,"reflection":false,"clicks":25,"user_id":1860,"domain":"www.localeapp.com"},{"url":"https://translations.launchpad.net/","title":"Launchpad Translations","fancy_title":null,"internal":false,"reflection":false,"clicks":23,"user_id":761,"domain":"translations.launchpad.net"},{"url":"https://www.transifex.com/","title":"Transifex - Continuous Localization Platform","fancy_title":null,"internal":false,"reflection":false,"clicks":22,"user_id":1979,"domain":"www.transifex.com"},{"url":"https://github.com/berk/tr8n","title":"berk/tr8n · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":22,"user_id":1,"domain":"github.com"},{"url":"http://translate.wordpress.org/projects/bbpress/dev","title":"WordPress › Development < GlotPress","fancy_title":null,"internal":false,"reflection":false,"clicks":16,"user_id":7,"domain":"translate.wordpress.org"},{"url":"http://weblate.org","title":"Weblate - web-based translation","fancy_title":null,"internal":false,"reflection":false,"clicks":15,"user_id":2316,"domain":"weblate.org"},{"url":"https://github.com/discourse/discourse/tree/master/config/locales","title":"discourse/config/locales at master · discourse/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":14,"user_id":19,"domain":"github.com"},{"url":"https://github.com/discourse/discourse/pull/493","title":"Danish translation. by mikl · Pull Request #493 · discourse/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":12,"user_id":2753,"domain":"github.com"},{"url":"https://github.com/SlexAxton","title":"SlexAxton (Alex Sexton) · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":10,"user_id":1,"domain":"github.com"},{"url":"https://github.com/gururea/discourse/tree/master/config/locales","title":"discourse/config/locales at master · gururea/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":9,"user_id":3190,"domain":"github.com"},{"url":"https://github.com/discourse/discourse/blob/master/config/locales/client.en.yml#L691","title":"discourse/config/locales/client.en.yml at master · discourse/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":7,"user_id":461,"domain":"github.com"},{"url":"https://github.com/dacap/discourse/tree/spanish","title":"dacap/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":7,"user_id":1275,"domain":"github.com"},{"url":"https://github.com/discourse/discourse/blob/master/config/locales/client.nl.yml","title":"discourse/config/locales/client.nl.yml at master · discourse/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":7,"user_id":461,"domain":"github.com"},{"url":"https://github.com/discourse/discourse/commit/c5761eae8afe37e20cec0d0f9d14b85b6e585bda","title":"Support for Simplified Chinese thanks to tangramor · c5761ea · discourse/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":6,"user_id":212,"domain":"github.com"},{"url":"http://tr8n.github.com/","title":"tr8n","fancy_title":null,"internal":false,"reflection":false,"clicks":6,"user_id":212,"domain":"tr8n.github.com"},{"url":"http://www.getlocalization.com/","title":"Crowdsourced, Social and Collaborative App & Website Translation - Get Localization","fancy_title":null,"internal":false,"reflection":false,"clicks":6,"user_id":22,"domain":"www.getlocalization.com"},{"url":"http://blog.discourse.org/2013/04/discourse-as-your-first-rails-app/","title":"Discourse as Your First Rails App","fancy_title":null,"internal":false,"reflection":false,"clicks":5,"user_id":1995,"domain":"blog.discourse.org"},{"url":"https://github.com/alxndr/discourse/blob/i18n-chinese/config/locales/server.zh.yml","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":5,"user_id":212,"domain":"github.com"},{"url":"http://translate.sourceforge.net/wiki/virtaal/index","title":"Easy-to-use and powerful offline translation tool | Virtaal","fancy_title":null,"internal":false,"reflection":false,"clicks":4,"user_id":1979,"domain":"translate.sourceforge.net"},{"url":"https://poeditor.com/","title":"POEditor - online software localization tool","fancy_title":null,"internal":false,"reflection":false,"clicks":4,"user_id":1979,"domain":"poeditor.com"},{"url":"http://en.lichess.org/@/Hellball","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":3,"user_id":1979,"domain":"en.lichess.org"},{"url":"http://en.wikipedia.org/wiki/T%E2%80%93V_distinction","title":"T–V distinction - Wikipedia, the free encyclopedia","fancy_title":null,"internal":false,"reflection":false,"clicks":3,"user_id":3620,"domain":"en.wikipedia.org"},{"url":"http://www.slideshare.net/HeatherRivers/linguistic-potluck-crowdsourcing-localization-with-rails","title":"Linguistic Potluck: Crowdsourcing localization with Rails","fancy_title":null,"internal":false,"reflection":false,"clicks":3,"user_id":212,"domain":"www.slideshare.net"},{"url":"https://meta.discourse.org/t/language-mirrors/2378/2","title":"Language mirrors","fancy_title":null,"internal":true,"reflection":true,"clicks":3,"user_id":32,"domain":"meta.discourse.org"},{"url":"http://www.madanalogy.com/2012/06/rails-i18n-translations-in-yaml.html","title":"Mad Analogy: Rails i18n translations in Yaml: translation tool support","fancy_title":null,"internal":false,"reflection":false,"clicks":3,"user_id":3190,"domain":"www.madanalogy.com"},{"url":"https://github.com/tr8n","title":"Translation Exchange · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":3,"user_id":9006,"domain":"github.com"},{"url":"http://pootle.locamotion.org/","title":"Main | Pootle Demo","fancy_title":null,"internal":false,"reflection":false,"clicks":2,"user_id":3190,"domain":"pootle.locamotion.org"},{"url":"http://www.youtube.com/watch?v=MqqdzJ98q7s","title":"GoGaRuCo 2012 - Linguistic Potluck: Crowdsourcing Localization in Rails by Heather Rivers - YouTube","fancy_title":null,"internal":false,"reflection":false,"clicks":2,"user_id":212,"domain":"www.youtube.com"},{"url":"https://meta.discourse.org/t/translation-workflow/6102","title":"Translation workflow","fancy_title":null,"internal":true,"reflection":true,"clicks":2,"user_id":4702,"domain":"meta.discourse.org"},{"url":"https://meta.discourse.org/t/solving-xda-developer-style-forums/4368/4","title":"Solving XDA-Developer style forums","fancy_title":null,"internal":true,"reflection":true,"clicks":2,"user_id":639,"domain":"meta.discourse.org"},{"url":"https://tr8nhub.com","title":"TranslationExchange","fancy_title":null,"internal":false,"reflection":false,"clicks":2,"user_id":9006,"domain":"tr8nhub.com"},{"url":"https://meta.discourse.org/t/roadplan-for-discourse/2939/3","title":"Roadplan for Discourse 2013","fancy_title":null,"internal":true,"reflection":true,"clicks":1,"user_id":2540,"domain":"meta.discourse.org"},{"url":"http://sugarjs.com/dates#date_locales","title":"Dates - Sugar","fancy_title":null,"internal":false,"reflection":false,"clicks":1,"user_id":461,"domain":"sugarjs.com"},{"url":"http://blog.discourse.org/2013/03/localizing-discourse/","title":"Localizing Discourse","fancy_title":null,"internal":false,"reflection":false,"clicks":1,"user_id":893,"domain":"blog.discourse.org"},{"url":"https://github.com/discourse/discourse/blob/master/app/assets/javascripts/locales/date_locales.js","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":1,"user_id":461,"domain":"github.com"},{"url":"http://transifex.com/projects/p/discourse-pt-br/","title":"Discourse-Translations-Project localization","fancy_title":null,"internal":false,"reflection":false,"clicks":1,"user_id":893,"domain":"transifex.com"},{"url":"https://github.com/discourse/discourse/issues/279","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":1,"user_id":893,"domain":"github.com"},{"url":"https://meta.discourse.org/t/comrades-lets-join-our-efforts-on-ukrainian-and-russian-translations/4403/5","title":"Comrades let's join our efforts on ukrainian and russian translations","fancy_title":null,"internal":true,"reflection":true,"clicks":1,"user_id":3417,"domain":"meta.discourse.org"},{"url":"https://meta.discourse.org/t/translation-workflow/6102/6","title":"Translation workflow","fancy_title":null,"internal":true,"reflection":false,"clicks":0,"user_id":1995,"domain":"meta.discourse.org"},{"url":"https://meta.discourse.org/t/bookmark-last-read-sometimes-doesn-t-go-to-the-end-of-a-topic/4825/9","title":"Bookmark/last read sometimes doesn't go to the end of a topic","fancy_title":null,"internal":true,"reflection":true,"clicks":0,"user_id":3681,"domain":"meta.discourse.org"},{"url":"https://github.com/discourse/discourse/blob/master/config/locales/client.de.yml","title":"discourse/config/locales/client.de.yml at master · discourse/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":0,"user_id":2,"domain":"github.com"},{"url":"https://meta.discourse.org/t/what-i-love-about-wordpress-plugins/5697","title":"What I love about WordPress plugins","fancy_title":null,"internal":true,"reflection":true,"clicks":0,"user_id":1,"domain":"meta.discourse.org"},{"url":"https://meta.discourse.org/t/github-onebox-rendering-issue/7616","title":"GitHub OneBox Rendering Issue","fancy_title":null,"internal":true,"reflection":true,"clicks":0,"user_id":5372,"domain":"meta.discourse.org"},{"url":"https://github.com/discourse/discourse/blob/master/config/locales/server.de.yml","title":"discourse/config/locales/server.de.yml at master · discourse/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":0,"user_id":2,"domain":"github.com"},{"url":"https://meta.discourse.org/t/roadplan-for-discourse/2939/5","title":"Roadplan for Discourse 2013","fancy_title":null,"internal":true,"reflection":true,"clicks":0,"user_id":32,"domain":"meta.discourse.org"},{"url":"https://meta.discourse.org/t/translation-tools-transifex-localeapp/7763","title":"Translation Tools: Transifex? Localeapp?","fancy_title":null,"internal":true,"reflection":true,"clicks":0,"user_id":2,"domain":"meta.discourse.org"},{"url":"http://guides.rubyonrails.org/i18n.html#the-public-i18n-api","title":"Rails Internationalization (I18n) API — Ruby on Rails Guides","fancy_title":null,"internal":false,"reflection":false,"clicks":0,"user_id":1895,"domain":"guides.rubyonrails.org"},{"url":"https://meta.discourse.org/t/hi-support-chinese/4393/6","title":"Hi, support Chinese?","fancy_title":null,"internal":true,"reflection":true,"clicks":0,"user_id":2014,"domain":"meta.discourse.org"},{"url":"https://meta.discourse.org/t/translation-tools-transifex-localeapp/7763/41","title":"Translation Tools: Transifex? Localeapp?","fancy_title":null,"internal":true,"reflection":false,"clicks":0,"user_id":6626,"domain":"meta.discourse.org"}],"notification_level":2,"notifications_reason_id":4,"can_move_posts":true,"can_edit":true,"can_delete":true,"can_recover":true,"can_remove_allowed_users":true,"can_invite_to":true,"can_create_post":true,"can_reply_as_new_topic":true,"can_flag_topic":true},"highest_post_number":10,"last_read_post_number":10,"deleted_by":null,"has_deleted":true,"actions_summary":[{"id":4,"count":0,"hidden":false,"can_act":true},{"id":7,"count":0,"hidden":false,"can_act":true},{"id":8,"count":0,"hidden":false,"can_act":true}],"chunk_size":20,"bookmarked":false,"tags":null}, -"/t/28830/1.json": {"post_stream":{"posts":[{"id":118591,"name":"spends too much time on WTDWTF","username":"RaceProUK","avatar_template":"/user_avatar/meta.discourse.org/raceprouk/{size}/40071.png","uploaded_avatar_id":40071,"created_at":"2015-05-14T20:18:17.954Z","cooked":"

Normally, actions such as Liking are rate-limited, and when you hit the limit, you get a message telling you you've hit the limit. However, in 1.3.0beta9, it seems those popups are no longer appearing.

\n\n

Edit: Possibly linked to this issue?

","post_number":1,"post_type":1,"updated_at":"2015-05-14T20:21:42.825Z","like_count":6,"reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":null,"incoming_link_count":14,"reads":24,"score":224.6,"yours":false,"topic_id":28830,"topic_slug":"1-3-0beta9-no-rate-limit-popups","display_username":"spends too much time on WTDWTF","primary_group_name":null,"version":1,"can_edit":false,"can_delete":false,"can_recover":false,"link_counts":[{"url":"https://meta.discourse.org/t/post-reply-on-different-topic-no-longer-works/28825","internal":true,"reflection":false,"title":"Post reply on different topic no longer works","clicks":6}],"read":true,"user_title":"Contributor","actions_summary":[{"id":2,"count":6,"hidden":false,"can_act":false},{"id":3,"count":0,"hidden":false,"can_act":false},{"id":4,"count":0,"hidden":false,"can_act":false},{"id":5,"count":0,"hidden":true,"can_act":false},{"id":6,"count":0,"hidden":false,"can_act":false},{"id":7,"count":0,"hidden":false,"can_act":false},{"id":8,"count":0,"hidden":false,"can_act":false}],"moderator":false,"admin":false,"staff":false,"user_id":14169,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":"","can_view_edit_history":true,"wiki":false},{"id":118597,"name":"Sam","username":"Yuun","avatar_template":"/letter_avatar/yuun/{size}/3_90a587a04512ff220ac26ec1465844c5.png","uploaded_avatar_id":null,"created_at":"2015-05-14T20:35:03.793Z","cooked":"

I'm seeing this issue as well. When you hit the rate limit, any further likes look like the forum is attempting and failing to apply them - the text saying 'you liked this' comes into place before quickly being removed.

\n\n

This makes it look (to the user) like the forum software is running into errors instead of said user hitting an intentional limit, which is a bit unfortunate.

","post_number":2,"post_type":1,"updated_at":"2015-05-14T20:35:03.793Z","like_count":0,"reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":null,"incoming_link_count":6,"reads":22,"score":34.2,"yours":false,"topic_id":28830,"topic_slug":"1-3-0beta9-no-rate-limit-popups","display_username":"Sam","primary_group_name":null,"version":1,"can_edit":false,"can_delete":false,"can_recover":false,"read":true,"user_title":null,"actions_summary":[{"id":2,"count":0,"hidden":false,"can_act":false},{"id":3,"count":0,"hidden":false,"can_act":false},{"id":4,"count":0,"hidden":false,"can_act":false},{"id":5,"count":0,"hidden":true,"can_act":false},{"id":6,"count":0,"hidden":false,"can_act":false},{"id":7,"count":0,"hidden":false,"can_act":false},{"id":8,"count":0,"hidden":false,"can_act":false}],"moderator":false,"admin":false,"staff":false,"user_id":14795,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":118601,"name":"Kane York","username":"riking","avatar_template":"/user_avatar/meta.discourse.org/riking/{size}/40212.png","uploaded_avatar_id":40212,"created_at":"2015-05-14T21:05:19.837Z","cooked":"

I'm going to guess that the bootbox library got broken somehow?

","post_number":3,"post_type":1,"updated_at":"2015-05-14T21:05:19.837Z","like_count":0,"reply_count":1,"reply_to_post_number":null,"quote_count":0,"avg_time":null,"incoming_link_count":0,"reads":14,"score":7.2,"yours":false,"topic_id":28830,"topic_slug":"1-3-0beta9-no-rate-limit-popups","display_username":"Kane York","primary_group_name":null,"version":1,"can_edit":false,"can_delete":false,"can_recover":false,"read":true,"user_title":"team summer intern 2014","actions_summary":[{"id":2,"count":0,"hidden":false,"can_act":false},{"id":3,"count":0,"hidden":false,"can_act":false},{"id":4,"count":0,"hidden":false,"can_act":false},{"id":5,"count":0,"hidden":true,"can_act":false},{"id":6,"count":0,"hidden":false,"can_act":false},{"id":7,"count":0,"hidden":false,"can_act":false},{"id":8,"count":0,"hidden":false,"can_act":false}],"moderator":false,"admin":false,"staff":false,"user_id":6626,"hidden":false,"hidden_reason_id":null,"trust_level":4,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":118606,"name":"Jeff Atwood","username":"codinghorror","avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png","uploaded_avatar_id":5297,"created_at":"2015-05-14T21:15:41.612Z","cooked":"

Yeah maybe another Ember 1.10 regression for @eviltrout ?

","post_number":4,"post_type":1,"updated_at":"2015-05-14T21:15:41.612Z","like_count":0,"reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":null,"incoming_link_count":6,"reads":12,"score":31.6,"yours":false,"topic_id":28830,"topic_slug":"1-3-0beta9-no-rate-limit-popups","display_username":"Jeff Atwood","primary_group_name":"discourse","version":1,"can_edit":false,"can_delete":false,"can_recover":false,"read":true,"user_title":"co-founder","actions_summary":[{"id":2,"count":0,"hidden":false,"can_act":false},{"id":3,"count":0,"hidden":false,"can_act":false},{"id":4,"count":0,"hidden":false,"can_act":false},{"id":5,"count":0,"hidden":true,"can_act":false},{"id":6,"count":0,"hidden":false,"can_act":false},{"id":7,"count":0,"hidden":false,"can_act":false},{"id":8,"count":0,"hidden":false,"can_act":false}],"moderator":true,"admin":true,"staff":true,"user_id":32,"hidden":false,"hidden_reason_id":null,"trust_level":4,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":118612,"name":"TDWTF member","username":"Onyx","avatar_template":"/user_avatar/meta.discourse.org/onyx/{size}/33015.png","uploaded_avatar_id":33015,"created_at":"2015-05-14T21:23:09.562Z","cooked":"\n\n

You mean the popup box library, guessing by the name? Still shows up when you want to cancel a post, so it's not all popups it seems.

","post_number":5,"post_type":1,"updated_at":"2015-05-14T21:23:09.562Z","like_count":1,"reply_count":0,"reply_to_post_number":3,"quote_count":1,"avg_time":null,"incoming_link_count":0,"reads":11,"score":16.0,"yours":false,"topic_id":28830,"topic_slug":"1-3-0beta9-no-rate-limit-popups","display_username":"TDWTF member","primary_group_name":null,"version":1,"can_edit":false,"can_delete":false,"can_recover":false,"read":true,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":false},{"id":3,"count":0,"hidden":false,"can_act":false},{"id":4,"count":0,"hidden":false,"can_act":false},{"id":5,"count":0,"hidden":true,"can_act":false},{"id":6,"count":0,"hidden":false,"can_act":false},{"id":7,"count":0,"hidden":false,"can_act":false},{"id":8,"count":0,"hidden":false,"can_act":false}],"moderator":false,"admin":false,"staff":false,"user_id":10886,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false}],"stream":[118591,118597,118601,118606,118612]},"id":28830,"title":"1.3.0beta9: No rate-limit popups","fancy_title":"1.3.0beta9: No rate-limit popups","posts_count":5,"created_at":"2015-05-14T20:18:17.877Z","views":38,"reply_count":1,"participant_count":5,"like_count":7,"last_posted_at":"2015-05-14T21:23:09.562Z","visible":true,"closed":false,"archived":false,"has_summary":false,"archetype":"regular","slug":"1-3-0beta9-no-rate-limit-popups","category_id":1,"word_count":198,"deleted_at":null,"pending_posts_count":0,"draft":null,"draft_key":"topic_28830","draft_sequence":null,"unpinned":null,"pinned_globally":false,"pinned":false,"pinned_at":null,"details":{"auto_close_at":null,"auto_close_hours":null,"auto_close_based_on_last_post":false,"created_by":{"id":14169,"username":"RaceProUK","uploaded_avatar_id":40071,"avatar_template":"/user_avatar/meta.discourse.org/raceprouk/{size}/40071.png"},"last_poster":{"id":10886,"username":"Onyx","uploaded_avatar_id":33015,"avatar_template":"/user_avatar/meta.discourse.org/onyx/{size}/33015.png"},"participants":[{"id":14795,"username":"Yuun","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/yuun/{size}/3_90a587a04512ff220ac26ec1465844c5.png","post_count":1},{"id":10886,"username":"Onyx","uploaded_avatar_id":33015,"avatar_template":"/user_avatar/meta.discourse.org/onyx/{size}/33015.png","post_count":1},{"id":14169,"username":"RaceProUK","uploaded_avatar_id":40071,"avatar_template":"/user_avatar/meta.discourse.org/raceprouk/{size}/40071.png","post_count":1},{"id":6626,"username":"riking","uploaded_avatar_id":40212,"avatar_template":"/user_avatar/meta.discourse.org/riking/{size}/40212.png","post_count":1},{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png","post_count":1}],"suggested_topics":[{"id":2890,"title":"Expanded quoted text not highlighting when text is formatted","fancy_title":"Expanded quoted text not highlighting when text is formatted","slug":"expanded-quoted-text-not-highlighting-when-text-is-formatted","posts_count":8,"reply_count":5,"highest_post_number":8,"image_url":null,"created_at":"2013-02-12T12:18:02.181Z","last_posted_at":"2013-02-14T15:59:40.014Z","bumped":true,"bumped_at":"2013-02-14T15:59:40.014Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":3,"views":361,"category_id":1},{"id":14213,"title":"Plugins not being parsed in correct javascript context when loaded for jobs","fancy_title":"Plugins not being parsed in correct javascript context when loaded for jobs","slug":"plugins-not-being-parsed-in-correct-javascript-context-when-loaded-for-jobs","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":"/plugins/emoji/images/frowning.png","created_at":"2014-03-27T23:57:00.974Z","last_posted_at":"2015-03-20T04:56:03.982Z","bumped":true,"bumped_at":"2015-03-20T04:56:03.982Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":0,"views":156,"category_id":1},{"id":22544,"title":"Like count on profile off by one","fancy_title":"Like count on profile off by one","slug":"like-count-on-profile-off-by-one","posts_count":7,"reply_count":2,"highest_post_number":7,"image_url":null,"created_at":"2014-11-26T08:15:39.802Z","last_posted_at":"2014-11-27T07:23:37.638Z","bumped":true,"bumped_at":"2014-11-27T07:23:37.638Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":18,"views":192,"category_id":1},{"id":27670,"title":"Using back still shows unread indicator on the topic","fancy_title":"Using back still shows unread indicator on the topic","slug":"using-back-still-shows-unread-indicator-on-the-topic","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2015-04-16T23:21:42.739Z","last_posted_at":"2015-04-17T02:43:08.447Z","bumped":true,"bumped_at":"2015-04-17T02:43:08.447Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":1,"views":85,"category_id":1},{"id":26628,"title":"Embed blacklist selector is broken","fancy_title":"Embed blacklist selector is broken","slug":"embed-blacklist-selector-is-broken","posts_count":11,"reply_count":7,"highest_post_number":11,"image_url":null,"created_at":"2015-03-22T11:21:14.825Z","last_posted_at":"2015-04-20T09:11:38.999Z","bumped":true,"bumped_at":"2015-04-20T09:11:38.999Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":1,"views":247,"category_id":1},{"id":18027,"title":"Minor: delete/undelete needs a rate limit","fancy_title":"Minor: delete/undelete needs a rate limit","slug":"minor-delete-undelete-needs-a-rate-limit","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":null,"created_at":"2014-07-25T02:51:41.158Z","last_posted_at":"2014-07-25T04:01:15.343Z","bumped":true,"bumped_at":"2014-07-25T11:06:46.213Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":1,"views":165,"category_id":1},{"id":17396,"title":"Bad Reply Key when pulling Autoforwarded Emails to Discourse","fancy_title":"Bad Reply Key when pulling Autoforwarded Emails to Discourse","slug":"bad-reply-key-when-pulling-autoforwarded-emails-to-discourse","posts_count":20,"reply_count":15,"highest_post_number":20,"image_url":null,"created_at":"2014-07-09T18:34:57.114Z","last_posted_at":"2014-10-21T15:08:50.441Z","bumped":true,"bumped_at":"2014-10-21T15:08:50.441Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":7,"views":542,"category_id":1}],"links":[{"url":"https://meta.discourse.org/t/post-reply-on-different-topic-no-longer-works/28825","title":"Post reply on different topic no longer works","fancy_title":null,"internal":true,"reflection":false,"clicks":6,"user_id":14169,"domain":"meta.discourse.org"}],"notification_level":1,"can_flag_topic":false},"highest_post_number":5,"deleted_by":null,"actions_summary":[{"id":4,"count":0,"hidden":false,"can_act":false},{"id":7,"count":0,"hidden":false,"can_act":false},{"id":8,"count":0,"hidden":false,"can_act":false}],"chunk_size":20,"bookmarked":null,"tags":null}, +export default {"/t/280/1.json": {"post_stream":{"posts":[{"id":398,"name":"Uwe Keim","username":"uwe_keim","avatar_template":"/user_avatar/meta.discourse.org/uwe_keim/{size}/5697.png","uploaded_avatar_id":5697,"created_at":"2013-02-05T21:29:00.280Z","cooked":"

Any plans to support localization of UI elements, so that I (for example) could set up a completely German speaking forum?

","post_number":1,"post_type":1,"updated_at":"2013-02-05T21:29:00.280Z","like_count":0,"reply_count":1,"reply_to_post_number":null,"quote_count":0,"avg_time":25,"incoming_link_count":314,"reads":475,"score":1702.25,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Uwe Keim","primary_group_name":null,"version":1,"can_edit":true,"can_delete":false,"can_recover":true,"link_counts":[{"url":"https://meta.discourse.org/t/language-mirrors/2378/2","internal":true,"reflection":true,"title":"Language mirrors","clicks":3},{"url":"https://meta.discourse.org/t/translation-workflow/6102","internal":true,"reflection":true,"title":"Translation workflow","clicks":2},{"url":"https://meta.discourse.org/t/solving-xda-developer-style-forums/4368/4","internal":true,"reflection":true,"title":"Solving XDA-Developer style forums","clicks":2},{"url":"https://meta.discourse.org/t/comrades-lets-join-our-efforts-on-ukrainian-and-russian-translations/4403/5","internal":true,"reflection":true,"title":"Comrades let's join our efforts on ukrainian and russian translations","clicks":1},{"url":"https://meta.discourse.org/t/bookmark-last-read-sometimes-doesn-t-go-to-the-end-of-a-topic/4825/9","internal":true,"reflection":true,"title":"Bookmark/last read sometimes doesn't go to the end of a topic","clicks":0},{"url":"https://meta.discourse.org/t/roadplan-for-discourse/2939/5","internal":true,"reflection":true,"title":"Roadplan for Discourse 2013","clicks":0}],"read":true,"user_title":null,"actions_summary":[{"id":2,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":255,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":419,"name":"Tim Stone","username":"tms","avatar_template":"/user_avatar/meta.discourse.org/tms/{size}/40181.png","uploaded_avatar_id":40181,"created_at":"2013-02-05T21:32:47.649Z","cooked":"

The application strings are externalized, so localization should be entirely possible with enough translation effort.

","post_number":2,"post_type":1,"updated_at":"2013-02-06T10:15:27.965Z","like_count":4,"reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":27,"incoming_link_count":16,"reads":460,"score":308.35,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Tim Stone","primary_group_name":null,"version":2,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"https://github.com/discourse/discourse/blob/master/config/locales/en.yml","internal":false,"reflection":false,"clicks":118}],"read":true,"user_title":"Great contributor","actions_summary":[{"id":2,"count":4,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":9,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":1060,"name":"Jeff Atwood","username":"codinghorror","avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png","uploaded_avatar_id":5297,"created_at":"2013-02-06T02:26:24.922Z","cooked":"

Yep, all strings are going through a lookup table.*

\n\n

master/config/locales

\n\n

So you could replace that lookup table with the \"de\" one to get German.

\n\n

* we didn't get all the strings into the lookup table for launch, but the vast, vast majority of them are and the ones that are not, we will fix as we go!

","post_number":3,"post_type":1,"updated_at":"2014-02-24T05:23:39.211Z","like_count":4,"reply_count":3,"reply_to_post_number":null,"quote_count":0,"avg_time":33,"incoming_link_count":5,"reads":449,"score":191.45,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Jeff Atwood","primary_group_name":"discourse","version":4,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"https://github.com/discourse/discourse/blob/master/config/locales","internal":false,"reflection":false,"title":"discourse/config/locales at master · discourse/discourse · GitHub","clicks":62},{"url":"https://meta.discourse.org/t/github-onebox-rendering-issue/7616","internal":true,"reflection":true,"title":"GitHub OneBox Rendering Issue","clicks":0}],"read":true,"user_title":"co-founder","actions_summary":[{"id":2,"count":4,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":true,"admin":true,"staff":true,"user_id":32,"hidden":false,"hidden_reason_id":null,"trust_level":3,"deleted_at":null,"user_deleted":false,"edit_reason":"","can_view_edit_history":true,"wiki":false},{"id":3623,"name":"Shade","username":"shade","avatar_template":"/user_avatar/meta.discourse.org/shade/{size}/8306.png","uploaded_avatar_id":8306,"created_at":"2013-02-07T12:55:33.129Z","cooked":"

Is it a coincidence that the strings file is 1337 lines long? \"smiley\"

","post_number":4,"post_type":1,"updated_at":"2013-02-07T12:55:33.129Z","like_count":7,"reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":20,"incoming_link_count":15,"reads":401,"score":291.2,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Shade","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"https://meta.discourse.org/t/hi-support-chinese/4393/6","internal":true,"reflection":true,"title":"Hi, support Chinese?","clicks":0}],"read":true,"user_title":null,"actions_summary":[{"id":2,"count":7,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":1808,"hidden":false,"hidden_reason_id":null,"trust_level":1,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3651,"name":"Pekka Gaiser","username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253,"created_at":"2013-02-07T14:02:07.869Z","cooked":"

\n\n

The problem I see here is that this file is likely two grow and change massively over the next couple months, and tracking these changes in order to keep a localized file up to date is going to be a bitch.

\n\n

I wonder where there is a tool that can compare two yml structures and point out which nodes are missing? That would help keep track of new strings.

\n\n

Re keeping track of changed strings, @codinghorror I found this very interesting: http://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders if plain English placeholders were used, any change in strings would lead to a new node in the yml file, making keeping the translation up to date easier. Maybe worth thinking about in the future.

","post_number":5,"post_type":1,"updated_at":"2013-02-07T14:05:42.328Z","like_count":2,"reply_count":2,"reply_to_post_number":3,"quote_count":1,"avg_time":22,"incoming_link_count":10,"reads":386,"score":213.3,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Pekka Gaiser","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"http://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders","internal":false,"reflection":false,"title":"internationalization - Why do people use plain english as translation placeholders? - Stack Overflow","clicks":63}],"read":true,"user_title":null,"actions_summary":[{"id":2,"count":2,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":7,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3654,"name":"Sam Saffron","username":"sam","avatar_template":"/user_avatar/meta.discourse.org/sam/{size}/5243.png","uploaded_avatar_id":5243,"created_at":"2013-02-07T14:05:39.910Z","cooked":"

Yes, I really like the concept of fuzzy matching for localization, perhaps you can chase up alex sexton he was meaning to upload a localization tool for this kind of stuff.

\n\n

Also, I am a big fan of ICU message format, but it is not the \"Rails way (tm)\".

","post_number":6,"post_type":1,"updated_at":"2013-02-07T14:05:39.910Z","like_count":1,"reply_count":1,"reply_to_post_number":5,"quote_count":0,"avg_time":17,"incoming_link_count":4,"reads":329,"score":106.65,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Sam Saffron","primary_group_name":"discourse","version":1,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"https://github.com/SlexAxton/messageformat.js","internal":false,"reflection":false,"title":"SlexAxton/messageformat.js · GitHub","clicks":46},{"url":"https://github.com/SlexAxton","internal":false,"reflection":false,"title":"SlexAxton (Alex Sexton) · GitHub","clicks":10}],"read":true,"user_title":"co-founder","reply_to_user":{"username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":true,"admin":true,"staff":true,"user_id":1,"hidden":false,"hidden_reason_id":null,"trust_level":4,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3655,"name":"Pekka Gaiser","username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253,"created_at":"2013-02-07T14:08:17.493Z","cooked":"

Looks interesting, I'll take a peek.

\n\n

As said on dev, the best tool I can see in terms of giving translators a proper interface and quality control would be something like GlotPress. It's based on the PO messages format (is that somehow related to ICU?) but looks pretty great.

\n\n

\n\n

I'm not familiar with the term in this context, you mean keeping the English version in the code base (instead of a generic code like message_error_nametooshort ?)

","post_number":7,"post_type":1,"updated_at":"2013-02-07T14:12:02.965Z","like_count":1,"reply_count":1,"reply_to_post_number":6,"quote_count":1,"avg_time":16,"incoming_link_count":0,"reads":326,"score":86.0,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Pekka Gaiser","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"http://translate.wordpress.org/projects/bbpress/dev","internal":false,"reflection":false,"title":"WordPress › Development < GlotPress","clicks":16}],"read":true,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":7,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3658,"name":"Sam Saffron","username":"sam","avatar_template":"/user_avatar/meta.discourse.org/sam/{size}/5243.png","uploaded_avatar_id":5243,"created_at":"2013-02-07T14:12:22.582Z","cooked":"

ICU Message format is basically Gettext on steroids, Gettext has been around for so many years and actually works pretty well, being super prevalent in Linux.

\n\n

Trouble is you need a fuzzy matcher for translators if you are going to store stuff like mf.compile( 'This is a message.' ) in source, one letter change and all your translators need to validate it.

","post_number":8,"post_type":1,"updated_at":"2013-02-07T14:12:22.582Z","like_count":1,"reply_count":1,"reply_to_post_number":7,"quote_count":0,"avg_time":11,"incoming_link_count":2,"reads":296,"score":89.75,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Sam Saffron","primary_group_name":"discourse","version":1,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"https://meta.discourse.org/t/what-i-love-about-wordpress-plugins/5697","internal":true,"reflection":true,"title":"What I love about WordPress plugins","clicks":0}],"read":true,"user_title":"co-founder","reply_to_user":{"username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":true,"admin":true,"staff":true,"user_id":1,"hidden":false,"hidden_reason_id":null,"trust_level":4,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3660,"name":"Pekka Gaiser","username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253,"created_at":"2013-02-07T14:14:12.666Z","cooked":"

\n\n

Yeah, that's why I've always been a friend of message_error_nametooshort placeholders, until I asked the SO question linked above. The accepted answer makes a good argument against those placeholders: you want translations to break even on small changes in the English original because the translations will probably need to reflect the change, too. Maybe that's not the case right now as new stuff is being checked in pretty much every couple of hours, but in the long run, it'll be overwhelmingly true.

","post_number":9,"post_type":1,"updated_at":"2013-02-07T14:18:09.569Z","like_count":1,"reply_count":1,"reply_to_post_number":8,"quote_count":1,"avg_time":10,"incoming_link_count":0,"reads":293,"score":79.1,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Pekka Gaiser","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":7,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3667,"name":"Tim Stone","username":"tms","avatar_template":"/user_avatar/meta.discourse.org/tms/{size}/40181.png","uploaded_avatar_id":40181,"created_at":"2013-02-07T14:25:16.859Z","cooked":"

Hmm...You could theoretically also build something into the development process that would monitor changes to the English locale file and make a translator-friendly list of changes between versions.

","post_number":10,"post_type":1,"updated_at":"2013-02-07T14:25:16.859Z","like_count":1,"reply_count":1,"reply_to_post_number":9,"quote_count":0,"avg_time":7,"incoming_link_count":0,"reads":275,"score":75.35,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Tim Stone","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":"Great contributor","reply_to_user":{"username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":9,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3673,"name":"Sam Saffron","username":"sam","avatar_template":"/user_avatar/meta.discourse.org/sam/{size}/5243.png","uploaded_avatar_id":5243,"created_at":"2013-02-07T14:30:21.401Z","cooked":"

Yeah, totally, also we could build tools for dev that make introducing string less annoying and make it possible to garbage collect old unused strings, I hate trudging through that file.

","post_number":11,"post_type":1,"updated_at":"2013-02-07T14:30:21.401Z","like_count":1,"reply_count":1,"reply_to_post_number":10,"quote_count":0,"avg_time":7,"incoming_link_count":1,"reads":273,"score":79.95,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Sam Saffron","primary_group_name":"discourse","version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":"co-founder","reply_to_user":{"username":"tms","avatar_template":"/user_avatar/meta.discourse.org/tms/{size}/40181.png","uploaded_avatar_id":40181},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":true,"admin":true,"staff":true,"user_id":1,"hidden":false,"hidden_reason_id":null,"trust_level":4,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3675,"name":"Pekka Gaiser","username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253,"created_at":"2013-02-07T14:33:38.280Z","cooked":"

\n\n

As said, I'd look into whether WP's tools can't be reused for this with some tweaking. They seem to be able to scan a code base for new strings, and make them available automatically to translators.

\n\n

They're PHP based which isn't ideal, but it looks like they've done a crapload of work to take the hassle out of translations.

","post_number":12,"post_type":1,"updated_at":"2013-02-07T14:34:39.910Z","like_count":1,"reply_count":1,"reply_to_post_number":11,"quote_count":1,"avg_time":7,"incoming_link_count":2,"reads":273,"score":84.95,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Pekka Gaiser","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":7,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3690,"name":"Valts","username":"Vilx","avatar_template":"/user_avatar/meta.discourse.org/vilx/{size}/7299.png","uploaded_avatar_id":7299,"created_at":"2013-02-07T15:05:35.867Z","cooked":"

This site looks so nice with all the little tweaks like \"10 minutes ago\" instead of simply time, etc - I wonder if there will also be support for proper pluralization in other languages? That's a pretty hard task though, I don't think I've ever seen a website that has done that. But it would be awesome.

","post_number":13,"post_type":1,"updated_at":"2013-02-07T15:05:35.867Z","like_count":3,"reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":7,"incoming_link_count":11,"reads":290,"score":158.35,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Valts","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":null,"actions_summary":[{"id":2,"count":3,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":1216,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3925,"name":"Eric Kidd","username":"emk","avatar_template":"/user_avatar/meta.discourse.org/emk/{size}/8400.png","uploaded_avatar_id":8400,"created_at":"2013-02-07T19:37:06.194Z","cooked":"

\n\n

I've had pretty decent luck using Localeapp to localize Rails applications:

\n\n

http://www.localeapp.com/

\n\n

The developer workflow took me about an hour to really get used to, and there were a few minor glitches. But the non-technical translators had very few problems. One limitation: It insists on rewriting all those yaml files full of strings.

\n\n

Anyway, it's worth a look, and it's free for open source, if I recall correctly. Certainly easier than doing a whole bunch of toolsmithing from scratch.

","post_number":14,"post_type":1,"updated_at":"2013-02-07T19:37:06.194Z","like_count":3,"reply_count":1,"reply_to_post_number":12,"quote_count":1,"avg_time":9,"incoming_link_count":0,"reads":283,"score":137.05,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Eric Kidd","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"http://www.localeapp.com/","internal":false,"reflection":false,"title":"Easy localization for Rails apps | Locale","clicks":69}],"read":true,"user_title":null,"actions_summary":[{"id":2,"count":3,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":1860,"hidden":false,"hidden_reason_id":null,"trust_level":1,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3938,"name":"Pekka Gaiser","username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253,"created_at":"2013-02-07T19:52:13.748Z","cooked":"

\n\n

Ohhh. Looking sexy. droool

","post_number":15,"post_type":1,"updated_at":"2013-02-07T19:52:13.748Z","like_count":1,"reply_count":1,"reply_to_post_number":14,"quote_count":1,"avg_time":7,"incoming_link_count":0,"reads":260,"score":72.35,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Pekka Gaiser","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":7,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3982,"name":"Eric Kidd","username":"emk","avatar_template":"/user_avatar/meta.discourse.org/emk/{size}/8400.png","uploaded_avatar_id":8400,"created_at":"2013-02-07T20:52:22.454Z","cooked":"

\n\n

Yeah, it's pretty. \"smile\" But there were still some rough edges as of a few months ago.

\n\n

Whether or not those rough edges are a deal-breaker will probably depends on whether or not localization is already a source of acute pain. If you're already hurting, Localeapp is a pretty useful tool, especially when it comes to enlisting non-technical translators.

\n\n

But it does require changing how you work with text, and adding one new tool to the mix. So for projects that just don't want to know about non-English languages, it's not yet seamless the way Unicode is these days.

\n\n

(Sweet forum software, by the way. I was just testing out Egyptian hieroglyphics on the test server, because they're well off the Basic Multilingual Plane, and tend to flush Unicode bugs. Everything worked flawlessly.)

","post_number":16,"post_type":1,"updated_at":"2013-02-07T20:52:22.454Z","like_count":1,"reply_count":1,"reply_to_post_number":15,"quote_count":1,"avg_time":7,"incoming_link_count":0,"reads":254,"score":71.15,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Eric Kidd","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":1860,"hidden":false,"hidden_reason_id":null,"trust_level":1,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3989,"name":"Pekka Gaiser","username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253,"created_at":"2013-02-07T21:04:15.405Z","cooked":"

\n\n

Interesting, thanks for the insight. I don't think localization is seriously on their table right now, there's likely to be many other things on the table before it... but it will become an issue sooner or later.

","post_number":17,"post_type":1,"updated_at":"2013-02-07T21:04:15.405Z","like_count":1,"reply_count":2,"reply_to_post_number":16,"quote_count":1,"avg_time":7,"incoming_link_count":0,"reads":255,"score":76.35,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Pekka Gaiser","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":7,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":3996,"name":"Sam Saffron","username":"sam","avatar_template":"/user_avatar/meta.discourse.org/sam/{size}/5243.png","uploaded_avatar_id":5243,"created_at":"2013-02-07T21:12:06.575Z","cooked":"

I had an idea ... what if in dev mode, you could right-click on a page and get access to all the translations on the page, make your edits and have it refreshed live.

\n\n

I think it would be awesome, very doable technically.

","post_number":18,"post_type":1,"updated_at":"2013-02-07T21:12:06.575Z","like_count":7,"reply_count":2,"reply_to_post_number":17,"quote_count":0,"avg_time":8,"incoming_link_count":0,"reads":264,"score":168.2,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Sam Saffron","primary_group_name":"discourse","version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":"co-founder","reply_to_user":{"username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253},"actions_summary":[{"id":2,"count":7,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":true,"admin":true,"staff":true,"user_id":1,"hidden":false,"hidden_reason_id":null,"trust_level":4,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":4009,"name":"Pekka Gaiser","username":"pekka","avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","uploaded_avatar_id":5253,"created_at":"2013-02-07T21:18:47.422Z","cooked":"

That would be fricking cool. There'd still be some leftovers (like error messages that normally never show up, etc.) but you could corral those up on a specific page.

\n\n

It could have a dropdown giving you all the languages that you have a .yml for in the locale directory, and write the changes into the one selected. I'm sure people would love it.

","post_number":19,"post_type":1,"updated_at":"2013-02-07T21:22:10.692Z","like_count":1,"reply_count":0,"reply_to_post_number":18,"quote_count":0,"avg_time":8,"incoming_link_count":1,"reads":241,"score":68.6,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Pekka Gaiser","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":null,"reply_to_user":{"username":"sam","avatar_template":"/user_avatar/meta.discourse.org/sam/{size}/5243.png","uploaded_avatar_id":5243},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":7,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":4012,"name":"Marco Ceppi","username":"marcoceppi","avatar_template":"/user_avatar/meta.discourse.org/marcoceppi/{size}/6552.png","uploaded_avatar_id":6552,"created_at":"2013-02-07T21:22:46.376Z","cooked":"

If you use gettext format you could leverage Launchpad translations and the community behind it.

","post_number":20,"post_type":1,"updated_at":"2013-02-07T21:22:46.376Z","like_count":1,"reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":9,"incoming_link_count":2,"reads":244,"score":74.25,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Marco Ceppi","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"link_counts":[{"url":"https://translations.launchpad.net/","internal":false,"reflection":false,"title":"Launchpad Translations","clicks":13}],"read":true,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":761,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false}],"stream":[398,419,1060,3623,3651,3654,3655,3658,3660,3667,3673,3675,3690,3925,3938,3982,3989,3996,4009,4012],"gaps":{"before":{"20706":[20125]},"after":{}}},"id":280,"title":"Internationalization / localization","fancy_title":"Internationalization / localization","posts_count":103,"created_at":"2013-02-05T21:29:00.174Z","views":5211,"reply_count":67,"participant_count":40,"like_count":135,"last_posted_at":"2015-03-04T15:07:10.487Z","visible":true,"closed":false,"archived":false,"has_summary":true,"archetype":"regular","slug":"internationalization-localization","category_id":2,"word_count":6198,"deleted_at":null,"draft":null,"draft_key":"topic_280","draft_sequence":4,"posted":true,"unpinned":null,"pinned_globally":false,"pinned":false,"pinned_at":null,"details":{"auto_close_at":null,"auto_close_hours":null,"auto_close_based_on_last_post":false,"created_by":{"id":255,"username":"uwe_keim","uploaded_avatar_id":5697,"avatar_template":"/user_avatar/meta.discourse.org/uwe_keim/{size}/5697.png"},"last_poster":{"id":14091,"username":"Luciano_Fantuzzi","uploaded_avatar_id":39484,"avatar_template":"/user_avatar/meta.discourse.org/luciano_fantuzzi/{size}/39484.png"},"participants":[{"id":212,"username":"alxndr","uploaded_avatar_id":5619,"avatar_template":"/user_avatar/meta.discourse.org/alxndr/{size}/5619.png","post_count":11},{"id":1,"username":"sam","uploaded_avatar_id":5243,"avatar_template":"/user_avatar/meta.discourse.org/sam/{size}/5243.png","post_count":11},{"id":7,"username":"pekka","uploaded_avatar_id":5253,"avatar_template":"/user_avatar/meta.discourse.org/pekka/{size}/5253.png","post_count":8},{"id":461,"username":"kuba","uploaded_avatar_id":6049,"avatar_template":"/user_avatar/meta.discourse.org/kuba/{size}/6049.png","post_count":7},{"id":2995,"username":"tattoo","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/tattoo/{size}/3.png","post_count":6},{"id":2540,"username":"jgourdon","uploaded_avatar_id":9537,"avatar_template":"/user_avatar/meta.discourse.org/jgourdon/{size}/9537.png","post_count":5},{"id":1860,"username":"emk","uploaded_avatar_id":8400,"avatar_template":"/user_avatar/meta.discourse.org/emk/{size}/8400.png","post_count":4},{"id":1275,"username":"dacap","uploaded_avatar_id":7401,"avatar_template":"/user_avatar/meta.discourse.org/dacap/{size}/7401.png","post_count":4},{"id":19,"username":"eviltrout","uploaded_avatar_id":5275,"avatar_template":"/user_avatar/meta.discourse.org/eviltrout/{size}/5275.png","post_count":4},{"id":3704,"username":"mojzis","uploaded_avatar_id":31201,"avatar_template":"/user_avatar/meta.discourse.org/mojzis/{size}/31201.png","post_count":3},{"id":3190,"username":"gururea","uploaded_avatar_id":10663,"avatar_template":"/user_avatar/meta.discourse.org/gururea/{size}/10663.png","post_count":3},{"id":1895,"username":"maciek","uploaded_avatar_id":8463,"avatar_template":"/user_avatar/meta.discourse.org/maciek/{size}/8463.png","post_count":3},{"id":22,"username":"splattne","uploaded_avatar_id":5280,"avatar_template":"/user_avatar/meta.discourse.org/splattne/{size}/5280.png","post_count":2},{"id":1979,"username":"Superuser","uploaded_avatar_id":8604,"avatar_template":"/user_avatar/meta.discourse.org/superuser/{size}/8604.png","post_count":2},{"id":3818,"username":"Tudor","uploaded_avatar_id":11675,"avatar_template":"/user_avatar/meta.discourse.org/tudor/{size}/11675.png","post_count":2},{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png","post_count":2},{"id":3620,"username":"potthast","uploaded_avatar_id":11363,"avatar_template":"/user_avatar/meta.discourse.org/potthast/{size}/11363.png","post_count":2},{"id":9,"username":"tms","uploaded_avatar_id":40181,"avatar_template":"/user_avatar/meta.discourse.org/tms/{size}/40181.png","post_count":2},{"id":14091,"username":"Luciano_Fantuzzi","uploaded_avatar_id":39484,"avatar_template":"/user_avatar/meta.discourse.org/luciano_fantuzzi/{size}/39484.png","post_count":1},{"id":255,"username":"uwe_keim","uploaded_avatar_id":5697,"avatar_template":"/user_avatar/meta.discourse.org/uwe_keim/{size}/5697.png","post_count":1},{"id":9006,"username":"berk","uploaded_avatar_id":19348,"avatar_template":"/user_avatar/meta.discourse.org/berk/{size}/19348.png","post_count":1},{"id":754,"username":"danneu","uploaded_avatar_id":6540,"avatar_template":"/user_avatar/meta.discourse.org/danneu/{size}/6540.png","post_count":1},{"id":761,"username":"marcoceppi","uploaded_avatar_id":6552,"avatar_template":"/user_avatar/meta.discourse.org/marcoceppi/{size}/6552.png","post_count":1},{"id":2753,"username":"mikl","uploaded_avatar_id":9918,"avatar_template":"/user_avatar/meta.discourse.org/mikl/{size}/9918.png","post_count":1}],"suggested_topics":[{"id":27331,"title":"Polls are still very buggy","fancy_title":"Polls are still very buggy","slug":"polls-are-still-very-buggy","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":"/uploads/default/_optimized/cd1/b8c/c162528887_690x401.png","created_at":"2015-04-08T09:51:00.357Z","last_posted_at":"2015-04-08T15:59:16.258Z","bumped":true,"bumped_at":"2015-04-08T16:05:09.842Z","unseen":false,"last_read_post_number":3,"unread":0,"new_posts":1,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"notification_level":2,"bookmarked":false,"liked":false,"archetype":"regular","like_count":11,"views":55,"category_id":1},{"id":27343,"title":"Mobile theme doesn't show last activity time for topics on category page","fancy_title":"Mobile theme doesn’t show last activity time for topics on category page","slug":"mobile-theme-doesnt-show-last-activity-time-for-topics-on-category-page","posts_count":4,"reply_count":2,"highest_post_number":4,"image_url":"/uploads/default/_optimized/13e/25c/bd30b466be_281x500.png","created_at":"2015-04-08T14:20:51.177Z","last_posted_at":"2015-04-08T15:40:30.037Z","bumped":true,"bumped_at":"2015-04-08T15:40:30.037Z","unseen":false,"last_read_post_number":2,"unread":0,"new_posts":2,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"notification_level":2,"bookmarked":false,"liked":false,"archetype":"regular","like_count":3,"views":23,"category_id":9},{"id":27346,"title":"Reply+{messagekey}@... optionaly in header \"from\" in addition to \"reply-to\"","fancy_title":"Reply+{messagekey}@… optionaly in header “from” in addition to “reply-to”","slug":"reply-messagekey-optionaly-in-header-from-in-addition-to-reply-to","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2015-04-08T16:05:13.103Z","last_posted_at":"2015-04-08T16:05:13.415Z","bumped":true,"bumped_at":"2015-04-08T16:05:13.415Z","unseen":true,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":0,"views":8,"category_id":2},{"id":19670,"title":"Parsing (Oneboxing) IMDB links","fancy_title":"Parsing (Oneboxing) IMDB links","slug":"parsing-oneboxing-imdb-links","posts_count":8,"reply_count":1,"highest_post_number":8,"image_url":null,"created_at":"2014-09-05T07:19:26.161Z","last_posted_at":"2015-04-07T09:21:21.570Z","bumped":true,"bumped_at":"2015-04-07T09:21:21.570Z","unseen":false,"last_read_post_number":8,"unread":0,"new_posts":0,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"notification_level":2,"bookmarked":false,"liked":false,"archetype":"regular","like_count":4,"views":253,"category_id":2},{"id":7512,"title":"Support for Piwik Analytics as an alternative to Google Analytics","fancy_title":"Support for Piwik Analytics as an alternative to Google Analytics","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","posts_count":53,"reply_count":41,"highest_post_number":65,"image_url":"/plugins/emoji/images/smile.png","created_at":"2013-06-16T01:32:30.596Z","last_posted_at":"2015-02-22T13:46:26.845Z","bumped":true,"bumped_at":"2015-02-22T13:46:26.845Z","unseen":false,"last_read_post_number":65,"unread":0,"new_posts":0,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"notification_level":2,"bookmarked":false,"liked":false,"archetype":"regular","like_count":62,"views":1877,"category_id":2},{"id":25480,"title":"CSS admin-contents reloaded","fancy_title":"CSS admin-contents reloaded","slug":"css-admin-contents-reloaded","posts_count":22,"reply_count":15,"highest_post_number":22,"image_url":null,"created_at":"2015-02-21T12:15:57.707Z","last_posted_at":"2015-03-02T23:24:18.899Z","bumped":true,"bumped_at":"2015-03-02T23:24:18.899Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":21,"views":185,"category_id":2},{"id":26576,"title":"Badge timestamp should be the time the badge was granted?","fancy_title":"Badge timestamp should be the time the badge was granted?","slug":"badge-timestamp-should-be-the-time-the-badge-was-granted","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2015-03-20T13:22:08.266Z","last_posted_at":"2015-03-21T00:33:52.243Z","bumped":true,"bumped_at":"2015-03-21T00:33:52.243Z","unseen":false,"last_read_post_number":1,"unread":0,"new_posts":0,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"notification_level":1,"bookmarked":false,"liked":false,"archetype":"regular","like_count":9,"views":87,"category_id":2}],"links":[{"url":"https://github.com/discourse/discourse/blob/master/config/locales/en.yml","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":118,"user_id":9,"domain":"github.com"},{"url":"http://www.localeapp.com/","title":"Easy localization for Rails apps | Locale","fancy_title":null,"internal":false,"reflection":false,"clicks":69,"user_id":1860,"domain":"www.localeapp.com"},{"url":"http://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders","title":"internationalization - Why do people use plain english as translation placeholders? - Stack Overflow","fancy_title":null,"internal":false,"reflection":false,"clicks":63,"user_id":7,"domain":"stackoverflow.com"},{"url":"https://github.com/discourse/discourse/blob/master/config/locales","title":"discourse/config/locales at master · discourse/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":62,"user_id":32,"domain":"github.com"},{"url":"https://github.com/SlexAxton/messageformat.js","title":"SlexAxton/messageformat.js · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":46,"user_id":1,"domain":"github.com"},{"url":"http://www.localeapp.com/projects/1537/translations?utf8=%E2%9C%93&search=source_code","title":"langforums | Locale","fancy_title":null,"internal":false,"reflection":false,"clicks":25,"user_id":1860,"domain":"www.localeapp.com"},{"url":"https://translations.launchpad.net/","title":"Launchpad Translations","fancy_title":null,"internal":false,"reflection":false,"clicks":23,"user_id":761,"domain":"translations.launchpad.net"},{"url":"https://www.transifex.com/","title":"Transifex - Continuous Localization Platform","fancy_title":null,"internal":false,"reflection":false,"clicks":22,"user_id":1979,"domain":"www.transifex.com"},{"url":"https://github.com/berk/tr8n","title":"berk/tr8n · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":22,"user_id":1,"domain":"github.com"},{"url":"http://translate.wordpress.org/projects/bbpress/dev","title":"WordPress › Development < GlotPress","fancy_title":null,"internal":false,"reflection":false,"clicks":16,"user_id":7,"domain":"translate.wordpress.org"},{"url":"http://weblate.org","title":"Weblate - web-based translation","fancy_title":null,"internal":false,"reflection":false,"clicks":15,"user_id":2316,"domain":"weblate.org"},{"url":"https://github.com/discourse/discourse/tree/master/config/locales","title":"discourse/config/locales at master · discourse/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":14,"user_id":19,"domain":"github.com"},{"url":"https://github.com/discourse/discourse/pull/493","title":"Danish translation. by mikl · Pull Request #493 · discourse/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":12,"user_id":2753,"domain":"github.com"},{"url":"https://github.com/SlexAxton","title":"SlexAxton (Alex Sexton) · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":10,"user_id":1,"domain":"github.com"},{"url":"https://github.com/gururea/discourse/tree/master/config/locales","title":"discourse/config/locales at master · gururea/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":9,"user_id":3190,"domain":"github.com"},{"url":"https://github.com/discourse/discourse/blob/master/config/locales/client.en.yml#L691","title":"discourse/config/locales/client.en.yml at master · discourse/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":7,"user_id":461,"domain":"github.com"},{"url":"https://github.com/dacap/discourse/tree/spanish","title":"dacap/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":7,"user_id":1275,"domain":"github.com"},{"url":"https://github.com/discourse/discourse/blob/master/config/locales/client.nl.yml","title":"discourse/config/locales/client.nl.yml at master · discourse/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":7,"user_id":461,"domain":"github.com"},{"url":"https://github.com/discourse/discourse/commit/c5761eae8afe37e20cec0d0f9d14b85b6e585bda","title":"Support for Simplified Chinese thanks to tangramor · c5761ea · discourse/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":6,"user_id":212,"domain":"github.com"},{"url":"http://tr8n.github.com/","title":"tr8n","fancy_title":null,"internal":false,"reflection":false,"clicks":6,"user_id":212,"domain":"tr8n.github.com"},{"url":"http://www.getlocalization.com/","title":"Crowdsourced, Social and Collaborative App & Website Translation - Get Localization","fancy_title":null,"internal":false,"reflection":false,"clicks":6,"user_id":22,"domain":"www.getlocalization.com"},{"url":"http://blog.discourse.org/2013/04/discourse-as-your-first-rails-app/","title":"Discourse as Your First Rails App","fancy_title":null,"internal":false,"reflection":false,"clicks":5,"user_id":1995,"domain":"blog.discourse.org"},{"url":"https://github.com/alxndr/discourse/blob/i18n-chinese/config/locales/server.zh.yml","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":5,"user_id":212,"domain":"github.com"},{"url":"http://translate.sourceforge.net/wiki/virtaal/index","title":"Easy-to-use and powerful offline translation tool | Virtaal","fancy_title":null,"internal":false,"reflection":false,"clicks":4,"user_id":1979,"domain":"translate.sourceforge.net"},{"url":"https://poeditor.com/","title":"POEditor - online software localization tool","fancy_title":null,"internal":false,"reflection":false,"clicks":4,"user_id":1979,"domain":"poeditor.com"},{"url":"http://en.lichess.org/@/Hellball","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":3,"user_id":1979,"domain":"en.lichess.org"},{"url":"http://en.wikipedia.org/wiki/T%E2%80%93V_distinction","title":"T–V distinction - Wikipedia, the free encyclopedia","fancy_title":null,"internal":false,"reflection":false,"clicks":3,"user_id":3620,"domain":"en.wikipedia.org"},{"url":"http://www.slideshare.net/HeatherRivers/linguistic-potluck-crowdsourcing-localization-with-rails","title":"Linguistic Potluck: Crowdsourcing localization with Rails","fancy_title":null,"internal":false,"reflection":false,"clicks":3,"user_id":212,"domain":"www.slideshare.net"},{"url":"https://meta.discourse.org/t/language-mirrors/2378/2","title":"Language mirrors","fancy_title":null,"internal":true,"reflection":true,"clicks":3,"user_id":32,"domain":"meta.discourse.org"},{"url":"http://www.madanalogy.com/2012/06/rails-i18n-translations-in-yaml.html","title":"Mad Analogy: Rails i18n translations in Yaml: translation tool support","fancy_title":null,"internal":false,"reflection":false,"clicks":3,"user_id":3190,"domain":"www.madanalogy.com"},{"url":"https://github.com/tr8n","title":"Translation Exchange · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":3,"user_id":9006,"domain":"github.com"},{"url":"http://pootle.locamotion.org/","title":"Main | Pootle Demo","fancy_title":null,"internal":false,"reflection":false,"clicks":2,"user_id":3190,"domain":"pootle.locamotion.org"},{"url":"http://www.youtube.com/watch?v=MqqdzJ98q7s","title":"GoGaRuCo 2012 - Linguistic Potluck: Crowdsourcing Localization in Rails by Heather Rivers - YouTube","fancy_title":null,"internal":false,"reflection":false,"clicks":2,"user_id":212,"domain":"www.youtube.com"},{"url":"https://meta.discourse.org/t/translation-workflow/6102","title":"Translation workflow","fancy_title":null,"internal":true,"reflection":true,"clicks":2,"user_id":4702,"domain":"meta.discourse.org"},{"url":"https://meta.discourse.org/t/solving-xda-developer-style-forums/4368/4","title":"Solving XDA-Developer style forums","fancy_title":null,"internal":true,"reflection":true,"clicks":2,"user_id":639,"domain":"meta.discourse.org"},{"url":"https://tr8nhub.com","title":"TranslationExchange","fancy_title":null,"internal":false,"reflection":false,"clicks":2,"user_id":9006,"domain":"tr8nhub.com"},{"url":"https://meta.discourse.org/t/roadplan-for-discourse/2939/3","title":"Roadplan for Discourse 2013","fancy_title":null,"internal":true,"reflection":true,"clicks":1,"user_id":2540,"domain":"meta.discourse.org"},{"url":"http://sugarjs.com/dates#date_locales","title":"Dates - Sugar","fancy_title":null,"internal":false,"reflection":false,"clicks":1,"user_id":461,"domain":"sugarjs.com"},{"url":"http://blog.discourse.org/2013/03/localizing-discourse/","title":"Localizing Discourse","fancy_title":null,"internal":false,"reflection":false,"clicks":1,"user_id":893,"domain":"blog.discourse.org"},{"url":"https://github.com/discourse/discourse/blob/master/app/assets/javascripts/locales/date_locales.js","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":1,"user_id":461,"domain":"github.com"},{"url":"http://transifex.com/projects/p/discourse-pt-br/","title":"Discourse-Translations-Project localization","fancy_title":null,"internal":false,"reflection":false,"clicks":1,"user_id":893,"domain":"transifex.com"},{"url":"https://github.com/discourse/discourse/issues/279","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":1,"user_id":893,"domain":"github.com"},{"url":"https://meta.discourse.org/t/comrades-lets-join-our-efforts-on-ukrainian-and-russian-translations/4403/5","title":"Comrades let's join our efforts on ukrainian and russian translations","fancy_title":null,"internal":true,"reflection":true,"clicks":1,"user_id":3417,"domain":"meta.discourse.org"},{"url":"https://meta.discourse.org/t/translation-workflow/6102/6","title":"Translation workflow","fancy_title":null,"internal":true,"reflection":false,"clicks":0,"user_id":1995,"domain":"meta.discourse.org"},{"url":"https://meta.discourse.org/t/bookmark-last-read-sometimes-doesn-t-go-to-the-end-of-a-topic/4825/9","title":"Bookmark/last read sometimes doesn't go to the end of a topic","fancy_title":null,"internal":true,"reflection":true,"clicks":0,"user_id":3681,"domain":"meta.discourse.org"},{"url":"https://github.com/discourse/discourse/blob/master/config/locales/client.de.yml","title":"discourse/config/locales/client.de.yml at master · discourse/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":0,"user_id":2,"domain":"github.com"},{"url":"https://meta.discourse.org/t/what-i-love-about-wordpress-plugins/5697","title":"What I love about WordPress plugins","fancy_title":null,"internal":true,"reflection":true,"clicks":0,"user_id":1,"domain":"meta.discourse.org"},{"url":"https://meta.discourse.org/t/github-onebox-rendering-issue/7616","title":"GitHub OneBox Rendering Issue","fancy_title":null,"internal":true,"reflection":true,"clicks":0,"user_id":5372,"domain":"meta.discourse.org"},{"url":"https://github.com/discourse/discourse/blob/master/config/locales/server.de.yml","title":"discourse/config/locales/server.de.yml at master · discourse/discourse · GitHub","fancy_title":null,"internal":false,"reflection":false,"clicks":0,"user_id":2,"domain":"github.com"},{"url":"https://meta.discourse.org/t/roadplan-for-discourse/2939/5","title":"Roadplan for Discourse 2013","fancy_title":null,"internal":true,"reflection":true,"clicks":0,"user_id":32,"domain":"meta.discourse.org"},{"url":"https://meta.discourse.org/t/translation-tools-transifex-localeapp/7763","title":"Translation Tools: Transifex? Localeapp?","fancy_title":null,"internal":true,"reflection":true,"clicks":0,"user_id":2,"domain":"meta.discourse.org"},{"url":"http://guides.rubyonrails.org/i18n.html#the-public-i18n-api","title":"Rails Internationalization (I18n) API — Ruby on Rails Guides","fancy_title":null,"internal":false,"reflection":false,"clicks":0,"user_id":1895,"domain":"guides.rubyonrails.org"},{"url":"https://meta.discourse.org/t/hi-support-chinese/4393/6","title":"Hi, support Chinese?","fancy_title":null,"internal":true,"reflection":true,"clicks":0,"user_id":2014,"domain":"meta.discourse.org"},{"url":"https://meta.discourse.org/t/translation-tools-transifex-localeapp/7763/41","title":"Translation Tools: Transifex? Localeapp?","fancy_title":null,"internal":true,"reflection":false,"clicks":0,"user_id":6626,"domain":"meta.discourse.org"}],"notification_level":2,"notifications_reason_id":4,"can_move_posts":true,"can_edit":true,"can_delete":true,"can_recover":true,"can_remove_allowed_users":true,"can_invite_to":true,"can_create_post":true,"can_reply_as_new_topic":true,"can_flag_topic":true},"highest_post_number":10,"last_read_post_number":10,"deleted_by":null,"has_deleted":true,"actions_summary":[{"id":4,"count":0,"hidden":false,"can_act":true},{"id":7,"count":0,"hidden":false,"can_act":true},{"id":8,"count":0,"hidden":false,"can_act":true}],"chunk_size":20,"bookmarked":false,"tags":null}, +"/t/28830/1.json": {"post_stream":{"posts":[{"id":118591,"name":"spends too much time on WTDWTF","username":"RaceProUK","avatar_template":"/user_avatar/meta.discourse.org/raceprouk/{size}/40071.png","uploaded_avatar_id":40071,"created_at":"2015-05-14T20:18:17.954Z","cooked":"

Normally, actions such as Liking are rate-limited, and when you hit the limit, you get a message telling you you've hit the limit. However, in 1.3.0beta9, it seems those popups are no longer appearing.

\n\n

Edit: Possibly linked to this issue?

","post_number":1,"post_type":1,"updated_at":"2015-05-14T20:21:42.825Z","like_count":6,"reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":null,"incoming_link_count":14,"reads":24,"score":224.6,"yours":false,"topic_id":28830,"topic_slug":"1-3-0beta9-no-rate-limit-popups","display_username":"spends too much time on WTDWTF","primary_group_name":null,"version":1,"can_edit":false,"can_delete":false,"can_recover":false,"link_counts":[{"url":"https://meta.discourse.org/t/post-reply-on-different-topic-no-longer-works/28825","internal":true,"reflection":false,"title":"Post reply on different topic no longer works","clicks":6}],"read":true,"user_title":"Contributor","actions_summary":[{"id":2,"count":6,"hidden":false,"can_act":false},{"id":3,"count":0,"hidden":false,"can_act":false},{"id":4,"count":0,"hidden":false,"can_act":false},{"id":5,"count":0,"hidden":true,"can_act":false},{"id":6,"count":0,"hidden":false,"can_act":false},{"id":7,"count":0,"hidden":false,"can_act":false},{"id":8,"count":0,"hidden":false,"can_act":false}],"moderator":false,"admin":false,"staff":false,"user_id":14169,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":"","can_view_edit_history":true,"wiki":false},{"id":118597,"name":"Sam","username":"Yuun","avatar_template":"/letter_avatar/yuun/{size}/3_90a587a04512ff220ac26ec1465844c5.png","uploaded_avatar_id":null,"created_at":"2015-05-14T20:35:03.793Z","cooked":"

I'm seeing this issue as well. When you hit the rate limit, any further likes look like the forum is attempting and failing to apply them - the text saying 'you liked this' comes into place before quickly being removed.

\n\n

This makes it look (to the user) like the forum software is running into errors instead of said user hitting an intentional limit, which is a bit unfortunate.

","post_number":2,"post_type":1,"updated_at":"2015-05-14T20:35:03.793Z","like_count":0,"reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":null,"incoming_link_count":6,"reads":22,"score":34.2,"yours":false,"topic_id":28830,"topic_slug":"1-3-0beta9-no-rate-limit-popups","display_username":"Sam","primary_group_name":null,"version":1,"can_edit":false,"can_delete":false,"can_recover":false,"read":true,"user_title":null,"actions_summary":[{"id":2,"count":0,"hidden":false,"can_act":false},{"id":3,"count":0,"hidden":false,"can_act":false},{"id":4,"count":0,"hidden":false,"can_act":false},{"id":5,"count":0,"hidden":true,"can_act":false},{"id":6,"count":0,"hidden":false,"can_act":false},{"id":7,"count":0,"hidden":false,"can_act":false},{"id":8,"count":0,"hidden":false,"can_act":false}],"moderator":false,"admin":false,"staff":false,"user_id":14795,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":118601,"name":"Kane York","username":"riking","avatar_template":"/user_avatar/meta.discourse.org/riking/{size}/40212.png","uploaded_avatar_id":40212,"created_at":"2015-05-14T21:05:19.837Z","cooked":"

I'm going to guess that the bootbox library got broken somehow?

","post_number":3,"post_type":1,"updated_at":"2015-05-14T21:05:19.837Z","like_count":0,"reply_count":1,"reply_to_post_number":null,"quote_count":0,"avg_time":null,"incoming_link_count":0,"reads":14,"score":7.2,"yours":false,"topic_id":28830,"topic_slug":"1-3-0beta9-no-rate-limit-popups","display_username":"Kane York","primary_group_name":null,"version":1,"can_edit":false,"can_delete":false,"can_recover":false,"read":true,"user_title":"team summer intern 2014","actions_summary":[{"id":2,"count":0,"hidden":false,"can_act":false},{"id":3,"count":0,"hidden":false,"can_act":false},{"id":4,"count":0,"hidden":false,"can_act":false},{"id":5,"count":0,"hidden":true,"can_act":false},{"id":6,"count":0,"hidden":false,"can_act":false},{"id":7,"count":0,"hidden":false,"can_act":false},{"id":8,"count":0,"hidden":false,"can_act":false}],"moderator":false,"admin":false,"staff":false,"user_id":6626,"hidden":false,"hidden_reason_id":null,"trust_level":4,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":118606,"name":"Jeff Atwood","username":"codinghorror","avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png","uploaded_avatar_id":5297,"created_at":"2015-05-14T21:15:41.612Z","cooked":"

Yeah maybe another Ember 1.10 regression for @eviltrout ?

","post_number":4,"post_type":1,"updated_at":"2015-05-14T21:15:41.612Z","like_count":0,"reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":null,"incoming_link_count":6,"reads":12,"score":31.6,"yours":false,"topic_id":28830,"topic_slug":"1-3-0beta9-no-rate-limit-popups","display_username":"Jeff Atwood","primary_group_name":"discourse","version":1,"can_edit":false,"can_delete":false,"can_recover":false,"read":true,"user_title":"co-founder","actions_summary":[{"id":2,"count":0,"hidden":false,"can_act":false},{"id":3,"count":0,"hidden":false,"can_act":false},{"id":4,"count":0,"hidden":false,"can_act":false},{"id":5,"count":0,"hidden":true,"can_act":false},{"id":6,"count":0,"hidden":false,"can_act":false},{"id":7,"count":0,"hidden":false,"can_act":false},{"id":8,"count":0,"hidden":false,"can_act":false}],"moderator":true,"admin":true,"staff":true,"user_id":32,"hidden":false,"hidden_reason_id":null,"trust_level":4,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":118612,"name":"TDWTF member","username":"Onyx","avatar_template":"/user_avatar/meta.discourse.org/onyx/{size}/33015.png","uploaded_avatar_id":33015,"created_at":"2015-05-14T21:23:09.562Z","cooked":"\n\n

You mean the popup box library, guessing by the name? Still shows up when you want to cancel a post, so it's not all popups it seems.

","post_number":5,"post_type":1,"updated_at":"2015-05-14T21:23:09.562Z","like_count":1,"reply_count":0,"reply_to_post_number":3,"quote_count":1,"avg_time":null,"incoming_link_count":0,"reads":11,"score":16.0,"yours":false,"topic_id":28830,"topic_slug":"1-3-0beta9-no-rate-limit-popups","display_username":"TDWTF member","primary_group_name":null,"version":1,"can_edit":false,"can_delete":false,"can_recover":false,"read":true,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":false},{"id":3,"count":0,"hidden":false,"can_act":false},{"id":4,"count":0,"hidden":false,"can_act":false},{"id":5,"count":0,"hidden":true,"can_act":false},{"id":6,"count":0,"hidden":false,"can_act":false},{"id":7,"count":0,"hidden":false,"can_act":false},{"id":8,"count":0,"hidden":false,"can_act":false}],"moderator":false,"admin":false,"staff":false,"user_id":10886,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false}],"stream":[118591,118597,118601,118606,118612]},"id":28830,"title":"1.3.0beta9: No rate-limit popups","fancy_title":"1.3.0beta9: No rate-limit popups","posts_count":5,"created_at":"2015-05-14T20:18:17.877Z","views":38,"reply_count":1,"participant_count":5,"like_count":7,"last_posted_at":"2015-05-14T21:23:09.562Z","visible":true,"closed":false,"archived":false,"has_summary":false,"archetype":"regular","slug":"1-3-0beta9-no-rate-limit-popups","category_id":1,"word_count":198,"deleted_at":null,"pending_posts_count":0,"draft":null,"draft_key":"topic_28830","draft_sequence":null,"unpinned":null,"pinned_globally":false,"pinned":false,"pinned_at":null,"details":{"auto_close_at":null,"auto_close_hours":null,"auto_close_based_on_last_post":false,"created_by":{"id":14169,"username":"RaceProUK","uploaded_avatar_id":40071,"avatar_template":"/user_avatar/meta.discourse.org/raceprouk/{size}/40071.png"},"last_poster":{"id":10886,"username":"Onyx","uploaded_avatar_id":33015,"avatar_template":"/user_avatar/meta.discourse.org/onyx/{size}/33015.png"},"participants":[{"id":14795,"username":"Yuun","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/yuun/{size}/3_90a587a04512ff220ac26ec1465844c5.png","post_count":1},{"id":10886,"username":"Onyx","uploaded_avatar_id":33015,"avatar_template":"/user_avatar/meta.discourse.org/onyx/{size}/33015.png","post_count":1},{"id":14169,"username":"RaceProUK","uploaded_avatar_id":40071,"avatar_template":"/user_avatar/meta.discourse.org/raceprouk/{size}/40071.png","post_count":1},{"id":6626,"username":"riking","uploaded_avatar_id":40212,"avatar_template":"/user_avatar/meta.discourse.org/riking/{size}/40212.png","post_count":1},{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png","post_count":1}],"suggested_topics":[{"id":2890,"title":"Expanded quoted text not highlighting when text is formatted","fancy_title":"Expanded quoted text not highlighting when text is formatted","slug":"expanded-quoted-text-not-highlighting-when-text-is-formatted","posts_count":8,"reply_count":5,"highest_post_number":8,"image_url":null,"created_at":"2013-02-12T12:18:02.181Z","last_posted_at":"2013-02-14T15:59:40.014Z","bumped":true,"bumped_at":"2013-02-14T15:59:40.014Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":3,"views":361,"category_id":1},{"id":14213,"title":"Plugins not being parsed in correct javascript context when loaded for jobs","fancy_title":"Plugins not being parsed in correct javascript context when loaded for jobs","slug":"plugins-not-being-parsed-in-correct-javascript-context-when-loaded-for-jobs","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":"/plugins/emoji/images/frowning.png","created_at":"2014-03-27T23:57:00.974Z","last_posted_at":"2015-03-20T04:56:03.982Z","bumped":true,"bumped_at":"2015-03-20T04:56:03.982Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":0,"views":156,"category_id":1},{"id":22544,"title":"Like count on profile off by one","fancy_title":"Like count on profile off by one","slug":"like-count-on-profile-off-by-one","posts_count":7,"reply_count":2,"highest_post_number":7,"image_url":null,"created_at":"2014-11-26T08:15:39.802Z","last_posted_at":"2014-11-27T07:23:37.638Z","bumped":true,"bumped_at":"2014-11-27T07:23:37.638Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":18,"views":192,"category_id":1},{"id":27670,"title":"Using back still shows unread indicator on the topic","fancy_title":"Using back still shows unread indicator on the topic","slug":"using-back-still-shows-unread-indicator-on-the-topic","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2015-04-16T23:21:42.739Z","last_posted_at":"2015-04-17T02:43:08.447Z","bumped":true,"bumped_at":"2015-04-17T02:43:08.447Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":1,"views":85,"category_id":1},{"id":26628,"title":"Embed blacklist selector is broken","fancy_title":"Embed blacklist selector is broken","slug":"embed-blacklist-selector-is-broken","posts_count":11,"reply_count":7,"highest_post_number":11,"image_url":null,"created_at":"2015-03-22T11:21:14.825Z","last_posted_at":"2015-04-20T09:11:38.999Z","bumped":true,"bumped_at":"2015-04-20T09:11:38.999Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":1,"views":247,"category_id":1},{"id":18027,"title":"Minor: delete/undelete needs a rate limit","fancy_title":"Minor: delete/undelete needs a rate limit","slug":"minor-delete-undelete-needs-a-rate-limit","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":null,"created_at":"2014-07-25T02:51:41.158Z","last_posted_at":"2014-07-25T04:01:15.343Z","bumped":true,"bumped_at":"2014-07-25T11:06:46.213Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":1,"views":165,"category_id":1},{"id":17396,"title":"Bad Reply Key when pulling Autoforwarded Emails to Discourse","fancy_title":"Bad Reply Key when pulling Autoforwarded Emails to Discourse","slug":"bad-reply-key-when-pulling-autoforwarded-emails-to-discourse","posts_count":20,"reply_count":15,"highest_post_number":20,"image_url":null,"created_at":"2014-07-09T18:34:57.114Z","last_posted_at":"2014-10-21T15:08:50.441Z","bumped":true,"bumped_at":"2014-10-21T15:08:50.441Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"archetype":"regular","like_count":7,"views":542,"category_id":1}],"links":[{"url":"https://meta.discourse.org/t/post-reply-on-different-topic-no-longer-works/28825","title":"Post reply on different topic no longer works","fancy_title":null,"internal":true,"reflection":false,"clicks":6,"user_id":14169,"domain":"meta.discourse.org"}],"notification_level":1,"can_flag_topic":false},"highest_post_number":5,"deleted_by":null,"actions_summary":[{"id":4,"count":0,"hidden":false,"can_act":false},{"id":7,"count":0,"hidden":false,"can_act":false},{"id":8,"count":0,"hidden":false,"can_act":false}],"chunk_size":20,"bookmarked":null,"tags":null}, "/t/9/1.json": {"post_stream":{"posts":[{"id":18,"username":"eviltrout","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","name":"Evil Trout","uploaded_avatar_id":9,"created_at":"2015-08-13T14:49:11.840Z","cooked":"

This is the first post.

","post_number":1,"post_type":1,"updated_at":"2015-08-13T14:49:11.840Z","reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":null,"incoming_link_count":0,"reads":1,"score":0,"yours":true,"topic_id":9,"topic_slug":"this-is-a-test-topic","display_username":"","primary_group_name":null,"version":1,"can_edit":true,"can_delete":false,"can_recover":true,"read":true,"user_title":null,"actions_summary":[{"id":3,"can_act":true},{"id":4,"can_act":true},{"id":5,"hidden":true,"can_act":true},{"id":7,"can_act":true},{"id":8,"can_act":true}],"moderator":false,"admin":true,"staff":true,"user_id":1,"hidden":false,"hidden_reason_id":null,"trust_level":4,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":19,"username":"eviltrout","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","name":"Evil Trout","uploaded_avatar_id":9,"created_at":"2015-08-13T14:49:18.231Z","cooked":"

This is the second post.

","post_number":2,"post_type":1,"updated_at":"2015-08-13T14:49:18.231Z","reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":null,"incoming_link_count":0,"reads":1,"score":0,"yours":true,"topic_id":9,"topic_slug":"this-is-a-test-topic","display_username":"","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":null,"actions_summary":[{"id":3,"can_act":true},{"id":4,"can_act":true},{"id":5,"hidden":true,"can_act":true},{"id":7,"can_act":true},{"id":8,"can_act":true}],"moderator":false,"admin":true,"staff":true,"user_id":1,"hidden":false,"hidden_reason_id":null,"trust_level":4,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false},{"id":20,"username":"eviltrout","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","name":"Evil Trout","uploaded_avatar_id":9,"created_at":"2015-08-13T14:49:23.927Z","cooked":"

This is the third post.

","post_number":3,"post_type":1,"updated_at":"2015-08-13T14:49:23.927Z","reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":null,"incoming_link_count":0,"reads":1,"score":0,"yours":true,"topic_id":9,"topic_slug":"this-is-a-test-topic","display_username":"","primary_group_name":null,"version":1,"can_edit":true,"can_delete":true,"can_recover":true,"read":true,"user_title":null,"actions_summary":[{"id":3,"can_act":true},{"id":4,"can_act":true},{"id":5,"hidden":true,"can_act":true},{"id":7,"can_act":true},{"id":8,"can_act":true}],"moderator":false,"admin":true,"staff":true,"user_id":1,"hidden":false,"hidden_reason_id":null,"trust_level":4,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false}],"stream":[18,19,20]},"id":9,"title":"This is a test topic!","fancy_title":"This is a test topic!","posts_count":3,"created_at":"2015-08-13T14:49:11.720Z","views":1,"reply_count":0,"participant_count":1,"like_count":0,"last_posted_at":"2015-08-13T14:49:23.927Z","visible":true,"closed":false,"archived":false,"has_summary":false,"archetype":"regular","slug":"this-is-a-test-topic","category_id":1,"word_count":15,"deleted_at":null,"pending_posts_count":0,"user_id":1,"draft":null,"draft_key":"topic_9","draft_sequence":3,"posted":true,"unpinned":null,"pinned_globally":false,"pinned":false,"pinned_at":null,"pinned_until":null,"details":{"auto_close_at":null,"auto_close_hours":null,"auto_close_based_on_last_post":false,"created_by":{"id":1,"username":"tgxworld","uploaded_avatar_id":9,"avatar_template":"/user_avatar/localhost/tgxworld/{size}/9_1.png"},"last_poster":{"id":1,"username":"tgxworld","uploaded_avatar_id":9,"avatar_template":"/user_avatar/localhost/tgxworld/{size}/9_1.png"},"participants":[{"id":1,"username":"tgxworld","uploaded_avatar_id":9,"avatar_template":"/user_avatar/localhost/tgxworld/{size}/9_1.png","post_count":3}],"suggested_topics":[{"id":8,"title":"This is a new and awesome topic!","fancy_title":"This is a new and awesome topic!","slug":"this-is-a-new-and-awesome-topic","posts_count":3,"reply_count":0,"highest_post_number":5,"image_url":null,"created_at":"2015-08-13T05:17:00.000Z","last_posted_at":"2015-08-13T10:14:34.799Z","bumped":true,"bumped_at":"2015-08-13T10:14:34.799Z","unseen":false,"last_read_post_number":5,"unread":0,"new_posts":0,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"notification_level":2,"bookmarked":false,"liked":false,"archetype":"regular","like_count":0,"views":2,"category_id":1},{"id":7,"title":"This is a test category!","fancy_title":"This is a test category!","slug":"this-is-a-test-category","posts_count":3,"reply_count":0,"highest_post_number":3,"image_url":null,"created_at":"2015-08-10T13:40:38.439Z","last_posted_at":"2015-08-13T01:59:44.928Z","bumped":true,"bumped_at":"2015-08-13T01:58:35.206Z","unseen":false,"last_read_post_number":3,"unread":0,"new_posts":0,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"notification_level":3,"bookmarked":false,"liked":false,"archetype":"regular","like_count":0,"views":2,"category_id":1}],"notification_level":3,"notifications_reason_id":1,"can_move_posts":true,"can_edit":true,"can_delete":true,"can_recover":true,"can_remove_allowed_users":true,"can_invite_to":true,"can_create_post":true,"can_reply_as_new_topic":true,"can_flag_topic":true},"highest_post_number":3,"last_read_post_number":3,"deleted_by":null,"has_deleted":false,"actions_summary":[{"id":4,"count":0,"hidden":false,"can_act":true},{"id":7,"count":0,"hidden":false,"can_act":true},{"id":8,"count":0,"hidden":false,"can_act":true}],"chunk_size":20,"bookmarked":false} }; diff --git a/test/javascripts/fixtures/user-badges.js.es6 b/test/javascripts/fixtures/user-badges.js.es6 new file mode 100644 index 000000000..6b5cd069b --- /dev/null +++ b/test/javascripts/fixtures/user-badges.js.es6 @@ -0,0 +1,57 @@ +export default { + '/user_badges':{ + "badges":[ + { + "id":874, + "name":"Badge 2", + "description":null, + "badge_type_id":7 + } + ], + "badge_types":[ + { + "id":7, + "name":"Silver 2" + } + ], + "users":[ + { + "id":13470, + "username":"anne3", + "avatar_template":"//www.gravatar.com/avatar/a4151b1fd72089c54e2374565a87da7f.png?s={size}\u0026r=pg\u0026d=identicon" + } + ], + "user_badge":{ + "id":665, + "granted_at":"2014-03-09T20:30:01.190-04:00", + "badge_id":874, + "granted_by_id":13470 + } + }, + '/user-badges/:username':{ + "badges":[ + { + "id":880, + "name":"Badge 8", + "description":null, + "badge_type_id":13 + } + ], + "badge_types":[ + { + "id":13, + "name":"Silver 8" + } + ], + "users":[ + ], + "user_badges":[ + { + "id":668, + "granted_at":"2014-03-09T20:30:01.420-04:00", + "badge_id":880, + "granted_by_id":null + } + ] + } +}; diff --git a/test/javascripts/fixtures/user_fixtures.js.es6 b/test/javascripts/fixtures/user_fixtures.js.es6 index c123605c3..cfdbd38dd 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@gmail.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":"https://meta-discourse.global.ssl.fastly.net/uploads/default/36220/15b19c80dd99d5a5.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":"https://meta-discourse.global.ssl.fastly.net/uploads/default/36220/15b19c80dd99d5a5.png","image":"https://meta-discourse.global.ssl.fastly.net/uploads/default/36220/15b19c80dd99d5a5.png","listable":false,"enabled":false,"badge_grouping_id":8,"system":false,"badge_type_id":3}}}, +"/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@gmail.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 770e5e0d7..2597fa6b9 100644 --- a/test/javascripts/helpers/create-pretender.js.es6 +++ b/test/javascripts/helpers/create-pretender.js.es6 @@ -64,18 +64,21 @@ export default function() { return response(json); }); + this.get('/clicks/track', success); + this.put('/users/eviltrout', () => response({ user: {} })); this.get("/t/280.json", () => response(fixturesByUrl['/t/280/1.json'])); - this.get("/t/28830.json", () => response(fixturesByUrl['/t/28830/1.json'])); - this.get("/t/9.json", () => response(fixturesByUrl['/t/9/1.json'])); this.get("/t/id_for/:slug", () => { return response({id: 280, slug: "internationalization-localization", url: "/t/internationalization-localization/280"}); }); + this.delete('/t/:id', success); + this.put('/t/:id/recover', success); + this.get("/404-body", () => { return [200, {"Content-Type": "text/html"}, "
not found
"]; }); @@ -196,6 +199,9 @@ export default function() { return response(200, [ { id: 2222, post_number: 2222 } ]); }); + this.post('/user_badges', () => response(200, fixturesByUrl['/user_badges'])); + this.delete('/user_badges/:badge_id', success); + this.post('/posts', function(request) { const data = parsePostData(request.requestBody); @@ -238,6 +244,10 @@ export default function() { }); this.get('/tag_groups', () => response(200, {tag_groups: []})); + this.post('/admin/users/:user_id/generate_api_key', success); + this.delete('/admin/users/:user_id/revoke_api_key', success); + this.post('/admin/badges', success); + this.delete('/admin/badges/:id', success); }); server.prepareBody = function(body){ diff --git a/test/javascripts/lib/click-track-edit-history-test.js.es6 b/test/javascripts/lib/click-track-edit-history-test.js.es6 index f0973679b..4350ab6d2 100644 --- a/test/javascripts/lib/click-track-edit-history-test.js.es6 +++ b/test/javascripts/lib/click-track-edit-history-test.js.es6 @@ -12,7 +12,6 @@ module("lib:click-track-edit-history", { // Prevent any of these tests from navigating away win = {focus: function() { } }; redirectTo = sandbox.stub(DiscourseURL, "redirectTo"); - sandbox.stub(Discourse, "ajax"); windowOpen = sandbox.stub(window, "open").returns(win); sandbox.stub(win, "focus"); @@ -141,7 +140,6 @@ var testOpenInANewTab = function(description, clickEventModifier) { clickEventModifier(clickEvent); sandbox.stub(clickEvent, "preventDefault"); ok(track(clickEvent)); - ok(Discourse.ajax.calledOnce); ok(!clickEvent.preventDefault.calledOnce); }); }; @@ -167,7 +165,6 @@ test("tracks via AJAX if we're on the same site", function() { sandbox.stub(DiscourseURL, "origin").returns("http://discuss.domain.com"); ok(!track(generateClickEventOn('#same-site'))); - ok(Discourse.ajax.calledOnce); ok(DiscourseURL.routeTo.calledOnce); }); diff --git a/test/javascripts/lib/click-track-profile-page-test.js.es6 b/test/javascripts/lib/click-track-profile-page-test.js.es6 index cf6233fc3..bc0b46e55 100644 --- a/test/javascripts/lib/click-track-profile-page-test.js.es6 +++ b/test/javascripts/lib/click-track-profile-page-test.js.es6 @@ -12,7 +12,6 @@ module("lib:click-track-profile-page", { // Prevent any of these tests from navigating away win = {focus: function() { } }; redirectTo = sandbox.stub(DiscourseURL, "redirectTo"); - sandbox.stub(Discourse, "ajax"); windowOpen = sandbox.stub(window, "open").returns(win); sandbox.stub(win, "focus"); @@ -141,7 +140,6 @@ var testOpenInANewTab = function(description, clickEventModifier) { clickEventModifier(clickEvent); sandbox.stub(clickEvent, "preventDefault"); ok(track(clickEvent)); - ok(Discourse.ajax.calledOnce); ok(!clickEvent.preventDefault.calledOnce); }); }; @@ -167,7 +165,6 @@ test("tracks via AJAX if we're on the same site", function() { sandbox.stub(DiscourseURL, "origin").returns("http://discuss.domain.com"); ok(!track(generateClickEventOn('#same-site'))); - ok(Discourse.ajax.calledOnce); ok(DiscourseURL.routeTo.calledOnce); }); diff --git a/test/javascripts/lib/click-track-test.js.es6 b/test/javascripts/lib/click-track-test.js.es6 index 806b6b557..bebe653d6 100644 --- a/test/javascripts/lib/click-track-test.js.es6 +++ b/test/javascripts/lib/click-track-test.js.es6 @@ -12,7 +12,6 @@ module("lib:click-track", { // Prevent any of these tests from navigating away win = {focus: function() { } }; redirectTo = sandbox.stub(DiscourseURL, "redirectTo"); - sandbox.stub(Discourse, "ajax"); windowOpen = sandbox.stub(window, "open").returns(win); sandbox.stub(win, "focus"); @@ -161,7 +160,6 @@ var testOpenInANewTab = function(description, clickEventModifier) { clickEventModifier(clickEvent); sandbox.stub(clickEvent, "preventDefault"); ok(track(clickEvent)); - ok(Discourse.ajax.calledOnce); ok(!clickEvent.preventDefault.calledOnce); }); }; @@ -187,7 +185,6 @@ test("tracks via AJAX if we're on the same site", function() { sandbox.stub(DiscourseURL, "origin").returns("http://discuss.domain.com"); ok(!track(generateClickEventOn('#same-site'))); - ok(Discourse.ajax.calledOnce); ok(DiscourseURL.routeTo.calledOnce); }); diff --git a/test/javascripts/models/badge-test.js.es6 b/test/javascripts/models/badge-test.js.es6 index 0b92f88ce..644056d04 100644 --- a/test/javascripts/models/badge-test.js.es6 +++ b/test/javascripts/models/badge-test.js.es6 @@ -37,19 +37,15 @@ test('updateFromJson', function() { }); test('save', function() { - sandbox.stub(Discourse, 'ajax').returns(Ember.RSVP.resolve({})); + expect(0); const badge = Badge.create({name: "New Badge", description: "This is a new badge.", badge_type_id: 1}); - // TODO: clean API - badge.save(["name", "description", "badge_type_id"]); - ok(Discourse.ajax.calledOnce, "saved badge"); + return badge.save(["name", "description", "badge_type_id"]); }); test('destroy', function() { - sandbox.stub(Discourse, 'ajax'); + expect(0); const badge = Badge.create({name: "New Badge", description: "This is a new badge.", badge_type_id: 1}); badge.destroy(); - ok(!Discourse.ajax.calledOnce, "no AJAX call for a new badge"); badge.set('id', 3); - badge.destroy(); - ok(Discourse.ajax.calledOnce, "AJAX call was made"); + return badge.destroy(); }); diff --git a/test/javascripts/models/topic-test.js.es6 b/test/javascripts/models/topic-test.js.es6 index 7938b7afd..2c1ceac92 100644 --- a/test/javascripts/models/topic-test.js.es6 +++ b/test/javascripts/models/topic-test.js.es6 @@ -55,24 +55,18 @@ test("destroy", function() { var user = Discourse.User.create({username: 'eviltrout'}); var topic = Topic.create({id: 1234}); - sandbox.stub(Discourse, 'ajax'); - topic.destroy(user); present(topic.get('deleted_at'), 'deleted at is set'); equal(topic.get('deleted_by'), user, 'deleted by is set'); - //ok(Discourse.ajax.calledOnce, "it called delete over the wire"); }); test("recover", function() { var user = Discourse.User.create({username: 'eviltrout'}); var topic = Topic.create({id: 1234, deleted_at: new Date(), deleted_by: user}); - sandbox.stub(Discourse, 'ajax'); - topic.recover(); blank(topic.get('deleted_at'), "it clears deleted_at"); blank(topic.get('deleted_by'), "it clears deleted_by"); - //ok(Discourse.ajax.calledOnce, "it called recover over the wire"); }); test('fancyTitle', function() { diff --git a/test/javascripts/models/user-badge-test.js.es6 b/test/javascripts/models/user-badge-test.js.es6 index bf2a5bf98..5258d98b0 100644 --- a/test/javascripts/models/user-badge-test.js.es6 +++ b/test/javascripts/models/user-badge-test.js.es6 @@ -1,12 +1,10 @@ import UserBadge from 'discourse/models/user-badge'; +import badgeFixtures from 'fixtures/user-badges'; module("model:user-badge"); -const singleBadgeJson = {"badges":[{"id":874,"name":"Badge 2","description":null,"badge_type_id":7}],"badge_types":[{"id":7,"name":"Silver 2"}],"users":[{"id":13470,"username":"anne3","avatar_template":"//www.gravatar.com/avatar/a4151b1fd72089c54e2374565a87da7f.png?s={size}\u0026r=pg\u0026d=identicon"}],"user_badge":{"id":665,"granted_at":"2014-03-09T20:30:01.190-04:00","badge_id":874,"granted_by_id":13470}}, - multipleBadgesJson = {"badges":[{"id":880,"name":"Badge 8","description":null,"badge_type_id":13}],"badge_types":[{"id":13,"name":"Silver 8"}],"users":[],"user_badges":[{"id":668,"granted_at":"2014-03-09T20:30:01.420-04:00","badge_id":880,"granted_by_id":null}]}; - test('createFromJson single', function() { - const userBadge = UserBadge.createFromJson(singleBadgeJson); + const userBadge = UserBadge.createFromJson(badgeFixtures['/user_badges']); ok(!Array.isArray(userBadge), "does not return an array"); equal(userBadge.get('badge.name'), "Badge 2", "badge reference is set"); equal(userBadge.get('badge.badge_type.name'), "Silver 2", "badge.badge_type reference is set"); @@ -14,44 +12,31 @@ test('createFromJson single', function() { }); test('createFromJson array', function() { - const userBadges = UserBadge.createFromJson(multipleBadgesJson); + const userBadges = UserBadge.createFromJson(badgeFixtures['/user-badges/:username']); ok(Array.isArray(userBadges), "returns an array"); equal(userBadges[0].get('granted_by'), null, "granted_by reference is not set when null"); }); -asyncTestDiscourse('findByUsername', function() { - expect(2); - sandbox.stub(Discourse, 'ajax').returns(Ember.RSVP.resolve(multipleBadgesJson)); - UserBadge.findByUsername("anne3").then(function(badges) { +test('findByUsername', function() { + return UserBadge.findByUsername("anne3").then(function(badges) { ok(Array.isArray(badges), "returns an array"); - start(); }); - ok(Discourse.ajax.calledOnce, "makes an AJAX call"); }); -asyncTestDiscourse('findByBadgeId', function() { - expect(2); - sandbox.stub(Discourse, 'ajax').returns(Ember.RSVP.resolve(multipleBadgesJson)); - UserBadge.findByBadgeId(880).then(function(badges) { +test('findByBadgeId', function() { + return UserBadge.findByBadgeId(880).then(function(badges) { ok(Array.isArray(badges), "returns an array"); - start(); }); - ok(Discourse.ajax.calledOnce, "makes an AJAX call"); }); -asyncTestDiscourse('grant', function() { - expect(2); - sandbox.stub(Discourse, 'ajax').returns(Ember.RSVP.resolve(singleBadgeJson)); - UserBadge.grant(1, "username").then(function(userBadge) { +test('grant', function() { + return UserBadge.grant(1, "username").then(function(userBadge) { ok(!Array.isArray(userBadge), "does not return an array"); - start(); }); - ok(Discourse.ajax.calledOnce, "makes an AJAX call"); }); test('revoke', function() { - sandbox.stub(Discourse, 'ajax'); + expect(0); const userBadge = UserBadge.create({id: 1}); - userBadge.revoke(); - ok(Discourse.ajax.calledOnce, "makes an AJAX call"); + return userBadge.revoke(); });