diff --git a/.gitignore b/.gitignore index 1f4280195a..2b372eaa07 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ config/discourse.conf /tmp /logfile log/ +bootsnap-load-path-cache # Ignore plugins except for the bundled ones. /plugins/* @@ -50,6 +51,7 @@ log/ !/plugins/poll/ !/plugins/discourse-details/ !/plugins/discourse-nginx-performance-report +!/plugins/discourse-narrative-bot /plugins/*/auto_generated/ /spec/fixtures/plugins/my_plugin/auto_generated diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000000..792d6e22e1 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,14 @@ +AllCops: + TargetRubyVersion: 2.3 + +Metrics/LineLength: + Max: 120 + +Metrics/MethodLength: + Enabled: false + +Style/Documentation: + Enabled: false + +Style/FrozenStringLiteralComment: + Enabled: False diff --git a/.tx/config b/.tx/config index a07dd5ad46..bd09128d4c 100644 --- a/.tx/config +++ b/.tx/config @@ -32,6 +32,18 @@ source_file = vendor/gems/discourse_imgur/lib/discourse_imgur/locale/server.en.y source_lang = en type = YML +[discourse-org.narrativeclientenyml] +file_filter = plugins/discourse-narrative-bot/config/locales/client..yml +source_file = plugins/discourse-narrative-bot/config/locales/client.en.yml +source_lang = en +type = YML + +[discourse-org.narrativeserverenyml] +file_filter = plugins/discourse-narrative-bot/config/locales/server..yml +source_file = plugins/discourse-narrative-bot/config/locales/server.en.yml +source_lang = en +type = YML + [discourse-org.403html] file_filter = public/403..html source_file = public/403.html diff --git a/Gemfile b/Gemfile index 906b0f3aa2..a3222a8b26 100644 --- a/Gemfile +++ b/Gemfile @@ -190,4 +190,5 @@ gem 'sassc', require: false if ENV["IMPORT"] == "1" gem 'mysql2' gem 'redcarpet' + gem 'sqlite3', '~> 1.3.13' end diff --git a/Gemfile.lock b/Gemfile.lock index b7ad587144..d392a8cfd7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -62,7 +62,7 @@ GEM rack (>= 0.9.0) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) - bootsnap (0.2.14) + bootsnap (0.3.0) msgpack (~> 1.0) builder (3.2.3) bullet (5.4.2) @@ -103,7 +103,7 @@ GEM railties (>= 3.1) ember-source (2.10.0) erubis (2.7.0) - excon (0.53.0) + excon (0.55.0) execjs (2.7.0) exifr (1.2.5) fabrication (2.9.8) @@ -125,7 +125,7 @@ GEM globalid (0.3.7) activesupport (>= 4.1.0) guess_html_encoding (0.0.11) - hashdiff (0.3.2) + hashdiff (0.3.4) hashie (3.5.5) highline (1.7.8) hiredis (0.6.1) @@ -214,7 +214,7 @@ GEM omniauth-twitter (1.3.0) omniauth-oauth (~> 1.1) rack - onebox (1.8.6) + onebox (1.8.8) fast_blank (>= 1.0.0) htmlentities (~> 4.3) moneta (~> 1.0) diff --git a/app/assets/javascripts/admin/components/ace-editor.js.es6 b/app/assets/javascripts/admin/components/ace-editor.js.es6 index 372a32b33a..749ce2492d 100644 --- a/app/assets/javascripts/admin/components/ace-editor.js.es6 +++ b/app/assets/javascripts/admin/components/ace-editor.js.es6 @@ -1,6 +1,8 @@ import loadScript from 'discourse/lib/load-script'; import { observes } from 'ember-addons/ember-computed-decorators'; +const LOAD_ASYNC = !Ember.Test; + export default Ember.Component.extend({ mode: 'css', classNames: ['ace-wrapper'], @@ -23,7 +25,7 @@ export default Ember.Component.extend({ @observes('mode') modeChanged() { - if (this._editor && !this._skipContentChangeEvent) { + if (LOAD_ASYNC && this._editor && !this._skipContentChangeEvent) { this._editor.getSession().setMode("ace/mode/" + this.get('mode')); } }, @@ -56,10 +58,14 @@ export default Ember.Component.extend({ if (!this.element || this.isDestroying || this.isDestroyed) { return; } const editor = loadedAce.edit(this.$('.ace')[0]); - editor.setTheme("ace/theme/chrome"); + if (LOAD_ASYNC) { + editor.setTheme("ace/theme/chrome"); + } editor.setShowPrintMargin(false); editor.setOptions({fontSize: "14px"}); - editor.getSession().setMode("ace/mode/" + this.get('mode')); + if (LOAD_ASYNC) { + editor.getSession().setMode("ace/mode/" + this.get('mode')); + } editor.on('change', () => { this._skipContentChangeEvent = true; this.set('content', editor.getSession().getValue()); diff --git a/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 index 98f135dc57..4899c4b59e 100644 --- a/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 @@ -5,9 +5,20 @@ import StaffActionLog from 'admin/models/staff-action-log'; export default Ember.Controller.extend({ loading: false, filters: null, + userHistoryActions: [], filtersExists: Ember.computed.gt('filterCount', 0), + filterActionIdChanged: function(){ + const filterActionId = this.get('filterActionId'); + if (filterActionId) { + this._changeFilters({ + action_name: this.get('userHistoryActions').findBy("id", parseInt(filterActionId,10)).name_raw, + action_id: filterActionId + }); + } + }.observes('filterActionId'), + actionFilter: function() { var name = this.get('filters.action_name'); if (name) { @@ -20,7 +31,6 @@ export default Ember.Controller.extend({ showInstructions: Ember.computed.gt('model.length', 0), refresh: function() { - var self = this; this.set('loading', true); var filters = this.get('filters'), @@ -37,10 +47,21 @@ export default Ember.Controller.extend({ }); this.set('filterCount', count); - StaffActionLog.findAll(params).then(function(result) { - self.set('model', result); - }).finally(function() { - self.set('loading', false); + StaffActionLog.findAll(params).then((result) => { + this.set('model', result.staff_action_logs); + if (this.get('userHistoryActions').length === 0) { + let actionTypes = result.user_history_actions.map(pair => { + return { + id: pair.id, + name: I18n.t("admin.logs.staff_actions.actions." + pair.name), + name_raw: pair.name + }; + }); + actionTypes = _.sortBy(actionTypes, row => row.name); + this.set('userHistoryActions', actionTypes); + } + }).finally(()=>{ + this.set('loading', false); }); }, @@ -63,6 +84,7 @@ export default Ember.Controller.extend({ changed.action_name = null; changed.action_id = null; changed.custom_type = null; + this.set("filterActionId", null); } else { changed[key] = null; } @@ -70,6 +92,7 @@ export default Ember.Controller.extend({ }, clearAllFilters: function() { + this.set("filterActionId", null); this.resetFilters(); }, 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 175463c0a1..834ed3912b 100644 --- a/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 @@ -3,6 +3,7 @@ import CanCheckEmails from 'discourse/mixins/can-check-emails'; import { propertyNotEqual, setting } from 'discourse/lib/computed'; import { userPath } from 'discourse/lib/url'; import { popupAjaxError } from 'discourse/lib/ajax-error'; +import computed from 'ember-addons/ember-computed-decorators'; export default Ember.Controller.extend(CanCheckEmails, { editingUsername: false, @@ -34,6 +35,11 @@ export default Ember.Controller.extend(CanCheckEmails, { return []; }.property('model.user_fields.[]'), + @computed('model.username_lower') + preferencesPath(username) { + return userPath(`${username}/preferences`); + }, + actions: { impersonate() { return this.get("model").impersonate(); }, 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 f108dd21d8..e60d815f7c 100644 --- a/app/assets/javascripts/admin/models/staff-action-log.js.es6 +++ b/app/assets/javascripts/admin/models/staff-action-log.js.es6 @@ -57,10 +57,13 @@ StaffActionLog.reopenClass({ }, findAll: function(filters) { - return ajax("/admin/logs/staff_action_logs.json", { data: filters }).then(function(staff_actions) { - return staff_actions.map(function(s) { - return StaffActionLog.create(s); - }); + return ajax("/admin/logs/staff_action_logs.json", { data: filters }).then((data) => { + return { + staff_action_logs: data.staff_action_logs.map(function(s) { + return StaffActionLog.create(s); + }), + user_history_actions: data.user_history_actions + }; }); } }); diff --git a/app/assets/javascripts/admin/templates/flags-list.hbs b/app/assets/javascripts/admin/templates/flags-list.hbs index e315ce0b9e..7edc1b30f2 100644 --- a/app/assets/javascripts/admin/templates/flags-list.hbs +++ b/app/assets/javascripts/admin/templates/flags-list.hbs @@ -3,7 +3,6 @@ - @@ -13,30 +12,42 @@ {{#each content as |flaggedPost|}} - - @@ -104,7 +115,6 @@ {{#each flaggedPost.conversations as |c|}} - -
{{i18n 'admin.flags.flagged_by'}} {{#if adminOldFlagsView}}{{i18n 'admin.flags.resolved_by'}}{{/if}}
- {{#if flaggedPost.postAuthorFlagged}} - {{#if flaggedPost.user}} - {{#link-to 'adminUser' flaggedPost.user}}{{avatar flaggedPost.user imageSize="large"}}{{/link-to}} - {{#if flaggedPost.wasEdited}}{{/if}} - {{/if}} - {{/if}} - {{#if adminActiveFlagsView}} - {{#if flaggedPost.previous_flags_count}} - {{flaggedPost.previous_flags_count}} - {{/if}} - {{/if}} - -

- {{#if flaggedPost.topic.isPrivateMessage}} - {{fa-icon "envelope"}} + +
+
+ {{#if flaggedPost.postAuthorFlagged}} + {{#if flaggedPost.user}} + {{#link-to 'adminUser' flaggedPost.user}}{{avatar flaggedPost.user imageSize="large"}}{{/link-to}} + {{#if flaggedPost.wasEdited}}{{/if}} + {{/if}} + {{/if}} + {{#if adminActiveFlagsView}} + {{#if flaggedPost.previous_flags_count}} + {{flaggedPost.previous_flags_count}} + {{/if}} + {{/if}} +
+
+

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

+ {{#unless site.mobileView}} + {{#if flaggedPost.postAuthorFlagged}} +

{{{flaggedPost.excerpt}}}

+ {{/if}} + {{/unless}} +
+
+ + {{#if site.mobileView}} + {{#if flaggedPost.postAuthorFlagged}} +

{{{flaggedPost.excerpt}}}

{{/if}} - {{topic-status topic=flaggedPost.topic}} - {{{unbound flaggedPost.topic.fancyTitle}}} -

- {{#if flaggedPost.postAuthorFlagged}} -

{{{flaggedPost.excerpt}}}

{{/if}}
{{#if c.response}} @@ -130,7 +140,7 @@ {{#unless adminOldFlagsView}}
+ {{#if adminActiveFlagsView}} {{#if flaggedPost.postHidden}} diff --git a/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs b/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs index 18a622f958..f469ec266e 100644 --- a/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs +++ b/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs @@ -1,41 +1,43 @@ -
- {{d-button action="exportStaffActionLogs" label="admin.export_csv.button_text" icon="download"}} -
-
- -
- {{i18n 'admin.logs.staff_actions.instructions'}} +
+ {{d-button action="exportStaffActionLogs" label="admin.export_csv.button_text" icon="download"}} +
+
diff --git a/app/assets/javascripts/admin/templates/user-index.hbs b/app/assets/javascripts/admin/templates/user-index.hbs index 069d47ecb2..f6a7a2d75e 100644 --- a/app/assets/javascripts/admin/templates/user-index.hbs +++ b/app/assets/javascripts/admin/templates/user-index.hbs @@ -99,6 +99,9 @@
{{i18n 'user.avatar.title'}}
{{avatar content imageSize="large"}}
+
+ {{{i18n "admin.user.visit_profile" url=preferencesPath}}} +
diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index 70e1d4ee64..6c10b0b1dc 100644 --- a/app/assets/javascripts/discourse/controllers/composer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/composer.js.es6 @@ -3,11 +3,11 @@ import Quote from 'discourse/lib/quote'; import Draft from 'discourse/models/draft'; import Composer from 'discourse/models/composer'; import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; -import { relativeAge } from 'discourse/lib/formatter'; import InputValidation from 'discourse/models/input-validation'; import { getOwner } from 'discourse-common/lib/get-owner'; import { escapeExpression } from 'discourse/lib/utilities'; import { emojiUnescape } from 'discourse/lib/text'; +import { shortDate } from 'discourse/lib/formatter'; function loadDraft(store, opts) { opts = opts || {}; @@ -239,7 +239,7 @@ export default Ember.Controller.extend({ domain: info.domain, username: info.username, post_url: topic.urlForPostNumber(info.post_number), - ago: relativeAge(moment(info.posted_at).toDate(), { format: 'medium' }) + ago: shortDate(info.posted_at) }); this.appEvents.trigger('composer-messages:create', { extraClass: 'custom-body', @@ -485,7 +485,7 @@ export default Ember.Controller.extend({ if (this.get('model.action') === 'edit') { this.appEvents.trigger('post-stream:refresh', { id: parseInt(result.responseJson.id) }); if (result.responseJson.post.post_number === 1) { - this.appEvents.trigger('header:show-topic', composer.get('topic')); + this.appEvents.trigger('header:update-topic', composer.get('topic')); } } else { this.appEvents.trigger('post-stream:refresh'); diff --git a/app/assets/javascripts/discourse/controllers/create-account.js.es6 b/app/assets/javascripts/discourse/controllers/create-account.js.es6 index d77a4db489..956b9bf35b 100644 --- a/app/assets/javascripts/discourse/controllers/create-account.js.es6 +++ b/app/assets/javascripts/discourse/controllers/create-account.js.es6 @@ -6,9 +6,10 @@ import { emailValid } from 'discourse/lib/utilities'; import InputValidation from 'discourse/models/input-validation'; import PasswordValidation from "discourse/mixins/password-validation"; import UsernameValidation from "discourse/mixins/username-validation"; +import NameValidation from "discourse/mixins/name-validation"; import { userPath } from 'discourse/lib/url'; -export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, UsernameValidation, { +export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, UsernameValidation, NameValidation, { login: Ember.inject.controller(), complete: false, @@ -85,15 +86,6 @@ export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, U return I18n.t(Discourse.SiteSettings.full_name_required ? 'user.name.instructions_required' : 'user.name.instructions'); }.property(), - // Validate the name. - nameValidation: function() { - if (Discourse.SiteSettings.full_name_required && Ember.isEmpty(this.get('accountName'))) { - return InputValidation.create({ failed: true }); - } - - return InputValidation.create({ok: true}); - }.property('accountName'), - // Check the email address emailValidation: function() { // If blank, fail without a reason diff --git a/app/assets/javascripts/discourse/controllers/invites-show.js.es6 b/app/assets/javascripts/discourse/controllers/invites-show.js.es6 index f6f5fa8150..d032c76985 100644 --- a/app/assets/javascripts/discourse/controllers/invites-show.js.es6 +++ b/app/assets/javascripts/discourse/controllers/invites-show.js.es6 @@ -4,9 +4,10 @@ import DiscourseURL from 'discourse/lib/url'; import { ajax } from 'discourse/lib/ajax'; import PasswordValidation from "discourse/mixins/password-validation"; import UsernameValidation from "discourse/mixins/username-validation"; +import NameValidation from "discourse/mixins/name-validation"; import { findAll as findLoginMethods } from 'discourse/models/login-method'; -export default Ember.Controller.extend(PasswordValidation, UsernameValidation, { +export default Ember.Controller.extend(PasswordValidation, UsernameValidation, NameValidation, { invitedBy: Ember.computed.alias('model.invited_by'), email: Ember.computed.alias('model.email'), accountUsername: Ember.computed.alias('model.username'), @@ -20,6 +21,11 @@ export default Ember.Controller.extend(PasswordValidation, UsernameValidation, { return I18n.t('invites.welcome_to', {site_name: this.siteSettings.title}); }, + @computed + nameLabel() { + return I18n.t(this.siteSettings.full_name_required ? 'invites.name_label' : 'invites.name_label_optional'); + }, + @computed('email') yourEmailMessage(email) { return I18n.t('invites.your_email', {email: email}); @@ -30,9 +36,9 @@ export default Ember.Controller.extend(PasswordValidation, UsernameValidation, { return findLoginMethods(this.siteSettings, this.capabilities, this.site.isMobileDevice).length > 0; }, - @computed('usernameValidation.failed', 'passwordValidation.failed') - submitDisabled(usernameFailed, passwordFailed) { - return usernameFailed || passwordFailed; + @computed('usernameValidation.failed', 'passwordValidation.failed', 'nameValidation.failed') + submitDisabled(usernameFailed, passwordFailed, nameFailed) { + return usernameFailed || passwordFailed || nameFailed; }, actions: { @@ -42,6 +48,7 @@ export default Ember.Controller.extend(PasswordValidation, UsernameValidation, { type: 'PUT', data: { username: this.get('accountUsername'), + name: this.get('accountName'), password: this.get('accountPassword') } }).then(result => { diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index 57c70024e0..8d36ea0e75 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -542,6 +542,7 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { } else { selectedReplies.removeObject(post); } + this.appEvents.trigger('post-stream:refresh', { force: true }); }, deleteSelected() { diff --git a/app/assets/javascripts/discourse/lib/load-script.js.es6 b/app/assets/javascripts/discourse/lib/load-script.js.es6 index 8df26b8bb6..ddcdaf3d77 100644 --- a/app/assets/javascripts/discourse/lib/load-script.js.es6 +++ b/app/assets/javascripts/discourse/lib/load-script.js.es6 @@ -35,6 +35,11 @@ export default function loadScript(url, opts) { opts = opts || {}; + $('script').each((i, tag) => { + _loaded[tag.getAttribute('src')] = true; + }); + + return new Ember.RSVP.Promise(function(resolve) { url = Discourse.getURL(url); @@ -42,7 +47,7 @@ export default function loadScript(url, opts) { if (_loaded[url]) { return resolve(); } if (_loading[url]) { return _loading[url].then(resolve);} - var done; + let done; _loading[url] = new Ember.RSVP.Promise(function(_done){ done = _done; }); @@ -60,7 +65,7 @@ export default function loadScript(url, opts) { resolve(); }; - var cdnUrl = url; + let cdnUrl = url; // Scripts should always load from CDN // CSS is type text, to accept it from a CDN we would need to handle CORS @@ -72,6 +77,9 @@ export default function loadScript(url, opts) { // to dynamically load more JS. In that case, add the `scriptTag: true` // option. if (opts.scriptTag) { + if (Ember.Test) { + throw `In test mode scripts cannot be loaded async ${cdnUrl}`; + } loadWithTag(cdnUrl, cb); } else { ajax({url: cdnUrl, dataType: opts.css ? "text": "script", cache: true}).then(cb); diff --git a/app/assets/javascripts/discourse/mixins/name-validation.js.es6 b/app/assets/javascripts/discourse/mixins/name-validation.js.es6 new file mode 100644 index 0000000000..8a286522df --- /dev/null +++ b/app/assets/javascripts/discourse/mixins/name-validation.js.es6 @@ -0,0 +1,15 @@ +import InputValidation from 'discourse/models/input-validation'; +import { default as computed } from 'ember-addons/ember-computed-decorators'; + +export default Ember.Mixin.create({ + + // Validate the name. + @computed('accountName') + nameValidation() { + if (this.siteSettings.full_name_required && Ember.isEmpty(this.get('accountName'))) { + return InputValidation.create({ failed: true }); + } + + return InputValidation.create({ok: true}); + } +}); diff --git a/app/assets/javascripts/discourse/mixins/username-validation.js.es6 b/app/assets/javascripts/discourse/mixins/username-validation.js.es6 index 91e9554c9f..515d32c313 100644 --- a/app/assets/javascripts/discourse/mixins/username-validation.js.es6 +++ b/app/assets/javascripts/discourse/mixins/username-validation.js.es6 @@ -40,7 +40,7 @@ export default Ember.Mixin.create({ } // If too short - if (accountUsername.length < Discourse.SiteSettings.min_username_length) { + if (accountUsername.length < this.siteSettings.min_username_length) { return InputValidation.create({ failed: true, reason: I18n.t('user.username.too_short') diff --git a/app/assets/javascripts/discourse/templates/account-created/index.hbs b/app/assets/javascripts/discourse/templates/account-created/index.hbs index ca16f10e43..8c9babb1f9 100644 --- a/app/assets/javascripts/discourse/templates/account-created/index.hbs +++ b/app/assets/javascripts/discourse/templates/account-created/index.hbs @@ -1,7 +1,7 @@
{{{accountCreated.message}}}
-{{#if accountCreated.username}} +{{#if accountCreated.show_controls}} {{activation-controls sendActivationEmail=(action "sendActivationEmail") editActivationEmail=(action "editActivationEmail")}} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/activation-controls.hbs b/app/assets/javascripts/discourse/templates/components/activation-controls.hbs index ccf263ad03..0b94c0ef94 100644 --- a/app/assets/javascripts/discourse/templates/components/activation-controls.hbs +++ b/app/assets/javascripts/discourse/templates/components/activation-controls.hbs @@ -1,7 +1,10 @@ -{{d-button action=sendActivationEmail - label="login.resend_title" - icon="envelope" - class="btn-primary resend"}} +{{#unless siteSettings.must_approve_users}} + {{d-button action=sendActivationEmail + label="login.resend_title" + icon="envelope" + class="btn-primary resend"}} +{{/unless}} + {{d-button action=editActivationEmail label="login.change_email" icon="pencil" diff --git a/app/assets/javascripts/discourse/templates/components/discourse-banner.hbs b/app/assets/javascripts/discourse/templates/components/discourse-banner.hbs index 306f2924b4..b46760d5c1 100644 --- a/app/assets/javascripts/discourse/templates/components/discourse-banner.hbs +++ b/app/assets/javascripts/discourse/templates/components/discourse-banner.hbs @@ -1,8 +1,8 @@ {{#if visible}}