diff --git a/Gemfile b/Gemfile index a513adb80e..cf25046564 100644 --- a/Gemfile +++ b/Gemfile @@ -36,7 +36,7 @@ gem 'redis-namespace' gem 'active_model_serializers', '~> 0.8.3' -gem 'onebox', '1.8.36' +gem 'onebox', '1.8.38' gem 'http_accept_language', '~>2.0.5', require: false @@ -59,7 +59,7 @@ gem 'aws-sdk-s3', require: false gem 'excon', require: false gem 'unf', require: false -gem 'email_reply_trimmer', '0.1.9' +gem 'email_reply_trimmer', '0.1.10' # Forked until https://github.com/toy/image_optim/pull/149 is merged gem 'discourse_image_optim', require: 'image_optim' @@ -67,9 +67,6 @@ gem 'multi_json' gem 'mustache' gem 'nokogiri' -# this may end up deprecating nokogiri -gem 'oga', require: false - gem 'omniauth' gem 'omniauth-openid' gem 'openid-redis-store' diff --git a/Gemfile.lock b/Gemfile.lock index b2397688ec..09d9ff3bb3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -41,7 +41,6 @@ GEM annotate (2.7.2) activerecord (>= 3.2, < 6.0) rake (>= 10.4, < 13.0) - ansi (1.5.0) arel (8.0.0) ast (2.3.0) aws-partitions (1.24.0) @@ -91,7 +90,7 @@ GEM image_size (~> 1.5) in_threads (~> 1.3) progress (~> 3.0, >= 3.0.1) - email_reply_trimmer (0.1.9) + email_reply_trimmer (0.1.10) ember-data-source (2.2.1) ember-source (>= 1.8, < 3.0) ember-handlebars-template (0.7.5) @@ -165,7 +164,7 @@ GEM lru_redux (1.1.0) mail (2.6.6) mime-types (>= 1.16, < 4) - memory_profiler (0.9.8) + memory_profiler (0.9.10) message_bus (2.1.2) rack (>= 1.1.3) metaclass (0.0.4) @@ -189,7 +188,7 @@ GEM multi_xml (0.6.0) multipart-post (2.0.0) mustache (1.0.5) - nokogiri (1.8.1) + nokogiri (1.8.2) mini_portile2 (~> 2.3.0) nokogumbo (1.4.13) nokogiri @@ -200,9 +199,6 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) - oga (2.10) - ast - ruby-ll (~> 2.1) oj (3.1.0) omniauth (1.6.1) hashie (>= 3.4.6, < 3.6.0) @@ -232,7 +228,7 @@ GEM omniauth-twitter (1.3.0) omniauth-oauth (~> 1.1) rack - onebox (1.8.36) + onebox (1.8.38) fast_blank (>= 1.0.0) htmlentities (~> 4.3) moneta (~> 1.0) @@ -275,7 +271,7 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.0.3) loofah (~> 2.0) - rails_multisite (2.0.2) + rails_multisite (2.0.4) activerecord (> 4.2, < 6) railties (> 4.2, < 6) railties (5.1.4) @@ -334,9 +330,6 @@ GEM rainbow (>= 2.2.2, < 3.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) - ruby-ll (2.1.2) - ansi - ast ruby-openid (2.7.0) ruby-prof (0.16.2) ruby-progressbar (1.9.0) @@ -421,7 +414,7 @@ DEPENDENCIES cppjieba_rb discourse-qunit-rails discourse_image_optim - email_reply_trimmer (= 0.1.9) + email_reply_trimmer (= 0.1.10) ember-handlebars-template (= 0.7.5) ember-rails (= 0.18.5) ember-source (= 2.13.3) @@ -459,7 +452,6 @@ DEPENDENCIES multi_json mustache nokogiri - oga oj omniauth omniauth-facebook @@ -469,7 +461,7 @@ DEPENDENCIES omniauth-oauth2 omniauth-openid omniauth-twitter - onebox (= 1.8.36) + onebox (= 1.8.38) openid-redis-store pg (~> 0.21.0) pry-nav diff --git a/app/assets/javascripts/admin/components/flagged-post.js.es6 b/app/assets/javascripts/admin/components/flagged-post.js.es6 index aaa4759e82..8aff3d2a2d 100644 --- a/app/assets/javascripts/admin/components/flagged-post.js.es6 +++ b/app/assets/javascripts/admin/components/flagged-post.js.es6 @@ -4,8 +4,6 @@ import computed from 'ember-addons/ember-computed-decorators'; export default Ember.Component.extend({ adminTools: Ember.inject.service(), expanded: false, - suspended: false, - tagName: 'div', classNameBindings: [ ':flagged-post', @@ -21,12 +19,7 @@ export default Ember.Component.extend({ }, removeAfter(promise) { - return promise.then(() => { - this.attrs.removePost(); - }).catch(error => { - if (error._discourse_displayed) { return; } - bootbox.alert(I18n.t("admin.flags.error")); - }); + return promise.then(() => this.attrs.removePost()); }, _spawnModal(name, model, modalClass) { @@ -36,7 +29,7 @@ export default Ember.Component.extend({ actions: { removeAfter(promise) { - this.removeAfter(promise); + return this.removeAfter(promise); }, disagree() { @@ -58,18 +51,6 @@ export default Ember.Component.extend({ filter: 'post', post_id: this.get('flaggedPost.id') }); - }, - - showSuspendModal() { - let post = this.get('flaggedPost'); - let user = post.get('user'); - this.get('adminTools').showSuspendModal( - user, - { - post, - successCallback: result => this.set('suspended', result.suspended) - } - ); } } }); diff --git a/app/assets/javascripts/admin/components/penalty-post-action.js.es6 b/app/assets/javascripts/admin/components/penalty-post-action.js.es6 new file mode 100644 index 0000000000..d89c69a32d --- /dev/null +++ b/app/assets/javascripts/admin/components/penalty-post-action.js.es6 @@ -0,0 +1,32 @@ +import computed from 'ember-addons/ember-computed-decorators'; + +const ACTIONS = ['delete', 'edit', 'none']; +export default Ember.Component.extend({ + postAction: null, + postEdit: null, + + @computed + penaltyActions() { + return ACTIONS.map(id => { + return { id, name: I18n.t(`admin.user.penalty_post_${id}`) }; + }); + }, + + editing: Ember.computed.equal('postAction', 'edit'), + + actions: { + penaltyChanged() { + let postAction = this.get('postAction'); + + // If we switch to edit mode, jump to the edit textarea + if (postAction === 'edit') { + Ember.run.scheduleOnce('afterRender', () => { + let $elem = this.$(); + let body = $elem.closest('.modal-body'); + body.scrollTop(body.height()); + $elem.find('.post-editor').focus(); + }); + } + } + } +}); diff --git a/app/assets/javascripts/admin/components/staff-actions.js.es6 b/app/assets/javascripts/admin/components/staff-actions.js.es6 new file mode 100644 index 0000000000..9e742526af --- /dev/null +++ b/app/assets/javascripts/admin/components/staff-actions.js.es6 @@ -0,0 +1,22 @@ +import DiscourseURL from 'discourse/lib/url'; + +export default Ember.Component.extend({ + classNames: ['table', 'staff-actions'], + + willDestroyElement() { + this.$().off('click.discourse-staff-logs'); + }, + + didInsertElement() { + this._super(); + + this.$().on('click.discourse-staff-logs', '[data-link-post-id]', e => { + let postId = $(e.target).attr('data-link-post-id'); + + this.store.find('post', postId).then(p => { + DiscourseURL.routeTo(p.get('url')); + }); + return false; + }); + } +}); diff --git a/app/assets/javascripts/admin/controllers/modals/admin-silence-user.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-silence-user.js.es6 index 9f1aef916f..ec55cbdbe8 100644 --- a/app/assets/javascripts/admin/controllers/modals/admin-silence-user.js.es6 +++ b/app/assets/javascripts/admin/controllers/modals/admin-silence-user.js.es6 @@ -1,26 +1,13 @@ -import ModalFunctionality from 'discourse/mixins/modal-functionality'; import computed from 'ember-addons/ember-computed-decorators'; -import { popupAjaxError } from 'discourse/lib/ajax-error'; +import PenaltyController from 'admin/mixins/penalty-controller'; -export default Ember.Controller.extend(ModalFunctionality, { +export default Ember.Controller.extend(PenaltyController, { silenceUntil: null, - reason: null, - message: null, silencing: false, - user: null, - post: null, - successCallback: null, onShow() { - this.setProperties({ - silenceUntil: null, - reason: null, - message: null, - silencing: false, - loadingUser: true, - post: null, - successCallback: null, - }); + this.resetModal(); + this.setProperties({ silenceUntil: null, silencing: false }); }, @computed('silenceUntil', 'reason', 'silencing') @@ -33,18 +20,16 @@ export default Ember.Controller.extend(ModalFunctionality, { if (this.get('submitDisabled')) { return; } this.set('silencing', true); - this.get('user').silence({ - silenced_till: this.get('silenceUntil'), - reason: this.get('reason'), - message: this.get('message'), - post_id: this.get('post.id') - }).then(result => { - this.send('closeModal'); - let callback = this.get('successCallback'); - if (callback) { - callback(result); - } - }).catch(popupAjaxError).finally(() => this.set('silencing', false)); + this.penalize(() => { + return this.get('user').silence({ + silenced_till: this.get('silenceUntil'), + reason: this.get('reason'), + message: this.get('message'), + post_id: this.get('post.id'), + post_action: this.get('postAction'), + post_edit: this.get('postEdit') + }); + }).finally(() => this.set('silencing', false)); } } }); diff --git a/app/assets/javascripts/admin/controllers/modals/admin-suspend-user.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-suspend-user.js.es6 index efcd142670..f66a1eec69 100644 --- a/app/assets/javascripts/admin/controllers/modals/admin-suspend-user.js.es6 +++ b/app/assets/javascripts/admin/controllers/modals/admin-suspend-user.js.es6 @@ -1,26 +1,13 @@ -import ModalFunctionality from 'discourse/mixins/modal-functionality'; import computed from 'ember-addons/ember-computed-decorators'; -import { popupAjaxError } from 'discourse/lib/ajax-error'; +import PenaltyController from 'admin/mixins/penalty-controller'; -export default Ember.Controller.extend(ModalFunctionality, { +export default Ember.Controller.extend(PenaltyController, { suspendUntil: null, - reason: null, - message: null, suspending: false, - user: null, - post: null, - successCallback: null, onShow() { - this.setProperties({ - suspendUntil: null, - reason: null, - message: null, - suspending: false, - loadingUser: true, - post: null, - successCallback: null, - }); + this.resetModal(); + this.setProperties({ suspendUntil: null, suspending: false }); }, @computed('suspendUntil', 'reason', 'suspending') @@ -33,19 +20,17 @@ export default Ember.Controller.extend(ModalFunctionality, { if (this.get('submitDisabled')) { return; } this.set('suspending', true); - this.get('user').suspend({ - suspend_until: this.get('suspendUntil'), - reason: this.get('reason'), - message: this.get('message'), - post_id: this.get('post.id') - }).then(result => { - this.send('closeModal'); - let callback = this.get('successCallback'); - if (callback) { - callback(result); - } - }).catch(popupAjaxError).finally(() => this.set('suspending', false)); + + this.penalize(() => { + return this.get('user').suspend({ + suspend_until: this.get('suspendUntil'), + reason: this.get('reason'), + message: this.get('message'), + post_id: this.get('post.id'), + post_action: this.get('postAction'), + post_edit: this.get('postEdit') + }); + }).finally(() => this.set('suspending', false)); } } - }); diff --git a/app/assets/javascripts/admin/mixins/penalty-controller.js.es6 b/app/assets/javascripts/admin/mixins/penalty-controller.js.es6 new file mode 100644 index 0000000000..8b5bbb0629 --- /dev/null +++ b/app/assets/javascripts/admin/mixins/penalty-controller.js.es6 @@ -0,0 +1,41 @@ +import ModalFunctionality from 'discourse/mixins/modal-functionality'; +import { popupAjaxError } from 'discourse/lib/ajax-error'; + +export default Ember.Mixin.create(ModalFunctionality, { + reason: null, + message: null, + postEdit: null, + postAction: null, + user: null, + post: null, + successCallback: null, + + resetModal() { + this.setProperties({ + reason: null, + message: null, + loadingUser: true, + post: null, + postEdit: null, + postAction: 'delete', + before: null, + successCallback: null + }); + }, + + penalize(cb) { + let before = this.get('before'); + let promise = before ? before() : Ember.RSVP.resolve(); + + return promise + .then(() => cb()) + .then(result => { + this.send('closeModal'); + let callback = this.get('successCallback'); + if (callback) { + callback(result); + } + }) + .catch(popupAjaxError); + } +}); 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 e60d815f7c..78d39869cf 100644 --- a/app/assets/javascripts/admin/models/staff-action-log.js.es6 +++ b/app/assets/javascripts/admin/models/staff-action-log.js.es6 @@ -10,7 +10,7 @@ const StaffActionLog = Discourse.Model.extend({ }.property('action_name'), formattedDetails: function() { - var formatted = ""; + let formatted = ""; formatted += this.format('email', 'email'); formatted += this.format('admin.logs.ip_address', 'ip_address'); formatted += this.format('admin.logs.topic_id', 'topic_id'); @@ -26,9 +26,13 @@ const StaffActionLog = Discourse.Model.extend({ return formatted; }.property('ip_address', 'email', 'topic_id', 'post_id', 'category_id'), - format: function(label, propertyName) { + format(label, propertyName) { if (this.get(propertyName)) { - return ('' + I18n.t(label) + ': ' + escapeExpression(this.get(propertyName)) + '
'); + let value = escapeExpression(this.get(propertyName)); + if (propertyName === 'post_id') { + value = `${value}`; + } + return `${I18n.t(label)}: ${value}
`; } else { return ''; } diff --git a/app/assets/javascripts/admin/services/admin-tools.js.es6 b/app/assets/javascripts/admin/services/admin-tools.js.es6 index a6a56d0f14..9f9bec8b03 100644 --- a/app/assets/javascripts/admin/services/admin-tools.js.es6 +++ b/app/assets/javascripts/admin/services/admin-tools.js.es6 @@ -52,7 +52,10 @@ export default Ember.Service.extend({ modalClass: `${type}-user-modal` }); if (opts.post) { - controller.set('post', opts.post); + controller.setProperties({ + post: opts.post, + postEdit: opts.post.get('raw') + }); } return (user.adminUserView ? @@ -62,6 +65,7 @@ export default Ember.Service.extend({ controller.setProperties({ user: loadedUser, loadingUser: false, + before: opts.before, successCallback: opts.successCallback }); }); diff --git a/app/assets/javascripts/admin/templates/backups.hbs b/app/assets/javascripts/admin/templates/backups.hbs index 5cfd123114..33229e2253 100644 --- a/app/assets/javascripts/admin/templates/backups.hbs +++ b/app/assets/javascripts/admin/templates/backups.hbs @@ -1,12 +1,12 @@
-
+
+
{{#if model.canRollback}} {{d-button action="rollback" diff --git a/app/assets/javascripts/admin/templates/badges-index.hbs b/app/assets/javascripts/admin/templates/badges-index.hbs index 674eae8de2..4a71e7d69a 100644 --- a/app/assets/javascripts/admin/templates/badges-index.hbs +++ b/app/assets/javascripts/admin/templates/badges-index.hbs @@ -1,4 +1,4 @@ -{{#d-section class="current-badge span13"}} +{{#d-section class="current-badge content-body"}}

{{i18n 'admin.badges.none_selected'}}

diff --git a/app/assets/javascripts/admin/templates/badges-show.hbs b/app/assets/javascripts/admin/templates/badges-show.hbs index 94239f9d03..6fa158d0f3 100644 --- a/app/assets/javascripts/admin/templates/badges-show.hbs +++ b/app/assets/javascripts/admin/templates/badges-show.hbs @@ -1,4 +1,4 @@ -{{#d-section class="current-badge span13"}} +{{#d-section class="current-badge content-body"}}
@@ -144,7 +144,7 @@ {{/d-section}} {{#if grant_count}} -
+
{{#link-to 'badges.show' this}}{{i18n 'badges.granted' count=grant_count}}{{/link-to}}
diff --git a/app/assets/javascripts/admin/templates/badges.hbs b/app/assets/javascripts/admin/templates/badges.hbs index aeeaac9c9e..b5789d4c42 100644 --- a/app/assets/javascripts/admin/templates/badges.hbs +++ b/app/assets/javascripts/admin/templates/badges.hbs @@ -1,6 +1,6 @@
-
+

{{i18n 'admin.badges.title'}}

    {{#each model as |badge|}} diff --git a/app/assets/javascripts/admin/templates/components/admin-nav.hbs b/app/assets/javascripts/admin/templates/components/admin-nav.hbs index c5b1d3b58a..eb705949e5 100644 --- a/app/assets/javascripts/admin/templates/components/admin-nav.hbs +++ b/app/assets/javascripts/admin/templates/components/admin-nav.hbs @@ -1,7 +1,7 @@
    -
    +
    +
    diff --git a/app/assets/javascripts/admin/templates/components/flagged-post.hbs b/app/assets/javascripts/admin/templates/components/flagged-post.hbs index 04b953c451..37631f1442 100644 --- a/app/assets/javascripts/admin/templates/components/flagged-post.hbs +++ b/app/assets/javascripts/admin/templates/components/flagged-post.hbs @@ -68,12 +68,6 @@ {{flag-user-lists flaggedPost=flaggedPost showResolvedBy=showResolvedBy}} - {{#if suspended}} -
    - {{i18n "admin.flags.suspended_for_post"}} -
    - {{/if}} -
    {{#if canAct}} {{admin-agree-flag-dropdown @@ -106,15 +100,6 @@ {{admin-delete-flag-dropdown post=flaggedPost removeAfter=(action "removeAfter")}} - - {{#unless suspended}} - {{d-button - class="btn-danger suspend-user" - icon="ban" - label="admin.flags.suspend_user" - title="admin.flags.suspend_user_title" - action=(action "showSuspendModal")}} - {{/unless}} {{/if}} {{d-button diff --git a/app/assets/javascripts/admin/templates/components/penalty-post-action.hbs b/app/assets/javascripts/admin/templates/components/penalty-post-action.hbs new file mode 100644 index 0000000000..1c8ffe56d8 --- /dev/null +++ b/app/assets/javascripts/admin/templates/components/penalty-post-action.hbs @@ -0,0 +1,16 @@ +
    + + {{combo-box value=postAction content=penaltyActions onSelect=(action "penaltyChanged")}} +
    + +{{#if editing}} +
    + {{textarea + value=postEdit + class="post-editor"}} +
    +{{/if}} diff --git a/app/assets/javascripts/admin/templates/customize-colors.hbs b/app/assets/javascripts/admin/templates/customize-colors.hbs index 920f6f1954..761996b6cb 100644 --- a/app/assets/javascripts/admin/templates/customize-colors.hbs +++ b/app/assets/javascripts/admin/templates/customize-colors.hbs @@ -1,4 +1,4 @@ -
    +

    {{i18n 'admin.customize.colors.long_title'}}

      {{#each model as |scheme|}} diff --git a/app/assets/javascripts/admin/templates/customize-email-templates.hbs b/app/assets/javascripts/admin/templates/customize-email-templates.hbs index 152c02e44a..7dd95b092f 100644 --- a/app/assets/javascripts/admin/templates/customize-email-templates.hbs +++ b/app/assets/javascripts/admin/templates/customize-email-templates.hbs @@ -1,5 +1,5 @@
      -
      +
        {{#each sortedTemplates as |et|}}
      • diff --git a/app/assets/javascripts/admin/templates/customize-themes.hbs b/app/assets/javascripts/admin/templates/customize-themes.hbs index c9474917ef..e2f9f8b99a 100644 --- a/app/assets/javascripts/admin/templates/customize-themes.hbs +++ b/app/assets/javascripts/admin/templates/customize-themes.hbs @@ -1,5 +1,5 @@ {{#unless editingTheme}} -
        +

        {{i18n 'admin.customize.theme.long_title'}}

          {{#each sortedThemes as |theme|}} diff --git a/app/assets/javascripts/admin/templates/email-index.hbs b/app/assets/javascripts/admin/templates/email-index.hbs index 7fc5ce5d83..80b3d5d062 100644 --- a/app/assets/javascripts/admin/templates/email-index.hbs +++ b/app/assets/javascripts/admin/templates/email-index.hbs @@ -19,7 +19,7 @@
          {{text-field value=testEmailAddress placeholderKey="admin.email.test_email_address"}}
          -
          +
          {{#if sentTestEmail}}{{i18n 'admin.email.sent_test'}}{{/if}}
          diff --git a/app/assets/javascripts/admin/templates/emojis.hbs b/app/assets/javascripts/admin/templates/emojis.hbs index 675e8249be..4cf568fd45 100644 --- a/app/assets/javascripts/admin/templates/emojis.hbs +++ b/app/assets/javascripts/admin/templates/emojis.hbs @@ -6,7 +6,7 @@

          {{emoji-uploader done="emojiUploaded"}}

          {{#if sortedEmojis}} -
          +
          diff --git a/app/assets/javascripts/admin/templates/groups-type.hbs b/app/assets/javascripts/admin/templates/groups-type.hbs index a1fa829897..47bbb1c51e 100644 --- a/app/assets/javascripts/admin/templates/groups-type.hbs +++ b/app/assets/javascripts/admin/templates/groups-type.hbs @@ -1,6 +1,6 @@
          {{#if sortedGroups}} -
          +

          {{i18n 'admin.groups.edit'}}

            {{#each sortedGroups as |group|}} @@ -25,7 +25,7 @@
          {{/if}} -
          +
          {{outlet}}
          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 5df150c79f..3445abf8df 100644 --- a/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs +++ b/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs @@ -39,7 +39,7 @@
          -
          +{{#staff-actions}}
          {{i18n 'admin.logs.staff_actions.staff_user'}}
          {{i18n 'admin.logs.action'}}
          @@ -86,4 +86,4 @@ {{i18n 'search.no_results'}} {{/each}} {{/conditional-loading-spinner}} -
          +{{/staff-actions}} diff --git a/app/assets/javascripts/admin/templates/modal/admin-silence-user.hbs b/app/assets/javascripts/admin/templates/modal/admin-silence-user.hbs index 6b3b899ac8..b31251a91d 100644 --- a/app/assets/javascripts/admin/templates/modal/admin-silence-user.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin-silence-user.hbs @@ -12,6 +12,12 @@
          {{silence-details reason=reason message=message}} + {{#if post}} + {{penalty-post-action + post=post + postAction=postAction + postEdit=postEdit}} + {{/if}} {{/conditional-loading-spinner}} diff --git a/app/assets/javascripts/admin/templates/modal/admin-suspend-user.hbs b/app/assets/javascripts/admin/templates/modal/admin-suspend-user.hbs index 4c0af11512..6c2e8e1636 100644 --- a/app/assets/javascripts/admin/templates/modal/admin-suspend-user.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin-suspend-user.hbs @@ -13,6 +13,13 @@
          {{suspension-details reason=reason message=message}} + {{#if post}} + {{penalty-post-action + post=post + postAction=postAction + postEdit=postEdit}} + {{/if}} + {{else}}
          {{i18n "admin.user.cant_suspend"}} diff --git a/app/assets/javascripts/admin/templates/user-badges.hbs b/app/assets/javascripts/admin/templates/user-badges.hbs index 167f4ffa1d..52faf9b433 100644 --- a/app/assets/javascripts/admin/templates/user-badges.hbs +++ b/app/assets/javascripts/admin/templates/user-badges.hbs @@ -1,9 +1,9 @@
          -
          +
          +
          {{#conditional-loading-spinner condition=loading}} diff --git a/app/assets/javascripts/admin/templates/user-index.hbs b/app/assets/javascripts/admin/templates/user-index.hbs index 24122f3877..28b526da55 100644 --- a/app/assets/javascripts/admin/templates/user-index.hbs +++ b/app/assets/javascripts/admin/templates/user-index.hbs @@ -124,7 +124,7 @@
          -
          +
          {{i18n 'user.ip_address.title'}}
          {{model.ip_address}}
          @@ -135,7 +135,7 @@
          -
          +
          {{i18n 'user.registration_ip_address.title'}}
          {{model.registration_ip_address}}
          diff --git a/app/assets/javascripts/admin/templates/user-tl3-requirements.hbs b/app/assets/javascripts/admin/templates/user-tl3-requirements.hbs index 1561a95670..1a848710a3 100644 --- a/app/assets/javascripts/admin/templates/user-tl3-requirements.hbs +++ b/app/assets/javascripts/admin/templates/user-tl3-requirements.hbs @@ -1,10 +1,10 @@
          -
          +
          +
          diff --git a/app/assets/javascripts/admin/templates/users-list.hbs b/app/assets/javascripts/admin/templates/users-list.hbs index 398732685b..645fc83c1f 100644 --- a/app/assets/javascripts/admin/templates/users-list.hbs +++ b/app/assets/javascripts/admin/templates/users-list.hbs @@ -1,5 +1,5 @@
          -
          +
          +
          {{#unless siteSettings.enable_sso}} {{d-button action="sendInvites" title="admin.invite.button_title" icon="user-plus" label="admin.invite.button_text"}} diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 02bc257b12..c6ca0b0c90 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -25,6 +25,7 @@ //= require ./discourse/lib/key-value-store //= require ./discourse/lib/computed //= require ./discourse/lib/formatter +//= require ./discourse/lib/text-direction //= require ./discourse/lib/eyeline //= require ./discourse/lib/show-modal //= require ./discourse/mixins/scrolling diff --git a/app/assets/javascripts/discourse-common/lib/icon-library.js.es6 b/app/assets/javascripts/discourse-common/lib/icon-library.js.es6 index 84242d03f6..aa3f057e00 100644 --- a/app/assets/javascripts/discourse-common/lib/icon-library.js.es6 +++ b/app/assets/javascripts/discourse-common/lib/icon-library.js.es6 @@ -90,7 +90,7 @@ registerIconRenderer({ if (params.label) { html += " aria-hidden='true'"; } html += `>`; if (params.label) { - html += "" + I18n.t(params.label) + ""; + html += `${params.label}`; } return html; }, diff --git a/app/assets/javascripts/discourse/components/composer-action-title.js.es6 b/app/assets/javascripts/discourse/components/composer-action-title.js.es6 new file mode 100644 index 0000000000..2bc6927300 --- /dev/null +++ b/app/assets/javascripts/discourse/components/composer-action-title.js.es6 @@ -0,0 +1,63 @@ +import { default as computed } from 'ember-addons/ember-computed-decorators'; +import { PRIVATE_MESSAGE, CREATE_TOPIC, REPLY, EDIT } from "discourse/models/composer"; +import { iconHTML } from 'discourse-common/lib/icon-library'; + +export default Ember.Component.extend({ + classNames: ["composer-action-title"], + options: Ember.computed.alias("model.replyOptions"), + action: Ember.computed.alias("model.action"), + isEditing: Ember.computed.equal("action", EDIT), + + @computed("options", "action") + actionTitle(opts, action) { + switch (action) { + case PRIVATE_MESSAGE: + return I18n.t("topic.private_message"); + case CREATE_TOPIC: + return I18n.t("topic.create_long"); + case REPLY: + if (opts.userAvatar && opts.userLink) { + return this._formatReplyToUserPost(opts.userAvatar, opts.userLink); + } else if (opts.topicLink) { + return this._formatReplyToTopic(opts.topicLink); + } + case EDIT: + if (opts.userAvatar && opts.userLink && opts.postLink) { + return this._formatEditUserPost( + opts.userAvatar, + opts.userLink, + opts.postLink, + opts.originalUser + ); + } + }; + }, + + _formatEditUserPost(userAvatar, userLink, postLink, originalUser) { + let editTitle = ` + ${postLink.anchor} + ${userAvatar} + ${userLink.anchor} + ${iconHTML("mail-forward", { class: "reply-to-glyph" })} + `; + + if (originalUser) { + editTitle += ` + ${originalUser.avatar} + ${originalUser.username} + `; + } + + return editTitle.htmlSafe(); + }, + + _formatReplyToTopic(link) { + return `${link.anchor}`.htmlSafe(); + }, + + _formatReplyToUserPost(avatar, link) { + const htmlLink = `${link.anchor}`; + return `${avatar}${htmlLink}`.htmlSafe(); + }, + +}); diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6 index c70f0b3bc9..f5c30f3b52 100644 --- a/app/assets/javascripts/discourse/components/d-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/d-editor.js.es6 @@ -8,6 +8,7 @@ import { emojiSearch, isSkinTonableEmoji } from 'pretty-text/emoji'; import { emojiUrlFor } from 'discourse/lib/text'; import { getRegister } from 'discourse-common/lib/get-owner'; import { findRawTemplate } from 'discourse/lib/raw-templates'; +import { siteDir } from 'discourse/lib/text-direction'; import { determinePostReplaceSelection, clipboardData } from 'discourse/lib/utilities'; import toMarkdown from 'discourse/lib/to-markdown'; import deprecated from 'discourse-common/lib/deprecated'; @@ -44,7 +45,8 @@ const isInside = (text, regex) => { class Toolbar { - constructor(site) { + constructor(opts) { + const { site, siteSettings } = opts; this.shortcuts = {}; this.groups = [ @@ -73,7 +75,14 @@ class Toolbar { perform: e => e.applySurround('_', '_', 'italic_text') }); - this.addButton({id: 'link', group: 'insertions', shortcut: 'K', action: 'showLinkModal'}); + if (opts.showLink) { + this.addButton({ + id: 'link', + group: 'insertions', + shortcut: 'K', + action: 'showLinkModal' + }); + } this.addButton({ id: 'quote', @@ -107,6 +116,17 @@ class Toolbar { perform: e => e.applyList(i => !i ? "1. " : `${parseInt(i) + 1}. `, 'list_item') }); + if (siteSettings.support_mixed_text_direction) { + this.addButton({ + id: 'toggle-direction', + group: 'extras', + icon: 'exchange', + shortcut: 'Shift+6', + title: 'composer.toggle_direction', + perform: e => e.toggleDirection(), + }); + } + if (site.mobileView) { this.groups.push({group: 'mobileExtras', buttons: []}); } @@ -188,6 +208,7 @@ export default Ember.Component.extend({ lastSel: null, _mouseTrap: null, emojiPickerIsActive: false, + showLink: true, @computed('placeholder') placeholderTranslated(placeholder) { @@ -267,7 +288,9 @@ export default Ember.Component.extend({ @computed toolbar() { - const toolbar = new Toolbar(this.site); + const toolbar = new Toolbar( + this.getProperties('site', 'siteSettings', 'showLink') + ); _createCallbacks.forEach(cb => cb(toolbar)); this.sendAction('extraButtons', toolbar); return toolbar; @@ -647,6 +670,14 @@ export default Ember.Component.extend({ return null; }, + _toggleDirection() { + const $textArea = $(".d-editor-input"); + let currentDir = $textArea.attr('dir') ? $textArea.attr('dir') : siteDir(), + newDir = currentDir === 'ltr' ? 'rtl' : 'ltr'; + + $textArea.attr('dir', newDir).focus(); + }, + paste(e) { if (!$(".d-editor-input").is(":focus")) { return; @@ -724,6 +755,7 @@ export default Ember.Component.extend({ addText: text => this._addText(selected, text), replaceText: text => this._addText({pre: '', post: ''}, text), getText: () => this.get('value'), + toggleDirection: () => this._toggleDirection(), }; if (button.sendAction) { diff --git a/app/assets/javascripts/discourse/components/flag-action-type.js.es6 b/app/assets/javascripts/discourse/components/flag-action-type.js.es6 index 7f0ffb70ed..fe2b5c601a 100644 --- a/app/assets/javascripts/discourse/components/flag-action-type.js.es6 +++ b/app/assets/javascripts/discourse/components/flag-action-type.js.es6 @@ -34,13 +34,13 @@ export default Ember.Component.extend({ @computed('message.length') customMessageLengthClasses(messageLength) { - return (messageLength < Discourse.SiteSettings.min_private_message_post_length) ? "too-short" : "ok"; + return (messageLength < Discourse.SiteSettings.min_personal_message_post_length) ? "too-short" : "ok"; }, @computed('message.length') customMessageLength(messageLength) { const len = messageLength || 0; - const minLen = Discourse.SiteSettings.min_private_message_post_length; + const minLen = Discourse.SiteSettings.min_personal_message_post_length; if (len === 0) { return I18n.t("flagging.custom_message.at_least", { count: minLen }); } else if (len < minLen) { diff --git a/app/assets/javascripts/discourse/components/group-post.js.es6 b/app/assets/javascripts/discourse/components/group-post.js.es6 new file mode 100644 index 0000000000..51e67b8490 --- /dev/null +++ b/app/assets/javascripts/discourse/components/group-post.js.es6 @@ -0,0 +1,6 @@ +import computed from 'ember-addons/ember-computed-decorators'; + +export default Ember.Component.extend({ + @computed('post.url') + postUrl: Discourse.getURL +}); diff --git a/app/assets/javascripts/discourse/components/tag-chooser.js.es6 b/app/assets/javascripts/discourse/components/tag-chooser.js.es6 index 00fc244487..9f962596e4 100644 --- a/app/assets/javascripts/discourse/components/tag-chooser.js.es6 +++ b/app/assets/javascripts/discourse/components/tag-chooser.js.es6 @@ -109,12 +109,15 @@ export default Ember.TextField.extend({ url: Discourse.getURL("/tags/filter/search"), dataType: 'json', data: function (term) { + const selectedTags = self.get('tags'); const d = { q: term, limit: self.siteSettings.max_tag_search_results, - categoryId: self.get('categoryId'), - selected_tags: self.get('tags') + categoryId: self.get('categoryId') }; + if (selectedTags) { + d.selected_tags = selectedTags.slice(0,100); + } if (!self.get('everyTag')) { d.filterForInput = true; } diff --git a/app/assets/javascripts/discourse/components/text-field.js.es6 b/app/assets/javascripts/discourse/components/text-field.js.es6 index a9246efa7d..6e1fabc819 100644 --- a/app/assets/javascripts/discourse/components/text-field.js.es6 +++ b/app/assets/javascripts/discourse/components/text-field.js.es6 @@ -1,7 +1,35 @@ import computed from "ember-addons/ember-computed-decorators"; +import { siteDir, isRTL, isLTR } from "discourse/lib/text-direction"; export default Ember.TextField.extend({ - attributeBindings: ['autocorrect', 'autocapitalize', 'autofocus', 'maxLength'], + attributeBindings: ['autocorrect', 'autocapitalize', 'autofocus', 'maxLength', 'dir'], + + @computed + dir() { + if (this.siteSettings.support_mixed_text_direction) { + let val = this.value; + if (val) { + return isRTL(val) ? 'rtl' : 'ltr'; + } else { + return siteDir(); + } + } + }, + + keyUp(event) { + this._super(event); + + if (this.siteSettings.support_mixed_text_direction) { + let val = this.value; + if (isRTL(val)) { + this.set('dir', 'rtl'); + } else if (isLTR(val)) { + this.set('dir', 'ltr'); + } else { + this.set('dir', siteDir()); + } + } + }, @computed("placeholderKey") placeholder(placeholderKey) { diff --git a/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 b/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 index d5ae0f8b0a..af05e670fa 100644 --- a/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 @@ -8,12 +8,12 @@ export default Ember.Component.extend({ @computed('topic.isPrivateMessage') canArchive(isPM) { - return this.siteSettings.enable_private_messages && isPM; + return this.siteSettings.enable_personal_messages && isPM; }, @computed('topic.isPrivateMessage') showNotificationsButton(isPM) { - return (!isPM) || this.siteSettings.enable_private_messages; + return (!isPM) || this.siteSettings.enable_personal_messages; }, @computed('topic.details.can_invite_to') diff --git a/app/assets/javascripts/discourse/components/topic-status.js.es6 b/app/assets/javascripts/discourse/components/topic-status.js.es6 index f1c8695dfd..be8076458e 100644 --- a/app/assets/javascripts/discourse/components/topic-status.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-status.js.es6 @@ -8,15 +8,10 @@ export default Ember.Component.extend(bufferedRender({ rerenderTriggers: ['topic.archived', 'topic.closed', 'topic.pinned', 'topic.visible', 'topic.unpinned', 'topic.is_warning'], click(e) { - if ($(e.target).hasClass('d-icon-thumb-tack')) { + // only pin unpin for now + if (this.get("canAct") && $(e.target).hasClass('d-icon-thumb-tack')) { const topic = this.get('topic'); - - // only pin unpin for now - if (topic.get('pinned')) { - topic.clearPin(); - } else { - topic.rePin(); - } + topic.get('pinned') ? topic.clearPin() : topic.rePin(); } return false; diff --git a/app/assets/javascripts/discourse/components/user-selector.js.es6 b/app/assets/javascripts/discourse/components/user-selector.js.es6 index 830a74d708..3c9aefe6fb 100644 --- a/app/assets/javascripts/discourse/components/user-selector.js.es6 +++ b/app/assets/javascripts/discourse/components/user-selector.js.es6 @@ -16,20 +16,30 @@ export default TextField.extend({ didInsertElement(opts) { this._super(); + + const bool = (n => { + const val = this.get(n); + return val === true || val === "true"; + }); + var self = this, selected = [], groups = [], currentUser = this.currentUser, - includeMentionableGroups = this.get('includeMentionableGroups') === 'true', - includeMessageableGroups = this.get('includeMessageableGroups') === 'true', - includeGroups = this.get('includeGroups') === 'true', - allowedUsers = this.get('allowedUsers') === 'true'; + includeMentionableGroups = bool('includeMentionableGroups'), + includeMessageableGroups = bool('includeMessageableGroups'), + includeGroups = bool('includeGroups'), + allowedUsers = bool('allowedUsers'), + excludeCurrentUser = bool('excludeCurrentUser'), + single = bool('single'), + allowAny = bool('allowAny'), + disabled = bool('disabled'); function excludedUsernames() { // hack works around some issues with allowAny eventing - const usernames = self.get('single') ? [] : selected; + const usernames = single ? [] : selected; - if (currentUser && self.get('excludeCurrentUser')) { + if (currentUser && excludeCurrentUser) { return usernames.concat([currentUser.get('username')]); } return usernames; @@ -37,9 +47,9 @@ export default TextField.extend({ this.$().val(this.get('usernames')).autocomplete({ template: findRawTemplate('user-selector-autocomplete'), - disabled: this.get('disabled'), - single: this.get('single'), - allowAny: this.get('allowAny'), + disabled: disabled, + single: single, + allowAny: allowAny, updateData: (opts && opts.updateData) ? opts.updateData : false, dataSource(term) { diff --git a/app/assets/javascripts/discourse/controllers/flag.js.es6 b/app/assets/javascripts/discourse/controllers/flag.js.es6 index e8d309c8c9..87b9daf001 100644 --- a/app/assets/javascripts/discourse/controllers/flag.js.es6 +++ b/app/assets/javascripts/discourse/controllers/flag.js.es6 @@ -80,7 +80,7 @@ export default Ember.Controller.extend(ModalFunctionality, { if (selected.get('is_custom_flag')) { const len = this.get('message.length') || 0; - return len >= Discourse.SiteSettings.min_private_message_post_length && + return len >= Discourse.SiteSettings.min_personal_message_post_length && len <= MAX_MESSAGE_LENGTH; } return true; diff --git a/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 b/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 index 4311368186..7e385fb302 100644 --- a/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 +++ b/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 @@ -20,48 +20,54 @@ export default Ember.Controller.extend(ModalFunctionality, { }, actions: { - submit() { - if (this.get('submitDisabled')) return false; - - this.set('disabled', true); - - ajax('/session/forgot_password', { - data: { login: this.get('accountEmailOrUsername').trim() }, - type: 'POST' - }).then(data => { - const escaped = escapeExpression(this.get('accountEmailOrUsername')); - const isEmail = this.get('accountEmailOrUsername').match(/@/); - let key = 'forgot_password.complete_' + (isEmail ? 'email' : 'username'); - let extraClass; - - if (data.user_found === true) { - key += '_found'; - this.set('accountEmailOrUsername', ''); - this.set('offerHelp', I18n.t(key, {email: escaped, username: escaped})); - } else { - if (data.user_found === false) { - key += '_not_found'; - extraClass = 'error'; - } - - this.flash(I18n.t(key, {email: escaped, username: escaped}), extraClass); - } - }).catch(e => { - this.flash(extractError(e), 'error'); - }).finally(() => { - setTimeout(() => this.set('disabled', false), 1000); - }); - - return false; - }, - ok() { this.send('closeModal'); }, help() { this.setProperties({ offerHelp: I18n.t('forgot_password.help'), helpSeen: true }); - } - } + }, + resetPassword() { + return this._submit('/session/forgot_password', 'forgot_password.complete'); + }, + + emailLogin() { + return this._submit('/u/email-login', 'email_login.complete'); + } + }, + + _submit(route, translationKey) { + if (this.get('submitDisabled')) return false; + this.set('disabled', true); + + ajax(route, { + data: { login: this.get('accountEmailOrUsername').trim() }, + type: 'POST' + }).then(data => { + const escaped = escapeExpression(this.get('accountEmailOrUsername')); + const isEmail = this.get('accountEmailOrUsername').match(/@/); + let key = `${translationKey}_${isEmail ? 'email' : 'username'}`; + let extraClass; + + if (data.user_found === true) { + key += '_found'; + this.set('accountEmailOrUsername', ''); + this.set('offerHelp', I18n.t(key, { email: escaped, username: escaped })); + } else { + if (data.user_found === false) { + key += '_not_found'; + extraClass = 'error'; + } + + this.flash(I18n.t(key, { email: escaped, username: escaped }), extraClass); + } + }).catch(e => { + this.flash(extractError(e), 'error'); + }).finally(() => { + this.set('disabled', false); + }); + + return false; + }, }); 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 2045b6daa6..a33492a997 100644 --- a/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 +++ b/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 @@ -167,9 +167,9 @@ export default Ember.Controller.extend({ return this.currentUser && this.currentUser.staff && hasResults; }, - @computed('expanded', 'model.grouped_search_result.can_create_topic') - canCreateTopic(expanded, userCanCreateTopic) { - return this.currentUser && userCanCreateTopic && !expanded; + @computed('model.grouped_search_result.can_create_topic') + canCreateTopic(userCanCreateTopic) { + return this.currentUser && userCanCreateTopic; }, @computed('expanded') diff --git a/app/assets/javascripts/discourse/controllers/group-activity.js.es6 b/app/assets/javascripts/discourse/controllers/group-activity.js.es6 index de4bda4dcd..2722bebdd3 100644 --- a/app/assets/javascripts/discourse/controllers/group-activity.js.es6 +++ b/app/assets/javascripts/discourse/controllers/group-activity.js.es6 @@ -6,6 +6,9 @@ export default Ember.Controller.extend({ @computed('model.is_group_user') showGroupMessages(isGroupUser) { + if (!this.siteSettings.enable_personal_messages) { + return false; + } return isGroupUser || (this.currentUser && this.currentUser.admin); } }); diff --git a/app/assets/javascripts/discourse/controllers/invite.js.es6 b/app/assets/javascripts/discourse/controllers/invite.js.es6 index 71509d9e13..178bb02234 100644 --- a/app/assets/javascripts/discourse/controllers/invite.js.es6 +++ b/app/assets/javascripts/discourse/controllers/invite.js.es6 @@ -158,6 +158,11 @@ export default Ember.Controller.extend(ModalFunctionality, { return Group.findAll({ term: term, ignore_automatic: true }); }, + @computed('isPrivateTopic', 'isMessage') + includeMentionableGroups(isPrivateTopic, isMessage) { + return !isPrivateTopic && !isMessage; + }, + @computed('isMessage', 'emailOrUsername', 'invitingExistingUserToTopic') successMessage(isMessage, emailOrUsername, invitingExistingUserToTopic) { if (this.get('hasGroups')) { diff --git a/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 index fd688471bc..9eb2960b83 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 @@ -57,16 +57,24 @@ export default Ember.Controller.extend(PreferencesTabController, { }, homeChanged() { - const siteHome = Discourse.SiteSettings.top_menu.split("|")[0].split(",")[0]; + const siteHome = this.siteSettings.top_menu.split("|")[0].split(",")[0]; const userHome = USER_HOMES[this.get('model.user_option.homepage_id')]; + setDefaultHomepage(userHome || siteHome); }, @computed() userSelectableHome() { - return _.map(USER_HOMES, (name, num) => { - return {name: I18n.t('filters.' + name + '.title'), value: Number(num)}; + let homeValues = _.invert(USER_HOMES); + + let result = []; + this.siteSettings.top_menu.split('|').forEach(m => { + let id = homeValues[m]; + if (id) { + result.push({ name: I18n.t(`filters.${m}.title`), value: Number(id) }); + } }); + return result; }, actions: { diff --git a/app/assets/javascripts/discourse/controllers/user.js.es6 b/app/assets/javascripts/discourse/controllers/user.js.es6 index c11210ee8b..c8fce33d5a 100644 --- a/app/assets/javascripts/discourse/controllers/user.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user.js.es6 @@ -52,7 +52,7 @@ export default Ember.Controller.extend(CanCheckEmails, { @computed('viewingSelf', 'currentUser.admin') showPrivateMessages(viewingSelf, isAdmin) { - return this.siteSettings.enable_private_messages && (viewingSelf || isAdmin); + return this.siteSettings.enable_personal_messages && (viewingSelf || isAdmin); }, @computed('viewingSelf', 'currentUser.staff') diff --git a/app/assets/javascripts/discourse/helpers/bound-avatar.js.es6 b/app/assets/javascripts/discourse/helpers/bound-avatar.js.es6 index 2833f8cefb..b8f04b1e6e 100644 --- a/app/assets/javascripts/discourse/helpers/bound-avatar.js.es6 +++ b/app/assets/javascripts/discourse/helpers/bound-avatar.js.es6 @@ -1,11 +1,13 @@ import { htmlHelper } from 'discourse-common/lib/helpers'; import { avatarImg } from 'discourse/lib/utilities'; +import { addExtraUserClasses } from 'discourse/helpers/user-avatar'; export default htmlHelper((user, size) => { if (Ember.isEmpty(user)) { return "
          "; } - const avatarTemplate = Em.get(user, 'avatar_template'); + const avatarTemplate = Ember.get(user, 'avatar_template'); return avatarImg({ size, avatarTemplate }); + return avatarImg(addExtraUserClasses(user, { size, avatarTemplate })); }); diff --git a/app/assets/javascripts/discourse/helpers/category-link.js.es6 b/app/assets/javascripts/discourse/helpers/category-link.js.es6 index 336e25314e..7735be3d69 100644 --- a/app/assets/javascripts/discourse/helpers/category-link.js.es6 +++ b/app/assets/javascripts/discourse/helpers/category-link.js.es6 @@ -1,4 +1,5 @@ import { registerUnbound } from 'discourse-common/lib/helpers'; +import { isRTL } from "discourse/lib/text-direction"; import { iconHTML } from 'discourse-common/lib/icon-library'; var get = Em.get, @@ -38,6 +39,7 @@ export function categoryBadgeHTML(category, opts) { let color = get(category, 'color'); let html = ""; let parentCat = null; + let categoryDir = ""; if (!opts.hideParent) { parentCat = Discourse.Category.findById(get(category, 'parent_category_id')); @@ -66,10 +68,14 @@ export function categoryBadgeHTML(category, opts) { let categoryName = escapeExpression(get(category, 'name')); + if (Discourse.SiteSettings.support_mixed_text_direction) { + categoryDir = isRTL(categoryName) ? 'dir="rtl"' : 'dir="ltr"'; + } + if (restricted) { - html += `${iconHTML('lock')}${categoryName}`; + html += `${iconHTML('lock')}${categoryName}`; } else { - html += `${categoryName}`; + html += `${categoryName}`; } html += ""; diff --git a/app/assets/javascripts/discourse/helpers/dir-span.js.es6 b/app/assets/javascripts/discourse/helpers/dir-span.js.es6 new file mode 100644 index 0000000000..3f25802565 --- /dev/null +++ b/app/assets/javascripts/discourse/helpers/dir-span.js.es6 @@ -0,0 +1,15 @@ +import { registerUnbound } from "discourse-common/lib/helpers"; +import { isRTL } from 'discourse/lib/text-direction'; + +function setDir(text) { + let content = text ? text : ""; + if (content && Discourse.SiteSettings.support_mixed_text_direction) { + let textDir = isRTL(content) ? 'rtl' : 'ltr'; + return `${content}`; + } + return content; +} + +export default registerUnbound('dir-span', function(str) { + return new Handlebars.SafeString(setDir(str)); +}); diff --git a/app/assets/javascripts/discourse/helpers/period-title.js.es6 b/app/assets/javascripts/discourse/helpers/period-title.js.es6 index 37af602c29..5a8315117d 100644 --- a/app/assets/javascripts/discourse/helpers/period-title.js.es6 +++ b/app/assets/javascripts/discourse/helpers/period-title.js.es6 @@ -30,7 +30,7 @@ export default htmlHelper((period, options) => { break; } - return `${title} ${dateString}`; + return `${title}${dateString}`; } else { return title; } diff --git a/app/assets/javascripts/discourse/helpers/user-avatar.js.es6 b/app/assets/javascripts/discourse/helpers/user-avatar.js.es6 index 71e6e87249..e20528bd42 100644 --- a/app/assets/javascripts/discourse/helpers/user-avatar.js.es6 +++ b/app/assets/javascripts/discourse/helpers/user-avatar.js.es6 @@ -1,6 +1,31 @@ import { registerUnbound } from 'discourse-common/lib/helpers'; import { avatarImg, formatUsername } from 'discourse/lib/utilities'; +let _customAvatarHelpers; + +export function registerCustomAvatarHelper(fn) { + _customAvatarHelpers = _customAvatarHelpers || []; + _customAvatarHelpers.push(fn); +} + +export function addExtraUserClasses(u, args) { + let extraClasses = classesForUser(u).join(' '); + if (extraClasses && extraClasses.length) { + args.extraClasses = extraClasses; + } + return args; +} + +export function classesForUser(u) { + let result = []; + if (_customAvatarHelpers) { + for (let i=0; i<_customAvatarHelpers.length; i++) { + result = result.concat(_customAvatarHelpers[i](u)); + } + } + return result; +} + function renderAvatar(user, options) { options = options || {}; diff --git a/app/assets/javascripts/discourse/initializers/banner.js.es6 b/app/assets/javascripts/discourse/initializers/banner.js.es6 index 9c9ff47d92..4ce88b2c48 100644 --- a/app/assets/javascripts/discourse/initializers/banner.js.es6 +++ b/app/assets/javascripts/discourse/initializers/banner.js.es6 @@ -5,7 +5,6 @@ export default { after: "message-bus", initialize(container) { - const banner = Em.Object.create(PreloadStore.get("banner")), site = container.lookup('site:main'); diff --git a/app/assets/javascripts/discourse/initializers/post-decorations.js.es6 b/app/assets/javascripts/discourse/initializers/post-decorations.js.es6 index d17b2087ac..0cab9b554d 100644 --- a/app/assets/javascripts/discourse/initializers/post-decorations.js.es6 +++ b/app/assets/javascripts/discourse/initializers/post-decorations.js.es6 @@ -1,13 +1,18 @@ import highlightSyntax from 'discourse/lib/highlight-syntax'; import lightbox from 'discourse/lib/lightbox'; +import { setTextDirections } from "discourse/lib/text-direction"; import { withPluginApi } from 'discourse/lib/plugin-api'; export default { name: "post-decorations", - initialize() { + initialize(container) { withPluginApi('0.1', api => { + const siteSettings = container.lookup('site-settings:main'); api.decorateCooked(highlightSyntax); api.decorateCooked(lightbox); + if (siteSettings.support_mixed_text_direction) { + api.decorateCooked(setTextDirections); + } api.decorateCooked($elem => { const players = $('audio', $elem); diff --git a/app/assets/javascripts/discourse/initializers/register-service-worker.js.es6 b/app/assets/javascripts/discourse/initializers/register-service-worker.js.es6 index 7c16106dda..c539469475 100644 --- a/app/assets/javascripts/discourse/initializers/register-service-worker.js.es6 +++ b/app/assets/javascripts/discourse/initializers/register-service-worker.js.es6 @@ -2,11 +2,27 @@ export default { name: 'register-service-worker', initialize() { - const isSecure = (document.location.protocol === 'https:') || - (location.hostname === "localhost"); + window.addEventListener('load', () => { + const isSecured = (document.location.protocol === 'https:') || + (location.hostname === "localhost"); - if (isSecure && ('serviceWorker' in navigator)) { - navigator.serviceWorker.register(`${Discourse.BaseUri}/service-worker.js`); - } + const isSupported= isSecured && ('serviceWorker' in navigator); + + if (isSupported) { + if (Discourse.ServiceWorkerURL) { + navigator.serviceWorker + .register(`${Discourse.BaseUri}/${Discourse.ServiceWorkerURL}`) + .catch(error => { + Ember.Logger.info(`Failed to register Service Worker: ${error}`); + }); + } else { + navigator.serviceWorker.getRegistrations().then(registrations => { + for(let registration of registrations) { + registration.unregister(); + }; + }); + } + } + }); } }; diff --git a/app/assets/javascripts/discourse/lib/ajax-error.js.es6 b/app/assets/javascripts/discourse/lib/ajax-error.js.es6 index cb155ec258..9666d9c378 100644 --- a/app/assets/javascripts/discourse/lib/ajax-error.js.es6 +++ b/app/assets/javascripts/discourse/lib/ajax-error.js.es6 @@ -54,9 +54,11 @@ export function throwAjaxError(undoCallback) { } export function popupAjaxError(error) { + if (error && error._discourse_displayed) { return; } bootbox.alert(extractError(error)); error._discourse_displayed = true; + // We re-throw in a catch to not swallow the exception throw error; } diff --git a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 index 367e6aa304..e65240981e 100644 --- a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 +++ b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 @@ -71,7 +71,7 @@ export default { let siteSettings = this.container.lookup('site-settings:main'); // Disable the shortcut if private messages are disabled - if (!siteSettings.enable_private_messages) { + if (!siteSettings.enable_personal_messages) { delete bindings['g m']; } diff --git a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 index 60fc1233a5..bc15c48952 100644 --- a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 +++ b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 @@ -23,9 +23,10 @@ import { addNavItem } from 'discourse/models/nav-item'; import { replaceFormatter } from 'discourse/lib/utilities'; import { modifySelectKit } from "select-kit/mixins/plugin-api"; import { addGTMPageChangedCallback } from 'discourse/lib/page-tracker'; +import { registerCustomAvatarHelper } from 'discourse/helpers/user-avatar'; // If you add any methods to the API ensure you bump up this number -const PLUGIN_API_VERSION = '0.8.17'; +const PLUGIN_API_VERSION = '0.8.18'; class PluginApi { constructor(version, container) { @@ -368,6 +369,23 @@ class PluginApi { appEvents.on(name, fn); } + /** + Registers a function to generate custom avatar CSS classes + for a particular user. + + Takes a function that will accept a user as a parameter + and return an array of CSS classes to apply. + + ```javascript + api.customUserAvatarClasses(user => { + if (Ember.get(user, 'primary_group_name') === 'managers') { + return ['managers']; + } + }); + **/ + customUserAvatarClasses(fn) { + registerCustomAvatarHelper(fn); + } /** * Changes a setting associated with a widget. For example, if diff --git a/app/assets/javascripts/discourse/lib/text-direction.js.es6 b/app/assets/javascripts/discourse/lib/text-direction.js.es6 new file mode 100644 index 0000000000..928b147ed7 --- /dev/null +++ b/app/assets/javascripts/discourse/lib/text-direction.js.es6 @@ -0,0 +1,30 @@ +const ltrChars = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF'; +const rtlChars = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC'; +const rtlDirCheck = new RegExp('^[^'+ltrChars+']*['+rtlChars+']'); +const ltrDirCheck = new RegExp('^[^'+rtlChars+']*['+ltrChars+']'); +let _siteDir; + +export function isRTL(text) { + return rtlDirCheck.test(text); +} + +export function isLTR(text) { + return ltrDirCheck.test(text); +} + +export function setTextDirections($elem) { + $elem.find('*').each((i, e) => { + let $e = $(e), + textContent = $e.text(); + if (textContent) { + isRTL(textContent) ? $e.attr('dir', 'rtl') : $e.attr('dir', 'ltr'); + } + }); +} + +export function siteDir() { + if (!_siteDir) { + _siteDir = $('html').hasClass('rtl') ? 'rtl' : 'ltr'; + } + return _siteDir; +} diff --git a/app/assets/javascripts/discourse/lib/to-markdown.js.es6 b/app/assets/javascripts/discourse/lib/to-markdown.js.es6 index 3a4ff590dc..692d4350ed 100644 --- a/app/assets/javascripts/discourse/lib/to-markdown.js.es6 +++ b/app/assets/javascripts/discourse/lib/to-markdown.js.es6 @@ -3,6 +3,7 @@ import parseHTML from 'discourse/helpers/parse-html'; const trimLeft = text => text.replace(/^\s+/,""); const trimRight = text => text.replace(/\s+$/,""); const countPipes = text => (text.replace(/\\\|/,"").match(/\|/g) || []).length; +const msoListClasses = ["MsoListParagraphCxSpFirst", "MsoListParagraphCxSpMiddle", "MsoListParagraphCxSpLast"]; class Tag { constructor(name, prefix = "", suffix = "", inline = false) { @@ -207,7 +208,22 @@ class Tag { static li() { return class extends Tag.slice("li", "\n") { decorate(text) { - const indent = this.element.filterParentNames(["ol", "ul"]).slice(1).map(() => "\t").join(""); + let indent = this.element.filterParentNames(["ol", "ul"]).slice(1).map(() => "\t").join(""); + const attrs = this.element.attributes; + + if (msoListClasses.includes(attrs.class)) { + try { + const level = parseInt(attrs.style.match(/level./)[0].replace("level", "")); + indent = Array(level).join("\t") + indent; + } finally { + if (attrs.class === "MsoListParagraphCxSpFirst") { + indent = `\n\n${indent}`; + } else if (attrs.class === "MsoListParagraphCxSpLast") { + text = `${text}\n`; + } + } + } + return super.decorate(`${indent}* ${trimLeft(text)}`); } }; @@ -356,6 +372,13 @@ class Element { this.parentNames = this.parentNames || []; this.previous = previous; this.next = next; + + if (this.name === "p") { + if (msoListClasses.includes(this.attributes.class)) { + this.name = "li"; + this.parentNames.push("ul"); + } + } } tag() { @@ -433,7 +456,7 @@ class Element { } } -function trimUnwantedSpaces(html) { +function trimUnwanted(html) { const body = html.match(/]*>([\s\S]*?)<\/body>/); html = body ? body[1] : html; html = html.replace(/\r|\n| /g, " "); @@ -443,6 +466,8 @@ function trimUnwantedSpaces(html) { html = html.replace(match[0], match[0].replace(/>\s{2,} <")); } + html = html.replace(/[^!]*/g, ""); // to support ms word list tags + return html; } @@ -461,7 +486,7 @@ function putPlaceholders(html) { match = codeRegEx.exec(origHtml); } - const elements = parseHTML(trimUnwantedSpaces(html)); + const elements = parseHTML(trimUnwanted(html)); return { elements, placeholders }; } diff --git a/app/assets/javascripts/discourse/lib/url.js.es6 b/app/assets/javascripts/discourse/lib/url.js.es6 index 2fe1b79a8b..487b4467f9 100644 --- a/app/assets/javascripts/discourse/lib/url.js.es6 +++ b/app/assets/javascripts/discourse/lib/url.js.es6 @@ -5,6 +5,11 @@ import { defaultHomepage } from 'discourse/lib/utilities'; const rewrites = []; const TOPIC_REGEXP = /\/t\/([^\/]+)\/(\d+)\/?(\d+)?/; +function redirectTo(url) { + document.location = url; + return true; +} + // We can add links here that have server side responses but not client side. const SERVER_SIDE_ONLY = [ /^\/assets\//, @@ -17,6 +22,7 @@ const SERVER_SIDE_ONLY = [ /^\/wizard/, /\.rss$/, /\.json$/, + /^\/admin\/upgrade$/ ]; export function rewritePath(path) { @@ -162,15 +168,23 @@ const DiscourseURL = Ember.Object.extend({ if (Em.isEmpty(path)) { return; } if (Discourse.get('requiresRefresh')) { - document.location.href = Discourse.getURL(path); - return; + return redirectTo(Discourse.getURL(path)); } const pathname = path.replace(/(https?\:)?\/\/[^\/]+/, ''); + let baseUri = Discourse.BaseUri; + + // If we have a baseUri and an absolute URL, make sure the baseUri + // is the same. Otherwise we could be switching forums. + if (baseUri && + path.indexOf('http') === 0 && + pathname.indexOf(baseUri) !== 0) { + return redirectTo(path); + } + const serverSide = SERVER_SIDE_ONLY.some(r => { if (pathname.match(r)) { - document.location = path; - return true; + return redirectTo(path); } }); @@ -178,8 +192,7 @@ const DiscourseURL = Ember.Object.extend({ // Protocol relative URLs if (path.indexOf('//') === 0) { - document.location = path; - return; + return redirectTo(path); } // Scroll to the same page, different anchor @@ -193,19 +206,19 @@ const DiscourseURL = Ember.Object.extend({ path = path.replace(/(https?\:)?\/\/[^\/]+/, ''); // Rewrite /my/* urls - if (path.indexOf(Discourse.BaseUri + '/my/') === 0) { + let myPath = `${baseUri}/my/`; + if (path.indexOf(myPath) === 0) { const currentUser = Discourse.User.current(); if (currentUser) { - path = path.replace(Discourse.BaseUri + '/my/', userPath(currentUser.get('username_lower') + "/")); + path = path.replace(myPath, userPath(currentUser.get('username_lower') + "/")); } else { - document.location.href = "/404"; - return; + return redirectTo('/404'); } } // handle prefixes if (path.match(/^\//)) { - let rootURL = (Discourse.BaseUri === undefined ? "/" : Discourse.BaseUri); + let rootURL = (baseUri === undefined ? "/" : baseUri); rootURL = rootURL.replace(/\/$/, ''); path = path.replace(rootURL, ''); } diff --git a/app/assets/javascripts/discourse/lib/user-search.js.es6 b/app/assets/javascripts/discourse/lib/user-search.js.es6 index 29ad228a05..ceed4404e7 100644 --- a/app/assets/javascripts/discourse/lib/user-search.js.es6 +++ b/app/assets/javascripts/discourse/lib/user-search.js.es6 @@ -69,10 +69,11 @@ function organizeResults(r, options) { if (r.groups) { r.groups.every(function(g) { - if (results.length > limit && options.term.toLowerCase() !== g.name.toLowerCase()) return false; - if (exclude.indexOf(g.name) === -1) { - groups.push(g); - results.push(g); + if (options.term.toLowerCase() === g.name.toLowerCase() || results.length < limit) { + if (exclude.indexOf(g.name) === -1) { + groups.push(g); + results.push(g); + } } return true; }); diff --git a/app/assets/javascripts/discourse/lib/utilities.js.es6 b/app/assets/javascripts/discourse/lib/utilities.js.es6 index 1d24485e0f..671e3ca799 100644 --- a/app/assets/javascripts/discourse/lib/utilities.js.es6 +++ b/app/assets/javascripts/discourse/lib/utilities.js.es6 @@ -369,7 +369,7 @@ export function displayErrorForUpload(data) { if (data.jqXHR.responseJSON.message) { bootbox.alert(data.jqXHR.responseJSON.message); } else { - bootbox.alert(data.jqXHR.responseJSON.join("\n")); + bootbox.alert(data.jqXHR.responseJSON.errors.join("\n")); } return; } diff --git a/app/assets/javascripts/discourse/mixins/open-composer.js.es6 b/app/assets/javascripts/discourse/mixins/open-composer.js.es6 index 2303281b63..dcb92d1478 100644 --- a/app/assets/javascripts/discourse/mixins/open-composer.js.es6 +++ b/app/assets/javascripts/discourse/mixins/open-composer.js.es6 @@ -7,8 +7,8 @@ export default Ember.Mixin.create({ this.controllerFor('composer').open({ categoryId: controller.get('category.id'), action: Composer.CREATE_TOPIC, - draftKey: controller.get('model.draft_key'), - draftSequence: controller.get('model.draft_sequence') + draftKey: controller.get('model.draft_key') || Composer.CREATE_TOPIC, + draftSequence: controller.get('model.draft_sequence') || 0 }); }, diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6 index 4cdc2bd4c4..3b9815b9d8 100644 --- a/app/assets/javascripts/discourse/models/composer.js.es6 +++ b/app/assets/javascripts/discourse/models/composer.js.es6 @@ -1,4 +1,3 @@ -import { iconHTML } from 'discourse-common/lib/icon-library'; import RestModel from 'discourse/models/rest'; import Topic from 'discourse/models/topic'; import { throwAjaxError } from 'discourse/lib/ajax-error'; @@ -6,45 +5,45 @@ import Quote from 'discourse/lib/quote'; import Draft from 'discourse/models/draft'; import computed from 'ember-addons/ember-computed-decorators'; import { escapeExpression, tinyAvatar } from 'discourse/lib/utilities'; -import { emojiUnescape } from 'discourse/lib/text'; + +// The actions the composer can take +export const + CREATE_TOPIC = 'createTopic', + PRIVATE_MESSAGE = 'privateMessage', + NEW_PRIVATE_MESSAGE_KEY = 'new_private_message', + REPLY = 'reply', + EDIT = 'edit', + REPLY_AS_NEW_TOPIC_KEY = "reply_as_new_topic", + REPLY_AS_NEW_PRIVATE_MESSAGE_KEY = "reply_as_new_private_message"; const CLOSED = 'closed', - SAVING = 'saving', - OPEN = 'open', - DRAFT = 'draft', + SAVING = 'saving', + OPEN = 'open', + DRAFT = 'draft', - // The actions the composer can take - CREATE_TOPIC = 'createTopic', - PRIVATE_MESSAGE = 'privateMessage', - NEW_PRIVATE_MESSAGE_KEY = 'new_private_message', - REPLY = 'reply', - EDIT = 'edit', - REPLY_AS_NEW_TOPIC_KEY = "reply_as_new_topic", - REPLY_AS_NEW_PRIVATE_MESSAGE_KEY = "reply_as_new_private_message", + // When creating, these fields are moved into the post model from the composer model + _create_serializer = { + raw: 'reply', + title: 'title', + unlist_topic: 'unlistTopic', + category: 'categoryId', + topic_id: 'topic.id', + is_warning: 'isWarning', + whisper: 'whisper', + archetype: 'archetypeId', + target_usernames: 'targetUsernames', + typing_duration_msecs: 'typingTime', + composer_open_duration_msecs: 'composerTime', + tags: 'tags', + featured_link: 'featuredLink' + }, - // When creating, these fields are moved into the post model from the composer model - _create_serializer = { - raw: 'reply', - title: 'title', - unlist_topic: 'unlistTopic', - category: 'categoryId', - topic_id: 'topic.id', - is_warning: 'isWarning', - whisper: 'whisper', - archetype: 'archetypeId', - target_usernames: 'targetUsernames', - typing_duration_msecs: 'typingTime', - composer_open_duration_msecs: 'composerTime', - tags: 'tags', - featured_link: 'featuredLink' - }, - - _edit_topic_serializer = { - title: 'topic.title', - categoryId: 'topic.category.id', - tags: 'topic.tags', - featuredLink: 'topic.featured_link' - }; + _edit_topic_serializer = { + title: 'topic.title', + categoryId: 'topic.category.id', + tags: 'topic.tags', + featuredLink: 'topic.featured_link' + }; const _saveLabels = {}; _saveLabels[EDIT] = 'composer.save_edit'; @@ -167,52 +166,55 @@ const Composer = RestModel.extend({ return this.get('canEditTopicFeaturedLink') ? 'composer.title_or_link_placeholder' : 'composer.title_placeholder'; }, - // Determine the appropriate title for this action - actionTitle: function() { - const topic = this.get('topic'); + @computed("action", "post", "topic", "topic.title") + replyOptions(action, post, topic, topicTitle) { + let options = { + userLink: null, + topicLink: null, + postLink: null, + userAvatar: null, + originalUser: null + }; - let postLink, topicLink, usernameLink; if (topic) { - const postNumber = this.get('post.post_number'); - postLink = "" + - I18n.t("post.post_number", { number: postNumber }) + ""; - - let title = topic.get('fancy_title') || escapeExpression(topic.get('title')); - - topicLink = " " + title + ""; - usernameLink = "" + this.get('post.username') + ""; + options.topicLink = { + href: topic.get("url"), + anchor: topic.get("fancy_title") || escapeExpression(topicTitle) + }; } - let postDescription; - const post = this.get('post'); - if (post) { - postDescription = I18n.t('post.' + this.get('action'), { - link: postLink, - replyAvatar: tinyAvatar(post.get('avatar_template')), - username: this.get('post.username'), - usernameLink - }); + options.label = I18n.t(`post.${action}`); + options.userAvatar = tinyAvatar(post.get("avatar_template")); if (!this.site.mobileView) { - const replyUsername = post.get('reply_to_user.username'); - const replyAvatarTemplate = post.get('reply_to_user.avatar_template'); - if (replyUsername && replyAvatarTemplate && this.get('action') === EDIT) { - postDescription += ` ${iconHTML('mail-forward', { class: 'reply-to-glyph' })} ` + tinyAvatar(replyAvatarTemplate) + " " + replyUsername; + const originalUserName = post.get('reply_to_user.username'); + const originalUserAvatar = post.get('reply_to_user.avatar_template'); + if (originalUserName && originalUserAvatar && action === EDIT) { + options.originalUser = { + username: originalUserName, + avatar: tinyAvatar(originalUserAvatar) + }; } } } - switch (this.get('action')) { - case PRIVATE_MESSAGE: return I18n.t('topic.private_message'); - case CREATE_TOPIC: return I18n.t('topic.create_long'); - case REPLY: - case EDIT: - if (postDescription) return postDescription; - if (topic) return emojiUnescape(I18n.t('post.reply_topic', { link: topicLink })); + if (topic && post) { + const postNumber = post.get("post_number"); + + options.postLink = { + href: `${topic.get("url")}/${postNumber}`, + anchor: I18n.t("post.post_number", { number: postNumber }) + }; + + options.userLink = { + href: `${topic.get("url")}/${postNumber}`, + anchor: post.get("username") + }; } - }.property('action', 'post', 'topic', 'topic.title'), + return options; + }, // whether to disable the post button cantSubmitPost: function() { @@ -302,7 +304,7 @@ const Composer = RestModel.extend({ @computed('privateMessage') minimumTitleLength(privateMessage) { if (privateMessage) { - return this.siteSettings.min_private_message_title_length; + return this.siteSettings.min_personal_message_title_length; } else { return this.siteSettings.min_topic_title_length; } @@ -325,7 +327,7 @@ const Composer = RestModel.extend({ if (pmWithNonHumanUser) { return 1; } else if (privateMessage) { - return this.siteSettings.min_private_message_post_length; + return this.siteSettings.min_personal_message_post_length; } else if (topicFirstPost) { // first post (topic body) return this.siteSettings.min_first_post_length; diff --git a/app/assets/javascripts/discourse/models/post.js.es6 b/app/assets/javascripts/discourse/models/post.js.es6 index c6903f71fd..955c301837 100644 --- a/app/assets/javascripts/discourse/models/post.js.es6 +++ b/app/assets/javascripts/discourse/models/post.js.es6 @@ -168,7 +168,8 @@ const Post = RestModel.extend({ this.setProperties({ deleted_at: new Date(), deleted_by: deletedBy, - can_delete: false + can_delete: false, + can_recover: true }); } else { promise = cookAsync(I18n.t("post.deleted_by_author", {count: Discourse.SiteSettings.delete_removed_posts_after})).then(cooked => { diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index 69a4f9c27f..b8e6f89725 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -3,6 +3,7 @@ import { flushMap } from 'discourse/models/store'; import RestModel from 'discourse/models/rest'; import { propertyEqual } from 'discourse/lib/computed'; import { longDate } from 'discourse/lib/formatter'; +import { isRTL } from 'discourse/lib/text-direction'; import computed from 'ember-addons/ember-computed-decorators'; import ActionSummary from 'discourse/models/action-summary'; import { popupAjaxError } from 'discourse/lib/ajax-error'; @@ -58,7 +59,13 @@ const Topic = RestModel.extend({ @computed('fancy_title') fancyTitle(title) { - return censor(emojiUnescape(title || ""), Discourse.Site.currentProp('censored_words')); + let fancyTitle = censor(emojiUnescape(title || ""), Discourse.Site.currentProp('censored_words')); + + if (Discourse.SiteSettings.support_mixed_text_direction) { + let titleDir = isRTL(title) ? 'rtl' : 'ltr'; + return `${fancyTitle}`; + } + return fancyTitle; }, // returns createdAt if there's no bumped date diff --git a/app/assets/javascripts/discourse/routes/badges-show.js.es6 b/app/assets/javascripts/discourse/routes/badges-show.js.es6 index 16e166c6b5..e5d1aae23c 100644 --- a/app/assets/javascripts/discourse/routes/badges-show.js.es6 +++ b/app/assets/javascripts/discourse/routes/badges-show.js.es6 @@ -30,9 +30,20 @@ export default Discourse.Route.extend({ afterModel(model, transition) { const username = transition.queryParams && transition.queryParams.username; - return UserBadge.findByBadgeId(model.get("id"), {username}).then(userBadges => { + const userBadgesGrant = UserBadge.findByBadgeId(model.get("id"), {username}).then(userBadges => { this.userBadgesGrant = userBadges; }); + + const userBadgesAll = UserBadge.findByUsername(username).then(userBadges => { + this.userBadgesAll = userBadges; + }); + + const promises = { + userBadgesGrant, + userBadgesAll, + }; + + return Ember.RSVP.hash(promises); }, titleToken() { diff --git a/app/assets/javascripts/discourse/routes/discourse.js.es6 b/app/assets/javascripts/discourse/routes/discourse.js.es6 index 8b3d3bbc31..169d50cd94 100644 --- a/app/assets/javascripts/discourse/routes/discourse.js.es6 +++ b/app/assets/javascripts/discourse/routes/discourse.js.es6 @@ -1,11 +1,19 @@ import Composer from 'discourse/models/composer'; const DiscourseRoute = Ember.Route.extend({ + showFooter: false, // Set to true to refresh a model without a transition if a query param // changes resfreshQueryWithoutTransition: false, + activate() { + this._super(); + if (this.get('showFooter')) { + this.controllerFor('application').set('showFooter', true); + } + }, + refresh() { if (!this.refreshQueryWithoutTransition) { return this._super(); } diff --git a/app/assets/javascripts/discourse/routes/preferences-about.js.es6 b/app/assets/javascripts/discourse/routes/preferences-about.js.es6 index 12e819af59..e5c86f6fdb 100644 --- a/app/assets/javascripts/discourse/routes/preferences-about.js.es6 +++ b/app/assets/javascripts/discourse/routes/preferences-about.js.es6 @@ -1,6 +1,8 @@ import RestrictedUserRoute from "discourse/routes/restricted-user"; export default RestrictedUserRoute.extend({ + showFooter: true, + model: function() { return this.modelFor('user'); }, diff --git a/app/assets/javascripts/discourse/routes/preferences-account.js.es6 b/app/assets/javascripts/discourse/routes/preferences-account.js.es6 index 2c34c9df2f..bde67c6ce9 100644 --- a/app/assets/javascripts/discourse/routes/preferences-account.js.es6 +++ b/app/assets/javascripts/discourse/routes/preferences-account.js.es6 @@ -1,6 +1,8 @@ import RestrictedUserRoute from "discourse/routes/restricted-user"; export default RestrictedUserRoute.extend({ + showFooter: true, + setupController(controller, user) { controller.reset(); controller.setProperties({ diff --git a/app/assets/javascripts/discourse/routes/preferences-badge-title.js.es6 b/app/assets/javascripts/discourse/routes/preferences-badge-title.js.es6 index 4491aff254..358159f452 100644 --- a/app/assets/javascripts/discourse/routes/preferences-badge-title.js.es6 +++ b/app/assets/javascripts/discourse/routes/preferences-badge-title.js.es6 @@ -2,6 +2,8 @@ import UserBadge from 'discourse/models/user-badge'; import RestrictedUserRoute from "discourse/routes/restricted-user"; export default RestrictedUserRoute.extend({ + showFooter: true, + model: function() { return UserBadge.findByUsername(this.modelFor('user').get('username')); }, diff --git a/app/assets/javascripts/discourse/routes/preferences-card-badge.js.es6 b/app/assets/javascripts/discourse/routes/preferences-card-badge.js.es6 index 8ec81a95d1..b8acd6d729 100644 --- a/app/assets/javascripts/discourse/routes/preferences-card-badge.js.es6 +++ b/app/assets/javascripts/discourse/routes/preferences-card-badge.js.es6 @@ -2,6 +2,8 @@ import UserBadge from 'discourse/models/user-badge'; import RestrictedUserRoute from "discourse/routes/restricted-user"; export default RestrictedUserRoute.extend({ + showFooter: true, + model: function() { return UserBadge.findByUsername(this.modelFor('user').get('username')); }, diff --git a/app/assets/javascripts/discourse/routes/preferences-categories.js.es6 b/app/assets/javascripts/discourse/routes/preferences-categories.js.es6 new file mode 100644 index 0000000000..713d79e420 --- /dev/null +++ b/app/assets/javascripts/discourse/routes/preferences-categories.js.es6 @@ -0,0 +1,5 @@ +import RestrictedUserRoute from "discourse/routes/restricted-user"; + +export default RestrictedUserRoute.extend({ + showFooter: true +}); diff --git a/app/assets/javascripts/discourse/routes/preferences-email.js.es6 b/app/assets/javascripts/discourse/routes/preferences-email.js.es6 index b9c02c3a85..77e80e7576 100644 --- a/app/assets/javascripts/discourse/routes/preferences-email.js.es6 +++ b/app/assets/javascripts/discourse/routes/preferences-email.js.es6 @@ -1,6 +1,8 @@ import RestrictedUserRoute from "discourse/routes/restricted-user"; export default RestrictedUserRoute.extend({ + showFooter: true, + model: function() { return this.modelFor('user'); }, diff --git a/app/assets/javascripts/discourse/routes/preferences-index.js.es6 b/app/assets/javascripts/discourse/routes/preferences-index.js.es6 index 3006f4b853..49964f42cc 100644 --- a/app/assets/javascripts/discourse/routes/preferences-index.js.es6 +++ b/app/assets/javascripts/discourse/routes/preferences-index.js.es6 @@ -1,6 +1,8 @@ import RestrictedUserRoute from "discourse/routes/restricted-user"; export default RestrictedUserRoute.extend({ + showFooter: true, + redirect() { this.transitionTo('preferences.account'); } diff --git a/app/assets/javascripts/discourse/routes/preferences-interface.js.es6 b/app/assets/javascripts/discourse/routes/preferences-interface.js.es6 index f58658956f..729e79dedd 100644 --- a/app/assets/javascripts/discourse/routes/preferences-interface.js.es6 +++ b/app/assets/javascripts/discourse/routes/preferences-interface.js.es6 @@ -1,6 +1,8 @@ import RestrictedUserRoute from "discourse/routes/restricted-user"; export default RestrictedUserRoute.extend({ + showFooter: true, + setupController(controller, user) { controller.setProperties({ model: user diff --git a/app/assets/javascripts/discourse/routes/preferences-notifications.js.es6 b/app/assets/javascripts/discourse/routes/preferences-notifications.js.es6 new file mode 100644 index 0000000000..713d79e420 --- /dev/null +++ b/app/assets/javascripts/discourse/routes/preferences-notifications.js.es6 @@ -0,0 +1,5 @@ +import RestrictedUserRoute from "discourse/routes/restricted-user"; + +export default RestrictedUserRoute.extend({ + showFooter: true +}); diff --git a/app/assets/javascripts/discourse/routes/preferences-username.js.es6 b/app/assets/javascripts/discourse/routes/preferences-username.js.es6 index 09fbc0ec6b..6737345bcd 100644 --- a/app/assets/javascripts/discourse/routes/preferences-username.js.es6 +++ b/app/assets/javascripts/discourse/routes/preferences-username.js.es6 @@ -1,6 +1,8 @@ import RestrictedUserRoute from "discourse/routes/restricted-user"; export default RestrictedUserRoute.extend({ + showFooter: true, + model: function() { return this.modelFor('user'); }, diff --git a/app/assets/javascripts/discourse/routes/preferences.js.es6 b/app/assets/javascripts/discourse/routes/preferences.js.es6 index e1cc50fc5c..1eb3d4b3e1 100644 --- a/app/assets/javascripts/discourse/routes/preferences.js.es6 +++ b/app/assets/javascripts/discourse/routes/preferences.js.es6 @@ -3,6 +3,7 @@ import showModal from 'discourse/lib/show-modal'; import { popupAjaxError } from 'discourse/lib/ajax-error'; export default RestrictedUserRoute.extend({ + model() { return this.modelFor('user'); }, diff --git a/app/assets/javascripts/discourse/routes/user-summary.js.es6 b/app/assets/javascripts/discourse/routes/user-summary.js.es6 index a191c41872..39d4571973 100644 --- a/app/assets/javascripts/discourse/routes/user-summary.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-summary.js.es6 @@ -1,4 +1,6 @@ export default Discourse.Route.extend({ + showFooter: true, + model() { return this.modelFor("user").summary(); }, diff --git a/app/assets/javascripts/discourse/templates/components/categories-only.hbs b/app/assets/javascripts/discourse/templates/components/categories-only.hbs index f10712bb61..2fa528e3a9 100644 --- a/app/assets/javascripts/discourse/templates/components/categories-only.hbs +++ b/app/assets/javascripts/discourse/templates/components/categories-only.hbs @@ -16,7 +16,7 @@
          {{category-title-link category=c}}
          - {{{c.description_excerpt}}} + {{{dir-span c.description_excerpt}}}
          diff --git a/app/assets/javascripts/discourse/templates/components/category-title-link.hbs b/app/assets/javascripts/discourse/templates/components/category-title-link.hbs index fcb0bdeb07..3403e70d49 100644 --- a/app/assets/javascripts/discourse/templates/components/category-title-link.hbs +++ b/app/assets/javascripts/discourse/templates/components/category-title-link.hbs @@ -3,7 +3,7 @@ {{d-icon 'lock'}} {{/if}} - {{category.name}} + {{dir-span category.name}} {{#if category.uploaded_logo.url}}
          {{cdn-img src=category.uploaded_logo.url class="category-logo"}}
          diff --git a/app/assets/javascripts/discourse/templates/components/composer-action-title.hbs b/app/assets/javascripts/discourse/templates/components/composer-action-title.hbs new file mode 100644 index 0000000000..6075400d90 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/composer-action-title.hbs @@ -0,0 +1,13 @@ +{{#if isEditing}} + {{d-icon "pencil"}} +{{else}} + {{composer-actions + composerModel=model + options=model.replyOptions + canWhisper=canWhisper + action=model.action}} +{{/if}} + + + {{actionTitle}} + diff --git a/app/assets/javascripts/discourse/templates/components/composer-editor.hbs b/app/assets/javascripts/discourse/templates/components/composer-editor.hbs index 037ee5440f..511961d8f1 100644 --- a/app/assets/javascripts/discourse/templates/components/composer-editor.hbs +++ b/app/assets/javascripts/discourse/templates/components/composer-editor.hbs @@ -11,6 +11,7 @@ validation=validation loading=composer.loading forcePreview=forcePreview + showLink=currentUser.can_post_link composerEvents=true onExpandPopupMenuOptions="onExpandPopupMenuOptions" onPopupMenuAction=onPopupMenuAction diff --git a/app/assets/javascripts/discourse/templates/components/composer-user-selector.hbs b/app/assets/javascripts/discourse/templates/components/composer-user-selector.hbs index 99fc0a5b97..0722426f16 100644 --- a/app/assets/javascripts/discourse/templates/components/composer-user-selector.hbs +++ b/app/assets/javascripts/discourse/templates/components/composer-user-selector.hbs @@ -3,7 +3,6 @@ onChangeCallback='triggerResize' id="private-message-users" includeMessageableGroups='true' - class="span8" placeholderKey="composer.users_placeholder" tabindex="1" usernames=usernames diff --git a/app/assets/javascripts/discourse/templates/components/group-post.hbs b/app/assets/javascripts/discourse/templates/components/group-post.hbs index 9f51a1584f..a6d3d6b101 100644 --- a/app/assets/javascripts/discourse/templates/components/group-post.hbs +++ b/app/assets/javascripts/discourse/templates/components/group-post.hbs @@ -7,7 +7,7 @@
          - - + + - - - + + + @@ -40,14 +30,11 @@ -
          - +{{#if siteSettings.enable_personal_messages}} +
          + -
          - {{preference-checkbox - labelKey="user.allow_private_messages" - checked=model.user_option.allow_private_messages}} +
          + {{preference-checkbox + labelKey="user.allow_private_messages" + checked=model.user_option.allow_private_messages}} +
          -
          +{{/if}}
          diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs index 1c9678a69c..ef2d4912cc 100644 --- a/app/assets/javascripts/discourse/templates/topic.hbs +++ b/app/assets/javascripts/discourse/templates/topic.hbs @@ -42,7 +42,7 @@ {{else}}

          {{#unless model.is_warning}} - {{#if siteSettings.enable_private_messages}} + {{#if siteSettings.enable_personal_messages}} {{d-icon "envelope"}} diff --git a/app/assets/javascripts/discourse/templates/user-invited-show.hbs b/app/assets/javascripts/discourse/templates/user-invited-show.hbs index 5f8dc6d18a..c02499e76b 100644 --- a/app/assets/javascripts/discourse/templates/user-invited-show.hbs +++ b/app/assets/javascripts/discourse/templates/user-invited-show.hbs @@ -5,13 +5,13 @@

          {{i18n 'user.invited.title'}}

          {{#if model.can_see_invite_details}} -
          -
          +
          +
          +
          {{d-button icon="plus" action="showInvite" label="user.invited.create" class="btn"}} diff --git a/app/assets/javascripts/discourse/templates/user.hbs b/app/assets/javascripts/discourse/templates/user.hbs index 882b312c80..a585892676 100644 --- a/app/assets/javascripts/discourse/templates/user.hbs +++ b/app/assets/javascripts/discourse/templates/user.hbs @@ -39,7 +39,16 @@
          - {{bound-avatar model "huge"}} +
            {{#if model.can_send_private_message_to_user}} @@ -73,7 +82,7 @@

            {{model.title}}

            {{/if}} {{plugin-outlet name="user-post-names" args=(hash model=model)}} -

            +

            {{#if model.location}}{{d-icon "map-marker"}} {{model.location}}{{/if}} {{#if model.website_name}} {{d-icon "globe"}} diff --git a/app/assets/javascripts/discourse/widgets/header.js.es6 b/app/assets/javascripts/discourse/widgets/header.js.es6 index db166e6659..04fdd8902a 100644 --- a/app/assets/javascripts/discourse/widgets/header.js.es6 +++ b/app/assets/javascripts/discourse/widgets/header.js.es6 @@ -5,6 +5,7 @@ import DiscourseURL from 'discourse/lib/url'; import { wantsNewWindow } from 'discourse/lib/intercept-click'; import { applySearchAutocomplete } from "discourse/lib/search"; import { ajax } from 'discourse/lib/ajax'; +import { addExtraUserClasses } from 'discourse/helpers/user-avatar'; import { h } from 'virtual-dom'; @@ -30,10 +31,12 @@ createWidget('header-notifications', { html(attrs) { const { user } = attrs; - const contents = [ avatarImg(this.settings.avatarSize, { - template: user.get('avatar_template'), - username: user.get('username') - }) ]; + const contents = [ + avatarImg(this.settings.avatarSize, addExtraUserClasses(user, { + template: user.get('avatar_template'), + username: user.get('username') + })) + ]; const unreadNotifications = user.get('unread_notifications'); if (!!unreadNotifications) { @@ -400,6 +403,10 @@ export default createWidget('header', { this.toggleHamburger(); break; case 'page-search': + let contextType = this.searchContextType(); + if (contextType === 'topic') { + this.state.searchContextType = contextType; + } if (!this.togglePageSearch()) { msg.event.preventDefault(); msg.event.stopPropagation(); diff --git a/app/assets/javascripts/discourse/widgets/post-admin-menu.js.es6 b/app/assets/javascripts/discourse/widgets/post-admin-menu.js.es6 index d79580ae45..32f0a43c38 100644 --- a/app/assets/javascripts/discourse/widgets/post-admin-menu.js.es6 +++ b/app/assets/javascripts/discourse/widgets/post-admin-menu.js.es6 @@ -10,7 +10,7 @@ createWidget('post-admin-menu-button', jQuery.extend(ButtonClass, { } })); -export function buildManageButtons(attrs, currentUser) { +export function buildManageButtons(attrs, currentUser, siteSettings) { if (!currentUser) { return []; } @@ -67,12 +67,14 @@ export function buildManageButtons(attrs, currentUser) { } if (currentUser.staff) { - contents.push({ - icon: 'certificate', - label: 'post.controls.grant_badge', - action: 'grantBadge', - className: 'grant-badge' - }); + if (siteSettings.enable_badges) { + contents.push({ + icon: 'certificate', + label: 'post.controls.grant_badge', + action: 'grantBadge', + className: 'grant-badge' + }); + } const action = attrs.locked ? "unlock" : "lock"; contents.push({ @@ -112,7 +114,7 @@ export default createWidget('post-admin-menu', { const contents = []; contents.push(h('h3', I18n.t('admin_title'))); - buildManageButtons(this.attrs, this.currentUser).forEach(b => { + buildManageButtons(this.attrs, this.currentUser, this.siteSettings).forEach(b => { contents.push(this.attach('post-admin-menu-button', b)); }); diff --git a/app/assets/javascripts/discourse/widgets/post.js.es6 b/app/assets/javascripts/discourse/widgets/post.js.es6 index 29618627e6..887493fd45 100644 --- a/app/assets/javascripts/discourse/widgets/post.js.es6 +++ b/app/assets/javascripts/discourse/widgets/post.js.es6 @@ -17,9 +17,13 @@ export function avatarImg(wanted, attrs) { if (!url || url.length === 0) { return; } const title = formatUsername(attrs.username); + let className = 'avatar' + ( + attrs.extraClasses ? " " + attrs.extraClasses : "" + ); + const properties = { attributes: { alt: '', width: size, height: size, src: Discourse.getURLWithCDN(url), title }, - className: 'avatar' + className }; return h('img', properties); diff --git a/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 b/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 index 0b4d32661a..a82525157f 100644 --- a/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 +++ b/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 @@ -5,11 +5,17 @@ import { relativeAge } from 'discourse/lib/formatter'; import { iconNode } from 'discourse-common/lib/icon-library'; import RawHtml from 'discourse/widgets/raw-html'; -const SCROLLAREA_HEIGHT = 300; const SCROLLER_HEIGHT = 50; -const SCROLLAREA_REMAINING = SCROLLAREA_HEIGHT - SCROLLER_HEIGHT; const LAST_READ_HEIGHT = 20; +function scrollareaHeight() { + return ($(window).height() < 425) ? 150 : 300; +} + +function scrollareaRemaining() { + return scrollareaHeight() - SCROLLER_HEIGHT; +} + function clamp(p, min=0.0, max=1.0) { return Math.max(Math.min(p, max), min); } @@ -27,7 +33,7 @@ createWidget('timeline-last-read', { tagName: 'div.timeline-last-read', buildAttributes(attrs) { - const bottom = SCROLLAREA_HEIGHT - (LAST_READ_HEIGHT / 2); + const bottom = scrollareaHeight() - (LAST_READ_HEIGHT / 2); const top = attrs.top > bottom ? bottom : attrs.top; return { style: `height: ${LAST_READ_HEIGHT}px; top: ${top}px` }; }, @@ -115,7 +121,7 @@ createWidget('timeline-scrollarea', { buildKey: () => `timeline-scrollarea`, buildAttributes() { - return { style: `height: ${SCROLLAREA_HEIGHT}px` }; + return { style: `height: ${scrollareaHeight()}px` }; }, defaultState(attrs) { @@ -168,8 +174,8 @@ createWidget('timeline-scrollarea', { const percentage = state.percentage; if (percentage === null) { return; } - const before = SCROLLAREA_REMAINING * percentage; - const after = (SCROLLAREA_HEIGHT - before) - SCROLLER_HEIGHT; + const before = scrollareaRemaining() * percentage; + const after = (scrollareaHeight() - before) - SCROLLER_HEIGHT; let showButton = false; const hasBackPosition = @@ -179,13 +185,13 @@ createWidget('timeline-scrollarea', { (position.lastRead && position.lastRead !== position.total); if (hasBackPosition) { - const lastReadTop = Math.round(position.lastReadPercentage * SCROLLAREA_HEIGHT); + const lastReadTop = Math.round(position.lastReadPercentage * scrollareaHeight()); showButton = ((before + SCROLLER_HEIGHT - 5) < lastReadTop) || (before > (lastReadTop + 25)); // Don't show if at the bottom of the timeline - if (lastReadTop > (SCROLLAREA_HEIGHT - (LAST_READ_HEIGHT / 2))) { + if (lastReadTop > (scrollareaHeight() - (LAST_READ_HEIGHT / 2))) { showButton = false; } } @@ -200,7 +206,7 @@ createWidget('timeline-scrollarea', { ]; if (hasBackPosition) { - const lastReadTop = Math.round(position.lastReadPercentage * SCROLLAREA_HEIGHT); + const lastReadTop = Math.round(position.lastReadPercentage * scrollareaHeight()); result.push(this.attach('timeline-last-read', { top: lastReadTop, lastRead: position.lastRead, diff --git a/app/assets/javascripts/discourse/widgets/user-menu.js.es6 b/app/assets/javascripts/discourse/widgets/user-menu.js.es6 index 115c3deb34..9b5502e91a 100644 --- a/app/assets/javascripts/discourse/widgets/user-menu.js.es6 +++ b/app/assets/javascripts/discourse/widgets/user-menu.js.es6 @@ -40,7 +40,7 @@ createWidget('user-menu-links', { icon: 'bookmark', href: `${path}/activity/bookmarks` }); - if (siteSettings.enable_private_messages) { + if (siteSettings.enable_personal_messages) { glyphs.push({ label: 'user.private_messages', className: 'user-pms-link', icon: 'envelope', diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown-it.js.es6 b/app/assets/javascripts/pretty-text/engines/discourse-markdown-it.js.es6 index 4af5b67f08..ecec3b1697 100644 --- a/app/assets/javascripts/pretty-text/engines/discourse-markdown-it.js.es6 +++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown-it.js.es6 @@ -215,10 +215,12 @@ export function setup(opts, siteSettings, state) { html: true, breaks: opts.discourse.features.newline, xhtmlOut: false, - linkify: opts.discourse.features.linkify, + linkify: siteSettings.enable_markdown_linkify, typographer: siteSettings.enable_markdown_typographer }); + opts.engine.linkify.tlds((siteSettings.markdown_linkify_tlds || '').split('|')); + setupUrlDecoding(opts.engine); setupHoister(opts.engine); setupImageDimensions(opts.engine); diff --git a/app/assets/javascripts/pretty-text/white-lister.js.es6 b/app/assets/javascripts/pretty-text/white-lister.js.es6 index 4fd6e36295..6dd1523f53 100644 --- a/app/assets/javascripts/pretty-text/white-lister.js.es6 +++ b/app/assets/javascripts/pretty-text/white-lister.js.es6 @@ -137,6 +137,7 @@ const DEFAULT_LIST = [ 'div.quote-controls', 'div.title', 'div[align]', + 'div[data-theme-*]', 'div[dir]', 'dl', 'dt', diff --git a/app/assets/javascripts/select-kit/components/admin-agree-flag-dropdown.js.es6 b/app/assets/javascripts/select-kit/components/admin-agree-flag-dropdown.js.es6 index 55708f10ea..3020529ef6 100644 --- a/app/assets/javascripts/select-kit/components/admin-agree-flag-dropdown.js.es6 +++ b/app/assets/javascripts/select-kit/components/admin-agree-flag-dropdown.js.es6 @@ -55,6 +55,22 @@ export default DropdownSelectBox.extend({ label: I18n.t("admin.flags.agree_flag"), }); + content.push({ + icon: 'ban', + id: 'confirm-agree-suspend', + description: I18n.t('admin.flags.agree_flag_suspend_title'), + action: () => this.send("showSuspendModal"), + label: I18n.t("admin.flags.agree_flag_suspend"), + }); + + content.push({ + icon: 'microphone-slash', + id: 'confirm-agree-silence', + description: I18n.t('admin.flags.agree_flag_silence_title'), + action: () => this.send("showSilenceModal"), + label: I18n.t("admin.flags.agree_flag_silence"), + }); + if (canDeleteSpammer) { content.push({ title: I18n.t("admin.flags.delete_spammer_title"), @@ -79,6 +95,28 @@ export default DropdownSelectBox.extend({ this.attrs.removeAfter(spammerDetails.deleteUser()); }, + showSuspendModal() { + let post = this.get('post'); + let user = post.get('user'); + this.get('adminTools').showSuspendModal(user, { + post, + before: () => { + return this.attrs.removeAfter(post.agreeFlags('suspended')); + } + }); + }, + + showSilenceModal() { + let post = this.get('post'); + let user = post.get('user'); + this.get('adminTools').showSilenceModal(user, { + post, + before: () => { + return this.attrs.removeAfter(post.agreeFlags('silenced')); + } + }); + }, + perform(action) { let flaggedPost = this.get("post"); this.attrs.removeAfter(flaggedPost.agreeFlags(action)); diff --git a/app/assets/javascripts/select-kit/components/category-chooser.js.es6 b/app/assets/javascripts/select-kit/components/category-chooser.js.es6 index 65030131c7..82c9f27d09 100644 --- a/app/assets/javascripts/select-kit/components/category-chooser.js.es6 +++ b/app/assets/javascripts/select-kit/components/category-chooser.js.es6 @@ -3,6 +3,7 @@ import { on } from "ember-addons/ember-computed-decorators"; import computed from "ember-addons/ember-computed-decorators"; import PermissionType from "discourse/models/permission-type"; import Category from "discourse/models/category"; +import { categoryBadgeHTML } from "discourse/helpers/category-link"; const { get, isNone, isEmpty } = Ember; export default ComboBoxComponent.extend({ @@ -57,6 +58,36 @@ export default ComboBoxComponent.extend({ } }, + computeHeaderContent() { + let content = this.baseHeaderComputedContent(); + + if (this.get("hasSelection")) { + const category = Category.findById(content.value); + const parentCategoryId = category.get("parent_category_id"); + const hasParentCategory = Ember.isPresent(parentCategoryId); + + let badge = ""; + + if (hasParentCategory) { + const parentCategory = Category.findById(parentCategoryId); + badge += categoryBadgeHTML(parentCategory, { + link: false, + allowUncategorized: true + }).htmlSafe(); + } + + badge += categoryBadgeHTML(category, { + link: false, + hideParent: hasParentCategory ? true : false, + allowUncategorized: true + }).htmlSafe(); + + content.label = badge; + } + + return content; + }, + @on("didRender") _bindComposerResizing() { this.appEvents.on("composer:resized", this, this.applyDirection); diff --git a/app/assets/javascripts/select-kit/components/category-drop.js.es6 b/app/assets/javascripts/select-kit/components/category-drop.js.es6 index 89a8d485f0..83ac00ff1d 100644 --- a/app/assets/javascripts/select-kit/components/category-drop.js.es6 +++ b/app/assets/javascripts/select-kit/components/category-drop.js.es6 @@ -9,7 +9,6 @@ export default ComboBoxComponent.extend({ classNameBindings: ["categoryStyle"], classNames: "category-drop", verticalOffset: 3, - collectionHeight: "200", content: Ember.computed.alias("categories"), rowComponent: "category-row", headerComponent: "category-drop/category-drop-header", @@ -37,6 +36,11 @@ export default ComboBoxComponent.extend({ }); }, + @computed("content") + filterable(content) { + return content && content.length >= 15; + }, + @computed("allCategoriesUrl", "allCategoriesLabel", "noCategoriesUrl", "noCategoriesLabel") collectionHeader(allCategoriesUrl, allCategoriesLabel, noCategoriesUrl, noCategoriesLabel) { let shortcuts = ""; @@ -70,9 +74,9 @@ export default ComboBoxComponent.extend({ }).htmlSafe(); } else { if (this.get("noSubcategories")) { - content.label = this.get("noCategoriesLabel"); + content.label = `${this.get("noCategoriesLabel")}`; } else { - content.label = this.get("allCategoriesLabel"); + content.label = `${this.get("allCategoriesLabel")}`; } } diff --git a/app/assets/javascripts/select-kit/components/category-row.js.es6 b/app/assets/javascripts/select-kit/components/category-row.js.es6 index ccd131c75d..8d1c8cd3ed 100644 --- a/app/assets/javascripts/select-kit/components/category-row.js.es6 +++ b/app/assets/javascripts/select-kit/components/category-row.js.es6 @@ -32,18 +32,21 @@ export default SelectKitRowComponent.extend({ } }, - @computed("category") - badgeForCategory(category) { + @computed("category", "parentCategory") + badgeForCategory(category, parentCategory) { return categoryBadgeHTML(category, { link: this.get("categoryLink"), allowUncategorized: this.get("allowUncategorized"), - hideParent: this.get("hideParentCategory") + hideParent: parentCategory ? true : false }).htmlSafe(); }, @computed("parentCategory") badgeForParentCategory(parentCategory) { - return categoryBadgeHTML(parentCategory, {link: false}).htmlSafe(); + return categoryBadgeHTML(parentCategory, { + link: this.get("categoryLink"), + allowUncategorized: this.get("allowUncategorized") + }).htmlSafe(); }, @computed("parentCategoryid") diff --git a/app/assets/javascripts/select-kit/components/composer-actions.js.es6 b/app/assets/javascripts/select-kit/components/composer-actions.js.es6 new file mode 100644 index 0000000000..174563ba04 --- /dev/null +++ b/app/assets/javascripts/select-kit/components/composer-actions.js.es6 @@ -0,0 +1,206 @@ +import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; +import computed from "ember-addons/ember-computed-decorators"; +import { default as Composer, PRIVATE_MESSAGE, CREATE_TOPIC, REPLY, EDIT } from "discourse/models/composer"; + +// Component can get destroyed and lose state +let _topicSnapshot = null; +let _postSnapshot = null; + +export function _clearSnapshots() { + _topicSnapshot = null; + _postSnapshot = null; +} + +export default DropdownSelectBoxComponent.extend({ + composerController: Ember.inject.controller("composer"), + pluginApiIdentifiers: ["composer-actions"], + classNames: "composer-actions", + fullWidthOnMobile: true, + autofilterable: false, + filterable: false, + allowInitialValueMutation: false, + allowAutoSelectFirst: false, + showFullTitle: false, + + didReceiveAttrs() { + this._super(); + + // if we change topic we want to change both snapshots + if (this.get("composerModel.topic") && (!_topicSnapshot || this.get("composerModel.topic.id") !== _topicSnapshot.get("id"))) { + _topicSnapshot = this.get("composerModel.topic"); + _postSnapshot = this.get("composerModel.post"); + } + + // if we hit reply on a different post we want to change postSnapshot + if (this.get("composerModel.post") && (!_postSnapshot || this.get("composerModel.post.id") !== _postSnapshot.get("id"))) { + _postSnapshot = this.get("composerModel.post"); + } + }, + + computeHeaderContent() { + let content = this.baseHeaderComputedContent(); + + switch (this.get("action")) { + case PRIVATE_MESSAGE: + case CREATE_TOPIC: + case REPLY: + content.icon = "mail-forward"; + break; + case EDIT: + content.icon = "pencil"; + break; + }; + + return content; + }, + + @computed("options", "canWhisper", "action") + content(options, canWhisper, action) { + let items = []; + + if (action !== CREATE_TOPIC) { + items.push({ + name: I18n.t("composer.composer_actions.reply_as_new_topic.label"), + description: I18n.t("composer.composer_actions.reply_as_new_topic.desc"), + icon: "plus", + id: "reply_as_new_topic" + }); + } + + if ((action !== REPLY && _postSnapshot) || (action === REPLY && _postSnapshot && !(options.userAvatar && options.userLink))) { + items.push({ + name: I18n.t("composer.composer_actions.reply_to_post.label", { + postNumber: _postSnapshot.get("post_number"), + postUsername: _postSnapshot.get("username") + }), + description: I18n.t("composer.composer_actions.reply_to_post.desc"), + icon: "mail-forward", + id: "reply_to_post" + }); + } + + if (this.siteSettings.enable_personal_messages && action !== PRIVATE_MESSAGE) { + items.push({ + name: I18n.t("composer.composer_actions.reply_as_private_message.label"), + description: I18n.t("composer.composer_actions.reply_as_private_message.desc"), + icon: "envelope", + id: "reply_as_private_message" + }); + } + + if ((action !== REPLY && _topicSnapshot) || (action === REPLY && _topicSnapshot && (options.userAvatar && options.userLink && options.topicLink))) { + items.push({ + name: I18n.t("composer.composer_actions.reply_to_topic.label"), + description: I18n.t("composer.composer_actions.reply_to_topic.desc"), + icon: "mail-forward", + id: "reply_to_topic" + }); + } + + // if answered post is a whisper, we can only answer with a whisper so no need for toggle + if (canWhisper && (!_postSnapshot || _postSnapshot && _postSnapshot.post_type !== this.site.post_types.whisper)) { + items.push({ + name: I18n.t("composer.composer_actions.toggle_whisper.label"), + description: I18n.t("composer.composer_actions.toggle_whisper.desc"), + icon: "eye-slash", + id: "toggle_whisper" + }); + } + + // Edge case: If personal messages are disabled, it is possible to have + // no items which stil renders a button that pops up nothing. In this + // case, add an option for what you're currently doing. + if (action === CREATE_TOPIC && items.length === 0) { + items.push({ + name: I18n.t("composer.composer_actions.create_topic.label"), + description: I18n.t("composer.composer_actions.reply_as_new_topic.desc"), + icon: "mail-forward", + id: "create_topic" + }); + } + return items; + }, + + _replyFromExisting(options, post, topic) { + const reply = this.get("composerModel.reply"); + + let url; + if (post) url = post.get("url"); + if (!post && topic) url = topic.get("url"); + + let topicTitle; + if (topic) topicTitle = topic.get("title"); + + this.get("composerController").close(); + this.get("composerController").open(options).then(() => { + if (!url || ! topicTitle) return; + + url = `${location.protocol}//${location.host}${url}`; + const link = `[${Handlebars.escapeExpression(topicTitle)}](${url})`; + const continueDiscussion = I18n.t("post.continue_discussion", { postLink: link }); + + if (!reply.includes(continueDiscussion)) { + this.get("composerController") + .get("model") + .prependText(continueDiscussion, {new_line: true}); + } + }); + }, + + actions: { + onSelect(value) { + let options = { + draftKey: this.get("composerModel.draftKey"), + draftSequence: this.get("composerModel.draftSequence"), + reply: this.get("composerModel.reply") + }; + + switch(value) { + case "toggle_whisper": + this.set("composerModel.whisper", !this.get("composerModel.whisper")); + break; + + case "reply_to_post": + options.action = Composer.REPLY; + options.post = _postSnapshot; + + this.get("composerController").close(); + this.get("composerController").open(options); + break; + + case "reply_to_topic": + options.action = Composer.REPLY; + options.topic = _topicSnapshot; + + this.get("composerController").close(); + this.get("composerController").open(options); + break; + + case "reply_as_new_topic": + options.action = Composer.CREATE_TOPIC; + options.categoryId = this.get("composerModel.topic.category.id"); + + this._replyFromExisting(options, _postSnapshot, _topicSnapshot); + break; + + case "reply_as_private_message": + let usernames; + + if (_postSnapshot && !_postSnapshot.get("yours")) { + const postUsername = _postSnapshot.get("username"); + if (postUsername) { + usernames = postUsername; + } + } + + options.action = Composer.PRIVATE_MESSAGE; + options.usernames = usernames; + options.archetypeId = "private_message"; + options.draftKey = Composer.NEW_PRIVATE_MESSAGE_KEY; + + this._replyFromExisting(options, _postSnapshot, _topicSnapshot); + break; + } + } + } +}); diff --git a/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 b/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 new file mode 100644 index 0000000000..d28f7a1caa --- /dev/null +++ b/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 @@ -0,0 +1,261 @@ +import ComboBox from "select-kit/components/combo-box"; +import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from 'discourse/lib/ajax-error'; +import { default as computed } from "ember-addons/ember-computed-decorators"; +import renderTag from "discourse/lib/render-tag"; +const { get, isEmpty, isPresent, run, makeArray } = Ember; + +export default ComboBox.extend({ + allowContentReplacement: true, + pluginApiIdentifiers: ["mini-tag-chooser"], + classNames: ["mini-tag-chooser"], + classNameBindings: ["noTags"], + verticalOffset: 3, + filterable: true, + noTags: Ember.computed.empty("computedTags"), + allowAny: true, + maximumSelectionSize: Ember.computed.alias("siteSettings.max_tags_per_topic"), + caretUpIcon: Ember.computed.alias("caretIcon"), + caretDownIcon: Ember.computed.alias("caretIcon"), + + init() { + this._super(); + + this.set("termMatchesForbidden", false); + + this.set("templateForRow", (rowComponent) => { + const tag = rowComponent.get("computedContent"); + return renderTag(get(tag, "value"), { + count: get(tag, "originalContent.count"), + noHref: true + }); + }); + }, + + @computed("limitReached", "maximumSelectionSize") + maxContentRow(limitReached, count) { + if (limitReached) { + return I18n.t("select_kit.max_content_reached", { count }); + } + }, + + mutateAttributes() { + this.set("value", null); + }, + + @computed("limitReached") + caretIcon(limitReached) { + return limitReached ? null : "plus"; + }, + + @computed("computedTags.[]", "maximumSelectionSize") + limitReached(computedTags, maximumSelectionSize) { + if (computedTags.length >= maximumSelectionSize) { + return true; + } + + return false; + }, + + @computed("tags") + computedTags(tags) { + return makeArray(tags); + }, + + validateCreate(term) { + if (this.get("limitReached") || !this.site.get("can_create_tag")) { + return false; + } + + const filterRegexp = new RegExp(this.site.tags_filter_regexp, "g"); + term = term.replace(filterRegexp, "").trim().toLowerCase(); + + if (!term.length || this.get("termMatchesForbidden")) { + return false; + } + + if (this.get("siteSettings.max_tag_length") < term.length) { + return false; + } + + return true; + }, + + validateSelect() { + return this.get("computedTags").length < this.get("siteSettings.max_tags_per_topic"); + }, + + filterComputedContent(computedContent) { + return computedContent; + }, + + didRender() { + this._super(); + + this.$().on("click.mini-tag-chooser", ".selected-tag", (event) => { + event.stopImmediatePropagation(); + this.send("removeTag", $(event.target).attr("data-value")); + }); + }, + + willDestroyElement() { + this._super(); + + $(".select-kit-body").off("click.mini-tag-chooser"); + + const searchDebounce = this.get("searchDebounce"); + if (isPresent(searchDebounce)) { run.cancel(searchDebounce); } + }, + + didPressEscape(event) { + const $lastSelectedTag = $(".selected-tag.selected:last"); + + if ($lastSelectedTag && this.get("isExpanded")) { + $lastSelectedTag.removeClass("selected"); + this._destroyEvent(event); + } else { + this._super(event); + } + }, + + backspaceFromFilter(event) { + this.didPressBackspace(event); + }, + + didPressBackspace() { + if (!this.get("isExpanded")) { + this.expand(); + return; + } + + const $lastSelectedTag = $(".selected-tag:last"); + + if (!isEmpty(this.get("filter"))) { + $lastSelectedTag.removeClass("is-highlighted"); + return; + } + + if (!$lastSelectedTag.length) return; + + if (!$lastSelectedTag.hasClass("is-highlighted")) { + $lastSelectedTag.addClass("is-highlighted"); + } else { + this.send("removeTag", $lastSelectedTag.attr("data-value")); + } + }, + + @computed("tags.[]", "filter") + collectionHeader(tags, filter) { + if (!isEmpty(tags)) { + let output = ""; + + if (tags.length >= 20) { + tags = tags.filter(t => t.indexOf(filter) >= 0); + } + + tags.map((tag) => { + output += ` + + `; + }); + + return `
            ${output}
            `; + } + }, + + computeHeaderContent() { + let content = this.baseHeaderComputedContent(); + const joinedTags = this.get("computedTags").join(", "); + + if (isEmpty(this.get("computedTags"))) { + content.label = I18n.t("tagging.choose_for_topic"); + } else { + content.label = joinedTags; + } + + content.title = content.name = content.value = joinedTags; + + return content; + }, + + actions: { + removeTag(tag) { + let tags = this.get("computedTags"); + delete tags[tags.indexOf(tag)]; + this.set("tags", tags.filter(t => t)); + this.set("content", []); + this.set("searchDebounce", run.debounce(this, this._searchTags, this.get("filter"), 250)); + }, + + onExpand() { + if (isEmpty(this.get("content"))) { + this.set("searchDebounce", run.debounce(this, this._searchTags, this.get("filter"), 250)); + } + }, + + onFilter(filter) { + filter = isEmpty(filter) ? null : filter; + this.set("searchDebounce", run.debounce(this, this._searchTags, filter, 250)); + }, + + onSelect(tag) { + if (isEmpty(this.get("computedTags"))) { + this.set("tags", makeArray(tag)); + } else { + this.set("tags", this.get("computedTags").concat(tag)); + } + + this.set("content", []); + this.set("searchDebounce", run.debounce(this, this._searchTags, this.get("filter"), 250)); + } + }, + + _searchTags(query) { + this.startLoading(); + + const self = this; + const selectedTags = makeArray(this.get("computedTags")).filter(t => t); + const sortTags = this.siteSettings.tags_sort_alphabetically; + const data = { + q: query, + limit: this.siteSettings.max_tag_search_results, + categoryId: this.get("categoryId") + }; + + if (selectedTags) { + data.selected_tags = selectedTags.slice(0, 100); + } + + ajax(Discourse.getURL("/tags/filter/search"), { + quietMillis: 200, + cache: true, + dataType: "json", + data, + }).then(json => { + let results = json.results; + + self.set("termMatchesForbidden", json.forbidden ? true : false); + + if (sortTags) { + results = results.sort((a, b) => a.id > b.id); + } + + const content = results.map((result) => { + return { + id: result.text, + name: result.text, + count: result.count + }; + }).filter(c => !selectedTags.includes(c.id)); + + self.set("content", content); + self.stopLoading(); + this.autoHighlight(); + }).catch(error => { + self.stopLoading(); + popupAjaxError(error); + }); + } +}); diff --git a/app/assets/javascripts/select-kit/components/multi-select.js.es6 b/app/assets/javascripts/select-kit/components/multi-select.js.es6 index beec884459..e3e0ad9a13 100644 --- a/app/assets/javascripts/select-kit/components/multi-select.js.es6 +++ b/app/assets/javascripts/select-kit/components/multi-select.js.es6 @@ -252,10 +252,6 @@ export default SelectKitComponent.extend({ this.autoHighlight(); }, - validateComputedContentItem(computedContentItem) { - return !this.get("computedValues").includes(computedContentItem.value); - }, - actions: { clearSelection() { this.send("deselect", this.get("selectedComputedContents")); @@ -263,7 +259,8 @@ export default SelectKitComponent.extend({ }, create(computedContentItem) { - if (this.validateComputedContentItem(computedContentItem)) { + if (!this.get("computedValues").includes(computedContentItem.value) && + this.validateCreate(computedContentItem.value)) { this.get("computedContent").pushObject(computedContentItem); this._boundaryActionHandler("onCreate"); this.send("select", computedContentItem); @@ -274,9 +271,14 @@ export default SelectKitComponent.extend({ select(computedContentItem) { this.willSelect(computedContentItem); - this.get("computedValues").pushObject(computedContentItem.value); - Ember.run.next(() => this.mutateAttributes()); - Ember.run.schedule("afterRender", () => this.didSelect(computedContentItem)); + + if (this.validateSelect(computedContentItem)) { + this.get("computedValues").pushObject(computedContentItem.value); + Ember.run.next(() => this.mutateAttributes()); + Ember.run.schedule("afterRender", () => this.didSelect(computedContentItem)); + } else { + this._boundaryActionHandler("onSelectFailure"); + } }, deselect(rowComputedContentItems) { diff --git a/app/assets/javascripts/select-kit/components/none-category-row.js.es6 b/app/assets/javascripts/select-kit/components/none-category-row.js.es6 index 13636aa770..51b6b4add0 100644 --- a/app/assets/javascripts/select-kit/components/none-category-row.js.es6 +++ b/app/assets/javascripts/select-kit/components/none-category-row.js.es6 @@ -1,9 +1,20 @@ import CategoryRowComponent from "select-kit/components/category-row"; +import { categoryBadgeHTML } from "discourse/helpers/category-link"; +import computed from "ember-addons/ember-computed-decorators"; export default CategoryRowComponent.extend({ layoutName: "select-kit/templates/components/category-row", classNames: "none category-row", + @computed("category") + badgeForCategory(category) { + return categoryBadgeHTML(category, { + link: this.get("categoryLink"), + allowUncategorized: true, + hideParent: true + }).htmlSafe(); + }, + click() { this.sendAction("clearSelection"); } diff --git a/app/assets/javascripts/select-kit/components/notifications-button.js.es6 b/app/assets/javascripts/select-kit/components/notifications-button.js.es6 index eaa9c6780f..1c91d22a8e 100644 --- a/app/assets/javascripts/select-kit/components/notifications-button.js.es6 +++ b/app/assets/javascripts/select-kit/components/notifications-button.js.es6 @@ -8,7 +8,6 @@ export default DropdownSelectBoxComponent.extend({ nameProperty: "key", fullWidthOnMobile: true, content: allLevels, - collectionHeight: "auto", castInteger: true, autofilterable: false, filterable: false, diff --git a/app/assets/javascripts/select-kit/components/period-chooser.js.es6 b/app/assets/javascripts/select-kit/components/period-chooser.js.es6 index 02bb13c907..939bdedd94 100644 --- a/app/assets/javascripts/select-kit/components/period-chooser.js.es6 +++ b/app/assets/javascripts/select-kit/components/period-chooser.js.es6 @@ -4,7 +4,6 @@ export default DropdownSelectBoxComponent.extend({ classNames: ["period-chooser"], rowComponent: "period-chooser/period-chooser-row", headerComponent: "period-chooser/period-chooser-header", - collectionHeight: "auto", content: Ember.computed.alias("site.periods"), value: Ember.computed.alias("period"), isHidden: Ember.computed.alias("showPeriods"), diff --git a/app/assets/javascripts/select-kit/components/select-kit.js.es6 b/app/assets/javascripts/select-kit/components/select-kit.js.es6 index e08b74e32f..b8bdf5c667 100644 --- a/app/assets/javascripts/select-kit/components/select-kit.js.es6 +++ b/app/assets/javascripts/select-kit/components/select-kit.js.es6 @@ -14,8 +14,9 @@ import { export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixin, EventsMixin, { pluginApiIdentifiers: ["select-kit"], layoutName: "select-kit/templates/components/select-kit", - classNames: ["select-kit", "select-box-kit"], + classNames: ["select-kit"], classNameBindings: [ + "isLoading", "isFocused", "isExpanded", "isDisabled", @@ -30,17 +31,18 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi isExpanded: false, isFocused: false, isHidden: false, + isLoading: false, renderedBodyOnce: false, renderedFilterOnce: false, tabindex: 0, none: null, highlightedValue: null, - noContentLabel: "select_kit.no_content", valueAttribute: "id", nameProperty: "name", autoFilterable: false, filterable: false, filter: "", + previousFilter: null, filterPlaceholder: "select_kit.filter_placeholder", filterIcon: "search", headerIcon: null, @@ -54,7 +56,6 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi headerComputedContent: null, collectionHeaderComputedContent: null, collectionComponent: "select-kit/select-kit-collection", - collectionHeight: 200, verticalOffset: 0, horizontalOffset: 0, fullWidthOnMobile: false, @@ -68,6 +69,8 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi allowContentReplacement: false, collectionHeader: null, allowAutoSelectFirst: true, + maximumSelectionSize: null, + maxContentRow: null, init() { this._super(); @@ -119,6 +122,10 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi return this.baseComputedContentItem(contentItem, options); }, + validateCreate() { return true; }, + + validateSelect() { return true; }, + baseComputedContentItem(contentItem, options) { let originalContent; options = options || {}; @@ -149,8 +156,10 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi }, @computed("filter", "filteredComputedContent.[]") - shouldDisplayNoContentRow(filter, filteredComputedContent) { - return filter.length > 0 && filteredComputedContent.length === 0; + noContentRow(filter, filteredComputedContent) { + if (filter.length > 0 && filteredComputedContent.length === 0) { + return I18n.t("select_kit.no_content"); + } }, @computed("filter", "filterable", "autoFilterable", "renderedFilterOnce") @@ -164,7 +173,7 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi @computed("filter", "computedContent") shouldDisplayCreateRow(filter, computedContent) { if (computedContent.map(c => c.value).includes(filter)) return false; - if (this.get("allowAny") && filter.length > 0) return true; + if (this.get("allowAny") && filter.length > 0 && this.validateCreate(filter)) return true; return false; }, @@ -185,7 +194,7 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi @computed("filter") templateForCreateRow() { return (rowComponent) => { - return I18n.t("select_box.create", { + return I18n.t("select_kit.create", { content: rowComponent.get("computedContent.name") }); }; @@ -239,6 +248,16 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi this.setProperties({ filter: "" }); }, + startLoading() { + this.set("isLoading", true); + this._boundaryActionHandler("onStartLoading"); + }, + + stopLoading() { + this.set("isLoading", false); + this._boundaryActionHandler("onStopLoading"); + }, + _setCollectionHeaderComputedContent() { const collectionHeaderComputedContent = applyCollectionHeaderCallbacks( this.get("pluginApiIdentifiers"), @@ -284,9 +303,12 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi }, filterComputedContent(filter) { + if (filter === this.get("previousFilter")) return; + this.setProperties({ highlightedValue: null, renderedFilterOnce: true, + previousFilter: filter, filter }); this.autoHighlight(); diff --git a/app/assets/javascripts/select-kit/components/select-kit/select-kit-collection.js.es6 b/app/assets/javascripts/select-kit/components/select-kit/select-kit-collection.js.es6 index 67508609d2..3dd760e985 100644 --- a/app/assets/javascripts/select-kit/components/select-kit/select-kit-collection.js.es6 +++ b/app/assets/javascripts/select-kit/components/select-kit/select-kit-collection.js.es6 @@ -1,5 +1,5 @@ export default Ember.Component.extend({ layoutName: "select-kit/templates/components/select-kit/select-kit-collection", - classNames: ["select-kit-collection", "select-box-kit-collection"], + classNames: ["select-kit-collection"], tagName: "ul" }); diff --git a/app/assets/javascripts/select-kit/components/select-kit/select-kit-filter.js.es6 b/app/assets/javascripts/select-kit/components/select-kit/select-kit-filter.js.es6 index f60a627de8..fbfed55bc3 100644 --- a/app/assets/javascripts/select-kit/components/select-kit/select-kit-filter.js.es6 +++ b/app/assets/javascripts/select-kit/components/select-kit/select-kit-filter.js.es6 @@ -1,6 +1,6 @@ export default Ember.Component.extend({ layoutName: "select-kit/templates/components/select-kit/select-kit-filter", - classNames: ["select-kit-filter", "select-box-kit-filter"], + classNames: ["select-kit-filter"], classNameBindings: ["isFocused", "isHidden"], isHidden: Ember.computed.not("shouldDisplayFilter") }); diff --git a/app/assets/javascripts/select-kit/components/select-kit/select-kit-header.js.es6 b/app/assets/javascripts/select-kit/components/select-kit/select-kit-header.js.es6 index 3ae115399f..d5e76b4a76 100644 --- a/app/assets/javascripts/select-kit/components/select-kit/select-kit-header.js.es6 +++ b/app/assets/javascripts/select-kit/components/select-kit/select-kit-header.js.es6 @@ -2,7 +2,7 @@ import computed from 'ember-addons/ember-computed-decorators'; export default Ember.Component.extend({ layoutName: "select-kit/templates/components/select-kit/select-kit-header", - classNames: ["select-kit-header", "select-box-kit-header"], + classNames: ["select-kit-header"], classNameBindings: ["isFocused"], attributeBindings: [ "tabindex", diff --git a/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6 b/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6 index 493b7a32bb..3ac3a2ce8e 100644 --- a/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6 +++ b/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6 @@ -5,7 +5,7 @@ import UtilsMixin from "select-kit/mixins/utils"; export default Ember.Component.extend(UtilsMixin, { layoutName: "select-kit/templates/components/select-kit/select-kit-row", - classNames: ["select-kit-row", "select-box-kit-row"], + classNames: ["select-kit-row"], tagName: "li", tabIndex: -1, attributeBindings: [ diff --git a/app/assets/javascripts/select-kit/components/single-select.js.es6 b/app/assets/javascripts/select-kit/components/single-select.js.es6 index af7c1e035a..d442a9e2ae 100644 --- a/app/assets/javascripts/select-kit/components/single-select.js.es6 +++ b/app/assets/javascripts/select-kit/components/single-select.js.es6 @@ -145,10 +145,6 @@ export default SelectKitComponent.extend({ }); }, - validateComputedContentItem(computedContentItem) { - return this.get("computedValue") !== computedContentItem.value; - }, - actions: { clearSelection() { this.send("deselect", this.get("selectedComputedContent")); @@ -156,7 +152,8 @@ export default SelectKitComponent.extend({ }, create(computedContentItem) { - if (this.validateComputedContentItem(computedContentItem)) { + if (this.get("computedValue") !== computedContentItem.value && + this.validateCreate(computedContentItem.value)) { this.get("computedContent").pushObject(computedContentItem); this._boundaryActionHandler("onCreate"); this.send("select", computedContentItem); @@ -166,10 +163,14 @@ export default SelectKitComponent.extend({ }, select(rowComputedContentItem) { - this.willSelect(rowComputedContentItem); - this.set("computedValue", rowComputedContentItem.value); - this.mutateAttributes(); - run.schedule("afterRender", () => this.didSelect(rowComputedContentItem)); + if (this.validateSelect(rowComputedContentItem)) { + this.willSelect(rowComputedContentItem); + this.set("computedValue", rowComputedContentItem.value); + this.mutateAttributes(); + run.schedule("afterRender", () => this.didSelect(rowComputedContentItem)); + } else { + this._boundaryActionHandler("onSelectFailure"); + } }, deselect(rowComputedContentItem) { diff --git a/app/assets/javascripts/select-kit/components/tag-drop.js.es6 b/app/assets/javascripts/select-kit/components/tag-drop.js.es6 index 1ebcd1e9ba..79e9f017cf 100644 --- a/app/assets/javascripts/select-kit/components/tag-drop.js.es6 +++ b/app/assets/javascripts/select-kit/components/tag-drop.js.es6 @@ -8,7 +8,6 @@ export default ComboBoxComponent.extend({ classNameBindings: ["categoryStyle", "tagClass"], classNames: "tag-drop", verticalOffset: 3, - collectionHeight: "200", value: Ember.computed.alias("tagId"), headerComponent: "tag-drop/tag-drop-header", rowComponent: "tag-drop/tag-drop-row", @@ -32,6 +31,11 @@ export default ComboBoxComponent.extend({ return true; }, + @computed("content") + filterable(content) { + return content && content.length >= 15; + }, + computeHeaderContent() { let content = this.baseHeaderComputedContent(); diff --git a/app/assets/javascripts/select-kit/components/toolbar-popup-menu-options.js.es6 b/app/assets/javascripts/select-kit/components/toolbar-popup-menu-options.js.es6 index d679b9421e..8a54a215f1 100644 --- a/app/assets/javascripts/select-kit/components/toolbar-popup-menu-options.js.es6 +++ b/app/assets/javascripts/select-kit/components/toolbar-popup-menu-options.js.es6 @@ -6,7 +6,6 @@ export default DropdownSelectBoxComponent.extend({ classNames: ["toolbar-popup-menu-options"], isHidden: Ember.computed.empty("computedContent"), showFullTitle: false, - collectionHeight: "auto", @computed("title") collectionHeader(title) { diff --git a/app/assets/javascripts/select-kit/mixins/dom-helpers.js.es6 b/app/assets/javascripts/select-kit/mixins/dom-helpers.js.es6 index eb7e93715f..e3521cfdc0 100644 --- a/app/assets/javascripts/select-kit/mixins/dom-helpers.js.es6 +++ b/app/assets/javascripts/select-kit/mixins/dom-helpers.js.es6 @@ -42,7 +42,6 @@ export default Ember.Mixin.create({ @on("didRender") _adjustPosition() { - this.$collection().css("max-height", this.get("collectionHeight")); this._applyFixedPosition(); this._applyDirection(); this._positionWrapper(); diff --git a/app/assets/javascripts/select-kit/mixins/events.js.es6 b/app/assets/javascripts/select-kit/mixins/events.js.es6 index 87d4df2561..b5abd5dd03 100644 --- a/app/assets/javascripts/select-kit/mixins/events.js.es6 +++ b/app/assets/javascripts/select-kit/mixins/events.js.es6 @@ -108,6 +108,9 @@ export default Ember.Mixin.create({ .on("keydown.select-kit", (event) => { const keyCode = event.keyCode || event.which; + if (keyCode === this.keys.BACKSPACE && typeof this.backspaceFromFilter === "function") { + this.backspaceFromFilter(event); + }; if (keyCode === this.keys.TAB) this.tabFromFilter(event); if (keyCode === this.keys.ESC) this.escapeFromFilter(event); if (keyCode === this.keys.ENTER) this.enterFromFilter(event); diff --git a/app/assets/javascripts/select-kit/templates/components/category-row.hbs b/app/assets/javascripts/select-kit/templates/components/category-row.hbs index 985fe9cd74..85cdfd8f15 100644 --- a/app/assets/javascripts/select-kit/templates/components/category-row.hbs +++ b/app/assets/javascripts/select-kit/templates/components/category-row.hbs @@ -15,7 +15,7 @@ {{/if}} {{#if shouldDisplayDescription}} -
            {{{description}}}
            +
            {{{dir-span description}}}
            {{/if}} {{else}} {{{label}}} diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit.hbs index 20bc4ca19f..cd92fb8312 100644 --- a/app/assets/javascripts/select-kit/templates/components/select-kit.hbs +++ b/app/assets/javascripts/select-kit/templates/components/select-kit.hbs @@ -6,6 +6,7 @@ computedContent=headerComputedContent deselect=(action "deselect") toggle=(action "toggle") + isLoading=isLoading filterComputedContent=(action "filterComputedContent") clearSelection=(action "clearSelection") options=headerComponentOptions @@ -14,6 +15,7 @@
            {{component filterComponent filter=filter + isLoading=isLoading icon=filterIcon shouldDisplayFilter=shouldDisplayFilter placeholder=(i18n filterPlaceholder) @@ -38,11 +40,11 @@ select=(action "select") highlight=(action "highlight") create=(action "create") - noContentLabel=noContentLabel highlightedValue=highlightedValue computedValue=computedValue - shouldDisplayNoContentRow=shouldDisplayNoContentRow rowComponentOptions=rowComponentOptions + noContentRow=noContentRow + maxContentRow=maxContentRow }} {{/if}}
            diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs index bb02559029..35d46789dd 100644 --- a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs +++ b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs @@ -30,22 +30,26 @@ }} {{/if}} -{{#each filteredComputedContent as |computedContent|}} - {{component rowComponent - computedContent=computedContent - highlightedValue=highlightedValue - computedValue=computedValue - templateForRow=templateForRow - select=select - highlight=highlight - options=rowComponentOptions - }} -{{/each}} - -{{#if shouldDisplayNoContentRow}} - {{#if noContentLabel}} +{{#if maxContentRow}} +
          • + {{maxContentRow}} +
          • +{{else}} + {{#if noContentRow}}
          • - {{i18n noContentLabel}} + {{noContentRow}}
          • + {{else}} + {{#each filteredComputedContent as |computedContent|}} + {{component rowComponent + computedContent=computedContent + highlightedValue=highlightedValue + computedValue=computedValue + templateForRow=templateForRow + select=select + highlight=highlight + options=rowComponentOptions + }} + {{/each}} {{/if}} {{/if}} diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-filter.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-filter.hbs index 6815973db3..e7d1e96a7e 100644 --- a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-filter.hbs +++ b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-filter.hbs @@ -10,6 +10,10 @@ value=filter }} -{{#if icon}} - {{d-icon icon class="filter-icon"}} +{{#if isLoading}} + {{loading-spinner size="small"}} +{{else}} + {{#if icon}} + {{d-icon icon class="filter-icon"}} + {{/if}} {{/if}} diff --git a/app/assets/javascripts/service-worker.js.erb b/app/assets/javascripts/service-worker.js.erb index 43fb85a33e..7202c6b6f0 100644 --- a/app/assets/javascripts/service-worker.js.erb +++ b/app/assets/javascripts/service-worker.js.erb @@ -11,7 +11,11 @@ const CURRENT_CACHES = { const OFFLINE_URL = 'offline.html'; function createCacheBustedRequest(url) { - var request = new Request(url, {cache: 'reload'}); + var headers = new Headers({ + 'Discourse-Track-View': '0' + }); + + var request = new Request(url, {cache: 'reload', headers: headers}); // See https://fetch.spec.whatwg.org/#concept-request-mode // This is not yet supported in Chrome as of M48, so we need to explicitly check to see // if the cache: 'reload' option had any effect. @@ -22,7 +26,7 @@ function createCacheBustedRequest(url) { // If {cache: 'reload'} didn't have any effect, append a cache-busting URL parameter instead. var bustedUrl = new URL(url, self.location.href); bustedUrl.search += (bustedUrl.search ? '&' : '') + 'cachebust=' + Date.now(); - return new Request(bustedUrl); + return new Request(bustedUrl, {headers: headers}); } self.addEventListener('install', function(event) { diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index 4fb3d28771..670d93d894 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -130,14 +130,23 @@ $mobile-breakpoint: 700px; } } -.content-list li a span.count { - font-size: $font-down-1; - float: right; - margin-right: 10px; - background-color: $primary-low; - padding: 2px 5px; - border-radius: 5px; - color: $primary; +.content-list { + width: 27%; + float: left; + li a span.count { + font-size: $font-down-1; + float: right; + margin-right: 10px; + background-color: $primary-low; + padding: 2px 5px; + border-radius: 5px; + color: $primary; + } +} + +.content-body { + float: left; + width: 60%; } .admin-content { @@ -229,7 +238,7 @@ $mobile-breakpoint: 700px; .select-kit.multi-select { width: 500px; } - .select-box-kit.dropdown-select-box { + .select-kit.dropdown-select-box { width: auto; } @@ -259,6 +268,10 @@ $mobile-breakpoint: 700px; background-color: $primary-low; padding: 10px 10px 3px 0; @include clearfix; + nav { + float: left; + margin-left: 12px; + } .nav.nav-pills { li.active { a { diff --git a/app/assets/stylesheets/common/admin/customize.scss b/app/assets/stylesheets/common/admin/customize.scss index 9a23fb7d73..e3204d60f5 100644 --- a/app/assets/stylesheets/common/admin/customize.scss +++ b/app/assets/stylesheets/common/admin/customize.scss @@ -220,3 +220,7 @@ margin-top: 20px; } } + +#custom_emoji { + width: 27%; +} diff --git a/app/assets/stylesheets/common/admin/suspend.scss b/app/assets/stylesheets/common/admin/suspend.scss index efdfb68f79..8e2ab667e1 100644 --- a/app/assets/stylesheets/common/admin/suspend.scss +++ b/app/assets/stylesheets/common/admin/suspend.scss @@ -21,3 +21,13 @@ float: right; } } + +.modal-body { + .penalty-post-edit { + margin-top: 1em; + + textarea { + height: 10em; + } + } +} diff --git a/app/assets/stylesheets/common/base/_topic-list.scss b/app/assets/stylesheets/common/base/_topic-list.scss index b35977b652..19e372f95c 100644 --- a/app/assets/stylesheets/common/base/_topic-list.scss +++ b/app/assets/stylesheets/common/base/_topic-list.scss @@ -155,6 +155,10 @@ } +.heatmap-high, .heatmap-high a {color: #fe7a15 !important;} +.heatmap-med, .heatmap-med a {color: #cf7721 !important;} +.heatmap-low, .heatmap-low a {color: #9b764f !important;} + .topic-list.categories { .category .badge-notification { diff --git a/app/assets/stylesheets/common/base/compose.scss b/app/assets/stylesheets/common/base/compose.scss index bd343963b3..1a93bad72d 100644 --- a/app/assets/stylesheets/common/base/compose.scss +++ b/app/assets/stylesheets/common/base/compose.scss @@ -11,7 +11,7 @@ max-width: 1475px; width: 100%; &.hide-preview { - max-width:740px; + max-width:740px; } @media screen and (max-width: 1200px) { min-width: 0; @@ -29,7 +29,7 @@ .saving-text, .draft-text { display: none; - padding-left: 10px; + padding-left: 10px; .spinner { margin-left: 5px; border-color: $secondary; @@ -96,10 +96,48 @@ color: $primary-high; } .reply-details { - overflow: hidden; - text-overflow: ellipsis; + max-width: calc(100% - 60px); + flex: 1 1 auto; white-space: nowrap; - max-width: calc(100% - 100px); + } + .composer-action-title { + display: inline-flex; + align-items: center; + width: auto; + max-width: 100%; + + .avatar { + width: 20px; + } + + .action-title { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .topic-link, .user-link, .post-link { + margin-right: 5px; + } + + .username { + margin-right: 5px; + max-width: 100px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + @media screen and (max-width: 500px) { + display: none; + } + } + + .d-icon { + margin-right: 5px; + } + + img.avatar { + margin-right: 3px; + } } .composer-controls { margin-left: auto; @@ -183,9 +221,9 @@ flex-basis: 50%; } - .tag-chooser { + .mini-tag-chooser { flex: 1 1 25%; - margin: 0 0 5px 10px; + margin: 0 0 5px 5px; background: $secondary; @media all and (max-width: 900px) { margin: 0; @@ -367,6 +405,7 @@ div.ac-wrap { box-shadow: none; border: 0; margin: 0; + background: transparent; } } @@ -378,4 +417,4 @@ div.ac-wrap { .md-table { overflow-y: auto; -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/common/base/history.scss b/app/assets/stylesheets/common/base/history.scss index 07f659a6eb..a1faeea65e 100644 --- a/app/assets/stylesheets/common/base/history.scss +++ b/app/assets/stylesheets/common/base/history.scss @@ -13,6 +13,7 @@ #revision-controls { display: inline-block; + margin-bottom: 5px; } .revision-content { diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss index e54693bbce..ad27956a56 100644 --- a/app/assets/stylesheets/common/base/menu-panel.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -75,22 +75,45 @@ li.category-link { float: left; background-color: transparent; - width: 45%; - margin: 5px 5px 0 8px; - .box {margin-top: 0;} + display: inline-flex; + margin: 0.25em 0.5em; + width: 44%; .badge-notification { color: dark-light-choose($primary-medium, $secondary-medium); background-color: transparent; display: inline; padding: 0; + font-size: $font-down-1; + line-height: $line-height-large; + } + .badge-wrapper { + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + max-width: 80%; + &.bar, &.bullet { + color: $primary; + } + &.box { + color: $secondary; + + a.badge.badge-notification { + padding-top: 2px; + } + span { + z-index: z("base") * -1; + } + } } } - + // note these topic counts only appear for anons in the category hamburger drop down b.topics-count { color: dark-light-choose($primary-medium, $secondary-medium); font-weight: normal; - font-size: $font-down-2; + font-size: $font-down-1; + } + .box + b.topics-count { + padding-top: 2px; } span.badge-category { diff --git a/app/assets/stylesheets/common/base/modal.scss b/app/assets/stylesheets/common/base/modal.scss index c47c9d4c2d..2a5b765cd3 100644 --- a/app/assets/stylesheets/common/base/modal.scss +++ b/app/assets/stylesheets/common/base/modal.scss @@ -75,12 +75,13 @@ .modal-outer-container { display:table; + table-layout: fixed; width:100%; height:100%; } .modal-inner-container { - max-width: 710px; + max-width: 700px; margin: 0 auto; background-color: $secondary; background-clip: padding-box; diff --git a/app/assets/stylesheets/common/base/onebox.scss b/app/assets/stylesheets/common/base/onebox.scss index 61d92687fe..c692cfceb7 100644 --- a/app/assets/stylesheets/common/base/onebox.scss +++ b/app/assets/stylesheets/common/base/onebox.scss @@ -463,10 +463,22 @@ aside.onebox.stackexchange .onebox-body { .label2 { float: right; } - .site-icon { - width: 16px; - height: 16px; - margin-right: 3px; +} + +.onebox { + &.whitelistedgeneric, + &.gfycat { + .site-icon { + width: 16px; + height: 16px; + margin-right: 3px; + } + } +} + +.onebox.gfycat p { + span.label1 a { + white-space: nowrap; } } diff --git a/app/assets/stylesheets/common/base/tagging.scss b/app/assets/stylesheets/common/base/tagging.scss index d87997dc27..11248496fc 100644 --- a/app/assets/stylesheets/common/base/tagging.scss +++ b/app/assets/stylesheets/common/base/tagging.scss @@ -44,7 +44,9 @@ } .topic-category { - margin-top: 5px; + display: flex; + flex-wrap: wrap; + align-items: flex-end; .topic-header-extra { display: inline-block; @@ -55,17 +57,14 @@ margin-top: 0; &.bullet .badge-category { - vertical-align: middle; } &.box, &.bullet { - vertical-align: middle; } &.box + .topic-header-extra, &.bullet + .topic-header-extra, &.bar + .topic-header-extra { - vertical-align: middle; } } } @@ -133,7 +132,7 @@ $tag-color: $primary-medium; .d-header .topic-header-extra { .discourse-tags { - display: inline-block; + display: inline-block; font-size: $font-down-1; } .topic-featured-link { margin-left: 8px; } @@ -176,9 +175,13 @@ $tag-color: $primary-medium; } .mobile-view .topic-list-item .discourse-tags { - display: inline-block; + display: inline-flex; + flex-wrap: wrap; font-size: $font-down-1; margin-top: 0; + .discourse-tag { + margin-right: .2em; + } .discourse-tag.box { position:relative; top: 0; @@ -201,6 +204,7 @@ $tag-color: $primary-medium; header .discourse-tag {color: $tag-color } .list-tags { + display: inline-block; margin-right: 3px; font-size: $font-down-1; } @@ -275,8 +279,9 @@ header .discourse-tag {color: $tag-color } } .group-tags-list .tag-chooser { height: 250px !important; - .select2-choices { + ul.select2-choices { height: 250px !important; // to fight with select2.scss's important + max-height: none; } } .btn {margin-left: 10px;} diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss index b10b4d1833..baee2ab77a 100644 --- a/app/assets/stylesheets/common/base/topic-post.scss +++ b/app/assets/stylesheets/common/base/topic-post.scss @@ -199,7 +199,10 @@ aside.quote { } .topic-map { - + background: blend-primary-secondary(5%); + border: 1px solid $primary-low; + border-top: none; // would cause double top border + .avatars { > div { float: left; @@ -223,9 +226,44 @@ aside.quote { margin-right: 4px; } + section { + border-top: 1px solid $primary-low; + } + + ul { + margin: 0; + list-style: none; + } + + h3 { + margin-bottom: 4px; + color: dark-light-choose($primary-high, $secondary-low); + line-height: $line-height-large; + font-weight: normal; + font-size: $font-0; + } + + h4 { + margin: 1px 0 2px 0; + color: dark-light-choose($primary-medium, $secondary-medium); + font-weight: normal; + font-size: $font-down-1; + line-height: $line-height-small; + } + + span.domain { + font-size: $font-down-2; + color: dark-light-choose($primary-medium, $secondary-medium); + } + + td { + vertical-align: top; + padding:1px; + } + } -.topic-avatar, .avatar-flair-preview, .user-card-avatar, .topic-map .poster { +.topic-avatar, .avatar-flair-preview, .user-card-avatar, .topic-map .poster, .user-profile-avatar { .avatar-flair { display: flex; align-items: center; @@ -237,7 +275,7 @@ aside.quote { right: -6px; } } -.topic-avatar .avatar-flair, .avatar-flair-preview .avatar-flair { +.topic-avatar .avatar-flair, .avatar-flair-preview .avatar-flair, .collapsed-info .user-profile-avatar .avatar-flair { background-size: 20px 20px; width: 20px; height: 20px; @@ -250,7 +288,7 @@ aside.quote { right: -8px; } } -.user-card-avatar .avatar-flair { +.user-card-avatar .avatar-flair, .user-profile-avatar .avatar-flair { background-size: 40px 40px; width: 40px; height: 40px; @@ -385,7 +423,7 @@ blockquote > *:last-child { } .gap { - padding: 0.25em 0 0.5em 4.3em; + padding: 0.25em 0 0.5em 4.6em; color: dark-light-choose($primary-medium, $secondary-high); cursor: pointer; text-transform: uppercase; @@ -420,17 +458,20 @@ blockquote > *:last-child { } .small-action { + display: flex; + align-items: center; max-width: 755px; border-top: 1px solid $primary-low; .topic-avatar { - padding: .67em 0; + align-self: flex-start; + padding: .7em 0; border-top: none; - float: left; + margin-right: 11px; i { font-size: 2em; width: 45px; text-align: center; - color: dark-light-choose($primary-low-mid, $secondary-high); + color: $primary-low-mid; } } @@ -439,27 +480,33 @@ blockquote > *:last-child { } .small-action-desc.timegap { - color: dark-light-choose($primary-medium, $secondary-high); + color: $primary-medium; } .small-action-desc { - padding: 1em; + display: flex; + flex-wrap: wrap; + flex: 1 1 100%; + align-items: center; + padding: 1em 0; text-transform: uppercase; font-weight: bold; font-size: $font-down-1; - color: dark-light-choose($primary-low-mid, $secondary-high); + color: $primary-medium; .custom-message { + flex: 1 1 100%; text-transform: none; - margin: 15px 0 5px; font-weight: normal; font-size: $font-up-1; + order: 12; p { - margin: 5px 0; - line-height: $line-height-large; + margin-bottom: 0; } } - + a.trigger-user-card { + align-self: stretch; + } .avatar { margin-right: 0.8em; float: left; @@ -467,18 +514,21 @@ blockquote > *:last-child { > p { margin: 0; - padding-top: 4px; + line-height: $line-height-medium; + flex: 1 1; + } } button { background: transparent; border: 0; - float: right; - margin-top: -2px; + order: 9; + &:last-of-type { + margin-left: auto; + order: 8; + } } - - clear: both; } .whisper { diff --git a/app/assets/stylesheets/common/base/topic.scss b/app/assets/stylesheets/common/base/topic.scss index f23249d122..5b267622e3 100644 --- a/app/assets/stylesheets/common/base/topic.scss +++ b/app/assets/stylesheets/common/base/topic.scss @@ -45,20 +45,39 @@ } } +.title-wrapper { + display: flex; + flex-wrap: wrap; + width: 90%; + align-items: flex-end; + + .btn-small { + margin: 0 6px 0 0; + } + + a.topic-featured-link { + display: inline-block; + } +} + +h1 { + margin: 0 0 4px 0; +} + +a.badge-category { + margin-top: 5px; +} #topic-title { .title-wrapper { - float: left; + display: flex; + flex-wrap: wrap; width: 90%; .btn-small { margin: 0 6px 0 0; } - .badge-wrapper { - margin-right: 12px; - } - a.topic-featured-link { display: inline-block; } @@ -66,6 +85,7 @@ h1 { margin: 0 0 4px 0; + width: 90%; } a.badge-category { @@ -181,6 +201,9 @@ margin-top: 1em; padding-top: 1em; border-top: 1px solid $primary-low; + li:last-of-type { + margin-bottom: 1em; + } } .expand-links { @@ -188,9 +211,13 @@ } .track-link { - padding-left: 0; - display: inline-block; - overflow: hidden; + display: flex; + align-items: center; + span:not(.badge) { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } } ul { @@ -204,11 +231,11 @@ } .clicks { margin-left: 0.5em; + flex: 0 0 auto; } i { - float: left; font-size: $font-down-2; - margin: 0.3em 0.5em 0 0; + margin: 0 0.5em 0 0; } } } diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss index 5efd7ed9ca..8c39904e93 100644 --- a/app/assets/stylesheets/common/base/user.scss +++ b/app/assets/stylesheets/common/base/user.scss @@ -120,6 +120,16 @@ } } } + + .user-profile-avatar { + position: relative; + float: left; + height: 100%; + .avatar-flair { + bottom: 8px; + right: 16px; + } + } } .controls { @@ -176,6 +186,13 @@ } } } + + .user-profile-avatar { + .avatar-flair { + bottom: 8px; + right: 2px; + } + } } } diff --git a/app/assets/stylesheets/common/components/badges.scss b/app/assets/stylesheets/common/components/badges.scss index 940351824e..d7123032ba 100644 --- a/app/assets/stylesheets/common/components/badges.scss +++ b/app/assets/stylesheets/common/components/badges.scss @@ -20,104 +20,49 @@ font-weight: bold; white-space: nowrap; position: relative; - display: inline-flex; - align-items: center; - + .badge-category { .d-icon { margin-right: 3px; } } - &.bar { //bar category style - line-height: $line-height-medium; - margin-right: 5px; + // ----- Bullet - span.badge-category { - color: $primary; - padding: 3px; - overflow: hidden; - text-overflow: ellipsis; - display: inline-flex; - align-items: center; - justify-content: space-between; - - .extra-info-wrapper & { - color: $header-primary; - } - } - - .badge-category-parent-bg, .badge-category-bg { - display: inline-block; - padding: 1px; - - &:before { - content: "\a0"; - } - } - } - - &.none { // no category style - color: $primary; - margin-right: 5px; - } - - &.bullet { //bullet category style - display: inline-flex; - align-items: center; + &.bullet { margin-right: 12px; - - .extra-info-wrapper & { - margin-top: .25em; - } - span.badge-category { color: $primary; overflow: hidden; text-overflow: ellipsis; - line-height: $line-height-medium; - - #search-dropdown & { - margin-top: -2px; - } - .extra-info-wrapper & { color: $header-primary; } } - .badge-category-parent-bg, .badge-category-bg { - width: 10px; - height: 10px; + width: 9px; + height: 9px; margin-right: 5px; display: inline-block; - line-height: $line-height-small; - - &:before { - content: "\a0"; } - } - - span { - &.badge-category-parent-bg { //subcategory style + .badge-category-parent-bg { // Subcategories + width: 5px; + margin-right: 0; + +.badge-category-bg { width: 5px; - margin-right: 0; - & + .badge-category-bg { - width: 5px; - } } } } + // ----- Box - &.box { //box category style (apply custom widths to the wrapper, not the children) - line-height: $line-height-large; + &.box { margin-right: 5px; - + padding: 2px 4px 2px 4px; + display: inline-flex; span { overflow: hidden; text-overflow: ellipsis; - &.badge-category-bg, &.badge-category-parent-bg { position: absolute; top: 0; @@ -126,7 +71,7 @@ left: 0; } - &.badge-category-parent-bg { //subcategory style + &.badge-category-parent-bg { // Subcategories width: calc(100% - 5px); & + .badge-category-bg { left: 5px; @@ -139,23 +84,52 @@ &.badge-category { position: relative; - padding: 0 5px; - margin: 2px 0; + } + } + + .topic-header-extra { + padding: 2px 4px 2px 4px; + } + } + + // ----- Bar + + &.bar { + margin-right: 5px; + + span.badge-category { + color: $primary; + padding: 3px; + overflow: hidden; + text-overflow: ellipsis; + + .extra-info-wrapper & { + color: $header-primary; + } + } + + .badge-category-parent-bg, .badge-category-bg { // Subcategories + display: inline-block; + padding: 0 1px; + + &:before { + content: "\a0"; } } } + + // ----- No category style + + &.none { + color: $primary; + margin-right: 5px; + } + } -.autocomplete, td.category { - .badge-wrapper { - max-width: 230px; - } -} // Category badge dropdown // -------------------------------------------------- - .list-controls { .category-breadcrumb { a.badge-category { diff --git a/app/assets/stylesheets/common/components/keyboard_shortcuts.scss b/app/assets/stylesheets/common/components/keyboard_shortcuts.scss index 04045f843f..13010953fe 100644 --- a/app/assets/stylesheets/common/components/keyboard_shortcuts.scss +++ b/app/assets/stylesheets/common/components/keyboard_shortcuts.scss @@ -15,8 +15,12 @@ } #keyboard-shortcuts-help { - .span6 { - width:32%; + div.row { + width: 100%; + div { + float: left; + width:32%; + } } ul { list-style: none; diff --git a/app/assets/stylesheets/common/select-kit/admin-agree-flag-dropdown.scss b/app/assets/stylesheets/common/select-kit/admin-agree-flag-dropdown.scss index 9c1e778499..762b99dd44 100644 --- a/app/assets/stylesheets/common/select-kit/admin-agree-flag-dropdown.scss +++ b/app/assets/stylesheets/common/select-kit/admin-agree-flag-dropdown.scss @@ -1,14 +1,12 @@ -.select-box-kit-body, .select-kit { +.select-kit { &.dropdown-select-box { &.admin-agree-flag-dropdown { - .select-box-kit-body { + .select-kit-body { width: 485px; max-width: 485px; } - .select-box-kit-row[data-value="delete-spammer"] .texts .name, .select-kit-row[data-value="delete-spammer"] .texts .name, - .select-box-kit-row[data-value="delete-spammer"] .icons .d-icon, .select-kit-row[data-value="delete-spammer"] .icons .d-icon { color: $danger; } diff --git a/app/assets/stylesheets/common/select-kit/admin-delete-flag-dropdown.scss b/app/assets/stylesheets/common/select-kit/admin-delete-flag-dropdown.scss index ffdf62634b..c76f9e6dd2 100644 --- a/app/assets/stylesheets/common/select-kit/admin-delete-flag-dropdown.scss +++ b/app/assets/stylesheets/common/select-kit/admin-delete-flag-dropdown.scss @@ -1,4 +1,4 @@ -.select-box-kit { +.select-kit { &.dropdown-select-box { width: auto; &.admin-delete-flag-dropdown { @@ -7,8 +7,8 @@ color: white; } - .select-box-kit-row[data-value="delete-spammer"] .texts .name, - .select-box-kit-row[data-value="delete-spammer"] .icons .d-icon { + .select-kit-row[data-value="delete-spammer"] .texts .name, + .select-kit-row[data-value="delete-spammer"] .icons .d-icon { color: $danger; } } diff --git a/app/assets/stylesheets/common/select-kit/categories-admin-dropdown.scss b/app/assets/stylesheets/common/select-kit/categories-admin-dropdown.scss index 9e3326dd0e..915d576837 100644 --- a/app/assets/stylesheets/common/select-kit/categories-admin-dropdown.scss +++ b/app/assets/stylesheets/common/select-kit/categories-admin-dropdown.scss @@ -1,11 +1,11 @@ -.select-box-kit, .select-kit { +.select-kit { &.categories-admin-dropdown { - .select-box-kit-body, .select-kit-body { + .select-kit-body { min-width: auto; width: 250px; } - .select-box-kit-header .d-icon, .select-kit-header .d-icon { + .select-kit-header .d-icon { justify-content: space-between; } } diff --git a/app/assets/stylesheets/common/select-kit/category-chooser.scss b/app/assets/stylesheets/common/select-kit/category-chooser.scss index 503a9bd275..9102b7cf87 100644 --- a/app/assets/stylesheets/common/select-kit/category-chooser.scss +++ b/app/assets/stylesheets/common/select-kit/category-chooser.scss @@ -1,8 +1,8 @@ -.select-box-kit, .select-kit { +.select-kit { &.combo-box { &.category-chooser { width: 300px; - .select-box-kit-row, .select-kit-row { + .select-kit-row { display: -webkit-box; display: -ms-flexbox; display: flex; diff --git a/app/assets/stylesheets/common/select-kit/category-drop.scss b/app/assets/stylesheets/common/select-kit/category-drop.scss index 6fe2e3595a..391f80d8ae 100644 --- a/app/assets/stylesheets/common/select-kit/category-drop.scss +++ b/app/assets/stylesheets/common/select-kit/category-drop.scss @@ -6,7 +6,6 @@ .badge-wrapper { font-size: $font-0; font-weight: normal; - line-height: $line-height-large; &.box { margin: 0; @@ -17,43 +16,24 @@ } } - .category-drop-header { - padding: 6px 10px; - } - - &.bar.has-selection .category-drop-header { - padding: 1.5px 10px; + &.bar.has-selection .category-drop-header, + &.box.has-selection .category-drop-header, + &.none.has-selection .category-drop-header { + padding: 5px 10px; } &.bullet.has-selection .category-drop-header { - padding: 3.5px 10px; - span.badge-category { - line-height: $line-height-large; - } - } - - &.box.has-selection .category-drop-header { - padding: 4.5px 10px; - } - - &.none.has-selection .category-drop-header { - padding: 4.5px 10px; + padding: 5px 10px; } .category-drop-header { background: $primary-low; color: $primary; - border: none; - padding: 6px 10px; - font-size: $font-up-1; - line-height: $line-height-medium; + border: 1px solid transparent; + padding: 5px 10px; + font-size: $font-0; transition: none; - .badge-category { - display: flex; - align-items: center; - } - .badge-wrapper { margin-right: 0; } @@ -67,6 +47,12 @@ } } + &.is-expanded .category-drop-header { + border: 1px solid $tertiary; + -webkit-box-shadow: $tertiary 0 0 6px 0px; + box-shadow: $tertiary 0 0 6px 0px; + } + .select-kit-collection { display: flex; flex-direction: column; @@ -132,19 +118,9 @@ .badge-wrapper { margin: 0; - display: flex; - flex: 1 1 auto; } } - .select-kit-row .badge-wrapper.box { - padding: 2.5px 0; - } - - .select-kit-row .badge-wrapper.bullet, .select-kit-row .badge-wrapper.none { - margin: 2.5px; - } - &.is-expanded .select-kit-wrapper, .select-kit-wrapper { display: none; } diff --git a/app/assets/stylesheets/common/select-kit/combo-box.scss b/app/assets/stylesheets/common/select-kit/combo-box.scss index 59db74d3b0..e69bfdcdcd 100644 --- a/app/assets/stylesheets/common/select-kit/combo-box.scss +++ b/app/assets/stylesheets/common/select-kit/combo-box.scss @@ -1,17 +1,17 @@ -.select-box-kit, .select-kit { +.select-kit { &.combo-box { - .select-box-kit-body, .select-kit-body { + .select-kit-body { width: 100%; } - .select-box-kit-row, .select-kit-row { + .select-kit-row { margin: 5px; min-height: 1px; padding: 5px; } - .select-box-kit-filter, .select-kit-filter { + .select-kit-filter { line-height: $line-height-medium; padding: 6px 10px; border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); @@ -22,7 +22,7 @@ } } - .select-box-kit-header, .select-kit-header { + .select-kit-header { background: $secondary; border: 1px solid $primary-medium; padding: 4px 10px; diff --git a/app/assets/stylesheets/common/select-kit/composer-actions.scss b/app/assets/stylesheets/common/select-kit/composer-actions.scss new file mode 100644 index 0000000000..7dfefaa08a --- /dev/null +++ b/app/assets/stylesheets/common/select-kit/composer-actions.scss @@ -0,0 +1,23 @@ +.select-kit { + &.dropdown-select-box { + &.composer-actions { + margin: 0; + .select-kit-header { + background: none; + outline: none; + padding: 0; + margin-right: 5px; + + .d-icon { + border: 1px solid $primary-low; + padding: 4px 5px; + margin: 0!important; + } + + &:hover { + background: $primary-low; + } + } + } + } +} diff --git a/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss b/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss index dca3847436..98f7173e36 100644 --- a/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss +++ b/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss @@ -1,4 +1,4 @@ -.select-box-kit, .select-kit { +.select-kit { &.dropdown-select-box { display: -webkit-inline-box; display: -ms-inline-flexbox; @@ -7,15 +7,13 @@ border: none; &.is-expanded { - .select-box-kit-collection, - .select-box-kit-body, .select-kit-collection, .select-kit-body { border-radius: 0; } } - .select-box-kit-body, .select-kit-body { + .select-kit-body { border: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); background-clip: padding-box; -webkit-box-shadow: 0 2px 2px rgba(0,0,0,0.4); @@ -24,7 +22,7 @@ width: 300px; } - .select-box-kit-row, .select-kit-row { + .select-kit-row { margin: 0; padding: 10px 5px; @@ -93,8 +91,9 @@ } } - .select-box-kit-collection, .select-kit-collection { + .select-kit-collection { padding: 0; + max-height: 100%; } .dropdown-select-box-header { @@ -121,6 +120,12 @@ margin-left: 5px; } + &:hover { + .d-icon { + color: $primary-low; + } + } + &.is-focused { outline-style: auto; outline-color: $tertiary; diff --git a/app/assets/stylesheets/common/select-kit/future-date-input-selector.scss b/app/assets/stylesheets/common/select-kit/future-date-input-selector.scss index ea78ae0690..8ca4efde5b 100644 --- a/app/assets/stylesheets/common/select-kit/future-date-input-selector.scss +++ b/app/assets/stylesheets/common/select-kit/future-date-input-selector.scss @@ -1,4 +1,4 @@ -.select-box-kit, .select-kit { +.select-kit { &.combobox { &.future-date-input-selector { min-width: 50%; diff --git a/app/assets/stylesheets/common/select-kit/list-setting.scss b/app/assets/stylesheets/common/select-kit/list-setting.scss index 1f7103ef98..4fd866173f 100644 --- a/app/assets/stylesheets/common/select-kit/list-setting.scss +++ b/app/assets/stylesheets/common/select-kit/list-setting.scss @@ -1,7 +1,7 @@ -.select-box-kit, .select-kit { +.select-kit { &.multi-select { &.list-setting { - .select-box-kit-row.create, .select-kit-row.create { + .select-kit-row.create { .square { width: 12px; height: 12px; diff --git a/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss b/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss new file mode 100644 index 0000000000..956c9a8064 --- /dev/null +++ b/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss @@ -0,0 +1,87 @@ +.select-kit { + &.combo-box { + &.mini-tag-chooser { + margin-bottom: 5px; + margin-left: 5px; + + &.is-expanded { + .select-kit-header { + border: 1px solid $tertiary; + -webkit-box-shadow: $tertiary 0 0 6px 0px; + box-shadow: $tertiary 0 0 6px 0px; + } + } + + &.no-tags { + .select-kit-header .selected-name { + color: $primary-medium; + } + } + + .select-kit-body { + max-width: 500px; + width: 500px; + border: 1px solid $primary-low; + } + + .select-kit-filter { + border-top: 0; + } + + &.is-expanded .select-kit-wrapper, .select-kit-wrapper { + display: none; + } + + .select-kit-row { + &.is-selected { + background: none; + } + + &.is-highlighted.is-selected { + background: $tertiary-low; + } + + .discourse-tag-count { + margin-left: 5px; + } + } + + .select-kit-collection { + .collection-header { + max-height: 125px; + overflow-y: auto; + + .selected-tags { + display: flex; + padding: 3px; + flex-wrap: wrap; + border-bottom: 1px solid $primary-low; + } + + .selected-tag { + background: $primary-very-low; + padding: 2px 4px; + margin: 2px; + border: 0; + + &.is-highlighted { + box-shadow: 0 0 2px $danger, 0 1px 0 rgba(0,0,0,0.05); + } + + &:after { + content: '\f00d'; + color: $primary-low-mid; + font-family: 'FontAwesome'; + } + + &:hover { + &:after { + color: $danger; + } + } + } + } + } + } + } +} diff --git a/app/assets/stylesheets/common/select-kit/multi-select.scss b/app/assets/stylesheets/common/select-kit/multi-select.scss index 61ca932379..de86261025 100644 --- a/app/assets/stylesheets/common/select-kit/multi-select.scss +++ b/app/assets/stylesheets/common/select-kit/multi-select.scss @@ -1,21 +1,21 @@ -.select-box-kit, .select-kit { +.select-kit { &.multi-select { width: 300px; background: $secondary; border-radius: 0; - .select-box-kit-body, .select-kit-body { + .select-kit-body { width: 100%; } - .select-box-kit-row, .select-kit-row { + .select-kit-row { margin: 5px; min-height: 1px; padding: 5px; border-radius: 0; } - .select-box-kit-filter, .select-kit-filter { + .select-kit-filter { border: 0; } @@ -45,7 +45,7 @@ } &.is-expanded { - .select-box-kit-wrapper, .select-kit-wrapper { + .select-kit-wrapper { display: block; border: 1px solid $tertiary; box-shadow: $tertiary 0 0 6px 0px; @@ -57,7 +57,7 @@ box-shadow: none; } - .select-box-kit-body, .select-kit-body { + .select-kit-body { border-radius: 0; } } diff --git a/app/assets/stylesheets/common/select-kit/notifications-button.scss b/app/assets/stylesheets/common/select-kit/notifications-button.scss index 78db012b79..34b447ed30 100644 --- a/app/assets/stylesheets/common/select-kit/notifications-button.scss +++ b/app/assets/stylesheets/common/select-kit/notifications-button.scss @@ -1,12 +1,12 @@ .select-kit { &.dropdown-select-box { &.notifications-button { - .select-box-kit-body, .select-kit-body { + .select-kit-body { min-width: 550px; max-width: 550px; } - .select-box-kit-row, .select-kit-row { + .select-kit-row { .icons { -ms-flex-item-align: start; align-self: flex-start; diff --git a/app/assets/stylesheets/common/select-kit/period-chooser.scss b/app/assets/stylesheets/common/select-kit/period-chooser.scss index 3994057f83..1759b9ddf6 100644 --- a/app/assets/stylesheets/common/select-kit/period-chooser.scss +++ b/app/assets/stylesheets/common/select-kit/period-chooser.scss @@ -18,10 +18,15 @@ h2.selected-name { overflow: auto; - color: black; + color: $secondary; display: inline-block; box-sizing: border-box; + .date-section { + color: $primary; + margin-right: 5px; + } + .top-date-string { font-size: $font-down-1; color: dark-light-choose($primary-medium, $secondary-high); @@ -31,7 +36,7 @@ } .d-icon { - color: black; + color: $primary; opacity: 1; margin: 5px 0 10px 5px; font-size: $font-up-3; @@ -46,8 +51,7 @@ .period-chooser-row { font-weight: bold; - padding: 5px; - color: #222; + padding: 5px;; font-size: $font-up-1; align-items: center; display: flex; @@ -56,6 +60,10 @@ flex: 1; } + .date-section { + color: $primary; + } + .top-date-string { font-weight: normal; font-size: $font-down-1; diff --git a/app/assets/stylesheets/common/select-kit/pinned-button.scss b/app/assets/stylesheets/common/select-kit/pinned-button.scss index 5428e7ef57..9364205007 100644 --- a/app/assets/stylesheets/common/select-kit/pinned-button.scss +++ b/app/assets/stylesheets/common/select-kit/pinned-button.scss @@ -38,7 +38,7 @@ } .pinned-options { - .select-box-kit-body, .select-kit-body { + .select-kit-body { min-width: unset; max-width: unset; width: 550px; diff --git a/app/assets/stylesheets/common/select-kit/select-kit.scss b/app/assets/stylesheets/common/select-kit/select-kit.scss index 26e4ba8242..b5c1597ef3 100644 --- a/app/assets/stylesheets/common/select-kit/select-kit.scss +++ b/app/assets/stylesheets/common/select-kit/select-kit.scss @@ -2,7 +2,7 @@ z-index: z("dropdown"); } -.select-box-kit, .select-kit { +.select-kit { border: none; min-width: 220px; -webkit-box-sizing: border-box; @@ -70,7 +70,7 @@ opacity: 0.7; } - .select-box-kit-header, .select-kit-header { + .select-kit-header { box-sizing: border-box; overflow: hidden; -webkit-transition: all .25s; @@ -137,14 +137,14 @@ } } - .select-box-kit-body, .select-kit-body { + .select-kit-body { display: none; background: $secondary; -webkit-box-sizing: border-box; box-sizing: border-box; } - .select-box-kit-row, .select-kit-row { + .select-kit-row { cursor: pointer; line-height: $line-height-medium; outline: none; @@ -165,6 +165,11 @@ white-space: nowrap; } + &.max-content { + white-space: nowrap; + color: $danger; + } + .name { margin: 0; overflow: hidden; @@ -190,13 +195,14 @@ } } - .select-box-kit-collection, .select-kit-collection { + .select-kit-collection { background: $secondary; overflow-x: hidden; overflow-y: auto; border-radius: inherit; -webkit-overflow-scrolling: touch; margin: 0; + max-height: 200px; .select-kit-collection { padding: 0; @@ -224,7 +230,7 @@ } } - .select-box-kit-filter, .select-kit-filter { + .select-kit-filter { display: -webkit-box; display: -ms-flexbox; display: flex; @@ -265,7 +271,7 @@ } } - .select-box-kit-wrapper, .select-kit-wrapper { + .select-kit-wrapper { position: absolute; top: 0; left: 0; diff --git a/app/assets/stylesheets/common/select-kit/tag-drop.scss b/app/assets/stylesheets/common/select-kit/tag-drop.scss index 24f08ebc06..cc647cf79b 100644 --- a/app/assets/stylesheets/common/select-kit/tag-drop.scss +++ b/app/assets/stylesheets/common/select-kit/tag-drop.scss @@ -6,10 +6,10 @@ .tag-drop-header { background: $primary-low; color: $primary; - border: none; - padding: 4.5px 10px; - font-size: $font-up-1; - line-height: $line-height-large; + border: 1px solid transparent; + padding: 5px 10px; + font-size: $font-0; + transition: none; .d-icon { opacity: 1; @@ -17,6 +17,12 @@ } } + &.is-expanded .tag-drop-header { + border: 1px solid $tertiary; + -webkit-box-shadow: $tertiary 0 0 6px 0px; + box-shadow: $tertiary 0 0 6px 0px; + } + .select-kit-collection { display: flex; flex-direction: column; diff --git a/app/assets/stylesheets/common/topic-timeline.scss b/app/assets/stylesheets/common/topic-timeline.scss index b3d84c056e..6a9899154d 100644 --- a/app/assets/stylesheets/common/topic-timeline.scss +++ b/app/assets/stylesheets/common/topic-timeline.scss @@ -32,6 +32,9 @@ &.timeline-fullscreen.show { max-height: 700px; transition: max-height 0.4s ease-out; + @media screen and (max-height: 425px) { + max-height: 75vh; + } .topic-timeline { .timeline-footer-controls { display: inherit; @@ -52,6 +55,9 @@ box-shadow: 0px -2px 4px -1px rgba(0,0,0,.25); padding-top: 20px; z-index: z("fullscreen"); + @media screen and (max-height: 425px) { + padding-top: 10px; + } .back-button { display: none; } @@ -77,6 +83,9 @@ display: block; display: -webkit-box; -webkit-line-clamp: 8; + @media screen and (max-height: 425px) { + -webkit-line-clamp: 5; + } -webkit-box-orient: vertical; } .username { diff --git a/app/assets/stylesheets/desktop/compose.scss b/app/assets/stylesheets/desktop/compose.scss index 5ecc5d7fea..00e4a9f85a 100644 --- a/app/assets/stylesheets/desktop/compose.scss +++ b/app/assets/stylesheets/desktop/compose.scss @@ -10,10 +10,14 @@ align-items: center; } } + + #private-message-users { + width: 404px; + } } .category-input { - margin-left: 10px; + margin-left: 10px; } .edit-title { @@ -23,16 +27,16 @@ &:not(.private-message) { .d-editor-preview-wrapper { @media screen and (max-width: 955px) { - margin-top: -79px; + margin-top: -77px; } } } - + .with-tags { .d-editor-preview-wrapper { - margin-top: -79px; + margin-top: -77px; @media screen and (max-width: 900px) { - margin-top: -116px; + margin-top: -105px; } } } diff --git a/app/assets/stylesheets/desktop/discourse.scss b/app/assets/stylesheets/desktop/discourse.scss index 5f718e9502..8dce9693b5 100644 --- a/app/assets/stylesheets/desktop/discourse.scss +++ b/app/assets/stylesheets/desktop/discourse.scss @@ -351,46 +351,6 @@ input { @include clearfix; } -.span { - &4 { - width: 196px; - margin-right: 12px; - float: left; - } - - &6 { - width: 27.027%; - float: left; - } - - &8 { - width: 404px; - float: left; - } - - &10 { - width: 508px; - float: left; - } - - &13 { - width: 59.8198%; - float: left; - } - - &15 { - /* intentionally no width set here, do not add one */ - margin-left: 12px; - float: left; - } - - &24 { - width: 1236px; - float: left; - color: amarillo; - } -} - .offset { &2 { margin-left: 116px; diff --git a/app/assets/stylesheets/desktop/history.scss b/app/assets/stylesheets/desktop/history.scss index 81241cb8b4..8481485d10 100644 --- a/app/assets/stylesheets/desktop/history.scss +++ b/app/assets/stylesheets/desktop/history.scss @@ -3,8 +3,8 @@ .modal.history-modal { .modal-inner-container { - min-width: 960px; min-height: 500px; + max-width: 960px; } #revision-controls { float: left; diff --git a/app/assets/stylesheets/desktop/login.scss b/app/assets/stylesheets/desktop/login.scss index 23c933180f..3e7e61efcc 100644 --- a/app/assets/stylesheets/desktop/login.scss +++ b/app/assets/stylesheets/desktop/login.scss @@ -16,6 +16,9 @@ a { color: dark-light-choose($primary-high, $secondary-low); } + td { + padding-right: 5px; + } } // Create account diff --git a/app/assets/stylesheets/desktop/topic-list.scss b/app/assets/stylesheets/desktop/topic-list.scss index 347988a558..11bd7c3df1 100644 --- a/app/assets/stylesheets/desktop/topic-list.scss +++ b/app/assets/stylesheets/desktop/topic-list.scss @@ -82,9 +82,6 @@ padding-left: 5px; } } - td.category { - line-height: $line-height-small; - } .posters { // we know there are up to 5 avatars of fixed size diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss index e869ad4dfd..dd65265203 100644 --- a/app/assets/stylesheets/desktop/topic-post.scss +++ b/app/assets/stylesheets/desktop/topic-post.scss @@ -307,39 +307,6 @@ nav.post-controls { .topic-map { margin: 20px 0; - background: blend-primary-secondary(5%); - border: 1px solid $primary-low; - border-top: none; // would cause double top border - - section { - border-top: 1px solid $primary-low; - } - - h3 { - margin-bottom: 4px; - color: dark-light-choose($primary-high, $secondary-low); - line-height: $line-height-large; - font-weight: normal; - font-size: $font-0; - } - - h4 { - margin: 1px 0 2px 0; - color: dark-light-choose($primary-medium, $secondary-medium); - font-weight: normal; - font-size: $font-down-1; - line-height: $line-height-small; - } - - ul { - margin: 0; - list-style: none; - } - - span.domain { - font-size: $font-down-2; - color: dark-light-choose($primary-medium, $secondary-medium); - } .map { .secondary {text-align: center;} @@ -391,11 +358,6 @@ nav.post-controls { } } - td { - vertical-align: top; - padding:1px; - } - .buttons { float: right; .btn { @@ -545,6 +507,7 @@ video { margin: 5px 0 0 0; font-size: $font-up-3; line-height: $line-height-large; + width: 100%; } .topic-statuses { @@ -557,6 +520,7 @@ video { h1 { line-height: $line-height-medium; margin: 0; + width: 100%; } } diff --git a/app/assets/stylesheets/desktop/topic.scss b/app/assets/stylesheets/desktop/topic.scss index 6f0e876dfb..b4d33b0958 100644 --- a/app/assets/stylesheets/desktop/topic.scss +++ b/app/assets/stylesheets/desktop/topic.scss @@ -28,6 +28,7 @@ font-size: $font-up-4; line-height: $line-height-medium; overflow: hidden; + width: 100%; a {color: $primary;} } .topic-statuses { @@ -178,15 +179,6 @@ } } -.heatmap-high {color: #fe7a15 !important;} -.heatmap-med {color: #cf7721 !important;} -.heatmap-low {color: #9b764f !important;} -.heatmap-high a {color: #fe7a15 !important;} -.heatmap-med a {color: #cf7721 !important;} -.heatmap-low a {color: #9b764f !important;} - - - #topic-filter { background-color: $highlight-medium; padding: 8px; diff --git a/app/assets/stylesheets/desktop/user-card.scss b/app/assets/stylesheets/desktop/user-card.scss index e55a5acea5..8f09dd9fc8 100644 --- a/app/assets/stylesheets/desktop/user-card.scss +++ b/app/assets/stylesheets/desktop/user-card.scss @@ -165,15 +165,31 @@ $user_card_background: $secondary; } .location-and-website { - clear: left; - margin-top: 5px; - .location {margin-right: 10px;} - .website-name { - a { - text-decoration: underline; - color: $user_card_primary; + display: flex; + width: 100%; + align-items: center; + padding-top: 5px; + .location, .website-name { + display: flex; + max-width: 90%; + overflow: hidden; + align-items: baseline; + i { + margin-right: .25em; } } + .website-name a, .location span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: $user_card_primary; + } + .location { + margin-right: .5em; + } + .website-name a { + text-decoration: underline; + } } .user-card-avatar { diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss index 52611d69be..0fb3e102f9 100644 --- a/app/assets/stylesheets/desktop/user.scss +++ b/app/assets/stylesheets/desktop/user.scss @@ -109,12 +109,6 @@ } } -.user-invite-controls { - background-color: $primary-low; - padding: 5px 10px 0 0; - height: 35px; -} - .user-invite-search { clear: both; margin: 15px 0px -15px 0px; @@ -176,6 +170,12 @@ a[href] { text-decoration: underline; } + .location-and-website { + max-width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } } .bio { diff --git a/app/assets/stylesheets/mobile/history.scss b/app/assets/stylesheets/mobile/history.scss index aa2413b644..93a2a29e7e 100644 --- a/app/assets/stylesheets/mobile/history.scss +++ b/app/assets/stylesheets/mobile/history.scss @@ -17,4 +17,11 @@ max-width: 95%; height: auto; } + .revision-content { + table { + table-layout: fixed; + width: 100%; + word-wrap: break-word; + } + } } diff --git a/app/assets/stylesheets/mobile/select-kit/dropdown-select-box.scss b/app/assets/stylesheets/mobile/select-kit/dropdown-select-box.scss new file mode 100644 index 0000000000..1853c4e230 --- /dev/null +++ b/app/assets/stylesheets/mobile/select-kit/dropdown-select-box.scss @@ -0,0 +1,8 @@ +.select-kit { + &.dropdown-select-box { + .select-kit-collection { + max-height: 200px; + overflow-y: scroll; + } + } +} diff --git a/app/assets/stylesheets/mobile/topic-post.scss b/app/assets/stylesheets/mobile/topic-post.scss index e507b59fcd..3b48e5c91e 100644 --- a/app/assets/stylesheets/mobile/topic-post.scss +++ b/app/assets/stylesheets/mobile/topic-post.scss @@ -3,6 +3,12 @@ border-top: none; } +.time-gap { + .topic-avatar { + display: none; + } +} + .topic-post article { border-top: 1px solid $primary-low; padding: 15px 0 8px 0; @@ -137,30 +143,10 @@ a.reply-to-tab { } .topic-map { - margin: 10px 0; - background: blend-primary-secondary(5%); - border: 1px solid $primary-low; - border-top: none; // would cause double top border - - section { - border-top: 1px solid $primary-low; - } - - h3 { - margin-bottom: 4px; - margin-top: 0; - color: dark-light-choose($primary-medium, $secondary-medium); - line-height: $line-height-large; - font-weight: normal; - font-size: $font-0; - } h4 { margin: 0 0 3px 0; - color: dark-light-choose($primary-medium, $secondary-medium); - font-weight: normal; - font-size: $font-down-1; line-height: $line-height-medium; } @@ -169,11 +155,6 @@ a.reply-to-tab { margin-right: 10px; } - ul { - margin: 0; - list-style: none; - } - .map-collapsed { .secondary { display: none; @@ -189,7 +170,7 @@ a.reply-to-tab { } } a, .number { - line-height: $line-height-large; + line-height: $line-height-medium; } .number, i { color: dark-light-choose($primary-high, $secondary-low); @@ -220,21 +201,12 @@ a.reply-to-tab { .user {float: left; margin: 0 10px 10px 0;} } - .domain { - color: dark-light-choose($primary-low-mid, $secondary-high); - } - .topic-links { .badge-notification { margin: 1px 5px 2px 0; } } - td { - vertical-align: top; - padding:1px; - } - .buttons { .btn { border: 0; @@ -431,6 +403,10 @@ blockquote { margin-right: 0; } +.gap { + padding: 0.25em 0; +} + .gutter { display: none; } .posts-wrapper { position: relative; } diff --git a/app/assets/stylesheets/mobile/topic.scss b/app/assets/stylesheets/mobile/topic.scss index c87355ccfd..dafbfd69e5 100644 --- a/app/assets/stylesheets/mobile/topic.scss +++ b/app/assets/stylesheets/mobile/topic.scss @@ -45,7 +45,7 @@ .topic-status-info { padding-left: 10px; - border-top: 1px solid $primary-low; + border-top: 1px solid $primary-low; padding-top: 10px; h3 { margin: 0; @@ -66,7 +66,7 @@ } #topic-progress-expanded { - border: 1px solid $primary-low; + border: 1px solid $primary-low; padding: 5px; background: $secondary; @@ -172,10 +172,6 @@ .topic-post:last-of-type {padding-bottom: 40px;} -.heatmap-high {color: $danger !important;} -.heatmap-med {color: $danger-medium !important;} -.heatmap-low {color: $danger-low !important;} - sup sup, sub sup, sup sub, sub sub { top: 0; } // inline editing of title on mobile @@ -199,7 +195,7 @@ sup sup, sub sup, sup sub, sub sub { top: 0; } margin: 6px 6px 0 0; } - .select-box-kit.combo-box.category-chooser { + .select-kit.combo-box.category-chooser { width: 100%; margin-top: 0; } @@ -207,9 +203,9 @@ sup sup, sub sup, sup sub, sub sub { top: 0; } // make mobile timeline top and bottom dates easier to select .topic-timeline { - .start-date, .now-date { - font-size: $font-up-1; - padding: 5px; + .start-date, .now-date { + font-size: $font-up-1; + padding: 5px; } } diff --git a/app/assets/stylesheets/mobile/user.scss b/app/assets/stylesheets/mobile/user.scss index fccb2542ef..7a652d9904 100644 --- a/app/assets/stylesheets/mobile/user.scss +++ b/app/assets/stylesheets/mobile/user.scss @@ -127,6 +127,10 @@ max-width: 700px; } } + + .user-profile-avatar .avatar-flair { + right: 2px; + } } .controls { @@ -177,6 +181,9 @@ } } } + .user-profile-avatar .avatar-flair { + bottom: 12px; + } } } diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb index 0ca88b6945..a95cedfca7 100644 --- a/app/controllers/about_controller.rb +++ b/app/controllers/about_controller.rb @@ -1,8 +1,10 @@ require_dependency 'rate_limiter' class AboutController < ApplicationController + + requires_login only: [:live_post_counts] + skip_before_action :check_xhr, only: [:index] - before_action :ensure_logged_in, only: [:live_post_counts] def index return redirect_to path('/login') if SiteSetting.login_required? && current_user.nil? diff --git a/app/controllers/admin/admin_controller.rb b/app/controllers/admin/admin_controller.rb index 1b786ac0de..2a33fd6c90 100644 --- a/app/controllers/admin/admin_controller.rb +++ b/app/controllers/admin/admin_controller.rb @@ -1,6 +1,6 @@ class Admin::AdminController < ApplicationController - before_action :ensure_logged_in + requires_login before_action :ensure_staff def index diff --git a/app/controllers/admin/embeddable_hosts_controller.rb b/app/controllers/admin/embeddable_hosts_controller.rb index f43ff95505..667db524fe 100644 --- a/app/controllers/admin/embeddable_hosts_controller.rb +++ b/app/controllers/admin/embeddable_hosts_controller.rb @@ -1,7 +1,5 @@ class Admin::EmbeddableHostsController < Admin::AdminController - before_action :ensure_logged_in, :ensure_staff - def create save_host(EmbeddableHost.new) end diff --git a/app/controllers/admin/embedding_controller.rb b/app/controllers/admin/embedding_controller.rb index ada7c668b9..ebac31e576 100644 --- a/app/controllers/admin/embedding_controller.rb +++ b/app/controllers/admin/embedding_controller.rb @@ -2,7 +2,7 @@ require_dependency 'embedding' class Admin::EmbeddingController < Admin::AdminController - before_action :ensure_logged_in, :ensure_staff, :fetch_embedding + before_action :fetch_embedding def show render_serialized(@embedding, EmbeddingSerializer, root: 'embedding', rest_serializer: true) diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index 6490c96d73..dec448528d 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -7,8 +7,8 @@ class Admin::ReportsController < Admin::AdminController raise Discourse::NotFound unless report_type =~ /^[a-z0-9\_]+$/ - start_date = params[:start_date].present? ? Time.parse(params[:start_date]) : 30.days.ago - end_date = params[:end_date].present? ? Time.parse(params[:end_date]) : start_date + 30.days + start_date = (params[:start_date].present? ? Time.zone.parse(params[:start_date]) : 30.days.ago).beginning_of_day + end_date = (params[:end_date].present? ? Time.zone.parse(params[:end_date]) : start_date + 30.days).end_of_day if params.has_key?(:category_id) && params[:category_id].to_i > 0 category_id = params[:category_id].to_i diff --git a/app/controllers/admin/site_texts_controller.rb b/app/controllers/admin/site_texts_controller.rb index 8cdb0d9b19..ebb99aa8c5 100644 --- a/app/controllers/admin/site_texts_controller.rb +++ b/app/controllers/admin/site_texts_controller.rb @@ -71,7 +71,7 @@ class Admin::SiteTextsController < Admin::AdminController def record_for(k, value = nil) if k.ends_with?("_MF") - ovr = TranslationOverride.where(translation_key: k).pluck(:value) + ovr = TranslationOverride.where(translation_key: k, locale: I18n.locale).pluck(:value) value = ovr[0] if ovr.present? end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 2095903ab6..d948d5f5f0 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -93,6 +93,8 @@ class Admin::UsersController < Admin::AdminController suspended_at: DateTime.now ) + perform_post_action + render_json_dump( suspension: { suspended: true, @@ -287,7 +289,8 @@ class Admin::UsersController < Admin::AdminController silenced_till: params[:silenced_till], reason: params[:reason], message_body: message, - keep_posts: true + keep_posts: true, + post_id: params[:post_id] ) if silencer.silence && message.present? Jobs.enqueue( @@ -297,6 +300,7 @@ class Admin::UsersController < Admin::AdminController user_history_id: silencer.user_history.id ) end + perform_post_action render_json_dump( silence: { @@ -467,6 +471,27 @@ class Admin::UsersController < Admin::AdminController private + def perform_post_action + return unless params[:post_id].present? && + params[:post_action].present? + + if post = Post.where(id: params[:post_id]).first + case params[:post_action] + when 'delete' + PostDestroyer.new(current_user, post).destroy + when 'edit' + revisor = PostRevisor.new(post) + + # Take what the moderator edited in as gospel + revisor.revise!( + current_user, + { raw: params[:post_edit] }, + skip_validations: true, skip_revision: true + ) + end + end + end + def fetch_user @user = User.find_by(id: params[:user_id]) end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 479082fc6a..653ba952fc 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -48,8 +48,9 @@ class ApplicationController < ActionController::Base before_action :set_mobile_view before_action :block_if_readonly_mode before_action :authorize_mini_profiler - before_action :preload_json before_action :redirect_to_login_if_required + before_action :block_if_requires_login + before_action :preload_json before_action :check_xhr after_action :add_readonly_header after_action :perform_refresh_session @@ -106,7 +107,7 @@ class ApplicationController < ActionController::Base end def render_rate_limit_error(e) - render_json_error e.description, type: :rate_limit, status: 429 + render_json_error e.description, type: :rate_limit, status: 429, extras: { wait_seconds: e&.available_in } end # If they hit the rate limiter @@ -158,6 +159,10 @@ class ApplicationController < ActionController::Base end rescue_from Discourse::InvalidAccess do |e| + + if e.opts[:delete_cookie].present? + cookies.delete(e.opts[:delete_cookie]) + end rescue_discourse_actions( :invalid_access, 403, @@ -187,7 +192,9 @@ class ApplicationController < ActionController::Base render_json_error message, type: type, status: status_code else begin + # 404 pages won't have the session and theme_keys without these: current_user + handle_theme rescue Discourse::InvalidAccess return render plain: message, status: status_code end @@ -570,6 +577,28 @@ class ApplicationController < ActionController::Base raise RenderEmpty.new unless ((request.format && request.format.json?) || request.xhr?) end + def self.requires_login(arg = {}) + @requires_login_arg = arg + end + + def self.requires_login_arg + @requires_login_arg + end + + def block_if_requires_login + if arg = self.class.requires_login_arg + check = + if except = arg[:except] + !except.include?(action_name.to_sym) + elsif only = arg[:only] + only.include?(action_name.to_sym) + else + true + end + ensure_logged_in if check + end + end + def ensure_logged_in raise Discourse::NotLoggedIn.new unless current_user.present? end diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index 56c49b9da9..58a95610c4 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -2,7 +2,8 @@ require_dependency 'category_serializer' class CategoriesController < ApplicationController - before_action :ensure_logged_in, except: [:index, :categories_and_latest, :show, :redirect, :find_by_slug] + requires_login except: [:index, :categories_and_latest, :show, :redirect, :find_by_slug] + before_action :fetch_category, only: [:show, :update, :destroy] before_action :initialize_staff_action_logger, only: [:create, :update, :destroy] skip_before_action :check_xhr, only: [:index, :categories_and_latest, :redirect] diff --git a/app/controllers/category_hashtags_controller.rb b/app/controllers/category_hashtags_controller.rb index e0b6f768d7..4bbb85a406 100644 --- a/app/controllers/category_hashtags_controller.rb +++ b/app/controllers/category_hashtags_controller.rb @@ -1,5 +1,5 @@ class CategoryHashtagsController < ApplicationController - before_action :ensure_logged_in + requires_login def check category_slugs = params[:category_slugs] diff --git a/app/controllers/composer_controller.rb b/app/controllers/composer_controller.rb index 6eb78a5d06..f5cb462ba4 100644 --- a/app/controllers/composer_controller.rb +++ b/app/controllers/composer_controller.rb @@ -2,7 +2,7 @@ require_dependency 'html_to_markdown' class ComposerController < ApplicationController - before_action :ensure_logged_in + requires_login def parse_html markdown_text = HtmlToMarkdown.new(params[:html]).to_markdown diff --git a/app/controllers/composer_messages_controller.rb b/app/controllers/composer_messages_controller.rb index 60bc8a75ba..26e879b109 100644 --- a/app/controllers/composer_messages_controller.rb +++ b/app/controllers/composer_messages_controller.rb @@ -2,7 +2,7 @@ require_dependency 'composer_messages_finder' class ComposerMessagesController < ApplicationController - before_action :ensure_logged_in + requires_login def index finder = ComposerMessagesFinder.new(current_user, params.slice(:composer_action, :topic_id, :post_id)) diff --git a/app/controllers/draft_controller.rb b/app/controllers/draft_controller.rb index 5734a0326a..ebd0dd1bcc 100644 --- a/app/controllers/draft_controller.rb +++ b/app/controllers/draft_controller.rb @@ -1,6 +1,6 @@ class DraftController < ApplicationController - before_action :ensure_logged_in - # TODO really do we need to skip this? + requires_login + skip_before_action :check_xhr, :preload_json def show diff --git a/app/controllers/email_controller.rb b/app/controllers/email_controller.rb index 9701cfc096..f0556f608d 100644 --- a/app/controllers/email_controller.rb +++ b/app/controllers/email_controller.rb @@ -1,7 +1,7 @@ class EmailController < ApplicationController - skip_before_action :check_xhr, :preload_json, :redirect_to_login_if_required layout 'no_ember' + skip_before_action :check_xhr, :preload_json, :redirect_to_login_if_required before_action :ensure_logged_in, only: :preferences_redirect def preferences_redirect diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 3fde94894c..74f5479d26 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -1,6 +1,6 @@ class GroupsController < ApplicationController - before_action :ensure_logged_in, only: [ + requires_login only: [ :set_notifications, :mentionable, :messageable, diff --git a/app/controllers/inline_onebox_controller.rb b/app/controllers/inline_onebox_controller.rb index c7f4e30f75..832f3d66a0 100644 --- a/app/controllers/inline_onebox_controller.rb +++ b/app/controllers/inline_onebox_controller.rb @@ -1,7 +1,7 @@ require_dependency 'inline_oneboxer' class InlineOneboxController < ApplicationController - before_action :ensure_logged_in + requires_login def show oneboxes = InlineOneboxer.new(params[:urls] || []).process diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 9bd0c0e391..8773a34970 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -2,11 +2,15 @@ require_dependency 'rate_limiter' class InvitesController < ApplicationController + requires_login only: [ + :destroy, :create, :create_invite_link, :rescind_all_invites, + :resend_invite, :resend_all_invites, :upload_csv + ] + skip_before_action :check_xhr, except: [:perform_accept_invitation] skip_before_action :preload_json, except: [:show] skip_before_action :redirect_to_login_if_required - before_action :ensure_logged_in, only: [:destroy, :create, :create_invite_link, :rescind_all_invites, :resend_invite, :resend_all_invites, :upload_csv] before_action :ensure_new_registrations_allowed, only: [:show, :perform_accept_invitation] before_action :ensure_not_logged_in, only: [:show, :perform_accept_invitation] diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index bedfb6b515..f20e6b9b13 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -2,7 +2,9 @@ require_dependency 'notification_serializer' class NotificationsController < ApplicationController - before_action :ensure_logged_in + requires_login + before_action :ensure_admin, only: [:create, :update, :destroy] + before_action :set_notification, only: [:update, :destroy] def index user = @@ -64,4 +66,33 @@ class NotificationsController < ApplicationController render json: success_json end + def create + @notification = Notification.create!(notification_params) + render_notification + end + + def update + @notification.update!(notification_params) + render_notification + end + + def destroy + @notification.destroy! + render json: success_json + end + + private + + def set_notification + @notification = Notification.find(params[:id]) + end + + def notification_params + params.permit(:notification_type, :user_id, :data, :read, :topic_id, :post_number, :post_action_id) + end + + def render_notification + render_json_dump(NotificationSerializer.new(@notification, scope: guardian, root: false)) + end + end diff --git a/app/controllers/onebox_controller.rb b/app/controllers/onebox_controller.rb index 2da4bd82f0..dea29ce536 100644 --- a/app/controllers/onebox_controller.rb +++ b/app/controllers/onebox_controller.rb @@ -1,7 +1,7 @@ require_dependency 'oneboxer' class OneboxController < ApplicationController - before_action :ensure_logged_in + requires_login def show unless params[:refresh] == 'true' diff --git a/app/controllers/post_actions_controller.rb b/app/controllers/post_actions_controller.rb index 2bf6027ba2..fa0314b999 100644 --- a/app/controllers/post_actions_controller.rb +++ b/app/controllers/post_actions_controller.rb @@ -1,7 +1,8 @@ require_dependency 'discourse' class PostActionsController < ApplicationController - before_action :ensure_logged_in + requires_login + before_action :fetch_post_from_params before_action :fetch_post_action_type_id_from_params diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 639ee168c2..e3e4a21910 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -8,7 +8,7 @@ require_dependency 'post_locker' class PostsController < ApplicationController - before_action :ensure_logged_in, except: [ + requires_login except: [ :show, :replies, :by_number, diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb index e96566fd25..8b0e0f243c 100644 --- a/app/controllers/session_controller.rb +++ b/app/controllers/session_controller.rb @@ -7,9 +7,10 @@ class SessionController < ApplicationController render body: nil, status: 500 end - before_action :check_local_login_allowed, only: %i(create forgot_password) + before_action :check_local_login_allowed, only: %i(create forgot_password email_login) + before_action :rate_limit_login, only: %i(create email_login) skip_before_action :redirect_to_login_if_required - skip_before_action :preload_json, :check_xhr, only: ['sso', 'sso_login', 'become', 'sso_provider', 'destroy'] + skip_before_action :preload_json, :check_xhr, only: %i(sso sso_login become sso_provider destroy email_login) ACTIVATE_USER_KEY = "activate_user" @@ -187,9 +188,6 @@ class SessionController < ApplicationController end def create - RateLimiter.new(nil, "login-hr-#{request.remote_ip}", SiteSetting.max_logins_per_ip_per_hour, 1.hour).performed! - RateLimiter.new(nil, "login-min-#{request.remote_ip}", SiteSetting.max_logins_per_ip_per_minute, 1.minute).performed! - params.require(:login) params.require(:password) @@ -208,7 +206,7 @@ class SessionController < ApplicationController # If the site requires user approval and the user is not approved yet if login_not_approved_for?(user) - login_not_approved + render json: login_not_approved return end @@ -220,20 +218,31 @@ class SessionController < ApplicationController return end - if user.suspended? - failed_to_login(user) - return + if payload = login_error_check(user) + render json: payload + else + (user.active && user.email_confirmed?) ? login(user) : not_activated(user) end + end - if ScreenedIpAddress.should_block?(request.remote_ip) - return not_allowed_from_ip_address(user) + def email_login + raise Discourse::NotFound if !SiteSetting.enable_local_logins_via_email + + if EmailToken.valid_token_format?(params[:token]) && (user = EmailToken.confirm(params[:token])) + if login_not_approved_for?(user) + @error = login_not_approved[:error] + return render layout: 'no_ember' + elsif payload = login_error_check(user) + @error = payload[:error] + return render layout: 'no_ember' + else + log_on_user(user) + redirect_to path("/") + end + else + @error = I18n.t('email_login.invalid_token') + return render layout: 'no_ember' end - - if ScreenedIpAddress.block_admin_login?(user, request.remote_ip) - return admin_not_allowed_from_ip_address(user) - end - - (user.active && user.email_confirmed?) ? login(user) : not_activated(user) end def forgot_password @@ -252,7 +261,7 @@ class SessionController < ApplicationController Jobs.enqueue(:critical_user_email, type: :forgot_password, user_id: user.id, email_token: email_token.token) end - json = { result: "ok" } + json = success_json unless SiteSetting.hide_email_address_taken json[:user_found] = user_presence end @@ -291,6 +300,18 @@ class SessionController < ApplicationController private + def login_error_check(user) + return failed_to_login(user) if user.suspended? + + if ScreenedIpAddress.should_block?(request.remote_ip) + return not_allowed_from_ip_address(user) + end + + if ScreenedIpAddress.block_admin_login?(user, request.remote_ip) + return admin_not_allowed_from_ip_address(user) + end + end + def login_not_approved_for?(user) SiteSetting.must_approve_users? && !user.approved? && !user.admin? end @@ -300,7 +321,7 @@ class SessionController < ApplicationController end def login_not_approved - render json: { error: I18n.t("login.not_approved") } + { error: I18n.t("login.not_approved") } end def not_activated(user) @@ -314,19 +335,21 @@ class SessionController < ApplicationController end def not_allowed_from_ip_address(user) - render json: { error: I18n.t("login.not_allowed_from_ip_address", username: user.username) } + { error: I18n.t("login.not_allowed_from_ip_address", username: user.username) } end def admin_not_allowed_from_ip_address(user) - render json: { error: I18n.t("login.admin_not_allowed_from_ip_address", username: user.username) } + { error: I18n.t("login.admin_not_allowed_from_ip_address", username: user.username) } end def failed_to_login(user) message = user.suspend_reason ? "login.suspended_with_reason" : "login.suspended" - render json: { - error: I18n.t(message, date: I18n.l(user.suspended_till, format: :date_only), - reason: Rack::Utils.escape_html(user.suspend_reason)), + { + error: I18n.t(message, + date: I18n.l(user.suspended_till, format: :date_only), + reason: Rack::Utils.escape_html(user.suspend_reason) + ), reason: 'suspended' } end @@ -342,6 +365,22 @@ class SessionController < ApplicationController end end + def rate_limit_login + RateLimiter.new( + nil, + "login-hr-#{request.remote_ip}", + SiteSetting.max_logins_per_ip_per_hour, + 1.hour + ).performed! + + RateLimiter.new( + nil, + "login-min-#{request.remote_ip}", + SiteSetting.max_logins_per_ip_per_minute, + 1.minute + ).performed! + end + def render_sso_error(status:, text:) @sso_error = text render status: status, layout: 'no_ember' diff --git a/app/controllers/static_controller.rb b/app/controllers/static_controller.rb index 03a88119c4..069562e3e3 100644 --- a/app/controllers/static_controller.rb +++ b/app/controllers/static_controller.rb @@ -148,9 +148,9 @@ class StaticController < ApplicationController def service_worker_asset respond_to do |format| format.js do - - # we take 1 hour to give a new service worker to all users - immutable_for 1.hour + # https://github.com/w3c/ServiceWorker/blob/master/explainer.md#updating-a-service-worker + # Maximum cache that the service worker will respect is 24 hours. + immutable_for 24.hours render( plain: Rails.application.assets_manifest.find_sources('service-worker.js').first, diff --git a/app/controllers/steps_controller.rb b/app/controllers/steps_controller.rb index ebfd4b530d..d2b9285c67 100644 --- a/app/controllers/steps_controller.rb +++ b/app/controllers/steps_controller.rb @@ -3,9 +3,9 @@ require_dependency 'wizard/builder' require_dependency 'wizard/step_updater' class StepsController < ApplicationController + requires_login before_action :ensure_wizard_enabled - before_action :ensure_logged_in before_action :ensure_admin def update diff --git a/app/controllers/tag_groups_controller.rb b/app/controllers/tag_groups_controller.rb index b55120954e..8d819827f7 100644 --- a/app/controllers/tag_groups_controller.rb +++ b/app/controllers/tag_groups_controller.rb @@ -1,6 +1,7 @@ class TagGroupsController < ApplicationController + requires_login except: [:index, :show] + skip_before_action :check_xhr, only: [:index, :show] - before_action :ensure_logged_in, except: [:index, :show] before_action :fetch_tag_group, only: [:show, :update, :destroy] def index diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index a73416d04f..b97de2b817 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -7,8 +7,7 @@ class TagsController < ::ApplicationController before_action :ensure_tags_enabled - skip_before_action :check_xhr, only: [:tag_feed, :show, :index] - before_action :ensure_logged_in, except: [ + requires_login except: [ :index, :show, :tag_feed, @@ -16,7 +15,11 @@ class TagsController < ::ApplicationController :check_hashtag, Discourse.anonymous_filters.map { |f| :"show_#{f}" } ].flatten - before_action :set_category_from_params, except: [:index, :update, :destroy, :tag_feed, :search, :notifications, :update_notifications] + + skip_before_action :check_xhr, only: [:tag_feed, :show, :index] + + before_action :set_category_from_params, except: [:index, :update, :destroy, + :tag_feed, :search, :notifications, :update_notifications] def index @description_meta = I18n.t("tags.title") diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index 439c568343..76e6c879e1 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -6,31 +6,32 @@ require_dependency 'discourse_event' require_dependency 'rate_limiter' class TopicsController < ApplicationController - before_action :ensure_logged_in, only: [:timings, - :destroy_timings, - :update, - :star, - :destroy, - :recover, - :status, - :invite, - :mute, - :unmute, - :set_notifications, - :move_posts, - :merge_topic, - :clear_pin, - :re_pin, - :status_update, - :timer, - :bulk, - :reset_new, - :change_post_owners, - :change_timestamps, - :archive_message, - :move_to_inbox, - :convert_topic, - :bookmark] + requires_login only: [ + :timings, + :destroy_timings, + :update, + :destroy, + :recover, + :status, + :invite, + :mute, + :unmute, + :set_notifications, + :move_posts, + :merge_topic, + :clear_pin, + :re_pin, + :status_update, + :timer, + :bulk, + :reset_new, + :change_post_owners, + :change_timestamps, + :archive_message, + :move_to_inbox, + :convert_topic, + :bookmark + ] before_action :consider_user_for_promotion, only: :show diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index dcfaa4ed3c..39fc24124b 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -2,7 +2,8 @@ require "mini_mime" require_dependency 'upload_creator' class UploadsController < ApplicationController - before_action :ensure_logged_in, except: [:show] + requires_login except: [:show] + skip_before_action :preload_json, :check_xhr, :redirect_to_login_if_required, only: [:show] def create diff --git a/app/controllers/user_api_keys_controller.rb b/app/controllers/user_api_keys_controller.rb index c6d41d2cc3..498a055e5d 100644 --- a/app/controllers/user_api_keys_controller.rb +++ b/app/controllers/user_api_keys_controller.rb @@ -2,9 +2,9 @@ class UserApiKeysController < ApplicationController layout 'no_ember' + requires_login only: [:create, :revoke, :undo_revoke] skip_before_action :redirect_to_login_if_required, only: [:new] skip_before_action :check_xhr, :preload_json - before_action :ensure_logged_in, only: [:create, :revoke, :undo_revoke] AUTH_API_VERSION ||= 2 diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index f4706ed858..4d0726d865 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -8,10 +8,18 @@ require_dependency 'admin_confirmation' class UsersController < ApplicationController skip_before_action :authorize_mini_profiler, only: [:avatar] - skip_before_action :check_xhr, only: [:show, :badges, :password_reset, :update, :account_created, :activate_account, :perform_account_activation, :user_preferences_redirect, :avatar, :my_redirect, :toggle_anon, :admin_login, :confirm_admin] - before_action :ensure_logged_in, only: [:username, :update, :user_preferences_redirect, :upload_user_image, - :pick_avatar, :destroy_user_image, :destroy, :check_emails, :topic_tracking_state] + requires_login only: [ + :username, :update, :user_preferences_redirect, :upload_user_image, + :pick_avatar, :destroy_user_image, :destroy, :check_emails, :topic_tracking_state, + :preferences + ] + + skip_before_action :check_xhr, only: [ + :show, :badges, :password_reset, :update, :account_created, + :activate_account, :perform_account_activation, :user_preferences_redirect, :avatar, + :my_redirect, :toggle_anon, :admin_login, :confirm_admin, :email_login + ] before_action :respond_to_suspicious_request, only: [:create] @@ -29,6 +37,7 @@ class UsersController < ApplicationController :update_activation_email, :password_reset, :confirm_email_token, + :email_login, :admin_login, :confirm_admin] @@ -555,6 +564,7 @@ class UsersController < ApplicationController elsif params[:token].present? if EmailToken.valid_token_format?(params[:token]) @user = EmailToken.confirm(params[:token]) + if @user&.admin? log_on_user(@user) return redirect_to path("/") @@ -572,6 +582,40 @@ class UsersController < ApplicationController render layout: false end + def email_login + raise Discourse::NotFound if !SiteSetting.enable_local_logins_via_email + return redirect_to path("/") if current_user + + expires_now + params.require(:login) + + RateLimiter.new(nil, "email-login-hour-#{request.remote_ip}", 6, 1.hour).performed! + RateLimiter.new(nil, "email-login-min-#{request.remote_ip}", 3, 1.minute).performed! + user = User.human_users.find_by_username_or_email(params[:login]) + user_presence = user.present? && !user.staged + + if user + RateLimiter.new(nil, "email-login-hour-#{user.id}", 6, 1.hour).performed! + RateLimiter.new(nil, "email-login-min-#{user.id}", 3, 1.minute).performed! + + if user_presence + email_token = user.email_tokens.create!(email: user.email) + + Jobs.enqueue(:critical_user_email, + type: :email_login, + user_id: user.id, + email_token: email_token.token + ) + end + end + + json = success_json + json[:user_found] = user_presence unless SiteSetting.hide_email_address_taken + render json: json + rescue RateLimiter::LimitExceeded + render_json_error(I18n.t("rate_limiter.slow_down")) + end + def toggle_anon user = AnonymousShadowCreator.get_master(current_user) || AnonymousShadowCreator.get(current_user) @@ -743,6 +787,7 @@ class UsersController < ApplicationController if include_groups || groups groups = Group.search_groups(term, groups: groups) groups = groups.where(visibility_level: Group.visibility_levels[:public]) if include_groups + groups = groups.order('groups.name asc') to_render[:groups] = groups.map do |m| { name: m.name, full_name: m.full_name } diff --git a/app/controllers/users_email_controller.rb b/app/controllers/users_email_controller.rb index 6e1e2a1f0c..e408a84f2a 100644 --- a/app/controllers/users_email_controller.rb +++ b/app/controllers/users_email_controller.rb @@ -4,7 +4,7 @@ require_dependency 'email_updater' class UsersEmailController < ApplicationController - before_action :ensure_logged_in, only: [:index, :update] + requires_login only: [:index, :update] skip_before_action :check_xhr, only: [:confirm] skip_before_action :redirect_to_login_if_required, only: [:confirm] diff --git a/app/controllers/wizard_controller.rb b/app/controllers/wizard_controller.rb index 3c52ee68f3..1bfa774f14 100644 --- a/app/controllers/wizard_controller.rb +++ b/app/controllers/wizard_controller.rb @@ -2,10 +2,10 @@ require_dependency 'wizard' require_dependency 'wizard/builder' class WizardController < ApplicationController - before_action :ensure_wizard_enabled, only: [:index] - before_action :ensure_logged_in, except: [:qunit] - before_action :ensure_admin, except: [:qunit] + requires_login except: [:qunit] + before_action :ensure_admin, except: [:qunit] + before_action :ensure_wizard_enabled, only: [:index] skip_before_action :check_xhr, :preload_json layout false diff --git a/app/jobs/onceoff/init_category_tag_stats.rb b/app/jobs/onceoff/init_category_tag_stats.rb new file mode 100644 index 0000000000..43abdc0b42 --- /dev/null +++ b/app/jobs/onceoff/init_category_tag_stats.rb @@ -0,0 +1,18 @@ +module Jobs + class InitCategoryTagStats < Jobs::Onceoff + def execute_onceoff(args) + CategoryTagStat.exec_sql "DELETE FROM category_tag_stats" + + CategoryTagStat.exec_sql <<~SQL + INSERT INTO category_tag_stats (category_id, tag_id, topic_count) + SELECT topics.category_id, tags.id, COUNT(topics.id) + FROM tags + INNER JOIN topic_tags ON tags.id = topic_tags.tag_id + INNER JOIN topics ON topics.id = topic_tags.topic_id + AND topics.deleted_at IS NULL + AND topics.category_id IS NOT NULL + GROUP BY tags.id, topics.category_id + SQL + end + end +end diff --git a/app/jobs/regular/emit_web_hook_event.rb b/app/jobs/regular/emit_web_hook_event.rb index 82832d946d..cc80fcebb2 100644 --- a/app/jobs/regular/emit_web_hook_event.rb +++ b/app/jobs/regular/emit_web_hook_event.rb @@ -33,7 +33,7 @@ module Jobs end def setup_post(args) - post = Post.find_by(id: args[:post_id]) + post = Post.with_deleted.find_by(id: args[:post_id]) return if post.blank? args[:payload] = WebHookPostSerializer.new(post, scope: guardian, root: false).as_json end diff --git a/app/jobs/regular/process_post.rb b/app/jobs/regular/process_post.rb index 88e340501a..6c8b4e7d2f 100644 --- a/app/jobs/regular/process_post.rb +++ b/app/jobs/regular/process_post.rb @@ -17,7 +17,7 @@ module Jobs cooking_options = args[:cooking_options] || {} cooking_options[:topic_id] = post.topic_id recooked = post.cook(post.raw, cooking_options.symbolize_keys) - post.update_column(:cooked, recooked) + post.update_columns(cooked: recooked, baked_at: Time.zone.now, baked_version: Post::BAKED_VERSION) end cp = CookedPostProcessor.new(post, args) diff --git a/app/jobs/scheduled/ensure_db_consistency.rb b/app/jobs/scheduled/ensure_db_consistency.rb index a482631621..03060c5d3e 100644 --- a/app/jobs/scheduled/ensure_db_consistency.rb +++ b/app/jobs/scheduled/ensure_db_consistency.rb @@ -16,6 +16,7 @@ module Jobs CategoryUser.ensure_consistency! UserOption.ensure_consistency! Tag.ensure_consistency! + CategoryTagStat.ensure_consistency! end end end diff --git a/app/jobs/scheduled/grant_new_user_of_the_month_badges.rb b/app/jobs/scheduled/grant_new_user_of_the_month_badges.rb index 86d3a3a1d9..5a2803b720 100644 --- a/app/jobs/scheduled/grant_new_user_of_the_month_badges.rb +++ b/app/jobs/scheduled/grant_new_user_of_the_month_badges.rb @@ -64,12 +64,16 @@ module Jobs LEFT OUTER JOIN topics AS t ON t.id = p.topic_id WHERE u.active AND u.id > 0 + AND u.id NOT IN (#{current_owners.join(',')}) AND NOT u.staged AND NOT u.admin AND NOT u.moderator - AND t.archetype <> '#{Archetype.private_message}' + AND u.suspended_at IS NULL + AND u.suspended_till IS NULL AND u.created_at >= CURRENT_TIMESTAMP - '1 month'::INTERVAL - AND u.id NOT IN (#{current_owners.join(',')}) + AND t.archetype <> '#{Archetype.private_message}' + AND t.deleted_at IS NULL + AND p.deleted_at IS NULL GROUP BY u.id HAVING COUNT(DISTINCT p.id) > 1 AND COUNT(DISTINCT p.topic_id) > 1 diff --git a/app/jobs/scheduled/poll_feed.rb b/app/jobs/scheduled/poll_feed.rb index 2f885c69a2..494fcf2a70 100644 --- a/app/jobs/scheduled/poll_feed.rb +++ b/app/jobs/scheduled/poll_feed.rb @@ -3,9 +3,6 @@ # require 'digest/sha1' require 'excon' -require 'rss' -require_dependency 'feed_item_accessor' -require_dependency 'feed_element_installer' require_dependency 'final_destination' require_dependency 'post_creator' require_dependency 'post_revisor' @@ -27,12 +24,25 @@ module Jobs end def poll_feed + ensure_rss_loaded + # defer loading rss feed = Feed.new import_topics(feed.topics) end private + @@rss_loaded = false + + # rss lib is very expensive memory wise, no need to load it till it is needed + def ensure_rss_loaded + return if @@rss_loaded + require 'rss' + require_dependency 'feed_item_accessor' + require_dependency 'feed_element_installer' + @@rss_loaded = true + end + def not_polled_recently? $redis.set( 'feed-polled-recently', diff --git a/app/jobs/scheduled/tl3_promotions.rb b/app/jobs/scheduled/tl3_promotions.rb index 94c3d42fc7..ce78290eae 100644 --- a/app/jobs/scheduled/tl3_promotions.rb +++ b/app/jobs/scheduled/tl3_promotions.rb @@ -21,13 +21,22 @@ module Jobs end # Promotions - User.real.where( - trust_level: TrustLevel[2], - manual_locked_trust_level: nil, - group_locked_trust_level: nil - ).where.not(id: demoted_user_ids).find_each do |u| + User.real.not_suspended.where( + trust_level: TrustLevel[2], + manual_locked_trust_level: nil, + group_locked_trust_level: nil + ).where.not(id: demoted_user_ids) + .joins(:user_stat) + .where("user_stats.days_visited >= ?", SiteSetting.tl3_requires_days_visited) + .where("user_stats.topic_reply_count >= ?", SiteSetting.tl3_requires_topics_replied_to) + .where("user_stats.topics_entered >= ?", SiteSetting.tl3_requires_topics_viewed_all_time) + .where("user_stats.posts_read_count >= ?", SiteSetting.tl3_requires_posts_read_all_time) + .where("user_stats.likes_given >= ?", SiteSetting.tl3_requires_likes_given) + .where("user_stats.likes_received >= ?", SiteSetting.tl3_requires_likes_received) + .find_each do |u| Promotion.new(u).review_tl2 end + end end diff --git a/app/jobs/scheduled/unsilence_users.rb b/app/jobs/scheduled/unsilence_users.rb new file mode 100644 index 0000000000..c609894fd5 --- /dev/null +++ b/app/jobs/scheduled/unsilence_users.rb @@ -0,0 +1,11 @@ +module Jobs + class UnsilenceUsers < Jobs::Scheduled + every 15.minutes + + def execute(args) + User.where("silenced_till IS NOT NULL AND silenced_till < now()").find_each do |user| + UserSilencer.unsilence(user, Discourse.system_user) + end + end + end +end diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb index 236acc0dab..f5d8e537ec 100644 --- a/app/mailers/user_notifications.rb +++ b/app/mailers/user_notifications.rb @@ -12,17 +12,17 @@ class UserNotifications < ActionMailer::Base include Email::BuildEmailHelper def signup(user, opts = {}) - build_email(user.email, - template: "user_notifications.signup", - locale: user_locale(user), - email_token: opts[:email_token]) + build_user_email_token_by_template( + "user_notifications.signup", + user, + opts[:email_token] + ) end def signup_after_approval(user, opts = {}) build_email(user.email, template: 'user_notifications.signup_after_approval', locale: user_locale(user), - email_token: opts[:email_token], new_user_tips: I18n.t('system_messages.usage_tips.text_body_template', base_url: Discourse.base_url, locale: locale)) end @@ -34,38 +34,51 @@ class UserNotifications < ActionMailer::Base end def confirm_old_email(user, opts = {}) - build_email(user.email, - template: "user_notifications.confirm_old_email", - locale: user_locale(user), - email_token: opts[:email_token]) + build_user_email_token_by_template( + "user_notifications.confirm_old_email", + user, + opts[:email_token] + ) end def confirm_new_email(user, opts = {}) - build_email(user.email, - template: "user_notifications.confirm_new_email", - locale: user_locale(user), - email_token: opts[:email_token]) + build_user_email_token_by_template( + "user_notifications.confirm_new_email", + user, + opts[:email_token] + ) end def forgot_password(user, opts = {}) - build_email(user.email, - template: user.has_password? ? "user_notifications.forgot_password" : "user_notifications.set_password", - locale: user_locale(user), - email_token: opts[:email_token]) + build_user_email_token_by_template( + user.has_password? ? "user_notifications.forgot_password" : "user_notifications.set_password", + user, + opts[:email_token] + ) + end + + def email_login(user, opts = {}) + build_user_email_token_by_template( + "user_notifications.email_login", + user, + opts[:email_token] + ) end def admin_login(user, opts = {}) - build_email(user.email, - template: "user_notifications.admin_login", - locale: user_locale(user), - email_token: opts[:email_token]) + build_user_email_token_by_template( + "user_notifications.admin_login", + user, + opts[:email_token] + ) end def account_created(user, opts = {}) - build_email(user.email, - template: "user_notifications.account_created", - locale: user_locale(user), - email_token: opts[:email_token]) + build_user_email_token_by_template( + "user_notifications.account_created", + user, + opts[:email_token] + ) end def account_silenced(user, opts = nil) @@ -533,6 +546,15 @@ class UserNotifications < ActionMailer::Base private + def build_user_email_token_by_template(template, user, email_token) + build_email( + user.email, + template: template, + locale: user_locale(user), + email_token: email_token + ) + end + def build_summary_for(user) @site_name = SiteSetting.email_prefix.presence || SiteSetting.title # used by I18n @user = user diff --git a/app/models/category_tag_stat.rb b/app/models/category_tag_stat.rb new file mode 100644 index 0000000000..f201de08cd --- /dev/null +++ b/app/models/category_tag_stat.rb @@ -0,0 +1,63 @@ +class CategoryTagStat < ActiveRecord::Base + belongs_to :category + belongs_to :tag + + def self.topic_moved(topic, from_category_id, to_category_id) + if from_category_id + self.where(tag_id: topic.tags.map(&:id), category_id: from_category_id) + .where('topic_count > 0') + .update_all('topic_count = topic_count - 1') + end + + if to_category_id + sql = <<~SQL + UPDATE #{self.table_name} + SET topic_count = topic_count + 1 + WHERE tag_id in (:tag_ids) + AND category_id = :category_id + RETURNING tag_id + SQL + + tag_ids = topic.tags.map(&:id) + updated_tag_ids = self.exec_sql(sql, tag_ids: tag_ids, category_id: to_category_id).map { |row| row['tag_id'] } + + (tag_ids - updated_tag_ids).each do |tag_id| + CategoryTagStat.create!(tag_id: tag_id, category_id: to_category_id, topic_count: 1) + end + end + end + + def self.topic_deleted(topic) + topic_moved(topic, topic.category_id, nil) + end + + def self.topic_recovered(topic) + topic_moved(topic, nil, topic.category_id) + end + + def self.ensure_consistency! + self.update_topic_counts + end + + # Recalculate all topic counts if they got out of sync + def self.update_topic_counts + CategoryTagStat.exec_sql <<~SQL + UPDATE category_tag_stats stats + SET topic_count = x.topic_count + FROM ( + SELECT COUNT(topics.id) AS topic_count, + tags.id AS tag_id, + topics.category_id as category_id + FROM tags + INNER JOIN topic_tags ON tags.id = topic_tags.tag_id + INNER JOIN topics ON topics.id = topic_tags.topic_id + AND topics.deleted_at IS NULL + AND topics.category_id IS NOT NULL + GROUP BY tags.id, topics.category_id + ) x + WHERE stats.tag_id = x.tag_id + AND stats.category_id = x.category_id + AND x.topic_count <> stats.topic_count + SQL + end +end diff --git a/app/models/group.rb b/app/models/group.rb index a86d60c581..61f7e79f13 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -281,13 +281,13 @@ class Group < ActiveRecord::Base remove_subquery = case name when :admins - "SELECT id FROM users WHERE NOT admin" + "SELECT id FROM users WHERE id <= 0 OR NOT admin" when :moderators - "SELECT id FROM users WHERE NOT moderator" + "SELECT id FROM users WHERE id <= 0 OR NOT moderator" when :staff - "SELECT id FROM users WHERE NOT admin AND NOT moderator" + "SELECT id FROM users WHERE id <= 0 OR (NOT admin AND NOT moderator)" when :trust_level_0, :trust_level_1, :trust_level_2, :trust_level_3, :trust_level_4 - "SELECT id FROM users WHERE trust_level < #{id - 10}" + "SELECT id FROM users WHERE id <= 0 OR trust_level < #{id - 10}" end exec_sql <<-SQL @@ -301,15 +301,15 @@ class Group < ActiveRecord::Base insert_subquery = case name when :admins - "SELECT id FROM users WHERE admin" + "SELECT id FROM users WHERE id > 0 AND admin" when :moderators - "SELECT id FROM users WHERE moderator" + "SELECT id FROM users WHERE id > 0 AND moderator" when :staff - "SELECT id FROM users WHERE moderator OR admin" + "SELECT id FROM users WHERE id > 0 AND (moderator OR admin)" when :trust_level_1, :trust_level_2, :trust_level_3, :trust_level_4 - "SELECT id FROM users WHERE trust_level >= #{id - 10}" + "SELECT id FROM users WHERE id > 0 AND trust_level >= #{id - 10}" when :trust_level_0 - "SELECT id FROM users" + "SELECT id FROM users WHERE id > 0" end exec_sql <<-SQL diff --git a/app/models/post.rb b/app/models/post.rb index 7a8ce9a573..5863cc49c1 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -222,7 +222,13 @@ class Post < ActiveRecord::Base @post_analyzers[raw_hash] ||= PostAnalyzer.new(raw, topic_id) end - %w{raw_mentions linked_hosts image_count attachment_count link_count raw_links}.each do |attr| + %w{raw_mentions + linked_hosts + image_count + attachment_count + link_count + raw_links + has_oneboxes?}.each do |attr| define_method(attr) do post_analyzer.send(attr) end diff --git a/app/models/post_action.rb b/app/models/post_action.rb index 617d0c72b3..8bcb64482b 100644 --- a/app/models/post_action.rb +++ b/app/models/post_action.rb @@ -538,7 +538,7 @@ SQL end def self.auto_hide_if_needed(acting_user, post, post_action_type) - return if post.hidden + return if post.hidden || post.user.staff? if post_action_type == :spam && acting_user.has_trust_level?(TrustLevel[3]) && diff --git a/app/models/post_analyzer.rb b/app/models/post_analyzer.rb index 3526100962..ddcfa2fd0c 100644 --- a/app/models/post_analyzer.rb +++ b/app/models/post_analyzer.rb @@ -13,6 +13,13 @@ class PostAnalyzer @found_oneboxes end + def has_oneboxes? + return false unless @raw.present? + + cooked_stripped + found_oneboxes? + end + # What we use to cook posts def cook(raw, opts = {}) cook_method = opts[:cook_method] @@ -104,7 +111,6 @@ class PostAnalyzer return @raw_links if @raw_links.present? @raw_links = [] - cooked_stripped.css("a[href]").each do |l| # Don't include @mentions in the link count next if l['href'].blank? || link_is_a_mention?(l) diff --git a/app/models/post_mover.rb b/app/models/post_mover.rb index 42e8ad42b5..7544a87ef1 100644 --- a/app/models/post_mover.rb +++ b/app/models/post_mover.rb @@ -47,7 +47,7 @@ class PostMover notify_users_that_posts_have_moved update_statistics update_user_actions - set_last_post_user_id(destination_topic) + update_last_post_stats if moving_all_posts @original_topic.update_status('closed', true, @user) @@ -96,6 +96,7 @@ class PostMover via_email: post.via_email, raw_email: post.raw_email, skip_validations: true, + created_at: post.created_at, guardian: Guardian.new(user) ) @@ -127,7 +128,8 @@ class PostMover update[:reply_to_user_id] = nil end - post.update(update) + post.attributes = update + post.save(validate: false) move_incoming_emails(post, post) move_email_logs(post, post) @@ -204,9 +206,15 @@ class PostMover end end - def set_last_post_user_id(topic) - user_id = topic.posts.last.user_id rescue nil - return if user_id.nil? - topic.update_attribute :last_post_user_id, user_id + def update_last_post_stats + post = destination_topic.posts.where.not(post_type: Post.types[:whisper]).last + if post && post_ids.include?(post.id) + attrs = {} + attrs[:last_posted_at] = post.created_at + attrs[:last_post_user_id] = post.user_id + attrs[:bumped_at] = post.created_at unless post.no_bump + attrs[:updated_at] = 'now()' + destination_topic.update_columns(attrs) + end end end diff --git a/app/models/report.rb b/app/models/report.rb index 83f046cc36..04f27453a0 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -10,7 +10,7 @@ class Report def initialize(type) @type = type - @start_date ||= 1.month.ago.beginning_of_day + @start_date ||= Report.default_days.days.ago.beginning_of_day @end_date ||= Time.zone.now.end_of_day end @@ -75,11 +75,12 @@ class Report report.data << { x: date, y: count } end - report.total = data.sum(:count) - report.prev30Days = data.where('date >= ? AND date <= ?', - (report.start_date - 31.days).to_date, - (report.end_date - 31.days).to_date) - .sum(:count) + report.total = data.sum(:count) + + report.prev30Days = data.where( + 'date >= ? AND date < ?', + (report.start_date - 31.days).to_date, report.start_date.to_date + ).sum(:count) end def self.report_visits(report) diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb index 46951fa379..d0ce4465eb 100644 --- a/app/models/site_setting.rb +++ b/app/models/site_setting.rb @@ -38,7 +38,7 @@ class SiteSetting < ActiveRecord::Base end def self.private_message_title_length - min_private_message_title_length..max_topic_title_length + min_personal_message_title_length..max_topic_title_length end def self.post_length @@ -50,7 +50,7 @@ class SiteSetting < ActiveRecord::Base end def self.private_message_post_length - min_private_message_post_length..max_post_length + min_personal_message_post_length..max_post_length end def self.top_menu_items diff --git a/app/models/tag.rb b/app/models/tag.rb index 2e8a2c6694..45d900d422 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -59,13 +59,19 @@ class Tag < ActiveRecord::Base scope_category_ids &= ([category.id] + category.subcategories.pluck(:id)) end - tags = DiscourseTagging.filter_allowed_tags( - tags_by_count_query(limit: limit).where("topics.category_id in (?)", scope_category_ids), - nil, # Don't pass guardian. You might not be able to use some tags, but should still be able to see where they've been used. - category: category - ) + return [] if scope_category_ids.empty? - tags.count(COUNT_ARG).map { |name, _| name } + tag_names_with_counts = Tag.exec_sql <<~SQL + SELECT tags.name as tag_name, SUM(stats.topic_count) AS sum_topic_count + FROM category_tag_stats stats + INNER JOIN tags ON stats.tag_id = tags.id AND stats.topic_count > 0 + WHERE stats.category_id in (#{scope_category_ids.join(',')}) + GROUP BY tags.name + ORDER BY sum_topic_count DESC, tag_name ASC + LIMIT #{limit} + SQL + + tag_names_with_counts.map { |row| row['tag_name'] } end def self.include_tags? diff --git a/app/models/topic.rb b/app/models/topic.rb index 3561ffbc5b..3b3c74b956 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -54,14 +54,20 @@ class Topic < ActiveRecord::Base end def trash!(trashed_by = nil) - update_category_topic_count_by(-1) if deleted_at.nil? + if deleted_at.nil? + update_category_topic_count_by(-1) + CategoryTagStat.topic_deleted(self) if self.tags.present? + end super(trashed_by) update_flagged_posts_count self.topic_embed.trash! if has_topic_embed? end def recover! - update_category_topic_count_by(1) unless deleted_at.nil? + unless deleted_at.nil? + update_category_topic_count_by(1) + CategoryTagStat.topic_recovered(self) if self.tags.present? + end super update_flagged_posts_count unless (topic_embed = TopicEmbed.with_deleted.find_by_topic_id(id)).nil? @@ -122,8 +128,8 @@ class Topic < ActiveRecord::Base has_many :allowed_users, through: :topic_allowed_users, source: :user has_many :queued_posts - has_many :topic_tags, dependent: :destroy - has_many :tags, through: :topic_tags + has_many :topic_tags + has_many :tags, through: :topic_tags, dependent: :destroy # dependent destroy applies to the topic_tags records has_many :tag_users, through: :tags has_one :top_topic @@ -226,6 +232,12 @@ class Topic < ActiveRecord::Base UserActionCreator.log_topic(self) end + after_update do + if saved_changes[:category_id] && self.tags.present? + CategoryTagStat.topic_moved(self, *saved_changes[:category_id]) + end + end + def initialize_default_values self.bumped_at ||= Time.now self.last_post_user_id ||= user_id @@ -310,7 +322,7 @@ class Topic < ActiveRecord::Base def limit_private_messages_per_day return unless private_message? - apply_per_day_rate_limit_for("pms", :max_private_messages_per_day) + apply_per_day_rate_limit_for("pms", :max_personal_messages_per_day) end def self.fancy_title(title) diff --git a/app/models/topic_embed.rb b/app/models/topic_embed.rb index 8e3071ea4c..2f2b26e195 100644 --- a/app/models/topic_embed.rb +++ b/app/models/topic_embed.rb @@ -183,7 +183,7 @@ class TopicEmbed < ActiveRecord::Base def self.topic_id_for_embed(embed_url) embed_url = normalize_url(embed_url).sub(/^https?\:\/\//, '') - TopicEmbed.where("embed_url ~* '^https?://#{embed_url}$'").pluck(:topic_id).first + TopicEmbed.where("embed_url ~* '^https?://#{Regexp.escape(embed_url)}$'").pluck(:topic_id).first end def self.first_paragraph_from(html) diff --git a/app/models/topic_tag.rb b/app/models/topic_tag.rb index f8dc3cfee0..2dc203290b 100644 --- a/app/models/topic_tag.rb +++ b/app/models/topic_tag.rb @@ -1,6 +1,24 @@ class TopicTag < ActiveRecord::Base belongs_to :topic belongs_to :tag, counter_cache: "topic_count" + + after_create do + if topic.category_id + if stat = CategoryTagStat.where(tag_id: tag_id, category_id: topic.category_id).first + stat.increment!(:topic_count) + else + CategoryTagStat.create(tag_id: tag_id, category_id: topic.category_id, topic_count: 1) + end + end + end + + after_destroy do + if topic.category_id + if stat = CategoryTagStat.where(tag_id: tag_id, category: topic.category_id).first + stat.topic_count == 1 ? stat.destroy : stat.decrement!(:topic_count) + end + end + end end # == Schema Information diff --git a/app/models/topic_tracking_state.rb b/app/models/topic_tracking_state.rb index a7d48758ba..0e144d991e 100644 --- a/app/models/topic_tracking_state.rb +++ b/app/models/topic_tracking_state.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # this class is used to mirror unread and new status back to end users # in JavaScript there is a mirror class that is kept in-sync using the mssage bus # the allows end users to always know which topics have unread posts in them @@ -175,8 +177,7 @@ class TopicTrackingState sql << report_raw_sql(topic_id: topic_id, skip_new: true, skip_order: true, staff: user.staff?) SqlBuilder.new(sql) - .map_exec(TopicTrackingState, user_id: user.id, topic_id: topic_id) - + .map_exec(TopicTrackingState, user_id: user.id, topic_id: topic_id, min_new_topic_date: Time.at(SiteSetting.min_new_topics_time).to_datetime) end def self.report_raw_sql(opts = nil) @@ -196,7 +197,8 @@ class TopicTrackingState if opts && opts[:skip_new] "1=0" else - TopicQuery.new_filter(Topic, "xxx").where_clause.send(:predicates).join(" AND ").gsub!("'xxx'", treat_as_new_topic_clause) + TopicQuery.new_filter(Topic, "xxx").where_clause.send(:predicates).join(" AND ").gsub!("'xxx'", treat_as_new_topic_clause) + + " AND topics.created_at > :min_new_topic_date" end select = (opts && opts[:select]) || " @@ -208,7 +210,7 @@ class TopicTrackingState c.id AS category_id, tu.notification_level" - sql = <= min_days_visited && - num_topics_replied_to >= min_topics_replied_to && - topics_viewed >= min_topics_viewed && - posts_read >= min_posts_read && - num_flagged_posts <= max_flagged_posts && - num_flagged_by_users <= max_flagged_by_users && - topics_viewed_all_time >= min_topics_viewed_all_time && - posts_read_all_time >= min_posts_read_all_time && - num_likes_given >= min_likes_given && - num_likes_received >= min_likes_received && - num_likes_received_users >= min_likes_received_users && - num_likes_received_days >= min_likes_received_days + + (!@user.suspended?) && + (!@user.silenced?) && + days_visited >= min_days_visited && + num_topics_replied_to >= min_topics_replied_to && + topics_viewed >= min_topics_viewed && + posts_read >= min_posts_read && + num_flagged_posts <= max_flagged_posts && + num_flagged_by_users <= max_flagged_by_users && + topics_viewed_all_time >= min_topics_viewed_all_time && + posts_read_all_time >= min_posts_read_all_time && + num_likes_given >= min_likes_given && + num_likes_received >= min_likes_received && + num_likes_received_users >= min_likes_received_users && + num_likes_received_days >= min_likes_received_days end def requirements_lost? return false if trust_level_locked + @user.suspended? || - days_visited < min_days_visited * LOW_WATER_MARK || - num_topics_replied_to < min_topics_replied_to * LOW_WATER_MARK || - topics_viewed < min_topics_viewed * LOW_WATER_MARK || - posts_read < min_posts_read * LOW_WATER_MARK || - num_flagged_posts > max_flagged_posts || - num_flagged_by_users > max_flagged_by_users || - topics_viewed_all_time < min_topics_viewed_all_time || - posts_read_all_time < min_posts_read_all_time || - num_likes_given < min_likes_given * LOW_WATER_MARK || - num_likes_received < min_likes_received * LOW_WATER_MARK || - num_likes_received_users < min_likes_received_users * LOW_WATER_MARK || - num_likes_received_days < min_likes_received_days * LOW_WATER_MARK + @user.silenced? || + days_visited < min_days_visited * LOW_WATER_MARK || + num_topics_replied_to < min_topics_replied_to * LOW_WATER_MARK || + topics_viewed < min_topics_viewed * LOW_WATER_MARK || + posts_read < min_posts_read * LOW_WATER_MARK || + num_flagged_posts > max_flagged_posts || + num_flagged_by_users > max_flagged_by_users || + topics_viewed_all_time < min_topics_viewed_all_time || + posts_read_all_time < min_posts_read_all_time || + num_likes_given < min_likes_given * LOW_WATER_MARK || + num_likes_received < min_likes_received * LOW_WATER_MARK || + num_likes_received_users < min_likes_received_users * LOW_WATER_MARK || + num_likes_received_days < min_likes_received_days * LOW_WATER_MARK end def time_period diff --git a/app/models/user.rb b/app/models/user.rb index 1d4283659e..db69ef9f96 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -743,7 +743,8 @@ class User < ActiveRecord::Base def activate if email_token = self.email_tokens.active.where(email: self.email).first - EmailToken.confirm(email_token.token) + user = EmailToken.confirm(email_token.token) + self.update!(active: true) if user.nil? else self.update!(active: true) end @@ -1096,8 +1097,7 @@ class User < ActiveRecord::Base if SiteSetting.must_approve_users Jobs.enqueue(:critical_user_email, type: :signup_after_approval, - user_id: id, - email_token: email_tokens.first.token + user_id: id ) end end diff --git a/app/models/user_option.rb b/app/models/user_option.rb index e882e4ccf3..828ac58c60 100644 --- a/app/models/user_option.rb +++ b/app/models/user_option.rb @@ -27,7 +27,7 @@ class UserOption < ActiveRecord::Base self.mailing_list_mode_frequency = SiteSetting.default_email_mailing_list_mode_frequency self.email_direct = SiteSetting.default_email_direct self.automatically_unpin_topics = SiteSetting.default_topics_automatic_unpin - self.email_private_messages = SiteSetting.default_email_private_messages + self.email_private_messages = SiteSetting.default_email_personal_messages self.email_previous_replies = SiteSetting.default_email_previous_replies self.email_in_reply_to = SiteSetting.default_email_in_reply_to diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb index 46fa8fe05c..b96d7f28b3 100644 --- a/app/serializers/current_user_serializer.rb +++ b/app/serializers/current_user_serializer.rb @@ -39,7 +39,12 @@ class CurrentUserSerializer < BasicUserSerializer :seen_notification_id, :primary_group_id, :primary_group_name, - :can_create_topic + :can_create_topic, + :can_post_link + + def can_post_link + scope.can_post_link? + end def can_create_topic scope.can_create_topic?(nil) diff --git a/app/services/anonymous_shadow_creator.rb b/app/services/anonymous_shadow_creator.rb index 7cb27438a6..fc602b8255 100644 --- a/app/services/anonymous_shadow_creator.rb +++ b/app/services/anonymous_shadow_creator.rb @@ -13,6 +13,7 @@ class AnonymousShadowCreator return unless user return unless SiteSetting.allow_anonymous_posting return if user.trust_level < SiteSetting.anonymous_posting_min_trust_level + return if SiteSetting.must_approve_users? && !user.approved? if (shadow_id = user.custom_fields["shadow_id"].to_i) > 0 shadow = User.find_by(id: shadow_id) @@ -40,6 +41,8 @@ class AnonymousShadowCreator active: true, trust_level: 1, manual_locked_trust_level: 1, + approved: true, + approved_at: 1.day.ago, created_at: 1.day.ago # bypass new user restrictions ) diff --git a/app/services/notification_emailer.rb b/app/services/notification_emailer.rb index 4a6b56f2a2..099b2030b4 100644 --- a/app/services/notification_emailer.rb +++ b/app/services/notification_emailer.rb @@ -99,7 +99,7 @@ class NotificationEmailer end def private_delay - SiteSetting.private_email_time_window_seconds + SiteSetting.personal_email_time_window_seconds end def post_type diff --git a/app/services/staff_action_logger.rb b/app/services/staff_action_logger.rb index 6261e65580..b5a64c3f2f 100644 --- a/app/services/staff_action_logger.rb +++ b/app/services/staff_action_logger.rb @@ -289,13 +289,14 @@ class StaffActionLogger def log_silence_user(user, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user - UserHistory.create( - params(opts).merge( - action: UserHistory.actions[:silence_user], - target_user_id: user.id, - details: opts[:details] - ) + create_args = params(opts).merge( + action: UserHistory.actions[:silence_user], + target_user_id: user.id, + details: opts[:details] ) + create_args[:post_id] = opts[:post_id] if opts[:post_id] + + UserHistory.create(create_args) end def log_unsilence_user(user, opts = {}) diff --git a/app/services/user_silencer.rb b/app/services/user_silencer.rb index 5db360637b..a9fff88dd0 100644 --- a/app/services/user_silencer.rb +++ b/app/services/user_silencer.rb @@ -33,10 +33,12 @@ class UserSilencer SystemMessage.create(@user, message_type) if @by_user + log_params = { context: context, details: details } + log_params[:post_id] = @opts[:post_id].to_i if @opts[:post_id] + @user_history = StaffActionLogger.new(@by_user).log_silence_user( @user, - context: context, - details: details + log_params ) end diff --git a/app/views/common/_discourse_javascript.html.erb b/app/views/common/_discourse_javascript.html.erb index 3ee86fa4d4..3211308db2 100644 --- a/app/views/common/_discourse_javascript.html.erb +++ b/app/views/common/_discourse_javascript.html.erb @@ -44,6 +44,7 @@ Discourse.SiteSettings = ps.get('siteSettings'); Discourse.LetterAvatarVersion = '<%= LetterAvatar.version %>'; Discourse.MarkdownItURL = '<%= asset_url('markdown-it-bundle.js') %>'; + Discourse.ServiceWorkerURL = '<%= Rails.application.assets_manifest.assets['service-worker.js'] %>' I18n.defaultLocale = '<%= SiteSetting.default_locale %>'; Discourse.start(); Discourse.set('assetVersion','<%= Discourse.assets_digest %>'); diff --git a/app/views/session/email_login.html.erb b/app/views/session/email_login.html.erb new file mode 100644 index 0000000000..7bd6cf03fd --- /dev/null +++ b/app/views/session/email_login.html.erb @@ -0,0 +1,17 @@ +<%if @error%> +
            + <%= @error %> +
            +<%end%> + +<% content_for :title do %><%=t "email_login.title" %><% end %> + +<%- content_for(:no_ember_head) do %> + + <%= preload_script "ember_jquery" %> + <%= render_google_universal_analytics_code %> +<%- end %> + +<%- content_for(:head) do %> + +<%- end %> diff --git a/config/environments/test.rb b/config/environments/test.rb index 6143f0004a..8a0fc8c6c9 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -55,7 +55,7 @@ Discourse::Application.configure do s.set_regardless_of_locale(:s3_upload_bucket, 'bucket') s.set_regardless_of_locale(:min_post_length, 5) s.set_regardless_of_locale(:min_first_post_length, 5) - s.set_regardless_of_locale(:min_private_message_post_length, 10) + s.set_regardless_of_locale(:min_personal_message_post_length, 10) s.set_regardless_of_locale(:crawl_images, false) s.set_regardless_of_locale(:download_remote_images_to_local, false) s.set_regardless_of_locale(:unique_posts_mins, 0) diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index dfe5b65c70..fcbc996ac8 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -1855,8 +1855,6 @@ ar: many: لقد حدّدت {{count}} منشور. other: لقد حدّدت {{count}} منشور. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "اقتبس" edit_reason: "السبب:" post_number: "المنشور {{number}}" diff --git a/config/locales/client.ca.yml b/config/locales/client.ca.yml index bd1bb04b05..936df7f7c8 100644 --- a/config/locales/client.ca.yml +++ b/config/locales/client.ca.yml @@ -122,6 +122,7 @@ ca: split_topic: "va dividir aquest tema %{when}" invited_user: "va invitar %{who} %{when}" invited_group: "va invitar %{who} %{when}" + user_left: "%{who} ha deixat aquest missatge %{when}" removed_user: "va eliminar %{who} %{when}" removed_group: "va eliminar %{who} %{when}" autoclosed: @@ -178,7 +179,7 @@ ca: sign_up: "Registra't" log_in: "Inicia sessió" age: "Edat" - joined: "T'hi has afegit" + joined: "Es va registrar" admin_title: "Admin" flags_title: "Avisos" show_more: "mostrar més" @@ -491,6 +492,7 @@ ca: mute: "Silenci" edit: "Editar preferències" download_archive: + button_text: "Descarrega tot" confirm: "Segur que vols descarregat les teves publicacions?" success: "Descàrrega inciada, quan el procés s'acabi t'ho notificarem amb un missatge." rate_limit_error: "Les publicacions es poden descarregar un cop al dia, si us plau torna a provar-ho demà." @@ -1286,7 +1288,7 @@ ca: one: "hi ha 1 nova publicació a aquest tema des de la teva darrera lectura" other: "hi ha {{count}} noves publicacions a aquest tema des de la teva darrera lectura" likes: - one: "hi ha 1 M'agrada a aquest tema" + one: "hi ha 1 M'agrada a aquest tema" other: "hi ha {{count}} m'agrades en aquest tema" back_to_list: "Torna al llistat de temes" options: "Opcions de tema" @@ -1336,6 +1338,10 @@ ca: '3_2': 'Rebràs alertes perquè estàs mirant aquest tema.' '3_1': 'Rebràs alertes perquè has creat aquest tema.' '3': 'Rebràs alertes perquè estàs mirant aquest tema.' + '2_8': 'Rebràs notificacions, perquè estàs seguint aquesta categoria.' + '2_4': 'Rebràs notificacions, perquè has escrit una resposta a aquest tema.' + '2_2': 'Rebràs notificacions, perquè estàs seguint aquest tema.' + '2': 'Rebràs notificacions, perquè has llegit aquest tema.' '1_2': 'Només se''t notificarà si algú menciona el teu @nom o contesta la teva entrada.' '1': 'Se''t notificarà si algú menciona el teu @nom o contesta la teva entrada.' '0_7': 'No estàs fent cas de les alertes d''aquesta categoria.' @@ -1505,8 +1511,6 @@ ca: one: Has seleccionat 1 publicació other: Has seleccionat {{count}} publicacions. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Cita" edit_reason: "Motiu:" post_number: "publicació {{number}}" @@ -1530,7 +1534,7 @@ ca: one: "{{count}} Resposta" other: "{{count}} Respostes" has_likes: - one: "{{count}} M'agrada" + one: "{{count}} M'agrada" other: "{{count}} M'agrades" has_likes_title: one: "La publicació agrada a 1 persona" diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index b4aff06758..f6d67fd699 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -112,15 +112,15 @@ cs: x_days: one: "za 1 den" few: "za %{count} dny" - other: "za %{count} dní" + other: "o %{count} dní později" x_months: one: "za 1 měsíc" few: "za %{count} měsíce" - other: "za %{count} měsíců" + other: "o %{count} měsíců později" x_years: one: "za 1 rok" few: "za %{count} roků" - other: "za %{count} let" + other: "o %{count} let později" previous_month: 'Předchozí měsíc' next_month: 'Další měsíc' placeholder: datum @@ -135,7 +135,7 @@ cs: action_codes: public_topic: "Téma zveřejněno %{when}" private_topic: "Téma změněno na soukromé %{when}" - split_topic: "rozděl toto téma %{when}" + split_topic: "rozdělil toto téma %{when}" invited_user: "%{who} pozván %{when}" invited_group: "%{who} pozvána %{when}" removed_user: "%{who} smazán %{when}" @@ -1062,7 +1062,7 @@ cs: title_or_link_placeholder: "Sem vložte název téma" edit_reason_placeholder: "proč byla nutná úprava?" show_edit_reason: "(přidat důvod úpravy)" - reply_placeholder: "Piš tady. Pro formátování používej Markdown, BBCode nebo HTML. Přetáhni nebo vlož obrázky." + reply_placeholder: "Pište sem. Můžete použít Markdown, BBCode nebo HTML. Obrázky nahrajte přetáhnutím nebo vložením ze schránky." view_new_post: "Zobrazit váš nový příspěvek." saving: "Ukládám" saved: "Uloženo!" @@ -1168,7 +1168,7 @@ cs: advanced: title: Pokročilé hledání posted_by: - label: Zaslal + label: Od uživatele in_category: label: V kategorii in_group: @@ -1178,15 +1178,19 @@ cs: with_tags: label: Se štítkem filters: - likes: líbí se mi + label: Pouze v tématech/příspěvcích, které + likes: se mi líbí posted: Přidal jsem příspěvek watching: Sleduji tracking: Sleduji. + private: v mých zprávách first: jsou první příspěvek v tématu pinned: jsou připnuty unpinned: nejsou připnuty + seen: která jsem četl unseen: jsem nečetl wiki: jsou wiki + all_tags: Všechny uvedené štítky statuses: label: Kde příspěvky open: jsou otevřeny @@ -1207,7 +1211,7 @@ cs: not_logged_in_user: 'stránka uživatele s přehledem o aktuální činnosti a nastavení' current_user: 'jít na vaši uživatelskou stránku' topics: - new_messages_marker: "poslední navštívení" + new_messages_marker: "poslední návštěva" bulk: select_all: "Vybrat vše" clear_all: "Zrušit vše" @@ -1321,13 +1325,16 @@ cs: toggle_information: "zobrazit/skrýt detaily tématu" read_more_in_category: "Chcete si toho přečíst víc? Projděte si témata v {{catLink}} nebo {{latestLink}}." read_more: "Chcete si přečíst další informace? {{catLink}} nebo {{latestLink}}." - read_more_MF: "{ UNREAD, plural, =0 {} one { Je tu 1 nepřečtené } other { Je tu # nepřečtených } } { NEW, plural, =0 {} one { {BOTH, select, true{and } false {is } other{}} 1 nové téma} other { {BOTH, select, true{and } false {are } other{}} # nových témat} } remaining, nebo {CATEGORY, select, true {si projděte ostatní témata v kategorii {catLink}} false {{latestLink}} other {}}" + read_more_MF: "{ UNREAD, plural, =0 {} one { Zbývá 1 nepřečtené téma } other { Je tu # nepřečtených témat } } { NEW, plural, =0 {} one { {BOTH, select, true{and } false {is } other{}} 1 nové téma} other { {BOTH, select, true{and } false {are } other{}} # nových témat} }, nebo {CATEGORY, select, true {zobrazit další témata v kategorii {catLink}} false {{latestLink}} other {}}" browse_all_categories: Projděte všechny kategorie view_latest_topics: zobrazte si populární témata suggest_create_topic: Co takhle založit nové téma? jump_reply_up: přejít na předchozí odpověď jump_reply_down: přejít na následující odpověď deleted: "Téma bylo smazáno" + auto_update_input: + later_today: "Později během dnešního dne" + later_this_week: "Později během tohoto týdne" auto_close_title: 'Nastavení automatického zavření' auto_close_immediate: one: "Poslední příspěvek v témetu je již 1 hodinu starý, takže toto téma bude okamžitě uzavřeno." @@ -1358,6 +1365,10 @@ cs: '3_2': 'Budete dostávat oznámení, protože hlídáte toto téma.' '3_1': 'Budete dostávat oznámení, protože jste autorem totoho tématu.' '3': 'Budete dostávat oznámení, protože hlídáte toto téma.' + '2_8': 'Uvidíte počet nových odpovědí, jelikož sledujete tuto kategorii.' + '2_4': 'Uvidíte počet nových odpovědí, jelikož jste přispěli do tohoto tématu.' + '2_2': 'Uvidíte počet nových odpovědí, jelikož sledujete toto téma.' + '2': 'Uvidíte počet nových odpovědí, protože jste četl(a) toto téma.' '1_2': 'Budete informováni pokud někdo zmíní vaše @jméno nebo odpoví na váš příspěvek.' '1': 'Budete informováni pokud někdo zmíní vaše @jméno nebo odpoví na váš příspěvek.' '0_7': 'Ignorujete všechna oznámení v této kategorii.' @@ -1542,8 +1553,6 @@ cs: few: Máte označeny {{count}} příspěvky. other: Máte označeno {{count}} příspěvků. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Cituj" edit_reason: "Důvod: " post_number: "příspěvek č. {{number}}" @@ -1602,7 +1611,7 @@ cs: about: "toto je wiki příspěvek" archetypes: save: 'Uložit nastavení' - few_likes_left: "Díky za šíření lásky! Zbývá ti pro dnešek už jen několi \"líbí se\"." + few_likes_left: "Díky za šíření lásky! Zbývá ti pro dnešek už jen několik málo lajků." controls: reply: "otevře okno pro sepsání odpovědi na tento příspěvek" like: "to se mi líbí" @@ -1915,9 +1924,9 @@ cs: posts_long: "v tomto tématu je {{number}} příspěvků" posts_likes_MF: | Toto téma má {count, plural, one {1 příspěvek} other {# příspěvků}} {ratio, select, - low {s velkým poměrem líbí se na příspěvek} - med {s velmi velkým poměrem líbí se na příspěvek} - high {s extrémně velkým poměrem líbí se na příspěvek} + low {s vysokým počtem lajků} + med {s velmi vysokým počtem lajků} + high {s extrémně vysokým počtem lajků} other {}} original_post: "Původní příspěvek" views: "Zobrazení" @@ -1932,7 +1941,7 @@ cs: one: "líbí se" few: "líbí se" other: "líbí se" - likes_long: "v tomto tématu je {{number}} 'líbí se'" + likes_long: "v tomto tématu je {{number}} lajků" users: "Účastníci" users_lowercase: one: "uživatel" @@ -1949,12 +1958,12 @@ cs: with_topics: "%{filter} témata" with_category: "%{filter} %{category} témata" latest: - title: "Nejaktuálnější" + title: "Aktuální" title_with_count: one: "Nedávné (1)" few: "Nedávná ({{count}})" other: "Nedávná ({{count}})" - help: "nejaktuálnější témata" + help: "aktuální témata" hot: title: "Populární" help: "populární témata z poslední doby" @@ -2737,7 +2746,7 @@ cs: reputation: Reputace permissions: Oprávnění activity: Aktivita - like_count: Rozdaných / obdržených 'líbí se' + like_count: Rozdaných / obdržených lajků last_100_days: 'Za posledních 100 dní' private_topics_count: Počet soukromých témat posts_read_count: Přečteno příspěvků @@ -2783,6 +2792,7 @@ cs: activate_failed: "Nasstal problém při aktivování tohoto uživatele." deactivate_account: "Deaktivovat účet" deactivate_failed: "Nastal problém při deaktivování tohoto uživatele." + silence_accept: 'Ano, umlčet tohoto uživatele' bounce_score: "Bounce skóre" reset_bounce_score: label: "obnovit výchozí" @@ -2901,6 +2911,7 @@ cs: developer: 'Vývojáři' embedding: "Embedding" legal: "Právní záležitosti" + api: 'API' user_api: 'Uživatelské API' uncategorized: 'Ostatní' backups: "Zálohy" diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index a48aaee063..b652bd9ad8 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -1540,8 +1540,6 @@ da: one: Du har valgt 1 indlæg. other: Du har valgt {{count}} indlæg. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Citér" edit_reason: "Reason: " post_number: "indlæg {{number}}" diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index 16fc0a2d61..56e79ecfa8 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -821,7 +821,7 @@ de: other: "gegeben" likes_received: one: "vergeben" - other: "vergeben" + other: "erhalten" days_visited: one: "Tag vorbeigekommen" other: "Tage vorbeigekommen" @@ -1057,7 +1057,6 @@ de: default_header_text: Auswählen… no_content: Keine Treffer gefunden filter_placeholder: Suchen… - create: "Erstelle {{content}}" emoji_picker: filter_placeholder: Emoji suchen people: Personen @@ -1695,8 +1694,6 @@ de: one: Du hast 1 Beitrag ausgewählt. other: Du hast {{count}} Beiträge ausgewählt. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Zitat" edit: " {{link}} {{replyAvatar}} {{username}}" edit_reason: "Grund: " @@ -2426,11 +2423,9 @@ de: moderation_history: "Moderationshistorie" agree: "Zustimmen" agree_title: "Meldung bestätigen, weil diese gültig und richtig ist" - agree_flag_hide_post: "Zustimmen und Beitrag verstecken" agree_flag_hide_post_title: "Verstecke diesen Beitrag und sende dem Benutzer/der Benutzerin eine Nachricht mit der Bitte, ihn zu bearbeiten." agree_flag_restore_post: "Zustimmen und Beitrag wiederherstellen" agree_flag_restore_post_title: "Beitrag wiederherstellen, sodass alle Benutzer/-innen ihn sehen können." - agree_flag: "Zustimmen und Beitrag behalten" agree_flag_title: "Meldung zustimmen und den Beitrag unbearbeitet lassen." delete: "Löschen" delete_title: "Lösche den Beitrag, auf den diese Meldung verweist." diff --git a/config/locales/client.el.yml b/config/locales/client.el.yml index 51d9a38c54..b20362728f 100644 --- a/config/locales/client.el.yml +++ b/config/locales/client.el.yml @@ -1054,7 +1054,6 @@ el: default_header_text: Επιλογή... no_content: Δεν βρέθηκαν αποτελέσματα filter_placeholder: Αναζήτηση... - create: "Δημιουργία {{content}}" emoji_picker: filter_placeholder: Αναζήτηση για emoji people: Άνθρωποι @@ -1654,8 +1653,6 @@ el: one: Έχεις επιλέξει 1 ανάρτηση. other: Έχεις επιλέξει {{count}} αναρτήσεις. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Παράθεση" edit_reason: "Αιτία:" post_number: "ανάρτηση {{number}}" @@ -2371,11 +2368,9 @@ el: topics: "Επισημασμένα Νήματα" agree: "Συμφωνώ" agree_title: "Επιβεβαίωσε αυτή τη σήμανση ως έγκυρη και σωστή" - agree_flag_hide_post: "Αποδοχή και απόκρυψη ανάρτησης" agree_flag_hide_post_title: "Κρύψε αυτή την ανάρτηση και αυτόματα στείλε στον χρήστη ένα μήνυμα που θα τους προτρέπει να το επεξεργαστούν." agree_flag_restore_post: "Αποδοχή και επαναφορά ανάρτησης" agree_flag_restore_post_title: "Επαναφορά της ανάρτησης ώστε όλοι οι χρήστες να μπορούν να το δουν." - agree_flag: "Αποδοχή και διατήρηση της ανάρτησης" agree_flag_title: "Αποδοχή επισήμανσης και διατήρηση της ανάρτησης αμετάβλητης." delete: "Διαγραφή" delete_title: "Διέγραψε την ανάρτηση στην οποία αναφέρεται αυτή η επισήμανση." diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 0f71614128..383e300706 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1083,6 +1083,16 @@ en: help: "Email not arriving? Be sure to check your spam folder first.

            Not sure which email address you used? Enter an email address and we’ll let you know if it exists here.

            If you no longer have access to the email address on your account, please contact our helpful staff.

            " button_ok: "OK" button_help: "Help" + + email_login: + label: "Login With Email" + complete_username: "If an account matches the username %{username}, you should receive an email with a magic login link shortly." + complete_email: "If an account matches %{email}, you should receive an email with a magic login link shortly." + complete_username_found: "We found an account that matches the username %{username}, you should receive an email with a magic login link shortly." + complete_email_found: "We found an account that matches %{email}, you should receive an email with a magic login link shortly." + complete_username_not_found: "No account matches the username %{username}" + complete_email_not_found: "No account matches %{email}" + login: title: "Log In" username: "User" @@ -1173,7 +1183,8 @@ en: default_header_text: Select... no_content: No matches found filter_placeholder: Search... - create: "Create {{content}}" + create: "Create: '{{content}}'" + max_content_reached: "You can only select {{count}} items." emoji_picker: filter_placeholder: Search for emoji @@ -1278,6 +1289,7 @@ en: olist_title: "Numbered List" ulist_title: "Bulleted List" list_item: "List item" + toggle_direction: "Toggle Direction" help: "Markdown Editing Help" collapse: "minimize the composer panel" abandon: "close composer and discard draft" @@ -1290,6 +1302,25 @@ en: admin_options_title: "Optional staff settings for this topic" + composer_actions: + reply_to_post: + label: Reply to post %{postNumber} by %{postUsername} + desc: Reply to a specific post + reply_as_new_topic: + label: Reply as linked topic + desc: Create a new topic + reply_as_private_message: + label: New message + desc: Create a private message + reply_to_topic: + label: Reply to topic + desc: Reply to the original post without replying to a specific post + toggle_whisper: + label: Toggle whipser + desc: Whispers will only be visible by staff members + create_topic: + label: "New Topic" + notifications: tooltip: regular: @@ -1881,8 +1912,6 @@ en: other: "You have selected {{count}} posts." post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Quote" edit: " {{link}} {{replyAvatar}} {{username}}" edit_reason: "Reason: " @@ -2678,11 +2707,16 @@ en: agree: "Agree" agree_title: "Confirm this flag as valid and correct" - agree_flag_hide_post: "Agree and Hide Post" + agree_flag_hide_post: "Hide Post" agree_flag_hide_post_title: "Hide this post and automatically send the user a message urging them to edit it." agree_flag_restore_post: "Agree and Restore Post" agree_flag_restore_post_title: "Restore the post so that all users can see it." - agree_flag: "Agree and Keep Post" + agree_flag_suspend: "Suspend User" + agree_flag_suspend_title: "Agree with flag and suspend the user." + agree_flag_silence: "Silence User" + agree_flag_silence_title: "Agree with flag and silence the user." + + agree_flag: "Keep Post" agree_flag_title: "Agree with flag and keep the post unchanged." ignore_flag: "Ignore" ignore_flag_title: "Remove this flag; it requires no action at this time." @@ -2716,7 +2750,6 @@ en: system: "System" error: "Something went wrong" reply_message: "Reply" - suspended_for_post: "The user was suspended for this post." no_results: "There are no flagged posts." topic_flagged: "This topic has been flagged." show_full: "show full post" @@ -3379,6 +3412,10 @@ en: suspended_until: "(until %{until})" cant_suspend: "This user cannot be suspended." delete_all_posts: "Delete all posts" + penalty_post_actions: "What would you like to do with the associated post?" + penalty_post_delete: "Delete the post" + penalty_post_edit: "Edit the post" + penalty_post_none: "Do nothing" # keys ending with _MF use message format, see https://meta.discourse.org/t/message-format-support-for-localization/7035 for details delete_all_posts_confirm_MF: "You are about to delete {POSTS, plural, one {1 post} other {# posts}} and {TOPICS, plural, one {1 topic} other {# topics}}. Are you sure?" diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index 7d90c9c29d..c2cd21dee2 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -1059,7 +1059,6 @@ es: default_header_text: Seleccionar... no_content: Ninguna coincidencia encontrada filter_placeholder: Buscar... - create: "Crear {{content}}" emoji_picker: filter_placeholder: Buscar emoji people: People @@ -1702,8 +1701,6 @@ es: one: Has seleccionado 1 post. other: Has seleccionado {{count}} posts. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Citar" edit: "{{link}} {{replyAvatar}} {{username}} " edit_reason: "Motivo:" @@ -2447,11 +2444,9 @@ es: moderation_history: "Histórico de moderación" agree: "De acuerdo" agree_title: "Confirmar esta indicación como válido y correcto." - agree_flag_hide_post: "Coincidir y Ocultar Mensaje" agree_flag_hide_post_title: "Ocultar este post y enviar automáticamente un mensaje al usuario para que lo edite de forma urgente" agree_flag_restore_post: "Coincidir y Restaurar Mensaje" agree_flag_restore_post_title: "Restaurar el post para que todos los usuarios puedan verlo." - agree_flag: "Estar de acuerdo y mantener post" agree_flag_title: "Estar de acuerdo con el reporte y mantener la publicación intacta." ignore_flag: "Ignorar" ignore_flag_title: "Quitar este reporte; no requiere tomar medidas en este momento." diff --git a/config/locales/client.et.yml b/config/locales/client.et.yml index b5af341632..523ee1ccd1 100644 --- a/config/locales/client.et.yml +++ b/config/locales/client.et.yml @@ -1021,7 +1021,6 @@ et: default_header_text: Vali... no_content: Midagi ei leitud filter_placeholder: Otsi... - create: "Loo {{content}}" emoji_picker: filter_placeholder: Otsi emojit people: Inimesed @@ -1599,8 +1598,6 @@ et: one: Oled valinud 1 postituse. other: Oled valinud {{count}} postitust. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Tsitaat" edit_reason: "Põhjus:" post_number: "postitus {{number}}" diff --git a/config/locales/client.fa_IR.yml b/config/locales/client.fa_IR.yml index 7324b3d6e5..c0c07f992d 100644 --- a/config/locales/client.fa_IR.yml +++ b/config/locales/client.fa_IR.yml @@ -45,12 +45,16 @@ fa_IR: other: "< %{count} ثانیه" x_seconds: other: "%{count} ثانیه" + less_than_x_minutes: + other: "< %{count} دقیقه" x_minutes: other: "%{count} دقیقه" about_x_hours: other: "%{count} ساعت" x_days: other: "%{count} روز" + x_months: + other: "%{count} ماه" about_x_years: other: "%{count} سال" over_x_years: @@ -83,6 +87,7 @@ fa_IR: other: "%{count} سال بعد" previous_month: 'ماه قبل' next_month: 'ماه بعد' + placeholder: تاریخ share: topic: 'پیوندی به این موضوع را به اشتراک بگذارید' post: 'ارسال #%{postNumber}' @@ -97,6 +102,7 @@ fa_IR: split_topic: "این موضوع %{when} جدا شد " invited_user: "%{who} در %{when} دعوت شده" invited_group: "%{who} در %{when} دعوت شده" + user_left: "%{who}%{when} خود را از این پیغام حذف کرد." removed_user: "%{who} در %{when} حذف شد" removed_group: "%{who} در %{when} حذف شد" autoclosed: @@ -234,6 +240,7 @@ fa_IR: uploading: "در حال بارگذاری..." uploading_filename: "بارگذاری {{filename}}..." uploaded: "بارگذاری شد!" + pasting: "چسباندن..." enable: "فعال کردن" disable: "ازکاراندازی" undo: "بی‌اثر کردن" @@ -1512,8 +1519,6 @@ fa_IR: description: other: شما {{count}} نوشته انتخاب کرده اید post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "نقل‌قول" edit_reason: "دلیل:" post_number: "نوشته {{number}}" diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index 80ff33b64e..606a942356 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -1059,7 +1059,6 @@ fi: default_header_text: Valitse... no_content: Ei osumia filter_placeholder: Hae... - create: "Luo {{content}}" emoji_picker: filter_placeholder: Etsi emojia people: Ihmiset @@ -1699,8 +1698,6 @@ fi: one: Olet valinnut yhden viestin. other: Olet valinnut {{count}} viestiä. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Lainaa" edit: " {{link}} {{replyAvatar}} {{username}}" edit_reason: "Syy:" @@ -2440,11 +2437,9 @@ fi: moderation_history: "Valvontahistoria" agree: "Ole samaa mieltä" agree_title: "Vahvista, että lippu on annettu oikeasta syystä" - agree_flag_hide_post: "Ole samaa mieltä ja piilota viesti" agree_flag_hide_post_title: "Piilota viesti ja lähetä automaattinen yksityisviesti, joka kehottaa käyttäjää muokkaamaan viestiä." agree_flag_restore_post: "Ole samaa mieltä ja palauta viesti" agree_flag_restore_post_title: "Palauta viesti niin että kaikki käyttäjät näkevät sen." - agree_flag: "Ole samaa mieltä ja säilytä viesti" agree_flag_title: "Ole samaa mieltä lipun kanssa ja pidä viesti ennallaan" ignore_flag: "Sivuuta" ignore_flag_title: "Poista tämä lippu; toimenpiteitä ei tarvita juuri nyt." diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index fb23784f14..7db09ec015 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -1057,7 +1057,6 @@ fr: default_header_text: Sélectionner… no_content: Aucune correspondance trouvée filter_placeholder: Rechercher... - create: "Créer {{content}}" emoji_picker: filter_placeholder: Chercher un emoji people: Personnes @@ -1695,8 +1694,6 @@ fr: one: vous avez sélectionné 1 message. other: Vous avez sélectionné {{count}} messages. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Citer" edit: " {{link}} {{replyAvatar}} {{username}}" edit_reason: "Raison :" @@ -2429,11 +2426,9 @@ fr: moderation_history: "Historique de la modération" agree: "Accepter" agree_title: "Confirmer que le signalement est valide et correcte" - agree_flag_hide_post: "Accepter et cacher le message" agree_flag_hide_post_title: "Cacher le message et envoyer automatiquement un message à l'utilisateur l'incitant à le modifier." agree_flag_restore_post: "Accepter et rétablir le message" agree_flag_restore_post_title: "Rétablir le message afin que tous les utilisateurs puissent le voir." - agree_flag: "Accepter et conserver le message" agree_flag_title: "Accepter le signalement et conserver le message tel quel." delete: "Supprimer" delete_title: "Supprimer le message signalé." diff --git a/config/locales/client.gl.yml b/config/locales/client.gl.yml index b982402aba..a70d651147 100644 --- a/config/locales/client.gl.yml +++ b/config/locales/client.gl.yml @@ -1188,8 +1188,6 @@ gl: one: Seleccionaches unha publicación. other: Seleccionaches {{count}} publicacións. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" edit_reason: "Razón:" post_number: "publicación {{number}}" last_edited_on: "última edición da publicación" diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index 3e7e7ddf0c..c3a9655f08 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -1561,8 +1561,6 @@ he: one: בחרתם פוסט אחד. other: בחרתם {{count}} פוסטים. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "ציטוט" edit_reason: "סיבה: " post_number: "פוסט {{number}}" diff --git a/config/locales/client.id.yml b/config/locales/client.id.yml index e36c3001fd..6cd66cd73a 100644 --- a/config/locales/client.id.yml +++ b/config/locales/client.id.yml @@ -744,7 +744,6 @@ id: password_label: "Atur Kata Sandi" select_kit: filter_placeholder: Cari... - create: "Buat {{konten}}" emoji_picker: filter_placeholder: Cari emoji people: Orang diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index a646603399..e05a3d5f29 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -1055,7 +1055,6 @@ it: default_header_text: Selezione... no_content: Nessun risultato trovato filter_placeholder: Ricerca... - create: "Crea {{content}}" emoji_picker: filter_placeholder: Ricerca per emoji people: Persone @@ -1691,8 +1690,6 @@ it: one: Hai selezionato 1 messaggio. other: Hai selezionato {{count}} messaggi. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Cita" edit_reason: "Motivo:" post_number: "messaggio {{number}}" @@ -2404,11 +2401,9 @@ it: topics: "Argomenti Segnalati" agree: "Acconsento" agree_title: "Conferma che questa segnalazione è valida e corretta" - agree_flag_hide_post: "Accetta e Nascondi il Messaggio" agree_flag_hide_post_title: "Nascondi questo messaggio e invia automaticamente all'utente un messaggio privato che lo invita a modificarlo." agree_flag_restore_post: "Accetta e Ripristina il Messaggio" agree_flag_restore_post_title: "Ripristina il messaggio in modo che tutti gli utenti possano vederlo." - agree_flag: "Accetto e Mantieni il Messaggio" agree_flag_title: "Accetta la segnalazione e mantieni invariato il messaggio." delete: "Cancella" delete_title: "Cancella il messaggio a cui si riferisce la segnalazione." diff --git a/config/locales/client.ja.yml b/config/locales/client.ja.yml index 2db0766819..3733350c17 100644 --- a/config/locales/client.ja.yml +++ b/config/locales/client.ja.yml @@ -1000,7 +1000,6 @@ ja: default_header_text: 選択... no_content: 一致する項目が見つかりませんでした filter_placeholder: 検索... - create: "{{content}} を作成" emoji_picker: filter_placeholder: 絵文字を探す people: ピープル @@ -1567,8 +1566,6 @@ ja: description: other: {{count}}個の投稿を選択中。 post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "引用" edit_reason: "理由: " post_number: "投稿{{number}}" diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml index 4a72621106..557ba11bdc 100644 --- a/config/locales/client.ko.yml +++ b/config/locales/client.ko.yml @@ -122,7 +122,7 @@ ko: enabled: '%{when} 목록에 게시' disabled: '%{when} 목록에서 감춤' banner: - enabled: 'made this a banner %{when}. It will appear at the top of every page until it is dismissed by the user.' + enabled: '%{when} 이 내용을 배너로 만드세요 . 사용자가 제거하지 않을 때 까지 배너는 모든 페이지 상단에 노출됩니다.' disabled: '이 배너를 제거했습니다 %{when}. 더 이상 모든 페이지의 상단에 표시되지 않습니다.' topic_admin_menu: "토픽 관리" wizard_required: "새로운 Discourse에 오신것을 환영합니다! 설치 마법사 로 시작해봅시다✨" @@ -1578,8 +1578,6 @@ ko: description: other: {{count}}개의 개시글을 선택하셨어요. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "인용하기" edit_reason: "Reason: " post_number: "{{number}}번째 글" @@ -2197,8 +2195,8 @@ ko: critical_available: "중요 업데이트를 사용할 수 있습니다." updates_available: "업데이트를 사용할 수 있습니다." please_upgrade: "업그레이드하세요." - no_check_performed: "A check for updates has not been performed. Ensure sidekiq is running." - stale_data: "A check for updates has not been performed lately. Ensure sidekiq is running." + no_check_performed: "업데이트 확인이 수행되지 않았습니다. sidekiq가 실행 중인지 확인하십시오." + stale_data: "최근 업데이트 확인이 수행되지 않았습니다. sidekiq가 실행 중인지 확인하십시오." version_check_pending: "최근에 업데이트 되었군요! 환상적입니다!!" installed_version: "설치됨" latest_version: "최근" @@ -2314,7 +2312,7 @@ ko: automatic_membership_email_domains: "이 목록의 있는 항목과 사용자들이 등록한 이메일 도메인이 일치할때 이 그룹에 포함" automatic_membership_retroactive: "이미 등록된 사용자에게 같은 이메일 도메인 규칙 적용하기" default_title: "이 그룹의 모든 사용자를 위한 기본 제목" - primary_group: "Automatically set as primary group" + primary_group: "기본 그룹으로 자동적으로 설정" group_owners: 소유자 add_owners: 소유자 추가하기 incoming_email: "사용자 설정 수신 이메일 주소" diff --git a/config/locales/client.lv.yml b/config/locales/client.lv.yml index ed00ec83da..df3d7e9591 100644 --- a/config/locales/client.lv.yml +++ b/config/locales/client.lv.yml @@ -1623,8 +1623,6 @@ lv: one: Jūs esat izvēlējies 1 ierakstu. other: Jūs esat izvēlējies {{count}} ierakstus. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Citāts" edit_reason: "Iemesls:" post_number: "ieraksts {{number}}" diff --git a/config/locales/client.nb_NO.yml b/config/locales/client.nb_NO.yml index 6c5b33a3f2..cba0adfda4 100644 --- a/config/locales/client.nb_NO.yml +++ b/config/locales/client.nb_NO.yml @@ -764,6 +764,7 @@ nb_NO: title: "invitasjoner" user: "Invitert bruker" sent: "Sendt" + none: "Ingen invitasjoner å vise." truncated: one: "Viser den første invitasjonen." other: "Viser de {{count}} første invitisajonene." @@ -1059,7 +1060,6 @@ nb_NO: default_header_text: Velg… no_content: Ingen treff funnet filter_placeholder: Søk… - create: "Opprett {{content}}" emoji_picker: filter_placeholder: Søk etter emoji people: Folk @@ -1702,8 +1702,6 @@ nb_NO: one: Du har valgt 1 innlegg. other: Du har valgt {{count}} innlegg. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Sitat" edit: " {{link}} {{replyAvatar}} {{username}}" edit_reason: "Begrunnelse:" @@ -1929,7 +1927,7 @@ nb_NO: edit: 'rediger' edit_long: "Rediger" view: 'Se tråder i kategori' - general: 'Generellt' + general: 'Generelt' settings: 'Innstillinger' topic_template: "Mal for tråd" tags: "Stikkord" @@ -2449,11 +2447,9 @@ nb_NO: moderation_history: "Moderatorhistorikk" agree: "Godta" agree_title: "Bekreft at denne rapporteringen er gyldig og korrekt" - agree_flag_hide_post: "Samtykk og skjul innlegg" agree_flag_hide_post_title: "Gjem dette innlegget og send en melding til brukeren automatisk med en forespørsel om gjøre endringer." agree_flag_restore_post: "Samtykk og gjenopprett innlegg" agree_flag_restore_post_title: "Gjenopprett innlegget slik at alle brukere kan se det." - agree_flag: "Samtykk og behold innlegg" agree_flag_title: "Samtykk med flagg og behold innlegget uendret." ignore_flag: "Ignorer" ignore_flag_title: "Fjern denne rapporteringen; den krever ingen handling på dette tidspunktet." @@ -2485,7 +2481,6 @@ nb_NO: system: "System" error: "Noe gikk galt" reply_message: "Svar" - suspended_for_post: "Brukeren ble utestengt for dette innlegget." no_results: "Det finnes ingen rapporterte innlegg." topic_flagged: "Denne tråden er blitt rapportert." show_full: "vis hele innlegget" @@ -2657,6 +2652,7 @@ nb_NO: label: "Last opp" title: "Last opp en sikkerhetskopi til denne instansen" uploading: "Laster opp…" + success: "Opplastingen av '{{filename}}' var vellykket. Filen blir nå bearbeidet og det vil ta opp til et minutt før den dukker opp i listen." error: "Det oppstod en feil ved opplastingen av '{{filename}}': {{message}}" operations: is_running: "En prosess pågår…" diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index c50421b1a1..05d96910ef 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -1623,8 +1623,6 @@ nl: one: U hebt 1 bericht geselecteerd. other: U hebt {{count}} berichten geselecteerd. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Citeren" edit_reason: "Reden: " post_number: "bericht {{number}}" diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml index 44980ea0d0..c552e29164 100644 --- a/config/locales/client.pl_PL.yml +++ b/config/locales/client.pl_PL.yml @@ -1123,7 +1123,6 @@ pl_PL: default_header_text: Wybierz... no_content: Nie znaleziono dopasowań filter_placeholder: Wyszukiwanie... - create: "Utwórz {{content}}" emoji_picker: filter_placeholder: Szukaj emoji people: Ludzie @@ -1784,8 +1783,6 @@ pl_PL: many: Wybrano {{count}} wpisów. other: Wybrano {{count}} wpisów. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Cytuj" edit_reason: "Powód" post_number: "wpis {{number}}" @@ -2583,9 +2580,7 @@ pl_PL: moderation_history: "Historia moderacji" agree: "Potwierdź" agree_title: "Potwierdź to zgłoszenie jako uzasadnione i poprawne" - agree_flag_hide_post: "Potwierdź i ukryj post" agree_flag_restore_post: "Potwierdź i przywróć post" - agree_flag: "Potwierdź i zachowaj post" delete: "Usuń" delete_title: "Usuń wpis do którego odnosi się flaga." delete_post_defer_flag: "Usuń post i ignoruj flagę" diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index fd667b5ac2..641d809ba4 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -1514,8 +1514,6 @@ pt: one: Selecionou 1 publicação. other: Selecionou {{count}} publicações. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Citar" edit_reason: "Motivo:" post_number: "publicação {{number}}" diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml index f94403a25e..95838a2e05 100644 --- a/config/locales/client.pt_BR.yml +++ b/config/locales/client.pt_BR.yml @@ -1581,8 +1581,6 @@ pt_BR: one: 1 resposta selecionada. other: {{count}} respostas selecionadas. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Citação" edit_reason: "Motivo:" post_number: "resposta {{number}}" diff --git a/config/locales/client.ro.yml b/config/locales/client.ro.yml index 11a6e9f587..eead0fe643 100644 --- a/config/locales/client.ro.yml +++ b/config/locales/client.ro.yml @@ -51,6 +51,10 @@ ro: one: "1s" few: "%{count} s" other: "%{count} s" + less_than_x_minutes: + one: "< 1m" + few: "< 1m" + other: "< %{count}m" x_minutes: one: "1m" few: "%{count} m" @@ -63,6 +67,10 @@ ro: one: "1z" few: "%{count} z" other: "%{count} z" + x_months: + one: "1mon" + few: "1mon" + other: "%{count}mon" about_x_years: one: "1a" few: "%{count} a" @@ -119,6 +127,7 @@ ro: other: "După %{count} de ani" previous_month: 'Luna anterioară' next_month: 'Luna următoare' + placeholder: dată share: topic: 'Distribuie un link cu acest subiect' post: 'Distribuie postarea #%{postNumber}' @@ -133,6 +142,7 @@ ro: split_topic: "desparte această discuție %{when}" invited_user: "a invitat pe %{who} %{when}" invited_group: "a invitat pe %{who} %{when}" + user_left: "%{who} s-a îndepărtat de la acest mesaj %{when}" removed_user: "a scos pe %{who} %{when}" removed_group: "scos pe %{who} %{when}" autoclosed: @@ -153,7 +163,11 @@ ro: visible: enabled: 'afișat %{when}' disabled: 'ascuns %{when}' + banner: + enabled: 'a creat acest banner %{when}. Va apărea la începutul fiecărei pagini până când va fi ascuns de către utilizator.' + disabled: 'a îndepărtat acest banner %{when}. Nu va mai apărea la începutul fiecărei pagini.' topic_admin_menu: "Opțiuni administrare subiect" + wizard_required: "Bine ai venit la noul tău Discourse! Să începem cu expertul de configurare ✨" emails_are_disabled: "Trimiterea de emailuri a fost dezactivată global de către un administrator. Nu vor fi trimise notificări email de nici un fel." bootstrap_mode_enabled: "Pentru a ușura lansarea site-ului tău ești în modul bootstrap. Toți noii utilizatori vor primi nivelul de încredere 1 și vor avea activată primirea zilnică a unui email-rezumat. Această setare va fi dezactivată automat de îndată ce numărul total de utilizatori depășește %{min_users}." bootstrap_mode_disabled: "Modul bootstrap va fi dezactivat în următoarele 24 de ore." @@ -169,8 +183,10 @@ ro: cn_north_1: "China (Beijing)" eu_central_1: "EU (Frankfurt)" eu_west_1: "EU (Irlanda)" + eu_west_2: "UE (Londra)" sa_east_1: "South America (Sao Paulo)" us_east_1: "US East (N. Virginia)" + us_east_2: "America de Est (Ohio)" us_gov_west_1: "AWS GovCloud (US)" us_west_1: "US West (N. California)" us_west_2: "US West (Oregon)" @@ -274,6 +290,7 @@ ro: uploading: "Se încarcă..." uploading_filename: "Se încarcă {{filename}}..." uploaded: "Încărcat!" + pasting: "Lipire..." enable: "Activează" disable: "Dezactivează" undo: "Anulează acțiunea" @@ -372,6 +389,8 @@ ro: add_members: "Adaugă membri" delete_member_confirm: "Șterge utilizatorul '%{username}' din grupul '%{group}'?" name_placeholder: "Numele grupului, fără spații, la fel ca regula pentru nume utilizator" + public_admission: "Permite utilizatorilor să adere la grup fără restricții (Presupune ca grupul să fie vizibil)" + public_exit: "Permite utilizatorilor să părăsească grupul în mod liber." empty: posts: "Nu există postări ale membrilor acestui grup." members: "Nu există nici un membru în acest grup." @@ -383,9 +402,16 @@ ro: join: "Alătură-te grupului" leave: "Părăsește grupul" request: "Cere să te alături grupului" + message: "Mesaj" automatic_group: Grup automat closed_group: Grup închis is_group_user: "Ești membru al acestui grup" + allow_membership_requests: "Permite utilizatorilor să trimită cereri de membru către proprietarul grupului." + membership_request_template: "Șablonul personalizat pentru a fi afișat utilizatorilor atunci când trimiteți o solicitare de membru" + membership_request: + submit: "Trimite cerere" + title: "Cerere de aderare @%{group_name}" + reason: "Aduceți la cunoștință proprietarilor grupului motivul pentru care dorești să aderi la acest grup" membership: "Apartenență" name: "Nume" user_count: "Număr de membri" @@ -395,13 +421,26 @@ ro: index: title: "Grupuri" empty: "Nu există nici un grup vizibil." + title: + one: "Grup" + few: "Grupuri" + other: "Grupuri" activity: "Activitate" members: "Membri" topics: "Subiecte" posts: "Postări" mentions: "Mențiuni" messages: "Mesaje" + notification_level: "Nivelul de notificare prestabilit pentru mesajele de grup" + visibility_levels: + title: "Cine poate vedea acest grup?" + public: "Toată lumea" + members: "Proprietarii grupului, membrii și adminii" + staff: "Proprietarii grupului și echipa" + owners: "Proprietarii și adminii din grup" alias_levels: + mentionable: "Cine poate @menționa acest grup?" + messageable: "Cine poate trimite mesaje acestui grup?" nobody: "Nimeni" only_admins: "Doar adminii" mods_and_admins: "Doar moderatorii și adminii" @@ -450,6 +489,7 @@ ro: '14': "În așteptare" categories: all: "Toate categoriile" + all_subcategories: "toate în %{categoryName}" no_subcategory: "Niciuna" category: "Categorie" category_list: "Afișează lista de categorii" @@ -490,6 +530,7 @@ ro: topics_entered: "Subiecte la care particip" post_count: "# postări" confirm_delete_other_accounts: "Ești sigur că vrei să ștergi aceste conturi?" + powered_by: "creat de ipinfo.io" user_fields: none: "(alege o opțiune)" user: @@ -498,6 +539,7 @@ ro: mute: "Silențios" edit: "Editează preferințe" download_archive: + button_text: "Descarcă tot" confirm: "Ești sigur că vrei să îți descarci postările?" success: "Descărcarea a început, vei fi notificat printr-un mesaj atunci când procesul se va termina." rate_limit_error: "Postările pot fi descărcate doar o singură dată pe zi, te rugăm să încerci din nou mâine." @@ -522,11 +564,14 @@ ro: disable: "Dezactivează notificări" enable: "Activează notificările" each_browser_note: "Observație: Setările trebuie modificate pe fiecare browser utilizat." + dismiss: 'Înlătură' dismiss_notifications: "Elimină tot" dismiss_notifications_tooltip: "Marchează toate notificările ca citite" first_notification: "Prima notificare pe care ai primit-o! Selectează-o ca să continui. " disable_jump_reply: "Nu sări la postarea mea după ce răspund" dynamic_favicon: "Arată subiectele noi/actualizate în pictograma browserului." + theme_default_on_all_devices: "Fă această temă implicită pentru toate dispozitivele mele" + allow_private_messages: "Permite altor utilizatori să îmi trimită mesaje private" external_links_in_new_tab: "Deschide toate adresele externe într-un tab nou" enable_quoting: "Activează citarea răspunsurilor pentru textul selectat" change: "Schimbă" @@ -534,7 +579,9 @@ ro: admin: "{{user}} este admin" moderator_tooltip: "Acest utilizator este moderator" admin_tooltip: "Acest utilizator este admin" + silenced_tooltip: "Acest utilizator este silențios" suspended_notice: "Acest utilizator este suspendat până la {{date}}." + suspended_permanently: "Acest utilizator este suspendat." suspended_reason: "Motiv: " github_profile: "Github" email_activity_summary: "Sumarul activității" @@ -565,6 +612,7 @@ ro: watched_first_post_tags_instructions: "Vei fi notificat cu privire la prima postare din fiecare nou subiect cu aceste etichete." muted_categories: "Setat pe silențios" muted_categories_instructions: "Nu vei fi notificat despre nimic legat de noile subiecte din aceste categorii și subiectele respective nu vor apărea în lista cu cele mai recente subiecte." + no_category_access: "În calitate de moderator ai acces limitat la categorii, salvarea este dezactivată." delete_account: "Șterge-mi contul" delete_account_confirm: "Ești sigur că vrei să ștergi contul? Această acțiune este ireversibilă!" deleted_yourself: "Contul tău a fost șters cu succes." @@ -576,11 +624,15 @@ ro: muted_users_instructions: "Oprește toate notificările de la aceşti utilizatori." muted_topics_link: "Arată subiectele dezactivate" watched_topics_link: "Arată subiectele urmărite activ" + tracked_topics_link: "Afișează subiectele urmărite" automatically_unpin_topics: "Detașare automată a subiectelor când ajung la sfârșitul paginii." apps: "Aplicații" revoke_access: "Revocă accesul" undo_revoke_access: "Anulează revocarea accesului" api_approved: "Aprobate:" + theme: "Temă" + home: "Pagina inițială implicită" + staged: "Pus în scenă" staff_counters: flags_given: "Marcaje ajutătoare" flagged_posts: "Postări marcate" @@ -598,6 +650,15 @@ ro: move_to_archive: "Arhivează" failed_to_move: "Mutarea mesajelor selectate a eșuat (poate că v-a căzut rețeaua)" select_all: "Selectează tot" + preferences_nav: + account: "Cont" + profile: "Profil" + emails: "Adrese de email" + notifications: "Notificări" + categories: "Categorii" + tags: "Etichete" + interface: "Interfață" + apps: "Aplicații" change_password: success: "(email trimis)" in_progress: "(se trimite email)" @@ -619,10 +680,12 @@ ro: taken: "Această adresă există deja în baza de date." error: "A apărut o eroare la schimbarea de email. Poate această adresă este deja utilizată?" success: "Am trimis un email către adresa respectivă. Urmează instrucțiunile de confirmare." + success_staff: "Ți-am trimis un email la adresa curentă. Urmează instrucțiunile de confirmare." change_avatar: title: "Schimbă poza de profil" gravatar: "Gravatar, bazat pe" gravatar_title: "Schimbă-ți avatarul de pe site-ul Gravatar." + gravatar_failed: "Gravatarul nu a putut fi preluat. Există unul asociat cu adresa de email respectivă?" refresh_gravatar_title: "Reactualizează Gravatarul" letter_based: "Poză de profil atribuită de sistem." uploaded_avatar: "Poză preferată" @@ -639,6 +702,7 @@ ro: instructions: "Fundalul va fi centrat şi va avea o dimensiune standard de 590px." email: title: "Email" + instructions: "nu afișa niciodată către public" ok: "Îți vom trimite un email pentru confirmare." invalid: "Introduceți o adresă de email validă." authenticated: "Emailul a fost autentificat de către {{provider}}." @@ -655,6 +719,7 @@ ro: ok: "Numele tău este OK." username: title: "Nume utilizator" + instructions: "unic, fără spații, scurt" short_instructions: "Ceilalți te pot menționa ca @{{username}}." available: "Numele de utilizator este disponibil." not_available: "Nu este disponibil. Încerci {{suggestion}}?" @@ -731,6 +796,7 @@ ro: title: "Invitații" user: "Utilizator invitat" sent: "Trimis(e)" + none: "Nu există invitații." truncated: one: "Se afișează prima invitație." few: "Se afișează primele {{count}} invitații." @@ -747,8 +813,12 @@ ro: expired: "Această invitație a expirat." rescind: "Elimină" rescinded: "Invitație eliminată" + rescind_all: "Elimină toate invitațiile" + rescinded_all: "Toate invitațiile au fost eliminate!" + rescind_all_confirm: "Sigur elimini toate invitațiile?" reinvite: "Retrimite invitaţia" reinvite_all: "Retrimite toate invitațiile" + reinvite_all_confirm: "Sigur retrimiți toate invitațiile?" reinvited: "Invitaţia a fost retrimisă" reinvited_all: "Toate invitațiile retrimise!" time_read: "Timp de citire" @@ -759,8 +829,10 @@ ro: link_generated: "Link de invitare generat cu succes!" valid_for: "Link-ul de invitare este valid doar pentru următoarele adrese de email: %{email}" bulk_invite: + none: "Încă nu ai invitat pe nimeni. Trimite invitații indivituale sau invită mai multe persoane odată uploadând un fișier CSV." text: "Invitație multiplă din fișier" success: "Fișier încărcat cu succes, vei fi înștiințat printr-un mesaj când procesarea este completă." + error: "Scuze, fișierul trebuie să fie în format CSV." password: title: "Parolă" too_short: "Parola este prea scurtă." @@ -781,10 +853,22 @@ ro: one: "o postare creată" few: "postări create" other: "de postări create" + likes_given: + one: "oferit" + few: "oferite" + other: "oferite" + likes_received: + one: "primit" + few: "primite" + other: "primite" days_visited: one: "vizită" few: "vizite" other: "de vizite" + topics_entered: + one: "subiect vizualizat" + few: "subiecte vizualizate" + other: "subiecte vizualizate" posts_read: one: "postare citită" few: "postări citite" @@ -876,6 +960,11 @@ ro: first_post: Prima postare mute: Blochează unmute: Deblochează + last_post: Postat + time_read: Citit + time_read_recently: '%{time_read} recent' + time_read_tooltip: '%{time_read} total citit' + time_read_recently_tooltip: '%{time_read} total citit (%{recent_time_read} în ultimele 60 de zile)' last_reply_lowercase: Ultimul răspuns replies_lowercase: one: răspuns @@ -902,6 +991,7 @@ ro: private_message_info: title: "Mesaj" invite: "Invită alte persoane..." + leave_message: "Sigur părăsești acest mesaj?" remove_allowed_user: "Chiar dorești să îl elimini pe {{name}} din acest mesaj privat?" remove_allowed_group: "Ești sigur că vrei să îl scoți pe {{name}} din acest mesaj?" email: 'Email' @@ -926,6 +1016,9 @@ ro: complete_email_found: "Am găsit un cont care se potrivește cu adresa %{email}, vei primi un email cu instrucțiunile de resetare a parolei în cel mai scurt timp." complete_username_not_found: "Nici un cont nu se potriveşte cu utilizatorul %{username}" complete_email_not_found: "Nici un cont nu se potrivește cu adresa %{email}" + help: "Email-ul nu a sosit? Asigură-te că verifici mai întâi în folderul Spam.

            Nu ești sigur ce adresă de email folosești? Scrie adresa de email și îți vom spune dacă aceasta există.

            Dacă nu mai ai acces în contul tău la adresa de email, te rog contactează echipa de ajutor.

            " + button_ok: "Ok" + button_help: "Ajutor" login: title: "Autentificare cu" username: "Utilizator" @@ -939,18 +1032,22 @@ ro: logging_in: "În curs de autentificare..." or: "sau" authenticating: "Se autentifică..." + awaiting_activation: "Contul tău așteaptă activarea, folosește linkul pentru recuperarea parolei pentru a primi un alt link de activare a adresei de email." awaiting_approval: "Contul tău nu a fost aprobat încă de un admin . Vei primi un email când se aprobă." requires_invite: "Ne pare rău, accesul la forum se face pe bază de invitație." not_activated: "Nu te poți loga încă. Am trimis anterior un email de activare pentru {{sentTo}}. Urmează instrucțiunile din email pentru a-ți activa contul." not_allowed_from_ip_address: "Nu te poți conecta cu această adresă IP." admin_not_allowed_from_ip_address: "Nu te poți conecta ca administrator cu această adresă IP." resend_activation_email: "Click aici pentru a retrimite emailul de activare." + resend_title: "Retrimite emailul de activare" change_email: "Schimbă adresa de email" + provide_new_email: "Furnizează o nouă adresă de email și îți vom retrimite linkul de confirmare." submit_new_email: "Actualizează adresa de email" sent_activation_email_again: "Am trimis un alt email de activare pentru tine la {{currentEmail}}. Poate dura câteva minute până ajunge; vezi și în secțiunea de spam a mailului." to_continue: "Te rog să te autentifici." preferences: "Trebuie să fii autentificat pentru a schimba preferințele." forgot: "Nu îmi amintesc detaliile contului meu." + not_approved: "Contul tău încă nu a fost aprobat. Vei primi notificarea prin email când ești gata de logare." google: title: "Google" message: "Autentificare cu Google (Verifică browserul să nu blocheze ferestrele pop-up)" @@ -972,12 +1069,27 @@ ro: github: title: "GitHub" message: "Autentificare cu GitHub (Verifică browserul să nu blocheze ferestrele pop-up)" + invites: + accept_title: "Initație" + welcome_to: "Bine ai venit la %{site_name}!" + invited_by: "Ai fost invitat de:" + social_login_available: "De asemenea te poți loga cu un alt cont care folosește această adresă de email." + your_email: "Adresa de email a contului este %{email}." + accept_invite: "Acceptă invitație" + success: "Contul tău a fost creat și acum ești logat." + name_label: "Nume" + password_label: "Setează parolă" + optional_description: "(opțional)" + password_reset: + continue: "Continuă la %{site_name}" emoji_set: apple_international: "Apple/International" google: "Google" twitter: "Twitter" emoji_one: "Emoji One" win10: "Win10" + google_classic: "Google clasic" + facebook_messenger: "Facebook Messenger" category_page_style: categories_only: "Numai categorii" categories_with_featured_topics: "Categorii cu subiecte promovate" @@ -986,12 +1098,28 @@ ro: shift: 'Shift' ctrl: 'Ctrl' alt: 'Alt' + select_kit: + default_header_text: Selectează... + no_content: Nu s-au găsit potriviri + filter_placeholder: Caută... + emoji_picker: + filter_placeholder: Caută emoji + people: Oameni + nature: Natură + food: Mâncare + activity: Activitate + travel: Călătorie + objects: Obiecte + celebration: Sărbători + custom: Emojii personalizate + recent: Folosite recent composer: emoji: "Emoji :)" more_emoji: "mai multe..." options: "Opțiuni" whisper: "discret" unlist: "nelistat" + blockquote_text: "Bloc citat" add_warning: "Aceasta este o avertizare oficială." toggle_whisper: "Comută modul discret" toggle_unlisted: "Comută modul nelistat" @@ -1001,6 +1129,7 @@ ro: saved_local_draft_tip: "salvat local" similar_topics: "Subiectul tău seamănă cu..." drafts_offline: "schiță offline" + group_mentioned_limit: "1Atenție!1 Ai menționat 2{{group}}2, dar acest grup are mai mulți membri decât limita de {{max}} utilizatori configurată de către administrator. Nimeni nu va fi notificat." group_mentioned: one: "Prin menționarea {{group}}, urmează să notifici o persoană – ești sigur?" few: "Prin menționarea {{group}}, urmează să notifici {{count}} persoane – ești sigur?" @@ -1023,6 +1152,7 @@ ro: cancel: "Anulează" create_topic: "Creează un subiect" create_pm: "Mesaj" + create_whisper: "Șoaptă" title: "Sau apasă Ctrl+Enter" users_placeholder: "Adaugă un utilizator" title_placeholder: "Despre ce e vorba în acest subiect - pe scurt?" @@ -1060,6 +1190,7 @@ ro: olist_title: "Listă numerotată" ulist_title: "Listă cu marcatori" list_item: "Element listă" + toggle_direction: "Schimbă direcția" help: "Ajutor pentru formatarea cu Markdown" modal_ok: "Ok" modal_cancel: "Anulare" @@ -1068,12 +1199,50 @@ ro: title: "Ai uitat să adaugi destinatari?" body: "În acest moment mesajul acesta nu este trimis decât către tine însuți!" admin_options_title: "Setări opționale pentru moderatori cu privire la acest subiect" + composer_actions: + reply_as_new_topic: + label: Răspunde cu link către subiect + desc: Creează un nou subiect + reply_as_private_message: + label: Mesaj nou + desc: Creează mesaj privat + reply_to_topic: + label: Răspunde la subiect + desc: Răspunde la postarea originală fără a răspunde la vreo postare specifică + toggle_whisper: + label: Comută modul discret notifications: + tooltip: + message: + one: "1 mesaj necitit" + few: "{{count}} mesaje necitite" + other: "{{count}} mesaje necitite" title: "notificări la menționările @numelui tău, răspunsuri la postările sau subiectele tale, mesaje, etc." none: "Nu pot încărca notificările în acest moment." empty: "Nu au fost găsite notificări." more: "vezi notificările mai vechi" total_flagged: "toate postările marcate" + mentioned: "{{username}} {{description}}" + group_mentioned: "{{username}} {{description}}" + quoted: "{{username}} {{description}}" + replied: "{{username}} {{description}}" + posted: "{{username}} {{description}}" + edited: "{{username}} {{description}}" + liked: "{{username}} {{description}}" + liked_2: "{{username}}, {{username2}} {{description}}" + liked_many: + one: "{{username}}, {{username2}} și încă o persoană {{description}}" + few: "{{username}}, {{username2}} și încă o persoană {{description}}" + other: "{{username}}, {{username2}} și alți {{count}} {{description}}" + private_message: "{{username}} {{description}}" + invited_to_private_message: "

            {{username}} {{description}}" + invited_to_topic: "{{username}} {{description}}" + invitee_accepted: "{{username}} ți-a acceptat invitația" + moved_post: "{{username}} a mutat {{description}}" + linked: "{{username}} {{description}}" + granted_badge: "A câștigat '{{description}}'" + topic_reminder: "{{username}} {{description}}" + watching_first_post: "Subiect nou {{description}}" alt: mentioned: "Menționat de" quoted: "Citat de" @@ -1081,6 +1250,8 @@ ro: posted: "Postat de" edited: "Editează postarea prin" liked: "V-a apreciat postarea" + private_message: "Mesaj personal de la" + invited_to_private_message: "Invitat la un mesaj privat de către" invited_to_topic: "Invitat la un subiect de către" invitee_accepted: "Invitație acceptată de către" moved_post: "Postarea ta a fost mutată de către" @@ -1108,10 +1279,12 @@ ro: uploading: "Se încarcă" select_file: "Alege fișier" image_link: "Link-ul de la imagine va duce la" + default_image_alt_text: imagine search: sort_by: "Sortează după" relevance: "Relevanță" latest_post: "Cele mai recente postări" + latest_topic: "Ultimul subiect" most_viewed: "Cele mai văzute" most_liked: "Cele mai apreciate" select_all: "Selectează tot" @@ -1122,6 +1295,11 @@ ro: no_more_results: "Nu s-au mai găsit alte rezultate." searching: "Se caută..." post_format: "#{{post_number}} de {{username}}" + results_page: "Caută rezultate pentru '{{term}}'" + start_new_topic: "Creează un nou subiect?" + or_search_google: "Sau încearcă să cauți cu Google:" + search_google_button: "Google" + search_google_title: "Caută în această pagină" context: user: "Caută postări după @{{username}}" category: "Caută în categoria #{{category}} " @@ -1131,19 +1309,27 @@ ro: title: Căutare avansată posted_by: label: Postat de + in_category: + label: Categorie in_group: label: În grupul with_badge: label: Cu ecusonul + with_tags: + label: Etichetat filters: likes: pe care le-am apreciat posted: în care am postat watching: pe care le urmăresc activ tracking: le urmăresc + private: În mesajele mele first: sunt chiar pe primul loc în listă pinned: sunt fixate unpinned: nu sunt fixate + seen: Citesc + unseen: Nu am citit wiki: sunt wiki + images: include poza (pozele) statuses: label: Unde subiectele open: sunt deschise @@ -1179,14 +1365,19 @@ ro: dismiss_new: "Anulează cele noi" toggle: "activează selecția multiplă a subiectelor" actions: "Acțiuni multiple" + change_category: "Alege categoria" close_topics: "Închide subiectele" archive_topics: "Arhivează subiectele" + notification_level: "Notificări" choose_new_category: "Alege o nouă categorie pentru acest subiect" selected: one: "Ai selectat un subiect." few: "Ai selectat {{count}} subiecte." other: "Ai selectat {{count}} de subiecte." + change_tags: "Înlocuiește etichete" + append_tags: "Adaugă etichete" choose_new_tags: "Alege etichete noi pentru aceste subiecte:" + choose_append_tags: "Alege etichete noi pentru aceste subiecte:" changed_tags: "Etichetele pentru aceste subiecte au fost schimbate." none: unread: "Nu sunt subiecte necitite." @@ -1281,7 +1472,48 @@ ro: jump_reply_up: sări la un răspuns mai vechi jump_reply_down: sări la un răspuns mai nou deleted: "Subiectul a fost șters" + topic_status_update: + title: "Temporizator subiect" + save: "Setează temporizator" + num_of_hours: "Număr de ore:" + remove: "Elimină temporizator" + publish_to: "Publică la:" + when: "Când:" + public_timer_types: Temporizatoare subiect + auto_update_input: + none: "Alege un interval de timp" + later_today: "Mai târziu azi" + tomorrow: "Mâine" + later_this_week: "Mai târziu săptămâna asta" + this_weekend: "Acest weekend" + next_week: "Săptămâna viitoare" + two_weeks: "Două săptămâni" + next_month: "Luna viitoare" + three_months: "Trei luni" + six_months: "6 luni" + one_year: "Un an" + forever: "Pentru totdeanua" + pick_date_and_time: "Alege data și ora" + set_based_on_last_post: "Închide pe baza ultimei postări" + publish_to_category: + title: "Programează publicarea" + temp_open: + title: "Deschide temporar" + auto_reopen: + title: "Deschide automat subiect" + temp_close: + title: "Închide temporar" + auto_close: + title: "Închide automat subiectul" + label: "Ore pentru închidere automată a subiectului:" + error: "Introduceţi o valoare validă." + based_on_last_post: "Nu închide până când ultima postare din subiect este măcar atât de veche." + auto_delete: + title: "Șterge automat subiect" + reminder: + title: "Amintește-mi" status_update_notice: + auto_open: "Acest subiect va fi deschis automat la %{timeLeft}." auto_close: "Acest subiect va fi automat închis în %{timeLeft}." auto_close_based_on_last_post: "Acest subiect va fi automat închis după %{duration} de la ultimul răspuns." auto_close_title: 'Setări de închidere automată' @@ -1299,6 +1531,8 @@ ro: go_bottom: "sfârșit" go: "mergi" jump_bottom: "sari la ultimul mesaj" + jump_prompt: "sari la..." + jump_prompt_of: "din %{count} postări" jump_prompt_long: "La ce postare dorești să sari?" jump_bottom_with_number: "sari la mesajul %{post_number}" total: toate postările @@ -1313,6 +1547,7 @@ ro: '3_2': 'Vei primi notificări deoarece urmărești activ acest subiect.' '3_1': 'Vei primi notificări deoarece ai creat acest subiect.' '3': 'Vei primi notificări deoarece urmărești activ acest subiect.' + '2_2': 'Vei vedea numărul răspunsurilor noi deoarece urmărești acest subiect.' '2': 'Vei vedea numărul răspunsurilor noi deoarece citești acest subiect' '1_2': 'Vei fi notificat dacă cineva îți menționează @numele sau îți scrie un răspuns.' '1': 'Vei fi notificat dacă cineva îți menționează @numele sau îți scrie un răspuns.' @@ -1357,12 +1592,16 @@ ro: visible: "Fă vizibil" reset_read: "Resetează datele despre subiecte citite" make_public: "Transformă în subiect public" + make_private: "Transformă în mesaj privat" feature: pin: "Fixează subiectul" unpin: "Anulează subiect fixat" pin_globally: "Promovează discuţia global" make_banner: "Adaugă statutul de banner" remove_banner: "Șterge statutul de banner" + reply: + title: 'Răspunde' + help: 'începe să scrii un răspuns la acest subiect' clear_pin: title: "Anulează subiect fixat" help: "Elimină subiectul fixat pentru a nu mai apărea la începutul listei de subiecte." @@ -1478,6 +1717,15 @@ ro: multi_select: select: 'selectează' selected: 'selectate ({{count}})' + select_post: + label: 'alege' + title: 'Adaugă postare în selecție' + selected_post: + label: 'selectat' + title: 'Apasă pentru a exclude postarea din selecție' + select_replies: + label: 'selectează +răspunsuri' + title: 'Adaugă postarea și toate răspunsurile la selecție' delete: șterge selecția cancel: anulare selecție select_all: selectează tot @@ -1487,13 +1735,12 @@ ro: few: Ai selectat {{count}} mesaje. other: Ai selectat {{count}} de mesaje. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Citează" edit_reason: "Motivul edit[rii: " post_number: "postarea {{number}}" last_edited_on: "postare editată ultima oară la" reply_as_new_topic: "Răspunde cu link către subiect" + reply_as_new_private_message: "Răspunde cu un mesaj nou aceluiași destinatar" continue_discussion: "În continuarea discuției de la {{postLink}}:" follow_quote: "mergi la postarea citată" show_full: "Arată postarea în întregime" @@ -1502,6 +1749,7 @@ ro: one: "(post retras de autor, va fi şters automat într-o oră, cu excepţia cazului în care mesajul este marcat)" few: "(postări retrase de autor, vor fi şterse automat în %{count} ore, cu excepţia cazului în care mesajele sunt marcate)" other: "(postări retrase de autor, vor fi şterse automat în %{count} de ore, cu excepţia cazului în care mesajele sunt marcate)" + collapse: "restrânge" expand_collapse: "extinde/restrânge" gap: one: "vezi un răspuns ascuns" @@ -1551,12 +1799,24 @@ ro: has_liked: "ai apreciat acest răspuns" undo_like: "anulează aprecierea" edit: "editează această postare" + edit_action: "Modifică" edit_anonymous: "Ne pare rău, dar trebuie să fii autentificat pentru a edita această postare." flag: "marchează această postare ca mesaj privat sau trimite o notificare privată despre ea către un admin/moderator" delete: "șterge această postare" undelete: "restaurează această postare" share: "distribuie un link către această postare" more: "Mai mult" + delete_replies: + confirm: "Dorești să ștergi și răspunsurile la această postare?" + direct_replies: + one: "Da, și {{count}} răspuns direct" + few: "Da, și {{count}} răspunsuri directe" + other: "Da, și {{count}} răspunsuri directe" + all_replies: + one: "Da, și 1 răspuns" + few: "Da, și toate {{count}} răspunsurile" + other: "Da, și toate {{count}} răspunsurile" + just_the_post: "Nu, doare această postare" admin: "acțiuni administrative pentru postare" wiki: "Fă postarea Wiki" unwiki: "Nu mai fă Wiki din postare" @@ -1565,6 +1825,9 @@ ro: rebake: "Reconstruieşte HTML" unhide: "Arată" change_owner: "Schimbă proprietarul" + grant_badge: "Acordă ecuson" + lock_post: "Blochează postarea" + unlock_post: "Deblochează postarea" actions: flag: 'Marchează cu marcaj de avertizare' undo: @@ -1582,6 +1845,10 @@ ro: notify_user: "a trimis un mesaj" bookmark: "a pus semn de carte la asta" like: "a apreciat asta" + like_capped: + one: "și {{count}} altul au apreciat asta" + few: "și {{count}} alții au apreciat asta" + other: "și {{count}} alții au apreciat asta" vote: "a votat la asta" by_you: off_topic: "Ai marcat asta cu mesaj de avertizare, ca fiind în afara subiectului" @@ -1658,6 +1925,16 @@ ro: one: "O persoană a votat pentru acest mesaj" few: "{{count}} persoane au votat pentru acest mesaj" other: "{{count}} de persoane au votat pentru acest mesaj" + delete: + confirm: + one: "Ești sigur că vrei să ștergi aca postare?" + few: "Ești sigur că vrei să ștergi acele {{count}} postări?" + other: "Ești sigur că vrei să ștergi acele {{count}} postări?" + merge: + confirm: + one: "Ești sigur că vrei să comasezi acele postări?" + few: "Ești sigur că vrei să comasezi acele {{count}} postări?" + other: "Ești sigur că vrei să comasezi acele {{count}} postări?" revisions: controls: first: "Prima revizie" @@ -1667,14 +1944,25 @@ ro: hide: "Ascunde revizia" show: "Afișează revizia" revert: "Restaurează această revizie" + edit_post: "Modifică postarea" comparing_previous_to_current_out_of_total: "{{previous}} {{current}} / {{total}}" displays: inline: title: "Arată output-ul randat, cu adăugiri și ștergeri intercalate" + button: 'HTML' side_by_side: title: "Arată una lângă alta diferențele din output-ul randat" + button: 'HTML' side_by_side_markdown: title: "Arată una lângă alta diferențele din sursa brută" + raw_email: + displays: + text_part: + title: "Arată partea de text a emailului" + button: 'Text' + html_part: + title: "Arată partea HTML a emailului" + button: 'HTML' category: can: 'can… ' none: '(nicio categorie)' @@ -1687,6 +1975,8 @@ ro: settings: 'Setări' topic_template: "Șablon subiect" tags: "Etichete" + tags_allowed_tags: "Permite doar folosirea acestor etichete în această categorie:" + tags_allowed_tag_groups: "Permite doar folosirea etichetelor din aceste grupuri în această categorie:" tags_placeholder: "(Opțional) lista etichetelor permise" tag_groups_placeholder: "(Opțional) lista grupurilor de etichete permise" topic_featured_link_allowed: "Permite link-uri promovate în această categorie." @@ -1723,6 +2013,8 @@ ro: email_in_disabled_click: 'activează opțiunea "primire email ".' suppress_from_homepage: "Elimină această categorie de pe pagina principală." all_topics_wiki: "Transformă subiectele noi în wiki-uri, implicit." + sort_order: "Sortează lista de subiecte după:" + default_view: "Listă implicită de subiecte:" allow_badges_label: "Permite acordarea de ecusoane în această categorie" edit_permissions: "Editează permisiuni" add_permission: "Adaugă permisiune" @@ -1760,6 +2052,9 @@ ro: created: "Creat" sort_ascending: 'Crescător' sort_descending: 'Descrescător' + subcategory_list_styles: + rows: "Rânduri" + rows_with_featured_topics: "Rânduri cu subiecte promovate" flagging: title: 'Îți mulțumim că ne ajuți să păstrăm o comunitate civilizată!' action: 'Marcare' @@ -2015,6 +2310,7 @@ ro: granted_on: "Acordat acum %{date}" others_count: "Numărul celorlalți care au acest ecuson (%{count})" title: Ecusoane + multiple_grant: "Poți primi asta de mai multe ori" badge_count: one: "%{count} ecuson" few: "%{count} ecusoane" @@ -2028,6 +2324,7 @@ ro: few: "%{count} acordate" other: "%{count} acordate" select_badge_for_title: Selectați un ecuson pentru a-l folosi ca titlu + none: "(nimic)" badge_grouping: getting_started: name: Cum începem @@ -2050,11 +2347,18 @@ ro:

            tagging: all_tags: "Toate etichetele" + other_tags: "Alte etichete" selector_all_tags: "toate etichetele" selector_no_tags: "fără etichete" changed: "etichete schimbate:" tags: "Etichete" + choose_for_topic: "etichete opționale" delete_tag: "Șterge etichetă" + delete_confirm: + one: "Ești sigur că vrei să ștergi această etichetă și să o scoți dintr-un subiect care o folosește?" + few: "Ești sigur că vrei să ștergi această etichetă și să o scoți din {{count}} subiecte care o folosesc?" + other: "Ești sigur că vrei să ștergi această etichetă și să o scoți din {{count}} subiecte care o folosesc?" + delete_confirm_no_topics: "Ești sigur că vrei să ștergi această etichetă?" rename_tag: "Redenumire etichetă" rename_instructions: "Alege un nume nou pentru etichetă:" sort_by: "Sortat după:" @@ -2158,6 +2462,8 @@ ro: backups: "back-up" traffic_short: "Trafic" traffic: "Cereri web" + page_views: "Vizualizări" + page_views_short: "Vizualizări" show_traffic_report: "Arată raportul detaliat cu privire la trafic" reports: today: "Astăzi" @@ -2179,10 +2485,20 @@ ro: by: "de către" flags: title: "Marcaje de avertizare" + active_posts: "Postări marcate" + old_posts: "Postări marcate vechi" + topics: "Subiecte marcate" + moderation_history: "Istoric de moderare" agree: "De acord" agree_title: "Confirmă acest marcaj ca valid și corect" + agree_flag_hide_post: "Ascunde postarea" + agree_flag_restore_post_title: "Restabilește postarea pentru astfel încât toți utilizatorii să o poată vedea." + agree_flag_suspend: "Suspendă utilizator" + agree_flag: "Păstrează postarea" + ignore_flag: "Ignoră" delete: "Șterge" delete_title: "Șterge postarea la care se referă marcajul de avertizare." + delete_post_defer_flag: "Șterge postarea și ignoră marcarea" delete_post_defer_flag_title: "Șterge postarea; dacă este prima, șterge subiectul" delete_post_agree_flag_title: "Șterge postarea; dacă este prima, șterge subiectul" delete_flag_modal_title: "Șterge și..." @@ -2195,9 +2511,11 @@ ro: clear_topic_flags: "Terminat" clear_topic_flags_title: "Subiectul a fost analizat iar problema rezolvată. Fă click pe Terminat pentru a înlătura marcajele." more: "(mai multe răspunsuri...)" + suspend_user: "Suspendă utilizator" dispositions: agreed: "aprobate" disagreed: "respinse" + deferred: "ignorat" flagged_by: "Marcat de către" resolved_by: "Rezolvat de " took_action: "A luat măsuri" @@ -2205,9 +2523,21 @@ ro: error: "Ceva nu a funcționat" reply_message: "Răspunde" topic_flagged: "Aceast subiect a fost marcat cu marcaj de avertizare." + show_full: "arată întreaga postare" visit_topic: "Vizualizează subiectul pentru a acționa." was_edited: "Mesajul a fost editat după primul marcaj" previous_flags_count: "Acest mesaj a fost deja marcat de {{count}} (de) ori." + details: "detalii" + flagged_topics: + topic: "Subiect" + type: "Tip" + users: "Utilizatori" + short_names: + off_topic: "în afara subiectului" + inappropriate: "neadecvat" + spam: "spam" + notify_user: "personalizat" + notify_moderators: "personalizat" groups: primary: "Grup primar" no_primary: "(nu există grup primar)" @@ -2226,6 +2556,7 @@ ro: add_members: "Adaugă membri" custom: "Personalizat" bulk_complete: "Utilizatorii au fost adăugați în grup." + bulk_complete_users_not_added: "Acești utilizatori nu au fost adăugați (asigură-te că au cont):" bulk: "Adaugă în grup mai mulți utilizatori odată" bulk_paste: "Lipește o listă de utilizatori sau email-uri, câte unul pe linie:" bulk_select: "(selectează un grup)" @@ -2238,6 +2569,8 @@ ro: add_owners: Adaugă proprietari incoming_email: "Adresă de primire emailuri personalizată" incoming_email_placeholder: "introducere adresă de email" + none_selected: "Alege un grup pentru a începe" + no_custom_groups: "Creează un nou grup personalizat" api: generate_master: "Generează cheie API principală" none: "Nu sunt chei API principale active deocamdată." @@ -2269,6 +2602,7 @@ ro: warn_local_payload_url: "Se pare că dorești să setezi un webhook pentru un url local. Un eveniment livrat către o adresă locală ar putea avea efecte secundare și genera comportamente neașteptate. Continuă?" secret_invalid: "Secretul nu trebuie să aibă nici un caracter spațiu-gol." secret_too_short: "Secretul trebuie sa conțină cel puțin 12 caractere." + secret_placeholder: "Un șir de caractere opțional, folosit la generarea semnăturii" event_type_missing: "Va trebui să setezi cel puțin un tip de evenimente." content_type: "Tip conținut" secret: "Secret" @@ -2371,6 +2705,8 @@ ro: without_uploads: "Da (nu include fişierele)" download: label: "Descărcare" + title: "Trimite email cu link de descărcare" + alert: "Un link pentru descărcarea acestei copii de rezervă ți-a fost trimis pe email." destroy: title: "Șterge backup-ul" confirm: "Ești sigur că vrei să ștergi acest backup?" @@ -2402,15 +2738,21 @@ ro: title: "Personalizare" long_title: "Personalizările site-ului" preview: "previzualizare" + explain_preview: "Vizualizează site-ul cu această temă activată" save: "Salvare" new: "Nou" new_style: "Stil nou" import: "Importă" delete: "Șterge" + delete_confirm: "Șterge această temă" about: "Modifică foaia de stil CSS și header-ele HTML din site. Adaugă o personalizare pentru a începe." color: "Culoare" opacity: "Opacitate" copy: "Copiază" + copy_to_clipboard: "Copiază în clipboard" + copied_to_clipboard: "Copiat în clipboard" + copy_to_clipboard_error: "Eroare la copierea datelor în clipboard" + theme_owner: "Nemodificabil, deținut de:" email_templates: title: "Șabloane de emailuri" subject: "Subiect" @@ -2419,8 +2761,58 @@ ro: none_selected: "Selectează un șablon pentru a începe editarea" revert: "Revocă schimbările" revert_confirm: "Ești sigur că vrei să revoci schimbările?" + theme: + import_theme: "Importă temă" + customize_desc: "Personalizează:" + title: "Teme" + long_title: "Modifică culori, CSS și conținutul HTML al site-ului" + edit: "Modifică" + common: "Comun" + desktop: "Desktop" + mobile: "Mobil" + preview: "Previzualizează" + is_default: "Tema este activată implicit" + user_selectable: "Tema poate fi aleasă de către utilizatori" + color_scheme: "Schemă de culori" + color_scheme_select: "Alege culorile folosite de către temă" + custom_sections: "Secțiuni personalizate:" + theme_components: "Componentele temei" + uploads: "Urcări" + variable_name: "Numele variabilei SCSS:" + upload: "Urcă" + css_html: "CSS/HTML personalizat" + edit_css_html: "Modifică CSS/HTML" + edit_css_html_help: "Nu ai modificat CSS sau HTML" + import_file_tip: "Fișier .dcstyle.json care conține tema" + about_theme: "Despre temă" + license: "Licență" + component_of: "Tema este o componentă a:" + update_to_latest: "Actualizat la ultima" + check_for_updates: "Caută actualizări" + updating: "Actualizare în curs..." + up_to_date: "Tema este actuală, ultima verificare:" + add: "Adaugă" + scss: + text: "CSS" + header: + text: "Antet" + after_header: + text: "După antet" + footer: + text: "Subsol" + embedded_scss: + text: "CSS încorporat" + head_tag: + text: "1" + title: "HTML care va fi inserat înainte de 1 tag" + body_tag: + text: "1" colors: + select_base: + title: "Alege schema de culori de bază" + description: "Schemă de bază:" title: "Culori" + edit: "Modifică scheme de culori" long_title: "Scheme de culori" new_name: "Nouă schemă de culori" copy_name_prefix: "Copie a" @@ -2522,6 +2914,13 @@ ro: type_placeholder: "rezumat, înregistrare..." reply_key_placeholder: "cheie de răspuns" skipped_reason_placeholder: "motivul" + moderation_history: + no_results: "Niciun istoric de moderare nu e disponibil." + actions: + delete_user: "Utilizator șters" + suspend_user: "Utilizator suspendat" + delete_post: "Postare ștearsă" + delete_topic: "Subiect șters" logs: title: "Rapoarte" action: "Acțiune" @@ -2539,6 +2938,8 @@ ro: block: "blochează" do_nothing: "nu acționa" staff_actions: + all: "tot" + filter: "Filtru:" title: "Acțiunile membrilor echipei" clear_filters: "Arată tot" staff_user: "Utilizator-membru al echipei" @@ -2559,6 +2960,8 @@ ro: change_trust_level: "schimbă nivelul de încredere" change_username: "schimbă numele utilizatorului" change_site_setting: "schimbă setările site-ului" + change_theme: "schimbă temă" + delete_theme: "șterge temă" change_site_text: "schimbă textul site-ului" suspend_user: "suspendă utilizatorul" unsuspend_user: "reactivează utilizator" @@ -2577,9 +2980,20 @@ ro: revoke_admin: "Revocă titlul de Admin" grant_moderation: "Acordă titlul de Moderator" revoke_moderation: "Revocă titlul de Moderator" + backup_create: "creaază copie de rezervă" deleted_tag: "etichetă ștearsă" renamed_tag: "etichetă redenumită" revoke_email: "revoca email" + lock_trust_level: "blochează nivel de încredere" + unlock_trust_level: "deblochează nivel de încredere" + activate_user: "activează utilizator" + deactivate_user: "dezactivează utilizator" + backup_download: "descarcă copie de rezervă" + backup_destroy: "distruge copie de rezervă" + reviewed_post: "postare revizuită" + post_locked: "postare blocată" + post_unlocked: "postare deblocată" + check_personal_message: "verifică mesaj personal" screened_emails: title: "Email-uri filtrate" description: "Când cineva încearcă să creeze un cont nou, următorul email va fi verificat iar înregistrarea va fi blocată, sau o altă acțiune va fi inițiată." @@ -2610,8 +3024,28 @@ ro: roll_up: text: "Consolidează" title: "Crează noi înregistrari cu subrețele blocate dacă există cel puțin 'min_ban_entries_for_roll_up' înregistrări." + search_logs: + term: "Termen" + searches: "Căutări" + unique: "Unic" + types: + all_search_types: "Toate tipurile de căutare" + header: "Antet" + full_page: "Întreaga pagină" logster: title: "Raport de erori" + watched_words: + search: "caută" + show_words: "arată cuvinte" + actions: + block: 'Blochează' + censor: 'Cenzurează' + require_approval: 'Necesită aprobare' + form: + label: 'Cuvânt nou:' + add: 'Adaugă' + success: 'Succes' + upload: "Urcă" impersonate: title: "Joacă rolul utilizatorului" help: "Folosește această unealtă pentru a imita un cont de utilizator pentru depanare. Va trebui să te deconectezi după ce termini." @@ -2673,8 +3107,18 @@ ro: suspend_duration: "Pentru cât timp va fi suspendat utilizatorul?" suspend_reason_label: "Motivul suspendării? Acest text va fi vizibil pe pagina de profil a utilizatorului, și va fi arătat utilizatorului atunci când încearcă să se autentifice. Încearcă să fii succint." suspend_reason: "Motiv" + suspend_message: "Mesaj email" suspended_by: "Suspendat de" + silence_reason: "Motiv" + silence_message: "Mesaj email" + silence_message_placeholder: "(lăsați necompletat pentru a trimite mesajul implicit)" + suspended_until: "(până la %{until})" + cant_suspend: "Utilizatorul nu poate fi suspendat." delete_all_posts: "Șterge toate postările" + penalty_post_actions: "Ce dorești să faci cu postarea asociată?" + penalty_post_delete: "Șterge postarea" + penalty_post_edit: "Modifică postarea" + penalty_post_none: "Nu face nimic" delete_all_posts_confirm_MF: "Ești pe cale să ștergi {POSTS, plural, one {1 postare} other {# postări}} și {TOPICS, plural, one {1 subiect} other {# subiecte}}. Ești sigur?" moderator: "Moderator?" admin: "Admin?" @@ -2690,6 +3134,7 @@ ro: logged_out: "Acest utilizator a ieșit de pe toate dispozitivele" revoke_admin: 'Revocă titlu de admin' grant_admin: 'Acordă titlu de admin' + grant_admin_confirm: "V-am trimis un email pentru a verifica noul administrator. Vă rog să îl deschideți și să urmați instrucțiunile din el." revoke_moderation: 'Revocă titlu de moderator' grant_moderation: 'Acordă titlu de moderator' unsuspend: 'Reactivează' @@ -2867,6 +3312,7 @@ ro: developer: 'Dezvoltator' embedding: "Embedding" legal: "Legal" + api: 'API' user_api: 'API Utilizator' uncategorized: 'Altele' backups: "Back-up" @@ -2875,6 +3321,7 @@ ro: user_preferences: "Preferințe" tags: "Etichete" search: "Căutare" + groups: "Grupuri" badges: title: Ecusoane new_badge: Ecuson nou @@ -2957,6 +3404,7 @@ ro: sample: "Folosește următorul cod HTML în site-ul tău pentru a crea și pentru a încorpora subiecte Discourse. Înlocuiește ÎNLOCUIEȘTE_MĂ cu URL-ul canonic al paginii pe care dorești să o încorporezi." title: "Embedding" host: "Host-uri permise" + class_name: "Numele clasei" path_whitelist: "Cale permise" edit: "editează" category: "Postează în categoria" @@ -3001,6 +3449,10 @@ ro: upload: "Încărcare" uploading: "Se încarcă..." quit: "Poate mai târziu" + staff_count: + one: "Comunitatea ta are 1 membru (tu)." + few: "Comunitatea ta are %{count} membri cu tot cu tine." + other: "Comunitatea ta are %{count} membri cu tot cu tine." invites: add_user: "adaugă" none_added: "Nu ați invitat nici un membru al echipei. Ești sigur că vrei să continui?" diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index f0421e01e9..5309896b4b 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -54,6 +54,11 @@ ru: few: "%{count}с" many: "%{count}с" other: "%{count}с" + less_than_x_minutes: + one: "< 1мин" + few: "~ 1m" + many: "> 1m" + other: "< %{count}мин" x_minutes: one: "1мин" few: "%{count}мин" @@ -1100,6 +1105,8 @@ ru: alt: 'Alt' select_kit: default_header_text: Выбрать... + no_content: Совпадений не найдено + filter_placeholder: Поиск... emoji_picker: filter_placeholder: Искать emoji people: People @@ -1197,6 +1204,17 @@ ru: title: "Забыли указать получателей?" body: "В списке получателей сейчас только вы сами!" admin_options_title: "Дополнительные настройки темы для персонала" + composer_actions: + reply_as_new_topic: + label: Ответить в новой связанной теме + desc: Создать новую тему + reply_as_private_message: + label: Новое сообщение + desc: Написать личное сообщение + reply_to_topic: + label: Ответить на тему + create_topic: + label: "Новая тема" notifications: title: "уведомления об упоминании @псевдонима, ответах на ваши посты и темы, сообщения и т.д." none: "Уведомления не могут быть загружены." @@ -1204,6 +1222,12 @@ ru: more: "посмотреть более ранние уведомления" total_flagged: "всего сообщений с жалобами" granted_badge: "Заслужил(а) '{{description}}'" + watching_first_post: "Новая тема {{description}}" + group_message_summary: + one: "{{count}} сообщение в вашей группе: {{group_name}} " + few: "{{count}} сообщений в вашей группе: {{group_name}} " + many: "{{count}} сообщений в вашей группе: {{group_name}} " + other: "{{count}} сообщений в вашей группе: {{group_name}} " alt: mentioned: "Упомянуто" quoted: "Процитировано пользователем" @@ -1211,6 +1235,7 @@ ru: posted: "Опубликовано" edited: "Изменил ваше сообщение" liked: "Понравилось ваше сообщение" + private_message: "Личное сообщение от" invited_to_topic: "Приглашение в тему от" invitee_accepted: "Приглашение принято" moved_post: "Ваша тема перенесена участником " @@ -1257,6 +1282,8 @@ ru: more_results: "Найдено множество результатов. Пожалуйста, уточните, критерии поиска." cant_find: "Не можете найти нужную информацию?" start_new_topic: "Создать новую тему?" + search_google_button: "Google" + search_google_title: "Искать на этом сайте" context: user: "Искать сообщения от @{{username}}" category: "Искать в разделе #{{category}}" @@ -1266,6 +1293,8 @@ ru: title: Расширенный поиск posted_by: label: Автор + in_category: + label: Категоризированный in_group: label: Группа with_badge: @@ -1275,6 +1304,7 @@ ru: posted: В которых я писал watching: За которыми я наблюдаю tracking: За которыми я слежу + private: В моих сообщениях first: Только первые сообщения в темах pinned: Закреплены unpinned: Не закреплены @@ -1435,6 +1465,7 @@ ru: this_weekend: "В эти выходные" next_week: "На следующей неделе" next_month: "В следующем месяце" + forever: "Навсегда" pick_date_and_time: "Выбрать дату и время" temp_open: title: "Открыть на время" @@ -1444,6 +1475,7 @@ ru: title: "Закрыть на время" auto_close: title: "Автоматическое закрытие темы" + label: "Закрыть тему через:" error: "Пожалуйста, введите корректное значение." auto_delete: title: "Автоматическое удаление темы" @@ -1687,8 +1719,6 @@ ru: many: Вы выбрали {{count}} сообщений. other: Вы выбрали {{count}} сообщений. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Цитата" edit_reason: "Причина:" post_number: "сообщение {{number}}" @@ -1762,6 +1792,7 @@ ru: has_liked: "Вам понравилось это сообщение" undo_like: "больше не нравится" edit: "Изменить сообщение" + edit_action: "Изменить" edit_anonymous: "Войдите, чтобы отредактировать это сообщение." flag: "пожаловаться на сообщение" delete: "удалить сообщение" @@ -2011,6 +2042,8 @@ ru: subcategory_list_styles: rows: "Строки" rows_with_featured_topics: "Строки с обсуждаемыми темами" + boxes: "Блоки" + boxes_with_featured_topics: "Блоки с обсуждаемыми темами" flagging: title: 'Спасибо за вашу помощь в поддержании порядка!' action: 'Пожаловаться на сообщение' @@ -2251,10 +2284,14 @@ ru: hamburger_menu: '= Открыть меню гамбургер' user_profile_menu: 'p Открыть меню профиля' show_incoming_updated_topics: '. Показать обновленные темы' + search: '/ или ctrl+alt+f Поиск' help: '? Показать сочетания клавиш' dismiss_new_posts: 'x, r Отложить новые сообщения' dismiss_topics: 'x, t Отложить темы' log_out: 'shift+z shift+z Выйти' + composing: + title: 'Редактирование' + return: 'shift+c Вернуться в редактор' actions: title: 'Темы' bookmark_topic: ' f Добавить / удалить из заклодок' @@ -2302,6 +2339,8 @@ ru: many: "выдано %{count}" other: "выдано %{count}" select_badge_for_title: Использовать награду в качестве Вашего титула + none: "(нет)" + successfully_granted: "Награда %{badge} успешно присвоена %{username}" badge_grouping: getting_started: name: Начало работы @@ -2324,11 +2363,19 @@ ru:

            tagging: all_tags: "Все теги" + other_tags: "Изменить тэги" selector_all_tags: "Все теги" selector_no_tags: "Нет тегов" changed: "Теги изменены:" tags: "Теги" + choose_for_topic: "Выберите теги для этой темы (опционально)" delete_tag: "Удалить тег" + delete_confirm: + one: "Вы действительно хотите удалить этот тэг и удалить его из 1 топика, которому он присвоен?" + few: "Вы действительно хотите удалить этот тэг и удалить его из {{count}} топиков, которым он присвоен?" + many: "Вы действительно хотите удалить этот тэг и удалить его из {{count}} топиков, которым он присвоен?" + other: "Вы действительно хотите удалить этот тэг и удалить его из {{count}} топиков, которым он присвоен?" + delete_confirm_no_topics: "Вы действительно хотите удалить этот тэг?" rename_tag: "Редактировать тег" rename_instructions: "Выберите новое название тега:" sort_by: "Сортировка:" @@ -2423,6 +2470,7 @@ ru: no_problems: "Проблем не обнаружено." moderators: 'Модераторы:' admins: 'Администраторы:' + silenced: 'Отключенные:' suspended: 'Заморожен:' private_messages_short: "Сообщ." private_messages_title: "Сообщений" @@ -2455,12 +2503,29 @@ ru: by: "от" flags: title: "Жалобы" + active_posts: "Сообщения с жалобами" + old_posts: "Старые сообщения с жалобами" + topics: "Темы с жалобами" moderation_history: "История модерации" agree: "Принять" agree_title: "Подтвердить корректность жалобы" + agree_flag_hide_post: "Скрыть запись" + agree_flag_hide_post_title: "Скрыть это сообщение и автоматически отправить пользователю личное сообщение с просьбой отредактировать его" + agree_flag_restore_post: "Согласиться (восстановить сообщение)" + agree_flag_restore_post_title: "Восстановить сообщение, чтобы все пользователи могли его видеть." + agree_flag_suspend: "Заморозить Пользователя" + agree_flag_suspend_title: "Согласиться с флагом и заблокировать пользователя." + agree_flag_silence: "Отключить пользователя" + agree_flag_silence_title: "Согласиться с флагом и отключить пользователя." + agree_flag: "Оставить сообщение нетронутым." + agree_flag_title: "Согласиться с флагом и оставить сообщение нетронутым." + ignore_flag: "Игнорировать" + ignore_flag_title: "Удалить этот флаг; в это время он не требует никаких действий." delete: "Удалить" delete_title: "Удалить обжалованное сообщение." + delete_post_defer_flag: "Удалить сообщение и отклонить флаг" delete_post_defer_flag_title: "Удалить сообщение; если это первое сообщение, удалить тему целиком" + delete_post_agree_flag: "Удалить сообщение и согласиться с флагом" delete_post_agree_flag_title: "Удалить сообщение; если это первое сообщение, удалить тему целиком" delete_flag_modal_title: "Удалить и..." delete_spammer: "Удалить спамера" @@ -2472,19 +2537,37 @@ ru: clear_topic_flags: "Готово" clear_topic_flags_title: "Тема была просмотрена, и все проблемы были решены. Нажмите Готово, чтобы удалить все жалобы." more: "(ещё ответы...)" + suspend_user: "Заблокировать пользователя" + suspend_user_title: "Заблокировать пользователя за это сообщение" dispositions: agreed: "принято" disagreed: "отклонено" + deferred: "пропущено" flagged_by: "Отмечено" resolved_by: "Разрешено" took_action: "Принята мера" system: "Системные" error: "что-то пошло не так" reply_message: "Ответить" + no_results: "Сообщений с жалобами нет." topic_flagged: "Эта тема была помечена." + show_full: "показать сообщение полностью" visit_topic: "Посетите тему чтобы принять меры" was_edited: "Сообщение было отредактировано после первой жалобы" previous_flags_count: "На это сообщение уже пожаловались {{count}} раз(а)." + show_details: "Показать детали жалобы" + details: "детали" + flagged_topics: + topic: "Тема" + type: "Набирать текст" + users: "Пользователи" + last_flagged: "Последняя жалоба" + short_names: + off_topic: "Не по теме" + inappropriate: "неприемлемый" + spam: "спам" + notify_user: "персональный" + notify_moderators: "персональный" groups: primary: "Основная группа" no_primary: "(нет основной группы)" @@ -2572,6 +2655,7 @@ ru: details: "Происходит, когда сообщение создается, редактируется, удаляется или восстанавливается." user_event: name: "Событие пользователя" + details: "Когда пользователь входит, выходит, создается, подтверждается или изменяется." delivery_status: title: "Статус передачи" inactive: "Неактивна" @@ -2640,6 +2724,7 @@ ru: label: "Загрузить" title: "Загрузить копию на сервер" uploading: "Загрузка..." + success: "'{{filename}}' успешно загружен. Файл теперь обрабатывается и займет минуту, чтобы его строки отразились в списке." error: "При загрузке '{{filename}}' произошла ошибка: {{message}}" operations: is_running: "Операция в данный момент исполняется..." @@ -2730,8 +2815,10 @@ ru: theme_components: "Компоненты стиля" uploads: "Загрузки" no_uploads: "Вы можете загружать различные ресурсы для вашего стиля, такие как шрифты и изображения" + add_upload: "Добавить вложение" upload_file_tip: "Выберите файл для загрузки (png, woff2 и т.д.)" variable_name: "Имя переменной SCSS:" + upload: "Загрузить" child_themes_check: "Стиль включает дочерние стили" css_html: "Настройка CSS/HTML" edit_css_html: "Редактировать CSS/HTML" @@ -2747,6 +2834,11 @@ ru: updating: "Обновляю..." up_to_date: "Стиль текущей версии, последняя проверка:" add: "Добавить" + commits_behind: + one: "Тема находится на 1 коммит сзади!" + few: "Тема находится на {{count}} коммитов сзади!" + many: "Тема находится на {{count}} коммитов сзади!" + other: "Тема находится на {{count}} коммитов сзади!" scss: text: "CSS" title: "Введите CSS; допускаются все стили CSS и SCSS" @@ -2876,6 +2968,15 @@ ru: type_placeholder: "дайджест, подписка..." reply_key_placeholder: "Ключ ответа" skipped_reason_placeholder: "Причина" + moderation_history: + performed_by: "Выполнено пользователем " + no_results: "История модерации недоступна." + actions: + delete_user: "Удалить Пользователя" + suspend_user: "Пользователь приостановлен" + silence_user: "Пользователь отключен" + delete_post: "Комментарий удален" + delete_topic: "Тема удалена" logs: title: "Логи" action: "Действие" @@ -2931,6 +3032,8 @@ ru: change_category_settings: "изменена настройка раздела" delete_category: "удален раздел" create_category: "создан раздел" + silence_user: "заблокировать пользователя" + unsilence_user: "разблокировать пользователя" grant_admin: "выданы права администратора" revoke_admin: "отозваны права администратора" grant_moderation: "выданы права модератора" @@ -2946,6 +3049,11 @@ ru: change_readonly_mode: "изменение режима \"только для чтения\"" backup_download: "скачать резервную копию" backup_destroy: "удалить резервную копию" + reviewed_post: "просмотренное сообщение" + custom_staff: "действия в плагинах" + post_locked: "сообщение заблокировано" + post_unlocked: "сообщение разблокировано" + check_personal_message: "проверить личное сообщение" screened_emails: title: "Почтовые адреса" description: "Когда кто-то создаёт новую учётную запись, проверяется данный почтовый адрес и регистрация блокируется или производятся другие дополнительные действия." @@ -2977,17 +3085,48 @@ ru: text: "Группировка" title: "Создание новой записи бана целой подсети если уже имеется хотя бы 'min_ban_entries_for_roll_up' записей отдельных IP адресов." search_logs: + title: "Логи поиска" + term: "Правило" + searches: "Поиски" + click_through: "Пролистать" + unique: "Уникальный" + unique_title: "уникальные пользователи, выполняющие поиск" types: all_search_types: "Все типы поиска" header: "Шапка" + full_page: "Полная страница" + click_through_only: "Все (только листинг)" + header_search_results: "Результаты поиска по заголовкам" logster: title: "Журнал ошибок" watched_words: + title: "Отслеживаемые слова" + search: "поиск" + clear_filter: "Очистить" + show_words: "показать слова" + word_count: + one: "1 слово" + few: "%{count}слова" + many: "%{count}слов" + other: "%{count}слов" actions: + block: 'Заблокировать' + censor: 'Цензура' + require_approval: 'Требующие одобрения' flag: 'Жалоба' + action_descriptions: + block: 'Запретить публикацию сообщений, содержащих эти слова. Пользователь увидит сообщение об ошибке при попытке отправить свое сообщение.' + censor: 'Разрешить сообщения, содержащие эти слова, но заменять их символами, которые скрывают цензурные выражения.' + require_approval: 'Комментарии, содержащие эти слова, будут требовать одобрения персонала, прежде чем их можно будет увидеть.' + flag: 'Разрешить сообщения, содержащие эти слова, но помечать их как неприемлемые, чтобы модераторы могли их оценивать.' form: + label: 'Новое слово:' + placeholder: 'слово целиком, звездочка (*) используется как знак подстановки ' placeholder_regexp: "Регулярное выражение" add: 'Добавить' + success: 'Успех' + upload: "Загрузить" + upload_successful: "Загрузка прошла успешна. Слова добавлены." impersonate: title: "Войти от имени пользователя" help: "Используйте этот инструмент, чтобы войти от имени пользователя. Может быть полезно для отладки. После этого необходимо выйти и зайти под своей учетной записью снова." @@ -3007,6 +3146,7 @@ ru: pending: "Ожидает одобрения" staff: 'Персонал' suspended: 'Замороженные' + silenced: 'Отключенный' suspect: 'Подозрительные' approved: "Подтвердить?" approved_selected: @@ -3031,6 +3171,7 @@ ru: staff: "Персонал" admins: 'Администраторы' moderators: 'Модераторы' + silenced: 'Отключенные пользователи' suspended: 'Замороженные пользователи' suspect: 'Подозрительные пользователи' reject_successful: @@ -3052,11 +3193,31 @@ ru: unsuspend_failed: "Ошибка разморозки пользователя {{error}}" suspend_duration: "На сколько времени заморозить пользователя?" suspend_reason_label: "Причина заморозки? Данный текст будет виден всем на странице профиля пользователя и будет отображаться, когда пользователь пытается войти. Введите краткое описание." + suspend_reason_hidden_label: "Почему вы приостанавливаете пользователя? Этот текст будет показан пользователю, когда он попытается войти в систему. Будьте краткими." suspend_reason: "Причина" + suspend_reason_placeholder: "Причина приостановки" + suspend_message: "Сообщение почты" + suspend_message_placeholder: "При желании, предоставьте дополнительную информацию о приостановке, и она будет отправлена пользователю по электронной почте." suspended_by: "Заморожен (кем)" + silence_reason: "Причина" + silenced_by: "Отключен пользователем " + silence_modal_title: "Отключенный пользователь" + silence_duration: "Как долго пользователь будет отключен?" + silence_reason_label: "Почему вы собираетесь отключить пользователя?" + silence_reason_placeholder: "Причина отключения" + silence_message: "Сообщение почты" + silence_message_placeholder: "(оставьте незаполненным, чтобы отправить дефолтное сообщение)" + suspended_until: "(пока не будет %{until})" cant_suspend: "Этого пользователя нельзя заморозить." delete_all_posts: "Удалить все сообщения" + penalty_post_actions: "Что нужно сделать со связанным сообщением?" + penalty_post_delete: "Удалить сообщение" + penalty_post_edit: "Редактировать сообщение" + penalty_post_none: "Ничего не делать" delete_all_posts_confirm_MF: "Вы собираетесь удалить {POSTS, plural, one {1 сообщение} other {# сообщений}} и {TOPICS, plural, one {1 тему} other {# тем}}. Вы уверены?" + silence: "Отключить" + unsilence: "Подключить" + silenced: "Отключен?" moderator: "Модератор?" admin: "Администратор?" suspended: "Заморожен?" @@ -3077,6 +3238,9 @@ ru: grant_moderation: 'Выдать права Модератора' unsuspend: 'Разморозить' suspend: 'Заморозить' + show_flags_received: "Показать полученные флаги" + flags_received_by: "Флаги, полученные %{username}" + flags_received_none: "Этот пользователь не получил никаких флагов." reputation: Репутация permissions: Права activity: Активность @@ -3129,12 +3293,18 @@ ru: activate_failed: "Во время активации пользователя произошла ошибка." deactivate_account: "Деактивировать" deactivate_failed: "Во время деактивации пользователя произошла ошибка." + unsilence_failed: 'При подключении этого пользователя произошла ошибка.' + silence_failed: 'При отключении этого пользователя произошла ошибка.' + silence_confirm: 'Вы уверены, что хотите отключить этого пользователя? Он не сможет создавать новые темы или сообщения.' + silence_accept: 'Да, отключить этого пользователя' bounce_score: "Возвратов Писем" reset_bounce_score: label: "Сбросить" title: "сбросить карму к 0" + visit_profile: "Посетитестраницу настроек этого пользователя , чтобы отредактировать его профиль" deactivate_explanation: "Дезактивированные пользователи должны заново подтвердить свой e-mail." suspended_explanation: "Замороженный пользователь не может войти (авторизоваться)." + silence_explanation: "Отключенный пользователь не может публиковать или отвечать на темы." staged_explanation: "Имитированный пользователь может отправлять сообщения только по эл.почте в определённые темы." bounce_score_explanation: none: "Нет возвратов полученных недавно от этой эл.почты." @@ -3347,6 +3517,7 @@ ru: sample: "Используйте следующий HTML-код на своём сайте, для возможности создания связанных тем. Замените REPLACE_ME канонической ссылкой страницы, куда производится встраивание." title: "Встраивание" host: "Разрешённые Хосты" + class_name: "Имя класса" path_whitelist: "Разрешённый Путь" edit: "изменить" category: "Опубликовать в разделе" @@ -3366,6 +3537,7 @@ ru: embed_classname_whitelist: "Разрешённые CSS-классы" feed_polling_enabled: "Импорт сообщений через RSS/ATOM" feed_polling_url: "Ссылка на RSS/ATOM" + feed_polling_frequency_mins: "Частота опроса ленты (в минутах)" save: "Сохранить настройки встраивания" permalink: title: "Постоянные ссылки" diff --git a/config/locales/client.sk.yml b/config/locales/client.sk.yml index 928dea873a..824557fc18 100644 --- a/config/locales/client.sk.yml +++ b/config/locales/client.sk.yml @@ -67,6 +67,10 @@ sk: one: "1d" few: "%{count}d" other: "%{count}d" + x_months: + one: "%{count} mesiac" + few: "%{count} mesiace" + other: "%{count} mesiace" about_x_years: one: "1r" few: "%{count}r" @@ -379,11 +383,23 @@ sk: title: 'Upraviť skupinu' full_name: 'Celé meno' add_members: "Pridať používateľov" + public_admission: "Povoliť používateľom slobodne sa stať člen skupiny (kupina musí byť verejne viditeľná)" + public_exit: "Povoliť používateľom slobodne opustiť skupinu." + empty: + posts: "Nie sú tu žiadne príspevky od členov tejto skupiny" + members: "V tejto skupine niesú žiadny členovia" + messages: "Pre túto skupinu niesú žiadne správy" add: "Pridať" join: "Pridať sa do skupiny" leave: "Opustiť skupinu" request: "Požiadať o pridanie do skupiny" + message: "Správa" + automatic_group: Automatická skúpina closed_group: Uzavretá skupina + is_group_user: "Si členom tejto skupiny" + membership_request: + submit: "Odošli požiadavku" + title: "Požiadavka o členstvo v %{group_name}" membership: "Členstvo" name: "Meno" user_count: "Počet členov" @@ -403,6 +419,9 @@ sk: posts: "Príspevky" mentions: "Zmienky" messages: "Správy" + visibility_levels: + title: "Kto môže vidieť túto skupinu?" + public: "Každý" alias_levels: nobody: "Nikto" only_admins: "Iba administrátori" @@ -539,6 +558,7 @@ sk: Toto nastavenie nahradí Zhrnutie aktivíť.
            Stíšené témy a kategórie nie sú v týchto emailoch zahrnuté. individual: "Pošli email pri každom novom príspevku" + individual_no_echo: "Pošli email pri každom novom príspevku okrem vlastných." many_per_day: "Pošli mi email pri každom novom príspevku (cca {{dailyEmailEstimate}} denne)" few_per_day: "Pošli mi email pri každom novom príspevku (cca 2 denne)" tag_settings: "Štítky" @@ -569,10 +589,12 @@ sk: muted_users_instructions: "Pozastaviť všetky notifikácie od týchto používateľov." muted_topics_link: "Zobraziť stíšené témy" watched_topics_link: "Zobraziť sledované témy" + tracked_topics_link: "Zobraziť sledované témy" apps: "Appky" revoke_access: "Odvolať prístup" undo_revoke_access: "Zrušiť odvolanie prístupu" api_approved: "Schválený:" + theme: "Téma" staff_counters: flags_given: "nápomocné značky" flagged_posts: "označkované príspevky" @@ -590,6 +612,13 @@ sk: move_to_archive: "Archív" failed_to_move: "Zlyhalo presunutie označených správ (možno je chyba vo vašom pripojení)" select_all: "Označ všetky" + preferences_nav: + account: "Účet" + profile: "Profil" + emails: "Emaily" + notifications: "Upozornenia" + categories: "Kategórie" + apps: "Aplikácie" change_password: success: "(email odoslaný)" in_progress: "(email sa odosiela)" @@ -649,6 +678,7 @@ sk: short_instructions: "Ostatní vás môžu zmieniť ako @{{username}}" available: "Vaše používateľské meno je voľné" not_available: "Nie je k dispozícii. Skúste {{suggestion}}?" + not_available_no_suggestion: "Nedostupné" too_short: "Vaše používateľské meno je prikrátke" too_long: "Vaše používateľské meno je pridlhé" checking: "Kontrolujeme dostupnosť používateľského meno" @@ -734,6 +764,8 @@ sk: expired: "Táto pozvánka vypršala." rescind: "Odstrániť" rescinded: "Pozvánka odstránená" + rescind_all: "Odstrániť všetky pozvánky" + rescinded_all: "Všetky pozvánky odstránené!" reinvite: "Poslať pozvánku znovu" reinvite_all: "Poslať všetky pozvánky znovu" reinvited: "Poslať pozvánku znovu" @@ -901,6 +933,7 @@ sk: complete_email_found: "Našli sme účet priradený k %{email}, čoskoro dostanete e-mail s pokynmi, ako si obnoviť svoje heslo." complete_username_not_found: "Žiadny účet nemá priradené používateľské meno %{username}" complete_email_not_found: "Žiadny účet nie je s e-mailom %{email}" + button_help: "Pomoc" login: title: "Prihlásenie" username: "Používateľ" @@ -920,6 +953,8 @@ sk: not_allowed_from_ip_address: "Nie je možné prihlásenie z tejto IP adresy." admin_not_allowed_from_ip_address: "Nie je možné prihlásenie ako admin z tejto IP adresy." resend_activation_email: "Kliknite sem pre opätovné odoslanie aktivačného emailu." + change_email: "Zmeniť emailovú adresu" + submit_new_email: "Aktualizovať emailovú adresu" sent_activation_email_again: "Odoslali sme vám ďalší aktivačný email na {{currentEmail}}. Môže trvať niekoľko minút kým príde; pre istotu si skontrolujte spamový priečinok." to_continue: "Prosím, prihláste sa" preferences: "Na zmenu používateľských nastavení musíte byť prihlásený." @@ -949,6 +984,8 @@ sk: accept_title: "Pozvánka" accept_invite: "Prijať pozvánku" success: "Váš účet bol vytvorený a ste prihlásený." + name_label: "Meno" + optional_description: "(nepovinné)" emoji_set: apple_international: "Apple/Medzinárodné" google: "Google" @@ -962,6 +999,12 @@ sk: shift: 'Shift' ctrl: 'Ctrl' alt: 'Alt' + select_kit: + filter_placeholder: Hľadať + emoji_picker: + people: Ľudia + food: Jedlo + travel: Cestovanie composer: emoji: "Emoji :)" more_emoji: "viac ..." @@ -1034,6 +1077,9 @@ sk: modal_cancel: "Zrušiť" cant_send_pm: "Ľutujeme, nemôžete poslať správu používateľovi %{username}." admin_options_title: "Nepovinné zamestnanecké nastavenia pre túto tému" + composer_actions: + reply_as_private_message: + label: Nová správa notifications: title: "oznámenia o zmienkach pomocou @meno, odpovede na Vaše príspevky a témy, správy, atď." none: "Notifikácie sa nepodarilo načítať" @@ -1232,6 +1278,15 @@ sk: jump_reply_up: prejsť na predchádzajúcu odpoveď jump_reply_down: prejsť na nasledujúcu odpoveď deleted: "Téma bola vymazaná" + auto_update_input: + tomorrow: "Zajtra" + this_weekend: "Tento víkend" + next_week: "Budúci týždeň" + two_weeks: "Dva týždne" + next_month: "Budúci mesiac" + three_months: "Tri mesiace" + six_months: "Šesť mesiacov" + one_year: "Jeden rok" auto_close_title: 'Nastavenia automatického zatvárania' auto_close_immediate: one: "Posledný príspevok k téme je starý už 1 hodinu, takže téma bude okamžite uzavretá. " @@ -1245,6 +1300,7 @@ sk: go_bottom: "na spodok" go: "Choď" jump_bottom: "choď na posledný príspevok" + jump_prompt: "choď na..." jump_bottom_with_number: "choď na príspevok číslo %{post_number}" total: Všetkých príspevkov current: tento príspevok @@ -1428,8 +1484,6 @@ sk: few: Označili ste {{count}} príspevky other: Označili ste {{count}} príspevkov post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Citácia" edit_reason: "Dôvod:" post_number: "príspevok {{number}}" @@ -2089,6 +2143,18 @@ sk: visit_topic: "Navšťívte tému pre prijatie opatrení" was_edited: "Príspevok bol upravený po prvom označení" previous_flags_count: "Tento príspevok bol už označený {{count}} krát." + details: "detaily" + flagged_topics: + topic: "Téma" + type: "Typ" + users: "Používatelia" + last_flagged: "Naposledy označené" + short_names: + off_topic: "mimo tému" + inappropriate: "nevhodné" + spam: "spam" + notify_user: "vlastné" + notify_moderators: "vlastné" groups: primary: "Hlavná skupina" no_primary: "(bez hlavnej skupiny)" @@ -2133,6 +2199,13 @@ sk: info_html: "Váš API kľúč Vám umožní vytváranie a aktualizovanie tém prostredníctvom volaní JSON." all_users: "Všetci používatelia" note_html: "Držte tento kľúč v tajnosti, všetci užívatelia ktorí ho vlastnia môžu vytvárať ľubovoľné príspevky pod ľubovoľným používateľským menom. " + web_hooks: + create: "Vytvoriť" + save: "Uložiť" + destroy: "Vymazať" + description: "Popis" + go_back: "Späť na zoznam" + payload_url_placeholder: "https://example.com/postreceive" plugins: title: "Pluginy" installed: "Nainštalované pluginy" diff --git a/config/locales/client.sq.yml b/config/locales/client.sq.yml index 665b5e622b..ceca48bba6 100644 --- a/config/locales/client.sq.yml +++ b/config/locales/client.sq.yml @@ -1391,8 +1391,6 @@ sq: one: Keni përzgjedhur 1 postim. other: Keni përzgjedhur {{count}} postime. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" edit_reason: "Arsyeja:" post_number: "postimi {{number}}" last_edited_on: "redaktimi i fundit u krye më" diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index f2bcf7ae5e..bbb3959bf5 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -1485,8 +1485,6 @@ sv: one: Du har markerat 1 inlägg. other: Du har markerat {{count}} inlägg. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Citat" edit_reason: "Anledning:" post_number: "inlägg {{number}}" diff --git a/config/locales/client.th.yml b/config/locales/client.th.yml index 4b4130e1ae..d78a454e76 100644 --- a/config/locales/client.th.yml +++ b/config/locales/client.th.yml @@ -1129,8 +1129,6 @@ th: description: other: คุณได้เลือก {{count}} โพสต์. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" edit_reason: "เหตุผล:" post_number: "โพสต์ {{number}}" last_edited_on: "โพสแก้ไขล่าสุดเมื่อ" diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml index 0b5a6255f2..ab264a3e03 100644 --- a/config/locales/client.tr_TR.yml +++ b/config/locales/client.tr_TR.yml @@ -45,12 +45,16 @@ tr_TR: other: "< %{count}s" x_seconds: other: "%{count}s" + less_than_x_minutes: + other: "< %{count}dk" x_minutes: other: "%{count}d" about_x_hours: other: "%{count}s" x_days: other: "%{count}g" + x_months: + other: "%{count}ay" about_x_years: other: "%{count}y" over_x_years: @@ -83,6 +87,7 @@ tr_TR: other: "%{count} yıl sonra" previous_month: 'Önceki Ay' next_month: 'Sonraki Ay' + placeholder: tarih share: topic: 'bu konunun bağlantısını paylaşın' post: '#%{postNumber} numaralı gönderiyi paylaşın' @@ -97,6 +102,7 @@ tr_TR: split_topic: "bu konuyu %{when} ayırdı" invited_user: "%{when} %{who} davet edildi" invited_group: "%{who} %{when} davet edildi" + user_left: "%{who} bu iletiden ayrıldı %{when}" removed_user: "%{when} %{who} silindi" removed_group: "%{who} %{when} kaldırıldı" autoclosed: @@ -117,6 +123,8 @@ tr_TR: visible: enabled: '%{when} listelendi' disabled: '%{when} listelenmedi' + banner: + enabled: 'Bu konu manşete taşınmıştır %{when}. Kullanıcı tarafından yoksayılana kadar her sayfanın en üstünde belirecektir.' topic_admin_menu: "konuyla alakalı yönetici eylemleri" wizard_required: "Yeni Discourse'unuza hoşgeldiniz! Hadi kuruluma başlayalım! Kurulum Sihirbazı ✨" emails_are_disabled: "Tüm giden e-postalar yönetici tarafından genel olarak devre dışı bırakıldı. Herhangi bir e-posta bildirimi gönderilmeyecek." @@ -231,6 +239,7 @@ tr_TR: uploading: "Yükleniyor..." uploading_filename: "{{filemame}} yükleniyor..." uploaded: "Yüklendi!" + pasting: "Yapıştırılıyor..." enable: "Etkinleştir" disable: "Devredışı Bırak" undo: "Geri Al" @@ -323,7 +332,7 @@ tr_TR: add_members: "Üyeleri ekle" delete_member_confirm: "'%{username}' adlı kullanıcıyı '%{group}' grubundan çıkart?" name_placeholder: "Grup adı, kullanıcı adındaki gibi boşluksuz olmalı" - public_admission: "Kullanıcıların gruba özgürce katılmalarına izin ver (Herkese açık olarak görünür grup gerektirir)" + public_admission: "Kullanıcıların gruba özgürce katılmalarına izin ver (grubun Herkese açık olması gerekir)" public_exit: "Kullanıcıların grubu özgürce terk etmesine izin ver" empty: posts: "Bu grubun üyelerinden konu yok." @@ -340,14 +349,14 @@ tr_TR: automatic_group: Otomatik Grup closed_group: Kapanmış Grup is_group_user: "Bu grubun bir üyesisiniz." - allow_membership_requests: "Kullanıcıların grup sahiplerine üyelik talepleri göndermesine izin ver" + allow_membership_requests: "Kullanıcılar'ın grup sahiplerine üyelik talepleri göndermesine izin ver" membership_request: submit: "İstek Gönderin" title: "@%{group_name} için Katılma isteği " reason: "Grup sahiplerine bu gruba neden üye olduğunuzu bildirin" membership: "Üyelik" name: "İsim" - user_count: "Grup Sayısı" + user_count: "Üyeler" bio: "Grup Hakkında" selector_placeholder: "Üye ekle" owner: "sahip" @@ -456,6 +465,7 @@ tr_TR: topics_entered: "açılan konular" post_count: "# gönderi" confirm_delete_other_accounts: "Bu hesapları silmek isteğinize emin misiniz?" + powered_by: "ipinfo.io tarafından güçlendirildi" user_fields: none: "(bir seçenek seçin)" user: @@ -716,6 +726,7 @@ tr_TR: title: "Davetler" user: "Davet Edilen Kullanıcı" sent: "Gönderildi" + none: "Gösterilecek davet yok." truncated: other: "İlk {{count}} davet gösteriliyor." redeemed: "Kabul Edilen Davetler" @@ -772,6 +783,8 @@ tr_TR: other: "alınan" days_visited: other: "ziyaret edilen gün" + topics_entered: + other: "görüntülenmiş başlıklar" posts_read: other: "okunmuş gönderi" bookmark_count: @@ -964,6 +977,7 @@ tr_TR: accept_title: "Davet" welcome_to: "%{site_name} topluluğuna hoş geldiniz!" invited_by: "Davet gönderen:" + social_login_available: "Ayrıca bu eposta adresini kullanan tüm sosyal ağ girişleriyle oturum açabileceksiniz." your_email: "Hesap e-posta adresiniz %{email}." accept_invite: "Daveti kabul et" success: "Hesabınız oluşturuldu ve şimdi giriş yaptınız." @@ -989,9 +1003,9 @@ tr_TR: ctrl: 'Ctrl' alt: 'Alt' select_kit: + default_header_text: Seç... no_content: Hiçbir sonuç bulunamadı filter_placeholder: Ara... - create: "{{content}} oluştur" emoji_picker: filter_placeholder: Emoji ara people: İnsanlar @@ -999,10 +1013,15 @@ tr_TR: food: Gıda activity: Etkinlik travel: Seyahat + objects: Nesneler celebration: Kutlama + custom: Özel emojiler recent: Son zamanlarda kullanılmış default_tone: Cilt rengi yok light_tone: Açık cilt tonu + medium_light_tone: Orta açık cilt tonu + medium_tone: Orta cilt tonu + medium_dark_tone: Orta koyu cilt tonu dark_tone: Koyu cilt tonu composer: emoji: "Emoji :)" @@ -1048,7 +1067,9 @@ tr_TR: edit_reason_placeholder: "neden düzenleme yapıyorsunuz?" show_edit_reason: "(düzenleme sebebi ekle)" topic_featured_link_placeholder: "Başlığı olan bir link giriniz." + remove_featured_link: "Konudan bağlantıyı kaldır." reply_placeholder: "Buraya yazın. Biçimlendirmek için Markdown, BBCode ya da HTML kullanabilirsin. Resimleri sürükleyebilir ya da yapıştırabilirsin." + reply_placeholder_no_images: "Buraya yazın. Biçimlendirme için Markdown, BBCode ya da HTML kullanın." view_new_post: "Yeni gönderinizi görüntüleyin." saving: "Kaydediliyor" saved: "Kaydedildi!" @@ -1079,6 +1100,8 @@ tr_TR: ulist_title: "Madde İşaretli Liste" list_item: "Liste öğesi" help: "Markdown Düzenleme Yardımı" + collapse: "yazım alanını küçült" + abandon: "yazım alanını kapat ve taslağı sil" modal_ok: "Tamam" modal_cancel: "İptal" cant_send_pm: "Üzgünüz, %{username} kullanıcısına ileti gönderemezsiniz." @@ -1087,11 +1110,35 @@ tr_TR: body: "Bu ileti şu an sadece sana gönderiliyor!" admin_options_title: "Bu konu için isteğe bağlı görevli ayarları" notifications: + tooltip: + regular: + other: "{{count}} görülmemiş bildirim" + message: + other: "{{count}} okunmamış ileti" title: "@isim bahsedilişleri, gönderileriniz ve konularınıza verilen cevaplar, iletilerle vb. ilgili bildiriler" none: "Şu an için bildirimler yüklenemiyor." empty: "Bildirim yok." more: "daha eski bildirimleri görüntüle" total_flagged: "toplam bildirilen gönderiler" + mentioned: "{{username}} {{description}}" + group_mentioned: "{{username}} {{description}}" + quoted: "{{username}} {{description}}" + replied: "{{username}} {{description}}" + posted: "{{username}} {{description}}" + edited: "{{username}} {{description}}" + liked: "{{username}} {{description}}" + liked_2: "{{username}}, {{username2}} {{description}}" + liked_many: + other: "{{username}}, {{username2}} ve {{count}} diğer {{description}}" + private_message: "{{username}} {{description}}" + invited_to_private_message: "

            {{username}} {{description}}" + invited_to_topic: "{{username}} {{description}}" + invitee_accepted: "{{username}} davetinizi kabul etti" + moved_post: "{{username}} {{description}} taşıdı" + linked: "{{username}} {{description}}" + granted_badge: "'{{description}}' kazandı" + topic_reminder: "{{username}} {{description}}" + watching_first_post: "Yeni Konu {{description}}" alt: mentioned: "Bahsedildi, şu kişi tarafından" quoted: "Alıntılandı, şu kişi tarafından" @@ -1105,6 +1152,7 @@ tr_TR: linked: "Gönderinize bağlantı" granted_badge: "Rozet alındı" group_message_summary: "Grup gelen kutusundaki iletiler" + topic_reminder: "Bir hatırlatıcı" popup: mentioned: '{{username}}, "{{topic}}" başlıklı konuda sizden bahsetti - {{site_title}}' group_mentioned: '{{username}} sizden bahsetti "{{topic}}" - {{site_title}}' @@ -1126,6 +1174,7 @@ tr_TR: uploading: "Yükleniyor" select_file: "Dosya seçin" image_link: "resminizin yönleneceği bağlantı" + default_image_alt_text: resim search: sort_by: "Sırala" relevance: "Alaka" @@ -1136,12 +1185,19 @@ tr_TR: select_all: "Tümünü Seç" clear_all: "Tümünü Temizle" too_short: "Aradığın terim çok kısa." + result_count: + other: "{{term}} için {{count}}{{plus}} sonuç" title: "konu, gönderi, kullanıcı veya kategori ara" no_results: "Hiç bir sonuç bulunamadı." no_more_results: "Başka sonuç yok." searching: "Aranıyor..." post_format: "{{username}} tarafından #{{post_number}}" + results_page: "'{{term}}' için arama sonuçları" more_results: "Daha fazla sonuç var. Lütfen arama kriterlerini daraltın." + cant_find: "Aradığınızı bulamıyor musunuz?" + start_new_topic: "Belki de yeni bir konu oluşturmalısınız?" + or_search_google: "Ya da Google'la aramayı deneyin:" + search_google: "Google'la aramayı deneyin:" search_google_button: "Google" search_google_title: "Bu sitede ara" context: @@ -1153,20 +1209,29 @@ tr_TR: title: Gelişmiş Arama posted_by: label: Gönderen + in_category: + label: Kategorilendirilmiş in_group: label: Şu Grupta with_badge: label: Rozetle + with_tags: + label: Etiketlenmiş filters: likes: beğendiğim posted: gönderide bulunduğum watching: gözlediğim tracking: takip ettiğim + private: İletilerimde + bookmarks: İmledim first: en ilk gönderidir. pinned: tutturulmuş unpinned: tutturulmamış + seen: Okudum unseen: Okumadım wiki: wiki olan + images: resim(ler)i dahil et + all_tags: Yukarıdaki tüm etiketler statuses: label: Şöyle olan konular open: açık @@ -1294,22 +1359,50 @@ tr_TR: jump_reply_down: Daha sonraki cevaba geç deleted: "Konu silindi " topic_status_update: + title: "Konu Zamanlayıcısı" + save: "Zamanlayıcı Ayarla" + num_of_hours: "Saat değeri:" + remove: "Zamanlayıcıyı Kaldır" + publish_to: "Şuraya Yayınla:" when: "Ne zaman:" public_timer_types: Konu Zamanlayıcıları private_timer_types: Kullanıcı Konusu Zamanlayıcılar auto_update_input: none: "Bir zaman çerçevesi seçin" + later_today: "Bugünün sonlarında" tomorrow: "Yarın" + later_this_week: "Bu haftanın sonlarında" this_weekend: "Bu hafta sonu" next_week: "Gelecek hafta" + two_weeks: "İki Hafta" next_month: "Gelecek ay" + three_months: "Üç Ay" + six_months: "Altı Ay" one_year: "Bir Yıl" forever: "Sonsuza dek" pick_date_and_time: "Tarih ve saat seç" + set_based_on_last_post: "Son gönderiye göre kapat" + publish_to_category: + title: "Yayınlamayı Zamanla" + temp_open: + title: "Geçici Olarak Aç" + auto_reopen: + title: "Konuyu Otomatik Aç" + temp_close: + title: "Geçici Olarak Kapat" auto_close: + title: "Konuyu Otomatik Kapat" + label: "Şu kadar saat sonra konuyu otomatik kapat:" error: "Lütfen geçerli bir değer giriniz." + based_on_last_post: "Konudaki son gönderi en az bu kadar eski olmadıkça kapatma." + auto_delete: + title: "Konuyu Otomatik Sil" reminder: title: "Bana hatırlat" + status_update_notice: + auto_open: "Bu konu %{timeLeft} otomatik olarak açılacak." + auto_close: "Bu konu %{timeLeft} otomatik olarak kapanacak." + auto_publish_to_category: "Bu konu %{timeLeft} #%{categoryName} kategorisinde yayınlanacak." auto_close_title: 'Otomatik Kapatma Ayarları' auto_close_immediate: other: "Konudaki son gönderi zaten %{count} saat eski, bu yüzden konu hemen kapatılacak." @@ -1501,8 +1594,6 @@ tr_TR: description: other: {{count}} gönderi seçtiniz. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Alıntı" edit_reason: "Neden: " post_number: "gönderi {{number}}" @@ -2998,6 +3089,8 @@ tr_TR: upload: "Yükle" uploading: "Yükleniyor..." quit: "Belki Sonra" + staff_count: + other: "Topluluğunuzda %{count} görevli üye var." invites: add_user: "ekle" none_added: "Herhangi bir görevli davet etmediniz. Devam etmek istediğinize emin misiniz?" diff --git a/config/locales/client.ur.yml b/config/locales/client.ur.yml index 21604d91b5..0e7c1899e3 100644 --- a/config/locales/client.ur.yml +++ b/config/locales/client.ur.yml @@ -1443,8 +1443,6 @@ ur: one: آپ نے 1 پوسٹ منتخب کی ہے۔ other: آپ نے {{count}} پوسٹس منتخب کی ہیں۔ post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "اقتباس کریں" edit_reason: "وجہ:" post_number: "پوسٹ {{number}}" diff --git a/config/locales/client.vi.yml b/config/locales/client.vi.yml index 143b0a6fad..d4543a45ac 100644 --- a/config/locales/client.vi.yml +++ b/config/locales/client.vi.yml @@ -45,12 +45,16 @@ vi: other: "< %{count}s" x_seconds: other: "%{count}s" + less_than_x_minutes: + other: "< %{count} phút" x_minutes: other: "%{count}m" about_x_hours: other: "%{count}h" x_days: other: "%{count}d" + x_months: + other: "%{count} tháng" about_x_years: other: "%{count}y" over_x_years: @@ -83,6 +87,7 @@ vi: other: "còn %{count} năm" previous_month: 'Tháng trước' next_month: 'Tháng sau' + placeholder: ngày share: topic: 'chia sẻ chủ đề này' post: 'đăng #%{postNumber}' @@ -97,6 +102,7 @@ vi: split_topic: "tách chủ đề này lúc %{when}" invited_user: "đã mời %{who} lúc %{when}" invited_group: "đã mời %{who} lúc %{when}" + user_left: "%{who}tự xóa mình khỏi tin nhắn này %{when}" removed_user: "xoá %{who} lúc %{when}" removed_group: "xoá %{who} lúc %{when}" autoclosed: @@ -234,6 +240,7 @@ vi: uploading: "Đang tải lên..." uploading_filename: "Đang tải lên {{filename}}..." uploaded: "Đã tải lên!" + pasting: "Đang gõ" enable: "Kích hoạt" disable: "Vô hiệu hóa" undo: "Hoàn tác" @@ -326,6 +333,8 @@ vi: add_members: "Thêm thành viên" delete_member_confirm: "Xoá '%{username}' khỏi nhóm '%{group}' ?" name_placeholder: "Tên nhóm, không có khoảng trắng, tương tự như luật đặt tên người dùng" + public_admission: "Cho phép Thành viên tham gia nhóm một cách tự do (nhóm hiển thị công khai)" + public_exit: "Cho phép Thành viên thoát khỏi nhóm một cách tự do" empty: posts: "Không có bài viết nào của các thành viên trong nhóm này" members: "Không có thành viên nào trong nhóm này" @@ -342,8 +351,11 @@ vi: closed_group: Nhóm kín is_group_user: "Bạn là một thành viên của nhóm này" allow_membership_requests: "Cho phép người dùng gửi đơn xin gia nhập nhóm đến người quản lí nhóm." + membership_request_template: "đã tùy chỉnh để hiển thị cho người dùng khi gửi yêu cầu thành viên" membership_request: submit: "Gửi yêu c" + title: "Yêu cầu tham gia @%{group_name}" + reason: "Cho phép chủ sở hữu nhóm biết lý do bạn thuộc nhóm này" membership: "Thành viên" name: "Tên" user_count: "Số lượng thành viên" @@ -413,6 +425,7 @@ vi: '14': "Đang chờ xử lý" categories: all: "tất cả chuyên mục" + all_subcategories: "tất cả trong %{categoryName}" no_subcategory: "không có gì" category: "Chuyên mục" category_list: "Hiễn thị danh sách chuyên mục" @@ -482,6 +495,7 @@ vi: disable: "Khóa Notification" enable: "Cho phép Notification" each_browser_note: "Lưu ý: Bạn phải thay đổi trong cấu hình mỗi trình duyệt bạn sử dụng." + dismiss: 'Hủy bỏ' dismiss_notifications: "Bỏ qua tất cả" dismiss_notifications_tooltip: "Đánh dấu đã đọc cho tất cả các thông báo chưa đọc" first_notification: "Thông báo đầu tiên của bạn! Chọn để bắt đầu" @@ -700,6 +714,7 @@ vi: title: "Lời mời" user: "User được mời" sent: "Đã gửi" + none: "Không tìm thấy lời mời nào." truncated: other: "Hiện {{count}} thư mời đầu tiên" redeemed: "Lời mời bù lại" @@ -716,8 +731,10 @@ vi: rescinded: "Lời mời bị xóa" rescind_all: "Xóa tất cả lời m" rescinded_all: "Tất cả lời mời đã được xóa!" + rescind_all_confirm: "Bạn có muốn xóa bỏ tất cả các lời mời?" reinvite: "Mời lại" reinvite_all: "Gửi lại tất cả lời mời" + reinvite_all_confirm: "Bạn có chắc chắn gửi lại tất cả các lời mời?" reinvited: "Gửi lại lời mời" reinvited_all: "Tất cả lời mời đã được gửi lại" time_read: "Đọc thời gian" @@ -743,12 +760,19 @@ vi: title: "Tóm tắt" stats: "Thống kê" time_read: "thời gian đọc" + recent_time_read: "đã đọc gần đây" topic_count: other: "Chủ đề đã được tạo" post_count: other: "Bài viết đã được tạo" + likes_given: + other: "nhận" + likes_received: + other: "Đã nhận" days_visited: other: "Ngày đã ghé thăm" + topics_entered: + other: "chủ đề đã xem" posts_read: other: "Bài viết đã đọc" bookmark_count: @@ -1414,8 +1438,6 @@ vi: description: other: Bạn đã chọn {{count}} bài viết. post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "Trích dẫn" edit_reason: "Lý do: " post_number: "bài viết {{number}}" diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index 35835966f2..7b38316673 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -1014,7 +1014,6 @@ zh_CN: default_header_text: 选择... no_content: 无符合的结果 filter_placeholder: 搜索…… - create: "创建{{content}}" emoji_picker: filter_placeholder: 查找表情符号 people: 人物 @@ -1623,8 +1622,6 @@ zh_CN: description: other: 已选择 {{count}} 个帖子。 post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "引用" edit: " {{link}} {{replyAvatar}} {{username}}" edit_reason: "理由:" @@ -2299,11 +2296,13 @@ zh_CN: moderation_history: "管理日志" agree: "确认标记" agree_title: "确认这个标记有效且正确" - agree_flag_hide_post: "确认并隐藏帖子" agree_flag_hide_post_title: "隐藏帖子并自动发送私信给作者使其修改" agree_flag_restore_post: "确认并恢复帖子" agree_flag_restore_post_title: "恢复帖子为所有用户可见。" - agree_flag: "确认并保持帖子" + agree_flag_suspend: "暂停用户" + agree_flag_suspend_title: "同意标记并暂停用户。" + agree_flag_silence: "禁言用户" + agree_flag_silence_title: "同意标记并禁言用户。" agree_flag_title: "确认标记并保持帖子不变。" delete: "删除" delete_title: "删除标记指向的帖子。" diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml index 6fd464b20e..0a26c393e2 100644 --- a/config/locales/client.zh_TW.yml +++ b/config/locales/client.zh_TW.yml @@ -1404,8 +1404,6 @@ zh_TW: description: other: 你已選擇了 {{count}} 篇文章。 post: - reply: " {{replyAvatar}} {{usernameLink}}" - reply_topic: " {{link}}" quote_reply: "引用" edit_reason: "原因: " post_number: "文章 {{number}}" diff --git a/config/locales/server.ar.yml b/config/locales/server.ar.yml index 46334a785b..c2549c23b3 100644 --- a/config/locales/server.ar.yml +++ b/config/locales/server.ar.yml @@ -429,7 +429,6 @@ ar: change_failed_explanation: "حاولت تخفيض رتبة %{user_name} إلى '%{new_trust_level}'. أيضا مستوى الثقة لهم حاليا '%{current_trust_level}'. %{user_name} سيبقى في '%{current_trust_level}' - إذا رغبت في تخفيض رتبة عضو أنظر لمستوى الثقة أولاً." rate_limiter: slow_down: "لقد قمت بهذا الإجراء عدّة مرات. فحاول مجددا لاحقا." - too_many_requests: "لدينا حد يومي لعدد المرات التي يمكن للعمل أن ينجز بها. رجاءا انتظر %{time_left} قبل المحاولة مجدداً." by_type: first_day_replies_per_day: "لقد وصلت للعدد الأقصى للردود التي يمكن للعضو الجديد إنشائها في يومهم الأول. رجاء انتظر %{time_left} قبل المحاولة مجددا." first_day_topics_per_day: "لقد وصلت للعدد الأقصى للمواضيع التي يمكن للعضو الجديد إنشائها في يومهم الأول. رجاء انتظر %{time_left} قبل المحاولة مجددا." @@ -917,7 +916,6 @@ ar: image_magick_warning: 'ضُبط الخادوم لإنشاء مصغّرات للصّور الكبيرة، ولكنّ ImageMagick غير مثبّت. ثبّت ImageMagick مستخدمًا مدير الحزم الذي تفضّله أو نزّل آخر إصدارة.' failing_emails_warning: 'يوجد %{num_failed_jobs} مهام بريد إلكتروني فشلت. تحقق من app.yml الخاص بك وتأكد من إعدادات خادم البريد أنها صحيحة. أنظر للمهام الفاشلة في Sidekiq.' subfolder_ends_in_slash: "إعدادات المجلدات الداخلية خاطئ;ال DISCOURSE_RELATIVE_URL_ROOT يجب ان تنتهي ب سلاش." - missing_mailgun_api_key: "ضُبط الموقع ليُرسل الرّسائل الإلكترونيّ عبر mailgun ولكنّك لم توفّر مفتاح API ليُستخدم في تأكيد رسائل webhook." site_settings: censored_words: "الكلمات التي ستُستبدل آليًّا ب‍ ■■■■" censored_pattern: "نمط التّعبير النّمطيّ الذي سيُستبدل آليًّا ب‍ ■■■■" @@ -926,12 +924,10 @@ ar: set_locale_from_accept_language_header: "اختيار لغة الواجة للمستخدمين المتخفون طبقا للغة المختارة بمتصفح الشبكة. ( إعداد تجريبى، لا يعمل مع ذاكرة المتصفح )" min_post_length: "الحد الأدنى المسموح به لطول المنشور بالأحرف" min_first_post_length: "الحد الأدنى المسموح به لطول أول منشور (المنشور الاول بالموضوع) بالأحرف" - min_private_message_post_length: "الحد الأدنى المسموح به لطول المنشور بالأحرف في الرسائل" max_post_length: "الحد الأقصي المسموح به لطول المنشور بالأحرف" topic_featured_link_enabled: "السماح بنشر روابط في الموضوعات" min_topic_title_length: "الحد الأدنى المسموح به موضوع طول اللقب في الأحرف" max_topic_title_length: "الحد الأعلى المسموح به لطول عنوان موضوع في الأحرف" - min_private_message_title_length: "الحد الأدنى المسموح به لطول عنوان لرسالة في الأحرف" min_search_term_length: "الحد الأدنى الصالح لطول مصطلح في الأحرف" search_tokenize_chinese_japanese_korean: "ارغام الباحث على تجميع احرف اللغات الصينية واليابانية والكورية حتى على المواقع الغير معدة لتلك اللغات." search_prefer_recent_posts: "اذا كان البحث في المنتدي بطئ, هذا الخيار سوف يحاول فهرسة اخر المنشورات اولاً" @@ -1090,7 +1086,6 @@ ar: max_bookmarks_per_day: "أقصى عدد من العلامات لكلّ مستخدم يوميًّا." max_edits_per_day: "أقصى عدد من عمليّات التّحرير لكلّ مستخدم يوميًّا." max_topics_per_day: "أقصى عدد للمواضيع التي يمكن للعضو إنشائها باليوم." - max_private_messages_per_day: "أقصى عدد لرسائل الأعضاء التي يمكن إنشائها باليوم." max_invites_per_day: "أقصى عدد للدعوات التي يمكن للعضو إرسالها باليوم." max_topic_invitations_per_day: "أقصى عدد لدعوات الموضوع التي يمكن للعضو إرسالها باليوم." alert_admins_if_errors_per_minute: "عدد الأخطاء في الدقيقة الواحدة لكى يرسل تنبيه للمدير. قيمة 0 تعطل هذه الميزة. ملاحظة: تتطلب إعادة تشغيل." @@ -1263,7 +1258,6 @@ ar: enforce_square_emoji: "أجبِر النّسبة الباعيّة لكلّ الإيموجي لتكون مربّعة." approve_post_count: "عدد المنشورات للمستخدم الجديد او الاساسي يجب ان تتم الموافقه عليه " approve_unless_trust_level: "مشاركات للأعضاء أدنى من مستوى الثقة هذا يجب أن تتم الموافقة عليها." - default_email_private_messages: "أرسل بريد إلكتروني عندما يراسل شخص ما العضو إفتراضيا." default_email_direct: "ارسل بريد الكتروني عندما يقوم احدهم بالرد/الاقتباس الي/ذكر او دعوه مستخدم افتراضيا" default_email_mailing_list_mode: "ارسل بريد إلكتروني لكل مشاركة جديدة افتراضيا." disable_mailing_list_mode: "عدم السماح للمستخدمين بتفعيل خيار المراسله الجماعيه" diff --git a/config/locales/server.bs_BA.yml b/config/locales/server.bs_BA.yml index c56485b2c9..f99df09f81 100644 --- a/config/locales/server.bs_BA.yml +++ b/config/locales/server.bs_BA.yml @@ -175,7 +175,6 @@ bs_BA: title: "korisnik" change_failed_explanation: "You attempted to demote %{user_name} to '%{new_trust_level}'. However their trust level is already '%{current_trust_level}'. %{user_name} will remain at '%{current_trust_level}' - if you wish to demote user lock trust level first" rate_limiter: - too_many_requests: "We have a daily limit on how many times that action can be taken. Please wait %{time_left} before trying again." hours: one: "1 sat" few: "Par sati" @@ -357,11 +356,9 @@ bs_BA: delete_old_hidden_posts: "Auto-delete any hidden posts that stay hidden for more than 30 days." allow_user_locale: "Allow users to choose their own language interface preference" min_post_length: "Minimum allowed post length in characters" - min_private_message_post_length: "Minimum allowed post length in characters for private messages" max_post_length: "Maximum allowed post length in characters" min_topic_title_length: "Minimum allowed topic title length in characters" max_topic_title_length: "Maximum allowed topic title length in characters" - min_private_message_title_length: "Minimum allowed title length for a private message in characters" min_search_term_length: "Minimum valid search term length in characters" allow_duplicate_topic_titles: "Allow topics with identical, duplicate titles." unique_posts_mins: "How many minutes before a user can make a post with the same content again" @@ -461,7 +458,6 @@ bs_BA: max_bookmarks_per_day: "Maximum number of bookmarks per user per day." max_edits_per_day: "Maximum number of edits per user per day." max_topics_per_day: "Maximum number of topics a user can create per day." - max_private_messages_per_day: "Maximum number of private messages users can create per day." suggested_topics: "Number of suggested topics shown at the bottom of a topic." limit_suggested_to_category: "Only show topics from the current category in suggested topics." clean_up_uploads: "Remove orphan unreferenced uploads to prevent illegal hosting. WARNING: you may want to back up of your /uploads directory before enabling this setting." diff --git a/config/locales/server.ca.yml b/config/locales/server.ca.yml index 21a6465faf..e65ec18c10 100644 --- a/config/locales/server.ca.yml +++ b/config/locales/server.ca.yml @@ -392,7 +392,6 @@ ca: change_failed_explanation: "Has provat de degradar %{user_name} a '%{new_trust_level}'. En qualsevol cas, el seu nivell de confiança ja és '%{current_trust_level}'. %{user_name} restarà a '%{current_trust_level}' - Si vols degradar una persona usuària, bloca-li abans el nivell de confiança" rate_limiter: slow_down: "Has provat de fer el mateix moltes vegades, prova-ho més tard." - too_many_requests: "Tenim un límit diari sobre quants cops es pot realitzar aquesta acció. Si us plau, espera %{time_left} abans de tornar a provar-ho." by_type: first_day_replies_per_day: "Has arribat a la quantitat màxima de respostes que una nova persona usuària pot crear durant el seu primer dia. Si us plau, espera %{time_left} abans de tornar a provar-ho." first_day_topics_per_day: "Has arribat a la quantitat màxima de temes que una nova persona usuària pot crear durant el seu primer dia. Si us plau, espera %{time_left} abans de tornar a provar-ho." @@ -771,7 +770,6 @@ ca: email_polling_errored_recently: one: "L'enquesta per correu electrònic ha generat un error durant les darreres 24 hores. Fes un cop d'ull als registres per saber-ne més." other: "L'enquesta per correu electrònic ha generat %{count} errors durant les darreres 24 hores. Fes un cop d'ull als registres per saber-ne més." - missing_mailgun_api_key: "El servidor està configurat per enviar correus amb mailgun però no has facilitat una clau API emprada per verificar els missatges de ganxo de web." bad_favicon_url: "La càrrega de favicon està fallant. Revisa la configuració de favicon_url a la Configuració del lloc." poll_pop3_timeout: "S'ha esgotat del temps de connexió al servidor POP3. No s'ha pogut lliurar el correu entrant. Si us plau, revisa la teva configuració de POP3 i el proveïdor de serveis." poll_pop3_auth_error: "La connexió al servidor POP3 està fallant amb un error d'autenticació. Si us plau, revisa la teva configuració de POP3." @@ -783,13 +781,11 @@ ca: set_locale_from_accept_language_header: "configura la llengua d'interfície per a persones anònimes des dels encapçalaments dels seus navegadors. (EXPERIMENTAL, no funciona amb memòria cau anònima)" min_post_length: "Mínim permès en caràcters per a l'extensió de publicacions " min_first_post_length: "Mínim permès en caràcters per a l'extensió de primera publicació (cos del tema)" - min_private_message_post_length: "Mínim permès en caràcters per a l'extensió de publicacions a missatges" max_post_length: "Màxim permès en caràcters per a l'extensió de publicacions " topic_featured_link_enabled: "Activa la publicació d'enllaç amb temes." show_topic_featured_link_in_digest: "Mostra el tema de l'enllaç destacat al correu electrònic de resum automàtic." min_topic_title_length: "Mida mínima permesa del títol del tema en caràcters " max_topic_title_length: "Mida màxima permesa del títol del tema en caràcters " - min_private_message_title_length: "Mida mínima permesa del títol d'un missatge en caràcters " min_search_term_length: "Mida mínima permesa d'un camp de cerca en caràcters " search_tokenize_chinese_japanese_korean: "Força la cerca per assegurar les dades en Xinès/Japonès/Coreà fins i tot en llocs que no siguin en aquestes llengües" search_prefer_recent_posts: "Si la cerca al teu fòrum va lenta, aquesta opció prova primer amb un índex de publicacions recents" @@ -835,7 +831,6 @@ ca: summary_likes_required: "Mínim nombre de m'agrades a un tema abans d'activar 'Resumeix aquest tema'" summary_percent_filter: "Quan una persona clica 'Resumeix aquest tema', mostra el '% o' principal de publicacions" summary_max_results: "Màxim de publicacions resultat de 'Resumeix aquest tema'" - enable_private_messages: "Permet que les persones amb nivell de confiança 1 puguin crear missatges i respondre'ls (configurable amb el mínim de nivell de confiança per enviar missatges). Fixa't que l'equip sempre pot enviar qualsevol mena de missatges." enable_long_polling: "El bus de missatges emprat per a la notificació pot fer servir el mostreig llarg" long_polling_base_url: "La URL bàsica emprada per a mostreig llarg (quan una xarxa de lliurament de continguts serveix contingut dinàmic, assegura't de configurar-ho per llençar l'origen). Per exemple, http://origin.site.com" long_polling_interval: "Quantitat de temps que el servidor hauria d'esperar abans de respondre clients quan no hi ha dades d'enviament (connectat només a persones usuàries)" @@ -955,7 +950,6 @@ ca: max_bookmarks_per_day: "Quantitat màxima de preferits per persona i dia." max_edits_per_day: "Quantitat màxima d'edicions per persona i dia." max_topics_per_day: "Quantitat màxima de temes que una persona pot crear cada dia." - max_private_messages_per_day: "Quantitat màxima de missatges que una persona pot crear cada dia." max_invites_per_day: "Quantitat màxima d'invitacions que una persona pot enviar cada dia." max_topic_invitations_per_day: "Quantitat màxima d'invitacions a temes que una persona por enviar cada dia." alert_admins_if_errors_per_minute: "Quantitat d'errors per minut per desencadenar una alerta admin. Un valor 0 inhabilita aquesta característica. NOTA: cal reiniciar." @@ -1019,7 +1013,6 @@ ca: max_users_notified_per_group_mention: "Quantitat màxima de persones que poden rebre alertes si un grup és mencionat (si el valor límit no troba alertes, serà augmentat)" create_thumbnails: "Crea miniatures i finestres d'imatge de massa amplada per cabre a una publicació." email_time_window_mins: "Espera (n) minuts abans d'enviar qualsevol alerta de correu, per tal de possibilitar l'edició i la finalització de les publicacions." - private_email_time_window_seconds: "Espera (n) segons abans d'enviar qualsevol correu privat d'alerta, per tal de possibilitar l'edició i la finalització dels missatges." email_posts_context: "Quantes respostes prèvies per incloure com a context a correus d'alerta." flush_timings_secs: "Amb quanta freqüència descarreguem al servidor les dades de temporització, en segons." title_max_word_length: "La mida màxima permesa de la paraula, en caràcters al títol d'un tema." @@ -1181,7 +1174,6 @@ ca: code_formatting_style: "El botó de codi al compositor serà per defecte d'aquest estil de format de codi" default_email_digest_frequency: "Amb quina freqüència les persones rebran els correus resumits per defecte." default_include_tl0_in_digests: "Inclou publicacions de noves persones a correus resumits per defecte. Les persones ho poden canviar a les seves preferències." - default_email_private_messages: "Envia un correu s'enviïn missatges a la persona usuària per defecte." default_email_direct: "Envia un correu quan se citi/respongui/mencioni la persona usuària o persones convidades per defecte." default_email_mailing_list_mode: "Envia un correu per a cada nova publicació per defecte." default_email_mailing_list_mode_frequency: "Per defecte, les persones que activen el mode llista de correu rebran correus amb aquesta freqüència." diff --git a/config/locales/server.cs.yml b/config/locales/server.cs.yml index cd757daaa3..3b2076df2f 100644 --- a/config/locales/server.cs.yml +++ b/config/locales/server.cs.yml @@ -103,6 +103,7 @@ cs: user_exists: "Netřeba posílat pozvánku na %{email}. Tento email je veden u tohoto účtu!" bulk_invite: file_should_be_csv: "Nahraný soubor by měl být ve formátu csv." + error: "Nastala chyba při nahrávání souboru. Prosím opakujte akci později." backup: operation_already_running: "Právě probíhá operace %{operation}. V tuto chvíli nelze zahájit novou operaci %{operation}." backup_file_should_be_tar_gz: "Záloha by měla být archiv s příponou .tar.gz." @@ -280,7 +281,7 @@ cs: image_placeholder: broken: "Tento obrázek je rozbitý" rate_limiter: - too_many_requests: "Děláte tuto akci příliš často. Prosím počkejte %{time_left} a zkuste to znovu." + slow_down: "Tuto akci provádíte příliš často, zkuste to prosím později." by_type: pms_per_day: "Odeslal jsi maximum povolených zpráv za den. Další můžeš odeslat za {time_left}." hours: @@ -446,6 +447,7 @@ cs: long_form: 'hlasoval pro tento příspěvek' user_activity: no_bookmarks: + self: "Nemáte žádné příspěvky v záložkách. Přidání do záložek vám umožní téma v budoucnu snadněji nalézt." others: "Žádné záložky." no_likes_given: self: "Zatím se ti nelíbil žádný příspěvek." @@ -686,13 +688,18 @@ cs: types: category: 'Kategorie' user: 'Uživatelé' - original_poster: "Původní zasilatel" + original_poster: "Autor tématu" most_posts: "Více příspěvků" - most_recent_poster: "Poslední zasilatel" - frequent_poster: "Častý zasilatel" + most_recent_poster: "Poslední přispěvatel" + frequent_poster: "Častý přispěvatel" redirected_to_top_reasons: new_user: "Vítejte v naší komunitě! Tohle jsou poslední populární témata." not_seen_in_a_month: "Vítejte zprátky! Chvíli jsme se neviděli. Tohle jsou nejpopulárnější témata od vaší poslední návštěvy." + move_posts: + new_topic_moderator_post: + one: "Příspěvek byl oddělen do nového tématu: %{topic_link}" + few: "%{count}příspěvky byly odděleny do nového tématu: %{topic_link}" + other: "%{count}příspěvky byly odděleny do nového tématu: %{topic_link}" topic_statuses: archived_enabled: "Toto téma je archivováno. Je zmraženo a již nemůže být nijak změněno." archived_disabled: "Toto téma je vráceno z archivu. Již není zmraženo a může být měněno." @@ -788,7 +795,7 @@ cs: post_not_found: "Can't find a post with id %{post_id}" notification_already_read: "The notification this email is about has already been read" topic_nil: "post.topic is nil" - post_deleted: "post was deleted by the author" + post_deleted: "příspěvek byl odstraněn autorem" user_suspended: "user was suspended" already_read: "user has already read this post" message_blank: "message is blank" diff --git a/config/locales/server.da.yml b/config/locales/server.da.yml index 59a8da7edb..218bd22837 100644 --- a/config/locales/server.da.yml +++ b/config/locales/server.da.yml @@ -421,7 +421,6 @@ da: change_failed_explanation: "Du har forsøgt at degradere %{user_name} til '%{new_trust_level}'. Imidlertid er deres niveau af tillid allerede '%{current_trust_level}'. %{user_name} vil forblive'%{current_trust_level}' - hvis du ønsker at degradere brugeren, skal du først låse brugerens tillids niveau / Trust Level" rate_limiter: slow_down: "Du har udført denne handling for mange gange, prøv igen senere" - too_many_requests: "Vi har en daglig grænse for hvor mange gange den pågældende handling kan udføres. Vent venligst %{time_left} før du prøver igen." by_type: first_day_replies_per_day: "Du har nået det maksimalt antal tilladte svar, en ny bruger kan lave på deres første dag. Vent venligst %{time_left} før du fortsætter med at besvare tanker og indlæg." first_day_topics_per_day: "Du har nået det maksimalt antal af tilladte emner, en ny bruger kan lave på første dag.\nVent venligst %{time_left} før du prøver igen." @@ -807,7 +806,6 @@ da: email_polling_errored_recently: one: "Email afstemning har afstedkommet en fejl i de seneste 24 timer. Venligst se the logs for detaljer." other: "Email afstemning har afstedkommet %{count} fejl i de seneste 24 timer. Se venligst the logs for detaljer." - missing_mailgun_api_key: "Serveren er konfigureret til at sende emails via Mailgun, men der er ikke indtastet en API nøgle som bruges til at verificere webhook beskederne." bad_favicon_url: "Favicon kan ikke loades. Tjek dine favicon_url indstilling i Site Settings" poll_pop3_timeout: "Forbindelsen til til POP3 serveren er udløbet. Indkomne mail kunne ikke hentes. Venligst tjek POP3 settings og udbyderen." poll_pop3_auth_error: "Forbindelsen til POP3 serveren melder fejl. Venligst tjek POP3 settings." @@ -819,13 +817,11 @@ da: set_locale_from_accept_language_header: "set interface language for anonymous users from their web browser's language headers. (EXPERIMENTAL, does not work with anonymous cache)" min_post_length: "Minimumlængde tilladt for indlæg i tegn" min_first_post_length: "Minimum tilladte antal tegn (i emne felt) i første indlæg" - min_private_message_post_length: "Minimumlængde tilladt for indlæg i tegn" max_post_length: "Maksimal længde af indlæg i tegn" topic_featured_link_enabled: "Tillad opslag af et link med emner." show_topic_featured_link_in_digest: "Vis link med det fremhævede emne i e-mail-sammendraget." min_topic_title_length: "Minimumslængde af emnetitel i tegn." max_topic_title_length: "Maksimumslængde af emnetitel i tegn." - min_private_message_title_length: "Minimumslængde af emnetitel i tegn." min_search_term_length: "Antal tegn i søgefeltet skal have et minimum" search_tokenize_chinese_japanese_korean: "Tving søg til at \"tokenize\" Chinese/Japanese/Korean selv på sites som ikke er CJK" search_prefer_recent_posts: "Såfremt søgning på forum er langsomt, kan denne option indeksere og vise nyeste indlæg først" @@ -869,7 +865,6 @@ da: summary_likes_required: "Minimum antal likes på et emne før 'opsummer dette emne' er aktiveret" summary_percent_filter: "Når en bruger kllikker 'Opsummer dette Emne' vises top % af indlæg" summary_max_results: "Max antal indlæg indeholdt i 'Opsummer dette Emne'" - enable_private_messages: "Tillad Trust Level 1 (kan konfigureres minimum trust level til at sende beskeder), brugere til at sende og svare på beskeder. Bemærk at moderatorer beskeder, uanset hvad." enable_long_polling: "Message bus til underretninger kan bruge long polling" long_polling_base_url: "URL anvendt for afsteminger (når CDN leverer dynamisk indhold, så sæt dette til oprindelig / orginal) f.eks: http://origin.site.com" long_polling_interval: "Mængde tid før serveren bør vente før den svarer klienter når der ikke er ny data at sende (kun for brugere der er logget ind)" @@ -991,7 +986,6 @@ da: emoji_set: "Hvordan kunne du tænke dig din emoji?" default_email_digest_frequency: "Hvor ofte brugere som standard modtager mails med sammendrag." default_include_tl0_in_digests: "Inkluder som standard indlæg fra nye brugre i mails med sammendrag. Brugere kan ændre dette i deres indstillinger." - default_email_private_messages: "Send som standard en email når nogen sender brugeren en besked." default_email_direct: "Send som standard en email når nogen citerer/svarer/nævner/inviterer brugeren." default_email_mailing_list_mode: "Send som standard en email for hvert nyt indlæg." default_email_always: "Send som standard en email-notifikation selv hvis brugeren er aktiv." diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml index 2fa6cc8568..7388ad3a1c 100644 --- a/config/locales/server.de.yml +++ b/config/locales/server.de.yml @@ -457,7 +457,6 @@ de: broken: "Dieses Bild ist beschädigt" rate_limiter: slow_down: "Du hast diese Aktion zu oft durchgeführt. Versuche es später wieder." - too_many_requests: "Diese Aktion kann nur ein begrenztes Mal pro Tag durchgeführt werden. Bitte warte %{time_left} bis zum nächsten Versuch." by_type: first_day_replies_per_day: "Du hast die maximale Anzahl an Antworten erreicht, die ein neuer Benutzer am ersten Tag erstellen kann. Bitte warte %{time_left}, bis Du es wieder versuchst." first_day_topics_per_day: "Du hast die maximale Anzahl an Themen erreicht, die ein neuer Benutzer am ersten Tag erstellen kann. Bitte warte %{time_left}, bis Du es wieder versuchst." @@ -861,7 +860,6 @@ de: email_polling_errored_recently: one: "Beim Abrufen von E-Mails ist in den letzten 24 Stunden ein Fehler aufgetreten. Weitere Informationen findest du im Fehlerprotokoll." other: "Beim Abrufen von E-Mails sind in den letzten 24 Stunden %{count} Fehler aufgetreten. Weitere Informationen findest du im Fehlerprotokoll." - missing_mailgun_api_key: "Der Server wurde für den E-Mail-Versand über mailgun konfiguriert, aber du hast keinen API-Schlüssel hinterlegt, um die WebHook-Nachrichten zu überprüfen." bad_favicon_url: "Das Favicon lässt sich nicht laden. Prüfe die favicon_url-Einstellung in den Einstellungen." poll_pop3_timeout: "Die Verbindung zum POP3-Server schlägt mit einer Zeitüberschreitung fehl. Eingehende E-Mails konnten nicht abgerufen werden. Überprüfe deine POP3-Einstellungen." poll_pop3_auth_error: "Die Verbindung zum POP3-Server schlägt mit einem Authentisierungsfehler fehl. Überprüfe deine POP3-Einstellungen." @@ -873,13 +871,11 @@ de: set_locale_from_accept_language_header: "Sprache der Benutzeroberfläche für anonyme Benutzer an Hand der Spracheinstellung ihres Browsers wählen (EXPERIMENTELL, funktioniert nicht mit Caches für anonyme Benutzer)" min_post_length: "Minimal zulässige Beitragslänge in Zeichen." min_first_post_length: "Minimal zulässige Länge des ersten Beitrags (eines Themas) in Zeichen" - min_private_message_post_length: "Minimale zulässige Beitragslänge in Zeichen für Nachrichten" max_post_length: "Maximale zulässige Beitragslänge in Zeichen." topic_featured_link_enabled: "Beitrag mit Link zu hervorgehobenen Themen erlauben" show_topic_featured_link_in_digest: "Zeige den Hervorgehobene Themen Link in der E-Mail-Zusammenfassung." min_topic_title_length: "Minimale zulässige Titellänge von Themen in Zeichen." max_topic_title_length: "Maximale zulässige Titellänge von Themen in Zeichen." - min_private_message_title_length: "Minimale zulässige Titellänge von Nachrichten in Zeichen." min_search_term_length: "Minimale zulässige Länge der Suche in Zeichen." search_tokenize_chinese_japanese_korean: "Zwinge die Suche Chinesisch, Japanisch und Koreanisch zu erkennen, auch wenn die Site keine dieser Sprachen nutzt" search_prefer_recent_posts: "Wenn das Durchsuchen deines großen Forums langsam ist, dann versucht diese Option zuerst einen Index der letzten Beiträge." @@ -934,8 +930,6 @@ de: summary_likes_required: "Mindestanzahl an Likes in einem Thema, bevor die \"Thema zusammenfassen\" Funktion aktiviert wird." summary_percent_filter: "Zeige die besten (n)% der Beiträge eines Themas in der \"Thema zusammenfassen\"-Ansicht." summary_max_results: "Maximale Anzahl der sichtbaren Beiträge beim Zusammenfassen von Themen" - enable_private_messages: "Erlaube Benutzer mit der Vertrauensstufe 1 (konfigurierbar über die minimale Vertrauensstufe zum Senden von Nachrichten), Nachrichten zu erstellen und auf Nachrichten zu antworten. Beachte, dass das Team immer Nachrichten und Antworten senden können." - enable_private_email_messages: "Erlaube Benutzern mit Vertrauensstufe 4 (konfigurierbar über die Mindestvertrauensstufe, um Nachrichten zu senden), private E-Mail-Nachrichten zu senden. Bitte beachte, dass Team-Mitglieder immer und in jedem Fall Nachrichten senden können." enable_long_polling: "Nachrichtenbus für Benachrichtigungen kann Long-Polling nutzen." long_polling_base_url: "Basis-URL für Long Polling (wenn zum Ausliefern von dynamischen Inhalten ein CDN verwendet wird, setze es auf Origin Pull), z. B. http://origin.site.com" long_polling_interval: "Wartezeit, bevor der Server auf Clients reagiert, wenn keine Daten gesendet werden müssen (nur für angemeldete Benutzer)" @@ -1070,7 +1064,6 @@ de: max_bookmarks_per_day: "Maximale Anzahl der Lesezeichen pro Benutzer und Tag." max_edits_per_day: "Maximale Anzahl der Bearbeitungen pro Benutzer und Tag." max_topics_per_day: "Maximale Anzahl der Themen, die ein Benutzer pro Tag erstellen kann." - max_private_messages_per_day: "Maximale Zahl Direktnachrichten, die ein Benutzer pro Tag erstellen kann." max_invites_per_day: "Maximale Zahl an Einladungen, die ein Benutzer pro Tag verschicken kann." max_topic_invitations_per_day: "Maximale Zahl an Thema-Einladungen, die ein Benutzer pro Tag verschicken kann." max_logins_per_ip_per_hour: "Maximale Anzahl der erlaubten Anmeldungen pro IP-Adresse und Stunde" @@ -1141,7 +1134,6 @@ de: enable_mentions: "Erlaube Benutzern, andere Benutzer zu erwähnen." create_thumbnails: "Erzeuge ein Vorschaubild und eine Lightbox für Bilder, die zu groß sind, um in einem Beitrag angezeigt zu werden." email_time_window_mins: "Warte (n) Minuten bevor eine E-Mail-Benachrichtigung geschickt wird, um Benutzern Gelegenheit zu geben, ihre Beiträge abschließend bearbeiten zu können." - private_email_time_window_seconds: "Warte (n) Sekunden bevor eine E-Mail-Benachrichtigung geschickt wird, um Benutzern Gelegenheit zu geben, ihre Beiträge abschließend bearbeiten zu können." email_posts_context: "Anzahl der Antworten, welche als Kontext einer Benachrichtigungs-E-Mail hinzugefügt werden." flush_timings_secs: "Sekunden, nach denen Zeiteinstellungen auf den Server übertragen werden." title_max_word_length: "Maximal erlaubte Wortlänge in Thementiteln, in Zeichen." @@ -1320,7 +1312,6 @@ de: watched_words_regular_expressions: "Beobachtete Wörter sind reguläre Ausdrücke." default_email_digest_frequency: "Wie häufig sollen Benutzer standardmäßig E-Mail-Zusammenfassungen erhalten?" default_include_tl0_in_digests: "Beiträge von neuen Benutzern in E-Mail-Zusammenfassungen standardmäßig anzeigen. Benutzer können dies in ihren Einstelllungen ändern." - default_email_private_messages: "Sende einem Benutzer standardmäßig eine E-Mail, wenn dieser eine Nachricht von einem anderen Benutzer erhält." default_email_direct: "Aktiviere standardmäßig, dass eine E-Mail gesendet wird, sobald ein Benutzer einen anderen Benutzer zitiert / einem anderen Benutzer antwortet / oder einen anderen Benutzer erwähnt bzw. einlädt." default_email_mailing_list_mode: "Sende standardmäßig eine E-Mail für jeden neuen Beitrag." default_email_mailing_list_mode_frequency: "Benutzer, die den Mailinglisten-Modus einschalten, werden standardmäßig so häufig eine E-Mail erhalten." @@ -2451,21 +2442,6 @@ de: signup_after_approval: title: "Konto bestätigen nach Genehmigung" subject_template: "You've been approved on %{site_name}!" - text_body_template: | - Willkommen bei%{site_name}! - - Ein Team-Mitglied hat dein Benutzerkonto auf %{site_name} bestätigt. - - Klicke auf den folgenden Link, um dein neues Konto zu bestätigen und zu aktivieren: - %{base_url}/u/activate-account/%{email_token} - - Wenn sich der obenstehende Link nicht anklicken lässt, versuche ihn zu kopieren und in die Adresszeile deines Webbrowsers einzufügen. - - %{new_user_tips} - - Wir glauben an [zivilisiertes Community-Verhalten](%{base_url}/guidelines) zu jeder Zeit. - - Genieße deinen Aufenthalt! signup: title: "Konto bestätigen nach Anmeldung" subject_template: "[%{email_prefix}] Bestätige dein neues Konto" diff --git a/config/locales/server.el.yml b/config/locales/server.el.yml index f271eb0d4d..1b01dce4bb 100644 --- a/config/locales/server.el.yml +++ b/config/locales/server.el.yml @@ -454,7 +454,6 @@ el: broken: "Η εικόνα είναι χαλασμένη" rate_limiter: slow_down: "Η εντολή αυτή έχει εκτελεστεί πάρα πολλές φορές, δοκιμάστε ξανά αργότερα." - too_many_requests: "Υπάρχει περιορισμός στις φορές που μπορεί να επαναληφθεί αυτή η ενέργεια. Παρακαλούμε παριμένετε %{time_left} προτού δοκιμάσετε ξανά." by_type: first_day_replies_per_day: "Έχετε φτάσει τον μέγιστο αριθμό απαντήσεων που δικαιούται ένας νέος χρήστης την πρώτη του μέρα. Παρακαλώ περιμένετε %{time_left} πριν προσπαθήσετε ξανά." first_day_topics_per_day: "Έχεται φτάσει τον μέγιστο αριθμό θεμάτων που δικαιούται ένας νέος χρήστης την πρώτη του μέρα. Παρακαλώ περιμένετε %{time_left} πριν προσπαθήσετε ξανά." @@ -860,7 +859,6 @@ el: email_polling_errored_recently: one: "To Email polling έχει παράγει ένα σφάλμα τις τελευταίες 24 ώρες . Δείτε τα αρχεία καταγραφής για περισσότερες λεπτομέρειες. " other: "To Email polling έχει παράγει %{count} σφάλματα τις τελευταίες 24 ώρες . Δείτε τα αρχεία καταγραφής για περισσότερες λεπτομέρειες. " - missing_mailgun_api_key: "Ο διακομιστής εχεί ρυθμιστεί για να στέλνει email μέσω του mailgun, αλλά δεν εχεις ρυθμίσει το κλειδί API που χρειάζεται για να επαληθευτούν τα webhook μυνήματα. " bad_favicon_url: "Το favicon δεν μπορεί να φορτωθεί 'Ελεγξε το favicon_url στις Ρυθμίσεις Iστοσελίδας" poll_pop3_timeout: "Το χρονικό όριο σύνδεσης με τον διακομιστή POP3 έληξε. Τα εισερχόμενα email δεν μπορούν να ανακτηθούν. Παρακαλώ ελέγξτε τις POP3 ρυθμίσεις σας και τον πάροχο υπηρεσιών." poll_pop3_auth_error: "Υπήρξε σφάλμα ελεχγου ταυτότητας κατά την σύνδεση με τον διακομιστή POP3. Παρακαλώ όπως ελέξετε τις POP3 ρυθμίσεις σας. " @@ -872,13 +870,11 @@ el: set_locale_from_accept_language_header: "θέστε την γλώσσα του interface για ανώνυμους χρήστες από τη γλώσσα που χρησιμοποιεί το πρόγραμμα περιήγησης τους. (ΠΕΙΡΑΜΑΤΙΚΟ ΧΑΡΑΚΤΗΡΙΣΤΙΚΟ, δεν λειτουργεί με ανώνυμη cache)" min_post_length: "Ελάχιστο επιτρεπτό μήκος ανάρτησης σε χαρακτήρες" min_first_post_length: "Ελάχιστο επιτρεπτό μήκος πρώτης ανάρτησης (σώμα νήματος) σε χαρακτήρες" - min_private_message_post_length: "Ελάχιστο επιτρεπτό μήκος ανάρτησης σε χαρακτήρες για μηνύματα" max_post_length: "Μέγιστο επιτρεπτό μήκος ανάρτησης σε χαρακτήρες" topic_featured_link_enabled: "Ενεργοποίηση ανάρτησης ενός συνδέσμου με νήματα." show_topic_featured_link_in_digest: "Δείξε το σύνδεσμο προτεινόμενου νήματος στο συνοπτικό email." min_topic_title_length: "Ελάχιστο επιτρεπτό μήκος τίτλου νήματος σε χαρακτήρες" max_topic_title_length: "Μέγιστο επιτρεπτό μήκος τίτλου νήματος σε χαρακτήρες" - min_private_message_title_length: "Ελάχιστο επιτρεπτό μήκος τίτλου για ένα μήνυμα σε χαρακτήρες" min_search_term_length: "Ελάχιστο έγκυρο μήκος όρου αναζήτησης σε χαρακτήρες" search_tokenize_chinese_japanese_korean: "Ανάγκασε την αναζήτηση να κάνει tokenize Κινέζικα / Ιαπωνικά / κορεατικά, ακόμη και σε μη CJK ιστότοπους" search_prefer_recent_posts: "Εάν η αναζήτηση στην ιστοσελίδα σας είναι αργή, αυτή η επιλογή δημιουργεί κατάλογο των πιο πρόσφατων αναρτήσεων πρώτα" @@ -933,8 +929,6 @@ el: summary_likes_required: "Ελάχιστος αριθμός ''Μου αρέσει'' σε ένα θέμα πριν ενεργοποιηθεί το «Συνοψίστε αυτό το θέμα» " summary_percent_filter: "Όταν ο χρήστης επιλέξει «Συνοψίστε αυτό το θέμα», δείξε το κορυφαίο % των αναρτήσεων" summary_max_results: "Μέγιστος αριθμός αναρτήσεων στο «Συνοψίστε αυτό το θέμα»" - enable_private_messages: "Επίτρεψε στους χρήστες επιπέδου εμπιστοσύνης 1 (ρυθμιζόμενο μέσω min trust level to send messages) να δημιουργήσουν μηνύματα και να απαντήσουν σε μηνύματα. Σημειώστε ότι οι συνεργάτες μπορούν πάντα να στέλνουν μηνύματα." - enable_private_email_messages: "Επίτρεψε στους χρήστες επιπέδου εμπιστοσύνης 4 (ρυθμιζόμενο μέσω min trust level to send messages) να στέλνουν προσωπικά μηνύματα email. Σημειώστε ότι οι συνεργάτες μπορούν πάντα να στέλνουν μηνύματα." enable_long_polling: "Η αρτηρία μηνυμάτων που χρησιμοποιείτε για ειδοποιήσεις μπορεί να χρησιμοποιήσει μακρυά μέθοδο εξέτασης." long_polling_base_url: "Base URL που χρησιμοποιείτε για μακρύ ψήφισμα (όταν ένα CDN εξυπηρετεί δυναμικό περιεχόμενο, βεβαιωθείτε ότι το ορίσατε σε έλξη προέλευσης ) π.χ.: http://origin.site.com" long_polling_interval: "Χρονικό διάστημα που ο διακομιστής θα πρέπει να περιμένει πριν απαντήσει στους πελάτες όταν δεν υπάρχουν δεδομένα για την αποστολή (μόνο συνδεδεμένοι χρήστες )" @@ -1067,7 +1061,6 @@ el: max_bookmarks_per_day: "Μέγιστος αριθμός σελιδοδεικτών ανά χρήστη ανά ημέρα." max_edits_per_day: "Μέγιστος αριθμός επεξεργασιών ανά χρήστη ανά ημέρα." max_topics_per_day: "Μέγιστος αριθμός νημάτων που μπορεί να δημιουργήσει ο χρήστης σε μια ημέρα." - max_private_messages_per_day: "Μέγιστος αριθμός μηνυμάτων που μπορεί να δημιουργήσει ο χρήστης σε μια ημέρα." max_invites_per_day: "Μέγιστος αριθμός προσκλήσεων που μπορεί να στείλει ο χρήστης σε μια ημέρα." max_topic_invitations_per_day: "Μέγιστος αριθμός προσκλήσεων νημάτων που μπορεί να στείλει ο χρήστης σε μια ημέρα." max_logins_per_ip_per_hour: "Μέγιστος αριθμός επιτρεπόμενων συνδέσεων ανα διεύθυνση IP την ώρα" @@ -1137,7 +1130,6 @@ el: max_users_notified_per_group_mention: "Μέγιστος αριθμός χρηστών που ίσως λάβουν μια ειδοποίηση εάν μια ομάδα αναφέρεται (εάν φτάσουν το κατώφλι, δεν θα σταλούν ειδοποιήσεις)" create_thumbnails: "Δημιούργησε μικρογραφίες και lightbox για εικόνες που είναι πολύ μεγάλες για να χωρέσουν σε μια ανάρτηση." email_time_window_mins: "Αναμονή (χ) λεπτών πριν την αποστολή οποιουδήποτε ειδοποιητικού email, έτσι ώστε να δωθεί στους χρήστες η ευκαιρία να επεξεργασθούν και να οριστικοποιήσουν τις αναρτήσεις τους. " - private_email_time_window_seconds: "Αναμονή (χ) δευτερολέπτων πριν την αποστολή οποιουδήποτε προσωπικού ειδοποιητικού email, έτσι ώστε να δωθεί στους χρήστες η ευκαιρία να επεξεργασθούν και να οριστικοποιήσουν τα μηνύματα τους. " email_posts_context: "Πόσες προηγούμενες απαντήσεις να συμπεριλαμβάνονται ως περιεχόμενο στα ειδοποιητικά μηνύματα. " flush_timings_secs: "Πόσο συχνά θα καθαρίσουμε τα δεδομένα χρονισμού στο σέρβερ, σε δευτερόλεπτα." title_max_word_length: "Το μέγιστο επιτρεπόμενο όριο λέξεων, σε χαρακτήρες, σε ένα τίτλο νήματος." @@ -1314,7 +1306,6 @@ el: watched_words_regular_expressions: "Οι λέξεις που επιτηρούνται είναι regular expressions." default_email_digest_frequency: "Πόσο συχνά οι χρήστες λαμβάνουν συνοπτικά email από προεπιλογή." default_include_tl0_in_digests: "Συμπεριέλαβε αναρτήσεις από νέους χρήστες στα συνοπτικά email από προεπιλογή. Οι χρήστες μπορούν να το αλλάξουν αυτό στις προτιμήσεις τους. " - default_email_private_messages: "Στείλε email όταν κάποιος στέλνει μήνυμα στο χρήστη από προεπιλογή." default_email_direct: "Στείλε email όταν κάποιος παραθέτει/απαντάει σε/αναφέρει ή προσκαλεί το χρήστη από προεπιλογή." default_email_mailing_list_mode: "Στείλε email για κάθε νέα ανάρτηση από προεπιλογή." default_email_mailing_list_mode_frequency: "Οι χρήστες, οι οποίοι έχουν ενεργοποιήσει την λειτουργία ταχυδρομικής λίστας θα λαμβάνουν emails τόσο συχνά από προεπιλογή." @@ -2333,21 +2324,6 @@ el: signup_after_approval: title: "Εγγραφή μετά από έγκριση " subject_template: "Έχετε εγκριθεί στην %{site_name}!" - text_body_template: | - Καλώς ήρθατε στην %{site_name}! - - Ένα μέλος του προσωπικού ενέκρινε τον λογαριασμό σας στην %{site_name}. - - Κάντε κλικ στον παρακάτω σύνδεσμο για να επικυρώσετε και να ενεργοποιήσετε τον νέο λογαριασμό σας: - %{base_url}/u/activate-account/%{email_token} - - Αν για κάποιο λόγο δεν μπορείτε να κάνετε κλικ στον παραπάνω σύνδεσμο, παρακαλούμε αντιγράψτε ολόκληρη την διεύθυνση και επικολλήστε την στην γραμμή διευθύνσεων του περιηγητή σας. - - %{new_user_tips} - - Επικροτούμε πάντα την [πολιτισμένη κοινωνική συμπεριφορά](%{base_url}/guidelines). - - Καλά να περάσετε! signup: title: "Εγγραφή" subject_template: "[%{email_prefix}] Επικυρώστε τον νέο σας λογαριασμό" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 7b162be31e..27e31c7540 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -215,6 +215,7 @@ en: one: "Sorry, new users can only put one attachment in a post." other: "Sorry, new users can only put %{count} attachments in a post." no_links_allowed: "Sorry, new users can't put links in posts." + links_require_trust: "Sorry, you can't include links in your posts." too_many_links: one: "Sorry, new users can only put one link in a post." other: "Sorry, new users can only put %{count} links in a post." @@ -393,6 +394,7 @@ en: cant_send_pm: "Sorry, you cannot send a personal message to that user." no_user_selected: "You must select a valid user." reply_by_email_disabled: "Reply by email has been disabled." + target_user_not_found: "One of the users you are sending this message to could not be found." featured_link: invalid: "is invalid. URL should include http:// or https://." invalid_category: "can't be edited in this category." @@ -536,7 +538,7 @@ en: rate_limiter: slow_down: "You have performed this action too many times, try again later." - too_many_requests: "We have a daily limit on how many times that action can be taken. Please wait %{time_left} before trying again." + too_many_requests: "You have performed this action too many times. Please wait %{time_left} before trying again." by_type: first_day_replies_per_day: "You've reached the maximum number of replies a new user can create on their first day. Please wait %{time_left} before trying again." first_day_topics_per_day: "You've reached the maximum number of topics a new user can create on their first day. Please wait %{time_left} before trying again." @@ -646,6 +648,10 @@ en: success: "You successfully changed your password and are now logged in." success_unapproved: "You successfully changed your password." + email_login: + invalid_token: "Sorry, that email login link is too old. Select the Log In button and use 'I forgot my password' to get a new link." + title: "Email login" + change_email: confirmed: "Your email has been updated." please_continue: "Continue to %{site_name}" @@ -955,7 +961,7 @@ en: email_polling_errored_recently: one: "Email polling has generated an error in the past 24 hours. Look at the logs for more details." other: "Email polling has generated %{count} errors in the past 24 hours. Look at the logs for more details." - missing_mailgun_api_key: "The server is configured to send emails via mailgun but you haven't provided an API key used the verify the webhook messages." + missing_mailgun_api_key: "The server is configured to send emails via mailgun but you haven't provided an API key used to verify the webhook messages." bad_favicon_url: "The favicon is failing to load. Check your favicon_url setting in Site Settings." poll_pop3_timeout: "Connection to the POP3 server is timing out. Incoming email could not be retrieved. Please check your POP3 settings and service provider." poll_pop3_auth_error: "Connection to the POP3 server is failing with an authentication error. Please check your POP3 settings." @@ -968,15 +974,16 @@ en: default_locale: "The default language of this Discourse instance" allow_user_locale: "Allow users to choose their own language interface preference" set_locale_from_accept_language_header: "set interface language for anonymous users from their web browser's language headers. (EXPERIMENTAL, does not work with anonymous cache)" + support_mixed_text_direction: "Support mixed left-to-right and right-to-left text directions." min_post_length: "Minimum allowed post length in characters" min_first_post_length: "Minimum allowed first post (topic body) length in characters" - min_private_message_post_length: "Minimum allowed post length in characters for messages" + min_personal_message_post_length: "Minimum allowed post length in characters for messages" max_post_length: "Maximum allowed post length in characters" topic_featured_link_enabled: "Enable posting a link with topics." show_topic_featured_link_in_digest: "Show the topic featured link in the digest email." min_topic_title_length: "Minimum allowed topic title length in characters" max_topic_title_length: "Maximum allowed topic title length in characters" - min_private_message_title_length: "Minimum allowed title length for a message in characters" + min_personal_message_title_length: "Minimum allowed title length for a message in characters" min_search_term_length: "Minimum valid search term length in characters" search_tokenize_chinese_japanese_korean: "Force search to tokenize Chinese/Japanese/Korean even on non CJK sites" search_prefer_recent_posts: "If searching your large forum is slow, this option tries an index of more recent posts first" @@ -1036,9 +1043,9 @@ en: summary_percent_filter: "When a user clicks 'Summarize This Topic', show the top % of posts" summary_max_results: "Maximum posts returned by 'Summary This Topic'" - enable_private_messages: "Allow trust level 1 (configurable via min trust level to send messages) users to create messages and reply to messages. Note that staff can always send messages no matter what." - enable_system_message_replies: "Allows users to reply to system messages, even if private messages are disabled" - enable_private_email_messages: "Allow trust level 4 (configurable via min trust level to send messages) users to send private email messages. Note that staff can always send messages no matter what." + enable_personal_messages: "Allow trust level 1 (configurable via min trust level to send messages) users to create messages and reply to messages. Note that staff can always send messages no matter what." + enable_system_message_replies: "Allows users to reply to system messages, even if personal messages are disabled" + enable_personal_email_messages: "Allow trust level 4 (configurable via min trust level to send messages) users to send private email messages. Note that staff can always send messages no matter what." enable_long_polling: "Message bus used for notification can use long polling" long_polling_base_url: "Base URL used for long polling (when a CDN is serving dynamic content, be sure to set this to origin pull) eg: http://origin.site.com" @@ -1066,6 +1073,8 @@ en: traditional_markdown_linebreaks: "Use traditional linebreaks in Markdown, which require two trailing spaces for a linebreak." enable_markdown_typographer: "Use basic typography rules to improve text readability of paragraphs of text, replaces (c) (tm) etc, with symbols, reduces number of question marks and so on" + enable_markdown_linkify: "Automatically treat text that looks like a link as a link: www.site.com and http://site.com will be automatically linked" + markdown_linkify_tlds: "List of top level domains that get automatically treated as links" post_undo_action_window_mins: "Number of minutes users are allowed to undo recent actions on a post (like, flag, etc)." must_approve_users: "Staff must approve all new user accounts before they are allowed to access the site. WARNING: enabling this for a live site will revoke access for existing non-staff users!" pending_users_reminder_delay: "Notify moderators if new users have been waiting for approval for longer than this many hours. Set to -1 to disable notifications." @@ -1144,6 +1153,7 @@ en: sso_allows_all_return_paths: "Do not restrict the domain for return_paths provided by SSO (by default return path must be on current site)" enable_local_logins: "Enable local username and password login based accounts. (Note: this must be enabled for invites to work)" + enable_local_logins_via_email: "Email user logins via email." allow_new_registrations: "Allow new user registrations. Uncheck this to prevent anyone from creating a new account." enable_signup_cta: "Show a notice to returning anonymous users prompting them to sign up for an account." enable_yahoo_logins: "Enable Yahoo authentication" @@ -1201,7 +1211,7 @@ en: max_bookmarks_per_day: "Maximum number of bookmarks per user per day." max_edits_per_day: "Maximum number of edits per user per day." max_topics_per_day: "Maximum number of topics a user can create per day." - max_private_messages_per_day: "Maximum number of messages users can create per day." + max_personal_messages_per_day: "Maximum number of messages users can create per day." max_invites_per_day: "Maximum number of invites a user can send per day." max_topic_invitations_per_day: "Maximum number of topic invitations a user can send per day." @@ -1277,6 +1287,7 @@ en: tl3_links_no_follow: "Do not remove rel=nofollow from links posted by trust level 3 users." min_trust_to_create_topic: "The minimum trust level required to create a new topic." + allow_flagging_staff: "If enabled, users can flag posts from staff accounts." min_trust_to_edit_wiki_post: "The minimum trust level required to edit post marked as wiki." @@ -1288,6 +1299,9 @@ en: min_trust_to_send_email_messages: "The minimum trust level required to send new personal messages via email (to staged users)." + min_trust_to_flag_posts: "The minimum trust level required to flag posts" + min_trust_to_post_links: "The minimum trust level required to include links in posts" + newuser_max_links: "How many links a new user can add to a post." newuser_max_images: "How many images a new user can add to a post." newuser_max_attachments: "How many attachments a new user can add to a post." @@ -1300,7 +1314,7 @@ en: create_thumbnails: "Create thumbnails and lightbox images that are too large to fit in a post." email_time_window_mins: "Wait (n) minutes before sending any notification emails, to give users a chance to edit and finalize their posts." - private_email_time_window_seconds: "Wait (n) seconds before sending any private notification emails, to give users a chance to edit and finalize their messages." + personal_email_time_window_seconds: "Wait (n) seconds before sending any private notification emails, to give users a chance to edit and finalize their messages." email_posts_context: "How many prior replies to include as context in notification emails." flush_timings_secs: "How frequently we flush timing data to the server, in seconds." title_max_word_length: "The maximum allowed word length, in characters, in a topic title." @@ -1548,7 +1562,7 @@ en: default_email_digest_frequency: "How often users receive summary emails by default." default_include_tl0_in_digests: "Include posts from new users in summary emails by default. Users can change this in their preferences." - default_email_private_messages: "Send an email when someone messages the user by default." + default_email_personal_messages: "Send an email when someone messages the user by default." default_email_direct: "Send an email when someone quotes/replies to/mentions or invites the user by default." default_email_mailing_list_mode: "Send an email for every new post by default." default_email_mailing_list_mode_frequency: "Users who enable mailing list mode will receive emails this often by default." @@ -1631,6 +1645,7 @@ en: staged_users_disabled: "You must first enable 'staged users' before enabling this setting." reply_by_email_disabled: "You must first enable 'reply by email' before enabling this setting." sso_url_is_empty: "You must set a 'sso url' before enabling this setting." + enable_local_logins_disabled: "You must first enable 'enable local logins' before enabling this setting." search: within_post: "#%{post_number} by %{username}" @@ -2763,6 +2778,17 @@ en: Click the following link to choose a new password: %{base_url}/u/password-reset/%{email_token} + email_login: + title: "Email login link" + subject_template: "[%{email_prefix}] Email login link" + text_body_template: | + Somebody asked to login your account on [%{site_name}](%{base_url}). + + If it was not you, you can safely ignore this email. + + Click the following link to login: + %{base_url}/session/email-login/%{email_token} + set_password: title: "Set Password" subject_template: "[%{email_prefix}] Set Password" @@ -2834,8 +2860,8 @@ en: A staff member approved your account on %{site_name}. - Click the following link to confirm and activate your new account: - %{base_url}/u/activate-account/%{email_token} + You can now access your new account by logging in at: + %{base_url} If the above link is not clickable, try copying and pasting it into the address bar of your web browser. diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml index 3fb1b1119f..a8e2900321 100644 --- a/config/locales/server.es.yml +++ b/config/locales/server.es.yml @@ -465,7 +465,6 @@ es: broken: "Esta imagen está rota" rate_limiter: slow_down: "Has realizado esta acción muchas veces, inténtalo de nuevo más tarde." - too_many_requests: "Estas haciendo eso demasiado a menudo. Por favor espera %{time_left} antes de intentarlo de nuevo." by_type: first_day_replies_per_day: "Has llegado al límite de respuestas que un nuevo usuario puede crear en su primer día. Por favor, espera %{time_left} antes de intentarlo de nuevo." first_day_topics_per_day: "Has llegado al límite de temas que un nuevo usuario puede crear en su primer día. Por favor, espera %{time_left} antes de intentarlo de nuevo." @@ -869,7 +868,6 @@ es: email_polling_errored_recently: one: "El email polling ha generado un error en las pasadas 24 horas. Mira en los logs para más detalles." other: "El email polling ha generado %{count} errores en las pasadas 24 horas. Mira en los logs para más detalles." - missing_mailgun_api_key: "El servidor está configurado para enviar emails vía mailgun pero no se ha proporcionado una API key para verificar los mensajes webhook." bad_favicon_url: "El favicon está dando fallos en su carga. Revisa la opción favicon_url en Ajustes del sitio." poll_pop3_timeout: "La conexión al servidor POP3 está rebasando el tiempo de espera. Los emails entrantes no han podido ser recogidos. Por favor revisa los ajustes de POP3 y tu proveedor de servicio." poll_pop3_auth_error: "La conexión al servidor POP3 está fallando debido a un error de autenticación. Por favor revisa los ajustes POP3." @@ -881,13 +879,11 @@ es: set_locale_from_accept_language_header: "Establece el lenguaje de la interfaz para usuarios anónimos desde el lenguaje declarado por su navegador web. (EXPERIMENTAL, no funciona con caché anónimo)" min_post_length: "Extensión mínima de los posts, en número de caracteres" min_first_post_length: "Extensión mínima permitida en el primer mensaje (cuerpo del tema) en caracteres" - min_private_message_post_length: "Extensión mínima de los posts en los mensajes, en número de caracteres" max_post_length: "Extensión máxima de los posts, en número de caracteres" topic_featured_link_enabled: "Activar publicar temas a partir de un enlace." show_topic_featured_link_in_digest: "Mostrar el enlace destacado por el tema en el email de resumen." min_topic_title_length: "Extensión mínima del título de los temas, en número de caracteres" max_topic_title_length: "Extensión máxima del título de los temas, en número de caracteres" - min_private_message_title_length: "Extensión mínima del título de los temas en mensajes, en número de caracteres" min_search_term_length: "Extensión mínima de una búsqueda válida, en número de caracteres" search_tokenize_chinese_japanese_korean: "Forzar la búsqueda a tokenizar Chino/Japonés/Coreano incluso en sitios que no basados en esos idiomas" search_prefer_recent_posts: "Si la búsqueda en tu foro gigante es lenta, esta opción prueba primero un índice de posts más recientes" @@ -943,8 +939,6 @@ es: summary_likes_required: "Mínimo de \"me gusta\" en un tema para habilitar 'Resumen de este tema'" summary_percent_filter: "Cuando un usuario hace clic en 'Resumen de este tema', se muestra el n % mejores posts" summary_max_results: "Máximo de posts devueltos en \"Resumen de este tema\"" - enable_private_messages: "Permitir a los usuarios con nivel 1 de confianza (configurable vía mínimo nivel para enviar mensajes) crear y responder a mensajes directos. Ten en cuenta que el staff siempre puede enviar mensajes directos." - enable_private_email_messages: "Permita que el nivel de confianza 4 (configurable a través del nivel mínimo de confianza para enviar mensajes) envíe mensajes de correo electrónico privados. Tenga en cuenta que el personal siempre puede enviar mensajes, pase lo que pase." enable_long_polling: "Los mensajes usados para notificaciones pueden usar el long polling" long_polling_base_url: "URL base usada para el 'long polling' (cuando un CDN esta sirviendo contenido dinámico, asegúrate de ajustar esto al 'pull' de origen) ejemplo: http://origin.site.com" long_polling_interval: "Cantidad de tiempo que el servidor debe de esperar antes de responder a los clientes que no hay datos enviados (solamente usuarios con sesión iniciada)." @@ -1079,7 +1073,6 @@ es: max_bookmarks_per_day: "Máximo número de marcadores por usuario y día." max_edits_per_day: "Máximo número de ediciones por usuario y día." max_topics_per_day: "Máximo número de temas que un usuario puede crear al día." - max_private_messages_per_day: "Máximo número de mensajes por usuario y día." max_invites_per_day: "Máximo número de invitaciones que un usuario puede enviar al día." max_topic_invitations_per_day: "Máximo número de invitaciones a un tema que un usuario puede enviar por día." max_logins_per_ip_per_hour: "Máximo número de inicios de sesión permitidos por direcciones IP por hora." @@ -1150,7 +1143,6 @@ es: enable_mentions: "Permitir a los usuarios mencionar a otros usuarios." create_thumbnails: "Crear miniaturas de imágenes y lightbox cuando estas son demasiado grandes para encajar en un post." email_time_window_mins: "Esperar (n) minutos antes de enviar cualquier email de notificación, para dar a los usuarios margen con el que editar y finalizar sus posts." - private_email_time_window_seconds: "Espera (n) segundos antes de enviar cualquier email de notificación, para dar a los usuarios margen con el que editar y finalizar sus mensajes." email_posts_context: "Cuántas respuestas previas se incluirán como contexto en los emails de notificación." flush_timings_secs: "Cuán frecuente, en segundos, se alinean los datos de sincronización con el servidor." title_max_word_length: "La longitud máxima permitida de una palabra, en caracteres, en el título del tema." @@ -1329,7 +1321,6 @@ es: watched_words_regular_expressions: "Palabras observadas son expresiones regulares." default_email_digest_frequency: "Cuán a menudo recibirán los usuarios emails de resumen por defecto." default_include_tl0_in_digests: "Incluir temas de usuarios nuevos en los emails de resumen por defecto. Los usuarios pueden cambiar esto en sus preferencias." - default_email_private_messages: "Enviar un email cuando alguien envíe un mensaje al usuario por defecto." default_email_direct: "Enviar un email cuando alguien cite/responda/mencione o invite al usuario por defecto." default_email_mailing_list_mode: "Enviar un email por cada nuevo post por defecto." default_email_mailing_list_mode_frequency: "Los usuarios que activen el modo lista de correo recibirán correos con esta frecuencia por defecto." @@ -2465,21 +2456,6 @@ es: signup_after_approval: title: "Entra después de ser aprobado" subject_template: "¡Tu solicitud ha sido aprobada en %{site_name}!" - text_body_template: | - ¡Te damos la bienvenida a %{site_name}! - - Un miembro del staff aprobó tu cuenta en %{site_name}. - - Haz clic en el siguiente enlace para confirmar y activar tu nueva cuenta: - %{base_url}/u/activate-account/%{email_token} - - Si no puedes hacer clic en el enlace, intenta copiándolo y pegándolo en la barra de direcciones de tu navegador. - - %{new_user_tips} - - Creemos en una comunidad con un [comportamiento civilizado](%{base_url}/guidelines). - - ¡Disfruta de tu estancia! signup: title: "Registrate" subject_template: "[%{email_prefix}] Confirma tu nueva cuenta" diff --git a/config/locales/server.et.yml b/config/locales/server.et.yml index 4f9f296593..92c9fe8522 100644 --- a/config/locales/server.et.yml +++ b/config/locales/server.et.yml @@ -357,7 +357,6 @@ et: change_failed_explanation: "Üritasid alandada %{user_name} tasemele '%{new_trust_level}'. Samas on tal juba tase'%{current_trust_level}'. %{user_name} jääb tasemele '%{current_trust_level}' - kui soovid alandada kasutaja taset, siis lukusta kõigepealt usaldustase." rate_limiter: slow_down: "Oled antud seda toimingut liiga palju proovinud. Proovi hiljem uuesti." - too_many_requests: "Antud tegevuse jaoks on meil päevane kordade limiit. Palun oota %{time_left} enne kui uuesti proovid." by_type: first_day_replies_per_day: "Oled ületanud uuele kasutajale esimesel päeval lubatud vastuste limiidi. Palun oota %{time_left} enne kui uuesti proovid." first_day_topics_per_day: "Oled ületanud uuele kasutajale esimesel päeval lubatud teemade lisamise limiidi. Palun oota %{time_left} enne kui uuesti proovid." @@ -679,7 +678,6 @@ et: max_post_length: "Maksimaalne lubatud postituse pikkus tähemärkides" min_topic_title_length: "Lühim lubatud teema pealkirja pikkus tähemärkides" max_topic_title_length: "Maksimaalne lubatud teema pealkirja pikkus tähemärkides" - min_private_message_title_length: "Minimaalne lubatud teema pealkirja pikkus tähemärkides" enable_instagram_logins: "Luba autentimine Instagrami abil, nõuab instagram_consumer_key ja instagram_consumer_secret" instagram_consumer_key: "Teenusekasutaja avalik võti Instagrami abil autentimiseks" instagram_consumer_secret: "Teenusekasutaja salajane võti Instagrami abil autentimiseks" diff --git a/config/locales/server.fa_IR.yml b/config/locales/server.fa_IR.yml index c68066aac8..14abd92a92 100644 --- a/config/locales/server.fa_IR.yml +++ b/config/locales/server.fa_IR.yml @@ -423,7 +423,6 @@ fa_IR: broken: "عکس خراب شده است." rate_limiter: slow_down: "شما این عمل را بیش از حد انجام داده اید, بعدا دوباره امتحان کنید." - too_many_requests: "ما روزانه محدودیت زمانی داریم برای اینکه این اقدام چند بار انجام شود. لطفا %{time_left} قبل از اینکه دوباره تلاش کنید صبر کنید . " by_type: first_day_replies_per_day: "شما به حداکثر تعداد پاسخ هایی که یک کاربر تازه میتواند در روز اولش ایجاد کند رسیده اید. لطفا به مدت %{time_left} صبر کنید قبل از اینکه دوباره امتحان کنید." first_day_topics_per_day: "شما به حداکثر تعداد موضوعاتی که یک کاربر تازه میتواند در روز اولش ایجاد کند رسیده‌اید. لطفا به مدت %{time_left} صبر کنید قبل از اینکه دوباره امتحان کنید." @@ -797,7 +796,6 @@ fa_IR: subfolder_ends_in_slash: "تنظیمات زیرپوشه نادرست است، مقدار DISCOURSE_RELATIVE_URL_ROOT با نویسه‌ی slash تمام می‌شود." email_polling_errored_recently: other: "رای‌گیری ایمیلی %{count} خطا در 24 ساعت گذشته ایجاد کرده. گزارشات را ببینید." - missing_mailgun_api_key: "سرور تنظیم شده که با mailgun ایمیل ارسال کند ولی شما کلید API را برای تایید پیام وب‌هوک تنظیم نکردید." bad_favicon_url: "favicon نمی‌تواند بارگذاری شود. مقدار favicon_url را در تنظیمات سایت بررسی کنید." poll_pop3_timeout: "اتصال به سرور POP3 انجام نشد. ایمیل‌های ورودی ممکن است دریافت نشوند. لطفا تنظیمات POP3 و فراهم کننده خدمات را بررسی کنید." poll_pop3_auth_error: "اتصال به سرور POP3 ناموفق بود، خطای اعتبار سنجی. لطفا تنظیمات POP3 را بررسی کنید." @@ -809,13 +807,11 @@ fa_IR: set_locale_from_accept_language_header: "تنظیم زبان برای رابط کاربری کاربران ناشناس که از هدر مرورگر دریافت می‌شود. (تجربی، با کش ناشناس کار نمی‌کند)" min_post_length: "حداقل طول مجاز نوشته به نویسه" min_first_post_length: "حداقل طول نوشته به نویسه (topic body)" - min_private_message_post_length: "حداقل طول نوشته به نویسه در پیام‌ها " max_post_length: "حداکثر طول نوشته به نویسه" topic_featured_link_enabled: "فعال بودن ارسال لینک در موضوعات" show_topic_featured_link_in_digest: "نمایش پیوند‌های برجسته موضوع در ایمیل خلاصه" min_topic_title_length: "حداقل طول عنوان نوشته به نویسه" max_topic_title_length: "حداکثر طول مجاز عنوان موضوع به نویسه" - min_private_message_title_length: "حداقل طول مجاز عنوان برای پیام به نویسه" min_search_term_length: "حداقل طول واژه جستجوی معتبر به نویسه" search_tokenize_chinese_japanese_korean: "اجبار جستجو برای tokenize کردن چینی/ژاپنی/کره‌ای حتی در سایت هایی که از این زبان‌ها استفاده نمی‌کنند" search_prefer_recent_posts: "اگر سرعت جستجوی انجمن پایین است، این گزینه آخرین نوشته‌ها را در ابتدا ایندکس گذاری می‌کند." @@ -864,7 +860,6 @@ fa_IR: summary_likes_required: "حداقل پسندها در این جستار قبل از اینکه \" خلاصه این موضوع\" فعال شود" summary_percent_filter: "وقتی کاربر بر روی ' خلاصه این موضوع' کلیک کرد٬‌ % بهترین نوشته‌ها را نشان بده" summary_max_results: "حداکثر نوشته‌های برگردانده شد با \" خلاصه این موضوع\"" - enable_private_messages: "اجازه ارسال پیام به کاربران سطح اعتماد 1 (قابل تنظیم با حداقل سطح اعتماد برای ارسال پیام). توجه کنید که همکاران در هر شرایطی می‌توانند پیام ارسال کنند." enable_long_polling: "message bus استفاده شده برای آگاه سازی می تواند برای رای گیری طولانی استفاده شود. " long_polling_base_url: " URL پایه استفاده شده برای رای گیری طولانی (وقتی CDN خدمت محتوای پویا می دهد٬ از تنظیم بودن منشا این کشش مطمئن شوید) برای نمونه : http://origin.site.com" long_polling_interval: "مدت زمانی که سرور قبل پاسخ دادن به مشتری‌ها باید صبر کند، وقتی در آن‌جا داده ای برای ارسال نیست (فقط کاربران وارد شده)" @@ -986,7 +981,6 @@ fa_IR: max_bookmarks_per_day: "حداکثر تعداد نشانک هر کاربر در روز" max_edits_per_day: "حداکثر تعداد ویرایش هر کاربر در روز" max_topics_per_day: "حداکثر تعداد موضوعاتی که هر کاربر در روز می توانند ایجاد کند" - max_private_messages_per_day: "حداکثر تعداد پیام هایی که هر کاربر در روز می توانند ایجاد کند" max_invites_per_day: "حداکثر تعداد دعوت‌نامه‌هایی که هر کاربر در روز می توانند ارسال کند" max_topic_invitations_per_day: "حداکثر تعداد دعوت‌‌نامه‌هایی که یک کاربر می‌تواند برای عناوین در یک روز ارسال کند." max_logins_per_ip_per_hour: "حداکثر تعداد ورود به ازای هر آیپی در ساعت" @@ -1055,7 +1049,6 @@ fa_IR: max_users_notified_per_group_mention: "حداکثر تعداد کاربرانی که اگر به گروه اشاره شود، اعلان دریافت می‌کنند (اگر آستانه براورده شود هیچ اعلانی ارسال نخواهد شد)" create_thumbnails: "ایجاد تصویر بند‌انگشتی و کادر تصاویر کوچک عکس‌ها را درست که برای جا شدن در نوشته بسیار بزگ هستند" email_time_window_mins: "قبل از ارسال هرگونه آگاهی‌سازی از طریق ایمیل (n) دقیقه صبر کن، برای اینکه به کاربران فرصت بدهید تا بتوانند نوشته‌های خود را ویرایش و نهایی کنند. " - private_email_time_window_seconds: "قبل از ارسال ایمیل آگاه‌سازی خصوصی، (n) ثانیه صبر کنید تا به کاربر اجازه ویرایش پیام نهایی خود را بدهید." email_posts_context: "چند پاسخ قبلی تا شامل متن خلاصه در ایمیل های آگاهی سازی شود. " flush_timings_secs: "هر چند وقت یک بار اطلاعات زمان‌بندی سرور را خالی کنیم، واحد ثانیه" title_max_word_length: "حداکثر طول مجاز کلمه٬ در نویسه، در عنوان موضوع." @@ -1221,7 +1214,6 @@ fa_IR: code_formatting_style: "کد دکمه در composer به صورت پیشفرض این حالت خواهد بود" default_email_digest_frequency: "به صورت پیشفرض هر چند وقت یک بار ایمیل خلاصه دریافت شود." default_include_tl0_in_digests: "قرار دادن نوشته‌های کاربران جدید در خلاصه ایمیل به صورت پیشفرض. کاربران می‌توانند این تنظیمات را از طریق تنظیمات شخصیشان ویرایش کنند." - default_email_private_messages: "به صورت پیشفرض وقتی پیامی ارسال می‌شود به او ایمیل بفرست." default_email_direct: "ارسال ایمیل وقتی نقل‌قول، پاسخ، اشاره یا دعوت دریافت می‌کند" default_email_mailing_list_mode: "ارسال ایمیل برای نوشته‌های جدید" default_email_mailing_list_mode_frequency: "کاربرانی که ارسال ایمیل را فعال کنند، در این بازه زمانی ایمیل دریافت می‌کنند." @@ -1990,21 +1982,6 @@ fa_IR: signup_after_approval: title: "ثبت نام بعد از تایید" subject_template: "شما در %{site_name} تایید شدید!" - text_body_template: | - به %{site_name} خوش آمدید! - - حساب کاربری شما در %{site_name} توسط همکاران تایید شده تسن. - - برای فعالسازی حساب‌کاربری خود روی لینک زیر کلیک کنید: - %{base_url}/u/activate-account/%{email_token} - - اگر لینک بالا قابل کلیک نیست، آن را در نوار‌ آدرس مرورگر اینترنتی خود کپی کنید. - - %{new_user_tips} - - ما همیشه به [رفتار اجتماعی متمدنانه](%{base_url}/guidelines) اعتقاد داریم. - - از بودن در انجمن لذت ببرید! signup: title: "ثبت‌نام" subject_template: "[%{email_prefix}] حساب کاربری خود را تایید کنید." @@ -2124,6 +2101,7 @@ fa_IR: این نشان را وقتی که اولین پسند را دریافت کنید به شما اعطا می‌شود. تبریک، نوشته‌ای ارسال کردید که اعضای انجمن به آن علاقه نشان دادند، باحال، یا مفید بود! autobiographer: name: نویسنده شرح‌حال + description: اطلاعات صفحه شخصی خود را تکمیل کرده anniversary: name: سالگرد description: برای یک سال کاربر فعال بوده، حداقل یک نوشته دارد. @@ -2175,13 +2153,13 @@ fa_IR: long_description: | این نشان زمانی اعطا می‌شود که یک لینک را به اشتراک بگذارید و 1000 کلیک دریافت کند. وای!‌ شما یک بحث جالب را به حضار زیادی ترویج کردید، شما کمک بزرگ به رشد انجمن کردید! first_like: - name: اولین پسندیدن + name: اولین پسند description: یک پست را پسندیده است long_description: | این نشان زمانی اعطا می‌شود که برای بار اول یک نوشته را با گزینه :heart: بپسندید. پسندیدن نوشته‌ها یک راه عالی برای ابراز علاقه شما به نوشته‌های اعضاست که بدانند نوشته‌هایشان جالب، مفید، باحال، یا خنده دار است. علاقه‌تان را به اشتراک بگذارید! first_flag: - name: اولین نشان - description: اولین نوشته را نشانه‌گذاری کرد + name: اولین پرچم + description: یک نوشته را پرچم گذاری کرده long_description: | این نشان زمانی اعطا می‌شود که برای بار اول یک نوشته را پرچم گذاری کنید. پرچم گذاری راهی است برای تمیز نگه داشتن انجمن، جای روشنی برای همه. اگر فکر می‌کنید نوشته‌ای به هر دلیلی نیاز به توجه مدیریت دارد لطفا از پرچم گذاری دریغ نکنید. می‌توانید برای ارسال پیام خصوصی هم پرچم بزنید تا اعضا متوجه ایراد در نوشته‌هایشان شوند. اگر مشکلی میبینید، :flag_black: پرچم‌گذاری کنید! promoter: @@ -2205,7 +2183,7 @@ fa_IR: long_description: | این نشان زمانی اعطا می‌شود که یک پیوند پاسخ یا موضوع را با دکمه اشتراک گذاری، به اشتراک بگذارید. به اشتراک گذاشتن پیوند‌ها یک راه علی برای نمایش دادن موضوعات جالب با سایر مردم جهان و رشد انجمتان است! first_link: - name: اولین پیوند + name: اولین لینک description: لینکی به موضوع دیگر قرار داده long_description: | این نشان زمانی اعطا می‌شود که برای بار اول پیوندی به موضوع دیگر اضافه کنید. پیوند به موضوعات به خوانندگان موضوع شما کمک می‌کند گفتگو‌های جالب مرتبط را با نمایش اتصال بین موضوعات، پیدا کنند. با آزادی پیوند بگذارید! @@ -2216,6 +2194,7 @@ fa_IR: این نشان زمانی اعطا می‌شود که برای بار اول یک نوشته را در پاسخ خود نقل‌قول کنید. نقل‌قول بخش‌های مربوطه کمک می‌کند که بحث‌های موضوع به هم متصل شوند. بهترین راه برای نقل قول انتخاب متن در نوشته و کلیک روی دکمه پاسخ است. سخاوتمندانه نقل‌قول کنید! read_guidelines: name: خواندن دستورالعمل‌ها + description: ' دستورالعمل های انجمن را خوانده' reader: name: اهل مطالعه description: همه‌ی پاسخ‌های یک موضوع با ۱۰۰ پاسخ را مطالعه کرده. @@ -2232,18 +2211,18 @@ fa_IR: long_description: | این نشان زمانی اعطا می‌شود که یک پیوند با 300 کلیک به اشتراک بگذارید. با تشکر از شما بابت لینک شگفت‌انگیزی که قرار دادید که باعث جلو رفتن بحث و روشن شدن گفتگو شد! famous_link: - name: پیوند معروف + name: پیوند برجسته description: یک پیوند خروجی با 1000 کلیک ارسال کرده long_description: | این نشان زمانی اعطا می‌شود که یک پیوند با 1000 کلیک به اشتراک بگذارید. وای! شما یک لینک قرار دادید که به طور قابل ملاحظه‌ای سطح گفتگو را با اضافه کردن جزئیات ضروری، زمینه و اطلاعات بالا برد. عالی بود! appreciated: - name: استقبال می‌نماید - description: 20 پست 1 بار پسندیده شده دارد. + name: تقدیر شده + description: ۱ پسند در ۲۰ پست دریافت کرده long_description: | این نشان زمانی اعطا می‌شود که حداقل یک پسند در 20 نوشته مختلف خود دریافت کنید. این انجمن از کمک‌های شما به گفتگو ها لذت می‌برد! respected: name: محترم - description: 100 پست 2 بار پسندیده شده دارد. + description: ۲ پسند در ۱۰۰ پست دریافت کرده long_description: | این نشان زمانی اعطا می‌شود که حداقل 2 پسند در 100 نوشته مختلف داشته باشید. انجمن در حال بزرگ شدن و احترام به تمام کمک‌های شما به گفتگو‌ها است. admired: @@ -2252,17 +2231,17 @@ fa_IR: long_description: | این نشان را در زمانی که حداقل 5 پسند در 300 نوشته مختلف داشته باشید، دریافت می‌کنید. انجمن همکاری مکرر و کمک‌های باکیفیت شما در گفتگو‌ها را تحسین می‌کند. out_of_love: - name: نبض احساس + name: مهربان description: در یک روز 50 نوشته را پسندیده است. long_description: | این نشان را در زمانی دریافت می‌کنید که تمام 50 پسند روزانه خود را استفاده کنید. به خاطر داشته باشید که برای پسندیدن نوشته‌هایی که از آن لذت بردید و تشویق و قدردانی از اعضای جامعه همکار برای ایجاد بحث‌های بهتر در‌آینده،‌ زمانی را اختصاص دهید. higher_love: - name: یکی هست + name: مهرورز description: در 5 روز، روزانه 50 نوشته را پسندیده است. long_description: | این مدال وقتی به شما تعلق می‌گیرد که برای 5 روز متوالی روزانه 50 بار نوشته دیگران را بپسندید. با تشکر از شما برای تشویق بهترین گفتگو‌ها! crazy_in_love: - name: نگران منی + name: الهه‌ی مهر description: در 20 روز، روزانه 50 نوشته را پسندیده است. long_description: | این مدال وقتی به شما تعلق می‌گیرد که برای 20 روز متوالی روزانه 50 بار نوشته دیگران را بپسندید. شما نمونه فردی هستید که افراد انجمن را به طور منظم تشویق می‌کنید! @@ -2272,7 +2251,7 @@ fa_IR: long_description: | این مدال زمانی به شما تعلق می‌گیرد که نوشته‌هایتان 20 بار پسندیده شده باشد و شما هم 10 نوشته را بپسندید. وقتی شما نوشته‌های دیگری را می‌پسندید زمان پسندیدن نوشته‌های سایرین را نیز پیدای می‌کنید. gives_back: - name: برگشت می‌دهد + name: قدر شناس description: 100 نوشته پسندیده شده دارد و 100 نوشته را پسندیده است. long_description: | این نشان را بعد از دریافت 100 پسند در نوشته‌هایتان و پسندیدن 100 نوشته دریافت می‌کنید. با تشکر از شما! @@ -2283,27 +2262,39 @@ fa_IR: این مدال زمانی به شما تعلق می‌گیرد که نوشته‌هایتان 1000 بار پسندیده شده باشد و شما هم 500 نوشته را بپسندید. شما نمونه بخشندگی و قدردانی متقابل هستید :two_hearts:. first_emoji: name: اولین شکلک - description: از یک شکلک در پست استفاده کرد + description: در یک نوشته از شکلک استفاده کرده. long_description: | این نشان در زمانی که برای بار اول یک شکلک به نوشته اضافه کنید به شما تعلق می‌گیرد :thumbsup:. شکلک‌ها اجازه می‌دهند که احساسات خود را در نوشته انتقال دهید، از خوشحالی :smiley: تا ناراحتی :anguished: تا عصبانیت :angry: و هرچیزی که در بین آن‌هاست :sunglasses:. فقط یک : (دو نقطه) تایپ کنید یا از نوار ابزار شکلک در ویرایشگر استفاده کنید تا بتوانید از صد‌ها شکلک انتخاب کنید :ok_hand: first_mention: name: اولین اشاره - description: اشاره به یک کاربر در یک نوشته + description: کاربری را در یک نوشته مخاطب قرار داده. long_description: این نشان را در زمانی که برای بار اول به یک کاربر به صورت @نام‌کاربری اشاره کنید، دریافت می‌کنید. هر اشاره به نام کاربری یه اعلان برای آن شخص ارسال می‌کند، بنابراین آن‌ها از نوشته‌ی شما مطلع خواهند شد. فقط علامت @ (علامت at) را تایپ کنید تا بتوانید به هر کاربری اشاره کنید یا، اگر اجازه وجود دارد، در گروه. -- این یک راه ساده برای جلب توجه آن‌ها است. first_onebox: - name: اولین جعبه - description: لینک باز شده ارسال کرده + name: اولین لینک قاب‌شده + description: یک لینک قاب شده پست کرده long_description: این مدال زمانی به شما تعلق می‌گیرد که برای بار اول یک پیوند قرار می‌دهید، که به صورت خودکار در جعبه باز شده و عنوان و خلاصه و عکس آن (در صورت وجود) نمایش داده می‌شود. first_reply_by_email: name: اولین پاسخ با ایمیل - description: پاسخ داده شده با ایمیل + description: به یک نوشته از طریق ایمیل پاسخ داده long_description: | این مدال وقتی دریافت می‌کنید که اولین پاسخ خود با ایمیل را ثبت کنید :e-mail:. new_user_of_the_month: - name: "کاربران جدید این ماه" + name: "تازه‌وارد منتخب ماه" description: مشارکت‌های برجسته در ماه اول long_description: | این مدال را وقتی دریافت می‌کنید که به دو کاربر در هر ماه به خاطر همکاری هایشان تبریک بگویید، به عنوان اندازه گیری که چقدر نوشته‌هایشان پسندیده شده و توسط چه کسی. + enthusiast: + name: علاقه‌مند + description: ۱۰ روز به اینجا سر زده + long_description: 'به به! این مدال رو به شما دادیم چون ده روزه پشت سر هم به اینجا سر زدی. از اینکه بیش از یک هفته با ما هستی ازت متشکریم. ' + aficionado: + name: طرفدار + description: ۱۰۰ روز به اینجا سر زده. + long_description: این نشان برای بازدید ۱۰۰ روز متوالی به شما اهدا شده. یعنی بیشتر از سه ماه! از همراهی‌تون ممنونیم. + devotee: + name: هواخواه + description: ۳۶۵ روز به اینجا سر زده + long_description: این نشان برای ۳۶۵ روز بازدید متوالی به شما اهدا شده. دمت گرم! یک سال تمام! چه زود گذشت. نه؟ badge_title_metadata: "نشان %{display_name} در %{site_title}" admin_login: success: "ایمیل ارسال شد " diff --git a/config/locales/server.fi.yml b/config/locales/server.fi.yml index cc8c3e9a59..3031f5181a 100644 --- a/config/locales/server.fi.yml +++ b/config/locales/server.fi.yml @@ -452,7 +452,6 @@ fi: broken: "Tämä kuva ei toimi" rate_limiter: slow_down: "Olet tehnyt tämän toiminnon liian monta kertaa, yritä uudelleen myöhemmin." - too_many_requests: "Tämä toiminto voidaan suorittaa vain määrätyn monta kertaa päivässä. Odota %{time_left} ja yritä sitten uudelleen." by_type: first_day_replies_per_day: "Uusi käyttäjä ei voi ensimmäisenä päivänään kirjoittaa enempää viestejä. Odota %{time_left} ja yritä sitten uudelleen." first_day_topics_per_day: "Uusi käyttäjä ei voi ensimmäisenä päivänään aloittaa enempää ketjuja. Odota %{time_left} ja yritä sitten uudelleen." @@ -856,7 +855,6 @@ fi: email_polling_errored_recently: one: "Sähköpostin pollaus on aiheuttanut virheen edellisen 24 tunnin aikana. Tarkastele lokeja saadaksesi lisätietoja." other: "Sähköpostin pollaus aiheutti %{count} virhettä edellisen 24 tunnin aikana. Tarkastele lokeja saadaksesi lisätietoja." - missing_mailgun_api_key: "Palvelin on asetettu lähettämään sähköposti mailgunin kautta, mutta et ole syöttänyt API tunnusta, jolla verifioidaan webhook-viestit." bad_favicon_url: "Faviconin asettaminen epäonnistui. Tarkista favicon_url -asetus sivuston asetuksissa." poll_pop3_timeout: "Yhteyttä POP3-palvelimelle aikakatkaistaan ja saapuvaa sähköpostia ei voitu hakea. Tarkista POP3-asetukset ja palveluntarjoaja." poll_pop3_auth_error: "Yhteys POP3-palvelimelle epäonnistuu autentikaatiovirheen vuoksi. Tarkista POP3-asetukset." @@ -868,13 +866,11 @@ fi: set_locale_from_accept_language_header: "Aseta sivuston kieli kirjautumattomille käyttäjille selaimen kielivalinnan perusteella. (KOKEELLINEN, ei toimi anonyymin välimuistin kanssa)" min_post_length: "Viestin merkkien minimimäärä" min_first_post_length: "Ketjun aloitusviestin (leipätekstin) merkkien minimimäärä" - min_private_message_post_length: "Viestin merkkien minimimäärä viesteille" max_post_length: "Viestin merkkien minimimäärä" topic_featured_link_enabled: "Ota käyttöön ketjulinkit." show_topic_featured_link_in_digest: "Näytä ketjulinkki tiivistelmäsähköpostissa." min_topic_title_length: "Viestin otsikon merkkien minimimäärä" max_topic_title_length: "Viestin otsikon merkkien maksimimäärä" - min_private_message_title_length: "Viestin otsikon merkkien minimimäärä viestille" min_search_term_length: "Haun merkkien minimimäärä" search_tokenize_chinese_japanese_korean: "Pakota haku käsittelemään kiinaa/japania/koreaa myös muunkielisillä sivustoilla" search_prefer_recent_posts: "Jos hakeminen suurelta palstaltasi on hidasta, tämä asetus kokeilee hakemistorakennetta, jossa tuoreimmat viestit ovat ensin" @@ -928,8 +924,6 @@ fi: summary_likes_required: "Montako tykkäystä ketjussa pitää olla, jotta ketjun tiivistelmä otetaan käyttöön" summary_percent_filter: "Kun käyttäjä klikkaa 'Näytä ketjun tiivistelmä', näytä paras % viesteistä" summary_max_results: "Maksimimäärä viestejä, jotka näytetään ketjun tiivistelmässä" - enable_private_messages: "Salli luottamustason 1 (muokattavissa asetuksista) käyttäjien lähettää yksityisviestejä ja vastata viesteihin. Huomaa, että henkilökunta voi aina lähettää viestejä." - enable_private_email_messages: "Salli luottamustason 4 (säädettävissä toisella asetuksella) käyttäjien lähettää yksityisviestitoiminnolla sähköpostia. Huomioi, että henkilökunta voi lähettää riippumatta asetuksista." enable_long_polling: "Ilmoitusten käyttämä viestiväylä voi käyttää long pollingia" long_polling_base_url: "Base URL, jota käytetään long pollingissa (kun CDN on käytössä, varmista että tähän on asetettu origin pull) esim: http://origin.site.com" long_polling_interval: "Kuinka kauan palvelimen pitäisi odottaa ennen vastaamista asiakkaalle, kun ei ole mitään dataa jota lähettää (vain kirjautuneille käyttäjille)" @@ -1062,7 +1056,6 @@ fi: max_bookmarks_per_day: "Kirjanmerkkien päivittäinen maksimimäärä per käyttäjä." max_edits_per_day: "Muokkausten päivittäinen maksimimäärä per käyttäjä." max_topics_per_day: "Kuinka monta ketjua käyttäjä voi aloittaa päivässä." - max_private_messages_per_day: "Viestien päivittäinen maksimimäärä per käyttäjä." max_invites_per_day: "Maksimimäärä kutsuja, jonka käyttäjä voi lähettää päivässä." max_topic_invitations_per_day: "Maksimimäärä ketjukutsuja, jonka yksittäinen käyttäjä voi lähettää päivässä" max_logins_per_ip_per_hour: "Enimmäismäärä kirjautumisia IP-osoitetta kohden tunnissa" @@ -1133,7 +1126,6 @@ fi: enable_mentions: "Salli käyttäjän mainita toinen käyttäjä." create_thumbnails: "Luo esikatselu- ja lightbox-kuvia, jotka ovat liian suuria mahtuakseen viestiin." email_time_window_mins: "Odota (n) minuuttia ennen ilmoitussähköpostien lähettämistä, jotta käyttäjällä on aikaa muokata ja viimeistellä viestinsä." - private_email_time_window_seconds: "Odota (n) sekuntia ennen sähköposti-ilmoitusten lähettämistä käyttäjille, jotta kirjoittajalla on mahdollisuus tehdä muokkaukset ja viimeistellä viestinsä." email_posts_context: "Kuinka monta edellistä vastausta liitetään kontekstiksi sähköposti-ilmoituksessa." flush_timings_secs: "Kuinka usein timing data päivitetään palvelimelle, sekunneissa." title_max_word_length: "Sanan enimmäispituus merkkeinä ketjun otsikossa" @@ -1312,7 +1304,6 @@ fi: watched_words_regular_expressions: "Tarkkaillut sanat ovat säännöllisiä lausekkeita." default_email_digest_frequency: "Kuinka usein käyttäjille lähetetään sähköpostikooste oletuksena." default_include_tl0_in_digests: "Sisällytä uusien käyttäjien viestit sähköpostikoosteisiin oletuksena. Tätä voi muuttaa käyttäjäasetuksissa." - default_email_private_messages: "Lähetä oletuksena sähköposti, kun joku lähettää käyttäjälle viestin." default_email_direct: "Lähetä oletuksena sähköposti, kun joku lainaa/vastaa/mainitsee tai kutsuu käyttäjän." default_email_mailing_list_mode: "Lähetä oletuksena sähköposti jokaisesta uudesta viestistä." default_email_mailing_list_mode_frequency: "Postituslistatilassa käyttäjä saa sähköpostia oletuksena näin usein." @@ -2442,19 +2433,6 @@ fi: signup_after_approval: title: "Liity hyväksynnän jälkeen" subject_template: "Sinut on hyväksytty sivustolle %{site_name}!" - text_body_template: | - Kiitos kun liityit %{site_name} ja tervetuloa! - - Henkilökunnan jäsen hyväksyi tunnuksesi sivustolla %{site_name}. - - Vahvista ja aktivoi uusi tunnuksesi klikkaamalla linkkiä: - %{base_url}/u/activate-account/%{email_token} - - %{new_user_tips} - - Vaalimme [sivistynyttä yhteisökäyttäytymistä](%{base_url}/guidelines) tilanteessa kuin tilanteessa. - - Toivottavasti viihdyt! signup: title: "Liity" subject_template: "[%{email_prefix}] Vahvista uusi käyttäjätilisi" diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml index 7b75135a66..3d4fd7cb83 100644 --- a/config/locales/server.fr.yml +++ b/config/locales/server.fr.yml @@ -451,7 +451,6 @@ fr: broken: "Cette image ne fonctionne pas" rate_limiter: slow_down: "Vous avez réalisé cette action un trop grand nombre de fois, essayez à nouveau plus tard." - too_many_requests: "Nous avons une limite journalière du nombre d'actions qui peuvent être effectuées. Veuillez patienter %{time_left} avant de recommencer." by_type: first_day_replies_per_day: "Vous avez atteint le nombre maximum de réponses qu'un nouvel utilisateur peut créer pour son premier jour. Patientez s'il vous plaît %{time_left} avant d'essayer à nouveau." first_day_topics_per_day: "Vous avez atteint le nombre maximum de sujets qu'un nouvel utilisateur peut créer pour son premier jour. Patientez s'il vous plaît %{time_left} avant d'essayer à nouveau." @@ -855,7 +854,6 @@ fr: email_polling_errored_recently: one: "La vérification des courriels a généré une erreur au cours des 24 dernières heures. Vérifiez le journal pour plus de détails." other: "La vérification des courriels a généré %{count} erreurs au cours des 24 dernières heures. Vérifiez le journal pour plus de détails." - missing_mailgun_api_key: "Le serveur est configuré pour envoyer des courriels via Mailgun mais vous n'avez pas spécifié une clé API utilisée pour vérifier les messages du webhook." bad_favicon_url: "Impossible de charger la favicon. Vérifiez le paramètre favicon_url dans les paramètres du site" poll_pop3_timeout: "La connexion vers le serveur POP3 a expiré. Les courriels entrants n'ont pas pu être téléchargés. Veuillez vérifier les paramètres POP3 et votre fournisseur de service courriel." poll_pop3_auth_error: "La connexion vers le serveur POP3 échoue avec une erreur d'authentification. Veuillez vérifier les paramètres POP3." @@ -867,13 +865,11 @@ fr: set_locale_from_accept_language_header: "configurer la langue de l'interface pour les visiteurs à partir des entêtes de langue de leur navigateur. (EXPÉRIMENTAL, ne fonctionne pas avec le cache anonyme)" min_post_length: "Longueur minimale autorisée des messages en nombre de caractères" min_first_post_length: "Longueur minimale d'un premier message (corps de sujet) en nombre de caractères" - min_private_message_post_length: "Longueur minimale des messages en nombre de caractères" max_post_length: "Longueur maximale autorisée des messages en nombres de caractères" topic_featured_link_enabled: "Activer la création de sujets avec lien" show_topic_featured_link_in_digest: "Afficher les sujets avec lien dans le résumé par courriel." min_topic_title_length: "Longueur minimale autorisée des titres de sujet en nombre de caractères" max_topic_title_length: "Longueur maximale autorisée des titres de sujet en nombre de caractères" - min_private_message_title_length: "Longueur minimale pour un titre de message en nombre de caractères" min_search_term_length: "Longueur minimale autorisée du texte saisie avant de lancer une recherche en nombre de caractères" search_tokenize_chinese_japanese_korean: "Forcer la tokenisation dans la recherche chinois/japonais/koréen, même sur des sites non-CJK." search_prefer_recent_posts: "Si la recherche dans votre forum est lente, cette option tente l'indéxation des messages les plus récents en premier" @@ -925,7 +921,6 @@ fr: summary_likes_required: "Nombre de J'aime minimum dans un sujet avant que le 'Résumé du sujet' soit activé" summary_percent_filter: "Quand un utilisateur clique sur « Résumer ce sujet », montrer le top % des messages" summary_max_results: "Nombre maximum de messages retournés par « Résumer ce sujet »" - enable_private_messages: "Autoriser les utilisateurs de niveau de confiance 1 à créer des messages et à répondre (configurable via le niveau de confiance minimum pour envoyer des messages). Notez que les responsables peuvent toujours envoyer des messages." enable_long_polling: "Utiliser les requêtes longues pour le flux de notifications." long_polling_base_url: "Racine de l'URL utilisée pour les requêtes longues (dans le cas de l'utilisation d'un CDN pour fournir du contenu dynamique, pensez à le configurer en mode \"origin pull\") par exemple : http://origin.site.com" long_polling_interval: "Délai d'attente du serveur avant de répondre aux clients lorsqu'il n'y a pas de données à envoyer\n(réservé aux utilisateurs connectés)" @@ -1048,7 +1043,6 @@ fr: max_bookmarks_per_day: "Nombre maximum de signets par utilisateur et par jour." max_edits_per_day: "Nombre maximum de modifications par utilisateur chaque jour." max_topics_per_day: "Nombre maximum de sujet qu'utilisateur peut créer par jour." - max_private_messages_per_day: "Nombre maximum de messages que les utilisateurs peuvent créer chaque jour." max_invites_per_day: "Nombre maximum d'invitations qu'un utilisateur peut envoyer par jour." max_topic_invitations_per_day: "Nombre maximum d'invitations à un sujet qu'un utilisateur peut envoyer par jour." max_logins_per_ip_per_hour: "Nombre maximum de connexions autorisées par adresse IP et par heure" @@ -1117,7 +1111,6 @@ fr: max_users_notified_per_group_mention: "Nombre maximum d'utilisateurs qui peuvent recevoir une notification si un groupe est mentionné (si le seuil est atteint, aucune notification ne sera envoyée)" create_thumbnails: "Créer un aperçu pour les images imbriquées qui sont trop large pour le message." email_time_window_mins: "Attendre (n) minutes avant l'envoi des courriels de notification, afin de laisser une chance aux utilisateurs de modifier ou finaliser leurs messages." - private_email_time_window_seconds: "Attendre (n) secondes avant d'envoyer des courriels de notification privés, afin de donner aux utilisateurs la chance d'éditer et de finaliser leurs messages." email_posts_context: "Combien de réponses précédentes doit-on inclure dans les courriels de notifications pour situer le contexte." flush_timings_secs: "À quelle fréquence les données de timing doivent être vider, en secondes." title_max_word_length: "Le nombre maximum de caractères dans le titre d'un sujet." @@ -1288,7 +1281,6 @@ fr: watched_words_regular_expressions: "Les mots surveillés sont des expressions régulières." default_email_digest_frequency: "Par défaut, à quelle fréquence les utilisateurs reçoivent les résumés par courriel." default_include_tl0_in_digests: "Par défaut, inclure les messages des nouveaux utilisateurs dans les résumés par courriel. Les utilisateurs peuvent changer cela dans leurs préférences." - default_email_private_messages: "Envoyer un courriel quand quelqu'un envoie un message à un utilisateur." default_email_direct: "Envoyer un courriel quand quelqu'un cite/répond à/mentionne ou invite un utilisateur." default_email_mailing_list_mode: "Envoyer un courriel pour chaque nouveau message." default_email_mailing_list_mode_frequency: "Par défaut, les utilisateurs ayant activé la liste de diffusion recevront des courriels à cette fréquence." @@ -2236,22 +2228,6 @@ fr: signup_after_approval: title: "Inscription après approbation" subject_template: "Votre compte a été approuvé sur %{site_name} !" - text_body_template: |+ - Bienvenue sur %{site_name} ! - - Un responsable a approuvé votre compte sur %{site_name}. - - Cliquez sur le lien suivant pour confirmer et activer votre nouveau compte : - %{base_url}/u/activate-account/%{email_token} - - Si le lien ci-dessus n'est pas cliquable, essayez de le copier/coller dans la barre d'adresse de votre navigateur internet. - - %{new_user_tips} - - Nous croyons constamment au [comportement communautaire civilisé](%{base_url}/guidelines). - - Amusez-vous bien ! - signup: title: "Inscription" subject_template: "[%{email_prefix}] Activez votre nouveau compte" diff --git a/config/locales/server.gl.yml b/config/locales/server.gl.yml index d77428c94c..770dd3fa4b 100644 --- a/config/locales/server.gl.yml +++ b/config/locales/server.gl.yml @@ -388,7 +388,6 @@ gl: allow_user_locale: "Permitir os usuarios escoller o idioma da interface" min_post_length: "Número mínimo de caracteres permitido para unha publicación" min_first_post_length: "Número mínimo de caracteres (corpo do tema) permitido para unha primeira publicación" - min_private_message_post_length: "Número mínimo de caracteres permitido para unha mensaxe" max_post_length: "Número máximo de caracteres permitido para unha publicación" post_undo_action_window_mins: "Número de minutos que os usuarios teñen para desfacer accións recentes nunha publicación (gústames, denuncias, etc)." enable_badges: "Activar o sistema de insignias" diff --git a/config/locales/server.he.yml b/config/locales/server.he.yml index 67f443f2c1..e348560589 100644 --- a/config/locales/server.he.yml +++ b/config/locales/server.he.yml @@ -436,7 +436,6 @@ he: broken: "תמונה זו שבורה" rate_limiter: slow_down: "ביצעתם פעולה זו מספר רב מדי של פעמים. נסו שוב מאוחר יותר." - too_many_requests: "יש לנו מגבלה יומית על מספר הפעמים שניתן לבצע פעולה זו. אנא המתינו %{time_left} לפני ניסיון חוזר." by_type: first_day_replies_per_day: "הגעתם למספר המירבי של תגובות שמתמשים חדשים יכולים ליצור ביומם הראשון. אנא המתינו %{time_left} לפני ניסיון חוזר לבצע פעולה זו." first_day_topics_per_day: "הגעתם למספר המירבי של נושאים שמשתמשים חדשים יכולים ליצור ביומם הראשון. אנא המתינו %{time_left} לפני ניסיון חוזר לבצע פעולה זו." @@ -837,7 +836,6 @@ he: email_polling_errored_recently: one: "ניסיונות שליחת מיילים יצרו תקלה ב 24 השעות האחרונות. צפו ביומנים לפרטים נוספים." other: "ניסיונות שליחת מיילים יצרו %{count} תקלות ב 24 השעות האחרונות. צפו ביומנים לפרטים נוספים." - missing_mailgun_api_key: "השרת מכוון לשלוח מיילים באמצעות mailgun אבל לא סיפקתם מפתח API שמוודא את הודעות ה webhook." bad_favicon_url: "ה favicon לא עולה. אנא בדקו את הגדרת ה favicon_url ב הגדרות האתר." poll_pop3_timeout: "החיבור לשרת POP3 התנתק. דוא\"ל נכנס לא יכול להשלף ואינו מאוחזר. אנא בדקו את הגדרות ה-POP3 שלכם ואת ספק השירות." poll_pop3_auth_error: "החיבור לשרת POP3 נכשל בשל שגיאת הזדהות. אנא בדקו את הגדרות ה-POP3 שלכם." @@ -849,13 +847,11 @@ he: set_locale_from_accept_language_header: "קבעו את שפת הממשק עבור משתמשים אנונימיים לפי השפה בדפדפן. (נ-י-ס-י-ו-נ-י, לא עובד עם cache אנונימי)" min_post_length: "מספר התווים המותר כאורך מינימלי לפוסט" min_first_post_length: "אורך מינימלי מותר לפוסט ראשון (בגוף הנושא) בתווים " - min_private_message_post_length: "אורך הפוסט המינימלי המותר בתווים להודעות" max_post_length: "מספר התווים המקסימלי כאורך פוסט" topic_featured_link_enabled: "איפשור של פרסום קישור עם נושאים." show_topic_featured_link_in_digest: "הצגת קישור מומלץ של הנושא במייל התמצות." min_topic_title_length: "מספר התווים המינימלי הנדרש לכותרת נושא" max_topic_title_length: "מספר התווים המקסימלי המותר לכותרת נושא" - min_private_message_title_length: "אורך הכותרת המנימילי המותר להודעה בתווים" min_search_term_length: "מספר התווים המינמלי התקין כאורך מונח לחיפוש" search_tokenize_chinese_japanese_korean: "אלצו את החיפוש לנתח סינית/יפנית/קוריאנית גם באתרים שאינם בשפות אלו" search_prefer_recent_posts: "אם חיפוש בפורום הגדול שלכם איטי, אופציה זו מנסה לאנדקס קודם כל את הפוסטים החדשים יותר" @@ -904,7 +900,6 @@ he: summary_likes_required: "מינימום הלייקים לנושא לפני שהאפשרות \"סיכום נושא זה\" תתאפשר" summary_percent_filter: "כאשר משתמש/ת מקליקים על \"סיכום נושא זה\", הציגו את % הפוסטים הראשונים" summary_max_results: "מספר הפוסטים שיוחזרו באמצעות \"סיכום נושא זה\"" - enable_private_messages: "הרשו למשתמשי רמת אמון 1 (ניתן להגדרה באמצעות רמת אמון מינימלית לשליחת הודעות) ליצור הודעות ולענות להודעות. שימו לב שהצוות תמיד יכול לשלוח הודעות, לא משנה מה." enable_long_polling: "באס הודעות שמשמש להתראות יכול להשתמש בתשאול ארוך (long polling)" long_polling_base_url: "בסיס ה-URL שנמצא בשימוש עבור long polling (כאשר CDN מחזיר תוכן דינמי, זכרו להגדיר את ערך זה ל-Origin pull, דוגמת http://origin.site.com)" long_polling_interval: "כמות הזמן שהשרת צריך לחכות לפני שעונה ללקוחות, כאשר אין מידע לשליחה (משתמשים רשומים מחוברים למערכת בלבד)" @@ -1027,7 +1022,6 @@ he: max_bookmarks_per_day: "מספר מקסימלי של סימניות למשתמשים ביום." max_edits_per_day: "מספר עריכות מקסימלי מותר למשתמשים ליום." max_topics_per_day: "מספר מקסימלי של נושאים שמשתמשים יכולים ליצור ביום." - max_private_messages_per_day: "מספר מקסימלי של הודעות שמשתמשים יכולים ליצור ביום." max_invites_per_day: "מספר מקסימלי של הזמנות שיכולים משתמשים לשלוח ביום." max_topic_invitations_per_day: "מספר מירבי של הזמנות לנושא שמשתמשים יכולים לשלוח ביום. " max_logins_per_ip_per_hour: "מספר מקסימלי של התחברויות מורשות לכל כתובת IP בשעה" @@ -1096,7 +1090,6 @@ he: max_users_notified_per_group_mention: "מספר מקסימלי של משתמשים שיכולים לקבל התראה אם מוזכרת קבוצה (אם מגיעים לסף זה לא תועלה התראה)" create_thumbnails: "יצירת תמונות מוקטנות והארת תמונות גדולות מידי מלהיכלל בפוסט." email_time_window_mins: "המתינו (n) דקות לפני משלוח כל התראת מייל, כדי לאפשר למשתמשים הזדמנות לערוך ולוודא באופן סופי את הפוסטים שלהם." - private_email_time_window_seconds: "המתינו (n) שניות לפני משלוח מיילים אישיים להתראה, על מנת לאפשר למשתמשים לערוך או לתקן את ההודעה." email_posts_context: "כמה תגובות קודמות יש לכלול כהקשר במיילים עם התראות." flush_timings_secs: "באיזו תדירות אנחנו מזרימים מידע לשרת, בשניות." title_max_word_length: "האורך המקסימלי המותר למילה בכותרת נושא, בתווים. " @@ -1263,7 +1256,6 @@ he: max_allowed_message_recipients: "מספר מקסימלי של נמענים מותר בהודעה." default_email_digest_frequency: "באיזו תדירות משתמשים יקבלו סיכומי מיילים כברירת מחדל." default_include_tl0_in_digests: "כללו פוסטים ממשתמשים חדשים בדוא\"ל מסכם כברירת מחדל. משתמשים יוכלו לשנות זאת בהעדפות האישיות." - default_email_private_messages: "שלח מייל כשמישהו שולח הודעה למשתמש, בתור ברירת מחדל." default_email_direct: "שלח מייל כשמישהו מצטט/מגיב-ל/מזכיר או מזמין משתמש, בתור ברירת מחדל." default_email_mailing_list_mode: "שלח מייל עבור כל פוסט חדש בתור ברירת מחדל. " default_email_mailing_list_mode_frequency: "משתמשים שאיפשרו את מצב רשימת התפוצה יקבלו מיילים בתדירות זו כברירת מחדל." diff --git a/config/locales/server.id.yml b/config/locales/server.id.yml index 322bc4eb79..7af5a0e37c 100644 --- a/config/locales/server.id.yml +++ b/config/locales/server.id.yml @@ -219,7 +219,6 @@ id: leader: title: "pemimpin" rate_limiter: - too_many_requests: "Kami membatasi jumlah tindakan itu dapat dilakukan dalam satu hari. Mohon tunggu %{time_left} sebelum mencoba lagi." hours: other: "%{count} jam" minutes: diff --git a/config/locales/server.it.yml b/config/locales/server.it.yml index af3edb7ef8..f7cb2cefb9 100644 --- a/config/locales/server.it.yml +++ b/config/locales/server.it.yml @@ -448,7 +448,6 @@ it: broken: "Questa immagine è assente" rate_limiter: slow_down: "Hai eseguito questa operazione troppe volte, riprova più tardi." - too_many_requests: "C'è un limite giornaliero su quante volte si può fare questa azione. Per favore attendi %{time_left} prima di riprovare." by_type: first_day_replies_per_day: "Hai raggiunto il massimo numero di risposte che può creare un nuovo utente il suo primo giorno. Per favore attendi %{time_left} prima di riprovare." first_day_topics_per_day: "Hai raggiunto il massimo numero di argomenti che può creare un nuovo utente il suo primo giorno. Per favore attendi %{time_left} prima di riprovare." @@ -852,7 +851,6 @@ it: email_polling_errored_recently: one: "Il polling delle email ha generato un errore nelle ultime 24 ore. Controlla i log per maggiori dettagli." other: "Il polling delle email ha generato %{count}errori nelle ultime 24 ore. Controlla i log per maggiori dettagli. " - missing_mailgun_api_key: "Il server è configurato per inviare email via mailgun ma non sono state fornite le API key necessarie per verificare i messaggi webhook." bad_favicon_url: "La favicon non si carica. Verifica la configurazione della tua favicon_url in Impostazioni del sito." poll_pop3_timeout: "La connessione al server POP3 sta scadendo. Le email in entrata non sono state scaricate. Per favore verifica il la tua configurazione POP3 e il fornitore di servizi." poll_pop3_auth_error: "La connessione al server POP3 è fallita per un errore di autenticazione. Per favore verifica la tua configurazione POP3." @@ -864,13 +862,11 @@ it: set_locale_from_accept_language_header: "imposta la lingua di interfaccia per gli utenti anonimi in base ai language header del loro browser.\n(SPERIMENTALE, non funziona con cache anonima)" min_post_length: "Lunghezza minima dei messaggi in caratteri" min_first_post_length: "Lunghezza minima del primo messaggio (corpo del testo), in caratteri" - min_private_message_post_length: "Lunghezza minima in caratteri per i messaggi" max_post_length: "Lunghezza massima dei messaggi in caratteri" topic_featured_link_enabled: "Abilita la pubblicazione di collegamenti con gli argomenti." show_topic_featured_link_in_digest: "Mostra i collegamenti in primo piano dell'argomento nella email riepilogativa." min_topic_title_length: "Numero minimo di caratteri per i titoli degli argomenti" max_topic_title_length: "Numero massimo di caratteri per i titoli degli argomenti" - min_private_message_title_length: "Numero minimo di caratteri per un messaggio" min_search_term_length: "Numero minimo di caratteri per le parole cercate" search_tokenize_chinese_japanese_korean: "Attiva la tokenizzazione dei caratteri Cinesi/Giapponesi/Coreani nella ricerca anche sui siti non CJK" search_prefer_recent_posts: "Se il tuo forum è corposo e la ricerca è lenta, questa opzione tenta di indicizzare prima i messaggi più recenti" @@ -925,8 +921,6 @@ it: summary_likes_required: "Minimo numero di \"Mi piace\" in un argomento affinché venga abilitato 'Riassumi Questo Argomento'" summary_percent_filter: "Quando un utente clicca su 'Riassumi Questo Argomento', mostra i primi % messaggi" summary_max_results: "Massimo numero di messaggi mostrati in 'Riassumi Argomento'" - enable_private_messages: "Autorizza gli utenti con livello di esperienza 1 (configurabile attraverso \"min livello di esperienza per l'invio di messaggi\") a creare e rispondere ai messaggi. Nota che lo staff può inviare messaggi in ogni caso." - enable_private_email_messages: "Consenti agli utenti a livello di esperienza 4 (configurabile attraverso \"min trust level to send messages\") di inviare messaggi privati via email . Nota che lo staff può inviare messaggi in ogni caso." enable_long_polling: "Il message bus per le notifiche può usare il long polling" long_polling_base_url: "URL di base usato per il long polling (quando una CDN serve contenuto dinamico, bisogna impostarlo come origin pull) es. http://origin.site.com" long_polling_interval: "Tempo di attesa prima che il server risponda ai client che non ci sono dati da trasmettere (solo per utenti autenticati)" @@ -1059,7 +1053,6 @@ it: max_bookmarks_per_day: "Massimo numero di segnalibri per utente al giorno." max_edits_per_day: "Massimo numero di modifiche per utente al giorno." max_topics_per_day: "Massimo numero di argomenti che un utente può creare al giorno." - max_private_messages_per_day: "Numero massimo di messaggi che gli utenti possono creare al giorno." max_invites_per_day: "Numero massimo di inviti che un utente può inviare in un giorno." max_topic_invitations_per_day: "Numero massimo di inviti ad argomenti che un utente può inviare al giorno." max_logins_per_ip_per_hour: "Numero massimo di accessi consentiti per indirizzo Ip per ora" @@ -1129,7 +1122,6 @@ it: max_users_notified_per_group_mention: "Massimo numero di utenti che possono ricevere una notifica se un gruppo è menzionato (se la soglia è raggiunta, nessuna notifica verrà inviata)" create_thumbnails: "Crea anteprime e lightbox delle immagini che sono troppo grandi per essere contenute in un messaggio." email_time_window_mins: "Aspetta (n) minuti prima di inviare email di notifica, per dare agli utenti la possibilità di modificare e completare i loro messaggi." - private_email_time_window_seconds: "Attendi (n) secondi prima di inviare una notifica privata per email, per dare agli utenti la possibilità di modificare e finalizzare i loro messaggi." email_posts_context: "Quante risposte precedenti inserire come contesto nelle email di notifica." flush_timings_secs: "Frequenza di svuotamento dei dati temporali verso il server, in secondi." title_max_word_length: "La lunghezza massima di una parola, in caratteri, nel titolo di un argomento." @@ -1306,7 +1298,6 @@ it: watched_words_regular_expressions: "Le parole osservate sono espressioni regolari." default_email_digest_frequency: "Con quale frequenza gli utenti ricevono email riepilogative di default." default_include_tl0_in_digests: "Per impostazione predefinita, includi i messaggi dei nuovi utenti nelle email riepilogative. Gli utenti possono modificare questa impostazione nelle loro preferenze" - default_email_private_messages: " Invia una email quando qualcuno scrive un messaggio ad un utente di default." default_email_direct: "Invia una email quando qualcuno cita/risponde/menziona un utente di default." default_email_mailing_list_mode: "Invia una email per ogni nuovo messaggio di default." default_email_mailing_list_mode_frequency: "Con quale frequenza gli utenti che hanno attivato la modalità mailing list riceveranno email di default." @@ -2327,21 +2318,6 @@ it: signup_after_approval: title: "Iscrizione Dopo Approvazione" subject_template: "Sei stato ammesso su %{site_name}!" - text_body_template: | - Benvenuto su %{site_name}! - - Un membro dello staff ha approvato il tuo account su %{site_name}. - - Clicca sul seguente collegamento per confermare e attivare il tuo nuovo account: - %{base_url}/u/activate-account/%{email_token} - - Se il collegamento qui sopra non è cliccabile, prova a copiarlo e incollarlo nella barra degli indirizzi del tuo browser. - - %{new_user_tips} - - Noi crediamo da sempre in un [comportamento comunitario civile](%{base_url}/guidelines). - - Buona permanenza! signup: title: "Iscrizione" subject_template: "[%{email_prefix}] Conferma il tuo nuovo account" diff --git a/config/locales/server.ja.yml b/config/locales/server.ja.yml index 787fa3dd93..20df5c6004 100644 --- a/config/locales/server.ja.yml +++ b/config/locales/server.ja.yml @@ -249,7 +249,6 @@ ja: title: "ベーシックユーザー" change_failed_explanation: "%{user_name} を '%{new_trust_level}' に下げようとしましたが、既にトラストレベルが '%{current_trust_level}' です。%{user_name} は '%{current_trust_level}' のままになります - もしユーザーを降格させたい場合は、トラストレベルをロックしてください" rate_limiter: - too_many_requests: "このアクションを一日の間に実施可能な回数が決まっています。%{time_left}待ってから再度試してください。" by_type: public_group_membership: "グループへの参加/離脱が多すぎます。%{time_left}お待ち下さい。" hours: @@ -392,6 +391,7 @@ ja: unsubscribe: title: "配信停止" unwatch_category: "%{category}内の全トピックのウォッチを解除" + all: "%{sitename}からのメールを私に送信しないでください。" user_api_key: title: "アプリケーションアクセスの認証" description: "\"%{application_name}\" があなたのアカウントへのアクセスを要求しています: " @@ -552,11 +552,9 @@ ja: allow_user_locale: "ユーザーが言語を選択できるようにする" min_post_length: "投稿を許可する最少の文字数" min_first_post_length: "最初の投稿(投稿本文)を許可する最少の文字数" - min_private_message_post_length: "メッセージに投稿可能な最少の文字数" max_post_length: "投稿を許可する最大の文字数" min_topic_title_length: "トピックタイトルとして許可する最小の文字数" max_topic_title_length: "トピックタイトルとして許可する最大の文字数" - min_private_message_title_length: "プライベートメッセージのタイトルとして許容する最小の文字数" min_search_term_length: "検索ワードとして有効にする最小の文字数" allow_uncategorized_topics: "カテゴリなしのトピック作成を許可するか。警告:未分類のトピックがある場合は、これをオフにする前に、再分類する必要があります。" allow_duplicate_topic_titles: "トピックタイトルの重複を許可" @@ -676,7 +674,6 @@ ja: max_bookmarks_per_day: "ユーザが一日にブックマークできる最大数" max_edits_per_day: "ユーザが一日に編集できる最大数" max_topics_per_day: "ユーザが一日に作成できるトピックの最大数" - max_private_messages_per_day: "ユーザが一日に作成できるメッセージの最大数" max_invites_per_day: "ユーザが一日に招待できる最大数" max_topic_invitations_per_day: "ユーザが一日にトピックに招待できる最大数" suggested_topics: "トピック下部に表示されるおすすめトピックの数" diff --git a/config/locales/server.ko.yml b/config/locales/server.ko.yml index 27f21799fc..351eb5c9c6 100644 --- a/config/locales/server.ko.yml +++ b/config/locales/server.ko.yml @@ -405,7 +405,6 @@ ko: change_failed_explanation: "당신은 %{user_name} 사용자를 '%{new_trust_level}'으로 강등시키려 하였습니다. 하지만 해당 사용자의 회원등급는 이미 '%{current_trust_level}'입니다. %{user_name} 사용자의 회원등급는 '%{current_trust_level}'으로 유지됩니다. - if you wish to demote user lock trust level first" rate_limiter: slow_down: "해당 작업을 너무 많이 수행했습니다. 잠시후 다시 시도해보세요. " - too_many_requests: "지금 하시려는 행동에는 하루 제한이 있습니다. %{time_left} 동안 기다리시고 다시 시도해 주세요." by_type: first_day_replies_per_day: "새 유저가 가입한 첫 날에 작성할 수 있는 댓글의 최대 갯수에 도달했습니다. %{time_left}동안 기다리셨다가 다시 시도해보세요." first_day_topics_per_day: "새 유저가 가입한 첫 날에 작성할 수 있는 토픽의 최대 갯수에 도달했습니다. %{time_left}동안 기다리셨다가 다시 시도해보세요." @@ -781,7 +780,6 @@ ko: subfolder_ends_in_slash: "서브폴더 설정이 정확하지 않습니다. DISCOURSE_RELATIVE_URL_ROOT 다음에 슬래시가 있습니다." email_polling_errored_recently: other: "이메일 폴링에서 지난 24시간 동안%{count}개의 에러가 발생하였습니다. 세부 정보는 로그에서 확인하세요." - missing_mailgun_api_key: "서버가 mailgun으로 이메일을 보내도록 설정되어 있지만, webhook 메시지를 인증한 API키를 입력하지 않으셨습니다." bad_favicon_url: "파비콘 불러오기가 실패했습니다. 사이트 설정에서 favicon_url 설정을 확인해보세요." poll_pop3_timeout: "타임아웃으로 POP3 연결이 실패했습니다. 수신 이메일을 가져올 수 없습니다. POP3 설정 과 서비스 제공자를 확인하세요." poll_pop3_auth_error: "인증 실패로 POP3 연결이 실패했습니다. POP3 설정을 확인하세요." @@ -793,13 +791,11 @@ ko: set_locale_from_accept_language_header: "익명 사용자의 인터페이스 언어를 웹브라우저 언어 헤더를 기준으로 변경하기(실험적인 기능입니다. 익명 cache와 동작하지 않습니다.)" min_post_length: "글의 최소 글자 수" min_first_post_length: "첫 글 (내용)의 최소 길이" - min_private_message_post_length: "메세지 내용의 최소 길이" max_post_length: "글의 최대 글자 수" topic_featured_link_enabled: "토픽에 링크 게시글 달기 활성화" show_topic_featured_link_in_digest: "요약 메일에 토픽 주요 링크 보이게 하기." min_topic_title_length: "글타래 제목의 최소 글자 수" max_topic_title_length: "글타래 제목의 최대 글자 수" - min_private_message_title_length: "메세지 제목의 최소 길이" min_search_term_length: "검색을 하기 위한 최소 글자 수" search_tokenize_chinese_japanese_korean: "CJK(한중일) 설정이 되어 있지 않은 사이트라도, 검색시 한중일 언어를 토크나이징하도록 강제합니다." search_prefer_recent_posts: "이 옵션을 켜면, 포럼이 너무 커서 검색이 느리게 작동할 때 최근의 포스트부터 먼저 검색합니다." @@ -848,7 +844,6 @@ ko: summary_likes_required: "하나의 글타래에 대하여 요약본 보기 모드가 활성화되기 전까지 요구되는 최소 좋아요 수" summary_percent_filter: "요약본 보기를 클릭시, 글 중에 몇 %의 상위 글을 보여줄 것인가?" summary_max_results: "이 주제에 대한 요약 글 최대 갯수" - enable_private_messages: "신뢰도 1 사용자(메시지 전송을 위한 최소 신뢰도로 설정가능)가 메시지를 작성하고 답장을 쓸 수 있도록 허용합니다. 운영진은 언제나 메시지를 보낼 수 있음을 참고하세요." enable_long_polling: "Message bus used for notification can use long polling" long_polling_base_url: "long polling에 사용 될 Base URL (CDN이 동적 콘텐트를 제공할 시에는 origin pull로 설정) eg: http://origin.site.com" long_polling_interval: "보낼 데이터가 없을 때 응답 전에 서버가 기다려야하는 시간 (로그인된 유저 전용)" @@ -973,7 +968,6 @@ ko: max_bookmarks_per_day: "사용자가 하루동안 할 수 있는 최대 북마크 개수" max_edits_per_day: "사용자가 하루동안 할 수 있는 최대 편집 수" max_topics_per_day: "사용자가 하루동안 생성할 수 있는 최대 글타래 개수" - max_private_messages_per_day: "하루에 유저가 쓸 수 있는 최대 메세지 갯수" max_invites_per_day: "하루에 보낼 수 있는 초대장의 최대치입니다." max_topic_invitations_per_day: "하루에 유저가 최대로 보낼 수 있는 글타래 초대장 수" max_logins_per_ip_per_hour: "시간 당 동일 IP에서 로그인할 수 있는 최대 사용자 수" @@ -1041,7 +1035,6 @@ ko: max_users_notified_per_group_mention: "그룹이 멘션을 받으면 알림을 받는 최대사용자의 수(최대치에 도달하면 알림이 전송되지 않음)" create_thumbnails: "글의 너무 큰 크기의 이미지는 thumnails와 lightbox를 만든다." email_time_window_mins: "알림 메일을 보내기 전 대기 기간(분), 사용자에게 글의 변경하고 완료할 수 있는 기회를 준다." - private_email_time_window_seconds: "사용자가 메시지를 재편집할 기회를 주기 위하여 개인 알림 메일을 보내기 이전에 설정한 시간만큼 대기합니다." email_posts_context: "알림메일의 내용에 추가할 기존 답글 수" flush_timings_secs: "사용자의 이용 시간 데이터를 서버로 보내는 기간(초)" title_max_word_length: "글타래 제목안에 단어들의 최대 길이" diff --git a/config/locales/server.nb_NO.yml b/config/locales/server.nb_NO.yml index ef38d44f60..2c91f12b9a 100644 --- a/config/locales/server.nb_NO.yml +++ b/config/locales/server.nb_NO.yml @@ -350,7 +350,6 @@ nb_NO: leader: title: "leder" rate_limiter: - too_many_requests: "Vi har en daglig begrensning for hvor mange ganger den handlingen kan utføres. Vent %{time_left} før du prøver igjen." by_type: first_day_replies_per_day: "Du har nådd det maksimale antall svar en ny bruker kan publisere på sin første dag. Vennligst vent %{time_left} før du prøver igjen." first_day_topics_per_day: "Du har nådd det maksimale antall tråder en ny bruker kan publisere på sin første dag. Vennligst vent %{time_left} før du prøver igjen." @@ -693,11 +692,9 @@ nb_NO: allow_user_locale: "Tillat brukere å velge eget språk" min_post_length: "Minimum tillatt lengde for innlegg i tegn" min_first_post_length: "Minimum tillatt lengde på for teksten i første innlegg" - min_private_message_post_length: "Minste tillatte innleggslengde for meldinger i antall tegn" max_post_length: "Maksimum tillatt lengde for innlegg i tegn" min_topic_title_length: "Minimum tillatt lengde for tittel i tegn" max_topic_title_length: "Maksimum tillatt lengde for innlegg i tegn" - min_private_message_title_length: "Minste antall tegn tillatt til bruk for tittellengde i meldinger" min_search_term_length: "Minimum lengde på søkeord i tegn" allow_duplicate_topic_titles: "Tillat flere tråder med identisk tittel." unique_posts_mins: "Hvor mange minutter før en bruker kan lage et innlegg med det samme innholdet igjen" diff --git a/config/locales/server.nl.yml b/config/locales/server.nl.yml index f486c0f4ca..65352818d6 100644 --- a/config/locales/server.nl.yml +++ b/config/locales/server.nl.yml @@ -435,7 +435,6 @@ nl: change_failed_explanation: "U probeerde %{user_name} naar '%{new_trust_level}' te degraderen. Zijn of haar vertrouwensniveau is echter al '%{current_trust_level}'. %{user_name} blijft op vertrouwensniveau '%{current_trust_level}'. Als u de gebruiker wil degraderen, vergrendel dan eerst het vertrouwensniveau." rate_limiter: slow_down: "U hebt deze actie te vaak uitgevoerd; probeer het later opnieuw." - too_many_requests: "Er is een dagelijks limiet voor hoe vaak die actie kan worden uitgevoerd. Wacht %{time_left} voordat u het opnieuw probeert." by_type: first_day_replies_per_day: "U hebt het maximale aantal antwoorden dat een nieuwe gebruiker op de eerste dag kan plaatsen bereikt. Wacht %{time_left} voordat u het opnieuw probeert." first_day_topics_per_day: "U hebt het maximale aantal topics dat een nieuwe gebruiker op de eerste dag kan plaatsen bereikt. Wacht %{time_left} voordat u het opnieuw probeert." @@ -834,7 +833,6 @@ nl: email_polling_errored_recently: one: "E-mailpolling heeft de afgelopen 24 uur een fout gegenereerd. Bekijk de logboeken voor meer details." other: "E-mailpolling heeft de afgelopen 24 uur %{count} fouten gegenereerd. Bekijk de logboeken voor meer details." - missing_mailgun_api_key: "De server is geconfigureerd om e-mails via Mailgun te verzenden, maar u hebt geen API-sleutel opgegeven voor verificatie van de webhookberichten." bad_favicon_url: "De favicon wordt niet geladen. Controleer uw favicon_url-instelling in de Website-instellingen." poll_pop3_timeout: "Time-out voor verbinding met de POP3-server. Binnenkomende e-mail kon niet worden opgehaald. Controleer uw POP3-instellingen en serviceprovider." poll_pop3_auth_error: "Verbinding met de POP3-server is mislukt met een authenticatiefout. Controleer uw POP3-instellingen." @@ -846,13 +844,11 @@ nl: set_locale_from_accept_language_header: "Taal van interface voor anonieme gebruikers instellen op basis van de taalheaders van hun webbrowser. (EXPERIMENTEEL, werkt niet met anonieme cache)" min_post_length: "Minimaal toegestane lengte van een bericht in tekens" min_first_post_length: "Minimaal toegestane lengte van eerste bericht (topictekst) in tekens" - min_private_message_post_length: "Minimaal toegestane berichtlengte in tekens voor privéberichten" max_post_length: "Maximaal toegestane lengte van een bericht in tekens" topic_featured_link_enabled: "Plaatsing van een koppeling met topics toestaan" show_topic_featured_link_in_digest: "Koppeling naar aanbevolen topics in de samenvattingsmail tonen" min_topic_title_length: "Minimaal toegestane lengte van een topictitel in tekens" max_topic_title_length: "Maximaal toegestane lengte van een topictitel in tekens" - min_private_message_title_length: "Minimaal toegestane titellengte voor een bericht in tekens" min_search_term_length: "Minimumlengte van een zoekterm in tekens" search_prefer_recent_posts: "Als doorzoeken van uw grote forum traag werkt, probeert deze optie eerst een index van recentere berichten" search_recent_posts_size: "Het aantal in de index te behouden recente berichten " @@ -897,7 +893,6 @@ nl: summary_likes_required: "Minimale aantal likes in een topic voordat 'Dit topic samenvatten' wordt ingeschakeld" summary_percent_filter: "Wanneer een gebruiker op 'Dit topic samenvatten' klikt, de top % van berichten tonen" summary_max_results: "Maximale aantal getoonde berichten in 'Dit topic samenvatten'" - enable_private_messages: "Gebruikers met vertrouwensniveau 1 (instelbaar via het minimale niveau om berichten te verzenden) toestaan om berichten aan te maken en op berichten te antwoorden. Houd er rekening mee dat stafleden altijd berichten kunnen verzenden." enable_long_polling: "Gebruikte 'message bus' voor melding kan 'long polling' gebruiken" long_polling_base_url: "Basis-URL voor 'long polling' (als een CDN dynamische inhoud aanbiedt, zorg er dan voor dat dit op 'origin pull' is ingesteld), zoals http://origin.site.com" long_polling_interval: "De hoeveelheid tijd die de server zou moeten wachten voordat het clients beantwoord als er geen data te verzenden is (alleen voor ingelogde gebruikers)" @@ -1000,7 +995,6 @@ nl: max_bookmarks_per_day: "Maximale aantal bladwijzers per gebruiker per dag." max_edits_per_day: "Maximale aantal bewerkingen per gebruiker per dag." max_topics_per_day: "Maximale aantal topics dat een gebruiker per dag kan plaatsen." - max_private_messages_per_day: "Maximale aantal berichten dat een gebruiker per dag kan plaatsen." max_invites_per_day: "Maximale aantal uitnodigingen dat een gebruiker per dag kan versturen." max_topic_invitations_per_day: "Maximale aantal uitnodigingen voor een topic dat een gebruiker per dag kan versturen." max_logins_per_ip_per_hour: "Maximale aantal toegestane aanmeldingen per IP-adres per uur" @@ -1180,7 +1174,6 @@ nl: approve_post_count: "Het aantal berichten van een nieuwe of normale gebruiker dat moet worden goedgekeurd" approve_unless_trust_level: "Posts voor gebruikers onder dit vertrouwensniveau moeten worden goedgekeurd" approve_new_topics_unless_trust_level: "Nieuwe topics voor gebruikers onder dit vertrouwensniveau moeten worden goedgekeurd" - default_email_private_messages: "Stuur standaard een e-mail als iemand de gebruiker bericht." default_email_direct: "Stuur standaard een e-mail wanneer iemand de gebruiker citeert/beantwoordt/vermeldt of uitnodigt." default_email_mailing_list_mode: "Stuur standaard een email voor elk nieuw bericht." default_email_always: "Standaard een e-mailmelding verzenden, zelfs wanneer de gebruiker actief is" diff --git a/config/locales/server.pl_PL.yml b/config/locales/server.pl_PL.yml index 80e185dc7e..c5ebe7974c 100644 --- a/config/locales/server.pl_PL.yml +++ b/config/locales/server.pl_PL.yml @@ -468,7 +468,6 @@ pl_PL: broken: "Ten obraz jest uszkodzony" rate_limiter: slow_down: "Powtórzyłeś to działanie zbyt wiele razy, spróbuj ponownie później." - too_many_requests: "Liczba wykonań tej czynności w ciągu dnia jest ograniczona. Odczekaj %{time_left} przed ponowną próbą." by_type: first_day_replies_per_day: "Osiągnąłeś maksymalną liczbę odpowiedzi, jakie może napisać nowy użytkownik pierwszego dnia. Musisz odczekać %{time_left} zanim znów spróbujesz." first_day_topics_per_day: "Osiągnąłeś maksymalną liczbę tematów, jakie może napisać nowy użytkownik pierwszego dnia. Musisz odczekać %{time_left}, zanim znów spróbujesz." @@ -923,7 +922,6 @@ pl_PL: few: "Email polling wygenerował %{count} błędy w przeciągu ostatniej doby. Więcej szczegółów w logach." many: "Email polling wygenerował %{count} błędów w przeciągu ostatniej doby. Więcej szczegółów w logach." other: "Email polling wygenerował %{count} błędów w przeciągu ostatniej doby. Więcej szczegółów w logach." - missing_mailgun_api_key: "Serwer jest skonfigurowany by wysyłać maile przez mailguna, ale nie ustawiłeś klucza API niezbędnego do zweryfikowania wiadomości webhook." bad_favicon_url: "Nie można załadować favikony. Sprawdź ustawiony favicon_url w Ustawieniach Strony." poll_pop3_timeout: "Połączenie z serwerem POP3 zostało zerwane. Przychodząca wiadomość mogła nie zostać odebrany. Proszę sprawdź swoje i dostawcę usług." poll_pop3_auth_error: "Połączenie z serwerem POP3 nie powiodło się przez błąd uwierzytelnienia. Proszę sprawdź swoje ustawienia POP3." @@ -935,13 +933,11 @@ pl_PL: set_locale_from_accept_language_header: "ustaw język interfejsu dla niezalogowanych użytkowników na podstawie języka w nagłówku ich przeglądarki. (EKSPERYMENTALNE, nie działa z anonimowym cache)" min_post_length: "Minimalna długość wpisu w znakach" min_first_post_length: "Minimalna długość treści (liczba znaków) pierwszego wpisu w temacie " - min_private_message_post_length: "Minimalna długość treści wiadomości " max_post_length: "Maksymalna długość wpisu, w znakach" topic_featured_link_enabled: "Włącz dodawanie linków w tematach." show_topic_featured_link_in_digest: "Pokazuj polecany link tematu w podsumowaniu mailowym." min_topic_title_length: "Minimalna długość tytułu tematu, w znakach" max_topic_title_length: "Maksymalna długość tytułu tematu, w znakach" - min_private_message_title_length: "Minimalna liczba znaków w temacie wiadomości " min_search_term_length: "Minimalna długość wyszukiwanego tekstu, w znakach" search_tokenize_chinese_japanese_korean: "Wymuś wyszukiwanie żeby tokenizowało Chiński/Japoński/Koreański nawet na stronach nie-CJK" search_prefer_recent_posts: "Jeśli wyszukiwanie na twoim duży forum jest wolne, ta opcja pozwala zaindeskować ostanie posty wpierw." @@ -993,7 +989,6 @@ pl_PL: summary_likes_required: "Minimalna liczba polubień w temacie zanim 'Podsumowanie tematu' jest dostępne" summary_percent_filter: "Gdy użytkownik kliknie na 'Podsumowaniu tematu', pokaż % najlepszych wpisów" summary_max_results: "Maksymalna liczba wpisów w 'Podsumowaniu tematu'" - enable_private_messages: "Zezwalaj użytkownikom o poziomie zaufania 1 (możliwe do zmiany przez min trust level to send messages) na tworzenie wiadomości i odpowiadanie na nie. Zwróć uwagę, że administracja zawsze może wysyłać wiadomości bez względu na wszystko." enable_long_polling: "Message bus used for notification can use long polling" long_polling_base_url: "Podstawowy URL używany dla long polling (kiedy CDN dostarcza dynamicznych treści, upewnij się że ustawiłeś to w origin pull) np.: http://origin.site.com" long_polling_interval: "Okres czasu jaki serwer powinien odczekać przed odpowiedzią klientowi kiedy nie ma danych do przesłania (tylko do zalogowanych użytkowników)" @@ -1119,7 +1114,6 @@ pl_PL: max_bookmarks_per_day: "Maksymalna liczba zakładek per użytkownik per dzień." max_edits_per_day: "Maksymalna liczba edycji per użytkownik per dzień." max_topics_per_day: "Maksymalna liczba tematów jakie użytkownik może stworzyć jednego dnia." - max_private_messages_per_day: "Maksymalna liczba wiadomości jakie użytkownik może wysłać jednego dnia." max_invites_per_day: "Maksymalna liczba zaproszeń jakie użytkownik może wysłać jednego dnia." max_topic_invitations_per_day: "Maksymalna dzienna liczba zaproszeń do tematu, jakie może wysłać użytkownik." max_logins_per_ip_per_hour: "Maksymalna liczba logowań dozwolona per adres IP na godzinę" @@ -1188,7 +1182,6 @@ pl_PL: max_users_notified_per_group_mention: "Maksymalna liczba użytkowników, którzy mogą otrzymać powiadomienie jeśli ktoś wspomniał o grupie (jeśli próg został osiągnięty, nie będzie żadnych powiadomień)" create_thumbnails: "Stwórz miniatury i obrazy lightbox, które są za duże, aby pasować do postu." email_time_window_mins: "Odczekaj (n) minut przed wysłaniem maila z powiadomieniem, aby dać użytkownikom szansę na edytowanie i ukończenie postów." - private_email_time_window_seconds: "Poczekaj (n) sekund przed wysłaniem jakichkolwiek prywatnych powiadomień emailem, by dać szansę użytkownikom na edycję i sfinalizowanie ich wiadomości." email_posts_context: "Jak wiele poprzednich wiadomości zawrzeć jako kontekst i emailu powiadamiającym." flush_timings_secs: "W sekundach, jak często wysyłamy dane czasowe na serwer." title_max_word_length: "Maksymalna dozwolona długość słowa, w znakach, jako tytuł tematu." @@ -1356,7 +1349,6 @@ pl_PL: max_allowed_message_recipients: "Maksymalna dozwolona liczba odbiorców dla wiadomości." default_email_digest_frequency: "Domyślnie, jak często użytkownicy otrzymują emaile podsumowujące." default_include_tl0_in_digests: "Domyślnie uwzględniaj posty od nowych użytkowników w podsumowaniach mailowych. Użytkownicy mogą dokonywać zmian w swoich ustawieniach." - default_email_private_messages: "Domyślnie wysyłaj email, gdy użytkownik otrzyma wiadomość." default_email_direct: "Domyślnie wysyłaj email, gdy ktoś zacytuje/odpowie/wspomni lub zaprosi użytkownika." default_email_mailing_list_mode: "Domyślnie wyślij email dla każdego nowego posta." default_email_mailing_list_mode_frequency: "Domyślnie użytkownicy, którzy włączą tryb listy mailingowej będą otrzymywać emaile tak często." @@ -2255,21 +2247,6 @@ pl_PL: signup_after_approval: title: "Zarejestruj się po zatwierdzeniu" subject_template: "Zostałeś zaakceptowany na forum %{site_name}!" - text_body_template: | - Witamy na %{site_name}! - - Nasz personel zatwierdził twoje konto na %{site_name}. - - Kliknij na podany link, aby potwierdzić i aktywować swoje nowe konto: - %{base_url}/u/activate-account/%{email_token} - - Jeśli podany link nie działa, spróbuj skopiować go i przykleić w pasku adresów swojej wyszukiwarki. - - %{new_user_tips} - - Wierzymy, że [cywilizowana dyskusja](%{base_url}/guidelines) jest zawsze możliwa. - - Baw się dobrze! signup: title: "Podpis" subject_template: "[%{site_name}] Potwierdź swoje nowe konto" diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml index e7346ed940..c2d79d6f3b 100644 --- a/config/locales/server.pt.yml +++ b/config/locales/server.pt.yml @@ -378,7 +378,6 @@ pt: change_failed_explanation: "Tentou despromover %{user_name} para '%{new_trust_level}'. Contudo o Nível de Confiança é atualmente '%{current_trust_level}'. %{user_name} irá permanecer em '%{current_trust_level}' - se deseja despromover o utilizador, bloqueie o Nível de Confiança primeiro" rate_limiter: slow_down: "Realizou esta ação demasiadas vezes, tente novamente mais tarde." - too_many_requests: "Possuímos um limite diário de número de vezes que uma ação pode ser tomada. Por favor aguarde %{time_left} antes de tentar novamente." by_type: first_day_replies_per_day: "Atingiu o número máximo de respostas que um novo utilizador pode criar no seu primeiro dia. Por favor espere %{time_left} antes de tentar novamente." first_day_topics_per_day: "Atingiu o número máximo de tópicos que um novo utilizador pode criar no seu primeiro dia. Por favor espere %{time_left} antes de tentar novamente." @@ -769,7 +768,6 @@ pt: email_polling_errored_recently: one: "A consulta automática de emails gerou um erro nas últimas 24 horas. Consulte em os registos para mais detalhes." other: "A consulta automática de emails gerou %{count} erros nas últimas 24 horas. Consulte em os registos para mais detalhes." - missing_mailgun_api_key: "O servidor está configurado para enviar email via mailgun mas você não forneceu uma chave de API para verificar as mensagens de webhook." bad_favicon_url: "Não estamos a conseguir carregar o favicon. Por favor, confirme a sua configuração favicon_url nas Configurações do sítio." poll_pop3_timeout: "A tentativa de ligação ao servidor POP3 está a ultrapassar o tempo máximo. O email da caixa de entrada não pôde ser obtido. Por favor verifique a sua configuração de POP3 e fornecedor de serviço internet." poll_pop3_auth_error: "A tentativa de ligação ao servidor POP3 está a falhar por motivos de erro de autenticação. Por favor verifique a sua configuração de POP3." @@ -781,13 +779,11 @@ pt: set_locale_from_accept_language_header: "defina a língua de interface para utilizadores anónimos com base nos cabeçalhos de língua dos seus navegadores. (EXPERIMENTAL, não funciona com cache anónima)" min_post_length: "Tamanho mínimo permitido por mensagem, em caracteres" min_first_post_length: "Tamanho mínimo permitido para a primeira mensagem (corpo do tópico), em caracteres" - min_private_message_post_length: "Tamanho mínimo permitido para mensagens, em caracteres" max_post_length: "Tamanho máximo permitido por mensagem, em caracteres" topic_featured_link_enabled: "Permitir publicar uma ligação com tópicos." show_topic_featured_link_in_digest: "Mostrar a ligação destacada do tópico no email de resumo." min_topic_title_length: "Tamanho mínimo permitido por título de cada tópico, em caracteres" max_topic_title_length: "Tamanho máximo permitido por título de cada tópico, em caracteres" - min_private_message_title_length: "Tamanho mínimo permitido por título nas mensagens, em caracteres" min_search_term_length: "Tamanho mínimo válido para termos de pesquisa, em caracteres" search_tokenize_chinese_japanese_korean: "Forçar pesquisa para atomizar Chinês/Japonês/Coreano mesmo em sites que não sejam CJC" search_prefer_recent_posts: "Se pesquisar no seu fórum vasto é lento, esta opção tenta usar um índex de publicações recentes primeiro" @@ -833,7 +829,6 @@ pt: summary_likes_required: "Número mínimo de gostos num tópico antes que 'Resumir Este Tópico' seja ativo." summary_percent_filter: "Quando um utilizador clica em 'Resumir Este Tópico', mostrar as melhores % de mensagens" summary_max_results: "Número máximo de mensagens devolvidas por 'Resumo deste Tópico'" - enable_private_messages: "Permitir que utilizadores de nível de confiança 1 (configurável através do nível de confiança mínimo para enviar mensagens) criem mensagens e respostas a mensagens. Note que a equipa de apoio pode mandar mensagens de qualquer maneira." enable_long_polling: "O sistema de mensagens usado para notificações pode fazer solicitações longas" long_polling_base_url: "URL base usada para solicitação ao servidor (quando um CDN serve conteúdo dinâmico, certifique-se de configurá-lo para a 'pull' original) ex: http://origem.sítio.com" long_polling_interval: "Quantidade de tempo que um servidor deve esperar antes de notificar os clientes quando não há dados para serem enviados (apenas utilizadores ligados)" @@ -949,7 +944,6 @@ pt: max_bookmarks_per_day: "Número máximo de marcadores por utilizador por dia." max_edits_per_day: "Número máximo de edições por utilizador por dia." max_topics_per_day: "Número máximo de tópicos que um utilizador pode criar por dia." - max_private_messages_per_day: "Número máximo de mensagens que os utilizadores podem criar por dia." max_invites_per_day: "Número máximo de convites que um utilizador pode enviar por dia." max_topic_invitations_per_day: "Número máximo de convites para tópicos que um utilizador pode enviar por dia." alert_admins_if_errors_per_minute: "Número de erros por minuto necessários para disparar um alerta de administração. Um valor maior que 0 desativa esta funcionalidade. NOTA: requer reinício." @@ -1013,7 +1007,6 @@ pt: max_users_notified_per_group_mention: "Número máximo de utilizadores que podem receber uma notificação se um grupo é mencionado (se o limite é atingido nenhuma notificação será levantada)" create_thumbnails: "Criar imagens miniatura e lightbox que são demasiado largas para caber numa mensagem." email_time_window_mins: "Esperar (n) minutos antes de enviar quaisquer emails de notificação, para dar aos utilizadores a hipótese de editarem e finalizarem as suas mensagens." - private_email_time_window_seconds: "Espere (n) segundos antes de enviar emails de notificação privados, para dar aos utilizadores a oportunidade de editar e finalizar as suas mensagens." email_posts_context: "Quantas respostas prévias a serem incluídas como contexto em emails de notificação." flush_timings_secs: "Com que frequência o servidor é alimentado com dados de sincronização, em segundos." title_max_word_length: "Tamanho máximo permitido de comprimento de palavras, em caracteres, no título de um tópico." @@ -1170,7 +1163,6 @@ pt: code_formatting_style: "Botão de código no compositor por defeito usa este estilo de formatação" default_email_digest_frequency: "Por defeito, quantas frequentemente os utilizadores recebem emails de resumo." default_include_tl0_in_digests: "Incluir publicações de novos utilizadores nos emails de resumo por defeito. Utilizadores podem alterar isto nas suas preferências." - default_email_private_messages: "Por defeito, enviar um email quando alguém envia mensagens ao utilizador." default_email_direct: "Por defeito, enviar um email quando alguém cita/responde a/menciona ou convida o utilizador." default_email_mailing_list_mode: "Por defeito, enviar um email por cada nova mensagem." default_email_mailing_list_mode_frequency: "Utilizadores que activem o modo de lista de distribuição receberão, por omissão, emails com esta frequência." diff --git a/config/locales/server.pt_BR.yml b/config/locales/server.pt_BR.yml index d8f6f1a75a..448090358f 100644 --- a/config/locales/server.pt_BR.yml +++ b/config/locales/server.pt_BR.yml @@ -398,7 +398,6 @@ pt_BR: broken: "Esta imagem está danificada" rate_limiter: slow_down: "Você fez essa ação demais, tente novamente mais tarde." - too_many_requests: "Nós possuímos um limite diário do número de vezes que uma ação pode ser tomada. Por favor aguarde %{time_left} antes de tentar novamente." by_type: first_day_replies_per_day: "Você atingiu o número máximo de respostas que um novo usuário pode criar em seu primeiro dia. Por favor aguarde %{time_left} antes de tentar novamente." first_day_topics_per_day: "Você atingiu o número máximo de tópicos que um novo usuário pode criar em seu primeiro dia. Por favor aguarde %{time_left} antes de tentar novamente." @@ -781,7 +780,6 @@ pt_BR: email_polling_errored_recently: one: "A apuração de email gerou um erro nas últimas 24 horas. Veja os logs para mais detalhes." other: "A apuração de email gerou %{count} erros nas últimas 24 horas. Veja os logs para mais detalhes." - missing_mailgun_api_key: "O servidor está configurado para enviar email pelo mailgun, mas você não forneceu uma chave de API usada para verificar as mensagens de webhook." bad_favicon_url: "O carregamento do favicon está falhando. Verifique a sua configuração de favicon_url nas Configurações do Site." poll_pop3_timeout: "A conexão com o servidor POP3 está atingindo o tempo limite. Por favor, verifique suas configurações de POP3 e selecione um servidor de serviço." poll_pop3_auth_error: "A conexão com o servidor POP3 está falhando com um erro de autenticação. Por favor, verifique suas configurações de POP3." @@ -793,11 +791,9 @@ pt_BR: set_locale_from_accept_language_header: "define a língua da interface para os usuários anônimos de acordo com os cabeçalhos de língua de seus navegadores. (EXPERIMENTAL, não funciona com cache anônimo)" min_post_length: "Comprimento mínimo permitido do post em caracteres" min_first_post_length: "Comprimento mínimo de caracteres permitido para primeira mensagem (corpo do tópico)" - min_private_message_post_length: "Comprimento mínimo de caracteres permitido para mensagens" max_post_length: "Comprimento máximo permitido para o post em caracteres" min_topic_title_length: "Comprimento mínimo permitido para o título do post em caracteres" max_topic_title_length: "Comprimento máximo permitido para o título do post em caracteres" - min_private_message_title_length: "Comprimento mínimo de caracteres permitido para o título de uma mensagem" min_search_term_length: "Comprimento mínimo válido para o termo de pesquisa em caracteres" search_tokenize_chinese_japanese_korean: "Força a busca a tokenizar Mandarim/Japonês/Coreano até para sites que não são nesses idiomas" search_prefer_recent_posts: "Se buscar o seu fórum está lento, essa opção tenta indexar as publicações mais recentes primeiro" @@ -843,7 +839,6 @@ pt_BR: summary_likes_required: "Curtidas mínimas em um tópico antes de 'Resumir este tópico, ficar habilitado" summary_percent_filter: "Quando um usuário clicar em 'Resumor este tópico', mostrar os melhores % mensagens" summary_max_results: "Máximo número de posts quando resumidos por Categoria " - enable_private_messages: "Permitir que usuários de nível de confiança 1 (configurável via o nível mínimo de confiança para enviar mensagens) criem e respondam a mensagens. Note que a equipe sempre pode enviar mensagens, independente do caso." enable_long_polling: "O sistema de mensagens das notificações pode fazer solicitações longas." long_polling_base_url: "URL Utilizada para \"long polling\" ( Quando um CDN for configurado, tenha certeza que essa configuração seja a padrão) ex: http://origin.site.com" long_polling_interval: "Tempo que o servidor deve aguardar antes de responder quando não existe nenhum dado para ser enviado. (apenas usuários logados)" @@ -957,7 +952,6 @@ pt_BR: max_bookmarks_per_day: "Número máximo de novos favoritos por usuário por dia." max_edits_per_day: "Número máximo de edições que um usuário pode fazer por dia." max_topics_per_day: "Número máximo de postagens que um usuário pode criar por dia." - max_private_messages_per_day: "Máximo número de mensagens que usuários podem criar por dia." max_invites_per_day: "Número máximo de convites que um usuário pode enviar por dia." max_topic_invitations_per_day: "Número máximo de convites para tópicos que um usuário pode enviar por dia." alert_admins_if_errors_per_minute: "Número de erros por minutos para desencadear um alerta aos administradores. Um valor igual a 0 desabilita esse recurso. NOTA: requer reinicialização." diff --git a/config/locales/server.ro.yml b/config/locales/server.ro.yml index 5764c9fb1d..fbfb757e98 100644 --- a/config/locales/server.ro.yml +++ b/config/locales/server.ro.yml @@ -367,7 +367,6 @@ ro: change_failed_explanation: "Ai încercat să retrogradezi utilizatorul %{user_name} la nivelul '%{new_trust_level}'. Însă nivelul este deja '%{current_trust_level}'. %{user_name} va rămâne la '%{current_trust_level}' - dacă dorești să retrogradezi utilizatorul, trebuie să îi blochezi întâi nivelul de încredere." rate_limiter: slow_down: "Ai executat această acțiune de prea multe ori. Încearcă mai târziu." - too_many_requests: "Există o limită zilnică de executare a acestei acțiuni. Te rugăm așteaptă %{time_left} până să încerci iar." by_type: first_day_replies_per_day: "Ai atins numărul maxim de răspunsuri pe care un nou utilizator le poate crea în prima sa zi. Te rugăm să aștepți %{time_left} înainte să încerci din nou." first_day_topics_per_day: "Ai atins numărul maxim de subiecte pe care un nou utilizator poate să le creeze în prima sa zi. Te rugăm să aștepți %{time_left} înainte să încerci din nou." @@ -777,7 +776,6 @@ ro: one: "Email polling a generat o eroare în ultimele 24 de ore. Vizualizează rapoartele pentru mai multe detalii." few: "Email polling a generat %{count} erori în ultimele 24 de ore. Vizualizează rapoartele pentru mai multe detalii." other: "Retragerea de emailuri generat %{count} de erori în ultimele 24 de ore. Vizualizează rapoartele pentru mai multe detalii." - missing_mailgun_api_key: "Serverul este configurat să trimită emailuri printr-un mailgun dar nu ai furnizat nici o cheie API folosită pentru a verifica mesajele webhook." bad_favicon_url: "Eroare la încărcarea Favicon. Verifică setarea favicon_url în Setările site-ului." poll_pop3_timeout: "Conexiunea la serverul POP3 a dat time-out. Mailurile primite nu au putut fi accesate. Te rugăm să verifici setările POP3 și furnizorul de servicii." poll_pop3_auth_error: "Conexiunea la serverul POP3 a eșuat cu o eroare de autentificare. Te rugăm să verifici setările POP3." @@ -789,13 +787,11 @@ ro: set_locale_from_accept_language_header: "setează limba interfeței pentru utilizatorii anonimi pe baza header-elor de limbă ale web browser-elor lor. (EXPERIMENTAL, nu funcționează cu cache anonim)" min_post_length: "Lungimea minimă a postării, în caractere" min_first_post_length: "Numărul minim de caractere permis pentru prima postare (în corpul mesajului)" - min_private_message_post_length: "Numărul minim de caractere permis pentru mesaje, per postare" max_post_length: "Lungimea maximă permisă a postării, în caractere" topic_featured_link_enabled: "Activează postarea unui link cu subiecte." show_topic_featured_link_in_digest: "Arată link-ul promovat al subiectului în rezumatul pe email." min_topic_title_length: "Minimul de caractere permis în titlul unui subiect" max_topic_title_length: "Maximul de caractere permis în titlul unui subiect" - min_private_message_title_length: "Numărul minim de caractere permis în titlul unui mesaj" min_search_term_length: "Lungimea minimă a unui termen de căutare valid, în caractere." search_tokenize_chinese_japanese_korean: "Forțează căutarea să utilizeze tokens chineză/japoneză/coreană chiar și pe site-uri care nu sunt CJK." search_prefer_recent_posts: "Dacă ai un forum mare și căutarea este prea lentă, această opțiune încearcă să indexeze mai multe postări recente întâi." @@ -841,7 +837,6 @@ ro: summary_likes_required: "Minimul de aprecieri într-un subiect înainte de a se activa 'Rezumă acest subiect'." summary_percent_filter: "Când un utilizator face click pe 'Rezumatul acestui subiect', arată primele % de postări" summary_max_results: "Numărul maxim de postări returnate de \"Rezumatul acestui subiect\"" - enable_private_messages: "Acordă nivelul de încredere 1 (configurabil via nivel minim de încredere pentru trimiterea de mesaje) utilizatorilor, pentru a le permite să creeze și să răspundă la mesaje. Atenție: echipa poate oricând să trimită mesaje, indiferent de setări." enable_long_polling: "Bus-ul de mesaje folosit pentru notificări poate utiliza long polling." long_polling_base_url: "URL de bază folosit pentru long polling (atunci când un CDN servește conținut dinamic, asigură-te că setezi asta pe origin pull) ex: http://origin.site.com" long_polling_interval: "Durata cât serverul va trebui să aștepte înainte de a răspunde clienților atunci când nu există date de trimis (exclusiv utilizatori autentificați)" @@ -958,7 +953,6 @@ ro: max_bookmarks_per_day: "Numărul maxim zilnic de semne de carte per utilizator." max_edits_per_day: "Numărul maxim zilnic de editări per utilizator." max_topics_per_day: "Numărul maxim de subiecte pe care un utilizator le poare crea zilnic." - max_private_messages_per_day: "Număr maxim de mesaje pe care utilizatorii le pot crea pe zi." max_invites_per_day: "Număr maxim de invitații pe care un utilizator le poate trimite pe zi." max_topic_invitations_per_day: "Numărul maxim de invitații la subiect pe care un utilizator le poate trimite pe zi." alert_admins_if_errors_per_minute: "Numărul de erori pe minut la care să se declanșeze o alerta admin. Valoarea 0 dezactivează această funcționalitate. NOTĂ: necesită restart." @@ -1022,7 +1016,6 @@ ro: max_users_notified_per_group_mention: "Numărul maxim de utilizatori care pot primi o notificare dacă un grup este menționat (dacă pragul este atins, nici o notificare nu va fi trimisă)" create_thumbnails: "Creează previzualizări de tipul thumbnail și lightbox ale imaginilor ce sunt prea mari să încapă într-o postare." email_time_window_mins: "Așteaptă (n) minute până să trimiți un email de notificare, pentru a da utilizatorilor șansa să-și editeze și să-și finalizeze postările." - private_email_time_window_seconds: "Așteaptă (n) secunde înainte de a trimite emailuri de notificare privată, pentru a-i da utilizatorului șansa să își editeze sau finalizeze mesajele." email_posts_context: "Câte răspunsuri precedente să fie incluse drept context în email-urile de notificare." flush_timings_secs: "Cât de frecvent se resetează datele legate de timp de pe server, în secunde." title_max_word_length: "Numărul maxim de caractere din titlul unui subiect." @@ -1178,7 +1171,6 @@ ro: code_formatting_style: "Butonul Cod din cadrul editorului va fi setat cu opțiunea implicită pe acest stil de formatare cod " default_email_digest_frequency: "Frecvența implicită cu care utilizatorii primesc emailuri-rezumat." default_include_tl0_in_digests: "Implicit, include postări de la utilizatori noi în emailurile-rezumat. Utilizatorii pot schimba această opțiune din preferințele lor." - default_email_private_messages: "Implicit, trimite un email atunci când cineva trimite un mesaj unui utilizator." default_email_direct: "Implicit, trimite un mesaj când cineva citează/răspunde/menționează sau invită un utilizator." default_email_mailing_list_mode: "Implicit, trimite un email pentru fiecare nou mesaj." default_email_mailing_list_mode_frequency: "Implicit, utilizatorii care activează modul mailing list vor primi emailuri cu această frecvență." @@ -1433,6 +1425,20 @@ ro: system_messages: post_hidden: subject_template: "Postare ascunsă din cauza marcajelor de avertizare ale comunității" + text_body_template: | + Salut, + + Acesta este un mesaj automat de pe %{site_name} pentru a te informa că mesajul tău a fost ascuns. + + %{base_url}%{url} + + %{flag_reason} + + Mai mulți membri ai comunității au marcat acestă postare cu marcaje de avertizare înainte ca ea să fie ascunsă, așa că te rugăm să îți revizuiești postarea pentru a ține cont de feedback-ul primit. **Îți poți edita postarea după %{edit_delay} minute, și ea va fi automat re-afișată** + + Totuși, dacă postarea este ascunsă de comunitate pentru a doua oară, va rămâne așa până ce va fi gestionată de un membru al echipei -- și asta ar putea atrage și alte consecințe, inclusiv posibilitatea suspendării contului tău. + + Pentru informații suplimentare, te rugăm să citești [ghidul comunității](%{base_url}/guidelines). welcome_user: subject_template: "Bine ai venit pe %{site_name}!" welcome_invite: @@ -1533,8 +1539,41 @@ ro: Dacă există interfață utilizator web pentru contul tău POP, ar trebui să te autentifici pe ea și să îți verifici acolo setările. too_many_spam_flags: subject_template: "Cont nou suspendat" + text_body_template: | + Salut, + + Acesta este un mesaj automat de pe %{site_name} pentru a te informa că postările tale au fost temporar ascunse pentru că au fost marcate cu marcaje de avertizare de către comunitate.. + + Ca măsura de precauție, noului tău cont i s-a blocat posibilitatea de a crea noi răspunsuri sau subiecte până ce un un membru al echipei nu îl va verifica. Ne cerem scuze pentru inconveniență. + + Pentru informații suplimentare, te rugăm să citești [ghidul comunității](%{base_url}/guidelines). too_many_tl3_flags: subject_template: "Cont nou suspendat" + text_body_template: | + Salut, + + Acesta este un mesaj automat de pe %{site_name} pentru a te informa că ți-a fost suspendat contul din cauza unui număr mare de marcaje de avertizare primite de la comunitate. + + Ca măsura de precauție, noului tău cont i s-a blocat posibilitatea de a crea noi răspunsuri sau subiecte până ce un un membru al echipei nu îl va verifica. Ne cerem scuze pentru inconveniență. + + Pentru informații suplimentare, te rugăm să citești [ghidul comunității](%{base_url}/guidelines). + silenced_by_staff: + text_body_template: |+ + Salut, + + Acesta este un mesaj automat de pe %{site_name} pentru a te informa că ți-a fost suspendat temporar contul, ca măsură de precauție. + + Poți să continui să răsfoiești, dar nu vei mai putea să răspunzi sau să creezi subiecte până când un [membru al echipei](%{base_url}/about) nu îți verifică postările recente. Ne cerem scuze pentru inconveniență. + + Pentru informații suplimentare, te rugăm să citești [ghidul comunității](%{base_url}/guidelines). + + unsilenced: + text_body_template: | + Salut, + + Acesta este un mesaj automat din partea %{site_name} pentru a vă informa cu privire la deblocarea contului dumneavoastră după o revizie a personalului. + + Acum puteți crea discuții și răspunsuri noi. pending_users_reminder: subject_template: one: "un utilizator în așteptarea aprobării" diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml index ffa033217b..bf41ab5ad0 100644 --- a/config/locales/server.ru.yml +++ b/config/locales/server.ru.yml @@ -184,11 +184,13 @@ ru: many: "К сожалению, новые пользователи могут добавить только %{count} вложений в сообщение." other: "К сожалению, новые пользователи могут добавить только %{count} вложений в сообщение." no_links_allowed: "Извините, новые пользователи не могут размещать ссылки." + links_require_trust: "Извините, Вы не можете включать ссылки в Ваши сообщения." too_many_links: one: "Извините, новые пользователи могут размещать только одну ссылку в сообщении." few: "Извините, новые пользователи могут размещать только %{count} ссылок в сообщении." many: "Извините, новые пользователи могут размещать только %{count} ссылок в сообщении." other: "Извините, новые пользователи могут размещать только %{count} ссылок в сообщении." + contains_blocked_words: "Ваш пост содержит слова, которые не разрешены." spamming_host: "Извините, вы не можете разместить ссылку в этом сообщении." user_is_suspended: "Заблокированным пользователям запрещено писать." topic_not_found: "Что-то пошло не так. Возможно, эта тема была закрыта или заархивирована, пока вы ее читали?" @@ -220,6 +222,7 @@ ru: top_weekly: "Лучшие темы за неделю" top_daily: "Лучшие темы за день" posts: "Последние сообщения" + private_posts: "Последние личные сообщения" group_posts: "Последние сообщения от %{group_name}" group_mentions: "Последние упоминания от %{group_name}" user_posts: "Последние сообщения, отправленные @%{username}" @@ -230,6 +233,7 @@ ru: excerpt_image: "изображение" queue: delete_reason: "Удалено по запросу модератора." + not_found: "Сообщение не найдено или уже обновлено." groups: success: bulk_add: "%{users_added}пользователей было добавлено в группу." @@ -337,6 +341,10 @@ ru: attributes: payload_url: invalid: "URL недействителен. URL должен включает в себя HTTP: // или https: //. И ни один пустой не допускается." + watched_word: + attributes: + word: + too_many: "Слишком много слов для этого действия" <<: *errors user_profile: no_info_other: "

            %{name} еще не заполнил поле «Обо мне» в своём профайле.
            " @@ -382,6 +390,7 @@ ru: invalid_email_in: "'%{email}' не является корректным адресом электронной почты" email_already_used_in_group: "'%{email}' уже используется группой '%{group_name}'." email_already_used_in_category: "'%{email}' уже используется категорией '%{category_name}'." + description_incomplete: "Описание категории должна иметь по крайней мере один абзац." cannot_delete: uncategorized: "Нельзя удалить раздел, предназначенный для тем вне разделов" has_subcategories: "Невозможно удалить этот раздел, т.к. в нем есть подразделы." @@ -406,7 +415,6 @@ ru: change_failed_explanation: "Вы пытаетесь понизить пользователя %{user_name} до уровня доверия '%{new_trust_level}'. Однако, его уровень доверия уже '%{current_trust_level}'. %{user_name} останется с уровнем доверия '%{current_trust_level}'. Если вы все же хотите понизить пользователя, заблокируйте вначале уровень доверия." rate_limiter: slow_down: "Вы выполняли это действие слишком часто, попробуйте еще раз позже." - too_many_requests: "Вы повторяете действие слишком часто. Пожалуйста, подождите %{time_left} до следующей попытки." by_type: first_day_replies_per_day: "Вы достигли лимита на создание ответов для нового пользователя в первый день на сайте. Пожалуйста, попробуйте через %{time_left}." first_day_topics_per_day: "Вы достигли лимита на создание тем для нового пользователя в первый день на сайте. Пожалуйста, попробуйте через %{time_left}." @@ -830,12 +838,10 @@ ru: allow_user_locale: "Позволять пользователям выбирать язык интерфейса" min_post_length: "Минимально допустимое количество символов в одном сообщении." min_first_post_length: "Минимально допустимое количество символов в первом сообщении (или теле темы)" - min_private_message_post_length: "Минимально допустимое количество символов в сообщении в беседе." max_post_length: "Максимально допустимое количество символов в одном сообщении." topic_featured_link_enabled: "Разрешить публиковать ссылку с темами." min_topic_title_length: "Минимально допустимое количество символов в названии темы." max_topic_title_length: "Максимально допустимое количество символов в названии темы." - min_private_message_title_length: "Минимально допустимое количество символов в заголовке сообщения в беседе." min_search_term_length: "Минимальное количество символов в поисковом запросе." allow_duplicate_topic_titles: "Разрешить создание тем с одинаковыми названиями." unique_posts_mins: "Количество минут до того, как пользователь сможет разместить сообщение с тем же содержанием." @@ -952,7 +958,6 @@ ru: max_bookmarks_per_day: "Максимальное количество созданных закладок пользователем в день." max_edits_per_day: "Максимальное количество редактирований пользователем в день." max_topics_per_day: "Максимальное количество тем, которые пользователь может создать за один день." - max_private_messages_per_day: "Максимальное количество тем, которые пользователь может создать за один день." max_invites_per_day: "Максимальное количество приглашений, которое может отправить пользователь за один день." max_topic_invitations_per_day: "Максимальное количество приглашений в тему, которое может отправить пользователь в течении дня." alert_admins_if_errors_per_minute: "Количество ошибок в минуту для предупреждения администратора. Значение 0 отключает эту опцию. ВНИМАНИЕ: требуется перезагрузка." @@ -1105,7 +1110,6 @@ ru: emoji_set: "Какую коллекцию Emoji использовать?" enforce_square_emoji: "Принудительно использовать квадратный формат для всех смайлов." approve_unless_trust_level: "Сообщения для пользователей ниже этого уровня доверия подлежат проверки" - default_email_private_messages: "По умолчанию присылать почтовое уведомление, когда кто-то оставляет пользователю личное сообщение." default_email_mailing_list_mode: "По умолчанию присылать почтовое уведомление, когда появляется новое сообщение." default_other_external_links_in_new_tab: "По умолчанию открывать внешние ссылки в новой вкладке." default_other_dynamic_favicon: "Показывать количество новых/обновлённых тем на иконке веб-браузера по умолчанию." diff --git a/config/locales/server.sk.yml b/config/locales/server.sk.yml index d300784b05..4f555f3d0e 100644 --- a/config/locales/server.sk.yml +++ b/config/locales/server.sk.yml @@ -355,7 +355,6 @@ sk: broken: "Tento obrázok je poškodený" rate_limiter: slow_down: "Vykonali ste túto akciu príliš veľa krát, skúste neskôr" - too_many_requests: "Na vykonávanie tejto akcie máme nastavený denný limit. Prosím počkajte %{time_left} než skúsite znovu." by_type: first_day_replies_per_day: "Dosiahli ste maximálny počet odpovedí stanovený pre nového používateľa v jeho prvý deň. Prosím čakajte %{time_left} , než skúsite znova" first_day_topics_per_day: "Dosiahli ste maximálny počet tém stanovený pre nového používateľa v jeho prvý deň. Prosím čakajte %{time_left} , než skúsite znova" @@ -731,11 +730,9 @@ sk: allow_user_locale: "Povoliť používateľom zvoliť si vlastný jazyk" min_post_length: "Minimálny povolený počet znakov v príspevkoch" min_first_post_length: "Minimálny povolený počet znakov pre prvý príspevok (obsah témy)" - min_private_message_post_length: "Minimálny povolený počet znakov v správe" max_post_length: "Maximálny povolený počet znakov v príspevkoch" min_topic_title_length: "Minimálny povolený počet znakov v názve témy" max_topic_title_length: "Maximálny povolený počet znakov v názve témy" - min_private_message_title_length: "Minimálny povolený počet znakov v správe" min_search_term_length: "Minimálny povolený počet znakov vo vyhľadávaní" search_tokenize_chinese_japanese_korean: "Prinúť vyhľádávanie rozložiť Čínštinu/Japončinu/Kórejčinu dokonca i pre nie CJK stránky" allow_uncategorized_topics: "Pvoliť vytváranie tém bez kategórií. UPOZORNENIE: Pokiaľ existujú nekategorizované témy, musíte ich zaradiť do kategórii skôr než túto možnosť vypnete." @@ -870,7 +867,6 @@ sk: max_bookmarks_per_day: "Maximálny počet záložiek na jedného používateľa denne." max_edits_per_day: "Maximálny počet úprav používateľa denne." max_topics_per_day: "Maximálny počet tém používateľa denne." - max_private_messages_per_day: "Maximálny počet správ ktoré môže používateľ denne vytvoriť." max_invites_per_day: "Maximálny počet pozvánok ktoré môže používateľ denne zaslať." max_topic_invitations_per_day: "Maximálny počet pozvánok do tém ktoré môže používateľ denne zaslať." suggested_topics: "Zobrazovaný počet navrhovaných tém na konci témy." @@ -1029,7 +1025,6 @@ sk: approve_unless_trust_level: "Príspevky používateľov pod touto úrovňou důvery musia byť schválené" auto_close_messages_post_count: "Maximálny počet povolených príspevkov v správe kým je automaticky uzavretá (0 znamená vypnuté)" auto_close_topics_post_count: "Maximálny počet povolených príspevkov v téme kým je automaticky uzavretá (0 znamená vypnuté)" - default_email_private_messages: "Štandardne poslať email použivateľovi, ktorému niekto poslal správu." default_email_direct: "Štandardne poslať email ak niekto cituje/odpovedá na/zmieni alebo pozve používateľa." default_email_mailing_list_mode: "Štandardne poslať email pre každý nový príspevok." default_email_always: "Štandardne poslať emailovú správu dokonca i vtedy, ak je používateľ aktívny." diff --git a/config/locales/server.sq.yml b/config/locales/server.sq.yml index 537722da2d..fccd25fc9d 100644 --- a/config/locales/server.sq.yml +++ b/config/locales/server.sq.yml @@ -214,7 +214,6 @@ sq: title: "udhëheqës" rate_limiter: slow_down: "E keni bërë këtë veprim shumë shpesh, prisni pak e provoni më vonë. " - too_many_requests: "Sistemi ka një limit për sa herë ai veprim mund të kryhet në një ditë. Prisni %{time_left} para se të provoni përsëri. " by_type: first_day_replies_per_day: "Keni arritur maksimumin e përgjigjeve që një përdorues i ri mund të postojë në ditën e parë. Prisni %{time_left} dhe provoni përsëri." first_day_topics_per_day: "Keni postuar maksimumin e temave që një anëtar i ri mund të krijojë në ditën e parë. Prisni %{time_left} para se të provoni përsëri. " @@ -546,11 +545,9 @@ sq: allow_user_locale: "Allow users to choose their own language interface preference" min_post_length: "Minimum allowed post length in characters" min_first_post_length: "Minimum allowed first post (topic body) length in characters" - min_private_message_post_length: "Minimum allowed post length in characters for messages" max_post_length: "Maximum allowed post length in characters" min_topic_title_length: "Minimum allowed topic title length in characters" max_topic_title_length: "Maximum allowed topic title length in characters" - min_private_message_title_length: "Minimum allowed title length for a message in characters" min_search_term_length: "Minimum valid search term length in characters" allow_uncategorized_topics: "Allow topics to be created without a category. WARNING: If there are any uncategorized topics, you must recategorize them before turning this off." allow_duplicate_topic_titles: "Allow topics with identical, duplicate titles." @@ -671,7 +668,6 @@ sq: max_bookmarks_per_day: "Maximum number of bookmarks per user per day." max_edits_per_day: "Maximum number of edits per user per day." max_topics_per_day: "Maximum number of topics a user can create per day." - max_private_messages_per_day: "Maximum number of messages users can create per day." max_invites_per_day: "Maximum number of invites a user can send per day." max_topic_invitations_per_day: "Maximum number of topic invitations a user can send per day." suggested_topics: "Number of suggested topics shown at the bottom of a topic." diff --git a/config/locales/server.sr.yml b/config/locales/server.sr.yml index 4d988855b4..f2a4d6fdc8 100644 --- a/config/locales/server.sr.yml +++ b/config/locales/server.sr.yml @@ -118,7 +118,6 @@ sr: site_settings: min_topic_title_length: "Minimalna dozvoljena dužina naslova teme u znakovima" max_topic_title_length: "Maksimalna dozvoljena dužina naslova teme u znakovima" - min_private_message_title_length: "Minimalna dozvoljena dužina naslova poruke u znakovima" min_search_term_length: "Minimalna dozvoljena dužina termina za pretragu u znakovima" apple_touch_icon_url: "Ikonica koja se koristi za Apple touch uređaje. Preporučena veličina 144px sa 144px." max_replies_in_first_day: "Maksimalan broj odgovora koje korisnik može da kreira u prvih 24 sata nakon kreiranja svoje prve poruke." diff --git a/config/locales/server.sv.yml b/config/locales/server.sv.yml index 4074488961..878bb0f76b 100644 --- a/config/locales/server.sv.yml +++ b/config/locales/server.sv.yml @@ -349,7 +349,6 @@ sv: change_failed_explanation: "Du försökte degradera %{user_name} till '%{new_trust_level}'. Användarens förtroendenivå är redan '%{current_trust_level}'. %{user_name} kommer behålla '%{current_trust_level}'. Om du vill degradera användaren, lås förtroendenivån först" rate_limiter: slow_down: "Du har utfört den här handlingen för många gånger, försök igen senare." - too_many_requests: "Du gör det där för ofta. Var god vänta %{time_left} innan du försöker igen." by_type: first_day_replies_per_day: "Du har uppnått maximalt antal inlägg en ny användare kan göra under första dagen. Var god vänta %{time_left} innan du försöker igen." first_day_topics_per_day: "Du har uppnått maximalt antal nya ämnen som nya användare kan skapa under första dagen. Var god vänta %{time_left} innan du försöker igen." @@ -725,7 +724,6 @@ sv: email_polling_errored_recently: one: "E-postpolling har genererat ett fel de senaste 24 timmarna. Se loggarna för mer detaljer." other: "E-postpolling har genererat %{count} fel de senaste 24 timmarna. Se loggarna för mer detaljer." - missing_mailgun_api_key: "Servern är konfigurerad att skicka e-post via mailgun men du har inte tillhandahållit en API-nyckel att använda för verifiera webhook-meddelanden. " bad_favicon_url: "Uppladdningen av favoritikonen misslyckas. Kontrollera inställningen favicon_url i webbplatsinställningarna." poll_pop3_timeout: "Anslutningen till POP3-servern har nått tidsgränsen. Inkommande e-postmeddelanden kunde inte mottas. Var vänlig kontrollera dina POP3-inställningar och tjänsteleverantör." poll_pop3_auth_error: "Anslutning till POP3-servern misslyckas med ett autentiseringsfel. Var vänlig kontrollera dina POP3-inställningar." @@ -736,13 +734,11 @@ sv: set_locale_from_accept_language_header: "sätt gränssnittets språk för anonyma användare från deras webbläsares rubriks språk. (EXPERIMENTELLT, det fungerar inte med anonym cache)" min_post_length: "Minsta tillåtna inläggslängd i antal tecken" min_first_post_length: "Lägsta antal tillåtna tecken i första inlägget (ämnestext)" - min_private_message_post_length: "Lägst antal tillåtna tecken i inlägg för meddelanden" max_post_length: "Högsta tillåtna längd på inlägg i antal tecken" topic_featured_link_enabled: "Möjliggör att lägga upp en länk med ämnen." show_topic_featured_link_in_digest: "Visa ämnet i skiss länken som fanns i det nerkortade emailet." min_topic_title_length: "Lägsta tillåtna längd på ämnesrubrik i antal tecken" max_topic_title_length: "Högsta tillåtna längd på ämnesrubrik i antal tecken" - min_private_message_title_length: "Lägst tillåtna längd på rubrik för ett meddelande i antal tecken" min_search_term_length: "Lägsta giltiga teckenlängd på sökterm" search_tokenize_chinese_japanese_korean: "Framtvinga sökning för att tokenisera kinesiska/japanska/koreanska även på webbplatser som inte är på dessa språk" search_prefer_recent_posts: "Om sökningar på ditt stora forum går långsamt, försök detta alternativ som är ett index av de senaste inläggen först." @@ -787,7 +783,6 @@ sv: summary_likes_required: "Minsta antal gillningar i ett ämne innan 'Sammanfatta det här ämnet' möjliggörs" summary_percent_filter: "Visa högsta % av inläggen när en användare bockar i 'Sammanfatta det här ämnet'" summary_max_results: "Maximalt antal inlägg som returneras av 'Sammanfatta det här ämnet'" - enable_private_messages: "Tillåt användare med förtroendenivå 1 (konfigurera via minsta förtroendenivå för att skicka meddelanden) att skapa meddelanden och svara på meddelanden. Notera att personalen oavsett alltid kan skicka meddelanden. " enable_long_polling: "Meddelande-buss som används för notifiering kan använda long polling" long_polling_base_url: "URL som används för long polling (när en CDN levererar dynamiskt innehåll, kontrollera att det här är inställt till origin pull) se: http://origin.site.com" long_polling_interval: "Tid som servern bör vänta innan den svarar på klienter när det inte finns någon data att skicka (endast loggad på användare)" @@ -901,7 +896,6 @@ sv: max_bookmarks_per_day: "Max antal bokmärken per användare och dag." max_edits_per_day: "Max antal redigeringar per användare och dag." max_topics_per_day: "Max antal ämnen en användare kan skapa per dag." - max_private_messages_per_day: "Max antal meddelanden användare kan skapa per dag." max_invites_per_day: "Max antal inbjudningar en användare kan skicka per dag." max_topic_invitations_per_day: "Max antal ämnesinbjudningar en användare kan skicka per dag." alert_admins_if_errors_per_minute: "Antal felindikeringar per minut för att utlösa ett administratörslarm. Ange 0 för att inaktivera den här funktionen. OBS: kräver omstart." @@ -963,7 +957,6 @@ sv: max_users_notified_per_group_mention: "Maximalt antal användare som kan få ett meddelande om en grupp anges (om tröskeln är uppfyllt kommer inga meddelande att skickas)" create_thumbnails: "Skapa miniatyrer och \"lightboxa\" bilder som är för stora för att passa in i ett inlägg." email_time_window_mins: "Vänta (n) minuter innan något notifikationsmejl skickas ut, för att ge användare chansen att redigera och slutföra deras inlägg." - private_email_time_window_seconds: "Vänta (n) sekunder innan någon privat e-postnotifikation skickas ut för att ge användare chansen att redigera och slutföra deras meddelande." email_posts_context: "Hur många tidigare svar som ska inkluderas som kontext i e-postnotifikationer." flush_timings_secs: "Hur frekvent vi spolar tidsdata till servern, i sekunder." title_max_word_length: "Den maximala tillåtna ordlängden, i tecken, i ett ämnes rubrik." @@ -1119,7 +1112,6 @@ sv: code_formatting_style: "Kodknappen i redigeraren kommer att använda den här kodformatteringsstilen som standard" default_email_digest_frequency: "Standardinställning för hur ofta användare mottar e-postsammanfattningar." default_include_tl0_in_digests: "Standardinställning för hur ofta nya användare får e-postsammanfattningar. Användare kan ändra det i sina inställningar." - default_email_private_messages: "Ange standardinställningen till att skicka ett e-post när någon skickar användaren ett meddelande." default_email_direct: "Ange standardinställningen till att skicka ett e-post när någon citerar/svarar/nämner eller bjuder in användaren." default_email_mailing_list_mode: "Ange standardinställningen till att skicka ett e-post för varje nytt inlägg." default_email_mailing_list_mode_frequency: "Standardinställning för hur ofta användare som aktiverar utskicksläge kommer att motta e-post." diff --git a/config/locales/server.te.yml b/config/locales/server.te.yml index e48114f9a1..5af4961c4e 100644 --- a/config/locales/server.te.yml +++ b/config/locales/server.te.yml @@ -185,7 +185,6 @@ te: basic: title: "ప్రాథమిక సభ్యుడు" rate_limiter: - too_many_requests: "ఈ చర్యకు రోజువారీపరిమితి ఉంది. దయచేసి %{time_left} సమయం తర్వాత ప్రయత్నించండి" hours: one: "ఒక గంట" other: "%{count} గంటలు" diff --git a/config/locales/server.tr_TR.yml b/config/locales/server.tr_TR.yml index 2d49802ea4..40c01b079b 100644 --- a/config/locales/server.tr_TR.yml +++ b/config/locales/server.tr_TR.yml @@ -306,7 +306,6 @@ tr_TR: change_failed_explanation: " %{user_name} adlı kullanıcıyı '%{new_trust_level}' seviyesine düşürmeye çalıştınız. Ancak, halihazırda kullanıcının güven seviyesi zaten '%{current_trust_level}'. %{user_name} '%{current_trust_level}' seviyesinde kalacak - eğer seviyesini düşürmek istiyorsanız öncelikle güven seviyesini kilitlemelisiniz" rate_limiter: slow_down: "Bu eylemi birçok sefer gerçekleştirdiniz, daha sonra tekrar deneyin." - too_many_requests: "Bu eylem için günlük limitinizi aştınız. Lütfen tekrar denemek için %{time_left} bekleyin. " by_type: first_day_replies_per_day: "Yeni bir kullanıcının ilk gününde verebileceği en fazla cevap sayısına ulaştınız. Lütfen tekrar denemek için %{time_left} bekleyin." first_day_topics_per_day: "Yeni bir kullanıcının ilk gününde oluşturabileceği en fazla konu sayısına ulaştınız. Lütfen tekrar denemek için %{time_left} bekleyin." @@ -641,11 +640,9 @@ tr_TR: set_locale_from_accept_language_header: "anonim kullanıcıların arayüz dilini tarayıcılarının dil başlığından al. (DENEYSELDİR, anonim önbellek ile çalışmaz)" min_post_length: "Gönderide olması gereken en az karakter sayısı" min_first_post_length: "İlk gönderi için (konu içi) izin verilen en az karakter sayısı" - min_private_message_post_length: "İleti gönderileri için izin verilen en az karakter sayısı" max_post_length: "Gönderide izin verilen en fazla karakter sayısı" min_topic_title_length: "Konuda olması gereken en az karakter sayısı" max_topic_title_length: "Konu başlığında izin verilen en fazla karakter sayısı" - min_private_message_title_length: "İleti başlıkları için izin verilen en az karakter sayısı" min_search_term_length: "Arama için girilecek kelimede olması gereken en az karakter sayısı" search_tokenize_chinese_japanese_korean: " CJK olmayan siteler dahil, -Çince/Japonca/Korece için aramayı bilgileri sıfırlamaya zorla" search_prefer_recent_posts: "Eğer büyük forumunuzda arama yavaş ise, bu seçenek daha yeni gönderilerin dizine eklenmesini deneyecek" @@ -689,7 +686,6 @@ tr_TR: summary_likes_required: "'Bu Konuyu Özetle'nin etkinleştirilmesi için konuda olması gereken en az beğeni sayısı" summary_percent_filter: "Kullanıcı 'Bu Konuyu Özetle'ye tıkladığında, gönderinin ilk % kısmını göster" summary_max_results: "'Bu Konuyu Özetle'den dönen en fazla gönderi sayısı" - enable_private_messages: "Güven seviyesi 1'e(özel ileti göndermek için en az seviye ayarıyla belirlenebilir) sahip kullanıcıların ileti oluşturup cevaplamasına izin ver. Görevliler her durumda ileti gönderebilir." enable_long_polling: "Bildiri için kullanılan ileti yolu uzun sorgular yapabilir" long_polling_base_url: "Uzun sorgular için kullanılan baz URL (CDN dinamik içerik sunuyorsa, bunu origin olarak ayarladığına emin ol) ör: http://origin.site.com" long_polling_interval: "Gönderilecek bilgi olmadığı zaman sunucunun kullanıcılara geri dönmeden önce beklemesi gereken zaman (sadece giriş yapmış kullanıcın için)" @@ -794,7 +790,6 @@ tr_TR: max_bookmarks_per_day: "Kullanıcı başına düşen günlük en fazla imleme sayısı." max_edits_per_day: "Bir günde, bir kullanıcının yapabileceği en fazla düzenleme sayısı." max_topics_per_day: "Bir günde, bir kullanıcının oluşturabileceği en fazla konu sayısı." - max_private_messages_per_day: "Bir günde kullanıcıların oluşturabileceği en fazla ileti sayısı" max_invites_per_day: "Bir günde, bir kullanıcının yollayabileceği en fazla davet sayısı." max_topic_invitations_per_day: "Bir günde, bir kullanıcının yollayabileceği en fazla başlık daveti sayısı." categories_topics: "/categories sayfasında gösterilecek olan konu sayısı." @@ -970,7 +965,6 @@ tr_TR: enforce_square_emoji: "Tüm emojileri kare en-boy oranına zorla" approve_unless_trust_level: "Bu güven seviyesi altındaki kullanıcılardan gelen gönderilerin onaylanması gerekir" default_email_digest_frequency: "Öntanımlı olarak kullanıcılar ne sıklıkla e-posta özeti alacak." - default_email_private_messages: "Öntanımlı olarak birisi bir kullanıcıya ileti attığında e-posta gönder." default_email_direct: "Öntanımlı olarak birisi bir kullanıcı hakkında alıntı yapma, cevaplama, bahsetme ya da davet etme eylemlerini gerçekleştirdiğinde e-posta gönder." default_email_mailing_list_mode: "Öntanımlı olarak her yeni gönderi için bir e-posta gönder." disable_mailing_list_mode: "Kullanıcıların duyuru listesi modunu etkinleştirmesini engelle." diff --git a/config/locales/server.uk.yml b/config/locales/server.uk.yml index 7dbc3e88a0..a702350c09 100644 --- a/config/locales/server.uk.yml +++ b/config/locales/server.uk.yml @@ -143,7 +143,6 @@ uk: member: title: "учасник" rate_limiter: - too_many_requests: "У нас є обмеження щодо того, скільки разів за добу можна виконати цю дію. Будь ласка, зачекайте %{time_left} перед тим, як спробувати ще раз." hours: one: "1 година" few: "%{count} години" diff --git a/config/locales/server.vi.yml b/config/locales/server.vi.yml index a55aafc269..c9bd715532 100644 --- a/config/locales/server.vi.yml +++ b/config/locales/server.vi.yml @@ -297,7 +297,6 @@ vi: change_failed_explanation: "Bạn đã cố gắng để giảm hạng %{user_name} xuống '%{new_trust_level}'. Tuy nhiên cấp độ tin cậy hiện tại của họ đã là '%{current_trust_level}'. %{user_name} sẽ được giữ lại ở cấp độ '%{current_trust_level}' - nếu bạn muốn giảm hạng thành viên, trước tiên hãy khóa cấp độ tin cậy" rate_limiter: slow_down: "Hành động này đã được thực hiện quá nhiều lần. Bạn vui lòng thử lại sau." - too_many_requests: "Hành động bạn vừa thực hiện bị giới hạn theo ngày. Hãy chờ %{time_left} và thử lại." by_type: first_day_replies_per_day: "Bạn vừa vượt quá số lần trả lời tối đa trong ngày đầu của thành viên mới. Xin hãy chờ %{time_left} và thử lại sau." first_day_topics_per_day: "Bạn vừa đạt tới số lần mở topic tối đa cho thành viên mới. Xin vui lòng chờ %{time_left} trước khi thử lại." @@ -616,11 +615,9 @@ vi: set_locale_from_accept_language_header: "đặt ngôn ngữ giao diện cho người dùng ẩn danh từ tiêu đề ngôn ngữ trình duyệt web của họ. (KINH NGHIỆM, không hoạt động với bộ nhớ cache ẩn danh)" min_post_length: "Số kí tự tối thiểu trong bài đăng." min_first_post_length: "Chiều dài tối thiểu cho bài viết đầu tiên (nội dung chủ đề) tính theo ký tự." - min_private_message_post_length: "Số kí tự tối thiểu trong tin nhắn." max_post_length: "Số kí tự tối đa trong bài đăng." min_topic_title_length: "Số kí tự tối thiểu trong tiêu đề chủ đề." max_topic_title_length: "Số kí tự tối đa trong tiêu đề chủ đề." - min_private_message_title_length: "Chiều dài tối thiểu cho phép theo số kí tự của một thông điệp" min_search_term_length: "Số kí tự tối thiểu trong từ khóa tìm kiếm." search_tokenize_chinese_japanese_korean: "Bắt buộc tìm kiếm tách từ Chinese/Japanese/Korean ngay cả trên các site không phải là CJK" allow_uncategorized_topics: "Cho phép các chủ đề được tạo ra mà không gán chuyên mục. LƯU Ý: Nếu có bất kỳ chủ đề nào chưa gán chuyên mục, bạn phải phân loại chúng trước khi thay đổi." @@ -761,7 +758,6 @@ vi: max_bookmarks_per_day: "Số tối đa người dùng có thể đánh dấu mỗi ngày." max_edits_per_day: "Số tối đa người dùng có thể chỉnh sửa mỗi ngày." max_topics_per_day: "Số chủ đề tối đa người dùng có thể tạo mỗi ngày." - max_private_messages_per_day: "Số tin nhắn tối đa người dùng có thể tạo mỗi ngày." max_invites_per_day: "Số tối đa người dùng có thể gửi lời mời mỗi ngày." max_topic_invitations_per_day: "Số tối đa lời mời chủ đề thành viên có thể gửi mỗi ngày." alert_admins_if_errors_per_minute: "Số lỗi trong một phút để kích hoạt cảnh báo admin, điền 0 để tắt tính năng này. LƯU Ý: yêu cầu khởi động lại." @@ -817,7 +813,6 @@ vi: max_mentions_per_post: "Số tối đa thông báo @name mà tất cả mọi người có thể sử dụng trong bài viết." create_thumbnails: "Tạo ảnh nhỏ và ảnh lightbox nếu quá lớn để vừa trong một bài đăng." email_time_window_mins: "Chờ (n) phút trước khi gửi bất kỳ một email thông báo nào, để cung cấp cho người dùng cơ hội để chỉnh sửa và hoàn tất bài viết của họ." - private_email_time_window_seconds: "Đợi (n) giây trước khi gửi bất kỳ thông báo email nào, để cho thành viên cơ hội để chỉnh sửa và hoàn thiện các thông điệp của họ." email_posts_context: "Có bao nhiêu trả lời trước được kèm theo như là bối cảnh trong email thông báo." flush_timings_secs: "Tần suất tuôn dữ liệu thời gian tới server, theo giây." title_max_word_length: "Chiều dài tối đa chữ cho phép, tính theo ký tự, trong một tiêu đề chủ đề." @@ -934,7 +929,6 @@ vi: enforce_square_emoji: "Sử dụng kích thước vuông cho tất cả các emoji." approve_post_count: "Số lượng bài viết từ một thành viên mới hoặc thành viên cũ phải được duyệt" approve_unless_trust_level: "Bài viết của thành viên dưới cấp độ tin cậy này phải được duyệt" - default_email_private_messages: "Gửi email khi ai đó nhắn tin cho thành viên theo mặc định." default_email_direct: "Gửi email khi ai đó trích dẫn/trả lời/đề cập hoặc mời thành viên theo mặc định." default_email_mailing_list_mode: "Mặc định gửi email cho mỗi bài viết mới." disable_mailing_list_mode: "Không cho phép thành viên bật chế độ danh sách thư." diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml index 114480b9ec..f52dc202c4 100644 --- a/config/locales/server.zh_CN.yml +++ b/config/locales/server.zh_CN.yml @@ -437,7 +437,6 @@ zh_CN: broken: "此图片已损坏" rate_limiter: slow_down: "你执行这个操作太多次了,请稍后再试。" - too_many_requests: "你的请求过于频繁,请等待%{time_left}之后再试。" by_type: first_day_replies_per_day: "你回帖的数量已经超出身为新用户当天允许的上限,请等待%{time_left}之后再试。" first_day_topics_per_day: "你创建新主题的数量已经超出身为新用户当天允许的上限,请等待%{time_left}之后再试。" @@ -815,7 +814,6 @@ zh_CN: subfolder_ends_in_slash: "你的子目录设置不正确;DISCOURSE_RELATIVE_URL_ROOT以斜杠结尾。" email_polling_errored_recently: other: "邮件轮询在过去的 24 小时内出现了 %{count} 个错误。看一看日志寻找详情。" - missing_mailgun_api_key: "服务器设置使用 Mailgun 发送邮件,但并未设置验证 webhook 私信的 API 密钥。" bad_favicon_url: "网站图标无法载入。检查站点设置中的 favicon_url。" poll_pop3_timeout: "至 POP3 服务器的连接超时。无法获取进站邮件。请检查POP3 设置和服务商。" poll_pop3_auth_error: "至 POP3 服务器的连接验证失败。请检查POP3 设置。" @@ -827,13 +825,11 @@ zh_CN: set_locale_from_accept_language_header: "为未登录用户按照他们的浏览器发送的请求头部设置界面语言。(实验性,无法和匿名缓存共同使用)" min_post_length: "帖子允许的最少字符数" min_first_post_length: "第一帖(主题内容)允许的最少字符数" - min_private_message_post_length: "私信允许的最小字符数" max_post_length: "帖子允许的最大字符数" topic_featured_link_enabled: "允许发链接帖。" show_topic_featured_link_in_digest: "在摘要邮件中显示主题特色链接。" min_topic_title_length: "标题允许的最少字符数" max_topic_title_length: "标题允许的最大字符数" - min_private_message_title_length: "私信标题允许的最小字符数" min_search_term_length: "搜索条件允许的最少字符数" search_tokenize_chinese_japanese_korean: "在非中/日/韩语站点强制切割中/日/韩语搜索分词" search_prefer_recent_posts: "如果搜索大型论坛较慢,这个选项将优先尝试最新的帖子" @@ -888,8 +884,6 @@ zh_CN: summary_likes_required: "在一个主题启用'摘要模式'的最小赞的数量" summary_percent_filter: "当用户点击摘要,显示前 % 几的帖子" summary_max_results: "“概括主题”返回的最大帖子数量" - enable_private_messages: "允许信任等级1(可以另外选择发送私信的信任等级)的用户创建私信和回复私信。注意:管理人员不受限制。" - enable_private_email_messages: "允许信任等级 4 (可通过发送私信的最低信任等级进行设置) 的用户创建私信和回复私信。注意:管理人员不受限制。" enable_long_polling: "启用 Message bus 使通知功能可以使用长轮询(long polling)" long_polling_base_url: "长轮询的基本 URL(当用 CDN 分发动态能让时,请设置此至原始拉取地址)例如:http://origin.site.com" long_polling_interval: "当没有数据向客户端发送时服务器端应等待的时间(仅对已登录用户有效)" @@ -1022,7 +1016,6 @@ zh_CN: max_bookmarks_per_day: "每个用户每天能做书签数量的最大值。" max_edits_per_day: "每个用户每天能编辑的次数的最大值。" max_topics_per_day: "每个用户每天能创建的主题数量的最大值。" - max_private_messages_per_day: "每个用户每天能发私信数量的最大值。" max_invites_per_day: "每个用户每天能创建的邀请数量的最大值。" max_topic_invitations_per_day: "每个用户每天能创建的邀请至主题数量的最大值。" max_logins_per_ip_per_hour: "一小时内同一个IP(网络)地址能允许最大的登陆次数。" @@ -1093,7 +1086,6 @@ zh_CN: enable_mentions: "允许用户提及其他用户。" create_thumbnails: "为太大而无法恰当地显示在帖子里的图片创建 lightbox 缩略图。" email_time_window_mins: "等待多少(n)分钟才给用户发送通知电子邮件,好让他们有机会自己来编辑和完善他们的帖子。" - private_email_time_window_seconds: "等待多少(n)秒再给用户发送通知电子邮件,这可以让用户有时间来编辑和完善他们的私信。" email_posts_context: "在通知邮件中包含的作为上下文的回复数量。" flush_timings_secs: "向服务器刷新时间数据的频率,以秒为单位。" title_max_word_length: "在主题的标题中,允许的词语长度的最大字符数。" @@ -1271,7 +1263,6 @@ zh_CN: watched_words_regular_expressions: "监视词是正则表达式。" default_email_digest_frequency: "用户收到摘要邮件的默认频率。" default_include_tl0_in_digests: "在摘要邮件中默认包含新用户帖子。用户可以自行在参数设置中更改这个设置。" - default_email_private_messages: "默认在有人发私信给用户时发送一封邮件通知。" default_email_direct: "默认在有人引用、回复、提及或者邀请用户时发送一封邮件通知。" default_email_mailing_list_mode: "默认为每一个新帖子发送一封邮件通知。" default_email_mailing_list_mode_frequency: "邮件列表模式下,用户收到邮件的默认频率。" @@ -2368,21 +2359,6 @@ zh_CN: signup_after_approval: title: "在审批之后注册" subject_template: "你已经被 %{site_name} 论坛批准加入了!" - text_body_template: | - 欢迎来到%{site_name}! - - 管理人员批准了你在%{site_name}的帐号。 - - 点击下面的链接确认并激活你的新帐号: - %{base_url}/u/activate-account/%{email_token} - - 如果以上链接无法点击,请将它复制并粘贴到浏览器的地址栏。 - - %{new_user_tips} - - 我们相信任何时候[讨论应该文明](%{base_url}/guidelines)。 - - 享受你的时光! signup: title: "注册" subject_template: "[%{email_prefix}] 确认你的新账户" diff --git a/config/locales/server.zh_TW.yml b/config/locales/server.zh_TW.yml index 94d81c4a70..cec5b5db8f 100644 --- a/config/locales/server.zh_TW.yml +++ b/config/locales/server.zh_TW.yml @@ -395,7 +395,6 @@ zh_TW: change_failed_explanation: "你嘗試將%{user_name}降至%{new_trust_level}。然而他們的信任等級已經是%{current_trust_level}。%{user_name}將仍處于%{current_trust_level}——如果你想要降級用戶,先鎖定信任等級。" rate_limiter: slow_down: "您這個動作重複太多次,請稍後再試。" - too_many_requests: "你的瀏覽速度過於頻繁,請等待 %{time_left} 後再試。" by_type: first_day_replies_per_day: "你回帖的數量已經超出身為新用戶當天允許的上限,請等待%{time_left}之後再試。" first_day_topics_per_day: "你創建新主題的數量已經超出身為新用戶當天允許的上限,請等待%{time_left}之後再試。" @@ -754,7 +753,6 @@ zh_TW: subfolder_ends_in_slash: "你的子目錄設置不正確;DISCOURSE_RELATIVE_URL_ROOT以斜杠結尾。" email_polling_errored_recently: other: "郵件輪詢在過去的 24 小時內出現了 %{count} 個錯誤。看一看日誌尋找詳情。" - missing_mailgun_api_key: "伺服器設置使用 Mailgun 寄送電子郵件,但你尚未設置用來驗證 webhook 訊息的 API 密鑰。" bad_favicon_url: "網站表徵圖無法載入。檢查站點設置中的 favicon_url。" poll_pop3_timeout: "至 POP3 伺服器的連接超時。無法獲取進站郵件。請檢查POP3 設置和服務商。" poll_pop3_auth_error: "至 POP3 伺服器的連接驗證失敗。請檢查POP3 設置。" @@ -766,13 +764,11 @@ zh_TW: set_locale_from_accept_language_header: "為未登錄用戶按照他們的瀏覽器發送的請求頭部設置界面語言。(實驗性,無法和匿名緩存共同使用)" min_post_length: "文章允許的最小文字數" min_first_post_length: "第一篇文章允許的最少文字數" - min_private_message_post_length: "私訊允許的最小文字數" max_post_length: "文章允許的最大文字數" topic_featured_link_enabled: "允許發連結帖。" show_topic_featured_link_in_digest: "在摘要郵件中顯示主題特色連結。" min_topic_title_length: "標題允許的最小文字數" max_topic_title_length: "標題允許的最大文字數" - min_private_message_title_length: "標題允許的最小文字數" min_search_term_length: "搜尋條件允許的最小文字數" search_tokenize_chinese_japanese_korean: "在非中/日/韓語站點強制切割中/日/韓語搜索分詞" search_prefer_recent_posts: "如果搜索大型論壇較慢,這個選項將優先嘗試最新的帖子" @@ -820,7 +816,6 @@ zh_TW: summary_likes_required: "如果使用了\"此話題的摘用\",話題顯示時需滿足最小得到\"讚\"的數量" summary_percent_filter: "當用戶點擊 \"此話題的摘要\",顯示前面多少 % 的文章" summary_max_results: "“概括主題”返回的最大帖子數量" - enable_private_messages: "允許信任等級1(可以另外選擇發送消息的信任等級)的用戶創建消息和回覆消息。注意:管理人員不受限制。" enable_long_polling: "啟用消息匯流排使通知功能可以使用長輪詢(long polling)" long_polling_base_url: "長輪詢的基本 URL(當用 CDN 分發動態內容,請設置此至原始拉取地址)例如:http://origin.site.com" long_polling_interval: "當沒有數據向客戶端發送時伺服器端應等待的時間(僅對已登錄用戶有效)" @@ -939,7 +934,6 @@ zh_TW: max_bookmarks_per_day: "每個用戶每天最多能建立\"書籤\"的數量" max_edits_per_day: "每個用戶每天最大的\"編輯次數\"的數量" max_topics_per_day: "每個用戶每天最多建立\"討論話題\"的數量" - max_private_messages_per_day: "用戶每天能建立訊息的數量上限" max_invites_per_day: "每個用戶每天最多能邀請用戶的數量。" max_topic_invitations_per_day: "每個用戶每天能創建的邀請至主題數量的最大值。" alert_admins_if_errors_per_minute: "觸發管理員警示的每分鐘錯誤數量。設為 0 會停用這個功能。注意:需要重啟。" @@ -1003,7 +997,6 @@ zh_TW: max_users_notified_per_group_mention: "當群組被提及時,接受提醒的最大用戶數 ( 超過閾值後將不發送提醒 )" create_thumbnails: "為太大而無法在帖子裡正常顯示的圖片創造縮略圖及 lightbox 圖片。" email_time_window_mins: "等待多少 (n) 分鐘才給用戶發送通知電子郵件,好讓他們有機會自己來編輯和完善他們的帖子。" - private_email_time_window_seconds: "等待多少(n)秒再給用戶發送通知電子郵件,這可以讓用戶有時間來編輯和完善他們的消息。" email_posts_context: "在通知電郵中包含的作為上下文的回覆數量。" flush_timings_secs: "刷新時間資料的頻率,以秒為單位" title_max_word_length: "在主題的標題中,允許的詞語長度的最大字元數。" @@ -1164,7 +1157,6 @@ zh_TW: code_formatting_style: "編輯器中的代碼格式化按鈕設置的預設格式" default_email_digest_frequency: "用戶收到摘要郵件的預設頻率。" default_include_tl0_in_digests: "在摘要郵件中預設包含新用戶的貼文。用戶可以自行在參數設置中更改這個設定。" - default_email_private_messages: "預設在有人發消息給用戶時發送一封郵件通知。" default_email_direct: "預設在有人引用、回覆、提及或者邀請用戶時發送一封郵件通知。" default_email_mailing_list_mode: "預設為每一個新帖子發送一封郵件通知。" default_email_mailing_list_mode_frequency: "郵件列表模式下,用戶收到郵件的預設頻率。" @@ -1899,21 +1891,6 @@ zh_TW: signup_after_approval: title: "在同意之後註冊" subject_template: "你已受到 %{site_name} 的認可!" - text_body_template: | - 歡迎加入 %{site_name}! - - 管理員已通過你的帳號申請。 - - 請點擊以下連結,以確認啟用你的新帳號: - %{base_url}/u/activate-account/%{email_token} - - 如果上面的連結無法點擊,請複製該連結,並貼到瀏覽器中開啟。 - - %{new_user_tips} - - 我們永遠相信[文明的社群行為](%{base_url}/guidelines)。 - - 好好享受您在此的時光吧! signup: title: "註冊" subject_template: "[%{email_prefix}] 確認你的新帳號" diff --git a/config/nginx.sample.conf b/config/nginx.sample.conf index a1f0551589..6253e04146 100644 --- a/config/nginx.sample.conf +++ b/config/nginx.sample.conf @@ -188,7 +188,7 @@ server { # This big block is needed so we can selectively enable # acceleration for backups and avatars # see note about repetition above - location ~ ^/(letter_avatar/|user_avatar|highlight-js|stylesheets|favicon/proxied) { + location ~ ^/(letter_avatar/|user_avatar|highlight-js|stylesheets|favicon/proxied|service-worker-.*.js) { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/config/routes.rb b/config/routes.rb index 23d6447d86..a7409d3afd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -301,6 +301,7 @@ Discourse::Application.routes.draw do get "session/sso_provider" => "session#sso_provider" get "session/current" => "session#current" get "session/csrf" => "session#csrf" + get "session/email-login/:token" => "session#email_login" get "composer_messages" => "composer_messages#index" post "composer/parse_html" => "composer#parse_html" @@ -330,6 +331,7 @@ Discourse::Application.routes.draw do put "#{root_path}/update-activation-email" => "users#update_activation_email" get "#{root_path}/hp" => "users#get_honeypot_value" + post "#{root_path}/email-login" => "users#email_login" get "#{root_path}/admin-login" => "users#admin_login" put "#{root_path}/admin-login" => "users#admin_login" get "#{root_path}/admin-login/:token" => "users#admin_login" @@ -489,11 +491,14 @@ Discourse::Application.routes.draw do end end - get 'notifications' => 'notifications#index' - put 'notifications/mark-read' => 'notifications#mark_read' - # creating an alias cause the api was extended to mark a single notification - # this allows us to cleanly target it - put 'notifications/read' => 'notifications#mark_read' + resources :notifications, except: :show do + collection do + put 'mark-read' => 'notifications#mark_read' + # creating an alias cause the api was extended to mark a single notification + # this allows us to cleanly target it + put 'read' => 'notifications#mark_read' + end + end match "/auth/:provider/callback", to: "users/omniauth_callbacks#complete", via: [:get, :post] match "/auth/failure", to: "users/omniauth_callbacks#failure", via: [:get, :post] @@ -691,7 +696,9 @@ Discourse::Application.routes.draw do post "draft" => "draft#update" delete "draft" => "draft#destroy" - get "service-worker" => "static#service_worker_asset", format: :js + if service_worker_asset = Rails.application.assets_manifest.assets['service-worker.js'] + get service_worker_asset => "static#service_worker_asset", format: :js + end get "cdn_asset/:site/*path" => "static#cdn_asset", format: false get "brotli_asset/*path" => "static#brotli_asset", format: false diff --git a/config/site_settings.yml b/config/site_settings.yml index 1b19897afc..ea7d2140aa 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -79,6 +79,9 @@ basic: set_locale_from_accept_language_header: default: false validator: "AllowUserLocaleEnabledValidator" + support_mixed_text_direction: + client: true + default: false categories_topics: default: 20 min: 5 @@ -237,6 +240,10 @@ login: enable_local_logins: client: true default: true + enable_local_logins_via_email: + client: true + default: false + validator: "EnableLocalLoginsViaEmailValidator" allow_new_registrations: client: true default: true @@ -328,7 +335,6 @@ login: default: 1440 min: 1 max: 175200 - users: min_username_length: client: true @@ -447,7 +453,7 @@ posting: locale_default: zh_CN: 8 zh_TW: 8 - min_private_message_post_length: + min_personal_message_post_length: client: true min: 1 default: 10 @@ -490,7 +496,7 @@ posting: zh_CN: false zh_TW: false title_fancy_entities: true - min_private_message_title_length: + min_personal_message_title_length: client: true default: 2 min: 1 @@ -505,12 +511,12 @@ posting: locale_default: zh_CN: 4 zh_TW: 4 - enable_private_messages: + enable_personal_messages: default: true client: true enable_system_message_replies: default: true - enable_private_email_messages: + enable_personal_email_messages: default: false client: true validator: "EnablePrivateEmailMessagesValidator" @@ -531,6 +537,13 @@ posting: enable_markdown_typographer: client: true default: true + enable_markdown_linkify: + client: true + default: true + markdown_linkify_tlds: + client: true + type: list + default: 'com|net|org|io|co|tv|ru|cn|us|uk|me|de|fr|fi|gov' enable_rich_text_paste: client: true default: false @@ -653,7 +666,7 @@ email: email_time_window_mins: default: 10 client: true - private_email_time_window_seconds: 20 + personal_email_time_window_seconds: 20 email_posts_context: 5 digest_min_excerpt_length: default: 100 @@ -884,6 +897,13 @@ trust: min_trust_to_send_email_messages: default: 4 enum: 'TrustLevelSetting' + min_trust_to_flag_posts: + default: 1 + enum: 'TrustLevelSetting' + min_trust_to_post_links: + default: 0 + enum: 'TrustLevelSetting' + allow_flagging_staff: true tl1_requires_topics_entered: 5 tl1_requires_read_posts: default: 30 @@ -949,7 +969,7 @@ security: allow_moderators_to_create_categories: false non_crawler_user_agents: hidden: true - default: 'trident|webkit|gecko|chrome|safari|msie|opera' + default: 'trident|webkit|gecko|chrome|safari|msie|opera|goanna' type: list crawler_user_agents: hidden: true @@ -1034,7 +1054,7 @@ rate_limits: rate_limit_new_user_create_topic: 120 rate_limit_new_user_create_post: 30 max_topics_per_day: 20 - max_private_messages_per_day: 20 + max_personal_messages_per_day: 20 max_likes_per_day: 50 max_bookmarks_per_day: 20 max_flags_per_day: 20 @@ -1414,7 +1434,7 @@ user_preferences: enum: 'DigestEmailSiteSetting' default: 10080 default_include_tl0_in_digests: false - default_email_private_messages: true + default_email_personal_messages: true default_email_direct: true default_email_mailing_list_mode: false default_email_mailing_list_mode_frequency: diff --git a/db/migrate/20180131052859_rename_private_personal.rb b/db/migrate/20180131052859_rename_private_personal.rb new file mode 100644 index 0000000000..8a4edead62 --- /dev/null +++ b/db/migrate/20180131052859_rename_private_personal.rb @@ -0,0 +1,26 @@ +class RenamePrivatePersonal < ActiveRecord::Migration[5.1] + + def setting(old, new) + execute "UPDATE site_settings SET name='#{new}' where name='#{old}'" + end + + def up + setting :min_private_message_post_length, :min_personal_message_post_length + setting :min_private_message_title_length, :min_personal_message_title_length + setting :enable_private_messages, :enable_personal_messages + setting :enable_private_email_messages, :enable_personal_email_messages + setting :private_email_time_window_seconds, :personal_email_time_window_seconds + setting :max_private_messages_per_day, :max_personal_messages_per_day + setting :default_email_private_messages, :default_email_personal_messages + end + + def down + setting :min_personal_message_post_length, :min_private_message_post_length + setting :min_personal_message_title_length, :min_private_message_title_length + setting :enable_personal_messages, :enable_private_messages + setting :enable_personal_email_messages, :enable_private_email_messages + setting :personal_email_time_window_seconds, :private_email_time_window_seconds + setting :max_personal_messages_per_day, :max_private_messages_per_day + setting :default_email_personal_messages, :default_email_private_messages + end +end diff --git a/db/migrate/20180207161422_add_skipped_created_at_user_id_index_on_email_logs.rb b/db/migrate/20180207161422_add_skipped_created_at_user_id_index_on_email_logs.rb new file mode 100644 index 0000000000..d8d13ff0ed --- /dev/null +++ b/db/migrate/20180207161422_add_skipped_created_at_user_id_index_on_email_logs.rb @@ -0,0 +1,8 @@ +class AddSkippedCreatedAtUserIdIndexOnEmailLogs < ActiveRecord::Migration[5.1] + def up + execute "CREATE INDEX idx_email_logs_user_created_filtered ON email_logs(user_id, created_at) WHERE skipped = 'f'" + end + def down + execute "DROP INDEX idx_email_logs_user_created_filtered" + end +end diff --git a/db/migrate/20180207163946_create_category_tag_stats.rb b/db/migrate/20180207163946_create_category_tag_stats.rb new file mode 100644 index 0000000000..6ee70da8e7 --- /dev/null +++ b/db/migrate/20180207163946_create_category_tag_stats.rb @@ -0,0 +1,12 @@ +class CreateCategoryTagStats < ActiveRecord::Migration[5.1] + def change + create_table :category_tag_stats do |t| + t.references :category, null: false + t.references :tag, null: false + t.integer :topic_count, default: 0, null: false + end + + add_index :category_tag_stats, [:category_id, :topic_count] + add_index :category_tag_stats, [:category_id, :tag_id], unique: true + end +end diff --git a/lib/auth/default_current_user_provider.rb b/lib/auth/default_current_user_provider.rb index a23f028152..c46ee178b3 100644 --- a/lib/auth/default_current_user_provider.rb +++ b/lib/auth/default_current_user_provider.rb @@ -62,7 +62,11 @@ class Auth::DefaultCurrentUserProvider begin limiter.performed! rescue RateLimiter::LimitExceeded - raise Discourse::InvalidAccess + raise Discourse::InvalidAccess.new( + 'Invalid Access', + nil, + delete_cookie: TOKEN_COOKIE + ) end end end diff --git a/lib/cooked_post_processor.rb b/lib/cooked_post_processor.rb index c3de112b40..263d139e72 100644 --- a/lib/cooked_post_processor.rb +++ b/lib/cooked_post_processor.rb @@ -247,9 +247,9 @@ class CookedPostProcessor rescue URI::InvalidURIError end - # only crop when the image is taller than 16:9 - # we only use 95% of that to allow for a small margin - MIN_RATIO_TO_CROP ||= (9.0 / 16.0) * 0.95 + # only crop when the image is taller than 18:9 + # we only use 90% of that to allow for a small margin + MIN_RATIO_TO_CROP ||= (9.0 / 18.0) * 0.9 def convert_to_link!(img) src = img["src"] @@ -268,11 +268,7 @@ class CookedPostProcessor return if original_width <= width && original_height <= height return if original_width <= SiteSetting.max_image_width && original_height <= SiteSetting.max_image_height - crop = original_width.to_f / original_height.to_f < MIN_RATIO_TO_CROP - # prevent iPhone X screenshots from being cropped - crop &= original_width != 1125 && original_height != 2436 - - if crop + if crop = (original_width.to_f / original_height.to_f < MIN_RATIO_TO_CROP) width, height = ImageSizer.crop(original_width, original_height) img["width"] = width img["height"] = height diff --git a/lib/discourse.rb b/lib/discourse.rb index b83dec54be..45b6416c6b 100644 --- a/lib/discourse.rb +++ b/lib/discourse.rb @@ -64,12 +64,12 @@ module Discourse # When they don't have permission to do something class InvalidAccess < StandardError - attr_reader :obj, :custom_message + attr_reader :obj, :custom_message, :opts def initialize(msg = nil, obj = nil, opts = nil) super(msg) - opts ||= {} - @custom_message = opts[:custom_message] if opts[:custom_message] + @opts = opts || {} + @custom_message = opts[:custom_message] if @opts[:custom_message] @obj = obj end end diff --git a/lib/flag_query.rb b/lib/flag_query.rb index 15b1591d3f..cda69b8f91 100644 --- a/lib/flag_query.rb +++ b/lib/flag_query.rb @@ -31,6 +31,7 @@ module FlagQuery posts = SqlBuilder.new(" SELECT p.id, p.cooked, + p.raw, p.user_id, p.topic_id, p.post_number, diff --git a/lib/guardian.rb b/lib/guardian.rb index 82f66a7016..19d9fe7823 100644 --- a/lib/guardian.rb +++ b/lib/guardian.rb @@ -247,7 +247,7 @@ class Guardian def can_invite_to?(object, groups = nil) return false unless authenticated? return true if is_admin? - return false unless SiteSetting.enable_private_messages? + return false unless SiteSetting.enable_personal_messages? return false if (SiteSetting.max_invites_per_day.to_i == 0 && !is_staff?) return false unless can_see?(object) return false if groups.present? @@ -303,7 +303,7 @@ class Guardian # User disabled private message (is_staff? || is_group || target.user_option.allow_private_messages) && # PMs are enabled - (is_staff? || SiteSetting.enable_private_messages) && + (is_staff? || SiteSetting.enable_personal_messages) && # Can't send PMs to suspended users (is_staff? || is_group || !target.suspended?) && # Check group messageable level @@ -320,7 +320,7 @@ class Guardian # User is trusted enough @user.has_trust_level?(SiteSetting.min_trust_to_send_email_messages) && # PMs to email addresses are enabled - (is_staff? || SiteSetting.enable_private_email_messages) + (is_staff? || SiteSetting.enable_personal_email_messages) end def can_see_emails? diff --git a/lib/guardian/group_guardian.rb b/lib/guardian/group_guardian.rb index 95ec4f2fde..492924c92e 100644 --- a/lib/guardian/group_guardian.rb +++ b/lib/guardian/group_guardian.rb @@ -13,6 +13,8 @@ module GroupGuardian end def can_see_group_messages?(group) - is_admin? || group.users.include?(user) + SiteSetting.enable_personal_messages? && ( + is_admin? || group.users.include?(user) + ) end end diff --git a/lib/guardian/post_guardian.rb b/lib/guardian/post_guardian.rb index 5996f84812..4344e5fd34 100644 --- a/lib/guardian/post_guardian.rb +++ b/lib/guardian/post_guardian.rb @@ -1,6 +1,11 @@ #mixin for all guardian methods dealing with post permissions module PostGuardian + def can_post_link? + authenticated? && + @user.has_trust_level?(TrustLevel[SiteSetting.min_trust_to_post_links]) + end + # Can the user act on the post in a particular way. # taken_actions = the list of actions the user has already taken def post_can_act?(post, action_key, opts: {}, can_see_post: nil) @@ -16,12 +21,17 @@ module PostGuardian result = if authenticated? && post && !@user.anonymous? + # post made by staff, but we don't allow staff flags + return false if is_flag && + (!SiteSetting.allow_flagging_staff?) && + post.user.staff? + return false if [:notify_user, :notify_moderators].include?(action_key) && - !SiteSetting.enable_private_messages? + !SiteSetting.enable_personal_messages? # we allow flagging for trust level 1 and higher # always allowed for private messages - (is_flag && not(already_did_flagging) && (@user.has_trust_level?(TrustLevel[1]) || post.topic.private_message?)) || + (is_flag && not(already_did_flagging) && (@user.has_trust_level?(TrustLevel[SiteSetting.min_trust_to_flag_posts]) || post.topic.private_message?)) || # not a flagging action, and haven't done it already not(is_flag || already_taken_this_action) && diff --git a/lib/html_normalize.rb b/lib/html_normalize.rb deleted file mode 100644 index 1243a4426a..0000000000 --- a/lib/html_normalize.rb +++ /dev/null @@ -1,151 +0,0 @@ -# frozen_string_literal: true -# -# this class is used to normalize html output for internal comparisons in specs -# -require 'oga' - -class HtmlNormalize - - def self.normalize(html) - parsed = Oga.parse_html(html.strip, strict: true) - if parsed.children.length != 1 - puts parsed.children.count - raise "expecting a single child" - end - new(parsed.children.first).format - end - - SELF_CLOSE = Set.new(%w{area base br col command embed hr img input keygen line meta param source track wbr}) - - BLOCK = Set.new(%w{ - html - body - aside - p - h1 h2 h3 h4 h5 h6 - ol ul - address - blockquote - dl - div - fieldset - form - hr - noscript - table - pre - }) - - def initialize(doc) - @doc = doc - end - - def format - buffer = String.new - dump_node(@doc, 0, buffer) - buffer.strip! - buffer - end - - def inline?(node) - Oga::XML::Text === node || !BLOCK.include?(node.name.downcase) - end - - def dump_node(node, indent = 0, buffer) - - if Oga::XML::Text === node - if node.parent&.name - buffer << node.text - end - return - end - - name = node.name.downcase - - block = BLOCK.include?(name) - - buffer << " " * indent * 2 if block - - buffer << "<" << name - - attrs = node&.attributes - if (attrs && attrs.length > 0) - attrs.sort! { |x, y| x.name <=> y.name } - attrs.each do |a| - buffer << " " - buffer << a.name - if a.value - buffer << "='" - buffer << a.value - buffer << "'" - end - end - end - - buffer << ">" - - if block - buffer << "\n" - end - - children = node.children - children = trim(children) if block - - inline_buffer = nil - - children&.each do |child| - if block && inline?(child) - inline_buffer ||= String.new - dump_node(child, indent + 1, inline_buffer) - else - if inline_buffer - buffer << " " * (indent + 1) * 2 - buffer << inline_buffer.strip - inline_buffer = nil - else - dump_node(child, indent + 1, buffer) - end - end - end - - if inline_buffer - buffer << " " * (indent + 1) * 2 - buffer << inline_buffer.strip - inline_buffer = nil - end - - if block - buffer << "\n" unless buffer[-1] == "\n" - buffer << " " * indent * 2 - end - - unless SELF_CLOSE.include?(name) - buffer << "\n" - end - end - - def trim(nodes) - start = 0 - finish = nodes.length - - nodes.each do |n| - if Oga::XML::Text === n && n.text.blank? - start += 1 - else - break - end - end - - nodes.reverse_each do |n| - if Oga::XML::Text === n && n.text.blank? - finish -= 1 - else - break - end - end - - nodes[start...finish] - end - -end diff --git a/lib/middleware/request_tracker.rb b/lib/middleware/request_tracker.rb index 6f802ec16f..f0ce009a6c 100644 --- a/lib/middleware/request_tracker.rb +++ b/lib/middleware/request_tracker.rb @@ -6,6 +6,7 @@ require_dependency 'method_profiler' class Middleware::RequestTracker @@detailed_request_loggers = nil + @@ip_skipper = nil # register callbacks for detailed request loggers called on every request # example: @@ -35,7 +36,26 @@ class Middleware::RequestTracker if @@detailed_request_loggers.length == 0 @detailed_request_loggers = nil end + end + # used for testing + def self.unregister_ip_skipper + @@ip_skipper = nil + end + + # Register a custom `ip_skipper`, a function that will skip rate limiting + # for any IP that returns true. + # + # For example, if you never wanted to rate limit 1.2.3.4 + # + # ``` + # Middleware::RequestTracker.register_ip_skipper do |ip| + # ip == "1.2.3.4" + # end + # ``` + def self.register_ip_skipper(&blk) + raise "IP skipper is already registered!" if @@ip_skipper + @@ip_skipper = blk end def initialize(app, settings = {}) @@ -146,7 +166,7 @@ class Middleware::RequestTracker log_request_info(env, result, info) unless env["discourse.request_tracker.skip"] end - PRIVATE_IP = /^(127\.)|(192\.168\.)|(10\.)|(172\.1[6-9]\.)|(172\.2[0-9]\.)|(172\.3[0-1]\.)|(::1$)|([fF][cCdD])/ + PRIVATE_IP ||= /^(127\.)|(192\.168\.)|(10\.)|(172\.1[6-9]\.)|(172\.2[0-9]\.)|(172\.3[0-1]\.)|(::1$)|([fF][cCdD])/ def is_private_ip?(ip) ip = IPAddr.new(ip) rescue nil @@ -167,6 +187,8 @@ class Middleware::RequestTracker return false if is_private_ip?(ip) end + return false if @@ip_skipper&.call(ip) + limiter10 = RateLimiter.new( nil, "global_ip_limit_10_#{ip}", diff --git a/lib/oneboxer.rb b/lib/oneboxer.rb index 64e1ee8b3c..88fd33245a 100644 --- a/lib/oneboxer.rb +++ b/lib/oneboxer.rb @@ -165,16 +165,31 @@ module Oneboxer def self.local_topic_html(url, route, opts) return unless current_user = User.find_by(id: opts[:user_id]) - return unless current_category = Category.find_by(id: opts[:category_id]) - return unless Guardian.new(current_user).can_see_category?(current_category) + + if current_category = Category.find_by(id: opts[:category_id]) + return unless Guardian.new(current_user).can_see_category?(current_category) + end + + topic = Topic.find_by(id: route[:topic_id]) + + return unless topic + return if topic.private_message? + + if current_category&.id != topic.category_id + return unless Guardian.new.can_see_topic?(topic) + end + + post = nil + post_number = route[:post_number].to_i + if post_number > 1 + post = topic.posts.where(post_number: route[:post_number].to_i).first + else + post = topic.ordered_posts.first + end + + return if !post || post.hidden || post.post_type != Post.types[:regular] if route[:post_number].to_i > 1 - post = Post.find_by(topic_id: route[:topic_id], post_number: route[:post_number]) - - return unless post.present? && !post.hidden - return unless current_category.id == post.topic.category_id || Guardian.new.can_see_post?(post) - - topic = post.topic excerpt = post.excerpt(SiteSetting.post_onebox_maxlength) excerpt.gsub!(/[\r\n]+/, " ") excerpt.gsub!("[/quote]", "[quote]") # don't break my quote @@ -183,18 +198,13 @@ module Oneboxer PrettyText.cook(quote) else - return unless topic = Topic.find_by(id: route[:topic_id]) - return unless current_category.id == topic.category_id || Guardian.new.can_see_topic?(topic) - - first_post = topic.ordered_posts.first - args = { topic_id: topic.id, avatar: PrettyText.avatar_img(topic.user.avatar_template, "tiny"), original_url: url, title: PrettyText.unescape_emoji(CGI::escapeHTML(topic.title)), category_html: CategoryBadge.html_for(topic.category), - quote: first_post.excerpt(SiteSetting.post_onebox_maxlength), + quote: post.excerpt(SiteSetting.post_onebox_maxlength), } template = File.read("#{Rails.root}/lib/onebox/templates/discourse_topic_onebox.hbs") diff --git a/lib/pretty_text.rb b/lib/pretty_text.rb index 7655c76c8d..fafc6c7eb4 100644 --- a/lib/pretty_text.rb +++ b/lib/pretty_text.rb @@ -167,7 +167,6 @@ module PrettyText __optInput.emojiUnicodeReplacer = __emojiUnicodeReplacer; __optInput.lookupInlineOnebox = __lookupInlineOnebox; __optInput.lookupImageUrls = __lookupImageUrls; - #{opts[:linkify] == false ? "__optInput.linkify = false;" : ""} __optInput.censoredWords = #{WordWatcher.words_for_action(:censor).join('|').to_json}; JS diff --git a/lib/site_settings/deprecated_settings.rb b/lib/site_settings/deprecated_settings.rb index ca63777b4c..f4bc23820a 100644 --- a/lib/site_settings/deprecated_settings.rb +++ b/lib/site_settings/deprecated_settings.rb @@ -2,7 +2,14 @@ module SiteSettings; end module SiteSettings::DeprecatedSettings DEPRECATED_SETTINGS = [ - %w[use_https force_https 1.7] + %w[use_https force_https 1.7], + %w[min_private_message_post_length min_personal_message_post_length 2.0], + %w[min_private_message_title_length min_personal_message_title_length 2.0], + %w[enable_private_messages enable_personal_messages 2.0], + %w[enable_private_email_messages enable_personal_email_messages 2.0], + %w[private_email_time_window_seconds personal_email_time_window_seconds 2.0], + %w[max_private_messages_per_day max_personal_messages_per_day 2.0], + %w[default_email_private_messages default_email_personal_messages 2.0] ] def setup_deprecated_methods diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake index f26e7b3025..15458e27cc 100644 --- a/lib/tasks/import.rake +++ b/lib/tasks/import.rake @@ -19,6 +19,7 @@ task "import:ensure_consistency" => :environment do update_categories update_users update_groups + update_tag_stats log "Done!" end @@ -164,7 +165,7 @@ def insert_user_options , #{SiteSetting.default_email_mailing_list_mode} , #{SiteSetting.default_email_mailing_list_mode_frequency} , #{SiteSetting.default_email_direct} - , #{SiteSetting.default_email_private_messages} + , #{SiteSetting.default_email_personal_messages} , #{SiteSetting.default_email_previous_replies} , #{SiteSetting.default_email_in_reply_to} , #{SiteSetting.default_email_digest_frequency.to_i > 0} @@ -419,6 +420,10 @@ def update_groups SQL end +def update_tag_stats + Tag.ensure_consistency! +end + def log(message) puts "[#{DateTime.now.strftime("%Y-%m-%d %H:%M:%S")}] #{message}" end diff --git a/lib/text_sentinel.rb b/lib/text_sentinel.rb index fa88e647f9..e9b5232004 100644 --- a/lib/text_sentinel.rb +++ b/lib/text_sentinel.rb @@ -19,9 +19,9 @@ class TextSentinel def self.body_sentinel(text, opts = {}) entropy = SiteSetting.body_min_entropy if opts[:private_message] - scale_entropy = SiteSetting.min_private_message_post_length.to_f / SiteSetting.min_post_length.to_f + scale_entropy = SiteSetting.min_personal_message_post_length.to_f / SiteSetting.min_post_length.to_f entropy = (entropy * scale_entropy).to_i - entropy = (SiteSetting.min_private_message_post_length.to_f * ENTROPY_SCALE).to_i if entropy > SiteSetting.min_private_message_post_length + entropy = (SiteSetting.min_personal_message_post_length.to_f * ENTROPY_SCALE).to_i if entropy > SiteSetting.min_personal_message_post_length else entropy = (SiteSetting.min_post_length.to_f * ENTROPY_SCALE).to_i if entropy > SiteSetting.min_post_length end diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 1556e085fc..34ca3f32dd 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -104,7 +104,7 @@ class TopicQuery # Don't suggest messages unless we have a user, and private messages are # enabled. return if topic.private_message? && - (@user.blank? || !SiteSetting.enable_private_messages?) + (@user.blank? || !SiteSetting.enable_personal_messages?) builder = SuggestedTopicsBuilder.new(topic) diff --git a/lib/validators/enable_local_logins_via_email_validator.rb b/lib/validators/enable_local_logins_via_email_validator.rb new file mode 100644 index 0000000000..0537f3697e --- /dev/null +++ b/lib/validators/enable_local_logins_via_email_validator.rb @@ -0,0 +1,14 @@ +class EnableLocalLoginsViaEmailValidator + def initialize(opts = {}) + @opts = opts + end + + def valid_value?(val) + return true if val == 'f' + SiteSetting.enable_local_logins + end + + def error_message + I18n.t('site_settings.errors.enable_local_logins_disabled') + end +end diff --git a/lib/validators/post_validator.rb b/lib/validators/post_validator.rb index a9b1f468c4..18e8d4330d 100644 --- a/lib/validators/post_validator.rb +++ b/lib/validators/post_validator.rb @@ -15,7 +15,8 @@ class Validators::PostValidator < ActiveModel::Validator max_mention_validator(record) max_images_validator(record) max_attachments_validator(record) - max_links_validator(record) + can_post_links_validator(record) + newuser_links_validator(record) unique_post_validator(record) end @@ -91,8 +92,16 @@ class Validators::PostValidator < ActiveModel::Validator add_error_if_count_exceeded(post, :no_attachments_allowed, :too_many_attachments, post.attachment_count, SiteSetting.newuser_max_attachments) end + def can_post_links_validator(post) + return if (post.link_count == 0 && !post.has_oneboxes?) || + Guardian.new(post.acting_user).can_post_link? || + private_message?(post) + + post.errors.add(:base, I18n.t(:links_require_trust)) + end + # Ensure new users can not put too many links in a post - def max_links_validator(post) + def newuser_links_validator(post) return if acting_user_is_trusted?(post) || private_message?(post) add_error_if_count_exceeded(post, :no_links_allowed, :too_many_links, post.link_count, SiteSetting.newuser_max_links) end @@ -113,8 +122,8 @@ class Validators::PostValidator < ActiveModel::Validator private - def acting_user_is_trusted?(post) - post.acting_user.present? && post.acting_user.has_trust_level?(TrustLevel[1]) + def acting_user_is_trusted?(post, level = 1) + post.acting_user.present? && post.acting_user.has_trust_level?(TrustLevel[level]) end def private_message?(post) diff --git a/lib/version.rb b/lib/version.rb index 3089a596ac..415e9a5706 100644 --- a/lib/version.rb +++ b/lib/version.rb @@ -5,7 +5,7 @@ module Discourse MAJOR = 2 MINOR = 0 TINY = 0 - PRE = 'beta2' + PRE = 'beta3' STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/plugins/discourse-details/assets/stylesheets/details.scss b/plugins/discourse-details/assets/stylesheets/details.scss index 2ab27809d2..a0265d894e 100644 --- a/plugins/discourse-details/assets/stylesheets/details.scss +++ b/plugins/discourse-details/assets/stylesheets/details.scss @@ -51,21 +51,24 @@ details .lazyYT-container { summary:before { content: '' !important; + display: none; } summary { @include unselectable; + display: flex; + align-items: center; + justify-content: center; + text-align: center; box-sizing: border-box; margin: 0; padding: 0; color: #aaa; background: #f1f1f1; border: 1px solid #ddd; - width: 20px; - display: flex; - text-align: center; - vertical-align: middle; + width: 21px; line-height: 12px; + } summary:hover { diff --git a/plugins/discourse-details/plugin.rb b/plugins/discourse-details/plugin.rb index 116836d74c..8b537509b9 100644 --- a/plugins/discourse-details/plugin.rb +++ b/plugins/discourse-details/plugin.rb @@ -13,7 +13,7 @@ after_initialize do Email::Styles.register_plugin_style do |fragment| # remove all elided content - fragment.css("details.elided").each { |d| d.remove } + fragment.css("details.elided").each(&:remove) # replace all details with their summary in emails fragment.css("details").each do |details| @@ -28,4 +28,8 @@ after_initialize do end end + on(:reduce_cooked) do |fragment| + fragment.css("details.elided").each(&:remove) + end + end diff --git a/plugins/discourse-details/spec/components/pretty_text_spec.rb b/plugins/discourse-details/spec/components/pretty_text_spec.rb index cd03a2fb6b..338e10f4c3 100644 --- a/plugins/discourse-details/spec/components/pretty_text_spec.rb +++ b/plugins/discourse-details/spec/components/pretty_text_spec.rb @@ -4,9 +4,6 @@ require 'pretty_text' describe PrettyText do it "supports details tag" do - cooked_html = "
            foobar
            " - expect(PrettyText.cook("
            foobar
            ")).to match_html(cooked_html) - cooked_html = <<~HTML
            @@ -14,7 +11,16 @@ describe PrettyText do

            bar

            HTML - expect(PrettyText.cook("[details=foo]\nbar\n[/details]")).to eq(cooked_html.strip) + + expect(cooked_html).to match_html(cooked_html) + expect(PrettyText.cook("[details=foo]\nbar\n[/details]")).to match_html(cooked_html) + end + + it "deletes elided content" do + cooked_html = PrettyText.cook("Hello World\n\n
            42
            ") + mail_html = PrettyText.cook("Hello World") + + expect(PrettyText.format_for_email(cooked_html)).to match_html(mail_html) end end diff --git a/plugins/discourse-narrative-bot/config/locales/client.ro.yml b/plugins/discourse-narrative-bot/config/locales/client.ro.yml index 31d1c44013..43a7a03b6c 100644 --- a/plugins/discourse-narrative-bot/config/locales/client.ro.yml +++ b/plugins/discourse-narrative-bot/config/locales/client.ro.yml @@ -5,4 +5,9 @@ # To work with us on translations, join this project: # https://www.transifex.com/projects/p/discourse-org/ -ro: {} +ro: + js: + discourse_narrative_bot: + welcome_post_type: + new_user_track: "Pornește tutorialul pentru toți utilizatorii noi" + welcome_message: "Trimite tuturor utilizatorilor noi un mesaj de întâmpinare cu un ghid de pornire rapidă" diff --git a/plugins/discourse-narrative-bot/config/locales/server.cs.yml b/plugins/discourse-narrative-bot/config/locales/server.cs.yml index a7647ca261..c07c4e7576 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.cs.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.cs.yml @@ -87,7 +87,7 @@ cs: '9': "Ano" '10': "Dle znamení ano" '11': "Odpověď nejistá, zeptej se znovu" - '12': "Zeptej se jindy" + '12': "Zeptej se později" '13': "To ti teď raději neřeknu" '14': "To nyní nedokážu předpovědět" '15': "Soustřeď se a zeptej se znovu" diff --git a/plugins/discourse-narrative-bot/config/locales/server.fa_IR.yml b/plugins/discourse-narrative-bot/config/locales/server.fa_IR.yml index 3fad299411..cb766a13ec 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.fa_IR.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.fa_IR.yml @@ -160,6 +160,8 @@ fa_IR: - https://en.wikipedia.org/wiki/Inherently_funny_word - https://en.wikipedia.org/wiki/Death_by_coconut - https://en.wikipedia.org/wiki/Calculator_spelling + reply: |- + عالی! این کار برای اکثر پیوندها صدق میکنه. به یاد داشته باش که لینک بایستی در یک خط به تنهایی و _بدون پس و پیش_ قرار بگیرد. not_found: |- متاسفم، نمیتوانم پیوند را در پاسخ شما پیدا کنم! :cry: @@ -167,6 +169,14 @@ fa_IR: images: + instructions: |- + این عکسی از اسب تک شاخ است: + + + + اگر پسندیدی (کیه که نپسنده!) برای اینکه من بدونم لطفا دکمه‌ی پسند :heart: زیر این نوشته را فشار بده. + + میتونی با **یک عکس پاسخ بدهی؟** هر عکسی بذاری اشکالی نداره. میتونی عکس رو بکشی و رها کنی، یا از گزینه ی آپلود استفاده کنی، یا حتی کپی پیست کنی. reply: |- عجب عکس شگفت‌انگیزی! من دکمه‌ی پسند :heart: رو برای اینکه بفهمی چقدر قدردان آن هستم، فشار دادم. like_not_found: |- @@ -211,11 +221,31 @@ fa_IR: هر متنی را که در نوشته‌ی من انتخاب کنی، کلید **نقل‌قول**را برایت نشان می‌دهد. همچنین انتخاب کلید **پاسخ** بعد از انتخاب متن هم همان کار را می‌کند. می‌توانی یکبار دیگر امتحان کنی؟ bookmark: + instructions: |- + اگر می‌خواهی بیشتر یاد بگیری، در زیر و **نشانه‌گذاری این پیام خصوصی** را انتخاب کن. اگر این کار را بکنی، ممکن است هدیه‌ای :gift: در آینده‌ برای شما باشد. reply: |- عالی! الان تو میتوانی راه برگشت به این پیغام خصوصی را هر زمان که دوست داشتی از [برگه‌ی نشانک‌ها در پروفایل کاربری](%{profile_page_url}/activity/bookmarks) پیدا کنی. فقط کافی است که روی عکس پروفایل خود در بالا سمت چپ انتخاب کنی ↖ + not_found: |- + اوه، هیچ نشانکی در این موضوع نمیبینم. آیا آیکون نشانک را زیر هر نوشته پیدا کردی؟ در صورت لزوم از آیکون "بیشتر نشان بده" برای آشکار کردن دیگر قابلیت ها استفاده کن. emoji: + instructions: |- + شاید دیده باشی که من از عکس‌های کوچکی مثل :blue_car::dash: در پاسخ‌هایم استفاده می‌کنم. اینها [شکلک](https://en.wikipedia.org/wiki/Emoji) نام دارند. آیا می‌توانی که یک شکلک را به پاسخت اضافه کنی؟ هر یک از موارد زیر این کار را میتواند انجام بدهد: + + - بنویس `:) ;) :O` + + - علامت دونقطه : را بنویس و سپس اسم شکلک را کامل کن `:tada:` + + - کلیک شکلک را در ویرایشگر، یا در صفحه‌کلید موبایلت فشار بده reply: |- :sparkles: خیلی شکلک شگفت انگیزی بود :sparkles: + not_found: |- + اوپس، هیچ شکلکی در پاسخت نمیبینم؟ ای وای! :sob: + + سعی کن علامت دونقطه : رو تایپ کنی تا صفحه‌ی انتخاب شکلک رو باز کنی، سپس اولین حروف چیزی که میخوای را تایپ کن. مانند `:bird:` + + یا کلید شکلک را در ویرایشگر انتخاب کن. + + (اگر با موبایلت می‌نویسی، میتوانی شکلک را مستقیما از طریق صفحه کلید خود موبایل انتخاب بکنی.) mention: instructions: |- شاید گاهی اوقات بخواهی که توجه شخصی را، اگرچه در حال پاسخ دادن به ایشان نیستی، جلب کنی. بنویس `@` و سپس نام کاربری ایشان را کامل کن که به ایشان اشاره کنی. @@ -226,9 +256,27 @@ fa_IR: not_found: |- من اسم خودم را هیچ جا پیدا نکردم. :frowning: آیا میتوانی دوباره تلاش کنی که به اسم من `@%{discobot_username}` اشاره کنی؟ flag: + instructions: |- + ما دوست داریم که گفتگوها دوستانه باشد و به کمک شما برای [متمدنانه نگه داشتن تالار](%{guidelines_url}) احتیاج داریم. اگر مشکلی مشاهده کردی، آن را پرچم گذاری کن که به صورت نامحسوس نویسنده یا [کارکنان ما](%{about_url}) را در جریان موضوع قرار بدهی. + + > :imp: من چیز زشتی اینجا نوشتم + + فکر کنم بدانی که چه کار بایستی بکنید. بفرمایید این نوشته را به عنوان نامناسب **پرچم گذاری** کنید! reply: |- [کارکنان ما](/groups/staff) به طور خصوصی از پرچم گذاری شما آگاه می‌شوند. اگر اعضای تالار به تعداد کافی یک نوشته را پرچم گذاری کنند، آن نوشته به عنوان پیشگیری خود به خود پنهان می‌شود. (به خاطر اینکه من در حقیقت چیز زشتی ننوشتم :angel: خودم پرچم گذاری شما را فعلا پاک کردم.) + not_found: |- + اوه نه، نوشته‌ی زشت من هنوز پرچم گذاری نشده. :worried: آیا میتوانی با استفاده از آیکون **پرچم** این نوشته را به عنوان نامناسب گزارش کنی؟ فراموش نکن که برای آشکارسازی قابلیت‌های بیشتر می‌توانی کلید "بیشتر نشان بده" را برای هر نوشته انتخاب کنی. search: + instructions: |- + من در این مبحث یک سوپرایز برای شما دارم. اگر برای یک چالش آماده ای، **آیکون جستجو** را درگوشه ی بالا سمت چپ ↖ انتخاب کن که جستجو اش بکنی. + + سعی کن که عبارت "سنجاب" را در این مبحث جستجو کنی + hidden_message: |- + چطور تونستی این سنجاب رو نادیده بگیری؟ :wink: + + + + آیا متوجه شدی که الان تو در ابتدای مبحث هستی؟ به این سنجاب گرسنه‌ی بیچاره، با **پاسخ دادن توسط شکلک `:herb:`** (در پاسخ بنویس `:herb:`) غذا بده تا علاوه بر آن به صورت اتوماتیک به آخر مبحث برگردی. reply: |- بله پیداش کردی :tada: @@ -237,6 +285,8 @@ fa_IR: - برای پرش به هر قسمتی از یک مبحث دراز، از کنترل‌کننده های زمانی سمت چپ (یا پایین در موبایل) استفاده بکن. - اگر یه صفحه کلید فیزیکی داری، ؟ رو فشار بده تا میانبرهای بدردبخوری که داریم را مشاهده کنی. + not_found: |- + :thinking: ظاهرا مشکلی برای شما بوجود آمده. متاسفیم. آیا دقیقا عبارت **سنجاب** را جستجو کردی؟ certificate: alt: 'گواهی موفقیت' advanced_user_narrative: diff --git a/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js.es6 b/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js.es6 index a16c2d2293..14654a0fce 100644 --- a/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js.es6 +++ b/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js.es6 @@ -68,6 +68,10 @@ export default Ember.Component.extend({ this.clear(); + if (this.get('action') !== 'reply' || this.get('action') !== 'edit') { + return; + } + this.publish({ response_needed: true, previous: this.get('previousState'), diff --git a/plugins/discourse-presence/config/locales/client.ro.yml b/plugins/discourse-presence/config/locales/client.ro.yml index 31d1c44013..2a98a275bf 100644 --- a/plugins/discourse-presence/config/locales/client.ro.yml +++ b/plugins/discourse-presence/config/locales/client.ro.yml @@ -5,4 +5,8 @@ # To work with us on translations, join this project: # https://www.transifex.com/projects/p/discourse-org/ -ro: {} +ro: + js: + presence: + replying: "răspunde" + editing: "editează" diff --git a/plugins/discourse-presence/config/locales/client.sk.yml b/plugins/discourse-presence/config/locales/client.sk.yml index bcc3a93dc5..4f72b9d611 100644 --- a/plugins/discourse-presence/config/locales/client.sk.yml +++ b/plugins/discourse-presence/config/locales/client.sk.yml @@ -5,4 +5,12 @@ # To work with us on translations, join this project: # https://www.transifex.com/projects/p/discourse-org/ -sk: {} +sk: + js: + presence: + replying: "odpovedanie" + editing: "upravovanie" + replying_to_topic: + one: "odpovedá" + few: "odpovedajú" + other: "odpovedajú" diff --git a/plugins/discourse-presence/config/locales/server.ro.yml b/plugins/discourse-presence/config/locales/server.ro.yml index 31d1c44013..bba2871a96 100644 --- a/plugins/discourse-presence/config/locales/server.ro.yml +++ b/plugins/discourse-presence/config/locales/server.ro.yml @@ -5,4 +5,7 @@ # To work with us on translations, join this project: # https://www.transifex.com/projects/p/discourse-org/ -ro: {} +ro: + site_settings: + presence_enabled: 'Afișează utilizatorii care răspund acum la subiectul curent sau editează subiectul?' + presence_max_users_shown: 'Numărul maxim de utilizatori afișați.' diff --git a/plugins/poll/config/locales/client.ro.yml b/plugins/poll/config/locales/client.ro.yml index df87f6641d..1b9159d8bd 100644 --- a/plugins/poll/config/locales/client.ro.yml +++ b/plugins/poll/config/locales/client.ro.yml @@ -59,6 +59,8 @@ ro: insert: Introdu sondaj help: options_count: Trebuie să introduci cel puțin 2 opțiuni + invalid_values: Valoarea minimă trebuie să fie mai mică decât valoarea maximă. + min_step_value: Valoarea minimă a pasului este 1 poll_type: label: Tip regular: Opțiune unică diff --git a/plugins/poll/config/locales/client.sk.yml b/plugins/poll/config/locales/client.sk.yml index 5424aff098..1e8b8f6e33 100644 --- a/plugins/poll/config/locales/client.sk.yml +++ b/plugins/poll/config/locales/client.sk.yml @@ -60,6 +60,7 @@ sk: help: options_count: Zadajte aspoň 2 možnosti invalid_values: Minimálna hodnota musí byť menšia ako maximálna hodnota. + min_step_value: Minimálna hodnota pre krok je 1 poll_type: label: Typ regular: Jedna možnosť diff --git a/plugins/poll/config/locales/client.zh_TW.yml b/plugins/poll/config/locales/client.zh_TW.yml index 078e9f1b6d..5b2348af81 100644 --- a/plugins/poll/config/locales/client.zh_TW.yml +++ b/plugins/poll/config/locales/client.zh_TW.yml @@ -36,7 +36,7 @@ zh_TW: open: title: "開啟投票" label: "開啟" - confirm: "你確定要開啟這個投票麼?" + confirm: "你確定要開啟這個投票嗎?" close: title: "關閉投票" label: "關閉" diff --git a/plugins/poll/config/locales/server.ro.yml b/plugins/poll/config/locales/server.ro.yml index 80260a08bb..10b3406484 100644 --- a/plugins/poll/config/locales/server.ro.yml +++ b/plugins/poll/config/locales/server.ro.yml @@ -7,8 +7,10 @@ ro: site_settings: + poll_enabled: "Permiți sondaje?" poll_maximum_options: "Numărul maxim admis de opțiuni într-un sondaj" poll_edit_window_mins: "Număr de minute după crearea postării pe parcursul cărora sondajele pot fi editate." + poll_minimum_trust_level_to_create: "Definește nivelul minim de încredere pentru a crea sondaje." poll: multiple_polls_without_name: "Există mai multe sondaje fără nume. Folosește atributul 'name' pentru a identifica sondajele proprii" multiple_polls_with_same_name: "Există mai multe sondaje cu același nume: %{name}. Folosește atributul 'name' pentru a identifica sondajele." @@ -40,5 +42,6 @@ ro: poll_must_be_open_to_vote: "Sondajul trebuie să fie deschis pentru votare." topic_must_be_open_to_toggle_status: "Subiectul trebuie să fie activ pentru a se schimba statutul." only_staff_or_op_can_toggle_status: "Doar un administrator sau autorul postării inițiale poate schimba starea unui sondaj." + insufficient_rights_to_create: "Nu ai permisiunea de a crea sondaje." email: link_to_poll: "Click pentru afișarea sondajului." diff --git a/plugins/poll/spec/lib/pretty_text_spec.rb b/plugins/poll/spec/lib/pretty_text_spec.rb index 85ee2c2a00..fedc70eb98 100644 --- a/plugins/poll/spec/lib/pretty_text_spec.rb +++ b/plugins/poll/spec/lib/pretty_text_spec.rb @@ -1,10 +1,9 @@ require 'rails_helper' -require 'html_normalize' describe PrettyText do def n(html) - HtmlNormalize.normalize(html) + html.strip end it 'supports multi choice polls' do @@ -95,14 +94,13 @@ describe PrettyText do cooked = PrettyText.cook md expected = <<~MD -
            +
              -
            1. test 1 :slight_smile: test -
            2. -
            3. test 2
            4. +
            5. test 1 :slight_smile: test
            6. +
            7. test 2
            @@ -110,12 +108,9 @@ describe PrettyText do 0 voters

            -

            - Choose up to 2 options

            diff --git a/public/500.cs.html b/public/500.cs.html index a3177c6bda..5402855206 100644 --- a/public/500.cs.html +++ b/public/500.cs.html @@ -8,6 +8,6 @@

            Jejda

            Software, na němž běží toto diskuzní fórum, narazil na nečekané problémy. Omlouváme se za způsobené nepříjemnosti.

            Podrobné informace o chybě byly zaznamenány a automatické oznámení vygenerováno. Podíváme se na to.

            -

            Žádná další akce není třeba. Pokud chyba nezmizí, můžete poskytnout podrobnosti, včetně kroků, jak chybu reprodukovat, založením nového tématu v kategorii zpětné vazby k webu.

            +

            Žádná další akce není potřeba. Ale pokud chyba přetrvá, doplňte prosím podrobnosti, včetně kroků jak chybu reprodukovat, založením nového tématu v kategorii fóra určené pro zpětnou vazbu.

            diff --git a/script/benchmarks/middleware/test.rb b/script/benchmarks/middleware/test.rb new file mode 100644 index 0000000000..58595dfcf9 --- /dev/null +++ b/script/benchmarks/middleware/test.rb @@ -0,0 +1,71 @@ +require 'memory_profiler' +require 'benchmark/ips' + +ENV["RAILS_ENV"] = "production" + +require File.expand_path("../../../../config/environment", __FILE__) + +def req + _t = "9c1a318cb72cca57daf413cc511f0993" + + data = { + "timings[1]" => "1001", + "timings[2]" => "1001", + "timings[3]" => "1001", + "topic_id" => "490310" + } + + data = data.map do |k, v| + "#{CGI.escape(k)}=#{v}" + end.join("&") + + { + "REQUEST_METHOD" => "POST", + "SCRIPT_NAME" => "", + "PATH_INFO" => "/topics/timings.json", + "QUERY_STRING" => "", + "SERVER_NAME" => "localhost", + "SERVER_PORT" => "80", + "HTTP_CONTENT_TYPE" => "application/x-www-form-urlencoded", + "HTTP_VERSION" => "HTTP/1.0", + "HTTP_COOKIE" => "_t=#{_t}", + "rack.input" => StringIO.new(data), + "rack.version" => [1, 2], + "rack.url_scheme" => "http" + } +end + +1.times do + s = Time.now + Rails.application.call(req) + puts(Time.now - s) +end +exit +# +# +StackProf.run(mode: :wall, out: 'report.dump') do + 1000.times do + Rails.application.call(req) + end +end +# +# MemoryProfiler.start +# Rails.application.call(req) +# MemoryProfiler.stop.pretty_print +# exit + +# # exit +# exit + +# Benchmark.ips do |x| +# x.report("default") do +# Rails.application.call(req) +# end +# end + +# status, headers, body = Rails.application.call(req) +# p status +# p headers +# body.each do |s| +# p s.to_s +# end diff --git a/script/boot_mem.rb b/script/boot_mem.rb new file mode 100644 index 0000000000..61da1fa6c6 --- /dev/null +++ b/script/boot_mem.rb @@ -0,0 +1,21 @@ +# simple script to measure memory at boot + +if ENV['RAILS_ENV'] != "production" + exec "RAILS_ENV=production ruby #{__FILE__}" +end + +require 'memory_profiler' + +MemoryProfiler.report do + require File.expand_path("../../config/environment", __FILE__) + + Rails.application.routes.recognize_path('abc') rescue nil + + # load up the yaml for the localization bits, in master process + I18n.t(:posts) + + # load up all models and schema + (ActiveRecord::Base.connection.tables - %w[schema_migrations versions]).each do |table| + table.classify.constantize.first rescue nil + end +end.pretty_print diff --git a/script/bulk_import/base.rb b/script/bulk_import/base.rb index 0413e56f41..ce9bb45f0e 100644 --- a/script/bulk_import/base.rb +++ b/script/bulk_import/base.rb @@ -1,9 +1,11 @@ require "pg" require "set" require "redcarpet" +require "htmlentities" puts "Loading application..." require_relative "../../config/environment" +require_relative '../import_scripts/base/uploader' module BulkImport; end @@ -12,10 +14,53 @@ class BulkImport::Base NOW ||= "now()".freeze PRIVATE_OFFSET ||= 2**30 + CHARSET_MAP = { + "armscii8" => nil, + "ascii" => Encoding::US_ASCII, + "big5" => Encoding::Big5, + "binary" => Encoding::ASCII_8BIT, + "cp1250" => Encoding::Windows_1250, + "cp1251" => Encoding::Windows_1251, + "cp1256" => Encoding::Windows_1256, + "cp1257" => Encoding::Windows_1257, + "cp850" => Encoding::CP850, + "cp852" => Encoding::CP852, + "cp866" => Encoding::IBM866, + "cp932" => Encoding::Windows_31J, + "dec8" => nil, + "eucjpms" => Encoding::EucJP_ms, + "euckr" => Encoding::EUC_KR, + "gb2312" => Encoding::EUC_CN, + "gbk" => Encoding::GBK, + "geostd8" => nil, + "greek" => Encoding::ISO_8859_7, + "hebrew" => Encoding::ISO_8859_8, + "hp8" => nil, + "keybcs2" => nil, + "koi8r" => Encoding::KOI8_R, + "koi8u" => Encoding::KOI8_U, + "latin1" => Encoding::ISO_8859_1, + "latin2" => Encoding::ISO_8859_2, + "latin5" => Encoding::ISO_8859_9, + "latin7" => Encoding::ISO_8859_13, + "macce" => Encoding::MacCentEuro, + "macroman" => Encoding::MacRoman, + "sjis" => Encoding::SHIFT_JIS, + "swe7" => nil, + "tis620" => Encoding::TIS_620, + "ucs2" => Encoding::UTF_16BE, + "ujis" => Encoding::EucJP_ms, + "utf8" => Encoding::UTF_8, + } + def initialize + charset = ENV["DB_CHARSET"] || "utf8" db = ActiveRecord::Base.connection_config @encoder = PG::TextEncoder::CopyRow.new @raw_connection = PG.connect(dbname: db[:database], host: db[:host_names]&.first, port: db[:port]) + @uploader = ImportScripts::Uploader.new + @html_entities = HTMLEntities.new + @encoding = CHARSET_MAP[charset] @markdown = Redcarpet::Markdown.new( Redcarpet::Render::HTML.new(hard_wrap: true), @@ -27,6 +72,7 @@ class BulkImport::Base def run puts "Starting..." + Rails.logger.level = 3 # :error, so that we don't create log files that are many GB preload_i18n fix_highest_post_numbers load_imported_ids @@ -194,6 +240,10 @@ class BulkImport::Base topic_id user_id created_at updated_at } + TOPIC_TAG_COLUMNS ||= %i{ + topic_id tag_id created_at updated_at + } + def create_groups(rows, &block); create_records(rows, "group", GROUP_COLUMNS, &block); end def create_users(rows, &block) @@ -218,6 +268,7 @@ class BulkImport::Base def create_posts(rows, &block); create_records(rows, "post", POST_COLUMNS, &block); end def create_post_actions(rows, &block); create_records(rows, "post_action", POST_ACTION_COLUMNS, &block); end def create_topic_allowed_users(rows, &block); create_records(rows, "topic_allowed_user", TOPIC_ALLOWED_USER_COLUMNS, &block); end + def create_topic_tags(rows, &block); create_records(rows, "topic_tag", TOPIC_TAG_COLUMNS, &block); end def process_group(group) @groups[group[:imported_id].to_s] = group[:id] = @last_group_id += 1 @@ -386,6 +437,12 @@ class BulkImport::Base topic_allowed_user end + def process_topic_tag(topic_tag) + topic_tag[:created_at] = NOW + topic_tag[:updated_at] = NOW + topic_tag + end + def process_raw(raw) # fix whitespaces raw.gsub!(/(\\r)?\\n/, "\n") @@ -436,6 +493,9 @@ class BulkImport::Base # [IMG]...[/IMG] raw.gsub!(/(?:\s*\[IMG\]\s*)+(.+?)(?:\s*\[\/IMG\]\s*)+/im) { "\n\n#{$1}\n\n" } + # [IMG=url] + raw.gsub!(/\[IMG=([^\]]*)\]/im) { "\n\n#{$1}\n\n" } + # [URL=...]...[/URL] raw.gsub!(/\[URL="?(.+?)"?\](.+?)\[\/URL\]/im) { "[#{$2.strip}](#{$1})" } @@ -462,14 +522,19 @@ class BulkImport::Base raw.gsub!(/\[TD="?.*?"?\](.*?)\[\/TD\]/im, "\\1") # [QUOTE]...[/QUOTE] - raw.gsub!(/\[QUOTE\](.+?)\[\/QUOTE\]/im) { |quote| - quote.gsub!(/\[QUOTE\](.+?)\[\/QUOTE\]/im) { "\n#{$1}\n" } - quote.gsub!(/\n(.+?)/) { "\n> #{$1}" } - } + raw.gsub!(/\[QUOTE="([^\]]+)"\]/i) { "[QUOTE=#{$1}]" } - # [QUOTE=;]...[/QUOTE] - raw.gsub!(/\[QUOTE=([^;]+);(\d+)\](.+?)\[\/QUOTE\]/im) do - imported_username, imported_postid, quote = $1, $2, $3 + # Nested Quotes + raw.gsub!(/(\[\/?QUOTE.*?\])/mi) { |q| "\n#{q}\n" } + + # raw.gsub!(/\[QUOTE\](.+?)\[\/QUOTE\]/im) { |quote| + # quote.gsub!(/\[QUOTE\](.+?)\[\/QUOTE\]/im) { "\n#{$1}\n" } + # quote.gsub!(/\n(.+?)/) { "\n> #{$1}" } + # } + + # [QUOTE=;] + raw.gsub!(/\[QUOTE=([^;\]]+);(\d+)\]/i) do + imported_username, imported_postid = $1, $2 username = @mapped_usernames[imported_username] || imported_username post_id = post_id_from_imported_id(imported_postid) @@ -477,9 +542,9 @@ class BulkImport::Base topic_id = @topic_id_by_post_id[post_id] if post_number && topic_id - "\n[quote=\"#{username}, post:#{post_number}, topic:#{topic_id}\"]\n#{quote}\n[/quote]" + "\n[quote=\"#{username}, post:#{post_number}, topic:#{topic_id}\"]\n" else - "\n[quote=\"#{username}\"]\n#{quote}\n[/quote]\n" + "\n[quote=\"#{username}\"]\n" end end @@ -545,6 +610,10 @@ class BulkImport::Base end end + def create_upload(user_id, path, source_filename) + @uploader.create_upload(user_id, path, source_filename) + end + def fix_name(name) name.scrub! if name.valid_encoding? == false return if name.blank? @@ -610,4 +679,14 @@ class BulkImport::Base Redcarpet::Render::SmartyPants.render(ERB::Util.html_escape(title)).scrub.strip end + def normalize_text(text) + return nil unless text.present? + @html_entities.decode(normalize_charset(text.presence || "").scrub) + end + + def normalize_charset(text) + return text if @encoding == Encoding::UTF_8 + return text && text.encode(@encoding).force_encoding(Encoding::UTF_8) + end + end diff --git a/script/bulk_import/phpbb_postgresql.rb b/script/bulk_import/phpbb_postgresql.rb index 3c171424b4..570944b397 100644 --- a/script/bulk_import/phpbb_postgresql.rb +++ b/script/bulk_import/phpbb_postgresql.rb @@ -7,44 +7,6 @@ class BulkImport::PhpBB < BulkImport::Base SUSPENDED_TILL ||= Date.new(3000, 1, 1) TABLE_PREFIX ||= ENV['TABLE_PREFIX'] || "phpbb_" - CHARSET_MAP = { - "armscii8" => nil, - "ascii" => Encoding::US_ASCII, - "big5" => Encoding::Big5, - "binary" => Encoding::ASCII_8BIT, - "cp1250" => Encoding::Windows_1250, - "cp1251" => Encoding::Windows_1251, - "cp1256" => Encoding::Windows_1256, - "cp1257" => Encoding::Windows_1257, - "cp850" => Encoding::CP850, - "cp852" => Encoding::CP852, - "cp866" => Encoding::IBM866, - "cp932" => Encoding::Windows_31J, - "dec8" => nil, - "eucjpms" => Encoding::EucJP_ms, - "euckr" => Encoding::EUC_KR, - "gb2312" => Encoding::EUC_CN, - "gbk" => Encoding::GBK, - "geostd8" => nil, - "greek" => Encoding::ISO_8859_7, - "hebrew" => Encoding::ISO_8859_8, - "hp8" => nil, - "keybcs2" => nil, - "koi8r" => Encoding::KOI8_R, - "koi8u" => Encoding::KOI8_U, - "latin1" => Encoding::ISO_8859_1, - "latin2" => Encoding::ISO_8859_2, - "latin5" => Encoding::ISO_8859_9, - "latin7" => Encoding::ISO_8859_13, - "macce" => Encoding::MacCentEuro, - "macroman" => Encoding::MacRoman, - "sjis" => Encoding::SHIFT_JIS, - "swe7" => nil, - "tis620" => Encoding::TIS_620, - "ucs2" => Encoding::UTF_16BE, - "ujis" => Encoding::EucJP_ms, - "utf8" => Encoding::UTF_8, - } def initialize super @@ -371,16 +333,6 @@ class BulkImport::PhpBB < BulkImport::Base pm_title end - def normalize_text(text) - return nil unless text.present? - @html_entities.decode(normalize_charset(text.presence || "").scrub) - end - - def normalize_charset(text) - return text if @encoding == Encoding::UTF_8 - return text && text.encode(@encoding).force_encoding(Encoding::UTF_8) - end - def parse_birthday(birthday) return if birthday.blank? date_of_birth = Date.strptime(birthday.gsub(/[^\d-]+/, ""), "%m-%d-%Y") rescue nil diff --git a/script/bulk_import/vanilla.rb b/script/bulk_import/vanilla.rb new file mode 100644 index 0000000000..37b6acf231 --- /dev/null +++ b/script/bulk_import/vanilla.rb @@ -0,0 +1,577 @@ +require_relative "base" +require "mysql2" +require "rake" + +class BulkImport::Vanilla < BulkImport::Base + + VANILLA_DB = "dbname" + TABLE_PREFIX = "GDN_" + ATTACHMENTS_BASE_DIR = "/my/absolute/path/to/from_vanilla/uploads" + BATCH_SIZE = 1000 + CONVERT_HTML = true + + SUSPENDED_TILL ||= Date.new(3000, 1, 1) + + def initialize + super + @htmlentities = HTMLEntities.new + @client = Mysql2::Client.new( + host: "localhost", + username: "root", + database: VANILLA_DB + ) + + @import_tags = false + begin + r = @client.query("select count(*) count from #{TABLE_PREFIX}Tag where countdiscussions > 0") + @import_tags = true if r.first["count"].to_i > 0 + rescue => e + puts "Tags won't be imported. None found. #{e.message}" + end + + @category_mappings = {} + end + + def start + run # will call execute, and then "complete" the migration + + # Now that the migration is complete, do some more work: + + Discourse::Application.load_tasks + Rake::Task["import:ensure_consistency"].invoke + + import_avatars # slow + create_permalinks # TODO: do it bulk style + end + + def execute + if @import_tags + SiteSetting.tagging_enabled = true + SiteSetting.max_tags_per_topic = 10 + SiteSetting.max_tag_length = 100 + end + + # other good ones: + + # SiteSetting.port = 3000 + # SiteSetting.automatic_backups_enabled = false + # SiteSetting.disable_emails = true + # etc. + + import_users + import_user_emails + import_user_profiles + import_user_stats + + import_categories + import_topics + import_tags if @import_tags + import_posts + + import_private_topics + import_topic_allowed_users + import_private_posts + end + + def import_users + puts '', "Importing users..." + + username = nil + total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}User;").first['count'] + + users = mysql_stream <<-SQL + SELECT UserID, Name, Title, Location, Email, + DateInserted, DateLastActive, InsertIPAddress, Admin, Banned + FROM #{TABLE_PREFIX}User + WHERE UserID > #{@last_imported_user_id} + AND Deleted = 0 + ORDER BY UserID ASC + SQL + + create_users(users) do |row| + next if row['Email'].blank? + next if row['Name'].blank? + + if ip_address = row['InsertIPAddress']&.split(',').try(:[], 0) + ip_address = nil unless (IPAddr.new(ip_address) rescue false) + end + + u = { + imported_id: row['UserID'], + email: row['Email'], + username: row['Name'], + name: row['Name'], + created_at: row['DateInserted'] == nil ? 0 : Time.zone.at(row['DateInserted']), + registration_ip_address: ip_address, + last_seen_at: row['DateLastActive'] == nil ? 0 : Time.zone.at(row['DateLastActive']), + location: row['Location'], + admin: row['Admin'] > 0 + } + if row["Banned"] > 0 + u[:suspended_at] = Time.zone.at(row['DateInserted']) + u[:suspended_till] = SUSPENDED_TILL + end + u + end + end + + def import_user_emails + puts '', 'Importing user emails...' + + users = mysql_stream <<-SQL + SELECT UserID, Name, Email, DateInserted + FROM #{TABLE_PREFIX}User + WHERE UserID > #{@last_imported_user_id} + AND Deleted = 0 + ORDER BY UserID ASC + SQL + + create_user_emails(users) do |row| + next if row['Email'].blank? + next if row['Name'].blank? + + { + imported_id: row["UserID"], + imported_user_id: row["UserID"], + email: row["Email"], + created_at: Time.zone.at(row["DateInserted"]) + } + end + end + + def import_user_profiles + puts '', 'Importing user profiles...' + + user_profiles = mysql_stream <<-SQL + SELECT UserID, Name, Email, Location, About + FROM #{TABLE_PREFIX}User + WHERE UserID > #{@last_imported_user_id} + AND Deleted = 0 + ORDER BY UserID ASC + SQL + + create_user_profiles(user_profiles) do |row| + next if row['Email'].blank? + next if row['Name'].blank? + + { + user_id: user_id_from_imported_id(row["UserID"]), + location: row["Location"], + bio_raw: row["About"] + } + end + end + + def import_user_stats + puts '', "Importing user stats..." + + users = mysql_stream <<-SQL + SELECT UserID, CountDiscussions, CountComments, DateInserted + FROM #{TABLE_PREFIX}User + WHERE UserID > #{@last_imported_user_id} + AND Deleted = 0 + ORDER BY UserID ASC + SQL + + now = Time.zone.now + + create_user_stats(users) do |row| + next unless @users[row['UserID'].to_s] # shouldn't need this but it can be NULL :< + + { + imported_id: row['UserID'], + imported_user_id: row['UserID'], + new_since: Time.zone.at(row['DateInserted'] || now), + post_count: row['CountComments'] || 0, + topic_count: row['CountDiscussions'] || 0 + } + end + end + + def import_avatars + if ATTACHMENTS_BASE_DIR && File.exists?(ATTACHMENTS_BASE_DIR) + puts "", "importing user avatars" + + start = Time.now + count = 0 + + User.find_each do |u| + count += 1 + print "\r%7d - %6d/sec".freeze % [count, count.to_f / (Time.now - start)] + + next unless u.custom_fields["import_id"] + + r = mysql_query("SELECT photo FROM #{TABLE_PREFIX}User WHERE UserID = #{u.custom_fields['import_id']};").first + next if r.nil? + photo = r["photo"] + next unless photo.present? + + # Possible encoded values: + # 1. cf://uploads/userpics/820/Y0AFUQYYM6QN.jpg + # 2. ~cf/userpics2/cf566487133f1f538e02da96f9a16b18.jpg + # 3. ~cf/userpics/txkt8kw1wozn.jpg + # 4. s3://uploads/xf/22/22690.jpg + + photo_real_filename = nil + parts = photo.squeeze("/").split("/") + if parts[0] =~ /^[a-z0-9]{2}:/ + photo_path = "#{ATTACHMENTS_BASE_DIR}/#{parts[2..-2].join('/')}".squeeze("/") + elsif parts[0] == "~cf" + photo_path = "#{ATTACHMENTS_BASE_DIR}/#{parts[1..-2].join('/')}".squeeze("/") + else + puts "UNKNOWN FORMAT: #{photo}" + next + end + + if !File.exists?(photo_path) + puts "Path to avatar file not found! Skipping. #{photo_path}" + next + end + + photo_real_filename = find_photo_file(photo_path, parts.last) + if photo_real_filename.nil? + puts "Couldn't find file for #{photo}. Skipping." + next + end + + print "." + + upload = create_upload(u.id, photo_real_filename, File.basename(photo_real_filename)) + if upload.persisted? + u.import_mode = false + u.create_user_avatar + u.import_mode = true + u.user_avatar.update(custom_upload_id: upload.id) + u.update(uploaded_avatar_id: upload.id) + else + puts "Error: Upload did not persist for #{u.username} #{photo_real_filename}!" + end + end + end + end + + def find_photo_file(path, base_filename) + base_guess = base_filename.dup + full_guess = File.join(path, base_guess) # often an exact match exists + + return full_guess if File.exists?(full_guess) + + # Otherwise, the file exists but with a prefix: + # The p prefix seems to be the full file, so try to find that one first. + ['p', 't', 'n'].each do |prefix| + full_guess = File.join(path, "#{prefix}#{base_guess}") + return full_guess if File.exists?(full_guess) + end + + # Didn't find it. + nil + end + + def import_categories + puts "", "Importing categories..." + + categories = mysql_query(" + SELECT CategoryID, ParentCategoryID, Name, Description, Sort + FROM #{TABLE_PREFIX}Category + WHERE CategoryID > 0 + ORDER BY Sort, CategoryID + ").to_a + + # Throw the -1 level categories away since they contain no topics. + # Use the next level as root categories. + root_category_ids = Set.new(categories.select { |c| c["ParentCategoryID"] == -1 }.map { |c| c["CategoryID"] }) + + top_level_categories = categories.select { |c| root_category_ids.include?(c["ParentCategoryID"]) } + + # Depth = 2 + create_categories(top_level_categories) do |category| + { + imported_id: category['CategoryID'], + name: CGI.unescapeHTML(category['Name']), + description: category['Description'] ? CGI.unescapeHTML(category['Description']) : nil, + position: category['Sort'] + } + end + + top_level_category_ids = Set.new(top_level_categories.map { |c| c["CategoryID"] }) + + subcategories = categories.select { |c| top_level_category_ids.include?(c["ParentCategoryID"]) } + + # Depth = 3 + create_categories(subcategories) do |category| + { + imported_id: category['CategoryID'], + parent_category_id: category_id_from_imported_id(category['ParentCategoryID']), + name: CGI.unescapeHTML(category['Name']), + description: category['Description'] ? CGI.unescapeHTML(category['Description']) : nil, + position: category['Sort'] + } + end + + subcategory_ids = Set.new(subcategories.map { |c| c['CategoryID'] }) + + # Depth 4 and 5 need to be tags + + categories.each do |c| + next if c['ParentCategoryID'] == -1 + next if top_level_category_ids.include?(c['CategoryID']) + next if subcategory_ids.include?(c['CategoryID']) + + # Find a depth 3 category for topics in this category + parent = c + while !parent.nil? && !subcategory_ids.include?(parent['CategoryID']) + parent = categories.find { |subcat| subcat['CategoryID'] == parent['ParentCategoryID'] } + end + + if parent + tag_name = DiscourseTagging.clean_tag(c['Name']) + @category_mappings[c['CategoryID']] = { + category_id: category_id_from_imported_id(parent['CategoryID']), + tag: Tag.find_by_name(tag_name) || Tag.create(name: tag_name) + } + else + puts '', "Couldn't find a category for #{c['CategoryID']} '#{c['Name']}'!" + end + end + end + + def import_topics + puts "", "Importing topics..." + + topics_sql = "SELECT DiscussionID, CategoryID, Name, Body, DateInserted, InsertUserID, Announce + FROM #{TABLE_PREFIX}Discussion + WHERE DiscussionID > #{@last_imported_topic_id} + ORDER BY DiscussionID ASC" + + create_topics(mysql_stream(topics_sql)) do |row| + { + imported_id: row["DiscussionID"], + title: normalize_text(row["Name"]), + category_id: category_id_from_imported_id(row["CategoryID"]) || + @category_mappings[row["CategoryID"]].try(:[], :category_id), + user_id: user_id_from_imported_id(row["InsertUserID"]), + created_at: Time.zone.at(row['DateInserted']), + pinned_at: row['Announce'] == 0 ? nil : Time.zone.at(row['DateInserted']) + } + end + + puts "", "importing first posts..." + + create_posts(mysql_stream(topics_sql)) do |row| + { + imported_id: "d-" + row['DiscussionID'].to_s, + topic_id: topic_id_from_imported_id(row["DiscussionID"]), + user_id: user_id_from_imported_id(row["InsertUserID"]), + created_at: Time.zone.at(row['DateInserted']), + raw: clean_up(row["Body"]) + } + end + + puts '', 'converting deep categories to tags...' + + create_topic_tags(mysql_stream(topics_sql)) do |row| + next unless mapping = @category_mappings[row['CategoryID']] + + { + tag_id: mapping[:tag].id, + topic_id: topic_id_from_imported_id(row["DiscussionID"]) + } + end + end + + def import_posts + puts "", "Importing posts..." + + posts = mysql_stream( + "SELECT CommentID, DiscussionID, Body, DateInserted, InsertUserID + FROM #{TABLE_PREFIX}Comment + WHERE CommentID > #{@last_imported_post_id} + ORDER BY CommentID ASC") + + create_posts(posts) do |row| + next unless topic_id = topic_id_from_imported_id(row['DiscussionID']) + next if row['Body'].blank? + + { + imported_id: row['CommentID'], + topic_id: topic_id, + user_id: user_id_from_imported_id(row["InsertUserID"]), + created_at: Time.zone.at(row['DateInserted']), + raw: clean_up(row["Body"]) + } + end + end + + def import_tags + puts "", "Importing tags..." + + tag_mapping = {} + + mysql_query("SELECT TagID, Name FROM #{TABLE_PREFIX}Tag").each do |row| + tag_name = DiscourseTagging.clean_tag(row['Name']) + tag = Tag.find_by_name(tag_name) || Tag.create(name: tag_name) + tag_mapping[row['TagID']] = tag.id + end + + tags = mysql_query( + "SELECT TagID, DiscussionID + FROM #{TABLE_PREFIX}TagDiscussion + WHERE DiscussionID > #{@last_imported_topic_id} + ORDER BY DateInserted") + + create_topic_tags(tags) do |row| + next unless topic_id = topic_id_from_imported_id(row['DiscussionID']) + + { + topic_id: topic_id, + tag_id: tag_mapping[row['TagID']] + } + end + end + + def import_private_topics + puts "", "Importing private topics..." + + topics_sql = "SELECT c.ConversationID, c.Subject, m.MessageID, m.Body, c.DateInserted, c.InsertUserID + FROM #{TABLE_PREFIX}Conversation c, #{TABLE_PREFIX}ConversationMessage m + WHERE c.FirstMessageID = m.MessageID + AND c.ConversationID > #{@last_imported_private_topic_id - PRIVATE_OFFSET} + ORDER BY c.ConversationID ASC" + + create_topics(mysql_stream(topics_sql)) do |row| + { + archetype: Archetype.private_message, + imported_id: row["ConversationID"] + PRIVATE_OFFSET, + title: row["Subject"] ? normalize_text(row["Subject"]) : "Conversation #{row["ConversationID"]}", + user_id: user_id_from_imported_id(row["InsertUserID"]), + created_at: Time.zone.at(row['DateInserted']) + } + end + end + + def import_topic_allowed_users + puts "", "importing topic_allowed_users..." + + topic_allowed_users_sql = " + SELECT ConversationID, UserID + FROM #{TABLE_PREFIX}UserConversation + WHERE Deleted = 0 + ORDER BY ConversationID ASC" + + added = 0 + + create_topic_allowed_users(mysql_stream(topic_allowed_users_sql)) do |row| + next unless topic_id = topic_id_from_imported_id(row['ConversationID'] + PRIVATE_OFFSET) + next unless user_id = user_id_from_imported_id(row["UserID"]) + added += 1 + { + topic_id: topic_id, + user_id: user_id, + } + end + + puts '', "Added #{added} topic_allowed_users records." + end + + def import_private_posts + puts "", "importing private replies..." + + private_posts_sql = " + SELECT ConversationID, MessageID, Body, InsertUserID, DateInserted + FROM GDN_ConversationMessage + WHERE ConversationID > #{@last_imported_private_topic_id - PRIVATE_OFFSET} + ORDER BY ConversationID ASC, MessageID ASC" + + create_posts(mysql_stream(private_posts_sql)) do |row| + next unless topic_id = topic_id_from_imported_id(row['ConversationID'] + PRIVATE_OFFSET) + + { + imported_id: row['MessageID'] + PRIVATE_OFFSET, + topic_id: topic_id, + user_id: user_id_from_imported_id(row['InsertUserID']), + created_at: Time.zone.at(row['DateInserted']), + raw: clean_up(row['Body']) + } + end + end + + # TODO: too slow + def create_permalinks + puts '', 'Creating permalinks...', '' + + puts ' User pages...' + + start = Time.now + count = 0 + now = Time.zone.now + + sql = "COPY permalinks (url, created_at, updated_at, external_url) FROM STDIN" + + @raw_connection.copy_data(sql, @encoder) do + User.includes(:_custom_fields).find_each do |u| + count += 1 + ucf = u.custom_fields + if ucf && ucf["import_id"] + vanilla_username = ucf["import_username"] || u.username + @raw_connection.put_copy_data( + ["profile/#{vanilla_username}", now, now, "/users/#{u.username}"] + ) + end + + print "\r%7d - %6d/sec".freeze % [count, count.to_f / (Time.now - start)] if count % 5000 == 0 + end + end + + puts '', '', ' Topics and posts...' + + start = Time.now + count = 0 + + sql = "COPY permalinks (url, topic_id, post_id, created_at, updated_at) FROM STDIN" + + @raw_connection.copy_data(sql, @encoder) do + Post.includes(:_custom_fields).find_each do |post| + count += 1 + pcf = post.custom_fields + if pcf && pcf["import_id"] + topic = post.topic + id = pcf["import_id"].split('-').last + if post.post_number == 1 + slug = Slug.for(topic.title) # probably matches what vanilla would do... + @raw_connection.put_copy_data( + ["discussion/#{id}/#{slug}", topic.id, nil, now, now] + ) + else + @raw_connection.put_copy_data( + ["discussion/comment/#{id}", nil, post.id, now, now] + ) + end + end + + print "\r%7d - %6d/sec".freeze % [count, count.to_f / (Time.now - start)] if count % 5000 == 0 + end + end + end + + def clean_up(raw) + # post id is sometimes prefixed with "c-" + raw.gsub!(/\[QUOTE="([^;]+);c-(\d+)"\]/i) { "[QUOTE=#{$1};#{$2}]" } + + raw + end + + def staff_guardian + @_staff_guardian ||= Guardian.new(Discourse.system_user) + end + + def mysql_stream(sql) + @client.query(sql, stream: true) + end + + def mysql_query(sql) + @client.query(sql) + end + +end + +BulkImport::Vanilla.new.start diff --git a/script/bulk_import/vbulletin.rb b/script/bulk_import/vbulletin.rb index d9d394a716..830c330e93 100644 --- a/script/bulk_import/vbulletin.rb +++ b/script/bulk_import/vbulletin.rb @@ -6,44 +6,6 @@ require "htmlentities" class BulkImport::VBulletin < BulkImport::Base SUSPENDED_TILL ||= Date.new(3000, 1, 1) - CHARSET_MAP = { - "armscii8" => nil, - "ascii" => Encoding::US_ASCII, - "big5" => Encoding::Big5, - "binary" => Encoding::ASCII_8BIT, - "cp1250" => Encoding::Windows_1250, - "cp1251" => Encoding::Windows_1251, - "cp1256" => Encoding::Windows_1256, - "cp1257" => Encoding::Windows_1257, - "cp850" => Encoding::CP850, - "cp852" => Encoding::CP852, - "cp866" => Encoding::IBM866, - "cp932" => Encoding::Windows_31J, - "dec8" => nil, - "eucjpms" => Encoding::EucJP_ms, - "euckr" => Encoding::EUC_KR, - "gb2312" => Encoding::EUC_CN, - "gbk" => Encoding::GBK, - "geostd8" => nil, - "greek" => Encoding::ISO_8859_7, - "hebrew" => Encoding::ISO_8859_8, - "hp8" => nil, - "keybcs2" => nil, - "koi8r" => Encoding::KOI8_R, - "koi8u" => Encoding::KOI8_U, - "latin1" => Encoding::ISO_8859_1, - "latin2" => Encoding::ISO_8859_2, - "latin5" => Encoding::ISO_8859_9, - "latin7" => Encoding::ISO_8859_13, - "macce" => Encoding::MacCentEuro, - "macroman" => Encoding::MacRoman, - "sjis" => Encoding::SHIFT_JIS, - "swe7" => nil, - "tis620" => Encoding::TIS_620, - "ucs2" => Encoding::UTF_16BE, - "ujis" => Encoding::EucJP_ms, - "utf8" => Encoding::UTF_8, - } def initialize super @@ -502,15 +464,6 @@ class BulkImport::VBulletin < BulkImport::Base normalize_text(title).scrub.gsub(/^Re\s*:\s*/i, "") end - def normalize_text(text) - @html_entities.decode(normalize_charset(text.presence || "").scrub) - end - - def normalize_charset(text) - return text if @encoding == Encoding::UTF_8 - return text && text.encode(@encoding).force_encoding(Encoding::UTF_8) - end - def parse_birthday(birthday) return if birthday.blank? date_of_birth = Date.strptime(birthday.gsub(/[^\d-]+/, ""), "%m-%d-%Y") rescue nil diff --git a/script/import_scripts/base.rb b/script/import_scripts/base.rb index 7f683818f7..594579eafe 100644 --- a/script/import_scripts/base.rb +++ b/script/import_scripts/base.rb @@ -71,8 +71,8 @@ class ImportScripts::Base min_topic_title_length: 1, min_post_length: 1, min_first_post_length: 1, - min_private_message_post_length: 1, - min_private_message_title_length: 1, + min_personal_message_post_length: 1, + min_personal_message_title_length: 1, allow_duplicate_topic_titles: true, disable_emails: true, max_attachment_size_kb: 102400, diff --git a/script/import_scripts/mbox/support/indexer.rb b/script/import_scripts/mbox/support/indexer.rb index d3980cd725..5d9d91f004 100644 --- a/script/import_scripts/mbox/support/indexer.rb +++ b/script/import_scripts/mbox/support/indexer.rb @@ -199,7 +199,7 @@ module ImportScripts::Mbox # TODO: make the list name (or maybe multiple names) configurable # Strip mailing list name from subject - subject = subject.gsub(/\[#{Regexp.escape(list_name)}\]/, '').strip + subject = subject.gsub(/\[#{Regexp.escape(list_name)}\]/i, '').strip clean_subject(subject) end diff --git a/script/import_scripts/simplepress.rb b/script/import_scripts/simplepress.rb index 62663842bc..d5846a359a 100644 --- a/script/import_scripts/simplepress.rb +++ b/script/import_scripts/simplepress.rb @@ -22,7 +22,8 @@ class ImportScripts::SimplePress < ImportScripts::Base def execute import_users import_categories - import_topics_and_posts + import_topics + import_posts end def import_users @@ -93,12 +94,12 @@ class ImportScripts::SimplePress < ImportScripts::Base end end - def import_topics_and_posts - puts "", "creating topics and posts" + def import_topics + puts "", "creating topics" - total_count = mysql_query("SELECT count(*) count from #{TABLE_PREFIX}posts").first["count"] + total_count = mysql_query("SELECT COUNT(*) count FROM #{TABLE_PREFIX}posts WHERE post_index = 1").first["count"] - topic_first_post_id = {} + @topic_first_post_id = {} batches(BATCH_SIZE) do |offset| results = mysql_query(" @@ -106,14 +107,16 @@ class ImportScripts::SimplePress < ImportScripts::Base p.topic_id topic_id, t.forum_id category_id, t.topic_name title, - p.post_index post_index, + t.topic_opened views, + t.topic_pinned pinned, p.user_id user_id, p.post_content raw, p.post_date post_time FROM #{TABLE_PREFIX}posts p, #{TABLE_PREFIX}topics t WHERE p.topic_id = t.topic_id - ORDER BY p.post_date + AND p.post_index = 1 + ORDER BY p.post_id LIMIT #{BATCH_SIZE} OFFSET #{offset}; ") @@ -123,29 +126,60 @@ class ImportScripts::SimplePress < ImportScripts::Base next if all_records_exist? :posts, results.map { |m| m['id'].to_i } create_posts(results, total: total_count, offset: offset) do |m| - skip = false - mapped = {} + @topic_first_post_id[m['topic_id']] = m['id'] + created_at = Time.zone.at(m['post_time']) + { + id: m['id'], + user_id: user_id_from_imported_user_id(m['user_id']) || -1, + raw: process_simplepress_post(m['raw'], m['id']), + created_at: created_at, + category: category_id_from_imported_category_id(m['category_id']), + title: CGI.unescapeHTML(m['title']), + views: m['views'], + pinned_at: m['pinned'] == 1 ? created_at : nil, + } + end + end + end - mapped[:id] = m['id'] - mapped[:user_id] = user_id_from_imported_user_id(m['user_id']) || -1 - mapped[:raw] = process_simplepress_post(m['raw'], m['id']) - mapped[:created_at] = Time.zone.at(m['post_time']) + def import_posts + puts "", "creating posts" - if m['post_index'] == 1 - mapped[:category] = category_id_from_imported_category_id(m['category_id']) - mapped[:title] = CGI.unescapeHTML(m['title']) - topic_first_post_id[m['topic_id']] = m['id'] + total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}posts WHERE post_index <> 1").first["count"] + + batches(BATCH_SIZE) do |offset| + results = mysql_query(" + SELECT p.post_id id, + p.topic_id topic_id, + p.user_id user_id, + p.post_content raw, + p.post_date post_time + FROM #{TABLE_PREFIX}posts p, + #{TABLE_PREFIX}topics t + WHERE p.topic_id = t.topic_id + AND p.post_index <> 1 + ORDER BY p.post_id + LIMIT #{BATCH_SIZE} + OFFSET #{offset}; + ") + + break if results.size < 1 + + next if all_records_exist? :posts, results.map { |m| m['id'].to_i } + + create_posts(results, total: total_count, offset: offset) do |m| + if parent = topic_lookup_from_imported_post_id(@topic_first_post_id[m['topic_id']]) + { + id: m['id'], + user_id: user_id_from_imported_user_id(m['user_id']) || -1, + topic_id: parent[:topic_id], + raw: process_simplepress_post(m['raw'], m['id']), + created_at: Time.zone.at(m['post_time']), + } else - parent = topic_lookup_from_imported_post_id(topic_first_post_id[m['topic_id']]) - if parent - mapped[:topic_id] = parent[:topic_id] - else - puts "Parent post #{first_post_id} doesn't exist. Skipping #{m["id"]}: #{m["title"][0..40]}" - skip = true - end + puts "Parent post #{m['topic_id']} doesn't exist. Skipping #{m["id"]}" + nil end - - skip ? nil : mapped end end end diff --git a/spec/components/cooked_post_processor_spec.rb b/spec/components/cooked_post_processor_spec.rb index ee40ef516d..618b69a349 100644 --- a/spec/components/cooked_post_processor_spec.rb +++ b/spec/components/cooked_post_processor_spec.rb @@ -180,7 +180,7 @@ describe CookedPostProcessor do SiteSetting.create_thumbnails = true Upload.expects(:get_from_url).returns(upload) - FastImage.expects(:size).returns([860, 1900]) + FastImage.expects(:size).returns([860, 2000]) OptimizedImage.expects(:resize).never OptimizedImage.expects(:crop).returns(true) diff --git a/spec/components/guardian_spec.rb b/spec/components/guardian_spec.rb index 09d4dc042d..0fc91d7b79 100644 --- a/spec/components/guardian_spec.rb +++ b/spec/components/guardian_spec.rb @@ -26,6 +26,24 @@ describe Guardian do expect { Guardian.new(user) }.not_to raise_error end + describe "can_post_link?" do + it "returns false for anonymous users" do + expect(Guardian.new.can_post_link?).to eq(false) + end + + it "returns true for a regular user" do + expect(Guardian.new(user).can_post_link?).to eq(true) + end + + it "supports customization by site setting" do + user.trust_level = 0 + SiteSetting.min_trust_to_post_links = 0 + expect(Guardian.new(user).can_post_link?).to eq(true) + SiteSetting.min_trust_to_post_links = 1 + expect(Guardian.new(user).can_post_link?).to eq(false) + end + end + describe 'post_can_act?' do let(:post) { build(:post) } let(:user) { build(:user) } @@ -48,11 +66,29 @@ describe Guardian do expect(Guardian.new(user).post_can_act?(post, :like)).to be_falsey end - it "always allows flagging" do + it "allows flagging archived posts" do post.topic.archived = true expect(Guardian.new(user).post_can_act?(post, :spam)).to be_truthy end + it "allows flagging of staff posts when allow_flagging_staff is true" do + SiteSetting.allow_flagging_staff = true + staff_post = Fabricate(:post, user: Fabricate(:moderator)) + expect(Guardian.new(user).post_can_act?(staff_post, :spam)).to be_truthy + end + + it "doesn't allow flagging of staff posts when allow_flagging_staff is false" do + SiteSetting.allow_flagging_staff = false + staff_post = Fabricate(:post, user: Fabricate(:moderator)) + expect(Guardian.new(user).post_can_act?(staff_post, :spam)).to eq(false) + end + + it "allows liking of staff when allow_flagging_staff is false" do + SiteSetting.allow_flagging_staff = false + staff_post = Fabricate(:post, user: Fabricate(:moderator)) + expect(Guardian.new(user).post_can_act?(staff_post, :like)).to eq(true) + end + it "returns false when liking yourself" do expect(Guardian.new(post.user).post_can_act?(post, :like)).to be_falsey end @@ -70,14 +106,14 @@ describe Guardian do end it "returns false for notify_user if private messages are disabled" do - SiteSetting.enable_private_messages = false + SiteSetting.enable_personal_messages = false user.trust_level = TrustLevel[2] expect(Guardian.new(user).post_can_act?(post, :notify_user)).to be_falsey expect(Guardian.new(user).post_can_act?(post, :notify_moderators)).to be_falsey end it "returns false for notify_user and notify_moderators if private messages are enabled but threshold not met" do - SiteSetting.enable_private_messages = true + SiteSetting.enable_personal_messages = true SiteSetting.min_trust_to_send_messages = 2 user.trust_level = TrustLevel[1] expect(Guardian.new(user).post_can_act?(post, :notify_user)).to be_falsey @@ -90,11 +126,17 @@ describe Guardian do expect(Guardian.new(user).post_can_act?(post, :like)).to be_truthy end - it "returns false for a new user flagging a standard post as spam" do + it "returns false for a new user flagging as spam" do user.trust_level = TrustLevel[0] expect(Guardian.new(user).post_can_act?(post, :spam)).to be_falsey end + it "returns true for a new user flagging as spam if enabled" do + SiteSetting.min_trust_to_flag_posts = 0 + user.trust_level = TrustLevel[0] + expect(Guardian.new(user).post_can_act?(post, :spam)).to be_truthy + end + it "returns true for a new user flagging a private message as spam" do post = Fabricate(:private_message_post, user: Fabricate(:admin)) user.trust_level = TrustLevel[0] @@ -170,8 +212,8 @@ describe Guardian do expect(Guardian.new(user).can_send_private_message?(another_user)).to be_falsey end - context "enable_private_messages is false" do - before { SiteSetting.enable_private_messages = false } + context "enable_personal_messages is false" do + before { SiteSetting.enable_personal_messages = false } it "returns false if user is not staff member" do expect(Guardian.new(trust_level_4).can_send_private_message?(another_user)).to be_falsey @@ -451,7 +493,7 @@ describe Guardian do context "when private messages are disabled" do before do - SiteSetting.enable_private_messages = false + SiteSetting.enable_personal_messages = false end it "doesn't allow a regular user to invite" do diff --git a/spec/components/html_normalize_spec.rb b/spec/components/html_normalize_spec.rb deleted file mode 100644 index 7c75d8a15b..0000000000 --- a/spec/components/html_normalize_spec.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'rails_helper' -require 'html_normalize' - -describe HtmlNormalize do - - def n(html) - HtmlNormalize.normalize(html) - end - - it "handles attributes without values" do - expect(n "").to eq("") - end - - it "handles self closing tags" do - - source = <<-HTML -
            - - boo -
            -HTML - expect(n source).to eq(source.strip) - end - - it "Can handle aside" do - - source = <<~HTML - -HTML - expected = <<~HTML - -HTML - - expect(n expected).to eq(n source) - end - - it "Can normalize attributes" do - - source = "b" - same = "b" - - expect(n source).to eq(n same) - end - - it "Can indent divs nicely" do - source = "
            hello world
            " - expected = <<~HTML -
            -
            -
            - hello world -
            -
            -
            -HTML - - expect(n source).to eq(expected.strip) - end -end diff --git a/spec/components/middleware/request_tracker_spec.rb b/spec/components/middleware/request_tracker_spec.rb index e56c708a22..d4455abbb4 100644 --- a/spec/components/middleware/request_tracker_spec.rb +++ b/spec/components/middleware/request_tracker_spec.rb @@ -127,6 +127,34 @@ describe Middleware::RequestTracker do expect(status).to eq(429) end + describe "register_ip_skipper" do + before do + Middleware::RequestTracker.register_ip_skipper do |ip| + ip == "1.1.1.2" + end + global_setting :max_reqs_per_ip_per_10_seconds, 1 + global_setting :max_reqs_per_ip_mode, 'block' + end + + after do + Middleware::RequestTracker.unregister_ip_skipper + end + + it "won't block if the ip is skipped" do + env1 = env("REMOTE_ADDR" => "1.1.1.2") + status, _ = middleware.call(env1) + status, _ = middleware.call(env1) + expect(status).to eq(200) + end + + it "blocks if the ip isn't skipped" do + env1 = env("REMOTE_ADDR" => "1.1.1.1") + status, _ = middleware.call(env1) + status, _ = middleware.call(env1) + expect(status).to eq(429) + end + end + it "does nothing for private IPs if skipped" do global_setting :max_reqs_per_ip_per_10_seconds, 1 global_setting :max_reqs_per_ip_mode, 'warn+block' @@ -206,7 +234,7 @@ describe Middleware::RequestTracker do end after do - Middleware::RequestTracker.register_detailed_request_logger(logger) + Middleware::RequestTracker.unregister_detailed_request_logger(logger) end it "can correctly log detailed data" do diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb index 6e0ee7e045..487ec56968 100644 --- a/spec/components/pretty_text_spec.rb +++ b/spec/components/pretty_text_spec.rb @@ -1,6 +1,5 @@ require 'rails_helper' require 'pretty_text' -require 'html_normalize' describe PrettyText do @@ -9,11 +8,11 @@ describe PrettyText do end def n(html) - HtmlNormalize.normalize(html) + html.strip end def cook(*args) - n(PrettyText.cook(*args)) + PrettyText.cook(*args) end let(:wrapped_image) { "" } @@ -33,13 +32,13 @@ describe PrettyText do topic = Fabricate(:topic, title: "this is a test topic :slight_smile:") expected = <<~HTML -

          - - - {{text-field value=loginName placeholderKey="login.email_placeholder" id="login-account-name" autocorrect="off" autocapitalize="off" autofocus="autofocus"}} - {{text-field value=loginName placeholderKey="login.email_placeholder" id="login-account-name" autocorrect="off" autocapitalize="off" autofocus="autofocus"}}
          - - - {{password-field value=loginPassword type="password" id="login-account-password" maxlength="200" capsLockOn=capsLockOn}}   - - {{i18n 'forgot_password.action'}} - {{password-field value=loginPassword type="password" id="login-account-password" maxlength="200" capsLockOn=capsLockOn}}{{i18n 'forgot_password.action'}}