From 1f0915bf838cfea3ab2aa9c00eda677dacf71789 Mon Sep 17 00:00:00 2001 From: Ryan Fox Date: Mon, 2 Feb 2015 12:55:32 -0500 Subject: [PATCH 0001/1435] Allow periods in the external_id value used in the /users/by-external route. --- app/controllers/users_controller.rb | 7 +++++++ config/routes.rb | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index a44008d11d..1c76a22aa8 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -28,6 +28,13 @@ class UsersController < ApplicationController def show @user = fetch_user_from_params user_serializer = UserSerializer.new(@user, scope: guardian, root: 'user') + + # This is a hack to get around a Rails issue where values with periods aren't handled correctly + # when used as part of a route. + if params[:external_id] and params[:external_id].ends_with? '.json' + return render_json_dump(user_serializer) + end + respond_to do |format| format.html do @restrict_fields = guardian.restrict_user_fields?(@user) diff --git a/config/routes.rb b/config/routes.rb index 8a41b8be10..03a92e4197 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -256,7 +256,8 @@ Discourse::Application.routes.draw do get "users/:username/badges" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/notifications" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} delete "users/:username" => "users#destroy", constraints: {username: USERNAME_ROUTE_FORMAT} - get "users/by-external/:external_id" => "users#show" + # The external_id constraint is to allow periods to be used in the value without becoming part of the format. ie: foo.bar.json + get "users/by-external/:external_id" => "users#show", constraints: {external_id: /[^\/]+/} get "users/:username/flagged-posts" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/deleted-posts" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/badges_json" => "user_badges#username" From c3f21dcdfc9fd73a962de021b05c81bb5b6f1e97 Mon Sep 17 00:00:00 2001 From: Ryan Fox Date: Mon, 2 Feb 2015 12:58:02 -0500 Subject: [PATCH 0002/1435] Remove the .json part from the external_id value when using it to lookup a user. --- app/controllers/application_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ff805687b4..e8313fbde4 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -239,7 +239,8 @@ class ApplicationController < ActionController::Base find_opts[:active] = true unless opts[:include_inactive] User.find_by(find_opts) elsif params[:external_id] - SingleSignOnRecord.find_by(external_id: params[:external_id]).try(:user) + external_id = params[:external_id].gsub(/\.json$/, '') + SingleSignOnRecord.find_by(external_id: external_id).try(:user) end raise Discourse::NotFound.new if user.blank? From 15e54c715f798382eabf47472c514a9423576e10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sigur=C3=B0ur=20Gu=C3=B0brandsson?= Date: Wed, 25 Feb 2015 17:23:57 +0000 Subject: [PATCH 0003/1435] FIX: Added two user badge triggers Created two triggers that trigger events when a badge is granted or removed. Trigger 1: user_badge_granted Variable - badge_id Variable - user_id Trigger 2: user_badge_removed Variable - badge_id Variable - user_id --- app/models/user_badge.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/user_badge.rb b/app/models/user_badge.rb index 5c6894d4d9..b176f27b9d 100644 --- a/app/models/user_badge.rb +++ b/app/models/user_badge.rb @@ -12,10 +12,12 @@ class UserBadge < ActiveRecord::Base after_create do Badge.increment_counter 'grant_count', self.badge_id + DiscourseEvent.trigger(:user_badge_granted, badge_id, user_id) end after_destroy do Badge.decrement_counter 'grant_count', self.badge_id + DiscourseEvent.trigger(:user_badge_removed, badge_id, user_id) end end From bee3bbdc05ed87b2226dc9a5b293bcf5789e8f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sigur=C3=B0ur=20Gu=C3=B0brandsson?= Date: Thu, 26 Feb 2015 00:50:58 +0000 Subject: [PATCH 0004/1435] FIX: the badge triggers broke Needed to add self. for the badge trigger variables, otherwise it breaks everything ;) --- app/models/user_badge.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/user_badge.rb b/app/models/user_badge.rb index b176f27b9d..f78ed824bb 100644 --- a/app/models/user_badge.rb +++ b/app/models/user_badge.rb @@ -12,12 +12,12 @@ class UserBadge < ActiveRecord::Base after_create do Badge.increment_counter 'grant_count', self.badge_id - DiscourseEvent.trigger(:user_badge_granted, badge_id, user_id) + DiscourseEvent.trigger(:user_badge_granted, self.badge_id, self.user_id) end after_destroy do Badge.decrement_counter 'grant_count', self.badge_id - DiscourseEvent.trigger(:user_badge_removed, badge_id, user_id) + DiscourseEvent.trigger(:user_badge_removed, self.badge_id, self.user_id) end end From 73068d5fa3ba39736d0591c8e92c25dc49545a1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sigur=C3=B0ur=20Gu=C3=B0brandsson?= Date: Thu, 26 Feb 2015 00:55:17 +0000 Subject: [PATCH 0005/1435] ADD: Spec tests for User Badge triggers NOTE: The DiscourseEvent trigger mechanism is VERY weird. If there are ANY triggers triggered in the chain, you can't only list the one you're looking for, you have to list all triggers in the order they will come. Example: line 98-100 :user_created and :user_verified are triggers that are introduced in PR #3237 so if this PR is accepted but not PR #3237 then lines 98-99 need to be removed. --- .../user_badges_controller_spec.rb | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/spec/controllers/user_badges_controller_spec.rb b/spec/controllers/user_badges_controller_spec.rb index 1bfb3ad31e..84225b39c6 100644 --- a/spec/controllers/user_badges_controller_spec.rb +++ b/spec/controllers/user_badges_controller_spec.rb @@ -5,7 +5,7 @@ describe UserBadgesController do let(:badge) { Fabricate(:badge) } context 'index' do - it 'doest not leak private info' do + it 'does not leak private info' do badge = Fabricate(:badge, target_posts: true, show_posts: false) p = create_post UserBadge.create(badge: badge, user: user, post_id: p.id, granted_by_id: -1, granted_at: Time.now) @@ -63,23 +63,13 @@ describe UserBadgesController do it 'grants badges from staff' do admin = Fabricate(:admin) - post = create_post - log_in_user admin - StaffActionLogger.any_instance.expects(:log_badge_grant).once - - xhr :post, :create, badge_id: badge.id, - username: user.username, - reason: Discourse.base_url + post.url - + xhr :post, :create, badge_id: badge.id, username: user.username expect(response.status).to eq(200) - user_badge = UserBadge.find_by(user: user, badge: badge) - expect(user_badge).to be_present expect(user_badge.granted_by).to eq(admin) - expect(user_badge.post_id).to eq(post.id) end it 'does not grant badges from regular api calls' do @@ -97,6 +87,19 @@ describe UserBadgesController do expect(user_badge).to be_present expect(user_badge.granted_by).to eq(Discourse.system_user) end + + it 'will trigger :user_badge_granted' do + log_in :admin + + # Make sure our extensibility points are triggered + # Stupid DiscourseEvent.clear doesn't work properly .. requires you to list ALL triggers in the chain! + # If there are future triggers added in the user creation chain, they need to be added anywhere you create a user and monitor ANY trigger. + # Perhaps DiscourseEvent needs a little fix so it doesn't break everything once you add a trigger in the chain. + DiscourseEvent.expects(:trigger).with(:user_created, anything).once + DiscourseEvent.expects(:trigger).with(:user_verified, anything).once + DiscourseEvent.expects(:trigger).with(:user_badge_granted, anything, anything).once + xhr :post, :create, badge_id: badge.id, username: user.username + end end context 'destroy' do @@ -114,5 +117,11 @@ describe UserBadgesController do expect(response.status).to eq(200) expect(UserBadge.find_by(id: user_badge.id)).to eq(nil) end + + it 'will trigger :user_badge_removed' do + log_in :admin + DiscourseEvent.expects(:trigger).with(:user_badge_removed, anything, anything).once + xhr :delete, :destroy, id: user_badge.id + end end end From 83f719fb809b37500ac968a9782b116abc56e715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sigur=C3=B0ur=20Gu=C3=B0brandsson?= Date: Thu, 26 Feb 2015 01:24:21 +0000 Subject: [PATCH 0006/1435] FIX: Cleaned the commit Only changing the code I changed, not other tests. --- spec/controllers/user_badges_controller_spec.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/spec/controllers/user_badges_controller_spec.rb b/spec/controllers/user_badges_controller_spec.rb index 84225b39c6..ac650b20fb 100644 --- a/spec/controllers/user_badges_controller_spec.rb +++ b/spec/controllers/user_badges_controller_spec.rb @@ -63,13 +63,23 @@ describe UserBadgesController do it 'grants badges from staff' do admin = Fabricate(:admin) + post = create_post + log_in_user admin + StaffActionLogger.any_instance.expects(:log_badge_grant).once - xhr :post, :create, badge_id: badge.id, username: user.username + + xhr :post, :create, badge_id: badge.id, + username: user.username, + reason: Discourse.base_url + post.url + expect(response.status).to eq(200) + user_badge = UserBadge.find_by(user: user, badge: badge) + expect(user_badge).to be_present expect(user_badge.granted_by).to eq(admin) + expect(user_badge.post_id).to eq(post.id) end it 'does not grant badges from regular api calls' do From 1e53c179a3c910f733a35cbd4f85fb3ceaa78bfa Mon Sep 17 00:00:00 2001 From: riking Date: Sat, 16 May 2015 18:15:42 -0700 Subject: [PATCH 0007/1435] FEATURE: Export customizations as JSON files --- .../admin/models/site_customization.js | 6 ++++- .../admin/templates/customize_css_html.hbs | 1 + .../stylesheets/common/admin/admin_base.scss | 3 +++ .../admin/site_customizations_controller.rb | 22 +++++++++++++++++++ .../site_customization_serializer.rb | 7 ++++++ config/locales/client.en.yml | 2 ++ 6 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 app/serializers/site_customization_serializer.rb diff --git a/app/assets/javascripts/admin/models/site_customization.js b/app/assets/javascripts/admin/models/site_customization.js index 27b6ddbdd9..4d9ca8c7bf 100644 --- a/app/assets/javascripts/admin/models/site_customization.js +++ b/app/assets/javascripts/admin/models/site_customization.js @@ -84,7 +84,11 @@ Discourse.SiteCustomization = Discourse.Model.extend({ destroy: function() { if (!this.id) return; return Discourse.ajax("/admin/site_customizations/" + this.id, { type: 'DELETE' }); - } + }, + + download_url: function() { + return Discourse.getURL('/admin/site_customizations/' + this.id); + }.property('id') }); var SiteCustomizations = Ember.ArrayProxy.extend({ diff --git a/app/assets/javascripts/admin/templates/customize_css_html.hbs b/app/assets/javascripts/admin/templates/customize_css_html.hbs index 004318e1eb..8b9fff5fcf 100644 --- a/app/assets/javascripts/admin/templates/customize_css_html.hbs +++ b/app/assets/javascripts/admin/templates/customize_css_html.hbs @@ -14,6 +14,7 @@
{{text-field class="style-name" value=selectedItem.name}} + {{fa-icon "download"}} {{i18n 'admin.export_json.button_text'}}
{{#if selectedItem}} diff --git a/app/assets/javascripts/discourse/components/json-file-uploader.js.es6 b/app/assets/javascripts/discourse/components/json-file-uploader.js.es6 new file mode 100644 index 0000000000..954d5f8d2a --- /dev/null +++ b/app/assets/javascripts/discourse/components/json-file-uploader.js.es6 @@ -0,0 +1,76 @@ + +export default Em.Component.extend({ + fileInput: null, + loading: false, + expectedRootObjectName: null, + + classNames: ['json-uploader'], + + _initialize: function() { + const $this = this.$(); + const self = this; + + const $fileInput = $this.find('#js-file-input'); + this.set('fileInput', $fileInput[0]); + + $fileInput.on('change', function() { + self.fileSelected(this.files); + }); + + const $fileSelect = $this.find('.fileSelect'); + + $fileSelect.on('dragover dragenter', function(e) { + if (e.preventDefault) e.preventDefault(); + return false; + }); + $fileSelect.on('drop', function(e) { + if (e.preventDefault) e.preventDefault(); + + self.fileSelected(e.dataTransfer.files); + return false; + }); + + }.on('didInsertElement'), + + setReady: function() { + let parsed; + try { + parsed = JSON.parse(this.get('value')); + } catch (e) { + this.set('ready', false); + return; + } + + const rootObject = parsed[this.get('expectedRootObjectName')]; + + if (rootObject !== null && rootObject !== undefined) { + this.set('ready', true); + } else { + this.set('ready', false); + } + }.observes('destination', 'expectedRootObjectName'), + + actions: { + selectFile: function() { + const $fileInput = $(this.get('fileInput')); + $fileInput.click(); + } + }, + + fileSelected(fileList) { + const self = this; + const numFiles = fileList.length; + const firstFile = fileList[0]; + + this.set('loading', true); + + let reader = new FileReader(); + reader.onload = function(evt) { + self.set('value', evt.target.result); + self.set('loading', false); + }; + + reader.readAsText(firstFile); + } + +}); diff --git a/app/assets/javascripts/discourse/controllers/upload-customization.js.es6 b/app/assets/javascripts/discourse/controllers/upload-customization.js.es6 new file mode 100644 index 0000000000..09158f5375 --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/upload-customization.js.es6 @@ -0,0 +1,52 @@ +import ModalFunctionality from 'discourse/mixins/modal-functionality'; + +export default Ember.Controller.extend(ModalFunctionality, { + notReady: Em.computed.not('ready'), + + needs: ['admin-customize-css-html'], + + title: "hi", + + ready: function() { + let parsed; + try { + parsed = JSON.parse(this.get('customizationFile')); + } catch (e) { + return false; + } + + return !!parsed["site_customization"]; + }.property('customizationFile'), + + actions: { + createCustomization: function() { + const self = this; + const object = JSON.parse(this.get('customizationFile')).site_customization; + + // Slight fixup before creating object + object.enabled = false; + delete object.id; + delete object.key; + + const customization = Discourse.SiteCustomization.create(object); + + this.set('loading', true); + customization.save().then(function(customization) { + self.send('closeModal'); + self.set('loading', false); + + const parentController = self.get('controllers.admin-customize-css-html'); + parentController.pushObject(customization); + parentController.set('selectedItem', customization); + }).catch(function(xhr) { + self.set('loading', false); + if (xhr.responseJSON) { + bootbox.alert(xhr.responseJSON.errors.join("
")); + } else { + bootbox.alert(I18n.t('generic_error')); + } + }); + } + } + +}); diff --git a/app/assets/javascripts/discourse/templates/components/json-file-uploader.hbs b/app/assets/javascripts/discourse/templates/components/json-file-uploader.hbs new file mode 100644 index 0000000000..655e3f7340 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/json-file-uploader.hbs @@ -0,0 +1,10 @@ +
+ + {{d-button class="fileSelect" action="selectFile" class="" icon="upload" label="upload_selector.select_file"}} + {{conditional-loading-spinner condition=loading size="small"}} +
+
{{i18n "alternation"}}
+
+ {{textarea value=value}} +
+ diff --git a/app/assets/javascripts/discourse/templates/modal/upload-customization.hbs b/app/assets/javascripts/discourse/templates/modal/upload-customization.hbs new file mode 100644 index 0000000000..8cc7851b3f --- /dev/null +++ b/app/assets/javascripts/discourse/templates/modal/upload-customization.hbs @@ -0,0 +1,8 @@ +
+ + +
diff --git a/app/assets/javascripts/discourse/views/upload-customization.js.es6 b/app/assets/javascripts/discourse/views/upload-customization.js.es6 new file mode 100644 index 0000000000..c6e336157c --- /dev/null +++ b/app/assets/javascripts/discourse/views/upload-customization.js.es6 @@ -0,0 +1,6 @@ +import ModalBodyView from "discourse/views/modal-body"; + +export default ModalBodyView.extend({ + templateName: 'modal/upload-customization', + title: I18n.t('admin.customize.import_title') +}); diff --git a/app/assets/stylesheets/common/base/modal.scss b/app/assets/stylesheets/common/base/modal.scss index d5aa63a9b3..1115a89acc 100644 --- a/app/assets/stylesheets/common/base/modal.scss +++ b/app/assets/stylesheets/common/base/modal.scss @@ -138,6 +138,29 @@ .raw-email-textarea { height: 300px; } + .json-uploader { + display: table-row; + .jsfu-file { + display: table-cell; + vertical-align: middle; + min-width: 120px; + } + .jsfu-separator { + vertical-align: middle; + display: table-cell; + font-size: 36px; + padding-left: 10px; + padding-right: 10px; + } + .jsfu-paste { + display: table-cell; + width: 100%; + textarea { + margin-bottom: 0; + margin-top: 4px; + } + } + } } .password-confirmation { display: none; diff --git a/app/serializers/site_customization_serializer.rb b/app/serializers/site_customization_serializer.rb index 7d52423559..1c8ff8f9da 100644 --- a/app/serializers/site_customization_serializer.rb +++ b/app/serializers/site_customization_serializer.rb @@ -1,6 +1,6 @@ class SiteCustomizationSerializer < ApplicationSerializer - attributes :name, :enabled, :created_at, :updated_at, + attributes :id, :name, :key, :enabled, :created_at, :updated_at, :stylesheet, :header, :footer, :top, :mobile_stylesheet, :mobile_header, :mobile_footer, :mobile_top, :head_tag, :body_tag diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 9286504b2f..064a928f6a 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -143,6 +143,7 @@ en: every_two_weeks: "every two weeks" every_three_days: "every three days" max_of_count: "max of {{count}}" + alternation: "or" character_count: one: "{{count}} character" other: "{{count}} characters" @@ -849,6 +850,7 @@ en: hint: "(you can also drag & drop into the editor to upload them)" hint_for_supported_browsers: "(you can also drag and drop or paste images into the editor to upload them)" uploading: "Uploading" + select_file: "Select File" image_link: "link your image will point to" search: @@ -1894,6 +1896,8 @@ en: save: "Save" new: "New" new_style: "New Style" + import: "Import" + import_title: "Select a file or paste text" delete: "Delete" delete_confirm: "Delete this customization?" about: "Modify CSS stylesheets and HTML headers on the site. Add a customization to start." From fbc06d044ff40774620d15460e518434db847e70 Mon Sep 17 00:00:00 2001 From: riking Date: Sat, 16 May 2015 20:33:31 -0700 Subject: [PATCH 0009/1435] Use .dcstylejson instead of .dcstyle.json --- .../discourse/components/json-file-uploader.js.es6 | 4 ++++ .../discourse/templates/components/json-file-uploader.hbs | 2 +- .../discourse/templates/modal/upload-customization.hbs | 2 +- app/controllers/admin/site_customizations_controller.rb | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/components/json-file-uploader.js.es6 b/app/assets/javascripts/discourse/components/json-file-uploader.js.es6 index 954d5f8d2a..250260dc48 100644 --- a/app/assets/javascripts/discourse/components/json-file-uploader.js.es6 +++ b/app/assets/javascripts/discourse/components/json-file-uploader.js.es6 @@ -32,6 +32,10 @@ export default Em.Component.extend({ }.on('didInsertElement'), + accept: function() { + return ".json,application/json" + (this.get('extension') ? "," + this.get('extension') : ""); + }.property('extension'), + setReady: function() { let parsed; try { diff --git a/app/assets/javascripts/discourse/templates/components/json-file-uploader.hbs b/app/assets/javascripts/discourse/templates/components/json-file-uploader.hbs index 655e3f7340..fba3461c54 100644 --- a/app/assets/javascripts/discourse/templates/components/json-file-uploader.hbs +++ b/app/assets/javascripts/discourse/templates/components/json-file-uploader.hbs @@ -1,5 +1,5 @@
- + {{d-button class="fileSelect" action="selectFile" class="" icon="upload" label="upload_selector.select_file"}} {{conditional-loading-spinner condition=loading size="small"}}
diff --git a/app/assets/javascripts/discourse/templates/modal/upload-customization.hbs b/app/assets/javascripts/discourse/templates/modal/upload-customization.hbs index 8cc7851b3f..3e49a755e5 100644 --- a/app/assets/javascripts/discourse/templates/modal/upload-customization.hbs +++ b/app/assets/javascripts/discourse/templates/modal/upload-customization.hbs @@ -1,6 +1,6 @@
diff --git a/app/models/topic.rb b/app/models/topic.rb index d9ebbd3af1..6f444e1ab4 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -662,7 +662,8 @@ class Topic < ActiveRecord::Base { html: post.cooked, - key: self.id + key: self.id, + url: self.url } end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index f4a746ba60..8e1e1b142e 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -218,6 +218,7 @@ en: banner: close: "Dismiss this banner." + edit: "Edit this banner >>" choose_topic: none_found: "No topics found." From 9049f31456186b34b099fce9829684af704402d9 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Wed, 10 Jun 2015 00:52:11 +0530 Subject: [PATCH 0039/1435] add posts.rss rel alternate --- app/views/list/list.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/list/list.erb b/app/views/list/list.erb index 7382cea1c0..dd75051b40 100644 --- a/app/views/list/list.erb +++ b/app/views/list/list.erb @@ -41,6 +41,7 @@ <% if @rss %> <% content_for :head do %> + <%= auto_discovery_link_tag(:rss, "#{Discourse.base_url}/posts.rss", title: I18n.t("rss_description.posts")) %> <%= auto_discovery_link_tag(:rss, { action: "#{@rss}_feed" }, title: I18n.t("rss_description.#{@rss}")) %> <% end %> <% end %> From 79027c2775ede0ae2377c86a8bc2d6c282db3244 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Wed, 10 Jun 2015 06:05:28 +1000 Subject: [PATCH 0040/1435] EXTENSIBILITY: add category-custom-settings outlet --- .../discourse/templates/modal/edit-category-settings.hbs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/discourse/templates/modal/edit-category-settings.hbs b/app/assets/javascripts/discourse/templates/modal/edit-category-settings.hbs index 2b8479e2bd..b6b08f881f 100644 --- a/app/assets/javascripts/discourse/templates/modal/edit-category-settings.hbs +++ b/app/assets/javascripts/discourse/templates/modal/edit-category-settings.hbs @@ -43,3 +43,5 @@ {{i18n 'category.position_disabled_click'}} {{/if}} + +{{plugin-outlet "category-custom-settings"}} From e3fa27a01cd591ea202ff805d79c2b6db6505d22 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Wed, 10 Jun 2015 06:07:37 +1000 Subject: [PATCH 0041/1435] FEATURE: serialize and update category custom_fields - send to client - update from client --- app/assets/javascripts/discourse/models/category.js | 3 ++- app/controllers/categories_controller.rb | 1 + app/serializers/category_serializer.rb | 3 ++- spec/controllers/categories_controller_spec.rb | 5 +++++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/models/category.js b/app/assets/javascripts/discourse/models/category.js index c354f0d48b..4f8ede69e7 100644 --- a/app/assets/javascripts/discourse/models/category.js +++ b/app/assets/javascripts/discourse/models/category.js @@ -75,7 +75,8 @@ Discourse.Category = Discourse.Model.extend({ parent_category_id: this.get('parent_category_id'), logo_url: this.get('logo_url'), background_url: this.get('background_url'), - allow_badges: this.get('allow_badges') + allow_badges: this.get('allow_badges'), + custom_fields: this.get('custom_fields') }, type: this.get('id') ? 'PUT' : 'POST' }); diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index 8199472107..c00b67876d 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -146,6 +146,7 @@ class CategoriesController < ApplicationController :background_url, :allow_badges, :slug, + :custom_fields => [params[:custom_fields].try(:keys)], :permissions => [*p.try(:keys)]) end end diff --git a/app/serializers/category_serializer.rb b/app/serializers/category_serializer.rb index 8242a57ff7..1757652094 100644 --- a/app/serializers/category_serializer.rb +++ b/app/serializers/category_serializer.rb @@ -10,7 +10,8 @@ class CategorySerializer < BasicCategorySerializer :email_in_allow_strangers, :can_delete, :cannot_delete_reason, - :allow_badges + :allow_badges, + :custom_fields def group_permissions @group_permissions ||= begin diff --git a/spec/controllers/categories_controller_spec.rb b/spec/controllers/categories_controller_spec.rb index 3aa0d31e76..e838b2e971 100644 --- a/spec/controllers/categories_controller_spec.rb +++ b/spec/controllers/categories_controller_spec.rb @@ -162,8 +162,12 @@ describe CategoriesController do permissions: { "everyone" => readonly, "staff" => create_post + }, + custom_fields: { + "dancing" => "frogs" } + expect(response.status).to eq(200) @category.reload expect(@category.category_groups.map{|g| [g.group_id, g.permission_type]}.sort).to eq([ @@ -173,6 +177,7 @@ describe CategoriesController do expect(@category.slug).to eq("hello-category") expect(@category.color).to eq("ff0") expect(@category.auto_close_hours).to eq(72) + expect(@category.custom_fields).to eq({"dancing" => "frogs"}) end end end From 49ca2481866906b38ce093a436b8085669f85855 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Wed, 10 Jun 2015 06:08:06 +1000 Subject: [PATCH 0042/1435] FEATURE: allow distributed cache to handle Set as value --- lib/distributed_cache.rb | 7 ++++-- spec/components/distributed_cache_spec.rb | 28 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/distributed_cache.rb b/lib/distributed_cache.rb index 777c9b780e..58974b77ad 100644 --- a/lib/distributed_cache.rb +++ b/lib/distributed_cache.rb @@ -31,7 +31,7 @@ class DistributedCache hash = current.hash(message.site_id) case payload["op"] - when "set" then hash[payload["key"]] = payload["value"] + when "set" then hash[payload["key"]] = payload["marshalled"] ? Marshal.load(payload["value"]) : payload["value"] when "delete" then hash.delete(payload["key"]) when "clear" then hash.clear end @@ -69,7 +69,10 @@ class DistributedCache end def self.set(hash, key, value) - publish(hash, { op: :set, key: key, value: value }) + # special support for set + marshal = Set === value + value = Marshal.dump(value) if marshal + publish(hash, { op: :set, key: key, value: value, marshalled: marshal }) end def self.delete(hash, key) diff --git a/spec/components/distributed_cache_spec.rb b/spec/components/distributed_cache_spec.rb index 68a0c259a4..a26f6fbbd3 100644 --- a/spec/components/distributed_cache_spec.rb +++ b/spec/components/distributed_cache_spec.rb @@ -11,6 +11,34 @@ describe DistributedCache do DistributedCache.new("test") end + it 'allows us to store Set' do + c1 = DistributedCache.new("test1") + c2 = DistributedCache.new("test1") + + set = {a: 1, b: 1} + set = Set.new + set << 1 + set << "b" + + c1["cats"] = set + + wait_for do + c2["cats"] == set + end + + expect(c2["cats"]).to eq(set) + + set << 5 + + c2["cats"] == set + + wait_for do + c1["cats"] == set + end + + expect(c1["cats"]).to eq(set) + end + it 'does not leak state across caches' do c2 = DistributedCache.new("test1") c3 = DistributedCache.new("test1") From ae277e28a64840433141f8085ab9a080ebce5ef2 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 9 Jun 2015 16:24:04 -0400 Subject: [PATCH 0043/1435] FEATURE: Allow embedding topics without creating them, by id --- app/controllers/embed_controller.rb | 15 ++++-- public/javascripts/embed.js | 57 +++++++++++++++-------- spec/controllers/embed_controller_spec.rb | 14 ++++++ 3 files changed, 61 insertions(+), 25 deletions(-) diff --git a/app/controllers/embed_controller.rb b/app/controllers/embed_controller.rb index 198288297a..f296998ef0 100644 --- a/app/controllers/embed_controller.rb +++ b/app/controllers/embed_controller.rb @@ -1,13 +1,18 @@ class EmbedController < ApplicationController skip_before_filter :check_xhr, :preload_json, :verify_authenticity_token - before_filter :ensure_embeddable layout 'embed' def comments - embed_url = params.require(:embed_url) - topic_id = TopicEmbed.topic_id_for_embed(embed_url) + embed_url = params[:embed_url] + + topic_id = nil + if embed_url.present? + topic_id = TopicEmbed.topic_id_for_embed(embed_url) + else + topic_id = params[:topic_id].to_i + end if topic_id @topic_view = TopicView.new(topic_id, @@ -21,7 +26,8 @@ class EmbedController < ApplicationController if @topic_view && @topic_view.posts.size == SiteSetting.embed_post_limit @posts_left = @topic_view.topic.posts_count - SiteSetting.embed_post_limit - 1 end - else + + elsif embed_url.present? Jobs.enqueue(:retrieve_topic, user_id: current_user.try(:id), embed_url: embed_url) render 'loading' end @@ -30,7 +36,6 @@ class EmbedController < ApplicationController end def count - embed_urls = params[:embed_url] by_url = {} diff --git a/public/javascripts/embed.js b/public/javascripts/embed.js index e299ae5476..33c11d337a 100644 --- a/public/javascripts/embed.js +++ b/public/javascripts/embed.js @@ -1,24 +1,41 @@ -/* global discourseUrl */ -/* global discourseUserName */ -/* global discourseEmbedUrl */ (function() { - var comments = document.getElementById('discourse-comments'), - iframe = document.createElement('iframe'); - if (typeof discourseUserName === 'undefined') { - iframe.src = - [ discourseUrl, - 'embed/comments?embed_url=', - encodeURIComponent(discourseEmbedUrl) - ].join(''); - } else { - iframe.src = - [ discourseUrl, - 'embed/comments?embed_url=', - encodeURIComponent(discourseEmbedUrl), - '&discourse_username=', - discourseUserName - ].join(''); + + var DE = window.DiscourseEmbed || {}; + var comments = document.getElementById('discourse-comments'); + var iframe = document.createElement('iframe'); + + ['discourseUrl', 'discourseEmbedUrl', 'discourseUserName'].forEach(function(i) { + if (window[i]) { DE[i] = DE[i] || window[i]; } + }); + + var queryParams = {}; + + if (DE.discourseEmbedUrl) { + queryParams.embed_url = encodeURIComponent(DE.discourseEmbedUrl); } + + if (DE.discourseUserName) { + queryParams.discourse_username = DE.discourseUserName; + } + + if (DE.topicId) { + queryParams.topic_id = DE.topicId; + } + + var src = DE.discourseUrl + 'embed/comments'; + var keys = Object.keys(queryParams); + if (keys.length > 0) { + src += "?"; + + for (var i=0; i 0) { src += "&"; } + + var k = keys[i]; + src += k + "=" + queryParams[k]; + } + } + + iframe.src = src; iframe.id = 'discourse-embed-frame'; iframe.width = "100%"; iframe.frameBorder = "0"; @@ -48,7 +65,7 @@ function postMessageReceived(e) { if (!e) { return; } - if (discourseUrl.indexOf(e.origin) === -1) { return; } + if (DE.discourseUrl.indexOf(e.origin) === -1) { return; } if (e.data) { if (e.data.type === 'discourse-resize' && e.data.height) { diff --git a/spec/controllers/embed_controller_spec.rb b/spec/controllers/embed_controller_spec.rb index 514d84f57c..4fc2319d62 100644 --- a/spec/controllers/embed_controller_spec.rb +++ b/spec/controllers/embed_controller_spec.rb @@ -16,6 +16,20 @@ describe EmbedController do expect(response).not_to be_success end + context "by topic id" do + + before do + SiteSetting.embeddable_hosts = host + controller.request.stubs(:referer).returns('http://eviltrout.com/some-page') + end + + it "allows a topic to be embedded by id" do + topic = Fabricate(:topic) + get :comments, topic_id: topic.id + expect(response).to be_success + end + end + context "with a host" do before do SiteSetting.embeddable_hosts = host From 195cdaec11b143d3d9e98cc2f813038359fd978d Mon Sep 17 00:00:00 2001 From: dfabulich Date: Tue, 9 Jun 2015 13:34:20 -0700 Subject: [PATCH 0044/1435] Convert author tag to dc:creator RSS spec says the author tag should be an email address, forcing us to put in a junk no-reply@example.com email. Instead, we should use dc:creator, which allows us to use any name we want for the user. --- app/views/list/list.rss.erb | 4 ++-- app/views/posts/latest.rss.erb | 4 ++-- app/views/topics/show.rss.erb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/views/list/list.rss.erb b/app/views/list/list.rss.erb index deb5e80f02..b1eb41f103 100644 --- a/app/views/list/list.rss.erb +++ b/app/views/list/list.rss.erb @@ -1,5 +1,5 @@ - + <% lang = SiteSetting.find_by_name('default_locale').try(:value) %> <% site_email = SiteSetting.find_by_name('contact_email').try(:value) %> @@ -14,7 +14,7 @@ <% topic_url = topic.url -%> <%= topic.title %> - <%= "no-reply@example.com (@#{topic.user.username}#{" #{topic.user.name}" if (topic.user.name.present? && SiteSetting.enable_names?)})" -%> + ]]> <%= topic.category.name %> <%= t('author_wrote', author: link_to("@#{topic.user.username}", "#{Discourse.base_url}/users/#{topic.user.username_lower}")).html_safe %>

diff --git a/app/views/posts/latest.rss.erb b/app/views/posts/latest.rss.erb index 04aeb02787..7cfb0a8e1d 100644 --- a/app/views/posts/latest.rss.erb +++ b/app/views/posts/latest.rss.erb @@ -1,5 +1,5 @@ - + <% lang = SiteSetting.find_by_name('default_locale').try(:value) %> <% site_email = SiteSetting.find_by_name('contact_email').try(:value) %> @@ -10,7 +10,7 @@ <% next unless post.user %> <%= post.topic.title %> - <%= "no-reply@example.com (@#{post.user.username}#{" #{post.user.name}" if (post.user.name.present? && SiteSetting.enable_names?)})" -%> + ]]> ]]> <%= Discourse.base_url + post.url %> <%= post.created_at.rfc2822 %> diff --git a/app/views/topics/show.rss.erb b/app/views/topics/show.rss.erb index 03b5471ecb..e49890e2d0 100644 --- a/app/views/topics/show.rss.erb +++ b/app/views/topics/show.rss.erb @@ -1,5 +1,5 @@ - + <% topic_url = @topic_view.absolute_url %> <% lang = SiteSetting.find_by_name('default_locale').try(:value) %> @@ -15,7 +15,7 @@ <% next unless post.user %> <%= @topic_view.title %> - <%= "no-reply@example.com (@#{post.user.username}#{" #{post.user.name}" if (post.user.name.present? && SiteSetting.enable_names?)})" -%> + ]]>

<%= t('author_wrote', author: link_to("@#{post.user.username}", user_url(post.user.username_lower))).html_safe %>

From d127e1179f39b977d6156b471208113170b19c12 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Wed, 10 Jun 2015 14:39:29 +0800 Subject: [PATCH 0045/1435] FIX: Incorrect check when no text is selected. --- .../javascripts/discourse/controllers/quote-button.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/controllers/quote-button.js.es6 b/app/assets/javascripts/discourse/controllers/quote-button.js.es6 index 0a4c0e3ada..c99f57247d 100644 --- a/app/assets/javascripts/discourse/controllers/quote-button.js.es6 +++ b/app/assets/javascripts/discourse/controllers/quote-button.js.es6 @@ -27,7 +27,7 @@ export default DiscourseController.extend({ const selection = window.getSelection(); // no selections - if (selection.rangeCount === 0) return; + if (selection.isCollapsed) return; // retrieve the selected range const range = selection.getRangeAt(0), From bf8c9c34110fdea5724067c4abc22e21b9f0235e Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Wed, 10 Jun 2015 18:30:29 +1000 Subject: [PATCH 0046/1435] FEATURE: ship user_id with topic serializer --- app/serializers/topic_view_serializer.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/serializers/topic_view_serializer.rb b/app/serializers/topic_view_serializer.rb index c2d98f077b..1fd56f54a1 100644 --- a/app/serializers/topic_view_serializer.rb +++ b/app/serializers/topic_view_serializer.rb @@ -32,7 +32,8 @@ class TopicViewSerializer < ApplicationSerializer :category_id, :word_count, :deleted_at, - :pending_posts_count + :pending_posts_count, + :user_id attributes :draft, :draft_key, From c5b6ace07bd1c5195c2e34f2fda1ebcabff7abb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 10 Jun 2015 10:32:02 +0200 Subject: [PATCH 0047/1435] update onebox to latest --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index e7b6d30551..e50f2484f3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -208,7 +208,7 @@ GEM omniauth-twitter (1.0.1) multi_json (~> 1.3) omniauth-oauth (~> 1.0) - onebox (1.5.19) + onebox (1.5.20) moneta (~> 0.7) multi_json (~> 1.7) mustache (~> 0.99) From effe83d7a961b2b0334d9aec1905559c8015c01b Mon Sep 17 00:00:00 2001 From: Noam Yorav-Raphael Date: Wed, 10 Jun 2015 11:47:07 +0300 Subject: [PATCH 0048/1435] Don't limit @mention autocomplete to latin characters The userSearch() function, used for @mention autocomplete, returned an empty list if the query string included non-latin characters or spaces. This removes this restriction, so you can search users by any characters in their display name, including spaces. --- app/assets/javascripts/discourse/lib/user-search.js.es6 | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/user-search.js.es6 b/app/assets/javascripts/discourse/lib/user-search.js.es6 index 790a00ff09..068282c75b 100644 --- a/app/assets/javascripts/discourse/lib/user-search.js.es6 +++ b/app/assets/javascripts/discourse/lib/user-search.js.es6 @@ -88,11 +88,6 @@ export default function userSearch(options) { currentTerm = term; return new Ember.RSVP.Promise(function(resolve) { - // TODO site setting for allowed regex in username - if (term.match(/[^a-zA-Z0-9_\.]/)) { - resolve([]); - return; - } if (((new Date() - cacheTime) > 30000) || (cacheTopicId !== topicId)) { cache = {}; } From 677cdbbda0f83dbbd5ea4dfbfaff3895168efc07 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Wed, 10 Jun 2015 02:26:25 -0700 Subject: [PATCH 0049/1435] updated welcome usage tips and images --- config/locales/server.en.yml | 42 +++++++++--------- public/images/welcome/notification-panel.png | Bin 3885 -> 3729 bytes public/images/welcome/progress-bar.png | Bin 1528 -> 1083 bytes public/images/welcome/username-completion.png | Bin 15028 -> 15521 bytes 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index bb6ce41067..6ce907efab 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1458,43 +1458,43 @@ en: usage_tips: text_body_template: | - A few quick tips to get you started: + Here are a few quick tips to get you started: - ## Keep scrolling + ## Just Scroll Down - There are no next page buttons or page numbers – to read more, **just keep scrolling down!** + To read more, **just keep scrolling down!** As new posts or new topics arrive, they will appear automatically. - As new posts come in, they will appear automatically. + ## Navigation - ## Where am I? + - For search, your user page, or the menu, use the **icon buttons at upper right**. - - For search, your user page, or the menu, use the **icon buttons at the upper right**. + - Selecting a topic title will always take you to the next unread post in the topic. To enter at the top or bottom instead, select the date or post count. - - Any topic title will take you to the next unread post. Use the last activity time and post count to enter at the top or bottom. - - - While reading a topic, jump to the top ↑ by selecting the topic title. Select the green progress bar at the bottom right for full navigation controls, or use the home and end keys. + - While reading a topic, jump to the top ↑ by selecting the topic title. Select the progress bar at the bottom right for full navigation controls, or use the home and end keys. - ## How do I reply? + ## Replying - - To reply to the overall topic, use the Reply button at the very bottom of the page. + To reply … - - To reply to a specific post, use the Reply button on that post. + - to the **topic in general**, use the Reply button at the very bottom of the topic. - - To take the conversation in a different direction, but keep them linked together, use Reply as linked Topic to the right of the post. + - to a **specific person**, use the Reply button on their post. - To quote someone in your reply, select the text you wish to quote, then press any Reply button. + - with **a linked topic**, use Reply as linked Topic to the right of the post. + + To quote, simply select the text you wish to quote, then press any Reply button. - To ping someone in your reply, mention their name. Type `@` and an autocompleter will pop up. + To ping someone in your reply, mention their name. Type `@` to begin selecting a name. - For [standard Emoji](http://www.emoji.codes/), just start typing `:` or the traditional smileys `:)` :smile: + For [standard Emoji](http://www.emoji.codes/), just type `:` or the traditional smileys `:)` :smile: - ## What else can I do? + ## Actions There are action buttons at the bottom of each post. @@ -1504,15 +1504,15 @@ en: You can also **share** a link to a post, or **bookmark** it for later reference on your user page. - ## Who is talking to me? + ## Notifications When someone replies to your post, quotes your post, or mentions your `@username`, a number will immediately appear at the top right of the page. Use it access your **notifications**. - Don't worry about missing a reply – you'll be emailed replies (and messages) if you aren't online when they arrive. + Don't worry about missing a reply – you'll be emailed notifications if you aren't online when they arrive. - ## When are conversations new? + ## Your Preferences By default all conversations less than two days old are considered new, and any conversation you've participated in (replied to, created, or read for an extended period) will automatically be tracked. @@ -1522,7 +1522,7 @@ en: You can change the individual notification state of a topic via the control at the bottom of the topic (this can also be set per category). To change how you track topics, or the definition of new, see [your user preferences](%{base_url}/my/preferences). - ## Why can't I do certain things? + ## Community Trust New users are somewhat limited for safety reasons. As you participate here, you'll gain the trust of the community, become a full citizen, and those limitations will automatically be removed. At a high enough [trust level](https://meta.discourse.org/t/what-do-user-trust-levels-do/4924), you'll gain even more abilities to help us manage our community together. diff --git a/public/images/welcome/notification-panel.png b/public/images/welcome/notification-panel.png index cd0550ec919a09d0ace1cb303454e42d2a6b623e..cbf6be4d8345740a2b93c373be0338a72af58f02 100644 GIT binary patch literal 3729 zcmV;C4sP*@P)+-y7@uKzJ9`h2(0Y{4^JvsLM=8PtJdG=4=oSb{^xmQ0! zk3Ja8&h{9C!5(9<#~2Lu7=ytc`$=S3N1{d4KaZNNsFgs23rMnr7Tt(MJ01-7*heDa z0(p6i{8ENaOVOvp=wk``M1oEaqcc+UwH)1?Ajci#4z(GBJ@)v~loMr7l3?w}A>-6A z;%G_ZD)R3roiW&BFNx~S zBay&Dp4_N|w-b=U<;%$~moeC5?FypGiEYPO1?lu64E8vg6-9=I3}dPSv32M>S*Ukl z*#~(&Ibg8I!i6wp@niK5R9P^Z+_BctK9XG~9CF9S>klKYd^z@-C zs!+Ov;7JgNwpk0G)q(Yg=O4F-FxvZKFA!*$k% zQSQQq$Ax2I-aMy7HV>j34E9)QLubNy48d6nUspiDKK7Cu-2;O?UR_S+O@Vw_VD^vkjstEE5p17sePV)k1^QeKK=5UVKS`8 z;#IWnM&E_=_-lDG>Iw|@SY%lGOzIEou_!W+|EEF@5270k_E>9~J1g^s^|-@{Tpn~j zTz$;eB99-@4F-D*+)}NFn#|NAryu$6j}s|~Q0=)kf<#FHgFPNGEPVR#68X#+D%7E( z`;n;*(R3r{T}k#Bv%0osoh8O5)MBZmUo_A=*e8~X<)aFt*<^RvNs`>P9uu>(+=-QN zU5oG?x1PDANsO6anD6TSp2f+#eDli1>zB3+_@`TUvKm_(wR)YO^zSy0feG{ce`VfS zB)&aK(q9>4JYLV}*l0m<0caXkfYANIfz`FuUF9*PWQk#ssxGz>XO(ofjWHxiPE1bZ zux^8t+m7p5H~NMB9*<|2cudiuUH)>!7i}hnUfZOTMD&7-V15-JM2t(^&dY_8diQt? zSy^gW{6_AH+~f3d-+~*_3C3VDKve+##WAjCq`|rna{O-an8HHL@-$9J%qK$~`X~P_ z^9rWzi_11dBN+9B8k__35)rMPt$x3Mmv~GiW~FJNX~xb_uR(hL`Uj_>dj8jl?^}h3 zGqKX9wZYO5UbETg^B6NWK6dHGWe_fL(bU!qS(yfp>CVjt>zv*?r?t*Qz#rhRR`8dC zMvpOCz4l7_RZuO_0WD`*JWf9G^_X-ym-W*cwQ7`NY<$8%EV+Fs0LI~Tq~FenP{a$@ zE}l=jP*}p@Rq;2XlE*A$!IKC*hLjJT5bSXjxL2(`&DCWkTy`Fd$z)J4Su8e(%ez}O zG;a3!5r!m5F25KIi`?U@=~oR#1F=yd67Z>+(!fKA2vxqBo$g$5?ih~=t8RKiCKh&g zwlxaw(cIS6Cmd3ZPn#@65{TvA>Ntu!lPrNUyhK&Lr!k0GiXYLf4<)iBjolzHmCH{W>m#IcuNcLJDR?0K2GMq+LYx;+`KaVKHKd*U)eG zqF7jWt*7UtM|3K=qzx+o!Z$ve1A!zcL^g2fxN*`31357%%kH_DDS6af&e^7BIiz_y-(OF`+ zEWL${IKZuFjj_KzG&~e-GEUFQ0Er>2R;x%XqVn;KZsx-Ei&3TIvWl{#cx)M}yYumD z#}4k_7cI{nfBQ10#b6~8?lI}rOFJIWlwHy#9@Wn-xja4ukiK=tl2)VWs4m`|5IOv& zIfwhv^SH8gF$RylK5sTNCn}H6U%OCUUro6?H9eJ?dyDcIKH1!Ys62)RAB-Q->M>z8 zcISU}^l)m*zHM^g(35}pwtC7!B-CTKb3ss+7Z4z?=&stb8nct)^ z(srw-cir4rS`a&pgW6ci3Y}gTZ8C;{$ssBqw{*08mv&w`s-QgX6ZS#L5tYhqUG21b zJaG5g%TGVHFE;k4{``xqI)jBsoX1wVAU|kvsbqS|AF4K-7d4gzO)iE1qy1v0w{n;;0v<19utJgKU$qEa$+Frey<%Hu3X7Dw8g_Se*2&5!^>Yj=~HO*_E+&c+**|r>Q zGKNphqz06Qo^e%O73DE}Dn{j$%90TYR2osKJfaw(!(;7mTs7 zHz+OAGSZ^*7>aBfJhqDXSB^do`o&Y^_b)Y@65z2-(Gl>txS`MKMiD>~(-IErzWrrw z^mLNFYleDmUhLHOO?gPPJ;YLRR31a#?dt8K@-7TvQcJpEC(dG~RIaS8jJEqmgU6QE zoO4g_i!b}%`h=rPfX7Z%XVByNKBp@(kEg@|kBfMn%a2&!ZTtP>p8lSwk}<5|^aed8 zYYD#uo@}n0l`18rxLhfZsEc9qOqzs&J-j@A%kruhg~zW~ zwg*}q#kJz~J+lwe*3}l3$DGn4s#BrUYhir`Pc}D|Aq`u9Sn6$*99cKu{m4&?(BLtl zXw3e_Q~Tqp@qv@MebxkcjNJN~g8K=X#T_y_B3S0F3G0Fli)zs}`s*~Jd-g_O!|Ub;riypH9?*m10SNR;rp0yKpaqfh};rdHHevI<#0 z3Pu4FI&stsFqTP)zH{mdz}%hrUi=yssmH!3GbC&u#9@{cZ_0Dk9iuBA-@``E940xx zAlPp%ZkGA>(%1CCS}k&q`BmkC(YD#S*)1b5Gei*9Y-V((y_o@tu+t2fRRtKBPgVPl(c12T<+40jX>oUl?%em~}i-kP3Wp5qJhRL2N2HfOQ zs%D@*9~OakS~^-IEG^(o`u3%#huhvg@m!25Kl$PbcFQP{;CVN>XABLQDCkJl1*5Vy6#nn_G;m2nO6P)(C2;ENe6wuKuvi@vz|XiUYZsS-^xRk&L|2 zJ%4PT8RLBO{-Fa=l@cijQxCm*>hhpkPtbQB#J?~jE9K;e9i(Tnpi{TG3@MgO&iVJu z{BcuTQ}_Y5w2SHqHI-$BL&6XfZSXuT^P0hApge|66X?f<_86u{X`B-ZV(H5H{BVSG zZwFFeJoefLpPw!5kR|1Oo@+@r&?zV`%-h;vW?p$sqex|$7;59>ZBn_gLNYby+XMR` z)EVip9_QxY38YvCm10A-=R^__CH=H^ntB-eMvEkz2eInL{`21Plpkl@gGZh}5;z@l z__^Qw@!bzEWfU}Z^p7Yf3}$N*&zX{bkHa>vRSnmb7Y8~MY))}Qw?t=MTv_*!2>HEk zy{Ivt0R*%1pn5i;S=qCytBHrmH9Zc}g?{LaEvvVK-0iUIJOSkkGdLT(S_pj_K{ot*4PW|)jm79#J=7DL0 zmF9a`=*r3-)3~_4`mUgT(Bg3IoqJf|K8h`kgvMMBD+eOuc4h<;2dk0nji0&=KX@bS z=1)C_-vSG=T3ZOf2~F@oe|@8#|8>n`GPVPE56^#%-WLJ0x)s%Z7P z5bHVYaq_{qJ?^FDW!T6VmPrSG_@4A(*|18j0@2sj)=&tr$GaB{_85abhW`Q<9tG@n vI|_oq9`7bF*kcR^dyK(gk1-hRF$VL0m~e7zV?m&F00000NkvXXu0mjfXck!j literal 3885 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 diff --git a/public/images/welcome/progress-bar.png b/public/images/welcome/progress-bar.png index 431d24b1c01db6fb2fbf27262d054b0b2954bb9e..aeff46a2f8ff580a764949dbe00fafe9df20795a 100644 GIT binary patch delta 1073 zcmXw(e=ys37{|YU%zE0c^P{s;v%B^dwY6>|-5o276?1x~hncfd*3#WJ*-cX|>bs`f z`lZ(d9a=M1;mW%6bAq~{OegV)uMZOhOPW*&X?>O;A*s*4-QAvh-TQf-_xsQ1?y>Cb z*-^ZYbRY=A;mS8I1YtKCdm=uZpa4P135q7DF@hQ=s7XRFK?oR~CQ2BTM46JPR=H8Y zO(1s@OnTm$cB@tswHg)$4l@YkuM5Ul)MTJQ9z=nl>SSgY+#_p z&2n)wEor00WY@T)V@%RD&R1CZ3M^FM!WnDf92Tl@#h=UN3S-4Qu2NacwYW-;CDLip z+*YSzRiwj>dMrY?MTaE@EJj#@@ZivdY+zg_og7rC24|Eq#Wa*oLNEpCDag!oN;o$Q zRkKh#5A~`+gk=ar9jS%dWvJIc!!k4=h|~~iAksp#2+cy<*TSCMJeWX1+9#te&QlhKSZHW@LtGCfXKcb_2aeWQ+Z;xDEcM(uVJ z#4h_^%v>baeH}*HKfSkmWBqwe6kKMr_lBpvy6xH@(Ry&hvrgHPBZ_$YTlY5=Dd|F& z*#|#3pBuK%d)7I6aij9`J2M_$*Nzyc*RS!sR()4_hFS-59DV1$<-shr zXBc}{#Q)o;>4@6j{KoB?o*wU5+j;gt-co{BY(ekoHIGDxkks|`0Kd31G|ru|>uY|g z;p9m6dCu>$1Gy58{|ocgI=2Zz=d?L<;X7f;F|4f|%VOD-dAyKRcFRdqw)AYB&lGa0IRU7=^M}nHKUVJ65&THEJT~mMy_%I1|Lnli|S7k4W zx=-aFmD>*V?{Cw#*Q`Iac#q|W{FG6^dbxdp*_$j^tq%VguAh8CXOhu}{QATXV@5t} ib=>^_VmnhUq@APd_6_~u=SP|6KtzSd9B&Fc&HoqOD}_Y> delta 1521 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 literal 15028 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 Date: Wed, 10 Jun 2015 18:56:44 +1000 Subject: [PATCH 0050/1435] FIX: s3 cdn would break cooking if tag had no src --- lib/pretty_text.rb | 1 + spec/components/pretty_text_spec.rb | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pretty_text.rb b/lib/pretty_text.rb index 2f6417f894..d5786c147f 100644 --- a/lib/pretty_text.rb +++ b/lib/pretty_text.rb @@ -224,6 +224,7 @@ module PrettyText def self.add_s3_cdn(doc) doc.css("img").each do |img| + next unless img["src"] img["src"] = img["src"].sub(Discourse.store.absolute_base_url, SiteSetting.s3_cdn_url) end end diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb index 66a64a404b..5da48e1e93 100644 --- a/spec/components/pretty_text_spec.rb +++ b/spec/components/pretty_text_spec.rb @@ -323,8 +323,9 @@ describe PrettyText do SiteSetting.s3_upload_bucket = "test" SiteSetting.s3_cdn_url = "https://awesome.cdn" - raw = " Date: Wed, 10 Jun 2015 19:27:29 +1000 Subject: [PATCH 0051/1435] FEATURE: expanded error reporting in logs - add hostname - add process_id --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index e50f2484f3..c93cc92661 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -147,7 +147,7 @@ GEM thor (~> 0.15) libv8 (3.16.14.7) listen (0.7.3) - logster (0.8.1) + logster (0.8.2) lru_redux (1.1.0) mail (2.5.4) mime-types (~> 1.16) From f2b3312ee263104059708754f132fbac5598e325 Mon Sep 17 00:00:00 2001 From: camelmasa Date: Wed, 10 Jun 2015 20:12:42 +0900 Subject: [PATCH 0052/1435] Remove actionpack-action_caching gem from Gemfile Discourse is not using the gem. --- Gemfile | 2 -- Gemfile.lock | 3 --- 2 files changed, 5 deletions(-) diff --git a/Gemfile b/Gemfile index 03750100f4..539bc0d34c 100644 --- a/Gemfile +++ b/Gemfile @@ -25,8 +25,6 @@ else gem 'seed-fu', '~> 2.3.3' end -gem 'actionpack-action_caching' - # Rails 4.1.6+ will relax the mail gem version requirement to `~> 2.5, >= 2.5.4`. # However, mail gem 2.6.x currently does not work with discourse because of the # reference to `Mail::RFC2822Parser` in `lib/email.rb`. This ensure discourse diff --git a/Gemfile.lock b/Gemfile.lock index c93cc92661..c64a4f916d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -15,8 +15,6 @@ GEM activesupport (= 4.1.10) rack (~> 1.5.2) rack-test (~> 0.6.2) - actionpack-action_caching (1.1.1) - actionpack (>= 4.0.0, < 5.0) actionview (4.1.10) activesupport (= 4.1.10) builder (~> 3.1) @@ -392,7 +390,6 @@ PLATFORMS ruby DEPENDENCIES - actionpack-action_caching active_model_serializers (~> 0.8.3) annotate aws-sdk From 7bc3a6fff038012e730da4554435625160dd3961 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Wed, 10 Jun 2015 18:10:46 +0530 Subject: [PATCH 0053/1435] Update Translations --- config/locales/client.ar.yml | 1 - config/locales/client.bs_BA.yml | 1 - config/locales/client.cs.yml | 11 +++++- config/locales/client.da.yml | 11 +++--- config/locales/client.de.yml | 2 +- config/locales/client.es.yml | 4 +- config/locales/client.fi.yml | 1 - config/locales/client.fr.yml | 2 +- config/locales/client.he.yml | 66 ++++++++++++++++++++++----------- config/locales/client.it.yml | 5 ++- config/locales/client.nb_NO.yml | 1 - config/locales/client.pt_BR.yml | 1 - config/locales/client.ru.yml | 21 ++++++++++- config/locales/client.te.yml | 1 - config/locales/client.tr_TR.yml | 1 - config/locales/client.zh_TW.yml | 30 ++++++++++++++- 16 files changed, 117 insertions(+), 42 deletions(-) diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index b0c09ba491..6379bc65eb 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -1398,7 +1398,6 @@ ar: this_month: "هذا الشهر" this_week: "هذا الاسبوع" today: "اليوم" - other_periods: "الاطلاع على مواضيع زيادة" browser_update: 'للأسف, متصفحك قديم لكي يفتح هذه الصفحة. Please قم بتحديث متصفحك.' permission_types: full: "انشاء / رد / مشاهدة" diff --git a/config/locales/client.bs_BA.yml b/config/locales/client.bs_BA.yml index 3108b32d5d..d6e30d9846 100644 --- a/config/locales/client.bs_BA.yml +++ b/config/locales/client.bs_BA.yml @@ -1074,7 +1074,6 @@ bs_BA: this_month: "Ovog mjeseca" this_week: "Ove nedelje" today: "Danas" - other_periods: "pogledaj još tema" browser_update: 'Nažalost, vaš internet browser je prestar za ovaj korišćenje ovog foruma. Idite na i obnovite vaš browser.' permission_types: full: "Kreiraj / Odgovori / Vidi" diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index 06f92da1f9..5eaf7317ca 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -420,6 +420,7 @@ cs: change_avatar: title: "Změňte si svůj profilový obrázek" gravatar: "Založeno na Gravataru" + gravatar_title: "Změňte si avatar na webových stránkách Gravatar" refresh_gravatar_title: "Obnovit Gravatar" letter_based: "Systémem přidělený profilový obrázek" uploaded_avatar: "Vlastní obrázek" @@ -555,6 +556,7 @@ cs: title: "Registrační IP adresa" avatar: title: "Profilový obrázek" + header_title: "profil, zprávy, záložky a nastavení" title: title: "Titul" filters: @@ -589,6 +591,7 @@ cs: read_only_mode: enabled: "Stranka je nastavena jen pro čtení. Můžete pokračovat v prohlížení ale interakce nemusí fungovat." login_disabled: "Přihlášení je zakázáno jelikož fórum je v režimu jen pro čtení." + too_few_topics_notice: "Vytvořte alespoň 5 veřejných témat a %{posts} veřejných příspěvků aby se nastartovala diskuze. Noví uživatelé bez obsahu co by mohli číst nezískají důvěryhodnost. Tato zpráva se zobrazuje jenom redakci." learn_more: "více informací..." year: 'rok' year_desc: 'témata za posledních 365 dní' @@ -1019,6 +1022,7 @@ cs: banner_note: "Uživatelé mohou odmítnout banner jeho zavřením. V jeden moment může být pouze jedno téma jako banner." already_banner: zero: "Žádné téma není jako banner." + one: "V současnosti je zde zakázané téma." inviting: "Odesílám pozvánku..." automatically_add_to_groups_optional: "Tato pozvánka obsahuje také přístup do této skupiny: (volitelné, pouze administrátor)" automatically_add_to_groups_required: "Tato pozvánka obsahuje také přístup do těchto skupin: (Vyžadováno, pouze administrátor)" @@ -1109,6 +1113,10 @@ cs: few: "(post withdrawn by author, will be automatically deleted in %{count} hours unless flagged)" other: "(post withdrawn by author, will be automatically deleted in %{count} hours unless flagged)" expand_collapse: "rozbalit/sbalit" + gap: + one: "zobrazit 1 skrytou odpověď" + few: "zobrazit {{count}} skryté odpovědi" + other: "zobrazit {{count}} skrytých odpovědí" more_links: "{{count}} dalších..." unread: "Příspěvek je nepřečtený." has_replies: @@ -1287,6 +1295,7 @@ cs: last: "Poslední revize" hide: "Schovejte revizi" show: "Zobrazte revizi" + comparing_previous_to_current_out_of_total: "{{previous}} {{current}} / {{total}}" displays: inline: title: "Vykreslený příspěvek se změnami zobrazenými v textu" @@ -1310,6 +1319,7 @@ cs: create: 'Nová kategorie' save: 'Uložit kategorii' slug: 'Odkaz kategorie' + slug_placeholder: '(Dobrovolné) podtržená URL' creation_error: Během vytváření nové kategorie nastala chyba. save_error: Během ukládání kategorie nastala chyba. name: "Název kategorie" @@ -1517,7 +1527,6 @@ cs: this_month: "Tenhle měsíc" this_week: "Tenhle týden" today: "Dnes" - other_periods: "další nejlepší témata" browser_update: 'Bohužel, váš prohlížeč je příliš starý, aby na něm Discourse mohl fungovat. Prosím aktualizujte svůj prohlížeč.' permission_types: full: "Vytvářet / Odpovídat / Prohlížet" diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index 58f8f43fae..c9beeb6974 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -430,14 +430,14 @@ da: instructions: "Unikt, ingen mellemrum, kort" short_instructions: "Folk kan benævne dig som @{{username}}" available: "Dit brugernavn er tilgængeligt" - global_match: "E-mail tilsvarende det registrerede brugernavn" + global_match: "E-mail svarer til det registrerede brugernavn" global_mismatch: "Allerede registreret. Prøv {{suggestion}}?" not_available: "Ikke ledigt. Prøv {{suggestion}}?" too_short: "Dit brugernavn er for kort" too_long: "Dit brugernavn er for langt" checking: "Kontrollerer om brugernavnet er ledigt…" enter_email: 'Brugernavn fundet; indtast tilsvarende e-mail' - prefilled: "E-mail tilsvarende dette registrerede brugernavn" + prefilled: "E-mail svarer til dette registrerede brugernavn" locale: title: "sprog" instructions: "Brugerinterface sprog. Det skifter når de reloader siden." @@ -609,10 +609,10 @@ da: reset: "Nulstil adgangskode" complete_username: "Hvis en konto matcher brugernavnet %{username}, vil du om lidt modtage en email med instruktioner om hvordan man nulstiller passwordet." complete_email: "Hvis en konto matcher %{email}, vil du om lidt modtage en email med instruktioner om hvordan man nulstiller passwordet." - complete_username_found: "Vi fandt ingen kontoer tilsvarende brugernavnet %{username}, du burde modtage en e-mail med instruktioner om hvordan du nulstiller din adgangskode, i løbet af kort tid." - complete_email_found: "Vi har fundet en konto tilsvarende %{email}, du burde modtage en e-mail med instruktioner om hvordan du nulstiller din adgangskode, i løbet af kort tid." + complete_username_found: "Vi fandt en konto der svarer til brugernavnet %{username}, du burde i løbet af kort tid modtage en e-mail med instruktioner om hvordan du nulstiller din adgangskode." + complete_email_found: "Vi har fundet en konto der svarer til %{email}, du burde i løbet af kort tid modtage en e-mail med instruktioner om hvordan du nulstiller din adgangskode." complete_username_not_found: "Ingen kontoer passer til brugernavnet %{username}" - complete_email_not_found: "Ingen kontoer tilsvarende %{email}" + complete_email_not_found: "Ingen kontoer svarer til %{email}" login: title: "Log ind" username: "Bruger" @@ -1461,7 +1461,6 @@ da: this_month: "Denne måned" this_week: "Denne uge" today: "I dag" - other_periods: "Se flere top emner" browser_update: 'Desværre, din browser er for gammel til at kunne virke med dette forum. Opgradér venligst din browser.' permission_types: full: "Opret / Besvar / Se" diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index dc1e737e94..89b0574990 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -1460,7 +1460,7 @@ de: this_month: "Dieser Monat" this_week: "Diese Woche" today: "Heute" - other_periods: "zeige weitere angesagte Themen" + other_periods: "zeige angesagte Themen:" browser_update: 'Dein Webbrowser ist leider zu alt, um dieses Forum zu besuchen. Bitte installiere einen neueren Browser.' permission_types: full: "Erstellen / Antworten / Ansehen" diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index d60fcd620c..4d0f2b20e5 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -867,7 +867,7 @@ es: toggle_information: "detalles del tema" read_more_in_category: "¿Quieres leer más? Consulta otros temas en {{catLink}} o {{latestLink}}." read_more: "¿Quieres leer más? {{catLink}} o {{latestLink}}." - read_more_MF: "Hay { UNREAD, plural, =0 {} one { 1 no leído } other { are # no leídos } } { NEW, plural, =0 {} one { {BOTH, select, true{y } false { } other{}} 1 nuevo tema} other { {BOTH, select, true{y } false { } other{}} # nuevos temas} } restantes, o {CATEGORY, select, true {explora otros temas en {catLink}} false {{latestLink}} other {}}" + read_more_MF: "Hay { UNREAD, plural, =0 {} one { 1 no leído } other { # no leídos } } { NEW, plural, =0 {} one { {BOTH, select, true{y } false { } other{}} 1 nuevo tema} other { {BOTH, select, true{y } false { } other{}} # nuevos temas} } restantes, o {CATEGORY, select, true {explora otros temas en {catLink}} false {{latestLink}} other {}}" browse_all_categories: Ver todas las categorías view_latest_topics: ver los temas recientes suggest_create_topic: ¿Por qué no creas un tema? @@ -1462,7 +1462,7 @@ es: this_month: "Este mes" this_week: "Esta semana" today: "Hoy" - other_periods: "ver más temas top" + other_periods: "ver temas top" browser_update: 'Desafortunadamente, tu navegador es demasiado antiguo para funcionar en este sitio. Por favor actualízalo.' permission_types: full: "Crear / Responder / Ver" diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index b56593412d..80b1c23a2e 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -1458,7 +1458,6 @@ fi: this_month: "Tässä kuussa" this_week: "Tällä viikolla" today: "Tänään" - other_periods: "näytä lisää huippuketjuja" browser_update: 'Valitettavasti tätä sivustoa ei voi käyttää näin vanhalla selaimella. Ole hyvä ja päivitä selaimesi.' permission_types: full: "Luoda / Vastata / Nähdä" diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index 9b6e02537c..19264e95d7 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -1466,7 +1466,7 @@ fr: this_month: "Ce mois-ci" this_week: "Cette semaine" today: "Aujourd'hui" - other_periods: "voir plus de meilleurs sujets" + other_periods: "voir le top" browser_update: 'Malheureusement, votre navigateur est trop vieux pour ce site. Merci de mettre à jour votre navigateur.' permission_types: full: "Créer / Répondre / Voir" diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index 18593486c0..c6b7c5adc3 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -379,6 +379,7 @@ he: set_password: "הזן סיסמה" change_about: title: "שינוי בנוגע אליי" + error: "הייתה שגיאה בשינוי הערך" change_username: title: "שנה שם משתמש" confirm: "אם תשנו את שם המשתמש/ת שלך, כל הציטוטים של ההודעות שלך ואזכורי @שם_המשתמש שלך יישברו. את/ה בטוחים לחלוטין שברצונך לשנות?" @@ -393,6 +394,7 @@ he: change_avatar: title: "שינוי תמונת הפרופיל" gravatar: "Gravatar, מבוסס על" + gravatar_title: "שנה את ה-avatar שלך באתר-Gravatar" refresh_gravatar_title: "רענון האווטר שלכם" letter_based: "תמונת פרופיל משובצת מערכתית" uploaded_avatar: "תמונה אישית" @@ -524,6 +526,7 @@ he: title: "כתובת IP בהרשמה" avatar: title: "תמונת פרופיל" + header_title: "פרופיל, הודעות, סימניות והגדרות " title: title: "כותרת" filters: @@ -682,6 +685,7 @@ he: title_placeholder: " במשפט אחד, במה עוסק הדיון הזה?" edit_reason_placeholder: "מדוע ערכת?" show_edit_reason: "(הוספת סיבת עריכה)" + reply_placeholder: "הקלד כאן. השתמש בMarkdown BBCode או HTML לערוך. גרור והדבק תמונה כדי להעלות אותה." view_new_post: "הצגת את ההודעה החדשה שלך." saving: "שומר..." saved: "נשמר!" @@ -743,7 +747,7 @@ he: linked: "

{{username}} {{description}}

" granted_badge: "

הרוויח/ה '{{description}}'

" popup: - mentioned: '{{username}} הזכיר אוך ב"{{topic}}" - {{site_title}}' + mentioned: '{{username}} הזכיר אותך ב{{topic}}" - {{site_title}}"' quoted: '{{username}} ציטט אותך ב"{{topic}}" - {{site_title}}' replied: '{{username}} הגיב לך ב"{{topic}}" - {{site_title}}' posted: '{{username}} הגיב ב"{{topic}}" - {{site_title}}' @@ -885,14 +889,14 @@ he: position: "הודעה %{current} מתוך %{total}" notifications: reasons: - '3_6': 'תקבלו התראות כיוון שאת/ה צופה בקטגוריה הזו.' - '3_5': 'תקבל/י התראות כיוון שהתחלת לצפות בנושא הזה אוטומטית.' - '3_2': 'תקבל/י התראות כיוון שאת/ה צופים בנושא הזה.' + '3_6': 'תקבלו התראות כיוון שאת/ה עוקב אחרי קטגוריה זו.' + '3_5': 'תקבל/י התראות כיוון שהתחלת לעקוב אחרי הנושא הזה אוטומטית.' + '3_2': 'תקבל/י התראות כיוון שאת/ה עוקב אחרי נושא הזה.' '3_1': 'תקבל/י התראות כיוון שאת/ה יצרת את הנושא הזה.' - '3': 'תקבל/י התראות כיוון שאת/ה צופה בנושא הזה.' + '3': 'תקבל/י התראות כיוון שאת/ה עוקב אחרי נושא זה.' '2_8': 'תקבלו התראות כיוון שאת/ה צופה בקטגוריה הזו.' '2_4': 'תקבל/י התראות כיוון שפרסמת תגובה לנושא הזה.' - '2_2': 'תקבל/י התראות כיוון שאת/ה עוקב/ת אחרי הנושא הזה.' + '2_2': 'תקבל/י התראות כיוון שאת/ה צופה אחרי הנושא הזה.' '2': 'תקבל/י התראות כיוון שקראת את הנושא הזה.' '1_2': 'תקבלו הודעה אם מישהו יזכיר את @שם_המשתמש/ת שלך או ישיב לפרסום שלך.' '1': 'תקבלו התראה אם מישהו יזכיר את @שם_המשתמש/ת שלך או ישיב לפרסום שלך.' @@ -900,17 +904,17 @@ he: '0_2': 'אתה מתעלם מכל ההתראות בנושא זה.' '0': 'אתה מתעלם מכל ההתראות בנושא זה.' watching_pm: - title: "צופה" - description: "תקבל/י התראה על כל פרסום בהודעה זו. גם סך הפרסומים החדשים ואלו שלא נקראו יופיעו לצד הנושא." + title: "עוקב" + description: "תקבל/י התראה על כל פרסום בהודעה זו. " watching: - title: "צופה" - description: "תקבל/י התראה על כל פרסום חדש תחת נושא זה. סך הפרסומים החדשים ושלא נקראו יופיע גם לצד הנושא." + title: "עוקב" + description: "תקבל/י התראה על כל פרסום חדש תחת נושא זה. " tracking_pm: - title: "עוקב" - description: "סך הפרסומים החדשים ואלו שלא נקראו יופיע לצד ההודעה. תקבל/י התראה אם מישהו/י יזכירו את @שם_המשתמש/ת שלך או יגיבו לפרסום שלך." + title: "רגיל+" + description: "כמו רגיל, כמו כן סך הפרסומים החדשים ואלו שלא נקראו יופיע לצד ההודעה." tracking: - title: "עוקב" - description: "סך הפרסום החדשים והלא נקראים יופיע לצד הנושא. תקבלו התראה אם מישהו יזכיר את @שם_המשתמש שלך או ישיב על פרסום שלך." + title: "רגיל+" + description: "כמו רגיל, כמו כן סך הפרסום החדשים והלא נקראים יופיע לצד הנושא. " regular: title: "רגיל" description: "תרבלו התראה אם מישהו יזכיר את @שם_המשתמש/ת שלך או ישיב לפרסום שלך." @@ -919,10 +923,10 @@ he: description: "תקבל/י התראה אם מישהו יזכיר את @שם_המשתמש/ת שלך או יגיב לפרסום שלך בהודעה." muted_pm: title: "מושתק" - description: "לעולם לא תקבל/י התראה לשום דבר בנוגע להודעה זו." + description: "לעולם לא תקבל/י התראה בנוגע להודעה זו." muted: title: "מושתק" - description: "לעולם לא תקבל/י התראות על הנושא הזה, והוא לא יופיע בעמוד הלא נקראו שלך." + description: "לעולם לא תקבל/י התראות על הנושא הזה, והוא לא יופיע בעמוד ה\"לא נקראו\" שלך." actions: recover: "שחזר נושא" delete: "מחק נושא" @@ -995,10 +999,13 @@ he: to_forum: "נשלח מייל קצר המאפשר לחברך להצטרף באופן מיידי באמצעות לחיצה על קישור, ללא צורך בהתחברות למערכת הפורומים." sso_enabled: "הכנס את שם המשתמש של האדם שברצונך להזמין לנושא זה." to_topic_blank: "הכנס את שם המשתמש או כתובת דואר האלקטרוני של האדם שברצונך להזמין לנושא זה." + to_topic_email: "הזנת כתובת אימייל. אנחנו נשלח הזמנה שתאפשר לחברך להשיב לנושא הזה." to_topic_username: "הכנסת את שם המשתמש של האדם שברצונך להזמין. אנו נשלח התראה למשתמש זה עם קישור המזמין אותו לנושא זה." to_username: "הכנס את שם המשתמש של האדם שברצונך להזמין. אנו נשלח התראה למשתמש זה עם קישור המזמין אותו לנושא זה." email_placeholder: 'name@example.com' + success_email: "שלחנו הזמנה ל: {{emailOrUsername}}. נודיע לך כשהזמנה תענה. בדוק את טאב ההזמנות בעמוד המשתמש שלך בשביל לעקוב אחרי ההזמנות ששלחת. " success_username: "הזמנו את המשתמש להשתתף בנושא." + error: "מצטערים, לא יכלנו להזמין האיש הזה. אולי הוא כבר הוזמן בעבר? (תדירות שליחת ההזמנות מוגבלת)" login_reply: 'התחברו כדי להשיב' filters: n_posts: @@ -1273,11 +1280,11 @@ he: parent: "קטגורית אם" notifications: watching: - title: "צופה" - description: "תצפו באופן אוטומטי בכל הנושאים החדשים בקטגוריות אלה. תקבלו התראה על כל פרסום ונושא חדש, ובנוסף, סך הפרסומים החדשים ושלא נקראו יופיעו לצד הנושא." - tracking: title: "עוקב" - description: "תעקבו באופן אוטומטי אחרי כל הנושאים החדשים בקטגוריות אלה. סך הפרסומים החדשים ושלא נקראו יופיעו לצד הנושא." + description: "תצפו באופן אוטומטי בכל הנושאים החדשים בקטגוריות אלה. תקבלו התראה על כל פרסום ונושא חדש." + tracking: + title: "רגיל+" + description: "כמו רגיל, וכן סך הפרסומים החדשים ושלא נקראו יופיעו לצד הנושא." regular: title: "רגיל" description: "תקבלו התראה אם מישהו יזכיר את @שם_המשתמש/ת שלכם או ישיב לפרסום שלך." @@ -1312,6 +1319,8 @@ he: action: "סימון נושא" topic_map: title: "סיכום נושא" + participants_title: "מפרסמים מתמידים" + links_title: "לינקים פופלארים" links_shown: "הצג את כל הקישורים {{totalLinks}}..." clicks: one: "לחיצה אחת" @@ -1347,12 +1356,21 @@ he: אחר {}} original_post: "הודעה מקורית" views: "צפיות" + views_lowercase: + one: "צפיה" + other: "צפיות" replies: "תגובות" views_long: "הנושא הזה נצפה {{number}} פעמים" activity: "פעילות" likes: "לייקים" + likes_lowercase: + one: "לייקים" + other: "לייקים" likes_long: "יש {{number}} לייקים לנושא הזה" users: "משתמשים" + users_lowercase: + one: "משתמש" + other: "משתמשים" category_title: "קטגוריה" history: "היסטוריה" changed_by: "מאת {{author}}" @@ -1372,6 +1390,9 @@ he: read: title: "נקרא" help: "נושאים שקראת, לפי סדר קריאתם" + search: + title: "חיפוש" + help: "חיפוש בכל הנושאים" categories: title: "קטגוריות" title_in: "קטגוריה - {{categoryName}}" @@ -1424,7 +1445,6 @@ he: this_month: "החודש" this_week: "השבוע" today: "היום" - other_periods: "ראו עוד נושאים מובילים." browser_update: 'למרבה הצער, הדפדפן שלכם זקן מידי מכדי לעבוד באתר זה.. אנא שדרגו את הדפדפן שלכם.' permission_types: full: "צרו / תגובה/ צפייה" @@ -1456,6 +1476,7 @@ he: admins: 'מנהלים ראשיים:' blocked: 'חסומים:' suspended: 'מושעים:' + private_messages_title: "הודעות" space_free: "{{size}} חופשיים" uploads: "העלאות" backups: "גיבויים" @@ -1560,8 +1581,11 @@ he: name: "שם" add: "הוספה" add_members: "הוספת חברים וחברות" + custom: "מותאם" + automatic: "אוטומטי" automatic_membership_email_domains: "משתמשים אשר נרשמים עם מארח דוא\"ל שתואם בדיוק לאחד מהרשימה, יוספו באופן אוטומטי לקבוצה זו:" automatic_membership_retroactive: "החלת כלל מארח דוא\"ל זהה כדי להוסיף משתמשים רשומים" + default_title: "ברירת המחדל לכל המשתמשים בקבוצה זו" api: generate_master: "ייצר מפתח מאסטר ל-API" none: "אין מפתחות API פעילים כרגע." diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index cc7a8f3c59..d7f518cc65 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -333,6 +333,7 @@ it: dismiss_notifications: "Imposta tutti come Letti" dismiss_notifications_tooltip: "Imposta tutte le notifiche non lette come lette " disable_jump_reply: "Non saltare al mio messaggio dopo la mia risposta" + dynamic_favicon: "Visualizza conteggio argomenti nuovi / creati nell'icona del browser" edit_history_public: "Consenti agli altri utenti di visualizzare le mie revisioni ai messaggi" external_links_in_new_tab: "Apri tutti i link esterni in nuove schede" enable_quoting: "Abilita \"rispondi quotando\" per il testo evidenziato" @@ -378,6 +379,7 @@ it: set_password: "Imposta Password" change_about: title: "Modifica i dati personali" + error: "Si è verificato un errore nel cambio di questo valore." change_username: title: "Cambia Utente" confirm: "Se modifichi il tuo nome utente, non funzioneranno più le precedenti citazioni ai tuoi messaggi e le menzioni @nome. Sei sicuro di volerlo fare?" @@ -392,6 +394,7 @@ it: change_avatar: title: "Cambia l'immagine del tuo profilo" gravatar: "Gravatar, basato su" + gravatar_title: "Cambia il tuo avatar sul sito Gravatar" refresh_gravatar_title: "Ricarica il tuo Gravatar" letter_based: "Immagine del profilo assegnata dal sistema" uploaded_avatar: "Immagine personalizzata" @@ -523,6 +526,7 @@ it: title: "Indirizzo IP di Registrazione" avatar: title: "Immagine Profilo" + header_title: "profilo, messaggi, segnalibri e preferenze" title: title: "Titolo" filters: @@ -1422,7 +1426,6 @@ it: this_month: "Questo mese" this_week: "Questa settimana" today: "Oggi" - other_periods: "vedi altri argomenti di punta" browser_update: 'Purtroppo il tuo browser è troppo vecchio per funzionare su questo forum. Per favore aggiorna il browser.' permission_types: full: "Crea / Rispondi / Visualizza" diff --git a/config/locales/client.nb_NO.yml b/config/locales/client.nb_NO.yml index 3a8e1bacde..c1d02aad75 100644 --- a/config/locales/client.nb_NO.yml +++ b/config/locales/client.nb_NO.yml @@ -1401,7 +1401,6 @@ nb_NO: this_month: "Denne måneden" this_week: "Denne uken" today: "I dag" - other_periods: "vis flere populære emner" browser_update: 'Dessverre, Din nettleser er for gammel og fungerer ikke med dette nettstedet.. Vennligst oppgrader nettleseren din.' permission_types: full: "Opprett / Svar / Se" diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml index 3289beb0e1..9e4aa0b89d 100644 --- a/config/locales/client.pt_BR.yml +++ b/config/locales/client.pt_BR.yml @@ -1452,7 +1452,6 @@ pt_BR: this_month: "Neste mes" this_week: "Nesta semana" today: "Hoje" - other_periods: "veja mais tópicos em alta" browser_update: 'Infelizmente, seu navegador é muito antigo para ser utilizado neste site. Por favor atualize seu navegador.' permission_types: full: "Criar / Responder / Ver" diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index f8225b3a52..c1ab2c6c14 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -255,6 +255,11 @@ ru: approval: title: "Сообщения для проверки" description: "Ваше сообщение отправлено, но требует проверки и утверждения модератором. Пожалуйста, будьте терпеливы." + pending_posts: + one: "1 сообщение ожидает одобрения." + few: "{{count}} сообщений ожидают одобрения." + many: "{{count}} сообщений ожидают одобрения." + other: "{{count}} сообщений ожидают одобрения." ok: "ОК" user_action: user_posted_topic: "{{user}} создал тему" @@ -425,6 +430,7 @@ ru: set_password: "Установить пароль" change_about: title: "Изменить информацию обо мне" + error: "При изменении значения произошла ошибка." change_username: title: "Изменить псевдоним" confirm: "Если вы измените свой псевдоним, то все существующие цитаты ваших сообщений и упоминания вас по @псевдониму в чужих сообщениях перестанут ссылаться на вас. Вы точно хотите изменить псевадоним?" @@ -439,6 +445,7 @@ ru: change_avatar: title: "Изменить фон профиля" gravatar: "На основе Gravatar" + gravatar_title: "Измените свой аватар на сайте Gravatar" refresh_gravatar_title: "Обновить ваш Gravatar" letter_based: "Фон профиля по умолчанию" uploaded_avatar: "Собственный аватар" @@ -578,6 +585,7 @@ ru: title: "IP адрес регистрации" avatar: title: "Аватар" + header_title: "профиль, сообщения, закладки и настройки" title: title: "Заголовок" filters: @@ -950,6 +958,7 @@ ru: go_top: "перейти наверх" go_bottom: "перейти вниз" go: "перейти" + jump_bottom: "перейти к последнему сообщению" jump_bottom_with_number: "перейти к сообщению %{post_number}" total: всего сообщений current: текущее сообщение @@ -1148,6 +1157,11 @@ ru: many: "(сообщение отозвано автором и будет автоматически удалено в течение %{count} часов, если только на сообщение не поступит жалоба)" other: "(сообщение отозвано автором и будет автоматически удалено в течение %{count} часов, если только на сообщение не поступит жалоба)" expand_collapse: "развернуть/свернуть" + gap: + one: "просмотреть 1 скрытый ответ" + few: "просмотреть {{count}} скрытых ответов" + many: "просмотреть {{count}} скрытых ответов" + other: "просмотреть {{count}} скрытых ответов" more_links: "еще {{count}}..." unread: "Сообщение не прочитано" has_replies: @@ -1344,6 +1358,7 @@ ru: last: "Последняя версия" hide: "Скрыть редакцию" show: "Показать редакцию" + comparing_previous_to_current_out_of_total: "{{previous}} {{current}} / {{total}}" displays: inline: title: "Отобразить сообщение с включенными добавлениями и удалениями." @@ -1446,6 +1461,8 @@ ru: notify_action: "Сообщение" topic_map: title: "Сводка по теме" + participants_title: "Частые авторы" + links_title: "Популярные ссылки" links_shown: "показать все {{totalLinks}} ссылок..." clicks: one: "1 клик" @@ -1576,7 +1593,7 @@ ru: this_month: "За месяц" this_week: "За неделю" today: "Сегодня" - other_periods: "смотреть больше обсуждаемых сообщений" + other_periods: "просмотреть выше" browser_update: 'К сожалению, ваш браузер устарел и не поддерживается этим сайтом. Пожалуйста, обновите браузер (нажмите на ссылку, чтобы узнать больше).' permission_types: full: "Создавать / Отвечать / Просматривать" @@ -2016,6 +2033,8 @@ ru: impersonate: title: "Войти от имени пользователя" help: "Используйте этот инструмент, чтобы войти от имени пользователя. Может быть полезно для отладки. После этого необходимо выйти и зайти под своей учетной записью снова." + not_found: "Пользователь не найден." + invalid: "Извините, но вы не можете представиться этим пользователем." users: title: 'Пользователи' create: 'Добавить администратора' diff --git a/config/locales/client.te.yml b/config/locales/client.te.yml index bb715389fc..cb70775663 100644 --- a/config/locales/client.te.yml +++ b/config/locales/client.te.yml @@ -1276,7 +1276,6 @@ te: this_month: "ఈ నెల" this_week: "ఈ వారం" today: "ఈ రోజు" - other_periods: "మరిన్ని అగ్ర విషయాలు చూడు" browser_update: 'దురదృష్టవశాత్తు, ఈ సైట్ లో పనిచేయడానికి మీ బ్రౌజర్ చాలా పాతది . దయచేసి మీ బ్రౌజర్ ని నవీకరించండి.' permission_types: full: "సృష్టించి / జవాబివ్వు / చూడు" diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml index 534de90325..efe5e9a016 100644 --- a/config/locales/client.tr_TR.yml +++ b/config/locales/client.tr_TR.yml @@ -1392,7 +1392,6 @@ tr_TR: this_month: "Bu ay" this_week: "Bu hafta" today: "Bugün" - other_periods: "daha fazla konuya bak" browser_update: 'Malesef, tarayıcınız bu site için çok eski. Lütfen tarayıcınızı güncelleyin.' permission_types: full: "Oluştur / Cevapla / Bak" diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml index 7827550ab5..758a2f9434 100644 --- a/config/locales/client.zh_TW.yml +++ b/config/locales/client.zh_TW.yml @@ -184,8 +184,17 @@ zh_TW: none: "沒有要回顧的文章。" edit: "編輯" cancel: "取消" + view_pending: "觀看等待審核的貼文" + has_pending_posts: + one: "本主題仍有 1 篇貼文等待審核" + many: "本主題仍有 {{count}}篇貼文等待審核" confirm: "儲存變更" + delete_prompt: "您確定要刪除 %{username} 這個帳號嗎?這會同時將該帳號的所有貼文一併刪除,並封鎖他的電子郵件與 IP。" approval: + title: "貼文需等待審核" + description: "貼文已經送出,但必須等待管理者審核過後才會出現在板上,請耐心等候。" + pending_posts: + other: "你有 {{count}} 篇貼文在等待審核中" ok: "確定" user_action: user_posted_topic: "{{user}} 建立了 此討論話題" @@ -246,6 +255,7 @@ zh_TW: '11': "編輯" '12': "送出的項目" '13': "收件匣" + '14': "等待中" categories: all: "所有分類" all_subcategories: "所有" @@ -356,12 +366,15 @@ zh_TW: change_avatar: title: "設定個人資料圖片" gravatar: "Gravatar, based on" + gravatar_title: "在 Gravatar 網站修改你的頭像" refresh_gravatar_title: "重新整理你的 Gravatar 頭像" letter_based: "系統分配的個人資料圖片" uploaded_avatar: "自訂圖片" uploaded_avatar_empty: "新增一張自訂圖片" upload_title: "上傳你的圖片" upload_picture: "上傳圖片" + image_is_not_a_square: "警告:我們裁切了你的圖片,因為該圖片不是正方形的。" + cache_notice: "你已經成功更改頭圖,但由於瀏覽器快取的關係,可能要一會兒後才會顯示新頭圖。" change_profile_background: title: "基本資料背景圖案" instructions: "個人資料背景會被置中,且默認寬度為850px。" @@ -419,6 +432,8 @@ zh_TW: every_three_days: "每三天" weekly: "每週" every_two_weeks: "每兩星期" + email_direct: "當有人引用、回覆我的發文,或以 @用戶名稱 提到我時,請以電子郵件通知我。" + email_private_messages: "當有人寄給我私人訊息時,以電子郵件通知我。" email_always: "不要因為我在網站上活動而不寄信通知。" other_settings: "其它" categories_settings: "分類" @@ -461,6 +476,7 @@ zh_TW: none: "你尚未邀請任何人。你可以發送個別邀請,或者透過上傳邀請名單一次邀請一群人。" text: "從檔案大量邀請" uploading: "正在上傳..." + success: "檔案已上傳成功,處理完畢後將以私人訊息通知你。" error: "上傳 '{{filename}}' 時發生問題:{{message}}" password: title: "密碼" @@ -509,6 +525,7 @@ zh_TW: logout: "已登出" refresh: "重新整理" read_only_mode: + enabled: "管理員開啟了唯讀模式。你可以繼續瀏覽網站,但一些互動功能可能無法使用。" login_disabled: "在唯讀模式下不能登入" learn_more: "進一步了解..." year: '年' @@ -523,6 +540,8 @@ zh_TW: unmute: 取消靜音 last_post: 最後一篇文章 last_reply_lowercase: 最新回覆 + replies_lowercase: + other: 回覆 summary: enabled_description: "你正在檢視此討論話題的摘要:在這個社群裡最熱門的文章。" description: "共有 {{count}} 個回覆。" @@ -628,6 +647,7 @@ zh_TW: title_placeholder: "用一個簡短的句子來描述想討論的內容。" edit_reason_placeholder: "你為什麼做編輯?" show_edit_reason: "(請加入編輯原因)" + reply_placeholder: "在此輸入。請使用 Markdown (http://markdown.tw/) 與 BBCode (http://www.bbcode.org/reference.php) 調整格式,拖曳或貼上圖片可以直接上傳。" view_new_post: "檢視你的新文章。" saving: "正在儲存..." saved: "儲存完畢!" @@ -842,6 +862,7 @@ zh_TW: title: "一般" muted_pm: title: "靜音" + description: "你將不會再收到關於此訊息的通知。" muted: title: "靜音" description: "你將不會收到任何關於此討論話題的通知,此討論話題也不會出現在你的未讀分頁裡。" @@ -862,6 +883,7 @@ zh_TW: feature: pin: "置頂主題" unpin: "取消置頂主題" + pin_globally: "全區置頂討論話題" make_banner: "討論話題橫幅" remove_banner: "移除討論話題橫幅" reply: @@ -879,6 +901,8 @@ zh_TW: success_message: '已投訴此討論話題。' feature_topic: title: "擁有這個話題" + pin: "將此討論主題在{{categoryLink}}類別中置頂" + unpin: "取消此主題在{{categoryLink}}類別的置頂狀態" already_banner: zero: "沒有頂置的話題。" inviting: "正在邀請..." @@ -898,6 +922,7 @@ zh_TW: to_forum: "我們將向你的朋友發出一封電子郵件,他不必登入,他只要按電子郵件裡的連結就可以加入此論壇。" to_topic_blank: "輸入你想邀請的用戶的用戶名稱或電子郵件地址到該討論主題" email_placeholder: '電子郵件地址' + success_username: "我們已經邀請該使用者加入此主題討論" login_reply: '登入以發表回應' filters: n_posts: @@ -1194,6 +1219,7 @@ zh_TW: notify_action: "訊息" topic_map: title: "討論話題摘要" + participants_title: "頻繁發文者" links_title: "熱門連結" links_shown: "顯示所有 {{totalLinks}} 個連結..." clicks: @@ -1310,7 +1336,7 @@ zh_TW: this_month: "本月" this_week: "本週" today: "今天" - other_periods: "看更多精選討論話題" + other_periods: "前往頂端" browser_update: '抱歉,您的瀏覽器版本太舊,無法正常訪問該站點。。請升級您的瀏覽器。' permission_types: full: "建立 / 回覆 / 觀看" @@ -1377,6 +1403,7 @@ zh_TW: agree_title: "確認此投訴為有效且正確" agree_flag_modal_title: "批准並且 ..." agree_flag_hide_post: "批准 (隱藏文章 + 送出私人訊息)" + agree_flag_hide_post_title: "隱藏此文章,並自動向此用戶送出私人訊息,要求盡快修改它" agree_flag_restore_post: "同意(還原文章)" agree_flag_restore_post_title: "回復此文章" agree_flag: "同意投訴" @@ -1525,6 +1552,7 @@ zh_TW: confirm: "你確定要回溯資料庫到以前的工作階段?" export_csv: user_archive_confirm: "你確定要下載你的文章嗎?" + success: "開始匯出,處理完畢後將以私人訊息通知你。" failed: "匯出失敗。請觀看紀錄。" rate_limit_error: "文章每天只能下載一次,請明天再試。" button_text: "匯出" From c1cf602de2def238e6008c63e269147354bd97dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 10 Jun 2015 17:19:58 +0200 Subject: [PATCH 0054/1435] FIX: uploads:backfill_shas rake task --- lib/tasks/uploads.rake | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/tasks/uploads.rake b/lib/tasks/uploads.rake index 8ff0c70e0c..3a1f178c7a 100644 --- a/lib/tasks/uploads.rake +++ b/lib/tasks/uploads.rake @@ -6,21 +6,19 @@ require "digest/sha1" task "uploads:backfill_shas" => :environment do RailsMultisite::ConnectionManagement.each_connection do |db| - puts "Backfilling #{db}" - Upload.select([:id, :sha, :url]).find_each do |u| - if u.sha.nil? + puts "Backfilling #{db}..." + Upload.where(sha1: nil).find_each do |u| + begin + path = Discourse.store.path_for(u) + u.sha1 = Digest::SHA1.file(path).hexdigest + u.save! putc "." - path = "#{Rails.root}/public/#{u.url}" - sha = Digest::SHA1.file(path).hexdigest - begin - Upload.update_all ["sha = ?", sha], ["id = ?", u.id] - rescue ActiveRecord::RecordNotUnique - # not a big deal if we've got a few duplicates - end + rescue Errno::ENOENT + putc "X" end end end - puts "done" + puts "", "Done" end ################################################################################ From bdfdbcd217c458a02978a6e3523521ee3bb137fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 10 Jun 2015 18:15:10 +0200 Subject: [PATCH 0055/1435] FIX: we need the sha of the upload to create a thumbnail --- app/models/optimized_image.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/optimized_image.rb b/app/models/optimized_image.rb index d29560ab12..3a0c5aaa82 100644 --- a/app/models/optimized_image.rb +++ b/app/models/optimized_image.rb @@ -8,6 +8,7 @@ class OptimizedImage < ActiveRecord::Base def self.create_for(upload, width, height, opts={}) return unless width > 0 && height > 0 + return if upload.try(:sha1).blank? DistributedMutex.synchronize("optimized_image_#{upload.id}_#{width}_#{height}") do # do we already have that thumbnail? From a52d31e25e29a9a0405113857df2ce598f0780dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 10 Jun 2015 18:18:20 +0200 Subject: [PATCH 0056/1435] FIX: properly handle external image download errors --- app/models/optimized_image.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/optimized_image.rb b/app/models/optimized_image.rb index 3a0c5aaa82..a908524675 100644 --- a/app/models/optimized_image.rb +++ b/app/models/optimized_image.rb @@ -26,7 +26,7 @@ class OptimizedImage < ActiveRecord::Base # create the thumbnail otherwise original_path = Discourse.store.path_for(upload) if original_path.blank? - external_copy = Discourse.store.download(upload) + external_copy = Discourse.store.download(upload) rescue nil original_path = external_copy.try(:path) end From 6c7e737294f68311a715480d6f446b9d53254a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 10 Jun 2015 18:53:14 +0200 Subject: [PATCH 0057/1435] FIX: truncate topic image_url --- lib/cooked_post_processor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cooked_post_processor.rb b/lib/cooked_post_processor.rb index c9a53e34a5..c853a1574c 100644 --- a/lib/cooked_post_processor.rb +++ b/lib/cooked_post_processor.rb @@ -207,7 +207,7 @@ class CookedPostProcessor def update_topic_image(images) if @post.is_first_post? img = images.first - @post.topic.update_column(:image_url, img["src"]) if img["src"].present? + @post.topic.update_column(:image_url, img["src"][0...255]) if img["src"].present? end end From e54125b5dc0db7a51839d096fe38927ddec8abcd Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 10 Jun 2015 13:12:15 -0400 Subject: [PATCH 0058/1435] FIX: Endless spinner when anonymous users navigated to 404s --- .../javascripts/discourse/models/post-stream.js.es6 | 2 +- app/assets/javascripts/discourse/templates/topic.hbs | 2 +- test/javascripts/acceptance/topic-anonymous-test.js.es6 | 8 ++++++++ test/javascripts/helpers/create-pretender.js.es6 | 4 ++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/models/post-stream.js.es6 b/app/assets/javascripts/discourse/models/post-stream.js.es6 index c6be242fa2..61a70f5d2c 100644 --- a/app/assets/javascripts/discourse/models/post-stream.js.es6 +++ b/app/assets/javascripts/discourse/models/post-stream.js.es6 @@ -713,7 +713,7 @@ const PostStream = RestModel.extend({ // If the result was 404 the post is not found if (status === 404) { topic.set('errorTitle', I18n.t('topic.not_found.title')); - topic.set('notFoundHtml', result.responseText); + topic.set('notFoundHtml', result.jqXHR.responseText); return; } diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs index ea25c5c0ea..a5bb12e87a 100644 --- a/app/assets/javascripts/discourse/templates/topic.hbs +++ b/app/assets/javascripts/discourse/templates/topic.hbs @@ -125,7 +125,7 @@
{{#conditional-loading-spinner condition=noErrorYet}} {{#if model.notFoundHtml}} - {{{model.notFoundHtml}}} +
{{{model.notFoundHtml}}}
{{else}}
{{model.message}}
diff --git a/test/javascripts/acceptance/topic-anonymous-test.js.es6 b/test/javascripts/acceptance/topic-anonymous-test.js.es6 index 2703ad0faa..8e5ddf45c2 100644 --- a/test/javascripts/acceptance/topic-anonymous-test.js.es6 +++ b/test/javascripts/acceptance/topic-anonymous-test.js.es6 @@ -16,6 +16,14 @@ test("Enter without an id", () => { }); }); +test("Enter a 404 topic", (assert) => { + visit("/t/not-found/404"); + andThen(() => { + assert.ok(!exists("#topic"), "The topic was not rendered"); + assert.ok(find(".not-found").text() === "not found", "it renders the error message"); + }); +}); + test("Enter without access", (assert) => { visit("/t/i-dont-have-access/403"); andThen(() => { diff --git a/test/javascripts/helpers/create-pretender.js.es6 b/test/javascripts/helpers/create-pretender.js.es6 index 247f09b299..cad384f5e2 100644 --- a/test/javascripts/helpers/create-pretender.js.es6 +++ b/test/javascripts/helpers/create-pretender.js.es6 @@ -179,6 +179,10 @@ export default function() { return response(403, {}); }); + this.get('/t/404.json', () => { + return response(404, "not found"); + }); + this.get('/t/500.json', () => { return response(502, {}); }); From 611b5f996e7a2b4a59ee820da0d1cf61d1552354 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Wed, 10 Jun 2015 14:36:47 -0400 Subject: [PATCH 0059/1435] FIX: unpinned topics shouldn't remain pinned on categories page --- app/models/category_list.rb | 27 +++++++++++++++++++++ lib/topic_query.rb | 3 ++- spec/components/category_list_spec.rb | 21 ++++++++++++++++ spec/models/category_featured_topic_spec.rb | 6 +++-- 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/app/models/category_list.rb b/app/models/category_list.rb index b7868cdb0f..b5cae822b8 100644 --- a/app/models/category_list.rb +++ b/app/models/category_list.rb @@ -1,3 +1,5 @@ +require_dependency 'pinned_check' + class CategoryList include ActiveModel::Serialization @@ -17,6 +19,8 @@ class CategoryList prune_empty find_user_data + sort_unpinned + trim_results end private @@ -151,4 +155,27 @@ class CategoryList @all_topics.each { |ft| ft.user_data = topic_lookup[ft.id] } end end + + def sort_unpinned + if @guardian.current_user && @all_topics.present? + # Put unpinned topics at the end of the list + @categories.each do |c| + next if c.displayable_topics.blank? || c.displayable_topics.size <= latest_posts_count + unpinned = [] + c.displayable_topics.each do |t| + unpinned << t if t.pinned_at && PinnedCheck.unpinned?(t, t.user_data) + end + unless unpinned.empty? + c.displayable_topics = (c.displayable_topics - unpinned) + unpinned + end + end + end + end + + def trim_results + @categories.each do |c| + next if c.displayable_topics.blank? + c.displayable_topics = c.displayable_topics[0,latest_posts_count] + end + end end diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 15af3dd515..13c3f9bb4b 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -135,9 +135,10 @@ class TopicQuery def list_category_topic_ids(category) query = default_results(category: category.id) pinned_ids = query.where('pinned_at IS NOT NULL AND category_id = ?', category.id) + .limit(nil) .order('pinned_at DESC').pluck(:id) non_pinned_ids = query.where('pinned_at IS NULL OR category_id <> ?', category.id).pluck(:id) - (pinned_ids + non_pinned_ids)[0...@options[:per_page]] + (pinned_ids + non_pinned_ids) end def list_new_in_category(category) diff --git a/spec/components/category_list_spec.rb b/spec/components/category_list_spec.rb index 104173d3b9..808abcd5d8 100644 --- a/spec/components/category_list_spec.rb +++ b/spec/components/category_list_spec.rb @@ -102,6 +102,27 @@ describe CategoryList do end end + context "with pinned topics in a category" do + let!(:topic1) { Fabricate(:topic, category: topic_category, bumped_at: 8.minutes.ago) } + let!(:topic2) { Fabricate(:topic, category: topic_category, bumped_at: 5.minutes.ago) } + let!(:topic3) { Fabricate(:topic, category: topic_category, bumped_at: 2.minutes.ago) } + let!(:pinned) { Fabricate(:topic, category: topic_category, pinned_at: 10.minutes.ago, bumped_at: 10.minutes.ago) } + let(:category) { category_list.categories.first } + + before do + SiteSetting.stubs(:category_featured_topics).returns(2) + end + + it "returns pinned topic first" do + expect(category.displayable_topics.map(&:id)).to eq([pinned.id, topic3.id]) + end + + it "returns topics in bumped_at order if pinned was unpinned" do + PinnedCheck.stubs(:unpinned?).returns(true) + expect(category.displayable_topics.map(&:id)).to eq([topic3.id, topic2.id]) + end + end + end describe 'category order' do diff --git a/spec/models/category_featured_topic_spec.rb b/spec/models/category_featured_topic_spec.rb index 4859ea7cde..38c3383a45 100644 --- a/spec/models/category_featured_topic_spec.rb +++ b/spec/models/category_featured_topic_spec.rb @@ -34,9 +34,11 @@ describe CategoryFeaturedTopic do it 'should feature stuff in the correct order' do + SiteSetting.stubs(:category_featured_topics).returns(3) category = Fabricate(:category) - _t3 = Fabricate(:topic, category_id: category.id, bumped_at: 7.minutes.ago) + t4 = Fabricate(:topic, category_id: category.id, bumped_at: 10.minutes.ago) + t3 = Fabricate(:topic, category_id: category.id, bumped_at: 7.minutes.ago) t2 = Fabricate(:topic, category_id: category.id, bumped_at: 4.minutes.ago) t1 = Fabricate(:topic, category_id: category.id, bumped_at: 5.minutes.ago) pinned = Fabricate(:topic, category_id: category.id, pinned_at: 10.minutes.ago, bumped_at: 10.minutes.ago) @@ -45,7 +47,7 @@ describe CategoryFeaturedTopic do expect( CategoryFeaturedTopic.where(category_id: category.id).pluck(:topic_id) - ).to eq([pinned.id, t2.id, t1.id]) + ).to eq([pinned.id, t2.id, t1.id, t3.id]) end end From ae52f4e776c3a466a96ba0f67edb39e8cd37b1c2 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Wed, 10 Jun 2015 12:53:57 -0700 Subject: [PATCH 0060/1435] Revert "Don't limit @mention autocomplete to latin characters" This reverts commit effe83d7a961b2b0334d9aec1905559c8015c01b. --- app/assets/javascripts/discourse/lib/user-search.js.es6 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/assets/javascripts/discourse/lib/user-search.js.es6 b/app/assets/javascripts/discourse/lib/user-search.js.es6 index 068282c75b..790a00ff09 100644 --- a/app/assets/javascripts/discourse/lib/user-search.js.es6 +++ b/app/assets/javascripts/discourse/lib/user-search.js.es6 @@ -88,6 +88,11 @@ export default function userSearch(options) { currentTerm = term; return new Ember.RSVP.Promise(function(resolve) { + // TODO site setting for allowed regex in username + if (term.match(/[^a-zA-Z0-9_\.]/)) { + resolve([]); + return; + } if (((new Date() - cacheTime) > 30000) || (cacheTopicId !== topicId)) { cache = {}; } From 857ff3515de6efd05f4dd892ad9f11615923a3a6 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Wed, 10 Jun 2015 16:14:51 -0700 Subject: [PATCH 0061/1435] minor copyedit --- config/locales/client.en.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 8e1e1b142e..c82164d1fa 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1131,8 +1131,8 @@ en: sso_enabled: "Enter the username of the person you'd like to invite to this topic." to_topic_blank: "Enter the username or email address of the person you'd like to invite to this topic." to_topic_email: "You've entered an email address. We'll email an invitation that allows your friend to immediately reply to this topic." - to_topic_username: "You've entered a username. We'll send a notification to that user with a link inviting them to this topic." - to_username: "Enter the username of the person you'd like to invite. We'll send a notification to that user with a link inviting them to this topic." + to_topic_username: "You've entered a username. We'll send a notification with a link inviting them to this topic." + to_username: "Enter the username of the person you'd like to invite. We'll send a notification with a link inviting them to this topic." email_placeholder: 'name@example.com' success_email: "We mailed out an invitation to {{emailOrUsername}}. We'll notify you when the invitation is redeemed. Check the invitations tab on your user page to keep track of your invites." From a14ea757a123ca9f89797d9bd6dd1b50556b9413 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Wed, 10 Jun 2015 17:07:29 -0700 Subject: [PATCH 0062/1435] improvements to new user welcome copy --- config/locales/server.en.yml | 32 ++++++++++-------- public/images/welcome/progress-bar.png | Bin 1083 -> 5388 bytes .../welcome/topic-list-select-areas.png | Bin 0 -> 8662 bytes .../welcome/topic-notification-control.png | Bin 0 -> 39580 bytes 4 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 public/images/welcome/topic-list-select-areas.png create mode 100644 public/images/welcome/topic-notification-control.png diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 6ce907efab..2f7854c1a0 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1460,39 +1460,39 @@ en: text_body_template: | Here are a few quick tips to get you started: - ## Just Scroll Down + ## Reading - To read more, **just keep scrolling down!** As new posts or new topics arrive, they will appear automatically. + To read more, **just keep scrolling down!** As new posts or new topics arrive, they will appear automatically – no need to refresh the page. ## Navigation - For search, your user page, or the menu, use the **icon buttons at upper right**. - - Selecting a topic title will always take you to the next unread post in the topic. To enter at the top or bottom instead, select the date or post count. + - Selecting a topic title will always take you to the next unread post in the topic. To enter at the top or bottom instead, select the date or post count instead. + + - While reading a topic, jump to the top ↑ by selecting the topic title. Select the progress bar at the bottom right for full navigation controls, or use the home and end keys. - + ## Replying - To reply … + - to the **topic in general**, use Reply at the very bottom of the topic. - - to the **topic in general**, use the Reply button at the very bottom of the topic. - - - to a **specific person**, use the Reply button on their post. + - to a **specific person**, use Reply on their post. - with **a linked topic**, use Reply as linked Topic to the right of the post. - To quote, simply select the text you wish to quote, then press any Reply button. + To insert a quote, simply select the text you wish to quote, then press any Reply button. - To ping someone in your reply, mention their name. Type `@` to begin selecting a name. + To notify someone in your reply, mention their name. Type `@` to begin selecting a name. - For [standard Emoji](http://www.emoji.codes/), just type `:` or the traditional smileys `:)` :smile: + To use [standard Emoji](http://www.emoji.codes/), just type `:` or the traditional smileys `:)` :smile: ## Actions @@ -1510,17 +1510,21 @@ en: - Don't worry about missing a reply – you'll be emailed notifications if you aren't online when they arrive. + Don't worry about missing a reply – you'll be emailed notifications that arrive when you are away. ## Your Preferences - By default all conversations less than two days old are considered new, and any conversation you've participated in (replied to, created, or read for an extended period) will automatically be tracked. + By default all conversations less than two days old are considered new, and any conversation you've actively participated in (by replying, creating the topic, or reading for an extended period) will automatically be tracked. You will see the blue new and number indicators next to these topics: - You can change the individual notification state of a topic via the control at the bottom of the topic (this can also be set per category). To change how you track topics, or the definition of new, see [your user preferences](%{base_url}/my/preferences). + You can change the individual notification state of a topic via the notification control at the bottom of the topic. You can also set notification state per category. + + + + To change these settings, see [your user preferences](%{base_url}/my/preferences). ## Community Trust diff --git a/public/images/welcome/progress-bar.png b/public/images/welcome/progress-bar.png index aeff46a2f8ff580a764949dbe00fafe9df20795a..7545a9b6cc72433b0bdb7f3772bad13ce8e4d6f0 100644 GIT binary patch literal 5388 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 diff --git a/public/images/welcome/topic-list-select-areas.png b/public/images/welcome/topic-list-select-areas.png new file mode 100644 index 0000000000000000000000000000000000000000..0652d30446b873995558cd3cb0c24486d8b49edd GIT binary patch literal 8662 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 Ur3Y_9Py10<)p<~){OtAr0AwD)P5=M^ literal 0 HcmV?d00001 diff --git a/public/images/welcome/topic-notification-control.png b/public/images/welcome/topic-notification-control.png new file mode 100644 index 0000000000000000000000000000000000000000..71b2b1d1bdd5096b271a6cb16834e6da497c754c GIT binary patch literal 39580 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

İstediğiniz değişiklik reddedildi.

-

Belki erişiminiz olmayan bir şeyi değiştirmeye çalıştınız.

+

Belki de erişiminiz olmayan bir şeyi değiştirmeye çalıştınız.

diff --git a/public/503.tr_TR.html b/public/503.tr_TR.html index ff627445d7..f9c2f8028c 100644 --- a/public/503.tr_TR.html +++ b/public/503.tr_TR.html @@ -4,7 +4,7 @@ -

Önceden planlanmış site bakımı nedeniyle şu an kapalıyız.

+

Önceden planlanmış bir site bakımı nedeniyle şu an kapalıyız.

Lütfen birkaç dakika içinde tekrar kontrol edin.

Verdiğimiz rahatsızlık için özür dileriz!

From e4085a64ce875399cf3b3d86eb93a880fba61f32 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Mon, 15 Jun 2015 10:57:41 -0400 Subject: [PATCH 0099/1435] FIX: Use proper upper/lower case when previewing mentions --- app/assets/javascripts/discourse/lib/link-mentions.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/link-mentions.js.es6 b/app/assets/javascripts/discourse/lib/link-mentions.js.es6 index c1aeb35414..27d4f02387 100644 --- a/app/assets/javascripts/discourse/lib/link-mentions.js.es6 +++ b/app/assets/javascripts/discourse/lib/link-mentions.js.es6 @@ -12,7 +12,7 @@ function updateFound($mentions, usernames) { $mentions.each((i, e) => { const $e = $(e); const username = usernames[i]; - if (found.indexOf(username) !== -1) { + if (found.indexOf(username.toLowerCase()) !== -1) { replaceSpan($e, username); } else if (checked.indexOf(username) !== -1) { $e.addClass('mention-tested'); @@ -24,7 +24,7 @@ function updateFound($mentions, usernames) { export function linkSeenMentions($elem, siteSettings) { const $mentions = $('span.mention:not(.mention-tested)', $elem); if ($mentions.length) { - const usernames = $mentions.map((_, e) => $(e).text().substr(1).toLowerCase()); + const usernames = $mentions.map((_, e) => $(e).text().substr(1)); const unseen = _.uniq(usernames).filter((u) => { return u.length >= siteSettings.min_username_length && checked.indexOf(u) === -1; }); From 08e62347e15c0188d8dc630031e4b70050f178bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 15 Jun 2015 17:27:56 +0200 Subject: [PATCH 0100/1435] FIX: blue notification bar overlaps text on New/Unread tab --- app/assets/javascripts/discourse/templates/discovery.hbs | 2 +- .../javascripts/discourse/templates/discovery/topics.hbs | 2 +- app/assets/stylesheets/common/base/_topic-list.scss | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/discovery.hbs b/app/assets/javascripts/discourse/templates/discovery.hbs index be18d07bae..195db18b2e 100644 --- a/app/assets/javascripts/discourse/templates/discovery.hbs +++ b/app/assets/javascripts/discourse/templates/discovery.hbs @@ -12,7 +12,7 @@ {{conditional-loading-spinner condition=loading}} -
+
diff --git a/app/assets/javascripts/discourse/templates/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/discovery/topics.hbs index 9c573ee3d2..f3e452a4ab 100644 --- a/app/assets/javascripts/discourse/templates/discovery/topics.hbs +++ b/app/assets/javascripts/discourse/templates/discovery/topics.hbs @@ -23,7 +23,7 @@
{{/if}} {{#if topicTrackingState.hasIncoming}} -
+
{{count-i18n key="topic_count_" suffix=topicTrackingState.filter count=topicTrackingState.incomingCount}} {{i18n 'click_to_show'}} diff --git a/app/assets/stylesheets/common/base/_topic-list.scss b/app/assets/stylesheets/common/base/_topic-list.scss index 0b857b0cd4..d6d2fb5b7a 100644 --- a/app/assets/stylesheets/common/base/_topic-list.scss +++ b/app/assets/stylesheets/common/base/_topic-list.scss @@ -1,8 +1,10 @@ .show-more { - position: absolute; - top: 4px; width: 100%; z-index: 1; + &.has-topics { + position: absolute; + top: 7px; + } } .list-controls { From fb8ba5e137eb87c1bfd2c7b70be86accfd0b3e12 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Mon, 15 Jun 2015 12:08:55 -0400 Subject: [PATCH 0101/1435] FIX: `PG::UniqueViolation` when trying to use the same embed code Previously providing an embed code already in use would result in a logged server error. After this commit the error is gracefully bubbled up from the `PostCreator` --- app/models/topic_embed.rb | 1 + lib/post_creator.rb | 3 ++- spec/components/post_creator_spec.rb | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/models/topic_embed.rb b/app/models/topic_embed.rb index fa80087035..823ce54843 100644 --- a/app/models/topic_embed.rb +++ b/app/models/topic_embed.rb @@ -4,6 +4,7 @@ class TopicEmbed < ActiveRecord::Base belongs_to :topic belongs_to :post validates_presence_of :embed_url + validates_uniqueness_of :embed_url def self.normalize_url(url) url.downcase.sub(/\/$/, '').sub(/\-+/, '-').strip diff --git a/lib/post_creator.rb b/lib/post_creator.rb index bb9723a959..8629d1e873 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -193,7 +193,8 @@ class PostCreator # discourse post. def create_embedded_topic return unless @opts[:embed_url].present? - TopicEmbed.create!(topic_id: @post.topic_id, post_id: @post.id, embed_url: @opts[:embed_url]) + embed = TopicEmbed.new(topic_id: @post.topic_id, post_id: @post.id, embed_url: @opts[:embed_url]) + rollback_from_errors!(embed) unless embed.save end def handle_spam diff --git a/spec/components/post_creator_spec.rb b/spec/components/post_creator_spec.rb index 411e8d7155..6a5feef97f 100644 --- a/spec/components/post_creator_spec.rb +++ b/spec/components/post_creator_spec.rb @@ -559,7 +559,17 @@ describe PostCreator do title: 'Reviews of Science Ovens', raw: 'Did you know that you can use microwaves to cook your dinner? Science!') creator.create + expect(creator.errors).to be_blank expect(TopicEmbed.where(embed_url: embed_url).exists?).to eq(true) + + # If we try to create another topic with the embed url, should fail + creator = PostCreator.new(user, + embed_url: embed_url, + title: 'More Reviews of Science Ovens', + raw: 'As if anyone ever wanted to learn more about them!') + result = creator.create + expect(result).to be_present + expect(creator.errors).to be_present end end From 87ab1cef8e005f8be13935d8251e66813d1f112d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 15 Jun 2015 18:30:11 +0200 Subject: [PATCH 0102/1435] FIX: destroy optimized images that cant' be migrated to new scheme Since we can always recompute an optimized image from the original upload, there's no need to keep optimized images that are generating errors. --- app/models/optimized_image.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/optimized_image.rb b/app/models/optimized_image.rb index 2d8098f1c7..969c20bbe7 100644 --- a/app/models/optimized_image.rb +++ b/app/models/optimized_image.rb @@ -220,6 +220,8 @@ class OptimizedImage < ActiveRecord::Base end rescue => e problems << { optimized_image: optimized_image, ex: e } + # just ditch the optimized image if there was any errors + optimized_image.destroy ensure file.try(:unlink) rescue nil file.try(:close) rescue nil From 81290d7f180d84f7c198dc799dfbc80b0236d205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 15 Jun 2015 18:53:53 +0200 Subject: [PATCH 0103/1435] UX: sort staff members by username --- app/models/about.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/about.rb b/app/models/about.rb index dedf9a0ea7..34fb8eadf1 100644 --- a/app/models/about.rb +++ b/app/models/about.rb @@ -27,11 +27,13 @@ class About def moderators @moderators ||= User.where(moderator: true, admin: false) .where.not(id: Discourse::SYSTEM_USER_ID) + .order(:username_lower) end def admins @admins ||= User.where(admin: true) .where.not(id: Discourse::SYSTEM_USER_ID) + .order(:username_lower) end def stats From 76a14cd279252083d531d7a5de9b22e764a91453 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Mon, 15 Jun 2015 13:16:23 -0400 Subject: [PATCH 0104/1435] FIX: user visit stats on admin dashboard should show sum of values for 7 day and 30 day columns --- app/assets/javascripts/admin/templates/dashboard.hbs | 2 +- app/models/report.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/templates/dashboard.hbs b/app/assets/javascripts/admin/templates/dashboard.hbs index 32ce15f9c2..1d4dcf6740 100644 --- a/app/assets/javascripts/admin/templates/dashboard.hbs +++ b/app/assets/javascripts/admin/templates/dashboard.hbs @@ -54,7 +54,7 @@ {{#unless loading}} - {{admin-report-per-day-counts report=visits}} + {{admin-report-counts report=visits}} {{admin-report-counts report=signups}} {{admin-report-counts report=topics}} {{admin-report-counts report=posts}} diff --git a/app/models/report.rb b/app/models/report.rb index 37df62e23f..76f5952d55 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -76,6 +76,7 @@ class Report def self.report_visits(report) basic_report_about report, UserVisit, :by_day, report.start_date, report.end_date + add_counts report, UserVisit, 'visited_at' end def self.report_signups(report) From 357d4e3dd3e68210f8dade35946670e1facecb84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 15 Jun 2015 23:44:39 +0200 Subject: [PATCH 0105/1435] FIX: support for more than 1 emojis in the title --- app/assets/javascripts/discourse/models/topic.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index 189db87207..60b40fe63c 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -9,7 +9,7 @@ const Topic = RestModel.extend({ let title = this.get("fancy_title"); if (Discourse.SiteSettings.enable_emoji && title.indexOf(":") >= 0) { - title = title.replace(/:\S+:?/, function(m) { + title = title.replace(/:\S+:?/g, function(m) { const emoji = Discourse.Emoji.translations[m] ? Discourse.Emoji.translations[m] : m.slice(1, m.length - 1), url = Discourse.Emoji.urlFor(emoji); return url ? "" + emoji + "" : m; From d396e4eeedfd898f5136160dff16936c3f67ad17 Mon Sep 17 00:00:00 2001 From: Simon Cossar Date: Mon, 15 Jun 2015 15:24:08 -0700 Subject: [PATCH 0106/1435] set inline category-badge to display inline --- Gemfile.lock | 3 +++ app/assets/stylesheets/common/base/topic.scss | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index 4b2f5efd72..75d65a9bb8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -484,3 +484,6 @@ DEPENDENCIES uglifier unf unicorn + +BUNDLED WITH + 1.10.3 diff --git a/app/assets/stylesheets/common/base/topic.scss b/app/assets/stylesheets/common/base/topic.scss index 17956cc16d..af7f87704a 100644 --- a/app/assets/stylesheets/common/base/topic.scss +++ b/app/assets/stylesheets/common/base/topic.scss @@ -43,3 +43,22 @@ float: right; } } + +// Target the .badge-category text, the bullet icon needs to maintain `display: block` +#suggested-topics h3 .badge-wrapper.bullet span.badge-category, +#suggested-topics h3 .badge-wrapper.box span, +#suggested-topics h3 .badge-wrapper.bar span { + display: inline; +} + +#suggested-topics h3 .badge-wrapper.bullet span.badge-category, { + // Override vertical-align: text-top from `badges.css.scss` + vertical-align: baseline; +} + +#suggested-topics h3 .badge-wrapper.bullet, +#suggested-topics h3 .badge-wrapper.bullet span.badge-category-parent-bg, +#suggested-topics h3 .badge-wrapper.bullet span.badge-category-bg { + // Top of bullet aligns with top of line - adjust line height to vertically align bullet. + line-height: 0.8; +} From 7b8786e14f1385cc516fb3f8ac7dc61043211d56 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 16 Jun 2015 09:31:41 +1000 Subject: [PATCH 0107/1435] FIX: maintain category ordering when position is fixed for browsing FEATURE: allow mods to set category ordering for topic creation dialog using fixed_category_positions_on_create --- .../discourse/components/category-chooser.js.es6 | 6 +++++- app/assets/javascripts/discourse/models/category.js | 8 ++++++++ config/locales/server.en.yml | 1 + config/site_settings.yml | 5 +++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/components/category-chooser.js.es6 b/app/assets/javascripts/discourse/components/category-chooser.js.es6 index ed2b8a55ea..856bcb4d1f 100644 --- a/app/assets/javascripts/discourse/components/category-chooser.js.es6 +++ b/app/assets/javascripts/discourse/components/category-chooser.js.es6 @@ -26,7 +26,11 @@ export default ComboboxView.extend({ }.property('scopedCategoryId', 'categories'), _setCategories: function() { - this.set('categories', this.get('categories') || Discourse.Category.list()); + this.set('categories', this.get('categories') || ( + Discourse.SiteSettings.fixed_category_positions_on_create ? + Discourse.Category.list() : Discourse.Category.listByActivity() + ) + ); }.on('init'), none: function() { diff --git a/app/assets/javascripts/discourse/models/category.js b/app/assets/javascripts/discourse/models/category.js index 4f8ede69e7..8deb5cb0c3 100644 --- a/app/assets/javascripts/discourse/models/category.js +++ b/app/assets/javascripts/discourse/models/category.js @@ -216,6 +216,14 @@ Discourse.Category.reopenClass({ }, list: function() { + if (Discourse.SiteSettings.fixed_category_positions) { + return Discourse.Site.currentProp('categories'); + } else { + return Discourse.Site.currentProp('sortedCategories'); + } + }, + + listByActivity: function() { return Discourse.Site.currentProp('sortedCategories'); }, diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 73ec8e4c5b..5206eb38d2 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -768,6 +768,7 @@ en: category_featured_topics: "Number of topics displayed per category on the /categories page. After changing this value, it takes up to 15 minutes for the categories page to update." show_subcategory_list: "Show subcategory list instead of topic list when entering a category." fixed_category_positions: "If checked, you will be able to arrange categories into a fixed order. If unchecked, categories are listed in order of activity." + fixed_category_positions_on_create: "If checked, category ordering will be maintained on topic creation dialog (requires fixed_category_positions)." add_rel_nofollow_to_user_content: "Add rel nofollow to all submitted user content, except for internal links (including parent domains). If you change this, you must rebake all posts with: \"rake posts:rebake\"" exclude_rel_nofollow_domains: "A list of domains where nofollow should not be added to links. tld.com will automatically allow sub.tld.com as well. As a minimum, you should add the top-level domain of this site to help web crawlers find all content. If other parts of your website are at other domains, add those too." diff --git a/config/site_settings.yml b/config/site_settings.yml index c1c79722f2..7f21b1a2c3 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -168,6 +168,11 @@ basic: fixed_category_positions: client: true default: false + + fixed_category_positions_on_create: + client: true + default: false + show_subcategory_list: default: false client: true From 9b8b1d0034a766709023f73508282a68e57cec1d Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 16 Jun 2015 09:54:44 +1000 Subject: [PATCH 0108/1435] FEATURE: add special header that names the action for the request --- app/controllers/application_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7b3534046a..962d3a3867 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -145,6 +145,7 @@ class ApplicationController < ActionController::Base if current_user Logster.add_to_env(request.env,"username",current_user.username) end + response.headers["Discourse-Route"] = "#{controller_name}/#{action_name}" end def set_locale From 690f4a4c374d59a1e77946dcc547765d9987a447 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 16 Jun 2015 10:27:42 +1000 Subject: [PATCH 0109/1435] add X so it shows up at the end of chrome --- app/controllers/application_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 962d3a3867..a1a223ff10 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -145,7 +145,7 @@ class ApplicationController < ActionController::Base if current_user Logster.add_to_env(request.env,"username",current_user.username) end - response.headers["Discourse-Route"] = "#{controller_name}/#{action_name}" + response.headers["X-Discourse-Route"] = "#{controller_name}/#{action_name}" end def set_locale From 1f9761e85d0a9ac93c05984eeb8041389e9fc166 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 16 Jun 2015 10:30:06 +1000 Subject: [PATCH 0110/1435] FEATURE: add a header to denote an anonymous req was cached (X-Discourse-Cached) --- lib/middleware/anonymous_cache.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/middleware/anonymous_cache.rb b/lib/middleware/anonymous_cache.rb index d315c53a50..a65eb2df1a 100644 --- a/lib/middleware/anonymous_cache.rb +++ b/lib/middleware/anonymous_cache.rb @@ -89,6 +89,7 @@ module Middleware if status == 200 && cache_duration headers_stripped = headers.dup.delete_if{|k, _| ["Set-Cookie","X-MiniProfiler-Ids"].include? k} + headers_stripped["X-Discourse-Cached"] = "true" parts = [] response.each do |part| parts << part From 771eeea837527acf2634154e773d04b66f5c08a0 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 16 Jun 2015 10:53:28 +1000 Subject: [PATCH 0111/1435] fix spec --- spec/components/middleware/anonymous_cache_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/components/middleware/anonymous_cache_spec.rb b/spec/components/middleware/anonymous_cache_spec.rb index b562f344b7..8bff0ae331 100644 --- a/spec/components/middleware/anonymous_cache_spec.rb +++ b/spec/components/middleware/anonymous_cache_spec.rb @@ -52,11 +52,11 @@ describe Middleware::AnonymousCache::Helper do helper = new_helper("ANON_CACHE_DURATION" => 10) helper.is_mobile = true - expect(helper.cached).to eq([200, {"HELLO" => "WORLD"}, ["hello my world"]]) + expect(helper.cached).to eq([200, {"X-Discourse-Cached" => "true", "HELLO" => "WORLD"}, ["hello my world"]]) expect(crawler.cached).to eq(nil) crawler.cache([200, {"HELLO" => "WORLD"}, ["hello ", "world"]]) - expect(crawler.cached).to eq([200, {"HELLO" => "WORLD"}, ["hello world"]]) + expect(crawler.cached).to eq([200, {"X-Discourse-Cached" => "true", "HELLO" => "WORLD"}, ["hello world"]]) end end From 0e4883a8aee9757aebfa4c76f275d501db453103 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 16 Jun 2015 11:16:33 +1000 Subject: [PATCH 0112/1435] correct regression where monitoring thread crashed out add logging --- lib/demon/base.rb | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/demon/base.rb b/lib/demon/base.rb index 0a7f201726..6fb162faad 100644 --- a/lib/demon/base.rb +++ b/lib/demon/base.rb @@ -50,9 +50,10 @@ class Demon::Base "#{Rails.root}/tmp/pids/#{self.class.prefix}_#{@index}.pid" end - def alive? - if @pid - Demon::Base.alive?(@pid) + def alive?(pid=nil) + pid ||= @pid + if pid + Demon::Base.alive?(pid) else false end @@ -145,12 +146,10 @@ class Demon::Base end def self.alive?(pid) - begin - Process.kill(0, pid) - true - rescue - false - end + Process.kill(0, pid) + true + rescue + false end private @@ -169,10 +168,14 @@ class Demon::Base def monitor_parent Thread.new do while true - unless alive?(@parent_pid) - Process.kill "TERM", Process.pid - sleep 10 - Process.kill "KILL", Process.pid + begin + unless alive?(@parent_pid) + Process.kill "TERM", Process.pid + sleep 10 + Process.kill "KILL", Process.pid + end + rescue => e + STDERR.puts "URGENT monitoring thread had an exception #{e}" end sleep 1 end From a6ac368476997748e0cb7ca1f3372f2d68a1a38b Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 16 Jun 2015 11:37:08 +1000 Subject: [PATCH 0113/1435] FEATURE: add a custom log format for better analysis --- config/nginx.sample.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/nginx.sample.conf b/config/nginx.sample.conf index 838e98b9cb..f81265a7bf 100644 --- a/config/nginx.sample.conf +++ b/config/nginx.sample.conf @@ -25,8 +25,12 @@ map $http_x_forwarded_proto $thescheme { https https; } +log_format log_discourse '[$time_local] $remote_addr $request $sent_http_x_discourse_route $upstream_response_time $request_time'; + server { + access_log /var/log/nginx/access.log log_discourse; + listen 80; gzip on; gzip_vary on; From 96d3d6fb63c2be111d3f3093bc5030055f5a0fa9 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 16 Jun 2015 16:01:51 +1000 Subject: [PATCH 0114/1435] update logster --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 75d65a9bb8..154d660bc2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -147,7 +147,7 @@ GEM thor (~> 0.15) libv8 (3.16.14.7) listen (0.7.3) - logster (0.8.2) + logster (0.8.3) lru_redux (1.1.0) mail (2.5.4) mime-types (~> 1.16) From 4628b06fd60e9bef7162cb1051ae5ee5f4bc109d Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Tue, 16 Jun 2015 11:38:48 +0530 Subject: [PATCH 0115/1435] FIX: use session instead of cookie to preserve url --- app/assets/javascripts/discourse/controllers/login.js.es6 | 7 +++++-- app/assets/javascripts/discourse/routes/new-topic.js.es6 | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/login.js.es6 b/app/assets/javascripts/discourse/controllers/login.js.es6 index 6ede862f1d..7db0f9b3aa 100644 --- a/app/assets/javascripts/discourse/controllers/login.js.es6 +++ b/app/assets/javascripts/discourse/controllers/login.js.es6 @@ -76,13 +76,16 @@ export default DiscourseController.extend(ModalFunctionality, { // Trigger the browser's password manager using the hidden static login form: var $hidden_login_form = $('#hidden-login-form'); var destinationUrl = $.cookie('destination_url'); + var shouldRedirectToUrl = self.session.get("shouldRedirectToUrl"); $hidden_login_form.find('input[name=username]').val(self.get('loginName')); $hidden_login_form.find('input[name=password]').val(self.get('loginPassword')); - if ((self.get('loginRequired') || $.cookie('shouldRedirectToUrl')) && destinationUrl) { + if (self.get('loginRequired') && destinationUrl) { // redirect client to the original URL $.cookie('destination_url', null); - $.cookie('shouldRedirectToUrl', null); $hidden_login_form.find('input[name=redirect]').val(destinationUrl); + } else if (shouldRedirectToUrl) { + self.session.set("shouldRedirectToUrl", null); + $hidden_login_form.find('input[name=redirect]').val(shouldRedirectToUrl); } else { $hidden_login_form.find('input[name=redirect]').val(window.location.href); } diff --git a/app/assets/javascripts/discourse/routes/new-topic.js.es6 b/app/assets/javascripts/discourse/routes/new-topic.js.es6 index 9c26ce5c8c..d27d6a2363 100644 --- a/app/assets/javascripts/discourse/routes/new-topic.js.es6 +++ b/app/assets/javascripts/discourse/routes/new-topic.js.es6 @@ -13,7 +13,7 @@ export default Discourse.Route.extend({ }); } else { // User is not logged in - $.cookie('shouldRedirectToUrl', true); + self.session.set("shouldRedirectToUrl", window.location.href); self.replaceWith('login'); } } From 4007484c54b07d1f33a054d3c020a12c54dd7e42 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 16 Jun 2015 16:43:43 +1000 Subject: [PATCH 0116/1435] also log user agent in NGINX --- config/nginx.sample.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/nginx.sample.conf b/config/nginx.sample.conf index f81265a7bf..ec81991638 100644 --- a/config/nginx.sample.conf +++ b/config/nginx.sample.conf @@ -25,7 +25,7 @@ map $http_x_forwarded_proto $thescheme { https https; } -log_format log_discourse '[$time_local] $remote_addr $request $sent_http_x_discourse_route $upstream_response_time $request_time'; +log_format log_discourse '[$time_local] $remote_addr "$request" "$http_user_agent" "$sent_http_x_discourse_route" $upstream_response_time $request_time'; server { From dc563b4484598a5f9e53562e81c4fd3561259693 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 16 Jun 2015 17:41:21 +1000 Subject: [PATCH 0117/1435] improve log format --- config/nginx.sample.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/nginx.sample.conf b/config/nginx.sample.conf index ec81991638..36acdb4ec5 100644 --- a/config/nginx.sample.conf +++ b/config/nginx.sample.conf @@ -25,7 +25,7 @@ map $http_x_forwarded_proto $thescheme { https https; } -log_format log_discourse '[$time_local] $remote_addr "$request" "$http_user_agent" "$sent_http_x_discourse_route" $upstream_response_time $request_time'; +log_format log_discourse '[$time_local] $remote_addr "$request" "$http_user_agent" "$sent_http_x_discourse_route" $status $bytes_sent "$http_referer" $upstream_response_time $request_time'; server { From f26eee8431b29b76e05870b6e4edf1122f46baff Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 16 Jun 2015 17:43:36 +1000 Subject: [PATCH 0118/1435] FEATURE: add username to NGINX logs --- app/controllers/application_controller.rb | 1 + config/nginx.sample.conf | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index a1a223ff10..bf9e764b39 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -144,6 +144,7 @@ class ApplicationController < ActionController::Base def set_current_user_for_logs if current_user Logster.add_to_env(request.env,"username",current_user.username) + response.headers["X-Discourse-Username"] = current_user.username end response.headers["X-Discourse-Route"] = "#{controller_name}/#{action_name}" end diff --git a/config/nginx.sample.conf b/config/nginx.sample.conf index 36acdb4ec5..df8368736e 100644 --- a/config/nginx.sample.conf +++ b/config/nginx.sample.conf @@ -25,7 +25,7 @@ map $http_x_forwarded_proto $thescheme { https https; } -log_format log_discourse '[$time_local] $remote_addr "$request" "$http_user_agent" "$sent_http_x_discourse_route" $status $bytes_sent "$http_referer" $upstream_response_time $request_time'; +log_format log_discourse '[$time_local] $remote_addr "$request" "$http_user_agent" "$sent_http_x_discourse_route" $status $bytes_sent "$http_referer" $upstream_response_time $request_time "sent_http_x_discourse_username"'; server { From 01a23203c7dde1e857ff5552a3097be438260abf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Tue, 16 Jun 2015 10:15:42 +0200 Subject: [PATCH 0119/1435] FIX: slightly improve emoji support in titles (no need for spaces between emojis) --- app/assets/javascripts/discourse/models/topic.js.es6 | 2 +- test/javascripts/models/topic-test.js.es6 | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index 60b40fe63c..1dfd002d34 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -9,7 +9,7 @@ const Topic = RestModel.extend({ let title = this.get("fancy_title"); if (Discourse.SiteSettings.enable_emoji && title.indexOf(":") >= 0) { - title = title.replace(/:\S+:?/g, function(m) { + title = title.replace(/:[^\s:]+:?/g, function(m) { const emoji = Discourse.Emoji.translations[m] ? Discourse.Emoji.translations[m] : m.slice(1, m.length - 1), url = Discourse.Emoji.urlFor(emoji); return url ? "" + emoji + "" : m; diff --git a/test/javascripts/models/topic-test.js.es6 b/test/javascripts/models/topic-test.js.es6 index 927572e3b0..c5a2e578fc 100644 --- a/test/javascripts/models/topic-test.js.es6 +++ b/test/javascripts/models/topic-test.js.es6 @@ -73,6 +73,8 @@ test("recover", function() { }); test('fancyTitle', function() { - var topic = Topic.create({ fancy_title: ":smile: with all the emojis" }); - equal(topic.get('fancyTitle'), "smile with all the emojis", "supports emojis"); + var topic = Topic.create({ fancy_title: ":smile: with all :) the emojis :pear::peach:" }); + equal(topic.get('fancyTitle'), + "smile with all smile the emojis pearpeach", + "supports emojis"); }); From db274c7969ac86de913b1d95b35ab2721bfe1b63 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 16 Jun 2015 19:30:15 +1000 Subject: [PATCH 0120/1435] missing $ --- config/nginx.sample.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/nginx.sample.conf b/config/nginx.sample.conf index df8368736e..5e436fcf24 100644 --- a/config/nginx.sample.conf +++ b/config/nginx.sample.conf @@ -25,7 +25,7 @@ map $http_x_forwarded_proto $thescheme { https https; } -log_format log_discourse '[$time_local] $remote_addr "$request" "$http_user_agent" "$sent_http_x_discourse_route" $status $bytes_sent "$http_referer" $upstream_response_time $request_time "sent_http_x_discourse_username"'; +log_format log_discourse '[$time_local] $remote_addr "$request" "$http_user_agent" "$sent_http_x_discourse_route" $status $bytes_sent "$http_referer" $upstream_response_time $request_time "$sent_http_x_discourse_username"'; server { From 0bb78ff53ce6a4f9c5f58d177375a21d827b5a29 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 17 Jun 2015 15:06:42 +1000 Subject: [PATCH 0121/1435] Work in progress, performance report --- script/nginx_analyze.rb | 121 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 script/nginx_analyze.rb diff --git a/script/nginx_analyze.rb b/script/nginx_analyze.rb new file mode 100644 index 0000000000..f83d6425d2 --- /dev/null +++ b/script/nginx_analyze.rb @@ -0,0 +1,121 @@ +class LogAnalyzer + + class LineParser + + + # log_format log_discourse '[$time_local] $remote_addr "$request" "$http_user_agent" "$sent_http_x_discourse_route" $status $bytes_sent "$http_referer" $upstream_response_time $request_time "$sent_http_x_discourse_username"'; + + attr_accessor :time, :ip_address, :url, :route, :user_agent, :rails_duration, :total_duration, + :username, :status, :bytes_sent, :referer + + PATTERN = /\[(.*)\] (\S+) \"(.*)\" \"(.*)\" \"(.*)\" ([0-9]+) ([0-9]+) \"(.*)\" ([0-9.]+) ([0-9.]+) "(.*)"/ + + def self.parse(line) + result = new + _, result.time, result.ip_address, result.url, result.user_agent, + result.route, result.status, result.bytes_sent, result.referer, + result.rails_duration, result.total_duration, result.username = line.match(PATTERN).to_a + + result.rails_duration = result.rails_duration.to_f + result.total_duration = result.total_duration.to_f + + result + end + end + + attr_reader :total_requests, :message_bus_requests, :filename, + :ip_to_rails_duration, :username_to_rails_duration, + :route_to_rails_duration, :url_to_rails_duration, + :status_404_to_count + + def self.analyze(filename) + new(filename).analyze + end + + def initialize(filename) + @filename = filename + @ip_to_rails_duration = Hash.new(0) + @username_to_rails_duration = Hash.new(0) + @route_to_rails_duration = Hash.new(0) + @url_to_rails_duration = Hash.new(0) + @status_404_to_count = Hash.new(0) + end + + def analyze + @total_requests = 0 + @message_bus_requests = 0 + File.open(@filename).each_line do |line| + @total_requests += 1 + parsed = LineParser.parse(line) + + if parsed.url =~ /(POST|GET) \/message-bus/ + @message_bus_requests += 1 + next + end + + @ip_to_rails_duration[parsed.ip_address] += parsed.rails_duration + + username = parsed.username == "-" ? "[Anonymous]" : parsed.username + @username_to_rails_duration[username] += parsed.rails_duration + + @route_to_rails_duration[parsed.route] += parsed.rails_duration + + @url_to_rails_duration[parsed.url] += parsed.rails_duration + + @status_404_to_count[parsed.url] += 1 if parsed.status == "404" + end + self + end + +end + +filename = ARGV[0] || "/var/log/nginx/access.log" +analyzer = LogAnalyzer.analyze(filename) + +SPACER = "-" * 80 + +def top(cols, hash, count) + sorted = hash.sort{|a,b| b[1] <=> a[1]}.first(30) + + longest_0 = [cols[0].length, sorted.map{|a,b| a.to_s.length}.max ].max + + puts "#{cols[0].ljust(longest_0)} #{cols[1]}" + puts "#{("-"*(cols[0].length)).ljust(longest_0)} #{"-"*cols[1].length}" + + sorted.each do |val, duration| + next unless val && val.length > 1 + n = Fixnum === duration ? duration : '%.2f' % duration + puts "#{val.to_s.ljust(longest_0)} #{n.to_s.rjust(cols[1].length)}" + end +end + +puts +puts "Analyzed: #{analyzer.filename}" +puts SPACER +puts "Total Requests: #{analyzer.total_requests} ( MessageBus: #{analyzer.message_bus_requests} )" +puts SPACER +puts "Top 30 IPs by Server Load" +puts +top(["IP Address", "Duration"], analyzer.ip_to_rails_duration, 30) +puts SPACER +puts +puts "Top 30 users by Server Load" +puts +top(["Username", "Duration"], analyzer.username_to_rails_duration, 30) +puts SPACER +puts +puts "Top 30 routes by Server Load" +puts +top(["Route", "Duration"], analyzer.route_to_rails_duration, 30) +puts SPACER +puts +puts "Top 30 urls by Server Load" +puts +top(["Url", "Duration"], analyzer.url_to_rails_duration, 30) + +puts "(all durations in seconds)" +puts SPACER +puts +puts "Top 30 not found urls (404s)" +puts +top(["Url", "Count"], analyzer.status_404_to_count, 30) From 1aa7cf842a9a1e98a4b4cf0101ff470f92f0d2f1 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 17 Jun 2015 18:05:52 +1000 Subject: [PATCH 0122/1435] PERF: missing index on notifications this was slowing down acting on a post --- db/migrate/20150617080349_add_index_on_post_notifications.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 db/migrate/20150617080349_add_index_on_post_notifications.rb diff --git a/db/migrate/20150617080349_add_index_on_post_notifications.rb b/db/migrate/20150617080349_add_index_on_post_notifications.rb new file mode 100644 index 0000000000..a8cbccfdc8 --- /dev/null +++ b/db/migrate/20150617080349_add_index_on_post_notifications.rb @@ -0,0 +1,5 @@ +class AddIndexOnPostNotifications < ActiveRecord::Migration + def change + add_index :notifications, [:user_id, :topic_id, :post_number] + end +end From 56b9528de8c7b1871e89dc3893b1301f28c1ca96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 17 Jun 2015 15:38:45 +0200 Subject: [PATCH 0123/1435] FIX: catch the PG::UniqueViolation exception too --- app/jobs/regular/automatic_group_membership.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/regular/automatic_group_membership.rb b/app/jobs/regular/automatic_group_membership.rb index 0b4488371a..ae524685cf 100644 --- a/app/jobs/regular/automatic_group_membership.rb +++ b/app/jobs/regular/automatic_group_membership.rb @@ -14,7 +14,7 @@ module Jobs domains = group.automatic_membership_email_domains.gsub('.', '\.') User.where("email ~* '@(#{domains})$'").find_each do |user| - group.add(user) rescue ActiveRecord::RecordNotUnique + group.add(user) rescue ActiveRecord::RecordNotUnique, PG::UniqueViolation end end From 874b2a628d93333a5152656787e50e9e516a159c Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 17 Jun 2015 11:09:19 -0400 Subject: [PATCH 0124/1435] FIX: `plugin-outlet` should do view injections --- .../discourse/helpers/plugin-outlet.js.es6 | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 b/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 index 18ae57fa1c..fc385a4491 100644 --- a/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 +++ b/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 @@ -99,6 +99,20 @@ function buildConnectorCache() { }); } +var _viewInjections; +function viewInjections(container) { + if (_viewInjections) { return _viewInjections; } + + const injections = container._registry.getTypeInjections('view'); + + _viewInjections = {}; + injections.forEach(function(i) { + _viewInjections[i.property] = container.lookup(i.fullName); + }); + + return _viewInjections; +} + Ember.HTMLBars._registerHelper('plugin-outlet', function(params, hash, options, env) { const connectionName = params[0]; @@ -112,7 +126,7 @@ Ember.HTMLBars._registerHelper('plugin-outlet', function(params, hash, options, const viewClass = (childViews.length > 1) ? Ember.ContainerView : childViews[0]; delete options.fn; // we don't need the default template since we have a connector - env.helpers.view.helperFunction.call(this, [viewClass], hash, options, env); + env.helpers.view.helperFunction.call(this, [viewClass], viewInjections(env.data.view.container), options, env); const cvs = env.data.view._childViews; if (childViews.length > 1 && cvs && cvs.length) { From 3db743640e3733b77c601e05444e48a84f52a9d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 17 Jun 2015 17:45:53 +0200 Subject: [PATCH 0125/1435] fix the build :fired: --- app/jobs/regular/automatic_group_membership.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/jobs/regular/automatic_group_membership.rb b/app/jobs/regular/automatic_group_membership.rb index ae524685cf..b849b018b2 100644 --- a/app/jobs/regular/automatic_group_membership.rb +++ b/app/jobs/regular/automatic_group_membership.rb @@ -14,7 +14,11 @@ module Jobs domains = group.automatic_membership_email_domains.gsub('.', '\.') User.where("email ~* '@(#{domains})$'").find_each do |user| - group.add(user) rescue ActiveRecord::RecordNotUnique, PG::UniqueViolation + begin + group.add(user) + rescue ActiveRecord::RecordNotUnique, PG::UniqueViolation + # we don't care about this + end end end From 6148ae8775df998ec06003b9e85cd916b6a1b8cb Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Wed, 17 Jun 2015 22:05:22 +0530 Subject: [PATCH 0126/1435] Update Translations --- config/locales/client.ar.yml | 15 --- config/locales/client.bs_BA.yml | 15 --- config/locales/client.cs.yml | 17 --- config/locales/client.da.yml | 17 --- config/locales/client.de.yml | 38 +++--- config/locales/client.es.yml | 30 ++--- config/locales/client.fi.yml | 17 --- config/locales/client.fr.yml | 17 --- config/locales/client.he.yml | 17 --- config/locales/client.it.yml | 15 --- config/locales/client.nb_NO.yml | 22 +--- config/locales/client.pl_PL.yml | 32 ++--- config/locales/client.pt_BR.yml | 17 --- config/locales/client.ru.yml | 23 +--- config/locales/client.sv.yml | 112 +++++++++++++++-- config/locales/client.te.yml | 12 -- config/locales/client.tr_TR.yml | 143 ++++++++++------------ config/locales/client.zh_CN.yml | 17 --- config/locales/server.cs.yml | 2 +- config/locales/server.de.yml | 31 +---- config/locales/server.fr.yml | 35 ------ config/locales/server.nl.yml | 13 -- config/locales/server.pt.yml | 53 ++------ config/locales/server.ro.yml | 44 +++---- config/locales/server.sv.yml | 50 ++++++++ config/locales/server.tr_TR.yml | 37 +----- plugins/poll/config/locales/client.da.yml | 2 + plugins/poll/config/locales/client.sv.yml | 37 +++++- plugins/poll/config/locales/server.da.yml | 3 + 29 files changed, 350 insertions(+), 533 deletions(-) diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index 6379bc65eb..3e27e04743 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -425,9 +425,7 @@ ar: github_profile: "Github" mailing_list_mode: "استقبال بريد الكتروني لكل مشاركة جديدة (بإسثناء لو قمت بكتم الموضوع او التصنيف)" watched_categories: "متابع" - watched_categories_instructions: "تلقائيا ستقوم بمتابعة جميع المواضيع في هذا التصنيف , وسيتم اشعارك لجميع المشاركات والمواضيع وكذلك عداد الغيرمقروء والمشاركات الجديدة" tracked_categories: "Tracked" - tracked_categories_instructions: "تلقائيا ستقوم بمتابعة جميع المواضيع في هذا التصنيف , وسيتم اشعارك لجميع المشاركات والمواضيع وكذلك عداد الغيرمقروء والمشاركات الجديدة" muted_categories: "كتم" muted_categories_instructions: "تلقائيا ستقوم بمتابعة جميع المواضيع في هذا التصنيف , وسيتم اشعارك لجميع المشاركات والمواضيع وكذلك عداد الغيرمقروء والمشاركات الجديدة" delete_account: "حذف الحساب" @@ -833,8 +831,6 @@ ar: from_the_web: "عن طريق الويب" remote_tip: "رابط لصورة" remote_tip_with_attachments: "رابط لصورة أو ملف ({{authorized_extensions}})" - local_tip: "اضغط لاختيار صورة من جهازك" - local_tip_with_attachments: "اضغط لاختيار صورة أو ملف من جهازك ({{authorized_extensions}})" hint: "(تستطيع أيضا أن تسحب و تفلت ملف أو صورة في المحرر لرفعه)" hint_for_supported_browsers: "(تستطيع أيضا أن تسحب وتفلت صورة أو تلصقها في المحرر لرفعها)" uploading: "يتم الرفع" @@ -999,29 +995,21 @@ ar: '2_4': 'سيصلك إشعارات لأنك أضفت مشاركة لهذا الموضوع.' '2_2': 'سيصلك إشعارات لأنك اخترت متابعة الموضوع' '2': 'سيصلك اشعار بسبب انك قراءة هذا الموضوع.' - '1_2': '.سيتم إشعارك إذا ذكر أحد ما اسمك أو رد على مشاركاتك' - '1': '.سيتم إشعارك إذا ذكر أحد ما اسمك أو رد على مشاركاتك' '0_7': 'لن يصلك أي إشعار يخص هذا التصنيف بناء على طلبك.' '0_2': 'لن يصلك أي إشعار يخص هذا الموضوع بناء على طلبك.' '0': 'لن يصلك أي إشعار يخص هذا الموضوع بناء على طلبك.' watching_pm: title: "تحت المتابعة" - description: "سيتم إشعارك بأية رد على هذه الرسالة الخاصة. عدد الرسائل الغير مقروءة و الجديدة ستظهر بجانب موضوع الرسالة." watching: title: "تحت المتابعة" - description: "سيتم إشعارك بأية رد على هذه الرسالة الخاصة. عدد الرسائل الغير مقروءة و الجديدة ستظهر بجانب الرسالة." tracking_pm: title: "متتبعة" - description: "عدد المشاركات الغير مقروءة و الجديدة سيظهر بجانب هذه الرسالة الخاصة. سيصلك إشعار إذا ذكرك أحد ما أو رد على مشاركتك." tracking: title: "تحت المتابعة" - description: "عدد المشاركات الغير مقروءة و الجديدة سيظهر بجانب هذا الموضوع. سيصلك إشعار إذا ذكرك أحد ما أو رد على مشاركتك." regular: title: "منتظم" - description: ".سيتم إشعارك إذا ذكر أحد ما اسمك أو رد على مشاركاتك" regular_pm: title: "منتظم" - description: "سيتم إشعارك إذا ذكر اسمك أحد ما أو رد على مشاركتك في الرسالة الخاصة." muted_pm: title: "كتم" description: "لن يتم إشعارك بأي جديد يخص هذه الرسالة الخاصة." @@ -1267,13 +1255,10 @@ ar: notifications: watching: title: "مشاهده " - description: "ستتابع بشكل تلقائي جديد هذه التصنيفات. سيتم إشعارك بكل مشاركة او موضوع، بالإضافة لذلك سيتم عرض عدد المشاركات الغير مقروءة و الجديدة بجانب الموضوع." tracking: title: "تتبع " - description: "ستتابع بشكل تلقائي جديد هذه التصنيفات. سيتم إشعارك بكل مشاركة او موضوع، بالإضافة لذلك سيتم عرض عدد المشاركات الغير مقروءة و الجديدة بجانب الموضوع." regular: title: "طبيعي" - description: ".سيتم إشعارك إذا ذكر أحد ما اسمك أو رد على مشاركاتك" muted: title: "كتم" description: "لن يتم إشعارك بأي جديد يخص هذا الموضوع ولن يظهرهذا الموضوع في تبويب المواضيع الغير مقروءة." diff --git a/config/locales/client.bs_BA.yml b/config/locales/client.bs_BA.yml index d6e30d9846..7b75d5a126 100644 --- a/config/locales/client.bs_BA.yml +++ b/config/locales/client.bs_BA.yml @@ -204,9 +204,7 @@ bs_BA: suspended_reason: "Reason: " mailing_list_mode: "Receive an email for every new post (unless you mute the topic or category)" watched_categories: "Watched" - watched_categories_instructions: "You will automatically watch all new topics in these categories. You will be notified of all new posts and topics, plus the count of unread and new posts will also appear next to the topic's listing." tracked_categories: "Tracked" - tracked_categories_instructions: "You will automatically track all new topics in these categories. A count of unread and new posts will appear next to the topic." muted_categories: "Muted" muted_categories_instructions: "You will not be notified of anything about new topics in these categories, and they will not appear on your unread tab." delete_account: "Delete My Account" @@ -560,8 +558,6 @@ bs_BA: from_the_web: "Sa neta" remote_tip: "link do slike http://primjer.com/slika.jpg" remote_tip_with_attachments: "link to image or file http://primjer.com/file.ext (allowed extensions: {{authorized_extensions}})." - local_tip: "klikni da odabereš sliku sa svog uređaja" - local_tip_with_attachments: "klikni da odabereš sliku ili fajl sa svog uređaja (dozvoljene ekstenzije: {{authorized_extensions}})" hint: "(možete i mišom prenijeti vaše slike direktno iz vašeg foldera ovdje)" hint_for_supported_browsers: "(možete i mišom prenijeti vaše slike direktno iz vašeg foldera ovdje a možete i copy/paste sliku)" uploading: "Uplodujem" @@ -672,29 +668,21 @@ bs_BA: '2_4': 'Dobijat ćete notifikacije zato što ste ostavili odgovor na ovoj temi.' '2_2': 'Dobijat ćete notifikacije zato što pratite ovu temu.' '2': 'Dobijat ćete notifikacije zato što pročitao ovu temu.' - '1_2': 'Dobijat ćete notifikacije kada neko spomene @name ili odgovori na vaš post.' - '1': 'Dobijat ćete notifikacije samo kada neko spomene @name ili odgovori na vaš post.' '0_7': 'Ignorišete sve notifikacije u ovoj kategoriji.' '0_2': 'Ignorišete sve notifikacije u ovoj temi.' '0': 'Ignorišete sve notifikacije u ovoj temi.' watching_pm: title: "Motrenje" - description: "You will be notified of every new post in this private message. A count of unread and new posts will also appear next to the topic." watching: title: "Motrenje" - description: "Stizat će vam obavještenja za svaki novi post u ovoj temi. Takođe broj novih i nepročitanih postova će stajati pored teme." tracking_pm: title: "Praćenje" - description: "Broj novih i nepročitanih postova će stajati pored teme. Dobijat ćete obaviješteni ako neko spomene vaš @nadimak or ili ako odgovori na vaš post." tracking: title: "Praćenje" - description: "Broj novih i nepročitanih postova će stajati pored teme. Dobijat ćete obaviješteni samo ako neko spomene vaš @nadimak ili ako odgovori na vaš post." regular: title: "Regularno" - description: "Bit ćete obaviješteni ako neko spomene vaš @nadimak ili ako odgovori na vaš post." regular_pm: title: "Regularno" - description: "You will be notified only if someone mentions your @name or replies to your post in the private message." muted_pm: title: "Mutirano" description: "You will never be notified of anything about this private message." @@ -940,13 +928,10 @@ bs_BA: notifications: watching: title: "Motrenje" - description: "You will automatically watch all new topics in these categories. You will be notified of all new posts and topics, plus the count of unread and new posts will also appear next to the topic." tracking: title: "Praćenje" - description: "You will automatically track all new topics in these categories. A count of unread and new posts will appear next to the topic." regular: title: "Regularno" - description: "You will be notified only if someone mentions your @name or replies to your post." muted: title: "Mutirano" description: "You will not be notified of anything about new topics in these categories, and they will not appear on your unread tab." diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index 5eaf7317ca..d4e65745dc 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -373,9 +373,7 @@ cs: github_profile: "Github" mailing_list_mode: "Upozornit emailem na každý nový příspěvek (kromě ztišených témat a kategorií)." watched_categories: "Hlídané" - watched_categories_instructions: "Nová témata v těchto kategoriích budou hlídaná. Na všechny nové příspěvky budete upozorněni." tracked_categories: "Sledované" - tracked_categories_instructions: "Všechna nová témata v této kategorii budou automaticky Hlídaná. Počet nepřečtených a nových příspěvků se zobrazí vedle tématu." muted_categories: "Ztišené" muted_categories_instructions: "Nebudete upozorněni na žádná nová témata v těchto kategoriích a ani se nebudou zobrazovat jako nepřečtené." delete_account: "Smazat můj účet" @@ -791,8 +789,6 @@ cs: from_the_web: "Z webu" remote_tip: "odkaz na obrázek" remote_tip_with_attachments: "odkaz na obrázek nebo osubor ({{authorized_extensions}})" - local_tip: "klikněte sem pro výběr obrázku z vašeho zařízení." - local_tip_with_attachments: "klikněte pro vybrání obrázku nebo souboru z vašeho zařízení ({{authorized_extensions}})" hint: "(můžete také rovnou soubor do editoru přetáhnout)" hint_for_supported_browsers: "(můžete také rovnou obrázky do editoru přetáhnout)" uploading: "Nahrávám" @@ -937,29 +933,21 @@ cs: '2_4': 'Budete dostávat oznámení, protože jste zaslal odpověď do tohoto tématu.' '2_2': 'Budete dostávat oznámení, protože sledujete toto téma.' '2': 'Budete dostávat oznámení, protože jste četli 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.' '0_2': 'Ignorujete všechna oznámení z tohoto tématu.' '0': 'Ignorujete všechna oznámení z tohoto tématu.' watching_pm: title: "Hlídání" - description: "Budete informováni o každém novém příspěvku v této zprávě. Vedle názvu tématu se objeví počet nepřečtených a nových příspěvků." watching: title: "Hlídané" - description: "Budete informováni o každém novém příspěvku v tomto tématu. Vedle názvu tématu se objeví počet nepřečtených a nových příspěvků." tracking_pm: title: "Sledování" - description: "Počet nepřečtených a nových příspěvků se objeví vedle zprávy. Budete upozorněni, pokud někdo zmíní vaše @jméno nebo odpoví na váš příspěvek." tracking: title: "Sledované" - description: "U tématu se zobrazí počet nepřečtených a nových příspěvků. Budete uporozněni když vás někdo zmíní jako @jmeno nebo odpoví na váš příspěvek." regular: title: "Klasicky" - description: "Budete informováni pokud někdo zmíní vaše @jméno nebo odpoví na váš příspěvek." regular_pm: title: "Normální" - description: "Budete upozorněni, když vás někdo zmíní vaše @jmeno nebo odpoví na váš příspěvek pomocí zprávy." muted_pm: title: "Ztišení" description: "Nikdy nedostanete oznámení týkající se čehokoliv v této zprávě." @@ -1043,8 +1031,6 @@ cs: sso_enabled: "Zadej uživatelské jméno člověka, kterého chceš pozvat do tohoto tématu." to_topic_blank: "Zadej uživatelské jméno a email člověka, kterého chceš pozvat do tohoto tématu." to_topic_email: "Zadal jste emailovou adresu. Pošleme na ni pozvánku, s jejíž pomocí bude moci váš kamarád ihned odpovědět do tohoto tématu." - to_topic_username: "Zadali jste uživatelské jméno. Zašleme mu oznámení s pozváním do tohoto tématu." - to_username: "Vložte uživatelské jméno osoby kterou chcete pozvat. Zašleme jí upozornění s pozvánkou do tohoto tématu." email_placeholder: 'jmeno@priklad.cz' success_email: "Zaslali jsme pozvánku na {{emailOrUsername}}. Upozorníme vás až bude pozvánka použita. Své pozvánky můžete sledovat v tabulce pozvánek na svém uživatelském profilu." success_username: "Pozvali jsme zadaného uživatele, aby se zúčastnil tématu." @@ -1358,13 +1344,10 @@ cs: notifications: watching: title: "Hlídání" - description: "Budete automaticky sledovat všechna nová témata v těchto kategoriích. Na všechny nové příspěvky budete upozorněni. Počet nepřečtených a nových příspěvků se zobrazí vedle tématu." tracking: title: "Sledování" - description: "Všechna nová témata v této kategorii budou automaticky hlídaná. Počet nepřečtených a nových příspěvků se zobrazí vedle tématu." regular: title: "Normální" - description: "Budete informováni, pokud někdo zmíní vaše @jméno nebo odpoví na váš příspěvek." muted: title: "Ztišený" description: "Nebudete upozorněni na žádná nová témata v těchto kategoriích a ani se nebudou zobrazovat jako nepřečtené." diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index c9beeb6974..280bbc4497 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -347,9 +347,7 @@ da: github_profile: "Github" mailing_list_mode: "Send mig en email for hvert nye indlæg (med mindre, at jeg muter emnet eller kategorien)" watched_categories: "Overvåget" - watched_categories_instructions: "Du overvåger automatisk alle emner i disse kategorier" tracked_categories: "Fulgt" - tracked_categories_instructions: "Du vil automatisk følge alle nye emner i disse kategorier. En optælling af ulæste og nye indlæg vises ved emnet." muted_categories: "Ignoreret" muted_categories_instructions: "Du ignorerer automatisk alle emner i disse kategorier" delete_account: "Slet min konto" @@ -759,8 +757,6 @@ da: from_the_web: "Fra nettet" remote_tip: "link til billede" remote_tip_with_attachments: "link til billede eller fil ({{authorized_extensions}})" - local_tip: "klik for at vælge et billede fra din computer." - local_tip_with_attachments: "klik for at vælge et billede eller fil fra din enhed ({{authorized_extensions}})" hint: "(du kan også trække og slippe ind i editoren for at uploade dem)" hint_for_supported_browsers: "(du kan også trække og slippe eller indsætte billeder ind i editoren for at uploade dem)" uploading: "Uploader billede" @@ -899,29 +895,21 @@ da: '2_4': 'Du får notifikationer fordi du har besvaret dette emne.' '2_2': 'Du får notifikationer fordi du følger dette emne.' '2': 'Du får notifikationer fordi du har læst dette emne.' - '1_2': 'Du får en notifikation hvis nogen nævner dit @navn eller svarer dit indlæg.' - '1': 'Du får notifikationer hvis nogen nævner dit @navn eller svarer på dit indlæg.' '0_7': 'Du ignorerer alle notifikationer i denne kategori.' '0_2': 'Du får ingen notifikationer for dette emne.' '0': 'Du får ingen notifikationer for dette emne.' watching_pm: title: "Følger" - description: "Du vil blive underrettet for hver ny post i denne besked. Antallet af ulæste og nye posts vil også blive vist ved siden af emnet" watching: title: "Overvåger" - description: "Du vil blive underrettet for hvert nyt indlæg i denne tråd. En optælling af nye indlæg vil blive vist ved siden af tråden." tracking_pm: title: "Følger" - description: "Antallet af ulæste og nye indlæg, vil stå efter beskeden. Du bliver notificeret hvis nogen nævner dit @name eller svarer på dit indlæg." tracking: title: "Følger" - description: "Antallet af ulæste og nye indlæg vil stå efter emne linien. Du bliver notificeret hvis nogen nævner dit @name eller svarer på dine indlæg." regular: title: "Standard" - description: "Du får notifikationer hvis nogen nævner dit @navn eller svarer på dit indlæg." regular_pm: title: "Standard" - description: "Du får notifikationer hvis nogen nævner dit @navn eller svarer på dit indlæg i denne besked." muted_pm: title: "Lydløs" description: "Du vil aldrig få notifikationer om denne besked." @@ -1005,8 +993,6 @@ da: sso_enabled: "Indtast brugernavnet på den person, du gerne vil invitere til dette emne." to_topic_blank: "Indtast brugernavnet eller en email adresse på den person, du gerne vil invitere til dette emne." to_topic_email: "Du har indtastet en email adresse. Vi vil sende en e-mail invitation, der giver din ven direkte adgang til at svare på dette emne." - to_topic_username: "Du har indtastet et brugernavn. Vi sender en notifikation til denne bruger med et link der inviterer dem til dette emne." - to_username: "Indtast brugernavnet på den person du gerne vil invitere. Vi sender en notifikation til denne bruger med et link der inviterer dem til dette emne." email_placeholder: 'e-mail-adresse' success_email: "Vi har e-mailet en invitation til {{emailOrUsername}}. Vi notificerer dig, når invitationen er blevet accepteret. Kig på invitations fanebladet på din bruger side for at se status på dine invitationer." success_username: "Vi har inviteret brugeren til at deltage i dette emne." @@ -1294,13 +1280,10 @@ da: notifications: watching: title: "Kigger" - description: "Du overvåger automatisk alle emner i disse kategorier. Du vil få en notifikation ved alle nye indlæg og emner og antallet af ulæste og nye indlæg vil også stå ved siden af emnet." tracking: title: "Følger" - description: "Du vil automatisk følge alle nye emner i disse kategorier. En optælling af ulæste og nye indlæg vises ved emnet." regular: title: "Standard" - description: "Du får notifikationer hvis nogen nævner dit @navn eller svarer på dit indlæg." muted: title: "Ignoreret" description: "Du ignorerer automatisk alle emner i disse kategorier. De vil heller ikke stå i sin \"ulæste\" indikation." diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index b99d65ca12..a97bf4eade 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -348,9 +348,9 @@ de: github_profile: "Github" mailing_list_mode: "Sende mir bei jedem neuen Beitrag eine E-Mail (außer wenn ich das Thema oder die Kategorie stummgeschaltet habe)" watched_categories: "Beobachtet" - watched_categories_instructions: "Du wirst in diesen Kategorien automatisch alle neuen Themen beobachten. Du wirst über alle neuen Beiträge und Themen benachrichtigt werden. Außerdem wird die Anzahl der ungelesenen und neuen Beiträge neben dem Eintrag in der Themenliste angezeigt." + watched_categories_instructions: "Du wirst automatisch alle neuen Themen in diesen Kategorien beobachten und über alle neuen Beiträge und Themen benachrichtigt werden. Die Anzahl der neuen Antworten wird bei den betroffenen Themen angezeigt." tracked_categories: "Verfolgt" - tracked_categories_instructions: "Du wirst in diesen Kategorien automatisch allen neuen Themen folgen. Die Anzahl der ungelesenen und neuen Beiträge wird neben dem Thema angezeigt." + tracked_categories_instructions: "Du wirst automatisch allen neuen Themen in diesen Kategorien folgen. Die Anzahl der neuen Antworten wird bei den betroffenen Themen angezeigt." muted_categories: "Stummgeschaltet" muted_categories_instructions: "Du erhältst keine Benachrichtigungen über neue Themen in diesen Kategorien und sie werden nicht in deiner Liste ungelesener Themen aufscheinen." delete_account: "Lösche mein Benutzerkonto" @@ -761,8 +761,8 @@ de: from_the_web: "Aus dem Web" remote_tip: "Link zu Bild" remote_tip_with_attachments: "Link zu Bild oder Datei ({{authorized_extensions}})" - local_tip: "Klicke hier, um ein Bild von deinem Gerät zu wählen." - local_tip_with_attachments: "Klicke hier, um ein Bild oder eine Datei von deinem Gerät zu wählen ({{authorized_extensions}})" + local_tip: "durch Klicken kannst du bis zu 10 Bilder von deinem Gerät auswählen" + local_tip_with_attachments: "durch Klicken kannst du bis zu 10 Bilder oder Dateien von deinem Gerät auswählen ({{authorized_extensions}})" hint: "(du kannst Dateien auch in den Editor ziehen, um diese hochzuladen)" hint_for_supported_browsers: "(du kannst Bilder auch durch Ziehen in den Editor oder durch Einfügen aus der Zwischenablage hochladen)" uploading: "Wird hochgeladen" @@ -903,29 +903,29 @@ de: '2_4': 'Du wirst Benachrichtigungen erhalten, weil du eine Antwort zu diesem Thema verfasst hast.' '2_2': 'Du wirst Benachrichtigungen erhalten, weil du dieses Thema verfolgst.' '2': 'Du wirst Benachrichtigungen erhalten, weil du dieses Thema gelesen hast.' - '1_2': 'Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder auf deinen Beitrag antwortet.' - '1': 'Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder auf deinen Beitrag antwortet.' + '1_2': 'Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder dir antwortet.' + '1': 'Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder dir antwortet.' '0_7': 'Du ignorierst alle Benachrichtigungen dieser Kategorie.' '0_2': 'Du ignorierst alle Benachrichtigungen dieses Themas.' '0': 'Du ignorierst alle Benachrichtigungen dieses Themas.' watching_pm: title: "Beobachten" - description: "Du wirst über jeden neuen Beitrag in dieser Unterhaltung benachrichtigt. Die Anzahl der ungelesenen und neuen Beiträge wird auch neben dem Thema erscheinen." + description: "Du wirst über jeden neuen Beitrag in dieser Unterhaltung benachrichtigt und die Anzahl der neuen Beiträge wird angezeigt." watching: title: "Beobachten" - description: "Du wirst über jeden neuen Beitrag in diesem Thema benachrichtigt. Die Anzahl der ungelesenen und neuen Beiträge wird auch neben dem Thema erscheinen." + description: "Du wirst über jeden neuen Beitrag in diesem Thema benachrichtigt und die Anzahl der neuen Antworten wird angezeigt." tracking_pm: title: "Verfolgen" - description: "Die Anzahl der ungelesenen und neuen Nachrichten wird neben dem Eintrag in der Nachrichtenliste angezeigt. Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder deine Nachricht beantwortet." + description: "Die Anzahl der neuen Antworten wird bei dieser Unterhaltung angezeigt. Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder dir antwortet." tracking: title: "Verfolgen" - description: "Die Anzahl der ungelesenen und neuen Beiträge wird neben dem Thema erscheinen. Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder auf deinen Beitrag antwortet." + description: "Die Anzahl der neuen Antworten wird bei diesem Thema angezeigt. Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder dir antwortet." regular: title: "Normal" - description: "Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder auf deinen Beitrag antwortet." + description: "Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder dir antwortet." regular_pm: title: "Normal" - description: "Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder auf deinen Beitrag in der Nachricht antwortet." + description: "Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder dir antwortet." muted_pm: title: "Stummgeschaltet" description: "Du erhältst keine Benachrichtigungen im Zusammenhang mit dieser Unterhaltung." @@ -1009,8 +1009,8 @@ de: sso_enabled: "Gib den Benutzername der Person ein, die du zu diesem Thema einladen willst." to_topic_blank: "Gib den Benutzername oder die E-Mail-Adresse der Person ein, die du zu diesem Thema einladen willst." to_topic_email: "Du hast eine E-Mail-Adresse eingegeben. Wir werden eine Einladung versenden, die ein direktes Antworten auf dieses Thema ermöglicht." - to_topic_username: "Du hast einen Benutzernamen eingegeben. Wir werden diesen Benutzer benachrichtigen und zur Teilnahme an diesem Thema einladen." - to_username: "Gib den Benutzername der Person ein, die du einladen willst. Wir werden diesen Benutzer benachrichtigen und zur Teilnahme an diesem Thema einladen." + to_topic_username: "Du hast einen Benutzernamen eingegeben. Wir werden eine Benachrichtigung versenden und mit einem Link zur Teilnahme an diesem Thema einladen." + to_username: "Gib den Benutzername der Person ein, die du einladen möchtest. Wir werden eine Benachrichtigung versenden und mit einem Link zur Teilnahme an diesem Thema einladen." email_placeholder: 'name@example.com' success_email: "Wir haben an {{emailOrUsername}} eine Einladung verschickt und werden dich benachrichtigen, sobald die Einladung angenommen wurde. In deinem Benutzerprofil kannst du alle deine Einladungen überwachen." success_username: "Wir haben den Benutzer gebeten, sich an diesem Thema zu beteiligen." @@ -1298,16 +1298,16 @@ de: notifications: watching: title: "Beobachten" - description: "Du wirst in diesen Kategorien automatisch alle neuen Themen beobachten. Du wirst über alle neuen Beiträge und Themen benachrichtigt werden. Außerdem wird die Anzahl der ungelesenen und neuen Beiträge neben dem Thema erscheinen." + description: "Du wirst automatisch alle neuen Themen in dieser Kategorie beobachten und über alle neuen Beiträge und Themen benachrichtigt werden. Die Anzahl der neuen Antworten wird bei den betroffenen Themen angezeigt." tracking: title: "Verfolgen" - description: "Du wirst in diesen Kategorien automatisch allen neuen Themen folgen. Die Anzahl der ungelesenen und neuen Beiträge wird neben dem Thema erscheinen." + description: "Du wirst automatisch allen neuen Themen in dieser Kategorie folgen. Die Anzahl der neuen Antworten wird bei den betroffenen Themen angezeigt." regular: title: "Normal" - description: "Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder auf deinen Beitrag antwortet." + description: "Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder dir antwortet." muted: title: "Stummgeschaltet" - description: "Du erhältst keine Benachrichtigungen über neue Themen in diesen Kategorien und sie werden nicht in deiner Liste ungelesener Themen aufscheinen." + description: "Du erhältst keine Benachrichtigungen über neue Themen in dieser Kategorie und die Themen werden nicht in deiner Liste ungelesener Themen aufscheinen." flagging: title: 'Danke für deine Mithilfe!' private_reminder: 'Meldungen sind vertraulich und nur für Mitarbeiter sichtbar' @@ -1350,7 +1350,7 @@ de: bookmarked: help: "Du hast in diesem Thema ein Lesezeichen gesetzt." locked: - help: "Dieses Thema ist geschlossen; Antworten sind nicht mehr möglich" + help: "Dieses Thema ist geschlossen. Das Antworten ist nicht mehr möglich." unpinned: title: "Losgelöst" help: "Dieses Thema ist für dich losgelöst; es wird in der normalen Reihenfolge angezeigt" diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index 3859ec3d4d..9ba731e84f 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -348,9 +348,9 @@ es: github_profile: "Github" mailing_list_mode: "Enviarme un e-mail para cada nuevo post (excepto las categorías o temas que tenga silenciadas)" watched_categories: "Vigiladas" - watched_categories_instructions: "Automáticamente vigilarás todos los temas en estas categorías. Se te notificará de todos los posts y temas, y además aparecerá un contador de posts nuevos y no leídos en la lista de temas." + watched_categories_instructions: "Seguirás automáticamente todos los nuevos temas en estas categorías. Se te notificará de cada nuevo post y tema, y además, se añadirá un contador de posts nuevos y sin leer al lado del tema." tracked_categories: "Siguiendo" - tracked_categories_instructions: "Automáticamente seguirás todos los nuevos temas en estas categorías. Se añadirá un contador de nuevos posts y posts sin leer al lado del tema." + tracked_categories_instructions: "Seguirás automáticamente todos los nuevos temas en estas categorías. Se añadirá un contador de posts nuevos y sin leer al lado del tema." muted_categories: "Silenciado" muted_categories_instructions: "No será notificado de nuevos temas en estas categorías, y no aparecerán en la sección de \"no leídos\"." delete_account: "Borrar Mi Cuenta" @@ -761,8 +761,8 @@ es: from_the_web: "Desde la web" remote_tip: "enlace a la imagen" remote_tip_with_attachments: "enlace a la imagen o al archivo ({{authorized_extensions}})" - local_tip: "clic para seleccionar una imagen desde tu dispositivo." - local_tip_with_attachments: "clic para seleccionar una imagen o un archivo desde tu dispositivo ({{authorized_extensions}})" + local_tip: "clic para seleccionar hasta 10 imágenes desde tu dispositivo" + local_tip_with_attachments: "clic para seleccionar hasta 10 imágenes o archivos desde tu dispositivo({{authorized_extensions}})" hint: "(también puedes arrastrarlos al editor para subirlos)" hint_for_supported_browsers: "(también puedes arrastrar o pegar imágenes en el editor para subirlas)" uploading: "Subiendo" @@ -901,29 +901,29 @@ es: '2_4': 'Recibirás notificaciones porque has publicado una respuesta en este tema.' '2_2': 'Recibirás notificaciones porque estás siguiendo este tema.' '2': 'Recibirás notificaciones porque has leído este tema.' - '1_2': 'Serás notificado si alguien menciona tu @nombre o responde a un post tuyo.' - '1': 'Serás notificado si alguien menciona tu @nombre o responde a un post tuyo.' + '1_2': 'Se te notificará solo si alguien menciona tu @nombre o te responde a un post.' + '1': 'Se te notificará si alguien menciona tu @nombre o te responde a un post.' '0_7': 'Estás ignorando todas las notificaciones en esta categoría.' '0_2': 'Estás ignorando todas las notificaciones en este tema.' '0': 'Estás ignorando todas las notificaciones en este tema.' watching_pm: title: "Vigilar" - description: "Se te notificará de cada nuevo post en este mensaje. Se añadirá un contador de posts nuevos y sin leer al lado del tema." + description: "Se te notificará de cada nuevo post en este mensaje y se mostrará un contador de nuevos posts." watching: title: "Vigilar" - description: "Se te notificará de cada nuevo post en este tema. Se añadirá un contador de posts nuevos y sin leer al lado del tema." + description: "Se te notificará de cada post en este tema y se mostrará un contador de nuevos post." tracking_pm: title: "Seguir" - description: "Un contador de nuevos posts y posts no leídos aparecerá al lado del mensaje. Se te notificará si alguien menciona tu @nombre o responde a un post tuyo." + description: "Se mostrará un contador de nuevos posts para este mensaje y se te notificará si alguien menciona tu @nombre o te responde a un post." tracking: title: "Seguir" - description: "Un contador de posts nuevos posts y posts sin ser leídos aparecerá en seguida del tema. Serás notificado si alguien menciona tu @nombre o responde a un post tuyo." + description: "Se mostrará un contador de nuevos posts en este tema y se te notificará si alguien menciona tu @nombre o te responde a un post." regular: title: "Normal" - description: "Serás notificado si alguien menciona tu @nombre o responde a un post tuyo." + description: "Se te notificará solo si alguien menciona tu @nombre o te responde a un post." regular_pm: title: "Normal" - description: "Se te notificará solo si alguien menciona tu @nombre o responde a tus posts en el hilo de mensajes." + description: "Se te notificará solo si alguien menciona tu @nombre o te responde a un post." muted_pm: title: "Silenciar" description: "Nunca se te notificará nada sobre este hilo de mensajes." @@ -1007,8 +1007,8 @@ es: sso_enabled: "Introduce el nombre de usuario de la persona a la que quieres invitar a este tema." to_topic_blank: "Introduzca el nombre de usuario o dirección de correo electrónico de la persona que desea invitar a este tema." to_topic_email: "Ha introducido una dirección de correo electrónico. Nosotros te enviaremos una invitación que le permita a su amigo responder inmediatamente a este tema." - to_topic_username: "Ha introducido un nombre de usuario. Le enviaremos una notificación a ese usuario con un enlace invitándolo a este tema." - to_username: "Introduce el nombre de usuario de la persona a la que te gustaría invitar. Le enviaremos una notificación con un enlace invitándole a este tema." + to_topic_username: "Has introducido un nombre de usuario. Le enviaremos una notificación con un enlace invitándole a este tema." + to_username: "Introduce el nombre de usuario de la persona a la que quieras invitar. Le enviaremos una notificación con un enlace invitándole a este tema." email_placeholder: 'nombre@ejemplo.com' success_email: "Hemos enviado un email con tu invitación a {{emailOrUsername}}. Te notificaremos cuando se acepte. Puedes revisar la pestaña invitaciones en tu perfil de usuario para consultar el estado de tus invitaciones." success_username: "Hemos invitado a ese usuario a participar en este tema." @@ -1302,7 +1302,7 @@ es: description: "Seguirás automáticamente todos los nuevos temas en estas categorías. Se añadirá un contador de posts nuevos y sin leer al lado del tema." regular: title: "Normal" - description: "Serás notificado si alguien menciona tu @nombre o responde a un post tuyo." + description: "Se te notificará solo si alguien menciona tu @nombre o te responde a un post." muted: title: "Silenciadas" description: "No se te notificará de nuevos temas en estas categorías y no aparecerán en la pestaña de no leídos." diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index 80b1c23a2e..b3d244cf19 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -347,9 +347,7 @@ fi: github_profile: "GitHub" mailing_list_mode: "Lähetä minulle sähköposti jokaisesta uudesta viestistä (paitsi jos vaimennan ketjun tai alueen)." watched_categories: "Tarkkaillut" - watched_categories_instructions: "Näiden alueiden ketjut asetetaan automaattisesti tarkkailuun. Saat ilmoituksen kaikista uusista viesteistä ja ketjuista ja uusien ja lukemattomien viestien lukumäärä näytetään ketjujen listauksissa. " tracked_categories: "Seuratut" - tracked_categories_instructions: "Näiden alueiden ketjut asetetaan automaattisesti seurantaan. Uusien ja lukemattomien viestien lukumäärä näytetään ketjun yhteydessä." muted_categories: "Vaimennetut" muted_categories_instructions: "Et saa imoituksia uusista viesteistä näillä alueilla, eivätkä ne näy Lukemattomat-välilehdellä." delete_account: "Poista tilini" @@ -759,8 +757,6 @@ fi: from_the_web: "Netistä" remote_tip: "linkki kuvaan" remote_tip_with_attachments: "linkki kuvaan tai tiedostoon ({{authorized_extensions}})" - local_tip: "Klikkaa tästä ladataksesi kuvan laitteeltasi" - local_tip_with_attachments: "klikkaa valitaksesi kuvan tai tiedoston laitteeltasi ({{authorized_extensions}})" hint: "(voit myös raahata ne editoriin ladataksesi ne sivustolle)" hint_for_supported_browsers: "(voit myös raahata tai liittää kuvia editoriin ladataksesi ne sivustolle)" uploading: "Lähettää" @@ -899,29 +895,21 @@ fi: '2_4': 'Saat ilmoituksia, koska olet kirjoittanut ketjuun viestin.' '2_2': 'Saat ilmoituksia, koska olet asettanut ketjun seurantaan.' '2': 'Saat ilmoituksia, koska luet tätä ketjua.' - '1_2': 'Saat ilmoituksen jos joku mainitsee @nimesi tai vastaa viestiisi.' - '1': 'Saat ilmoituksen jos joku mainitsee @nimesi tai vastaa viestiisi.' '0_7': 'Et saa mitään ilmoituksia tältä alueelta.' '0_2': 'Et saa mitään ilmoituksia tästä ketjusta.' '0': 'Et saa mitään ilmoituksia tästä ketjusta.' watching_pm: title: "Tarkkaile" - description: "Saat ilmoituksen kaikista uusista viesteistä tässä viestiketjussa. Uusien ja lukemattomien viestien lukumäärä näytetään ketjun yhteydessä." watching: title: "Tarkkaile" - description: "Saat ilmoituksen kaikista uusista viesteistä tässä ketjussa. Uusien ja lukemattomien viestien lukumäärä ilmestyy näkyviin ketjun yhteydessä." tracking_pm: title: "Seuraa" - description: "Lukemattomien ja uusien viestien lukumäärä näytetään viestin yhteydessä. Saat ilmoituksen, jos joku mainitsee @nimesi tai vastaa sinulle viestissään." tracking: title: "Seuraa" - description: "Lukemattomien ja uusien viestien lukumäärä näytetään ketjun yhteydessä. Saat ilmoituksen jos joku mainitsee @nimesi tai vastaa sinulle viestissään." regular: title: "Tavallinen" - description: "Saat ilmoituksen jos joku mainitsee @nimesi tai vastaa viestiisi." regular_pm: title: "Tavallinen" - description: "Saat ilmoituksen jos joku mainitsee @nimesi tai vastaa viestiisi tässä keskustelussa." muted_pm: title: "Vaimenna" description: "Et saa mitään ilmoituksia tästä keskustelusta." @@ -1005,8 +993,6 @@ fi: sso_enabled: "Syötä henkilön käyttäjätunnus, jonka haluaisit kutsua tähän ketjuun." to_topic_blank: "Syötä henkilön käyttäjätunnus tai sähköpostiosoite, jonka haluaisit kutsua tähän ketjuun." to_topic_email: "Syötit sähköpostiosoitteen. Lähetämme ystävällesi sähköpostin, jonka avulla hän voi heti vastata tähän ketjuun." - to_topic_username: "Syötit käyttäjätunnuksen. Lähetämme tälle käyttäjälle ilmoituksen, joka sisältää kutsun ja linkin tähän ketjuun." - to_username: "Kirjoita henkilön nimi, jonka haluat kutsua. Lähetämme tälle käyttäjälle ilmoituksen, joka sisältää kutsun ja linkin tähän ketjuun." email_placeholder: 'nimi@esimerkki.fi' success_email: "Olemme lähettäneet kutsun osoitteeseen {{emailOrUsername}}. Ilmoitamme, kun kutsuun on vastattu. Voit seurata käyttäjäsivusi kutsut-välilehdeltä kutsujesi tilannetta." success_username: "Olemme kutsuneet käyttäjän osallistumaan tähän ketjuun." @@ -1291,13 +1277,10 @@ fi: notifications: watching: title: "Tarkkaile" - description: "Tämän alueen ketjut asetetaan automaattisesti tarkkailuun. Saat ilmoituksen kaikista uusista viesteistä ja ketjuista ja alueen uusien ja lukemattomien viestien lukumäärä näytetään ketjujen yhteydessä. " tracking: title: "Seuraa" - description: "Näiden alueiden ketjut asetetaan automaattisesti seurantaan. Uusien ja lukemattomien viestien lukumäärä näytetään ketjujen yhteydessä." regular: title: "Tavallinen" - description: "Saat ilmoituksen jos joku mainitsee @nimesi tai vastaa viestiisi." muted: title: "Vaimennettu" description: "Et saa imoituksia uusista viesteistä näillä alueilla, eivätkä ne näy Lukemattomat-välilehdellä" diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index ab254f9e75..4833ba05db 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -348,9 +348,7 @@ fr: github_profile: "Github" mailing_list_mode: "M'envoyer un courriel pour tous les nouveaux messages (sauf si vous avez rendu silencieux le sujet ou la catégorie)" watched_categories: "Surveillés" - watched_categories_instructions: "Vous pourrez suivre attentivement automatiquement toutes les nouveaux sujets dans ces catégories. Vous serez notifié de tous les nouveaux messages et sujets, de plus les nombres de messages non lus et de nouveaux messages apparaîtront également à côté de la liste des sujets." tracked_categories: "Suivies" - tracked_categories_instructions: "Vous allez suivre automatiquement tous les nouveaux sujets dans ces catégories. Le nombre de messages non-lus et nouveaux apparaîtra à côté du sujet." muted_categories: "Désactivés" muted_categories_instructions: "Vous ne recevrez aucune notification concernant les nouveaux sujets de ces catégories, et ils n'apparaitront pas dans votre onglet non lu." delete_account: "Supprimer mon compte" @@ -761,8 +759,6 @@ fr: from_the_web: "Depuis le web" remote_tip: "lien vers l'image" remote_tip_with_attachments: "lien vers l'image ou le fichier ({{authorized_extensions}})" - local_tip: "cliquez pour sélectionner une image depuis votre ordinateur" - local_tip_with_attachments: "Cliquez pour sélectionner une image ou un fichier depuis votre ordinateur ({{authorized_extensions}})" hint: "(vous pouvez également faire un glisser-déposer dans l'éditeur pour les télécharger)" hint_for_supported_browsers: "(vous pouvez aussi glisser et déposer ou coller des images dans l'éditeur pour les envoyer) " uploading: "Fichier en cours d'envoi" @@ -905,29 +901,21 @@ fr: '2_4': 'Vous recevrez des notifications car vous avez écrit une réponse dans ce sujet.' '2_2': 'Vous recevrez des notifications car vous suivez ce sujet.' '2': 'Vous recevrez des notifications car vous avez lu ce sujet.' - '1_2': 'Vous serez notifié si un utilisateur mentionne votre @pseudo ou réponds à votre message.' - '1': 'Vous serez notifié si un utilisateur mentionne votre @pseudo ou réponds à votre message.' '0_7': 'Vous ignorez toutes les notifications de cette catégorie.' '0_2': 'Vous ignorez toutes les notifications de ce sujet.' '0': 'Vous ignorez toutes les notifications de ce sujet.' watching_pm: title: "Suivre attentivement" - description: "Vous serez averti de chaque nouvelle publication dans ce message. Le nombre de publications non lues et nouvelles apparaîtra également à côté du sujet." watching: title: "Surveiller" - description: "Vous serez notifier de chaque nouveau message de ce sujet. Les nombres de messages non lus et de nouveaux messages apparaîtront également à côté du sujet." tracking_pm: title: "Suivi simple" - description: "Le nombre de publications non lues et nouvelles apparaîtra à côté du message. Vous serez averti si quelqu'un mentionne votre @pseudo ou répond à votre publication." tracking: title: "Suivi" - description: "Le nombre de messages non lus et de nouveaux messages apparaîtra à côté du sujet. Vous serez averti si quelqu'un mentionne votre @pseudo ou répond à votre message." regular: title: "Normal" - description: "Vous serez averti si quelqu'un mentionne votre @pseudo ou répond à votre message." regular_pm: title: "Normal" - description: "Vous serez averti si quelqu'un mentionne votre @pseudo ou répond à votre publication dans le message." muted_pm: title: "Silencieux" description: "Vous ne serez jamais averti de quoi que ce soit à propos de ce message." @@ -1011,8 +999,6 @@ fr: sso_enabled: "Entrez le nom d'utilisateur de la personne que vous souhaitez inviter sur ce sujet." to_topic_blank: "Entrez le pseudo ou l'adresse email de la personne que vous souhaitez inviter sur ce sujet." to_topic_email: "Vous avez entré une adresse email. Nous allons envoyer une invitation à votre ami lui permettant de répondre immédiatement à ce sujet." - to_topic_username: "Vous avez entré un pseudo. Nous allons envoyer une notification à cet utilisateur avec un lien l'invitant sur ce sujet." - to_username: "Entrez le pseudo de la personne que vous souhaitez inviter. Nous lui enverrons une notification avec un lien vers ce sujet." email_placeholder: 'nom@exemple.com' success_email: "Nous avons envoyé un email à {{emailOrUsername}}. Nous vous avertirons lorsqu'il aura répondu à votre invitation. Suivez l'état de vos invitations dans l'onglet prévu à cet effet sur votre page utilisateur." success_username: "Nous avons invité cet utilisateur à participer à ce sujet." @@ -1300,13 +1286,10 @@ fr: notifications: watching: title: "S'abonner" - description: "Vous pourrez suivre attentivement automatiquement toutes les nouveaux sujets dans ces catégories. Vous serez notifié de tous les nouveaux messages et sujets, de plus les nombres de messages non lus et de nouveaux messages apparaîtront également à côté du sujet." tracking: title: "Suivi" - description: "Vous allez suivre automatiquement tous les nouveaux sujets dans ces catégories. Le nombre de messages non-lus et nouveaux apparaîtra à côté du sujet." regular: title: "Normal" - description: "Vous serez notifié si un utilisateur mentionne votre @pseudo ou réponds à votre message." muted: title: "Silencieux" description: "Vous ne recevrez aucune notification sur les sujets de ces catégories, et ils n'apparaîtront pas dans votre onglet \"non-lus\"." diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index c6b7c5adc3..cd333a1e3b 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -347,9 +347,7 @@ he: github_profile: "גיטהאב" mailing_list_mode: "שלחו לי דוא\"ל על כל פרסום חדש (אלא אם אשתיק את המעקב אחר הנושא או הקטגוריה)" watched_categories: "צופה" - watched_categories_instructions: "את/ה תצפו אוטומטית בכל הנושאים החדשים בקטגוריות האלה ותקבלו התראות על כל ההודעות והנושאים החדשים. כמו כן כמות ההודעות החדשות ואלו שלא נקראו תופיע לצד כל נושא." tracked_categories: "במעקב" - tracked_categories_instructions: "מעקב אוטומטי אחרי נושאים חדשים בקטגוריות אלו. מספר הפרסומים החדשים ואלו שלא נקראו יופיע לצד הנושא." muted_categories: "מושתק" muted_categories_instructions: "לא תקבלו התראות על נושאים חדשים בקטגוריות אלו, והם לא יופיעו בעמוד הלא נקראו שלך." delete_account: "מחק את החשבון שלי" @@ -760,8 +758,6 @@ he: from_the_web: "מהאינטרנט" remote_tip: "קישור לתמונה" remote_tip_with_attachments: "קישור לתמונה או קובץ ({{authorized_extensions}})" - local_tip: "לחץ לבחירת תמונה מהמכשיר שלך" - local_tip_with_attachments: "לחץ לבחירת תמונה או קובץ מהמכשיר שלך ({{authorized_extensions}})" hint: "(ניתן גם לגרור לעורך להעלאה)" hint_for_supported_browsers: "(אתה יכול גם לעשות drag and drop או להדביק תמונות אל תוך העורך כדי להעלות אותן)" uploading: "מעלה" @@ -898,29 +894,21 @@ he: '2_4': 'תקבל/י התראות כיוון שפרסמת תגובה לנושא הזה.' '2_2': 'תקבל/י התראות כיוון שאת/ה צופה אחרי הנושא הזה.' '2': 'תקבל/י התראות כיוון שקראת את הנושא הזה.' - '1_2': 'תקבלו הודעה אם מישהו יזכיר את @שם_המשתמש/ת שלך או ישיב לפרסום שלך.' - '1': 'תקבלו התראה אם מישהו יזכיר את @שם_המשתמש/ת שלך או ישיב לפרסום שלך.' '0_7': 'את/ה מתעלם/מתעלמת מכל ההתראות בקטגוריה זו.' '0_2': 'אתה מתעלם מכל ההתראות בנושא זה.' '0': 'אתה מתעלם מכל ההתראות בנושא זה.' watching_pm: title: "עוקב" - description: "תקבל/י התראה על כל פרסום בהודעה זו. " watching: title: "עוקב" - description: "תקבל/י התראה על כל פרסום חדש תחת נושא זה. " tracking_pm: title: "רגיל+" - description: "כמו רגיל, כמו כן סך הפרסומים החדשים ואלו שלא נקראו יופיע לצד ההודעה." tracking: title: "רגיל+" - description: "כמו רגיל, כמו כן סך הפרסום החדשים והלא נקראים יופיע לצד הנושא. " regular: title: "רגיל" - description: "תרבלו התראה אם מישהו יזכיר את @שם_המשתמש/ת שלך או ישיב לפרסום שלך." regular_pm: title: "רגיל" - description: "תקבל/י התראה אם מישהו יזכיר את @שם_המשתמש/ת שלך או יגיב לפרסום שלך בהודעה." muted_pm: title: "מושתק" description: "לעולם לא תקבל/י התראה בנוגע להודעה זו." @@ -1000,8 +988,6 @@ he: sso_enabled: "הכנס את שם המשתמש של האדם שברצונך להזמין לנושא זה." to_topic_blank: "הכנס את שם המשתמש או כתובת דואר האלקטרוני של האדם שברצונך להזמין לנושא זה." to_topic_email: "הזנת כתובת אימייל. אנחנו נשלח הזמנה שתאפשר לחברך להשיב לנושא הזה." - to_topic_username: "הכנסת את שם המשתמש של האדם שברצונך להזמין. אנו נשלח התראה למשתמש זה עם קישור המזמין אותו לנושא זה." - to_username: "הכנס את שם המשתמש של האדם שברצונך להזמין. אנו נשלח התראה למשתמש זה עם קישור המזמין אותו לנושא זה." email_placeholder: 'name@example.com' success_email: "שלחנו הזמנה ל: {{emailOrUsername}}. נודיע לך כשהזמנה תענה. בדוק את טאב ההזמנות בעמוד המשתמש שלך בשביל לעקוב אחרי ההזמנות ששלחת. " success_username: "הזמנו את המשתמש להשתתף בנושא." @@ -1281,13 +1267,10 @@ he: notifications: watching: title: "עוקב" - description: "תצפו באופן אוטומטי בכל הנושאים החדשים בקטגוריות אלה. תקבלו התראה על כל פרסום ונושא חדש." tracking: title: "רגיל+" - description: "כמו רגיל, וכן סך הפרסומים החדשים ושלא נקראו יופיעו לצד הנושא." regular: title: "רגיל" - description: "תקבלו התראה אם מישהו יזכיר את @שם_המשתמש/ת שלכם או ישיב לפרסום שלך." muted: title: "מושתק" description: "לא תקבל התראות בנוגע לנושאים חדשים בקטגוריות האלה, והם לא יופיעו בלשונית ה\"לא נקראו\" שלך." diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index d7f518cc65..191cc1eebb 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -347,9 +347,7 @@ it: github_profile: "Github" mailing_list_mode: "Inviami una email per ogni nuovo messaggio (a meno che io non ignori l'argomento o la categoria)" watched_categories: "Osservate" - watched_categories_instructions: "Osserverai automaticamente tutti i nuovi argomenti in queste categorie. Riceverai notifiche su tutti i nuovi messaggi e argomenti. Inoltre, accanto all'elenco degli argomenti apparirà il conteggio dei messaggi non letti e di quelli nuovi." tracked_categories: "Seguite" - tracked_categories_instructions: "Seguirai automaticamente tutti i nuovi argomenti appartenenti a queste categorie. Di fianco all'argomento comparirà il conteggio dei messaggi nuovi e non letti." muted_categories: "Silenziate" muted_categories_instructions: "Non ti verrà notificato nulla sui nuovi argomenti in queste categorie, e non compariranno nel tab dei non letti." delete_account: "Cancella il mio account" @@ -754,8 +752,6 @@ it: from_the_web: "Dal web" remote_tip: "collegamento all'immagine" remote_tip_with_attachments: "collegamento ad un'immagine o a un file ({{authorized_extensions}})" - local_tip: "clicca per selezionare un'immagine dal tuo dispositivo" - local_tip_with_attachments: "clicca per selezionare un'immagine o un file dal tuo dispositivo ({{authorized_extensions}})" hint: "(puoi anche trascinarle nell'editor per caricarle)" hint_for_supported_browsers: "(puoi anche trascinare o incollare immagini nell'editor per caricarle)" uploading: "In caricamento" @@ -892,29 +888,21 @@ it: '2_4': 'Riceverai notifiche perché hai pubblicato una risposta a questo argomento.' '2_2': 'Riceverai notifiche perché stai seguendo questo argomento.' '2': 'Riceverai notifiche perché hai letto questo argomento.' - '1_2': 'Riceverai notifiche se qualcuno menziona il tuo @nome o risponde al tuo messaggio.' - '1': 'Riceverai notifiche se qualcuno menziona il tuo @nome o risponde al tuo messaggio.' '0_7': 'Stai ignorando tutte le notifiche di questa categoria.' '0_2': 'Stai ignorando tutte le notifiche di questo argomento.' '0': 'Stai ignorando tutte le notifiche di questo argomento.' watching_pm: title: "In osservazione" - description: "Riceverai una notifica per ogni nuova pubblicazione in questo messaggio. Accanto all'argomento comparirà anche un conteggio dei messaggi non letti e di quelli nuovi." watching: title: "In osservazione" - description: "Riceverai una notifica per ogni nuovo messaggio in questo argomento. Accanto all'argomento comparirà anche un conteggio dei messaggi non letti e di quelli nuovi." tracking_pm: title: "Seguito" - description: "Vicino al messaggio apparirà il totale dei messaggi non letti e nuovi. Riceverai una notifica se qualcuno menziona il tuo @nome o risponde al tuo messaggio." tracking: title: "Seguito" - description: "Accanto all'argomento comparirà il totale dei messaggi non letti e di quelli nuovi. Riceverai una notifica se qualcuno menziona il tuo @nome o risponde al tuo messaggio." regular: title: "Normale" - description: "Riceverai notifiche se qualcuno menziona il tuo @nome o risponde al tuo messaggio." regular_pm: title: "Normale" - description: "Riceverai notifiche se qualcuno menziona il tuo @nome o risponde all'interno del messaggio." muted_pm: title: "Silenziato" description: "Non ti verrà notificato nulla per questo messaggio." @@ -1265,13 +1253,10 @@ it: notifications: watching: title: "In osservazione" - description: "Osserverai automaticamente tutti i nuovi argomenti appartenenti a queste categorie. Verrai avvisato di tutti i nuovi messaggi ed argomenti. Inoltre, accanto all'argomento apparirà il conteggio dei messaggi non letti e di quelli nuovi." tracking: title: "Seguendo" - description: "Seguirai automaticamente tutti i nuovi argomenti appartenenti a queste categorie. Di fianco all'argomento comparirà il conteggio dei messaggi nuovi e non letti." regular: title: "Normale" - description: "Riceverai notifiche se qualcuno menziona il tuo @nome o risponde al messaggio." muted: title: "Silenziato" description: "Non ti verrà notificato nulla sui nuovi argomenti di queste categorie, e non compariranno nella tua tab dei non letti." diff --git a/config/locales/client.nb_NO.yml b/config/locales/client.nb_NO.yml index cbce54270d..85201bee40 100644 --- a/config/locales/client.nb_NO.yml +++ b/config/locales/client.nb_NO.yml @@ -338,9 +338,7 @@ nb_NO: github_profile: "Github" mailing_list_mode: "Send meg en e-post for hvert nye innlegg (hvis ikke emnet eller kategorien er dempet)" watched_categories: "Følger" - watched_categories_instructions: "Du vil automatisk følge alle nye emner i disse kategoriene. Du vil bli varslet om alle nye innlegg og emner samt at antallet uleste og nye innlegg vil vises sammen med emneoppføringen." tracked_categories: "Sporet" - tracked_categories_instructions: "Du vil automatisk følge alle nye emner i disse kategoriene. Antallet uleste og nye emner vil vises ved siden av emnet." muted_categories: "Dempet" muted_categories_instructions: "Du vil ikke bli varslet om nye emner i disse kategoriene og de vil ikke vises i listen over ulest innhold." delete_account: "Slett kontoen min" @@ -745,8 +743,6 @@ nb_NO: from_the_web: "Fra nettet" remote_tip: "link til bilde" remote_tip_with_attachments: "link til bilde eller fil ({{authorized_extensions}})" - local_tip: "klikk for å velge et bilde fra din enhet." - local_tip_with_attachments: "klikk for å velge et bilde eller en fil fra din enhet ({{authorized_extensions}})" hint: "(du kan også drag & drop inn i editoren for å laste dem opp)" hint_for_supported_browsers: "(du kan også drag og drop eller lime inn bilder inn i editoren for å laste dem opp)" uploading: "Laster opp bilde" @@ -883,8 +879,6 @@ nb_NO: '2_4': 'Du vil motta varsler fordi du svarte på dette emnet.' '2_2': 'Du vil motta varsler fordi du følger dette emnet.' '2': 'Du vil motta varsler fordi du read this topic.' - '1_2': 'Du vil bli varslet hvis noen nevner ditt @navn eller svarer på din post.' - '1': 'Du vil bli varslet om noen nevner ditt @navn eller svarer på din post.' '0_7': 'Du ignorerer alle varsler i denne kategorien.' '0_2': 'Du ignorerer alle varsler på dette emnet.' '0': 'Du ignorerer alle varsler på dette emnet.' @@ -892,15 +886,12 @@ nb_NO: title: "Følger" watching: title: "Følger" - description: "Du vil bli varslet om hvert nye innlegg i dette emnet. Antallet uleste og nye emner vil også vises ved siden av emnets oppføring." tracking_pm: title: "Følger" tracking: title: "Følger" - description: "En opptelling av uleste og nye poster vil visest ved siden av emnet. Du vil bli varslet om noen nevner ditt @navn eller svarer på din post." regular: title: "Aktivt medlem" - description: "Du vil bli varslet om noen nevnet ditt @navn eller svarer på din post." regular_pm: title: "Aktivt medlem" muted_pm: @@ -1248,13 +1239,10 @@ nb_NO: notifications: watching: title: "Følger" - description: "Du vil automatisk følge alle nye emner i disse kategoriene. Du vil bli varslet om alle nye innlegg og emner. Antallet uleste og nye emner vil også vises." tracking: title: "Sporing" - description: "Du vil automatisk følge alle nye emner i disse kategoriene. En teller for antall uleste og nye innlegg vil vises ved siden av emnet." regular: title: "Vanlig" - description: "Du vil bli varslet om noen nevner ditt @navn eller svarer på din post." muted: title: "Dempet" description: "Du vil ikke bli varslet om noe vedrørende disse emnene i disse kategoriene og de vil ikke vises i din ulest-liste." @@ -1560,13 +1548,13 @@ nb_NO: all_users: "Alle brukere" note_html: "Hold denne nøkkelen hemmelig. Alle brukere som har den vil kunne opprette vilkårlige innlegg som en hvilken som helst bruker. " plugins: - title: "Plugins" - installed: "Installerte Plugins" + title: "Utvidelser" + installed: "Installerte Utvidelser" name: "Navn" - none_installed: "Du har ikke installert noen plugins." + none_installed: "Du har ikke installert noen utvidelser." version: "Versjon" change_settings: "Endre instillinger" - howto: "Hvordan innstallerer jeg plugins?" + howto: "Hvordan installerer jeg utvidelser?" backups: title: "Sikkerhetskopieringer" menu: @@ -2053,7 +2041,7 @@ nb_NO: uncategorized: 'Annet' backups: "Sikkerhetskopier" login: "Login" - plugins: "Plugins" + plugins: "Utvidelser" badges: title: Merker new_badge: Nytt merke diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml index ace21d37de..6712552ec1 100644 --- a/config/locales/client.pl_PL.yml +++ b/config/locales/client.pl_PL.yml @@ -374,9 +374,9 @@ pl_PL: github_profile: "Github" mailing_list_mode: "Wysyłaj e-mail z każdym nowym wpisem (o ile nie wyciszysz kategorii lub tematu)." watched_categories: "Obserwowane" - watched_categories_instructions: "Będziesz automatycznie śledzić wszystkie nowe tematy w tych kategoriach: liczba nieprzeczytanych i nowych wpisów będzie wyświetlana obok tytułów na liście tematów. Dodatkowo będziesz otrzymywać powiadomienie o każdym nowym wpisie i temacie." + watched_categories_instructions: "Będziesz automatycznie śledzić wszystkie nowe tematy w tych kategoriach. Będziesz otrzymywać powiadomienie o każdym nowym wpisie i temacie, a liczba nieprzeczytanych i nowych wpisów będzie wyświetlana obok tytułów na liście tematów. " tracked_categories: "Śledzone" - tracked_categories_instructions: "Będziesz automatycznie śledzić wszystkie nowe tematy w tych kategoriach: licznik nowych i nieprzeczytanych wpisów pojawi się obok tytułu." + tracked_categories_instructions: "Będziesz automatycznie śledzić wszystkie nowe tematy w tych kategoriach. Licznik nowych wpisów pojawi się obok tytułu na liście tematów." muted_categories: "Wyciszone" muted_categories_instructions: "Nie będziesz powiadamiany o niczym dotyczącym nowych tematów w tych kategoriach, i nie będą się one pojawiać na karcie nieprzeczytanych." delete_account: "Usuń moje konto" @@ -792,8 +792,8 @@ pl_PL: from_the_web: "Z Internetu" remote_tip: "link do obrazu" remote_tip_with_attachments: "odnośnik do obrazu lub pliku ({{authorized_extensions}})" - local_tip: "kliknij, aby wybrać obraz z Twojego urządzenia" - local_tip_with_attachments: "kliknij aby wybrać obraz lub plik ze swojego urządzenia ({{authorized_extensions}})" + local_tip: "kliknij, aby wybrać do 10 grafik z Twojego urządzenia" + local_tip_with_attachments: "kliknij, aby wybrać do 10 grafik lub plików z Twojego urządzenia ({{authorized_extensions}})" hint: "(możesz także upuścić plik z katalogu komputera w okno edytora)" hint_for_supported_browsers: "(możesz przeciągnąć lub wkleić obrazy do edytora, aby je wgrać)" uploading: "Wgrywanie" @@ -940,29 +940,29 @@ pl_PL: '2_4': 'Będziesz otrzymywać powiadomienia, ponieważ jesteś autorem odpowiedzi w tym temacie.' '2_2': 'Będziesz otrzymywać powiadomienia, ponieważ śledzisz ten temat.' '2': 'Będziesz otrzymywać powiadomienia, ponieważ ten temat został uznany za przeczytany.' - '1_2': 'Dostaniesz powiadomienie jedynie gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis.' - '1': 'Dostaniesz powiadomienie jedynie gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis.' + '1_2': 'Dostaniesz powiadomienie jedynie, gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis.' + '1': 'Dostaniesz powiadomienie jedynie, gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis.' '0_7': 'Ignorujesz wszystkie powiadomienia z tej kategorii.' '0_2': 'Ignorujesz wszystkie powiadomienia w tym temacie.' '0': 'Ignorujesz wszystkie powiadomienia w tym temacie.' watching_pm: title: "Obserwuj wszystko" - description: "Otrzymasz powiadomienie o każdym nowym wpisie w tej dyskusji. Liczba nowych i nieprzeczytanych wpisów pojawi się obok jej tytułu na liście wiadomości." + description: "Dostaniesz powiadomienie o każdym nowym wpisie w tej dyskusji. Liczba nowych wpisów pojawi się obok jej tytułu na liście wiadomości." watching: title: "Obserwuj wszystko" - description: "Dostaniesz powiadomienie o każdym nowym wpisie w tym temacie. Liczba nowych i nieprzeczytanych wpisów pojawi się obok jego tytułu." + description: "Dostaniesz powiadomienie o każdym nowym wpisie w tym temacie. Liczba nowych wpisów pojawi się obok jego tytułu na liście wiadomości." tracking_pm: title: "Śledzenie" - description: "Licznik nowych i nieprzeczytanych wpisów pojawi się obok wiadomości. Dostaniesz powiadomienie jedynie, gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis." + description: "Licznik nowych wpisów pojawi się obok tej dyskusji. Dostaniesz powiadomienie jedynie, gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis." tracking: title: "Śledzenie" - description: "Licznik nowych i nieprzeczytanych wpisów pojawi się obok tytułu tego tematu. Dostaniesz powiadomienie jedynie gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis." + description: "Licznik nowych odpowiedzi pojawi się obok tytułu tego tematu. Dostaniesz powiadomienie jedynie, gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis." regular: title: "Normalny" - description: "Dostaniesz powiadomienie jedynie gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis." + description: "Dostaniesz powiadomienie jedynie, gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis." regular_pm: title: "Normalny" - description: "Otrzymasz powiadomienie jedynie, gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis w tej dyskusji." + description: "Dostaniesz powiadomienie jedynie, gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis." muted_pm: title: "Wyciszono" description: "Nie będziesz otrzymywać powiadomień dotyczących tej dyskusji." @@ -1046,7 +1046,7 @@ pl_PL: sso_enabled: "Podaj nazwę użytkownika lub e-mail osoby którą chcesz zaprosić do tego tematu." to_topic_blank: "Podaj nazwę użytkownika lub e-mail osoby którą chcesz zaprosić do tego tematu." to_topic_email: "Wprowadzony został adres e-mail. Wyślemy tam zaproszenie umożliwiające wskazanej osobie odpowiedź w tym temacie." - to_topic_username: "Wprowadzono nazwę użytkownika. Wskazane konto otrzyma powiadomienie z linkiem do tematu." + to_topic_username: "Konto o wprowadzonej nazwie użytkownika otrzyma powiadomienie z linkiem do tego tematu." to_username: "Podaj nazwę użytkownika osoby którą chcesz zaprosić. Otrzyma powiadomienie z linkiem do tego tematu." email_placeholder: 'nazwa@example.com' success_email: "Wysłaliśmy zaproszenie do {{emailOrUsername}}. Otrzymasz powiadomienie, gdy zaproszenie zostanie przyjęte. Sprawdź zakładkę zaproszenia w swoim profilu, aby śledzić status tego i innych zaproszeń." @@ -1362,13 +1362,13 @@ pl_PL: notifications: watching: title: "Obserwuj wszystko" - description: "Będziesz automatycznie śledzić wszystkie nowe tematy w tych kategoriach: liczba nieprzeczytanych i nowych wpisów będzie wyświetlana obok tytułów. Dodatkowo będziesz otrzymywać powiadomienie o każdym nowym wpisie i temacie." + description: "Będziesz automatycznie śledzić wszystkie nowe tematy w tych kategoriach. Dostaniesz powiadomienie o każdym nowym wpisie i temacie. Liczba nowych odpowiedzi będzie wyświetlana na liście tematów." tracking: title: "Śledzona" - description: "Będziesz automatycznie śledzić wszystkie tematy w tych kategoriach: licznik nowych i nieprzeczytanych wpisów pojawi się obok ich tytułów." + description: "Będziesz automatycznie śledzić wszystkie tematy w tych kategoriach. Llicznik nowych odpowiedzi pojawi się obok ich tytułów." regular: title: "Normalny" - description: "Dostaniesz powiadomienie jedynie gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis." + description: "Dostaniesz powiadomienie jedynie, gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis." muted: title: "Wyciszone" description: "Nie będziesz powiadamiany o nowych tematach w tych kategoriach i nie będą się one pojawiać w karcie Nieprzeczytane." diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml index 9e4aa0b89d..5afff3516d 100644 --- a/config/locales/client.pt_BR.yml +++ b/config/locales/client.pt_BR.yml @@ -347,9 +347,7 @@ pt_BR: github_profile: "Github" mailing_list_mode: "Me envie um email para cada novo post (a menos que eu torne o tópico ou a categoria mudos)" watched_categories: "Acompanhados" - watched_categories_instructions: "Você vai acompanhar automaticamente todos os novos tópicos dessas categorias. Você será notificado de todas as novas mensagens e tópicos. Além disso, a contagem de mensagens não lidas e novas também aparecerá ao lado a lista do tópico." tracked_categories: "Monitorado" - tracked_categories_instructions: "Automaticamente monitora todos novos tópicos nestas categorias. Uma contagem de posts não lidos e novos aparecerá próximo ao tópico." muted_categories: "Silenciado" muted_categories_instructions: "Você não será notificado sobre novos tópicos dessas categorias e eles não vão aparecer na guia de mensagens não lidas." delete_account: "Excluir Minha Conta" @@ -757,8 +755,6 @@ pt_BR: from_the_web: "Da internet" remote_tip: "link da imagem" remote_tip_with_attachments: "link da imagem ou arquivo ({{authorized_extensions}})" - local_tip: "clique para selecionar um arquivo do seu dispositivo" - local_tip_with_attachments: "clique para selecionar uma imagem ou arquivo do seu dispositivo ({{authorized_extensions}})" hint: "(Você também pode arrastar e soltar para o editor para carregá-las)" hint_for_supported_browsers: "(você também pode arrastar e soltar ou colar imagens no editor para carregá-las)" uploading: "Enviando" @@ -896,29 +892,21 @@ pt_BR: '2_4': 'Você receberá notificações porque postou uma resposta neste tópico.' '2_2': 'Você receberá notificações porque está monitorando este tópico.' '2': 'Você receberá notificações porque você leu este tópico.' - '1_2': 'Você será notificado se alguém mencionar o seu @nome ou responder à sua postagem.' - '1': 'Você será notificado se alguém mencionar o seu @nome ou responder à sua postagem.' '0_7': 'Você está ignorando todas as notificações nessa categoria.' '0_2': 'Você está ignorando todas as notificações deste tópico.' '0': 'Você está ignorando todas as notificações deste tópico.' watching_pm: title: "Acompanhando" - description: "Você será notificado de cada nova postagem nesta mensagem. Um contador de posts novos e não lidos também aparecerá próximo ao tópico." watching: title: "Observar" - description: "Você será notificado de cada post novo neste tópico. Um contador de posts novos e não lidos também aparecerá próximo ao tópico." tracking_pm: title: "Monitorando" - description: "Um contador de postagens novas e não lidas aparecerá perto das mensagens privadas. Você será notificado se alguém mencionar seu @nome ou responder à sua postagem." tracking: title: "Monitorar" - description: "Um contador de postagens novas e não lidas aparecerá perto do tópico. Você será notificado se alguém mencionar seu @nome ou responder à sua postagem." regular: title: "Normal" - description: "Você será notificado se alguém mencionar seu @nome ou responder à sua postagem." regular_pm: title: "Regular" - description: "Você será notificado se alguém mencionar seu @nome ou responder à sua postagem em mensagem privada." muted_pm: title: "Silenciado" description: "Você nunca será notificado de qualquer coisa sobre essa mensagem privada." @@ -1002,8 +990,6 @@ pt_BR: sso_enabled: "Entrar o nome de usuário da pessoa que você gostaria de convidar para este tópico." to_topic_blank: "Entrar o nome de usuário ou endereço de email da pessoa que você gostaria de convidar para este tópico." to_topic_email: "Você digitou um endereço de email. Nós enviaremos um convite por email que permite seu amigo responder imediatamente a este tópico." - to_topic_username: "Você entrou um nome de usuário. Nós enviaremos uma notificação para esse usuário com um link convidando-o a esse tópico." - to_username: "Entrar com nome do usuário da pessoa a ser convidada. Enviaremos uma notificação para o usuário com um link convidando para este tópico." email_placeholder: 'nome@exemplo.com' success_email: "Enviamos um convite para {{emailOrUsername}}. Nós notificaremos você quando este convite for resgatado. Verifique a aba de convites na página de seu usuário para acompanhar seus convites." success_username: "Nós convidamos o usuário para participar neste tópico." @@ -1287,13 +1273,10 @@ pt_BR: notifications: watching: title: "Observar" - description: "Você vai acompanhar automaticamente todos os novos tópicos dessas categorias. Você será notificado de todas as novas mensagens e tópicos. Além disso, a contagem de mensagens não lidas e novas também aparecerá ao lado a lista do tópico." tracking: title: "Monitorar" - description: "Automaticamente monitora todos novos tópicos nestas categorias. Uma contagem de posts não lidos e novos aparecerá próximo ao tópico." regular: title: "Normal" - description: "Você será notificado se alguém mencionar o seu @nome ou responder à sua postagem." muted: title: "Silenciar" description: "Você não será notificado sobre novos tópicos dessas categorias e eles não vão aparecer na guia de mensagens não lidas." diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index cda788b778..10a1b83c7b 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -399,9 +399,8 @@ ru: github_profile: "Github" mailing_list_mode: "Получать письмо каждый раз, когда появляется новое сообщение (кроме тех случаев, когда тема или раздел отключены)" watched_categories: "Наблюдение" - watched_categories_instructions: "Вы будете автоматически отслеживать новые темы в этих разделах. Вам будут приходить уведомления о новых сообщениях и темах, плюс рядом со списком тем будет отображено количество непрочитанных и новых сообщений." + watched_categories_instructions: "Вы будете автоматически отслеживать все новые темы в этих разделах. Вам будут приходить уведомления о новых сообщениях и темах, плюс рядом со списком тем будет отображено количество новых сообщений." tracked_categories: "Отслеживаемые разделы" - tracked_categories_instructions: "Автоматически включать режим слежения за новыми темами в этих разделах и показывать счетчик непрочитанных и новых сообщений напротив названий тем." muted_categories: "Выключенные разделы" muted_categories_instructions: "Вы не будете получать уведомления о новых темах в этих разделах. Также, они не будут показываться во вкладке Непрочитанное." delete_account: "Удалить мою учётную запись" @@ -821,8 +820,6 @@ ru: from_the_web: "From the web" remote_tip: "ссылка на изображение" remote_tip_with_attachments: "ссылка на изображение или файл ({{authorized_extensions}})" - local_tip: "кликните для выбора изображения с вашего устройства" - local_tip_with_attachments: "кликните для выбора изображения с вашего устройства ({{authorized_extensions}})" hint: "(вы так же можете перетащить объект в редактор для его загрузки)" hint_for_supported_browsers: "(вы так же можете перетащить или вставить изображения в редактор для загрузки их на сервер)" uploading: "Загрузка" @@ -975,29 +972,23 @@ ru: '2_4': 'Вы будете получать уведомления, т.к. ответили в теме.' '2_2': 'Вы будете получать уведомления, т.к. следите за этой темой.' '2': 'Вы будете получать уведомления, т.к. читали эту тему.' - '1_2': 'Вам придёт уведомление, только если кто-нибудь упомянет ваш @псевдоним или ответит на ваше сообщение.' - '1': 'Вам придёт уведомление, только если кто-нибудь упомянет ваш @псевдоним или ответит на ваше сообщение.' '0_7': 'Не получать уведомлений из этого раздела.' '0_2': 'Не получать уведомлений по этой теме.' '0': 'Не получать уведомлений по этой теме.' watching_pm: title: "Наблюдать" - description: "Уведомлять о каждом новом сообщении в этой беседе и показывать счетчик непрочитанных и новых сообщений напротив названия темы." watching: title: "Наблюдать" - description: "Уведомлять о каждом новом сообщении в этой теме и показывать счетчик непрочитанных и новых сообщений напротив названия темы." tracking_pm: title: "Следить" - description: "Количество непрочитанных и новых сообщений появится рядом с беседой. Вам придет уведомление, если кто-нибудь упомянет ваше @имя или ответит на ваше сообщение." tracking: title: "Следить" - description: "Количество непрочитанных сообщений появится рядом с названием темы. Вам придёт уведомление, только если кто-нибудь упомянет ваш @псевдоним или ответит на ваше сообщение." regular: title: "Уведомлять" - description: "Вам придет уведомление, только когда вас упоминают по @псевдониму или ответят на ваше сообщенияе" + description: "Вам придёт уведомление, только если кто-нибудь упомянет ваш @псевдоним или ответит на ваше сообщение." regular_pm: title: "Уведомлять" - description: "Вам придет уведомление, если кто-нибудь упомянет ваше @имя или ответит на ваше сообщение приватно." + description: "Вам придёт уведомление, только если кто-нибудь упомянет ваш @псевдоним или ответит на ваше сообщение." muted_pm: title: "Без уведомлений" description: "Вы не будете получать уведомлений, связанных с этой беседой." @@ -1081,8 +1072,8 @@ ru: sso_enabled: "Введите псевдоним пользователя, которого вы хотите пригласить в эту тему." to_topic_blank: "Введите псевдоним или email пользователя, которого вы хотите пригласить в эту тему." to_topic_email: "Вы указали адрес электронной почты. Мы отправим приглашение, которое позволит вашему другу немедленно ответить в этой теме." - to_topic_username: "Вы указали псевдоним пользователя. Мы отправим ему уведомление со ссылкой, приглашающей его в эту тему." - to_username: "Введите псевдоним пользователя, которого вы хотите пригласить в эту тему. Мы отправим ему уведомление о том что вы приглашаете его присоединиться к теме." + to_topic_username: "Вы указали псевдоним пользователя. Мы отправим ему уведомление со ссылкой, чтобы пригласить его в эту тему." + to_username: "Введите псевдоним пользователя, которого вы хотите пригласить в эту тему. Мы отправим ему уведомление о том что вы приглашаете его присоединиться к этой теме." email_placeholder: 'name@example.com' success_email: "Приглашение отправлено по адресу {{emailOrUsername}}. Мы уведомим Вас, когда этим приглашением воспользуются. Проверьте вкладку Приглашения на вашей странице пользователя, чтобы узнать состояние всех ваших приглашений." success_username: "Мы пригласили этого пользователя принять участие в теме." @@ -1422,13 +1413,11 @@ ru: notifications: watching: title: "Наблюдать" - description: "Автоматически наблюдать за всеми новыми темами из этих разделов и получать уведомления обо всех новых сообщениях и темах. Дополнительно, счетчики новых и непрочитанных сообщений будут появляться рядом с названиями тем." + description: "Вы будете автоматически отслеживать все новые темы в этих разделах. Вам будут приходить уведомления о новых сообщениях и темах, плюс рядом со списком тем будет отображено количество новых ответов на ваши сообщения." tracking: title: "Следить" - description: "Автоматически следить за всеми новыми темами из этих разделов. Счетчики новых и непрочитанных сообщений будут появляться рядом с названиями тем." regular: title: "Уведомлять" - description: "Уведомлять, только если кто-нибудь упомянет меня по @псевдониму или ответит на мое сообщение." muted: title: "Без уведомлений" description: "Не получать уведомлений о новых темах из этих разделов и не показывать новые темы во вкладке «Непрочитанные»." diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index 8da0617193..7d84b5194e 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -246,6 +246,7 @@ sv: post_count_long: "Svar postade" no_results: "Inga resultat hittades." days_visited: "Besök" + days_visited_long: "Dagar Besökta" posts_read: "Läst" posts_read_long: "Lästa trådar" total_rows: @@ -345,9 +346,7 @@ sv: github_profile: "Github" mailing_list_mode: "Skicka mig e-post för varje nytt inlägg (om jag inte tystat ämnet eller kategorin)" watched_categories: "Tittade på" - watched_categories_instructions: "Du kommer automatiskt att följa alla nya trådar inom dessa kategorier. Du kommer att få notifieringar om alla nya inlägg och trådar; antalet olästa och nya inlägg kommer även att visas vid sidan av trådens namn." tracked_categories: "Följd" - tracked_categories_instructions: "Du kommer automatiskt att följa alla nya trådar inom dessa kategorier. Antalet olästa och nya inlägg kommer att visas vid sidan av trådens namn." muted_categories: "Tystad" muted_categories_instructions: "Du kommer inte att få notifieringar om nya trådar inom dessa kategorier, och de kommer inte att visas under din \"oläst\"-tab." delete_account: "Radera mitt konto" @@ -558,6 +557,7 @@ sv: read_only_mode: enabled: "Skrivskyddat läge är aktiverat. Du kan fortsätta visa sidan men interaktioner kanske inte fungerar." login_disabled: "Det går inte att logga in medan siten är i skrivskyddat läge." + too_few_topics_notice: "Skapa minst 5 publika ämnen och %{posts} publika inlägg för att få igång diskussionen. Nya användare kan inte uppnå nya förtroendenivåer om det inte finns innehåll för dem att läsa. Detta meddelande syns enbart för administratörer." learn_more: "lär dig mer..." year: 'år' year_desc: 'diskussioner skapade de senaste 365 dagarna' @@ -754,8 +754,6 @@ sv: from_the_web: "Från webben" remote_tip: "länk till bild" remote_tip_with_attachments: "länk till bild eller fil ({{authorized_extensions}})" - local_tip: "klicka för att välja en bild från din enhet." - local_tip_with_attachments: "klicka för att välja en bild eller fil från din enhet ({{authorized_extensions}})" hint: "(du kan också dra & släppa in i redigeraren för att ladda upp dem)" hint_for_supported_browsers: "(du kan också dra och släppa eller klistra in bilder i redigeraren för att ladda upp dem)" uploading: "Laddar upp bild" @@ -893,8 +891,6 @@ sv: '2_4': 'Du kommer ta emot notifikationer för att du postade ett svar till denna tråd.' '2_2': 'Du kommer ta emot notifikationer för att du följer denna tråd.' '2': 'Du kommer ta emot notifikationer för att du läser denna tråd.' - '1_2': 'Du kommer att få en notifiering om någon nämner ditt @namn eller svarar på ditt inlägg.' - '1': 'Du kommer att få en notifiering om någon nämner ditt @namn eller svarar på ditt inlägg.' '0_7': 'Du ignorerar alla notifikationer i den här kategorin.' '0_2': 'Du ignorerar alla notifikationer för denna tråd.' '0': 'Du ignorerar alla notifikationer för denna tråd.' @@ -902,15 +898,13 @@ sv: title: "följda" watching: title: "Kollar" - description: "Du kommer att notifieras om varje nytt inlägg i det här ämnet. En räknare över olästa och nya inlägg kommer också att visas brevid ämnet." tracking_pm: title: "bevakade" tracking: title: "Följer" - description: "En räknare över olästa och nya inlägg kommer att visas brevid ämnet. Du kommer att notifieras om någon nämner ditt @namn eller svarar på ditt inlägg." regular: title: "Vanlig" - description: "Du kommer att notifieras om någon nämner ditt @namn eller svarar på ditt inlägg." + description: "Du kommer att få en notifiering om någon nämner ditt @namn eller svarar dig." regular_pm: title: "Vanlig(t)" muted_pm: @@ -923,6 +917,7 @@ sv: delete: "Radera Tråd" open: "Öppna Tråd" close: "Stäng Tråd" + multi_select: "Välj inlägg..." unarchive: "Dearkivera Tråd" archive: "Arkivera Tråd" invisible: "Markera Olistad" @@ -952,6 +947,7 @@ sv: group_name: "gruppnamn" invite_reply: title: 'Bjud in' + username_placeholder: "användarnamn" to_forum: "Vi skickar ett kort e-postmeddelande som tillåter din vän att omedelbart delta genom att klicka på en länk, ingen inloggning krävs." to_topic_email: "Du har angett en e-postadress. Vi skickar en inbjudan som ger din vän möjlighet att svara på detta ämne direkt." email_placeholder: 'namn@exampel.se' @@ -1014,6 +1010,9 @@ sv: one: "(inlägg tillbakadraget av skaparen, kommer att raderas automatiskt om 1 timme om det inte flaggas)" other: "(inlägg tillbakadraget av skaparen, kommer att raderas automatiskt om %{count} timmar om det inte flaggas)" expand_collapse: "expandera/förminska" + gap: + one: "visa 1 dolt svar" + other: "visa {{count}} dolda svar" more_links: "{{count}} till..." unread: "Inlägget är oläst" has_replies: @@ -1026,6 +1025,7 @@ sv: attachment_too_large: "Tyvärr, filen du försöker ladda upp är för stor (maximal storlek är {{max_size_kb}} kb)." file_too_large: "Tyvärr, filen du försöker ladda upp är för stor (maximal filstorlek är {{max_size_kb}}kb)" too_many_uploads: "Tyvärr, du kan bara ladda upp en bild i taget." + too_many_dragged_and_dropped_files: "Tyvärr, du kan bara dra och släppa upp till 10 filer åt gången." upload_not_authorized: "Tyvärr, filen du försökte ladda upp är inte tillåten (tillåtna filtyper: {{authorized_extensions}})." image_upload_not_allowed_for_new_user: "Tyvärr, nya användare kan inte ladda upp bilder." attachment_upload_not_allowed_for_new_user: "Tyvärr, nya användare kan inte bifoga filer." @@ -1087,9 +1087,12 @@ sv: people: off_topic: "{{icons}} flaggade det här som off-topic" spam: "{{icons}} flaggade det här som spam" + spam_with_url: "{{icons}} flaggade detta som skräp" inappropriate: "{{icons}} flaggade det här som olämpligt" notify_moderators: "{{icons}} notifierade moderatorer" notify_moderators_with_url: "{{icons}} notifierade moderatorer" + notify_user: "{{icons}} skickade ett meddelande" + notify_user_with_url: "{{icons}} skickade ett meddelande" bookmark: "{{icons}} bokmärkte detta" like: "{{icons}} gillade detta" vote: "{{icons}} röstade för detta" @@ -1098,6 +1101,7 @@ sv: spam: "Du flaggade detta som spam" inappropriate: "Du flaggade detta som olämpligt" notify_moderators: "Du flaggade det för moderation." + notify_user: "Du skickade ett meddelande till denna användare" bookmark: "Du bokmärkte detta inlägg" like: "Du gillade detta" vote: "Du röstade för detta inlägg" @@ -1114,6 +1118,9 @@ sv: notify_moderators: one: "Du och 1 annan person har flaggat detta för moderation" other: "Du och {{count}} andra personer har flaggat detta för moderation" + notify_user: + one: "Du och 1 person till skickade ett meddelande till denna användare" + other: "Du och {{count}} andra personer skickade ett meddelande till denna användare" bookmark: one: "Du och 1 annan bokmärkte detta inlägg" other: "Du och {{count}} andra personer bokmärkte detta inlägg" @@ -1136,6 +1143,9 @@ sv: notify_moderators: one: "1 person flaggade detta för granskning" other: "{{count}} personer flaggade detta för granskning" + notify_user: + one: "1 person skickade ett meddelande till denna användare" + other: "{{count}} skickade ett meddelande till denna användare" bookmark: one: "1 person bokmärkte detta inlägg" other: "{{count}} personer bokmärkte detta inlägg" @@ -1161,6 +1171,7 @@ sv: last: "Senaste revisionen" hide: "Göm version" show: "Visa version" + comparing_previous_to_current_out_of_total: "{{previous}} {{current}} / {{total}}" displays: inline: title: "Visa resultat med tillägg och borttagningar inline" @@ -1221,13 +1232,12 @@ sv: notifications: watching: title: "Bevakar" - description: "Du kommer automatiskt att bevaka alla nya ämnen i de här kategorierna. Du kommer att få notifieringar om alla nya inlägg och ämnen, och en räknare över olästa och nya inlägg kommer att visas brevid ämnen." tracking: title: "Följer" - description: "Du kommer automatiskt följa alla nya ämnen i de här kategorierna. En räknare över olästa och nya inlägg kommer att visas brevid ämnen." + description: "Du kommer automatiskt att följa alla nya ämnen i dessa kategorier. Antalet olästa inlägg kommer att synas för dessa ämnen." regular: title: "Vanlig" - description: "Du kommer att notifieras om någon nämner ditt @namn eller svarar på ditt inlägg." + description: "Du notifieras om någon nämner ditt @namn eller svarar på ditt inlägg." muted: title: "Tystad" description: "Du kommer inte att notifieras om något som rör nya ämnen i de här kategorierna, och de kommer inte att dyka upp i din olästa tabb." @@ -1236,6 +1246,7 @@ sv: private_reminder: 'flaggor är privata, endast synliga för funktionärer' action: 'Flagga Inlägg' take_action: "Åtgärda" + notify_action: 'Meddelande' delete_spammer: "Radera spammare" delete_confirm: "Du håller på att radera %{posts} inlägg och %{topics} trådar från den här användaren, radera hans/hennes konto, blockera IP-adressen %{ip_address}, och lägga till email-adressen %{email} till en permanent blockeringslista. Är du säker på att den här användaren verkligen är en spammare?" yes_delete_spammer: "Ja, radera spammare" @@ -1257,8 +1268,10 @@ sv: flagging_topic: title: "Tack för att du hjälper oss hålla gemenskapen civiliserad!" action: "Flagga tråd" + notify_action: "Meddelande" topic_map: title: "Sammanfattning av tråd" + links_title: "Populära länkar" links_shown: "visa alla {{totalLinks}} länkar..." clicks: one: "1 klick" @@ -1361,6 +1374,14 @@ sv: top: title: "Topp" help: "de mest aktiva ämnena det senaste året, månaden, veckan och dagen" + yearly: + title: "Årsvis" + monthly: + title: "Månadsvis" + weekly: + title: "Veckovis" + daily: + title: "Dagligen" this_year: "I år" this_month: "Den här månaden" this_week: "Den här veckan" @@ -1786,6 +1807,7 @@ sv: impersonate: title: "Imitera" help: "Använd det här verktyget för att imitera ett användarkonto för avlusningssyften. Du kommer behöva logga ut när du är klar." + not_found: "Användaren kan ej hittas." users: title: 'Användare' create: 'Lägg till Administratör' @@ -1921,6 +1943,10 @@ sv: suspend_modal_title: "Stäng av användare" trust_level_2_users: "Användare med Förtroendenivå 2" trust_level_3_requirements: "Krav för Förtroendenivå 3" + trust_level_locked_tip: "förtroendenivå är låst, systemet kommer ej att befordra eller degradera användare" + trust_level_unlocked_tip: "förtroendenivå är olåst, systemet kan komma att befordra eller degradera användare" + lock_trust_level: "Lås förtroendenivå" + unlock_trust_level: "Lås upp förtroendenivå" tl3_requirements: title: "Krav för Förtroendenivå 3" table_title: "Under de senaste 100 dagarna:" @@ -1939,15 +1965,26 @@ sv: likes_received: "Mottagna Gillningar" likes_received_days: "Mottagna Gillningar: unika dagar" likes_received_users: "Mottagna Gillningar: unika användare" + qualifies: "Kvalificerad för förtroendenivå 3." + does_not_qualify: "Ej kvalificerad för förtroendenivå 3." + will_be_promoted: "Blir befordrad snart." + will_be_demoted: "Blir degraderad snart." + locked_will_not_be_promoted: "Förtroendenivå låst. Kommer aldrig bli befordrad." + locked_will_not_be_demoted: "Förtroendenivå låst. Kommer aldrig degraderas." sso: external_id: "Externt ID" external_username: "Användarnamn" external_name: "Namn" external_email: "Epost" + external_avatar_url: "URL till profilbild" user_fields: title: "Användarfält" + help: "Lägg till fält som dina användare kan fylla i." create: "Skapa ett användarfält" untitled: "Namnlös" + name: "Fältnamn" + type: "Fälttyp" + description: "Fältbeskrivning" save: "Spara" edit: "Redigera" delete: "Ta bort" @@ -1957,12 +1994,28 @@ sv: title: "Krävs vid registrering?" enabled: "krävs" disabled: "krävs ej" + editable: + title: "Redigerbar efter registrering?" + enabled: "redigerbar" + disabled: "ej redigerbar" + show_on_profile: + title: "Visa på offentlig profil?" + enabled: "visas på profil" + disabled: "visas ej på profil" + field_types: + text: 'Textfält' + confirm: 'Bekräftelse' + site_text: + none: "Välj typ av innehåll för att börja redigera." site_settings: show_overriden: 'Visa bara överskrivna' title: 'Webbplatsinställningar' + reset: 'återställ' none: 'inget' no_results: "Inga resultat hittades." clear_filter: "Rensa" + add_url: "lägg till URL" + add_host: "lägg till värd" categories: all_results: 'Alla' required: 'Krävs' @@ -1978,6 +2031,8 @@ sv: rate_limits: 'Begränsningar' developer: 'Utvecklare' embedding: "Inbäddning" + uncategorized: 'Övrigt' + login: "Inloggning" plugins: "Tillägg" badges: title: Utmärkelser @@ -1985,6 +2040,7 @@ sv: new: Ny name: Namn badge: Utmärkelse + display_name: Visa namn description: beskrivning badge_type: Utmärkelsetyp badge_grouping: Grupp @@ -1992,10 +2048,12 @@ sv: modal_title: Utmärkelsegrupper granted_by: Utfärdad av granted_at: Utfärdad vid + reason_help: (En länk till ett inlägg eller ämne) save: spara delete: ta bort delete_confirm: Är du säker på att du vill ta bort den utmärkelsen? revoke: Upphäv + reason: Anledning revoke_confirm: Är du säker på att du vill upphäva den utmärkelsen? edit_badges: Redigera utmärkelser grant_badge: Ge utmärkelse @@ -2009,33 +2067,63 @@ sv: enabled: Aktivera utmärkelse icon: Ikon image: Bild + trigger_type: + none: "Uppdatera dagligen" + preview: + bad_count_warning: + header: "VARNING!" + grant: + with: %{username} + emoji: + name: "Namn" + image: "Bild" lightbox: download: "ladda ned" + search_help: + title: 'Sökhjälp' keyboard_shortcuts_help: title: 'Snabbkommandon' jump_to: title: 'Hoppa till' + home: 'g, h Hem' + latest: 'g, l Senaste' + new: 'g, n Ny' + unread: 'g, u Olästa' + categories: 'g, c Kategorier' + bookmarks: 'g, b Bokmärken' navigation: title: 'Navigering' + jump: '# Gå till inlägg #' back: 'Tillbaka' open: 'ö eller Välj Öppna vald tråd' + next_prev: 'shift+j/shift+k Nästa/föregående avsnitt' application: create: 's Skapa en ny tråd' notifications: 'n Öppna notifikationer' + site_map_menu: '= Öppna webbplatsmeny' + user_profile_menu: 'p Öppna användarmeny' + show_incoming_updated_topics: '. Visa uppdaterade ämnen' search: 'Sök' help: '? Öppna tangentbordshjälp' actions: + title: 'Åtgärder' + share_topic: 'shift+s Dela ämne' + share_post: 's Dela inlägg' reply_as_new_topic: 't Svara med länkat ämne' reply_topic: 'shift+r Svara på ämne' reply_post: 'r Svara på inlägg' + quote_post: 'q Citera inlägg' like: 'Gilla inlägg' flag: '! flagga inlägg' bookmark: 'b bokmärk inlägg' edit: 'e redigera inlägg' delete: 'd radera inlägg' + mark_muted: 'm, m Tysta ämne' mark_tracking: 'm, t Följd diskussion' + mark_watching: 'm, w Bevaka ämne' badges: title: Utmärkelser + allow_title: "kan användas som titel" badge: basic_user: name: Grundläggande diff --git a/config/locales/client.te.yml b/config/locales/client.te.yml index cb70775663..9df92b323b 100644 --- a/config/locales/client.te.yml +++ b/config/locales/client.te.yml @@ -302,9 +302,7 @@ te: github_profile: "గిట్ హబ్" mailing_list_mode: "ప్రతి కొత్త టపాకూ నాకో ఈమెయిల్ పంపు (నేనా విషయాన్ని లేదా వర్గాన్ని నిశ్శబ్దించేంతవరకూ)" watched_categories: "ఒకకన్నేసారు" - watched_categories_instructions: "ఈ వర్గాలలోని అన్ని విషయాలపై మీరు ఒకకన్నేసారు. మీకు అన్ని కొత్త టపాలు మరియు విషయాలు గురించి ప్రకటన వస్తుంది. ఇంకా కొత్త టపాలు మరియు చదవని టపాల సంఖ్య విషయపు జాబితా పక్కన వస్తుంది" tracked_categories: "గమనించారు" - tracked_categories_instructions: "ఈ వర్గాలలోని అన్ని విషయాలూ మీరు గమనిస్తారు. చదవని మరియు కొత్త టపాల సంఖ్య విషయం పక్కన వస్తుంది." muted_categories: "నిశ్శబ్దం" muted_categories_instructions: "ఈ వర్గాలలోని ఏ కొత్త విషయాల గురించీ మీకు ప్రకటనలు రావు. ఇంకా అవి చదవని ట్యాబులో కనిపించవు." delete_account: "నా ఖాతా తొలగించు" @@ -678,8 +676,6 @@ te: from_the_web: "జాలం నుండి" remote_tip: "బొమ్మకు లంకె" remote_tip_with_attachments: "బొమ్మకు లేదా దస్త్రానికి లంకె ({{authorized_extensions}})" - local_tip: "మీ పరికరం నుండి బొమ్మను ఎంచుకోటానికి ఇక్కడన నొక్కండి" - local_tip_with_attachments: "మీ పరికరం నుండి బొమ్మను లేదా దస్త్రాన్ని ఎంచుకోవటానికి ఇక్కడ నొక్కండి ({{authorized_extensions}})" hint: "(మీరు వాటిని ఎడిటరులోకి లాగి వదిలెయ్యటు ద్వారా కూడా ఎగుమతించవచ్చు)" hint_for_supported_browsers: "(మీరు వాటిని ఎడిటరులోకి లాగి వదిలెయ్యటు ద్వారా కూడా ఎగుమతించవచ్చు)" uploading: "ఎగుమతవుతోంది" @@ -812,8 +808,6 @@ te: '2_4': 'మీకు ప్రకటనలు వస్తాయి ఎందుకంటే మీరు ఈ విషయానికి జవాబిచ్చారు.' '2_2': 'మీకు ప్రకటనలు వస్తాయి ఎందుకంటే మీరు ఈ విషయాన్ని గమనిస్తున్నారు.' '2': 'మీకు ప్రకటనలు వస్తాయి, ఎందుకంటే మీరు ఈ విషయాన్ని చదివారు.' - '1_2': 'ఎవరన్నా మీ టపాకు జవాబిచ్చినా లేదా @పేరు తో మిమ్ము ప్రస్తావించినా మీకు ప్రకటన వస్తుంది.' - '1': 'ఎవరన్నా మీ టపాకు జవాబిచ్చినా లేదా @పేరు తో మిమ్ము ప్రస్తావించినా మీకు ప్రకటన వస్తుంది.' '0_7': 'ఈ వర్గంలోని అన్ని ప్రకటనలనూ మీరు విస్మరిస్తున్నారు.' '0_2': 'ఈ విషయంలోని అన్ని ప్రకటనలనూ మీరు విస్మరిస్తున్నారు.' '0': 'ఈ విషయంలోని అన్ని ప్రకటనలనూ మీరు విస్మరిస్తున్నారు.' @@ -821,15 +815,12 @@ te: title: "కన్నేసారు" watching: title: "కన్నేసారు" - description: "ఈ విషయంలోని ప్రతి కొత్త టపాకు మీకు ప్రకటన వస్తుంది. ఇంకా చదవని మరియు కొత్త టపాల సంఖ్య విషయం పక్కన వస్తుంది." tracking_pm: title: "గమనిస్తున్నారు" tracking: title: "గమనిస్తున్నారు" - description: "ప్రైవేటు సందేశం పక్కన కొత్త మరియు చదవని టపాల సంఖ్య వస్తుంది. ఎవరన్నా మీకు జవాబిచ్చినా లేదా @మీ పేరు ప్రస్తావించినా మీకు ప్రకటన వస్తుంది. " regular: title: "రెగ్యులరు" - description: "ఎవరన్నా మీ @పేరు ప్రస్తావించినా లేదా జవాబిచ్చినా మీకు ప్రకటన వస్తుంది" regular_pm: title: "రెగ్యులరు" muted_pm: @@ -1142,13 +1133,10 @@ te: notifications: watching: title: "కన్నేసారు" - description: "మీరు స్వయంచాలకంగా ఈ వర్గాల లోని అన్ని విషయాలను చూడగలరు.మీకు అన్ని కొత్త టపాలు మరియు విషయాలు తెలియజేయబడతాయి,చదవని మరియు కొత్త టపాల లెక్క కలిపి విషయం ప్రక్కన కనిపిస్తుంది." tracking: title: "గమనిస్తున్నారు" - description: "ఈ వర్గాల లోని అన్ని విషయాలను మీరు స్వయంచాలకంగా కనిపెట్టగలరు.చదవని మరియు కొత్త టపాల సంఖ్య విషయం పక్కన కనిపిస్తుంది." regular: title: "రెగ్యులర్" - description: "ఎవరన్నా @మీపేరు ప్రస్తావిస్తే లేదా మీ టపాకు జవాబిస్తే మీకు ప్రకటన అందుతుంది." muted: title: "నిశ్శబ్దం" description: "ఈ వర్గాల్లో కొత్త విషయాల గురించి మీకు ప్రకటన రాదు. ఇంకా చదవి సంఖ్యలు కనిపించవు." diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml index 194c1650ea..3e8d9edd19 100644 --- a/config/locales/client.tr_TR.yml +++ b/config/locales/client.tr_TR.yml @@ -87,7 +87,7 @@ tr_TR: age: "Yaş" joined: "Katıldı" admin_title: "Yönetici" - flags_title: "Rapor edilenler" + flags_title: "Raporlar" show_more: "devamını göster" show_help: "yardım" links: "Bağlantılar" @@ -115,7 +115,7 @@ tr_TR: character_count: other: "{{count}} karakter" suggested_topics: - title: "Önerilen Konular" + title: "Önerilen konular" about: simple_title: "Hakkında" title: "%{title} Hakkında" @@ -123,7 +123,7 @@ tr_TR: our_admins: "Yöneticilerimiz" our_moderators: "Moderatörlerimiz" stat: - all_time: "Tüm Zamanlar" + all_time: "Tüm zamanlar" last_7_days: "Son 7 Gün" last_30_days: "Son 30 Gün" like_count: "Beğeniler" @@ -155,7 +155,7 @@ tr_TR: click_to_show: "Görüntülemek için tıklayın." preview: "önizleme" cancel: "iptal" - save: "Değişiklikleri Kaydet" + save: "Değişiklikleri kaydet" saving: "Kaydediliyor..." saved: "Kaydedildi!" upload: "Yükle" @@ -213,7 +213,7 @@ tr_TR: sent_by_you: "Sizin tarafınızdan yollandı" directory: filter_name: "kullanıcı adına göre filtrele" - title: "Kullanıcılar" + title: "Tüm kullanıcılar" likes_given: "Verilen" likes_received: "Alınan" topics_entered: "Açıldı" @@ -240,7 +240,7 @@ tr_TR: title: "Kimler bu grubu ikinci adı olarak kullanabilir?" nobody: " Hiç Kimse" only_admins: "Sadece yöneticiler" - mods_and_admins: "Sadece moderatörler ve adminler" + mods_and_admins: "Sadece moderatörler ve yöneticiler" members_mods_and_admins: "Sadece Grup Üyeleri, Moderatörler ve Yöneticiler" everyone: "Herkes" user_action_groups: @@ -258,13 +258,13 @@ tr_TR: '13': "Gelen Kutusu" '14': "Bekleyen" categories: - all: "Bütün Kategoriler" + all: "Tüm kategoriler" all_subcategories: "hepsi" no_subcategory: "hiçbiri" category: "Kategori" posts: "Gönderiler" topics: "Konular" - latest: "En Son" + latest: "En son" latest_by: "son gönderen" toggle_ordering: "sıralama kontrolünü aç/kapa" subcategories: "Alt kategoriler" @@ -295,11 +295,11 @@ tr_TR: mute: "Sustur" edit: "Ayarları Düzenle" download_archive: "Gönderilerimi İndir" - new_private_message: "Yeni Mesaj" + new_private_message: "Yeni mesaj" private_message: "Mesaj" private_messages: "Mesajlar" activity_stream: "Aktivite" - preferences: "Ayarlar" + preferences: "Seçenekler" bookmarks: "İşaretlenenler" bio: "Hakkımda" invited_by: "Tarafından Davet Edildi" @@ -322,15 +322,13 @@ tr_TR: github_profile: "Github" mailing_list_mode: "(Konuyu veya kategoriyi susturmadığım takdirde) her yeni gönderi için bana bir e-posta yolla" watched_categories: "Gözlendi" - watched_categories_instructions: "Bu kategorilerdeki tüm yeni konuları otomatik olarak gözleyeceksiniz. Tüm yeni gönderi ve konular size bildirilecek. Ayrıca, okunmamış ve yeni gönderilerin sayısı ilgili konu yanında belirecek. " tracked_categories: "Takip edildi" - tracked_categories_instructions: "Bu kategorilerdeki tüm yeni konuları otomatik olarak takip edeceksiniz. Okunmamış ve yeni gönderilerin sayısı ilgili konunun yanında belirecek." muted_categories: "Susturuldu" muted_categories_instructions: "Bu kategorideki yeni konular okunmamışlar sekmenizde belirmeyecek, ve haklarında hiçbir bildirim almayacaksınız." delete_account: "Hesabımı Sil" delete_account_confirm: "Hesabınızı kalıcı olarak silmek istediğinize emin misiniz? Bu işlemi geri alamazsınız!" deleted_yourself: "Hesabınız başarıyla silindi." - delete_yourself_not_allowed: "Hesabınızı şu an silemezsiniz. Hesabınızı silmesi için bir admin ile iletişime geçin." + delete_yourself_not_allowed: "Hesabınızı şu an silemezsiniz. Hesabınızı silmesi için bir yönetici ile iletişime geçin." unread_message_count: "Mesajlar" admin_delete: "Sil" users: "Kullanıcılar" @@ -356,7 +354,7 @@ tr_TR: title: "Hakkımda'yı Değiştir" error: "Değer değiştirilirken bir hata oluştu." change_username: - title: "Kullanıcı Adını Değiştir" + title: "Kullanıcı adını değiştir" confirm: "Kullanıcı adınızı degiştirmeniz halinde, eski gönderilerinizden yapılan tüm alıntılar ve @isim bahsedilişler bozulacak. Bunu yapmak istediginize gerçekten emin misiniz?" taken: "Üzgünüz, bu kullanıcı adı alınmış." error: "Kullanıcı adınızı değiştirirken bir hata oluştu." @@ -367,11 +365,11 @@ tr_TR: error: "E-posta adresinizi değiştirirken bir hata oluştu. Belki bu adres zaten kullanımdadır?" success: "Adresinize bir e-posta gönderdik. Lütfen onaylama talimatlarını uygulayınız." change_avatar: - title: "Profil resminizi değiştirin" + title: "Profil görselinizi değiştirin" gravatar: "Gravatar, baz alındı" gravatar_title: "Avatarınızı Gravatar sitesinde değiştirin" refresh_gravatar_title: "Avatarınızı Yenileyin" - letter_based: "Sistem tarafından verilen profil resmi" + letter_based: "Sistem tarafından verilen profil görseli" uploaded_avatar: "Özel resim" uploaded_avatar_empty: "Özel resim ekleyin" upload_title: "Resminizi yükleyin" @@ -379,10 +377,10 @@ tr_TR: image_is_not_a_square: "Uyarı: resminizi kırptık; genişlik ve yükseklik eşit değildi." cache_notice: "Avatarınızı başarıyla değiştirdiniz, fakat tarayıcınızın önbelleklemesinden ötürü gözükmesi biraz zaman alabilir." change_profile_background: - title: "Profil Arkaplanı" + title: "Profil arkaplanı" instructions: "Profil arkaplanları ortalanacak ve genişlikleri 850px olacak. " change_card_background: - title: "Kullanıcı Kartı Arkaplanı" + title: "Kullanıcı kartı arkaplanı" instructions: "Profil arkaplanları ortalanacak ve genişlikleri 590px olacak. " email: title: "E-posta" @@ -403,7 +401,7 @@ tr_TR: username: title: "Kullanıcı adı" instructions: "Özgün, boşluksuz ve kısa" - short_instructions: "Kişiler sizden @{{username}} olarak bahsedebilirler" + short_instructions: "Kullanıcılar sizden @{{username}} olarak bahsedebilirler." available: "Kullanıcı adınız müsait" global_match: "E-posta kayıtlı kullanıcı adıyla eşleşiyor" global_mismatch: "Zaten mevcut. {{suggestion}} deneyin?" @@ -426,8 +424,8 @@ tr_TR: log_out: "Oturumu kapat" location: "Yer" card_badge: - title: "Kullanıcı Kartı Rozeti" - website: "Web Sayfası" + title: "Kullanıcı kartı rozeti" + website: "Web sayfası" email_settings: "E-posta" email_digests: title: "Burayı ziyaret etmediğim zamanlarda bana yeni şeylerin özetini içeren bir email yolla:" @@ -494,9 +492,9 @@ tr_TR: ip_address: title: "Son IP Adresi" registration_ip_address: - title: "Kayıt Anındaki IP Adresi" + title: "Kayıt anındaki IP adresi" avatar: - title: "Profil Resmi" + title: "Profil görseli" header_title: "profil, mesajlar, işaretliler ve seçenekler" title: title: "Başlık" @@ -730,8 +728,8 @@ tr_TR: from_the_web: "Webden" remote_tip: "resme bağlantı ver" remote_tip_with_attachments: "resme ya da dosyaya ({{authorized_extensions}}) bağlantı ver" - local_tip: "cihazınızda bulunan bir resmi seçmek için tıklayın" - local_tip_with_attachments: "cihazınızdan resim ya da dosya seçmek için tıklayın ({{authorized_extensions}})" + local_tip: "Cihazınızdan 10 görsele kadar seçim yapmak için tıklayın" + local_tip_with_attachments: "Cihazınızdan 10 görsel ya da dosyaya kadar seçim yapmak için tıklayın ({{authorized_extensions}})" hint: "(editöre sürekle & bırak yaparak da yükleyebilirsiniz)" hint_for_supported_browsers: "(resimleri, editöre sürükle & bırak yaparak ya da yapıştırarak da yükleyebilirsiniz)" uploading: "Yükleniyor" @@ -760,7 +758,7 @@ tr_TR: dismiss_topics_tooltip: "Bu konularda yeni gönderiler oluşturulunca okunmamış listemde gösterme" dismiss_new: "Yenileri Yoksay" toggle: "konuların toplu seçimini aç/kapa" - actions: "Toplu Aksiyonlar" + actions: "Toplu işlemler" change_category: "Kategoriyi Değiştir" close_topics: "Konuları Kapat" archive_topics: "Konuları Arşivle" @@ -780,7 +778,7 @@ tr_TR: top: "Popüler bir konu yok." search: "Arama sonuçları yok." educate: - new: '

Yeni konularınız burada belirir.

Varsayılan ayarlarda, son 2 gün içerisinde yaratılan konular yeni sayılır ve yeni işaretiyle gösterilir.

Dilerseniz bu ayarları ayarlarsayfanızdan düzenleyebilirsiniz.

' + new: '

Yeni konularınız burada belirir.

Varsayılan ayarlarda, son 2 gün içerisinde yaratılan konular yeni sayılır ve yeni işaretiyle gösterilir.

Dilerseniz bu seçeneği ayarlar sayfanızdan düzenleyebilirsiniz.

' unread: '

Okunmamış konularınız burada belirecek.

Varsayılan ayarlarda, şu durumlarda konular okunmamış sayılır ve okunmamışların sayısı 1 gösterilir:

  • Konuyu oluşturduysanız
  • Konuyu cevapladıysanız
  • Konuyu 4 dakikadan uzun bir süre okuduysanız

Ya da, konunun altında bulunan bildirim kontrol bölümünden, konuyu Takip Edildi veya Gözlendi diye işaretlediyseniz.

Bu ayarları ayarlar sayfasından değiştirebilirsiniz.

' bottom: latest: "Daha fazla son konu yok." @@ -795,7 +793,7 @@ tr_TR: search: "Daha fazla arama sonucu yok." topic: filter_to: "Bu konuda {{post_count}} gönderi" - create: 'Yeni Konu' + create: 'Yeni konu' create_long: 'Yeni bir konu oluştur' private_message: 'Mesajlaşma başlat' list: 'Konular' @@ -839,7 +837,7 @@ tr_TR: deleted: "Konu silindi " auto_close_notice: "Bu konu otomatik olarak kapanacak %{timeLeft}." auto_close_notice_based_on_last_post: "Bu konu son cevaptan %{duration} sonra kapanacak." - auto_close_title: 'Otomatik Kapatma Ayarları' + auto_close_title: 'Otomatik kapatma ayarları' auto_close_save: "Kaydet" auto_close_remove: "Bu konuyu otomatik olarak kapatma" progress: @@ -863,29 +861,21 @@ tr_TR: '2_4': 'Bu konuya cevap yazdığınız için bildirimlerini alacaksınız.' '2_2': 'Bu konuyu takip ettiğiniz için bildirimlerini alacaksınız.' '2': 'Bu konuyu okuduğunuz için bildirimlerini alacaksınız.' - '1_2': 'Biri @isim şeklinde sizden bahsederse ya da gönderinize cevap verirse bildirim alacaksınız.' - '1': 'Biri @isim şeklinde sizden bahsederse ya da gönderinize cevap verirse bildirim alacaksınız.' '0_7': 'Bu kategoriye ait tüm bildirimleri görmezden geliyorsunuz.' '0_2': 'Bu konuya ait tüm bildirimleri görmezden geliyorsunuz.' '0': 'Bu konuya ait tüm bildirimleri görmezden geliyorsunuz.' watching_pm: title: "Gözleniyor" - description: "Bu mesajlaşmadaki her yeni gönderi için bir bildirim alacaksınız. Okunmamış ve yeni gönderilerin sayısı konunun yanında belirecek." watching: title: "Gözleniyor" - description: "Bu konudaki her yeni gönderi için bir bildirim alacaksınız. Okunmamış ve yeni gönderilerin sayısı konunun yanında belirecek." tracking_pm: title: "Takip Ediliyor" - description: "Okunmamış ve yeni gönderi sayısı mesajın yanında belirecek. Biri @isim şeklinde sizden bahsederse ya da gönderinize cevap verirse bildirim alacaksınız." tracking: title: "Takip Ediliyor" - description: "Okunmamış ve yeni gönderi sayısı başlığın yanında belirecek. Biri @isim şeklinde sizden bahsederse ya da gönderinize cevap verirse bildirim alacaksınız." regular: title: "Standart" - description: "Biri @isim şeklinde sizden bahsederse ya da gönderinize cevap verirse bildirim alacaksınız." regular_pm: title: "Standart" - description: "Biri @isim şeklinde sizden bahsederse ya da gönderinize mesajla cevap verirse bildirim alacaksınız." muted_pm: title: "Susturuldu" description: "Bu mesajlaşmayla ilgili hiç bir bildirim almayacaksınız." @@ -893,22 +883,22 @@ tr_TR: title: "Susturuldu" description: "Bu konu okunmamışlar sekmenizde belirmeyecek, ve hakkında hiç bir bildirim almayacaksınız." actions: - recover: "Konuyu Geri Getir" - delete: "Konuyu Sil" + recover: "Konuyu geri getir" + delete: "Konuyu sil" open: "Konuyu Aç" - close: "Konuyu Kapat" - multi_select: "Gönderileri Seç..." - auto_close: "Otomatik Kapat..." - pin: "Konuyu başa tuttur..." - unpin: "Konuyu başa tutturma..." + close: "Konuyu kapat" + multi_select: "Gönderileri seç..." + auto_close: "Otomatik kapat..." + pin: "Başa tuttur..." + unpin: "Baştan kaldır..." unarchive: "Konuyu Arşivden Kaldır" - archive: "Konuyu Arşivle" - invisible: "Listelenmemiş Yap" + archive: "Konuyu arşivle" + invisible: "Listelenmemiş yap" visible: "Listelenmiş Yap" reset_read: "Görüntüleme verilerini sıfırla" feature: - pin: "Konuyu başa tuttur" - unpin: "Konuyu başa tutturma" + pin: "Başa tuttur" + unpin: "Baştan kaldır" pin_globally: "Her yerde başa tuttur" make_banner: "Manşet Konusu" remove_banner: "Manşey Konusunu Kaldır" @@ -950,8 +940,8 @@ tr_TR: zero: "Manşet konusu yok." one: "Şu an bir manşet konusu var." inviting: "Davet Ediliyor..." - automatically_add_to_groups_optional: "Bu davet şu gruplara erişimi de içerir: (opsiyonel, sadece adminler için)" - automatically_add_to_groups_required: "Bu davet şu gruplara erişimi de içerir: (Gerekli, sadece adminler için)" + automatically_add_to_groups_optional: "Bu davet şu gruplara erişimi de içerir: (opsiyonel, sadece yöneticiler için)" + automatically_add_to_groups_required: "Bu davet şu gruplara erişimi de içerir: (Gerekli, sadece yöneticiler için)" invite_private: title: 'Mesajlaşmaya Davet Et' email_or_username: "Davet edilenin E-postası veya Kullanıcı Adı" @@ -969,8 +959,6 @@ tr_TR: sso_enabled: "Bu konuya davet etmek istediğiniz kişinin kullanıcı adını girin." to_topic_blank: "Bu konuya davet etmek istediğiniz kişinin kullanıcı adını veya e-posta adresini girin." to_topic_email: "Bir email adresi girdiniz. Arkadaşınızın konuya hemen cevap verebilmesini sağlayacak bir davetiye e-postalayacağız." - to_topic_username: "Bir kullanıcı adı girdiniz. Kullanıcıya, bu konuya davet bağlantısı içeren bir bildiri yollayacağız." - to_username: "Davet etmek istediğiniz kişinin kullanıcı adını girin. Kullanıcıya, bu konuya davet bağlantısı içeren bir bildiri yollayacağız." email_placeholder: 'isim@örnek.com' success_email: "{{emailOrUsername}} kullanıcısına davet e-postalandı. Davet kabul edildiğinde size bir bildiri göndereceğiz. Davetlerinizi takip etmek için kullanıcı sayfanızdaki davetler sekmesine göz atın." success_username: "Kullanıcıyı bu konuya katılması için davet ettik." @@ -981,9 +969,9 @@ tr_TR: other: "{{count}} gönderi" cancel: "Filteri kaldır" split_topic: - title: "Yeni Konuya Geç" + title: "Yeni konuya geç" action: "yeni konuya geç" - topic_name: "Yeni Konu Adı" + topic_name: "Yeni konu adı" error: "Gönderiler yeni konuya taşınırken bir hata oluştu." instructions: other: "Yeni bir konu oluşturmak ve bu konuyu seçtiğiniz {{count}} gönderi ile doldurmak üzeresiniz." @@ -1030,7 +1018,7 @@ tr_TR: expand_collapse: "aç/kapat" gap: other: "gizlenen {{count}} yorumu gör" - more_links: "{{count}} daha..." + more_links: "{{count}} tane daha..." unread: "Gönderi okunmamış" has_replies: other: "Cevaplar" @@ -1072,7 +1060,7 @@ tr_TR: other: "Bu gönderiye verilen {{count}} direk cevabı da silmek istiyor musunuz?" yes_value: "Evet, cevapları da sil" no_value: "Hayır, sadece bu gönderiyi" - admin: "gönderiyle alakalı admin aksiyonları" + admin: "gönderiyle alakalı yönetici işlemleri" wiki: "Wiki Yap" unwiki: "Wiki'yi Kaldır" convert_to_moderator: "Görevli Rengi Ekle" @@ -1189,7 +1177,7 @@ tr_TR: general: 'Genel' settings: 'Ayarlar' delete: 'Kategoriyi Sil' - create: 'Yeni Kategori' + create: 'Yeni kategori' save: 'Kategoriyi Kaydet' slug: 'Kategori Anahtarı' slug_placeholder: '(Opsiyonel) url için tire ile ayırılmış kelimeler' @@ -1231,13 +1219,10 @@ tr_TR: notifications: watching: title: "Gözleniyor" - description: "Bu kategorilerdeki tüm yeni konuları otomatik olarak gözleyeceksiniz. Tüm yeni gönderi ve konularla ilgili bildiri alacaksınız, ayrıca okunmamış ve yeni gönderilerin sayısı ilgili konunun yanında belirecek. " tracking: title: "Takip Ediliyor" - description: "Bu kategorilerdeki tüm yeni konuları otomatik olarak takip edeceksiniz. Okunmamış ve yeni gönderilerin sayısı ilgili konunun yanında belirecek." regular: title: "Standart" - description: "Biri @isim şeklinde sizden bahsederse ya da gönderinize cevap verirse bildirim alacaksınız." muted: title: "Susturuldu" description: "Bu kategorilerdeki yeni konular hakkında herhangi bir bildiri almayacaksınız ve okunmamışlar sekmenizde belirmeyecek. " @@ -1263,7 +1248,7 @@ tr_TR: custom_placeholder_notify_moderators: "Sizi neyin endişelendirdiğini açıklayıcı bir dille bize bildirin, ve mümkün olan yerlerde konu ile alakalı bağlantıları paylaşın." custom_message: at_least: "en az {{n}} karakter girin" - more: "{{n}} daha var.." + more: "{{n}} tane daha var.." left: "{{n}} kaldı" flagging_topic: title: "Topluluğumuzun medeni kalmasına yardımcı olduğunuz için teşekkürler!" @@ -1330,7 +1315,7 @@ tr_TR: with_topics: "%{filter} konular" with_category: "%{filter} %{category} konular" latest: - title: "En Son" + title: "En son" help: "yakın zamanda gönderi alan konular" hot: title: "Sıcak" @@ -1380,7 +1365,7 @@ tr_TR: title: "En Popüler" help: "geçtiğimiz yıl, ay, hafta veya gündeki en etkin başlıklar" all: - title: "Tüm Zamanlar" + title: "Tüm zamanlar" yearly: title: "Yıllık" monthly: @@ -1389,7 +1374,7 @@ tr_TR: title: "Haftalık" daily: title: "Günlük" - all_time: "Tüm Zamanlar" + all_time: "Tüm zamanlar" this_year: "Bu yıl" this_month: "Bu ay" this_week: "Bu hafta" @@ -1403,10 +1388,10 @@ tr_TR: admin_js: type_to_filter: "filtre girin..." admin: - title: 'Discourse Yönetici Paneli' + title: 'Discourse yönetici paneli' moderator: 'Moderatör' dashboard: - title: "Yönetici Paneli" + title: "Yönetici paneli" last_updated: "Yönetici panelinin son güncellenmesi:" version: "Versiyon" up_to_date: "Sisteminiz güncel durumda!" @@ -1417,7 +1402,7 @@ tr_TR: stale_data: "Güncelleme kontrolü bir süredir gerçekleşmiyor, lütfen sidekiq'in çalışır durumda olduğundan emin olun." version_check_pending: "Sanırım yeni güncelleme yaptınız. Harika!" installed_version: "Yüklendi" - latest_version: "En Son" + latest_version: "En son" problems_found: "Discourse kurulumuyla ilgili bazı sorunlar bulundu: " last_checked: "Son kontrol" refresh_problems: "Yenile" @@ -1430,18 +1415,18 @@ tr_TR: private_messages_title: "Mesajlar" space_free: "{{size}} serbest" uploads: "yüklemeler" - backups: "yedeklemeler" + backups: "Yedekler" traffic_short: "Trafik" traffic: "Uygulama web istekleri" page_views: "API İstekleri" page_views_short: "API İstekleri" - show_traffic_report: "Detaylı Trafik Raporunu Görüntüle" + show_traffic_report: "Detaylı trafik raporunu görüntüle" reports: today: "Bugün" yesterday: "Dün" last_7_days: "Son 7 Gün" last_30_days: "Son 30 Gün" - all_time: "Tüm Zamanlar" + all_time: "Tüm zamanlar" 7_days_ago: "7 Gün Önce" 30_days_ago: "30 Gün Önce" all: "Hepsi" @@ -1454,7 +1439,7 @@ tr_TR: latest_changes: "En son değişiklikler: lütfen sık güncelleyin!" by: "tarafından" flags: - title: "Rapor edilenler" + title: "Raporlar" old: "Eski" active: "Etkin" agree: "Onayla" @@ -1576,7 +1561,7 @@ tr_TR: filename: "Dosya adı" size: "Boyut" upload: - label: "Yükle" + label: "Yedek yükle" title: "Bu oluşuma bir yedekleme yükle" uploading: "Yükleniyor..." success: "'{{filename}}' başarıyla yüklendi." @@ -1589,7 +1574,7 @@ tr_TR: title: "Devam eden işlemi iptal et" confirm: "Devam eden işlemi iptal etmek istediğinize emin misiniz?" backup: - label: "Yedekleme" + label: "Yedek oluştur" title: "Yedek oluştur" confirm: "Yeni bir yedekleme başlatmak istiyor musunuz?" without_uploads: "Evet (dosya eklemeyin)" @@ -1601,7 +1586,7 @@ tr_TR: confirm: "Bu yedeği yok etmek istediğinize emin misiniz?" restore: is_disabled: "Geri getirme site ayarlarında devredışı bırakılmış." - label: "Geri getir" + label: "Geri yükle" title: "Yedeği geri getir" confirm: "Yedeği geri getirmek istediğinize emin misiniz?" rollback: @@ -1740,7 +1725,7 @@ tr_TR: title: "Kayıtlar" action: "Aksiyon" created_at: "Oluşturuldu" - last_match_at: "En Son Eşlenen" + last_match_at: "En son eşlenen" match_count: "Eşleşmeler" ip_address: "IP" topic_id: "Konu IDsi" @@ -2027,8 +2012,8 @@ tr_TR: add_host: "SUNUCU ekle" categories: all_results: 'Hepsi' - required: 'Gerekli' - basic: 'Basit kurulum' + required: 'Gerekli ayarlar' + basic: 'Genel ayarlar' users: 'Kullanıcılar' posting: 'Gönderiler' email: 'E-posta' @@ -2044,7 +2029,7 @@ tr_TR: legal: "Yasal" uncategorized: 'Diğer' backups: "Yedekler" - login: "Oturum aç" + login: "Oturum açma" plugins: "Eklentiler" badges: title: Rozetler @@ -2068,7 +2053,7 @@ tr_TR: reason: Neden expand: Genişlet … revoke_confirm: Bu rozeti iptal etmek istediğinize emin misiniz? - edit_badges: Rozetleri Düzenle + edit_badges: Rozetleri düzenle grant_badge: Rozet Ver granted_badges: Verilen Rozetler grant: Ver diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index c5ab9dd5c1..1918bfdbd9 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -321,9 +321,7 @@ zh_CN: github_profile: "Github" mailing_list_mode: "每有一个新帖就发送一封邮件给我(除非我屏蔽了主题或者分类)" watched_categories: "已关注" - watched_categories_instructions: "你将自动关注这些分类的所有新主题。你将收到新的帖子和主题的通知,并且会在靠近话题列表的地方增加一个关于未读和新帖子的数字。" tracked_categories: "已追踪" - tracked_categories_instructions: "你将会自动追踪这些分类中的所有新主题。未读和新帖子数量将出现在每个主题后。" muted_categories: "已屏蔽" muted_categories_instructions: "你不会收到这些分类的新主题的任何通知,他们也不会出现在你的未读标签中。" delete_account: "删除我的帐号" @@ -729,8 +727,6 @@ zh_CN: from_the_web: "来自网络" remote_tip: "图片链接" remote_tip_with_attachments: "图片或文件链接({{authorized_extensions}})" - local_tip: "点击从你的设备中选择一张图片。" - local_tip_with_attachments: "点击从你的设备中选择图片或文件(支持的格式: {{authorized_extensions}})。" hint: "(你也可以通过拖放至编辑器的方式来上传)" hint_for_supported_browsers: "(你也可以通过拖放或粘帖图片至编辑器的方式来上传)" uploading: "上传中" @@ -862,29 +858,21 @@ zh_CN: '2_4': '因为你在此主题内发表了回复,所以你将收到相关通知。' '2_2': '因为你在追踪此主题,所以你将收到相关通知。' '2': '因为你阅读了此主题,所以你将收到相关通知。' - '1_2': '只有当有人@你或者回复你的帖子时,你才会收到通知。' - '1': '只有当有人@你或者回复你的帖子时,你才会收到通知。' '0_7': '你将忽略关于此分类的所有通知。' '0_2': '你将忽略关于此主题的所有通知。' '0': '你将忽略关于此主题的所有通知。' watching_pm: title: "关注" - description: "一旦消息中有新消息,你都会收到通知。未读和新帖子的数量将显示在主题列表的每个主题后。" watching: title: "关注" - description: "一旦有关于这个主题的新帖子发表,你都会收到通知。未读贴的数量将出现在主题列表中每个主题的标题后。" tracking_pm: title: "追踪" - description: "未读和新帖子的数量将出现在消息旁。你只会在别人@你或回复你的主题时才会被提醒。" tracking: title: "追踪" - description: "未读贴和新帖的数量将出现在主题列表中每个主题的标题后。你只会在别人@你或有人回复了你的帖子时才会收到通知。" regular: title: "常规" - description: "当有人@你或者回复你的帖子时,你才会收到通知。" regular_pm: title: "常规" - description: "当有人@你或者回复你的消息时,你才会收到通知。" muted_pm: title: "防打扰" description: "你不会收到关于此消息的任何通知。" @@ -968,8 +956,6 @@ zh_CN: sso_enabled: "输入你想邀请的用户的用户名至该主题。" to_topic_blank: "输入你想邀请的用户的用户名或邮箱地址至该主题" to_topic_email: "你已经输入了邮箱地址。我们将发送一封邮件邀请让你的朋友可直接回复该主题。" - to_topic_username: "你已经输入了用户名。我们将发送提醒至该用户,其中将包含一个邀请他们至该主题的链接。" - to_username: "输入你想邀请的用户的用户名。我们将发送一个包含了到这个主题的链接的通知给用户。" email_placeholder: '电子邮箱地址' success_email: "我们发了一封邀请邮件给 {{emailOrUsername}}。我们将在邀请被接受后通知你。检查你的用户中的邀请标签页来追踪你的邀请。" success_username: "我们已经邀请了该用户参与该主题。" @@ -1230,13 +1216,10 @@ zh_CN: notifications: watching: title: "关注" - description: "你将会自动监视这些分类中的所有新主题。你会收到新帖子或新主题的通知,未读和新帖数量将出现在每个主题后。" tracking: title: "追踪" - description: "你将会自动追踪这些分类中的所有新主题。未读和新帖子数量将出现在每个主题后。" regular: title: "常规" - description: "当有人@你或者回复你的帖子时,你才会收到通知。" muted: title: "免打扰" description: "你不会收到这些分类中的任何新主题通知,并且他们将不会出现在你的未读列表中。" diff --git a/config/locales/server.cs.yml b/config/locales/server.cs.yml index 1b95b92ca8..70223dfdb6 100644 --- a/config/locales/server.cs.yml +++ b/config/locales/server.cs.yml @@ -185,7 +185,7 @@ cs: reviving_old_topic: | ### Oživit téma? - Poslední odpověď na tohle téma je přes %{days} dní stará. Vaše odpověď vyšvyhne toto téma v seznamu nahoru a uporozní všechny kdo se tohoto tématu účastní. + Poslední odpověď na tohle téma je přes %{days} dní stará. Vaše odpověď zvedne toto téma v seznamu nahoru a upozorní všechny diskutující. Určitě chcete pokračovat v této staré konverzaci? activerecord: diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml index 95d267228b..26466b8f9c 100644 --- a/config/locales/server.de.yml +++ b/config/locales/server.de.yml @@ -133,6 +133,7 @@ de: rss_description: latest: "Aktuelle Themen" hot: "Beliebte Themen" + posts: "Letzte Beiträge" too_late_to_edit: "Dieser Beitrag wurde vor zu langer Zeit erstellt. Er kann nicht mehr bearbeitet oder gelöscht werden." excerpt_image: "Bild" queue: @@ -1113,38 +1114,8 @@ de: subject_template: "[%{site_name}] Test der Mailzustellbarkeit" new_version_mailer: subject_template: "[%{site_name}] Neue Discourse Version, Update verfügbar" - text_body_template: | - Eine neue Version von [Discourse](http://www.discourse.org) ist verfügbar. - - Deine Version: %{installed_version} - Neue Version: **%{new_version}** - - Dies könnte interessant sein: - - - Erkundige dich über Neues im [GitHub Changelog](https://github.com/discourse/discourse/commits/master). - - - Upgrade deine Version unter [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade) mit dem "Upgrade"-Knopf. - - - Besuche [meta.discourse.org](http://meta.discourse.org) für Neuigkeiten, Diskussionen und Unterstützung für Discourse. new_version_mailer_with_notes: subject_template: "[%{site_name}] Update verfügbar" - text_body_template: | - Eine neue Version von [Discourse](http://www.discourse.org) ist verfügbar. - - Deine Version: %{installed_version} - Neue Version: **%{new_version}** - - So geht's weiter: - - - Lies nach, was sich in der neuen Version [geändert hat](https://github.com/discourse/discourse/commits/master). - - - Aktualisiere dein Forum mit dem "Upgrade" Knopf auf [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade). - - - Besuche [meta.discourse.org](http://meta.discourse.org) für weitere Neuigkeiten, Diskussionen und Hilfestellungen rund um Discourse. - - ### Versionshinweise - - %{notes} flags_reminder: flags_were_submitted: one: "Folgende Markierungen wurden währen der letzten Stunden vorgenommen." diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml index b8088bcaa8..0d04125421 100644 --- a/config/locales/server.fr.yml +++ b/config/locales/server.fr.yml @@ -1193,38 +1193,8 @@ fr: [mj]: https://www.mailjet.com/pricing new_version_mailer: subject_template: "[%{site_name}] Nouvelle version de Discourse, mise à jour disponible" - text_body_template: | - Une nouvelle version de [Discourse](http://www.discourse.org) est disponible. - - Votre version : %{installed_version} - Nouvelle version: **%{new_version}** - - Vous voulez peut-être: - - - Voir les nouveautés sur le [changelog de GitHub](https://github.com/discourse/discourse/commits/master). - - - Mettre à jour en allant sur [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade) en appuyant sur le bouton "Upgrade". - - - Visiter [meta.discourse.org](http://meta.discourse.org) pour vous tenir au courant, discuter et avoir de l'aide à propos de Discourse. new_version_mailer_with_notes: subject_template: "[%{site_name}] mise à jour disponible" - text_body_template: | - Une nouvelle version de [Discourse](http://www.discourse.org) est disponible. - - Votre version : %{installed_version} - Nouvelle version: **%{new_version}** - - Vous voulez peut-être: - - - Voir les nouveautés sur le [changelog de GitHub](https://github.com/discourse/discourse/commits/master). - - - Mettre à jour en allant sur [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade) et en appuyant sur le bouton "Upgrade". - - - Visiter [meta.discourse.org](http://meta.discourse.org) pour vous tenir au courant, discuter et avoir de l'aide à propos de Discourse. - - ### Notes de mise à jour - - %{notes} flags_reminder: flags_were_submitted: one: "Il y a un signalement qui a été soumis il y a plus de %{count} heures." @@ -1265,11 +1235,6 @@ fr: Afin d'être guider, merci de vous référer à notre [guide de bonne conduite](%{base_url}/guidelines). - usage_tips: - text_body_template: "Voilà quelques astuces pour vous aider à démarrer :\n\n## Continuez de descendre\n\nPour en lire plus, **il vous suffit de descendre !** Lorsque de nouvelles réponses arrivent, elles apparaissent automatiquement. \n\n## Navigation\n\n- Pour la recherche, votre page d'utilisateur, ou le menu, utiliser les **boutons icônes en haut à droite**. \n\n- Dans la liste des sujets, le titre vous emmènera toujours vers le prochain message non lu. Cliquer sur la date ou le nombre de messages pour allez au premier ou au dernier message. \n\n- Lorsque vous lisez un sujet, retournez en haut ↑ en cliquant sur le titre. Cliquez sur la barre de progression en bas à droite pour avoir une navigation complète, ou utilisez les touches Home et Fin. \n\n \n\n## Réponses\n\nPour répondre …\n\n- au **sujet dans sa globalité**, utilisez le bouton \"Répondre\" tout en bas de la page. \n\n- à une **personne spécifique**, utilisez le bouton \"Répondre\" sur leur message. \n\n- avec **un sujet lié**, utilisez le bouton \"Répondre en créant un\ - \ sujet lié\" à droite de chaque message. \n\nPour citer, sélectionnez le texte que vous voulez citer et appuyez sur un des boutons Répondre. \n\n \n\nPour alerter un utilisateur dans votre message, commencez à taper `@` pour pouvoir sélectionner un pseudo.\n\n\n\nConcernant les [icones Emoji](http://www.emoji.codes/), commencez par écrire `:` ou le traditionnel smiley `:)` :smile: \n\n## Actions \n\nÀ la fin de chaque message il y a des boutons pour les différentes actions possibles. \n\n \n\nPour faire savoir à quelqu'un que vous avez apprécié son message, cliquez sur le bouton **j'aime** en bas du message. Si vous voyez un problème avec un message, n'hésitez pas à cliquer sur le bouton **signaler** pour avertir en privé l'auteur ou les modérateurs du contenu de ce message. \n\nVous pouvez aussi **partager** un liens vers un message ou l'ajouter à vos **signets** pour le retrouver sur votre page d'utilisateur. \n\n## Notifications\n\nQuand quelqu'un répond à votre\ - \ message, vous cite, ou mentionne votre `@pseudo`, un nombre apparaitra immédiatement en haut à droite de la page. Utilisez-le pour consulter vos **notifications**. \n\n \n\nNe soyez pas inquiêt de manquer une réponse – vos notifications vous seront envoyées par courriel si vous n'êtes pas présent sur le site lorsqu'ils sont envoyés. \n\n## Vos préférences \n\nPar défaut, toutes les conversations de moins de deux jours sont considérées comme nouvelles, et chaque sujets auxquels vous avez participé (répondus, créés ou lu pendant un long moment) seront automatiquements suivis. \n\nVous verrez le badge bleu de nouveauté et le nombre de nouveaux messages au côté de ces discussions: \n\n \n\nVous pouvez modifier l'état du suivi des notifications d'une discussion en utilisant les boutons en bas de la discussion (vous pouvez également le modifier au niveau des catégories). Pour changer la façon dont vous suivez les discussions, ou pour définir de nouveaux suivis, consultez [vos préférences utilisateurs](%{base_url}/my/preferences). \n\n## Confiance de la communauté\n\nLes nouveaux utilisateurs sont limités pour des raisons de sécurité.\ - \ Plus vous participerez, plus vous gagnerez la confiance de la communauté et les restrictions disparaîtront. A plus haut [niveau de confiance](https://meta.discourse.org/t/what-do-user-trust-levels-do/4924), vous gagnerez de nouvelles compétences pour nous aider à gérer la communauté.\n" welcome_user: subject_template: "Bienvenue sur %{site_name} !" text_body_template: "Merci d'avoir rejoint %{site_name} et bienvenue ! \n\n%{new_user_tips}\n\nNous croyons au [comportement communautaire civilisé](%{base_url}/guidelines) en tous temps. \n\nAmusez-vous bien ! \n\n(Si, en tant que nouvel utilisateur, vous avez besoin de communiquer avec un [responsable](%{base_url}/about), répondez simplement à ce message.)\n" diff --git a/config/locales/server.nl.yml b/config/locales/server.nl.yml index 05f795aac9..c869a0cdbf 100644 --- a/config/locales/server.nl.yml +++ b/config/locales/server.nl.yml @@ -893,19 +893,6 @@ nl: subject_template: "[%{site_name}] E-mail Bezorgtest" new_version_mailer: subject_template: "[%{site_name}] Nieuwe Discourse-versie, update beschikbaar" - text_body_template: | - Er is een nieuwe versie van [Discourse](http://www.discourse.org) beschikbaar: - - Jouw versie: %{installed_version} - Nieuwe versie: **%{new_version}** - - Je wil misschien wel: - - - Kijken wat er nieuw is in de [GitHub changelog](https://github.com/discourse/discourse/commits/master). - - - Upgraden door naar [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade) te gaan en op de "Upgrade" knop te klikken. - - - Bezoek [meta.discourse.org](http://meta.discourse.org) voor nieuws, discussies over en ondersteuning voor Discourse. new_version_mailer_with_notes: subject_template: "[%{site_name}] Update beschikbaar" flags_reminder: diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml index ee3d18bd5c..30893d2a99 100644 --- a/config/locales/server.pt.yml +++ b/config/locales/server.pt.yml @@ -761,8 +761,8 @@ pt: email_domains_whitelist: "Lista de domínios de email que os utilizadores DEVEM usar para registar contas. AVISO: Utilizadores com domínios de email diferentes dos listados não serão permitidos!" forgot_password_strict: "Não informar os utilizadores da existência da conta quando estes utilizam o diálogo de password esquecida." log_out_strict: "Ao terminar sessão, saia de TODAS as sessões do utilizador em todos dispositivos" - version_checks: "Fazer o ping do Discourse Hub para atualização de versões e mostrar mensagens sobre novas versões no painel /admin" - new_version_emails: "Enviar um email para o endereço contact_email quando uma nova versão do Discourse estiver disponível." + version_checks: "Fazer o ping do Discourse Hub para atualização de versões e mostrar mensagens sobre novas versões no painel de administração" + new_version_emails: "Enviar um email para o endereço 'contact_email' quando uma nova versão do Discourse estiver disponível." port: "PROGRAMADOR APENAS! AVISO! Utilize esta porta HTTP em vez da porta 80. Deixar em branco para usar a porta padrão 80." force_hostname: "PROGRAMADOR APENAS! AVISO! Especifique um nome de servidor no URL. Deixe em branco para utilizar o valor por defeito." invite_expiry_days: "Durante quantos dias as chaves de convite são válidas." @@ -787,16 +787,16 @@ pt: enable_yahoo_logins: "Ativar autenticação pelo Yahoo" enable_google_oauth2_logins: "Ativar autenticação Google Oauth2. Este é o método que permite a autenticação que a Google atualmente suporta. Requer chave e segredo." google_oauth2_client_id: "ID do cliente da sua aplicação Google." - google_oauth2_client_secret: "Segredo do cliente da sua aplicação Google." + google_oauth2_client_secret: "Chave do cliente da sua aplicação Google." enable_twitter_logins: "Ativar autenticação pelo Twitter, requer twitter_consumer_key e twitter_consumer_secret" twitter_consumer_key: "Chave de consumidor para autenticação pelo Twitter, registada em http://dev.twitter.com" - twitter_consumer_secret: "Segredo do Consumidor para a autenticação pelo Twitter, registada em http://dev.twitter.com" + twitter_consumer_secret: "Chave do Consumidor para a autenticação pelo Twitter, registada em http://dev.twitter.com" enable_facebook_logins: "Ativar autenticação pelo Facebook, requer facebook_app_id e facebook_app_secret" facebook_app_id: "Id App usado para autenticação pelo Facebook, registado em https://developers.facebook.com/apps" - facebook_app_secret: "Segredo App para autenticação com o Facebook, registado em https://developers.facebook.com/apps" + facebook_app_secret: "Chave da App para autenticação com o Facebook, registado em https://developers.facebook.com/apps" enable_github_logins: "Ativar autenticação via Github, requer github_client_id e github_client_secret" github_client_id: "Id do cliente para autenticação pelo Github, registado em https://github.com/settings/applications" - github_client_secret: "Segredo do Cliente para autenticação pelo Github, registado em https://github.com/settings/applications" + github_client_secret: "Chave do Cliente para autenticação pelo Github, registado em https://github.com/settings/applications" allow_restore: "Permitir o restauro, que pode substituir TODOS os dados do sítio. Deixar a falso a menos que planeie restaurar uma cópia de segurança" maximum_backups: "Valor máximo de cópias de segurança a serem guardadas em disco. Cópias de Segurança antigas são automaticamente eliminadas." backup_daily: "Criar automaticamente uma cópia de segurança local, uma vez por dia." @@ -975,7 +975,7 @@ pt: embed_post_limit: "Número máximo de mensagens a serem incorporadas." embed_whitelist_selector: "Seletor CSS para elementos permitidos em incorporações." embed_blacklist_selector: "Seletor CSS para elementos que foram removidos de incorporações." - notify_about_flags_after: "Se houver sinalizações que não tenham sido tratadas após tantas horas, envie um email para contact_email. Configurar a 0 para desativar." + notify_about_flags_after: "Se houver sinalizações que não tenham sido tratadas após tantas horas, envie um email para 'contact_email'. Configurar a 0 para desativar." enable_cdn_js_debugging: "Permitir que /logs exiba erros próprios ao adicionar permissões de origem-cruzada em todos os js incluídos." show_create_topics_notice: "Se o sítio tem menos de 5 tópicos públicos, mostrar um aviso pedindo aos administradores a criação de mais tópicos." delete_drafts_older_than_n_days: Eliminar rascunhos mais antigos que (n) days. @@ -1159,38 +1159,10 @@ pt: \ o DNS na sua mensagem HELO. Se não for, isto irá fazer com que o seu email seja rejeitado por muitos serviços de email.\n\n(A maneira *fácil* é criar uma conta gratuita em [Mandrill][md] ou [Mailgun][mg] ou [Mailjet][mj], que tem vários planos de email gratuitos e será bom para pequenas comunidades. Irá precisar na mesma de configurar os registos SPF e o DKIM no seu DNS!) \n\nEsperamos que tenha recebido este teste de entrega de email sem problemas! \n\nBoa sorte, \n\nOs seus amigos em [Discourse](http://www.discourse.org)\n\n[0]: %{base_url}\n[1]: http://www.kitterman.com/spf/validate.html\n[2]: http://mxtoolbox.com/ReverseLookup.aspx\n[3]: http://www.dkim.org/\n[4]: http://whatismyipaddress.com/blacklist-check\n[7]: http://dkimcore.org/tools/dkimrecordcheck.html\n[8]: http://www.openspf.org/SPF_Record_Syntax\n[md]: http://mandrill.com\n[mg]: http://www.mailgun.com/\n[mj]: https://www.mailjet.com/pricing\n" new_version_mailer: subject_template: "[%{site_name}] Nova versão Discourse, atualização disponível" - text_body_template: | - Uma nova versão de [Discourse](http://www.discourse.org) está disponível. - - A sua versão: %{installed_version} - Nova versão: **%{new_version}** - - Pode desejar: - - - Ver o que há de novo em [GitHub changelog](https://github.com/discourse/discourse/commits/master). - - - Atualizar visitando [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade), e pressionando o botão "Atualizar". - - - Visitar [meta.discourse.org](http://meta.discourse.org) para notícias, debates, e suporte para o Discourse. + text_body_template: "Uma nova versão de [Discourse](http://www.discourse.org) está disponível. \n\nA sua versão: %{installed_version}\nNova versão: **%{new_version}**\n\nPode desejar: \n\n- Ver o que há de novo em [GitHub changelog](https://github.com/discourse/discourse/commits/master).\n\n- Atualizar a partir do seu navegador em [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade).\n\n- Visitar [meta.discourse.org](http://meta.discourse.org) para notícias, debates, e suporte para o Discourse.\n" new_version_mailer_with_notes: subject_template: "[%{site_name}] atualização disponível" - text_body_template: | - Uma nova versão de [Discourse](http://www.discourse.org) está disponível. - - A sua versão: %{installed_version} - Nova versão: **%{new_version}** - - Pode desejar: - - - Ver o que há de novo em [GitHub changelog](https://github.com/discourse/discourse/commits/master). - - - Atualizar visitando [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade), e pressionando o botão "Atualizar". - - - Visitar [meta.discourse.org](http://meta.discourse.org) para notícias, debates, e suporte para o Discourse. - - ### Notas de lançamento - - %{notes} + text_body_template: "Uma nova versão de [Discourse](http://www.discourse.org) está disponível. \n\nA sua versão: %{installed_version}\nNova versão: **%{new_version}**\n\nPode desejar: \n\n- Ver o que há de novo em [GitHub changelog](https://github.com/discourse/discourse/commits/master).\n\n- Atualizar a partir do seu navegador em [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade).\n\n- Visitar [meta.discourse.org](http://meta.discourse.org) para notícias, debates, e suporte para o Discourse.\n\n### Release notes\n\n%{notes}\n" flags_reminder: flags_were_submitted: one: "Estas sinalizações foram submetidas há 1 hora atrás." @@ -1218,9 +1190,10 @@ pt: subject_template: "Mensagem oculta devido a sinalizações da comunidade" text_body_template: "Olá,\n\nEsta é uma mensagem automática de %{site_name} para informá-lo que a sua mensagem foi oculta. \n\n%{base_url}%{url} \n\n%{flag_reason}\n\nMúltiplos membros da comunidade sinalizaram esta mensagem antes de ser escondida, por isso considere como poderá rever a sua mensagem para refletir o seu feedback. **Pode editar a sua mensagem após %{edit_delay} minutos, e esta será automaticamente exibida.**\n\nContudo, se a mensagem for escondida pela comunidade uma segunda vez, irá manter-se escondida até ser tratada pelo pessoal – e poderão ainda ocorrer ações, incluindo uma possível suspensão da sua conta.\n\nPara orientação adicional, por favor consulte as [diretrizes da comunidade](%{base_url}/guidelines).\n" usage_tips: - text_body_template: "Aqui ficam algumas dicas para que possa começar:\n\n## Apenas arraste para baixo\n\nPara ler mais, **simplesmente continue a arrastar para baixo!** À medida que novas mensagens ou novos tópicos vão surgindo, estes irão surgir automaticamente.\n\n## Navegação\n\n- Para pesquisar, a sua página de utilizador, ou o menu , utilize **o ícone no canto superior direito**.\n\n- Selecionar o título de um tópico irá levá-lo sempre à próxima mensagem não lida no tópico. Para inserir no início ou no final, selecione a data ou a contagem de mensagens.\n\n- Ao ler um tópico, salte para o topo ↑ ao selecionar o título do tópico. Selecione a barra de progresso no canto inferior direito para controlos de navegação completos, ou utilize as chaves página principal e final.\n\n\n\n## Responder\n\nPara responder ...\n\n- ao **tópico em geral**, utilize o botão Responder no final de cada tópico.\n\n- a uma **pessoa específica**, utilize o botão Responder nas suas mensagem.\n\n- com um **novo tópico**, utilize Responder com novo Tópico à direita desta mensagem.\n\nPara citar, selecione apenas\ - \ o texto que deseja citar, e de seguida pressione qualquer botão de Resposta.\n\n\n\nPara mencionar alguém na sua resposta, mencione o seu nome. Digite `@` para começar a selecionar um nome.\n\n\n\nPara [Emoji padrão](http://www.emoji.codes/), simplesmente digite `:` ou os tradicionais risonhos `:)` :smile: \n\n## Ações\n\nExistem botões de ação no final de cada mensagem.\n\n\n\nPara deixar alguém saber que gostou das suas mensagens, utilize o botão **gosto**. Se vir um problema com uma mensagem, avise-os ou avise o nosso pessoal em privado utilizando o botão de **sinalização**. \n\nPode também **partilhar** uma hiperligação para uma mensagem, ou **marcá-la** para mais tarde ter a referência na sua página de utilizador.\n\n## Notificações\n\nQuando alguém responde à sua mensagem, cita a sua mensagem ou menciona o seu `@nome-de-utilizador`, um número irá aparecer imediatamente no canto superior direito na página. Utilize-o ao aceder às suas **notificações**. \n\n\n\nNão se preocupe em falhar uma resposta – irá receber notificações por email se não estiver online no momento em que elas chegam.\n\n## As Suas Preferências\n\ - \nPor defeito, todas as conversações com menos de dois dias são consideradas novas, e qualquer conversação onde tenha participado (respondido, criado, ou lido por um extenso período) serão automaticamente acompanhadas.\n\nVerá o indicador numérico e o indicador azul (novo) junto desses tópicos: \n\n\n\nPode alterar o estado individual das notificações de um tópico através do controlo no final do tópico (isto também pode ser definido por categoria). Para mudar a maneira como os tópicos são acompanhados, ou a definição de novo, consulte [as suas preferências](%{base_url}/my/preferences). \n\n##Confiança da Comunidade\n\nNovos utilizadores estão um pouco limitados por razões de segurança. À medida que for participando, irá ganhar confiança por parte da comunidade, tornar-se um cidadão completo e essas limitações serão automaticamente removidas. Com um [nível de confiança] suficientemente alto (https://meta.discourse.org/t/what-do-user-trust-levels-do/4924), irá ganhar ainda mais capacidades para ajudar-nos a gerir a comunidade em conjunto.\n\n" + text_body_template: "Aqui ficam algumas dicas para que possa começar: \n\n## Leitura \n\nPara ler mais, **simplesmente continue a arrastar para baixo!** \n\nÀ medida que novas mensagens ou novos tópicos vão surgindo, estes irão surgir automaticamente – não é necessário atualizar a página. \n\n## Navegação \n\n- Para pesquisar, a sua página de utilizador, ou o menu , utilize **o ícone no canto superior direito**. \n\n- Selecionar o título de um tópico irá levá-lo sempre à **próxima mensagem não lida** no tópico. Para inserir no início ou no final, selecione a contagem de respostas ou a data da última resposta.\n\n \n\n- Ao ler um tópico, selecione a barra de progresso no canto inferior direito para controlos de navegação completos. Salte rapidamente para o topo ao selecionar o título do tópico. Pressione ? para uma lista rápida de atalhos do teclado. \n\n\n\n## Responder \n\n- Para responder ao **tópico em geral**, utilize no final de cada tópico. \n\n- Para responder a uma **pessoa específica**, utilize nas suas mensagens. \n\n- Para responder com um **novo tópico**, utilize à direita desta mensagem. Tanto os tópicos antigos como os novos serão ligados automaticamente.\n\nPara inserir uma citação, selecione o texto que deseja citar, e de seguida pressione qualquer botão de Resposta. Repita para múltiplas citações!\n \n\nPara notificar alguém da sua resposta, mencione o seu nome. Digite `@` para começar a selecionar um nome de utilizador.\n\n\n\nPara utilizar [Emoji padrão](http://www.emoji.codes/), simplesmente digite `:` para encontrar por nome, ou os tradicionais risonhos `;)` \n\n\n\n## Ações \n\nExistem botões de ação no final de cada mensagem. \n\n<\n\nPara deixar alguém saber que gostou e apreciou as suas mensagens, utilize o botão **gosto**. Partilhe o amor!\n\nSe vir um problema com uma mensagem de alguém, avise a pessoa em privado, ou avise [o nosso pessoal](/about) sobre a mesma utilizando o botão **sinalizar**. Pode também **partilhar** uma hiperligação para uma mensagem, ou **marcá-la** para mais tarde ter a referência na sua página de utilizador. \n\n## Notificações \n\nQuando alguém responde à sua mensagem, cita a sua mensagem\ + \ ou menciona o seu `@nome-de-utilizador`, um número irá aparecer imediatamente no canto superior direito na página. Utilize-o para aceder às suas **notificações**. \n\n\n\nNão se preocupe em falhar uma resposta – irá receber notificações por email que chegam quando está ausente. \n\n## As Suas Preferências \n\n- Todos os tópicos com menos de **dois dias** são considerados novos.\n\n- Qualquer tópico em que tenha **participado ativamente** (respondido, criado, ou lido por um extenso período) será acompanhado automaticamente.\n\n Verá o indicador numérico e o indicador azul (novo) junto desses tópicos: \n\n\n\nPode alterar as suas notificações para qualquer tópico através do controlo de notificaçõesno final do tópico \n\n\n\nPode também alterar as configurações de estados de notificação por categoria, se quer observar cada novo tópico numa categoria específica.\n\nPara alterar qualquer uma destas configurações, consulte [as suas preferências] (%{base_url}/my/preferences). \n\n##Confiança da Comunidade \n\nÀ medida que for participando, irá ganhar confiança por parte da comunidade, tornar-se um cidadão completo e limitações de novos utilizadores serão automaticamente removidas.\ + \ Com um [nível de confiança] suficientemente alto (https://meta.discourse.org/t/what-do-user-trust-levels-do/4924), irá ganhar novas capacidades para ajudar-nos a gerir a comunidade em conjunto.\n" welcome_user: subject_template: "Bem-vindo a %{site_name}!" text_body_template: | diff --git a/config/locales/server.ro.yml b/config/locales/server.ro.yml index 129ef0aef3..e917e7b789 100644 --- a/config/locales/server.ro.yml +++ b/config/locales/server.ro.yml @@ -208,6 +208,8 @@ ro: base: warning_requires_pm: "Puteţi ataşa avertizări doar la mesajele private." too_many_users: "Puteţi trimite avertizări la un singur utilizator odată." + cant_send_pm: "Ne pare rău, nu puteți trimite un mesaj privat acelui utilizator." + no_user_selected: "Trebuie selectat un nume de utilizator valid." user: attributes: password: @@ -350,6 +352,18 @@ ro: one: "mai puţin de 1 minut în urmă" few: "mai puţin de %{count} minute în urmă" other: "mai puţin de %{count} minute în urmă" + x_minutes: + one: "acum 1 minut" + few: "acum %{count} minute" + other: "acum %{count} minute" + about_x_hours: + one: "acum o ora" + few: "acum %{count} ore" + other: "acum %{count} ore" + x_days: + one: "acum o zi" + few: "acum %{count} zile" + other: "acum %{count} zile" password_reset: choose_new: "Alegeți o parolă nouă" choose: "Alegeți o parolă" @@ -911,38 +925,8 @@ ro: subject_template: "[%{site_name}] Testul de trimitere Email" new_version_mailer: subject_template: "[%{site_name}] O nouă versiune de Discourse, reactializare valabilă" - text_body_template: | - O nouă versiune de [Discourse](http://www.discourse.org) este valabilă. - - Your version: %{installed_version} - New version: **%{new_version}** - - Poate ați dorii să: - - - Vedeți ce mai e nou în [ registrul de schimbări GitHub](https://github.com/discourse/discourse/commits/master). - - - Reactualizați vizitând [%{base_url}/admin/docker](%{base_url}/admin/docker) Și apăsând butonul "Actualizare". - - - Vizitați [meta.discourse.org](http://meta.discourse.org) pentru noutăți, disctii, și ajutor pentru Discourse. new_version_mailer_with_notes: subject_template: "[%{site_name}] update Actualizare valabilă" - text_body_template: | - O nouă versiune de [Discourse](http://www.discourse.org) este valabilă. - - Versiunea actuală: %{installed_version} - Versiunea nouă: **%{new_version}** - - Poate ați dori să: - - - Vezi ce e nou în [registrul de schimbări GitHub](https://github.com/discourse/discourse/commits/master). - - - Reactualizați vizitând [%{base_url}/admin/docker](%{base_url}/admin/docker), Și apăsând butonul "Actualizare". - - - Vizitați [meta.discourse.org](http://meta.discourse.org) pentru noutăți, discutii, și ajutor pentru Discourse. - - ### Release notes - - %{notes} flags_reminder: please_review: "Vă rugăm revizionați-le." post_number: "postarea" diff --git a/config/locales/server.sv.yml b/config/locales/server.sv.yml index 008cd94fb4..5f8b7e9f18 100644 --- a/config/locales/server.sv.yml +++ b/config/locales/server.sv.yml @@ -22,6 +22,7 @@ sv: disable_remote_images_download_reason: "Fjärrbilds nedladdning är inaktiverad eftersom det inte fanns tillräckligt mycket lagringsutrymme tillgängligt." anonymous: "Anonym" errors: + format: '%{attribute} %{message}' messages: too_long_validation: "är begränsad till %{max} karaktärer; du skrev in %{length}. " invalid_boolean: "Ogiltigt boolean." @@ -128,6 +129,7 @@ sv: rss_description: latest: "Senaste trådar" hot: "Heta trådar" + posts: "Senaste inläggen" too_late_to_edit: "Inlägget skapades för långt tillbaka i tiden. Det kan inte lägre redigeras eller tas bort." excerpt_image: "bild" queue: @@ -135,6 +137,7 @@ sv: groups: errors: can_not_modify_automatic: "Du kan inte modifiera en automatisk grupp" + member_already_exist: "'%{username}' är redan medlem i denna grupp." default_names: everyone: "alla" admins: "administratörer" @@ -149,6 +152,16 @@ sv: until_posts: one: "1 inlägg" other: "%{count} inlägg" + new-topic: | + Välkommen till %{site_name} — **tack för att du startade en ny konversation!** + + - Låter titeln intressant om du läser det högt? Är det en bra sammanfattning? + + - Vem skulle kunna vara intresserad av detta? Varför är det viktigt? Vad för typ av svar förväntar du dig? + + - Inkludera vanligt förekommande ord i din tråd så att andra kan *hitta* det. För att gruppera din tråd med relaterade trådar, välj en kategori. + + För mer, [see our community guidelines](/guidelines). This panel will only appear for your first %{education_posts_text}. activerecord: attributes: category: @@ -518,6 +531,8 @@ sv: allow_user_locale: "Tillåt användare att själva välja språk" min_post_length: "Minsta tillåtna inläggslängd i antal tecken" unique_posts_mins: "Hur många minuter innan en användare kan göra ett inlägg med precis samma innehåll igen" + title: "Namnet på denna webbplats som används i titel-taggen." + site_description: "Beskriv denna webbplats i en mening, som sedan används i meta-description-taggen." queue_jobs: "Köa diverse jobb i sidekiq, om urkryssat så körs köer infogat" favicon_url: "En favicon för till webbplats, se http://en.wikipedia.org/wiki/Favicon" apple_touch_icon_url: "Ikon som används för Apples touch-enheter. Rekommenderad storlek är 144*144 pixlar." @@ -551,6 +566,9 @@ sv: faq_url: "Om du har en FAQ någon annanstans som du vill använda, skriv den fullständiga URL:en här." privacy_policy_url: "Om du har ett dokument med en integritetspolicy någon annanstans som du vill använda, skriv den fullständiga URL:en här." default_digest_email_frequency: "Hur ofta användare får emailutskick som standard. De kan ändra detta val under sina inställningar." + enable_user_directory: "Tillhandahåll en bläddringsbar användarkatalog" + allow_anonymous_posting: "Tillåt användare att växla till anonymt läge" + full_name_required: "Fullständigt namn är ett obligatoriskt fält i en användares profil." errors: invalid_email: "Felaktig e-postadress." invalid_username: "Det finns ingen användare med detta användarnamn." @@ -568,8 +586,10 @@ sv: moved_post: "%{display_username} moved your post to %{link}" invitee_accepted: "%{display_username} accepted your invitation" search: + within_post: "#%{post_number} av %{username}" types: category: 'Categories' + topic: 'Resultat' user: 'Users' original_poster: "Original Poster" most_posts: "Most Posts" @@ -592,7 +612,10 @@ sv: wait_approval: "Thanks for signing up. We will notify you when your account has been approved." active: "Your account is active and ready." not_activated: "You can't log in yet. We sent an activation email to you. Please follow the instructions in the email to activate your account." + not_allowed_from_ip_address: "Du kan inte logga in som %{username} från den IP-adressen." + admin_not_allowed_from_ip_address: "Du kan inte logga in som admin från den IP-adressen." suspended: "Du kan inte logga in förrän %{date}." + suspended_with_reason: "Konto avstängt t.o.m. %{date}: %{reason}" errors: "%{errors}" not_available: "Not available. Try %{suggestion}?" something_already_taken: "Something went wrong, perhaps the username or email is already registered. Try the forgot password link." @@ -607,6 +630,18 @@ sv: blocked: "är inte tillåtet." test_mailer: subject_template: "[%{site_name}] Email Deliverability Test" + new_version_mailer: + subject_template: "[%{site_name}] Ny version av Discourse, uppdatering tillgänglig" + new_version_mailer_with_notes: + subject_template: "[%{site_name}] uppdatering tillgänglig" + flags_reminder: + post_number: "inlägg" + flags_dispositions: + agreed: "Tack för att du meddelade oss. Vi håller med om att det finns ett problem och vi undersöker det. " + agreed_and_deleted: "Tack för att du meddelade oss. Vi håller med om att det finns ett problem och vi har raderat inlägget." + disagreed: "Tack för att du meddelande oss. Vi undersöker det." + deferred: "Tack för att du meddelande oss. Vi undersöker det." + deferred_and_deleted: "Tack för att du meddelande oss. Vi har raderat inlägget." system_messages: post_hidden: subject_template: "Post hidden due to community flagging" @@ -636,6 +671,7 @@ sv: one: "1 användare väntar på godkännande" other: "%{count} användare väntar på godkännande" unsubscribe_link: "To unsubscribe from these emails, visit your [user preferences](%{user_preferences_url})." + subject_re: "Sv:" user_notifications: previous_discussion: "Föregående Svar" unsubscribe: @@ -707,18 +743,32 @@ sv: subject_template: "[%{site_name}] Activate your new account" text_body_template: "Välkommen till %{site_name}!\n\nKlicka på följande länk för att bekräfta och aktivera ditt nya konto:\n%{base_url}/users/activate-account/%{email_token}\n\nOm länken ovan inte går att klicka på kan du kopiera och klistra in länken i adressfältet i din webbläsare. \n" page_not_found: + popular_topics: "Populära" + recent_topics: "Senaste" see_more: "Mer" + terms_of_service: + title: "Användarvillkor" + signup_form_message: 'Jag har läst och accepterat Användarvillkoren.' deleted: 'raderad' upload: pasted_image_filename: "Inklistrad bild" images: size_not_found: "Vi beklagar, men vi kunde inte avgöra bildens storlek. Kanske är din bildfil skadad?" + email_log: + no_user: "Kan inte hitta användare med id %{user_id}" + anonymous_user: "Användare är anonym" + seen_recently: "Användare sågs senast" + post_deleted: "inlägg raderades av författaren" + user_suspended: "användare blev avstängd" about: "Om" guidelines: "Riktlinjer" + privacy: "Integritet" edit_this_page: "Redigera denna sida" csv_export: boolean_yes: "Ja" boolean_no: "Nej" + guidelines_topic: + title: "FAQ/Riktlinjer" tos_topic: title: "Villkor" badges: diff --git a/config/locales/server.tr_TR.yml b/config/locales/server.tr_TR.yml index e99b4bb8ae..dd65116c4f 100644 --- a/config/locales/server.tr_TR.yml +++ b/config/locales/server.tr_TR.yml @@ -1074,39 +1074,8 @@ tr_TR: \ \n[2]: http://mxtoolbox.com/ReverseLookup.aspx \n[3]: http://www.dkim.org/ \n[4]: http://whatismyipaddress.com/blacklist-check \n[7]: http://dkimcore.org/tools/dkimrecordcheck.html \n[8]: http://www.openspf.org/SPF_Record_Syntax [md]: http://mandrill.com [mg]: http://www.mailgun.com/ [mj]: https://www.mailjet.com/pricing\n" new_version_mailer: subject_template: "[%{site_name}] Yeni Discourse versiyonu, güncelleme var" - text_body_template: |2 - [Discourse'un](http://www.discourse.org) yeni versiyonu hazır. - - Sizin kullandığınız versiyon: %{installed_version} - Yeni versiyon: **%{new_version}** - - Aşağıdakileri uygulayabilirsiniz: - - - Yenilikleri [GitHub değişiklikler listesinde] görüntüleyebilirsiniz (https://github.com/discourse/discourse/commits/master). - - - [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade) sayfasını ziyaret ederek ve "Güncelle" butonuna basarak yeni versiyona geçebilirsiniz. - - - Haberler, tartışmalar ve Discourse ile ilgili destek için burayı ziyaret edebilirsiniz: [meta.discourse.org](http://meta.discourse.org) new_version_mailer_with_notes: subject_template: "[%{site_name}] güncellemesi var" - text_body_template: |+ - [Discourse'un](http://www.discourse.org) yeni versiyonu hazır. - - Sizin kullandığınız versiyon: %{installed_version} - Yeni versiyon: **%{new_version}** - - Aşağıdakileri uygulayabilirsiniz: - - - Yenilikleri [GitHub değişiklikler listesinde] görüntüleyebilirsiniz (https://github.com/discourse/discourse/commits/master). - - - [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade) sayfasını ziyaret ederek ve "Güncelle" butonuna basarak yeni versiyona geçebilirsiniz. - - - Haberler, tartışmalar ve Discourse ile ilgili destek için burayı ziyaret edebilirsiniz: [meta.discourse.org](http://meta.discourse.org) - - ### Yeni sürüm notları - - %{notes} - flags_reminder: flags_were_submitted: other: "Bu bayraklar %{count} saat önce verildi." @@ -1144,6 +1113,10 @@ tr_TR: Ancak, eğer gönderiniz kullanıcılar tarafından ikinci kere gizlenirse, görevliler tarafından incelenene kadar gizli kalacaktır - ve hesabınızın askıya alınması dahil olmak üzere farklı önlemler alınabilir. Daha fazla bilgi için, lütfen [topluluk yönergelerine bakın](%{base_url}/guidelines). + usage_tips: + text_body_template: "%{site_name} &mdash sitesine katıldığınız için teşekkür ederiz, hoşgeldiniz!\n\nHemen başlayabilmeniz için bir kaç pratik tavsiye: \n\n## Sayfayı aşağı kaydırmaya devam edin\n\nFarkettiğiniz gibi bir sonraki sayfa butonu ya da sayfa sayısı yok - daha fazla okumak için, **sayfayı aşağı kaydırmanız yeterli!** \n\nYeni gönderiler geldikçe, otomatik olarak görünecekler. \n\n## Neredeyim?\n\n- Arama, kullanıcı sayfanız veya menü için **sağ üstteki ikonlu butona** tıklayın.\n\n- Herhangi bir konu başlığı sizi bir sonraki okunmamış gönderiye götürür. En üst veya en alta gitmek için son aktivite zamanı ve gönderi sayısını kullanın.\n\n\n\n- Bir konuyu okurken, konu başlığına tıklayarak en üste ↑ gidin. Tüm navigasyonla ilgili kontroller için sağ alttaki yeşil ilerleme barını seçin, veya home ve end tuşlarını kullanın. \n\nDilediğiniz zaman Klavye kısayollarını görüntülemek için klavyenizden ? tuşuna basabilirsiniz.\n\n\n\n## Nasıl cevaplarım?\n\n- Genel olarak konuya cevap vermek için, sayfanın en altındaki Cevapla butonunu kullanın.\n\n- Belirli bir gönderiye cevap vermek için, o gönderinin altında bulunan Cevapla butonunu kullanın. \n\n- Sohbeti bölmeden başka bir yöne çekmek için, gönderinin sağındaki Bağlantılı Konu Oluştururarak Cevapla'ya tıklayın.\n\nCevabınızda birinden alıntı yapmak için, alıntı yapmak istediğiniz metni seçin ve sonrasında herhangi bir Cevapla butonuna tıklayın.\n\n\n\nCevabınızda birine ping atmak istiyorsanız, o kişinin isminden bahsedin. `@` yazdığınız an otomatik tamamlayıcı pop-up'ı belirecektir.\n\ + \n\n\n[Standart Emoji](http://www.emoji.codes/) için, `:` yazın ya da gülücük `:)` atın :smile:\n\n\n\n## Başka neler yapabilirim?\n\nHer gönderinin altında aksiyon butonları var.\n\n\n\nBirine gönderisini beğendiğinizi belirtmek istiyorsanız, **beğen** butonunu kullanın. Gönderiyle ilgili bir sorun görüyorsanız, [görevlilere](%{base_url}/about) durumu bildirmek için **bayrakla** butonunu kullanın.\n\n**Paylaş** butonu aracılığıyla gönderiye bir bağlantı paylaşabilir, veya **işaretle** butonu ile gönderiyi işaretleyip sonrasında kullanıcı sayfanızdan kolayca ulaşabilirsiniz.\n\n## Kim benimle konuşuyor?\n\nBiri gönderinize cevap verir, gönderinizi alıntılar ya da `@kullanıcıadı` nızdan bahsederse, sayfanızın en sağ üstünde bir sayı hemen beliriverir. Bu sayıya tıklayarak **bildiriler**inize ulaşabilirsiniz.\n\n## Bildirimler\n\nEğer bir kullanıcımız sizden bir konusunda ya da mesajında @kullanıcıadı şeklinde bahsederse üst kısımda yer alan bildirim panelinden tüm bu etkinliği izleyebilirsiniz. Bu alandan yine konularınıza verilen yanıtları ve beğenileri de takip edebilirsiniz.\n\n\n\n\nBir cevabı kaçıracaksınız diye kaygılanmayın – çevrimiçi değilseniz direk cevaplar (ve mesajlar) geldikçe tarafınıza bilgilendirme e-postası atılır.\n\n## Hangi durumda sohbetler yeni sayılır?\n\nVarsayılan olarak, üzerinden henüz iki gün geçmemiş tüm sohbetler yeni sayılır. Katıldığınız (cevapladığınız, oluşturduğunuz, veya uzun bir süre okuduğunuz) tüm sohbetler otomatik olarak takip edilir.\n\nBu konuların yanında mavi yeni ve sayı işaretlerini göreceksiniz.\n\n\n\nHerhangi bir konunun bildiri durumunu o konunun altın bulunan kontrol aracılığıyla değiştirebilirsiniz.\ + \ (Bu ayar kategori bazında da yapılabilir.) Konuları nasıl takip ettiğinizi ya da yeninin tanımını değiştirmek için, [kullanıcı ayarlarınıza](%{base_url}/my/preferences) göz atın.\n\n\n\n## Bazı şeyleri niye yapamıyorum?\n\nGüvenlik sebeplerin ötürü yeni kullanıcılar bazı kısıtlamalara tabidir. Burada sohbetlere katıldıkça, topluluğun güvenini kazanırsınız, onun tam bir parçası haline gelirsiniz ve bu limitler de otomatik olarak kalkar. Yeterli yükseklikteki [güvenlik seviyelerinde](https://meta.discourse.org/t/what-do-user-trust-levels-do/4924) ise topluluğumuzu birlikte yönetmemize yardımcı olabilmenizi sağlayacak yetkiler kazanırsınız.\n\nZiyaretinizin keyfini çıkarın!\n\n(Eğer yeni bir kullanıcı olarak [görevlilerle](%{base_url}/about) iletişim kurmak isterseniz, bu mesajı cevaplamanız yeterli.)\n" welcome_user: subject_template: "%{site_name} sitesine hoşgeldiniz!" text_body_template: "%{site_name} sitesine katıldığınız için teşekkür ederiz, hoşgeldiniz!\n\n%{new_user_tips}\n\nBiz her zaman [medeni topluluk davranışına](%{base_url}/guidelines) inanıyoruz. \n\nZiyaretinizin keyfini çıkarın!\n\n(Eğer yeni bir kullanıcı olarak [görevlilerle](%{base_url}/about) iletişim kurmak isterseniz, bu mesajı cevaplamanız yeterli.)\n" @@ -1943,7 +1916,7 @@ tr_TR: admin_login: success: "E-posta Gönderildi" error: "Hata!" - email_input: "Yönetici E-postası" + email_input: "Yönetici e-postası" submit_button: "E-posta Gönder" discourse_hub: access_token_problem: "Admin'e iletin: Lütfen doğru discourse_org_access_key içerecek şekilde site ayarlarını güncelleyin." diff --git a/plugins/poll/config/locales/client.da.yml b/plugins/poll/config/locales/client.da.yml index d16eb468f0..f545a871bb 100644 --- a/plugins/poll/config/locales/client.da.yml +++ b/plugins/poll/config/locales/client.da.yml @@ -19,7 +19,9 @@ da: average_rating: "Gennemsnitlig rating: %{average}." multiple: help: + at_least_min_options: "Du skal mindst vælge %{count} muligheder." up_to_max_options: "Du kan vælge op til %{count} muligheder." + x_options: "Du skal vælge %{count} muligheder." between_min_and_max_options: "Du kan vælge mellem %{min} og %{max} muligheder." cast-votes: title: "Afgiv dine stemmer" diff --git a/plugins/poll/config/locales/client.sv.yml b/plugins/poll/config/locales/client.sv.yml index fd54901f75..6f2835adb7 100644 --- a/plugins/poll/config/locales/client.sv.yml +++ b/plugins/poll/config/locales/client.sv.yml @@ -5,4 +5,39 @@ # To work with us on translations, join this project: # https://www.transifex.com/projects/p/discourse-org/ -sv: {} +sv: + js: + poll: + voters: + zero: "röster" + one: "röst" + other: "röster" + total_votes: + zero: "totalt antal röster" + other: "totalt antal röster" + average_rating: "Medelbetyg: %{average}." + multiple: + help: + at_least_min_options: "Du måste välja minst %{count} alternativ." + up_to_max_options: "Du kan välja upp till %{count} alternativ." + x_options: "Du måste välja %{count} alternativ." + between_min_and_max_options: "Du kan välja mellan %{min} och %{max} alternativ." + cast-votes: + title: "Lägg din röster" + label: "Rösta nu!" + show-results: + title: "Visa omröstningsresultatet" + label: "Visa resultat" + hide-results: + title: "Tillbaka till dina röster" + label: "Göm resultat" + open: + title: "Öppna omröstningen" + label: "Öppna" + confirm: "Är du säker på att du vill öppna denna omröstning?" + close: + title: "Stäng omröstningen" + label: "Stäng" + confirm: "Är du säker på att du vill stänga denna omröstning?" + error_while_toggling_status: "Ett fel uppstod vid ändring av status för denna omröstning." + error_while_casting_votes: "Ett fel uppstod vid röstningen." diff --git a/plugins/poll/config/locales/server.da.yml b/plugins/poll/config/locales/server.da.yml index 02f8b25b78..ba7e89bf41 100644 --- a/plugins/poll/config/locales/server.da.yml +++ b/plugins/poll/config/locales/server.da.yml @@ -18,12 +18,15 @@ da: named_poll_must_have_less_options: "Afstemningen %{name} skal have mindre end %{max} valgmuligheder." default_poll_must_have_different_options: "Afstemningen skal have forskellige muligheder." named_poll_must_have_different_options: "Afstemningen %{name} skal have forskellige valgmuligheder." + default_poll_with_multiple_choices_has_invalid_parameters: "Afstemning med flere valgmuligheder har ugyldige parametre." + named_poll_with_multiple_choices_has_invalid_parameters: "Afstemningen %{name} med flere valgmuligheder har ugyldige parametre." requires_at_least_1_valid_option: "Du skal vælge mindst 1 gyldig mulighed." cannot_change_polls_after_5_minutes: "Du kan ikke tilføje, fjerne eller omdøbe afstemninger efter de første 5 minutter." op_cannot_edit_options_after_5_minutes: "Du kan ikke tilføje eller fjerne afstemningsmuligheder efter de første 5 minutter. Kontakt venligst en moderator hvis du har brug for at rette en mulighed." staff_cannot_add_or_remove_options_after_5_minutes: "Du kan ikke tilføje eller fjerne afstemningsmuligheder efter de første 5 minutter. Du kan lukke emnet og oprette et nyt istedet." no_polls_associated_with_this_post: "Ingen afstemninger er associeret med dette indlæg." no_poll_with_this_name: "Ingen afstemning med navnet %{name} er associeret med dette indlæg." + post_is_deleted: "Kan ikke gøres på et slettet indlæg." topic_must_be_open_to_vote: "Emnet skal være åbent for at kunne stemme." poll_must_be_open_to_vote: "Afstemning skal være åben for at kunne stemme." topic_must_be_open_to_toggle_status: "Emnet skal være åbent for at ændre status." From 9cdfef2b279a84c3e2a065a6059fc22738440673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 17 Jun 2015 21:46:55 +0200 Subject: [PATCH 0127/1435] UX: no need for all these margins --- app/assets/stylesheets/common/admin/admin_base.scss | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index a15ec7093d..ff10678748 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -681,15 +681,12 @@ section.details { .dashboard-left { float: left; - margin-top: 10px; width: 60%; } + .dashboard-right { float: right; - margin-top: 10px; width: 40%; - - .dashboard-stats { width: 100%; margin-left: 0; @@ -697,7 +694,6 @@ section.details { } .version-check { - margin-top: 10px; .version-number { font-size: 1.286em; @@ -822,15 +818,13 @@ table.api-keys { &.detected-problems { background: dark-light-diff($primary, $secondary, 90%, -75%); margin-bottom: 20px; - margin-top: 10px; .look-here { float: left; margin: 20px 10px 0 10px; .fa { - font-size: 2.286em -; + font-size: 2.286em; vertical-align: middle; color: $primary } From 1343d40558cd1aeeb33599a41dd2a46743849443 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 18 Jun 2015 09:58:32 +1000 Subject: [PATCH 0128/1435] PERF: deleting a post in huge topics was timing out - add missing index to user actions for fast retrieval by post - add missing indexes to users for fast retrieval of staff - only refresh topic_users liked/bookmarked cache for affected users --- app/models/topic_user.rb | 8 ++++++++ ...0617233018_add_index_target_post_id_on_user_actions.rb | 5 +++++ db/migrate/20150617234511_add_staff_index_to_users.rb | 6 ++++++ lib/post_destroyer.rb | 2 +- 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20150617233018_add_index_target_post_id_on_user_actions.rb create mode 100644 db/migrate/20150617234511_add_staff_index_to_users.rb diff --git a/app/models/topic_user.rb b/app/models/topic_user.rb index 2611039f31..30fdf0be30 100644 --- a/app/models/topic_user.rb +++ b/app/models/topic_user.rb @@ -232,6 +232,7 @@ class TopicUser < ActiveRecord::Base def self.update_post_action_cache(opts={}) user_id = opts[:user_id] + post_id = opts[:post_id] topic_id = opts[:topic_id] action_type = opts[:post_action_type] @@ -277,6 +278,13 @@ SQL builder.where("tu2.topic_id = :topic_id", topic_id: topic_id) end + if post_id + builder.where("tu2.topic_id IN (SELECT topic_id FROM posts WHERE id = :post_id)", post_id: post_id) + builder.where("tu2.user_id IN (SELECT user_id FROM post_actions + WHERE post_id = :post_id AND + post_action_type_id = :action_type_id)") + end + builder.exec(action_type_id: PostActionType.types[action_type]) end diff --git a/db/migrate/20150617233018_add_index_target_post_id_on_user_actions.rb b/db/migrate/20150617233018_add_index_target_post_id_on_user_actions.rb new file mode 100644 index 0000000000..973bda76f0 --- /dev/null +++ b/db/migrate/20150617233018_add_index_target_post_id_on_user_actions.rb @@ -0,0 +1,5 @@ +class AddIndexTargetPostIdOnUserActions < ActiveRecord::Migration + def change + add_index :user_actions, [:target_post_id] + end +end diff --git a/db/migrate/20150617234511_add_staff_index_to_users.rb b/db/migrate/20150617234511_add_staff_index_to_users.rb new file mode 100644 index 0000000000..f060b9b169 --- /dev/null +++ b/db/migrate/20150617234511_add_staff_index_to_users.rb @@ -0,0 +1,6 @@ +class AddStaffIndexToUsers < ActiveRecord::Migration + def change + add_index :users, [:id], name: 'idx_users_admin', where: 'admin' + add_index :users, [:id], name: 'idx_users_moderator', where: 'moderator' + end +end diff --git a/lib/post_destroyer.rb b/lib/post_destroyer.rb index fb6bd9079c..818f376635 100644 --- a/lib/post_destroyer.rb +++ b/lib/post_destroyer.rb @@ -90,7 +90,7 @@ class PostDestroyer end update_associated_category_latest_topic update_user_counts - TopicUser.update_post_action_cache(topic_id: @post.topic_id) + TopicUser.update_post_action_cache(post_id: @post.id) end feature_users_in_the_topic if @post.topic From c034dca844929e7fd698262a95e78c0744c33e8c Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 18 Jun 2015 12:29:06 +1000 Subject: [PATCH 0129/1435] improve nginx report to include counts and break down user traffic --- script/nginx_analyze.rb | 149 +++++++++++++++++++++++++++++++++------- 1 file changed, 124 insertions(+), 25 deletions(-) diff --git a/script/nginx_analyze.rb b/script/nginx_analyze.rb index f83d6425d2..177d45c480 100644 --- a/script/nginx_analyze.rb +++ b/script/nginx_analyze.rb @@ -1,3 +1,5 @@ +require 'date' + class LogAnalyzer class LineParser @@ -10,6 +12,8 @@ class LogAnalyzer PATTERN = /\[(.*)\] (\S+) \"(.*)\" \"(.*)\" \"(.*)\" ([0-9]+) ([0-9]+) \"(.*)\" ([0-9.]+) ([0-9.]+) "(.*)"/ + TIME_FORMAT = "%d/%b/%Y:%H:%M:%S %Z" + def self.parse(line) result = new _, result.time, result.ip_address, result.url, result.user_agent, @@ -21,24 +25,61 @@ class LogAnalyzer result end + + def parsed_time + DateTime.strptime(time, TIME_FORMAT) + end end attr_reader :total_requests, :message_bus_requests, :filename, :ip_to_rails_duration, :username_to_rails_duration, :route_to_rails_duration, :url_to_rails_duration, - :status_404_to_count + :status_404_to_count, :from_time, :to_time def self.analyze(filename) new(filename).analyze end + class Aggeregator + + def initialize + @data = {} + end + + def add(id, duration, aggregate=nil) + ary = (@data[id] ||= [0,0]) + ary[0] += duration + ary[1] += 1 + if aggregate + ary[2] ||= Hash.new(0) + ary[2][aggregate] += duration + end + end + + def top(n) + @data.sort{|a,b| b[1][0] <=> a[1][0]}.first(n).map do |metric, ary| + metric = metric.to_s + metric = "[empty]" if metric.length == 0 + result = [metric, ary[0], ary[1]] + # handle aggregate + if ary[2] + result.push ary[2].sort{|a,b| b[1] <=> a[1]}.first(5).map{|k,v| + v = "%.2f" % v if Float === v + "#{k}(#{v})"}.join(" ") + end + + result + end + end + end + def initialize(filename) @filename = filename - @ip_to_rails_duration = Hash.new(0) - @username_to_rails_duration = Hash.new(0) - @route_to_rails_duration = Hash.new(0) - @url_to_rails_duration = Hash.new(0) - @status_404_to_count = Hash.new(0) + @ip_to_rails_duration = Aggeregator.new + @username_to_rails_duration = Aggeregator.new + @route_to_rails_duration = Aggeregator.new + @url_to_rails_duration = Aggeregator.new + @status_404_to_count = Aggeregator.new end def analyze @@ -48,21 +89,24 @@ class LogAnalyzer @total_requests += 1 parsed = LineParser.parse(line) + @from_time ||= parsed.time + @to_time = parsed.time + if parsed.url =~ /(POST|GET) \/message-bus/ @message_bus_requests += 1 next end - @ip_to_rails_duration[parsed.ip_address] += parsed.rails_duration + @ip_to_rails_duration.add(parsed.ip_address, parsed.rails_duration) username = parsed.username == "-" ? "[Anonymous]" : parsed.username - @username_to_rails_duration[username] += parsed.rails_duration + @username_to_rails_duration.add(username, parsed.rails_duration, parsed.route) - @route_to_rails_duration[parsed.route] += parsed.rails_duration + @route_to_rails_duration.add(parsed.route, parsed.rails_duration) - @url_to_rails_duration[parsed.url] += parsed.rails_duration + @url_to_rails_duration.add(parsed.url, parsed.rails_duration) - @status_404_to_count[parsed.url] += 1 if parsed.status == "404" + @status_404_to_count.add(parsed.url,1) if parsed.status == "404" end self end @@ -72,46 +116,101 @@ end filename = ARGV[0] || "/var/log/nginx/access.log" analyzer = LogAnalyzer.analyze(filename) -SPACER = "-" * 80 +SPACER = "-" * 100 -def top(cols, hash, count) - sorted = hash.sort{|a,b| b[1] <=> a[1]}.first(30) +# don't feel like pulling in active support +def map_with_index(ary, &block) + idx = 0 + ary.map do |item| + v = block.call(item, idx) + idx += 1 + v + end +end - longest_0 = [cols[0].length, sorted.map{|a,b| a.to_s.length}.max ].max +def top(cols, aggregator, count) + sorted = aggregator.top(30) - puts "#{cols[0].ljust(longest_0)} #{cols[1]}" - puts "#{("-"*(cols[0].length)).ljust(longest_0)} #{"-"*cols[1].length}" + col_just = [] + + col_widths = map_with_index(cols) do |name,idx| + max_width = name.length + col_just[idx] = :ljust + sorted.each do |row| + col_just[idx] = :rjust unless String === row[idx] || row[idx].nil? + row[idx] = '%.2f' % row[idx] if Float === row[idx] + row[idx] = row[idx].to_s + max_width = row[idx].length if row[idx].length > max_width + end + [max_width,80].min + end + + puts(map_with_index(cols) do |name,idx| + name.ljust(col_widths[idx]) + end.join(" ")) + + puts(map_with_index(cols) do |name,idx| + ("-" * name.length).ljust(col_widths[idx]) + end.join(" ")) + + sorted.each do |raw_row| + + rows = [] + idx = 0 + raw_row.each do |col| + j = 0 + col.to_s.scan(/(.{1,80}($|\s)|.{1,80})/).each do |r| + rows[j] ||= [] + rows[j][idx] = r[0] + j += 1 + end + idx += 1 + end + + if rows.length > 1 + puts + end + + rows.each do |row| + cols.length.times do |i| + print row[i].to_s.send(col_just[i], col_widths[i]) + print " " + end + puts + end + + if rows.length > 1 + puts + end - sorted.each do |val, duration| - next unless val && val.length > 1 - n = Fixnum === duration ? duration : '%.2f' % duration - puts "#{val.to_s.ljust(longest_0)} #{n.to_s.rjust(cols[1].length)}" end end puts puts "Analyzed: #{analyzer.filename}" puts SPACER +puts "#{analyzer.from_time} - #{analyzer.to_time}" +puts SPACER puts "Total Requests: #{analyzer.total_requests} ( MessageBus: #{analyzer.message_bus_requests} )" puts SPACER puts "Top 30 IPs by Server Load" puts -top(["IP Address", "Duration"], analyzer.ip_to_rails_duration, 30) +top(["IP Address", "Duration", "Reqs"], analyzer.ip_to_rails_duration, 30) puts SPACER puts puts "Top 30 users by Server Load" puts -top(["Username", "Duration"], analyzer.username_to_rails_duration, 30) +top(["Username", "Duration", "Reqs", "Routes"], analyzer.username_to_rails_duration, 30) puts SPACER puts puts "Top 30 routes by Server Load" puts -top(["Route", "Duration"], analyzer.route_to_rails_duration, 30) +top(["Route", "Duration", "Reqs"], analyzer.route_to_rails_duration, 30) puts SPACER puts puts "Top 30 urls by Server Load" puts -top(["Url", "Duration"], analyzer.url_to_rails_duration, 30) +top(["Url", "Duration", "Reqs"], analyzer.url_to_rails_duration, 30) puts "(all durations in seconds)" puts SPACER From a3885a18f8db2f32d6556bae377a16c3a9e94130 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 18 Jun 2015 15:32:04 +1000 Subject: [PATCH 0130/1435] extra logic to force kill orphan sidekiqs --- config/unicorn.conf.rb | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/config/unicorn.conf.rb b/config/unicorn.conf.rb index 647b1f1735..1e3752218d 100644 --- a/config/unicorn.conf.rb +++ b/config/unicorn.conf.rb @@ -91,11 +91,25 @@ before_fork do |server, worker| rss * 1024 end + def max_allowed_size + [ENV['UNICORN_SIDEKIQ_MAX_RSS'].to_i, 500].max.megabytes + end + def out_of_memory? - max_allowed_size = [ENV['UNICORN_SIDEKIQ_MAX_RSS'].to_i, 500].max.megabytes; max_rss > max_allowed_size end + def force_kill_rogue_sidekiq + info = `ps -eo pid,rss,args | grep sidekiq | grep -v grep | awk '{print $1,$2}'` + info.split("\n").each do |row| + pid,mem = row.split(" ").map(&:to_i) + if pid > 0 && (mem*1024) > max_allowed_size + Rails.logger.warn "Detected rogue Sidekiq pid #{pid} mem #{mem*1024}, killing" + Process.kill("KILL", pid) rescue nil + end + end + end + def check_sidekiq_heartbeat @sidekiq_heartbeat_interval ||= 30.minutes @sidekiq_next_heartbeat_check ||= Time.new.to_i + @sidekiq_heartbeat_interval @@ -118,7 +132,11 @@ before_fork do |server, worker| end @sidekiq_next_heartbeat_check = Time.new.to_i + @sidekiq_heartbeat_interval - Demon::Sidekiq.restart if restart + if restart + Demon::Sidekiq.restart + sleep 10 + force_kill_rogue_sidekiq + end $redis.client.disconnect end end From 61df4bd90ad8d7a373172c3e99031efb3687c72c Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 18 Jun 2015 16:15:20 +1000 Subject: [PATCH 0131/1435] PERF: slow down the rate topic/timings is called (we still rush new posts) --- config/site_settings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/site_settings.yml b/config/site_settings.yml index 7f21b1a2c3..fa790c2436 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -727,7 +727,7 @@ developer: default: 15000 flush_timings_secs: client: true - default: 10 + default: 20 active_user_rate_limit_secs: 60 verbose_localization: default: false From f0c74d768572559dc700e24f18d65748e05fb640 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 18 Jun 2015 17:02:10 +1000 Subject: [PATCH 0132/1435] PERF: batch update post timings previously we would issue a query per row in post timings, this batches it --- app/models/post_timing.rb | 81 +++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 25 deletions(-) diff --git a/app/models/post_timing.rb b/app/models/post_timing.rb index f1a577f1ea..26d50f0e38 100644 --- a/app/models/post_timing.rb +++ b/app/models/post_timing.rb @@ -29,6 +29,25 @@ class PostTiming < ActiveRecord::Base TopicUser.ensure_consistency!(topic_id) end + def self.record_new_timing(args) + begin + exec_sql("INSERT INTO post_timings (topic_id, user_id, post_number, msecs) + SELECT :topic_id, :user_id, :post_number, :msecs + WHERE NOT EXISTS(SELECT 1 FROM post_timings + WHERE topic_id = :topic_id + AND user_id = :user_id + AND post_number = :post_number)", + args) + rescue PG::UniqueViolation + # concurrency is hard, we are not running serialized so this can possibly + # still happen, if it happens we just don't care, its an invalid record anyway + return + end + + Post.where(['topic_id = :topic_id and post_number = :post_number', args]).update_all 'reads = reads + 1' + UserStat.where(user_id: args[:user_id]).update_all 'posts_read_count = posts_read_count + 1' + end + # Increases a timer if a row exists, otherwise create it def self.record_timing(args) rows = exec_sql_row_count("UPDATE post_timings @@ -38,25 +57,7 @@ class PostTiming < ActiveRecord::Base AND post_number = :post_number", args) - if rows == 0 - - begin - exec_sql("INSERT INTO post_timings (topic_id, user_id, post_number, msecs) - SELECT :topic_id, :user_id, :post_number, :msecs - WHERE NOT EXISTS(SELECT 1 FROM post_timings - WHERE topic_id = :topic_id - AND user_id = :user_id - AND post_number = :post_number)", - args) - rescue PG::UniqueViolation - # concurrency is hard, we are not running serialized so this can possibly - # still happen, if it happens we just don't care, its an invalid record anyway - return - end - - Post.where(['topic_id = :topic_id and post_number = :post_number', args]).update_all 'reads = reads + 1' - UserStat.where(user_id: args[:user_id]).update_all 'posts_read_count = posts_read_count + 1' - end + record_new_timing(args) if rows == 0 end @@ -74,15 +75,45 @@ class PostTiming < ActiveRecord::Base account_age_msecs = ((Time.now - current_user.created_at) * 1000.0) highest_seen = 1 - timings.each do |post_number, time| - if post_number >= 0 && time < account_age_msecs - PostTiming.record_timing(topic_id: topic_id, - post_number: post_number, - user_id: current_user.id, - msecs: time) + + join_table = [] + + timings = timings.find_all do |post_number, time| + post_number >= 0 && time < account_age_msecs + end + + timings.each_with_index do |(post_number, time), index| + join_table << "SELECT #{topic_id.to_i} topic_id, #{post_number.to_i} post_number, + #{current_user.id.to_i} user_id, #{time.to_i} msecs, #{index} idx" + highest_seen = post_number.to_i > highest_seen ? post_number.to_i : highest_seen + end + + if join_table.length > 0 + sql = < Date: Thu, 18 Jun 2015 18:35:23 +0200 Subject: [PATCH 0133/1435] FIX: restore previous welcome PM images --- public/images/welcome/emoji-completion.png | 1 + public/images/welcome/like-link-flag-bookmark.png | 1 + public/images/welcome/notification-panel.png | 1 + public/images/welcome/progress-bar.png | 1 + public/images/welcome/quote-reply.png | 1 + public/images/welcome/reply-as-linked-topic.png | 1 + public/images/welcome/reply-post.png | 1 + public/images/welcome/reply-topic.png | 1 + public/images/welcome/topic-list-select-areas.png | 1 + public/images/welcome/topic-notification-control.png | 1 + public/images/welcome/topics-new-unread.png | 1 + public/images/welcome/username-completion.png | 1 + 12 files changed, 12 insertions(+) create mode 120000 public/images/welcome/emoji-completion.png create mode 120000 public/images/welcome/like-link-flag-bookmark.png create mode 120000 public/images/welcome/notification-panel.png create mode 120000 public/images/welcome/progress-bar.png create mode 120000 public/images/welcome/quote-reply.png create mode 120000 public/images/welcome/reply-as-linked-topic.png create mode 120000 public/images/welcome/reply-post.png create mode 120000 public/images/welcome/reply-topic.png create mode 120000 public/images/welcome/topic-list-select-areas.png create mode 120000 public/images/welcome/topic-notification-control.png create mode 120000 public/images/welcome/topics-new-unread.png create mode 120000 public/images/welcome/username-completion.png diff --git a/public/images/welcome/emoji-completion.png b/public/images/welcome/emoji-completion.png new file mode 120000 index 0000000000..b83c43b7f2 --- /dev/null +++ b/public/images/welcome/emoji-completion.png @@ -0,0 +1 @@ +emoji-completion-2x.png \ No newline at end of file diff --git a/public/images/welcome/like-link-flag-bookmark.png b/public/images/welcome/like-link-flag-bookmark.png new file mode 120000 index 0000000000..b58b2c1619 --- /dev/null +++ b/public/images/welcome/like-link-flag-bookmark.png @@ -0,0 +1 @@ +like-link-flag-bookmark-2x.png \ No newline at end of file diff --git a/public/images/welcome/notification-panel.png b/public/images/welcome/notification-panel.png new file mode 120000 index 0000000000..319fff39bc --- /dev/null +++ b/public/images/welcome/notification-panel.png @@ -0,0 +1 @@ +notification-panel-2x.png \ No newline at end of file diff --git a/public/images/welcome/progress-bar.png b/public/images/welcome/progress-bar.png new file mode 120000 index 0000000000..ce27bfde3e --- /dev/null +++ b/public/images/welcome/progress-bar.png @@ -0,0 +1 @@ +progress-bar-2x.png \ No newline at end of file diff --git a/public/images/welcome/quote-reply.png b/public/images/welcome/quote-reply.png new file mode 120000 index 0000000000..e4e7eae0b1 --- /dev/null +++ b/public/images/welcome/quote-reply.png @@ -0,0 +1 @@ +quote-reply-2x.png \ No newline at end of file diff --git a/public/images/welcome/reply-as-linked-topic.png b/public/images/welcome/reply-as-linked-topic.png new file mode 120000 index 0000000000..6ca565fe8a --- /dev/null +++ b/public/images/welcome/reply-as-linked-topic.png @@ -0,0 +1 @@ +reply-as-linked-topic-2x.png \ No newline at end of file diff --git a/public/images/welcome/reply-post.png b/public/images/welcome/reply-post.png new file mode 120000 index 0000000000..856fac057c --- /dev/null +++ b/public/images/welcome/reply-post.png @@ -0,0 +1 @@ +reply-post-2x.png \ No newline at end of file diff --git a/public/images/welcome/reply-topic.png b/public/images/welcome/reply-topic.png new file mode 120000 index 0000000000..05072ec604 --- /dev/null +++ b/public/images/welcome/reply-topic.png @@ -0,0 +1 @@ +reply-topic-2x.png \ No newline at end of file diff --git a/public/images/welcome/topic-list-select-areas.png b/public/images/welcome/topic-list-select-areas.png new file mode 120000 index 0000000000..8e9c6be52e --- /dev/null +++ b/public/images/welcome/topic-list-select-areas.png @@ -0,0 +1 @@ +topic-list-select-areas-2x.png \ No newline at end of file diff --git a/public/images/welcome/topic-notification-control.png b/public/images/welcome/topic-notification-control.png new file mode 120000 index 0000000000..a72ff3922e --- /dev/null +++ b/public/images/welcome/topic-notification-control.png @@ -0,0 +1 @@ +topic-notification-control-2x.png \ No newline at end of file diff --git a/public/images/welcome/topics-new-unread.png b/public/images/welcome/topics-new-unread.png new file mode 120000 index 0000000000..c25a613148 --- /dev/null +++ b/public/images/welcome/topics-new-unread.png @@ -0,0 +1 @@ +topics-new-unread-2x.png \ No newline at end of file diff --git a/public/images/welcome/username-completion.png b/public/images/welcome/username-completion.png new file mode 120000 index 0000000000..ce5b0c96ac --- /dev/null +++ b/public/images/welcome/username-completion.png @@ -0,0 +1 @@ +username-completion-2x.png \ No newline at end of file From 77595bcaa9640f6411bd5ca4d73c33b1fc066023 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Thu, 18 Jun 2015 15:46:50 -0400 Subject: [PATCH 0134/1435] FEATURE: notify by email when there are posts from new users waiting to be reviewed --- .../pending_queued_posts_reminder.rb | 34 +++++++++++++++++ app/mailers/pending_queued_posts_mailer.rb | 10 +++++ config/locales/server.en.yml | 10 +++++ config/site_settings.yml | 3 ++ .../pending_queued_posts_reminder_spec.rb | 38 +++++++++++++++++++ 5 files changed, 95 insertions(+) create mode 100644 app/jobs/scheduled/pending_queued_posts_reminder.rb create mode 100644 app/mailers/pending_queued_posts_mailer.rb create mode 100644 spec/jobs/pending_queued_posts_reminder_spec.rb diff --git a/app/jobs/scheduled/pending_queued_posts_reminder.rb b/app/jobs/scheduled/pending_queued_posts_reminder.rb new file mode 100644 index 0000000000..7eb3e3b8e8 --- /dev/null +++ b/app/jobs/scheduled/pending_queued_posts_reminder.rb @@ -0,0 +1,34 @@ +module Jobs + class PendingQueuedPostReminder < Jobs::Scheduled + + every 1.hour + + def execute(args) + return true unless SiteSetting.notify_about_queued_posts_after > 0 && SiteSetting.contact_email + + if should_notify_ids.size > 0 && last_notified_id.to_i < should_notify_ids.max + message = PendingQueuedPostsMailer.notify(count: should_notify_ids.size) + Email::Sender.new(message, :pending_queued_posts_reminder).send + self.last_notified_id = should_notify_ids.max + end + + true + end + + def should_notify_ids + @_should_notify_ids ||= QueuedPost.new_posts.visible.where('created_at < ?', SiteSetting.notify_about_queued_posts_after.hours.ago).pluck(:id) + end + + def last_notified_id + (i = $redis.get(self.class.last_notified_key)) && i.to_i + end + + def last_notified_id=(arg) + $redis.set(self.class.last_notified_key, arg) + end + + def self.last_notified_key + "last_notified_queued_post_id" + end + end +end diff --git a/app/mailers/pending_queued_posts_mailer.rb b/app/mailers/pending_queued_posts_mailer.rb new file mode 100644 index 0000000000..fec7c3ba72 --- /dev/null +++ b/app/mailers/pending_queued_posts_mailer.rb @@ -0,0 +1,10 @@ +require_dependency 'email/message_builder' + +class PendingQueuedPostsMailer < ActionMailer::Base + include Email::BuildEmailHelper + + def notify(opts={}) + return unless SiteSetting.contact_email + build_email(SiteSetting.contact_email, template: 'queued_posts_reminder', count: opts[:count]) + end +end diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 5206eb38d2..27ac0af5e7 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1155,6 +1155,7 @@ en: approve_post_count: "The amount of posts from a new user that must be approved" approve_unless_trust_level: "Posts for users below this trust level must be approved" + notify_about_queued_posts_after: "If there are posts that have been waiting to be reviewed for more than this many hours, an email will be sent to the contact email. Set to 0 to disable these emails." errors: invalid_email: "Invalid email address." @@ -1424,6 +1425,15 @@ en: one: "1 flag waiting to be handled" other: "%{count} flags waiting to be handled" + queued_posts_reminder: + subject_template: + one: "[%{site_name}] 1 post waiting to be reviewed" + other: "[%{site_name}] %{count} posts waiting to be reviewed" + text_body_template: | + Hello, + + There are posts from new users that are waiting to be reviewed. [They can be approved or rejected here](%{base_url}/queued-posts). + flag_reasons: off_topic: "Your post was flagged as **off-topic**: the community feels it is not a good fit for the topic, as currently defined by the title and the first post." inappropriate: "Your post was flagged as **inappropriate**: the community feels it is offensive, abusive, or a violation of [our community guidelines](/guidelines)." diff --git a/config/site_settings.yml b/config/site_settings.yml index fa790c2436..44c4fe249e 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -450,6 +450,9 @@ posting: approve_unless_trust_level: default: 0 enum: 'TrustLevelSetting' + notify_about_queued_posts_after: + default: 24 + min: 0 email: email_time_window_mins: diff --git a/spec/jobs/pending_queued_posts_reminder_spec.rb b/spec/jobs/pending_queued_posts_reminder_spec.rb new file mode 100644 index 0000000000..4af0e8bbcb --- /dev/null +++ b/spec/jobs/pending_queued_posts_reminder_spec.rb @@ -0,0 +1,38 @@ +require "spec_helper" + +describe Jobs::PendingQueuedPostReminder do + context "notify_about_queued_posts_after is 0" do + before { SiteSetting.stubs(:notify_about_queued_posts_after).returns(0) } + + it "never emails" do + described_class.any_instance.expects(:should_notify_ids).never + Email::Sender.any_instance.expects(:send).never + described_class.new.execute({}) + end + end + + context "notify_about_queued_posts_after is 24" do + before { SiteSetting.stubs(:notify_about_queued_posts_after).returns(24) } + + it "doesn't email if there are no queued posts" do + described_class.any_instance.stubs(:should_notify_ids).returns([]) + described_class.any_instance.stubs(:last_notified_id).returns(nil) + Email::Sender.any_instance.expects(:send).never + described_class.new.execute({}) + end + + it "emails if there are new queued posts" do + described_class.any_instance.stubs(:should_notify_ids).returns([1,2]) + described_class.any_instance.stubs(:last_notified_id).returns(nil) + Email::Sender.any_instance.expects(:send).once + described_class.new.execute({}) + end + + it "doesn't email again about the same posts" do + described_class.any_instance.stubs(:should_notify_ids).returns([2]) + described_class.any_instance.stubs(:last_notified_id).returns(2) + Email::Sender.any_instance.expects(:send).never + described_class.new.execute({}) + end + end +end From 4e898c604e5e5e1e6607c96a85f36613f427bef5 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 18 Jun 2015 15:52:51 -0400 Subject: [PATCH 0135/1435] UX: Update suggested topics to include topic status + category --- .../javascripts/discourse/adapters/topic.js.es6 | 12 ++++++++++++ .../discourse/controllers/composer.js.es6 | 5 ++--- app/assets/javascripts/discourse/models/topic.js.es6 | 10 ---------- .../discourse/templates/composer/similar_topics.hbs | 10 +++++++--- app/controllers/topics_controller.rb | 2 +- 5 files changed, 22 insertions(+), 17 deletions(-) create mode 100644 app/assets/javascripts/discourse/adapters/topic.js.es6 diff --git a/app/assets/javascripts/discourse/adapters/topic.js.es6 b/app/assets/javascripts/discourse/adapters/topic.js.es6 new file mode 100644 index 0000000000..858eee01eb --- /dev/null +++ b/app/assets/javascripts/discourse/adapters/topic.js.es6 @@ -0,0 +1,12 @@ +import RestAdapter from 'discourse/adapters/rest'; + +export default RestAdapter.extend({ + + find(store, type, findArgs) { + if (findArgs.similar) { + return Discourse.ajax("/topics/similar_to", { data: findArgs.similar }); + } else { + return this._super(store, type, findArgs); + } + } +}); diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index 357828a718..1cc221a9ad 100644 --- a/app/assets/javascripts/discourse/controllers/composer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/composer.js.es6 @@ -332,9 +332,9 @@ export default Ember.ObjectController.extend(Presence, { this.set('similarTopicsMessage', message); } - Discourse.Topic.findSimilarTo(title, body).then(function (newTopics) { + this.store.find('topic', {similar: {title, raw: body}}).then(function(newTopics) { similarTopics.clear(); - similarTopics.pushObjects(newTopics); + similarTopics.pushObjects(newTopics.get('content')); if (similarTopics.get('length') > 0) { message.set('similarTopics', similarTopics); @@ -343,7 +343,6 @@ export default Ember.ObjectController.extend(Presence, { messageController.send("hideMessage", message); } }); - }, saveDraft() { diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index 1dfd002d34..bba5b4c2ec 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -434,16 +434,6 @@ Topic.reopenClass({ return result; }, - findSimilarTo(title, body) { - return Discourse.ajax("/topics/similar_to", { data: {title: title, raw: body} }).then(function (results) { - if (Array.isArray(results)) { - return results.map(function(topic) { return Topic.create(topic); }); - } else { - return Ember.A(); - } - }); - }, - // Load a topic, but accepts a set of filters find(topicId, opts) { let url = Discourse.getURL("/t/") + topicId; diff --git a/app/assets/javascripts/discourse/templates/composer/similar_topics.hbs b/app/assets/javascripts/discourse/templates/composer/similar_topics.hbs index 73b5ee6e78..b8f183dd90 100644 --- a/app/assets/javascripts/discourse/templates/composer/similar_topics.hbs +++ b/app/assets/javascripts/discourse/templates/composer/similar_topics.hbs @@ -1,8 +1,12 @@ -{{fa-icon "times-circle"}} +{{fa-icon "times-circle"}}

{{i18n 'composer.similar_topics'}}

    - {{#each t in similarTopics}} -
  • {{topic-link t}} {{t.posts_count}}
  • + {{#each similarTopics as |t|}} +
  • + {{topic-status topic=t}} + {{topic-link t}} + {{category-link t.category}} +
  • {{/each}}
diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index c04c8c968f..287cf432de 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -155,7 +155,7 @@ class TopicsController < ApplicationController return render json: [] unless Topic.count_exceeds_minimum? topics = Topic.similar_to(title, raw, current_user).to_a - render_serialized(topics, BasicTopicSerializer) + render_serialized(topics, TopicListItemSerializer, root: :topics) end def feature_stats From 42bd9b61998743a38cd4bd299bb0716ebccd3a02 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 18 Jun 2015 17:06:25 -0400 Subject: [PATCH 0136/1435] FEATURE: Show time gap between posts if more than a few days --- .../discourse/components/time-gap.js.es6 | 20 ++++++++++ .../discourse/models/post-stream.js.es6 | 30 +++++++++++++-- .../javascripts/discourse/models/post.js.es6 | 4 ++ .../templates/components/time-gap.hbs | 5 +++ .../javascripts/discourse/templates/post.hbs | 4 ++ .../stylesheets/desktop/topic-post.scss | 15 ++++++++ config/locales/client.en.yml | 10 +++++ config/locales/server.en.yml | 1 + config/site_settings.yml | 3 ++ .../models/post-stream-test.js.es6 | 38 ++++++++++++++++++- 10 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 app/assets/javascripts/discourse/components/time-gap.js.es6 create mode 100644 app/assets/javascripts/discourse/templates/components/time-gap.hbs diff --git a/app/assets/javascripts/discourse/components/time-gap.js.es6 b/app/assets/javascripts/discourse/components/time-gap.js.es6 new file mode 100644 index 0000000000..fb42dc3246 --- /dev/null +++ b/app/assets/javascripts/discourse/components/time-gap.js.es6 @@ -0,0 +1,20 @@ +export default Ember.Component.extend({ + classNameBindings: [':time-gap'], + + render(buffer) { + const gapDays = this.get('gapDays'); + + let timeGapWords; + if (gapDays < 30) { + timeGapWords = I18n.t('dates.later.x_days', {count: gapDays}); + } else if (gapDays < 365) { + const gapMonths = Math.floor(gapDays / 30); + timeGapWords = I18n.t('dates.later.x_months', {count: gapMonths}); + } else { + const gapYears = Math.floor(gapDays / 365); + timeGapWords = I18n.t('dates.later.x_years', {count: gapYears}); + } + + buffer.push("
" + timeGapWords + "
"); + } +}); diff --git a/app/assets/javascripts/discourse/models/post-stream.js.es6 b/app/assets/javascripts/discourse/models/post-stream.js.es6 index 61a70f5d2c..e92f92f41c 100644 --- a/app/assets/javascripts/discourse/models/post-stream.js.es6 +++ b/app/assets/javascripts/discourse/models/post-stream.js.es6 @@ -1,5 +1,21 @@ import RestModel from 'discourse/models/rest'; +function calcDayDiff(p1, p2) { + if (!p1) { return; } + + const date = p1.get('created_at'); + if (date) { + if (p2) { + const lastDate = p2.get('created_at'); + if (lastDate) { + const delta = new Date(date).getTime() - new Date(lastDate).getTime(); + const days = Math.round(delta / (1000 * 60 * 60 * 24)); + + p1.set('daysSincePrevious', days); + } + } + } +} const PostStream = RestModel.extend({ loading: Em.computed.or('loadingAbove', 'loadingBelow', 'loadingFilter', 'stagingPost'), notLoading: Em.computed.not('loading'), @@ -367,14 +383,22 @@ const PostStream = RestModel.extend({ }, prependPost(post) { - this.get('posts').unshiftObject(this.storePost(post)); + const stored = this.storePost(post); + if (stored) { + const posts = this.get('posts'); + calcDayDiff(posts.get('firstObject'), stored); + posts.unshiftObject(stored); + } + return post; }, appendPost(post) { const stored = this.storePost(post); if (stored) { - this.get('posts').addObject(stored); + const posts = this.get('posts'); + calcDayDiff(stored, posts.get('lastObject')); + posts.addObject(stored); } return post; }, @@ -627,7 +651,7 @@ const PostStream = RestModel.extend({ const postId = Em.get(post, 'id'); if (postId) { const postIdentityMap = this.get('postIdentityMap'), - existing = postIdentityMap.get(post.get('id')); + existing = postIdentityMap.get(post.get('id')); if (existing) { // If the post is in the identity map, update it and return the old reference. diff --git a/app/assets/javascripts/discourse/models/post.js.es6 b/app/assets/javascripts/discourse/models/post.js.es6 index ba3744ec0a..9e720b36d1 100644 --- a/app/assets/javascripts/discourse/models/post.js.es6 +++ b/app/assets/javascripts/discourse/models/post.js.es6 @@ -27,6 +27,10 @@ const Post = RestModel.extend({ notDeleted: Em.computed.not('deleted'), userDeleted: Em.computed.empty('user_id'), + hasTimeGap: function() { + return (this.get('daysSincePrevious') || 0) > Discourse.SiteSettings.show_time_gap_days; + }.property('daysSincePrevious'), + showName: function() { const name = this.get('name'); return name && (name !== this.get('username')) && Discourse.SiteSettings.display_name_on_posts; diff --git a/app/assets/javascripts/discourse/templates/components/time-gap.hbs b/app/assets/javascripts/discourse/templates/components/time-gap.hbs new file mode 100644 index 0000000000..1667ed07ca --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/time-gap.hbs @@ -0,0 +1,5 @@ +
+
+ {{gapInWords}} +
+
diff --git a/app/assets/javascripts/discourse/templates/post.hbs b/app/assets/javascripts/discourse/templates/post.hbs index bbcd12acef..60750cc29a 100644 --- a/app/assets/javascripts/discourse/templates/post.hbs +++ b/app/assets/javascripts/discourse/templates/post.hbs @@ -1,5 +1,9 @@ {{post-gap post=this postStream=controller.model.postStream before="true"}} +{{#if hasTimeGap}} + {{time-gap gapDays=daysSincePrevious}} +{{/if}} +
{{view 'reply-history' content=replyHistory}}
diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss index dedd9735c2..6b6eb254e8 100644 --- a/app/assets/stylesheets/desktop/topic-post.scss +++ b/app/assets/stylesheets/desktop/topic-post.scss @@ -717,6 +717,21 @@ $topic-avatar-width: 45px; width: calc(#{$topic-avatar-width} + #{$topic-body-width} + 2 * #{$topic-body-width-padding}); } +.time-gap { + width: 755px; + border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); +} +.time-gap-words { + display: inline-block; + padding: 0.5em 1em; + margin: 0.5em 0 0.5em 56px; + text-transform: uppercase; + font-weight: bold; + font-size: 0.8em; + background-color: dark-light-diff($primary, $secondary, 90%, -60%); + color: lighten($primary, 30%); +} + .posts-wrapper { position: relative; -webkit-font-smoothing: subpixel-antialiased; diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 501c5e589d..fa4c6fbcfb 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -92,6 +92,16 @@ en: x_days: one: "1 day ago" other: "%{count} days ago" + later: + x_days: + one: "1 day layer" + other: "%{count} days later" + x_months: + one: "1 month layer" + other: "%{count} months later" + x_years: + one: "1 year layer" + other: "%{count} years later" share: topic: 'share a link to this topic' post: 'post #%{postNumber}' diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 27ac0af5e7..a90c20be9c 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1120,6 +1120,7 @@ en: full_name_required: "Full name is a required field of a user's profile." enable_names: "Show the user's full name on their profile, user card, and emails. Disable to hide full name everywhere." display_name_on_posts: "Show a user's full name on their posts in addition to their @username." + show_time_gap_days: "If two posts are made this many days apart, display the time gap in the topic." invites_per_page: "Default invites shown on the user page." short_progress_text_threshold: "After the number of posts in a topic goes above this number, the progress bar will only show the current post number. If you change the progress bar's width, you may need to change this value." default_code_lang: "Default programming language syntax highlighting applied to GitHub code blocks (lang-auto, ruby, python etc.)" diff --git a/config/site_settings.yml b/config/site_settings.yml index 44c4fe249e..792b824ed7 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -415,6 +415,9 @@ posting: display_name_on_posts: client: true default: false + show_time_gap_days: + default: 4 + client: true short_progress_text_threshold: client: true default: 10000 diff --git a/test/javascripts/models/post-stream-test.js.es6 b/test/javascripts/models/post-stream-test.js.es6 index a510c48398..74569a0365 100644 --- a/test/javascripts/models/post-stream-test.js.es6 +++ b/test/javascripts/models/post-stream-test.js.es6 @@ -26,6 +26,40 @@ test('defaults', function() { present(postStream.get('topic')); }); +test('daysSincePrevious when appending', function(assert) { + const postStream = buildStream(10000001, [1,2,3]); + const store = postStream.store; + + const p1 = store.createRecord('post', {id: 1, post_number: 1, created_at: "2015-05-29T18:17:35.868Z"}), + p2 = store.createRecord('post', {id: 2, post_number: 2, created_at: "2015-06-01T01:07:25.761Z"}), + p3 = store.createRecord('post', {id: 3, post_number: 3, created_at: "2015-06-02T01:07:25.761Z"}); + + postStream.appendPost(p1); + postStream.appendPost(p2); + postStream.appendPost(p3); + + assert.ok(!p1.get('daysSincePrevious')); + assert.equal(p2.get('daysSincePrevious'), 2); + assert.equal(p3.get('daysSincePrevious'), 1); +}); + +test('daysSincePrevious when prepending', function(assert) { + const postStream = buildStream(10000001, [1,2,3]); + const store = postStream.store; + + const p1 = store.createRecord('post', {id: 1, post_number: 1, created_at: "2015-05-29T18:17:35.868Z"}), + p2 = store.createRecord('post', {id: 2, post_number: 2, created_at: "2015-06-01T01:07:25.761Z"}), + p3 = store.createRecord('post', {id: 3, post_number: 3, created_at: "2015-06-02T01:07:25.761Z"}); + + postStream.prependPost(p3); + postStream.prependPost(p2); + postStream.prependPost(p1); + + assert.ok(!p1.get('daysSincePrevious')); + assert.equal(p2.get('daysSincePrevious'), 2); + assert.equal(p3.get('daysSincePrevious'), 1); +}); + test('appending posts', function() { const postStream = buildStream(4567, [1, 3, 4]); const store = postStream.store; @@ -96,8 +130,8 @@ test("removePosts", function() { const store = postStream.store; const p1 = store.createRecord('post', {id: 1, post_number: 2}), - p2 = store.createRecord('post', {id: 2, post_number: 3}), - p3 = store.createRecord('post', {id: 3, post_number: 4}); + p2 = store.createRecord('post', {id: 2, post_number: 3}), + p3 = store.createRecord('post', {id: 3, post_number: 4}); postStream.appendPost(p1); postStream.appendPost(p2); From e7c8f5bb2bf6298f9dfc2dd0f4f199ea276c52df Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 18 Jun 2015 15:31:31 -0700 Subject: [PATCH 0137/1435] minor copyedit --- config/locales/server.en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index a90c20be9c..9216c07e48 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -454,7 +454,7 @@ en: other: "almost %{count} years ago" password_reset: - no_token: "Sorry, that password change link is too old. Log in again and select 'I forgot my password' to get a new link." + no_token: "Sorry, that password change link is too old. Select 'I forgot my password' to get a new link." choose_new: "Please choose a new password" choose: "Please choose a password" update: 'Update Password' From 8feaa5c61345036ffbfb9509b32e408185981e72 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 18 Jun 2015 16:23:18 -0700 Subject: [PATCH 0138/1435] typo: later, not layer --- config/locales/client.en.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index fa4c6fbcfb..5108eabc35 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -94,13 +94,13 @@ en: other: "%{count} days ago" later: x_days: - one: "1 day layer" + one: "1 day later" other: "%{count} days later" x_months: - one: "1 month layer" + one: "1 month later" other: "%{count} months later" x_years: - one: "1 year layer" + one: "1 year later" other: "%{count} years later" share: topic: 'share a link to this topic' From 5eabf01c292a006f5da126f4334a6c0369df8724 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 19 Jun 2015 10:35:55 +1000 Subject: [PATCH 0139/1435] FIX: don't allow storage of post timings batch larger than 60 secs --- app/models/post_timing.rb | 13 ++++++++++--- spec/models/post_timing_spec.rb | 6 ++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/models/post_timing.rb b/app/models/post_timing.rb index 26d50f0e38..3afd1f7781 100644 --- a/app/models/post_timing.rb +++ b/app/models/post_timing.rb @@ -68,21 +68,27 @@ class PostTiming < ActiveRecord::Base end end + MAX_READ_TIME_PER_BATCH = 60*1000.0 def self.process_timings(current_user, topic_id, topic_time, timings) current_user.user_stat.update_time_read! - account_age_msecs = ((Time.now - current_user.created_at) * 1000.0) + max_time_per_post = ((Time.now - current_user.created_at) * 1000.0) + max_time_per_post = MAX_READ_TIME_PER_BATCH if max_time_per_post > MAX_READ_TIME_PER_BATCH highest_seen = 1 join_table = [] - timings = timings.find_all do |post_number, time| - post_number >= 0 && time < account_age_msecs + i = timings.length + while i > 0 + i -= 1 + timings[i][1] = max_time_per_post if timings[i][1] > max_time_per_post + timings.delete_at(i) if timings[i][0] < 1 end timings.each_with_index do |(post_number, time), index| + join_table << "SELECT #{topic_id.to_i} topic_id, #{post_number.to_i} post_number, #{current_user.id.to_i} user_id, #{time.to_i} msecs, #{index} idx" @@ -122,6 +128,7 @@ SQL total_changed = Notification.mark_posts_read(current_user, topic_id, timings.map{|t| t[0]}) end + topic_time = max_time_per_post if topic_time > max_time_per_post TopicUser.update_last_read(current_user, topic_id, highest_seen, topic_time) if total_changed > 0 diff --git a/spec/models/post_timing_spec.rb b/spec/models/post_timing_spec.rb index 002ba5729c..beb39c3d57 100644 --- a/spec/models/post_timing_spec.rb +++ b/spec/models/post_timing_spec.rb @@ -71,7 +71,7 @@ describe PostTiming do PostTiming.process_timings(user, post.topic_id, 1, [[post.post_number, 10.minutes.to_i * 1000]]) msecs = PostTiming.where(post_number: post.post_number, user_id: user.id).pluck(:msecs)[0] - expect(msecs).to eq(123) + expect(msecs).to eq(123 + PostTiming::MAX_READ_TIME_PER_BATCH) end end @@ -85,7 +85,7 @@ describe PostTiming do ActiveRecord::Base.observers.enable :all post = Fabricate(:post) - user2 = Fabricate(:coding_horror) + user2 = Fabricate(:coding_horror, created_at: 1.day.ago) PostAction.act(user2, post, PostActionType.types[:like]) @@ -96,6 +96,8 @@ describe PostTiming do post.user.reload expect(post.user.unread_notifications).to eq(0) + PostTiming.process_timings(post.user, post.topic_id, 1, [[post.post_number, 1.day]]) + end end From 054789038c52590ce8821c59a7b8840608f6124d Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 19 Jun 2015 11:23:52 +1000 Subject: [PATCH 0140/1435] remove leftover code --- .../javascripts/discourse/templates/components/time-gap.hbs | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 app/assets/javascripts/discourse/templates/components/time-gap.hbs diff --git a/app/assets/javascripts/discourse/templates/components/time-gap.hbs b/app/assets/javascripts/discourse/templates/components/time-gap.hbs deleted file mode 100644 index 1667ed07ca..0000000000 --- a/app/assets/javascripts/discourse/templates/components/time-gap.hbs +++ /dev/null @@ -1,5 +0,0 @@ -
-
- {{gapInWords}} -
-
From 0dfb9261eab8faff3572aecef4a7c0259ed6b926 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 19 Jun 2015 11:24:10 +1000 Subject: [PATCH 0141/1435] Improve time gap styling --- .../discourse/components/time-gap.js.es6 | 2 ++ app/assets/stylesheets/desktop/topic-post.scss | 18 ++++++++++++++---- app/assets/stylesheets/mobile/topic-post.scss | 4 ++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/discourse/components/time-gap.js.es6 b/app/assets/javascripts/discourse/components/time-gap.js.es6 index fb42dc3246..7b7b9a6c49 100644 --- a/app/assets/javascripts/discourse/components/time-gap.js.es6 +++ b/app/assets/javascripts/discourse/components/time-gap.js.es6 @@ -4,6 +4,8 @@ export default Ember.Component.extend({ render(buffer) { const gapDays = this.get('gapDays'); + buffer.push("
"); + let timeGapWords; if (gapDays < 30) { timeGapWords = I18n.t('dates.later.x_days', {count: gapDays}); diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss index 6b6eb254e8..081afbda71 100644 --- a/app/assets/stylesheets/desktop/topic-post.scss +++ b/app/assets/stylesheets/desktop/topic-post.scss @@ -720,16 +720,26 @@ $topic-avatar-width: 45px; .time-gap { width: 755px; border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); + .topic-avatar { + padding: 5px 0; + border-top: none; + } +} + +.time-gap .topic-avatar i { + font-size: 35px; + width: 45px; + text-align: center; + color: lighten($primary, 75%); } .time-gap-words { display: inline-block; padding: 0.5em 1em; - margin: 0.5em 0 0.5em 56px; + margin-top: 9px; text-transform: uppercase; font-weight: bold; - font-size: 0.8em; - background-color: dark-light-diff($primary, $secondary, 90%, -60%); - color: lighten($primary, 30%); + font-size: 0.9em; + color: lighten($primary, 60%); } .posts-wrapper { diff --git a/app/assets/stylesheets/mobile/topic-post.scss b/app/assets/stylesheets/mobile/topic-post.scss index 71e94ea58e..535bee6214 100644 --- a/app/assets/stylesheets/mobile/topic-post.scss +++ b/app/assets/stylesheets/mobile/topic-post.scss @@ -6,6 +6,10 @@ margin: 10px 0; } +.time-gap { + display: none; +} + .topic-post { border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); padding: 6px 0 3px 0; From 1bdf43d5cc7df0543ddac20686a0ddc351a2f125 Mon Sep 17 00:00:00 2001 From: Simon Cossar Date: Thu, 18 Jun 2015 19:28:02 -0700 Subject: [PATCH 0142/1435] Correct user-card positioning for rtl layouts --- .../discourse/views/user-card.js.es6 | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/discourse/views/user-card.js.es6 b/app/assets/javascripts/discourse/views/user-card.js.es6 index bdc35d5438..2e3fe8f3ea 100644 --- a/app/assets/javascripts/discourse/views/user-card.js.es6 +++ b/app/assets/javascripts/discourse/views/user-card.js.es6 @@ -84,18 +84,32 @@ export default Discourse.View.extend(CleansUp, { }, _willShow(target) { + //const direction = $('html').css('direction'); if (!target) { return; } const width = this.$().width(); + Ember.run.schedule('afterRender', () => { if (target) { let position = target.offset(); if (position) { - position.left += target.width() + 10; - const overage = ($(window).width() - 50) - (position.left + width); - if (overage < 0) { - position.left += overage; - position.top += target.height() + 48; + // Check for a right to left layout + if (($('html').css('direction')) === 'rtl') { + position.right = $(window).width() - position.left + 10; + position.left = 'auto'; + const overage = ($(window).width() - 50) - (position.right + width); + if (overage < 0) { + position.right += overage; + position.top += target.height() + 48; + } + } else { // The site direction is ltr + position.left += target.width() + 10; + + const overage = ($(window).width() - 50) - (position.left + width); + if (overage < 0) { + position.left += overage; + position.top += target.height() + 48; + } } position.top -= $('#main-outlet').offset().top; From 5ab7f7e88d59e015fe35990cb2df33c2dca03e56 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 19 Jun 2015 12:31:36 +1000 Subject: [PATCH 0143/1435] FIX: double like notification If you got a like -> edit/quote/etc -> like you would get a double notification --- app/services/post_alerter.rb | 4 +++- spec/services/post_alerter_spec.rb | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/services/post_alerter.rb b/app/services/post_alerter.rb index f90754c22c..59250eae3f 100644 --- a/app/services/post_alerter.rb +++ b/app/services/post_alerter.rb @@ -107,7 +107,9 @@ class PostAlerter # Don't notify the same user about the same notification on the same post existing_notification = user.notifications .order("notifications.id desc") - .find_by(topic_id: post.topic_id, post_number: post.post_number) + .find_by(topic_id: post.topic_id, + post_number: post.post_number, + notification_type: type) if existing_notification && existing_notification.notification_type == type return unless existing_notification.notification_type == Notification.types[:edited] && diff --git a/spec/services/post_alerter_spec.rb b/spec/services/post_alerter_spec.rb index df4b27510e..cd30348da7 100644 --- a/spec/services/post_alerter_spec.rb +++ b/spec/services/post_alerter_spec.rb @@ -9,6 +9,23 @@ describe PostAlerter do PostAlerter.post_created(post) end + context 'likes' do + it 'does not double notify users on likes' do + ActiveRecord::Base.observers.enable :all + + post = Fabricate(:post, raw: 'I love waffles') + PostAction.act(evil_trout, post, PostActionType.types[:like]) + + admin = Fabricate(:admin) + post.revise(admin, {raw: 'I made a revision'}) + + PostAction.act(admin, post, PostActionType.types[:like]) + + # one like and one edit notification + expect(Notification.count(post_number: 1, topic_id: post.topic_id)).to eq(2) + end + end + context 'quotes' do it 'does not notify for muted users' do From 7d898ff5175c3fc940eeb9dcee60c73864e9a877 Mon Sep 17 00:00:00 2001 From: Simon Cossar Date: Thu, 18 Jun 2015 19:33:23 -0700 Subject: [PATCH 0144/1435] remove comment --- app/assets/javascripts/discourse/views/user-card.js.es6 | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/discourse/views/user-card.js.es6 b/app/assets/javascripts/discourse/views/user-card.js.es6 index 2e3fe8f3ea..f95fa1475f 100644 --- a/app/assets/javascripts/discourse/views/user-card.js.es6 +++ b/app/assets/javascripts/discourse/views/user-card.js.es6 @@ -84,7 +84,6 @@ export default Discourse.View.extend(CleansUp, { }, _willShow(target) { - //const direction = $('html').css('direction'); if (!target) { return; } const width = this.$().width(); From 309d1b267b9b455f919b3616bc9c3d02dc6b3475 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 19 Jun 2015 13:30:01 +1000 Subject: [PATCH 0145/1435] style mobile time gap --- app/assets/stylesheets/mobile/topic-post.scss | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/mobile/topic-post.scss b/app/assets/stylesheets/mobile/topic-post.scss index 535bee6214..d29c185a95 100644 --- a/app/assets/stylesheets/mobile/topic-post.scss +++ b/app/assets/stylesheets/mobile/topic-post.scss @@ -7,7 +7,14 @@ } .time-gap { - display: none; + + border-bottom: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); + color: lighten($primary, 50%); + padding-bottom: 3px; + margin-bottom: 10px; + .topic-avatar { + margin: 0 5px 0 10px; + } } .topic-post { From d12de36c828dd58dac9780b94357a6763a1e3ac0 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 19 Jun 2015 16:09:26 +1000 Subject: [PATCH 0146/1435] FIX: denote that we are not collapsed if no buttons are hidden --- app/assets/javascripts/discourse/views/post-menu.js.es6 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/views/post-menu.js.es6 b/app/assets/javascripts/discourse/views/post-menu.js.es6 index 2276e95b74..e865eec7a2 100644 --- a/app/assets/javascripts/discourse/views/post-menu.js.es6 +++ b/app/assets/javascripts/discourse/views/post-menu.js.es6 @@ -135,8 +135,11 @@ var PostMenuView = Discourse.View.extend(StringBuffer, { }); // Only show ellipsis if there is more than one button hidden - if (!this.get('collapsed') || (allButtons.length <= visibleButtons.length + 1)) { + // if there are no more buttons, we are not collapsed + var collapsed = this.get('collapsed'); + if (!collapsed || (allButtons.length <= visibleButtons.length + 1)) { visibleButtons = allButtons; + if (collapsed) { this.set('collapsed', false); } } else { visibleButtons.splice(visibleButtons.length - 1, 0, this.buttonForShowMoreActions(post)); } From 00572c4f1134f2d717dee41c4d75e5d32afed534 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 19 Jun 2015 10:32:58 -0400 Subject: [PATCH 0147/1435] FIX: The time gap would disappear after a post was comitted --- .../discourse/models/post-stream.js.es6 | 7 ++++++- test/javascripts/models/post-stream-test.js.es6 | 14 +++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/discourse/models/post-stream.js.es6 b/app/assets/javascripts/discourse/models/post-stream.js.es6 index e92f92f41c..f882b258d1 100644 --- a/app/assets/javascripts/discourse/models/post-stream.js.es6 +++ b/app/assets/javascripts/discourse/models/post-stream.js.es6 @@ -397,8 +397,13 @@ const PostStream = RestModel.extend({ const stored = this.storePost(post); if (stored) { const posts = this.get('posts'); - calcDayDiff(stored, posts.get('lastObject')); + + calcDayDiff(stored, this.get('lastAppended')); posts.addObject(stored); + + if (stored.get('id') !== -1) { + this.set('lastAppended', stored); + } } return post; }, diff --git a/test/javascripts/models/post-stream-test.js.es6 b/test/javascripts/models/post-stream-test.js.es6 index 74569a0365..9790fcea41 100644 --- a/test/javascripts/models/post-stream-test.js.es6 +++ b/test/javascripts/models/post-stream-test.js.es6 @@ -349,7 +349,9 @@ test("staging and undoing a new post", function() { const postStream = buildStream(10101, [1]); const store = postStream.store; - postStream.appendPost(store.createRecord('post', {id: 1, post_number: 1, topic_id: 10101})); + const original = store.createRecord('post', {id: 1, post_number: 1, topic_id: 10101}); + postStream.appendPost(original); + ok(postStream.get('lastAppended'), original, "the original post is lastAppended"); const user = Discourse.User.create({username: 'eviltrout', name: 'eviltrout', id: 321}); const stagedPost = store.createRecord('post', { raw: 'hello world this is my new post', topic_id: 10101 }); @@ -365,6 +367,7 @@ test("staging and undoing a new post", function() { equal(result, "staged", "it returns staged"); equal(topic.get('highest_post_number'), 2, "it updates the highest_post_number"); ok(postStream.get('loading'), "it is loading while the post is being staged"); + ok(postStream.get('lastAppended'), original, "it doesn't consider staged posts as the lastAppended"); equal(topic.get('posts_count'), 2, "it increases the post count"); present(topic.get('last_posted_at'), "it updates last_posted_at"); @@ -384,13 +387,17 @@ test("staging and undoing a new post", function() { equal(topic.get('posts_count'), 1, "it reverts the post count"); equal(postStream.get('filteredPostsCount'), 1, "it retains the filteredPostsCount"); ok(!postStream.get('posts').contains(stagedPost), "the post is removed from the stream"); + ok(postStream.get('lastAppended'), original, "it doesn't consider undid post lastAppended"); }); test("staging and committing a post", function() { const postStream = buildStream(10101, [1]); const store = postStream.store; - postStream.appendPost(store.createRecord('post', {id: 1, post_number: 1, topic_id: 10101})); + const original = store.createRecord('post', {id: 1, post_number: 1, topic_id: 10101}); + postStream.appendPost(original); + ok(postStream.get('lastAppended'), original, "the original post is lastAppended"); + const user = Discourse.User.create({username: 'eviltrout', name: 'eviltrout', id: 321}); const stagedPost = store.createRecord('post', { raw: 'hello world this is my new post', topic_id: 10101 }); @@ -406,6 +413,7 @@ test("staging and committing a post", function() { result = postStream.stagePost(stagedPost, user); equal(result, "alreadyStaging", "you can't stage a post while it is currently staging"); + ok(postStream.get('lastAppended'), original, "staging a post doesn't change the lastAppended"); postStream.commitPost(stagedPost); ok(postStream.get('posts').contains(stagedPost), "the post is still in the stream"); @@ -417,7 +425,7 @@ test("staging and committing a post", function() { present(found, "the post is in the identity map"); ok(postStream.indexOf(stagedPost) > -1, "the post is in the stream"); equal(found.get('raw'), 'different raw value', 'it also updated the value in the stream'); - + ok(postStream.get('lastAppended'), found, "comitting a post changes lastAppended"); }); test('triggerNewPostInStream', function() { From 4b6cf528ce557331e41c69304c620bc12490c699 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Fri, 19 Jun 2015 11:29:13 -0400 Subject: [PATCH 0148/1435] update translations --- config/locales/client.fr.yml | 6 + config/locales/client.it.yml | 6 +- config/locales/client.pt.yml | 48 +-- config/locales/client.sv.yml | 13 + config/locales/client.tr_TR.yml | 301 ++++++++++--------- config/locales/server.es.yml | 16 +- config/locales/server.it.yml | 8 +- config/locales/server.ja.yml | 4 +- config/locales/server.pt.yml | 6 +- config/locales/server.sv.yml | 16 +- config/locales/server.tr_TR.yml | 50 ++- plugins/poll/config/locales/client.ar.yml | 8 + plugins/poll/config/locales/client.it.yml | 2 +- plugins/poll/config/locales/client.tr_TR.yml | 16 +- plugins/poll/config/locales/server.ar.yml | 4 +- plugins/poll/config/locales/server.it.yml | 8 +- 16 files changed, 300 insertions(+), 212 deletions(-) diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index 4833ba05db..1bbcfbc5c3 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -348,7 +348,9 @@ fr: github_profile: "Github" mailing_list_mode: "M'envoyer un courriel pour tous les nouveaux messages (sauf si vous avez rendu silencieux le sujet ou la catégorie)" watched_categories: "Surveillés" + watched_categories_instructions: "Vous surveillerez automatiquement les nouveaux sujets de ces catégories. Vous serez averti de tous les nouveaux messages et sujets. De plus, le nombre de messages non lus apparaîtra en regard de la liste des sujets." tracked_categories: "Suivies" + tracked_categories_instructions: "Vous allez suivre automatiquement tous les nouveaux sujets dans ces catégories. Le nombre de nouveaux messages apparaîtra à côté du sujet." muted_categories: "Désactivés" muted_categories_instructions: "Vous ne recevrez aucune notification concernant les nouveaux sujets de ces catégories, et ils n'apparaitront pas dans votre onglet non lu." delete_account: "Supprimer mon compte" @@ -759,6 +761,8 @@ fr: from_the_web: "Depuis le web" remote_tip: "lien vers l'image" remote_tip_with_attachments: "lien vers l'image ou le fichier ({{authorized_extensions}})" + local_tip: "cliquez pour sélectionner jusqu'à 10 images depuis votre ordinateur" + local_tip_with_attachments: "cliquez pour sélectionner jusqu'à 10 images ou fichiers depuis votre ordinateur ({{authorized_extensions}})" hint: "(vous pouvez également faire un glisser-déposer dans l'éditeur pour les télécharger)" hint_for_supported_browsers: "(vous pouvez aussi glisser et déposer ou coller des images dans l'éditeur pour les envoyer) " uploading: "Fichier en cours d'envoi" @@ -901,6 +905,8 @@ fr: '2_4': 'Vous recevrez des notifications car vous avez écrit une réponse dans ce sujet.' '2_2': 'Vous recevrez des notifications car vous suivez ce sujet.' '2': 'Vous recevrez des notifications car vous avez lu ce sujet.' + '1_2': 'Vous serez averti si quelqu''un mentionne votre @pseudo ou vous répond.' + '1': 'Vous serez averti si quelqu''un mentionne votre @pseudo ou vous répond.' '0_7': 'Vous ignorez toutes les notifications de cette catégorie.' '0_2': 'Vous ignorez toutes les notifications de ce sujet.' '0': 'Vous ignorez toutes les notifications de ce sujet.' diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index 191cc1eebb..62b313e338 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -191,6 +191,7 @@ it: switch_from_anon: "Esci Modalità Anonima" banner: close: "Nascondi questo banner." + edit: "Modifica questo annuncio >>" choose_topic: none_found: "Nessun argomento trovato." title: @@ -333,7 +334,7 @@ it: dismiss_notifications: "Imposta tutti come Letti" dismiss_notifications_tooltip: "Imposta tutte le notifiche non lette come lette " disable_jump_reply: "Non saltare al mio messaggio dopo la mia risposta" - dynamic_favicon: "Visualizza conteggio argomenti nuovi / creati nell'icona del browser" + dynamic_favicon: "Visualizza il conteggio degli argomenti nuovi / aggiornati sull'icona del browser" edit_history_public: "Consenti agli altri utenti di visualizzare le mie revisioni ai messaggi" external_links_in_new_tab: "Apri tutti i link esterni in nuove schede" enable_quoting: "Abilita \"rispondi quotando\" per il testo evidenziato" @@ -347,7 +348,9 @@ it: github_profile: "Github" mailing_list_mode: "Inviami una email per ogni nuovo messaggio (a meno che io non ignori l'argomento o la categoria)" watched_categories: "Osservate" + watched_categories_instructions: "Osserverai automaticamente tutti i nuovi argomenti in queste categorie. Riceverai notifiche su tutti i nuovi messaggi e argomenti e, accanto all'argomento, apparirà il conteggio dei nuovi messaggi." tracked_categories: "Seguite" + tracked_categories_instructions: "Seguirai automaticamente tutti i nuovi argomenti appartenenti a queste categorie. Di fianco all'argomento comparirà il conteggio dei nuovi messaggi." muted_categories: "Silenziate" muted_categories_instructions: "Non ti verrà notificato nulla sui nuovi argomenti in queste categorie, e non compariranno nel tab dei non letti." delete_account: "Cancella il mio account" @@ -559,6 +562,7 @@ it: read_only_mode: enabled: "La modalità di sola lettura è attiva. Puoi continuare a navigare nel sito ma le interazioni potrebbero non funzionare." login_disabled: "L'accesso è disabilitato quando il sito è in modalità di sola lettura." + too_few_topics_notice: "Crea almeno 5 argomenti pubblici e %{posts} risposte pubbliche per avviare la discussione. I nuovi utenti non possono guadagnare livelli di esperienza a meno che non abbiano contenuti da leggere. Questo messaggio appare solo allo staff." learn_more: "per saperne di più..." year: 'all''anno' year_desc: 'argomenti creati negli ultimi 365 giorni' diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index 59abdad397..785e03d6d7 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -348,9 +348,9 @@ pt: github_profile: "Github" mailing_list_mode: "Enviar-me um email por cada nova mensagem (a não ser que eu silencie o tópico ou categoria)" watched_categories: "Vigiado" - watched_categories_instructions: "Irá vigiar automaticamente todos os novos tópicos nestas categorias. Será notificado de todos as novas mensagens e tópicos. Além disso, a contagem de mensagens novas e não lidas surgirá junto do tópico. " + watched_categories_instructions: "Irá acompanhar automaticamente todos os novos tópicos nestas categorias. Será notificado de todas as novas mensagens e tópicos, e uma contagem de novas mensagens irá aparecer junto ao tópico." tracked_categories: "Acompanhado" - tracked_categories_instructions: "Acompanhará automaticamente todos os tópicos novos nestas categorias. Uma contagem de mensagens novas e não lidas surgirá ao lado do tópico." + tracked_categories_instructions: "Irá acompanhar automaticamente todos os novos tópicos nestas categorias. Uma contagem de novas mensagens irá aparecer junto ao tópico." muted_categories: "Silenciado" muted_categories_instructions: "Não será notificado relativamente a novos tópicos nestas categorias, e não aparecerão no seu separador de tópicos não lidos." delete_account: "Eliminar A Minha Conta" @@ -761,8 +761,8 @@ pt: from_the_web: "Da internet" remote_tip: "hiperligação para imagem" remote_tip_with_attachments: "hiperligação para imagem ou ficheiro ({{authorized_extensions}})" - local_tip: "Clique para selecionar um ficheiro do seu dispositivo" - local_tip_with_attachments: "clique para selecionar uma imagem ou ficheiro do seu dispositivo ({{authorized_extensions}})" + local_tip: "clique para selecionar até 10 imagens do seu dispositivo" + local_tip_with_attachments: "clique para selecionar até 10 imagens ou ficheiros do seu dispositivo ({{authorized_extensions}})" hint: "(pode também arrastar o ficheiro para o editor para fazer o carregamento)" hint_for_supported_browsers: "(pode também arrastar ou colar imagens no editor de forma a carregá-las)" uploading: "A carregar" @@ -868,7 +868,7 @@ pt: toggle_information: "alternar detalhes do tópico" read_more_in_category: "Pretende ler mais? Procure outros tópicos em {{catLink}} ou {{latestLink}}." read_more: "Pretende ler mais? {{catLink}} ou {{latestLink}}." - read_more_MF: "{ UNREAD, plural, =0 {} one { Existe 1 não lido } other { Existem # não lidos } } { NEW, plural, =0 {} one { {BOTH, select, true{e } false {existe } other{}} 1 novo tópico} other { {BOTH, select, true{e } false {existem } other{}} # novos tópicos} } restantes, eu {CATEGORY, select, true {pesquise outros tópicos em {catLink}} false {{latestLink}} other {}}" + read_more_MF: "{ UNREAD, plural, =0 {} one { Existe 1 não lido } other { Existem # não lidos } } { NEW, plural, =0 {} one { {BOTH, select, true{e } false {existe } other{}} 1 novo tópico} other { {BOTH, select, true{e } false {existem } other{}} # novos tópicos} } restantes, ou {CATEGORY, select, true {pesquise outros tópicos em {catLink}} false {{latestLink}} other {}}" browse_all_categories: Pesquisar em todas as categorias view_latest_topics: ver os tópicos mais recentes suggest_create_topic: Porque não começar um tópico? @@ -901,29 +901,29 @@ pt: '2_4': 'Receberá notificações porque publicou uma resposta a este tópico.' '2_2': 'Receberá notificações porque está a acompanhar este tópico.' '2': 'Receberá notificações porque leu este tópico.' - '1_2': 'Será notificado se alguém mencionar o seu @nome ou responder às suas mensagens' - '1': 'Será notificado se alguém mencionar o seu @nome ou responder às suas mensagens' + '1_2': 'Será notificado se alguém mencionar o seu @nome ou responder-lhe.' + '1': 'Será notificado se alguém mencionar o seu @nome ou responder-lhe.' '0_7': 'Está a ignorar todas as notificações nesta categoria.' '0_2': 'Está a ignorar todas as notificações para este tópico.' '0': 'Está a ignorar todas as notificações para este tópico.' watching_pm: title: "A vigiar" - description: "Será notificado de todas as novas publicações nesta mensagem. Uma contagem das publicações não lidas e novas irá também aparecer junto do tópico." + description: "Será notificado de cada nova resposta nesta mensagem, e uma contagem de novas respostas será exibida." watching: title: "A vigiar" - description: "Será notificado sobre todas as novas mensagens neste tópico. Será também apresentada uma contagem de mensagens novas e não lidas junto do tópico." + description: "Será notificado de cada nova resposta neste tópico, e uma contagem de novas respostas será exibida." tracking_pm: title: "Acompanhar" - description: "Uma contagem de publicações não lidas e novas irá aparecer junto à mensagem. Será notificado se alguém mencionar o seu @nome ou responder à sua publicação." + description: "Uma contagem de novas respostas será exibida para esta mensagem. Será notificado se alguém mencionar o seu @nome ou responder-lhe." tracking: title: "Acompanhar" - description: "Será apresentada uma contagem de mensagens novas e não lidas junto do tópico Será notificado se alguém mencionar o seu @nome ou responder à sua mensagem." + description: "Uma contagem de novas respostas será exibida para este tópico. Será notificado se alguém mencionar o seu @nome ou responder-lhe." regular: title: "Habitual" - description: "Será notificado se alguém mencionar o seu @nome ou responder às suas mensagens." + description: "Será notificado se alguém mencionar o seu @nome ou responder-lhe." regular_pm: title: "Habitual" - description: "Será notificado se alguém mencionar o seu @nome ou responder à sua publicação na mensagem." + description: "Será notificado se alguém mencionar o seu @nome ou responder-lhe." muted_pm: title: "Silenciado" description: "Não será notificado de nada relacionado com esta mensagem." @@ -1007,8 +1007,8 @@ pt: sso_enabled: "Introduza o nome de utilizador da pessoa que gostaria de convidar para este tópico." to_topic_blank: "Introduza o nome de utilizador ou endereço de email da pessoa que gostaria de convidar para este tópico." to_topic_email: "Introduziu um endereço de email. Iremos enviar um email com um convite que permite aos seus amigos responderem a este tópico imediatamente." - to_topic_username: "Introduziu um nome de utilizador. Iremos enviar uma notificação a esse utilizador com uma hiperligação convidando-o para este tópico." - to_username: "Introduza o nome de utilizador da pessoa que gostava de convidar. Iremos enviar uma notificação para esse utilizador com uma hiperligação convidando-o para este tópico." + to_topic_username: "Introduziu um nome de utilizador. Iremos enviar-lhe uma notificação com uma hiperligação convidando-o para este tópico." + to_username: "Introduza o nome de utilizador da pessoa que deseja convidar. Iremos enviar-lhe uma notificação com uma hiperligação convidando-o para este tópico." email_placeholder: 'nome@exemplo.com' success_email: "Enviámos por email um convite para {{emailOrUsername}}. Iremos notificá-lo quando o convite for utilizado. Verifique o separador de convites na sua página de utilizador para acompanhar os seus convites." success_username: "Convidámos esse utilizador para participar neste tópico." @@ -1296,13 +1296,13 @@ pt: notifications: watching: title: "A vigiar" - description: "Irá vigiar automaticamente todos os novos tópicos nestas categorias. Será notificado de todas as novas mensagens e tópicos. Além disso, a contagem de mensagens novas e não lidas surgirá junto do tópico." + description: "Irá vigiar automaticamente todos os novos tópicos nestas categorias. Será notificado de todas as novas mensagens e tópicos, e uma contagem de novas respostas irá aparecer para estes tópicos." tracking: title: "Acompanhar" - description: "Irá acompanhar automaticamente todos os novos tópicos nestas categorias. Uma contagem de mensagens novas e não lidas surgirá junto do tópico." + description: "Irá acompanhar automaticamente todos os novos tópicos nestas categorias. Uma contagem de novas respostas irá aparecer para estes tópicos." regular: title: "Habitual" - description: "Será notificado se alguém mencionar o seu @nome ou responder às suas mensagens." + description: "Será notificado se alguém mencionar o seu @nome ou responder-lhe." muted: title: "Silenciado" description: "Não será notificado relativamente acerca de novos tópicos nestas categorias, e não aparecerão no seu separador de não lidos." @@ -1475,7 +1475,7 @@ pt: title: 'Administração Discourse' moderator: 'Moderador' dashboard: - title: "Painel Administrativo" + title: "Painel de Administração" last_updated: "Painel atualizado em:" version: "Versão" up_to_date: "Está atualizado!" @@ -1499,7 +1499,7 @@ pt: private_messages_title: "Mensagens" space_free: "{{size}} livre" uploads: "carregamentos" - backups: "cópias de segurança" + backups: "fazer cópias de segurança" traffic_short: "Tráfego" traffic: "Pedidos de aplicação web" page_views: "Pedidos API" @@ -1631,9 +1631,9 @@ pt: change_settings: "Alterar Configurações" howto: "Como instalo plugins?" backups: - title: "Cópias de Segurança" + title: "Fazer Cópias de Segurança" menu: - backups: "Cópias de Segurança" + backups: "Fazer Cópias de Segurança" logs: "Logs" none: "Nenhuma cópia de segurança disponível." read_only: @@ -1663,7 +1663,7 @@ pt: title: "Cancelar a operação atual" confirm: "Tem a certeza que deseja cancelar a operação atual?" backup: - label: "Cópia de segurança" + label: "Fazer Cópia de segurança" title: "Criar uma cópia de segurança" confirm: "Deseja criar uma nova cópia de segurança?" without_uploads: "Sim (não incluir ficheiros)" @@ -2124,7 +2124,7 @@ pt: embedding: "Incorporação" legal: "Legal" uncategorized: 'Outro' - backups: "Cópias de Segurança" + backups: "Fazer Cópias de Segurança" login: "Iniciar Sessão" plugins: "Plugins" badges: diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index 7d84b5194e..5505456261 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -347,6 +347,7 @@ sv: mailing_list_mode: "Skicka mig e-post för varje nytt inlägg (om jag inte tystat ämnet eller kategorin)" watched_categories: "Tittade på" tracked_categories: "Följd" + tracked_categories_instructions: "Du kommer automatiskt att följa alla nya ämnen i dessa kategorier. Antalet nya inlägg visas bredvid ämnet." muted_categories: "Tystad" muted_categories_instructions: "Du kommer inte att få notifieringar om nya trådar inom dessa kategorier, och de kommer inte att visas under din \"oläst\"-tab." delete_account: "Radera mitt konto" @@ -652,6 +653,7 @@ sv: google: "Google" twitter: "Twitter" composer: + emoji: "Emoji :smile:" add_warning: "Det här är en officiell varning" posting_not_on_topic: "Du svarar på tråden \"{{title}}\", men du besöker just nu en annan tråd." saving_draft_tip: "sparar…" @@ -754,6 +756,7 @@ sv: from_the_web: "Från webben" remote_tip: "länk till bild" remote_tip_with_attachments: "länk till bild eller fil ({{authorized_extensions}})" + local_tip_with_attachments: "klicka för att välja upp till 10 bilder eller filer från din enhet ({{authorized_extensions}})" hint: "(du kan också dra & släppa in i redigeraren för att ladda upp dem)" hint_for_supported_browsers: "(du kan också dra och släppa eller klistra in bilder i redigeraren för att ladda upp dem)" uploading: "Laddar upp bild" @@ -859,6 +862,7 @@ sv: toggle_information: "slå av/på tråddetaljer" read_more_in_category: "Vill du läsa mer? Bläddra bland andra trådar i {{catLink}} eller {{latestLink}}." read_more: "Vill du läsa mer? {{catLink}} eller {{latestLink}}." + read_more_MF: "Det finns { UNREAD, plural, =0 {} one { 1 oläst } other { # olästa } } { NEW, plural, =0 {} one { {BOTH, select, true{och } false {} other{}} 1 nytt ämne} other { {BOTH, select, true{och } false {} other{}} # nya ämnen} } kvar, eller {CATEGORY, select, true {bläddra bland andra ämnen i {catLink}} false {{latestLink}} other {}}" browse_all_categories: Bläddra bland alla kategorier view_latest_topics: se de senaste trådarna suggest_create_topic: Varför inte skapa en tråd? @@ -891,11 +895,14 @@ sv: '2_4': 'Du kommer ta emot notifikationer för att du postade ett svar till denna tråd.' '2_2': 'Du kommer ta emot notifikationer för att du följer denna tråd.' '2': 'Du kommer ta emot notifikationer för att du läser denna tråd.' + '1_2': 'Du kommer få en notifiering om någon nämner ditt @namn eller svarar på ditt inlägg.' + '1': 'Du kommer få en notifiering om någon nämner ditt @namn eller svarar på ditt inlägg.' '0_7': 'Du ignorerar alla notifikationer i den här kategorin.' '0_2': 'Du ignorerar alla notifikationer för denna tråd.' '0': 'Du ignorerar alla notifikationer för denna tråd.' watching_pm: title: "följda" + description: "Du kommer att få en notifiering för varje nytt svar i detta meddelande, samt en räknare med antalet nya svar." watching: title: "Kollar" tracking_pm: @@ -2007,6 +2014,7 @@ sv: confirm: 'Bekräftelse' site_text: none: "Välj typ av innehåll för att börja redigera." + title: 'Textinnehåll' site_settings: show_overriden: 'Visa bara överskrivna' title: 'Webbplatsinställningar' @@ -2054,6 +2062,7 @@ sv: delete_confirm: Är du säker på att du vill ta bort den utmärkelsen? revoke: Upphäv reason: Anledning + expand: Expandera … revoke_confirm: Är du säker på att du vill upphäva den utmärkelsen? edit_badges: Redigera utmärkelser grant_badge: Ge utmärkelse @@ -2075,8 +2084,12 @@ sv: grant: with: %{username} emoji: + title: "Emoji" + help: "Lägg till en ny emoji för andra att använda. (TIPS: dra och släpp flera filer på en och samma gång)" + add: "Lägg till ny emoji" name: "Namn" image: "Bild" + delete_confirm: "Är du säker på att du vill radera emoji-ikonen :%{name}:?" lightbox: download: "ladda ned" search_help: diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml index 3e8d9edd19..29f41d018c 100644 --- a/config/locales/client.tr_TR.yml +++ b/config/locales/client.tr_TR.yml @@ -74,7 +74,7 @@ tr_TR: facebook: 'bu bağlantıyı Facebook''da paylaşın' google+: 'bu bağlantıyı Google+''da paylaşın' email: 'bu bağlantıyı e-posta ile gönderin' - topic_admin_menu: "konuyla alakalı yönetici aksiyonları" + topic_admin_menu: "konuyla alakalı yönetici işlemleri" emails_are_disabled: "Tüm giden e-postalar yönetici tarafından evrensel olarak devre dışı bırakıldı. Herhangi bir e-posta bildirimi gönderilmeyecek." edit: 'bu konunun başlığını ve kategorisini düzenleyin' not_implemented: "Bu özellik henüz geliştirilmedi, üzgünüz!" @@ -101,7 +101,7 @@ tr_TR: mobile_view: "Mobil görünümü" desktop_view: "Masaüstü görünümü" you: "Siz" - or: "veya" + or: "ya da" now: "hemen şimdi" read_more: 'devamını oku' more: "Daha fazla" @@ -124,18 +124,18 @@ tr_TR: our_moderators: "Moderatörlerimiz" stat: all_time: "Tüm zamanlar" - last_7_days: "Son 7 Gün" - last_30_days: "Son 30 Gün" + last_7_days: "Son 7 gün" + last_30_days: "Son 30 gün" like_count: "Beğeniler" topic_count: "Konular" post_count: "Gönderiler" - user_count: "Yeni Kullanıcılar" + user_count: "Yeni kullanıcılar" active_user_count: "Aktif Kullanıcılar" - contact: "İletişim" + contact: "Bize ulaşın" contact_info: "Bu siteyi etkileyen kritik bir problem ya da acil bir durum oluştuğunda, lütfen %{contact_info} adresi üzerinden bizimle iletişime geçin." bookmarked: title: "İşaretle" - clear_bookmarks: "İşaretlenenleri Temizle" + clear_bookmarks: "İşaretlenenleri yemizle" help: bookmark: "Bu konudaki ilk gönderiyi işaretlemek için tıklayın" unbookmark: "Bu konudaki bütün işaretleri kaldırmak için tıklayın" @@ -144,7 +144,7 @@ tr_TR: created: "bu gönderiyi işaretlediniz" not_bookmarked: "bu gönderiyi okudunuz; işaretlemek için tıklayın" last_read: "bu okuduğunuz son gönderi; işaretlemek için tıklayın" - remove: "İşareti Kaldır" + remove: "İşareti kaldır" confirm_clear: "Bu konuya ait tüm işaretleri kaldırmak istediğinize emin misiniz?" topic_count_latest: other: "{{count}} yeni ya da güncellenmiş konular." @@ -162,12 +162,12 @@ tr_TR: uploading: "Yükleniyor..." uploaded: "Yüklendi!" enable: "Etkinleştir" - disable: "Devre Dışı Bırak" - undo: "Geri Al" - revert: "Eski Haline Getir" - failed: "Başarısız Oldu" - switch_to_anon: "Anonim Modu" - switch_from_anon: "Anonim Modundan Çıkın" + disable: "Devredışı bırak" + undo: "Geri al" + revert: "Eski haline getir" + failed: "Başarısız oldu" + switch_to_anon: "Anonim ol" + switch_from_anon: "Anonim modundan çık" banner: close: "Bu manşeti yoksay." edit: "Bu manşeti düzenle >>" @@ -180,8 +180,8 @@ tr_TR: topic: "Konu:" approve: 'Onayla' reject: 'Reddet' - delete_user: 'Kullanıcıyı Sil' - title: "Onay Gerektirir" + delete_user: 'Kullanıcıyı sil' + title: "Onay gerektirir" none: "Gözden geçirilecek bir gönderi yok." edit: "Düzenle" cancel: "İptal" @@ -189,10 +189,10 @@ tr_TR: has_pending_posts: one: "Onay bekleyen 1 yazı var" many: "Onay bekleyen {{count}} yazı var" - confirm: "Düzenlemeleri Kaydet" + confirm: "Düzenlemeleri kaydet" delete_prompt: "%{username} kullanıcısını silmek istediğinize emin misiniz? Bu işlem kullanıcının tüm gönderilerini silecek, e-posta ve ip adresini engelleyecek." approval: - title: "Gönderi Onay Gerektirir" + title: "Gönderi onay gerektirir" description: "Gönderinizi aldık fakat gösterilmeden önce bir moderatör tarafından onaylanması gerekiyor. Lütfen sabırlı olun." pending_posts: other: "Bekleyen {{count}} yazınız bulunmaktadır." @@ -217,17 +217,17 @@ tr_TR: likes_given: "Verilen" likes_received: "Alınan" topics_entered: "Açıldı" - topics_entered_long: "Açılan Konular" - time_read: "Okuma Zamanı" + topics_entered_long: "Açılan konular" + time_read: "Okuma zamanı" topic_count: "Konular" - topic_count_long: "Oluşturulan Konular" + topic_count_long: "Oluşturulan konular" post_count: "Cevaplar" - post_count_long: "Gönderilen Cevaplar" + post_count_long: "Gönderilen cevaplar" no_results: "Sonuç bulunamadı." days_visited: "Ziyaretler" - days_visited_long: "Ziyaret Günleri" + days_visited_long: "Ziyaret günleri" posts_read: "Okunmuşlar" - posts_read_long: "Okunmuş Gönderiler" + posts_read_long: "Okunmuş gönderiler" total_rows: other: "%{count} kullanıcı" groups: @@ -238,14 +238,14 @@ tr_TR: posts: "Gönderiler" alias_levels: title: "Kimler bu grubu ikinci adı olarak kullanabilir?" - nobody: " Hiç Kimse" + nobody: " Hiç kimse" only_admins: "Sadece yöneticiler" mods_and_admins: "Sadece moderatörler ve yöneticiler" - members_mods_and_admins: "Sadece Grup Üyeleri, Moderatörler ve Yöneticiler" + members_mods_and_admins: "Sadece grup üyeleri, moderatörler ve yöneticiler" everyone: "Herkes" user_action_groups: - '1': "Verilen Beğeniler" - '2': "Alınan Beğeniler" + '1': "Verilen beğeniler" + '2': "Alınan beğeniler" '3': "İşaretlenenler" '4': "Konular" '5': "Cevaplar" @@ -275,7 +275,7 @@ tr_TR: post_stat_sentence: other: "%{unit} beri %{count} yeni gönderi." ip_lookup: - title: IP Adresi Ara + title: IP adresi ara hostname: Sunucu ismi location: Yer location_not_found: (bilinmeyen) @@ -293,8 +293,8 @@ tr_TR: said: "{{username}}:" profile: "Profil" mute: "Sustur" - edit: "Ayarları Düzenle" - download_archive: "Gönderilerimi İndir" + edit: "Ayarları düzenle" + download_archive: "Gönderilerimi indir" new_private_message: "Yeni mesaj" private_message: "Mesaj" private_messages: "Mesajlar" @@ -302,8 +302,8 @@ tr_TR: preferences: "Seçenekler" bookmarks: "İşaretlenenler" bio: "Hakkımda" - invited_by: "Tarafından Davet Edildi" - trust_level: "Güven Seviyesi" + invited_by: "Tarafından davet edildi" + trust_level: "Güven seviyesi" notifications: "Bildirimler" dismiss_notifications: "Hepsini okunmuş olarak işaretle" dismiss_notifications_tooltip: "Tüm okunmamış bildirileri okunmuş olarak işaretle" @@ -322,10 +322,12 @@ tr_TR: github_profile: "Github" mailing_list_mode: "(Konuyu veya kategoriyi susturmadığım takdirde) her yeni gönderi için bana bir e-posta yolla" watched_categories: "Gözlendi" + watched_categories_instructions: "Bu kategorilerdeki tüm yeni konuları otomatik olarak gözleyeceksiniz. Tüm yeni gönderi ve konular size bildirilecek. Ayrıca, okunmamış ve yeni gönderilerin sayısı ilgili konu yanında belirecek." tracked_categories: "Takip edildi" + tracked_categories_instructions: "Bu kategorilerdeki tüm yeni konuları otomatik olarak takip edeceksiniz. Okunmamış ve yeni gönderilerin sayısı ilgili konunun yanında belirecek." muted_categories: "Susturuldu" muted_categories_instructions: "Bu kategorideki yeni konular okunmamışlar sekmenizde belirmeyecek, ve haklarında hiçbir bildirim almayacaksınız." - delete_account: "Hesabımı Sil" + delete_account: "Hesabımı sil" delete_account_confirm: "Hesabınızı kalıcı olarak silmek istediğinize emin misiniz? Bu işlemi geri alamazsınız!" deleted_yourself: "Hesabınız başarıyla silindi." delete_yourself_not_allowed: "Hesabınızı şu an silemezsiniz. Hesabınızı silmesi için bir yönetici ile iletişime geçin." @@ -351,7 +353,7 @@ tr_TR: action: "Parola sıfırlama e-postası gönder" set_password: "Parola belirle" change_about: - title: "Hakkımda'yı Değiştir" + title: "Hakkımda'yı değiştir" error: "Değer değiştirilirken bir hata oluştu." change_username: title: "Kullanıcı adını değiştir" @@ -360,22 +362,22 @@ tr_TR: error: "Kullanıcı adınızı değiştirirken bir hata oluştu." invalid: "Bu kullanıcı adı geçersiz. Sadece sayı ve harf içermelidir." change_email: - title: "E-posta Adresinizi Değiştirin" - taken: "Üzgünüz, bu e-posta müsait değil." + title: "E-posta adresini değiştirin" + taken: "Üzgünüz, bu e-posta kullanılabilir değil." error: "E-posta adresinizi değiştirirken bir hata oluştu. Belki bu adres zaten kullanımdadır?" success: "Adresinize bir e-posta gönderdik. Lütfen onaylama talimatlarını uygulayınız." change_avatar: title: "Profil görselinizi değiştirin" gravatar: "Gravatar, baz alındı" - gravatar_title: "Avatarınızı Gravatar sitesinde değiştirin" - refresh_gravatar_title: "Avatarınızı Yenileyin" + gravatar_title: "Profil görselinizi Gravatar sitesinde değiştirin" + refresh_gravatar_title: "Profil görselinizi yenileyin" letter_based: "Sistem tarafından verilen profil görseli" uploaded_avatar: "Özel resim" uploaded_avatar_empty: "Özel resim ekleyin" upload_title: "Resminizi yükleyin" upload_picture: "Resim Yükle" image_is_not_a_square: "Uyarı: resminizi kırptık; genişlik ve yükseklik eşit değildi." - cache_notice: "Avatarınızı başarıyla değiştirdiniz, fakat tarayıcınızın önbelleklemesinden ötürü gözükmesi biraz zaman alabilir." + cache_notice: "Profil görselinizi başarıyla değiştirdiniz, fakat tarayıcınızın önbelleklemesinden ötürü gözükmesi biraz zaman alabilir." change_profile_background: title: "Profil arkaplanı" instructions: "Profil arkaplanları ortalanacak ve genişlikleri 850px olacak. " @@ -417,9 +419,9 @@ tr_TR: default: "(varsayılan)" password_confirmation: title: "Tekrar Parola" - last_posted: "Son Gönderi" + last_posted: "Son gönderi" last_emailed: "Son E-posta Atılan" - last_seen: "Görülmüş" + last_seen: "Son görülme" created: "Katıldı" log_out: "Oturumu kapat" location: "Yer" @@ -464,13 +466,13 @@ tr_TR: redeemed_at: "Kabul Edildi" pending: "Bekleyen davetler" topics_entered: "Görüntülenmiş Konular" - posts_read_count: "Okunmuş Yazılar" + posts_read_count: "Okunmuş yazılar" expired: "Bu davetin süresi doldu." rescind: "Kaldır" rescinded: "Davet kaldırıldı" reinvite: "Davetiyeyi Tekrar Yolla" reinvited: "Davetiye tekrar yollandı" - time_read: "Okunma Zamanı" + time_read: "Okunma zamanı" days_visited: "Ziyaret Edilen Günler" account_age_days: "Gün içinde Hesap yaş" create: "Davet Yolla" @@ -563,10 +565,10 @@ tr_TR: remove_allowed_user: "Bu mesajlaşmadan {{name}} isimli kullanıcıyı çıkarmak istediğinize emin misiniz?" email: 'E-posta' username: 'Kullanıcı Adı' - last_seen: 'Görülmüş' + last_seen: 'Son görülme' created: 'Oluşturuldu' created_lowercase: 'oluşturuldu' - trust_level: 'Güven Seviyesi' + trust_level: 'Güven seviyesi' search_hint: 'kullanıcı adı, e-posta veya IP adresi' create_account: title: "Yeni Hesap Oluştur" @@ -750,19 +752,19 @@ tr_TR: current_user: 'kendi kullanıcı sayfana git' topics: bulk: - reset_read: "Okunmuşları Sıfırla" - delete: "Konuları Sil" + reset_read: "Okunmuşları sıfırla" + delete: "Konuları sil" dismiss_posts: "Gönderileri Yoksay" dismiss_posts_tooltip: "Bu konulardaki okunmamışların sayısını sıfırla ama yeni gönderiler oluşturulduğunda okunmamışlar listemde göster" - dismiss_topics: "Konuları Yoksay" + dismiss_topics: "Konuları yoksay" dismiss_topics_tooltip: "Bu konularda yeni gönderiler oluşturulunca okunmamış listemde gösterme" - dismiss_new: "Yenileri Yoksay" + dismiss_new: "Yenileri yoksay" toggle: "konuların toplu seçimini aç/kapa" actions: "Toplu işlemler" - change_category: "Kategoriyi Değiştir" - close_topics: "Konuları Kapat" - archive_topics: "Konuları Arşivle" - notification_level: "Bildirim Seviyesini Değiştir" + change_category: "Kategoriyi değiştir" + close_topics: "Konuları kapat" + archive_topics: "Konuları arşivle" + notification_level: "Bildirim seviyesini değiştir" choose_new_category: "Konular için yeni bir kategori seçin:" selected: other: "{{count}} konu seçtiniz." @@ -822,8 +824,8 @@ tr_TR: other: "bu konuda, son okumanızdan bu yana {{count}} yeni gönderi var" likes: other: "bu konuda {{count}} beğeni var" - back_to_list: "Konu Listesine Geri Dön" - options: "Konu Seçenekleri" + back_to_list: "Konu listesine geri dön" + options: "Konu seçenekleri" show_links: "Bu konunun içindeki bağlantıları göster. " toggle_information: "konu ayrıntılarını aç/kapa" read_more_in_category: "Daha fazlası için {{catLink}} kategorisine göz atabilir ya da {{latestLink}}bilirsiniz." @@ -861,21 +863,29 @@ tr_TR: '2_4': 'Bu konuya cevap yazdığınız için bildirimlerini alacaksınız.' '2_2': 'Bu konuyu takip ettiğiniz için bildirimlerini alacaksınız.' '2': 'Bu konuyu okuduğunuz için bildirimlerini alacaksınız.' + '1_2': 'Birisi @isim şeklinde sizden bahsederse ya da gönderinize cevap verirse bildirim alacaksınız.' + '1': 'Birisi @isim şeklinde sizden bahsederse ya da gönderinize cevap verirse bildirim alacaksınız.' '0_7': 'Bu kategoriye ait tüm bildirimleri görmezden geliyorsunuz.' '0_2': 'Bu konuya ait tüm bildirimleri görmezden geliyorsunuz.' '0': 'Bu konuya ait tüm bildirimleri görmezden geliyorsunuz.' watching_pm: title: "Gözleniyor" + description: "Bu mesajlaşmada ki her yeni gönderi için bir bildirim alacaksınız. Okunmamış ve yeni gönderilerin sayısı konunun yanında belirecek." watching: title: "Gözleniyor" + description: "Bu konudaki her yeni gönderi için bir bildirim alacaksınız. Okunmamış ve yeni gönderilerin sayısı konunun yanında belirecek." tracking_pm: - title: "Takip Ediliyor" + title: "Takip ediliyor" + description: "Okunmamış ve yeni gönderi sayısı mesajın yanında belirecek. Birisi @isim şeklinde sizden bahsederse ya da gönderinize cevap verirse bildirim alacaksınız." tracking: - title: "Takip Ediliyor" + title: "Takip ediliyor" + description: "Okunmamış ve yeni gönderi sayısı başlığın yanında belirecek. Birisi @isim şeklinde sizden bahsederse ya da gönderinize cevap verirse bildirim alacaksınız." regular: title: "Standart" + description: "Birisi @isim şeklinde sizden bahsederse ya da gönderinize cevap verirse bildirim alacaksınız." regular_pm: title: "Standart" + description: "Birisi @isim şeklinde sizden bahsederse ya da gönderinize mesajla cevap verirse bildirim alacaksınız." muted_pm: title: "Susturuldu" description: "Bu mesajlaşmayla ilgili hiç bir bildirim almayacaksınız." @@ -885,23 +895,23 @@ tr_TR: actions: recover: "Konuyu geri getir" delete: "Konuyu sil" - open: "Konuyu Aç" + open: "Konuyu aç" close: "Konuyu kapat" multi_select: "Gönderileri seç..." auto_close: "Otomatik kapat..." pin: "Başa tuttur..." unpin: "Baştan kaldır..." - unarchive: "Konuyu Arşivden Kaldır" + unarchive: "Konuyu arşivden kaldır" archive: "Konuyu arşivle" invisible: "Listelenmemiş yap" - visible: "Listelenmiş Yap" + visible: "Listelenmiş yap" reset_read: "Görüntüleme verilerini sıfırla" feature: pin: "Başa tuttur" unpin: "Baştan kaldır" pin_globally: "Her yerde başa tuttur" - make_banner: "Manşet Konusu" - remove_banner: "Manşey Konusunu Kaldır" + make_banner: "Manşet konusu" + remove_banner: "Manşet konusunu kaldır" reply: title: 'Cevapla' help: 'bu konuya bir cevap oluşturmaya başlayın' @@ -939,13 +949,13 @@ tr_TR: already_banner: zero: "Manşet konusu yok." one: "Şu an bir manşet konusu var." - inviting: "Davet Ediliyor..." + inviting: "Davet ediliyor..." automatically_add_to_groups_optional: "Bu davet şu gruplara erişimi de içerir: (opsiyonel, sadece yöneticiler için)" automatically_add_to_groups_required: "Bu davet şu gruplara erişimi de içerir: (Gerekli, sadece yöneticiler için)" invite_private: - title: 'Mesajlaşmaya Davet Et' - email_or_username: "Davet edilenin E-postası veya Kullanıcı Adı" - email_or_username_placeholder: "e-posta veya kullanıcı adı" + title: 'Mesajlaşmaya davet et' + email_or_username: "Davet edilenin e-postası ya da kullanıcı adı" + email_or_username_placeholder: "e-posta ya da kullanıcı adı" action: "Davet et" success: "O kullanıcıyı bu mesajlaşmaya davet ettik." error: "Üzgünüz, kullanıcı davet edilirken bir hata oluştu." @@ -953,12 +963,14 @@ tr_TR: invite_reply: title: 'Davet et' username_placeholder: "kullanıcıadı" - action: 'Davet Yolla' + action: 'Davet gönder' help: 'e-posta veya bildiri aracılığıyla başkalarını bu konuya davet edin' to_forum: "Arkadaşınıza, oturum açması gerekmeden, bir bağlantıya tıklayarak katılabilmesi için kısa bir e-posta göndereceğiz. " sso_enabled: "Bu konuya davet etmek istediğiniz kişinin kullanıcı adını girin." to_topic_blank: "Bu konuya davet etmek istediğiniz kişinin kullanıcı adını veya e-posta adresini girin." to_topic_email: "Bir email adresi girdiniz. Arkadaşınızın konuya hemen cevap verebilmesini sağlayacak bir davetiye e-postalayacağız." + to_topic_username: "Bir kullanıcı adı girdiniz. Kullanıcıya, bu konuya davet bağlantısı içeren bir bildiri yollayacağız." + to_username: "Davet etmek istediğiniz kişinin kullanıcı adını girin. Kullanıcıya, bu konuya davet bağlantısı içeren bir bildiri yollayacağız." email_placeholder: 'isim@örnek.com' success_email: "{{emailOrUsername}} kullanıcısına davet e-postalandı. Davet kabul edildiğinde size bir bildiri göndereceğiz. Davetlerinizi takip etmek için kullanıcı sayfanızdaki davetler sekmesine göz atın." success_username: "Kullanıcıyı bu konuya katılması için davet ettik." @@ -976,16 +988,16 @@ tr_TR: instructions: other: "Yeni bir konu oluşturmak ve bu konuyu seçtiğiniz {{count}} gönderi ile doldurmak üzeresiniz." merge_topic: - title: "Var Olan Bir Konuya Taşı" + title: "Var olan bir konuya taşı" action: "var olan bir konuya taşı" - error: "Gönderiler o konuya taşınırken bir hata oluştu." + error: "Gönderiler konuya taşınırken bir hata oluştu." instructions: other: "Lütfen bu {{count}} gönderiyi taşımak istediğiniz konuyu seçin. " change_owner: - title: "Gönderilerin Sahibini Değiştir" + title: "Gönderilerin sahibini değiştir" action: "sahipliğini değiştir" error: "Gönderilerin sahipliği değiştirilirken bir hata oluştu." - label: "Gönderilerin Yeni Sahibi" + label: "Gönderilerin yeni sahibi" placeholder: "yeni sahibin kullanıcı adı" instructions: other: "Lütfen {{old_user}} kullanıcısına ait {{count}} gönderinin yeni sahibini seçin." @@ -1011,7 +1023,7 @@ tr_TR: reply_as_new_topic: "Bağlantılı konu olarak cevapla" continue_discussion: "{{postLink}} Gönderisinden tartışmaya devam ediliyor:" follow_quote: "alıntılanan mesaja git" - show_full: "Gönderinin Tamamını Göster" + show_full: "Gönderinin tamamını göster" show_hidden: 'Gizlenmiş içeriği görüntüle.' deleted_by_author: other: "(yazarı tarafından geri alınan gönderi, %{count} saat içinde otomatik olarak silinecek.)" @@ -1042,7 +1054,7 @@ tr_TR: wiki: about: "bu gönderi bir wiki; acemi kullanıcılar düzenleyebilir" archetypes: - save: 'Seçenekleri Kaydet' + save: 'Seçenekleri kaydet' controls: reply: "bu gönderiye bir cevap oluşturmaya başlayın" like: "bu gönderiyi beğen" @@ -1061,11 +1073,11 @@ tr_TR: yes_value: "Evet, cevapları da sil" no_value: "Hayır, sadece bu gönderiyi" admin: "gönderiyle alakalı yönetici işlemleri" - wiki: "Wiki Yap" - unwiki: "Wiki'yi Kaldır" - convert_to_moderator: "Görevli Rengi Ekle" - revert_to_regular: "Görevli Rengini Kaldır" - rebake: "HTML'i yeniden kur" + wiki: "Wiki yap" + unwiki: "Wiki'yi kaldır" + convert_to_moderator: "Görevli rengi ekle" + revert_to_regular: "Görevli rengini kaldır" + rebake: "HTML'i yeniden yapılandır" unhide: "Gizleme" actions: flag: 'Rapor et' @@ -1173,21 +1185,21 @@ tr_TR: choose: 'Kategori seç…' edit: 'düzenle' edit_long: "Düzenle" - view: 'Bu Kategorideki Konuları Görüntüle' + view: 'Bu kategorideki konuları görüntüle' general: 'Genel' settings: 'Ayarlar' - delete: 'Kategoriyi Sil' + delete: 'Kategoriyi sil' create: 'Yeni kategori' - save: 'Kategoriyi Kaydet' - slug: 'Kategori Anahtarı' - slug_placeholder: '(Opsiyonel) url için tire ile ayırılmış kelimeler' + save: 'Kategoriyi kaydet' + slug: 'Kategori kalıcı bağlantısı' + slug_placeholder: '(Opsiyonel) bağlantı için tire ile ayırılmış kelimeler' creation_error: Kategori oluşturulurken hata oluştu. save_error: Kategori kaydedilirken hata oluştu. name: "Kategori Adı" description: "Açıklama" topic: "kategori konusu" - logo: "Kategori Logosu Resmi" - background_image: "Kategori Arka Planı Görseli" + logo: "Kategori logosu görseli" + background_image: "Kategori arka planı görseli" badge_colors: "Rozet renkleri" background_color: "Arka plan rengi" foreground_color: "Ön plan rengi" @@ -1197,7 +1209,7 @@ tr_TR: delete_error: "Kategoriyi silinirken bir hata oluştu." list: "Kategorileri Listele" no_description: "Lütfen bu kategori için bir açıklama girin." - change_in_category_topic: "Açıklamayı Düzenle" + change_in_category_topic: "Açıklamayı düzenle" already_used: 'Bu renk başka bir kategori için kullanıldı' security: "Güvenlik" images: "Resimler" @@ -1208,21 +1220,24 @@ tr_TR: email_in_disabled: "E-posta üzerinden yeni konu oluşturma özelliği Site Ayarları'nda devre dışı bırakılmış. E-posta üzerinden yeni konu oluşturma özelliğini etkinleştirmek için," email_in_disabled_click: '"e-postala" ayarını etkinleştir' allow_badges_label: "Bu kategoride rozet verilmesine izin ver" - edit_permissions: "İzinleri Düzenle" - add_permission: "İzin Ekle" + edit_permissions: "İzinleri düzenle" + add_permission: "İzin ekle" this_year: "bu yıl" position: "pozisyon" - default_position: "Varsayılan Pozisyon" + default_position: "Varsayılan pozisyon" position_disabled: "Kategoriler etkinlik sıralarına göre görünecekler. Listelerdeki kategorilerin sıralamalarını kontrol edebilmek için," position_disabled_click: '"sabitlenmiş kategori pozisyonları" ayarını etklinleştirin.' - parent: "Üst Kategori" + parent: "Üst kategori" notifications: watching: title: "Gözleniyor" + description: "Bu kategorilerdeki tüm yeni konuları otomatik olarak gözleyeceksiniz. Tüm yeni gönderi ve konularla ilgili bildiri alacaksınız, ayrıca okunmamış ve yeni gönderilerin sayısı ilgili konunun yanında belirecek." tracking: - title: "Takip Ediliyor" + title: "Takip ediliyor" + description: "Bu kategorilerdeki tüm yeni konuları otomatik olarak takip edeceksiniz. Okunmamış ve yeni gönderilerin sayısı ilgili konunun yanında belirecek." regular: title: "Standart" + description: "Birisi @isim şeklinde sizden bahsederse ya da gönderinize cevap verirse bildirim alacaksınız." muted: title: "Susturuldu" description: "Bu kategorilerdeki yeni konular hakkında herhangi bir bildiri almayacaksınız ve okunmamışlar sekmenizde belirmeyecek. " @@ -1241,11 +1256,11 @@ tr_TR: take_action_tooltip: "Topluluğunuzdan daha fazla içerik raporu beklemek yerine bunu siz hızlıca yaparak eşiğe erişebilirsiniz" cant: "Üzgünüz, şu an bu gönderiyi rapor edemezsiniz." formatted_name: - off_topic: "Konu Dışı" + off_topic: "Konu dışı" inappropriate: "Uygunsuz" spam: "SPAM" - custom_placeholder_notify_user: "Açıklayıcı, yapıcı, ve her zaman nazik olun." - custom_placeholder_notify_moderators: "Sizi neyin endişelendirdiğini açıklayıcı bir dille bize bildirin, ve mümkün olan yerlerde konu ile alakalı bağlantıları paylaşın." + custom_placeholder_notify_user: "Açıklayıcı, yapıcı ve her zaman nazik olun." + custom_placeholder_notify_moderators: "Sizi neyin endişelendirdiğini açıklayıcı bir dille bize bildirin ve mümkün olan yerlerde konu ile alakalı bağlantıları paylaşın." custom_message: at_least: "en az {{n}} karakter girin" more: "{{n}} tane daha var.." @@ -1255,7 +1270,7 @@ tr_TR: action: "Konuyu rapor et" notify_action: "Mesaj" topic_map: - title: "Konu Özeti" + title: "Konu özeti" participants_title: "Yoğun konular" links_title: "Popüler bağlantılar" links_shown: "tüm {{totalLinks}} bağlantıları göster..." @@ -1272,7 +1287,7 @@ tr_TR: title: "Başa tutturma kaldırıldı" help: "Bu konu sizin için başa tutturulmuyor; normal sıralama içerisinde görünecek" pinned_globally: - title: "Her yerde başa turruldu" + title: "Her yerde başa tutturuldu" help: "Bu konu her yerde başa tutturuldu; tüm listelerin başında görünecek" pinned: title: "Başa tutturuldu" @@ -1418,23 +1433,23 @@ tr_TR: backups: "Yedekler" traffic_short: "Trafik" traffic: "Uygulama web istekleri" - page_views: "API İstekleri" - page_views_short: "API İstekleri" + page_views: "API istekleri" + page_views_short: "API istekleri" show_traffic_report: "Detaylı trafik raporunu görüntüle" reports: today: "Bugün" yesterday: "Dün" - last_7_days: "Son 7 Gün" - last_30_days: "Son 30 Gün" + last_7_days: "Son 7 gün" + last_30_days: "Son 30 gün" all_time: "Tüm zamanlar" - 7_days_ago: "7 Gün Önce" - 30_days_ago: "30 Gün Önce" + 7_days_ago: "7 gün önce" + 30_days_ago: "30 gün önce" all: "Hepsi" view_table: "tablo" view_chart: "sütunlu grafik" refresh_report: "Raporu Yenile" - start_date: "Başlangıç Tarihi" - end_date: "Bitiş Tarihi" + start_date: "Başlangıç tarihi" + end_date: "Bitiş tarihi" commits: latest_changes: "En son değişiklikler: lütfen sık güncelleyin!" by: "tarafından" @@ -1475,7 +1490,7 @@ tr_TR: deferred: "ertelendi" flagged_by: "Rapor eden" resolved_by: "Çözen" - took_action: "Aksiyon aldı" + took_action: "İşlem uygulandı" system: "Sistem" error: "Bir şeyler ters gitti" reply_message: "Yanıtla" @@ -1496,10 +1511,10 @@ tr_TR: action_type_8: other: "spam x{{count}}" groups: - primary: "Ana Grup" + primary: "Ana grup" no_primary: "(ana grup yok)" title: "Grup" - edit: "Grupları Düzenle" + edit: "Grupları düzenle" refresh: "Yenile" new: "Yeni" selector_placeholder: "kullanıcı adı girin" @@ -1524,11 +1539,11 @@ tr_TR: none: "Şu an etkin API anahtarı bulunmuyor." user: "Kullanıcı" title: "API" - key: "API Anahtarı" + key: "API anahtarı" generate: "Oluştur" - regenerate: "Tekrar Oluştur" + regenerate: "Tekrar oluştur" revoke: "İptal Et" - confirm_regen: "API Anahtarını yenisi ile değiştirmek istediğinize emin misiniz?" + confirm_regen: "API anahtarını yenisi ile değiştirmek istediğinize emin misiniz?" confirm_revoke: "Anahtarı iptal etmek istediğinize emin misiniz?" info_html: "API anahtarınız JSON çağrıları kullanarak konu oluşturup güncelleyebilmenize olanak sağlayacaktır." all_users: "Tüm kullanıcılar" @@ -1539,8 +1554,8 @@ tr_TR: name: "İsim" none_installed: "Yüklenmiş herhangi bir eklentiniz yok." version: "Versiyon" - change_settings: "Ayarları Değiştir" - howto: "Nasıl eklenti yüklerim?" + change_settings: "Ayarları değiştir" + howto: "Nasıl eklenti yükleyebilirim?" backups: title: "Yedekler" menu: @@ -1613,8 +1628,8 @@ tr_TR: long_title: "Site Özelleştirmeleri" css: "CSS" header: "Başlık" - top: "En Popüler" - footer: "Altbilgi" + top: "Üst kısım" + footer: "Alt kısım" head_tag: text: "" title: " etiketinden önce eklenecek HTML" @@ -1631,7 +1646,7 @@ tr_TR: explain_rescue_preview: "Websitesine varsayılan stil sayfası ile bak" save: "Kaydet" new: "Yeni" - new_style: "Yeni Stil" + new_style: "Yeni stil" delete: "Sil" delete_confirm: "Bu özelleştirmeyi sil?" about: "Websitesindeki CSS stil sayfalarını ve HTML başlıklarını değiştir. Özelleştirme ekleyerek başla." @@ -1640,12 +1655,12 @@ tr_TR: copy: "Kopyala" css_html: title: "CSS/HTML" - long_title: "CSS ve HTML Özelleştirmeleri" + long_title: "CSS ve HTML özelleştirmeleri" colors: title: "Renkler" - long_title: "Renk Düzenleri" + long_title: "Renk düzenleri" about: "Websitesindeki renkleri CSS yazmadan değiştir. Renk düzeni ekleyerek başla." - new_name: "Yeni Renk Düzeni" + new_name: "Yeni renk düzeni" copy_name_prefix: "Kopyası" delete_confirm: "Bu renk düzenini sil?" undo: "geri al" @@ -1694,11 +1709,11 @@ tr_TR: test_error: "Test e-postasının gönderilmesinde sorun yaşandı. Lütfen e-posta ayarlarınızı tekrar kontrol edin, yer sağlayıcınızın e-posta bağlantılarını bloke etmediğinden emin olun, ve tekrar deneyin." sent: "Gönderildi" skipped: "Atlandı" - sent_at: "Gönderildiği Zaman" + sent_at: "Gönderildiği zaman" time: "Zaman" user: "Kullanıcı" - email_type: "E-posta Tipi" - to_address: "Gönderi Adresi" + email_type: "E-posta türü" + to_address: "Gönderi adresi" test_email_address: "test için e-posta adresi" send_test: "Test e-postası gönder" sent_test: "gönderildi!" @@ -1723,7 +1738,7 @@ tr_TR: skipped_reason_placeholder: "neden" logs: title: "Kayıtlar" - action: "Aksiyon" + action: "İşlem" created_at: "Oluşturuldu" last_match_at: "En son eşlenen" match_count: "Eşleşmeler" @@ -1828,14 +1843,14 @@ tr_TR: reject_selected: other: "({{count}}) kullanıcıyı reddet" titles: - active: 'Etkin Kullanıcılar' - new: 'Yeni Kullanıcılar' + active: 'Etkin kullanıcılar' + new: 'Yeni kullanıcılar' pending: 'Gözden geçirilmeyi bekleyen kullanıcılar' - newuser: 'Güven Seviyesi 0 (Yeni Kullanıcı) olan kullanıcılar' - basic: 'Güven Seviyesi 1 (Acemi Kullanıcı) olan kullanıcılar' - regular: 'Güven Seviyesi 2 (Üye) olan kullanıcılar' - leader: 'Güven Seviyesi 3 (Müdavim) olan kullanıcılar' - elder: 'Güven Seviyesi 4 (Lider) olan kullanıcılar' + newuser: 'Güven seviyesi 0 (Yeni kullanıcı) olan kullanıcılar' + basic: 'Güven seviyesi 1 (Acemi kullanıcı) olan kullanıcılar' + regular: 'Güven seviyesi 2 (Üye) olan kullanıcılar' + leader: 'Güven seviyesi 3 (Müdavim) olan kullanıcılar' + elder: 'Güven seviyesi 4 (Lider) olan kullanıcılar' staff: "Görevli" admins: 'Yöneticiler' moderators: 'Moderatörler' @@ -1876,11 +1891,11 @@ tr_TR: ip_lookup: "IP Arama" log_out: "Oturumu kapat" logged_out: "Kullanıcının tüm cihazlarda oturumu kapatılmış" - revoke_admin: 'Yöneticiliğini İptal Et' - grant_admin: 'Yönetici Yetkisi Ver' - revoke_moderation: 'Moderasyonu İptal Et' - grant_moderation: 'Moderasyon Yetkisi Ver' - unblock: 'Engeli Kaldır' + revoke_admin: 'Yöneticiliğini iptal et' + grant_admin: 'Yönetici yetkisi ver' + revoke_moderation: 'Moderasyonu iptal et' + grant_moderation: 'Moderasyon yetkisi ver' + unblock: 'Engeli kaldır' block: 'Engelle' reputation: İtibar permissions: İzinler @@ -1900,7 +1915,7 @@ tr_TR: approve_success: "Kullanıcı onaylandı ve etkinleştirme bilgilerini içeren bir e-posta yollandı." approve_bulk_success: "Tebrikler! Seçilen tüm kullanıcılar onaylandı ve bilgilendirildi." time_read: "Okuma süresi" - anonymize: "Kullanıcıyı Anonimleştir" + anonymize: "Kullanıcıyı anonimleştir" anonymize_confirm: "Bu hesabı anonimleştirmek istediğinize EMİN misiniz? Kullanıcı adı ve e-posta değiştirilecek, ve tüm profil bilgileri sıfırlanacak." anonymize_yes: "Evet, bu hesap anonimleştir" anonymize_failed: "Hesap anonimleştirilirken bir hata oluştu." @@ -1932,14 +1947,14 @@ tr_TR: block_explanation: "Engellenmiş bir kullanıcı gönderi oluşturamaz veya konu başlatamaz." trust_level_change_failed: "Kullanıcının güven seviyesi değiştirilirken bir sorun yaşandı." suspend_modal_title: "Kullanıcıyı uzaklaştır" - trust_level_2_users: "Güven Seviyesi 2 olan kullanıcılar" - trust_level_3_requirements: "Güven Seviyesi 3 gereksinimleri" + trust_level_2_users: "Güven seviyesi 2 olan kullanıcılar" + trust_level_3_requirements: "Güven seviyesi 3 gereksinimleri" trust_level_locked_tip: "güven seviyesi kitlendi, sistem kullanıcının seviyesini ne yükseltebilecek ne de düşürebilecek" trust_level_unlocked_tip: "güven seviyesi kilidi çözüldü, sistem kullanıcının seviyesini yükseltebilir ya da düşürebilir" lock_trust_level: "Güven seviyesini kilitle" unlock_trust_level: "Güvenlik seviyesi kilidini aç" tl3_requirements: - title: "Güven Seviyesi 3 için gerekenler" + title: "Güven seviyesi 3 için gerekenler" table_title: "Son 100 günde:" value_heading: "Değer" requirement_heading: "Gereksinim" @@ -1952,8 +1967,8 @@ tr_TR: posts_read_all_time: "Okunmuş gönderiler (Tüm zamanlarda)" flagged_posts: "Rapor edilen gönderiler" flagged_by_users: "Rapor eden kullanıcılar" - likes_given: "Verilen Beğeniler" - likes_received: "Alınan Beğeniler" + likes_given: "Verilen beğeniler" + likes_received: "Alınan beğeniler" likes_received_days: "Alınan beğeniler: tekil günlük" likes_received_users: "Alınan beğeniler: tekil kullanıcı" qualifies: "Güven seviyesi 3 için yeterli." @@ -2174,11 +2189,11 @@ tr_TR: community: name: Topluluk trust_level: - name: Güven Seviyesi + name: Güven seviyesi other: name: Diğer posting: - name: Gönderiliyor + name: Gönderiler badge: editor: name: Editör diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml index 55ab4e7a10..8437759696 100644 --- a/config/locales/server.es.yml +++ b/config/locales/server.es.yml @@ -1179,33 +1179,33 @@ es: new_version_mailer: subject_template: "[%{site_name}] Nueva versión de Discourse, actualización disponible" text_body_template: | - Una nueva versión de [Discourse](http://www.discourse.org) esta disponible. + Está disponible una nueva versión de [Discourse](http://www.discourse.org). Tu versión: %{installed_version} Nueva versión: **%{new_version}** Tal vez quieras: - - Ver que hay de nuevo en el [GitHub changelog](https://github.com/discourse/discourse/commits/master). + - Ver qué hay de nuevo en el [historial de cambios de GitHub](https://github.com/discourse/discourse/commits/master). - - Actualizar visitando [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade) y presionando el botón "Actualizar". + - Actualizar desde tu navegador en [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade). - - Visitar [meta.discourse.org](http://meta.discourse.org) para ver noticias, debatir, y apoyar Discourse. + - Visitar [meta.discourse.org](http://meta.discourse.org) para ver noticias, debatir u obtener soporte de Discourse. new_version_mailer_with_notes: subject_template: "[%{site_name}] actualización disponible" text_body_template: | - Una nueva versión de [Discourse](http://www.discourse.org) esta disponible. + Está disponible una nueva versión de [Discourse](http://www.discourse.org). Tu versión: %{installed_version} Nueva versión: **%{new_version}** Tal vez quieras: - - Ver que hay de nuevo en el [GitHub changelog](https://github.com/discourse/discourse/commits/master). + - Ver qué hay de nuevo en el [historial de cambios de GitHub](https://github.com/discourse/discourse/commits/master). - - Actualizar visitando [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade), y presionando el botón "Actualizar". + - Actualizar desde tu navegador en [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade). - - Visita [meta.discourse.org](http://meta.discourse.org) para ver noticias, debatir, y apoyar a Discourse. + - Visitar [meta.discourse.org](http://meta.discourse.org) para ver noticias, debatir u obtener soporte de Discourse. ### Notas de lanzamiento diff --git a/config/locales/server.it.yml b/config/locales/server.it.yml index c66ddf94fd..1a5b7f3e08 100644 --- a/config/locales/server.it.yml +++ b/config/locales/server.it.yml @@ -133,8 +133,11 @@ it: rss_description: latest: "Argomenti più recenti" hot: "Argomenti caldi" + posts: "Ultimi messaggi" too_late_to_edit: "Questo messaggio è stato creato troppo tempo fa. Non può più essere modificato né cancellato." excerpt_image: "immagine" + queue: + delete_reason: "Cancellato attraverso la coda di moderazione" groups: errors: can_not_modify_automatic: "Non puoi modificare un gruppo automatico" @@ -237,6 +240,7 @@ it: uncategorized_parent: "La categoria \"Non classificato\" non può avere una categoria superiore." self_parent: "La categoria-genitore di una sottocategoria non può essere se stessa." depth: "Non puoi annidare una sottocategoria sotto un'altra" + email_in_already_exist: "L'indirizzo email in ingresso '%{email_in}' è già stato usato per la categoria '%{category_name}'." cannot_delete: uncategorized: "Non puoi eliminare la categoria \"Non classificato\"" has_subcategories: "Non puoi cancellare questa categoria perché ha sotto-categorie." @@ -340,7 +344,7 @@ it: one: "quasi 1 anno fa" other: "quasi %{count} anni fa" password_reset: - no_token: "Siamo spiacenti ma il collegamento per il cambio password è troppo vecchio. Collegati di nuovo e seleziona 'Ho dimenticato la password' per avere un nuovo collegamento." + no_token: "Siamo spiacenti ma il collegamento per il cambio password è troppo vecchio. Collegati di nuovo e seleziona 'Ho dimenticato la password' per ottenere un nuovo collegamento." choose_new: "Per favore scegli una nuova password." choose: "Per favore scegli una password" update: 'Aggiorna Password' @@ -360,7 +364,7 @@ it: continue_button: "Procedi su %{site_name}." welcome_to: "Benvenuto su %{site_name}!" approval_required: "Un moderatore deve approvare manualmente il tuo account prima che tu possa accedere al forum. Riceverai un'email quando il tuo account sarà approvato!" - missing_session: "Non possiamo verificare che il tuo account sia stato creato, per favore assicurati di avere i cookie abilitati nelle impostazioni del tuo browser." + missing_session: "Non possiamo verificare se il tuo account è stato creato, per favore assicurati di avere abilitato i cookie." post_action_types: off_topic: title: 'Fuori Tema' diff --git a/config/locales/server.ja.yml b/config/locales/server.ja.yml index f840212957..6db0d4feb2 100644 --- a/config/locales/server.ja.yml +++ b/config/locales/server.ja.yml @@ -17,7 +17,7 @@ ja: powered_by_html: 'Powered by Discourse, best viewed with JavaScript enabled' log_in: "ログイン" via: "%{username} via %{site_name}" - is_reserved: "is reserved" + is_reserved: "は予約されています" purge_reason: "アクティブでないアカウントは放棄されたとして削除されました" disable_remote_images_download_reason: "ディスク容量が不足しているため、リモートでの画像ダウンロードは無効になっています。" anonymous: "匿名" @@ -307,6 +307,7 @@ ja: almost_x_years: other: "だいたい %{count} 年前" password_reset: + no_token: "申し訳ありません。そのパスワード変更のリンクは有効期限を過ぎています。もう一度、ログインのところの「パスワードを忘れました」から新しいリンクを取得してください。" choose_new: "新しいパスワードを選択してください" choose: "パスワードを入力ください" update: 'パスワード更新' @@ -524,6 +525,7 @@ ja: gc_warning: '現在サーバはデフォルトの ruby ガベージコレクションパラメータを使用しており、パフォーマンスが最適化されていません。パフォーマンス・チューニングの方法についてはこちらのトピックを参考にしてください: Tuning Ruby and Rails for Discourse.' sidekiq_warning: 'Sidekiq が起動していません。メール送信等、多くのタスクが sidekiq により非同期に実行されます。少なくとも sidekiq のプロセスを1つは起動してください。Sidekiq についてはこちらを参考にしてください。' memory_warning: '現在サーバが 1GB 未満の総メモリで起動しています。推奨メモリサイズは 1GB 以上です。' + google_oauth2_config_warning: 'サーバが Google OAuth2 アカウントによるサインアップ・ログイン可能な設定 (enable_google_oauth2_logins) になっていますが、client id と secret が設定されていません。サイトの設定 にて設定を更新してください。詳しくはこちらを参考にしてください。' facebook_config_warning: 'サーバが Facebook アカウントによるサインアップ・ログイン可能な設定 (enable_facebook_logins) になっていますが、app id と app secret が設定されていません。サイトの設定 にて設定を更新してください。詳しくはこちらを参考にしてください。' twitter_config_warning: 'サーバが Twitter アカウントによるサインアップ・ログイン可能な設定 (enable_twitter_logins) になっていますが、key と secret が設定されていません。サイトの設定 にて設定を更新してください。詳しくはこちらを参考にしてください。' github_config_warning: 'サーバが Github アカウントによるサインアップ・ログイン可能な設定 (enable_github_logins) になっていますが、client id と secret が設定されていません。サイトの設定 にて設定を更新してください。詳しくはこちらを参考にしてください。' diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml index 30893d2a99..51b153d192 100644 --- a/config/locales/server.pt.yml +++ b/config/locales/server.pt.yml @@ -617,7 +617,7 @@ pt: title_nag: "Introduza o nome do seu sítio. Atualize o título nas Configurações do Sítio." site_description_missing: "Introduza uma descrição de uma frase do seu sítio que irá aparecer nos resultados da pesquisa. Atualize a descrição_sítio nas Configurações do Sítio." consumer_email_warning: "O seu sítio está configurado para usar Gmail (ou outro serviço de email)O Gmail limita a quantidade de emails que pode enviar. Considere usar um serviço de envio de emails como mandrill.com para assegurar a entregabilidade dos emails enviados." - site_contact_username_warning: "Introduza o nome de uma conta de utilizador de um membro do pessoal de onde possam ser enviadas mensagens importantes automatizadas. Atualize sítio_contato_nomedeutilizador nas Configurações do Sítio." + site_contact_username_warning: "Introduza o nome de uma conta de utilizador de um membro do pessoal de onde possam ser enviadas mensagens importantes automatizadas. Atualize sítio_contacto_nomedeutilizador nas Configurações do Sítio." notification_email_warning: "Não estão a ser enviados emails de notificação a partir de um endereço de email válido no seu domínio; a entrega do email será errática e não fiável. Por favor configure notificaçao_email para um endereço de email local válido nas Configurações do Sítio." content_types: education_new_reply: @@ -670,8 +670,8 @@ pt: educate_until_posts: "Quando um utilizador começar a escrever as primeiras (n) novas mensagens, mostrar o painel pop-up de educação do novo utilizador no editor." title: "O nome deste sítio, tal como usado na etiqueta do título." site_description: "Descrever este sítio em uma frase, tal como usado na etiqueta de descrição meta." - contact_email: "Endereço de email do principal contato para este sítio. Usado para notificações críticas tais como sinalizações não tratadas, assim como no formulário de contato /sobre para assuntos urgentes." - contact_url: "URL de Contato para este sítio. Utilizado no formulário de contato /sobre para assuntos urgentes." + contact_email: "Endereço de email do principal contacto para este sítio. Usado para notificações críticas tais como sinalizações não tratadas, assim como no formulário de contacto /sobre para assuntos urgentes." + contact_url: "URL de Contacto para este sítio. Utilizado no formulário de contacto /sobre para assuntos urgentes." queue_jobs: "PROGRAMADORES APENAS! AVISO! Por defeito, fila de trabalhos no sidekiq. Se não estiver ativa, o seu sítio irá ficar corrompido." crawl_images: "Recuperar imagens de URLs remotos para inserir o comprimento e largura corretos." download_remote_images_to_local: "Converter imagens remotas em imagens locais ao descarregá-las; isto previne imagens corrompidas." diff --git a/config/locales/server.sv.yml b/config/locales/server.sv.yml index 5f8b7e9f18..ab112f0e4f 100644 --- a/config/locales/server.sv.yml +++ b/config/locales/server.sv.yml @@ -11,7 +11,7 @@ sv: short_date: "D MMM, YYYY" long_date: "MMMM D, YYYY h:mma" title: "Discourse" - topics: "Trådar" + topics: "Ämnen" posts: "inlägg" loading: "Laddar" powered_by_html: 'Drivs av Discourse, visas bäst med JavaScript påslaget' @@ -395,6 +395,7 @@ sv: xaxis: "Dag" yaxis: "Besök" signups: + title: "Nya användare" xaxis: "Day" yaxis: "Number of new users" topics: @@ -502,6 +503,10 @@ sv: sidekiq_warning: 'Sidekiq is not running. Many tasks, like sending emails, are executed asynchronously by sidekiq. Please ensure at least one sidekiq process is running. Learn about Sidekiq here.' memory_warning: 'Your server is running with less than 1 GB of total memory. At least 1 GB of memory is recommended.' s3_config_warning: 'Servern är konfigurerad till att ladda upp filer till s3, men minst en av följande inställningar är inte satt: s3_access_key_id, s3_secret_access_key eller s3_upload_bucket. Gå till Inställningar och uppdatera dem. Se "How to set up image uploads to S3?" för att lära dig mer.' + default_logo_warning: "Ange de grafiska logotyperna för din webbplats. Uppdatera logo_url, logo_small och favicon_url i Inställningar." + contact_email_invalid: "Webbplatsens e-postadress för kontakt är ogiltig. Uppdatera den i Inställningar." + title_nag: "Ange namnet på webbplatsen. Uppdatera titel i Inställningar." + site_description_missing: "Ange en beskrivning om din webbplats i en mening, som dyker upp i sökresultat. Uppdatera site_description i Inställningar." consumer_email_warning: "Din sida är konfigurerad till att använda Gmail (eller någon annan konsumenttjänst) för att skicka email. Gmail limits how many emails you can send. Consider using an email service provider like mandrill.com to ensure email deliverability." content_types: education_new_reply: @@ -510,26 +515,35 @@ sv: education_new_topic: title: "New User Education: First Topics" description: "Pop up just-in-time guidance automatically displayed above the composer when new users begin typing their first two new topics." + usage_tips: + description: "Guidning och viktig information för nya användare." welcome_user: title: "Välkommen: Ny användare" + description: "Ett meddelande som skickas automatiskt till alla nya användare vid registrering." welcome_invite: title: "Välkommen: Inbjuden användare" + description: "Ett meddelande som skickas automatiskt till alla nya inbjudna användare när de accepterar inbjudan från en annan användare." login_required_welcome_message: title: "Inloggning krävs: Välkomstmeddelande" description: "Välkomstmeddelande som visas för utloggade användare när inställningen för 'inloggning krävs' är aktiv. " login_required: title: "Inloggning krävs: Huvudsida" + description: "Texten som visas för obehöriga användare när inloggning krävs på webbplatsen." head: title: "HTML-sidhuvud" description: "HTML som kommer infogas mellan taggarna och ." top: title: "Längst upp på sidorna" + description: "HTML som läggs till i toppen på varje sida (efter sidhuvudet, före navigeringen av ämnets titel)." bottom: title: "Botten av sidorna" + description: "HTML som läggs till före taggen ." site_settings: + censored_words: "Ord som automatiskt ersätts med ■■■■" default_locale: "Standardspråk för den här instansen av Discourse (ISO 639-1-kod)" allow_user_locale: "Tillåt användare att själva välja språk" min_post_length: "Minsta tillåtna inläggslängd i antal tecken" + allow_duplicate_topic_titles: "Tillåt ämnen med identiska rubriker." unique_posts_mins: "Hur många minuter innan en användare kan göra ett inlägg med precis samma innehåll igen" title: "Namnet på denna webbplats som används i titel-taggen." site_description: "Beskriv denna webbplats i en mening, som sedan används i meta-description-taggen." diff --git a/config/locales/server.tr_TR.yml b/config/locales/server.tr_TR.yml index dd65116c4f..9b2b94f1b7 100644 --- a/config/locales/server.tr_TR.yml +++ b/config/locales/server.tr_TR.yml @@ -426,7 +426,7 @@ tr_TR: description: "Tekrar abone oldunuz." reports: visits: - title: "Kullanıcı Ziyaretleri" + title: "Kullanıcı ziyaretleri" xaxis: "Gün" yaxis: "Ziyaret sayısı" signups: @@ -462,11 +462,11 @@ tr_TR: xaxis: "Güven Seviyesi" yaxis: "Kullanıcı Sayısı" emails: - title: "Gönderilen E-postalar" + title: "Gönderilen e-postalar" xaxis: "Gün" yaxis: "E-posta Sayısı" user_to_user_private_messages: - title: "Kullanıcıdan Kullanıcıya" + title: "Kullanıcıdan kullanıcıya" xaxis: "Gün" yaxis: "Mesaj sayısı" system_private_messages: @@ -474,48 +474,48 @@ tr_TR: xaxis: "Gün" yaxis: "Mesaj sayısı" moderator_warning_private_messages: - title: "Moderatör Uyarısı" + title: "Moderatör uyarıları" xaxis: "Gün" yaxis: "Mesaj sayısı" notify_moderators_private_messages: - title: "Moderatörleri Bilgilendir" + title: "Moderatör bilgilendirme" xaxis: "Gün" yaxis: "Mesaj sayısı" notify_user_private_messages: - title: "Kullanıcıyı Bilgilendir" + title: "Kullanıcı bilgilendirme" xaxis: "Gün" yaxis: "Mesaj sayısı" top_referrers: - title: "En Çok Atıfta Bulunanlar" + title: "En çok atıfta bulunanlar" xaxis: "Kullanıcı" num_clicks: "Tıklamalar" num_topics: "Konular" top_traffic_sources: - title: "En İyi Trafik Kaynakları" + title: "En iyi trafik kaynakları" xaxis: "Alan Adı" num_clicks: "Tıklamalar" num_topics: "Konular" num_users: "Kullanıcılar" top_referred_topics: - title: "En Çok Atıfta Bulunulan Konular" + title: "En çok atıfta bulunulan konular" xaxis: "Konu" num_clicks: "Tıklamalar" page_view_anon_reqs: title: "Anonim" xaxis: "Gün" - yaxis: "Anonim API İstekleri" + yaxis: "Anonim API istekleri" page_view_logged_in_reqs: - title: "Giriş Yapılmış" + title: "Oturum açıldı" xaxis: "Gün" - yaxis: "Giriş Yapılmış API İstekleri" + yaxis: "Oturum açanlardan API istekleri" page_view_crawler_reqs: - title: "Ağ Gezgini" + title: "Arama botları" xaxis: "Gün" - yaxis: "Ağ Gezgini API İstekleri" + yaxis: "Bot API istekleri" page_view_total_reqs: title: "Toplam" xaxis: "Gün" - yaxis: "Tüm API İstekleri" + yaxis: "Tüm API istekleri" http_background_reqs: title: "Arkaplan" xaxis: "Gün" @@ -631,6 +631,7 @@ tr_TR: show_subcategory_list: "Bir kategoriye girildiğinde konu listesi yerine alt kategori listesini göster." fixed_category_positions: "Seçerseniz, kategoriler için sabit bir sıralama belirleyebileceksiniz. Seçmezseniz, kategoriler aktivite sırasına göre listelenir. " add_rel_nofollow_to_user_content: "İç bağlantılar (ana alan adları dahil) hariç, gönderilen tüm kullanıcı içeriklerine rel nofollow ekle. Bu ayarı değiştirirseniz, tüm gönderileri \"rake post:rebake\" ile rebake etmeniz gerekir." + exclude_rel_nofollow_domains: "nofollow eklenmeyecek sınırlandırılmış alan adları listesi. tld.com'a izin verirseniz otomatik olarak sub.tld.com'a da izin verilir." post_excerpt_maxlength: "Gönderi alıntısının / özetinin en fazla uzunluğu." post_onebox_maxlength: "Kutulanmış bir Discourse gönderisinin en fazla karakter uzunluğu" onebox_domains_whitelist: "Kutulamaya izin verilen alan adları listesi; bu alan adları OpenGraph ya da oEmbed desteklemeliler. http://iframely.com/debug adresinden test edebilirsiniz." @@ -716,6 +717,7 @@ tr_TR: max_username_length: "Kullanıcı adında olabilecek en fazla karakter sayısı. UYARI: HALİHAZIRDA BUNDAN DAHA UZUN BİR KULLANICI ADINA SAHİP OLAN KULLANICILAR SİTEYE GİRİŞ YAPAMAYACAKLAR. " min_password_length: "En az parola uzunluğu." block_common_passwords: "En çok kullanılan 10,000 parola arasında yer alan parolalara izin verme." + enable_sso: "Dış bir site aracılığı ile tek oturum açma sistemini etkinleştir. (UYARI: etkinleştirildiği takdirde, doğru yapılandırılmamışsa bazı kişilerin, SİZ DAHİL, oturum açmasını engelleyebilir! Ayrıca davetiye sistemini de devre dışı bırakır.)" enable_sso_provider: "/session/sso_provider son noktasında Discourse SSO sağlayıcı protokolünü uygula, sso_secret değerinin seçilmiş olmasını gerektirir" sso_url: "Bitiş noktasındaki tek oturum açma URL'i" sso_secret: "SSO bilgisinin kritopgrafik şekilde doğrulanması için kullanılan gizli string, 10 karakter veya daha uzun olduğundan emin olun" @@ -825,6 +827,9 @@ tr_TR: topic_views_heat_low: "Bu kadar görüntülemeden sonra, görüntülemeler alanı hafifçe vurgulanacak." topic_views_heat_medium: "Bu kadar görüntülemeden sonra, görüntülemeler alanı kısmen vurgulanacak." topic_views_heat_high: "Bu kadar görüntülemeden sonra, görüntülemeler alanı belirgin şekilde vurgulanacak." + cold_age_days_low: "Sohbet başlangıcından bu kadar süre geçtiğinde, son aktivite tarihi hafifçe vurgulanır." + cold_age_days_medium: "Sohbet başlangıcından bu kadar süre geçtiğinde, son aktivite tarihi kısmen vurgulanır." + cold_age_days_high: "Sohbet başlangıcından bu kadar süre geçtiğinde, son aktivite tarihi belirgin şekilde vurgulanır." history_hours_low: "Bu kadar saat içerisinde düzenlenen bir gönderinin, düzenlendiğini belirten gösterge hafifçe vurgulanacak. " history_hours_medium: "Bu kadar saat içerisinde düzenlenen bir gönderinin, düzenlendiğini belirten gösterge kısmen vurgulanacak. " history_hours_high: "Bu kadar saat içerisinde düzenlenen bir gönderinin, düzenlendiğini belirten gösterge belirgin şekilde vurgulanacak. " @@ -904,6 +909,7 @@ tr_TR: warn_reviving_old_topic_age: "Herhangi bir kullanıcı, son cevabın burada belirtilen gün sayısından daha önce yazıldığı bir konuya cevap yazmaya başladığında, bir uyarı mesajı çıkacak. Bu özelliği devre dışı bırakmak için 0 girin. " autohighlight_all_code: "Tüm önceden formatlanan kod bloklarına, açıkça dil seçimi yapılmamış olsa da, zorla kod vurgulaması uygula." highlighted_languages: "Dahil edilen sözdizimi vurgulama kuralları. (Dikkat: çok fazla dili dahil etmek performansı etkileyebilir) Demo için https://highlightjs.org/static/demo/ adresine bakınız" + embeddable_hosts: "Bu Discourse forumundan yorumların yerleştirilebileceği sunucu adres(ler)idir. Sunucu, http:// ile başlamamalıdır." feed_polling_enabled: "SADECE YERLEŞTİRME İÇİN: RSS/ATOM beslemesinin gönderi olarak yerleştirilip yerleştirilemeyeceği." feed_polling_url: "SADECE YERLEŞTİRME İÇİN: Yerleştirilecek RSS/ATOM beslemesinin URL'i." embed_by_username: "Yerleştirilmiş konuları oluşturan kullanıcıya ait Discourse kullanıcı adı. " @@ -1074,8 +1080,12 @@ tr_TR: \ \n[2]: http://mxtoolbox.com/ReverseLookup.aspx \n[3]: http://www.dkim.org/ \n[4]: http://whatismyipaddress.com/blacklist-check \n[7]: http://dkimcore.org/tools/dkimrecordcheck.html \n[8]: http://www.openspf.org/SPF_Record_Syntax [md]: http://mandrill.com [mg]: http://www.mailgun.com/ [mj]: https://www.mailjet.com/pricing\n" new_version_mailer: subject_template: "[%{site_name}] Yeni Discourse versiyonu, güncelleme var" + text_body_template: | + [Discourse'un](http://www.discourse.org) yeni versiyonu hazır. Sizin kullandığınız versiyon: %{installed_version} Yeni versiyon: **%{new_version}** Aşağıdakileri uygulayabilirsiniz: - Yenilikleri [GitHub değişiklikler listesinde] görüntüleyebilirsiniz (https://github.com/discourse/discourse/commits/master). - [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade) sayfasını ziyaret ederek ve "Güncelle" butonuna basarak yeni versiyona geçebilirsiniz. - Haberler, tartışmalar ve Discourse ile ilgili destek için burayı ziyaret edebilirsiniz: [meta.discourse.org](http://meta.discourse.org) new_version_mailer_with_notes: subject_template: "[%{site_name}] güncellemesi var" + text_body_template: | + [Discourse'un](http://www.discourse.org) yeni bir versiyonu hazır. Sizin kullandığınız versiyon: %{installed_version} Yeni versiyon: **%{new_version}** Aşağıdakileri uygulayabilirsiniz: - Yenilikleri [GitHub değişiklikler listesinde] görüntüleyebilirsiniz (https://github.com/discourse/discourse/commits/master). - [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade) sayfasını ziyaret ederek ve "Güncelle" butonuna basarak yeni versiyona geçebilirsiniz. - Haberler, tartışmalar ve Discourse ile ilgili destek için burayı ziyaret edebilirsiniz: [meta.discourse.org](http://meta.discourse.org) ### Yeni sürüm notları %{notes} flags_reminder: flags_were_submitted: other: "Bu bayraklar %{count} saat önce verildi." @@ -1186,8 +1196,12 @@ tr_TR: Bu e-posta adresine sahip bir kullanıcı bulunamadı. Başka bir e-posta adresinden göndermeyi deneyin ya da bir görevli ile iletişime geçin. email_reject_empty: subject_template: "[%{site_name}] E-posta sorunu -- İçerik Yok" + text_body_template: | + Üzgünüz, ama %{destination} (titled %{former_title}) adresine göndermeye çalıştığınız e-posta başarısız oldu. E-postada herhangi bir içerik bulamadık. Cevabınızın e-postanın en üstünde yer aldığından emin olun -- aralardaki cevapları işleme alamıyoruz. E-postanızda içerik olduğu halde bu mesajı aldıysanız, bir sefer de HTML içeriği ile göndermeyi deneyin (sadece düz metin olarak değil). email_reject_parsing: subject_template: "[%{site_name}] E-posta sorunu -- Tanımlanamayan içerik" + text_body_template: | + Üzgünüz, ama %{destination} (titled %{former_title}) adresine göndermeye çalıştığınız e-posta başırısız oldu. E-postada herhangi bir cevap bulamadık. **Cevabınızın e-posta'nın en üstünde yer aldığından emin olun** -- aralardaki cevapları işleyemiyoruz. email_reject_invalid_access: subject_template: "[%{site_name}] E-posta sorunu -- Geçersiz giriş" text_body_template: | @@ -1224,10 +1238,16 @@ tr_TR: Gönderilen adreslerinin hiçbiri bilinmiyor. Site adresinin To: kısmında olduğundan (Cc: ya da Bcc: değil) ve görevliler tarafından sağlanan doğru email adresine yolladığınızdan emin olun. email_reject_topic_not_found: subject_template: "[%{site_name}] E-posta sorunu -- Konu Bulunamadı" + text_body_template: | + Üzgünüz, ama %{destination} (titled %{former_title}) adresine göndermek istediğiniz e-posta başarısız oldu. Konu bulunamadı, silinmiş olabilir. Yanlışlık olduğunu düşünüyorsanız, bir görevli ile iletişime geçin. email_reject_topic_closed: subject_template: "[%{site_name}] E-posta sorunu -- Konu Kapatıldı" + text_body_template: | + Üzgünüz, ama %{destination} (titled %{former_title}) adresine göndermek istediğiniz e-posta başarısız oldu. Konu kapatılmış. Yanlışlık olduğunu düşünüyorsanız, bir görevli ile iletişime geçin. email_reject_auto_generated: subject_template: "[%{site_name}] E-posta sorunu -- Otomatik Üretilmiş Cevap" + text_body_template: | + Üzgünüz, ama %{destination} (titled %{former_title}) adresine yollamak istediğiniz e-posta başarısız oldu. Otomatik olarak üretilmiş e-posta cevaplarını kabul edemiyoruz. Bir hata olduğuna inanıyorsanız, lütfen bir görevli ile iletişime geçin. email_error_notification: subject_template: "[%{site_name}] E-posta sorunu -- POP doğrulama hatası" text_body_template: | diff --git a/plugins/poll/config/locales/client.ar.yml b/plugins/poll/config/locales/client.ar.yml index 1d803323f4..37fab858e2 100644 --- a/plugins/poll/config/locales/client.ar.yml +++ b/plugins/poll/config/locales/client.ar.yml @@ -17,3 +17,11 @@ ar: one: "مجموع التصويت" other: "مجموع الأصوات" average_rating: "متوسط التصنيف: %{average} " + cast-votes: + label: "صوت اﻵن!" + show-results: + title: "عرض نتائج التصويت" + label: "عرض النتائج" + hide-results: + title: "العودة إلى تصويتاتك" + label: "إخفاء النتائج" diff --git a/plugins/poll/config/locales/client.it.yml b/plugins/poll/config/locales/client.it.yml index 9736f44e0b..c2b15d5bc8 100644 --- a/plugins/poll/config/locales/client.it.yml +++ b/plugins/poll/config/locales/client.it.yml @@ -14,7 +14,7 @@ it: other: "votanti" total_votes: zero: "voti totali" - one: "voto totale" + one: "voto in totale" other: "voti totali" average_rating: "Voto medio: %{average}." multiple: diff --git a/plugins/poll/config/locales/client.tr_TR.yml b/plugins/poll/config/locales/client.tr_TR.yml index ca05fa8f05..dab693d3e3 100644 --- a/plugins/poll/config/locales/client.tr_TR.yml +++ b/plugins/poll/config/locales/client.tr_TR.yml @@ -9,29 +9,29 @@ tr_TR: js: poll: voters: - zero: "oy verenler" - one: "oy veren" - other: "oy verenler" + zero: "oylayan" + one: "oylayan" + other: "oylayanlar" total_votes: - zero: "tüm oylar" + zero: "toplam oylama" one: "toplam oy" other: "tüm oylar" average_rating: "Ortalama oran: %{average}." multiple: help: - at_least_min_options: "En fazla %{count} seçim yapabilirsiniz." + at_least_min_options: "En az %{count} seçim yapmalısınız." up_to_max_options: "En fazla %{count} seçim yapabilirsiniz." - x_options: "En fazla %{count} seçim yapabilirsiniz." + x_options: "%{count} seçim yapmalısınız." between_min_and_max_options: "%{min} ve %{max} seçenekleri arasında seçim yapabilirsiniz." cast-votes: - title: "Oyunuzu verin" + title: "Oyunuzu kullanın" label: "Şimdi oylayın!" show-results: title: "Anket sonuçlarını göster" label: "Sonuçları göster" hide-results: title: "Oylarınıza dönün" - label: "Sonuçları sakla" + label: "Sonuçları gizle" open: title: "Anketi başlat" label: "Başlat" diff --git a/plugins/poll/config/locales/server.ar.yml b/plugins/poll/config/locales/server.ar.yml index 79ea4de815..4bb9a6b925 100644 --- a/plugins/poll/config/locales/server.ar.yml +++ b/plugins/poll/config/locales/server.ar.yml @@ -5,4 +5,6 @@ # To work with us on translations, join this project: # https://www.transifex.com/projects/p/discourse-org/ -ar: {} +ar: + poll: + cannot_change_polls_after_5_minutes: "لا يمكنك إضافة، حذف أو إعادة تسمية التصويتات بعد الـ5 دقائق اﻷولى." diff --git a/plugins/poll/config/locales/server.it.yml b/plugins/poll/config/locales/server.it.yml index 445eb7210e..2ece567bf8 100644 --- a/plugins/poll/config/locales/server.it.yml +++ b/plugins/poll/config/locales/server.it.yml @@ -19,14 +19,14 @@ it: default_poll_must_have_different_options: "Il sondaggio deve avere opzioni diverse." named_poll_must_have_different_options: "Il sondaggio dal nome %{name} deve avere opzioni diverse." default_poll_with_multiple_choices_has_invalid_parameters: "Il sondaggio a scelta multipla ha parametri non validi." - named_poll_with_multiple_choices_has_invalid_parameters: "Il sondaggio dal nome %{name} ha parametri non validi." + named_poll_with_multiple_choices_has_invalid_parameters: "Il sondaggio a scelta multipla dal nome %{name} ha parametri non validi." requires_at_least_1_valid_option: "Devi scegliere almeno una opzione valida." cannot_change_polls_after_5_minutes: "Non puoi aggiungere, cancellare o rinominare i sondaggi dopo i primi 5 minuti." - op_cannot_edit_options_after_5_minutes: "Non puoi aggiungere o rimuovere opzioni ad un sondaggio dopo i primi 5 minuti. Per favore contatta un moderatore se hai bisogno di modificare un'opzione del sondaggio." - staff_cannot_add_or_remove_options_after_5_minutes: "Non puoi aggiungere o rimuovere opzioni ad un sondaggio dopo i primi 5 minuti.Dovresti chiudere questo Argomento e crearne un'altro." + op_cannot_edit_options_after_5_minutes: "Non puoi aggiungere o rimuovere opzioni ad un sondaggio dopo i primi 5 minuti. Per favore contatta un moderatore se devi modificare un'opzione del sondaggio." + staff_cannot_add_or_remove_options_after_5_minutes: "Non puoi aggiungere o rimuovere opzioni ad un sondaggio dopo i primi 5 minuti. Dovresti chiudere questo argomento e crearne un altro." no_polls_associated_with_this_post: "Non ci sono sondaggi associati con questo messaggio." no_poll_with_this_name: "Nessun sondaggio avente nome %{name} è associato a questo messaggio." - post_is_deleted: "Non è possibile operare su un post cancellato." + post_is_deleted: "Non è possibile operare su un messaggio cancellato." topic_must_be_open_to_vote: "L'argomento deve essere aperto per poter votare." poll_must_be_open_to_vote: "Il sondaggio deve essere aperto per poter votare." topic_must_be_open_to_toggle_status: "L'argomento deve essere aperto per commutare lo stato." From 56ec41cd835b296c50660f9d63707311303cf8a4 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Fri, 19 Jun 2015 11:37:12 -0400 Subject: [PATCH 0149/1435] Version bump to v1.4.0.beta3 --- lib/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/version.rb b/lib/version.rb index 70607d934d..07e3e7d582 100644 --- a/lib/version.rb +++ b/lib/version.rb @@ -5,7 +5,7 @@ module Discourse MAJOR = 1 MINOR = 4 TINY = 0 - PRE = 'beta2' + PRE = 'beta3' STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end From 72db91716908b6c37330bac56ea9aa5bc333a245 Mon Sep 17 00:00:00 2001 From: Kane York Date: Fri, 19 Jun 2015 11:30:46 -0700 Subject: [PATCH 0150/1435] Fix copy error in temporarily_closed_due_to_flags --- config/locales/server.en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 9216c07e48..89309529d8 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1448,7 +1448,7 @@ en: deferred: "Thanks for letting us know. We're looking into it." deferred_and_deleted: "Thanks for letting us know. We've removed the post." - temporarily_closed_due_to_flags: "This topic is temporarily closed due to a large number of community flags" + temporarily_closed_due_to_flags: "This topic is temporarily closed due to a large number of community flags." system_messages: post_hidden: From 3e2653d198a144704aee6ab991d0b6c2ef779551 Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Fri, 19 Jun 2015 22:42:29 +0200 Subject: [PATCH 0151/1435] FEATURE: Close search window when Ctrl+F is pressed. FIX: All search related keyboard shortcuts enabled the "search context", but only Ctrl+F inside of topics should enable it. --- .../discourse/lib/keyboard_shortcuts.js | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js b/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js index ae9fca7e21..dc1a40987c 100644 --- a/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js +++ b/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js @@ -59,6 +59,7 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({ bindEvents: function(keyTrapper, container) { this.keyTrapper = keyTrapper; this.container = container; + this._stopCallback(); _.each(PATH_BINDINGS, this._bindToPath, this); _.each(CLICK_BINDINGS, this._bindToClick, this); @@ -128,6 +129,11 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({ }, showBuiltinSearch: function() { + if ($('#search-dropdown').is(':visible')) { + this._toggleSearch(false); + return true; + } + var currentPath = this.container.lookup('controller:application').get('currentPath'), blacklist = [ /^discovery\.categories/ ], whitelist = [ /^topic\./ ], @@ -137,10 +143,14 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({ // If we're viewing a topic, only intercept search if there are cloaked posts if (showSearch && currentPath.match(/^topic\./)) { showSearch = $('.cooked').length < this.container.lookup('controller:topic').get('postStream.stream.length'); - } - return showSearch ? this.showSearch(true) : true; + if (showSearch) { + this._toggleSearch(true); + return false; + } + + return true; }, createTopic: function() { @@ -155,11 +165,8 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({ Discourse.__container__.lookup('controller:topic-progress').send('toggleExpansion', {highlight: true}); }, - showSearch: function(selectContext) { - $('#search-button').click(); - if(selectContext) { - Discourse.__container__.lookup('controller:search').set('searchContextEnabled', true); - } + showSearch: function() { + this._toggleSearch(false); return false; }, @@ -340,5 +347,24 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({ if(index >= 0 && index < $sections.length){ $sections.eq(index).find('a').click(); } - } + }, + + _stopCallback: function() { + var oldStopCallback = this.keyTrapper.stopCallback; + + this.keyTrapper.stopCallback = function(e, element, combo) { + if ((combo === 'ctrl+f' || combo === 'command+f') && element.id === 'search-term') { + return false; + } + + return oldStopCallback(e, element, combo); + }; + }, + + _toggleSearch: function(selectContext) { + $('#search-button').click(); + if (selectContext) { + Discourse.__container__.lookup('controller:search').set('searchContextEnabled', true); + } + }, }); From aa8b06aed2cf362379aa09213c8c3615135a39ec Mon Sep 17 00:00:00 2001 From: Simon Cossar Date: Fri, 19 Jun 2015 14:43:34 -0700 Subject: [PATCH 0152/1435] Clean up code --- app/assets/javascripts/discourse/views/user-card.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/views/user-card.js.es6 b/app/assets/javascripts/discourse/views/user-card.js.es6 index f95fa1475f..6ed306c453 100644 --- a/app/assets/javascripts/discourse/views/user-card.js.es6 +++ b/app/assets/javascripts/discourse/views/user-card.js.es6 @@ -84,6 +84,7 @@ export default Discourse.View.extend(CleansUp, { }, _willShow(target) { + const rtl = ($('html').css('direction')) === 'rtl'; if (!target) { return; } const width = this.$().width(); @@ -92,8 +93,7 @@ export default Discourse.View.extend(CleansUp, { let position = target.offset(); if (position) { - // Check for a right to left layout - if (($('html').css('direction')) === 'rtl') { + if (rtl) { // The site direction is rtl position.right = $(window).width() - position.left + 10; position.left = 'auto'; const overage = ($(window).width() - 50) - (position.right + width); From 4896a7dec79203cd44265bb2372c2b4a223c3d26 Mon Sep 17 00:00:00 2001 From: Simon Cossar Date: Fri, 19 Jun 2015 15:31:03 -0700 Subject: [PATCH 0153/1435] Change const to a variable --- app/assets/javascripts/discourse/views/user-card.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/views/user-card.js.es6 b/app/assets/javascripts/discourse/views/user-card.js.es6 index 6ed306c453..08b42013a2 100644 --- a/app/assets/javascripts/discourse/views/user-card.js.es6 +++ b/app/assets/javascripts/discourse/views/user-card.js.es6 @@ -96,7 +96,7 @@ export default Discourse.View.extend(CleansUp, { if (rtl) { // The site direction is rtl position.right = $(window).width() - position.left + 10; position.left = 'auto'; - const overage = ($(window).width() - 50) - (position.right + width); + let overage = ($(window).width() - 50) - (position.right + width); if (overage < 0) { position.right += overage; position.top += target.height() + 48; @@ -104,7 +104,7 @@ export default Discourse.View.extend(CleansUp, { } else { // The site direction is ltr position.left += target.width() + 10; - const overage = ($(window).width() - 50) - (position.left + width); + let overage = ($(window).width() - 50) - (position.left + width); if (overage < 0) { position.left += overage; position.top += target.height() + 48; From ca42d008832497199d45e8e5961098cf0d203f2c Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 19 Jun 2015 23:08:49 -0700 Subject: [PATCH 0154/1435] match time gap styling for mobile/desktop --- app/assets/stylesheets/mobile/topic-post.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/mobile/topic-post.scss b/app/assets/stylesheets/mobile/topic-post.scss index d29c185a95..7ff4b69d6f 100644 --- a/app/assets/stylesheets/mobile/topic-post.scss +++ b/app/assets/stylesheets/mobile/topic-post.scss @@ -7,11 +7,12 @@ } .time-gap { - border-bottom: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); color: lighten($primary, 50%); padding-bottom: 3px; margin-bottom: 10px; + text-transform: uppercase; + font-weight: bold; .topic-avatar { margin: 0 5px 0 10px; } From 131cf643ced76674eb5b7afe7f36b67f8ae19026 Mon Sep 17 00:00:00 2001 From: Konstantin Ilchenko Date: Sun, 21 Jun 2015 14:52:52 +0300 Subject: [PATCH 0155/1435] FIX: Allow api to send uploads with :url --- app/controllers/uploads_controller.rb | 4 ++-- spec/controllers/uploads_controller_spec.rb | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index 29e1150b0b..923f624078 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -4,7 +4,7 @@ class UploadsController < ApplicationController def create type = params.require(:type) - file = params[:file] || params[:files].first + file = params[:file] || params[:files].try(:first) url = params[:url] client_id = params[:client_id] synchronous = is_api? && params[:synchronous] @@ -52,7 +52,7 @@ class UploadsController < ApplicationController # API can provide a URL if file.nil? && url.present? && is_api? tempfile = FileHelper.download(url, SiteSetting.max_image_size_kb.kilobytes, "discourse-upload-#{type}") rescue nil - filename = File.basename(URI.parse(file).path) + filename = File.basename(URI.parse(url).path) else tempfile = file.tempfile filename = file.original_filename diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index a29fafd815..a738489d9d 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -53,6 +53,22 @@ describe UploadsController do expect(message.data).to be end + it 'is successful with synchronous api' do + SiteSetting.stubs(:authorized_extensions).returns("*") + controller.stubs(:is_api?).returns(true) + + Jobs.expects(:enqueue).with(:create_thumbnails, anything) + + FakeWeb.register_uri(:get, "http://example.com/image.png", :body => File.read('spec/fixtures/images/logo.png')) + + xhr :post, :create, url: 'http://example.com/image.png', type: "avatar", synchronous: true + + json = ::JSON.parse(response.body) + + expect(response.status).to eq 200 + expect(json["id"]).to be + end + it 'correctly sets retain_hours for admins' do Jobs.expects(:enqueue).with(:create_thumbnails, anything) From c4224b896693087b5bf9a505971fd4e386ecba59 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 22 Jun 2015 09:09:20 +1000 Subject: [PATCH 0156/1435] add mobile breakdown to script --- script/nginx_analyze.rb | 68 ++++++++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/script/nginx_analyze.rb b/script/nginx_analyze.rb index 177d45c480..33c1681d09 100644 --- a/script/nginx_analyze.rb +++ b/script/nginx_analyze.rb @@ -23,9 +23,22 @@ class LogAnalyzer result.rails_duration = result.rails_duration.to_f result.total_duration = result.total_duration.to_f + verb = result.url[0..3] if result.url + if verb && verb == "POST" + result.route += " (POST)" + end + + if verb && verb == "PUT" + result.route += " (PUT)" + end + result end + def is_mobile? + user_agent =~ /Mobile|webOS|Nexus 7/ && !(user_agent =~ /iPad/) + end + def parsed_time DateTime.strptime(time, TIME_FORMAT) end @@ -42,30 +55,41 @@ class LogAnalyzer class Aggeregator + attr_accessor :aggregate_type + def initialize @data = {} + @aggregate_type = :duration end def add(id, duration, aggregate=nil) ary = (@data[id] ||= [0,0]) ary[0] += duration ary[1] += 1 - if aggregate + unless aggregate.nil? ary[2] ||= Hash.new(0) - ary[2][aggregate] += duration + if @aggregate_type == :duration + ary[2][aggregate] += duration + elsif @aggregate_type == :count + ary[2][aggregate] += 1 + end end end - def top(n) + def top(n, aggregator_formatter=nil) @data.sort{|a,b| b[1][0] <=> a[1][0]}.first(n).map do |metric, ary| metric = metric.to_s metric = "[empty]" if metric.length == 0 result = [metric, ary[0], ary[1]] # handle aggregate if ary[2] - result.push ary[2].sort{|a,b| b[1] <=> a[1]}.first(5).map{|k,v| + if aggregator_formatter + result.push aggregator_formatter.call(ary[2], ary[0], ary[1]) + else + result.push ary[2].sort{|a,b| b[1] <=> a[1]}.first(5).map{|k,v| v = "%.2f" % v if Float === v "#{k}(#{v})"}.join(" ") + end end result @@ -77,7 +101,10 @@ class LogAnalyzer @filename = filename @ip_to_rails_duration = Aggeregator.new @username_to_rails_duration = Aggeregator.new + @route_to_rails_duration = Aggeregator.new + @route_to_rails_duration.aggregate_type = :count + @url_to_rails_duration = Aggeregator.new @status_404_to_count = Aggeregator.new end @@ -102,7 +129,7 @@ class LogAnalyzer username = parsed.username == "-" ? "[Anonymous]" : parsed.username @username_to_rails_duration.add(username, parsed.rails_duration, parsed.route) - @route_to_rails_duration.add(parsed.route, parsed.rails_duration) + @route_to_rails_duration.add(parsed.route, parsed.rails_duration, parsed.is_mobile? ? "mobile" : "desktop") @url_to_rails_duration.add(parsed.url, parsed.rails_duration) @@ -128,16 +155,23 @@ def map_with_index(ary, &block) end end -def top(cols, aggregator, count) - sorted = aggregator.top(30) +def top(cols, aggregator, count, aggregator_formatter = nil) + sorted = aggregator.top(30, aggregator_formatter) col_just = [] col_widths = map_with_index(cols) do |name,idx| max_width = name.length - col_just[idx] = :ljust + + if cols[idx].respond_to? :align + col_just[idx] = cols[idx].align + skip_just_detection = true + else + col_just[idx] = :ljust + end + sorted.each do |row| - col_just[idx] = :rjust unless String === row[idx] || row[idx].nil? + col_just[idx] = :rjust unless (String === row[idx] || row[idx].nil?) && !skip_just_detection row[idx] = '%.2f' % row[idx] if Float === row[idx] row[idx] = row[idx].to_s max_width = row[idx].length if row[idx].length > max_width @@ -186,6 +220,15 @@ def top(cols, aggregator, count) end end +class Column < String + attr_accessor :align + + def initialize(val, align) + super(val) + @align = align + end +end + puts puts "Analyzed: #{analyzer.filename}" puts SPACER @@ -203,9 +246,12 @@ puts top(["Username", "Duration", "Reqs", "Routes"], analyzer.username_to_rails_duration, 30) puts SPACER puts -puts "Top 30 routes by Server Load" +puts "Top 100 routes by Server Load" puts -top(["Route", "Duration", "Reqs"], analyzer.route_to_rails_duration, 30) +top(["Route", "Duration", "Reqs", Column.new("Mobile", :rjust)], analyzer.route_to_rails_duration, 100, lambda{ + |hash, name, total| + "#{hash["mobile"] || 0} (#{"%.2f" % (((hash["mobile"] || 0) / (total + 0.0)) * 100)})%"} +) puts SPACER puts puts "Top 30 urls by Server Load" From 283459e496f87238d435f4095d9502d93969280f Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 22 Jun 2015 09:17:06 +1000 Subject: [PATCH 0157/1435] add more routes --- script/nginx_analyze.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/nginx_analyze.rb b/script/nginx_analyze.rb index 33c1681d09..c39e50eaa4 100644 --- a/script/nginx_analyze.rb +++ b/script/nginx_analyze.rb @@ -156,7 +156,7 @@ def map_with_index(ary, &block) end def top(cols, aggregator, count, aggregator_formatter = nil) - sorted = aggregator.top(30, aggregator_formatter) + sorted = aggregator.top(count, aggregator_formatter) col_just = [] From 701c23c8b72ad61d855c0484d47aee058fcea0a7 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 22 Jun 2015 10:54:50 +1000 Subject: [PATCH 0158/1435] REFACTOR: create component for navigation pills --- .../discourse/components/navigation-bar.js.es6 | 5 +++++ .../discourse/templates/components/navigation-bar.hbs | 6 ++++++ .../discourse/templates/navigation/categories.hbs | 7 +------ .../discourse/templates/navigation/category.hbs | 8 ++------ .../discourse/templates/navigation/default.hbs | 7 +------ 5 files changed, 15 insertions(+), 18 deletions(-) create mode 100644 app/assets/javascripts/discourse/components/navigation-bar.js.es6 create mode 100644 app/assets/javascripts/discourse/templates/components/navigation-bar.hbs diff --git a/app/assets/javascripts/discourse/components/navigation-bar.js.es6 b/app/assets/javascripts/discourse/components/navigation-bar.js.es6 new file mode 100644 index 0000000000..31c23f964a --- /dev/null +++ b/app/assets/javascripts/discourse/components/navigation-bar.js.es6 @@ -0,0 +1,5 @@ +export default Ember.Component.extend({ + tagName: 'ul', + classNameBindings: [':nav', ':nav-pills'], + id: 'navigation-bar' +}); diff --git a/app/assets/javascripts/discourse/templates/components/navigation-bar.hbs b/app/assets/javascripts/discourse/templates/components/navigation-bar.hbs new file mode 100644 index 0000000000..11a4f1f58e --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/navigation-bar.hbs @@ -0,0 +1,6 @@ + diff --git a/app/assets/javascripts/discourse/templates/navigation/categories.hbs b/app/assets/javascripts/discourse/templates/navigation/categories.hbs index 1db91ae46e..6ba5509e9b 100644 --- a/app/assets/javascripts/discourse/templates/navigation/categories.hbs +++ b/app/assets/javascripts/discourse/templates/navigation/categories.hbs @@ -1,11 +1,6 @@ {{bread-crumbs categories=categories}} - +{{navigation-bar navItems=navItems filterMode=filterMode}} {{#if canCreateCategory}} diff --git a/app/assets/javascripts/discourse/templates/navigation/category.hbs b/app/assets/javascripts/discourse/templates/navigation/category.hbs index 4a8aee557e..7d874edf17 100644 --- a/app/assets/javascripts/discourse/templates/navigation/category.hbs +++ b/app/assets/javascripts/discourse/templates/navigation/category.hbs @@ -3,12 +3,8 @@ noSubcategories=noSubcategories hideSubcategories=showingSubcategoryList}} - + +{{navigation-bar navItems=navItems filterMode=filterMode}} {{#if currentUser}} {{category-notifications-button category=category}} diff --git a/app/assets/javascripts/discourse/templates/navigation/default.hbs b/app/assets/javascripts/discourse/templates/navigation/default.hbs index d95de6b043..529dae5587 100644 --- a/app/assets/javascripts/discourse/templates/navigation/default.hbs +++ b/app/assets/javascripts/discourse/templates/navigation/default.hbs @@ -6,12 +6,7 @@ {{else}} {{bread-crumbs categories=categories}} - +{{navigation-bar navItems=navItems filterMode=filterMode}} {{#if canCreateTopic}} From fe6203d4ec58384c0778f67b2ac0bf054b7d2ed9 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 22 Jun 2015 14:25:17 +1000 Subject: [PATCH 0159/1435] UX: improve front page styling for mobile --- .../discourse/components/bread-crumbs.js.es6 | 13 ++++- .../components/navigation-bar.js.es6 | 53 ++++++++++++++++++- .../components/navigation-item.js.es6 | 14 +---- .../javascripts/discourse/models/nav_item.js | 12 +++++ .../templates/components/navigation-bar.hbs | 10 ++-- .../mobile/components/navigation-bar.hbs | 13 +++++ app/assets/stylesheets/mobile/topic-list.scss | 34 ++++++++++++ 7 files changed, 127 insertions(+), 22 deletions(-) create mode 100644 app/assets/javascripts/discourse/templates/mobile/components/navigation-bar.hbs diff --git a/app/assets/javascripts/discourse/components/bread-crumbs.js.es6 b/app/assets/javascripts/discourse/components/bread-crumbs.js.es6 index 236091072b..66fcd65a37 100644 --- a/app/assets/javascripts/discourse/components/bread-crumbs.js.es6 +++ b/app/assets/javascripts/discourse/components/bread-crumbs.js.es6 @@ -1,6 +1,6 @@ // A breadcrumb including category drop downs export default Ember.Component.extend({ - classNames: ['category-breadcrumb'], + classNameBindings: ['hidden:hidden',':category-breadcrumb'], tagName: 'ol', parentCategory: Em.computed.alias('category.parentCategory'), @@ -12,6 +12,10 @@ export default Ember.Component.extend({ return !c.get('parentCategory'); }), + hidden: function(){ + return Discourse.Mobile.mobileView && !this.get('category'); + }.property('category'), + firstCategory: function() { return this.get('parentCategory') || this.get('category'); }.property('parentCategory', 'category'), @@ -29,6 +33,11 @@ export default Ember.Component.extend({ return this.get('categories').filter(function (c) { return c.get('parentCategory') === firstCategory; }); - }.property('firstCategory', 'hideSubcategories') + }.property('firstCategory', 'hideSubcategories'), + + render: function(buffer) { + if (this.get('hidden')) { return; } + this._super(buffer); + } }); diff --git a/app/assets/javascripts/discourse/components/navigation-bar.js.es6 b/app/assets/javascripts/discourse/components/navigation-bar.js.es6 index 31c23f964a..3cf9a741f1 100644 --- a/app/assets/javascripts/discourse/components/navigation-bar.js.es6 +++ b/app/assets/javascripts/discourse/components/navigation-bar.js.es6 @@ -1,5 +1,56 @@ export default Ember.Component.extend({ tagName: 'ul', classNameBindings: [':nav', ':nav-pills'], - id: 'navigation-bar' + id: 'navigation-bar', + selectedNavItem: function(){ + const filterMode = this.get('filterMode'), + navItems = this.get('navItems'); + + var item = navItems.find(function(item){ + return item.get('filterMode').indexOf(filterMode) === 0; + }); + + return item || navItems[0]; + }.property('filterMode'), + + closedNav: function(){ + if (!this.get('expanded')) { + this.ensureDropClosed(); + } + }.observes('expanded'), + + ensureDropClosed: function(){ + if (!this.get('expanded')) { + this.set('expanded',false); + } + $(window).off('click.navigation-bar'); + Discourse.URL.appEvents.off('dom:clean', this, this.ensureDropClosed); + }, + + actions: { + toggleDrop: function(){ + this.set('expanded', !this.get('expanded')); + var self = this; + if (this.get('expanded')) { + + Discourse.URL.appEvents.on('dom:clean', this, this.ensureDropClosed); + + Em.run.next(function() { + + if (!self.get('expanded')) { return; } + + self.$('.drop a').on('click', function(){ + self.$('.drop').hide(); + self.set('expanded', false); + return true; + }); + + $(window).on('click.navigation-bar', function() { + self.set('expanded', false); + return true; + }); + }); + } + } + } }); diff --git a/app/assets/javascripts/discourse/components/navigation-item.js.es6 b/app/assets/javascripts/discourse/components/navigation-item.js.es6 index 56927d55bb..075919ca74 100644 --- a/app/assets/javascripts/discourse/components/navigation-item.js.es6 +++ b/app/assets/javascripts/discourse/components/navigation-item.js.es6 @@ -24,25 +24,13 @@ export default Ember.Component.extend(StringBuffer, { this.get('filterMode').indexOf(this.get('content.filterMode')) === 0; }.property('content.filterMode', 'filterMode'), - name: function() { - var categoryName = this.get('content.categoryName'), - name = this.get('content.name'), - extra = { count: this.get('content.count') || 0 }; - - if (categoryName) { - name = 'category'; - extra.categoryName = Discourse.Formatter.toTitleCase(categoryName); - } - return I18n.t("filters." + name.replace("/", ".") + ".title", extra); - }.property('content.{categoryName,name,count}'), - renderString(buffer) { const content = this.get('content'); buffer.push(""); if (content.get('hasIcon')) { buffer.push(""); } - buffer.push(this.get('name')); + buffer.push(this.get('content.displayName')); buffer.push(""); } }); diff --git a/app/assets/javascripts/discourse/models/nav_item.js b/app/assets/javascripts/discourse/models/nav_item.js index 41280548f7..18571d2deb 100644 --- a/app/assets/javascripts/discourse/models/nav_item.js +++ b/app/assets/javascripts/discourse/models/nav_item.js @@ -9,6 +9,18 @@ Discourse.NavItem = Discourse.Model.extend({ + displayName: function() { + var categoryName = this.get('categoryName'), + name = this.get('name'), + extra = { count: this.get('count') || 0 }; + + if (categoryName) { + name = 'category'; + extra.categoryName = Discourse.Formatter.toTitleCase(categoryName); + } + return I18n.t("filters." + name.replace("/", ".") + ".title", extra); + }.property('categoryName,name,count'), + topicTrackingState: function() { return Discourse.TopicTrackingState.current(); }.property(), diff --git a/app/assets/javascripts/discourse/templates/components/navigation-bar.hbs b/app/assets/javascripts/discourse/templates/components/navigation-bar.hbs index 11a4f1f58e..078b8b9315 100644 --- a/app/assets/javascripts/discourse/templates/components/navigation-bar.hbs +++ b/app/assets/javascripts/discourse/templates/components/navigation-bar.hbs @@ -1,6 +1,4 @@ - +{{#each navItem in navItems}} + {{navigation-item content=navItem filterMode=filterMode}} +{{/each}} +{{custom-html "extraNavItem"}} diff --git a/app/assets/javascripts/discourse/templates/mobile/components/navigation-bar.hbs b/app/assets/javascripts/discourse/templates/mobile/components/navigation-bar.hbs new file mode 100644 index 0000000000..8f366864e8 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/mobile/components/navigation-bar.hbs @@ -0,0 +1,13 @@ +
  • + + {{selectedNavItem.displayName}} + + +
  • +{{#if expanded}} +
      + {{#each navItem in navItems}} + {{navigation-item content=navItem filterMode=filterMode}} + {{/each}} +
    +{{/if}} diff --git a/app/assets/stylesheets/mobile/topic-list.scss b/app/assets/stylesheets/mobile/topic-list.scss index 1210b069ba..4a4ab4423a 100644 --- a/app/assets/stylesheets/mobile/topic-list.scss +++ b/app/assets/stylesheets/mobile/topic-list.scss @@ -6,6 +6,9 @@ // -------------------------------------------------- .list-controls { + .category-breadcrumb.hidden { + display: none; + } margin: 5px; .nav { float: left; @@ -17,6 +20,37 @@ .btn { float: right; margin-left: 8px; + margin-top: 5px; + } + .nav-pills { + position: relative; + } + .nav-pills .drop { + border: 1px solid dark-light-diff($primary, $secondary, 90%, -65%); + position: absolute; + z-index: 1000; + background-color: $secondary; + padding: 0 10px 10px 10px; + width: 150px; + top: 100%; + margin: 0; + li { + list-style-type: none; + margin-left: 0; + margin-top: 5px; + padding-top: 10px; + a { + width: 100%; + display: inline-block; + } + } + + } + .nav-pills > li { + background: dark-light-diff($primary, $secondary, 90%, -65%); + i.fa-caret-down { + margin-left: 8px; + } } } From 374f951c65527f56640c801e78dac18b9c3d2ce0 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 22 Jun 2015 14:35:11 +1000 Subject: [PATCH 0160/1435] fix live css changes on mobile --- .../discourse/initializers/live-development.js.es6 | 4 ++++ lib/autospec/reload_css.rb | 12 +++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/discourse/initializers/live-development.js.es6 b/app/assets/javascripts/discourse/initializers/live-development.js.es6 index 817b9e058d..701789cf7f 100644 --- a/app/assets/javascripts/discourse/initializers/live-development.js.es6 +++ b/app/assets/javascripts/discourse/initializers/live-development.js.es6 @@ -64,6 +64,10 @@ export default { $(this).data('orig', this.href); } const orig = $(this).data('orig'); + if (!me.hash) { + window.__uniq = window.__uniq || 1; + me.hash = window.__uniq++; + } this.href = orig + (orig.indexOf('?') >= 0 ? "&hash=" : "?hash=") + me.hash; } }); diff --git a/lib/autospec/reload_css.rb b/lib/autospec/reload_css.rb index 58e31811f6..e6bd00a6fa 100644 --- a/lib/autospec/reload_css.rb +++ b/lib/autospec/reload_css.rb @@ -27,11 +27,13 @@ class Autospec::ReloadCss if paths.any? { |p| p =~ /\.(css|s[ac]ss)/ } # todo connect to dev instead? ActiveRecord::Base.establish_connection - s = DiscourseStylesheets.new(:desktop) # TODO: what about mobile? - s.compile - s.ensure_digestless_file + [:desktop, :mobile].each do |style| + s = DiscourseStylesheets.new(style) + s.compile + s.ensure_digestless_file + paths << "public" + s.stylesheet_relpath_no_digest + end ActiveRecord::Base.clear_active_connections! - paths << "public" + s.stylesheet_relpath_no_digest end paths.map! do |p| hash = nil @@ -41,7 +43,7 @@ class Autospec::ReloadCss p = p.sub(/\.sass/, "") p = p.sub(/\.scss/, "") p = p.sub(/^app\/assets\/stylesheets/, "assets") - { name: p, hash: hash } + { name: p, hash: hash || SecureRandom.hex } end message_bus.publish "/file-change", paths end From b3e583faf35678caa7c61bce1d88bc9b8de94c65 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 22 Jun 2015 15:58:51 +1000 Subject: [PATCH 0161/1435] UX: correct text for sharing badges --- config/locales/client.en.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 5108eabc35..3ee218f287 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2545,10 +2545,10 @@ en: description: Invited a user campaigner: name: Campaigner - description: Invited 3 members + description: Invited 3 basic users (trust level 1) champion: name: Champion - description: Invited 10 members + description: Invited 5 members (trust level 2) first_share: name: First Share description: Shared a post From 41e427bd2ec95d1351b0bea33dd426ad8317173a Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 22 Jun 2015 18:09:08 +1000 Subject: [PATCH 0162/1435] Work in progress, full page search --- .../controllers/discovery-sortable.js.es6 | 3 +- .../controllers/discovery/topics.js.es6 | 2 + .../controllers/navigation/default.js.es6 | 4 +- .../discourse/helpers/topic-link.js.es6 | 3 +- .../discourse/models/topic-list.js.es6 | 9 +++- .../javascripts/discourse/models/topic.js.es6 | 3 +- .../templates/components/basic-topic-list.hbs | 4 +- .../discourse/templates/discovery/topics.hbs | 1 + .../discourse/views/topic-list-item.js.es6 | 4 ++ app/controllers/list_controller.rb | 3 +- app/models/topic.rb | 2 + app/serializers/listable_topic_serializer.rb | 19 +++++++- lib/search.rb | 4 -- lib/topic_query.rb | 43 ++++++++++++++++++- spec/components/topic_query_spec.rb | 22 +++++++++- 15 files changed, 108 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/discovery-sortable.js.es6 b/app/assets/javascripts/discourse/controllers/discovery-sortable.js.es6 index c48aa54ffa..d6077dd099 100644 --- a/app/assets/javascripts/discourse/controllers/discovery-sortable.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery-sortable.js.es6 @@ -7,7 +7,8 @@ export var queryParams = { status: { replace: true }, state: { replace: true }, search: { replace: true }, - max_posts: { replace: true } + max_posts: { replace: true }, + q: { replace: true } }; // Basic controller options diff --git a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 index 15c8d66c2e..d36adc423b 100644 --- a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 @@ -16,6 +16,8 @@ var controllerOpts = { expandGloballyPinned: false, expandAllPinned: false, + isSearch: Em.computed.equal('model.filter', 'search'), + actions: { changeSort: function(sortBy) { diff --git a/app/assets/javascripts/discourse/controllers/navigation/default.js.es6 b/app/assets/javascripts/discourse/controllers/navigation/default.js.es6 index 65bf573193..54f63d6d09 100644 --- a/app/assets/javascripts/discourse/controllers/navigation/default.js.es6 +++ b/app/assets/javascripts/discourse/controllers/navigation/default.js.es6 @@ -13,13 +13,13 @@ export default DiscourseController.extend({ isSearch: Em.computed.equal('filterMode', 'search'), - searchTerm: Em.computed.alias('controllers.discovery/topics.model.params.search'), + searchTerm: Em.computed.alias('controllers.discovery/topics.model.params.q'), actions: { search: function(){ var discovery = this.get('controllers.discovery/topics'); var model = discovery.get('model'); - discovery.set('search', this.get("searchTerm")); + discovery.set('q', this.get("searchTerm")); model.refreshSort(); } } diff --git a/app/assets/javascripts/discourse/helpers/topic-link.js.es6 b/app/assets/javascripts/discourse/helpers/topic-link.js.es6 index f0703c6d9e..105ec123a9 100644 --- a/app/assets/javascripts/discourse/helpers/topic-link.js.es6 +++ b/app/assets/javascripts/discourse/helpers/topic-link.js.es6 @@ -2,5 +2,6 @@ import registerUnbound from 'discourse/helpers/register-unbound'; registerUnbound('topic-link', function(topic) { var title = topic.get('fancyTitle'); - return new Handlebars.SafeString("" + title + ""); + var url = topic.linked_post_number ? topic.urlForPostNumber(topic.linked_post_number) : topic.get('lastUnreadUrl'); + return new Handlebars.SafeString("" + title + ""); }); diff --git a/app/assets/javascripts/discourse/models/topic-list.js.es6 b/app/assets/javascripts/discourse/models/topic-list.js.es6 index 7f158b924a..ab14099c81 100644 --- a/app/assets/javascripts/discourse/models/topic-list.js.es6 +++ b/app/assets/javascripts/discourse/models/topic-list.js.es6 @@ -40,8 +40,8 @@ const TopicList = RestModel.extend({ }, refreshSort: function(order, ascending) { - const self = this, - params = this.get('params') || {}; + const self = this; + var params = this.get('params') || {}; params.order = order || params.order; @@ -51,6 +51,11 @@ const TopicList = RestModel.extend({ params.ascending = ascending; } + if (params.q) { + // search is unique, nothing else allowed with it + params = {q: params.q}; + } + this.set('loaded', false); const store = this.store; store.findFiltered('topicList', {filter: this.get('filter'), params}).then(function(tl) { diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index bba5b4c2ec..798053e3c2 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -363,8 +363,7 @@ const Topic = RestModel.extend({ ); }, - excerptNotEmpty: Em.computed.notEmpty('excerpt'), - hasExcerpt: Em.computed.and('pinned', 'excerptNotEmpty'), + hasExcerpt: Em.computed.notEmpty('excerpt'), excerptTruncated: function() { const e = this.get('excerpt'); diff --git a/app/assets/javascripts/discourse/templates/components/basic-topic-list.hbs b/app/assets/javascripts/discourse/templates/components/basic-topic-list.hbs index f8afeb5755..6d323d7aab 100644 --- a/app/assets/javascripts/discourse/templates/components/basic-topic-list.hbs +++ b/app/assets/javascripts/discourse/templates/components/basic-topic-list.hbs @@ -3,7 +3,9 @@ {{topic-list showParticipants=showParticipants hideCategory=hideCategory - topics=topics}} + topics=topics + expandExcerpts=expandExcerpts + }} {{else}}
    {{i18n 'choose_topic.none_found'}} diff --git a/app/assets/javascripts/discourse/templates/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/discovery/topics.hbs index f3e452a4ab..ec4c3d822b 100644 --- a/app/assets/javascripts/discourse/templates/discovery/topics.hbs +++ b/app/assets/javascripts/discourse/templates/discovery/topics.hbs @@ -46,6 +46,7 @@ selected=selected expandGloballyPinned=expandGloballyPinned expandAllPinned=expandAllPinned + expandExcerpts=isSearch topics=model.topics}} {{/if}}
    diff --git a/app/assets/javascripts/discourse/views/topic-list-item.js.es6 b/app/assets/javascripts/discourse/views/topic-list-item.js.es6 index d1d0ca7e6d..f70fa98e6d 100644 --- a/app/assets/javascripts/discourse/views/topic-list-item.js.es6 +++ b/app/assets/javascripts/discourse/views/topic-list-item.js.es6 @@ -65,6 +65,10 @@ export default Discourse.View.extend(StringBuffer, { }, expandPinned: function() { + if (this.get('controller.expandExcerpts')) { + return true; + } + const pinned = this.get('topic.pinned'); if (!pinned) { return false; diff --git a/app/controllers/list_controller.rb b/app/controllers/list_controller.rb index 927519cb49..45d00fbc97 100644 --- a/app/controllers/list_controller.rb +++ b/app/controllers/list_controller.rb @@ -248,7 +248,8 @@ class ListController < ApplicationController status: params[:status], filter: params[:filter], state: params[:state], - search: params[:search] + search: params[:search], + q: params[:q] } options[:no_subcategories] = true if params[:no_subcategories] == 'true' options[:slow_platform] = true if slow_platform? diff --git a/app/models/topic.rb b/app/models/topic.rb index 6f444e1ab4..a5be02dbe3 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -106,7 +106,9 @@ class Topic < ActiveRecord::Base has_one :first_post, -> {where post_number: 1}, class_name: Post # When we want to temporarily attach some data to a forum topic (usually before serialization) + attr_accessor :search_data attr_accessor :user_data + attr_accessor :posters # TODO: can replace with posters_summary once we remove old list code attr_accessor :participants attr_accessor :topic_list diff --git a/app/serializers/listable_topic_serializer.rb b/app/serializers/listable_topic_serializer.rb index 5620bccf00..79ffae02ff 100644 --- a/app/serializers/listable_topic_serializer.rb +++ b/app/serializers/listable_topic_serializer.rb @@ -11,6 +11,7 @@ class ListableTopicSerializer < BasicTopicSerializer :bumped_at, :unseen, :last_read_post_number, + :linked_post_number, :unread, :new_posts, :pinned, @@ -77,6 +78,22 @@ class ListableTopicSerializer < BasicTopicSerializer !!object.user_data end + def excerpt + if object.search_data + object.search_data[:excerpt] + else + object.excerpt + end + end + + def include_linked_post_number? + object.search_data + end + + def linked_post_number + object.search_data[:post_number] + end + alias :include_last_read_post_number? :has_user_data def unread @@ -90,7 +107,7 @@ class ListableTopicSerializer < BasicTopicSerializer alias :include_new_posts? :has_user_data def include_excerpt? - pinned + pinned || object.search_data end def pinned diff --git a/lib/search.rb b/lib/search.rb index 97779db687..ad622435c2 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -452,10 +452,6 @@ class Search # double wrapping so we get correct row numbers post_sql = "SELECT *, row_number() over() row_number FROM (#{post_sql}) xxx" - # p Topic.exec_sql(post_sql).to_a - # puts post_sql - # p Topic.exec_sql("SELECT topic_id FROM topic_allowed_users WHERE user_id = 2").to_a - posts = Post.includes(:topic => :category) .joins("JOIN (#{post_sql}) x ON x.id = posts.topic_id AND x.post_number = posts.post_number") .order('row_number') diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 13c3f9bb4b..1146ceb6df 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -27,6 +27,7 @@ class TopicQuery search slow_platform filter + q ).map(&:to_sym) # Maps `order` to a columns in `topics` @@ -71,7 +72,47 @@ class TopicQuery end def list_search - create_list(:latest, {}, latest_results) + + results = nil + + if @options[:q].present? + search = Search.execute(@options[:q], + type_filter: 'topic', + guardian: Guardian.new(@user)) + + topic_ids = search.posts.map(&:topic_id) + + if topic_ids.present? + sql = topic_ids.each_with_index.map do |id, idx| + "SELECT #{idx} pos, #{id} id" + end.join(" UNION ALL ") + + results = Topic + .unscoped + .joins("JOIN (#{sql}) X on X.id = topics.id") + .order("X.pos") + + posts_map = Hash[*search.posts.map{|p| [p.topic_id, p]}.flatten] + end + end + + results ||= Topic.where("1=0") + + if @user + results = results.joins("LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{@user.id.to_i})") + .references('tu') + end + + list = create_list(:latest, {unordered: true}, results) + + + list.topics.each do |topic| + if post = posts_map[topic.id] + topic.search_data = {excerpt: search.blurb(post), post_number: post.post_number} + end + end + + list end def list_read diff --git a/spec/components/topic_query_spec.rb b/spec/components/topic_query_spec.rb index 54882d9669..8bd9c5a11d 100644 --- a/spec/components/topic_query_spec.rb +++ b/spec/components/topic_query_spec.rb @@ -43,15 +43,33 @@ describe TopicQuery do context "list_topics_by" do it "allows users to view their own invisible topics" do - topic = Fabricate(:topic, user: user) - invisible_topic = Fabricate(:topic, user: user, visible: false) + _topic = Fabricate(:topic, user: user) + _invisible_topic = Fabricate(:topic, user: user, visible: false) expect(TopicQuery.new(nil).list_topics_by(user).topics.count).to eq(1) expect(TopicQuery.new(user).list_topics_by(user).topics.count).to eq(2) + + # search should return nothing normally + expect(TopicQuery.new(nil).list_search.topics.count).to eq(0) end end + context 'search' do + it 'can correctly search' do + # got to enable indexing + ActiveRecord::Base.observers.enable :all + + p = create_post(raw: "I am super awesome and search will find me") + create_post(topic_id: p.topic_id, raw: "I am super spectacular post of doom") + + results = TopicQuery.new(nil, q: "doom").list_search + + expect(results.topics.count).to eq(1) + + end + end + context 'bookmarks' do it "filters and returns bookmarks correctly" do post = Fabricate(:post) From 565450601f0aa89b9f96a546e42aa6158ef9a976 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Mon, 22 Jun 2015 19:56:45 +0800 Subject: [PATCH 0163/1435] FIX: Tagging plugin was blocking composer status on smaller screens. --- app/assets/stylesheets/desktop/compose.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/desktop/compose.scss b/app/assets/stylesheets/desktop/compose.scss index be6ffebe7e..4c30572650 100644 --- a/app/assets/stylesheets/desktop/compose.scss +++ b/app/assets/stylesheets/desktop/compose.scss @@ -288,6 +288,7 @@ } .submit-panel { + width: 28%; position: absolute; display: block; bottom: 8px; From efb02ae561b160e9a74536edf3a0fba17de434bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 22 Jun 2015 14:08:30 +0200 Subject: [PATCH 0164/1435] FIX: take into account unlisted banners --- app/controllers/topics_controller.rb | 8 ++++---- spec/controllers/topics_controller_spec.rb | 22 ++++++++++++++++++++-- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index 287cf432de..5e42759e72 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -162,12 +162,12 @@ class TopicsController < ApplicationController params.require(:category_id) category_id = params[:category_id].to_i - topics = Topic.listable_topics.visible + visible_topics = Topic.listable_topics.visible render json: { - pinned_in_category_count: topics.where(category_id: category_id).where(pinned_globally: false).where.not(pinned_at: nil).count, - pinned_globally_count: topics.where(pinned_globally: true).where.not(pinned_at: nil).count, - banner_count: topics.where(archetype: Archetype.banner).count, + pinned_in_category_count: visible_topics.where(category_id: category_id).where(pinned_globally: false).where.not(pinned_at: nil).count, + pinned_globally_count: visible_topics.where(pinned_globally: true).where.not(pinned_at: nil).count, + banner_count: Topic.listable_topics.where(archetype: Archetype.banner).count, } end diff --git a/spec/controllers/topics_controller_spec.rb b/spec/controllers/topics_controller_spec.rb index c3f05529c5..19281aebe5 100644 --- a/spec/controllers/topics_controller_spec.rb +++ b/spec/controllers/topics_controller_spec.rb @@ -976,7 +976,6 @@ describe TopicsController do xhr :put, :remove_bookmarks, topic_id: post.topic_id expect(PostAction.where(user_id: user.id, post_action_type: bookmark).count).to eq(0) - end end @@ -996,8 +995,27 @@ describe TopicsController do xhr :put, :reset_new user.reload expect(user.user_stat.new_since.to_date).not_to eq(old_date.to_date) - end end + + describe "feature_stats" do + it "works" do + xhr :get, :feature_stats, category_id: 1 + + expect(response).to be_success + json = JSON.parse(response.body) + expect(json["pinned_in_category_count"]).to eq(0) + expect(json["pinned_globally_count"]).to eq(0) + expect(json["banner_count"]).to eq(0) + end + + it "allows unlisted banner topic" do + Fabricate(:topic, category_id: 1, archetype: Archetype.banner, visible: false) + + xhr :get, :feature_stats, category_id: 1 + json = JSON.parse(response.body) + expect(json["banner_count"]).to eq(1) + end + end end From ef0f1b2dbd7629d874e6ca65248bf120942c7774 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Thu, 11 Jun 2015 17:57:46 +0530 Subject: [PATCH 0165/1435] add XenForo importer --- script/import_scripts/xenforo.rb | 205 +++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 script/import_scripts/xenforo.rb diff --git a/script/import_scripts/xenforo.rb b/script/import_scripts/xenforo.rb new file mode 100644 index 0000000000..536c03ec14 --- /dev/null +++ b/script/import_scripts/xenforo.rb @@ -0,0 +1,205 @@ +require "mysql2" + +require File.expand_path(File.dirname(__FILE__) + "/base.rb") + +# Call it like this: +# RAILS_ENV=production bundle exec ruby script/import_scripts/xenforo.rb +class ImportScripts::XenForo < ImportScripts::Base + + XENFORO_DB = "xenforo_db" + TABLE_PREFIX = "xf_" + BATCH_SIZE = 1000 + + def initialize + super + + @client = Mysql2::Client.new( + host: "localhost", + username: "root", + password: "pa$$word", + database: XENFORO_DB + ) + end + + def execute + import_users + import_categories + import_posts + end + + def import_users + puts '', "creating users" + + total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}user;").first['count'] + + batches(BATCH_SIZE) do |offset| + results = mysql_query( + "SELECT user_id id, username, email, custom_title title, register_date created_at, + last_activity last_visit_time, user_group_id, is_moderator, is_admin, is_staff + FROM #{TABLE_PREFIX}user + LIMIT #{BATCH_SIZE} + OFFSET #{offset};") + + break if results.size < 1 + + create_users(results, total: total_count, offset: offset) do |user| + next if user['username'].blank? + { id: user['id'], + email: user['email'], + username: user['username'], + title: user['title'], + created_at: Time.zone.at(user['created_at']), + last_seen_at: Time.zone.at(user['last_visit_time']), + moderator: user['is_moderator'] == 1 || user['is_staff'] == 1, + admin: user['is_admin'] == 1 } + end + end + end + + def import_categories + puts "", "importing categories..." + + # Note that this script uses Prefix as Category, you may want to change this as per your requirement + categories = mysql_query(" + SELECT prefix_id id + FROM #{TABLE_PREFIX}thread_prefix + ORDER BY prefix_id ASC + ").to_a + + create_categories(categories) do |category| + { + id: category["id"], + name: "Category-#{category["id"]}" + } + end + end + + def import_posts + puts "", "creating topics and posts" + + total_count = mysql_query("SELECT count(*) count from #{TABLE_PREFIX}post").first["count"] + + batches(BATCH_SIZE) do |offset| + results = mysql_query(" + SELECT p.post_id id, + t.thread_id topic_id, + t.prefix_id category_id, + t.title title, + t.first_post_id first_post_id, + p.user_id user_id, + p.message raw, + p.post_date created_at + FROM #{TABLE_PREFIX}post p, + #{TABLE_PREFIX}thread t + WHERE p.thread_id = t.thread_id + ORDER BY p.post_date + LIMIT #{BATCH_SIZE} + OFFSET #{offset}; + ").to_a + + break if results.size < 1 + + create_posts(results, total: total_count, offset: offset) do |m| + skip = false + mapped = {} + + mapped[:id] = m['id'] + mapped[:user_id] = user_id_from_imported_user_id(m['user_id']) || -1 + mapped[:raw] = process_xenforo_post(m['raw'], m['id']) + mapped[:created_at] = Time.zone.at(m['created_at']) + + if m['id'] == m['first_post_id'] + if m['category_id'].to_i == 0 || m['category_id'].nil? + mapped[:category] = SiteSetting.uncategorized_category_id + else + mapped[:category] = category_id_from_imported_category_id(m['category_id'].to_i) + end + mapped[:title] = CGI.unescapeHTML(m['title']) + else + parent = topic_lookup_from_imported_post_id(m['first_post_id']) + if parent + mapped[:topic_id] = parent[:topic_id] + else + puts "Parent post #{m['first_post_id']} doesn't exist. Skipping #{m["id"]}: #{m["title"][0..40]}" + skip = true + end + end + + skip ? nil : mapped + end + end + + end + + def process_xenforo_post(raw, import_id) + s = raw.dup + + # :) is encoded as :) + s.gsub!(/]+) \/>/, '\1') + + # Some links look like this: http://www.onegameamonth.com + s.gsub!(/(.+)<\/a>/, '[\2](\1)') + + # Many phpbb bbcode tags have a hash attached to them. Examples: + # [url=https://google.com:1qh1i7ky]click here[/url:1qh1i7ky] + # [quote="cybereality":b0wtlzex]Some text.[/quote:b0wtlzex] + s.gsub!(/:(?:\w{8})\]/, ']') + + # Remove mybb video tags. + s.gsub!(/(^\[video=.*?\])|(\[\/video\]$)/, '') + + s = CGI.unescapeHTML(s) + + # phpBB shortens link text like this, which breaks our markdown processing: + # [http://answers.yahoo.com/question/index ... 223AAkkPli](http://answers.yahoo.com/question/index?qid=20070920134223AAkkPli) + # + # Work around it for now: + s.gsub!(/\[http(s)?:\/\/(www\.)?/, '[') + + # [QUOTE]...[/QUOTE] + s.gsub!(/\[quote\](.+?)\[\/quote\]/im) { "\n> #{$1}\n" } + + # [URL=...]...[/URL] + s.gsub!(/\[url="?(.+?)"?\](.+)\[\/url\]/i) { "[#{$2}](#{$1})" } + + # [IMG]...[/IMG] + s.gsub!(/\[\/?img\]/i, "") + + # convert list tags to ul and list=1 tags to ol + # (basically, we're only missing list=a here...) + s.gsub!(/\[list\](.*?)\[\/list:u\]/m, '[ul]\1[/ul]') + s.gsub!(/\[list=1\](.*?)\[\/list:o\]/m, '[ol]\1[/ol]') + # convert *-tags to li-tags so bbcode-to-md can do its magic on phpBB's lists: + s.gsub!(/\[\*\](.*?)\[\/\*:m\]/, '[li]\1[/li]') + + # [YOUTUBE][/YOUTUBE] + s.gsub!(/\[youtube\](.+?)\[\/youtube\]/i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } + + # [youtube=425,350]id[/youtube] + s.gsub!(/\[youtube="?(.+?)"?\](.+)\[\/youtube\]/i) { "\nhttps://www.youtube.com/watch?v=#{$2}\n" } + + # [MEDIA=youtube]id[/MEDIA] + s.gsub!(/\[MEDIA=youtube\](.+?)\[\/MEDIA\]/i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } + + # [ame="youtube_link"]title[/ame] + s.gsub!(/\[ame="?(.+?)"?\](.+)\[\/ame\]/i) { "\n#{$1}\n" } + + # [VIDEO=youtube;]...[/VIDEO] + s.gsub!(/\[video=youtube;([^\]]+)\].*?\[\/video\]/i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } + + # [USER=706]@username[/USER] + s.gsub!(/\[user="?(.+?)"?\](.+)\[\/user\]/i) { $2 } + + # Remove the color tag + s.gsub!(/\[color=[#a-z0-9]+\]/i, "") + s.gsub!(/\[\/color\]/i, "") + + s + end + + def mysql_query(sql) + @client.query(sql, cache_rows: false) + end +end + +ImportScripts::XenForo.new.perform From 5a77f6218101b85b360d2ec564928d863bc91964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 22 Jun 2015 14:32:45 +0200 Subject: [PATCH 0166/1435] PERF: poor SQL performances when counting notifications --- app/models/notification.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/models/notification.rb b/app/models/notification.rb index 884ddaff82..014a5b4e83 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -8,10 +8,8 @@ class Notification < ActiveRecord::Base validates_presence_of :notification_type scope :unread, lambda { where(read: false) } - scope :recent, lambda {|n=nil| n ||= 10; order('notifications.created_at desc').limit(n) } - scope :visible , lambda { where('notifications.topic_id IS NULL OR notifications.topic_id IN ( - SELECT id FROM topics - WHERE deleted_at IS NULL)') } + scope :recent, lambda { |n=nil| n ||= 10; order('notifications.created_at desc').limit(n) } + scope :visible , lambda { joins('LEFT JOIN topics ON notifications.topic_id = topics.id AND topics.deleted_at IS NULL') } after_save :refresh_notification_count after_destroy :refresh_notification_count From 0bfabed2d508c09abec54c14bd771b4625a522ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 22 Jun 2015 16:22:15 +0200 Subject: [PATCH 0167/1435] FIX: avatar selection wasn't properly pre-selected --- .../discourse/views/avatar-selector.js.es6 | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/discourse/views/avatar-selector.js.es6 b/app/assets/javascripts/discourse/views/avatar-selector.js.es6 index ed60610505..15b8541ef3 100644 --- a/app/assets/javascripts/discourse/views/avatar-selector.js.es6 +++ b/app/assets/javascripts/discourse/views/avatar-selector.js.es6 @@ -5,9 +5,12 @@ export default ModalBodyView.extend({ classNames: ['avatar-selector'], title: I18n.t('user.change_avatar.title'), - // *HACK* used to select the proper radio button, cause {{action}} - // stops the default behavior + // *HACK* used to select the proper radio button, because {{action}} stops the default behavior selectedChanged: function() { - Em.run.next(() => $('input:radio[name="avatar"]').val([this.get('controller.selected')]) ); - }.observes('controller.selected') + Em.run.next(() => $('input:radio[name="avatar"]').val([this.get('controller.selected')])); + }.observes('controller.selected').on("didInsertElement"), + + _focusSelectedButton: function() { + Em.run.next(() => $('input:radio[value="' + this.get('controller.selected') + '"]').focus()); + }.on("didInsertElement") }); From b25a16ee3e5a3010d1a69dbcdde05701ed8024e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 22 Jun 2015 19:46:51 +0200 Subject: [PATCH 0168/1435] FEATURE: 2 new reports: time to first response, topics with no response FIX: relativeAgeMediumSpan was off by 1 REFACTOR: extracted decimalAdjust & round functions from the poll plugin --- .../admin/models/{report.js => report.js.es6} | 18 +++--- .../javascripts/admin/templates/dashboard.hbs | 2 + .../discourse}/lib/decimal-adjust.js.es6 | 0 .../javascripts/discourse/lib/formatter.js | 2 +- .../javascripts/discourse}/lib/round.js.es6 | 2 +- app/models/admin_dashboard_data.rb | 2 + app/models/report.rb | 15 +++++ app/models/topic.rb | 60 +++++++++++++++++++ config/locales/server.en.yml | 8 +++ .../components/poll-results-number.js.es6 | 2 +- 10 files changed, 101 insertions(+), 10 deletions(-) rename app/assets/javascripts/admin/models/{report.js => report.js.es6} (93%) rename {plugins/poll/assets/javascripts => app/assets/javascripts/discourse}/lib/decimal-adjust.js.es6 (100%) rename {plugins/poll/assets/javascripts => app/assets/javascripts/discourse}/lib/round.js.es6 (54%) diff --git a/app/assets/javascripts/admin/models/report.js b/app/assets/javascripts/admin/models/report.js.es6 similarity index 93% rename from app/assets/javascripts/admin/models/report.js rename to app/assets/javascripts/admin/models/report.js.es6 index 6f8bfdfff3..d03e6d6053 100644 --- a/app/assets/javascripts/admin/models/report.js +++ b/app/assets/javascripts/admin/models/report.js.es6 @@ -1,9 +1,11 @@ -Discourse.Report = Discourse.Model.extend({ +import round from "discourse/lib/round"; + +const Report = Discourse.Model.extend({ reportUrl: function() { return("/admin/reports/" + this.get('type')); }.property('type'), - valueAt: function(numDaysAgo) { + valueAt(numDaysAgo) { if (this.data) { var wantedDate = moment().subtract(numDaysAgo, 'days').format('YYYY-MM-DD'); var item = this.data.find( function(d) { return d.x === wantedDate; } ); @@ -14,7 +16,7 @@ Discourse.Report = Discourse.Model.extend({ return 0; }, - sumDays: function(startDaysAgo, endDaysAgo) { + sumDays(startDaysAgo, endDaysAgo) { if (this.data) { var earliestDate = moment().subtract(endDaysAgo, 'days').startOf('day'); var latestDate = moment().subtract(startDaysAgo, 'days').startOf('day'); @@ -25,7 +27,7 @@ Discourse.Report = Discourse.Model.extend({ sum += datum.y; } }); - return sum; + return round(sum, -2); } }, @@ -100,7 +102,7 @@ Discourse.Report = Discourse.Model.extend({ } }.property('type'), - percentChangeString: function(val1, val2) { + percentChangeString(val1, val2) { var val = ((val1 - val2) / val2) * 100; if( isNaN(val) || !isFinite(val) ) { return null; @@ -111,7 +113,7 @@ Discourse.Report = Discourse.Model.extend({ } }, - changeTitle: function(val1, val2, prevPeriodString) { + changeTitle(val1, val2, prevPeriodString) { var title = ''; var percentChange = this.percentChangeString(val1, val2); if( percentChange ) { @@ -139,7 +141,7 @@ Discourse.Report = Discourse.Model.extend({ }); -Discourse.Report.reopenClass({ +Report.reopenClass({ find: function(type, startDate, endDate) { return Discourse.ajax("/admin/reports/" + type, {data: { @@ -162,3 +164,5 @@ Discourse.Report.reopenClass({ }); } }); + +export default Report; diff --git a/app/assets/javascripts/admin/templates/dashboard.hbs b/app/assets/javascripts/admin/templates/dashboard.hbs index 1d4dcf6740..5a61c52dd8 100644 --- a/app/assets/javascripts/admin/templates/dashboard.hbs +++ b/app/assets/javascripts/admin/templates/dashboard.hbs @@ -58,6 +58,8 @@ {{admin-report-counts report=signups}} {{admin-report-counts report=topics}} {{admin-report-counts report=posts}} + {{admin-report-counts report=time_to_first_response}} + {{admin-report-counts report=topics_with_no_response}} {{admin-report-counts report=likes}} {{admin-report-counts report=flags}} {{admin-report-counts report=bookmarks}} diff --git a/plugins/poll/assets/javascripts/lib/decimal-adjust.js.es6 b/app/assets/javascripts/discourse/lib/decimal-adjust.js.es6 similarity index 100% rename from plugins/poll/assets/javascripts/lib/decimal-adjust.js.es6 rename to app/assets/javascripts/discourse/lib/decimal-adjust.js.es6 diff --git a/app/assets/javascripts/discourse/lib/formatter.js b/app/assets/javascripts/discourse/lib/formatter.js index f2063e3786..2b80eb0872 100644 --- a/app/assets/javascripts/discourse/lib/formatter.js +++ b/app/assets/javascripts/discourse/lib/formatter.js @@ -188,7 +188,7 @@ relativeAgeMediumSpan = function(distance, leaveAgo) { }; switch(true){ - case(distanceInMinutes >= 1 && distanceInMinutes <= 56): + case(distanceInMinutes >= 1 && distanceInMinutes <= 55): formatted = t("x_minutes", {count: distanceInMinutes}); break; case(distanceInMinutes >= 56 && distanceInMinutes <= 89): diff --git a/plugins/poll/assets/javascripts/lib/round.js.es6 b/app/assets/javascripts/discourse/lib/round.js.es6 similarity index 54% rename from plugins/poll/assets/javascripts/lib/round.js.es6 rename to app/assets/javascripts/discourse/lib/round.js.es6 index b26eed0736..45d4f0eec7 100644 --- a/plugins/poll/assets/javascripts/lib/round.js.es6 +++ b/app/assets/javascripts/discourse/lib/round.js.es6 @@ -1,4 +1,4 @@ -import decimalAdjust from "discourse/plugins/poll/lib/decimal-adjust"; +import decimalAdjust from "discourse/lib/decimal-adjust"; export default function(value, exp) { return decimalAdjust("round", value, exp); diff --git a/app/models/admin_dashboard_data.rb b/app/models/admin_dashboard_data.rb index 04c199b400..f3d718b2b3 100644 --- a/app/models/admin_dashboard_data.rb +++ b/app/models/admin_dashboard_data.rb @@ -7,6 +7,8 @@ class AdminDashboardData 'signups', 'topics', 'posts', + 'time_to_first_response', + 'topics_with_no_response', 'flags', 'users_by_trust_level', 'likes', diff --git a/app/models/report.rb b/app/models/report.rb index 76f5952d55..5c6c9ad9db 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -93,6 +93,21 @@ class Report add_counts report, Post.public_posts, 'posts.created_at' end + def self.report_time_to_first_response(report) + report.data = [] + Topic.time_to_first_response_per_day(report.start_date, report.end_date).each do |r| + report.data << { x: Date.parse(r["date"]), y: r["hours"].to_f.round(2) } + end + report.total = Topic.time_to_first_response_total + report.prev30Days = Topic.time_to_first_response_total(report.start_date - 30.days, report.start_date) + end + + def self.report_topics_with_no_response(report) + basic_report_about report, Topic, :with_no_response_per_day, report.start_date, report.end_date + report.total = Topic.with_no_response_total + report.prev30Days = Topic.with_no_response_total(report.start_date - 30.days, report.start_date) + end + def self.report_emails(report) report_about report, EmailLog end diff --git a/app/models/topic.rb b/app/models/topic.rb index a5be02dbe3..cfdae5d20c 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -848,6 +848,66 @@ class Topic < ActiveRecord::Base SiteSetting.embeddable_hosts.present? && SiteSetting.embed_truncate? && has_topic_embed? end + TIME_TO_FIRST_RESPONSE_SQL ||= <<-SQL + SELECT AVG(t.hours)::float AS "hours", t.created_at AS "date" + FROM ( + SELECT t.id, t.created_at::date AS created_at, EXTRACT(EPOCH FROM MIN(p.created_at) - t.created_at)::float / 3600.0 AS "hours" + FROM topics t + LEFT JOIN posts p ON p.topic_id = t.id + /*where*/ + GROUP BY t.id + ) t + GROUP BY t.created_at + ORDER BY t.created_at + SQL + + TIME_TO_FIRST_RESPONSE_TOTAL_SQL ||= <<-SQL + SELECT AVG(t.hours)::float AS "hours" + FROM ( + SELECT t.id, EXTRACT(EPOCH FROM MIN(p.created_at) - t.created_at)::float / 3600.0 AS "hours" + FROM topics t + LEFT JOIN posts p ON p.topic_id = t.id + /*where*/ + GROUP BY t.id + ) t + SQL + + def self.time_to_first_response(sql, start_date=nil, end_date=nil) + builder = SqlBuilder.new(sql) + builder.where("t.created_at >= :start_date", start_date: start_date) if start_date + builder.where("t.created_at <= :end_date", end_date: end_date) if end_date + builder.where("t.archetype <> '#{Archetype.private_message}'") + builder.where("t.deleted_at IS NULL") + builder.where("p.deleted_at IS NULL") + builder.where("p.post_number > 1") + builder.where("EXTRACT(EPOCH FROM p.created_at - t.created_at) > 0") + builder.exec + end + + def self.time_to_first_response_per_day(start_date, end_date) + time_to_first_response(TIME_TO_FIRST_RESPONSE_SQL, start_date, end_date) + end + + def self.time_to_first_response_total(start_date=nil, end_date=nil) + result = time_to_first_response(TIME_TO_FIRST_RESPONSE_TOTAL_SQL, start_date, end_date) + result.first["hours"].to_f.round(2) + end + + def self.with_no_response_per_day(start_date, end_date) + listable_topics.where(highest_post_number: 1) + .where("created_at BETWEEN ? AND ?", start_date, end_date) + .group("created_at::date") + .order("created_at::date") + .count + end + + def self.with_no_response_total(start_date=nil, end_date=nil) + total = listable_topics.where(highest_post_number: 1) + total = total.where("created_at >= ?", start_date) if start_date + total = total.where("created_at <= ?", end_date) if end_date + total.count + end + private def update_category_topic_count_by(num) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 89309529d8..1330b1cd62 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -675,6 +675,14 @@ en: title: "Total" xaxis: "Day" yaxis: "Total requests" + time_to_first_response: + title: "Time to first response" + xaxis: "Day" + yaxis: "Average time (hours)" + topics_with_no_response: + title: "Topics with no response" + xaxis: "Day" + yaxis: "Total" dashboard: rails_env_warning: "Your server is running in %{env} mode." diff --git a/plugins/poll/assets/javascripts/components/poll-results-number.js.es6 b/plugins/poll/assets/javascripts/components/poll-results-number.js.es6 index da48d4abbf..42087e2933 100644 --- a/plugins/poll/assets/javascripts/components/poll-results-number.js.es6 +++ b/plugins/poll/assets/javascripts/components/poll-results-number.js.es6 @@ -1,4 +1,4 @@ -import round from "discourse/plugins/poll/lib/round"; +import round from "discourse/lib/round"; export default Em.Component.extend({ tagName: "span", From 6dfef49392d0aa5d868be93bf72075cba1442391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 22 Jun 2015 19:57:02 +0200 Subject: [PATCH 0169/1435] fix the build... --- test/javascripts/lib/formatter-test.js.es6 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/javascripts/lib/formatter-test.js.es6 b/test/javascripts/lib/formatter-test.js.es6 index 74a163aaa6..b0c0062372 100644 --- a/test/javascripts/lib/formatter-test.js.es6 +++ b/test/javascripts/lib/formatter-test.js.es6 @@ -46,8 +46,8 @@ test("formating medium length dates", function() { leaveAgo = true; equal(strip(formatMins(1.4)), "1 min ago"); equal(strip(formatMins(2)), "2 mins ago"); - equal(strip(formatMins(56)), "56 mins ago"); - equal(strip(formatMins(57)), "1 hour ago"); + equal(strip(formatMins(55)), "55 mins ago"); + equal(strip(formatMins(56)), "1 hour ago"); equal(strip(formatHours(4)), "4 hours ago"); equal(strip(formatHours(22)), "22 hours ago"); equal(strip(formatHours(23)), "1 day ago"); @@ -57,8 +57,8 @@ test("formating medium length dates", function() { equal(strip(formatMins(0)), "just now"); equal(strip(formatMins(1.4)), "1 min"); equal(strip(formatMins(2)), "2 mins"); - equal(strip(formatMins(56)), "56 mins"); - equal(strip(formatMins(57)), "1 hour"); + equal(strip(formatMins(55)), "55 mins"); + equal(strip(formatMins(56)), "1 hour"); equal(strip(formatHours(4)), "4 hours"); equal(strip(formatHours(22)), "22 hours"); equal(strip(formatHours(23)), "1 day"); From 2f0bd6294c2866889f5d7f741d19009efa64f7ff Mon Sep 17 00:00:00 2001 From: Kane York Date: Mon, 22 Jun 2015 11:00:39 -0700 Subject: [PATCH 0170/1435] Add noindex directive on unlisted topics --- app/controllers/topics_controller.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index 5e42759e72..495d535fd8 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -77,6 +77,10 @@ class TopicsController < ApplicationController @topic_view.draft = Draft.get(current_user, @topic_view.draft_key, @topic_view.draft_sequence) end + unless @topic_view.topic.visible + response.headers['X-Robots-Tag'] = 'noindex' + end + perform_show_response canonical_url UrlHelper.absolute_without_cdn("#{Discourse.base_uri}#{@topic_view.canonical_path}") From 7ed309666bd4488d9cb6e3b3c35dc1db4adbbc49 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Mon, 22 Jun 2015 14:05:35 -0400 Subject: [PATCH 0171/1435] Refactor search results to be components instead of views for reuse --- .../components/search-result-category.js.es6 | 2 + .../components/search-result-post.js.es6 | 2 + .../components/search-result-topic.js.es6 | 2 + .../components/search-result-user.js.es6 | 2 + .../discourse/components/search-result.js.es6 | 11 + .../components/search-text-field.js.es6 | 7 + .../discourse/lib/search-for-term.js.es6 | 16 +- .../components/search-result-category.hbs | 7 + .../components/search-result-post.hbs | 12 + .../components/search-result-topic.hbs | 14 + .../components/search-result-user.hbs | 8 + .../discourse/templates/search.hbs | 7 +- .../templates/search/category_result.hbs | 3 - .../templates/search/post_result.hbs | 10 - .../templates/search/topic_result.hbs | 10 - .../templates/search/user_result.hbs | 4 - .../views/search-results-type.js.es6 | 15 - .../discourse/views/search-text-field.js.es6 | 27 - app/assets/javascripts/main_include.js | 1 + .../javascripts/acceptance/search-test.js.es6 | 18 + .../fixtures/search-fixtures.js.es6 | 667 +++++++++++++++++- 21 files changed, 764 insertions(+), 81 deletions(-) create mode 100644 app/assets/javascripts/discourse/components/search-result-category.js.es6 create mode 100644 app/assets/javascripts/discourse/components/search-result-post.js.es6 create mode 100644 app/assets/javascripts/discourse/components/search-result-topic.js.es6 create mode 100644 app/assets/javascripts/discourse/components/search-result-user.js.es6 create mode 100644 app/assets/javascripts/discourse/components/search-result.js.es6 create mode 100644 app/assets/javascripts/discourse/components/search-text-field.js.es6 create mode 100644 app/assets/javascripts/discourse/templates/components/search-result-category.hbs create mode 100644 app/assets/javascripts/discourse/templates/components/search-result-post.hbs create mode 100644 app/assets/javascripts/discourse/templates/components/search-result-topic.hbs create mode 100644 app/assets/javascripts/discourse/templates/components/search-result-user.hbs delete mode 100644 app/assets/javascripts/discourse/templates/search/category_result.hbs delete mode 100644 app/assets/javascripts/discourse/templates/search/post_result.hbs delete mode 100644 app/assets/javascripts/discourse/templates/search/topic_result.hbs delete mode 100644 app/assets/javascripts/discourse/templates/search/user_result.hbs delete mode 100644 app/assets/javascripts/discourse/views/search-results-type.js.es6 delete mode 100644 app/assets/javascripts/discourse/views/search-text-field.js.es6 create mode 100644 test/javascripts/acceptance/search-test.js.es6 diff --git a/app/assets/javascripts/discourse/components/search-result-category.js.es6 b/app/assets/javascripts/discourse/components/search-result-category.js.es6 new file mode 100644 index 0000000000..e23284ed16 --- /dev/null +++ b/app/assets/javascripts/discourse/components/search-result-category.js.es6 @@ -0,0 +1,2 @@ +import SearchResult from 'discourse/components/search-result'; +export default SearchResult.extend(); diff --git a/app/assets/javascripts/discourse/components/search-result-post.js.es6 b/app/assets/javascripts/discourse/components/search-result-post.js.es6 new file mode 100644 index 0000000000..e23284ed16 --- /dev/null +++ b/app/assets/javascripts/discourse/components/search-result-post.js.es6 @@ -0,0 +1,2 @@ +import SearchResult from 'discourse/components/search-result'; +export default SearchResult.extend(); diff --git a/app/assets/javascripts/discourse/components/search-result-topic.js.es6 b/app/assets/javascripts/discourse/components/search-result-topic.js.es6 new file mode 100644 index 0000000000..e23284ed16 --- /dev/null +++ b/app/assets/javascripts/discourse/components/search-result-topic.js.es6 @@ -0,0 +1,2 @@ +import SearchResult from 'discourse/components/search-result'; +export default SearchResult.extend(); diff --git a/app/assets/javascripts/discourse/components/search-result-user.js.es6 b/app/assets/javascripts/discourse/components/search-result-user.js.es6 new file mode 100644 index 0000000000..e23284ed16 --- /dev/null +++ b/app/assets/javascripts/discourse/components/search-result-user.js.es6 @@ -0,0 +1,2 @@ +import SearchResult from 'discourse/components/search-result'; +export default SearchResult.extend(); diff --git a/app/assets/javascripts/discourse/components/search-result.js.es6 b/app/assets/javascripts/discourse/components/search-result.js.es6 new file mode 100644 index 0000000000..dacf1f2696 --- /dev/null +++ b/app/assets/javascripts/discourse/components/search-result.js.es6 @@ -0,0 +1,11 @@ +export default Ember.Component.extend({ + tagName: 'ul', + + _highlightOnInsert: function() { + const term = this.get('controller.term'); + if(!_.isEmpty(term)) { + this.$('.blurb').highlight(term.split(/\s+/), {className: 'search-highlight'}); + this.$('.topic-title').highlight(term.split(/\s+/), {className: 'search-highlight'} ); + } + }.on('didInsertElement') +}); diff --git a/app/assets/javascripts/discourse/components/search-text-field.js.es6 b/app/assets/javascripts/discourse/components/search-text-field.js.es6 new file mode 100644 index 0000000000..e833f66110 --- /dev/null +++ b/app/assets/javascripts/discourse/components/search-text-field.js.es6 @@ -0,0 +1,7 @@ +import TextField from 'discourse/components/text-field'; + +export default TextField.extend({ + placeholder: function() { + return this.get('searchContextEnabled') ? "" : I18n.t('search.title'); + }.property('searchContextEnabled') +}); diff --git a/app/assets/javascripts/discourse/lib/search-for-term.js.es6 b/app/assets/javascripts/discourse/lib/search-for-term.js.es6 index 12e05eb739..478b1bc6e9 100644 --- a/app/assets/javascripts/discourse/lib/search-for-term.js.es6 +++ b/app/assets/javascripts/discourse/lib/search-for-term.js.es6 @@ -4,7 +4,7 @@ function searchForTerm(term, opts) { if (!opts) opts = {}; // Only include the data we have - var data = { term: term, include_blurbs: 'true' }; + const data = { term: term, include_blurbs: 'true' }; if (opts.typeFilter) data.type_filter = opts.typeFilter; if (opts.searchForId) data.search_for_id = true; @@ -22,7 +22,7 @@ function searchForTerm(term, opts) { if (!results.posts) { results.posts = []; } if (!results.categories) { results.categories = []; } - var topicMap = {}; + const topicMap = {}; results.topics = results.topics.map(function(topic){ topic = Topic.create(topic); topicMap[topic.id] = topic; @@ -44,23 +44,23 @@ function searchForTerm(term, opts) { return Discourse.Category.list().findProperty('id', category.id); }).compact(); - var r = results.grouped_search_result; + const r = results.grouped_search_result; results.resultTypes = []; // TODO: consider refactoring front end to take a better structure [['topic','posts'],['user','users'],['category','categories']].forEach(function(pair){ - var type = pair[0], name = pair[1]; - if(results[name].length > 0) { + const type = pair[0], name = pair[1]; + if (results[name].length > 0) { results.resultTypes.push({ results: results[name], - displayType: (opts.searchContext && opts.searchContext.type === 'topic' && type === 'topic') ? 'post' : type, - type: type, + componentName: "search-result-" + ((opts.searchContext && opts.searchContext.type === 'topic' && type === 'topic') ? 'post' : type), + type, more: r['more_' + name] }); } }); - var noResults = !!(results.topics.length === 0 && + const noResults = !!(results.topics.length === 0 && results.posts.length === 0 && results.users.length === 0 && results.categories.length === 0); diff --git a/app/assets/javascripts/discourse/templates/components/search-result-category.hbs b/app/assets/javascripts/discourse/templates/components/search-result-category.hbs new file mode 100644 index 0000000000..071eba1841 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/search-result-category.hbs @@ -0,0 +1,7 @@ +{{#each results as |result|}} +
  • + + {{category-badge result}} + +
  • +{{/each}} diff --git a/app/assets/javascripts/discourse/templates/components/search-result-post.hbs b/app/assets/javascripts/discourse/templates/components/search-result-post.hbs new file mode 100644 index 0000000000..da9737daf4 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/search-result-post.hbs @@ -0,0 +1,12 @@ +{{#each results as |result|}} + + + {{i18n 'search.post_format' post_number=result.post_number username=result.username}} + + {{#unless site.mobileView}} + + {{{unbound result.blurb}}} + + {{/unless}} + +{{/each}} diff --git a/app/assets/javascripts/discourse/templates/components/search-result-topic.hbs b/app/assets/javascripts/discourse/templates/components/search-result-topic.hbs new file mode 100644 index 0000000000..66e3989387 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/search-result-topic.hbs @@ -0,0 +1,14 @@ +{{#each results as |result|}} +
  • + + + {{topic-status topic=result.topic disableActions=true}}{{unbound result.topic.title}}{{category-badge result.topic.category}} + + {{#unless site.mobileView}} + + {{format-age result.created_at}} - {{{unbound result.blurb}}} + + {{/unless}} + +
  • +{{/each}} diff --git a/app/assets/javascripts/discourse/templates/components/search-result-user.hbs b/app/assets/javascripts/discourse/templates/components/search-result-user.hbs new file mode 100644 index 0000000000..9cf46a1519 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/search-result-user.hbs @@ -0,0 +1,8 @@ +{{#each results as |result|}} +
  • + + {{avatar result imageSize="small"}} + {{unbound result.username}} + +
  • +{{/each}} diff --git a/app/assets/javascripts/discourse/templates/search.hbs b/app/assets/javascripts/discourse/templates/search.hbs index 6ad3c15955..60bf45870c 100644 --- a/app/assets/javascripts/discourse/templates/search.hbs +++ b/app/assets/javascripts/discourse/templates/search.hbs @@ -1,4 +1,5 @@ -{{view "search-text-field" value=term searchContextEnabled=searchContextEnabled searchContext=searchContext id="search-term"}} +{{search-text-field value=term searchContextEnabled=searchContextEnabled id="search-term"}} +
    {{#if searchContext}}
    {{else}} - {{#each resultType in content.resultTypes}} + {{#each content.resultTypes as |resultType|}}
    • {{resultType.name}}
    • - {{view "search-results-type" type=resultType.type displayType=resultType.displayType content=resultType.results}} + {{component resultType.componentName results=resultType.results term=term}}
    {{#if resultType.more}} diff --git a/app/assets/javascripts/discourse/templates/search/category_result.hbs b/app/assets/javascripts/discourse/templates/search/category_result.hbs deleted file mode 100644 index ad0c40031b..0000000000 --- a/app/assets/javascripts/discourse/templates/search/category_result.hbs +++ /dev/null @@ -1,3 +0,0 @@ - - {{category-badge this}} - diff --git a/app/assets/javascripts/discourse/templates/search/post_result.hbs b/app/assets/javascripts/discourse/templates/search/post_result.hbs deleted file mode 100644 index 197ba51641..0000000000 --- a/app/assets/javascripts/discourse/templates/search/post_result.hbs +++ /dev/null @@ -1,10 +0,0 @@ - - - {{i18n 'search.post_format' post_number=post_number username=username}} - - {{#unless controller.site.mobileView}} - - {{{unbound blurb}}} - - {{/unless}} - diff --git a/app/assets/javascripts/discourse/templates/search/topic_result.hbs b/app/assets/javascripts/discourse/templates/search/topic_result.hbs deleted file mode 100644 index ef58e870d9..0000000000 --- a/app/assets/javascripts/discourse/templates/search/topic_result.hbs +++ /dev/null @@ -1,10 +0,0 @@ - - - {{topic-status topic=topic disableActions=true}}{{unbound topic.title}}{{category-badge topic.category}} - - {{#unless controller.site.mobileView}} - - {{format-age created_at}} - {{{unbound blurb}}} - - {{/unless}} - diff --git a/app/assets/javascripts/discourse/templates/search/user_result.hbs b/app/assets/javascripts/discourse/templates/search/user_result.hbs deleted file mode 100644 index 057d214573..0000000000 --- a/app/assets/javascripts/discourse/templates/search/user_result.hbs +++ /dev/null @@ -1,4 +0,0 @@ - - {{avatar this imageSize="small"}} - {{unbound username}} - diff --git a/app/assets/javascripts/discourse/views/search-results-type.js.es6 b/app/assets/javascripts/discourse/views/search-results-type.js.es6 deleted file mode 100644 index f90de08c1e..0000000000 --- a/app/assets/javascripts/discourse/views/search-results-type.js.es6 +++ /dev/null @@ -1,15 +0,0 @@ -export default Ember.CollectionView.extend({ - tagName: 'ul', - itemViewClass: Discourse.GroupedView.extend({ - tagName: 'li', - classNameBindings: ['selected'], - templateName: Discourse.computed.fmt('parentView.displayType', "search/%@_result") - }), - didInsertElement: function(){ - var term = this.get('controller.term'); - if(!_.isEmpty(term)) { - this.$('.blurb').highlight(term.split(/\s+/), {className: 'search-highlight'}); - this.$('.topic-title').highlight(term.split(/\s+/), {className: 'search-highlight'} ); - } - } -}); diff --git a/app/assets/javascripts/discourse/views/search-text-field.js.es6 b/app/assets/javascripts/discourse/views/search-text-field.js.es6 deleted file mode 100644 index 4db554a7de..0000000000 --- a/app/assets/javascripts/discourse/views/search-text-field.js.es6 +++ /dev/null @@ -1,27 +0,0 @@ -/** - This is a text field that supports a dynamic placeholder based on search context. - - @class SearchTextField - @extends Discourse.TextField - @namespace Discourse - @module Discourse -**/ - -import TextField from 'discourse/components/text-field'; - -export default TextField.extend({ - - /** - A dynamic placeholder for the search field based on our context - - @property placeholder - **/ - placeholder: function() { - - if(this.get('searchContextEnabled')){ - return ""; - } - - return I18n.t('search.title'); - }.property('searchContextEnabled') -}); diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js index 9a3b8c092d..83e3490976 100644 --- a/app/assets/javascripts/main_include.js +++ b/app/assets/javascripts/main_include.js @@ -45,6 +45,7 @@ //= require ./discourse/views/cloaked //= require ./discourse/components/combo-box //= require ./discourse/views/button +//= require ./discourse/components/search-result //= require ./discourse/components/dropdown-button //= require ./discourse/components/notifications-button //= require ./discourse/components/topic-notifications-button diff --git a/test/javascripts/acceptance/search-test.js.es6 b/test/javascripts/acceptance/search-test.js.es6 new file mode 100644 index 0000000000..702cb1dde0 --- /dev/null +++ b/test/javascripts/acceptance/search-test.js.es6 @@ -0,0 +1,18 @@ +import { acceptance } from "helpers/qunit-helpers"; +acceptance("Search"); + +test("search", (assert) => { + visit("/"); + + click('#search-button'); + + andThen(() => { + assert.ok(exists('#search-term'), 'it shows the search bar'); + assert.ok(!exists('#search-dropdown .results ul li'), 'no results by default'); + }); + + fillIn('#search-term', 'dev'); + andThen(() => { + assert.ok(exists('#search-dropdown .results ul li'), 'it shows results'); + }); +}); diff --git a/test/javascripts/fixtures/search-fixtures.js.es6 b/test/javascripts/fixtures/search-fixtures.js.es6 index 73e405b520..b9f30c9fde 100644 --- a/test/javascripts/fixtures/search-fixtures.js.es6 +++ b/test/javascripts/fixtures/search-fixtures.js.es6 @@ -1 +1,666 @@ -export default {"/search": {"posts":[{"id":61693,"name":"Manoel Lemos","username":"mlemos","avatar_template":"/letter_avatar/mlemos/{size}/2.png","uploaded_avatar_id":null,"created_at":"2014-07-15T21:53:56.337-04:00","cooked":"

    Gents, I'm using an very interesting tool to drive traffic across the different destinations of my community. One destination is the discussion forum running Discourse and other is the blog running WordPress.

    \n\n

    This tool is called Hello Bar and it is basically a bar that stays in the top of the site and links to other places or can be used to collect emails or social networks followers. It worked fine when I added the bar to my Wordpress Blog, but it failed (a bit) when added to my Discourse forum.

    \n\n

    You can check the two situations here:

    \n\n\n\n

    The error is that the Discourse page was suposed to be pushed down by the Hello Bar, but it isn't being fully pushed down. Then the layout is broken.

    \n\n

    Can anyone help me with this?

    ","post_number":1,"post_type":1,"updated_at":"2014-07-15T21:53:56.337-04:00","reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":30,"incoming_link_count":2,"reads":40,"score":19.5,"yours":false,"topic_slug":null,"topic_id":17638,"display_username":"Manoel Lemos","primary_group_name":null,"version":2,"can_edit":false,"can_delete":false,"can_recover":false,"user_title":null,"actions_summary":[{"id":2,"count":0,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"admin":false,"staff":false,"user_id":7595,"hidden":false,"hidden_reason_id":null,"trust_level":1,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false,"blurb":"...the discussion forum running Discourse and other is the blog running WordPress. This tool is called Hello Bar and it is basically a bar that stays in the top of the site and links to other places or can be..."},{"id":56514,"name":"Ova Light","username":"ChrisOva","avatar_template":"/letter_avatar/chrisova/{size}/2.png","uploaded_avatar_id":null,"created_at":"2014-06-13T02:56:00.794-04:00","cooked":"

    Do you know what are the elements whichI need to modify in the custom CSS editor in order for these parts of the forum to change color ?

    \n\n

    \n\n

    Is anyone here willing to work for a theme? (50$)

    \n\n

    Thanks for reading !

    ","post_number":1,"post_type":1,"updated_at":"2014-06-13T03:05:31.865-04:00","reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":24,"incoming_link_count":1,"reads":31,"score":57.4,"yours":false,"topic_slug":null,"topic_id":16504,"display_username":"Ova Light","primary_group_name":null,"version":5,"can_edit":false,"can_delete":false,"can_recover":false,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"admin":false,"staff":false,"user_id":10477,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":"downloaded local copies of images","can_view_edit_history":true,"wiki":false,"blurb":"Do you know what are the elements whichI need to modify in the custom CSS editor in order for these parts of the forum to change color ? 00000674.png 00000674.png 2644x932 239 KB Is anyone here wil..."},{"id":40862,"name":"Moe","username":"Moe","avatar_template":"/letter_avatar/moe/{size}/2.png","uploaded_avatar_id":null,"created_at":"2014-02-14T18:35:48.395-05:00","cooked":"

    Hello,

    \n\n

    First and foremost, thanks to the developers of Discourse and everyone who is making great efforts in creating an awesome forum!

    \n\n

    I am a Discouse newbie and I am trying to do sso using the CAS sso auth plugin in https://github.com/eriko/cas_sso .

    \n\n

    I am currently making some experiments in authenticating users in Discourse using a test web app I am using as CAS server. I have set up discourse in a VM (vagrant) whereas the CAS server runs in the host machine. I have installed the sso plugin and set the values as indicated (i.e. cas_sso_host, cas_sso_port, etc, as it is a non-standard CAS server), with ssh disabled.

    \n\n

    One thing that I noticed is that it seems that there seems to be a cross domain protection acting, the following is a message that appears in the console when I go to login in vagrant discourse:

    \n\n

    Started GET \"/session/csrf\" for 10.0.2.2 at 2014-02-14 17:53:12 -0500\nProcessing by SessionController#csrf as */*\nCompleted 200 OK in 1ms (Views: 0.2ms | ActiveRecord: 0.0ms)
    \n\n

    Here 10.0.2.2 is the ip of the CAS server. This server is accessible from vagrant, I tested access to the CAS server URLs using lynx (everything works fine). I should likely mention also that cas_sso_login_url is set to /login and cas_sso_path is set to /cas in the Discourse sso plugin options. I have been also monitoring requests to /cas/login in the CAS server and none of my login attempts from Discourse have made it there.

    \n\n

    I am brand new to Discourse and Ruby, so I am having a bit of a struggle here and any insight would be greatly appreciated.

    \n\n

    Thank you very much in advance for your time and excellent disposition.

    \n\n

    Best regards,
    Moe

    ","post_number":1,"post_type":1,"updated_at":"2014-02-14T18:35:48.395-05:00","reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":20,"incoming_link_count":60,"reads":57,"score":357.4,"yours":false,"topic_slug":null,"topic_id":12731,"display_username":"Moe","primary_group_name":null,"version":2,"can_edit":false,"can_delete":false,"can_recover":false,"user_title":null,"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"admin":false,"staff":false,"user_id":8596,"hidden":false,"hidden_reason_id":null,"trust_level":1,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false,"blurb":"Hello, First and foremost, thanks to the developers of Discourse and everyone who is making great efforts..."},{"id":18691,"name":"Sam Saffron","username":"sam","avatar_template":"/letter_avatar/sam/{size}/2.png","uploaded_avatar_id":null,"created_at":"2013-05-30T21:17:09.213-04:00","cooked":"

    For the feature I was working on yesterday, @codinghorror wanted a rather complex sentence.

    \n\n

    There is 1 unread and 9 new topics remaining, or browse other topics in [category]
    \n\n

    This seemingly simple sentence was a royal nightmare to localize with our existing localization system. Think through all the permutations:

    \n\n

    \"There are 2 unread and 9 new topics remaining, or browse other topics in [category]\"
    \"There are 2 unread and 1 new topic remaining, or browse other topics in [category]\"
    \"There is 1 unread and 1 new topic remaining, or browse other topics in [category]\"

    \n\n

    Trouble with our current system was that you have no sane way of building these kind of sentences, see: http://stackoverflow.com/questions/16825932/clean-pattern-for-localizing-sentences-in-rails-i18n , you can only easily localize one count in a non compound sentence.

    \n\n

    To alleviate this I introduce a new mechanism that is available (optionally) client side. The above sentence is localized using:

    \n\n

    There {UNREAD, plural, \n   one {is <a href='/unread'>1 unread</a>} \n   other {are <a href='/unread'># unread</a>}\n} and {NEW, plural, \n  one {<a href='/new'>1 new</a> topic} \n  other {<a href='/new'># new</a> topics}} remaining, or browse other topics in {catLink}
    \n\n

    The client localization file has a special rule, if a key ends with _MF it is interpreted as a MessageFormat message, then to access it on the client you use:

    \n\n

    I18n.messageFormat(\"topic.read_more_in_category_MF\", {\"UNREAD\": unreadTopics, \"NEW\": newTopics, catLink: opts.catLink})
    \n\n

    You can see a few other examples here:

    \n\n

    \n\n

    We do not plan at the moment to move to message format style localization everywhere, however it is nice to have this extra bit of flexibility that lets us generate interesting sentences.

    \n\n

    On a technical note, this feature adds almost no weight to the client side JavaScript, all message format strings are pre-compiled into a JavaScript function with no external dependencies. The tricks used can be viewed here: https://github.com/discourse/discourse/blob/master/lib/js_locale_helper.rb

    \n\n

    1 minute Message Format primer

    \n\n

    f = \"hello\"\nf() => \"hello\"\n\nf = \"hello {WORLD}\"\nf(WORLD: \"world\") => \"hello world\" \nf(WORLD: \"other world\") => \"hello other world\" \n\nf = \"I have {HATS, plural, one {one hat} other {# hats}}\"\nf(HATS: 1) => \"I have one hat\"\nf(HATS: 10) => \"I have 10 hats\" \n\nf = \"I am a {GENDER, select, male {boy}, female {girl}}\"\nf(GENDER: \"male\") => \"I am a boy\"\nf(GENDER: \"female\") => \"I am a girl\"
    \n\n

    Our plan for now is to use this strategically, however it is worth noting that this gives more flexibility in localization, for example in czech, the plural form is rather interesting as @kuba could attest :

    \n\n

    MessageFormat.locale.cs = function (n) {\n  if (n == 1) {\n    return 'one';\n  }\n  if (n == 2 || n == 3 || n == 4) {\n    return 'few';\n  }\n  return 'other';\n};
    \n\n

    Message Format supports this fine, built in.

    \n\n

    f = \"I have {HATS, plural, one {one hat} other {# hats} few {# few hats}}\"
    ","post_number":1,"post_type":1,"updated_at":"2013-05-30T21:31:57.135-04:00","reply_count":1,"reply_to_post_number":null,"quote_count":0,"avg_time":33,"incoming_link_count":44,"reads":68,"score":450.25,"yours":false,"topic_slug":null,"topic_id":7035,"display_username":"Sam Saffron","primary_group_name":"discourse","version":3,"can_edit":false,"can_delete":false,"can_recover":false,"user_title":"co-founder","actions_summary":[{"id":2,"count":6,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":true,"admin":true,"staff":true,"user_id":1,"hidden":false,"hidden_reason_id":null,"trust_level":3,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false,"blurb":"...hub.com/discourse/discourse/blob/master/lib/js_locale_helper.rb 1 minute Message Format primer f = \"hello\" f() = > \"hello\" f = \"hello {WORLD}\" f(WORLD: \"world\") = > \"hello world\" f(WORLD: \"other world\") =..."},{"id":41969,"name":"Sam Saffron","username":"sam","avatar_template":"/letter_avatar/sam/{size}/2.png","uploaded_avatar_id":null,"created_at":"2014-02-25T03:30:34.423-05:00","cooked":"

    Discourse now ships with official hooks to perform auth offsite.

    \n\n

    The Problem

    \n\n

    Many sites wish to integrate with a Discourse site, however want to keep all user registration in a separate site. In such a setup all Login operations should be outsourced to a different site.

    \n\n

    What if I would like SSO in conjunction with existing auth?

    \n\n

    The intention around SSO is to replace Discourse authentication, if you would like to add a new provider see existing plugins such as: https://meta.discourse.org/t/vk-com-login-vkontakte/12987

    \n\n

    Enabling SSO

    \n\n

    To enable single sign on you have 3 settings you need to fill out:

    \n\n

    \n\n

    enable_sso : must be enabled, global switch
    sso_url: the offsite URL users will be sent to when attempting to log on
    sso_secret: a secret string used to hash SSO payloads. Ensures payloads are authentic.

    \n\n

    Once enable_sso is set to true:

    \n\n
      \n
    • Clicking on login or avatar will, redirect you to /session/sso which in turn will redirect users to sso_url with a signed payload.
    • \n
    • Users will not be allowed to \"change password\". That field is removed from the user profile.
    • \n
    • Users will no longer be able to use Discourse auth (username/password, google, etc)
    • \n
    \n\n

    What if you check it by mistake?

    \n\n

    If you check enable_sso by mistake and need to revert to the original state and no longer have access to the admin panel

    \n\n

    run:

    \n\n

    ./launcher enter app\nrails c\nirb > SiteSetting.enable_sso = false\nirb > exit\nexit
    \n\n

    Implementing SSO on your site

    \n\n

    Discourse will redirect clients to sso_url with a signed payload: (say sso_url is https://somesite.com/sso)

    \n\n

    You will receive incoming traffic with the following

    \n\n

    https://somesite.com/sso?sso=PAYLOAD&sig=SIG

    \n\n

    The payload is a Base64 encoded string comprising of a nonce. The payload is always a valid querystring.

    \n\n

    For example, if the nonce is ABCD. raw_payload will be:

    \n\n

    nonce=ABCD, this raw payload is base 64 encoded.

    \n\n

    The endpoint being called must

    \n\n
      \n
    1. Validate the signature, ensure that HMAC-SHA256 of sso_secret, PAYLOAD is equal to the sig
    2. \n
    3. Perform whatever authentication it has to
    4. \n
    5. Create a new payload with nonce, email, external_id and optionally (username, name)
    6. \n
    7. Base64 encode the payload
    8. \n
    9. Calculate a HMAC-SHA256 hash of the using sso_secret as the key and Base64 encoded payload as text
    10. \n
    11. Redirect back to http://discourse_site/session/sso_login?sso=payload&sig=sig\n
    12. \n
    \n\n

    Discourse will validate that the nonce is valid (if valid it will expire it right away so it can no longer be used) it will attempt to:

    \n\n
      \n
    1. Log the user on by looking up an already associated external_id in the SingleSignOnRecord model
    2. \n
    3. Log the user on by using the email provided (updating external_id)
    4. \n
    5. Create a new account for the user providing (email, username, name) updating external_id
    6. \n
    \n\n

    Security concerns

    \n\n

    The nonce (one time token) will expire automatically after 10 minutes. This means that as soon as the user is redirected to your site they have 10 minutes to log in / create a new account.

    \n\n

    The protocol is safe against replay attacks as nonce may only be used once.

    \n\n

    Reference implementation

    \n\n

    Discourse contains a reference implementation of the SSO class:

    \n\n\n\n\n

    A trivial implementation would be:

    \n\n

    class DiscourseSsoController < ApplicationController\n  def sso\n    secret = \"MY_SECRET_STRING\"\n    sso = SingleSignOn.parse(request.query_string, secret)\n    sso.email = \"user@email.com\"\n    sso.name = \"Bill Hicks\"\n    sso.username = \"bill@hicks.com\"\n    sso.external_id = \"123\" # unique to your application\n    sso.sso_secret = secret\n\n    redirect_to sso.to_url(\"http://l.discourse/session/sso_login\")\n  end\nend
    \n\n

    Transitioning to and from single sign on.

    \n\n

    The system always trusts emails provided by the single sign on endpoint. This means that if you had an existing account in the past on Discourse with SSO disabled, SSO will simply re-use it and avoid creating a new account.

    \n\n

    If you ever turn off SSO, users will be able to reset passwords and gain access back to their accounts.

    \n\n

    Real world example:

    \n\n

    Given the following settings:

    \n\n

    Discourse domain: http://discuss.example.com
    SSO url : http://www.example.com/discourse/sso
    SSO secret: d836444a9e4084d5b224a60c208dce14

    \n\n

    User attempt to login

    \n\n
      \n
    • Nonce is generated: cb68251eefb5211e58c00ff1395f0c0b

    • \n
    • Raw payload is generated: nonce=cb68251eefb5211e58c00ff1395f0c0b

    • \n
    • Payload is Base64 encoded: bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI=\\n

    • \n
    • Payload is URL encoded: bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI%3D%0A

    • \n
    • HMAC-SHA256 is generated on the encoded payload: 2828aa29899722b35a2f191d34ef9b3ce695e0e6eeec47deb46d588d70c7cb56

    • \n
    \n\n

    Finally browser is redirected to:

    \n\n

    http://www.example.com/discourse/sso?sso=bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI%3D%0A&sig=2828aa29899722b35a2f191d34ef9b3ce695e0e6eeec47deb46d588d70c7cb56

    \n\n

    On the other end

    \n\n
      \n
    1. Payload is validated using HMAC-SHA256, if the sig mismatches, process aborts.
    2. \n
    3. By reversing the steps above nonce is extracted.
    4. \n
    \n\n

    User logs in:

    \n\n

    name: sam\nexternal_id: hello123\nemail: test@test.com\nusername: samsam
    \n\n
    • Unsigned payload is generated:
    \n\n

    nonce=cb68251eefb5211e58c00ff1395f0c0b&name=sam&username=samsam&email=test%40test.com&external_id=hello123

    \n\n

    order does not matter, values are URL encoded

    \n\n
    • Payload is Base64 encoded
    \n\n

    \"bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGImbmFtZT1z\\nYW0mdXNlcm5hbWU9c2Ftc2FtJmVtYWlsPXRlc3QlNDB0ZXN0LmNvbSZleHRl\\ncm5hbF9pZD1oZWxsbzEyMw==\\n

    \n\n
    • Payload is URL encoded
    \n\n

    bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGImbmFtZT1z%0AYW0mdXNlcm5hbWU9c2Ftc2FtJmVtYWlsPXRlc3QlNDB0ZXN0LmNvbSZleHRl%0Acm5hbF9pZD1oZWxsbzEyMw%3D%3D%0A

    \n\n
    • Payload is signed
    \n\n

    1c884222282f3feacd76802a9dd94e8bc8deba5d619b292bed75d63eb3152c0b

    \n\n
    • Browser redirects to:
    \n\n

    http://discuss.example.com/session/sso_login?sso=bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGImbmFtZT1z%0AYW0mdXNlcm5hbWU9c2Ftc2FtJmVtYWlsPXRlc3QlNDB0ZXN0LmNvbSZleHRl%0Acm5hbF9pZD1oZWxsbzEyMw%3D%3D%0A&sig=1c884222282f3feacd76802a9dd94e8bc8deba5d619b292bed75d63eb3152c0b

    \n\n

    Future work

    \n\n
      \n
    • We would like to gather more reference implementations for SSO on other platforms. If you have one please post to the Extensibility / SSO category.

    • \n
    • Add session expiry and/or revalidation logic, so users are not logged in forever.

    • \n
    • Create an API endpoint to log off users, in case somebody logs off the main site.

    • \n
    • Consider adding a discourse_sso gem to make it easier to implement in Ruby.

    • \n
    \n\n

    Advanced Features

    \n\n\n\n

    Updates:

    \n\n

    2-Feb-2014

    \n\n
      \n
    • use HMAC-SHA256 instead of SHA256. This is more secure and cleanly separates key from payload.
    • \n
    • removed return_url, the system will automatically redirect users back to the page they were on after login
    • \n
    \n\n

    4-April-2014

    \n\n
    • Added example
    \n\n

    24-April-2014

    \n\n
    • Make note of custom user fields.
    \n\n

    01-August-2014

    \n\n
    • Changed Rails console instructions to assume Docker setup
    ","post_number":1,"post_type":1,"updated_at":"2014-08-01T17:44:20.164-04:00","reply_count":5,"reply_to_post_number":null,"quote_count":0,"avg_time":41,"incoming_link_count":2284,"reads":536,"score":12367.25,"yours":false,"topic_slug":null,"topic_id":13045,"display_username":"Sam Saffron","primary_group_name":"discourse","version":12,"can_edit":false,"can_delete":false,"can_recover":false,"user_title":"co-founder","actions_summary":[{"id":2,"count":41,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":true,"admin":true,"staff":true,"user_id":1,"hidden":false,"hidden_reason_id":null,"trust_level":3,"deleted_at":null,"user_deleted":false,"edit_reason":"","can_view_edit_history":true,"wiki":false,"blurb":"...ocess aborts. By reversing the steps above nonce is extracted. User logs in: name: sam external_id: hello123 email: test@test.com username: samsam Unsigned payload is generated: nonce=cb68251eefb5211e58c00..."}],"topics":[{"id":17638,"title":"Hello Bar integration issues","fancy_title":"Hello Bar integration issues","slug":"hello-bar-integration-issues","posts_count":5,"reply_count":2,"highest_post_number":5,"image_url":null,"created_at":"2014-07-15T21:53:56.226-04:00","last_posted_at":"2014-07-15T22:51:01.719-04:00","bumped":true,"bumped_at":"2014-07-15T22:01:39.716-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":84,"like_count":1,"has_summary":false,"archetype":"regular","last_poster_username":null,"category_id":6,"posters":[]},{"id":16504,"title":"Hello, I have two questions :D","fancy_title":"Hello, I have two questions :D","slug":"hello-i-have-two-questions-d","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":"/uploads/default/_optimized/b35/289/b2338e0876_690x243.png","created_at":"2014-06-13T02:56:00.695-04:00","last_posted_at":"2014-06-13T06:27:28.903-04:00","bumped":true,"bumped_at":"2014-06-13T06:27:28.903-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":97,"like_count":3,"has_summary":false,"archetype":"regular","last_poster_username":null,"category_id":14,"posters":[]},{"id":12731,"title":"CAS sso auth plugin question","fancy_title":"CAS sso auth plugin question","slug":"cas-sso-auth-plugin-question","posts_count":15,"reply_count":5,"highest_post_number":15,"image_url":null,"created_at":"2014-02-14T18:35:48.242-05:00","last_posted_at":"2014-02-25T20:50:16.306-05:00","bumped":true,"bumped_at":"2014-02-25T20:50:16.306-05:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":440,"like_count":2,"has_summary":false,"archetype":"regular","last_poster_username":null,"category_id":5,"posters":[]},{"id":7035,"title":"Message Format support for localization","fancy_title":"Message Format support for localization","slug":"message-format-support-for-localization","posts_count":7,"reply_count":5,"highest_post_number":7,"image_url":"http://meta.discourse.org/assets/favicons/github-65cd2c8ba8283c55eca7f9e257fa7604.png","created_at":"2013-05-30T21:17:08.971-04:00","last_posted_at":"2014-09-03T03:11:36.653-04:00","bumped":true,"bumped_at":"2014-09-03T03:11:36.653-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":699,"like_count":10,"has_summary":false,"archetype":"regular","last_poster_username":null,"category_id":2,"posters":[]},{"id":13045,"title":"Official Single-Sign-On for Discourse","fancy_title":"Official Single-Sign-On for Discourse","slug":"official-single-sign-on-for-discourse","posts_count":61,"reply_count":37,"highest_post_number":64,"image_url":"/uploads/default/_optimized/07c/3bf/3fa1d69ceb_690x207.png","created_at":"2014-02-25T03:30:34.321-05:00","last_posted_at":"2014-08-01T17:44:56.523-04:00","bumped":true,"bumped_at":"2014-08-07T13:27:14.684-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":13377,"like_count":74,"has_summary":true,"archetype":"regular","last_poster_username":null,"category_id":10,"posters":[]}],"users":[{"id":3836,"username":"HelloWorld","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/helloworld/{size}/2.png"},{"id":6315,"username":"instagra","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/instagra/{size}/2.png"},{"id":1743,"username":"hello_jmk","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/hello_jmk/{size}/2.png"},{"id":9349,"username":"hellocate","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/hellocate/{size}/2.png"},{"id":10596,"username":"hellooperator","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/hellooperator/{size}/2.png"}],"categories":[],"grouped_search_result":{"more_posts":true,"more_users":null,"more_categories":null,"post_ids":[61693,56514,40862,18691,41969],"user_ids":[3836,6315,1743,9349,10596],"category_ids":[]}}}; +export default { + "search/query": { +   "posts": [ +     { +       "id": 3833, +       "name": "Bill Dudney", +       "username": "bdudney", +       "avatar_template": "/user_avatar/meta.discourse.org/bdudney/{size}/8343_1.png", +       "uploaded_avatar_id": 8343, +       "created_at": "2013-02-07T17:46:57.469Z", +       "cooked": "

    I've gotten vagrant up and running with a development environment but it's taking forever to load.<\/p>\n\n

    For example http://192.168.10.200:3000/<\/a> takes tens of seconds to load.<\/p>\n\n

    I'm running the whole stack on a new rMBP with OS X 10.8.2.<\/p>\n\n

    Any ideas of what I've done wrong? Or is this just a function of being on the bleeding edge?<\/p>\n\n

    Thanks,<\/p>\n\n

    -bd<\/p>", +       "post_number": 1, +       "post_type": 1, +       "updated_at": "2013-02-07T17:46:57.469Z", +       "like_count": 0, +       "reply_count": 1, +       "reply_to_post_number": null, +       "quote_count": 0, +       "avg_time": 24, +       "incoming_link_count": 4422, +       "reads": 327, +       "score": 21978.4, +       "yours": false, +       "topic_id": 2179, +       "topic_slug": "development-mode-super-slow", +       "display_username": "Bill Dudney", +       "primary_group_name": null, +       "version": 2, +       "can_edit": false, +       "can_delete": false, +       "can_recover": false, +       "user_title": null, +       "actions_summary": [ +         { +           "id": 2, +           "count": 0, +           "hidden": false, +           "can_act": false +         }, +         { +           "id": 3, +           "count": 0, +           "hidden": false, +           "can_act": false +         }, +         { +           "id": 4, +           "count": 0, +           "hidden": false, +           "can_act": false +         }, +         { +           "id": 5, +           "count": 0, +           "hidden": true, +           "can_act": false +         }, +         { +           "id": 6, +           "count": 0, +           "hidden": false, +           "can_act": false +         }, +         { +           "id": 7, +           "count": 0, +           "hidden": false, +           "can_act": false +         }, +         { +           "id": 8, +           "count": 0, +           "hidden": false, +           "can_act": false +         } +       ], +       "moderator": false, +       "admin": false, +       "staff": false, +       "user_id": 1828, +       "hidden": false, +       "hidden_reason_id": null, +       "trust_level": 1, +       "deleted_at": null, +       "user_deleted": false, +       "edit_reason": null, +       "can_view_edit_history": true, +       "wiki": false, +       "blurb": "I've gotten vagrant up and running with a development environment but it's taking forever to load. For example http://192.168.10.200:3000/ takes..." +     }, +     { +       "id": 48887, +       "name": "Arpit Jalan", +       "username": "techAPJ", +       "avatar_template": "/user_avatar/meta.discourse.org/techapj/{size}/3281_1.png", +       "uploaded_avatar_id": 3281, +       "created_at": "2014-04-12T22:22:07.930Z", +       "cooked": "

    So you want to set up Discourse on Ubuntu to hack on and develop with?<\/p>\n\n

    We'll assume that you don't have Ruby/Rails/Postgre/Redis installed on your Ubuntu system. Let's begin!<\/p>\n\n

    Although this guide assumes that you are using Ubuntu, but the set-up instructions will work fine for any Debian based ditribution.<\/em><\/p>\n\n

    (If you want to install Discourse for production use, see our install guide<\/a>)<\/em><\/p>\n\n

    Install Discourse Dependencies<\/h2>\n\n

    Run this script<\/a> in terminal, to setup Rails development environment:<\/p>\n\n

    bash <(wget -qO- https://raw.githubusercontent.com/techAPJ/install-rails/master/linux)<\/code><\/pre>\n\n

    \nlinux_script.png<\/span>770x211 9.62 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

    This will install following new packages on your system:<\/p>\n\n

      \n
    • Git<\/a><\/li>\n
    • rbenv<\/a><\/li>\n
    • ruby-build<\/a><\/li>\n
    • \nRuby<\/a> (stable)<\/li>\n
    • Rails<\/a><\/li>\n
    • PostgreSQL<\/a><\/li>\n
    • SQLite<\/a><\/li>\n
    • Redis<\/a><\/li>\n
    • Bundler<\/a><\/li>\n
    • ImageMagick<\/a><\/li>\n<\/ul>\n\n

      Install Phantomjs:<\/p>\n\n

      For 32 bit macine:<\/p>\n\n

      cd /usr/local/share\nsudo wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-linux-i686.tar.bz2\nsudo tar xvf phantomjs-1.9.8-linux-i686.tar.bz2\nsudo rm phantomjs-1.9.8-linux-i686.tar.bz2\nsudo ln -s /usr/local/share/phantomjs-1.9.8-linux-i686/bin/phantomjs /usr/local/bin/phantomjs\ncd<\/code><\/pre>\n\n

      For 64 bit machine:<\/p>\n\n

      cd /usr/local/share\nsudo wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-linux-x86_64.tar.bz2\nsudo tar xvf phantomjs-1.9.8-linux-x86_64.tar.bz2\nsudo rm phantomjs-1.9.8-linux-x86_64.tar.bz2\nsudo ln -s /usr/local/share/phantomjs-1.9.8-linux-x86_64/bin/phantomjs /usr/local/bin/phantomjs\ncd<\/code><\/pre>\n\n

      \nphantomjs.png<\/span>969x171 10.1 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

      In case you have any of this package pre-installed and don't want to run entire script, see the script<\/a> and pick the packages you don't have currently installed. The script is fine-tuned for Discourse, and includes all the packages required for Discourse installation.<\/em><\/p>\n\n

      Now that we have installed Discourse dependencies, let's move on to install Discourse itself.<\/p>\n\n

      Clone Discourse<\/h2>\n\n

      Clone the Discourse repository in ~/discourse<\/code> folder:<\/p>\n\n

      git clone https://github.com/discourse/discourse.git ~/discourse<\/code><\/pre>\n\n

      \ngit_clone.png<\/span>967x137 7.73 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

      Setup Database<\/h2>\n\n

      Open psql prompt as postgre user<\/p>\n\n

      sudo -u postgres psql postgres<\/code><\/pre>\n\n

      \npg.png<\/span>725x187 5.79 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

      Create role with the same name as your ubuntu system username<\/strong> with discourse<\/em> as password:<\/p>\n\n

      CREATE ROLE discourse WITH LOGIN ENCRYPTED PASSWORD 'discourse' CREATEDB SUPERUSER;<\/code><\/pre>\n\n

      In the above command, I named the role as discourse<\/strong>, this means that my ubuntu system username is discourse<\/strong>. (It is necessary for role name to be same as system username, otherwise migrations will not run<\/em>)<\/p>\n\n

      Check that you have successfully created discourse<\/strong> role:<\/p>\n\n

      \\du<\/code><\/pre>\n\n

      \npg_user.png<\/span>725x185 7.5 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

      Create discourse_development<\/strong> and discourse_test<\/strong> database:<\/p>\n\n

      CREATE DATABASE discourse_development WITH OWNER discourse ENCODING 'UTF8' TEMPLATE template0;\nCREATE DATABASE discourse_test WITH OWNER discourse ENCODING 'UTF8' TEMPLATE template0;<\/code><\/pre>\n\n

      \npg_db.png<\/span>724x143 6.82 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

      Exit psql prompt by pressing ctrl<\/kbd>d<\/kbd><\/p>\n\n

      Now access psql prompt in discourse_development<\/strong> database as discourse<\/strong> user:<\/p>\n\n

      psql -d discourse_development -U discourse -h localhost<\/code><\/pre>\n\n

      When prompted for password, provide the password which you set at the time of creating role, if you followed the guide as is, the password is discourse<\/strong><\/p>\n\n

      Run following commands, separately:<\/p>\n\n

      CREATE EXTENSION pg_trgm;\nCREATE EXTENSION hstore;<\/code><\/pre>\n\n

      \npg_dev.png<\/span>726x316 13.4 KB<\/span><\/span>\n<\/div><\/a><\/div><\/p>\n\n

      Exit psql prompt by pressing ctrl<\/kbd>d<\/kbd><\/p>\n\n

      Now access psql prompt in discourse_test<\/strong> database as discourse<\/strong> user:<\/p>\n\n

      psql -d discourse_test -U discourse -h localhost<\/code><\/pre>\n\n

      When prompted for password, provide the password which you set at the time of creating role, if you followed the guide as is, the password is discourse<\/strong><\/p>\n\n

      Run following commands, separately:<\/p>\n\n

      CREATE EXTENSION pg_trgm;\nCREATE EXTENSION hstore;<\/code><\/pre>\n\n

      \npg_test.png<\/span>726x318 12.9 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

      Exit psql prompt by pressing ctrl<\/kbd>d<\/kbd><\/p>\n\n

      You have set-up the database successfully!<\/p>\n\n

      Bootstrap Discourse<\/h2>\n\n

      Switch to your Discourse folder:<\/p>\n\n

      cd ~/discourse<\/code><\/pre>\n\n

      Install the needed gems<\/p>\n\n

      bundle install<\/code><\/pre>\n\n

      \nbundle.png<\/span>724x248 9.75 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

      Now that you have successfully configured database connection, run this command:<\/p>\n\n

      bundle exec rake db:migrate db:test:prepare db:seed_fu<\/code><\/pre>\n\n

      Now, try running the specs: <\/p>\n\n

      bundle exec rake autospec<\/code><\/pre>\n\n

      \nspecs.png<\/span>717x263 8.63 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

      Start rails server:<\/p>\n\n

      bundle exec rails server<\/code><\/pre>\n\n

      \nserver.png<\/span>724x229 10.8 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

      You should now be able to connect to discourse app on http://localhost:3000<\/a> - try it out!<\/p>\n\n

      \ndiscourse_start.png<\/span>1919x525 20.3 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

      Configure Mail and Create New Account<\/h2>\n\n

      We will use MailCatcher<\/a> to serve emails in development environment. Install and run MailCatcher:<\/p>\n\n

      gem install mailcatcher\nmailcatcher --http-ip 0.0.0.0<\/code><\/pre>\n\n

      Create new account:<\/p>\n\n

      \ncreate_account.png<\/span>720x401 13.5 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

      Check confirmation email by going to MailCatcher web interface at http://localhost:1080/<\/a><\/p>\n\n

      \nmc_sign_up_email.png<\/span>1919x480 21.5 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

      If you did not receive the email, try running this in console<\/em>: bundle exec sidekiq -q default<\/code><\/p>\n\n

      Click the confirmation link and your account will be activated!<\/p>\n\n

      \ndisc_normal_acc.png<\/span>1919x430 21.8 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

      Access Admin<\/h2>\n\n

      Now, to make your account as admin, run the following commands in rails console:<\/p>\n\n

      RAILS_ENV=development bundle exec rails c\nu = User.last\nu.admin = true\nu.save<\/code><\/pre>\n\n

      \nadmin_console.png<\/span>722x462 31.7 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

      Once you execute the above commands successfully, check out your Discourse account again:<\/p>\n\n

      \nadmin_success.png<\/span>1919x1032 30.3 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

      Congratulations! You are now the admin of your own Discourse installation!<\/p>\n\n

      Happy hacking!<\/p>\n\n

      If anything needs to be improved in this guide, feel free to ask on meta.discourse.org<\/a>, or even better, submit a pull request<\/a>.<\/p>", +       "post_number": 1, +       "post_type": 1, +       "updated_at": "2015-06-22T17:24:20.607Z", +       "like_count": 15, +       "reply_count": 2, +       "reply_to_post_number": null, +       "quote_count": 0, +       "avg_time": 36, +       "incoming_link_count": 4680, +       "reads": 491, +       "score": 23815.8, +       "yours": false, +       "topic_id": 14727, +       "topic_slug": "beginners-guide-to-install-discourse-on-ubuntu-for-development", +       "display_username": "Arpit Jalan", +       "primary_group_name": null, +       "version": 26, +       "can_edit": false, +       "can_delete": false, +       "can_recover": false, +       "user_title": "team", +       "actions_summary": [ +         { +           "id": 2, +           "count": 15, +           "hidden": false, +           "can_act": false +         }, +         { +           "id": 3, +           "count": 0, +           "hidden": false, +           "can_act": false +         }, +         { +           "id": 4, +           "count": 0, +           "hidden": false, +           "can_act": false +         }, +         { +           "id": 5, +           "count": 0, +           "hidden": true, +           "can_act": false +         }, +         { +           "id": 6, +           "count": 0, +           "hidden": false, +           "can_act": false +         }, +         { +           "id": 7, +           "count": 0, +           "hidden": false, +           "can_act": false +         }, +         { +           "id": 8, +           "count": 0, +           "hidden": false, +           "can_act": false +         } +       ], +       "moderator": true, +       "admin": true, +       "staff": true, +       "user_id": 8222, +       "hidden": false, +       "hidden_reason_id": null, +       "trust_level": 4, +       "deleted_at": null, +       "user_deleted": false, +       "edit_reason": null, +       "can_view_edit_history": true, +       "wiki": true, +       "blurb": "So you want to set up Discourse on Ubuntu to hack on and develop with? We'll assume that you don't have Ruby/Rails/Postgre/Redis installed on your Ubuntu system..." +     }, +     { +       "id": 53437, +       "name": "Arpit Jalan", +       "username": "techAPJ", +       "avatar_template": "/user_avatar/meta.discourse.org/techapj/{size}/3281_1.png", +       "uploaded_avatar_id": 3281, +       "created_at": "2014-05-19T16:59:51.082Z", +       "cooked": "

      So you want to set up Discourse on Mac OS X to hack on and develop with?<\/p>\n\n

      We'll assume that you don't have Ruby/Rails/Postgre/Redis installed on your Mac. Let's begin!<\/p>\n\n

      (If you want to install Discourse for production use, see our install guide<\/a>)<\/em><\/p>\n\n

      Install Discourse Dependencies<\/h2>\n\n

      Run this script<\/a> in terminal, to setup Rails development environment:<\/p>\n\n

      bash <(curl -s https://raw.githubusercontent.com/techAPJ/install-rails/master/mac)<\/code><\/pre>\n\n

      This script will install following new packages on your system:<\/p>\n\n

        \n
      • Git<\/a><\/li>\n
      • rbenv<\/a><\/li>\n
      • ruby-build<\/a><\/li>\n
      • \nRuby<\/a> (latest stable)<\/li>\n
      • Rails<\/a><\/li>\n
      • PostgreSQL<\/a><\/li>\n
      • Redis<\/a><\/li>\n
      • Bundler<\/a><\/li>\n
      • ImageMagick<\/a><\/li>\n
      • PhantomJS<\/a><\/li>\n<\/ul>\n\n

        In case you have any of this package pre-installed and don't want to run entire script, see the script<\/a> and pick the packages you don't have currently installed. The script is fine-tuned for Discourse, and includes all the packages required for Discourse installation.<\/em><\/p>\n\n

        Now that we have installed Discourse dependencies, let's move on to install Discourse itself.<\/p>\n\n

        Clone Discourse<\/h2>\n\n

        Clone the Discourse repository in ~/discourse<\/code> folder:<\/p>\n\n

        git clone https://github.com/discourse/discourse.git ~/discourse<\/code><\/pre>\n\n

        <\/p>\n\n

        ~<\/code> indicates home folder, so Discourse source code will be available in your home folder.<\/p>\n\n

        Setup Database<\/h2>\n\n

        Open psql prompt:<\/p>\n\n

        psql postgres<\/code><\/pre>\n\n

        <\/p>\n\n

        Create discourse_development<\/strong> and discourse_test<\/strong> database with your account short name<\/a><\/em> specified as role:<\/p>\n\n

        CREATE DATABASE discourse_development WITH OWNER techapj ENCODING 'UTF8' TEMPLATE template0;\nCREATE DATABASE discourse_test WITH OWNER techapj ENCODING 'UTF8' TEMPLATE template0;<\/code><\/pre>\n\n

        Note that in above commands I specified the role as techapj<\/em>, this means that my short name<\/a> is techapj<\/em>, replace this with your own short name<\/a>.<\/strong><\/p>\n\n

        <\/p>\n\n

        Exit psql prompt by pressing control<\/kbd>d<\/kbd><\/p>\n\n

        Now access psql prompt in discourse_development<\/strong> database as your short name<\/em> user:<\/p>\n\n

        psql -d discourse_development -U techapj -h localhost<\/code><\/pre>\n\n

        Run following commands, separately:<\/p>\n\n

        CREATE EXTENSION pg_trgm;\nCREATE EXTENSION hstore;<\/code><\/pre>\n\n

        <\/p>\n\n

        Exit psql prompt by pressing control<\/kbd>d<\/kbd><\/p>\n\n

        Now access psql prompt in discourse_test<\/strong> database as your short name<\/em> user:<\/p>\n\n

        psql -d discourse_test -U techapj -h localhost<\/code><\/pre>\n\n

        Run following commands, separately:<\/p>\n\n

        CREATE EXTENSION pg_trgm;\nCREATE EXTENSION hstore;<\/code><\/pre>\n\n

        <\/p>\n\n

        Exit psql prompt by pressing control<\/kbd>d<\/kbd><\/p>\n\n

        You have set-up the database successfully!<\/p>\n\n

        Bootstrap Discourse<\/h2>\n\n

        Switch to your Discourse folder:<\/p>\n\n

        cd ~/discourse<\/code><\/pre>\n\n

        Install the needed gems<\/p>\n\n

        bundle install<\/code><\/pre>\n\n

        <\/p>\n\n

        Now that you have successfully installed gems, run this command:<\/p>\n\n

        bundle exec rake db:migrate db:test:prepare db:seed_fu<\/code><\/pre>\n\n

        Try running the specs: <\/p>\n\n

        bundle exec rake autospec<\/code><\/pre>\n\n

        <\/p>\n\n

        All the tests should pass.<\/p>\n\n

        Start rails server:<\/p>\n\n

        bundle exec rails server<\/code><\/pre>\n\n

        <\/p>\n\n

        You should now be able to connect with your Discourse app on http://localhost:3000<\/a> - try it out!<\/p>\n\n

        \nScreen Shot 2014-05-19 at 13.04.01.png<\/span>1255x461 98.7 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

        Create New Admin<\/h2>\n\n

        To create a new admin, run the following commands in rails console:<\/p>\n\n

        RAILS_ENV=development bundle exec rake admin:create<\/code><\/pre>\n\n

        Just enter your input as suggested, you can create an admin account. <\/p>\n\n

        \nfccdb29463e82f23.png<\/span>1919x430<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

        \nScreen Shot 2014-05-19 at 13.20.02.png<\/span>1256x756 124 KB<\/span><\/span>\n<\/div><\/a><\/div> <\/p>\n\n

        Happy hacking!<\/p>", +       "post_number": 1, +       "post_type": 1, +       "updated_at": "2015-04-26T06:51:23.549Z", +       "like_count": 13, +       "reply_count": 1, +       "reply_to_post_number": null, +       "quote_count": 0, +       "avg_time": 36, +       "incoming_link_count": 1483, +       "reads": 274, +       "score": 7985.4, +       "yours": false, +       "topic_id": 15772, +       "topic_slug": "beginners-guide-to-install-discourse-on-mac-os-x-for-development", +       "display_username": "Arpit Jalan", +       "primary_group_name": null, +       "version": 12, +       "can_edit": false, +       "can_delete": false, +       "can_recover": false, +       "user_title": "team", +       "actions_summary": [ +         { +           "id": 2, +           "count": 13, +           "hidden": false, +           "can_act": false +         }, +         { +           "id": 3, +           "count": 0, +           "hidden": false, +           "can_act": false +         }, +         { +           "id": 4, +           "count": 0, +           "hidden": false, +           "can_act": false +         }, +         { +           "id": 5, +           "count": 0, +           "hidden": true, +           "can_act": false +         }, +         { +           "id": 6, +           "count": 0, +           "hidden": false, +           "can_act": false +         }, +         { +           "id": 7, +           "count": 0, +           "hidden": false, +           "can_act": false +         }, +         { +           "id": 8, +           "count": 0, +           "hidden": false, +           "can_act": false +         } +       ], +       "moderator": true, +       "admin": true, +       "staff": true, +       "user_id": 8222, +       "hidden": false, +       "hidden_reason_id": null, +       "trust_level": 4, +       "deleted_at": null, +       "user_deleted": false, +       "edit_reason": "", +       "can_view_edit_history": true, +       "wiki": true, +       "blurb": "So you want to set up Discourse on Mac OS X to hack on and develop with? We'll assume that you don't have Ruby/Rails/Postgre/Redis installed on your Mac. Let's be..." +     }, +     { +       "id": 38398, +       "name": "Eric Carlson", +       "username": "ecuk", +       "avatar_template": "/letter_avatar/ecuk/{size}/5_fcf819f9b3791cb8c87edf29c8984f83.png", +       "uploaded_avatar_id": null, +       "created_at": "2014-01-24T15:08:06.111Z", +       "cooked": "

        Continuing the discussion from Log of setting up Docker in Virtualbox<\/a>:<\/p>\n\n

      -
      - Various greys used throught the UI. -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      - blend-primary-secondary() -
      - -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      - blend-primary-secondary() -
      - -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      - dark-light-diff() -
      - -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      - dark-light-diff() -
      -
      - {{#if colors.length}} From 3bc79f6885ee69e9a6fa30fb7b1d57734b60b3ca Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Sat, 29 Aug 2015 00:34:56 +0530 Subject: [PATCH 0998/1435] UX: select invite link by default --- app/assets/javascripts/discourse/views/invite.js.es6 | 11 ++++++++++- config/locales/client.en.yml | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/views/invite.js.es6 b/app/assets/javascripts/discourse/views/invite.js.es6 index 0c4a5b67a6..6a90097a7f 100644 --- a/app/assets/javascripts/discourse/views/invite.js.es6 +++ b/app/assets/javascripts/discourse/views/invite.js.es6 @@ -11,6 +11,15 @@ export default ModalBodyView.extend({ } else { return I18n.t('user.invited.create'); } - }.property('controller.{invitingToTopic,isMessage}') + }.property('controller.{invitingToTopic,isMessage}'), + + inviteLinkChanged: function() { + const self = this; + if (!Ember.isEmpty(this.get('controller.model.inviteLink'))) { + Em.run.next(function() { + $('.invite-link-input').select().focus(); + }); + } + }.observes('controller.model.inviteLink') }); diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 06f4f543f3..cf541775f8 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -626,7 +626,7 @@ en: account_age_days: "Account age in days" create: "Send an Invite" generate_link: "Copy Invite Link" - generated_link_message: '

      Invite link generated successfully!

      %{inviteLink}

      Invite link is only valid for this email address: %{invitedEmail}

      ' + generated_link_message: '

      Invite link generated successfully!

      Invite link is only valid for this email address: %{invitedEmail}

      ' bulk_invite: none: "You haven't invited anyone here yet. You can send individual invites, or invite a bunch of people at once by uploading a bulk invite file." text: "Bulk Invite from File" From d4b987ff32f77e99998dbb234c3cb0dc0a568135 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 26 Aug 2015 16:55:01 -0400 Subject: [PATCH 0999/1435] Migrate search drop down to `menu-panel` component. --- .../discourse/components/menu-panel.js.es6 | 65 +++++-- .../discourse/components/search-menu.js.es6 | 174 ++++++++++++++++++ .../components/search-text-field.js.es6 | 8 +- .../discourse/controllers/application.js.es6 | 2 +- .../discourse/controllers/header.js.es6 | 8 + .../discourse/controllers/search.js.es6 | 170 ----------------- .../discourse/controllers/topic.js.es6 | 21 +-- .../initializers/keyboard-shortcuts.js.es6 | 1 + .../discourse/lib/keyboard-shortcuts.js.es6 | 25 +-- .../inject-discourse-objects.js.es6 | 4 + .../discourse/routes/application.js.es6 | 7 - .../routes/build-category-route.js.es6 | 4 +- .../routes/build-user-topic-list-route.js.es6 | 4 +- .../javascripts/discourse/routes/topic.js.es6 | 11 +- .../javascripts/discourse/routes/user.js.es6 | 6 +- .../discourse/services/search.js.es6 | 30 +++ .../templates/components/search-menu.hbs | 44 +++++ .../discourse/templates/header.hbs | 14 +- .../discourse/templates/search.hbs | 37 ---- .../javascripts/discourse/views/post.js.es6 | 12 +- .../javascripts/discourse/views/search.js.es6 | 12 -- app/assets/javascripts/main_include.js | 1 + .../stylesheets/common/base/header.scss | 101 ---------- .../stylesheets/common/base/menu-panel.scss | 88 +++++++++ .../acceptance/header-anonymous-test.js.es6 | 11 +- .../javascripts/acceptance/search-test.js.es6 | 4 +- 26 files changed, 442 insertions(+), 422 deletions(-) create mode 100644 app/assets/javascripts/discourse/components/search-menu.js.es6 delete mode 100644 app/assets/javascripts/discourse/controllers/search.js.es6 create mode 100644 app/assets/javascripts/discourse/services/search.js.es6 create mode 100644 app/assets/javascripts/discourse/templates/components/search-menu.hbs delete mode 100644 app/assets/javascripts/discourse/templates/search.hbs delete mode 100644 app/assets/javascripts/discourse/views/search.js.es6 diff --git a/app/assets/javascripts/discourse/components/menu-panel.js.es6 b/app/assets/javascripts/discourse/components/menu-panel.js.es6 index 8b88fb9bca..3333ffa2ab 100644 --- a/app/assets/javascripts/discourse/components/menu-panel.js.es6 +++ b/app/assets/javascripts/discourse/components/menu-panel.js.es6 @@ -1,13 +1,14 @@ import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators'; const PANEL_BODY_MARGIN = 30; +const mutationSupport = !!window['MutationObserver']; export default Ember.Component.extend({ classNameBindings: [':menu-panel', 'visible::hidden', 'viewMode'], showClose: Ember.computed.equal('viewMode', 'slide-in'), - _resizeComponent() { + _layoutComponent() { if (!this.get('visible')) { return; } const viewMode = this.get('viewMode'); @@ -27,26 +28,22 @@ export default Ember.Component.extend({ this.$().css({ left: posLeft + "px", top: posTop + "px" }); // adjust panel height - let contentHeight = parseInt($('.panel-body-contents').height()); + let contentHeight = parseInt(this.$('.panel-body-contents').height()); const fullHeight = parseInt($(window).height()); const offsetTop = this.$().offset().top; - if (contentHeight + offsetTop + PANEL_BODY_MARGIN > fullHeight) { - contentHeight = fullHeight - (offsetTop - $(window).scrollTop()) - PANEL_BODY_MARGIN; + const scrollTop = $(window).scrollTop(); + if (contentHeight + (offsetTop - scrollTop) + PANEL_BODY_MARGIN > fullHeight) { + contentHeight = fullHeight - (offsetTop - scrollTop) - PANEL_BODY_MARGIN; } $panelBody.height(contentHeight); } else { $panelBody.height('auto'); - const headerHeight = parseInt($('header.d-header').height() + 3); this.$().css({ left: "auto", top: headerHeight + "px" }); } }, - _needsResize() { - Ember.run.scheduleOnce('afterRender', this, this._resizeComponent); - }, - @computed('force') viewMode() { const force = this.get('force'); @@ -61,6 +58,10 @@ export default Ember.Component.extend({ const markActive = this.get('markActive'); if (this.get('visible')) { + this.appEvents.on('dropdowns:closeAll', this, this.hide); + + // Allow us to hook into things being shown + Ember.run.scheduleOnce('afterRender', () => this.sendAction('onVisible')); if (isDropdown && markActive) { $(markActive).addClass('active'); @@ -72,14 +73,16 @@ export default Ember.Component.extend({ if ($target.closest('.menu-panel').length > 0) { return; } this.hide(); }); - + this.performLayout(); + this._watchSizeChanges(); } else { + Ember.run.scheduleOnce('afterRender', () => this.sendAction('onHidden')); if (markActive) { $(markActive).removeClass('active'); } $('html').off('click.close-menu-panel'); + this._stopWatchingSize(); } - this._needsResize(); }, @computed() @@ -102,9 +105,38 @@ export default Ember.Component.extend({ return this.siteSettings.faq_url ? this.siteSettings.faq_url : Discourse.getURL('/faq'); }, + performLayout() { + Ember.run.scheduleOnce('afterRender', this, this._layoutComponent); + }, + + _watchSizeChanges() { + if (mutationSupport) { + this._observer.disconnect(); + this._observer.observe(this.element, { childList: true, subtree: true }); + } else { + clearInterval(this._resizeInterval); + this._resizeInterval = setInterval(() => { + Ember.run(() => { + const contentHeight = parseInt(this.$('.panel-body-contents').height()); + if (contentHeight !== this._lastHeight) { this.performLayout(); } + this._lastHeight = contentHeight; + }); + }, 500); + } + }, + + _stopWatchingSize() { + if (mutationSupport) { + this._observer.disconnect(); + } else { + clearInterval(this._resizeInterval); + } + }, + @on('didInsertElement') _bindEvents() { - this.$().on('click.discourse-menu-panel', 'a', () => { + this.$().on('click.discourse-menu-panel', 'a', (e) => { + if ($(e.target).data('ember-action')) { return; } this.hide(); }); @@ -116,11 +148,16 @@ export default Ember.Component.extend({ } }); - // Recompute styles on resize $(window).on('resize.discourse-menu-panel', () => { this.propertyDidChange('viewMode'); - this._needsResize(); + this.performLayout(); }); + + if (mutationSupport) { + this._observer = new MutationObserver(() => { + Ember.run(() => this.performLayout()); + }); + } }, @on('willDestroyElement') diff --git a/app/assets/javascripts/discourse/components/search-menu.js.es6 b/app/assets/javascripts/discourse/components/search-menu.js.es6 new file mode 100644 index 0000000000..e8e85b3492 --- /dev/null +++ b/app/assets/javascripts/discourse/components/search-menu.js.es6 @@ -0,0 +1,174 @@ +import searchForTerm from 'discourse/lib/search-for-term'; +import DiscourseURL from 'discourse/lib/url'; +import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; +import showModal from 'discourse/lib/show-modal'; + +let _dontSearch = false; +export default Ember.Component.extend({ + searchService: Ember.inject.service('search'), + classNames: ['search-menu'], + typeFilter: null, + + @observes('searchService.searchContext') + contextChanged: function() { + if (this.get('searchService.searchContextEnabled')) { + _dontSearch = true; + this.set('searchService.searchContextEnabled', false); + _dontSearch = false; + } + }, + + @computed('searchService.searchContext', 'searchService.term', 'searchService.searchContextEnabled') + fullSearchUrlRelative(searchContext, term, searchContextEnabled) { + + if (searchContextEnabled && Ember.get(searchContext, 'type') === 'topic') { + return null; + } + + let url = '/search?q=' + encodeURIComponent(this.get('searchService.term')); + if (searchContextEnabled) { + if (searchContext.id.toString().toLowerCase() === this.get('currentUser.username_lower') && + searchContext.type === "private_messages" + ) { + url += ' in:private'; + } else { + url += encodeURIComponent(" " + searchContext.type + ":" + searchContext.id); + } + } + + return url; + }, + + @computed('fullSearchUrlRelative') + fullSearchUrl(fullSearchUrlRelative) { + if (fullSearchUrlRelative) { + return Discourse.getURL(fullSearchUrlRelative); + } + }, + + @computed('searchService.searchContext') + searchContextDescription(ctx) { + if (ctx) { + switch(Em.get(ctx, 'type')) { + case 'topic': + return I18n.t('search.context.topic'); + case 'user': + return I18n.t('search.context.user', {username: Em.get(ctx, 'user.username')}); + case 'category': + return I18n.t('search.context.category', {category: Em.get(ctx, 'category.name')}); + case 'private_messages': + return I18n.t('search.context.private_messages'); + } + } + }, + + @observes('searchService.searchContextEnabled') + searchContextEnabledChanged() { + if (_dontSearch) { return; } + this.newSearchNeeded(); + }, + + // If we need to perform another search + @observes('searchService.term', 'typeFilter') + newSearchNeeded() { + this.set('noResults', false); + const term = (this.get('searchService.term') || '').trim(); + if (term.length >= Discourse.SiteSettings.min_search_term_length) { + this.set('loading', true); + Ember.run.debounce(this, 'searchTerm', term, this.get('typeFilter'), 400); + } else { + this.setProperties({ content: null }); + } + this.set('selectedIndex', 0); + }, + + searchTerm(term, typeFilter) { + // for cancelling debounced search + if (this._cancelSearch){ + this._cancelSearch = null; + return; + } + + if (this._search) { + this._search.abort(); + } + + const searchContext = this.get('searchService.searchContextEnabled') ? this.get('searchService.searchContext') : null; + this._search = searchForTerm(term, { typeFilter, searchContext, fullSearchUrl: this.get('fullSearchUrl') }); + + this._search.then((content) => { + this.setProperties({ noResults: !content, content }); + }).finally(() => { + this.set('loading', false); + this._search = null; + }); + }, + + @computed('typeFilter', 'loading') + showCancelFilter(typeFilter, loading) { + if (loading) { return false; } + return !Ember.isEmpty(typeFilter); + }, + + @observes('searchService.term') + termChanged() { + this.cancelTypeFilter(); + }, + + actions: { + fullSearch() { + const self = this; + + if (this._search) { + this._search.abort(); + } + + // maybe we are debounced and delayed + // stop that as well + this._cancelSearch = true; + Em.run.later(function() { + self._cancelSearch = false; + }, 400); + + const url = this.get('fullSearchUrlRelative'); + if (url) { + DiscourseURL.routeTo(url); + } + }, + + moreOfType(type) { + this.set('typeFilter', type); + }, + + cancelType() { + this.cancelTypeFilter(); + }, + + showedSearch() { + $('#search-term').focus(); + }, + + showSearchHelp() { + // TODO: @EvitTrout how do we get a loading indicator here? + Discourse.ajax("/static/search_help.html", { dataType: 'html' }).then((model) => { + showModal('searchHelp', { model }); + }); + }, + + cancelHighlight() { + this.set('searchService.highlightTerm', null); + } + }, + + cancelTypeFilter() { + this.set('typeFilter', null); + }, + + keyDown(e) { + const term = this.get('searchService.term'); + if (e.which === 13 && term && term.length >= this.siteSettings.min_search_term_length) { + this.set('searchVisible', false); + this.send('fullSearch'); + } + } +}); diff --git a/app/assets/javascripts/discourse/components/search-text-field.js.es6 b/app/assets/javascripts/discourse/components/search-text-field.js.es6 index e833f66110..bf46ddf33f 100644 --- a/app/assets/javascripts/discourse/components/search-text-field.js.es6 +++ b/app/assets/javascripts/discourse/components/search-text-field.js.es6 @@ -1,7 +1,9 @@ +import computed from 'ember-addons/ember-computed-decorators'; import TextField from 'discourse/components/text-field'; export default TextField.extend({ - placeholder: function() { - return this.get('searchContextEnabled') ? "" : I18n.t('search.title'); - }.property('searchContextEnabled') + @computed('searchService.searchContextEnabled') + placeholder: function(searchContextEnabled) { + return searchContextEnabled ? "" : I18n.t('search.title'); + } }); diff --git a/app/assets/javascripts/discourse/controllers/application.js.es6 b/app/assets/javascripts/discourse/controllers/application.js.es6 index 20eb7f70ad..93d2755d31 100644 --- a/app/assets/javascripts/discourse/controllers/application.js.es6 +++ b/app/assets/javascripts/discourse/controllers/application.js.es6 @@ -15,5 +15,5 @@ export default Ember.Controller.extend({ @computed loginRequired() { return Discourse.SiteSettings.login_required && !Discourse.User.current(); - } + }, }); diff --git a/app/assets/javascripts/discourse/controllers/header.js.es6 b/app/assets/javascripts/discourse/controllers/header.js.es6 index 352c867c5c..f1749fc7c4 100644 --- a/app/assets/javascripts/discourse/controllers/header.js.es6 +++ b/app/assets/javascripts/discourse/controllers/header.js.es6 @@ -4,6 +4,7 @@ const HeaderController = Ember.Controller.extend({ notifications: null, loadingNotifications: false, hamburgerVisible: false, + searchVisible: false, needs: ['application'], loginRequired: Em.computed.alias('controllers.application.loginRequired'), @@ -71,9 +72,16 @@ const HeaderController = Ember.Controller.extend({ headerView.showDropdownBySelector("#user-notifications"); }, + toggleSearchMenu() { + this.appEvents.trigger('dropdowns:closeAll'); + this.toggleProperty('searchVisible'); + }, + toggleHamburgerMenu() { + this.appEvents.trigger('dropdowns:closeAll'); this.toggleProperty('hamburgerVisible'); } + } }); diff --git a/app/assets/javascripts/discourse/controllers/search.js.es6 b/app/assets/javascripts/discourse/controllers/search.js.es6 deleted file mode 100644 index 4c39f5dbca..0000000000 --- a/app/assets/javascripts/discourse/controllers/search.js.es6 +++ /dev/null @@ -1,170 +0,0 @@ -import searchForTerm from 'discourse/lib/search-for-term'; -import DiscourseURL from 'discourse/lib/url'; -import computed from 'ember-addons/ember-computed-decorators'; - -let _dontSearch = false; - -export default Em.Controller.extend({ - typeFilter: null, - - @computed('searchContext') - contextType: { - get(searchContext) { - if (searchContext) { - return Ember.get(searchContext, 'type'); - } - }, - set(value, searchContext) { - // a bit hacky, consider cleaning this up, need to work through all observers though - const context = $.extend({}, searchContext); - context.type = value; - this.set('searchContext', context); - return this.get('searchContext.type'); - } - }, - - contextChanged: function(){ - if (this.get('searchContextEnabled')) { - _dontSearch = true; - this.set('searchContextEnabled', false); - _dontSearch = false; - } - }.observes('searchContext'), - - fullSearchUrlRelative: function(){ - - if (this.get('searchContextEnabled') && this.get('searchContext.type') === 'topic') { - return null; - } - - let url = '/search?q=' + encodeURIComponent(this.get('term')); - const searchContext = this.get('searchContext'); - - if (this.get('searchContextEnabled')) { - if (searchContext.id.toString().toLowerCase() === this.get('currentUser.username_lower') && - searchContext.type === "private_messages" - ) { - url += ' in:private'; - } else { - url += encodeURIComponent(" " + searchContext.type + ":" + searchContext.id); - } - } - - return url; - - }.property('searchContext','term','searchContextEnabled'), - - fullSearchUrl: function(){ - const url = this.get('fullSearchUrlRelative'); - if (url) { - return Discourse.getURL(url); - } - }.property('fullSearchUrlRelative'), - - searchContextDescription: function(){ - const ctx = this.get('searchContext'); - if (ctx) { - switch(Em.get(ctx, 'type')) { - case 'topic': - return I18n.t('search.context.topic'); - case 'user': - return I18n.t('search.context.user', {username: Em.get(ctx, 'user.username')}); - case 'category': - return I18n.t('search.context.category', {category: Em.get(ctx, 'category.name')}); - case 'private_messages': - return I18n.t('search.context.private_messages'); - } - } - }.property('searchContext'), - - searchContextEnabledChanged: function(){ - if (_dontSearch) { return; } - this.newSearchNeeded(); - }.observes('searchContextEnabled'), - - // If we need to perform another search - newSearchNeeded: function() { - this.set('noResults', false); - const term = (this.get('term') || '').trim(); - if (term.length >= Discourse.SiteSettings.min_search_term_length) { - this.set('loading', true); - - Ember.run.debounce(this, 'searchTerm', term, this.get('typeFilter'), 400); - } else { - this.setProperties({ content: null }); - } - this.set('selectedIndex', 0); - }.observes('term', 'typeFilter'), - - searchTerm(term, typeFilter) { - const self = this; - - // for cancelling debounced search - if (this._cancelSearch){ - this._cancelSearch = null; - return; - } - - if (this._search) { - this._search.abort(); - } - - const searchContext = this.get('searchContextEnabled') ? this.get('searchContext') : null; - - this._search = searchForTerm(term, { - typeFilter, - searchContext, - fullSearchUrl: this.get('fullSearchUrl') - }); - - this._search.then(function(results) { - self.setProperties({ noResults: !results, content: results }); - }).finally(function() { - self.set('loading', false); - self._search = null; - }); - }, - - showCancelFilter: function() { - if (this.get('loading')) return false; - return !Ember.isEmpty(this.get('typeFilter')); - }.property('typeFilter', 'loading'), - - termChanged: function() { - this.cancelTypeFilter(); - }.observes('term'), - - actions: { - fullSearch() { - const self = this; - - if (this._search) { - this._search.abort(); - } - - // maybe we are debounced and delayed - // stop that as well - this._cancelSearch = true; - Em.run.later(function(){ - self._cancelSearch = false; - }, 400); - - const url = this.get('fullSearchUrlRelative'); - if (url) { - DiscourseURL.routeTo(url); - } - }, - - moreOfType(type) { - this.set('typeFilter', type); - }, - - cancelType() { - this.cancelTypeFilter(); - } - }, - - cancelTypeFilter() { - this.set('typeFilter', null); - } -}); diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index b9ef3a87bf..2a6d0ffaf9 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -8,14 +8,13 @@ import { popupAjaxError } from 'discourse/lib/ajax-error'; import computed from 'ember-addons/ember-computed-decorators'; export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { + needs: ['header', 'modal', 'composer', 'quote-button', 'topic-progress', 'application'], multiSelect: false, - needs: ['header', 'modal', 'composer', 'quote-button', 'search', 'topic-progress', 'application'], allPostsSelected: false, editingTopic: false, selectedPosts: null, selectedReplies: null, queryParams: ['filter', 'username_filters', 'show_deleted'], - searchHighlight: null, loadedAllPosts: false, enteredAt: null, firstPostExpanded: false, @@ -23,10 +22,6 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { maxTitleLength: setting('max_topic_title_length'), - contextChanged: function() { - this.set('controllers.search.searchContext', this.get('model.searchContext')); - }.observes('topic'), - _titleChanged: function() { const title = this.get('model.title'); if (!Ember.isEmpty(title)) { @@ -37,20 +32,6 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { } }.observes('model.title', 'category'), - termChanged: function() { - const dropdown = this.get('controllers.header.visibleDropdown'); - const term = this.get('controllers.search.term'); - - if(dropdown === 'search-dropdown' && term){ - this.set('searchHighlight', term); - } else { - if(this.get('searchHighlight')){ - this.set('searchHighlight', null); - } - } - - }.observes('controllers.search.term', 'controllers.header.visibleDropdown'), - postStreamLoadedAllPostsChanged: function() { // semantics of loaded all posts are slightly diff at topic level, // it just means that we "once" loaded all posts, this means we don't diff --git a/app/assets/javascripts/discourse/initializers/keyboard-shortcuts.js.es6 b/app/assets/javascripts/discourse/initializers/keyboard-shortcuts.js.es6 index 41485db6e6..f4fbf10acc 100644 --- a/app/assets/javascripts/discourse/initializers/keyboard-shortcuts.js.es6 +++ b/app/assets/javascripts/discourse/initializers/keyboard-shortcuts.js.es6 @@ -3,6 +3,7 @@ import KeyboardShortcuts from 'discourse/lib/keyboard-shortcuts'; export default { name: "keyboard-shortcuts", + initialize(container) { KeyboardShortcuts.bindEvents(Mousetrap, container); } diff --git a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 index c3aee329c7..7485240c27 100644 --- a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 +++ b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 @@ -63,6 +63,9 @@ export default { this.container = container; this._stopCallback(); + + this.searchService = this.container.lookup('search-service:main'); + _.each(PATH_BINDINGS, this._bindToPath, this); _.each(CLICK_BINDINGS, this._bindToClick, this); _.each(SELECTED_POST_BINDINGS, this._bindToSelectedPost, this); @@ -131,10 +134,7 @@ export default { }, showBuiltinSearch() { - if ($('#search-dropdown').is(':visible')) { - this._toggleSearch(false); - return true; - } + this.searchService.set('searchContextEnabled', false); const currentPath = this.container.lookup('controller:application').get('currentPath'), blacklist = [ /^discovery\.categories/ ], @@ -144,11 +144,12 @@ export default { // If we're viewing a topic, only intercept search if there are cloaked posts if (showSearch && currentPath.match(/^topic\./)) { - showSearch = $('.cooked').length < this.container.lookup('controller:topic').get('postStream.stream.length'); + showSearch = $('.cooked').length < this.container.lookup('controller:topic').get('model.postStream.stream.length'); } if (showSearch) { - this._toggleSearch(true); + this.searchService.set('searchContextEnabled', true); + this.showSearch(); return false; } @@ -168,8 +169,7 @@ export default { }, showSearch() { - this._toggleSearch(false); - return false; + this.container.lookup('controller:header').send('toggleSearchMenu'); }, toggleHamburgerMenu() { @@ -370,12 +370,5 @@ export default { return oldStopCallback(e, element, combo); }; - }, - - _toggleSearch(selectContext) { - $('#search-button').click(); - if (selectContext) { - this.container.lookup('controller:search').set('searchContextEnabled', true); - } - }, + } }; diff --git a/app/assets/javascripts/discourse/pre-initializers/inject-discourse-objects.js.es6 b/app/assets/javascripts/discourse/pre-initializers/inject-discourse-objects.js.es6 index 5d78569d4f..736f630f7c 100644 --- a/app/assets/javascripts/discourse/pre-initializers/inject-discourse-objects.js.es6 +++ b/app/assets/javascripts/discourse/pre-initializers/inject-discourse-objects.js.es6 @@ -3,6 +3,7 @@ import AppEvents from 'discourse/lib/app-events'; import Store from 'discourse/models/store'; import DiscourseURL from 'discourse/lib/url'; import DiscourseLocation from 'discourse/lib/discourse-location'; +import SearchService from 'discourse/services/search'; function inject() { const app = arguments[0], @@ -35,6 +36,9 @@ export default { app.register('site-settings:main', Discourse.SiteSettings, { instantiate: false }); injectAll(app, 'siteSettings'); + app.register('search-service:main', SearchService); + injectAll(app, 'searchService'); + app.register('session:main', Session.current(), { instantiate: false }); injectAll(app, 'session'); diff --git a/app/assets/javascripts/discourse/routes/application.js.es6 b/app/assets/javascripts/discourse/routes/application.js.es6 index a8ede91294..0b66dbdcd2 100644 --- a/app/assets/javascripts/discourse/routes/application.js.es6 +++ b/app/assets/javascripts/discourse/routes/application.js.es6 @@ -99,13 +99,6 @@ const ApplicationRoute = Discourse.Route.extend(OpenComposer, { showModal('keyboard-shortcuts-help', { title: 'keyboard_shortcuts_help.title'}); }, - showSearchHelp() { - // TODO: @EvitTrout how do we get a loading indicator here? - Discourse.ajax("/static/search_help.html", { dataType: 'html' }).then(function(model){ - showModal('searchHelp', { model }); - }); - }, - // Close the current modal, and destroy its state. closeModal() { this.render('hide-modal', { into: 'modal', outlet: 'modalBody' }); diff --git a/app/assets/javascripts/discourse/routes/build-category-route.js.es6 b/app/assets/javascripts/discourse/routes/build-category-route.js.es6 index fe794b4482..eaf26cc18c 100644 --- a/app/assets/javascripts/discourse/routes/build-category-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-category-route.js.es6 @@ -81,7 +81,7 @@ export default function(filter, params) { expandAllPinned: true }); - this.controllerFor('search').set('searchContext', model.get('searchContext')); + this.searchService.set('searchContext', model.get('searchContext')); this.set('topics', null); this.openTopicDraft(topics); @@ -98,7 +98,7 @@ export default function(filter, params) { deactivate: function() { this._super(); - this.controllerFor('search').set('searchContext', null); + this.searchService.set('searchContext', null); }, actions: { diff --git a/app/assets/javascripts/discourse/routes/build-user-topic-list-route.js.es6 b/app/assets/javascripts/discourse/routes/build-user-topic-list-route.js.es6 index c14174fae7..5704cd96db 100644 --- a/app/assets/javascripts/discourse/routes/build-user-topic-list-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-user-topic-list-route.js.es6 @@ -25,11 +25,11 @@ export default (viewName, path) => { }); this.controllerFor("user").set("pmView", viewName); - this.controllerFor("search").set("contextType", "private_messages"); + this.searchService.set('contextType', 'private_messages'); }, deactivate() { - this.controllerFor("search").set("contextType", "user"); + this.searchService.set('contextType', 'private_messages'); } }); }; diff --git a/app/assets/javascripts/discourse/routes/topic.js.es6 b/app/assets/javascripts/discourse/routes/topic.js.es6 index 04d3ae202c..5384dd3d21 100644 --- a/app/assets/javascripts/discourse/routes/topic.js.es6 +++ b/app/assets/javascripts/discourse/routes/topic.js.es6 @@ -175,14 +175,12 @@ const TopicRoute = Discourse.Route.extend({ const topic = this.modelFor('topic'); this.session.set('lastTopicIdViewed', parseInt(topic.get('id'), 10)); - this.controllerFor('search').set('searchContext', topic.get('searchContext')); }, deactivate() { this._super(); - // Clear the search context - this.controllerFor('search').set('searchContext', null); + this.searchService.set('searchContext', null); this.controllerFor('user-card').set('visible', false); const topicController = this.controllerFor('topic'), @@ -220,11 +218,8 @@ const TopicRoute = Discourse.Route.extend({ Discourse.TopicRoute.trigger('setupTopicController', this); - this.controllerFor('header').setProperties({ - topic: model, - showExtraInfo: false - }); - + this.controllerFor('header').setProperties({ topic: model, showExtraInfo: false }); + this.searchService.set('searchContext', model.get('searchContext')); this.controllerFor('topic-admin-menu').set('model', model); this.controllerFor('composer').set('topic', model); diff --git a/app/assets/javascripts/discourse/routes/user.js.es6 b/app/assets/javascripts/discourse/routes/user.js.es6 index cfbbf173e0..cf7ea55c0f 100644 --- a/app/assets/javascripts/discourse/routes/user.js.es6 +++ b/app/assets/javascripts/discourse/routes/user.js.es6 @@ -65,9 +65,7 @@ export default Discourse.Route.extend({ setupController(controller, user) { controller.set('model', user); - - // Add a search context - this.controllerFor('search').set('searchContext', user.get('searchContext')); + this.searchService.set('searchContext', user.get('searchContext')) }, activate() { @@ -83,7 +81,7 @@ export default Discourse.Route.extend({ this.messageBus.unsubscribe("/users/" + this.modelFor('user').get('username_lower')); // Remove the search context - this.controllerFor('search').set('searchContext', null); + this.searchService.set('searchContext', null) } }); diff --git a/app/assets/javascripts/discourse/services/search.js.es6 b/app/assets/javascripts/discourse/services/search.js.es6 new file mode 100644 index 0000000000..dcaa600aa6 --- /dev/null +++ b/app/assets/javascripts/discourse/services/search.js.es6 @@ -0,0 +1,30 @@ +import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; + +export default Ember.Object.extend({ + searchContextEnabled: false, + searchContext: null, + term: null, + highlightTerm: null, + + @observes('term') + _sethighlightTerm() { + this.set('highlightTerm', this.get('term')); + }, + + @computed('searchContext') + contextType: { + get(searchContext) { + if (searchContext) { + return Ember.get(searchContext, 'type'); + } + }, + set(value, searchContext) { + // a bit hacky, consider cleaning this up, need to work through all observers though + const context = $.extend({}, searchContext); + context.type = value; + this.set('searchContext', context); + return this.get('searchContext.type'); + } + }, + +}); diff --git a/app/assets/javascripts/discourse/templates/components/search-menu.hbs b/app/assets/javascripts/discourse/templates/components/search-menu.hbs new file mode 100644 index 0000000000..5fa83dc39e --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/search-menu.hbs @@ -0,0 +1,44 @@ +{{#menu-panel visible=searchVisible + markActive=".search-dropdown" + onVisible="showedSearch" + onHidden="cancelHighlight"}} + + {{plugin-outlet "above-search"}} + {{search-text-field value=searchService.term id="search-term"}} + +
      + {{#if searchService.searchContext}} + + {{/if}} + {{i18n "show_help"}} +
      +
      + {{#if loading}} +
      {{loading-spinner}}
      + {{else}} +
      + {{#if noResults}} +
      + {{i18n "search.no_results"}} +
      + {{else}} + {{#each content.resultTypes as |resultType|}} +
        +
      • {{resultType.name}}
      • + {{component resultType.componentName results=resultType.results term=searchService.term}} +
      +
      + {{#if resultType.moreUrl}} + {{i18n "show_more"}} {{fa-icon "chevron-down"}} + {{/if}} + {{#if resultType.more}} + {{i18n "show_more"}} {{fa-icon "chevron-down"}} + {{/if}} +
      + {{/each}} + {{/if}} +
      + {{/if}} +{{/menu-panel}} diff --git a/app/assets/javascripts/discourse/templates/header.hbs b/app/assets/javascripts/discourse/templates/header.hbs index c58e214b07..ddca4a9685 100644 --- a/app/assets/javascripts/discourse/templates/header.hbs +++ b/app/assets/javascripts/discourse/templates/header.hbs @@ -1,4 +1,5 @@ {{hamburger-menu hamburgerVisible=hamburgerVisible showKeyboardAction="showKeyboardShortcutsHelp"}} +{{search-menu searchVisible=searchVisible}}
      @@ -28,17 +29,15 @@ {{/if}} {{/if}} -
    • +
    • {{#if loginRequired}} - {{else}} - + {{fa-icon "search" label="search.title"}} {{/if}} @@ -81,7 +80,6 @@ {{#if view.renderDropdowns}} {{plugin-outlet "header-before-dropdowns"}} - {{render "search"}} {{render "notifications" notifications}} {{render "user-dropdown"}} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/search.hbs b/app/assets/javascripts/discourse/templates/search.hbs deleted file mode 100644 index 19eb370028..0000000000 --- a/app/assets/javascripts/discourse/templates/search.hbs +++ /dev/null @@ -1,37 +0,0 @@ -{{plugin-outlet "above-search"}} -{{search-text-field value=term searchContextEnabled=searchContextEnabled id="search-term"}} - -
      - {{#if searchContext}} - - {{/if}} - {{i18n "show_help"}} -
      -{{#if loading}} -
      {{loading-spinner}}
      -{{else}} -
      - {{#if noResults}} -
      - {{i18n "search.no_results"}} -
      - {{else}} - {{#each content.resultTypes as |resultType|}} -
        -
      • {{resultType.name}}
      • - {{component resultType.componentName results=resultType.results term=term}} -
      -
      - {{#if resultType.moreUrl}} - {{i18n "show_more"}} {{fa-icon "chevron-down"}} - {{/if}} - {{#if resultType.more}} - {{i18n "show_more"}} {{fa-icon "chevron-down"}} - {{/if}} -
      - {{/each}} - {{/if}} -
      -{{/if}} diff --git a/app/assets/javascripts/discourse/views/post.js.es6 b/app/assets/javascripts/discourse/views/post.js.es6 index fba794c605..bc555c6783 100644 --- a/app/assets/javascripts/discourse/views/post.js.es6 +++ b/app/assets/javascripts/discourse/views/post.js.es6 @@ -314,23 +314,23 @@ const PostView = Discourse.GroupedView.extend(Ember.Evented, { }.on('willInsertElement'), _applySearchHighlight: function() { - const highlight = this.get('controller.searchHighlight'); + const highlight = this.get('searchService.highlightTerm'); const cooked = this.$('.cooked'); - if(!cooked){ return; } + if (!cooked) { return; } - if(highlight && highlight.length > 2){ - if(this._highlighted){ + if (highlight && highlight.length > 2) { + if (this._highlighted) { cooked.unhighlight(); } cooked.highlight(highlight.split(/\s+/)); this._highlighted = true; - } else if(this._highlighted){ + } else if (this._highlighted) { cooked.unhighlight(); this._highlighted = false; } - }.observes('controller.searchHighlight', 'cooked') + }.observes('searchService.highlightTerm', 'cooked') }); export default PostView; diff --git a/app/assets/javascripts/discourse/views/search.js.es6 b/app/assets/javascripts/discourse/views/search.js.es6 deleted file mode 100644 index 861a29305f..0000000000 --- a/app/assets/javascripts/discourse/views/search.js.es6 +++ /dev/null @@ -1,12 +0,0 @@ -export default Ember.View.extend({ - tagName: 'div', - classNames: ['d-dropdown'], - elementId: 'search-dropdown', - templateName: 'search', - keyDown: function(e){ - var term = this.get('controller.term'); - if (e.which === 13 && term && term.length >= Discourse.SiteSettings.min_search_term_length) { - this.get('controller').send('fullSearch'); - } - } -}); diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js index ce051e83d5..e48e17368f 100644 --- a/app/assets/javascripts/main_include.js +++ b/app/assets/javascripts/main_include.js @@ -104,3 +104,4 @@ //= require_tree ./discourse/routes //= require_tree ./discourse/pre-initializers //= require_tree ./discourse/initializers +//= require_tree ./discourse/services diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss index d7c33b2cd2..e766433276 100644 --- a/app/assets/stylesheets/common/base/header.scss +++ b/app/assets/stylesheets/common/base/header.scss @@ -154,27 +154,6 @@ list-style: none; } - li:not(.category):not(.heading) { - font-size: 0.929em; - line-height: 16px; - - .fa { - font-size: inherit; - } - - a { - display: block; - padding: 5px; - transition: all linear .15s; - } - - &:hover a:not(.badge-notification) { - background-color: dark-light-diff($highlight, $secondary, 50%, -55%); - } - - button {margin-left: 5px;} - } - .heading a:hover { background-color: dark-light-diff($highlight, $secondary, 50%, -55%); } @@ -229,47 +208,6 @@ // Search - &#search-dropdown { - .heading { - padding: 5px 0 5px 5px; - .filter { - padding: 0 5px; - } - } - } - - input[type='text'] { - width: 518px; - height: 22px; - margin: 5px; - padding: 5px; - } - - .search-context { - padding: 0 5px; - label { margin-bottom: 0; } - } - - .searching { - position: absolute; - top: 0; - right: 0; - .spinner { - width: 10px; - height: 10px; - border-width: 2px; - margin: 20px 0 0 0; - } - } - // I am ghetto using this to display "Show More".. be warned - .no-results { - padding: 5px; - text-align: center; - } - .filter { - padding: 0; - &:hover {background: transparent;} - } // Categories @@ -286,24 +224,6 @@ } } -.search-link { - .badge-category-parent { - line-height: 0.8em; - } - .topic-title { - margin-right: 6px; - } - - .topic-statuses { - float: none; - display: inline-block; - color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%)); - margin: 0; - .fa { - margin: 0; - } - } -} .highlight-strong { background-color: dark-light-diff($highlight, $secondary, 40%, -45%); @@ -313,27 +233,6 @@ font-weight: bold; } -.search-context .show-help { - position: absolute; - right: 10px; - top: 0; -} - -.search-context { - min-height: 30px; - position: relative; -} - -.d-dropdown#search-dropdown { - max-height: none; - overflow: inherit; -} - -#search-dropdown .results { - max-height: 300px; - overflow: auto; -} - #search-help table td { padding-right: 10px; } diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss index 4a08b338c5..a64a79a0b4 100644 --- a/app/assets/stylesheets/common/base/menu-panel.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -7,6 +7,7 @@ position: absolute; top: 40px; bottom: 37px; + width: 96%; } } @@ -48,6 +49,7 @@ overflow-y: auto; overflow-x: hidden; } + } .hamburger-panel { @@ -83,3 +85,89 @@ } } +.search-menu { + + .search-context .show-help { + float: right; + } + + .heading { + padding: 5px 0 5px 5px; + .filter { + padding: 0 5px; + } + } + + input[type='text'] { + width: 93%; + height: 22px; + margin: 5px; + padding: 5px; + } + + .search-context { + padding: 0 5px; + label { margin-bottom: 0; } + } + + .searching { + position: absolute; + top: 0.1em; + right: 1.25em; + .spinner { + width: 10px; + height: 10px; + border-width: 2px; + margin: 20px 0 0 0; + } + } + // I am ghetto using this to display "Show More".. be warned + .no-results { + padding: 5px; + text-align: center; + } + .filter { + padding: 0; + &:hover {background: transparent;} + } + + .search-link { + .badge-category-parent { + line-height: 0.8em; + } + .topic-title { + margin-right: 6px; + } + + .topic-statuses { + float: none; + display: inline-block; + color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%)); + margin: 0; + .fa { + margin: 0; + } + } + } + + li:not(.category):not(.heading) { + font-size: 0.929em; + line-height: 16px; + + .fa { + font-size: inherit; + } + + a { + display: block; + padding: 5px; + transition: all linear .15s; + } + + &:hover a:not(.badge-notification) { + background-color: dark-light-diff($highlight, $secondary, 50%, -55%); + } + + button {margin-left: 5px;} + } +} diff --git a/test/javascripts/acceptance/header-anonymous-test.js.es6 b/test/javascripts/acceptance/header-anonymous-test.js.es6 index 6e642b7292..088cb4a30f 100644 --- a/test/javascripts/acceptance/header-anonymous-test.js.es6 +++ b/test/javascripts/acceptance/header-anonymous-test.js.es6 @@ -23,15 +23,8 @@ test("header", () => { // Search click("#search-button"); andThen(() => { - ok(exists("#search-dropdown:visible"), "after clicking a button search box opens"); - not(exists("#search-dropdown .heading"), "initially, immediately after opening, search box is empty"); + ok(exists(".search-menu:visible"), "after clicking a button search box opens"); + not(exists(".search-menu .heading"), "initially, immediately after opening, search box is empty"); }); - // Perform Search - // TODO how do I fix the fixture to be a POST instead of a GET @eviltrout - // fillIn("#search-term", "hello"); - // andThen(() => { - // ok(exists("#search-dropdown .heading"), "when user completes a search, search box shows search results"); - // equal(find("#search-dropdown .results a:first").attr("href"), "/t/hello-bar-integration-issues/17638", "there is a search result"); - // }); }); diff --git a/test/javascripts/acceptance/search-test.js.es6 b/test/javascripts/acceptance/search-test.js.es6 index 702cb1dde0..71208a7464 100644 --- a/test/javascripts/acceptance/search-test.js.es6 +++ b/test/javascripts/acceptance/search-test.js.es6 @@ -8,11 +8,11 @@ test("search", (assert) => { andThen(() => { assert.ok(exists('#search-term'), 'it shows the search bar'); - assert.ok(!exists('#search-dropdown .results ul li'), 'no results by default'); + assert.ok(!exists('.search-menu .results ul li'), 'no results by default'); }); fillIn('#search-term', 'dev'); andThen(() => { - assert.ok(exists('#search-dropdown .results ul li'), 'it shows results'); + assert.ok(exists('.search-menu .results ul li'), 'it shows results'); }); }); From 3ae5a0a2dfacaf9422603bd10a17143a0a5c3af3 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 28 Aug 2015 14:32:53 -0400 Subject: [PATCH 1000/1435] UX: Merge notifications and user dropdown --- .../conditional-loading-spinner.js.es6 | 4 +- .../components/header-dropdown.js.es6 | 17 +++ .../discourse/components/menu-panel.js.es6 | 12 +- .../discourse/components/search-menu.js.es6 | 2 +- .../discourse/components/user-menu.js.es6 | 66 ++++++++++ .../discourse/controllers/header.js.es6 | 61 +-------- .../controllers/notifications.js.es6 | 7 - .../controllers/user-dropdown.js.es6 | 25 ---- .../discourse/routes/discourse.js.es6 | 1 - .../javascripts/discourse/routes/topic.js.es6 | 7 - .../templates/components/hamburger-menu.hbs | 4 +- .../templates/components/header-dropdown.hbs | 9 ++ .../templates/components/search-menu.hbs | 6 +- .../user-menu.hbs} | 23 +++- .../discourse/templates/header.hbs | 90 +++++-------- .../discourse/templates/notifications.hbs | 16 --- .../javascripts/discourse/views/header.js.es6 | 123 ------------------ .../stylesheets/common/base/header.scss | 92 +------------ .../stylesheets/common/base/menu-panel.scss | 60 ++++++++- .../common/components/badges.css.scss | 2 +- app/assets/stylesheets/desktop/header.scss | 36 ----- app/assets/stylesheets/mobile/header.scss | 46 ------- .../acceptance/header-test-staff.js.es6 | 12 +- .../components/menu-panel-test.js.es6 | 5 - .../javascripts/controllers/topic-test.js.es6 | 2 +- .../controllers/user-dropdown-test.js.es6 | 10 -- 26 files changed, 211 insertions(+), 527 deletions(-) create mode 100644 app/assets/javascripts/discourse/components/header-dropdown.js.es6 create mode 100644 app/assets/javascripts/discourse/components/user-menu.js.es6 delete mode 100644 app/assets/javascripts/discourse/controllers/notifications.js.es6 delete mode 100644 app/assets/javascripts/discourse/controllers/user-dropdown.js.es6 create mode 100644 app/assets/javascripts/discourse/templates/components/header-dropdown.hbs rename app/assets/javascripts/discourse/templates/{user-dropdown.hbs => components/user-menu.hbs} (56%) delete mode 100644 app/assets/javascripts/discourse/templates/notifications.hbs delete mode 100644 test/javascripts/controllers/user-dropdown-test.js.es6 diff --git a/app/assets/javascripts/discourse/components/conditional-loading-spinner.js.es6 b/app/assets/javascripts/discourse/components/conditional-loading-spinner.js.es6 index 807990d707..49e2eefca1 100644 --- a/app/assets/javascripts/discourse/components/conditional-loading-spinner.js.es6 +++ b/app/assets/javascripts/discourse/components/conditional-loading-spinner.js.es6 @@ -1,8 +1,8 @@ export default Ember.Component.extend({ - classNameBindings: ['containerClass'], + classNameBindings: ['containerClass', 'condition:visible'], containerClass: function() { - return (this.get('size') === 'small') ? 'inline-spinner' : undefined; + return this.get('size') === 'small' ? 'inline-spinner' : undefined; }.property('size'), render: function(buffer) { diff --git a/app/assets/javascripts/discourse/components/header-dropdown.js.es6 b/app/assets/javascripts/discourse/components/header-dropdown.js.es6 new file mode 100644 index 0000000000..4990aa107a --- /dev/null +++ b/app/assets/javascripts/discourse/components/header-dropdown.js.es6 @@ -0,0 +1,17 @@ +export default Ember.Component.extend({ + tagName: 'li', + classNameBindings: [':header-dropdown-toggle', 'active'], + + active: Ember.computed.alias('toggleVisible'), + + actions: { + toggle() { + if (this.siteSettings.login_required && !this.currentUser) { + this.sendAction('loginAction'); + } else { + this.toggleProperty('toggleVisible'); + } + this.appEvents.trigger('dropdowns:closeAll'); + } + } +}); diff --git a/app/assets/javascripts/discourse/components/menu-panel.js.es6 b/app/assets/javascripts/discourse/components/menu-panel.js.es6 index 3333ffa2ab..190834d26e 100644 --- a/app/assets/javascripts/discourse/components/menu-panel.js.es6 +++ b/app/assets/javascripts/discourse/components/menu-panel.js.es6 @@ -55,21 +55,14 @@ export default Ember.Component.extend({ @observes('viewMode', 'visible') _visibleChanged() { const isDropdown = (this.get('viewMode') === 'drop-down'); - const markActive = this.get('markActive'); - if (this.get('visible')) { this.appEvents.on('dropdowns:closeAll', this, this.hide); // Allow us to hook into things being shown Ember.run.scheduleOnce('afterRender', () => this.sendAction('onVisible')); - - if (isDropdown && markActive) { - $(markActive).addClass('active'); - } - $('html').on('click.close-menu-panel', (e) => { const $target = $(e.target); - if ($target.closest(markActive).length > 0) { return; } + if ($target.closest('.header-dropdown-toggle').length > 0) { return; } if ($target.closest('.menu-panel').length > 0) { return; } this.hide(); }); @@ -77,9 +70,6 @@ export default Ember.Component.extend({ this._watchSizeChanges(); } else { Ember.run.scheduleOnce('afterRender', () => this.sendAction('onHidden')); - if (markActive) { - $(markActive).removeClass('active'); - } $('html').off('click.close-menu-panel'); this._stopWatchingSize(); } diff --git a/app/assets/javascripts/discourse/components/search-menu.js.es6 b/app/assets/javascripts/discourse/components/search-menu.js.es6 index e8e85b3492..4786747d99 100644 --- a/app/assets/javascripts/discourse/components/search-menu.js.es6 +++ b/app/assets/javascripts/discourse/components/search-menu.js.es6 @@ -167,7 +167,7 @@ export default Ember.Component.extend({ keyDown(e) { const term = this.get('searchService.term'); if (e.which === 13 && term && term.length >= this.siteSettings.min_search_term_length) { - this.set('searchVisible', false); + this.set('visible', false); this.send('fullSearch'); } } diff --git a/app/assets/javascripts/discourse/components/user-menu.js.es6 b/app/assets/javascripts/discourse/components/user-menu.js.es6 new file mode 100644 index 0000000000..5b12004665 --- /dev/null +++ b/app/assets/javascripts/discourse/components/user-menu.js.es6 @@ -0,0 +1,66 @@ +import { url } from 'discourse/lib/computed'; +import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; + +export default Ember.Component.extend({ + classNames: ['user-menu'], + notifications: null, + loadingNotifications: false, + myNotificationsUrl: url('/my/notifications'), + + @observes('visible') + _loadNotifications(visible) { + if (visible) { + this.refreshNotifications(); + } + }, + + @observes('currentUser.lastNotificationChange') + _resetCachedNotifications() { + const visible = this.get('visible'); + + if (!Discourse.get("hasFocus")) { + this.set('visible', false); + this.set('notifications', null); + return; + } + + if (visible) { + this.refreshNotifications(); + } else { + this.set('notifications', null); + } + }, + + refreshNotifications() { + if (this.get('loadingNotifications')) { return; } + this.set("loadingNotifications", true); + + // TODO: It's a bit odd to use the store in a component, but this one really + // wants to reach out and grab notifications + const store = this.container.lookup('store:main'); + store.find('notification', {recent: true}).then((notifications) => { + this.setProperties({ 'currentUser.unread_notifications': 0, notifications }); + }).catch(() => { + this.set('notifications', null); + }).finally(() => { + this.set("loadingNotifications", false); + }); + }, + + @computed() + allowAnon() { + return this.siteSettings.allow_anonymous_posting && + (this.get("currentUser.trust_level") >= this.siteSettings.anonymous_posting_min_trust_level || + this.get("isAnon")); + }, + + isAnon: Ember.computed.alias('currentUser.is_anonymous'), + + actions: { + toggleAnon() { + Discourse.ajax("/users/toggle-anon", {method: 'POST'}).then(function(){ + window.location.reload(); + }); + } + } +}); diff --git a/app/assets/javascripts/discourse/controllers/header.js.es6 b/app/assets/javascripts/discourse/controllers/header.js.es6 index f1749fc7c4..f81ec83cdd 100644 --- a/app/assets/javascripts/discourse/controllers/header.js.es6 +++ b/app/assets/javascripts/discourse/controllers/header.js.es6 @@ -1,13 +1,11 @@ const HeaderController = Ember.Controller.extend({ topic: null, showExtraInfo: null, - notifications: null, - loadingNotifications: false, hamburgerVisible: false, searchVisible: false, + userMenuVisible: false, needs: ['application'], - loginRequired: Em.computed.alias('controllers.application.loginRequired'), canSignUp: Em.computed.alias('controllers.application.canSignUp'), showSignUpButton: function() { @@ -18,70 +16,13 @@ const HeaderController = Ember.Controller.extend({ return Discourse.User.current() && !this.get('topic.isPrivateMessage'); }.property('topic.isPrivateMessage'), - _resetCachedNotifications: function() { - // a bit hacky, but if we have no focus, hide notifications first - const visible = $("#notifications-dropdown").is(":visible"); - - if(!Discourse.get("hasFocus")) { - if(visible){ - $("html").click(); - } - this.set("notifications", null); - return; - } - if(visible){ - this.refreshNotifications(); - } else { - this.set("notifications", null); - } - }.observes("currentUser.lastNotificationChange"), - - refreshNotifications: function(){ - const self = this; - if (self.get("loadingNotifications")) { return; } - - self.set("loadingNotifications", true); - - this.store.find('notification', {recent: true}).then(function(notifications) { - self.setProperties({ - 'currentUser.unread_notifications': 0, - notifications - }); - }).catch(function() { - self.setProperties({ - notifications: null - }); - }).finally(function() { - self.set("loadingNotifications", false); - }); - }, actions: { toggleStar() { const topic = this.get('topic'); if (topic) topic.toggleStar(); return false; - }, - - showNotifications(headerView) { - const self = this; - - if (self.get('currentUser.unread_notifications') || self.get('currentUser.unread_private_messages') || !self.get('notifications')) { - self.refreshNotifications(); - } - headerView.showDropdownBySelector("#user-notifications"); - }, - - toggleSearchMenu() { - this.appEvents.trigger('dropdowns:closeAll'); - this.toggleProperty('searchVisible'); - }, - - toggleHamburgerMenu() { - this.appEvents.trigger('dropdowns:closeAll'); - this.toggleProperty('hamburgerVisible'); } - } }); diff --git a/app/assets/javascripts/discourse/controllers/notifications.js.es6 b/app/assets/javascripts/discourse/controllers/notifications.js.es6 deleted file mode 100644 index e76d84e968..0000000000 --- a/app/assets/javascripts/discourse/controllers/notifications.js.es6 +++ /dev/null @@ -1,7 +0,0 @@ -import { url } from 'discourse/lib/computed'; - -export default Ember.ArrayController.extend({ - needs: ['header'], - loadingNotifications: Em.computed.alias('controllers.header.loadingNotifications'), - myNotificationsUrl: url('/my/notifications') -}); diff --git a/app/assets/javascripts/discourse/controllers/user-dropdown.js.es6 b/app/assets/javascripts/discourse/controllers/user-dropdown.js.es6 deleted file mode 100644 index b249ea1504..0000000000 --- a/app/assets/javascripts/discourse/controllers/user-dropdown.js.es6 +++ /dev/null @@ -1,25 +0,0 @@ -export default Ember.ArrayController.extend({ - showAdminLinks: Em.computed.alias("currentUser.staff"), - - allowAnon: function(){ - return this.siteSettings.allow_anonymous_posting && - (this.get("currentUser.trust_level") >= this.siteSettings.anonymous_posting_min_trust_level || - this.get("isAnon")); - }.property(), - - isAnon: function(){ - return this.get("currentUser.is_anonymous"); - }.property(), - - actions: { - logout() { - Discourse.logout(); - return false; - }, - toggleAnon() { - Discourse.ajax("/users/toggle-anon", {method: 'POST'}).then(function(){ - window.location.reload(); - }); - } - } -}); diff --git a/app/assets/javascripts/discourse/routes/discourse.js.es6 b/app/assets/javascripts/discourse/routes/discourse.js.es6 index 20ce38f40a..cf7a7c27a4 100644 --- a/app/assets/javascripts/discourse/routes/discourse.js.es6 +++ b/app/assets/javascripts/discourse/routes/discourse.js.es6 @@ -77,7 +77,6 @@ export function cleanDOM() { $('.profiler-results .profiler-result').remove(); // Close some elements that may be open - $('.d-dropdown').hide(); $('header ul.icons li').removeClass('active'); $('[data-toggle="dropdown"]').parent().removeClass('open'); // close the lightbox diff --git a/app/assets/javascripts/discourse/routes/topic.js.es6 b/app/assets/javascripts/discourse/routes/topic.js.es6 index 5384dd3d21..5c9a97733a 100644 --- a/app/assets/javascripts/discourse/routes/topic.js.es6 +++ b/app/assets/javascripts/discourse/routes/topic.js.es6 @@ -203,13 +203,6 @@ const TopicRoute = Discourse.Route.extend({ // In case we navigate from one topic directly to another isTransitioning = false; - if (Discourse.Mobile.mobileView) { - // close the dropdowns on mobile - $('.d-dropdown').hide(); - $('header ul.icons li').removeClass('active'); - $('[data-toggle="dropdown"]').parent().removeClass('open'); - } - controller.setProperties({ model, editingTopic: false, diff --git a/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs b/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs index d92a9cbfe0..098d310157 100644 --- a/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs +++ b/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs @@ -1,5 +1,5 @@ -{{#menu-panel visible=hamburgerVisible markActive=".hamburger-dropdown"}} -
    • {{#if showExtraInfo}} diff --git a/app/assets/javascripts/discourse/templates/notifications.hbs b/app/assets/javascripts/discourse/templates/notifications.hbs deleted file mode 100644 index 8e646bf6b8..0000000000 --- a/app/assets/javascripts/discourse/templates/notifications.hbs +++ /dev/null @@ -1,16 +0,0 @@ -
      - {{#conditional-loading-spinner condition=loadingNotifications}} - {{#if content}} - - {{else}} -
      {{i18n 'notifications.none'}}
      - {{/if}} - {{/conditional-loading-spinner}} -
      diff --git a/app/assets/javascripts/discourse/views/header.js.es6 b/app/assets/javascripts/discourse/views/header.js.es6 index 2251fe080a..d7088739c3 100644 --- a/app/assets/javascripts/discourse/views/header.js.es6 +++ b/app/assets/javascripts/discourse/views/header.js.es6 @@ -1,105 +1,10 @@ -let originalZIndex; - export default Ember.View.extend({ tagName: 'header', classNames: ['d-header', 'clearfix'], classNameBindings: ['editingTopic'], templateName: 'header', - renderDropdowns: false, - - showDropdown($target) { - var self = this; - - this.appEvents.trigger('dropdowns:closeAll'); - - if (!this.get("renderDropdowns")) { - this.set("renderDropdowns", true); - Em.run.next(function(){ - self.showDropdown($target); - }); - return; - } - - const elementId = $target.data('dropdown') || $target.data('notifications'), - $dropdown = $("#" + elementId), - $li = $target.closest('li'), - $ul = $target.closest('ul'), - $html = $('html'), - $header = $('header'), - replyZIndex = parseInt($('#reply-control').css('z-index'), 10); - - originalZIndex = originalZIndex || $('header').css('z-index'); - - if (replyZIndex > 0) { - $header.css("z-index", replyZIndex + 1); - } - - const controller = self.get('controller'); - if (controller && !controller.isDestroyed) { - controller.set('visibleDropdown', elementId); - } - // we need to ensure we are rendered, - // this optimises the speed of the initial render - const render = $target.data('render'); - if (render){ - if (!this.get(render)){ - this.set(render, true); - Em.run.next(this, function(){ - this.showDropdown.apply(self, [$target]); - }); - return; - } - } - - const hideDropdown = function() { - $header.css("z-index", originalZIndex); - $dropdown.fadeOut('fast'); - $li.removeClass('active'); - $html.data('hide-dropdown', null); - - if (controller && !controller.isDestroyed){ - controller.set('visibleDropdown', null); - } - $html.off('click.d-dropdown'); - $dropdown.off('click.d-dropdown'); - }; - - // if a dropdown is active and the user clicks on it, close it - if ($li.hasClass('active')) { return hideDropdown(); } - // otherwhise, mark it as active - $li.addClass('active'); - // hide the other dropdowns - $('li', $ul).not($li).removeClass('active'); - $('.d-dropdown').not($dropdown).fadeOut('fast'); - // fade it fast - $dropdown.fadeIn('fast'); - // autofocus any text input field - $dropdown.find('input[type=text]').focus().select(); - - $html.on('click.d-dropdown', function(e) { - return $(e.target).closest('.d-dropdown').length > 0 ? true : hideDropdown(); - }); - - $dropdown.on('click.d-dropdown', function(e) { - if(e.shiftKey || e.metaKey || e.ctrlKey || e.which === 2) return true; - return $(e.target).closest('a').not('.search-link, .filter-type').length > 0 ? hideDropdown() : true; - }); - - $html.data('hide-dropdown', hideDropdown); - - return false; - }, - - showDropdownBySelector: function(selector) { - this.showDropdown($(selector)); - }, - - showNotifications: function() { - this.get("controller").send("showNotifications", this); - }, examineDockHeader: function() { - var headerView = this; // Check the dock after the current run loop. While rendering, @@ -124,29 +29,18 @@ export default Ember.View.extend({ } } }); - }, _tearDown: function() { $(window).unbind('scroll.discourse-dock'); $(document).unbind('touchmove.discourse-dock'); this.$('a.unread-private-messages, a.unread-notifications, a[data-notifications]').off('click.notifications'); - this.$('a[data-dropdown]').off('click.dropdown'); $('body').off('keydown.header'); }.on('willDestroyElement'), _setup: function() { - const self = this; - this.$('a[data-dropdown]').on('click.dropdown', function(e) { - self.showDropdown.call(self, $(e.currentTarget)); - return false; - }); - this.$().on('click.notifications','a.unread-private-messages, a.unread-notifications, a[data-notifications]', function(e) { - self.showNotifications(e); - return false; - }); $(window).bind('scroll.discourse-dock', function() { self.examineDockHeader(); }); @@ -154,22 +48,5 @@ export default Ember.View.extend({ self.examineDockHeader(); }); self.examineDockHeader(); - - // Delegate ESC to the composer - $('body').on('keydown.header', function(e) { - // Hide dropdowns - if (e.which === 27) { - self.$('li').removeClass('active'); - self.$('.d-dropdown').fadeOut('fast'); - } - if (self.get('editingTopic')) { - if (e.which === 13) { - self.finishedEdit(); - } - if (e.which === 27) { - return self.cancelEdit(); - } - } - }); }.on('didInsertElement') }); diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss index e766433276..305aef128d 100644 --- a/app/assets/stylesheets/common/base/header.scss +++ b/app/assets/stylesheets/common/base/header.scss @@ -126,7 +126,7 @@ background-color: scale-color($tertiary, $lightness: 50%); } .unread-private-messages { - left: -4px; + left: 82px; } } .flagged-posts { @@ -134,96 +134,6 @@ } } -.d-dropdown { - display: none; - position: absolute; - background: $secondary; - max-height: 417px; - top: 100%; - right: 0; - z-index: 1100; - overflow: auto; - border: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); - padding: 5px; - - box-shadow: 0 2px 2px rgba(0,0,0, .4); - - - ul { - margin: 0; - list-style: none; - } - - .heading a:hover { - background-color: dark-light-diff($highlight, $secondary, 50%, -55%); - } - - .selected { - background-color: dark-light-diff($tertiary, $secondary, 85%, -65%); - } - - - // Notifications - &#notifications-dropdown { - .fa { color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%)); } - .icon { color: dark-light-choose(scale-color($primary, $lightness: 30%), scale-color($secondary, $lightness: 70%)); } - li { - background-color: dark-light-diff($tertiary, $secondary, 85%, -65%); - i { - float: left; - margin-right: 5px; - padding-top: 2px; - } - span { color: $primary; } - &:hover a { background-color: dark-light-diff($highlight, $secondary, 50%, -55%); } - a { padding: 4px 0 3px 2px; } - p { - margin: 0; - overflow: hidden; - } - } - .is-warning { - i.fa-envelope-o { - &:before { - content: "\f0e0"; - } - color: $danger; - } - } - .read { - background-color: $secondary; - } - .none { - padding: 5px; - } - .spinner { - width: 20px; - height: 20px; - border-width: 2px; - margin: 0 auto; - } - /* as a big ol' click target, don't let text inside be selected */ - @include unselectable; - } - - // Search - - - // Categories - - &#user-dropdown { - width: 118px; - } - - .btn { - padding: 2px 8px; - margin-bottom: 2px; - .fa { - margin-right: 5px; - } - } -} - .highlight-strong { background-color: dark-light-diff($highlight, $secondary, 40%, -45%); diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss index a64a79a0b4..9eb2797144 100644 --- a/app/assets/stylesheets/common/base/menu-panel.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -52,8 +52,8 @@ } -.hamburger-panel { - ul.location-links li, li.heading { +.menu-panel { + ul.menu-links li, ul li.heading { a { padding: 0.5em; display: block; @@ -74,6 +74,7 @@ background-color: transparent; vertical-align: top; padding: 5px 5px 2px 5px; + display: inline; } } @@ -171,3 +172,58 @@ button {margin-left: 5px;} } } + +.user-menu { + + .notifications { + h3 { + padding: 0 0.4em; + font-weight: bold; + margin: 0.5em 0; + } + + .fa { color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%)); } + .icon { color: dark-light-choose(scale-color($primary, $lightness: 30%), scale-color($secondary, $lightness: 70%)); } + li { + background-color: dark-light-diff($tertiary, $secondary, 85%, -65%); + padding: 0.5em; + i { + float: left; + margin-right: 5px; + padding-top: 2px; + } + span { color: $primary; } + &:hover { background-color: dark-light-diff($highlight, $secondary, 50%, -55%); } + a { padding: 0; } + p { + margin: 0; + overflow: hidden; + } + } + .is-warning { + i.fa-envelope-o { + &:before { + content: "\f0e0"; + } + color: $danger; + } + } + .read { + background-color: $secondary; + } + .none { + padding-top: 5px; + } + .spinner-container.visible { + min-height: 30px; + } + .spinner { + width: 20px; + height: 20px; + border-width: 2px; + margin: 0 auto; + } + /* as a big ol' click target, don't let text inside be selected */ + @include unselectable; + } +} diff --git a/app/assets/stylesheets/common/components/badges.css.scss b/app/assets/stylesheets/common/components/badges.css.scss index 67c361ff20..47fb1a1cb6 100644 --- a/app/assets/stylesheets/common/components/badges.css.scss +++ b/app/assets/stylesheets/common/components/badges.css.scss @@ -132,7 +132,7 @@ overflow: hidden; text-overflow: ellipsis; - .d-dropdown & { + .menu-panel & { max-width: 90px; } } diff --git a/app/assets/stylesheets/desktop/header.scss b/app/assets/stylesheets/desktop/header.scss index 74416dcf18..ff74c6714b 100644 --- a/app/assets/stylesheets/desktop/header.scss +++ b/app/assets/stylesheets/desktop/header.scss @@ -26,29 +26,6 @@ and (max-width : 570px) { padding-top: 82px; } -// Dropdowns -// -------------------------------------------------- - -.d-dropdown { - width: 320px; - overflow: auto; - // Common - - .heading { - border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); - } - - // Categories - - .btn { - padding: 2px 8px; - margin-bottom: 2px; - .fa { - margin-right: 5px; - } - } -} - .search-link .blurb { color: dark-light-choose(scale-color($primary, $lightness: 45%), scale-color($secondary, $lightness: 55%)); display: block; @@ -59,16 +36,3 @@ and (max-width : 570px) { color: dark-light-choose(scale-color($primary, $lightness: 25%), scale-color($secondary, $lightness: 75%)); } } - -.d-dropdown#search-dropdown { - width: 540px; -} - -.d-dropdown .searching { - right: 30px; -} - - -#search-dropdown .results { - max-height: 500px; -} diff --git a/app/assets/stylesheets/mobile/header.scss b/app/assets/stylesheets/mobile/header.scss index 08bb4dfb17..4428d805fa 100644 --- a/app/assets/stylesheets/mobile/header.scss +++ b/app/assets/stylesheets/mobile/header.scss @@ -37,48 +37,6 @@ padding-top: 60px; } -// Dropdowns -// -------------------------------------------------- - -.d-dropdown { - width: 290px; - margin-top: -1px; - right: -$mobile-wrapper-padding; // Line-up with edge of screen, not edge of padding - - // Common - - .heading { - color: $primary; - font-weight: bold; - font-size: 0.857em; - line-height: 15px; - } - - // Search - - .searching { - position: absolute; - top: 0; - right: 25px; - } - - input[type='text'] { - width: 265px; - font-size: 1.143em; - } - - &#user-dropdown { - width: 155px; - } - - .btn { - padding: 2px 8px; - .fa { - margin-right: 5px; - } - } -} - .search-link .badge-category { display: none; } @@ -86,7 +44,3 @@ .search-link .topic-statuses .topic-status i { font-size: 1em; } - -.d-dropdown#search-dropdown .heading { - padding: 0; -} diff --git a/test/javascripts/acceptance/header-test-staff.js.es6 b/test/javascripts/acceptance/header-test-staff.js.es6 index 5820eb327e..5c6f9f53f5 100644 --- a/test/javascripts/acceptance/header-test-staff.js.es6 +++ b/test/javascripts/acceptance/header-test-staff.js.es6 @@ -5,18 +5,10 @@ acceptance("Header (Staff)", { loggedIn: true }); test("header", () => { visit("/"); - // Notifications - click("#user-notifications"); - andThen(() => { - var $items = $("#notifications-dropdown li"); - ok(exists($items), "is lazily populated after user opens it"); - ok($items.first().hasClass("read"), "correctly binds items' 'read' class"); - }); - // User dropdown click("#current-user"); andThen(() => { - ok(exists("#user-dropdown:visible"), "is lazily rendered after user opens it"); - ok(exists("#user-dropdown .user-dropdown-links"), "has showing / hiding user-dropdown links correctly bound"); + ok(exists(".user-menu:visible"), "is lazily rendered after user opens it"); + ok(exists(".user-menu .menu-links"), "has showing / hiding user-dropdown links correctly bound"); }); }); diff --git a/test/javascripts/components/menu-panel-test.js.es6 b/test/javascripts/components/menu-panel-test.js.es6 index 98cc563176..64632263ec 100644 --- a/test/javascripts/components/menu-panel-test.js.es6 +++ b/test/javascripts/components/menu-panel-test.js.es6 @@ -18,19 +18,16 @@ componentTest('as a dropdown', { test(assert) { assert.ok(exists(".menu-panel.hidden"), "hidden by default"); - assert.ok(!exists(".menu-selected.active"), "does not mark anything as active"); this.set('panelVisible', true); andThen(() => { assert.ok(!exists('.menu-panel .close-panel'), "the close X is not shown"); assert.ok(!exists(".menu-panel.hidden"), "toggling visible makes it appear"); - assert.ok(exists(".menu-selected.active"), "marks the panel as active"); }); click('#outside-area') andThen(() => { assert.ok(exists(".menu-panel.hidden"), "clicking the body hides the menu"); - assert.ok(!exists(".menu-selected.active"), "removes the active class"); assert.equal(this.get('panelVisible'), false, 'it updates the bound variable'); }); } @@ -52,12 +49,10 @@ componentTest('as a slide-in', { test(assert) { assert.ok(exists(".menu-panel.hidden"), "hidden by default"); - assert.ok(!exists(".menu-selected.active"), "does not mark anything as active"); this.set('panelVisible', true); andThen(() => { assert.ok(!exists(".menu-panel.hidden"), "toggling visible makes it appear"); - assert.ok(!exists(".menu-selected.active"), "slide ins don't mark as active"); }); click('#outside-area') diff --git a/test/javascripts/controllers/topic-test.js.es6 b/test/javascripts/controllers/topic-test.js.es6 index afef0d4f0a..13fdc144eb 100644 --- a/test/javascripts/controllers/topic-test.js.es6 +++ b/test/javascripts/controllers/topic-test.js.es6 @@ -2,7 +2,7 @@ import { blank, present } from 'helpers/qunit-helpers'; moduleFor('controller:topic', 'controller:topic', { needs: ['controller:header', 'controller:modal', 'controller:composer', 'controller:quote-button', - 'controller:search', 'controller:topic-progress', 'controller:application'] + 'controller:topic-progress', 'controller:application'] }); import Topic from 'discourse/models/topic'; diff --git a/test/javascripts/controllers/user-dropdown-test.js.es6 b/test/javascripts/controllers/user-dropdown-test.js.es6 deleted file mode 100644 index 820ff8a69a..0000000000 --- a/test/javascripts/controllers/user-dropdown-test.js.es6 +++ /dev/null @@ -1,10 +0,0 @@ -moduleFor("controller:user-dropdown"); - -test("showAdminLinks", function() { - const currentUser = Ember.Object.create({ staff: true }); - const controller = this.subject({ currentUser }); - equal(controller.get("showAdminLinks"), true, "is true when current user is a staff member"); - - currentUser.set("staff", false); - equal(controller.get("showAdminLinks"), false, "is false when current user is not a staff member"); -}); From c1b38113778652a7682f2f91b0ea13734b462729 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 28 Aug 2015 16:25:29 -0400 Subject: [PATCH 1001/1435] FIX: JSHint failures --- app/assets/javascripts/discourse/components/menu-panel.js.es6 | 1 - app/assets/javascripts/discourse/views/invite.js.es6 | 1 - 2 files changed, 2 deletions(-) diff --git a/app/assets/javascripts/discourse/components/menu-panel.js.es6 b/app/assets/javascripts/discourse/components/menu-panel.js.es6 index 190834d26e..85d89a4da4 100644 --- a/app/assets/javascripts/discourse/components/menu-panel.js.es6 +++ b/app/assets/javascripts/discourse/components/menu-panel.js.es6 @@ -54,7 +54,6 @@ export default Ember.Component.extend({ @observes('viewMode', 'visible') _visibleChanged() { - const isDropdown = (this.get('viewMode') === 'drop-down'); if (this.get('visible')) { this.appEvents.on('dropdowns:closeAll', this, this.hide); diff --git a/app/assets/javascripts/discourse/views/invite.js.es6 b/app/assets/javascripts/discourse/views/invite.js.es6 index 6a90097a7f..811097ec14 100644 --- a/app/assets/javascripts/discourse/views/invite.js.es6 +++ b/app/assets/javascripts/discourse/views/invite.js.es6 @@ -14,7 +14,6 @@ export default ModalBodyView.extend({ }.property('controller.{invitingToTopic,isMessage}'), inviteLinkChanged: function() { - const self = this; if (!Ember.isEmpty(this.get('controller.model.inviteLink'))) { Em.run.next(function() { $('.invite-link-input').select().focus(); From b417f636aa2f1871f263b256e2642d3168bfef98 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 28 Aug 2015 16:55:18 -0400 Subject: [PATCH 1002/1435] FIX: Smoke test needs a class that was accidentally removed --- app/assets/javascripts/discourse/templates/header.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/discourse/templates/header.hbs b/app/assets/javascripts/discourse/templates/header.hbs index 43f68ce617..167c937fa5 100644 --- a/app/assets/javascripts/discourse/templates/header.hbs +++ b/app/assets/javascripts/discourse/templates/header.hbs @@ -37,6 +37,7 @@ {{#if currentUser}} {{#header-dropdown iconId="current-user" + class="current-user" showUser="true" toggleVisible=userMenuVisible loginAction="showLogin" From d334880ab3a86a7dede55f868f7eefaf3789c5e0 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 28 Aug 2015 15:39:47 -0700 Subject: [PATCH 1003/1435] add SparkPost as an option --- docs/INSTALL-cloud.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/INSTALL-cloud.md b/docs/INSTALL-cloud.md index 858f525bfe..4301163c97 100644 --- a/docs/INSTALL-cloud.md +++ b/docs/INSTALL-cloud.md @@ -77,7 +77,7 @@ After completing your edits, press CtrlO then Enter Date: Fri, 28 Aug 2015 15:47:35 -0700 Subject: [PATCH 1004/1435] remove a few things from user menu for now --- .../discourse/templates/components/user-menu.hbs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/components/user-menu.hbs b/app/assets/javascripts/discourse/templates/components/user-menu.hbs index 61d965e5db..102775fb54 100644 --- a/app/assets/javascripts/discourse/templates/components/user-menu.hbs +++ b/app/assets/javascripts/discourse/templates/components/user-menu.hbs @@ -1,13 +1,7 @@ {{#menu-panel visible=visible}}
      diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 0397e286d5..a8958b9035 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2581,6 +2581,7 @@ en: help: '? Open keyboard help' dismiss_new_posts: 'x, r Dismiss New/Posts' dismiss_topics: 'x, t Dismiss Topics' + log_out: 'shift+z shift+z Log Out' actions: title: 'Actions' bookmark_topic: 'f Toggle bookmark topic' From 1bd0f5b015dca764d2f6d91aa62f6a269ee96003 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Tue, 1 Sep 2015 16:52:05 -0400 Subject: [PATCH 1069/1435] FEATURE: group can grant a trust level when a user is added --- .../javascripts/admin/controllers/admin-group.js.es6 | 7 +++++++ app/assets/javascripts/admin/templates/group.hbs | 5 +++++ app/assets/javascripts/discourse/models/group.js.es6 | 3 ++- app/controllers/admin/groups_controller.rb | 2 ++ app/models/group.rb | 1 + app/models/group_user.rb | 11 +++++++++++ app/serializers/basic_group_serializer.rb | 3 ++- config/locales/client.en.yml | 3 +++ ...20150901192313_add_grant_trust_level_to_groups.rb | 5 +++++ spec/controllers/admin/groups_controller_spec.rb | 3 ++- spec/models/group_spec.rb | 12 ++++++++++++ 11 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20150901192313_add_grant_trust_level_to_groups.rb diff --git a/app/assets/javascripts/admin/controllers/admin-group.js.es6 b/app/assets/javascripts/admin/controllers/admin-group.js.es6 index dee7c45bb8..f16151ed29 100644 --- a/app/assets/javascripts/admin/controllers/admin-group.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-group.js.es6 @@ -27,6 +27,13 @@ export default Ember.Controller.extend({ ]; }.property(), + trustLevelOptions: function() { + return [ + { name: I18n.t("groups.trust_levels.none"), value: 0 }, + { name: 1, value: 1 }, { name: 2, value: 2 }, { name: 3, value: 3 }, { name: 4, value: 4 } + ]; + }.property(), + actions: { next() { if (this.get("showingLast")) { return; } diff --git a/app/assets/javascripts/admin/templates/group.hbs b/app/assets/javascripts/admin/templates/group.hbs index 6260d12aab..3da80d9af7 100644 --- a/app/assets/javascripts/admin/templates/group.hbs +++ b/app/assets/javascripts/admin/templates/group.hbs @@ -70,6 +70,11 @@ {{input value=model.title}}
      + +
      + + {{combo-box name="grant_trust_level" valueAttribute="value" value=model.grant_trust_level content=trustLevelOptions}} +
      {{/unless}}
      diff --git a/app/assets/javascripts/discourse/models/group.js.es6 b/app/assets/javascripts/discourse/models/group.js.es6 index b79db164f4..f90aee368d 100644 --- a/app/assets/javascripts/discourse/models/group.js.es6 +++ b/app/assets/javascripts/discourse/models/group.js.es6 @@ -62,7 +62,8 @@ const Group = Discourse.Model.extend({ automatic_membership_email_domains: this.get('emailDomains'), automatic_membership_retroactive: !!this.get('automatic_membership_retroactive'), title: this.get('title'), - primary_group: !!this.get('primary_group') + primary_group: !!this.get('primary_group'), + grant_trust_level: this.get('grant_trust_level') }; }, diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 42ed206bfd..35f3295d27 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -37,6 +37,8 @@ class Admin::GroupsController < Admin::AdminController def save_group(group) group.alias_level = params[:alias_level].to_i if params[:alias_level].present? group.visible = params[:visible] == "true" + grant_trust_level = params[:grant_trust_level].to_i + group.grant_trust_level = (grant_trust_level > 0 && grant_trust_level <= 4) ? grant_trust_level : nil group.automatic_membership_email_domains = params[:automatic_membership_email_domains] unless group.automatic group.automatic_membership_retroactive = params[:automatic_membership_retroactive] == "true" unless group.automatic diff --git a/app/models/group.rb b/app/models/group.rb index b36f5cee20..bdd3ca82e3 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -372,6 +372,7 @@ end # visible :boolean default(TRUE), not null # automatic_membership_email_domains :text # automatic_membership_retroactive :boolean default(FALSE) +# grant_trust_level :integer # # Indexes # diff --git a/app/models/group_user.rb b/app/models/group_user.rb index 38351f52b1..5febb87f3b 100644 --- a/app/models/group_user.rb +++ b/app/models/group_user.rb @@ -8,6 +8,8 @@ class GroupUser < ActiveRecord::Base after_save :set_primary_group after_destroy :remove_primary_group + after_save :grant_trust_level + protected def set_primary_group @@ -44,6 +46,15 @@ class GroupUser < ActiveRecord::Base title: group.title) end end + + def grant_trust_level + return if group.grant_trust_level.nil? + if user.trust_level < group.grant_trust_level + user.change_trust_level!(group.grant_trust_level) + user.trust_level_locked = true + user.save + end + end end # == Schema Information diff --git a/app/serializers/basic_group_serializer.rb b/app/serializers/basic_group_serializer.rb index 9f6ad6b7a2..38644aff4d 100644 --- a/app/serializers/basic_group_serializer.rb +++ b/app/serializers/basic_group_serializer.rb @@ -8,5 +8,6 @@ class BasicGroupSerializer < ApplicationSerializer :automatic_membership_email_domains, :automatic_membership_retroactive, :primary_group, - :title + :title, + :grant_trust_level end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index a8958b9035..7fe190f157 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -343,6 +343,9 @@ en: mods_and_admins: "Only moderators and Admins" members_mods_and_admins: "Only group members, moderators and admins" everyone: "Everyone" + trust_levels: + title: "Trust level automatically granted to members when they're added:" + none: "None" user_action_groups: "1": "Likes Given" diff --git a/db/migrate/20150901192313_add_grant_trust_level_to_groups.rb b/db/migrate/20150901192313_add_grant_trust_level_to_groups.rb new file mode 100644 index 0000000000..a654369a2e --- /dev/null +++ b/db/migrate/20150901192313_add_grant_trust_level_to_groups.rb @@ -0,0 +1,5 @@ +class AddGrantTrustLevelToGroups < ActiveRecord::Migration + def change + add_column :groups, :grant_trust_level, :integer + end +end diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb index b7710e7007..75bc9ddac1 100644 --- a/spec/controllers/admin/groups_controller_spec.rb +++ b/spec/controllers/admin/groups_controller_spec.rb @@ -29,7 +29,8 @@ describe Admin::GroupsController do "automatic_membership_email_domains"=>nil, "automatic_membership_retroactive"=>false, "title"=>nil, - "primary_group"=>false + "primary_group"=>false, + "grant_trust_level"=>nil }]) end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index c1b42d643c..b1d484d963 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -311,4 +311,16 @@ describe Group do end end + it "correctly grants a trust level to members" do + group = Fabricate(:group, grant_trust_level: 2) + u0 = Fabricate(:user, trust_level: 0) + u3 = Fabricate(:user, trust_level: 3) + + group.add(u0) + expect(u0.reload.trust_level).to eq(2) + + group.add(u3) + expect(u3.reload.trust_level).to eq(3) + end + end From f5cbaf56092f9a46217ec8d122d5276fe4f55992 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 1 Sep 2015 17:33:37 -0400 Subject: [PATCH 1070/1435] Have menu panels show up on top of the composer, stop composer at header --- .../discourse/views/composer.js.es6 | 38 ++++++++++--------- .../stylesheets/common/base/menu-panel.scss | 4 ++ app/assets/stylesheets/desktop/compose.scss | 2 +- vendor/assets/javascripts/div_resizer.js | 7 +++- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/app/assets/javascripts/discourse/views/composer.js.es6 b/app/assets/javascripts/discourse/views/composer.js.es6 index 22ab46f0fc..a8b4948018 100644 --- a/app/assets/javascripts/discourse/views/composer.js.es6 +++ b/app/assets/javascripts/discourse/views/composer.js.es6 @@ -60,23 +60,22 @@ const ComposerView = Ember.View.extend(Ember.Evented, { }, resize: function() { - const self = this; - Ember.run.scheduleOnce('afterRender', function() { - const h = $('#reply-control').height() || 0; - self.movePanels.apply(self, [h + "px"]); + Ember.run.scheduleOnce('afterRender', () => { + let h = $('#reply-control').height() || 0; + this.movePanels(h + "px"); // Figure out the size of the fields - const $fields = self.$('.composer-fields'); + const $fields = this.$('.composer-fields'); let pos = $fields.position(); if (pos) { - self.$('.wmd-controls').css('top', $fields.height() + pos.top + 5); + this.$('.wmd-controls').css('top', $fields.height() + pos.top + 5); } // get the submit panel height - pos = self.$('.submit-panel').position(); + pos = this.$('.submit-panel').position(); if (pos) { - self.$('.wmd-controls').css('bottom', h - pos.top + 7); + this.$('.wmd-controls').css('bottom', h - pos.top + 7); } }); @@ -117,20 +116,25 @@ const ComposerView = Ember.View.extend(Ember.Evented, { }, _enableResizing: function() { - const $replyControl = $('#reply-control'), - self = this; + const $replyControl = $('#reply-control'); - const resizer = function() { - Ember.run(function() { - self.resize(); - }); + const runResize = () => { + Ember.run(() => this.resize()); }; $replyControl.DivResizer({ - resize: resizer, - onDrag(sizePx) { self.movePanels.apply(self, [sizePx]); } + maxHeight(winHeight) { + const $header = $('header.d-header'); + const headerOffset = $header.offset(); + const headerOffsetTop = (headerOffset) ? headerOffset.top : 0; + const headerHeight = parseInt($header.height() + headerOffsetTop - $(window).scrollTop() + 5); + return winHeight - headerHeight; + }, + resize: runResize, + onDrag: (sizePx) => this.movePanels(sizePx) }); - afterTransition($replyControl, resizer); + + afterTransition($replyControl, runResize); this.set('controller.view', this); positioningWorkaround(this.$()); diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss index bf718f19ad..84ea5e6475 100644 --- a/app/assets/stylesheets/common/base/menu-panel.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -24,6 +24,10 @@ padding: 0.5em; width: 300px; + hr { + margin: 3px 0; + } + .panel-header { position: absolute; right: 20px; diff --git a/app/assets/stylesheets/desktop/compose.scss b/app/assets/stylesheets/desktop/compose.scss index 9ba434f904..714910492f 100644 --- a/app/assets/stylesheets/desktop/compose.scss +++ b/app/assets/stylesheets/desktop/compose.scss @@ -112,7 +112,7 @@ } transition: height 0.4s ease; width: 100%; - z-index: 1039; + z-index: 999; height: 0; background-color: dark-light-diff($primary, $secondary, 90%, -60%); bottom: 0; diff --git a/vendor/assets/javascripts/div_resizer.js b/vendor/assets/javascripts/div_resizer.js index fbcca76beb..07a99144f8 100644 --- a/vendor/assets/javascripts/div_resizer.js +++ b/vendor/assets/javascripts/div_resizer.js @@ -42,7 +42,12 @@ performDrag = function(e, opts) { thisMousePos = mousePosition(e).y; size = originalDivHeight + (originalPos - thisMousePos); lastMousePos = thisMousePos; - size = Math.min(size, $(window).height()); + + var maxHeight = $(window).height(); + if (opts.maxHeight) { + maxHeight = opts.maxHeight(maxHeight); + } + size = Math.min(size, maxHeight); size = Math.max(min, size); sizePx = size + "px"; if (typeof opts.onDrag === "function") { From c84a2632e38a9f4f0685d8f8f8332bcfdf67b204 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 1 Sep 2015 17:36:34 -0400 Subject: [PATCH 1071/1435] FIX: Number alignment on categories in hamburger --- app/assets/stylesheets/common/base/menu-panel.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss index 84ea5e6475..7ec8405613 100644 --- a/app/assets/stylesheets/common/base/menu-panel.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -66,8 +66,6 @@ .badge-notification { color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%)); background-color: transparent; - vertical-align: top; - padding: 5px 5px 2px 5px; display: inline; } } From f595e562eaf1ad063d55dc66dd3653048e24031d Mon Sep 17 00:00:00 2001 From: Kane York Date: Tue, 1 Sep 2015 16:16:19 -0700 Subject: [PATCH 1072/1435] Signup CTA first attempt --- .../discourse/components/signup-cta.js.es6 | 56 ++++++++++++ .../discourse/initializers/signup-cta.js.es6 | 86 +++++++++++++++++++ .../discourse/lib/key-value-store.js.es6 | 8 ++ .../discourse/lib/screen-track.js.es6 | 70 ++++++++++----- .../templates/components/signup-cta.hbs | 18 ++++ .../javascripts/discourse/templates/topic.hbs | 7 +- app/assets/stylesheets/desktop/alert.scss | 7 ++ config/locales/client.en.yml | 16 ++++ config/site_settings.yml | 3 + 9 files changed, 248 insertions(+), 23 deletions(-) create mode 100644 app/assets/javascripts/discourse/components/signup-cta.js.es6 create mode 100644 app/assets/javascripts/discourse/initializers/signup-cta.js.es6 create mode 100644 app/assets/javascripts/discourse/templates/components/signup-cta.hbs diff --git a/app/assets/javascripts/discourse/components/signup-cta.js.es6 b/app/assets/javascripts/discourse/components/signup-cta.js.es6 new file mode 100644 index 0000000000..b1224306e8 --- /dev/null +++ b/app/assets/javascripts/discourse/components/signup-cta.js.es6 @@ -0,0 +1,56 @@ +export default Ember.Component.extend({ + action: "showCreateAccount", + + actions: { + neverShow() { + this.keyValueStore.setItem('anon-cta-never', 't'); + this.session.set('showSignupCta', false); + }, + hideForSession() { + this.session.set('hideSignupCta', true); + this.keyValueStore.setItem('anon-cta-hidden', new Date().getTime()); + Em.run.later(() => + this.session.set('showSignupCta', false), + 20 * 1000); + }, + showCreateAccount() { + this.sendAction(); + } + }, + + signupMethodsTranslated: function() { + const methods = Ember.get('Discourse.LoginMethod.all'); + const loginWithEmail = this.siteSettings.enable_local_logins; + if (this.siteSettings.enable_sso) { + return I18n.t('signup_cta.methods.sso'); + } else if (methods.length === 0) { + if (loginWithEmail) { + return I18n.t('signup_cta.methods.only_email'); + } else { + return I18n.t('signup_cta.methods.unknown'); + } + } else if (methods.length === 1) { + let providerName = methods[0].name.capitalize(); + if (providerName === "Google_oauth2") { + providerName = "Google"; + } + if (loginWithEmail) { + return I18n.t('signup_cta.methods.one_and_email', {provider: providerName}); + } else { + return I18n.t('signup_cta.methods.only_other', {provider: providerName}); + } + } else { + if (loginWithEmail) { + return I18n.t('signup_cta.methods.multiple', {count: methods.length}); + } else { + return I18n.t('signup_cta.methods.multiple_no_email', {count: methods.length}); + } + } + }.property(), + + _turnOffIfHidden: function() { + if (this.session.get('hideSignupCta')) { + this.session.set('showSignupCta', false); + } + }.on('willDestroyElement') +}); diff --git a/app/assets/javascripts/discourse/initializers/signup-cta.js.es6 b/app/assets/javascripts/discourse/initializers/signup-cta.js.es6 new file mode 100644 index 0000000000..8b6011f355 --- /dev/null +++ b/app/assets/javascripts/discourse/initializers/signup-cta.js.es6 @@ -0,0 +1,86 @@ +import ScreenTrack from 'discourse/lib/screen-track'; +import Session from 'discourse/models/session'; + +const ANON_TOPIC_IDS = 5, + ANON_PROMPT_READ_TIME = 15 * 60 * 1000, + ANON_PROMPT_VISIT_COUNT = 2, + ONE_DAY = 24 * 60 * 60 * 1000, + PROMPT_HIDE_DURATION = ONE_DAY; + +export default { + name: "signup-cta", + + initialize(container) { + const screenTrack = ScreenTrack.current(), + session = Session.current(), + siteSettings = container.lookup('site-settings:main'), + keyValueStore = container.lookup('key-value-store:main'), + user = container.lookup('current-user:main'); + + // Preconditions + + if (user) return; // must not be logged in + if (keyValueStore.get('anon-cta-never')) return; // "never show again" + if (!siteSettings.allow_new_registrations) return; + if (siteSettings.invite_only) return; + if (siteSettings.must_approve_users) return; + if (siteSettings.login_required) return; + if (!siteSettings.enable_signup_cta) return; + + screenTrack.set('keyValueStore', keyValueStore); + + function checkSignupCtaRequirements() { + if (session.get('showSignupCta')) { + return; // already shown + } + + if (session.get('hideSignupCta')) { + return; // hidden for session + } + + if (keyValueStore.get('anon-cta-never')) { + return; // hidden forever + } + + const now = new Date().getTime(); + const hiddenAt = keyValueStore.getInt('anon-cta-hidden', 0); + if (hiddenAt > (now - PROMPT_HIDE_DURATION)) { + return; // hidden in last 24 hours + } + + const visitCount = keyValueStore.getInt('anon-visit-count'); + if (visitCount < ANON_PROMPT_VISIT_COUNT) { + return; + } + + const readTime = keyValueStore.getInt('anon-topic-time'); + if (readTime < ANON_PROMPT_READ_TIME) { + return; + } + + const topicIdsString = keyValueStore.get('anon-topic-ids'); + if (!topicIdsString) { return; } + let topicIdsAry = topicIdsString.split(','); + if (topicIdsAry.length < ANON_TOPIC_IDS) { + return; + } + + // Requirements met. + session.set('showSignupCta', true); + } + + screenTrack.set('anonFlushCallback', checkSignupCtaRequirements); + + // Record a visit + const nowVisit = new Date().getTime(); + const lastVisit = keyValueStore.getInt('anon-last-visit', nowVisit); + if (nowVisit - lastVisit > ONE_DAY) { + // more than a day + const visitCount = keyValueStore.getInt('anon-visit-count', 1); + keyValueStore.setItem('anon-visit-count', visitCount + 1); + } + keyValueStore.setItem('anon-last-visit', nowVisit); + + checkSignupCtaRequirements(); + } +}; diff --git a/app/assets/javascripts/discourse/lib/key-value-store.js.es6 b/app/assets/javascripts/discourse/lib/key-value-store.js.es6 index b5f13d024c..8c1e13515c 100644 --- a/app/assets/javascripts/discourse/lib/key-value-store.js.es6 +++ b/app/assets/javascripts/discourse/lib/key-value-store.js.es6 @@ -43,6 +43,14 @@ KeyValueStore.prototype = { get(key) { if (!safeLocalStorage) { return null; } return safeLocalStorage[this.context + key]; + }, + + getInt(key, def) { + if (!def) { def = 0; } + if (!safeLocalStorage) { return def; } + const result = parseInt(this.get(key)); + if (!isFinite(result)) { return def; } + return result; } }; diff --git a/app/assets/javascripts/discourse/lib/screen-track.js.es6 b/app/assets/javascripts/discourse/lib/screen-track.js.es6 index 3d55418cfb..345796ccc2 100644 --- a/app/assets/javascripts/discourse/lib/screen-track.js.es6 +++ b/app/assets/javascripts/discourse/lib/screen-track.js.es6 @@ -3,12 +3,15 @@ import Singleton from 'discourse/mixins/singleton'; const PAUSE_UNLESS_SCROLLED = 1000 * 60 * 3, - MAX_TRACKING_TIME = 1000 * 60 * 6; + MAX_TRACKING_TIME = 1000 * 60 * 6, + ANON_MAX_TOPIC_IDS = 5; const ScreenTrack = Ember.Object.extend({ init() { this.reset(); + // TODO fix this + this.set('keyValueStore', Discourse.__container__.lookup('key-value-store:main')); }, start(topicId, topicController) { @@ -82,9 +85,6 @@ const ScreenTrack = Ember.Object.extend({ flush() { if (this.get('cancelled')) { return; } - // We don't log anything unless we're logged in - if (!Discourse.User.current()) return; - const newTimings = {}, totalTimings = this.get('totalTimings'), self = this; @@ -115,26 +115,52 @@ const ScreenTrack = Ember.Object.extend({ Discourse.TopicTrackingState.current().updateSeen(topicId, highestSeen); if (!$.isEmptyObject(newTimings)) { - Discourse.ajax('/topics/timings', { - data: { - timings: newTimings, - topic_time: this.get('topicTime'), - topic_id: topicId - }, - cache: false, - type: 'POST', - headers: { - 'X-SILENCE-LOGGER': 'true' + if (Discourse.User.current()) { + Discourse.ajax('/topics/timings', { + data: { + timings: newTimings, + topic_time: this.get('topicTime'), + topic_id: topicId + }, + cache: false, + type: 'POST', + headers: { + 'X-SILENCE-LOGGER': 'true' + } + }).then(function() { + const controller = self.get('topicController'); + if (controller) { + const postNumbers = Object.keys(newTimings).map(function(v) { + return parseInt(v, 10); + }); + controller.readPosts(topicId, postNumbers); + } + }); + } else { + // Anonymous viewer - save to localStorage + const store = this.get('keyValueStore'); + + // Save total time + const existingTime = store.getInt('anon-topic-time'); + store.setItem('anon-topic-time', existingTime + this.get('topicTime')); + + // Save unique topic IDs up to a max + let topicIds = store.get('anon-topic-ids'); + if (topicIds) { + topicIds = topicIds.split(',').map(e => parseInt(e)); + } else { + topicIds = []; } - }).then(function(){ - const controller = self.get('topicController'); - if(controller){ - const postNumbers = Object.keys(newTimings).map(function(v){ - return parseInt(v,10); - }); - controller.readPosts(topicId, postNumbers); + if (topicIds.indexOf(topicId) === -1 && topicIds.length < ANON_MAX_TOPIC_IDS) { + topicIds.push(topicId); + store.setItem('anon-topic-ids', topicIds.join(',')); } - }); + + // Inform the observer + if (this.get('anonFlushCallback')) { + this.get('anonFlushCallback')(); + } + } this.set('topicTime', 0); } diff --git a/app/assets/javascripts/discourse/templates/components/signup-cta.hbs b/app/assets/javascripts/discourse/templates/components/signup-cta.hbs new file mode 100644 index 0000000000..dee31249a8 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/signup-cta.hbs @@ -0,0 +1,18 @@ + diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs index 0a38e3d574..986b8321ad 100644 --- a/app/assets/javascripts/discourse/templates/topic.hbs +++ b/app/assets/javascripts/discourse/templates/topic.hbs @@ -87,7 +87,12 @@ {{#if loadedAllPosts}} {{view "topic-closing" topic=model}} - {{view "topic-footer-buttons" topic=model}} + {{#if session.showSignupCta}} + {{! replace "Log In to Reply" with the infobox }} + {{signup-cta}} + {{else}} + {{view "topic-footer-buttons" topic=model}} + {{/if}} {{#if model.pending_posts_count}}
      diff --git a/app/assets/stylesheets/desktop/alert.scss b/app/assets/stylesheets/desktop/alert.scss index eead66319a..f019918a29 100644 --- a/app/assets/stylesheets/desktop/alert.scss +++ b/app/assets/stylesheets/desktop/alert.scss @@ -1,3 +1,10 @@ .alert { margin-bottom: 15px; } +.signup-cta { + margin-top: 15px; + a { + float: right; + text-decoration: underline; + } +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 7fe190f157..cd3a3b2d26 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -712,6 +712,22 @@ en: one: reply other: replies + signup_cta: + sign_up: "Sounds great!" + hide_session: "Maybe later" + hide_forever: "Never show this again" + hidden_for_session: "OK, I'll ask you tomorrow. You can always click the 'Log In' button to create an account, too." + line_1: Hey there! Looks like you're enjoying the forum, but you're not signed up for an account. + line_2: Logged-in users get their last read position in every topic saved, so you come right back where you left off. You can also "Watch" topics so that you get a notification whenever a new post is made, and give likes to others' posts. + methods: + sso: "Use your account on the main site to log in." + only_email: "You just need a valid email account to sign up." + only_other: "Just use your %{provider} account to sign up." + one_and_email: "Juse use your %{provider} account or your email to sign up." + multiple_no_email: "Choose from any of the %{count} supported login providers to get started." + multiple: "Signing up couldn't be easier: use any of the %{count} available login providers, or sign up with an email and password." + unknown: "error getting supported login methods" + summary: enabled_description: "You're viewing a summary of this topic: the most interesting posts as determined by the community." description: "There are {{count}} replies." diff --git a/config/site_settings.yml b/config/site_settings.yml index 7400d216e0..950555724b 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -199,6 +199,9 @@ login: allow_new_registrations: client: true default: true + enable_signup_cta: + client: true + default: true enable_google_oauth2_logins: client: true default: false From 65192a09a681b5dc2e149155b60f8c62c7b8b770 Mon Sep 17 00:00:00 2001 From: Kane York Date: Tue, 1 Sep 2015 16:20:24 -0700 Subject: [PATCH 1073/1435] copyedits --- config/locales/client.en.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index cd3a3b2d26..7c436af9ee 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -721,9 +721,9 @@ en: line_2: Logged-in users get their last read position in every topic saved, so you come right back where you left off. You can also "Watch" topics so that you get a notification whenever a new post is made, and give likes to others' posts. methods: sso: "Use your account on the main site to log in." - only_email: "You just need a valid email account to sign up." - only_other: "Just use your %{provider} account to sign up." - one_and_email: "Juse use your %{provider} account or your email to sign up." + only_email: "Signing up is easy: you just need a valid email account and a password." + only_other: "Use your %{provider} account to sign up." + one_and_email: "Use your %{provider} account, or an email and password, to sign up." multiple_no_email: "Choose from any of the %{count} supported login providers to get started." multiple: "Signing up couldn't be easier: use any of the %{count} available login providers, or sign up with an email and password." unknown: "error getting supported login methods" From 118f8227f1ac1f6495459af4965f757c8cb05ebb Mon Sep 17 00:00:00 2001 From: Kane York Date: Tue, 1 Sep 2015 16:23:57 -0700 Subject: [PATCH 1074/1435] Fix never link after clicking maybe later --- .../discourse/templates/components/signup-cta.hbs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/components/signup-cta.hbs b/app/assets/javascripts/discourse/templates/components/signup-cta.hbs index dee31249a8..71e59c4a80 100644 --- a/app/assets/javascripts/discourse/templates/components/signup-cta.hbs +++ b/app/assets/javascripts/discourse/templates/components/signup-cta.hbs @@ -1,9 +1,9 @@ {{/if}} +
      diff --git a/app/assets/stylesheets/desktop/alert.scss b/app/assets/stylesheets/desktop/alert.scss index f019918a29..eead66319a 100644 --- a/app/assets/stylesheets/desktop/alert.scss +++ b/app/assets/stylesheets/desktop/alert.scss @@ -1,10 +1,3 @@ .alert { margin-bottom: 15px; } -.signup-cta { - margin-top: 15px; - a { - float: right; - text-decoration: underline; - } -} diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss index 41409878ff..df6a7d3450 100644 --- a/app/assets/stylesheets/desktop/topic-post.scss +++ b/app/assets/stylesheets/desktop/topic-post.scss @@ -971,6 +971,14 @@ span.highlighted { transition: opacity ease-out 1s; } +.signup-cta { + margin-top: 15px; + width: $topic-body-width; + a { + float: right; + text-decoration: underline; + } +} /* Tablet (portrait) ----------- */ diff --git a/app/assets/stylesheets/mobile/topic-post.scss b/app/assets/stylesheets/mobile/topic-post.scss index ef1bbbe8b3..9c1c6a3dd5 100644 --- a/app/assets/stylesheets/mobile/topic-post.scss +++ b/app/assets/stylesheets/mobile/topic-post.scss @@ -509,6 +509,21 @@ span.highlighted { display: none; } +.signup-cta { + margin-top: 15px; + margin-left: auto; + margin-right: auto; + width: calc(100% - 50px); + a { + float: right; + text-decoration: underline; + margin-top: 7px; + } + button { + margin-right: 7px; + } +} + .small-action .small-action-desc { p { padding-top: 0; From 8b37dadec77033c02d4f1b442481609b00a33825 Mon Sep 17 00:00:00 2001 From: Kane York Date: Tue, 1 Sep 2015 16:52:29 -0700 Subject: [PATCH 1077/1435] remove TODO --- .../javascripts/discourse/initializers/signup-cta.js.es6 | 4 ++-- app/assets/javascripts/discourse/lib/screen-track.js.es6 | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/discourse/initializers/signup-cta.js.es6 b/app/assets/javascripts/discourse/initializers/signup-cta.js.es6 index 8b6011f355..cc25f64cb8 100644 --- a/app/assets/javascripts/discourse/initializers/signup-cta.js.es6 +++ b/app/assets/javascripts/discourse/initializers/signup-cta.js.es6 @@ -17,6 +17,8 @@ export default { keyValueStore = container.lookup('key-value-store:main'), user = container.lookup('current-user:main'); + screenTrack.set('keyValueStore', keyValueStore); + // Preconditions if (user) return; // must not be logged in @@ -27,8 +29,6 @@ export default { if (siteSettings.login_required) return; if (!siteSettings.enable_signup_cta) return; - screenTrack.set('keyValueStore', keyValueStore); - function checkSignupCtaRequirements() { if (session.get('showSignupCta')) { return; // already shown diff --git a/app/assets/javascripts/discourse/lib/screen-track.js.es6 b/app/assets/javascripts/discourse/lib/screen-track.js.es6 index 345796ccc2..26451f433c 100644 --- a/app/assets/javascripts/discourse/lib/screen-track.js.es6 +++ b/app/assets/javascripts/discourse/lib/screen-track.js.es6 @@ -10,8 +10,6 @@ const ScreenTrack = Ember.Object.extend({ init() { this.reset(); - // TODO fix this - this.set('keyValueStore', Discourse.__container__.lookup('key-value-store:main')); }, start(topicId, topicController) { From 32e5016dbb8c14fd0c8c1f8993e3be9fa8304802 Mon Sep 17 00:00:00 2001 From: Kane York Date: Tue, 1 Sep 2015 17:45:09 -0700 Subject: [PATCH 1078/1435] FEATURE: Include topic title, category in posts.json --- app/controllers/posts_controller.rb | 1 + app/serializers/post_serializer.rb | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 1e428c6e8c..9dd5c20372 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -58,6 +58,7 @@ class PostsController < ApplicationController scope: guardian, root: 'latest_posts', add_raw: true, + add_title: true, all_post_actions: counts) ) end diff --git a/app/serializers/post_serializer.rb b/app/serializers/post_serializer.rb index 4111fbbde2..5ba620ca27 100644 --- a/app/serializers/post_serializer.rb +++ b/app/serializers/post_serializer.rb @@ -5,6 +5,7 @@ class PostSerializer < BasicPostSerializer :topic_view, :parent_post, :add_raw, + :add_title, :single_post_link_counts, :draft_sequence, :post_actions, @@ -28,6 +29,9 @@ class PostSerializer < BasicPostSerializer :yours, :topic_id, :topic_slug, + :topic_title, + :topic_html_title, + :category_id, :display_username, :primary_group_name, :version, @@ -73,6 +77,30 @@ class PostSerializer < BasicPostSerializer object.try(:topic).try(:slug) end + def include_topic_title? + @add_title + end + + def include_topic_html_title? + @add_title + end + + def include_category_id? + @add_title + end + + def topic_title + object.topic.title + end + + def topic_html_title + object.topic.fancy_title + end + + def category_id + object.topic.category_id + end + def moderator? !!(object.try(:user).try(:moderator?)) end From 262f561a877e0296d7ca7f6ec9f27edd0d30ca8e Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 2 Sep 2015 12:13:44 +1000 Subject: [PATCH 1079/1435] FEATURE: relax username rules to allow - and . and leading _ This relaxes our very strict username rules to allow for some long asked for requests - leading _ is now allowed - . is allowed except for trailing char and confusing extensions like .gif .json - dash (-) is now permitted --- .../discourse/components/user-selector.js.es6 | 2 +- .../discourse/dialects/mention_dialect.js | 2 +- .../discourse/lib/user-search.js.es6 | 2 +- app/models/username_validator.rb | 28 ++++++++- config/locales/server.en.yml | 5 +- config/routes.rb | 2 +- spec/models/user_spec.rb | 62 ++++++++++++++----- .../services/username_checker_service_spec.rb | 2 +- 8 files changed, 80 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/discourse/components/user-selector.js.es6 b/app/assets/javascripts/discourse/components/user-selector.js.es6 index d3bed034e2..5b0be280c1 100644 --- a/app/assets/javascripts/discourse/components/user-selector.js.es6 +++ b/app/assets/javascripts/discourse/components/user-selector.js.es6 @@ -25,7 +25,7 @@ export default TextField.extend({ dataSource: function(term) { return userSearch({ - term: term.replace(/[^a-zA-Z0-9_]/, ''), + term: term.replace(/[^a-zA-Z0-9_\-\.]/, ''), topicId: self.get('topicId'), exclude: excludedUsernames(), includeGroups, diff --git a/app/assets/javascripts/discourse/dialects/mention_dialect.js b/app/assets/javascripts/discourse/dialects/mention_dialect.js index 4832d436b8..4f1b8b8da1 100644 --- a/app/assets/javascripts/discourse/dialects/mention_dialect.js +++ b/app/assets/javascripts/discourse/dialects/mention_dialect.js @@ -7,7 +7,7 @@ Discourse.Dialect.inlineRegexp({ start: '@', // NOTE: we really should be using SiteSettings here, but it loads later in process // also, if we do, we must ensure serverside version works as well - matcher: /^(@[A-Za-z0-9][A-Za-z0-9_]{0,40})/, + matcher: /^(@[A-Za-z0-9][A-Za-z0-9_\.\-]{0,40}[A-Za-z0-9])/, wordBoundary: true, emitter: function(matches) { diff --git a/app/assets/javascripts/discourse/lib/user-search.js.es6 b/app/assets/javascripts/discourse/lib/user-search.js.es6 index 790a00ff09..3600814bb8 100644 --- a/app/assets/javascripts/discourse/lib/user-search.js.es6 +++ b/app/assets/javascripts/discourse/lib/user-search.js.es6 @@ -89,7 +89,7 @@ export default function userSearch(options) { return new Ember.RSVP.Promise(function(resolve) { // TODO site setting for allowed regex in username - if (term.match(/[^a-zA-Z0-9_\.]/)) { + if (term.match(/[^a-zA-Z0-9_\.\-]/)) { resolve([]); return; } diff --git a/app/models/username_validator.rb b/app/models/username_validator.rb index 3b350d289e..b93ef78a03 100644 --- a/app/models/username_validator.rb +++ b/app/models/username_validator.rb @@ -30,6 +30,9 @@ class UsernameValidator username_length_max? username_char_valid? username_first_char_valid? + username_last_char_valid? + username_no_double_special? + username_does_not_end_with_confusing_suffix? errors.empty? end @@ -58,15 +61,36 @@ class UsernameValidator def username_char_valid? return unless errors.empty? - if username =~ /[^A-Za-z0-9_]/ + if username =~ /[^A-Za-z0-9_\.\-]/ self.errors << I18n.t(:'user.username.characters') end end def username_first_char_valid? return unless errors.empty? - if username[0] =~ /[^A-Za-z0-9]/ + if username[0] =~ /[^A-Za-z0-9_]/ self.errors << I18n.t(:'user.username.must_begin_with_alphanumeric') end end + + def username_last_char_valid? + return unless errors.empty? + if username[-1] =~ /[^A-Za-z0-9]/ + self.errors << I18n.t(:'user.username.must_end_with_alphanumeric') + end + end + + def username_no_double_special? + return unless errors.empty? + if username =~ /[\-_\.]{2,}/ + self.errors << I18n.t(:'user.username.must_not_contain_two_special_chars_in_seq') + end + end + + def username_does_not_end_with_confusing_suffix? + return unless errors.empty? + if username =~ /\.(json|gif|jpeg|png|htm|js|json|xml|woff|tif|html)/i + self.errors << I18n.t(:'user.username.must_not_contain_confusing_suffix') + end + end end diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 0b7c47b3af..77a8754d88 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1343,7 +1343,10 @@ en: characters: "must only include numbers, letters and underscores" unique: "must be unique" blank: "must be present" - must_begin_with_alphanumeric: "must begin with a letter or number" + must_begin_with_alphanumeric: "must begin with a letter or number or an underscore" + must_end_with_alphanumeric: "must end with a letter or number" + must_not_contain_two_special_chars_in_seq: "must not contain a sequence of 2 or more special chars (.-_)" + must_not_contain_confusing_suffix: "must not contain a confusing suffix like .json or .png etc." email: not_allowed: "is not allowed from that email provider. Please use another email address." blocked: "is not allowed." diff --git a/config/routes.rb b/config/routes.rb index 3675a5e769..c3164a2ff8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,7 +7,7 @@ require_dependency "permalink_constraint" # This used to be User#username_format, but that causes a preload of the User object # and makes Guard not work properly. -USERNAME_ROUTE_FORMAT = /[A-Za-z0-9\_]+/ unless defined? USERNAME_ROUTE_FORMAT +USERNAME_ROUTE_FORMAT = /[A-Za-z0-9\_.\-]+/ unless defined? USERNAME_ROUTE_FORMAT BACKUP_ROUTE_FORMAT = /[a-zA-Z0-9\-_]*\d{4}(-\d{2}){2}-\d{6}\.(tar\.gz|t?gz)/i unless defined? BACKUP_ROUTE_FORMAT Discourse::Application.routes.draw do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index e2bd09f772..5eaa7f6419 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -338,29 +338,57 @@ describe User do end describe 'username format' do - it "should be #{SiteSetting.min_username_length} chars or longer" do - @user = Fabricate.build(:user) - @user.username = 'ss' - expect(@user.save).to eq(false) + def assert_bad(username) + user = Fabricate.build(:user) + user.username = username + expect(user.valid?).to eq(false) end - it "should never end with a ." do - @user = Fabricate.build(:user) - @user.username = 'sam.' - expect(@user.save).to eq(false) + def assert_good(username) + user = Fabricate.build(:user) + user.username = username + expect(user.valid?).to eq(true) end - it "should never contain spaces" do - @user = Fabricate.build(:user) - @user.username = 'sam s' - expect(@user.save).to eq(false) + it "should be SiteSetting.min_username_length chars or longer" do + SiteSetting.min_username_length = 5 + assert_bad("abcd") + assert_good("abcde") end - ['Bad One', 'Giraf%fe', 'Hello!', '@twitter', 'me@example.com', 'no.dots', 'purple.', '.bilbo', '_nope', 'sa$sy'].each do |bad_nickname| - it "should not allow username '#{bad_nickname}'" do - @user = Fabricate.build(:user) - @user.username = bad_nickname - expect(@user.save).to eq(false) + %w{ first.last + first first-last + _name first_last + mc.hammer_nose + UPPERCASE + sgif + }.each do |username| + it "allows #{username}" do + assert_good(username) + end + end + + %w{ + traildot. + has\ space + double__underscore + with%symbol + Exclamation! + @twitter + my@email.com + .tester + sa$sy + sam.json + sam.xml + sam.html + sam.htm + sam.js + sam.woff + sam.Png + sam.gif + }.each do |username| + it "disallows #{username}" do + assert_bad(username) end end end diff --git a/spec/services/username_checker_service_spec.rb b/spec/services/username_checker_service_spec.rb index 258003b3fe..7b9a409410 100644 --- a/spec/services/username_checker_service_spec.rb +++ b/spec/services/username_checker_service_spec.rb @@ -26,7 +26,7 @@ describe UsernameCheckerService do end it 'rejects usernames that do not start with an alphanumeric character' do - result = @service.check_username('_vincent', @nil_email) + result = @service.check_username('.vincent', @nil_email) expect(result).to have_key(:errors) end end From c2e964455857f4ee92fdaafc70f9879b5b2bf912 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 2 Sep 2015 12:20:35 +1000 Subject: [PATCH 1080/1435] Revert "flexbox for the post header" This reverts commit bef3084516ebac401a903e30f3b06964d39c4e43. --- .../stylesheets/common/base/header.scss | 14 +---- .../stylesheets/common/base/menu-panel.scss | 3 ++ app/assets/stylesheets/common/base/topic.scss | 7 ++- .../common/components/badges.css.scss | 4 +- .../stylesheets/common/foundation/mixins.scss | 54 ------------------- 5 files changed, 9 insertions(+), 73 deletions(-) diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss index 6de1b2fcc3..bc20cefeaa 100644 --- a/app/assets/stylesheets/common/base/header.scss +++ b/app/assets/stylesheets/common/base/header.scss @@ -6,14 +6,6 @@ background-color: $header_background; box-shadow: 0 2px 4px -1px rgba(0,0,0, .25); - .ember-view { - min-width: 0; //flexbox fix - } - - .title { - @include flex(0,0,auto); - } - .docked & { position: fixed; backface-visibility: hidden; /** do magic for scrolling performance **/ @@ -21,8 +13,6 @@ .contents { margin: 8px 0; - @include flexbox(); - @include align-items(center); } .title { @@ -44,10 +34,8 @@ } .panel { + float: right; position: relative; - margin-left: auto; - min-width: 125px; - @include order(3) } .login-button, button.sign-up-button { diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss index 7561b34f80..7ec8405613 100644 --- a/app/assets/stylesheets/common/base/menu-panel.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -270,3 +270,6 @@ div.menu-links-header { margin-right: 0.2em; } } + + + diff --git a/app/assets/stylesheets/common/base/topic.scss b/app/assets/stylesheets/common/base/topic.scss index 873ab685bc..015b4b6871 100644 --- a/app/assets/stylesheets/common/base/topic.scss +++ b/app/assets/stylesheets/common/base/topic.scss @@ -27,12 +27,11 @@ } .extra-info-wrapper { - @include order(2); - line-height: 1.5; .badge-wrapper { float: left; - margin-left: 2px; - line-height: 1.2; + &.bullet { + margin-top: 5px; + } } } diff --git a/app/assets/stylesheets/common/components/badges.css.scss b/app/assets/stylesheets/common/components/badges.css.scss index 34b97c1784..47fb1a1cb6 100644 --- a/app/assets/stylesheets/common/components/badges.css.scss +++ b/app/assets/stylesheets/common/components/badges.css.scss @@ -52,8 +52,8 @@ &.bullet { //bullet category style - @include inline-flex; - @include align-items(baseline); + display: inline-flex; + align-items: baseline; margin-right: 10px; span.badge-category { diff --git a/app/assets/stylesheets/common/foundation/mixins.scss b/app/assets/stylesheets/common/foundation/mixins.scss index e9cb375a50..55b15a9b53 100644 --- a/app/assets/stylesheets/common/foundation/mixins.scss +++ b/app/assets/stylesheets/common/foundation/mixins.scss @@ -113,57 +113,3 @@ -webkit-transform: $transforms; transform: $transforms; } - -// --------------------------------------------------- - -//Flexbox - -@mixin flexbox() { - display: -webkit-box; - display: -moz-box; - display: -ms-flexbox; - display: -webkit-flex; - display: flex; -} - -@mixin inline-flex() { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -moz-inline-box; - display: -ms-inline-flexbox; - display: inline-flex; -} - - -@mixin align-items($alignment) { - -webkit-box-align: $alignment; - -webkit-align-items: $alignment; - -ms-flex-align: $alignment; - -ms-align-items: $alignment; - align-items:$alignment; -} - -@mixin order($val) { - -webkit-box-ordinal-group: $val; - -moz-box-ordinal-group: $val; - -ms-flex-order: $val; - -webkit-order: $val; - order: $val; -} - -@mixin flex($fg: 1, $fs: null, $fb: null) { - - $fg-boxflex: $fg; - - // Box-Flex only supports a flex-grow value - @if type-of($fg) == 'list' { - $fg-boxflex: nth($fg, 1); - } - - -webkit-box-flex: $fg-boxflex; - -webkit-flex: $fg $fs $fb; - -moz-box-flex: $fg-boxflex; - -moz-flex: $fg $fs $fb; - -ms-flex: $fg $fs $fb; - flex: $fg $fs $fb; -} From d8490fb65f2bbf04e592cb07c89a53f22034b658 Mon Sep 17 00:00:00 2001 From: James Kiesel Date: Tue, 1 Sep 2015 20:25:22 -0700 Subject: [PATCH 1081/1435] Move plugin menu outlet to live with other menus --- app/assets/javascripts/discourse/templates/header.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/templates/header.hbs b/app/assets/javascripts/discourse/templates/header.hbs index d09c4209ff..ae0ec11d40 100644 --- a/app/assets/javascripts/discourse/templates/header.hbs +++ b/app/assets/javascripts/discourse/templates/header.hbs @@ -1,3 +1,4 @@ +{{plugin-outlet "header-before-dropdowns"}} {{user-menu visible=userMenuVisible logoutAction="logout"}} {{hamburger-menu visible=hamburgerVisible showKeyboardAction="showKeyboardShortcutsHelp"}} {{search-menu visible=searchVisible}} @@ -51,7 +52,6 @@ {{/if}} {{/header-dropdown}} {{/if}} - {{plugin-outlet "header-before-dropdowns"}}
      From 6e04e5bd2c30a326fc2e397927b71c3504a7382e Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 2 Sep 2015 14:57:26 +1000 Subject: [PATCH 1082/1435] correct routing to allow for wider regex matching username --- config/routes.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index c3164a2ff8..f10ea5c2e9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,6 +8,7 @@ require_dependency "permalink_constraint" # This used to be User#username_format, but that causes a preload of the User object # and makes Guard not work properly. USERNAME_ROUTE_FORMAT = /[A-Za-z0-9\_.\-]+/ unless defined? USERNAME_ROUTE_FORMAT + BACKUP_ROUTE_FORMAT = /[a-zA-Z0-9\-_]*\d{4}(-\d{2}){2}-\d{6}\.(tar\.gz|t?gz)/i unless defined? BACKUP_ROUTE_FORMAT Discourse::Application.routes.draw do @@ -69,6 +70,7 @@ Discourse::Application.routes.draw do get "groups/:type" => "groups#show", constraints: AdminConstraint.new get "groups/:type/:id" => "groups#show", constraints: AdminConstraint.new + get "users/:id.json" => 'users#show' , id: USERNAME_ROUTE_FORMAT, defaults: {format: 'json'} resources :users, id: USERNAME_ROUTE_FORMAT do collection do get "list/:query" => "users#index" @@ -261,6 +263,7 @@ Discourse::Application.routes.draw do get "users/:username/private-messages/:filter" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/messages" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/messages/:filter" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT} + get "users/:username.json" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}, defaults: {format: :json} get "users/:username" => "users#show", as: 'user', constraints: {username: USERNAME_ROUTE_FORMAT} put "users/:username" => "users#update", constraints: {username: USERNAME_ROUTE_FORMAT} put "users/:username/emails" => "users#check_emails", constraints: {username: USERNAME_ROUTE_FORMAT} @@ -292,11 +295,12 @@ Discourse::Application.routes.draw do get "users/by-external/:external_id" => "users#show", constraints: {external_id: /[^\/]+/} get "users/:username/flagged-posts" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/deleted-posts" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} - get "user-badges/:username" => "user_badges#username" + get "user-badges/:username.json" => "user_badges#username", constraints: {username: USERNAME_ROUTE_FORMAT}, defaults: {format: :json} + get "user-badges/:username" => "user_badges#username", constraints: {username: USERNAME_ROUTE_FORMAT} - post "user_avatar/:username/refresh_gravatar" => "user_avatars#refresh_gravatar" - get "letter_avatar/:username/:size/:version.png" => "user_avatars#show_letter", format: false, constraints: { hostname: /[\w\.-]+/, size: /\d+/ } - get "user_avatar/:hostname/:username/:size/:version.png" => "user_avatars#show", format: false, constraints: { hostname: /[\w\.-]+/, size: /\d+/ } + post "user_avatar/:username/refresh_gravatar" => "user_avatars#refresh_gravatar", constraints: {username: USERNAME_ROUTE_FORMAT} + get "letter_avatar/:username/:size/:version.png" => "user_avatars#show_letter", format: false, constraints: { hostname: /[\w\.-]+/, size: /\d+/, username: USERNAME_ROUTE_FORMAT} + get "user_avatar/:hostname/:username/:size/:version.png" => "user_avatars#show", format: false, constraints: { hostname: /[\w\.-]+/, size: /\d+/, username: USERNAME_ROUTE_FORMAT } get "highlight-js/:hostname/:version.js" => "highlight_js#show", format: false, constraints: { hostname: /[\w\.-]+/ } From a0dd0bf1af3d4048e5c2c1864e2273bd20721aa6 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 2 Sep 2015 16:50:40 +1000 Subject: [PATCH 1083/1435] when looking at your own profile it should be collapsed. --- app/assets/javascripts/discourse/controllers/user.js.es6 | 9 ++++++++- app/assets/javascripts/discourse/templates/user/user.hbs | 7 ++++++- app/assets/stylesheets/desktop/user.scss | 6 ++++++ config/locales/client.en.yml | 1 + 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/user.js.es6 b/app/assets/javascripts/discourse/controllers/user.js.es6 index 6df5a32d6d..757779abec 100644 --- a/app/assets/javascripts/discourse/controllers/user.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user.js.es6 @@ -1,5 +1,6 @@ import { exportUserArchive } from 'discourse/lib/export-csv'; import CanCheckEmails from 'discourse/mixins/can-check-emails'; +import computed from 'ember-addons/ember-computed-decorators'; export default Ember.Controller.extend(CanCheckEmails, { indexStream: false, @@ -11,7 +12,10 @@ export default Ember.Controller.extend(CanCheckEmails, { return this.get('content.username') === Discourse.User.currentProp('username'); }.property('content.username'), - collapsedInfo: Em.computed.not('indexStream'), + @computed('indexStream', 'viewingSelf', 'forceExpand') + collapsedInfo(indexStream, viewingSelf, forceExpand){ + return (!indexStream || viewingSelf) && !forceExpand; + }, linkWebsite: Em.computed.not('model.isBasic'), @@ -59,6 +63,9 @@ export default Ember.Controller.extend(CanCheckEmails, { privateMessagesUnreadActive: Em.computed.equal('pmView', 'unread'), actions: { + expandProfile: function() { + this.set('forceExpand', true); + }, adminDelete: function() { Discourse.AdminUser.find(this.get('model.username').toLowerCase()).then(function(user){ user.destroy({deletePosts: true}); diff --git a/app/assets/javascripts/discourse/templates/user/user.hbs b/app/assets/javascripts/discourse/templates/user/user.hbs index 586ef838b3..475708f4ff 100644 --- a/app/assets/javascripts/discourse/templates/user/user.hbs +++ b/app/assets/javascripts/discourse/templates/user/user.hbs @@ -1,4 +1,4 @@ -
      +
      @@ -52,6 +52,11 @@ {{#if canInviteToForum}}
    • {{#link-to 'userInvited' class="btn right"}}{{fa-icon "user-plus"}}{{i18n 'user.invited.title'}}{{/link-to}}
    • {{/if}} + {{#if collapsedInfo}} + {{#if viewingSelf}} +
    • {{fa-icon "angle-double-down"}}{{i18n 'user.expand_profile'}}
    • + {{/if}} + {{/if}}
      diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss index bce1bfb3a1..9ce019329d 100644 --- a/app/assets/stylesheets/desktop/user.scss +++ b/app/assets/stylesheets/desktop/user.scss @@ -104,6 +104,12 @@ } +.viewing-self .user-main .about.collapsed-info { + .secondary, .staff-counters { + display: inherit; + } +} + .user-main { margin-bottom: 50px; diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 7fe190f157..0b6db04ac0 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -412,6 +412,7 @@ en: private_messages: "Messages" activity_stream: "Activity" preferences: "Preferences" + expand_profile: "Expand" bookmarks: "Bookmarks" bio: "About me" invited_by: "Invited By" From 0b20ded4fb3b9e44793a3fcf701856d4bdf9c6e3 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Wed, 2 Sep 2015 17:18:24 +0800 Subject: [PATCH 1084/1435] FIX: Timegap only shows up for sequential posts. --- .../discourse/components/time-gap.js.es6 | 2 ++ .../discourse/models/post-stream.js.es6 | 17 ++++++----------- .../javascripts/discourse/templates/post.hbs | 2 +- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/discourse/components/time-gap.js.es6 b/app/assets/javascripts/discourse/components/time-gap.js.es6 index 3cd887ec43..9e410c4b73 100644 --- a/app/assets/javascripts/discourse/components/time-gap.js.es6 +++ b/app/assets/javascripts/discourse/components/time-gap.js.es6 @@ -2,6 +2,8 @@ import SmallActionComponent from 'discourse/components/small-action'; export default SmallActionComponent.extend({ classNames: ['time-gap'], + classNameBindings: ['hideTimeGap::hidden'], + hideTimeGap: Em.computed.alias('postStream.hasNoFilters'), icon: 'clock-o', description: function() { diff --git a/app/assets/javascripts/discourse/models/post-stream.js.es6 b/app/assets/javascripts/discourse/models/post-stream.js.es6 index 9f625f3477..b8a71fd651 100644 --- a/app/assets/javascripts/discourse/models/post-stream.js.es6 +++ b/app/assets/javascripts/discourse/models/post-stream.js.es6 @@ -5,18 +5,13 @@ function calcDayDiff(p1, p2) { if (!p1) { return; } const date = p1.get('created_at'); - if (date) { - if (p2) { - const numDiff = p1.get('post_number') - p2.get('post_number'); - if (numDiff === 1) { - const lastDate = p2.get('created_at'); - if (lastDate) { - const delta = new Date(date).getTime() - new Date(lastDate).getTime(); - const days = Math.round(delta / (1000 * 60 * 60 * 24)); + if (date && p2) { + const lastDate = p2.get('created_at'); + if (lastDate) { + const delta = new Date(date).getTime() - new Date(lastDate).getTime(); + const days = Math.round(delta / (1000 * 60 * 60 * 24)); - p1.set('daysSincePrevious', days); - } - } + p1.set('daysSincePrevious', days); } } } diff --git a/app/assets/javascripts/discourse/templates/post.hbs b/app/assets/javascripts/discourse/templates/post.hbs index 6790353025..0ea628c93a 100644 --- a/app/assets/javascripts/discourse/templates/post.hbs +++ b/app/assets/javascripts/discourse/templates/post.hbs @@ -1,7 +1,7 @@ {{post-gap post=this postStream=controller.model.postStream before="true"}} {{#if hasTimeGap}} - {{time-gap daysAgo=daysSincePrevious}} + {{time-gap daysAgo=daysSincePrevious postStream=controller.model.postStream}} {{/if}}
      From 1fbc142b0c421ea68836eff9b46d257010ad9626 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Wed, 2 Sep 2015 12:29:29 -0400 Subject: [PATCH 1085/1435] UX: add screen reader support to notifications. (title attribute is ignored by screen readers) --- .../discourse/components/notification-item.js.es6 | 14 ++++++++------ config/locales/client.en.yml | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/discourse/components/notification-item.js.es6 b/app/assets/javascripts/discourse/components/notification-item.js.es6 index f0add4c2f3..2aa93e141f 100644 --- a/app/assets/javascripts/discourse/components/notification-item.js.es6 +++ b/app/assets/javascripts/discourse/components/notification-item.js.es6 @@ -4,17 +4,19 @@ export default Ember.Component.extend({ tagName: 'li', classNameBindings: ['notification.read', 'notification.is_warning'], - scope: function() { + name: function() { var notificationType = this.get("notification.notification_type"); var lookup = this.site.get("notificationLookup"); - var name = lookup[notificationType]; + return lookup[notificationType]; + }.property("notification.notification_type"), - if (name === "custom") { + scope: function() { + if (this.get("name") === "custom") { return this.get("notification.data.message"); } else { - return "notifications." + name; + return "notifications." + this.get("name"); } - }.property("notification.notification_type"), + }.property("name"), url: function() { const it = this.get('notification'); @@ -57,7 +59,7 @@ export default Ember.Component.extend({ const url = this.get('url'); if (url) { - buffer.push('' + text + ''); + buffer.push('' + text + ''); } else { buffer.push(text); } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 0b6db04ac0..2d75eef63a 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -899,6 +899,21 @@ en: linked: "

      {{username}} {{description}}

      " granted_badge: "

      Earned '{{description}}'

      " + alt: + mentioned: "Mentioned by" + quoted: "Quoted by" + replied: "Replied" + posted: "Post by" + edited: "Edit your post by" + liked: "Liked your post" + private_message: "Private message from" + invited_to_private_message: "Invited to a private message from" + invited_to_topic: "Invited to a topic from" + invitee_accepted: "Invite accepted by" + moved_post: "Your post was moved by" + linked: "Link to your post" + granted_badge: "Badge granted" + popup: mentioned: '{{username}} mentioned you in "{{topic}}" - {{site_title}}' quoted: '{{username}} quoted you in "{{topic}}" - {{site_title}}' From a76d1079b25c48f6d2048274cf1648bad6d7ea2c Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 2 Sep 2015 13:38:59 -0400 Subject: [PATCH 1086/1435] Support jumping to messages --- app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 | 3 ++- .../discourse/templates/modal/keyboard-shortcuts-help.hbs | 1 + config/locales/client.en.yml | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 index be0f13800d..29dbc01d66 100644 --- a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 +++ b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 @@ -8,7 +8,8 @@ const PATH_BINDINGS = { 'g c': '/categories', 'g t': '/top', 'g b': '/bookmarks', - 'g p': '/my/activity' + 'g p': '/my/activity', + 'g m': '/my/messages' }, SELECTED_POST_BINDINGS = { diff --git a/app/assets/javascripts/discourse/templates/modal/keyboard-shortcuts-help.hbs b/app/assets/javascripts/discourse/templates/modal/keyboard-shortcuts-help.hbs index 7581daf46f..4b1053397a 100644 --- a/app/assets/javascripts/discourse/templates/modal/keyboard-shortcuts-help.hbs +++ b/app/assets/javascripts/discourse/templates/modal/keyboard-shortcuts-help.hbs @@ -11,6 +11,7 @@
    • {{{i18n 'keyboard_shortcuts_help.jump_to.top'}}}
    • {{{i18n 'keyboard_shortcuts_help.jump_to.bookmarks'}}}
    • {{{i18n 'keyboard_shortcuts_help.jump_to.profile'}}}
    • +
    • {{{i18n 'keyboard_shortcuts_help.jump_to.messages'}}}
    • {{i18n 'keyboard_shortcuts_help.navigation.title'}}

        diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 2d75eef63a..76feed42e2 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2582,6 +2582,7 @@ en: top: 'g, t Top' bookmarks: 'g, b Bookmarks' profile: 'g, p Profile' + messages: 'g, m Messages' navigation: title: 'Navigation' jump: '# Go to post #' From a501947d67de04bc8000da9ce4e2b109a1603af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 2 Sep 2015 20:25:18 +0200 Subject: [PATCH 1087/1435] FEATURE: suppress categories from the homepage --- .../javascripts/discourse/models/category.js | 3 +- .../discourse/routes/build-topic-route.js.es6 | 13 +- .../components/edit-category-settings.hbs | 7 + app/controllers/categories_controller.rb | 4 +- app/controllers/list_controller.rb | 141 ++++++++---------- app/models/category_list.rb | 2 + app/serializers/category_serializer.rb | 5 + config/locales/client.en.yml | 1 + ..._add_suppress_from_homepage_to_category.rb | 5 + lib/discourse.rb | 8 - lib/topic_query.rb | 44 +++--- spec/controllers/list_controller_spec.rb | 18 +-- 12 files changed, 116 insertions(+), 135 deletions(-) create mode 100644 db/migrate/20150828155137_add_suppress_from_homepage_to_category.rb diff --git a/app/assets/javascripts/discourse/models/category.js b/app/assets/javascripts/discourse/models/category.js index 7c3cefc1e5..33b57894a7 100644 --- a/app/assets/javascripts/discourse/models/category.js +++ b/app/assets/javascripts/discourse/models/category.js @@ -77,7 +77,8 @@ Discourse.Category = Discourse.Model.extend({ background_url: this.get('background_url'), allow_badges: this.get('allow_badges'), custom_fields: this.get('custom_fields'), - topic_template: this.get('topic_template') + topic_template: this.get('topic_template'), + suppress_from_homepage: this.get('suppress_from_homepage'), }, type: this.get('id') ? 'PUT' : 'POST' }); diff --git a/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 b/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 index 185c2fd529..0011b03055 100644 --- a/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 @@ -40,7 +40,6 @@ function findTopicList(store, filter, filterParams, extras) { session.setProperties({topicList: null, topicListScrollPosition: null}); } - // Clean up any string parameters that might slip through filterParams = filterParams || {}; Ember.keys(filterParams).forEach(function(k) { @@ -50,17 +49,7 @@ function findTopicList(store, filter, filterParams, extras) { } }); - const findParams = {}; - Discourse.SiteSettings.top_menu.split('|').forEach(function (i) { - if (i.indexOf(filter) === 0) { - const exclude = i.split("-"); - if (exclude && exclude.length === 2) { - findParams.exclude_category = exclude[1]; - } - } - }); - return resolve(store.findFiltered('topicList', { filter, params:_.extend(findParams, filterParams || {})})); - + return resolve(store.findFiltered('topicList', { filter, params: filterParams || {} })); }).then(function(list) { list.set('listParams', filterParams); if (tracking) { diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs index 9dab35610f..dd943f9494 100644 --- a/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs +++ b/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs @@ -44,4 +44,11 @@ {{/if}}
      +
      + +
      + {{plugin-outlet "category-custom-settings"}} diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index 10500fc411..9d3ac191e9 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -16,6 +16,7 @@ class CategoriesController < ApplicationController options = {} options[:latest_posts] = params[:latest_posts] || SiteSetting.category_featured_topics options[:parent_category_id] = params[:parent_category_id] + options[:is_homepage] = current_homepage == "categories".freeze @list = CategoryList.new(guardian, options) @list.draft_key = Draft::NEW_TOPIC @@ -24,7 +25,7 @@ class CategoriesController < ApplicationController discourse_expires_in 1.minute - unless current_homepage == 'categories' + unless current_homepage == "categories" @title = I18n.t('js.filters.categories.title') end @@ -139,6 +140,7 @@ class CategoriesController < ApplicationController :position, :email_in, :email_in_allow_strangers, + :suppress_from_homepage, :parent_category_id, :auto_close_hours, :auto_close_based_on_last_post, diff --git a/app/controllers/list_controller.rb b/app/controllers/list_controller.rb index e5b6f63887..d894ee1f37 100644 --- a/app/controllers/list_controller.rb +++ b/app/controllers/list_controller.rb @@ -5,36 +5,34 @@ class ListController < ApplicationController skip_before_filter :check_xhr - @@categories = [ + before_filter :set_category, only: [ # filtered topics lists - Discourse.filters.map { |f| "category_#{f}".to_sym }, - Discourse.filters.map { |f| "category_none_#{f}".to_sym }, - Discourse.filters.map { |f| "parent_category_category_#{f}".to_sym }, - Discourse.filters.map { |f| "parent_category_category_none_#{f}".to_sym }, + Discourse.filters.map { |f| :"category_#{f}" }, + Discourse.filters.map { |f| :"category_none_#{f}" }, + Discourse.filters.map { |f| :"parent_category_category_#{f}" }, + Discourse.filters.map { |f| :"parent_category_category_none_#{f}" }, # top summaries :category_top, :category_none_top, :parent_category_category_top, # top pages (ie. with a period) - TopTopic.periods.map { |p| "category_top_#{p}".to_sym }, - TopTopic.periods.map { |p| "category_none_top_#{p}".to_sym }, - TopTopic.periods.map { |p| "parent_category_category_top_#{p}".to_sym }, + TopTopic.periods.map { |p| :"category_top_#{p}" }, + TopTopic.periods.map { |p| :"category_none_top_#{p}" }, + TopTopic.periods.map { |p| :"parent_category_category_top_#{p}" }, # category feeds :category_feed, ].flatten - before_filter :set_category, only: @@categories - before_filter :ensure_logged_in, except: [ :topics_by, # anonymous filters Discourse.anonymous_filters, - Discourse.anonymous_filters.map { |f| "#{f}_feed".to_sym }, + Discourse.anonymous_filters.map { |f| "#{f}_feed" }, # anonymous categorized filters - Discourse.anonymous_filters.map { |f| "category_#{f}".to_sym }, - Discourse.anonymous_filters.map { |f| "category_none_#{f}".to_sym }, - Discourse.anonymous_filters.map { |f| "parent_category_category_#{f}".to_sym }, - Discourse.anonymous_filters.map { |f| "parent_category_category_none_#{f}".to_sym }, + Discourse.anonymous_filters.map { |f| :"category_#{f}" }, + Discourse.anonymous_filters.map { |f| :"category_none_#{f}" }, + Discourse.anonymous_filters.map { |f| :"parent_category_category_#{f}" }, + Discourse.anonymous_filters.map { |f| :"parent_category_category_none_#{f}" }, # category feeds :category_feed, # top summaries @@ -43,14 +41,14 @@ class ListController < ApplicationController :category_none_top, :parent_category_category_top, # top pages (ie. with a period) - TopTopic.periods.map { |p| "top_#{p}".to_sym }, - TopTopic.periods.map { |p| "category_top_#{p}".to_sym }, - TopTopic.periods.map { |p| "category_none_top_#{p}".to_sym }, - TopTopic.periods.map { |p| "parent_category_category_top_#{p}".to_sym }, + TopTopic.periods.map { |p| :"top_#{p}" }, + TopTopic.periods.map { |p| :"category_top_#{p}" }, + TopTopic.periods.map { |p| :"category_none_top_#{p}" }, + TopTopic.periods.map { |p| :"parent_category_category_top_#{p}" }, ].flatten # Create our filters - Discourse.filters.each_with_index do |filter, idx| + Discourse.filters.each do |filter| define_method(filter) do |options = nil| list_opts = build_topic_list_options list_opts.merge!(options) if options @@ -60,6 +58,10 @@ class ListController < ApplicationController list_opts[:no_definitions] = true end + if filter.to_s == current_homepage + list_opts.merge!(exclude_category_ids: get_excluded_category_ids(list_opts[:category])) + end + list = TopicQuery.new(user, list_opts).public_send("list_#{filter}") list.more_topics_url = construct_url_with(:next, list_opts) list.prev_topics_url = construct_url_with(:prev, list_opts) @@ -83,34 +85,20 @@ class ListController < ApplicationController define_method("category_#{filter}") do canonical_url "#{Discourse.base_url}#{@category.url}" - self.send(filter, { category: @category.id }) + self.send(filter, category: @category.id) end define_method("category_none_#{filter}") do - self.send(filter, { category: @category.id, no_subcategories: true }) + self.send(filter, category: @category.id, no_subcategories: true) end define_method("parent_category_category_#{filter}") do canonical_url "#{Discourse.base_url}#{@category.url}" - self.send(filter, { category: @category.id }) + self.send(filter, category: @category.id) end define_method("parent_category_category_none_#{filter}") do - self.send(filter, { category: @category.id }) - end - end - - Discourse.feed_filters.each do |filter| - define_method("#{filter}_feed") do - discourse_expires_in 1.minute - - @title = "#{SiteSetting.title} - #{I18n.t("rss_description.#{filter}")}" - @link = "#{Discourse.base_url}/#{filter}" - @description = I18n.t("rss_description.#{filter}") - @atom_link = "#{Discourse.base_url}/#{filter}.rss" - @topic_list = TopicQuery.new(nil, order: 'created').public_send("list_#{filter}") - - render 'list', formats: [:rss] + self.send(filter, category: @category.id) end end @@ -127,14 +115,26 @@ class ListController < ApplicationController end end + def latest_feed + discourse_expires_in 1.minute + + @title = "#{SiteSetting.title} - #{I18n.t("rss_description.latest")}" + @link = "#{Discourse.base_url}/latest" + @atom_link = "#{Discourse.base_url}/latest.rss" + @description = I18n.t("rss_description.latest") + @topic_list = TopicQuery.new(nil, order: 'created').list_latest + + render 'list', formats: [:rss] + end + def category_feed guardian.ensure_can_see!(@category) discourse_expires_in 1.minute @title = @category.name @link = "#{Discourse.base_url}#{@category.url}" - @description = "#{I18n.t('topics_in_category', category: @category.name)} #{@category.description}" @atom_link = "#{Discourse.base_url}#{@category.url}.rss" + @description = "#{I18n.t('topics_in_category', category: @category.name)} #{@category.description}" @topic_list = TopicQuery.new.list_new_in_category(@category) render 'list', formats: [:rss] @@ -147,15 +147,15 @@ class ListController < ApplicationController end def category_top - top({ category: @category.id }) + top(category: @category.id) end def category_none_top - top({ category: @category.id, no_subcategories: true }) + top(category: @category.id, no_subcategories: true) end def parent_category_category_top - top({ category: @category.id }) + top(category: @category.id) end TopTopic.periods.each do |period| @@ -163,6 +163,11 @@ class ListController < ApplicationController top_options = build_topic_list_options top_options.merge!(options) if options top_options[:per_page] = SiteSetting.topics_per_period_in_top_page + + if "top".freeze == current_homepage + top_options.merge!(exclude_category_ids: get_excluded_category_ids(top_options[:category])) + end + user = list_target_user list = TopicQuery.new(user, top_options).list_top_for(period) list.for_period = period @@ -177,15 +182,15 @@ class ListController < ApplicationController end define_method("category_top_#{period}") do - self.send("top_#{period}", { category: @category.id }) + self.send("top_#{period}", category: @category.id) end define_method("category_none_top_#{period}") do - self.send("top_#{period}", { category: @category.id, no_subcategories: true }) + self.send("top_#{period}", category: @category.id, no_subcategories: true) end define_method("parent_category_category_top_#{period}") do - self.send("top_#{period}", { category: @category.id }) + self.send("top_#{period}", category: @category.id) end end @@ -204,16 +209,15 @@ class ListController < ApplicationController end end - private def page_params(opts = nil) opts ||= {} - route_params = {format: 'json'} - route_params[:category] = @category.slug_for_url if @category + route_params = { format: 'json' } + route_params[:category] = @category.slug_for_url if @category route_params[:parent_category] = @category.parent_category.slug_for_url if @category && @category.parent_category - route_params[:order] = opts[:order] if opts[:order].present? - route_params[:ascending] = opts[:ascending] if opts[:ascending].present? + route_params[:order] = opts[:order] if opts[:order].present? + route_params[:ascending] = opts[:ascending] if opts[:ascending].present? route_params end @@ -235,11 +239,10 @@ class ListController < ApplicationController end def build_topic_list_options - # exclude_category = 1. from params / 2. parsed from top menu / 3. nil options = { page: params[:page], topic_ids: param_to_integer_list(:topic_ids), - exclude_category: (params[:exclude_category] || select_menu_item.try(:filter)), + exclude_category_ids: params[:exclude_category_ids], category: params[:category], order: params[:order], ascending: params[:ascending], @@ -257,17 +260,6 @@ class ListController < ApplicationController options end - def select_menu_item - menu_item = SiteSetting.top_menu_items.select do |mu| - (mu.has_specific_category? && mu.specific_category == @category.try(:slug)) || - action_name == mu.name || - (action_name.include?("top") && mu.name == "top") - end.first - - menu_item = nil if menu_item.try(:has_specific_category?) && menu_item.specific_category == @category.try(:slug) - menu_item - end - def list_target_user if params[:user_id] && guardian.is_staff? User.find(params[:user_id].to_i) @@ -290,25 +282,16 @@ class ListController < ApplicationController url.sub('.json?','?') end - def generate_top_lists(options) - top = TopLists.new - - options[:per_page] = SiteSetting.topics_per_period_in_top_summary - topic_query = TopicQuery.new(current_user, options) - - periods = [ListController.best_period_for(current_user.try(:previous_visit_at), options[:category])] - - periods.each { |period| top.send("#{period}=", topic_query.list_top_for(period)) } - - top + def get_excluded_category_ids(current_category=nil) + exclude_category_ids = Category.where(suppress_from_homepage: true) + exclude_category_ids = exclude_category_ids.where.not(id: current_category) if current_category + exclude_category_ids.pluck(:id) end def self.best_period_for(previous_visit_at, category_id=nil) best_periods_for(previous_visit_at).each do |period| top_topics = TopTopic.where("#{period}_score > 0") - if category_id - top_topics = top_topics.joins(:topic).where("topics.category_id = ?", category_id) - end + top_topics = top_topics.joins(:topic).where("topics.category_id = ?", category_id) if category_id return period if top_topics.count >= SiteSetting.topics_per_period_in_top_page end # default period is yearly @@ -318,8 +301,8 @@ class ListController < ApplicationController def self.best_periods_for(date) date ||= 1.year.ago periods = [] - periods << :daily if date > 8.days.ago - periods << :weekly if date > 35.days.ago + periods << :daily if date > 8.days.ago + periods << :weekly if date > 35.days.ago periods << :monthly if date > 180.days.ago periods << :yearly periods diff --git a/app/models/category_list.rb b/app/models/category_list.rb index b5cae822b8..2e32369fda 100644 --- a/app/models/category_list.rb +++ b/app/models/category_list.rb @@ -66,6 +66,8 @@ class CategoryList @categories = @categories.where('categories.parent_category_id = ?', @options[:parent_category_id].to_i) end + @categories = @categories.where(suppress_from_homepage: false) if @options[:is_homepage] + if SiteSetting.fixed_category_positions @categories = @categories.order('position ASC').order('id ASC') else diff --git a/app/serializers/category_serializer.rb b/app/serializers/category_serializer.rb index 1757652094..1bf08f89fc 100644 --- a/app/serializers/category_serializer.rb +++ b/app/serializers/category_serializer.rb @@ -8,6 +8,7 @@ class CategorySerializer < BasicCategorySerializer :position, :email_in, :email_in_allow_strangers, + :suppress_from_homepage, :can_delete, :cannot_delete_reason, :allow_badges, @@ -56,4 +57,8 @@ class CategorySerializer < BasicCategorySerializer scope && scope.can_edit?(object) end + def include_suppress_from_homepage? + scope && scope.can_edit?(object) + end + end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 76feed42e2..7f4e13300e 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1539,6 +1539,7 @@ en: email_in_allow_strangers: "Accept emails from anonymous users with no accounts" email_in_disabled: "Posting new topics via email is disabled in the Site Settings. To enable posting new topics via email, " email_in_disabled_click: 'enable the "email in" setting.' + suppress_from_homepage: "Suppress this category from the homepage." allow_badges_label: "Allow badges to be awarded in this category" edit_permissions: "Edit Permissions" add_permission: "Add Permission" diff --git a/db/migrate/20150828155137_add_suppress_from_homepage_to_category.rb b/db/migrate/20150828155137_add_suppress_from_homepage_to_category.rb new file mode 100644 index 0000000000..e706f40f97 --- /dev/null +++ b/db/migrate/20150828155137_add_suppress_from_homepage_to_category.rb @@ -0,0 +1,5 @@ +class AddSuppressFromHomepageToCategory < ActiveRecord::Migration + def change + add_column :categories, :suppress_from_homepage, :boolean, default: false + end +end diff --git a/lib/discourse.rb b/lib/discourse.rb index b32fcf736a..82b1280eed 100644 --- a/lib/discourse.rb +++ b/lib/discourse.rb @@ -64,18 +64,10 @@ module Discourse @filters ||= [:latest, :unread, :new, :read, :posted, :bookmarks] end - def self.feed_filters - @feed_filters ||= [:latest] - end - def self.anonymous_filters @anonymous_filters ||= [:latest, :top, :categories] end - def self.logged_in_filters - @logged_in_filters ||= Discourse.filters - Discourse.anonymous_filters - end - def self.top_menu_items @top_menu_items ||= Discourse.filters + [:category, :categories, :top] end diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 9ffa5d8b59..25e2b6fd48 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -1,15 +1,16 @@ # -# Helps us find topics. Returns a TopicList object containing the topics -# found. +# Helps us find topics. +# Returns a TopicList object containing the topics found. # + require_dependency 'topic_list' require_dependency 'suggested_topics_builder' require_dependency 'topic_query_sql' class TopicQuery # Could be rewritten to %i if Ruby 1.9 is no longer supported - VALID_OPTIONS = %w(except_topic_ids - exclude_category + VALID_OPTIONS = %i(except_topic_ids + exclude_category_ids limit page per_page @@ -27,8 +28,7 @@ class TopicQuery search slow_platform filter - q - ).map(&:to_sym) + q) # Maps `order` to a columns in `topics` SORTABLE_MAPPING = { @@ -301,14 +301,17 @@ class TopicQuery if options[:no_subcategories] result = result.where('categories.id = ?', category_id) else - result = result.where('categories.id = ? or (categories.parent_category_id = ? AND categories.topic_id <> topics.id)', category_id, category_id) + result = result.where('categories.id = :category_id OR (categories.parent_category_id = :category_id AND categories.topic_id <> topics.id)', category_id: category_id) end result = result.references(:categories) end result = apply_ordering(result, options) result = result.listable_topics.includes(:category) - result = result.where('categories.name is null or categories.name <> ?', options[:exclude_category]).references(:categories) if options[:exclude_category] + + if options[:exclude_category_ids] && options[:exclude_category_ids].is_a?(Array) && options[:exclude_category_ids].size > 0 + result = result.where("categories.id NOT IN (?)", options[:exclude_category_ids]).references(:categories) + end # Don't include the category topics if excluded if options[:no_definitions] @@ -393,19 +396,20 @@ class TopicQuery def remove_muted_categories(list, user, opts=nil) category_id = get_category_id(opts[:exclude]) if opts + if user - list = list.where("NOT EXISTS( - SELECT 1 FROM category_users cu - WHERE cu.user_id = ? AND - cu.category_id = topics.category_id AND - cu.notification_level = ? AND - cu.category_id <> ? - )", - user.id, - CategoryUser.notification_levels[:muted], - category_id || -1 - ) - .references('cu') + list = list.references("cu") + .where(" + NOT EXISTS ( + SELECT 1 + FROM category_users cu + WHERE cu.user_id = :user_id + AND cu.category_id = topics.category_id + AND cu.notification_level = :muted + AND cu.category_id <> :category_id + )", user_id: user.id, + muted: CategoryUser.notification_levels[:muted], + category_id: category_id || -1) end list diff --git a/spec/controllers/list_controller_spec.rb b/spec/controllers/list_controller_spec.rb index 6f55de5b60..41f343ca10 100644 --- a/spec/controllers/list_controller_spec.rb +++ b/spec/controllers/list_controller_spec.rb @@ -32,12 +32,6 @@ describe ListController do end end - Discourse.logged_in_filters.each do |filter| - context "#{filter}" do - it { expect { xhr :get, filter }.to raise_error(Discourse::NotLoggedIn) } - end - end - it 'allows users to filter on a set of topic ids' do p = create_post @@ -51,14 +45,10 @@ describe ListController do describe 'RSS feeds' do - Discourse.feed_filters.each do |filter| - - it 'renders RSS' do - get "#{filter}_feed", format: :rss - expect(response).to be_success - expect(response.content_type).to eq('application/rss+xml') - end - + it 'renders RSS' do + get "latest_feed", format: :rss + expect(response).to be_success + expect(response.content_type).to eq('application/rss+xml') end end From b3a930f2edfb79efbaddc9c35cc30952900beb09 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 2 Sep 2015 14:12:40 -0400 Subject: [PATCH 1088/1435] DRY up header height calculation --- .../discourse/components/menu-panel.js.es6 | 7 +-- .../discourse/views/composer.js.es6 | 7 +-- .../javascripts/discourse/views/header.js.es6 | 55 ++++++++++--------- app/assets/javascripts/main_include.js | 1 + 4 files changed, 34 insertions(+), 36 deletions(-) diff --git a/app/assets/javascripts/discourse/components/menu-panel.js.es6 b/app/assets/javascripts/discourse/components/menu-panel.js.es6 index 6d1edefb26..a6a0b725a5 100644 --- a/app/assets/javascripts/discourse/components/menu-panel.js.es6 +++ b/app/assets/javascripts/discourse/components/menu-panel.js.es6 @@ -1,4 +1,5 @@ import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators'; +import { headerHeight } from 'discourse/views/header'; const PANEL_BODY_MARGIN = 30; const mutationSupport = !!window['MutationObserver']; @@ -46,11 +47,7 @@ export default Ember.Component.extend({ $('body').addClass('drop-down-visible'); } else { $panelBody.height('auto'); - const $header = $('header.d-header'); - const headerOffset = $header.offset(); - const headerOffsetTop = (headerOffset) ? headerOffset.top : 0; - const headerHeight = parseInt($header.height() + headerOffsetTop - $window.scrollTop() + 3); - this.$().css({ left: "auto", top: headerHeight + "px" }); + this.$().css({ left: "auto", top: headerHeight() + "px" }); $('body').removeClass('drop-down-visible'); } diff --git a/app/assets/javascripts/discourse/views/composer.js.es6 b/app/assets/javascripts/discourse/views/composer.js.es6 index a8b4948018..b17c9c73ba 100644 --- a/app/assets/javascripts/discourse/views/composer.js.es6 +++ b/app/assets/javascripts/discourse/views/composer.js.es6 @@ -5,6 +5,7 @@ import avatarTemplate from 'discourse/lib/avatar-template'; import positioningWorkaround from 'discourse/lib/safari-hacks'; import debounce from 'discourse/lib/debounce'; import { linkSeenMentions, fetchUnseenMentions } from 'discourse/lib/link-mentions'; +import { headerHeight } from 'discourse/views/header'; const ComposerView = Ember.View.extend(Ember.Evented, { _lastKeyTimeout: null, @@ -124,11 +125,7 @@ const ComposerView = Ember.View.extend(Ember.Evented, { $replyControl.DivResizer({ maxHeight(winHeight) { - const $header = $('header.d-header'); - const headerOffset = $header.offset(); - const headerOffsetTop = (headerOffset) ? headerOffset.top : 0; - const headerHeight = parseInt($header.height() + headerOffsetTop - $(window).scrollTop() + 5); - return winHeight - headerHeight; + return winHeight - headerHeight(); }, resize: runResize, onDrag: (sizePx) => this.movePanels(sizePx) diff --git a/app/assets/javascripts/discourse/views/header.js.es6 b/app/assets/javascripts/discourse/views/header.js.es6 index d7088739c3..17062acde2 100644 --- a/app/assets/javascripts/discourse/views/header.js.es6 +++ b/app/assets/javascripts/discourse/views/header.js.es6 @@ -1,52 +1,55 @@ +import { on } from 'ember-addons/ember-computed-decorators'; + export default Ember.View.extend({ tagName: 'header', classNames: ['d-header', 'clearfix'], classNameBindings: ['editingTopic'], templateName: 'header', - examineDockHeader: function() { - var headerView = this; - + examineDockHeader() { // Check the dock after the current run loop. While rendering, // it's much slower to calculate `outlet.offset()` - Em.run.next(function () { - if (!headerView.docAt) { - var outlet = $('#main-outlet'); + Ember.run.next(() => { + if (!this.docAt) { + const outlet = $('#main-outlet'); if (!(outlet && outlet.length === 1)) return; - headerView.docAt = outlet.offset().top; + this.docAt = outlet.offset().top; } - var offset = window.pageYOffset || $('html').scrollTop(); - if (offset >= headerView.docAt) { - if (!headerView.dockedHeader) { + const offset = window.pageYOffset || $('html').scrollTop(); + if (offset >= this.docAt) { + if (!this.dockedHeader) { $('body').addClass('docked'); - headerView.dockedHeader = true; + this.dockedHeader = true; } } else { - if (headerView.dockedHeader) { + if (this.dockedHeader) { $('body').removeClass('docked'); - headerView.dockedHeader = false; + this.dockedHeader = false; } } }); }, - _tearDown: function() { + @on('willDestroyElement') + _tearDown() { $(window).unbind('scroll.discourse-dock'); $(document).unbind('touchmove.discourse-dock'); this.$('a.unread-private-messages, a.unread-notifications, a[data-notifications]').off('click.notifications'); $('body').off('keydown.header'); - }.on('willDestroyElement'), + }, - _setup: function() { - const self = this; - - $(window).bind('scroll.discourse-dock', function() { - self.examineDockHeader(); - }); - $(document).bind('touchmove.discourse-dock', function() { - self.examineDockHeader(); - }); - self.examineDockHeader(); - }.on('didInsertElement') + @on('didInsertElement') + _setup() { + $(window).bind('scroll.discourse-dock', () => this.examineDockHeader()); + $(document).bind('touchmove.discourse-dock', () => this.examineDockHeader()); + this.examineDockHeader(); + } }); + +export function headerHeight() { + const $header = $('header.d-header'); + const headerOffset = $header.offset(); + const headerOffsetTop = (headerOffset) ? headerOffset.top : 0; + return parseInt($header.height() + headerOffsetTop - $(window).scrollTop() + 5); +} diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js index 25238bf923..1181452b4f 100644 --- a/app/assets/javascripts/main_include.js +++ b/app/assets/javascripts/main_include.js @@ -73,6 +73,7 @@ //= require ./discourse/components/notifications-button //= require ./discourse/components/topic-notifications-button //= require ./discourse/lib/link-mentions +//= require ./discourse/views/header //= require ./discourse/views/composer //= require ./discourse/lib/show-modal //= require ./discourse/lib/screen-track From e624b7198dbff56cae33ada62bf5b51cff8138fa Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 2 Sep 2015 14:29:53 -0400 Subject: [PATCH 1089/1435] Try to estimate the amount of notifications to return based on height --- .../javascripts/discourse/components/user-menu.js.es6 | 6 +++++- app/controllers/notifications_controller.rb | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/components/user-menu.js.es6 b/app/assets/javascripts/discourse/components/user-menu.js.es6 index a6a7e46be8..b60a0eb828 100644 --- a/app/assets/javascripts/discourse/components/user-menu.js.es6 +++ b/app/assets/javascripts/discourse/components/user-menu.js.es6 @@ -1,5 +1,6 @@ import { url } from 'discourse/lib/computed'; import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; +import { headerHeight } from 'discourse/views/header'; export default Ember.Component.extend({ classNames: ['user-menu'], @@ -43,10 +44,13 @@ export default Ember.Component.extend({ refreshNotifications() { if (this.get('loadingNotifications')) { return; } + // estimate (poorly) the amount of notifications to return + const limit = Math.round(($(window).height() - headerHeight()) / 50); + // TODO: It's a bit odd to use the store in a component, but this one really // wants to reach out and grab notifications const store = this.container.lookup('store:main'); - const stale = store.findStale('notification', {recent: true}); + const stale = store.findStale('notification', {recent: true, limit }); if (stale.hasResults) { this.set('notifications', stale.results); diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index 407d63ce2d..53e26afda2 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -7,7 +7,11 @@ class NotificationsController < ApplicationController def index user = current_user if params[:recent].present? - notifications = Notification.recent_report(current_user, 15) + + limit = params[:limit].to_i || 15 + limit = 50 if limit > 50 + + notifications = Notification.recent_report(current_user, limit) if notifications.present? # ordering can be off due to PMs From 5984b62347da402a48d202289195e9c3d5b386cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 2 Sep 2015 20:43:15 +0200 Subject: [PATCH 1090/1435] FIX: ensure we remove 'category_users' records when a user is deleted --- app/models/user.rb | 3 ++- spec/models/category_user_spec.rb | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index 8aa61600b4..a329f88dc0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -17,11 +17,12 @@ class User < ActiveRecord::Base has_many :posts has_many :notifications, dependent: :destroy has_many :topic_users, dependent: :destroy + has_many :category_users, dependent: :destroy has_many :topics has_many :user_open_ids, dependent: :destroy has_many :user_actions, dependent: :destroy has_many :post_actions, dependent: :destroy - has_many :user_badges, -> {where('user_badges.badge_id IN (SELECT id FROM badges where enabled)')}, dependent: :destroy + has_many :user_badges, -> { where('user_badges.badge_id IN (SELECT id FROM badges WHERE enabled)') }, dependent: :destroy has_many :badges, through: :user_badges has_many :email_logs, dependent: :delete_all has_many :post_timings diff --git a/spec/models/category_user_spec.rb b/spec/models/category_user_spec.rb index 3492713999..2365f611b5 100644 --- a/spec/models/category_user_spec.rb +++ b/spec/models/category_user_spec.rb @@ -80,6 +80,19 @@ describe CategoryUser do expect(TopicUser.get(post.topic, user)).to be_blank end + it "is destroyed when a user is deleted" do + user = Fabricate(:user) + category = Fabricate(:category) + + CategoryUser.create!(user: user, category: category, notification_level: CategoryUser.notification_levels[:watching]) + + expect(CategoryUser.where(user_id: user.id).count).to eq(1) + + user.destroy! + + expect(CategoryUser.where(user_id: user.id).count).to eq(0) + end + end end From f11bdd13fcdf0952bed3d912afe48266636ce295 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 2 Sep 2015 15:12:35 -0400 Subject: [PATCH 1091/1435] FIX: Menu panels scrolled weird in iOS --- .../javascripts/discourse/components/menu-panel.js.es6 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/components/menu-panel.js.es6 b/app/assets/javascripts/discourse/components/menu-panel.js.es6 index a6a0b725a5..18a36917c0 100644 --- a/app/assets/javascripts/discourse/components/menu-panel.js.es6 +++ b/app/assets/javascripts/discourse/components/menu-panel.js.es6 @@ -79,7 +79,11 @@ export default Ember.Component.extend({ }); this.performLayout(); this._watchSizeChanges(); - $(window).on('scroll.discourse-menu-panel', () => this.performLayout()); + + // iOS does not handle scroll events well + if (!this.capabilities.touch) { + $(window).on('scroll.discourse-menu-panel', () => this.performLayout()); + } } else { Ember.run.scheduleOnce('afterRender', () => this.sendAction('onHidden')); $('html').off('click.close-menu-panel'); @@ -175,7 +179,7 @@ export default Ember.Component.extend({ $('body').off('keydown.discourse-menu-panel'); $('html').off('click.close-menu-panel'); $(window).off('resize.discourse-menu-panel'); - $(window).off('scroll.discourse-menu-panel'); + $(window).off('scroll.discourse-menu-panel'); }, hide() { From d1717cdb12dc58fd06959c72ffb5a82b7f666801 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 2 Sep 2015 15:33:44 -0400 Subject: [PATCH 1092/1435] FIX: Safer JS code --- .../javascripts/discourse/components/menu-panel.js.es6 | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/components/menu-panel.js.es6 b/app/assets/javascripts/discourse/components/menu-panel.js.es6 index 18a36917c0..e92e855798 100644 --- a/app/assets/javascripts/discourse/components/menu-panel.js.es6 +++ b/app/assets/javascripts/discourse/components/menu-panel.js.es6 @@ -125,9 +125,13 @@ export default Ember.Component.extend({ clearInterval(this._resizeInterval); this._resizeInterval = setInterval(() => { Ember.run(() => { - const contentHeight = parseInt(this.$('.panel-body-contents').height()); - if (contentHeight !== this._lastHeight) { this.performLayout(); } - this._lastHeight = contentHeight; + const $panelBodyContents = this.$('.panel-body-contents'); + + if ($panelBodyContents.length) { + const contentHeight = parseInt($panelBodyContents.height()); + if (contentHeight !== this._lastHeight) { this.performLayout(); } + this._lastHeight = contentHeight; + } }); }, 500); } From 4a6f617f4da1e7301ef9980f465c4aaed11711ea Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 2 Sep 2015 15:42:20 -0400 Subject: [PATCH 1093/1435] UX: Long category names pushed badges to a new line in the hamburger --- app/assets/stylesheets/common/base/menu-panel.scss | 8 ++++++++ .../stylesheets/common/components/badges.css.scss | 11 ----------- app/assets/stylesheets/mobile.scss | 1 + app/assets/stylesheets/mobile/menu-panel.scss | 7 +++++++ 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss index 7ec8405613..4151fdab16 100644 --- a/app/assets/stylesheets/common/base/menu-panel.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -67,6 +67,7 @@ color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%)); background-color: transparent; display: inline; + padding: 0; } } @@ -76,6 +77,13 @@ font-weight: normal; font-size: 11px; } + + span.badge-category { + max-width: 90px; + overflow: hidden; + text-overflow: ellipsis; + } + } .search-menu { diff --git a/app/assets/stylesheets/common/components/badges.css.scss b/app/assets/stylesheets/common/components/badges.css.scss index 47fb1a1cb6..ed6dec22eb 100644 --- a/app/assets/stylesheets/common/components/badges.css.scss +++ b/app/assets/stylesheets/common/components/badges.css.scss @@ -126,17 +126,6 @@ } } } - - span.badge-category { - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - - .menu-panel & { - max-width: 90px; - } - } - } // Category badge dropdown diff --git a/app/assets/stylesheets/mobile.scss b/app/assets/stylesheets/mobile.scss index 91bf389d17..984da0c668 100644 --- a/app/assets/stylesheets/mobile.scss +++ b/app/assets/stylesheets/mobile.scss @@ -18,6 +18,7 @@ @import "mobile/user"; @import "mobile/history"; @import "mobile/directory"; +@import "mobile/menu-panel"; /* These files doesn't actually exist, they are injected by DiscourseSassImporter. */ diff --git a/app/assets/stylesheets/mobile/menu-panel.scss b/app/assets/stylesheets/mobile/menu-panel.scss index e69de29bb2..972ea30ab2 100644 --- a/app/assets/stylesheets/mobile/menu-panel.scss +++ b/app/assets/stylesheets/mobile/menu-panel.scss @@ -0,0 +1,7 @@ +.menu-panel { + span.badge-category { + max-width: 85px; + overflow: hidden; + text-overflow: ellipsis; + } +} From 85154422f129113352f4cbb1099a670ae85d2038 Mon Sep 17 00:00:00 2001 From: Tobias Eigen Date: Wed, 2 Sep 2015 12:46:47 -0700 Subject: [PATCH 1094/1435] Fix typo about table settings in server.en.yml Intrepid n00b pull request at encouragement by @zogstrip. Fixing a small typo, as discussed on meta: https://meta.discourse.org/t/typo-in-description-of-allow-html-tables-admin-setting/32835?u=tobiaseigen --- config/locales/server.en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 77a8754d88..39ca89934e 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -846,7 +846,7 @@ en: flag_sockpuppets: "If a new user replies to a topic from the same IP address as the new user who started the topic, flag both of their posts as potential spam." traditional_markdown_linebreaks: "Use traditional linebreaks in Markdown, which require two trailing spaces for a linebreak." - allow_html_tables: "Allow tables to be entered in Markdown using HTML tags, TABLE, THEAD, TD, TR, TH are whiteliseted (requires full rebake on all old posts containing tables)" + allow_html_tables: "Allow tables to be entered in Markdown using HTML tags, TABLE, THEAD, TD, TR, TH are whitelisted (requires full rebake on all old posts containing tables)" 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!" ga_tracking_code: "Google analytics (ga.js) tracking code code, eg: UA-12345678-9; see http://google.com/analytics" From 73dba5af38d852dbe3e48615f010f12ec837c056 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 2 Sep 2015 15:48:41 -0400 Subject: [PATCH 1095/1435] FIX: Notifications when no limit is provided --- app/controllers/notifications_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index 53e26afda2..9831ee88c3 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -8,7 +8,7 @@ class NotificationsController < ApplicationController user = current_user if params[:recent].present? - limit = params[:limit].to_i || 15 + limit = (params[:limit] || 15).to_i limit = 50 if limit > 50 notifications = Notification.recent_report(current_user, limit) From 2b9b29c8c8cdb001f0eb31ebf2371b22284da490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 2 Sep 2015 22:02:31 +0200 Subject: [PATCH 1096/1435] FIX: ensure CategoryUser consistency --- app/jobs/scheduled/ensure_db_consistency.rb | 1 + app/models/category_user.rb | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/app/jobs/scheduled/ensure_db_consistency.rb b/app/jobs/scheduled/ensure_db_consistency.rb index 274c666dea..c565023e5b 100644 --- a/app/jobs/scheduled/ensure_db_consistency.rb +++ b/app/jobs/scheduled/ensure_db_consistency.rb @@ -13,6 +13,7 @@ module Jobs UserStat.update_view_counts(13.hours.ago) Topic.ensure_consistency! Badge.ensure_consistency! + CategoryUser.ensure_consistency! end end end diff --git a/app/models/category_user.rb b/app/models/category_user.rb index 1fb537f546..cd7b719afd 100644 --- a/app/models/category_user.rb +++ b/app/models/category_user.rb @@ -92,6 +92,10 @@ class CategoryUser < ActiveRecord::Base ) end + def self.ensure_consistency! + exec_sql("DELETE FROM category_users WHERE user_id NOT IN (SELECT id FROM users)") + end + private_class_method :apply_default_to_topic, :remove_default_from_topic end From 286738c71268e1a34e49af71d1a336e2c981c800 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 2 Sep 2015 16:17:46 -0400 Subject: [PATCH 1097/1435] FIX: Include dummy capabilities object in component tests --- test/javascripts/helpers/component-test.js.es6 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/javascripts/helpers/component-test.js.es6 b/test/javascripts/helpers/component-test.js.es6 index f2ec007a4d..181ae53317 100644 --- a/test/javascripts/helpers/component-test.js.es6 +++ b/test/javascripts/helpers/component-test.js.es6 @@ -13,8 +13,10 @@ export default function(name, opts) { this.container.register('site-settings:main', Discourse.SiteSettings, { instantiate: false }); this.container.register('app-events:main', appEvents, { instantiate: false }); + this.container.register('capabilities:main', Ember.Object); this.container.injection('component', 'siteSettings', 'site-settings:main'); this.container.injection('component', 'appEvents', 'app-events:main'); + this.container.injection('component', 'capabilities', 'capabilities:main'); andThen(() => { this.render(opts.template); From 0cd393f310b4090a95de642fe2dcf9a95e8931c1 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 2 Sep 2015 16:46:25 -0400 Subject: [PATCH 1098/1435] Experiment with variable heights for slide-in menus --- .../discourse/components/menu-panel.js.es6 | 21 +++++++++++++------ .../stylesheets/common/base/menu-panel.scss | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/discourse/components/menu-panel.js.es6 b/app/assets/javascripts/discourse/components/menu-panel.js.es6 index e92e855798..8b5767137a 100644 --- a/app/assets/javascripts/discourse/components/menu-panel.js.es6 +++ b/app/assets/javascripts/discourse/components/menu-panel.js.es6 @@ -22,32 +22,41 @@ export default Ember.Component.extend({ const viewMode = this.get('viewMode'); const $panelBody = this.$('.panel-body'); + let contentHeight = parseInt(this.$('.panel-body-contents').height()); if (viewMode === 'drop-down') { const $buttonPanel = $('header ul.icons'); if ($buttonPanel.length === 0) { return; } const buttonPanelPos = $buttonPanel.offset(); - const posTop = parseInt(buttonPanelPos.top + $buttonPanel.height() - $('header.d-header').offset().top); const posLeft = parseInt(buttonPanelPos.left + $buttonPanel.width() - width); - this.$().css({ left: posLeft + "px", top: posTop + "px" }); + this.$().css({ left: posLeft + "px", top: posTop + "px", height: 'auto' }); // adjust panel height - let contentHeight = parseInt(this.$('.panel-body-contents').height()); const fullHeight = parseInt($window.height()); - const offsetTop = this.$().offset().top; const scrollTop = $window.scrollTop(); + if (contentHeight + (offsetTop - scrollTop) + PANEL_BODY_MARGIN > fullHeight) { contentHeight = fullHeight - (offsetTop - scrollTop) - PANEL_BODY_MARGIN; } $panelBody.height(contentHeight); $('body').addClass('drop-down-visible'); } else { - $panelBody.height('auto'); - this.$().css({ left: "auto", top: headerHeight() + "px" }); + + const menuTop = headerHeight(); + + let height; + if ((menuTop + contentHeight) < ($(window).height() - 20)) { + height = contentHeight + "px"; + } else { + height = $(window).height() - menuTop; + } + + $panelBody.height('100%'); + this.$().css({ left: "auto", top: (menuTop - 2) + "px", height }); $('body').removeClass('drop-down-visible'); } diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss index 4151fdab16..8a599647f1 100644 --- a/app/assets/stylesheets/common/base/menu-panel.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -2,7 +2,7 @@ position: fixed; right: 0; top: 0; - height: 100%; + .panel-body { position: absolute; top: 3px; From d34f42d2f70bd9c880e97cf63e7a38646567a59c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 2 Sep 2015 23:46:04 +0200 Subject: [PATCH 1099/1435] FIX: hide category column in topic list only when the current category has no children --- app/assets/javascripts/discourse/models/topic-list.js.es6 | 5 ++--- app/models/category.rb | 2 +- app/models/site.rb | 7 ++++--- app/serializers/basic_category_serializer.rb | 3 ++- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/discourse/models/topic-list.js.es6 b/app/assets/javascripts/discourse/models/topic-list.js.es6 index 782cb9fd06..cf65eb212a 100644 --- a/app/assets/javascripts/discourse/models/topic-list.js.es6 +++ b/app/assets/javascripts/discourse/models/topic-list.js.es6 @@ -163,10 +163,9 @@ TopicList.reopenClass({ return this.find(filter); }, - // Sets `hideCategory` if all topics in the last have a particular category + // hide the category when it has no children hideUniformCategory(list, category) { - const hideCategory = !list.get('topics').any(function (t) { return t.get('category') !== category; }); - list.set('hideCategory', hideCategory); + list.set('hideCategory', !category.get("has_children")); } }); diff --git a/app/models/category.rb b/app/models/category.rb index 1f6424d71a..311f3fad50 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -79,7 +79,7 @@ class Category < ActiveRecord::Base # permission is just used by serialization # we may consider wrapping this in another spot - attr_accessor :displayable_topics, :permission, :subcategory_ids, :notification_level + attr_accessor :displayable_topics, :permission, :subcategory_ids, :notification_level, :has_children def self.last_updated_at order('updated_at desc').limit(1).pluck(:updated_at).first.to_i diff --git a/app/models/site.rb b/app/models/site.rb index 719cf1d712..c2fcfeb4a4 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -30,7 +30,7 @@ class Site end def groups - @groups ||= Group.order(:name).map { |g| {:id => g.id, :name => g.name}} + @groups ||= Group.order(:name).map { |g| { id: g.id, name: g.name } } end def user_fields @@ -41,7 +41,7 @@ class Site @categories ||= begin categories = Category .secured(@guardian) - .includes(:topic_only_relative_url) + .includes(:topic_only_relative_url, :subcategories) .order(:position) unless SiteSetting.allow_uncategorized_topics @@ -62,10 +62,11 @@ class Site categories.each do |category| category.notification_level = category_user[category.id] category.permission = CategoryGroup.permission_types[:full] if allowed_topic_create.include?(category.id) + category.has_children = category.subcategories.present? by_id[category.id] = category end - categories.reject! {|c| c.parent_category_id && !by_id[c.parent_category_id]} + categories.reject! { |c| c.parent_category_id && !by_id[c.parent_category_id] } categories end end diff --git a/app/serializers/basic_category_serializer.rb b/app/serializers/basic_category_serializer.rb index 45b8243297..96b0268cfa 100644 --- a/app/serializers/basic_category_serializer.rb +++ b/app/serializers/basic_category_serializer.rb @@ -17,7 +17,8 @@ class BasicCategorySerializer < ApplicationSerializer :logo_url, :background_url, :can_edit, - :topic_template + :topic_template, + :has_children def include_parent_category_id? parent_category_id From be6e6dc129ee7d47bf372b6fd5034d1fd29b7a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Thu, 3 Sep 2015 00:22:25 +0200 Subject: [PATCH 1100/1435] UX: tidy up the category settings tab --- .../components/auto-close-form.js.es6 | 28 ++++--- .../components/edit-category-settings.hbs | 83 ++++++++++--------- .../stylesheets/common/base/compose.scss | 3 + 3 files changed, 64 insertions(+), 50 deletions(-) diff --git a/app/assets/javascripts/discourse/components/auto-close-form.js.es6 b/app/assets/javascripts/discourse/components/auto-close-form.js.es6 index fdad14df53..5166910106 100644 --- a/app/assets/javascripts/discourse/components/auto-close-form.js.es6 +++ b/app/assets/javascripts/discourse/components/auto-close-form.js.es6 @@ -1,25 +1,29 @@ +import computed from "ember-addons/ember-computed-decorators"; +import { observes } from "ember-addons/ember-computed-decorators"; + export default Ember.Component.extend({ autoCloseValid: false, limited: false, - autoCloseUnits: function() { - var key = this.get("limited") ? "composer.auto_close.limited.units" - : "composer.auto_close.all.units"; + @computed("limited") + autoCloseUnits(limited) { + const key = limited ? "composer.auto_close.limited.units" : "composer.auto_close.all.units"; return I18n.t(key); - }.property("limited"), + }, - autoCloseExamples: function() { - var key = this.get("limited") ? "composer.auto_close.limited.examples" - : "composer.auto_close.all.examples"; + @computed("limited") + autoCloseExamples(limited) { + const key = limited ? "composer.auto_close.limited.examples" : "composer.auto_close.all.examples"; return I18n.t(key); - }.property("limited"), + }, - _updateAutoCloseValid: function() { - var isValid = this._isAutoCloseValid(this.get("autoCloseTime"), this.get("limited")); + @observes("autoCloseTime", "limited") + _updateAutoCloseValid(autoCloseTime, limited) { + var isValid = this._isAutoCloseValid(autoCloseTime, limited); this.set("autoCloseValid", isValid); - }.observes("autoCloseTime", "limited"), + }, - _isAutoCloseValid: function(autoCloseTime, limited) { + _isAutoCloseValid(autoCloseTime, limited) { var t = (autoCloseTime || "").toString().trim(); if (t.length === 0) { // "empty" is always valid diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs index dd943f9494..722a9f5e1f 100644 --- a/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs +++ b/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs @@ -1,54 +1,61 @@
      {{auto-close-form autoCloseTime=category.auto_close_hours autoCloseBasedOnLastPost=category.auto_close_based_on_last_post + autoCloseExamples="" limited="true" }}
      -
      -
      - {{input type="checkbox" checked=category.allow_badges}} - {{i18n 'category.allow_badges_label'}} -
      -
      -
      - -
      - -
      - -
      - {{#if showPositionInput}} - - {{text-field value=category.position class="position-input"}} - {{else}} - {{i18n 'category.position_disabled'}} - {{i18n 'category.position_disabled_click'}} - {{/if}} +
      -
      +{{#if emailInEnabled}} +
      + +
      +
      + +
      +{{/if}} + +{{#if showPositionInput}} +
      + +
      +{{/if}} + +{{#unless emailInEnabled}} +
      + {{i18n 'category.email_in_disabled'}} + {{i18n 'category.email_in_disabled_click'}} +
      +{{/unless}} + +{{#unless showPositionInput}} +
      + {{i18n 'category.position_disabled'}} + {{i18n 'category.position_disabled_click'}} +
      +{{/unless}} + {{plugin-outlet "category-custom-settings"}} diff --git a/app/assets/stylesheets/common/base/compose.scss b/app/assets/stylesheets/common/base/compose.scss index 4ebe91a7ee..844a9e32d3 100644 --- a/app/assets/stylesheets/common/base/compose.scss +++ b/app/assets/stylesheets/common/base/compose.scss @@ -130,6 +130,9 @@ div.ac-wrap { input[type=text] { width: 50px; } + label { + font-size: .929em; + } } } From b97764554b195d2416f50c9de359c65d78f3d6d7 Mon Sep 17 00:00:00 2001 From: Anton Davydov Date: Thu, 3 Sep 2015 01:24:02 +0300 Subject: [PATCH 1101/1435] Add plugin for displaying sidekiq statistic in web ui --- Gemfile | 1 + Gemfile.lock | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Gemfile b/Gemfile index 98cdfdd790..e8b2403e5a 100644 --- a/Gemfile +++ b/Gemfile @@ -90,6 +90,7 @@ gem 'rinku' gem 'sanitize' gem 'sass' gem 'sidekiq' +gem 'sidekiq-statistic' # for sidekiq web gem 'sinatra', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 607e318db6..338070cfa7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -333,6 +333,8 @@ GEM json (~> 1.0) redis (~> 3.2, >= 3.2.1) redis-namespace (~> 1.5, >= 1.5.2) + sidekiq-statistic (1.1.0) + sidekiq (~> 3.3, >= 3.3.4) simple-rss (1.3.1) simplecov (0.9.1) docile (~> 1.1.0) @@ -474,6 +476,7 @@ DEPENDENCIES seed-fu (~> 2.3.3) shoulda sidekiq + sidekiq-statistic simple-rss simplecov sinatra From 25fb684565247acb5a694865d68d67ba0622fa93 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 3 Sep 2015 12:00:19 +1000 Subject: [PATCH 1102/1435] ensure statistic collection is on --- config/initializers/sidekiq.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 7a5781f146..bfac8028f1 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -6,9 +6,11 @@ end Sidekiq.configure_server do |config| config.redis = Discourse.sidekiq_redis_config - # add our pausable middleware + config.server_middleware do |chain| chain.add Sidekiq::Pausable + # ensure statistic middleware is included in case of a fork + chain.add Sidekiq::Statistic::Middleware end end From 73e6eebde88a99775eb9c6742072ce97194f0550 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Thu, 3 Sep 2015 13:46:55 +0530 Subject: [PATCH 1103/1435] UX: fix group header font color --- app/assets/stylesheets/desktop/user.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss index 9ce019329d..ea4202fb0c 100644 --- a/app/assets/stylesheets/desktop/user.scss +++ b/app/assets/stylesheets/desktop/user.scss @@ -225,6 +225,7 @@ .details { padding: 15px; margin: 0; + color: dark-light-choose($secondary, lighten($primary, 10%)); } } From a77d5d0cefd11c1a6bd4bc7cb09de21755d1c365 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Thu, 3 Sep 2015 00:09:44 +0800 Subject: [PATCH 1104/1435] UX: Make autocomplete usable on mobile. --- app/assets/javascripts/discourse/lib/autocomplete.js.es6 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/assets/javascripts/discourse/lib/autocomplete.js.es6 b/app/assets/javascripts/discourse/lib/autocomplete.js.es6 index 7750475c3b..6b7ec5e140 100644 --- a/app/assets/javascripts/discourse/lib/autocomplete.js.es6 +++ b/app/assets/javascripts/discourse/lib/autocomplete.js.es6 @@ -220,6 +220,13 @@ export default function(options) { vOffset = div.height(); } + if (Discourse.Mobile.mobileView && !isInput) { + div.css('width', 'auto'); + + if ((me.height() / 2) >= pos.top) { vOffset = -23; } + if ((me.width() / 2) <= pos.left) { hOffset = -div.width(); } + } + var mePos = me.position(); var borderTop = parseInt(me.css('border-top-width'), 10) || 0; div.css({ From 6a25a62e631ce39fd02bb0fd08001157e7589b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Thu, 3 Sep 2015 11:56:33 +0200 Subject: [PATCH 1105/1435] FIX: make sure we have a category --- app/assets/javascripts/discourse/models/topic-list.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/models/topic-list.js.es6 b/app/assets/javascripts/discourse/models/topic-list.js.es6 index cf65eb212a..742abdac40 100644 --- a/app/assets/javascripts/discourse/models/topic-list.js.es6 +++ b/app/assets/javascripts/discourse/models/topic-list.js.es6 @@ -165,7 +165,7 @@ TopicList.reopenClass({ // hide the category when it has no children hideUniformCategory(list, category) { - list.set('hideCategory', !category.get("has_children")); + list.set('hideCategory', category && !category.get("has_children")); } }); From e53d9f0e8bae9a25424f50c4169dba5d74f0dde5 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 3 Sep 2015 11:10:04 -0400 Subject: [PATCH 1106/1435] FIX: Don't use observers to update data Message bus events were triggering users who didn't have access to update posts to update them. Instead, perform the update in the action itself. --- .../discourse/controllers/topic.js.es6 | 14 ++++-------- .../javascripts/discourse/models/post.js.es6 | 22 +++++-------------- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index 2a6d0ffaf9..80ac937398 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -428,20 +428,14 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { }, toggleWiki(post) { - // the request to the server is made in an observer in the post class - post.toggleProperty('wiki'); + post.updatePostField('wiki', !post.get('wiki')); }, togglePostType(post) { - // the request to the server is made in an observer in the post class - const regular = this.site.get('post_types.regular'), - moderator = this.site.get('post_types.moderator_action'); + const regular = this.site.get('post_types.regular'); + const moderator = this.site.get('post_types.moderator_action'); - if (post.get("post_type") === moderator) { - post.set("post_type", regular); - } else { - post.set("post_type", moderator); - } + post.updatePostField('post_type', post.get('post_type') === moderator ? regular : moderator); }, rebakePost(post) { diff --git a/app/assets/javascripts/discourse/models/post.js.es6 b/app/assets/javascripts/discourse/models/post.js.es6 index 3719dc3d0d..c332836f67 100644 --- a/app/assets/javascripts/discourse/models/post.js.es6 +++ b/app/assets/javascripts/discourse/models/post.js.es6 @@ -83,23 +83,13 @@ const Post = RestModel.extend({ return this.get("user_id") === Discourse.User.currentProp("id") || Discourse.User.currentProp('staff'); }.property("user_id"), - wikiChanged: function() { - const data = { wiki: this.get("wiki") }; - this._updatePost("wiki", data); - }.observes('wiki'), + updatePostField(field, value) { + const data = {}; + data[field] = value; - postTypeChanged: function () { - const data = { post_type: this.get("post_type") }; - this._updatePost("post_type", data); - }.observes("post_type"), - - _updatePost(field, data) { - const self = this; - Discourse.ajax("/posts/" + this.get("id") + "/" + field, { - type: "PUT", - data: data - }).then(function () { - self.incrementProperty("version"); + Discourse.ajax(`/posts/${this.get('id')}/${field}`, { type: 'PUT', data }).then(() => { + this.set(field, value); + this.incrementProperty("version"); }).catch(popupAjaxError); }, From 12e0225c510e2bae2c0e8c23a16c12993ebf74b7 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 3 Sep 2015 11:47:18 -0400 Subject: [PATCH 1107/1435] FIX: Better `metaKey` support for menu panels --- .../discourse/components/d-link.js.es6 | 13 +++---- .../discourse/components/menu-panel.js.es6 | 5 +-- .../initializers/click-interceptor.js.es6 | 35 ++----------------- .../discourse/lib/intercept-click.js.es6 | 32 +++++++++++++++++ 4 files changed, 43 insertions(+), 42 deletions(-) create mode 100644 app/assets/javascripts/discourse/lib/intercept-click.js.es6 diff --git a/app/assets/javascripts/discourse/components/d-link.js.es6 b/app/assets/javascripts/discourse/components/d-link.js.es6 index a0babf3040..51387a5325 100644 --- a/app/assets/javascripts/discourse/components/d-link.js.es6 +++ b/app/assets/javascripts/discourse/components/d-link.js.es6 @@ -1,9 +1,10 @@ import computed from 'ember-addons/ember-computed-decorators'; import { iconHTML } from 'discourse/helpers/fa-icon'; -import DiscourseURL from 'discourse/lib/url'; +import interceptClick from 'discourse/lib/intercept-click'; export default Ember.Component.extend({ tagName: 'a', + classNames: ['d-link'], attributeBindings: ['translatedTitle:title', 'translatedTitle:aria-title', 'href'], @computed('path') @@ -27,18 +28,14 @@ export default Ember.Component.extend({ if (text) return I18n.t(text); }, - click() { + click(e) { const action = this.get('action'); if (action) { this.sendAction('action'); return false; } - const href = this.get('href'); - if (href) { - DiscourseURL.routeTo(href); - return false; - } - return false; + + return interceptClick(e); }, render(buffer) { diff --git a/app/assets/javascripts/discourse/components/menu-panel.js.es6 b/app/assets/javascripts/discourse/components/menu-panel.js.es6 index 8b5767137a..13330f21a4 100644 --- a/app/assets/javascripts/discourse/components/menu-panel.js.es6 +++ b/app/assets/javascripts/discourse/components/menu-panel.js.es6 @@ -156,7 +156,8 @@ export default Ember.Component.extend({ @on('didInsertElement') _bindEvents() { - this.$().on('click.discourse-menu-panel', 'a', (e) => { + this.$().on('click.discourse-menu-panel', 'a', e => { + if (e.metaKey) { return; } if ($(e.target).data('ember-action')) { return; } this.hide(); }); @@ -164,7 +165,7 @@ export default Ember.Component.extend({ this.appEvents.on('dropdowns:closeAll', this, this.hide); this.appEvents.on('dom:clean', this, this.hide); - $('body').on('keydown.discourse-menu-panel', (e) => { + $('body').on('keydown.discourse-menu-panel', e => { if (e.which === 27) { this.hide(); } diff --git a/app/assets/javascripts/discourse/initializers/click-interceptor.js.es6 b/app/assets/javascripts/discourse/initializers/click-interceptor.js.es6 index 5c04f67d01..d74e5264e3 100644 --- a/app/assets/javascripts/discourse/initializers/click-interceptor.js.es6 +++ b/app/assets/javascripts/discourse/initializers/click-interceptor.js.es6 @@ -1,37 +1,8 @@ -import DiscourseURL from 'discourse/lib/url'; +import interceptClick from 'discourse/lib/intercept-click'; -/** - Discourse does some server side rendering of HTML, such as the `cooked` contents of - posts. The downside of this in an Ember app is the links will not go through the router. - This jQuery code intercepts clicks on those links and routes them properly. -**/ export default { name: "click-interceptor", - initialize: function() { - $('#main').on('click.discourse', 'a', function(e) { - if (e.isDefaultPrevented() || e.shiftKey || e.metaKey || e.ctrlKey) { return; } - - var $currentTarget = $(e.currentTarget), - href = $currentTarget.attr('href'); - - if (!href || - href === '#' || - $currentTarget.attr('target') || - $currentTarget.data('ember-action') || - $currentTarget.data('auto-route') || - $currentTarget.data('share-url') || - $currentTarget.data('user-card') || - $currentTarget.hasClass('mention') || - $currentTarget.hasClass('ember-view') || - $currentTarget.hasClass('lightbox') || - href.indexOf("mailto:") === 0 || - (href.match(/^http[s]?:\/\//i) && !href.match(new RegExp("^http:\\/\\/" + window.location.hostname, "i")))) { - return; - } - - e.preventDefault(); - DiscourseURL.routeTo(href); - return false; - }); + initialize() { + $('#main').on('click.discourse', 'a', interceptClick); } }; diff --git a/app/assets/javascripts/discourse/lib/intercept-click.js.es6 b/app/assets/javascripts/discourse/lib/intercept-click.js.es6 new file mode 100644 index 0000000000..6ce7a300f3 --- /dev/null +++ b/app/assets/javascripts/discourse/lib/intercept-click.js.es6 @@ -0,0 +1,32 @@ +import DiscourseURL from 'discourse/lib/url'; + +/** + Discourse does some server side rendering of HTML, such as the `cooked` contents of + posts. The downside of this in an Ember app is the links will not go through the router. + This jQuery code intercepts clicks on those links and routes them properly. +**/ +export default function interceptClick(e) { + if (e.isDefaultPrevented() || e.shiftKey || e.metaKey || e.ctrlKey) { return; } + + const $currentTarget = $(e.currentTarget), + href = $currentTarget.attr('href'); + + if (!href || + href === '#' || + $currentTarget.attr('target') || + $currentTarget.data('ember-action') || + $currentTarget.data('auto-route') || + $currentTarget.data('share-url') || + $currentTarget.data('user-card') || + $currentTarget.hasClass('mention') || + (!$currentTarget.hasClass('d-link') && $currentTarget.hasClass('ember-view')) || + $currentTarget.hasClass('lightbox') || + href.indexOf("mailto:") === 0 || + (href.match(/^http[s]?:\/\//i) && !href.match(new RegExp("^http:\\/\\/" + window.location.hostname, "i")))) { + return; + } + + e.preventDefault(); + DiscourseURL.routeTo(href); + return false; +} From 7516643f1193680feefbf619aa621357e33c4cd6 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 3 Sep 2015 11:53:42 -0400 Subject: [PATCH 1108/1435] Middle clicking the avatar should go to profile --- .../discourse/components/header-dropdown.js.es6 | 7 +++++++ app/assets/javascripts/discourse/models/user.js.es6 | 6 ------ .../discourse/templates/components/header-dropdown.hbs | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/discourse/components/header-dropdown.js.es6 b/app/assets/javascripts/discourse/components/header-dropdown.js.es6 index 4990aa107a..0712306338 100644 --- a/app/assets/javascripts/discourse/components/header-dropdown.js.es6 +++ b/app/assets/javascripts/discourse/components/header-dropdown.js.es6 @@ -1,7 +1,14 @@ +import computed from 'ember-addons/ember-computed-decorators'; + export default Ember.Component.extend({ tagName: 'li', classNameBindings: [':header-dropdown-toggle', 'active'], + @computed('showUser') + href(showUser) { + return showUser ? this.currentUser.get('path') : ''; + }, + active: Ember.computed.alias('toggleVisible'), actions: { diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6 index 3f5941bc7d..01aef870af 100644 --- a/app/assets/javascripts/discourse/models/user.js.es6 +++ b/app/assets/javascripts/discourse/models/user.js.es6 @@ -60,12 +60,6 @@ const User = RestModel.extend({ return ('background-image: url(' + Discourse.getURLWithCDN(bgUrl) + ')').htmlSafe(); }, - /** - Path to this user. - - @property path - @type {String} - **/ path: function(){ return Discourse.getURL('/users/' + this.get('username_lower')); // no need to observe, requires a hard refresh to update diff --git a/app/assets/javascripts/discourse/templates/components/header-dropdown.hbs b/app/assets/javascripts/discourse/templates/components/header-dropdown.hbs index 732e31e06a..6cb4af742b 100644 --- a/app/assets/javascripts/discourse/templates/components/header-dropdown.hbs +++ b/app/assets/javascripts/discourse/templates/components/header-dropdown.hbs @@ -1,4 +1,4 @@ - + {{#if showUser}} {{bound-avatar currentUser "medium"}} {{else}} From 80041b874c3300ec8b49eec8d8ce51d344892460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Thu, 3 Sep 2015 19:18:46 +0200 Subject: [PATCH 1109/1435] FIX: don't show new topic notifications in homepag for suppressed categories --- .../models/topic-tracking-state.js.es6 | 109 +++++++++--------- app/models/site.rb | 4 + app/serializers/site_serializer.rb | 3 +- 3 files changed, 61 insertions(+), 55 deletions(-) diff --git a/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 b/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 index cf5f890375..87c256e202 100644 --- a/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 +++ b/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 @@ -1,4 +1,6 @@ import NotificationLevels from 'discourse/lib/notification-levels'; +import computed from "ember-addons/ember-computed-decorators"; +import { on } from "ember-addons/ember-computed-decorators"; function isNew(topic) { return topic.last_read_post_number === null && @@ -15,24 +17,25 @@ function isUnread(topic) { const TopicTrackingState = Discourse.Model.extend({ messageCount: 0, - _setup: function() { + @on("init") + _setup() { this.unreadSequence = []; this.newSequence = []; this.states = {}; - }.on('init'), + }, establishChannels() { const tracker = this; - const process = function(data){ + const process = data => { if (data.message_type === "delete") { tracker.removeTopic(data.topic_id); tracker.incrementMessageCount(); } if (data.message_type === "new_topic" || data.message_type === "latest") { - const ignored_categories = Discourse.User.currentProp("muted_category_ids"); - if(_.include(ignored_categories, data.payload.category_id)){ + const muted_category_ids = Discourse.User.currentProp("muted_category_ids"); + if (_.include(muted_category_ids, data.payload.category_id)) { return; } } @@ -45,7 +48,7 @@ const TopicTrackingState = Discourse.Model.extend({ tracker.notify(data); const old = tracker.states["t" + data.topic_id]; - if(!_.isEqual(old, data.payload)){ + if (!_.isEqual(old, data.payload)) { tracker.states["t" + data.topic_id] = data.payload; tracker.incrementMessageCount(); } @@ -60,20 +63,27 @@ const TopicTrackingState = Discourse.Model.extend({ }, updateSeen(topicId, highestSeen) { - if(!topicId || !highestSeen) { return; } + if (!topicId || !highestSeen) { return; } const state = this.states["t" + topicId]; - if(state && (!state.last_read_post_number || state.last_read_post_number < highestSeen)) { + if (state && (!state.last_read_post_number || state.last_read_post_number < highestSeen)) { state.last_read_post_number = highestSeen; this.incrementMessageCount(); } }, - notify(data){ + notify(data) { if (!this.newIncoming) { return; } const filter = this.get("filter"); - if ((filter === "all" || filter === "latest" || filter === "new") && data.message_type === "new_topic" ) { + if (filter === Discourse.Utilities.defaultHomepage()) { + const suppressed_from_homepage_category_ids = Discourse.Site.currentProp("suppressed_from_homepage_category_ids"); + if (_.include(suppressed_from_homepage_category_ids, data.payload.category_id)) { + return; + } + } + + if ((filter === "all" || filter === "latest" || filter === "new") && data.message_type === "new_topic") { this.addIncoming(data.topic_id); } @@ -84,7 +94,7 @@ const TopicTrackingState = Discourse.Model.extend({ } } - if(filter === "latest" && data.message_type === "latest") { + if (filter === "latest" && data.message_type === "latest") { this.addIncoming(data.topic_id); } @@ -92,12 +102,12 @@ const TopicTrackingState = Discourse.Model.extend({ }, addIncoming(topicId) { - if(this.newIncoming.indexOf(topicId) === -1){ + if (this.newIncoming.indexOf(topicId) === -1) { this.newIncoming.push(topicId); } }, - resetTracking(){ + resetTracking() { this.newIncoming = []; this.set("incomingCount", 0); }, @@ -109,10 +119,10 @@ const TopicTrackingState = Discourse.Model.extend({ this.set("incomingCount", 0); }, - hasIncoming: function(){ - const count = this.get('incomingCount'); - return count && count > 0; - }.property('incomingCount'), + @computed("incomingCount") + hasIncoming(incomingCount) { + return incomingCount && incomingCount > 0; + }, removeTopic(topic_id) { delete this.states["t" + topic_id]; @@ -124,7 +134,7 @@ const TopicTrackingState = Discourse.Model.extend({ if (Em.isEmpty(topics)) { return; } const states = this.states; - topics.forEach(function(t) { + topics.forEach(t => { const state = states['t' + t.get('id')]; if (state) { @@ -135,9 +145,7 @@ const TopicTrackingState = Discourse.Model.extend({ unread = postsCount - state.last_read_post_number; if (newPosts < 0) { newPosts = 0; } - if (!state.last_read_post_number) { - unread = 0; - } + if (!state.last_read_post_number) { unread = 0; } if (unread < 0) { unread = 0; } t.setProperties({ @@ -154,7 +162,7 @@ const TopicTrackingState = Discourse.Model.extend({ sync(list, filter) { const tracker = this, - states = tracker.states; + states = tracker.states; if (!list || !list.topics) { return; } @@ -198,14 +206,12 @@ const TopicTrackingState = Discourse.Model.extend({ }); // Correct missing states, safeguard in case message bus is corrupt - if((filter === "new" || filter === "unread") && !list.more_topics_url){ + if ((filter === "new" || filter === "unread") && !list.more_topics_url) { const ids = {}; - list.topics.forEach(function(r){ - ids["t" + r.id] = true; - }); + list.topics.forEach(r => ids["t" + r.id] = true); - _.each(tracker.states, function(v, k){ + _.each(tracker.states, (v, k) => { // we are good if we are on the list if (ids[k]) { return; } @@ -229,12 +235,12 @@ const TopicTrackingState = Discourse.Model.extend({ this.set("messageCount", this.get("messageCount") + 1); }, - countNew(category_id){ + countNew(category_id) { return _.chain(this.states) - .where(isNew) - .where(function(topic){ return topic.category_id === category_id || !category_id;}) - .value() - .length; + .where(isNew) + .where(topic => topic.category_id === category_id || !category_id) + .value() + .length; }, tooManyTracked() { @@ -242,20 +248,19 @@ const TopicTrackingState = Discourse.Model.extend({ }, resetNew() { - const self = this; - Object.keys(this.states).forEach(function (id) { - if (self.states[id].last_read_post_number === null) { - delete self.states[id]; + Object.keys(this.states).forEach(id => { + if (this.states[id].last_read_post_number === null) { + delete this.states[id]; } }); }, - countUnread(category_id){ + countUnread(category_id) { return _.chain(this.states) - .where(isUnread) - .where(function(topic){ return topic.category_id === category_id || !category_id;}) - .value() - .length; + .where(isUnread) + .where(topic => topic.category_id === category_id || !category_id) + .value() + .length; }, countCategory(category_id) { @@ -269,42 +274,37 @@ const TopicTrackingState = Discourse.Model.extend({ return sum; }, - lookupCount(name, category){ - + lookupCount(name, category) { if (name === "latest") { return this.lookupCount("new", category) + this.lookupCount("unread", category); } let categoryName = category ? Em.get(category, "name") : null; - if(name === "new") { + if (name === "new") { return this.countNew(categoryName); - } else if(name === "unread") { + } else if (name === "unread") { return this.countUnread(categoryName); } else { categoryName = name.split("/")[1]; - if(categoryName) { + if (categoryName) { return this.countCategory(categoryName); } } }, loadStates(data) { - // not exposed const states = this.states; - - if(data) { - _.each(data,function(topic){ - states["t" + topic.topic_id] = topic; - }); + if (data) { + _.each(data,topic => states["t" + topic.topic_id] = topic); } } }); TopicTrackingState.reopenClass({ - createFromStates(data) { + createFromStates(data) { // TODO: This should be a model that does injection automatically const container = Discourse.__container__, messageBus = container.lookup('message-bus:main'), @@ -316,7 +316,8 @@ TopicTrackingState.reopenClass({ instance.establishChannels(); return instance; }, - current(){ + + current() { if (!this.tracker) { const data = PreloadStore.get('topicTrackingStates'); this.tracker = this.createFromStates(data); diff --git a/app/models/site.rb b/app/models/site.rb index c2fcfeb4a4..c6a3313f17 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -71,6 +71,10 @@ class Site end end + def suppressed_from_homepage_category_ids + categories.select { |c| c.suppress_from_homepage == true }.map(&:id) + end + def archetypes Archetype.list.reject { |t| t.id == Archetype.private_message } end diff --git a/app/serializers/site_serializer.rb b/app/serializers/site_serializer.rb index 38c30737b2..dec52ed2f2 100644 --- a/app/serializers/site_serializer.rb +++ b/app/serializers/site_serializer.rb @@ -11,7 +11,8 @@ class SiteSerializer < ApplicationSerializer :uncategorized_category_id, # this is hidden so putting it here :is_readonly, :disabled_plugins, - :user_field_max_length + :user_field_max_length, + :suppressed_from_homepage_category_ids has_many :categories, serializer: BasicCategorySerializer, embed: :objects has_many :post_action_types, embed: :objects From 0e1d6272b9882f67502c8d28dda4d080dc274e36 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 3 Sep 2015 13:37:40 -0400 Subject: [PATCH 1110/1435] FIX: `highest_post_number` was not being updated from gaps --- .../discourse/components/post-gap.js.es6 | 20 +++++----- .../controllers/topic-entrance.js.es6 | 8 ++-- .../discourse/models/post-stream.js.es6 | 39 ++++++++----------- .../templates/components/topic-map.hbs | 2 +- 4 files changed, 30 insertions(+), 39 deletions(-) diff --git a/app/assets/javascripts/discourse/components/post-gap.js.es6 b/app/assets/javascripts/discourse/components/post-gap.js.es6 index 3c3209a89e..159d047f07 100644 --- a/app/assets/javascripts/discourse/components/post-gap.js.es6 +++ b/app/assets/javascripts/discourse/components/post-gap.js.es6 @@ -3,8 +3,8 @@ export default Ember.Component.extend({ initGaps: function(){ this.set('loading', false); - var before = this.get('before') === 'true', - gaps = before ? this.get('postStream.gaps.before') : this.get('postStream.gaps.after'); + const before = this.get('before') === 'true'; + const gaps = before ? this.get('postStream.gaps.before') : this.get('postStream.gaps.after'); if (gaps) { this.set('gap', gaps[this.get('post.id')]); @@ -16,29 +16,27 @@ export default Ember.Component.extend({ this.rerender(); }.observes('post.hasGap'), - render: function(buffer) { + render(buffer) { if (this.get('loading')) { buffer.push(I18n.t('loading')); } else { - var gapLength = this.get('gap.length'); + const gapLength = this.get('gap.length'); if (gapLength) { buffer.push(I18n.t('post.gap', {count: gapLength})); } } }, - click: function() { + click() { if (this.get('loading') || (!this.get('gap'))) { return false; } this.set('loading', true); this.rerender(); - var self = this, - postStream = this.get('postStream'), - filler = this.get('before') === 'true' ? postStream.fillGapBefore : postStream.fillGapAfter; + const postStream = this.get('postStream'); + const filler = this.get('before') === 'true' ? postStream.fillGapBefore : postStream.fillGapAfter; - filler.call(postStream, this.get('post'), this.get('gap')).then(function() { - // hide this control after the promise is resolved - self.set('gap', null); + filler.call(postStream, this.get('post'), this.get('gap')).then(() => { + this.set('gap', null); }); return false; diff --git a/app/assets/javascripts/discourse/controllers/topic-entrance.js.es6 b/app/assets/javascripts/discourse/controllers/topic-entrance.js.es6 index 4f453b98f1..4a7fdf61c6 100644 --- a/app/assets/javascripts/discourse/controllers/topic-entrance.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic-entrance.js.es6 @@ -1,7 +1,7 @@ import DiscourseURL from 'discourse/lib/url'; function entranceDate(dt, showTime) { - var today = new Date(); + const today = new Date(); if (dt.toDateString() === today.toDateString()) { return moment(dt).format(I18n.t("dates.time")); @@ -44,7 +44,7 @@ export default Ember.Controller.extend({ }.property('bumpedDate'), actions: { - show: function(data) { + show(data) { // Show the chooser but only if the model changes if (this.get('model') !== data.topic) { this.set('model', data.topic); @@ -52,11 +52,11 @@ export default Ember.Controller.extend({ } }, - enterTop: function() { + enterTop() { DiscourseURL.routeTo(this.get('model.url')); }, - enterBottom: function() { + enterBottom() { DiscourseURL.routeTo(this.get('model.lastPostUrl')); } } diff --git a/app/assets/javascripts/discourse/models/post-stream.js.es6 b/app/assets/javascripts/discourse/models/post-stream.js.es6 index 9f625f3477..b2f903526e 100644 --- a/app/assets/javascripts/discourse/models/post-stream.js.es6 +++ b/app/assets/javascripts/discourse/models/post-stream.js.es6 @@ -281,14 +281,13 @@ const PostStream = RestModel.extend({ // Fill in a gap of posts after a particular post fillGapAfter(post, gap) { const postId = post.get('id'), - stream = this.get('stream'), - idx = stream.indexOf(postId), - self = this; + stream = this.get('stream'), + idx = stream.indexOf(postId); if (idx !== -1) { stream.pushObjects(gap); - return this.appendMore().then(function() { - self.get('stream').enumerableContentDidChange(); + return this.appendMore().then(() => { + this.get('stream').enumerableContentDidChange(); }); } return Ember.RSVP.resolve(); @@ -296,24 +295,18 @@ const PostStream = RestModel.extend({ // Appends the next window of posts to the stream. Call it when scrolling downwards. appendMore() { - const self = this; - // Make sure we can append more posts - if (!self.get('canAppendMore')) { return Ember.RSVP.resolve(); } + if (!this.get('canAppendMore')) { return Ember.RSVP.resolve(); } - const postIds = self.get('nextWindow'); + const postIds = this.get('nextWindow'); if (Ember.isEmpty(postIds)) { return Ember.RSVP.resolve(); } - self.set('loadingBelow', true); + this.set('loadingBelow', true); - const stopLoading = function() { - self.set('loadingBelow', false); - }; + const stopLoading = () => this.set('loadingBelow', false); - return self.findPostsByIds(postIds).then(function(posts) { - posts.forEach(function(p) { - self.appendPost(p); - }); + return this.findPostsByIds(postIds).then((posts) => { + posts.forEach(p => this.appendPost(p)); stopLoading(); }, stopLoading); }, @@ -685,6 +678,12 @@ const PostStream = RestModel.extend({ const postIdentityMap = this.get('postIdentityMap'), existing = postIdentityMap.get(post.get('id')); + // Update the `highest_post_number` if this post is higher. + const postNumber = post.get('post_number'); + if (postNumber && postNumber > (this.get('topic.highest_post_number') || 0)) { + this.set('topic.highest_post_number', postNumber); + } + if (existing) { // If the post is in the identity map, update it and return the old reference. existing.updateFromPost(post); @@ -693,12 +692,6 @@ const PostStream = RestModel.extend({ post.set('topic', this.get('topic')); postIdentityMap.set(post.get('id'), post); - - // Update the `highest_post_number` if this post is higher. - const postNumber = post.get('post_number'); - if (postNumber && postNumber > (this.get('topic.highest_post_number') || 0)) { - this.set('topic.highest_post_number', postNumber); - } } return post; }, diff --git a/app/assets/javascripts/discourse/templates/components/topic-map.hbs b/app/assets/javascripts/discourse/templates/components/topic-map.hbs index b851f03bcd..915c3f0e71 100644 --- a/app/assets/javascripts/discourse/templates/components/topic-map.hbs +++ b/app/assets/javascripts/discourse/templates/components/topic-map.hbs @@ -14,7 +14,7 @@
    • - +

      {{i18n 'last_reply_lowercase'}}

      {{avatar details.last_poster imageSize="tiny"}} {{format-date topic.last_posted_at}} From ecf21cabe15992e78958ec677fd43615ae6bd46a Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 3 Sep 2015 15:05:47 -0400 Subject: [PATCH 1111/1435] Move Keyboard item to bottom. Add `d-link` to hamburger --- .../discourse/components/d-link.js.es6 | 8 ++- .../templates/components/hamburger-menu.hbs | 64 +++++++++---------- .../templates/components/user-menu.hbs | 2 +- 3 files changed, 38 insertions(+), 36 deletions(-) diff --git a/app/assets/javascripts/discourse/components/d-link.js.es6 b/app/assets/javascripts/discourse/components/d-link.js.es6 index 51387a5325..bde5d654a5 100644 --- a/app/assets/javascripts/discourse/components/d-link.js.es6 +++ b/app/assets/javascripts/discourse/components/d-link.js.es6 @@ -15,7 +15,13 @@ export default Ember.Component.extend({ if (route) { const router = this.container.lookup('router:main'); if (router && router.router) { - return router.router.generate(route, this.get('model')); + const params = [route]; + const model = this.get('model'); + if (model) { + params.push(model); + } + + return router.router.generate.apply(router.router, params); } } diff --git a/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs b/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs index 098d310157..72bf62a4cf 100644 --- a/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs +++ b/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs @@ -1,73 +1,69 @@ {{#menu-panel visible=visible}}
      +
      + {{#if categories}} {{/if}} + +
      + + {{/menu-panel}} diff --git a/app/assets/javascripts/discourse/templates/components/user-menu.hbs b/app/assets/javascripts/discourse/templates/components/user-menu.hbs index 2133666413..78593d30de 100644 --- a/app/assets/javascripts/discourse/templates/components/user-menu.hbs +++ b/app/assets/javascripts/discourse/templates/components/user-menu.hbs @@ -4,7 +4,7 @@
    • {{d-link route='user' model=currentUser class="user-activity-link" icon="user" label="user.profile"}}
    • {{#if showDisableAnon}} -
    • {{d-link action="toggleAnon" label="switch_from_anon"}}
    • +
    • {{d-link action="toggleAnon" label="switch_from_anon"}}
    • {{/if}}
    • {{d-link path=bookmarksPath title="user.bookmarks" icon="bookmark"}} From 0818a502f35e6eb1163f94fbbe9bf0b85b8fcc5b Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 3 Sep 2015 15:39:45 -0400 Subject: [PATCH 1112/1435] Group admin stuff in Hamburger --- .../templates/components/hamburger-menu.hbs | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs b/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs index 72bf62a4cf..c5ab7182b5 100644 --- a/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs +++ b/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs @@ -1,8 +1,7 @@ {{#menu-panel visible=visible}} -
    • + + + + + {{#each categoriesGrouped as |group|}} + + {{#each group.cats as |cat|}} + + + + + {{/each}} + + {{/each}} +
      PositionCategory
      + {{number-field number=cat.position}} + {{d-button class="no-text" action="moveUp" actionParam=cat icon="arrow-up"}} + {{d-button class="no-text" action="moveDown" actionParam=cat icon="arrow-down"}} + {{#if cat.hasBufferedChanges}} + {{d-button class="no-text" action="commit" icon="check"}} + {{/if}} + {{category-badge cat allowUncategorized="true"}}
      +
      +

      + + +
      diff --git a/app/assets/javascripts/discourse/templates/navigation/categories.hbs b/app/assets/javascripts/discourse/templates/navigation/categories.hbs index 6ba5509e9b..c6b4c1cbbc 100644 --- a/app/assets/javascripts/discourse/templates/navigation/categories.hbs +++ b/app/assets/javascripts/discourse/templates/navigation/categories.hbs @@ -3,7 +3,10 @@ {{navigation-bar navItems=navItems filterMode=filterMode}} {{#if canCreateCategory}} - + {{d-button action="createCategory" icon="plus" label="category.create"}} + {{#if siteSettings.fixed_category_positions}} + {{d-button action="reorderCategories" icon="random" label="category.reorder"}} + {{/if}} {{/if}} {{#if canCreateTopic}} diff --git a/app/assets/javascripts/discourse/views/reorder-categories.js.es6 b/app/assets/javascripts/discourse/views/reorder-categories.js.es6 new file mode 100644 index 0000000000..570c9ec963 --- /dev/null +++ b/app/assets/javascripts/discourse/views/reorder-categories.js.es6 @@ -0,0 +1,80 @@ +import ModalBodyView from "discourse/views/modal-body"; + +export default ModalBodyView.extend({ + title: I18n.t('categories.reorder.title'), + templateName: 'modal/reorder-categories', + + _setup: function() { + this.get('controller').on('scrollIntoView', this, this.scrollIntoView); + }.on('didInsertElement'), + _teardown: function() { + this.get('controller').off('scrollIntoView', this, this.scrollIntoView); + this.set('prevScrollElem', null); + }.on('willClearRender'), + + scrollIntoView() { + const elem = this.$('tr[data-category-id="' + this.get('controller.scrollIntoViewId') + '"]'); + const scrollParent = this.$('.modal-body'); + const eoff = elem.position(); + const poff = $(document.getElementById('rc-scroll-anchor')).position(); + const currHeight = scrollParent.height(); + + elem[0].className = "highlighted"; + + const goal = eoff.top - poff.top - currHeight / 2, + current = scrollParent.scrollTop(); + scrollParent.scrollTop(9999999); + const max = scrollParent.scrollTop(); + scrollParent.scrollTop(current); + + const doneTimeout = setTimeout(function() { + elem[0].className = "highlighted done"; + setTimeout(function() { + elem[0].className = ""; + }, 2000); + }, 0); + + if (goal > current - currHeight / 4 && goal < current + currHeight / 4) { + // Too close to goal + return; + } + if (max - current < 10 && goal > current) { + // Too close to bottom + return; + } + if (current < 10 && goal < current) { + // Too close to top + return; + } + + if (!window.requestAnimationFrame) { + scrollParent.scrollTop(goal); + } else { + clearTimeout(doneTimeout); + const startTime = performance.now(); + const duration = 100; + + function doScroll(timestamp) { + let progress = (timestamp - startTime) / duration; + if (progress > 1) { + progress = 1; + setTimeout(function() { + elem[0].className = "highlighted done"; + setTimeout(function() { + elem[0].className = ""; + }, 2000); + }, 0); + } else if (progress < 0) { + progress = 0; + } + if (progress < 1) { + window.requestAnimationFrame(doScroll); + } + + const iprogress = 1 - progress; + scrollParent.scrollTop(goal * progress + current * iprogress); + } + window.requestAnimationFrame(doScroll); + } + } +}); diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js index 0ff2430587..5ea3455611 100644 --- a/app/assets/javascripts/main_include.js +++ b/app/assets/javascripts/main_include.js @@ -94,13 +94,11 @@ //= require ./discourse/lib/export-result //= require ./discourse/dialects/dialect //= require ./discourse/lib/emoji/emoji -//= require ./discourse/lib/sharing -//= require discourse/lib/desktop-notifications +//= require_tree ./discourse/lib //= require ./discourse/router //= require_tree ./discourse/dialects //= require_tree ./discourse/controllers -//= require_tree ./discourse/lib //= require_tree ./discourse/models //= require_tree ./discourse/components //= require_tree ./discourse/views diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index d1de50da4e..577f9aba1c 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -1 +1,2 @@ -@import "common/admin/admin_base" +@import "common/admin/admin_base"; +@import "common/admin/cat_reorder"; diff --git a/app/assets/stylesheets/common/admin/cat_reorder.scss b/app/assets/stylesheets/common/admin/cat_reorder.scss new file mode 100644 index 0000000000..d5619e993c --- /dev/null +++ b/app/assets/stylesheets/common/admin/cat_reorder.scss @@ -0,0 +1,28 @@ +.reorder-categories { + input { + width: 4em; + } + .th-pos { + width: calc(4em + 150px); + } + tbody tr { + background-color: transparent; + transition: background 0s ease; + &.highlighted { + background-color: rgba($highlight, 0.4); + &.done { + background-color: transparent; + transition-duration: 1s; + } + } + &:first-child td { + padding-top: 7px; + } + } + tbody { + border-bottom: 1px solid blend-primary-secondary(50%); + } + table { + padding-bottom: 150px; + } +} diff --git a/app/assets/stylesheets/common/base/discourse.scss b/app/assets/stylesheets/common/base/discourse.scss index c8cedb4a6c..d8b4739804 100644 --- a/app/assets/stylesheets/common/base/discourse.scss +++ b/app/assets/stylesheets/common/base/discourse.scss @@ -144,6 +144,9 @@ body { } } +input[type].invalid { + background-color: dark-light-choose(scale-color($danger, $lightness: 80%), scale-color($danger, $lightness: -60%)); +} .wmd-input { resize: none; diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index 9d3ac191e9..b63ec503db 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -37,7 +37,7 @@ class CategoriesController < ApplicationController end def move - guardian.ensure_can_create!(Category) + guardian.ensure_can_create_category! params.require("category_id") params.require("position") @@ -50,6 +50,24 @@ class CategoriesController < ApplicationController end end + def reorder + guardian.ensure_can_create_category! + + params.require(:mapping) + change_requests = MultiJson.load(params[:mapping]) + by_category = Hash[change_requests.map { |cat, pos| [Category.find(cat.to_i), pos] }] + + unless guardian.is_admin? + raise Discourse::InvalidAccess unless by_category.keys.all? { |c| guardian.can_see_category? c } + end + + by_category.each do |cat, pos| + cat.position = pos + cat.save if cat.position_changed? + end + render json: success_json + end + def show if Category.topic_create_allowed(guardian).where(id: @category.id).exists? @category.permission = CategoryGroup.permission_types[:full] diff --git a/app/serializers/basic_category_serializer.rb b/app/serializers/basic_category_serializer.rb index 96b0268cfa..f62968a59f 100644 --- a/app/serializers/basic_category_serializer.rb +++ b/app/serializers/basic_category_serializer.rb @@ -7,6 +7,7 @@ class BasicCategorySerializer < ApplicationSerializer :slug, :topic_count, :post_count, + :position, :description, :description_text, :topic_url, diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index e92de892cf..06f06b09f5 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -367,6 +367,10 @@ en: all_subcategories: "all" no_subcategory: "none" category: "Category" + reorder: + title: "Reorder Categories" + save: "Save Order" + apply_all: "Apply" posts: "Posts" topics: "Topics" latest: "Latest" @@ -1546,6 +1550,7 @@ en: add_permission: "Add Permission" this_year: "this year" position: "position" + reorder: "Reorder" default_position: "Default Position" position_disabled: "Categories will be displayed in order of activity. To control the order of categories in lists, " position_disabled_click: 'enable the "fixed category positions" setting.' diff --git a/config/routes.rb b/config/routes.rb index f10ea5c2e9..d2adc0cfdb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -382,6 +382,7 @@ Discourse::Application.routes.draw do resources :categories, :except => :show post "category/:category_id/move" => "categories#move" + post "categories/reorder" => "categories#reorder" post "category/:category_id/notifications" => "categories#set_notifications" put "category/:category_id/slug" => "categories#update_slug" diff --git a/spec/controllers/categories_controller_spec.rb b/spec/controllers/categories_controller_spec.rb index e838b2e971..f03cfd05e8 100644 --- a/spec/controllers/categories_controller_spec.rb +++ b/spec/controllers/categories_controller_spec.rb @@ -95,6 +95,42 @@ describe CategoriesController do end + describe "reorder" do + it "reorders the categories" do + admin = log_in(:admin) + + c1 = Fabricate(:category) + c2 = Fabricate(:category) + c3 = Fabricate(:category) + c4 = Fabricate(:category) + if c3.id < c2.id + tmp = c3; c2 = c3; c3 = tmp; + end + c1.position = 8 + c2.position = 6 + c3.position = 7 + c4.position = 5 + + payload = {} + payload[c1.id] = 4 + payload[c2.id] = 6 + payload[c3.id] = 6 + payload[c4.id] = 5 + + xhr :post, :reorder, mapping: MultiJson.dump(payload) + + SiteSetting.fixed_category_positions = true + list = CategoryList.new(Guardian.new(admin)) + expect(list.categories).to eq([ + Category.find(SiteSetting.uncategorized_category_id), + c1, + c4, + c2, + c3 + ]) + end + end + describe "update" do it "requires the user to be logged in" do From 335be272ffac925fe84970f460e2d933d8d98832 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 7 Sep 2015 11:57:50 +1000 Subject: [PATCH 1142/1435] FEATURE: implement capping of new/unread We cap new and unread at 2/5th of SiteSetting.max_tracked_new_unread This dynamic capping is applied under 2 conditions: 1. New capping is applied once every 15 minutes in the periodical job, this effectively ensures that usually even super active sites are capped at 200 new items 2. Unread capping is applied if a user hits max_tracked_new_unread, meaning if new + unread == 500, we defer a job that runs within 15 minutes that will cap user at 200 unread This logic ensures that at worst case a user gets "bad" numbers for 15 minutes and then the system goes ahead and fixes itself up --- .../controllers/discovery/topics.js.es6 | 4 -- .../models/topic-tracking-state.js.es6 | 7 --- .../discourse/templates/discovery/topics.hbs | 4 -- app/controllers/application_controller.rb | 6 ++- app/jobs/scheduled/periodical_updates.rb | 10 ++++ app/models/topic_tracking_state.rb | 53 ++++++++++++++----- app/models/topic_user.rb | 37 +++++++++++++ app/models/user.rb | 6 ++- config/locales/client.en.yml | 1 - config/site_settings.yml | 6 +++ spec/models/topic_tracking_state_spec.rb | 44 +++++++++++++++ 11 files changed, 146 insertions(+), 32 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 index 45716ae2ee..28a663e385 100644 --- a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 @@ -85,10 +85,6 @@ const controllerOpts = { return this.get('model.filter') === 'new' && this.get('model.topics.length') > 0; }.property('model.filter', 'model.topics.length'), - tooManyTracked: function(){ - return this.topicTrackingState.tooManyTracked(); - }.property(), - showDismissAtTop: function() { return (this.isFilterPage(this.get('model.filter'), 'new') || this.isFilterPage(this.get('model.filter'), 'unread')) && diff --git a/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 b/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 index 0005b6e600..ded0d824c8 100644 --- a/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 +++ b/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 @@ -236,7 +236,6 @@ const TopicTrackingState = Discourse.Model.extend({ }, countNew(category_id) { - if (this.tooManyTracked()) { return(0); } return _.chain(this.states) .where(isNew) .where(topic => topic.category_id === category_id || !category_id) @@ -244,10 +243,6 @@ const TopicTrackingState = Discourse.Model.extend({ .length; }, - tooManyTracked() { - return this.initialStatesLength >= Discourse.SiteSettings.max_tracked_new_unread; - }, - resetNew() { Object.keys(this.states).forEach(id => { if (this.states[id].last_read_post_number === null) { @@ -257,7 +252,6 @@ const TopicTrackingState = Discourse.Model.extend({ }, countUnread(category_id) { - if (this.tooManyTracked()) { return(0); } return _.chain(this.states) .where(isUnread) .where(topic => topic.category_id === category_id || !category_id) @@ -266,7 +260,6 @@ const TopicTrackingState = Discourse.Model.extend({ }, countCategory(category_id) { - if (this.tooManyTracked()) { return(0); } let sum = 0; _.each(this.states, function(topic){ if (topic.category_id === category_id) { diff --git a/app/assets/javascripts/discourse/templates/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/discovery/topics.hbs index 6afe9d80f8..f3e452a4ab 100644 --- a/app/assets/javascripts/discourse/templates/discovery/topics.hbs +++ b/app/assets/javascripts/discourse/templates/discovery/topics.hbs @@ -1,7 +1,3 @@ -{{#if tooManyTracked}} -
      {{{i18n 'topics.too_many_tracked'}}}
      -{{/if}} - {{#if redirectedReason}}
      {{redirectedReason}}
      {{/if}} diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 60b6ba9938..d9fe0a4976 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -309,7 +309,11 @@ class ApplicationController < ActionController::Base def preload_current_user_data store_preloaded("currentUser", MultiJson.dump(CurrentUserSerializer.new(current_user, scope: guardian, root: false))) - serializer = ActiveModel::ArraySerializer.new(TopicTrackingState.report(current_user.id), each_serializer: TopicTrackingStateSerializer) + report = TopicTrackingState.report(current_user.id) + if report.length >= SiteSetting.max_tracked_new_unread.to_i + TopicUser.cap_unread_later(current_user.id) + end + serializer = ActiveModel::ArraySerializer.new(report, each_serializer: TopicTrackingStateSerializer) store_preloaded("topicTrackingStates", MultiJson.dump(serializer)) end diff --git a/app/jobs/scheduled/periodical_updates.rb b/app/jobs/scheduled/periodical_updates.rb index 2254235a99..625d67b8d8 100644 --- a/app/jobs/scheduled/periodical_updates.rb +++ b/app/jobs/scheduled/periodical_updates.rb @@ -32,6 +32,16 @@ module Jobs user_id = hash[:profile].user_id Discourse.handle_job_exception(hash[:ex], error_context(args, "Rebaking user id #{user_id}", user_id: user_id)) end + + TopicUser.cap_unread_backlog! + + offset = (SiteSetting.max_tracked_new_unread * (2/5.0)).to_i + last_new_topic = Topic.order('created_at desc').offset(offset).select(:created_at).first + if last_new_topic + SiteSetting.min_new_topics_time = last_new_topic.created_at.to_i + end + + nil end end diff --git a/app/models/topic_tracking_state.rb b/app/models/topic_tracking_state.rb index b6fae15187..0da784219c 100644 --- a/app/models/topic_tracking_state.rb +++ b/app/models/topic_tracking_state.rb @@ -106,11 +106,12 @@ class TopicTrackingState WHEN COALESCE(u.new_topic_duration_minutes, :default_duration) = :always THEN u.created_at WHEN COALESCE(u.new_topic_duration_minutes, :default_duration) = :last_visit THEN COALESCE(u.previous_visit_at,u.created_at) ELSE (:now::timestamp - INTERVAL '1 MINUTE' * COALESCE(u.new_topic_duration_minutes, :default_duration)) - END, us.new_since)", + END, us.new_since, :min_date)", now: DateTime.now, last_visit: User::NewTopicDuration::LAST_VISIT, always: User::NewTopicDuration::ALWAYS, - default_duration: SiteSetting.default_other_new_topic_duration_minutes + default_duration: SiteSetting.default_other_new_topic_duration_minutes, + min_date: Time.at(SiteSetting.min_new_topics_time).to_datetime ).where_values[0] end @@ -125,19 +126,49 @@ class TopicTrackingState # This code needs to be VERY efficient as it is triggered via the message bus and may steal # cycles from usual requests # - - unread = TopicQuery.unread_filter(Topic).where_values.join(" AND ") - new = TopicQuery.new_filter(Topic, "xxx").where_values.join(" AND ").gsub!("'xxx'", treat_as_new_topic_clause) + # + sql = report_raw_sql(topic_id: topic_id) sql = <Dismiss New or Dismiss Posts" bulk: reset_read: "Reset Read" delete: "Delete Topics" diff --git a/config/site_settings.yml b/config/site_settings.yml index 9a8d8ade1a..78f4b7b141 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -910,6 +910,12 @@ uncategorized: default: false hidden: true + # Nothing past this threshold is ever considered new + # this is calculated dynamically every 15 minutes + min_new_topics_time: + default: 0 + hidden: true + # Category IDs lounge_category_id: default: -1 diff --git a/spec/models/topic_tracking_state_spec.rb b/spec/models/topic_tracking_state_spec.rb index 2d857e320a..de8e3ff6b9 100644 --- a/spec/models/topic_tracking_state_spec.rb +++ b/spec/models/topic_tracking_state_spec.rb @@ -39,6 +39,50 @@ describe TopicTrackingState do expect(report.length).to eq(1) end + + it "correctly handles capping" do + $redis.del TopicUser.unread_cap_key + + user = Fabricate(:user) + + post1 = create_post + Fabricate(:post, topic: post1.topic) + + post2 = create_post + Fabricate(:post, topic: post2.topic) + + post3 = create_post + Fabricate(:post, topic: post3.topic) + + tracking = { + notification_level: TopicUser.notification_levels[:tracking], + last_read_post_number: 1, + highest_seen_post_number: 1 + } + + TopicUser.change(user.id, post1.topic_id, tracking) + TopicUser.change(user.id, post2.topic_id, tracking) + TopicUser.change(user.id, post3.topic_id, tracking) + + report = TopicTrackingState.report(user.id) + expect(report.length).to eq(3) + + SiteSetting.max_tracked_new_unread = 5 + # business logic, we allow for 2/5th new .. 2/5th unread ... 1/5th buffer + + TopicUser.cap_unread_backlog! + + report = TopicTrackingState.report(user.id) + expect(report.length).to eq(3) + + TopicUser.cap_unread_later(user.id) + TopicUser.cap_unread_backlog! + + report = TopicTrackingState.report(user.id) + expect(report.length).to eq(2) + + end + it "correctly gets the tracking state" do report = TopicTrackingState.report(user.id) expect(report.length).to eq(0) From d05bc64df87923c239982dc2a4d272129600f001 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Mon, 7 Sep 2015 19:21:10 +0530 Subject: [PATCH 1143/1435] do not default button title tag to label --- app/assets/javascripts/discourse/components/d-button.js.es6 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/components/d-button.js.es6 b/app/assets/javascripts/discourse/components/d-button.js.es6 index 5fcc00a0fc..068e814cae 100644 --- a/app/assets/javascripts/discourse/components/d-button.js.es6 +++ b/app/assets/javascripts/discourse/components/d-button.js.es6 @@ -8,9 +8,9 @@ export default Ember.Component.extend({ noText: Ember.computed.empty('translatedLabel'), - @computed("title", "translatedLabel") - translatedTitle(title, translatedLabel) { - return title ? I18n.t(title) : translatedLabel; + @computed("title") + translatedTitle(title) { + if (title) return I18n.t(title); }, @computed("label") From 21f81979cb54b5fc4e95ed48d02a0827d8830f3f Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Mon, 7 Sep 2015 19:48:43 +0530 Subject: [PATCH 1144/1435] Update Translations --- config/locales/client.ar.yml | 7 +- config/locales/client.bs_BA.yml | 5 +- config/locales/client.cs.yml | 5 +- config/locales/client.da.yml | 57 +++++++++++- config/locales/client.es.yml | 55 ++++++++++- config/locales/client.fa_IR.yml | 5 +- config/locales/client.fi.yml | 5 +- config/locales/client.fr.yml | 1 - config/locales/client.he.yml | 26 +++++- config/locales/client.ja.yml | 5 +- config/locales/client.ko.yml | 5 +- config/locales/client.nl.yml | 32 ++++++- config/locales/client.pl_PL.yml | 32 ++++++- config/locales/client.pt.yml | 32 ++++++- config/locales/client.ro.yml | 5 +- config/locales/client.ru.yml | 11 ++- config/locales/client.sq.yml | 5 +- config/locales/client.sv.yml | 6 +- config/locales/client.te.yml | 5 +- config/locales/client.tr_TR.yml | 159 +++++++++++++++++++++++++++++++- config/locales/client.uk.yml | 4 +- config/locales/client.zh_CN.yml | 37 ++++++-- config/locales/client.zh_TW.yml | 110 ++++++++++++++++++++++ config/locales/server.ar.yml | 63 ++++++++++--- config/locales/server.bs_BA.yml | 2 - config/locales/server.cs.yml | 2 - config/locales/server.da.yml | 2 - config/locales/server.de.yml | 2 - config/locales/server.es.yml | 3 - config/locales/server.fa_IR.yml | 2 - config/locales/server.fi.yml | 3 - config/locales/server.fr.yml | 3 - config/locales/server.he.yml | 82 +++++++++++++++- config/locales/server.it.yml | 2 - config/locales/server.ja.yml | 3 - config/locales/server.ko.yml | 2 - config/locales/server.nb_NO.yml | 2 - config/locales/server.nl.yml | 25 ++++- config/locales/server.pl_PL.yml | 2 - config/locales/server.pt.yml | 10 +- config/locales/server.pt_BR.yml | 2 - config/locales/server.ro.yml | 2 - config/locales/server.ru.yml | 2 - config/locales/server.sq.yml | 3 - config/locales/server.sv.yml | 2 - config/locales/server.te.yml | 2 - config/locales/server.tr_TR.yml | 157 +++++++++++++++++++++++++++---- config/locales/server.uk.yml | 2 - config/locales/server.zh_CN.yml | 10 +- config/locales/server.zh_TW.yml | 2 - 50 files changed, 846 insertions(+), 162 deletions(-) diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index d939df1ad1..96a75d1353 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -123,7 +123,7 @@ ar: two: "ساعتان" few: "%{count} ساعات" many: "%{count} ساعة" - other: "%{count} ساعة" + other: "%{count} ساعات" x_days: zero: "0 يوم" one: "يوم واحد" @@ -663,7 +663,6 @@ ar: title: "دعوة" user: "المستخدمين المدعويين" sent: "تم الإرسال" - none: ".لم تقم بدعوة أي شخص إلى هنا حتى الأن" truncated: "اظهار اوائل {{count}} المدعويين" redeemed: "دعوات مستخدمة" redeemed_tab: "محررة" @@ -1702,6 +1701,8 @@ ar: help: "قمت بتفضيل هذا الموضوع" locked: help: "هذا الموضوع مغلق, لن يتم قبول اي رد " + archived: + help: "هذا الموضوع مؤرشف,لن تستطيع أن تعدل عليه" unpinned: title: "غير مثبت" help: "هذا الموضوع غير مثبت بالنسبة لك, سيتم عرضه بالترتيب العادي" @@ -1711,8 +1712,6 @@ ar: pinned: title: "مثبت" help: "هذا الموضوع مثبت لك, سوف يتم عرضه في اول القسم" - archived: - help: "هذا الموضوع مؤرشف,لن تستطيع أن تعدل عليه" invisible: help: "هذا الموضوع غير مصنف لن يظهر في قائمة التصانيف ولايمكن الدخول عليه الابرابط مباشر." posts: "مشاركات" diff --git a/config/locales/client.bs_BA.yml b/config/locales/client.bs_BA.yml index 68010abc39..3723992ece 100644 --- a/config/locales/client.bs_BA.yml +++ b/config/locales/client.bs_BA.yml @@ -437,7 +437,6 @@ bs_BA: search: "kucaj da potražiš pozivnice..." title: "Pozivnice" user: "Pozvan Korisnik" - none: "You haven't invited anyone here yet." truncated: "Showing the first {{count}} invites." redeemed: "Redeemed Invites" redeemed_at: "Redeemed" @@ -1083,6 +1082,8 @@ bs_BA: help: "Ovo je zvanično upozorenje." locked: help: "Ova tema je zatvorena; zvanično ne prima nove postove" + archived: + help: "Ova tema je arhivirana; zaleđena je i ne može biti promjenjena" unpinned: title: "Unpinned" help: "This topic is unpinned; it will display in default order" @@ -1092,8 +1093,6 @@ bs_BA: pinned: title: "Zakačena" help: "Ova tema je zakačena; biće na vrhu svoje kategorije" - archived: - help: "Ova tema je arhivirana; zaleđena je i ne može biti promjenjena" invisible: help: "Ovu temu sajt ne lista među najnovijim temama. Neće biti prisutna ni među listama tema unutar kategorija. Jedini način da se dođe do ove teme je direktan link" posts: "Odgovori" diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index a7118147fb..95dafdc65b 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -524,7 +524,6 @@ cs: search: "pište pro hledání v pozvánkách..." title: "Pozvánky" user: "Pozvaný uživatel" - none: "Zatím jste nikoho nepozvali." truncated: "Showing the first {{count}} invites." redeemed: "Uplatněné pozvánky" redeemed_tab: "Uplatněno" @@ -1424,6 +1423,8 @@ cs: help: "V tématu je vložena záložka" locked: help: "toto téma je uzavřené; další odpovědi nejsou přijímány" + archived: + help: "toto téma je archivováno; je zmraženo a nelze ho již měnit" unpinned: title: "Nepřipnuté" help: "Pro vás toto téma není připnuté; bude se zobrazovat v běžném pořadí" @@ -1433,8 +1434,6 @@ cs: pinned: title: "Připnuto" help: "Pro vás je toto téma připnuté; bude se zobrazovat na vrcholu seznamu ve své kategorii" - archived: - help: "toto téma je archivováno; je zmraženo a nelze ho již měnit" invisible: help: "Toto téma je neviditelné; nebude se zobrazovat v seznamu témat a lze ho navštívit pouze přes přímý odkaz" posts: "Příspěvků" diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index 7295b6bc0f..13eccd9186 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -108,6 +108,16 @@ da: facebook: 'del dette link på Facebook' google+: 'del dette link på Google+' email: 'send dette link i en e-mail' + action_codes: + split_topic: "split dette emne op" + autoclosed: + enabled: 'lukket %{when}' + disabled: 'åbnet %{when}' + closed: + enabled: 'lukket %{when}' + disabled: 'åbnet %{when}' + archived: + enabled: 'arkiveret %{when}' topic_admin_menu: "administrationshandlinger på emne" emails_are_disabled: "Alle udgående emails er blevet deaktiveret globalt af en administrator. Ingen emailnotifikationer af nogen slags vil blive sendt." edit: 'redigér titel og kategori for dette emne' @@ -123,6 +133,7 @@ da: admin_title: "Admin" flags_title: "Flag" show_more: "vis mere" + show_help: "indstillinger" links: "Links" links_lowercase: one: "link" @@ -333,6 +344,8 @@ da: topics_entered: "emner indtastet" post_count: "# indlæg" confirm_delete_other_accounts: "Er du sikker på, at du vil slette disse kontoer?" + user_fields: + none: "(vælg en indstilling)" user: said: "{{username}}:" profile: "Profil" @@ -349,6 +362,14 @@ da: invited_by: "Inviteret af" trust_level: "Tillidsniveau" notifications: "Underretninger" + desktop_notifications: + perm_denied_btn: "Tilladelse nægtet" + perm_denied_expl: "Du har ikke givet tilladelse til meddelelser. Brug din browser til at aktivere meddelelser og klik derefter på knappen, når du er færdig. (Computer: Ikonet længst til venstre i adresselinjen. Mobil: 'Site info'.)" + disable: "Deaktiver meddelelser" + currently_enabled: "(slået til)" + enable: "Aktiver meddelelser" + currently_disabled: "(slået fra)" + each_browser_note: "Bemærk: Du skal ændre indstillingen i alle dine browsere." dismiss_notifications: "Marker alle som læst" dismiss_notifications_tooltip: "Marker alle ulæste meddelelser som læst" disable_jump_reply: "Ikke hop til mit indlæg efter jeg svarer" @@ -361,6 +382,7 @@ da: admin: "{{user}} er admin" moderator_tooltip: "Dette bruger er moderator" admin_tooltip: "Denne bruger er administrator" + blocked_tooltip: "Brugeren er blokeret" suspended_notice: "Denne bruger er suspenderet indtil {{date}}." suspended_reason: "Begrundelse: " github_profile: "Github" @@ -421,6 +443,7 @@ da: upload_title: "Upload dit profil billede" upload_picture: "Upload et billede" image_is_not_a_square: "Advarsel: vi har klippet i billedet; bredde og højde var ikke ens." + cache_notice: "Du har ændret dit profilbillede, men der kan godt gå lidt tid inden ændringen træder i kraft." change_profile_background: title: "Profil baggrundsbillede" instructions: "Profil baggrunde vil blive centrerede og have en standard bredde på 850 pixels" @@ -487,15 +510,27 @@ da: label: "Betragt emner som nye når" not_viewed: "Jeg har ikke set dem endnu" last_here: "oprettet siden jeg var her sidst" + after_1_day: "oprettet indenfor den seneste dag" + after_2_days: "oprettet i de seneste 2 dage" + after_1_week: "oprettet i seneste uge" + after_2_weeks: "oprettet i de seneste 2 uger" auto_track_topics: "Følg automatisk emner jeg åbner" auto_track_options: never: "aldrig" immediately: "med det samme" + after_30_seconds: "efter 30 sekunder" + after_1_minute: "efter 1 minut" + after_2_minutes: "efter 2 minutter" + after_3_minutes: "efter 3 minutter" + after_4_minutes: "efter 4 minutter" + after_5_minutes: "efter 5 minutter" + after_10_minutes: "efter 10 minutter" invited: search: "tast for at søge invitationer…" title: "Invitationer" user: "Inviteret bruger" - none: "Du har ikke inviteret nogen endnu." + sent: "Sendt" + none: "Der er ingen afventende invitationer." truncated: "Viser de første {{count}} invitationer." redeemed: "Brugte invitationer" redeemed_tab: "Indløst" @@ -513,6 +548,7 @@ da: days_visited: "Besøgsdage" account_age_days: "Kontoens alder i dage" create: "Send en invitation" + generate_link: "Kopier invitations-link" bulk_invite: none: "Du har ikke inviteret nogen her endnu. Du kan sende individuelle invitationer eller invitere en masse mennesker på én gang ved at uploade en samlet liste over invitationer." text: "Masse invitering fra en fil" @@ -778,6 +814,8 @@ da: search: title: "søg efter emner, indlæg, brugere eller kategorier" no_results: "Ingen resultater fundet." + no_more_results: "Ikke flere resultater." + search_help: Hjælp til søgning searching: "Søger…" post_format: "#{{post_number}} af {{username}}" context: @@ -785,6 +823,7 @@ da: category: "Søg i kategorien \"{{category}}\"" topic: "Søg i dette emne" private_messages: "Søg i beskeder" + hamburger_menu: "gå til en anden emneliste eller kategori" go_back: 'gå tilbage' not_logged_in_user: 'bruger side, med oversigt over aktivitet og indstillinger' current_user: 'gå til brugerside' @@ -926,8 +965,10 @@ da: title: "Følger" description: "En optælling af nye svar vil blive vist for denne tråd. Du vil modtage en notifikation, hvis nogen nævner dit @name eller svarer dig." regular: + title: "Normal" description: "Du vil modtage en notifikation, hvis nogen nævner dit @name eller svarer dig." regular_pm: + title: "Normal" description: "Du vil modtage en notifikation, hvis nogen nævner dit @name eller svarer dig." muted_pm: title: "Lydløs" @@ -1362,6 +1403,8 @@ da: help: "Du har bogmærket dette emne" locked: help: "emnet er låst; det modtager ikke flere svar" + archived: + help: "emnet er arkiveret; det er frosset og kan ikke ændres" unpinned: title: "Ikke fastgjort" help: "Dette emne er ikke fastgjort for dig; det vil blive vist i den normale rækkefølge" @@ -1371,8 +1414,6 @@ da: pinned: title: "Fastgjort" help: "Dette emne er fastgjort for dig; det vil blive vist i toppen af dets kategori" - archived: - help: "emnet er arkiveret; det er frosset og kan ikke ændres" invisible: help: "Dette emne er ulistet; det vil ikke blive vist i listen over emner og kan kun tilgås med et direkte link" posts: "Indlæg" @@ -2229,6 +2270,9 @@ da: name: "Navn" image: "Billede" delete_confirm: "Er du sikker på du vil slette emotikonnet: %{name} ?" + embedding: + feed_polling_enabled: "Importer indlæg via RSS/ATOM" + feed_polling_url: "URL på RSS/ATOM feed der skal kravles" permalink: title: "Permalinks" url: "URL" @@ -2270,6 +2314,7 @@ da: title: 'Applikation' create: 'c Opret et nyt emne' notifications: 'n Åbn notifikationer' + hamburger_menu: '= Åbn i hamburgermenu' user_profile_menu: 'p Åben bruger menu' show_incoming_updated_topics: '. Vis opdaterede emner' search: '/ Søg' @@ -2403,3 +2448,9 @@ da: reader: name: Læser description: Har læst hver eneste indlæg i et emne med mere end 100 posts + popular_link: + name: Populært link + description: Postede et eksternt link med mindst 50 klik + hot_link: + name: Hot link + description: Postede et eksternt link med mindst 300 klik diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index c4e9145217..e9c723fbcf 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -306,6 +306,9 @@ es: mods_and_admins: "Solo moderadores y administradores" members_mods_and_admins: "Solo miembros del grupo, moderadores y administradores" everyone: "Todos" + trust_levels: + title: "Nivel de confianza entregado automáticamente a miembros cuando son añadidos:" + none: "Ninguno" user_action_groups: '1': "'Me gusta' Dados" '2': "'Me gusta' Recibidos" @@ -367,6 +370,7 @@ es: private_messages: "Mensajes" activity_stream: "Actividad" preferences: "Preferencias" + expand_profile: "Expandir" bookmarks: "Marcadores" bio: "Acerca de mí" invited_by: "Invitado Por" @@ -543,7 +547,7 @@ es: title: "Invitaciones" user: "Invitar Usuario" sent: "Enviadas" - none: "No has invitado a nadie todavía." + none: "No hay ninguna invitación pendiente que mostrar." truncated: "Mostrando las primeras {{count}} invitaciones." redeemed: "Invitaciones aceptadas" redeemed_tab: "Usado" @@ -563,6 +567,8 @@ es: days_visited: "Días Visitados" account_age_days: "Antigüedad de la cuenta en días" create: "Enviar una Invitación" + generate_link: "Copiar Enlace de Invitación" + generated_link_message: '

      ¡Enlace de Invitación generado con éxito!

      Este enlace de Invitación es sólo válido para la siguiente dirección de email: %{invitedEmail}

      ' bulk_invite: none: "No has invitado a nadie todavía. Puedes enviar invitaciones individuales o invitar a un grupo de personas a la vez subiendo un archivo para invitaciones en masa." text: "Archivo de Invitación en Masa" @@ -619,6 +625,7 @@ es: read_only_mode: enabled: "Modo solo-lectura activado. Puedes continuar navegando por el sitio pero las interacciones podrían no funcionar." login_disabled: "Iniciar sesión está desactivado mientras el foro esté en modo solo lectura." + too_few_topics_notice: "¡Vamos a empezar con la comunidad! Hay actualmente %{currentTopics} / %{requiredTopics} temas y %{currentPosts} / %{requiredPosts} posts. Los nuevos visitantes necesitan algunas conversaciones que leer y a las que responder." learn_more: "saber más..." year: 'año' year_desc: 'temas creados en los últimos 365 días' @@ -804,6 +811,20 @@ es: moved_post: "

      {{username}} movió {{description}}

      " linked: "

      {{username}} {{description}}

      " granted_badge: "

      Se te ha concedido '{{description}}'

      " + alt: + mentioned: "Mencionado por" + quoted: "Citado por" + replied: "Respondido" + posted: "Publicado por" + edited: "Editado tu post por" + liked: "Gustado tu post" + private_message: "Mensaje privado de" + invited_to_private_message: "Invitado a un mensaje privado de" + invited_to_topic: "Invitado a un tema de" + invitee_accepted: "Invitación aceptada por" + moved_post: "Tu post fue eliminado por" + linked: "Enlace a tu post" + granted_badge: "Distintivo concedido" popup: mentioned: '{{username}} te mencionó en "{{topic}}" - {{site_title}}' quoted: '{{username}} te citó en "{{topic}}" - {{site_title}}' @@ -837,10 +858,12 @@ es: category: "Buscar en la categoría \"{{category}}\"" topic: "Buscar en este tema" private_messages: "Buscar en mensajes" + hamburger_menu: "ir a otra lista de temas o categoría" go_back: 'volver' not_logged_in_user: 'página con el resumen de actividad y preferencias' current_user: 'ir a tu página de usuario' topics: + too_many_tracked: "Atención: tienes demasiados temas seguidos y nuevos, quita algunos usando Ignorar nuevos o Ignorar posts" bulk: reset_read: "Restablecer leídos" delete: "Eliminar temas" @@ -1032,6 +1055,7 @@ es: unpin: "Eliminar este tema del top de la categoría {{categoryLink}}." unpin_until: "Quitar este tema del top de la categoría {{categoryLink}} o esperar al %{until}." pin_note: "Los usuarios pueden desanclar el tema de forma individual por sí mismos." + pin_validation: "Es obligatorio especificar una fecha para destacar este tema." already_pinned: zero: "No hay temas destacados en {{categoryLink}}." one: "Temas anclados actualmente en {{categoryLink}}: 1." @@ -1366,6 +1390,7 @@ es: email_in_allow_strangers: "Aceptar emails de usuarios anónimos sin cuenta" email_in_disabled: "La posibilidad de publicar nuevos temas por email está deshabilitada en los ajustes del sitio. Para habilitar la publicación de nuevos temas por email," email_in_disabled_click: 'activa la opción "email in".' + suppress_from_homepage: "Ocultar categoría de la página de inicio." allow_badges_label: "Permitir conceder distintivos en esta categoría" edit_permissions: "Editar permisos" add_permission: "Añadir permisos" @@ -1431,6 +1456,10 @@ es: help: "Has guardado en marcadores este tema." locked: help: "este tema está cerrado; ya no aceptan nuevas respuestas" + archived: + help: "este tema está archivado; está congelado y no puede ser cambiado" + locked_and_archived: + help: "Este tema está cerrado y archivado; no acepta nuevas respuestas y no puede ser cambiado de ningún modo." unpinned: title: "Deseleccionado como destacado" help: "Este tema se ha dejado de destacar para ti; en tu listado de temas se mostrará en orden normal" @@ -1440,8 +1469,6 @@ es: pinned: title: "Destacado" help: "Este tema ha sido destacado para ti; se mostrará en la parte superior de su categoría" - archived: - help: "este tema está archivado; está congelado y no puede ser cambiado" invisible: help: "Este tema es invisible; no se mostrará en la lista de temas y solo puede acceder a él a través de su enlace directo." posts: "Posts" @@ -1892,6 +1919,7 @@ es: sent_test: "enviado!" delivery_method: "Método de entrega" preview_digest: "Vista previa de Resumen" + preview_digest_desc: "Previsualiza el contenido del email de resumen enviado a usuarios inactivos." refresh: "Actualizar" format: "Formato" html: "html" @@ -2228,6 +2256,7 @@ es: backups: "Copias de seguridad" login: "Login" plugins: "Plugins" + user_preferences: "Preferencias de los Usuarios" badges: title: Distintivos new_badge: Nuevo distintivo @@ -2314,6 +2343,13 @@ es: feed_settings: "Ajustes de Feed" feed_description: "Discourse podrá importar tu contenido de forma más fácil si proporcionas un feed RSS/ATOM de tu sitio." crawling_settings: "Ajustes de Crawlers" + embed_by_username: "Usuario para la creación de temas" + embed_post_limit: "Máximo número de posts a incluir" + embed_username_key_from_feed: "Clave para extraer usuario de discourse del feed" + embed_truncate: "Truncar los posts insertados" + feed_polling_enabled: "Importar posts usando RSS/ATOM" + feed_polling_url: "URL del feed RSS/ATOM del que extraer datos" + save: "Guardar ajustes de Insertado" permalink: title: "Enlaces permanentes" url: "URL" @@ -2344,6 +2380,8 @@ es: categories: 'g, c Categorías' top: 'g, t Arriba' bookmarks: 'g, b Marcadores' + profile: 'g, p Perfil' + messages: 'g, m Mensajes' navigation: title: 'Navegación' jump: '# Ir al post #' @@ -2355,12 +2393,14 @@ es: title: 'Aplicación' create: 'c Crear un tema nuevo' notifications: 'n Abrir notificaciones' + hamburger_menu: '= Abrir Menú' user_profile_menu: 'p Abrir menú de usuario' show_incoming_updated_topics: '. Mostrar temas actualizados' search: '/ Buscar' help: '? Abrir la guía de atajos de teclado' dismiss_new_posts: 'x, r Descartar Nuevo/Posts' dismiss_topics: 'x, t Descartar Temas' + log_out: 'shift+z shift+z Cerrar sesión' actions: title: 'Acciones' bookmark_topic: 'f Guardar/Quitar el tema de marcadores' @@ -2488,6 +2528,15 @@ es: reader: name: Lector description: Leyó todos los posts en un tema con más de 100 + popular_link: + name: Enlace Popular + description: Publicó un enlace externo con al menos 50 clicks + hot_link: + name: Enlace Candente + description: Publicó un enlace externo con al menos 300 clicks + famous_link: + name: Enlace Famoso + description: Publicó un enlace externo con al menos 1000 clicks google_search: |

      Buscar con Google

      diff --git a/config/locales/client.fa_IR.yml b/config/locales/client.fa_IR.yml index 29bf483af8..55764ac69d 100644 --- a/config/locales/client.fa_IR.yml +++ b/config/locales/client.fa_IR.yml @@ -465,7 +465,6 @@ fa_IR: search: "بنویسید تا فراخوانه‌ها را جستجو کنید..." title: "فراخوانه‌ها" user: "کاربر فراخوانده شده" - none: "هنوز هیچ‌کسی را به اینجا فرانخوانده‌اید" truncated: "نمایش {{count}} فراخوانهٔ نخست" redeemed: "آزاد سازی دعوتنامه" redeemed_tab: "آزاد شده" @@ -1296,6 +1295,8 @@ fa_IR: help: "شما بر روی این موضوع نشانک گذاشته‌اید." locked: help: "این موضوع بسته شده؛ پاسخ‌های تازه اینجا پذیرفته نمی‌شوند" + archived: + help: "این موضوع بایگانی شده؛ یخ زده و نمی‌تواند تغییر کند." unpinned: title: "خارج کردن از سنجاق" help: "این موضوع برای شما شنجاق نشده است، آن طور منظم نمایش داده خواهد شد" @@ -1305,8 +1306,6 @@ fa_IR: pinned: title: "سنجاق شد" help: "این موضوع برای شما سنجاق شده است، آن طور منظم در بالای دسته بندی نمایش داده خواهد شد." - archived: - help: "این موضوع بایگانی شده؛ یخ زده و نمی‌تواند تغییر کند." invisible: help: "این موضوع از لیست خارج شد: آن درلیست موضوعات نمایش داده نخواهد شد، و فقط از طریق لینک مستقیم در دسترس خواهد بود. " posts: "نوشته‌ها" diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index 6ba830792b..068c9c1e50 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -512,7 +512,6 @@ fi: search: "kirjoita etsiäksesi kutsuja..." title: "Kutsut" user: "Kutsuttu käyttäjä" - none: "Et ole vielä kutsunut ketään." truncated: "Näytetään ensimmäiset {{count}} kutsua." redeemed: "Hyväksytyt kutsut" redeemed_tab: "Hyväksytyt" @@ -1387,6 +1386,8 @@ fi: help: "Olet lisännyt ketjun kirjanmerkkeihisi" locked: help: "Tämä ketju on suljettu; siihen ei voi enää vastata." + archived: + help: "Tämä ketju on arkistoitu; se on jäädytetty eikä sitä voi muuttaa" unpinned: title: "Kiinnitys poistettu" help: "Ketjun kiinnitys on poistettu sinulta; se näytetään tavallisessa järjestyksessä." @@ -1396,8 +1397,6 @@ fi: pinned: title: "Kiinnitetty" help: "Tämä ketju on kiinnitetty sinulle; se näytetään alueensa ensimmäisenä" - archived: - help: "Tämä ketju on arkistoitu; se on jäädytetty eikä sitä voi muuttaa" invisible: help: "Tämä ketju on poistettu listauksista; sitä ei näytetä ketjujen listauksissa ja siihen pääsee vain suoralla linkillä" posts: "Viestejä" diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index 39f0d752d3..fecec8cd58 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -845,7 +845,6 @@ fr: not_logged_in_user: 'page utilisateur avec un résumé de l''activité en cours et les préférences ' current_user: 'voir la page de l''utilisateur' topics: - too_many_tracked: "Avertissement: vous avez trop de sujets nouveaux ou non-lus, il faut en libérer avec \"Ignorer nouveaux\" ou \"Ignorer messages\"" bulk: reset_read: "Réinitialiser la lecture" delete: "Supprimer les sujets" diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index bb87353c19..e6cee16c72 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -543,7 +543,7 @@ he: title: "הזמנות" user: "משתמש/ת שהוזמנו" sent: "נשלח" - none: "עוד לא הזמנת לכאן אף אחד." + none: "אין הזמנות ממתינות להציג" truncated: "מראה את {{count}} ההזמנות הראשונות." redeemed: "הזמנות נוצלו" redeemed_tab: "נענו" @@ -845,7 +845,6 @@ he: not_logged_in_user: 'עמוד משתמש עם סיכום פעילות נוכחית והעדפות' current_user: 'לך לעמוד המשתמש שלך' topics: - too_many_tracked: "אזהרה: יש לך יותר מדי נושאים חדשים שלא נקראו במעקב. נקה חלק בעזרת \"התעלם מחדשים\" או \"התעלם מפרסומים\"" bulk: reset_read: "איפוס נקראו" delete: "מחיקת נושאים" @@ -1437,6 +1436,10 @@ he: help: "יצרת סימניה לנושא זה" locked: help: "הנושא הזה נעול, הוא לא מקבל יותר תגובות חדשות" + archived: + help: "הנושא הזה אוכסן בארכיון; הוא הוקפא ולא ניתן לשנותו" + locked_and_archived: + help: "הנושא הזה סגור ומאורכב. לא ניתן להגיב בו יותר או לשנות אותו. " unpinned: title: "הורד מנעיצה" help: "נושא זה אינו מקובע עבורך; הוא יופיע בסדר הרגיל" @@ -1446,8 +1449,6 @@ he: pinned: title: "נעוץ" help: "נושא זה מקובע עבורך, הוא יופיע בראש הקטגוריה" - archived: - help: "הנושא הזה אוכסן בארכיון; הוא הוקפא ולא ניתן לשנותו" invisible: help: "נושא זה מוסתר; הוא לא יוצג ברשימות הנושאים, וזמין רק באמצעות קישור ישיר." posts: "הודעות" @@ -1543,6 +1544,8 @@ he: title: "תמיד" yearly: title: "שנתי" + quarterly: + title: "רבעוני" monthly: title: "חודשי" weekly: @@ -1551,6 +1554,7 @@ he: title: "יומי" all_time: "כל הזמנים" this_year: "שנה" + this_quarter: "רבע" this_month: "חודש" this_week: "שבוע" today: "היום" @@ -1721,6 +1725,9 @@ he: none_installed: "אין לך הרחבות מותקנות" version: "גרסה" enabled: "מאופשר?" + is_enabled: "Y" + not_enabled: "N" + cant_disable: "-" change_settings: "שינוי הגדרות" change_settings_short: "הגדרות" howto: "איך אני מתקין/מתקינה הרחבות?" @@ -1800,6 +1807,7 @@ he: header: "כותרת" top: "למעלה" footer: "כותרת תחתית" + embedded_css: "Embedded CSS" head_tag: text: "" title: "קוד HTML שיוכנס לפני התגית " @@ -1891,6 +1899,7 @@ he: sent_test: "נשלח!" delivery_method: "שיטת העברה" preview_digest: "תצוגה מקדימה של סיכום" + preview_digest_desc: "תצוגה מקדימה של מייל סיכום שנשלח למשתמשים לא פעילים. " refresh: "רענן" format: "פורמט" html: "html" @@ -1955,6 +1964,7 @@ he: delete_post: "מחיקת פרסום" impersonate: "התחזה" anonymize_user: "הפיכת משתמש/ת לאנונימיים" + roll_up: "roll up IP blocks" screened_emails: title: "הודעות דואר מסוננות" description: "כשמישהו מנסה ליצור חשבון חדש, כתובות הדואר האלקטרוני הבאות ייבדקו וההרשמה תחסם או שיבוצו פעולות אחרות." @@ -2176,6 +2186,7 @@ he: delete: "מחיקה" cancel: "ביטול" delete_confirm: "האם את/ה בטוחים שאתם רוצים למחוק את שדה משתמש/ת הזה?" + options: "אפשרויות" required: title: "נדרש בעת הרשמה?" enabled: "נדרש" @@ -2191,6 +2202,7 @@ he: field_types: text: 'שדה טקסט' confirm: 'אישור' + dropdown: "נגלל" site_text: none: "בחרו את סוג התוכן לתחילת עריכה." title: 'תוכן טקסטואלי' @@ -2224,6 +2236,7 @@ he: backups: "גיבויים" login: "התחברות" plugins: "הרחבות" + user_preferences: "הגדרות משתמש" badges: title: תגים new_badge: תג חדש @@ -2297,6 +2310,11 @@ he: name: "שם" image: "תמונה" delete_confirm: "האם את/ה בטוח/ה שאתם רוצים למחוק את האמוג'י :%{name}:?" + embedding: + get_started: "אם ברצונך לשלב את דיסקורס באתר אחר, התחל בהוספת המערך שלו (host). " + confirm_delete: "האם אתה בטוח שאתה רוצה למחוק את הhost הזה? " + sample: "השתמש בקוד HTML הבא באתר שלך על מנת ליצור נושאי דיסקורס משולבים. החלף REPLACE_ME בURL הקאנוני של העמוד שבו אתה מכניס נושא מכונן. " + title: "שילוב (embedding)" permalink: title: "קישורים קבועים" url: "כתובת" diff --git a/config/locales/client.ja.yml b/config/locales/client.ja.yml index 12f4276388..9af856e528 100644 --- a/config/locales/client.ja.yml +++ b/config/locales/client.ja.yml @@ -482,7 +482,6 @@ ja: search: "招待履歴を検索するためにキーワードを入力してください..." title: "招待" user: "招待したユーザ" - none: "まだ招待を送っていません" truncated: "最初の {{count}} 個の招待履歴を表示しています。" redeemed: "受理された招待" redeemed_tab: "受理" @@ -1319,6 +1318,8 @@ ja: help: "このトピックをブックマークしました" locked: help: "このトピックは終了しています。新たに回答を投稿することはできません。" + archived: + help: "このトピックはアーカイブされています。凍結状態のため一切の変更ができません" unpinned: title: "ピン留め解除しました" help: "このトピックはピン留めされていません。 既定の順番に表示されます。" @@ -1328,8 +1329,6 @@ ja: pinned: title: "ピン留め" help: "このトピックはピン留めされています。常にカテゴリのトップに表示されます" - archived: - help: "このトピックはアーカイブされています。凍結状態のため一切の変更ができません" invisible: help: "このトピックはリストされていません。トピックリストには表示されません。直接リンクでのみアクセス可能です" posts: "投稿" diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml index e12299d1e1..6ec6c70699 100644 --- a/config/locales/client.ko.yml +++ b/config/locales/client.ko.yml @@ -513,7 +513,6 @@ ko: title: "초대" user: "사용자 초대" sent: "보냄" - none: "어떤 초대도 발견되지 않았습니다." truncated: "처음 {{count}}개 초대장 보여주기" redeemed: "초대를 받았습니다." redeemed_tab: "Redeemed" @@ -1358,6 +1357,8 @@ ko: help: "북마크한 토픽" locked: help: "이 토픽은 폐쇄되었습니다. 더 이상 새 답글을 받을 수 없습니다." + archived: + help: "이 토픽은 보관중입니다. 고정되어 변경이 불가능합니다." unpinned: title: "핀 제거" help: "이 토픽은 핀 제거 되었습니다. 목록에서 일반적인 순서대로 표시됩니다." @@ -1367,8 +1368,6 @@ ko: pinned: title: "핀 지정됨" help: "이 토픽은 고정되었습니다. 카테고리의 상단에 표시됩니다." - archived: - help: "이 토픽은 보관중입니다. 고정되어 변경이 불가능합니다." invisible: help: "이 토픽은 목록에서 제외됩니다. 토픽 목록에 표시되지 않으며 링크를 통해서만 접근 할 수 있습니다." posts: "게시물" diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index 1481dd7fc0..684c83590d 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -306,6 +306,9 @@ nl: mods_and_admins: "Alleen moderatoren and admins" members_mods_and_admins: "Alleen leden van de groep, moderatoren en admins" everyone: "Iedereen" + trust_levels: + title: "Trustlevel dat automatisch wordt toegekend aan nieuwe gebruikers:" + none: "Geen" user_action_groups: '1': "Likes gegeven" '2': "Likes ontvangen" @@ -367,6 +370,7 @@ nl: private_messages: "Berichten" activity_stream: "Activiteit" preferences: "Voorkeuren" + expand_profile: "Uitklappen" bookmarks: "Bladwijzers" bio: "Over mij" invited_by: "Uitgenodigd door" @@ -543,7 +547,7 @@ nl: title: "Uitnodigingen" user: "Uitgenodigd lid" sent: "Verzonden" - none: "Je hebt nog niemand uitgenodigd." + none: "Er zijn geen uitstaande uitnodigingen om weer te geven." truncated: "De eerste {{count}} uitnodigingen." redeemed: "Verzilverde uitnodigingen" redeemed_tab: "Verzilverd" @@ -807,6 +811,20 @@ nl: moved_post: "

      {{username}} verplaatste {{description}}

      " linked: "

      {{username}} {{description}}

      " granted_badge: "

      '{{description}}' ontvangen

      " + alt: + mentioned: "Genoemd door" + quoted: "Gequoot door" + replied: "Gereageerd" + posted: "Geplaatst door" + edited: "Wijzig je bericht door" + liked: "Vind je bericht leuk" + private_message: "Privébericht van" + invited_to_private_message: "Uitgenodigd voor een privébericht van" + invited_to_topic: "Uitgenodigd voor een topic door" + invitee_accepted: "Uitnodiging geaccepteerd door" + moved_post: "Je bericht is verplaatst door" + linked: "Link naar je bericht" + granted_badge: "Badge toegekend" popup: mentioned: '{{username}} heeft je genoemd in "{{topic}}" - {{site_title}}' quoted: '{{username}} heeft je geciteerd in "{{topic}}" - {{site_title}}' @@ -845,7 +863,7 @@ nl: not_logged_in_user: 'gebruikerspagina met samenvatting van huidige activiteit en voorkeuren' current_user: 'ga naar je gebruikerspagina' topics: - too_many_tracked: "Let op: je hebt te veel nieuwe en ongelezen topics gevolgd, verwijder ze met behulp van \"Markeer nieuwe berichten als gelezen\" of \"Verwijder Berichten\"" + too_many_tracked: "Let op: je hebt te veel topics staan onder nieuw en ongelezen, maak wat ruimte door Dismiss New of Dismiss Posts te gebruiken" bulk: reset_read: "markeer als ongelezen" delete: "Verwijder topics" @@ -1372,6 +1390,7 @@ nl: email_in_allow_strangers: "Accepteer mails van anonieme gebruikers zonder account" email_in_disabled: "Het plaatsen van nieuwe discussies via e-mail is uitgeschakeld in de Site Instellingen. Om het plaatsen van nieuwe discussie via e-mail aan te zetten," email_in_disabled_click: 'schakel "e-mail in" instelling in.' + suppress_from_homepage: "Negeer deze categorie op de homepage" allow_badges_label: "Laat badges toekennen voor deze categorie" edit_permissions: "Wijzig permissies" add_permission: "Nieuwe permissie" @@ -1437,6 +1456,10 @@ nl: help: "Je hebt een bladwijzer aan deze topic toegevoegd" locked: help: "Deze topic is gesloten; reageren is niet meer mogelijk" + archived: + help: "Deze topic is gearchiveerd en kan niet meer gewijzigd worden" + locked_and_archived: + help: "Deze topic is gesloten en gearchiveerd; reageren of wijzigen is niet langer mogelijk." unpinned: title: "Niet vastgepind" help: "Dit topic is niet langer vastgepind voor je en zal weer in de normale volgorde getoond worden" @@ -1446,8 +1469,6 @@ nl: pinned: title: "Vastgepind" help: "Dit topic is vastgepind voor je en zal bovenaan de categorie getoond worden" - archived: - help: "Deze topic is gearchiveerd en kan niet meer gewijzigd worden" invisible: help: "Dit topic is niet zichtbaar; het zal niet verschijnen in de topiclijst en kan alleen bekeken worden met een directe link" posts: "Berichten" @@ -2362,6 +2383,8 @@ nl: categories: 'g, c Categoriën' top: 'g, t Top' bookmarks: 'g, b Favorieten' + profile: 'g, p Profiel' + messages: 'g, m Berichten' navigation: title: 'Navigatie' jump: '# Ga naar bericht #' @@ -2380,6 +2403,7 @@ nl: help: '? Open sneltoetsen help' dismiss_new_posts: 'x, r Seponeer Nieuw/Berichten' dismiss_topics: 'x, t Seponeer Topics' + log_out: 'shift+z shift+z Uitloggen' actions: title: 'Acties' bookmark_topic: 'f Toggle bladwijzer van topic' diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml index 0bcfb2cd1f..37d0925066 100644 --- a/config/locales/client.pl_PL.yml +++ b/config/locales/client.pl_PL.yml @@ -334,6 +334,9 @@ pl_PL: mods_and_admins: "Tylko moderatorzy i administratorzy" members_mods_and_admins: "Tylko członkowie grupy, moderatorzy i administratorzy" everyone: "Wszyscy" + trust_levels: + title: "Domyślny poziom zaufania przyznawany nowych użytkownikom:" + none: "Brak" user_action_groups: '1': "Przyznane polubienia" '2': "Otrzymane polubienia" @@ -397,6 +400,7 @@ pl_PL: private_messages: "Wiadomości" activity_stream: "Aktywność" preferences: "Ustawienia" + expand_profile: "Rozwiń" bookmarks: "Zakładki" bio: "O mnie" invited_by: "Zaproszono przez" @@ -573,7 +577,7 @@ pl_PL: title: "Zaproszenia" user: "Zaproszony(-a) użytkownik(-czka)" sent: "Wysłane" - none: "Jeszcze nikt nie został przez ciebie zaproszony." + none: "Nie ma żadnych zaproszeń do wyświetlenia." truncated: "Pokaż pierwsze {{count}} zaproszeń." redeemed: "Cofnięte zaproszenia" redeemed_tab: "Przyjęte" @@ -838,6 +842,20 @@ pl_PL: moved_post: "

      {{username}} przenosi {{description}}

      " linked: "

      {{username}} {{description}}

      " granted_badge: "

      Otrzymujesz '{{description}}'

      " + alt: + mentioned: "Wywołanie przez" + quoted: "Cytowanie przez" + replied: "Odpowiedź" + posted: "Autor wpisu" + edited: "Edycja twojego wpisu" + liked: "Polubienie twojego wpisu" + private_message: "Prywatna wiadomość od" + invited_to_private_message: "Zaproszenie do prywatnej wiadomości od" + invited_to_topic: "Zaproszenie do tematu od" + invitee_accepted: "Zaproszenie zaakceptowane przez" + moved_post: "Twój wpis został przeniesiony przez" + linked: "Linkownie do twojego wpisu" + granted_badge: "Przyznanie odznaki" popup: mentioned: '{{username}} wspomina o tobie w "{{topic}}" - {{site_title}}' quoted: '{{username}} cytuje cie w "{{topic}}" - {{site_title}}' @@ -876,7 +894,7 @@ pl_PL: not_logged_in_user: 'strona użytkownika z podsumowaniem bieżących działań i ustawień' current_user: 'idź do swojej strony użytkowanika' topics: - too_many_tracked: "Uwaga: posiadasz zbyt wiele wpisów w nowych i śledzonych tematach. Aby kontynuować, wyczyść listę przyciskiem na dole strony." + too_many_tracked: "Uwaga: posiadasz zbyt wiele wpisów w nowych i śledzonych tematach. Aby kontynuować, wyczyść Nowe lub Śledzone" bulk: reset_read: "Wyzeruj przeczytane" delete: "Usuń tematy" @@ -1440,6 +1458,7 @@ pl_PL: email_in_allow_strangers: "Akceptuj wiadomości email od anonimowych, nieposiadających kont użytkowników " email_in_disabled: "Tworzenie nowych tematów emailem jest wyłączone w ustawieniach serwisu. " email_in_disabled_click: 'Kliknij tu, aby włączyć.' + suppress_from_homepage: "Nie wyświetlaj tej kategorii na stronie głównej." allow_badges_label: "Włącz przyznawanie odznak na podstawie aktywności w tej kategorii" edit_permissions: "Edytuj uprawnienia" add_permission: "Dodaj uprawnienie" @@ -1506,6 +1525,10 @@ pl_PL: help: "Temat został dodany do zakładek." locked: help: "Temat został zamknięty. Dodawanie nowych odpowiedzi nie jest możliwe." + archived: + help: "Ten temat został zarchiwizowany i nie można go zmieniać" + locked_and_archived: + help: "Ten temat jest zamknięty i zarchiwizowany. Dodawanie odpowiedzi i jego edycja nie są możliwe." unpinned: title: "Nieprzypięty" help: "Temat nie jest przypięty w ramach twojego konta. Będzie wyświetlany w normalnej kolejności." @@ -1515,8 +1538,6 @@ pl_PL: pinned: title: "Przypięty" help: "Temat przypięty dla twojego konta. Będzie wyświetlany na początku swojej kategorii." - archived: - help: "Ten temat został zarchiwizowany i nie można go zmieniać" invisible: help: "Temat jest niewidoczny: nie będzie wyświetlany na listach tematów a dostęp do niego można uzyskać jedynie poprzez link bezpośredni" posts: "Wpisy" @@ -2440,6 +2461,8 @@ pl_PL: categories: 'g, c Kategorie' top: 'g, t Popularne' bookmarks: 'g, b Zakładki' + profile: 'g, p Profil' + messages: 'g, m Wiadomości' navigation: title: 'Nawigacja' jump: '# idź do wpisu #' @@ -2458,6 +2481,7 @@ pl_PL: help: '? skróty klawiszowe' dismiss_new_posts: 'x, r wyczyść listę wpisów' dismiss_topics: 'x, t wyczyść listę tematów' + log_out: 'shift+z shift+z Wylogowanie' actions: title: 'Operacje' bookmark_topic: 'f dodaj/usuń zakładkę na temat' diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index 7c54bcd75c..33759f113d 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -306,6 +306,9 @@ pt: mods_and_admins: "Apenas moderadores e Administradores" members_mods_and_admins: "Apenas membros do grupo, moderadores e administradores" everyone: "Todos" + trust_levels: + title: "Nível de confiança concedido automaticamente a membros quando são adicionados:" + none: "Nenhum" user_action_groups: '1': "Gostos Dados" '2': "Gostos Recebidos" @@ -367,6 +370,7 @@ pt: private_messages: "Mensagens" activity_stream: "Atividade" preferences: "Preferências" + expand_profile: "Expandir" bookmarks: "Marcadores" bio: "Sobre mim" invited_by: "Convidado Por" @@ -543,7 +547,7 @@ pt: title: "Convites" user: "Utilizadores Convidados" sent: "Enviado" - none: "Ainda não convidou ninguém." + none: "Não há convites pendentes para mostrar." truncated: "A mostrar os primeiros {{count}} convites." redeemed: "Convites Resgatados" redeemed_tab: "Resgatado" @@ -807,6 +811,20 @@ pt: moved_post: "

      {{username}} moveu {{description}}

      " linked: "

      {{username}} {{description}}

      " granted_badge: "

      Ganhou '{{description}}'

      " + alt: + mentioned: "Mencionado por" + quoted: "Citado por" + replied: "Respondido" + posted: "Publicado por" + edited: "Edição da sua mensagem por" + liked: "Gostou da sua mensagem" + private_message: "Mensagem privada de" + invited_to_private_message: "Convidado para uma mensagem privada de" + invited_to_topic: "Convidado para um tópico de" + invitee_accepted: "Convite aceite por" + moved_post: "A sua mensagem foi movida por" + linked: "Hiperligação para a sua mensagem" + granted_badge: "Distintivo concedido" popup: mentioned: '{{username}} mencionou-o em "{{topic}}" - {{site_title}}' quoted: '{{username}} citou-o em "{{topic}}" - {{site_title}}' @@ -845,7 +863,7 @@ pt: not_logged_in_user: 'página de utilizador com resumo da atividade atual e preferências ' current_user: 'ir para a sua página de utilizador' topics: - too_many_tracked: "Aviso: tem demasiados tópicos novos ou não lidos a serem acompanhados, limpe alguns utilizando \"Destituir Novos\" ou \"Destituir Mensagens\"" + too_many_tracked: "Aviso: tem demasiados tópicos novos e não lidos a serem acompanhados, limpe alguns utilizando Destituir Novos ou Destituir Mensagens" bulk: reset_read: "Repor Leitura" delete: "Eliminar Tópicos" @@ -1372,6 +1390,7 @@ pt: email_in_allow_strangers: "Aceitar emails de utilizadores anónimos sem conta" email_in_disabled: "Publicar novos tópicos através do email está desactivado nas Configurações do Sítio. Para permitir a publicação de novos tópicos através do email," email_in_disabled_click: 'ative a definição "email em".' + suppress_from_homepage: "Suprimir esta categoria da página principal." allow_badges_label: "Permitir a atribuição de distintivos nesta categoria" edit_permissions: "Editar Permissões" add_permission: "Adicionar Permissões" @@ -1437,6 +1456,10 @@ pt: help: "Adicionou este tópico aos marcadores" locked: help: "Este tópico está fechado; já não são aceites novas respostas" + archived: + help: "Este tópico está arquivado; está congelado e não pode ser alterado" + locked_and_archived: + help: "Este tópico está fechado e arquivado; já não aceita novas respostas e não pode ser modificado" unpinned: title: "Desafixado" help: "Este tópico foi desafixado por si; será mostrado na ordem habitual" @@ -1446,8 +1469,6 @@ pt: pinned: title: "Fixado" help: "Este tópico foi fixado por si; será mostrado no topo da sua categoria" - archived: - help: "Este tópico está arquivado; está congelado e não pode ser alterado" invisible: help: "Este tópico não está listado; não será apresentado na lista de tópicos e poderá ser acedido apenas através de uma hiperligação direta" posts: "Mensagens" @@ -2362,6 +2383,8 @@ pt: categories: 'g, c Categorias' top: 'g, t Os Melhores' bookmarks: 'g, b Marcadores' + profile: 'g, p Perfil' + messages: 'g, m Mensagens' navigation: title: 'Navegação' jump: '# Ir para o post #' @@ -2380,6 +2403,7 @@ pt: help: '? Abrir ajuda do teclado' dismiss_new_posts: 'x, r Destituir Novos/Mensagens' dismiss_topics: 'x, t Destituir Tópicos' + log_out: 'shift+z shift+z Terminar Sessão' actions: title: 'Ações' bookmark_topic: 'f Alternar marcador de tópico' diff --git a/config/locales/client.ro.yml b/config/locales/client.ro.yml index a352bee210..b9d195509a 100644 --- a/config/locales/client.ro.yml +++ b/config/locales/client.ro.yml @@ -495,7 +495,6 @@ ro: search: "Scrie pentru a căuta invitații..." title: "Invitații" user: "Utilizatori invitați" - none: "Nu ai invitat înca pe nimeni." truncated: "Afișeaza primele {{count}} invitații." redeemed: "Invitații rascumpărate" redeemed_at: "Răscumpărate" @@ -1357,6 +1356,8 @@ ro: help: "Aţi pus un semn de carte pentru această discuţie" locked: help: "Această discuție este închisă; nu mai acceptă răspunsuri noi" + archived: + help: "Această discuție a fost arhivată; Este închetată și nu poate fi editată" unpinned: title: "Desprinde" help: "Această discuţie va fi afişată în ordinea iniţială, nici un mesaj nu este promovat la inceputul listei." @@ -1366,8 +1367,6 @@ ro: pinned: title: "Fixată" help: "Aceast mesaj va fi promovat. Va fi afişat la începutul discuţiei." - archived: - help: "Această discuție a fost arhivată; Este închetată și nu poate fi editată" invisible: help: "Această discuție este invizibilă; nu va fi afișată în listele de discuții și va fi accesată numai prin adresa directă" posts: "Postări" diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index cd135e8227..51574007e8 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -553,7 +553,6 @@ ru: search: "введите текст для поиска приглашений..." title: "Приглашения" user: "Приглашенный пользователь" - none: "Пока вы еще никого не пригласили." truncated: "Отображаются первые {{count}} приглашений." redeemed: "Принятые приглашения" redeemed_tab: "Принято" @@ -1490,6 +1489,8 @@ ru: help: "Вы добавили тему в Избранное " locked: help: "Тема закрыта; в ней больше нельзя отвечать" + archived: + help: "Тема заархивирована и не может быть изменена" unpinned: title: "Откреплена" help: "Эта тема не закреплена; она будет отображаться в обычном порядке" @@ -1499,8 +1500,6 @@ ru: pinned: title: "Закреплена" help: "Тема закреплена; она будет показана вверху соответствующего раздела" - archived: - help: "Тема заархивирована и не может быть изменена" invisible: help: "Тема исключена из всех списков тем и доступна только по прямой ссылке" posts: "Сообщ." @@ -2546,3 +2545,9 @@ ru: reader: name: Читатель description: Прочитал каждое сообщение в теме с более чем 100 сообщениями + popular_link: + name: Популярная ссылка + description: Оставил внешнюю ссылку с более чем 50 кликов + hot_link: + name: Горячая ссылка + description: Оставил внешнюю ссылку с более чем 300 кликов diff --git a/config/locales/client.sq.yml b/config/locales/client.sq.yml index 8c70fd5e04..bf8ffdfa91 100644 --- a/config/locales/client.sq.yml +++ b/config/locales/client.sq.yml @@ -512,7 +512,6 @@ sq: search: "shkruaj për të kërkuar ftesat..." title: "Ftesa" user: "Anëtarët e Ftuar" - none: "Nuk keni ftuar asnjë deri më tani." truncated: "Shfaq {{count}} ftesat e para." redeemed: "Ridërgo ftesat" redeemed_tab: "Redeemed" @@ -1387,6 +1386,8 @@ sq: help: "You bookmarked this topic" locked: help: "This topic is closed; it no longer accepts new replies" + archived: + help: "This topic is archived; it is frozen and cannot be changed" unpinned: title: "Unpinned" help: "This topic is unpinned for you; it will display in regular order" @@ -1396,8 +1397,6 @@ sq: pinned: title: "Pinned" help: "This topic is pinned for you; it will display at the top of its category" - archived: - help: "This topic is archived; it is frozen and cannot be changed" invisible: help: "This topic is unlisted; it will not be displayed in topic lists, and can only be accessed via a direct link" posts: "Postime" diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index 3a3fdf121f..8f4db72c6c 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -101,6 +101,7 @@ sv: google+: 'dela denna länk på Google+' email: 'skicka denna länk i ett email' action_codes: + split_topic: "Dela upp denna tråd" autoclosed: enabled: 'stängdes %{when}' disabled: 'öppnades %{when}' @@ -498,7 +499,6 @@ sv: search: "sök efter inbjudningar..." title: "Inbjudningar" user: "Inbjuden Användare" - none: "Du har inte bjudit in någon här ännu." truncated: "Visar de första {{count}} inbjudningarna." redeemed: "Inlösta Inbjudnignar" redeemed_at: "Inlöst" @@ -1356,6 +1356,8 @@ sv: help: "Du bokmärkte nu detta ämnet." locked: help: "Det här ämnet är stängt; det går inte längre att svara på inlägg" + archived: + help: "Det här ämnet är arkiverat; det är fryst och kan inte ändras" unpinned: title: "Avklistrat" help: "Detta ämne är oklistrat för dig. Det visas i vanlig ordning" @@ -1365,8 +1367,6 @@ sv: pinned: title: "Klistrat" help: "Detta ämne är klistrat för dig. Det visas i toppen av dess kategori" - archived: - help: "Det här ämnet är arkiverat; det är fryst och kan inte ändras" invisible: help: "Det här ämnet är olistat; det kommer inte visas i ämneslistorna och kan bara nås via en direktlänk" posts: "Inlägg" diff --git a/config/locales/client.te.yml b/config/locales/client.te.yml index 50dcae6405..e9726bc6b3 100644 --- a/config/locales/client.te.yml +++ b/config/locales/client.te.yml @@ -418,7 +418,6 @@ te: search: "ఆహ్వానాలను వెతకడానికి రాయండి ... " title: "ఆహ్వానాలు" user: "ఆహ్వానించిన సభ్యుడు" - none: "మీరు ఇక్కడ ఇంకా ఎవరినీ ఆహ్వానించలేదు." truncated: "తొలి {{count}} ఆహ్వానాలను చూపుతున్నాము." redeemed: "మన్నించిన ఆహ్వానాలు" redeemed_at: "మన్నించిన" @@ -1155,6 +1154,8 @@ te: help: "ఈ విషయానికి పేజీక ఉంచారు" locked: help: "ఈ విషయం ముగిసింది. కొత్త జవాబులు అంగీకరించదు. " + archived: + help: "ఈ విషయం కట్టకట్టబడింది. ఇది గడ్డకట్టుకుంది ఇహ మార్చయిత కాదు" unpinned: title: "అగ్గుచ్చిన" help: "ఈ విషయం మీకు అగ్గుచ్చబడింది. ఇది ఇహ క్రమ వరుసలోనే కనిపిస్తుంది" @@ -1164,8 +1165,6 @@ te: pinned: title: "గుచ్చారు" help: "ఈ విషయం మీకు గుచ్చబడింది. దాని వర్గంలో అది అగ్రభాగాన కనిపిస్తుంది." - archived: - help: "ఈ విషయం కట్టకట్టబడింది. ఇది గడ్డకట్టుకుంది ఇహ మార్చయిత కాదు" invisible: help: "ఈ విషయం జాబితాలనుండి తొలగించబడింది. ఇహ కేవలం నేరు లంకె ద్వారా మాత్రమే చూడగలరు." posts: "టపాలు" diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml index d359c28e6f..bb0ab800ec 100644 --- a/config/locales/client.tr_TR.yml +++ b/config/locales/client.tr_TR.yml @@ -89,6 +89,26 @@ tr_TR: facebook: 'bu bağlantıyı Facebook''da paylaşın' google+: 'bu bağlantıyı Google+''da paylaşın' email: 'bu bağlantıyı e-posta ile gönderin' + action_codes: + split_topic: "bu konuyu ayır" + autoclosed: + enabled: 'kapatıldı %{when}' + disabled: 'açıldı %{when}' + closed: + enabled: 'kapatıldı %{when}' + disabled: 'açıldı %{when}' + archived: + enabled: 'arşivlendi %{when}' + disabled: 'arşivden çıkarıldı %{when}' + pinned: + enabled: 'sabitlendi %{when}' + disabled: 'sabitlikten çıkarıldı %{when}' + pinned_globally: + enabled: 'genel olarak sabitlendi %{when}' + disabled: 'genel olarak sabitlikten çıkarıldı %{when}' + visible: + enabled: 'listelendi %{when}' + disabled: 'listelenmedi %{when}' topic_admin_menu: "konuyla alakalı yönetici işlemleri" emails_are_disabled: "Tüm giden e-postalar yönetici tarafından evrensel olarak devre dışı bırakıldı. Herhangi bir e-posta bildirimi gönderilmeyecek." edit: 'bu konunun başlığını ve kategorisini düzenleyin' @@ -104,6 +124,7 @@ tr_TR: admin_title: "Yönetici" flags_title: "Bayraklar" show_more: "devamını göster" + show_help: "seçenekler" links: "Bağlantılar" links_lowercase: other: "bağlantılar" @@ -157,8 +178,8 @@ tr_TR: bookmarks: not_logged_in: "üzgünüz, gönderileri işaretleyebilmeniz için oturum açmanız gerekiyor." created: "bu gönderiyi işaretlediniz" - not_bookmarked: "bu gönderiyi okudunuz; işaretlemek için tıklayın" - last_read: "bu okuduğunuz son gönderi; işaretlemek için tıklayın" + not_bookmarked: "bu gönderiyi okudunuz; yer imlerinize eklemek için tıklayın" + last_read: "bu okuduğunuz son gönderi; yer imlerinize eklemek için tıklayın" remove: "İşareti Kaldır" confirm_clear: "Bu konuya ait tüm işaretleri kaldırmak istediğinize emin misiniz?" topic_count_latest: @@ -257,6 +278,9 @@ tr_TR: mods_and_admins: "Sadece Moderatörler ve Yöneticiler" members_mods_and_admins: "Sadece Grup Üyeleri, Moderatörler ve Yöneticiler" everyone: "Herkes" + trust_levels: + title: "Eklendiklerinde üyelere otomatik olarak güven seviyesi verilir:" + none: "Hiç" user_action_groups: '1': "Verilen Beğeniler" '2': "Alınan Beğeniler" @@ -303,6 +327,8 @@ tr_TR: topics_entered: "açılan konular" post_count: "# gönderi" confirm_delete_other_accounts: "Bu hesapları silmek isteğinize emin misiniz?" + user_fields: + none: "(bir seçenek seçin)" user: said: "{{username}}:" profile: "Profil" @@ -314,11 +340,23 @@ tr_TR: private_messages: "Mesajlar" activity_stream: "Aktivite" preferences: "Seçenekler" + expand_profile: "Genişlet" bookmarks: "İşaretlenenler" bio: "Hakkımda" invited_by: "Tarafından Davet Edildi" trust_level: "Güven Seviyesi" notifications: "Bildirimler" + desktop_notifications: + label: "Masaüstü Bildirimleri" + not_supported: "Bildirimler bu tarayıcıda desteklenmiyor. Üzgünüz." + perm_default: "Bildirimleri Etkinleştirin" + perm_denied_btn: "Erişim İzni Reddedildi" + perm_denied_expl: "Bildirimler için gerekli izne sahip değilsiniz. Bildirimleri etkinleştirmek için tarayıcınızı kullanın, işlem tamamlandığında tuşa basın. (Masaüstü: Adres çubuğunda en soldaki simge. Mobil: 'Site Bilgisi'.)" + disable: "Bildirimleri Devre Dışı Bırakın" + currently_enabled: "(şu anda etkin)" + enable: "Bildirimleri Etkinleştirin" + currently_disabled: "(şu anda devre dışı)" + each_browser_note: "Not: Bu ayarı kullandığınız her tarayıcıda değiştirmelisiniz." dismiss_notifications: "Hepsini okunmuş olarak işaretle" dismiss_notifications_tooltip: "Tüm okunmamış bildirileri okunmuş olarak işaretle" disable_jump_reply: "Cevapladıktan sonra gönderime atlama" @@ -331,6 +369,7 @@ tr_TR: admin: "{{user}} bir yöneticidir" moderator_tooltip: "Bu kullanıcı bir moderatör" admin_tooltip: "Bu kullanıcı bir yönetici." + blocked_tooltip: "Bu kullanıcı engellendi" suspended_notice: "Bu kullanıcı {{tarih}} tarihine kadar uzaklaştırıldı." suspended_reason: "Neden:" github_profile: "Github" @@ -391,6 +430,7 @@ tr_TR: upload_title: "Resminizi yükleyin" upload_picture: "Resim Yükle" image_is_not_a_square: "Uyarı: resminizi kırptık; genişlik ve yükseklik eşit değildi." + cache_notice: "Profil resminizi başarıyla değiştirdiniz fakat tarayıcı önbelleklemesi nedeniyle görünür olması biraz zaman alabilir." change_profile_background: title: "Profil Arkaplanı" instructions: "Profil arkaplanları ortalanacak ve genişlikleri 850px olacak. " @@ -457,21 +497,35 @@ tr_TR: label: "Seçili durumdaki konular yeni sayılsın" not_viewed: "Onları henüz görüntülemedim" last_here: "son ziyaretimden beri oluşturulanlar" + after_1_day: "son 1 gün içinde oluşturuldu" + after_2_days: "son 2 gün içinde oluşturuldu" + after_1_week: "son 1 hafta içinde oluşturuldu" + after_2_weeks: "son 2 hafta içinde oluşturuldu" auto_track_topics: "Girdiğim konuları otomatik olarak takip et" auto_track_options: never: "asla" immediately: "hemen" + after_30_seconds: "30 saniye sonra" + after_1_minute: "1 dakika sonra" + after_2_minutes: "2 dakika sonra" + after_3_minutes: "3 dakika sonra" + after_4_minutes: "4 dakika sonra" + after_5_minutes: "5 dakika sonra" + after_10_minutes: "10 dakika sonra" invited: search: "davetler arasında aramak için yazın..." title: "Davetler" user: "Davet Edilen Kullanıcı" - none: "Henüz buraya kimseyi davet etmediniz." + sent: "Gönderildi" + none: "Bekleyen davet yok." truncated: "İlk {{count}} davetler gösteriliyor." redeemed: "Kabul Edilen Davetler" redeemed_tab: "Kabul Edildi" + redeemed_tab_with_count: "İtfa edilmiş ({{count}})" redeemed_at: "Kabul Edildi" pending: "Bekleyen Davetler" pending_tab: "Bekleyen" + pending_tab_with_count: "Beklemede ({{count}})" topics_entered: "Görüntülenmiş Konular" posts_read_count: "Okunmuş Yazılar" expired: "Bu davetin süresi doldu." @@ -483,6 +537,8 @@ tr_TR: days_visited: "Ziyaret Edilen Günler" account_age_days: "Gün içinde Hesap yaş" create: "Davet Yolla" + generate_link: "Davet bağlantısını kopyala" + generated_link_message: '

      Davet bağlantısı başarılı bir şekilde oluşturuldu!

      Davet bağlantısı sadece bu e-posta adresi için geçerlidir: %{invitedEmail}

      ' bulk_invite: none: "Henüz kimseyi buraya davet etmediniz. Tek tek davetiye gönderebilirsiniz, ya da toplu bir davetiye dosyası yükleyerek birçok kişiyi aynı anda davet edebilirsiniz. " text: "Dosyadan Toplu Davet Gönder" @@ -539,6 +595,7 @@ tr_TR: read_only_mode: enabled: "Salt-okunur modu etkin. Siteyi gezmeye devam edebilirsiniz fakat etkileşimler çalışmayabilir." login_disabled: "Site salt-okunur modda iken oturum açma devre dışı bırakılır ." + too_few_topics_notice: "Hadi bu tartışmayı başlatalım! Şu anda %{currentTopics} / %{requiredTopics} konu ve %{currentPosts} / %{requiredPosts} gönderi var. Yeni ziyaretçiler okumak ve yanıtlamak için birkaç tartışmaya ihtiyaç duyarlar." learn_more: "daha fazlasını öğren..." year: 'yıl' year_desc: 'son 365 günde oluşturulan konular' @@ -723,6 +780,20 @@ tr_TR: moved_post: "

      {{username}} taşıdı {{description}}

      " linked: "

      {{username}} {{description}}

      " granted_badge: "

      Kazanıldı'{{description}}'

      " + alt: + mentioned: "Bahsedildi, şu kişi tarafından" + quoted: "Alıntılandı, şu kişi tarafından" + replied: "Cevaplandı" + posted: "Gönderildi, şu kişi tarafından" + edited: "Gönderiniz düzenlendi, şu kişi tarafından" + liked: "Gönderiniz beğenildi" + private_message: "Özel mesaj, şu kişiden" + invited_to_private_message: "Bir özel mesaja davet edildiniz, şu kişi tarafından" + invited_to_topic: "Bir konuya davet edildiniz, şu kişi tarafından" + invitee_accepted: "Davet kabul edildi, şu kişi tarafından" + moved_post: "Gönderiniz taşındı, şu kişi tarafından" + linked: "Gönderinize bağlantı" + granted_badge: "Rozet alındı" popup: mentioned: '{{username}}, "{{topic}}" başlıklı konuda sizden bahsetti - {{site_title}}' quoted: '{{username}}, "{{topic}}" başlıklı konuda sizden alıntı yaptı - {{site_title}}' @@ -747,6 +818,8 @@ tr_TR: search: title: "konular, gönderiler, kullanıcılar, veya kategoriler arasında ara" no_results: "Hiç bir sonuç bulunamadı." + no_more_results: "Başka sonuç yok." + search_help: Arama yardımı searching: "Aranıyor..." post_format: "{{username}} tarafından #{{post_number}}" context: @@ -754,10 +827,12 @@ tr_TR: category: "\"{{category}}\" kategorisinde ara" topic: "Bu konuda ara" private_messages: "Mesajlarda ara" + hamburger_menu: "bir diğer konu ya da kategoriye git" go_back: 'geri dön' not_logged_in_user: 'güncel aktivitelerin ve ayarların özetinin bulunduğu kullanıcı sayfası' current_user: 'kendi kullanıcı sayfana git' topics: + too_many_tracked: "Uyarı: takip edilen çok fazla yeni ve okunmamış konunuz var, bazılarını Yeniyi Kaldır ya da Konuları Kaldır'ı kullanarak temizleyin." bulk: reset_read: "Okunmuşları Sıfırla" delete: "Konuları Sil" @@ -801,6 +876,9 @@ tr_TR: bookmarks: "Daha fazla işaretlenmiş konu yok." search: "Daha fazla arama sonucu yok." topic: + unsubscribe: + stop_notifications: "Artık {{title}} için daha az bildirim alacaksınız." + change_notification_state: "Geçerli bildirim durumunuz" filter_to: "Bu konuda {{post_count}} gönderi" create: 'Yeni Konu' create_long: 'Yeni bir konu oluştur' @@ -888,8 +966,10 @@ tr_TR: title: "Takip Ediliyor" description: "Okunmamış ve yeni gönderi sayısı başlığın yanında belirecek. Birisi @isim şeklinde sizden bahsederse ya da gönderinize cevap verirse bildirim alacaksınız." regular: + title: "Olağan" description: "Birisi @isim şeklinde sizden bahsederse ya da gönderinize cevap verirse bildirim alacaksınız." regular_pm: + title: "Olağan" description: "Birisi @isim şeklinde sizden bahsederse ya da gönderinize mesajla cevap verirse bildirim alacaksınız." muted_pm: title: "Susturuldu" @@ -932,15 +1012,20 @@ tr_TR: success_message: 'Bu konuyu başarıyla bayrakladınız.' feature_topic: title: "Bu konuyu ön plana çıkar" + pin: "Şu zamana kadar bu konunun {{categoryLink}} kategorisinin başında görünmesini sağla" confirm_pin: "Zaten başa tutturulan {{count}} konunuz var. Çok fazla konuyu başa tutturmak yeni ve anonim kullanıcılara sıkıntı çektirebilir. Bu kategoride bir konuyu başa tutturmak istediğinize emin misiniz?" unpin: "Bu konuyu {{categoryLink}} kategorisinin en üstünden kaldır." + unpin_until: "Bu konuyu {{categoryLink}} kategorisinin başından kaldır ya da şu zamana kadar bekle: %{until}." pin_note: "Kullanıcılar kendileri için konunun başa tutturulmasını kaldırabilir." + pin_validation: "Bu konuyu sabitlemek için bir tarih gerekli." already_pinned: zero: " {{categoryLink}} kategorisinde başa tutturulan herhangi bir konu yok." one: "Şu an {{categoryLink}} kategorisinde başa tutturulan konular: 1." other: "Şu an {{categoryLink}} kategorisinde başa tutturulan konular: {{count}}." + pin_globally: "Şu zamana kadar bu konunun bütün konu listelerinin başında yer almasını sağla" confirm_pin_globally: "Zaten her yerde başa tutturulan {{count}} konunuz var. Çok fazla konuyu başa tutturmak yeni ve anonim kullanıcılara sıkıntı çektirebilir. Bir konuyu daha her yerde başa tutturmak istediğinizden emin misiniz?" unpin_globally: "Bu konuyu tüm konu listelerinin en üstünden kaldır." + unpin_globally_until: "Bu konuyu bütün konu listelerinin başından kaldır ya da şu zamana kadar bekle: %{until}." global_pin_note: "Kullanıcılar kendileri için konunun başa tutturulmasını kaldırabilir." already_pinned_globally: zero: "Her yerde başa tutturulan herhangi bir konu yok." @@ -1005,6 +1090,12 @@ tr_TR: instructions: other: "Lütfen {{old_user}} kullanıcısına ait {{count}} gönderinin yeni sahibini seçin." instructions_warn: "Bu gönderi ile ilgili geriye dönük biriken bildirimler yeni kullanıcıya aktarılmayacak.
      Uyarı: Şu an, yeni kullanıcıya hiç bir gönderi-tabanlı ek bilgi aktarılmıyor. Dikkatli olun." + change_timestamp: + title: "Değişiklik Zaman Bilgisi" + action: "değişiklik zaman bilgisi" + invalid_timestamp: "Zaman bilgisi gelecekte olamaz." + error: "Konunun zaman bilgisini değiştirirken bir hata oluştu." + instructions: "Lütfen konunun yeni zaman bilgisini seçiniz. Konudaki gönderiler aynı zaman farkına sahip olmaları için güncellenecekler." multi_select: select: 'seç' selected: '({{count}}) seçildi' @@ -1016,6 +1107,8 @@ tr_TR: description: other: {{count}} gönderi seçtiniz. post: + reply: " {{replyAvatar}} {{usernameLink}}" + reply_topic: " {{link}}" quote_reply: "alıntıyla cevapla" edit: "{{link}} {{replyAvatar}} {{username}} düzenleniyor" edit_reason: "Neden: " @@ -1230,6 +1323,7 @@ tr_TR: email_in_allow_strangers: "Hesabı olmayan, anonim kullanıcılardan e-posta kabul et" email_in_disabled: "E-posta üzerinden yeni konu oluşturma özelliği Site Ayarları'nda devre dışı bırakılmış. E-posta üzerinden yeni konu oluşturma özelliğini etkinleştirmek için," email_in_disabled_click: '"e-postala" ayarını etkinleştir' + suppress_from_homepage: "Bu kategoriyi ana sayfadan gizle" allow_badges_label: "Bu kategoride rozet verilmesine izin ver" edit_permissions: "İzinleri Düzenle" add_permission: "İzin Ekle" @@ -1294,6 +1388,10 @@ tr_TR: help: "Bu konuyu işaretlediniz" locked: help: "Bu konu kapatıldı; artık yeni cevaplar kabul edilmiyor" + archived: + help: "Bu başlık arşive kaldırıldı; donduruldu ve değiştirilemez" + locked_and_archived: + help: "Bu konu kapatıldı ve arşivlendi; yeni cevaplar kabul edemez ve değiştirilemez." unpinned: title: "Başa tutturma kaldırıldı" help: "Bu konu sizin için başa tutturulmuyor; normal sıralama içerisinde görünecek" @@ -1303,8 +1401,6 @@ tr_TR: pinned: title: "Başa Tutturuldu" help: "Bu konu sizin için başa tutturuldu; kendi kategorisinin en üstünde görünecek" - archived: - help: "Bu başlık arşive kaldırıldı; donduruldu ve değiştirilemez" invisible: help: "Bu konu listelenmemiş; konu listelerinde görünmeyecek, ve sadece doğrudan bağlantı aracılığıyla erişilebilecek" posts: "Gönderi" @@ -1397,6 +1493,8 @@ tr_TR: title: "Tüm Zamanlar" yearly: title: "Yıllık" + quarterly: + title: "Üç aylık" monthly: title: "Aylı" weekly: @@ -1405,6 +1503,7 @@ tr_TR: title: "Günlük" all_time: "Tüm Zamanlar" this_year: "Yıl" + this_quarter: "Çeyrek" this_month: "Ay" this_week: "Hafta" today: "Bugün" @@ -1652,6 +1751,7 @@ tr_TR: header: "Başlık" top: "En Kısım" footer: "Alt Kısım" + embedded_css: "Gömülü CSS" head_tag: text: "" title: " etiketinden önce eklenecek HTML" @@ -1743,6 +1843,7 @@ tr_TR: sent_test: "gönderildi!" delivery_method: "Gönderme Metodu" preview_digest: "Özeti Önizle" + preview_digest_desc: "Durgun kullanıcılara gönderilen özet e-postaların içeriğini önizle." refresh: "Yenile" format: "Format" html: "html" @@ -2022,6 +2123,7 @@ tr_TR: delete: "Sil" cancel: "İptal et" delete_confirm: "Bu kullanıcı alanını silmek istediğinize emin misiniz?" + options: "Seçenekler" required: title: "Kayıt olurken zorunlu mu?" enabled: "gerekli" @@ -2037,6 +2139,7 @@ tr_TR: field_types: text: 'Yazı Alanı' confirm: 'Onay' + dropdown: "Açılır liste" site_text: none: "Düzenlemeye başlamak için içerik tipi seçin." title: 'Yazı İçeriği' @@ -2070,6 +2173,7 @@ tr_TR: backups: "Yedekler" login: "Oturum Açma" plugins: "Eklentiler" + user_preferences: "Kullanıcı Tercihleri" badges: title: Rozetler new_badge: Yeni Rozet @@ -2143,6 +2247,29 @@ tr_TR: name: "İsim" image: "Görsel" delete_confirm: ":%{name}: emojisini silmek istediğinize emin misiniz?" + embedding: + get_started: "Eğer Discourse'u bir başka web sitesine gömmek istiyorsanız, bu sitenin hostunu ekleyerek başlayın." + confirm_delete: "Bu hostu silmek istediğinize emin misiniz?" + sample: "Discourse konuları oluşturmak ve gömmek için aşağıdaki HTML kodunu sitenizde kullanın. REPLACE_ME'yi Discourse'u gömdüğünüz sayfanın tam URL'i ile değiştirin." + title: "Gömme" + host: "İzin Verilen Hostlar" + edit: "düzenle" + category: "Kategoriye Gönder" + add_host: "Host Ekle" + settings: "Ayarları Gömmek" + feed_settings: "Ayarları Besle" + feed_description: "Siteniz için bir RSS/ATOM beslemesi sağlamanız Discourse'un içeriğinizi içe aktarma yeteneğini geliştirebilir." + crawling_settings: "Böcek Ayarları" + crawling_description: "Discourse gönderileriniz için konular oluşturduğu zaman, eğer bir RSS/ATOM beslemesi yoksa içeriğinizi HTML'inizden ayrıştırmaya çalışacaktır. Bazen içeriğinizi çıkartmak çok zor olabilir, bu yüzden ayrıştırmayı kolaylaştırmak için CSS kuralları belirtme yeteneği sağlıyoruz." + embed_by_username: "Konu oluşturmak için kullanıcı adı" + embed_post_limit: "Gömmek için en büyük gönderi sayısı" + embed_username_key_from_feed: "Discourse kullanıcı adını beslemeden çekmek için anahtar" + embed_truncate: "Gömülü gönderileri buda" + embed_whitelist_selector: "Gömülüler içinde izin verilen elementler için CSS seçici" + embed_blacklist_selector: "Gömülülerden kaldırılan elementler için CSS seçici" + feed_polling_enabled: "Konuları RSS/ATOM aracılığıyla içe aktar" + feed_polling_url: "İstila etmek için RSS/ATOM beslemesi URL'i" + save: "Gömme Ayarlarını Kaydet" permalink: title: "Kalıcı Bağlantılar" url: "Bağlantı" @@ -2173,6 +2300,8 @@ tr_TR: categories: 'g, c Kategoriler' top: 'g, t En Popüler' bookmarks: 'g, b İşaretliler' + profile: 'g, p Profil' + messages: 'g, m İletiler' navigation: title: 'Navigasyon' jump: '# # numaralı gönderiye git' @@ -2184,12 +2313,14 @@ tr_TR: title: 'Uygulama' create: 'c Yeni konu aç' notifications: 'n Bildirileri aç' + hamburger_menu: '= Hamburger menüsünü aç' user_profile_menu: 'p Kullanıcı menüsünü aç' show_incoming_updated_topics: '. Güncellenmiş konuları göster' search: '/ Arama' help: '? Klavye yardımını göster' dismiss_new_posts: 'x, r Yeni Konuları/Gönderleri Yoksay' dismiss_topics: 'x, t Konuları Yoksay' + log_out: 'shift+z shift+z Çıkış Yapın' actions: title: 'Seçenekler' bookmark_topic: 'f Konu işaretlenmesini aç/kapa' @@ -2314,3 +2445,21 @@ tr_TR: reader: name: Okuyucu description: 100'den fazla gönderiye sahip bir konudaki tüm gönderileri oku + popular_link: + name: Gözde Bağlantı + description: En az 50 kere tıklanmış harici bir bağlantı gönderildi + hot_link: + name: Sıcak Bağlantı + description: En az 300 kere tıklanmış harici bir bağlantı gönderildi + famous_link: + name: Ünlü Bağlantı + description: En az 1000 kere tıklanmış harici bir bağlantı gönderildi + google_search: | +

      Google'la Ara

      +

      +

      +

      diff --git a/config/locales/client.uk.yml b/config/locales/client.uk.yml index 52807c6bc4..545d4d903a 100644 --- a/config/locales/client.uk.yml +++ b/config/locales/client.uk.yml @@ -913,12 +913,12 @@ uk: topic_statuses: locked: help: "цю тему закрито; нові відповіді більше не приймаються" + archived: + help: "цю тему заархівовано; вона заморожена і її не можна змінити" unpinned: title: "Не закріплені" pinned: title: "Закріплені" - archived: - help: "цю тему заархівовано; вона заморожена і її не можна змінити" posts: "Дописи" posts_lowercase: "дописи" posts_long: "тема містить {{number}} дописів" diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index 3d2ab622b9..b351c12c3c 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -278,6 +278,9 @@ zh_CN: mods_and_admins: "仅版主与管理员" members_mods_and_admins: "仅组员、版主与管理员" everyone: "任何人" + trust_levels: + title: "当这些用户加入时,信任等级将自动赋予给他们:" + none: "无" user_action_groups: '1': "给赞" '2': "被赞" @@ -337,6 +340,7 @@ zh_CN: private_messages: "消息" activity_stream: "活动" preferences: "设置" + expand_profile: "展开" bookmarks: "书签" bio: "关于我" invited_by: "邀请者为" @@ -513,7 +517,7 @@ zh_CN: title: "邀请" user: "邀请用户" sent: "已发送" - none: "你还没有邀请过任何人。" + none: "没有未接受状态的邀请。" truncated: "只显示前 {{count}} 个邀请。" redeemed: "确认邀请" redeemed_tab: "已确认" @@ -776,6 +780,20 @@ zh_CN: moved_post: "

      {{username}} 移动了 {{description}}

      " linked: "

      {{username}} {{description}}

      " granted_badge: "

      获得“{{description}}”

      " + alt: + mentioned: "被提及" + quoted: "被引用" + replied: "回复" + posted: "发自" + edited: "编辑你的帖子" + liked: "赞了你的帖子" + private_message: "私信来自" + invited_to_private_message: "私信邀请自" + invited_to_topic: "主题邀请自" + invitee_accepted: "介绍邀请自" + moved_post: "你的帖子被移动自" + linked: "链接至你的帖子" + granted_badge: "勋章授予" popup: mentioned: '{{username}}在“{{topic}}”提到了你 - {{site_title}}' quoted: '{{username}}在“{{topic}}”引用了你的帖子 - {{site_title}}' @@ -814,7 +832,7 @@ zh_CN: not_logged_in_user: '显示当前活动和设置的用户页面' current_user: '去你的用户页面' topics: - too_many_tracked: "警告:你有太多追踪的新主题和未读主题,使用“设为已读”清除一些" + too_many_tracked: "警告:你有太多追踪的新主题和未读主题,使用“设为已读”或忽略帖子清除一些" bulk: reset_read: "设为未读" delete: "删除主题" @@ -1305,6 +1323,7 @@ zh_CN: email_in_allow_strangers: "接受无账号的匿名用户的邮件" email_in_disabled: "站点设置中已经禁用通过邮件发表新主题。欲启用通过邮件发表新主题," email_in_disabled_click: '启用“邮件发表”设置。' + suppress_from_homepage: "不在主页中显示这个分类。" allow_badges_label: "允许在这个分类中授予徽章" edit_permissions: "编辑权限" add_permission: "添加权限" @@ -1369,6 +1388,10 @@ zh_CN: help: "你已经收藏了此主题" locked: help: "本主题已关闭;不再接受新的回复" + archived: + help: "本主题已归档;即已经冻结,无法修改" + locked_and_archived: + help: "本主题已经关闭并且存档;不再接受新回复且无法修改" unpinned: title: "解除置顶" help: "主题已经解除置顶;它将以默认顺序显示" @@ -1378,8 +1401,6 @@ zh_CN: pinned: title: "置顶" help: "本主题已置顶;它将始终显示在它所属分类的顶部" - archived: - help: "本主题已归档;即已经冻结,无法修改" invisible: help: "本主题被设置为不显示在主题列表中,并且只能通过直达链接来访问" posts: "帖子" @@ -2279,6 +2300,8 @@ zh_CN: categories: 'g 然后 c 分类列表' top: 'g, t 顶部' bookmarks: 'g 然后 b 书签' + profile: 'g 然后 p 个人页面' + messages: 'g 然后 m 消息' navigation: title: '导航' jump: '# 跳转到帖子 #' @@ -2297,6 +2320,7 @@ zh_CN: help: '? 打开键盘快捷键帮助' dismiss_new_posts: 'x, r 解除新/帖子提示' dismiss_topics: 'x, t 解除主题提示' + log_out: 'shift+z shift+z 登出' actions: title: '动作' bookmark_topic: 'f 切换主题收藏状态' @@ -2423,12 +2447,13 @@ zh_CN: description: 阅读一个超过 100 个帖子的主题中的每一个帖子 popular_link: name: 流行链接 - description: 发布了超过 50 个点击的外部链接 + description: 发布了超过 50 次点击的外部链接 hot_link: name: 热门链接 - description: 发布了超过 300 个点击的外部链接 + description: 发布了超过 300 次点击的外部链接 famous_link: name: 著名链接 + description: 发布了超过 1000 次点击的外部链接 google_search: |

      用 Google 搜索

      diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml index f9752d50db..6df19a62b1 100644 --- a/config/locales/client.zh_TW.yml +++ b/config/locales/client.zh_TW.yml @@ -66,6 +66,13 @@ zh_TW: other: "%{count} 小時前" x_days: other: "%{count} 天前" + later: + x_days: + other: "%{count} 天後" + x_months: + other: "%{count} 個月後" + x_years: + other: "%{count} 年後" share: topic: '在此話題內分享連結' post: '文章 #%{postNumber}' @@ -74,6 +81,8 @@ zh_TW: facebook: '在 Facebook 分享此連結' google+: '在 Google+ 分享此連結' email: '以電子郵件分享此連結' + action_codes: + split_topic: "拆分此主題" topic_admin_menu: "討論話題管理員操作" emails_are_disabled: "管理員已經停用了所有外寄郵件功能。通知信件都不會寄出。" edit: '編輯此討論話題的標題與分類' @@ -111,6 +120,7 @@ zh_TW: every_two_weeks: "每兩週" every_three_days: "每三天" max_of_count: "(最大 {{count}})" + alternation: "或者" character_count: other: "{{count}} 個字" suggested_topics: @@ -238,6 +248,8 @@ zh_TW: mods_and_admins: "只有板主以及管理員" members_mods_and_admins: "只有群組成員、板主以及管理員" everyone: "所有人" + trust_levels: + none: "無" user_action_groups: '1': "已按讚" '2': "已收到的讚" @@ -295,14 +307,25 @@ zh_TW: private_messages: "訊息" activity_stream: "活動" preferences: "偏好設定" + expand_profile: "展開" bookmarks: "書籤" bio: "關於我" invited_by: "邀請人" trust_level: "信任等級" notifications: "通知" + desktop_notifications: + label: "桌面通知" + not_supported: "非常遺憾,你的瀏覽器不支持桌面通知。" + perm_default: "啟用桌面通知" + perm_denied_btn: "權限被拒絕" + disable: "停用通知" + currently_enabled: "(當前已啟用)" + enable: "啟用通知" + currently_disabled: "(當前已關閉)" dismiss_notifications: "全部標記為已讀" dismiss_notifications_tooltip: "標記所有未讀通知為已讀" disable_jump_reply: "不要在回覆之後直接跳到我的文章" + dynamic_favicon: "在瀏覽器小圖示上顯示新主題/更新的主題數" edit_history_public: "讓其他用戶檢視我的文章修訂紀錄" external_links_in_new_tab: "以新分頁開啟所有外部連結" enable_quoting: "允許引用已標註的文字" @@ -311,6 +334,7 @@ zh_TW: admin: "{{user}} 是管理員" moderator_tooltip: "此用戶為板主" admin_tooltip: "此用戶為管理員" + blocked_tooltip: "此用戶被屏蔽" suspended_notice: "此用戶已被停權至 {{date}}。" suspended_reason: "原因: " github_profile: "Github" @@ -433,13 +457,26 @@ zh_TW: label: "視為新討論話題的條件" not_viewed: "我未看過的討論" last_here: "我上次到訪後的討論" + after_1_day: "昨天發佈的討論" + after_2_days: "過去兩天發佈的討論" + after_1_week: "過去一週發佈的討論" + after_2_weeks: "過去兩週發佈的討論" auto_track_topics: "自動追蹤我參與的討論" auto_track_options: never: "永不" + immediately: "立即" + after_30_seconds: "30 秒後" + after_1_minute: "一分鐘後" + after_2_minutes: "兩分鐘後" + after_3_minutes: "三分鐘後" + after_4_minutes: "四分鐘後" + after_5_minutes: "五分鐘後" + after_10_minutes: "十分鐘後" invited: search: "輸入要搜尋邀請的文字..." title: "邀請" user: "受邀請的用戶" + sent: "送出" truncated: "顯示前 {{count}} 個邀請。" redeemed: "已接受的邀請" redeemed_at: "接受日期" @@ -455,6 +492,7 @@ zh_TW: days_visited: "到訪天數" account_age_days: "帳號已建立 (天)" create: "送出邀請" + generate_link: "拷貝邀請連結" bulk_invite: none: "你尚未邀請任何人。你可以發送個別邀請,或者透過上傳邀請名單一次邀請一群人。" text: "從檔案大量邀請" @@ -690,6 +728,8 @@ zh_TW: moved_post: "

      {{username}} 移動了 {{description}}

      " linked: "

      {{username}} {{description}}

      " granted_badge: "

      獲得徽章「{{description}}」

      " + alt: + linked: "連結到你的討論" upload_selector: title: "加入一張圖片" title_with_attachments: "加入一張圖片或一個檔案" @@ -699,10 +739,12 @@ zh_TW: remote_tip_with_attachments: "圖片或文件連結 ({{authorized_extensions}})" hint: "(你也可以將檔案拖放至編輯器直接上傳)" uploading: "正在上傳" + select_file: "選取檔案" image_link: "連結你的圖片將指向" search: title: "搜尋討論話題、文章、用戶或分類" no_results: "未找到任何結果。" + search_help: 搜尋幫助 searching: "正在搜尋..." post_format: "#{{post_number}} {{username}}" context: @@ -832,6 +874,10 @@ zh_TW: title: "追蹤" tracking: title: "追蹤" + regular: + title: "一般" + regular_pm: + title: "一般" muted_pm: title: "靜音" description: "你將不會再收到關於此訊息的通知。" @@ -921,6 +967,10 @@ zh_TW: instructions: other: "請選擇一位新用戶作為此 {{count}} 篇由 {{old_user}} 撰寫之文章的擁有者。" instructions_warn: "注意,關於此篇文章的舊通知,並不會移轉到新用戶。
      警告:目前所有與文章相關的資料都不會移轉至新用戶,請謹慎使用。" + change_timestamp: + title: "變更時間戳記" + action: "變更時間戳記" + invalid_timestamp: "時間戳記不能為將來的時刻。" multi_select: select: '選取' selected: '選取了 ({{count}})' @@ -949,6 +999,14 @@ zh_TW: other: "檢視 {{count}} 則隱藏回應" more_links: "{{count}} 更多..." unread: "文章未讀" + has_replies: + other: "{{count}} 個回覆" + has_likes: + other: "{{count}} 個讚" + has_likes_title: + other: "{{count}} 個使用者對此文章讚好" + has_likes_title_you: + zero: "你已按讚" errors: create: "抱歉,建立你的文章時發生錯誤,請再試一次。" edit: "抱歉,編輯你的文章時發生錯誤,請再試一次。" @@ -1097,12 +1155,14 @@ zh_TW: category: can: '可以… ' none: '( 無分類 )' + all: '所有分類' choose: '選擇一個分類…' edit: '編輯' edit_long: "編輯" view: '檢視分類裡的討論話題' general: '一般' settings: '設定' + topic_template: "主題模板" delete: '刪除分類' create: '新分類' save: '儲存分類' @@ -1134,6 +1194,7 @@ zh_TW: email_in_allow_strangers: "接受非用戶的電郵" email_in_disabled: "\"用電子郵件張貼新的討論話題\"功能已被關閉。若要使用此功能," email_in_disabled_click: '請啟用"email in"功能' + suppress_from_homepage: "不在首頁上顯示此分類。" allow_badges_label: "允許授予本分類的徽章" edit_permissions: "編輯權限" add_permission: "新增權限" @@ -1236,6 +1297,10 @@ zh_TW: with_topics: "%{filter} 討論話題" with_category: "%{filter} %{category} 討論話題" latest: + title: + zero: "最新主題" + one: "最新主題 (1)" + other: "最新主題 ({{count}})" help: "最近的討論話題" hot: title: "熱門" @@ -1288,6 +1353,8 @@ zh_TW: title: "所有時間" yearly: title: "年" + quarterly: + title: "季度" monthly: title: "月" weekly: @@ -1295,6 +1362,10 @@ zh_TW: daily: title: "日" all_time: "所以時間" + this_year: "年" + this_quarter: "季度" + this_month: "月" + this_week: "週" today: "今天" other_periods: "前往頂端" browser_update: '抱歉,您的瀏覽器版本太舊,無法正常訪問該站點。。請升級您的瀏覽器。' @@ -1457,6 +1528,8 @@ zh_TW: none_installed: "尚未安裝任何外掛" version: "版本" enabled: "啟用?" + is_enabled: "是" + not_enabled: "否" change_settings: "更改設定" change_settings_short: "設定" howto: "如何安裝外掛?" @@ -1524,6 +1597,8 @@ zh_TW: screened_email: "以 CSV 格式匯出所有已顯示的電子郵件列表" screened_ip: "以 CSV 格式匯出所有已顯示的 IP 列表" screened_url: "以 CSV 格式匯出所有已顯示的 URL 列表" + export_json: + button_text: "匯出" invite: button_text: "送出邀請" button_title: "送出邀請" @@ -1534,6 +1609,7 @@ zh_TW: header: "標頭" top: "精選" footer: "頁尾" + embedded_css: "內嵌 CSS" head_tag: text: "" title: "HTML 將會置於 之前" @@ -1552,6 +1628,7 @@ zh_TW: new: "新增" new_style: "新增樣式" import: "匯入" + import_title: "選取檔案或貼上文本" delete: "刪除" delete_confirm: "刪除此樣式?" about: "修改網站的 CSS 和 HTML headers。請新增一個自定樣式來開始使用。" @@ -1901,6 +1978,7 @@ zh_TW: delete: "刪除" cancel: "取消" delete_confirm: "你確定要刪除此用戶欄位 ?" + options: "選項" required: title: "在註冊時必填?" enabled: "必填" @@ -1916,6 +1994,7 @@ zh_TW: field_types: text: '文字區域' confirm: '確認' + dropdown: "下拉" site_text: none: "選擇一個內容類別開始編輯" title: '文字內容' @@ -1927,6 +2006,7 @@ zh_TW: no_results: "未找到任何結果。" clear_filter: "清除" add_url: "加入網址" + add_host: "新增主機" categories: all_results: '全部' required: '必要設定' @@ -1948,6 +2028,7 @@ zh_TW: backups: "備份" login: "登入" plugins: "延伸套件" + user_preferences: "偏好設定" badges: title: 徽章 new_badge: 新徽章 @@ -2021,6 +2102,29 @@ zh_TW: name: "名稱" image: "圖片" delete_confirm: "你確定要刪除 :%{name}: emoji ?" + embedding: + confirm_delete: "你確定要刪除此主機?" + title: "嵌入" + host: "允許的主機" + edit: "編輯" + add_host: "新增主機" + settings: "嵌入設定" + crawling_settings: "爬蟲設定" + permalink: + title: "固定連結" + url: "網址" + topic_id: "討論話題 ID" + topic_title: "討論話題" + post_id: "貼文 ID" + post_title: "貼文" + category_id: "分類 ID" + category_title: "分類" + external_url: "外部網址" + delete_confirm: 你確定要刪除此固定連結? + form: + label: "新增:" + add: "新增" + filter: "搜尋 (網址或外部網址)" lightbox: download: "下載" search_help: @@ -2029,12 +2133,14 @@ zh_TW: title: '快捷鍵' jump_to: title: '跳到' + home: 'g, h 首頁' latest: 'g, l 最新' new: 'g , n 新' unread: 'g , u 未讀' categories: 'g, c 分類' top: 'g, t 頂端' bookmarks: 'g, b 書籤' + messages: 'g, m 私人訊息' navigation: title: '導航' jump: '# 前往文章 #' @@ -2046,12 +2152,14 @@ zh_TW: title: 'Application' create: 'c 表示新討論話題' notifications: 'n 開啟通知' + hamburger_menu: '= 打開漢堡選單' user_profile_menu: 'p 打開使用者選單' show_incoming_updated_topics: '. 顯示有更新的討論話題' search: '/ 搜尋' help: '? 打開按鍵說明' dismiss_new_posts: 'x, r 解除新文章或回覆的提示' dismiss_topics: 'x, t 解除主題的提示' + log_out: 'shift+z shift+z 登出' actions: title: '行動' bookmark_topic: 'f 加入/移除書籤' @@ -2174,3 +2282,5 @@ zh_TW: reader: name: 讀者 description: 觀看每個超過100篇文章的討論話題 + popular_link: + name: 熱門連結 diff --git a/config/locales/server.ar.yml b/config/locales/server.ar.yml index 78a28608e2..56cddab015 100644 --- a/config/locales/server.ar.yml +++ b/config/locales/server.ar.yml @@ -20,7 +20,7 @@ ar: is_reserved: "محجوز" purge_reason: "حذف آلي للحسابات المهجوره وغير النشطه " disable_remote_images_download_reason: "لقد تم تعطيل تحميل الصور عن بعد بسبب عدم وجود مساحة كافية" - anonymous: "مجهولون" + anonymous: "مجهول" errors: format: '%{attribute} %{message}' messages: @@ -30,31 +30,31 @@ ar: accepted: يجب أن تُقبل blank: لا يمكن جعله فارغا present: يجب أن يكون فارغ - confirmation: "ليست متوافقه%{attribute}" + confirmation: "ليست مطابقة ل %{attribute}" empty: لا يمكن جعله فارغا equal_to: يجب أن تكون مساوي لـ %{count} - even: يجب أن يكون منتظم + even: يجب أن يكون زوجي exclusion: محجوز greater_than: يجب أن تكون أكبر من %{count} greater_than_or_equal_to: يجب أن تكون أكبر من أو تساوي %{count} inclusion: غير متضمن في القائمة - invalid: فاشل + invalid: غير صالح less_than: يجب أن يكون أقل من %{count} less_than_or_equal_to: يجب أن تكون أقل من أو تساوي %{count} - not_a_number: ليس رقم + not_a_number: ليس عدد not_an_integer: يجب أن يكون عدد صحيح odd: يجب أن يكون مفرد - record_invalid: 'التحقق من الفشل: %{errors}' + record_invalid: 'التأكد من الصلاحية غير ممكن : %{errors}' restrict_dependent_destroy: - one: "لا يمكن حذف السجل " + one: "لا يمكن حذف السجل لإعتماد سجل آخر عليه %{record} " many: "لا يمكن حذف السجل لاعتماده %{record} موجود" too_long: zero: طويل جدا (الحد الأقصى بدون الحروف) one: طويل جدا (الحد الأقصى حرف واحد) two: طويل جدا (الحد الأقصى حرفين) few: طويل جدا (الحد الأقصى حروف قليلة) - many: طويل جدا (الحد الأقصى حروف كثيرة) - other: طويل جدا (الحد الأقصى %{count} حروف) + many: طويل جدا (الحد الأقصى %{count} حرفا) + other: طويل جدا (الحد الأقصى %{count} حرفا) too_short: zero: قصير جدا (الحد الأدنى بدون حروف) one: قصير جدا (الحد الأدنى حرف واحد) @@ -287,8 +287,32 @@ ar: assets_topic_body: "هذا الموضوع مشاهد بشكل دائم للأعضاء فقط . ولحفظ الصورة والملف استخدم موقع التصميم. لا تقوم بحذفها\n\n\nكيف يكون العمل هنا.\n\n1. الرد على الموضوع.\n\n2. رفع كل الصور التي تود أن تستخدمها في الشعار." lounge_welcome: title: "مرحبا بك في الاستراحة." + body: |2 + + تهانينا! :confetti_ball: + + إذا تمكنت من رؤية هذا الموضوع, فأنت رقيت مؤخرا لـ**منتظم** (مستوى الثقة 3). + + You can now … + + * تعديل العنوان لأي موضوع + * تغيير الفئة لأي موضوع + * جميع روابطك المتبوعة بـ ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) محذوفة) + * الوصول لفئة استراحة خاصة مرئية فقط لأعضاء مستوى الثقة 3 أو أعلى + * إخفاء الرسائل الغير هامة بتبليغ واحد + + من هنا [القائمة الحالية لمتابع منتظم](/badges/3/regular). طبعا قل مرحبا. + + شكرا لكونك جزءً هاما من هذا المجتمع! + + (لمعلومات أكثر حول مستويات الثقة, [see this topic][trust]. نرجو ملاحظة أن العضو الوحيد الذي يستمر في تلبية إحتياجات أكثر من الوقت سيبقون منتظمين.) + + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "عن الفئة %{category}." + replace_paragraph: "[استبدل هذا الفقرة الأولى بوصف قصير لفئتك الجديدة. سيظهر هذا التوجيه في منطقة اختيار الفئة, لذا حاول أن تبقيها أقل من 200 حرف. حتى عند تحريرك لهذا النص أو إنشائك لموضوع, هذه الفئة لن تظهر على صفحة الفئات.]" + post_template: "%{replace_paragraph}\n\nاستخدم الفقرات التالي لوصف طويل, إضافة لإنشاء أي فئة قواعد أو توجيهات.\n\nبعض الأشياء تؤخذ بعين الإعتبار في أسفل أي ردود لنقاش:\n\n- لما هذا الفئة؟لماذا يختار الناس هذه الفئة لمواضيعهم؟\n\n- كيف تختلف الفئات الأخرى عن التي لدينا؟\n\n- هل نحتاج هذه الفئة؟\n\n- هل يجب أن ندمج هذه مع فئة أخرى, أو تقسم إلى أكثر من قئة\ + \ ؟\n" errors: uncategorized_parent: "غير مصنف لا يمكن أن يكون فئة الأم" self_parent: "الفئة الفرعية لا يمكن ان تكون كالفئة الرئيسية." @@ -515,7 +539,7 @@ ar: please_continue: "الاستمرار لـ %{site_name}" error: "حدث خطأ عند تغير بريدك الإلكتروني. ربما يكون هذا البريد الإلكتروني قد أستوخدم من قبل ؟" activation: - action: "تم تفعيل حسابك" + action: "أضغط هنا لتفعيل حسابك" already_done: "رابط تأكيد الحساب لم يعد صالحاً , ربما الحساب نشط ؟" please_continue: "تأكيد حسابك الجديد : جاري توجيهك إلى الصفحة الرئيسية ." continue_button: "الاستمرار لـ %{site_name}" @@ -755,15 +779,20 @@ ar: title: "مرحباً: عضو مدعو" login_required_welcome_message: title: "مطلوب لتسجيل الدخول : رسالة ترحيب" + description: "رسالة الترحيب التي تظهر للأعضاء المسجلين خروجهم عندما يفعلون إعداد \"تسجيل الدخول مطلوب\"" login_required: title: "مطلوب لتسجيل الدخول : الصفحة الرئيسية" + description: "النص الظاهر للأعضاء الغير مصرح بهم عند الحاجة لتسجيل الدخول للموقع." head: title: "عنوان HTML رئيسي " + description: "HTML الذي سيدرج داخل علامات ." top: title: "أعلى الصفحة" bottom: title: "أسفل الصفحة" + description: "HTML التي ستضاف قبل علامة ." site_settings: + censored_words: "الكلمات التي ستستبدل تلقائيا مع ■■■■" delete_old_hidden_posts: "سيتم حذف الوظائف المخفية تلقائيًا إذا زادت مدة الإخفاء أكثر من 30 يومًا" default_locale: "اللغة الإفتراضية لهذا الديسكورس نموذج (ISO 639-1 Code)" allow_user_locale: "السماح للمستخدمين باختيار واجهة تفضيل لغة خاصة بهم." @@ -776,8 +805,10 @@ ar: min_private_message_title_length: "الحد الأدنى المسموح به لطول عنوان لرسالة في الأحرف" min_search_term_length: "الحد الأدنى الصالح لطول مصطلح في الأحرف" uncategorized_description: "الوصف للفئة غير المصنفة. اتركه فارغا لعدم الوصف." + allow_duplicate_topic_titles: "اسمح بالمواضيع المماثلة والعناوين المكررة." unique_posts_mins: "كمية الدقائق التي يمكن للعضو قبلها إنشاء مشاركة مع نفس المحتوى مجددا" title: "الاسم لهذا الموقع، كأنه يستخدم علامة العنوان." + site_description: "صف هذا الموقع بجملة واحدة باستخدام علامة الوصف meta." max_image_width: "أقصى عرض للصور المصغرة في مشاركة" max_image_height: "أقصى ارتفاع للصور المصغرة في مشاركة" category_featured_topics: "عدد المواضيع المعروضة لكل فئة من صفحة الفئات /categories .بعد تغير هذه القيمة, قد تستغرق صفحة الفئات 15 دقيقة لتُحَدّث." @@ -821,6 +852,9 @@ ar: new_version_emails: "إرسال بريد إلكتروني إلى عنوان contact_email عندما نسخة جديدة من ديسكورس هو متاح." port: "DEVELOPER فقط! تحذير! استخدام هذا المنفذ HTTP بدلا من الافتراضي من المنفذ 80. المغادرة ابحث عن الافتراضي من 80." force_hostname: "DEVELOPER فقط! تحذير! تحديد اسم المضيف في URL. اتركه فارغا لالافتراضي." + min_username_length: "الحد الأدنى لطول اسم العضو في الأحرف." + max_username_length: "الحد الأعلى لطول اسم العضو في الأحرف." + reserved_usernames: "الأعضاء الغير مسموح لها بالتسجيل." min_password_length: "أقل طول لكلمة المرور" block_common_passwords: "لا تسمح لكلمات المرور المسجلة في قائمة كلمات المرور الشائعةز" sso_url: "نقطة نهاية URL الدخول الموحد" @@ -879,12 +913,16 @@ ar: tl2_requires_read_posts: "كمية المشاركات التي يجب على العضو قرائتها قبل ترقيته لمستوى الثقة 2." tl2_requires_time_spent_mins: "كمية الدقائق التي يجب على العضو قراءة المشاركات فيها قبل ترقيته لمستوى الثقة 2." tl2_requires_days_visited: "كمية الأيام التي يجب على العضو زيارة الموقع فيها قبل ترقيته لمستوى الثقة 2." + tl2_requires_likes_received: "كمية الإعجابات التي يجب على العضو إرسالها قبل ترقيته لمستوى الثقة 2." + tl2_requires_likes_given: "كمية الإعجابات التي يجب على العضو جمعها قبل ترقيته لمستوى الثقة 2." + tl2_requires_topic_reply_count: "كمية مواضيع العضو التي يجب الرد عليها قبل الترقية لمستوى الثقة 2." newuser_max_links: "عدد الروابط التي يمكن للمستخدم الجديد إضافتها للمشاركة." newuser_max_images: "عدد الصور التي يمكن للمستخدم الجديد إضافتها للمشاركة." newuser_max_attachments: "عدد المرفقات التي يمكن للمستخدم الجديد إضافتها للمشاركة." title_max_word_length: "الحد الأقصى المسموح لطول كلمة، بالأحرف، في عنوان الموضوع." category_style: "النمط المرئي لفئة الشارات." auto_respond_to_flag_actions: "تمكين الرد التلقائي عند التخلص من التبليغ." + auto_block_fast_typers_max_trust_level: "الحد الأعلى لمستوى الثقة لأنواع الحظر التلقائي السريع." full_name_required: "الإسم الكامل مطلوب وهو ضروري لإكمال الحساب " enable_names: "عرض الاسم الكامل للعضو , بطاقة العضو , ورسائل البريد الالكتروني , تعطيل عرض الاسم في اي مكان " display_name_on_posts: "عرض الاسم الكامل للعضو على التعليقات بالاضافة الى @username." @@ -976,7 +1014,6 @@ ar: characters: "يجب ان تحتوي الارقام والاحرف الصغيرة فقط " unique: "يجب أن يكون فريدا" blank: "يجب أن يكون موجود" - must_begin_with_alphanumeric: "يجب ان يبدأ بحرف أو رقم " email: not_allowed: "بريد الكتروني غير مسموح . يرجى استخدام بريد الكتروني آخر " blocked: "غير مسموح" @@ -1038,8 +1075,8 @@ ar: text_body_template: "نحن آسفون ، ولكن رسالة البريد الإلكتروني إلى %{destination} (titled %{former_title}) لا تعمل. \n\nليس هنالك حساب عضو يمتلك هذا البريد الالكتروني. حاول أن ترسل من بريد الكتروني مختلف، أو أتصل بـ أحد المشرفين.\n\n" email_reject_empty: subject_template: "[%{site_name}] بريد الكتروني -- بدون محتوى" - text_body_template: "نحن آسفون ، ولكن رسالة البريد الإلكتروني إلى %{destination} (titled %{former_title}) لا تعمل. \n\nلم نتمكن من العثور على أي محتوى في البريد الإلكتروني الخاص بك. تأكد من الرد الخاص بك هو في الجزء العلوي من البريد الإلكتروني -- نحن لا يمكننا معالجة الردود في سطر .\n\nإذا كنت الحصول على هذا وانت_ قمت _\ - \ بتضمين المحتوى، حاول مرة أخرى مع محتوى HTML المضمنه في بريدك الالكتروني ( ليس مجرد نص عادي).\n" + text_body_template: "نحن آسفون ، ولكن رسالة البريد الإلكتروني إلى %{destination} (titled %{former_title}) لا تعمل. \n\nلم نتمكن من العثور على أي محتوى في البريد الإلكتروني الخاص بك. تأكد من الرد الخاص بك هو في الجزء العلوي من البريد الإلكتروني -- نحن لا يمكننا معالجة الردود في سطر .\n\nإذا كنت الحصول على هذا وانت_ قمت _ بتضمين المحتوى، حاول مرة أخرى\ + \ مع محتوى HTML المضمنه في بريدك الالكتروني ( ليس مجرد نص عادي).\n" email_reject_parsing: subject_template: "[%{site_name}] بريد الكتروني -- محتواه غير معروف" text_body_template: "نحن آسفون ، ولكن رسالة البريد الإلكتروني إلى %{destination} (titled %{former_title}) لا تعمل. \n\nلم نتمكن من العثور على أي محتوى في البريد الإلكتروني الخاص بك. **تأكد من الرد الخاص بك هو في الجزء العلوي من البريد الإلكتروني -- نحن لا يمكننا معالجة الردود في سطر .\n" diff --git a/config/locales/server.bs_BA.yml b/config/locales/server.bs_BA.yml index 8fcf2759eb..b093fa26c7 100644 --- a/config/locales/server.bs_BA.yml +++ b/config/locales/server.bs_BA.yml @@ -247,7 +247,6 @@ bs_BA: please_continue: "Continue to %{site_name}" error: "There was an error changing your email address. Perhaps the address is already in use?" activation: - action: "Aktiviraj svoj nalog" already_done: "Link za konfirmaciju nije validan. Možda ste već aktivirani?" please_continue: "Vaš novi nalog je verifikovan, i možete se ulogovati." continue_button: "Idi na Revolucionar.com" @@ -800,7 +799,6 @@ bs_BA: characters: "must only include numbers, letters and underscores" unique: "must be unique" blank: "must be present" - must_begin_with_alphanumeric: "must begin with a letter or number" email: not_allowed: "is not allowed from that email provider. Please use another email address." blocked: "is not allowed." diff --git a/config/locales/server.cs.yml b/config/locales/server.cs.yml index efdb2e458b..8366c878d5 100644 --- a/config/locales/server.cs.yml +++ b/config/locales/server.cs.yml @@ -372,7 +372,6 @@ cs: please_continue: "Pokračovat na %{site_name}" error: "Nastala chyba běhěm změny emailové adresy. Není nová adresa již někým používána?" activation: - action: "Aktivovat účet" already_done: "Bohužel, tento odkaz pro aktivaci účtu již není platný. Není váš účet jíž aktivní?" please_continue: "Váš účet je aktivovaný; budete přesměrování na výchozí stránku." continue_button: "Pokračovat na %{site_name}" @@ -717,7 +716,6 @@ cs: characters: "musí obsahovat pouze písmena a číslice" unique: "musí být unikátní" blank: "nesmí být prázdný" - must_begin_with_alphanumeric: "musí začínat písmenem nebo číslicí" email: not_allowed: "není povolen od tohoto emailového poskytovatele. Prosím použijte jinou emailovou adresu." blocked: "není povolen." diff --git a/config/locales/server.da.yml b/config/locales/server.da.yml index dfd87f18a9..ca41625e60 100644 --- a/config/locales/server.da.yml +++ b/config/locales/server.da.yml @@ -343,7 +343,6 @@ da: please_continue: "Fortsæt til %{site_name}" error: "Der opstod en fejl under opdateringen af din e-mail-adresse. Måske er adressen allerede i brug?" activation: - action: "Aktiver din konto" already_done: "Beklager, dette bekræftelses-link er ikke længere gyldigt. Måske er din konto allerede aktiv?" please_continue: "Din nye konto er bekræftet; du bliver nu ledt til forsiden." continue_button: "Fortsæt til %{site_name}" @@ -671,7 +670,6 @@ da: characters: "må kun indeholde bogstaver og tal" unique: "skal være unik" blank: "skal udfyldes" - must_begin_with_alphanumeric: "skal starte med et bogstav eller tal" email: not_allowed: "er ikke tilladt fra den e-mail-udbyder. Brug venligst en anden e-mail-adresse." blocked: "er ikke tilladt." diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml index 674f115455..fd3f07f885 100644 --- a/config/locales/server.de.yml +++ b/config/locales/server.de.yml @@ -376,7 +376,6 @@ de: please_continue: "Weiter zu %{site_name}" error: "Es gab einen Fehler beim Ändern deiner Mailadresse. Wird diese Adresse bereits genutzt?" activation: - action: "Aktiviere dein Benutzerkonto" already_done: "Entschuldige, dieser Link zur Aktivierung des Benutzerkontos ist nicht mehr gültig. Ist dein Konto schon aktiviert?" please_continue: "Dein neues Konto ist jetzt bestätigt; du wirst auf die Startseite weitergeleitet." continue_button: "Weiter zu %{site_name}" @@ -1076,7 +1075,6 @@ de: characters: "darf nur aus Zahlen und Buchstaben bestehen" unique: "muss eindeutig sein" blank: "muss angegeben werden" - must_begin_with_alphanumeric: "muss mit einer Zahl oder einem Buchstaben anfangen" email: not_allowed: "ist für diesen Mailprovider nicht erlaubt. Bitte verwende eine andere Mailadresse." blocked: "ist nicht erlaubt." diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml index c66bc0b0e9..4bad0f0d0e 100644 --- a/config/locales/server.es.yml +++ b/config/locales/server.es.yml @@ -392,7 +392,6 @@ es: please_continue: "Continuar a %{site_name}" error: "Hubo un problema cambiando tu dirección de email. ¿Quizás la dirección ya está en uso?" activation: - action: "Activar tu cuenta" already_done: "Lo sentimos, este link de confirmación de cuenta ya no es válido. ¿Quizás tu cuenta ya está activa?" please_continue: "Tu nueva cuenta está confirmada; se te redirigirá a la página de inicio." continue_button: "Continuar a %{site_name}" @@ -742,7 +741,6 @@ es: notify_mods_when_user_blocked: "Si un usuario es bloqueado automáticamente, enviar un mensaje a todos los moderadores." flag_sockpuppets: "Si un nuevo usuario responde a un tema desde la misma dirección de IP que el nuevo usuario que inició el tema, reportar los posts de los dos como spam en potencia." traditional_markdown_linebreaks: "Utiliza saltos de línea tradicionales en Markdown, que requieren dos espacios al final para un salto de línea." - allow_html_tables: "Permitir en Markdown la inserción de tablas usando etiquetas HTML como TABLE, THEAD, TD, TR o TH (requiere un rebake completo para todos los posts antiguos que contengan tablas)" post_undo_action_window_mins: "Número de minutos durante los cuales los usuarios pueden deshacer sus acciones recientes en un post (me gusta, reportes, etc)." must_approve_users: "Los miembros administración deben aprobar todas las nuevas cuentas antes de que se les permita el acceso al sitio. AVISO: ¡habilitar esta opción en un sitio activo revocará el acceso a los usuarios que no sean moderadores o admin!" ga_tracking_code: "Código de Google Analytics, ej: UA-12345678-9; visita http://google.com/analytics" @@ -1127,7 +1125,6 @@ es: characters: "solo debe incluir números y letras" unique: "debe ser único" blank: "debe estar presente" - must_begin_with_alphanumeric: "debe comenzar con una letra o número" email: not_allowed: "este proveedor de email no está permitido. Por favor, utiliza otra dirección de email." blocked: "no está permitido." diff --git a/config/locales/server.fa_IR.yml b/config/locales/server.fa_IR.yml index d679cd9332..f617f6f9ac 100644 --- a/config/locales/server.fa_IR.yml +++ b/config/locales/server.fa_IR.yml @@ -349,7 +349,6 @@ fa_IR: please_continue: "برو به %{site_name}" error: "در تغییر ایمیلتان خطایی روی داد. شاید آن نشانی از پیش در حال استفاده است؟" activation: - action: "حساب کاربریتان را فعال کنید" already_done: "متاسفیم٬‌ این پیوند تاییدیه حساب کاربری دیگر معتبر نیست. شاید حساب کاربری شما در حال حاضر فعال است." please_continue: "حساب کاربری جدید شما تایید شد; شما به صفحه اصلی هدایت می شوید. " continue_button: "برو به %{site_name}" @@ -1052,7 +1051,6 @@ fa_IR: characters: "باید شامل٬‌اعداد٬ حروف و زیر خط باشد" unique: "باید خاص باشد" blank: "باید حاضر باشد" - must_begin_with_alphanumeric: "حتما باید با حرف یا عدد شروع شود" email: not_allowed: "این قابل قبول نیست با این ایمیل ارائه شده. لطفا از یک ایمیل دیگر استفاده کنید. " blocked: "مجاز نیست." diff --git a/config/locales/server.fi.yml b/config/locales/server.fi.yml index c892ed6061..40be494111 100644 --- a/config/locales/server.fi.yml +++ b/config/locales/server.fi.yml @@ -401,7 +401,6 @@ fi: please_continue: "Jatka sivustolle %{site_name}" error: "Sähköpostiosoitteen vaihdossa tapahtui virhe. Ehkäpä tämä sähköpostiosoite on jo käytössä?" activation: - action: "Vahvista käyttäjätilisi" already_done: "Pahoittelut, tämän tilin varmennuslinkki ei ole enää voimassa. Ehkäpä tili on jo varmennettu?" please_continue: "Tilisi on nyt varmennettu; sivu ohjautuu palstan etusivulle." continue_button: "Jatka sivustolle %{site_name}" @@ -751,7 +750,6 @@ fi: notify_mods_when_user_blocked: "Jos käyttäjä estetään automaattisesti, lähetä viesti kaikille valvojille." flag_sockpuppets: "Jos uusi käyttäjä vastaa toisen uuden käyttäjän luomaan ketjun samasta IP osoitteesta, liputa molemmat viestit mahdolliseksi roskapostiksi." traditional_markdown_linebreaks: "Käytä perinteisiä rivinvaihtoja Markdownissa, joka vaatii kaksi perättäistä välilyöntiä rivin vaihtoon." - allow_html_tables: "Salli taulukoiden syöttäminen Markdowniin käyttäen HTML tageja, TABLE, THEAD, TD, TR, TH on whitelistattu (edellyttää kaikkien vanhojen viestien, jotka sisältävät taulukoita, uudelleen rakentamisen)" post_undo_action_window_mins: "Kuinka monta minuuttia käyttäjällä on aikaa perua viestiin kohdistuva toimi (tykkäys, liputus, etc)." must_approve_users: "Henkilökunnan täytyy hyväksyä kaikki uudet tilit, ennen uusien käyttäjien päästämistä sivustolle. VAROITUS: tämän asetuksen valitseminen poistaa pääsyn kaikilta jo olemassa olevilta henkilökuntaan kuulumattomilta käyttäjiltä." ga_tracking_code: "Google analytics (ga.js) seurantakoodi, esim.: UA-12345678-9; katso http://google.com/analytics" @@ -1136,7 +1134,6 @@ fi: characters: "täytyy koostua vain numeroista, kirjaimista ja alaviivoista" unique: "täytyy olla uniikki" blank: "pakollinen kenttä" - must_begin_with_alphanumeric: "täytyy alkaa kirjaimella tai numerolla" email: not_allowed: "ei sallita tältä sähköpostin palvelunatarjoajalta. Ole hyvä, ja käytä toista sähköpostiosoitetta." blocked: "ei ole sallittu." diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml index 5416f1f209..58e438ddb0 100644 --- a/config/locales/server.fr.yml +++ b/config/locales/server.fr.yml @@ -402,7 +402,6 @@ fr: please_continue: "Continuer vers %{site_name}" error: "Il y a eu une erreur lors de la modification de votre adresse de courriel. Elle est peut-être déjà utilisée ?" activation: - action: "Activer votre compte" already_done: "Désolé, ce lien de confirmation n'est plus valide. Votre compte est peut-être déjà activé ?" please_continue: "Votre nouveau compte est confirmé; vous allez être redirigé vers la page d'accueil." continue_button: "Continuer vers %{site_name}" @@ -753,7 +752,6 @@ fr: notify_mods_when_user_blocked: "Si un utilisateur est bloqué automatiquement, envoyer un message à tous les modérateurs." flag_sockpuppets: "Si un nouvel utilisateur répond à un sujet avec la même adresse I¨P que le nouvel utilisateur qui a commencé le sujet, alors leurs messages seront automatiquement marqués comme spam." traditional_markdown_linebreaks: "Utiliser le retour à la ligne traditionnel dans Markdown, qui nécessite deux espaces pour un saut de ligne." - allow_html_tables: "Autoriser la saisie des tableaux dans le Markdown en utilisant les tags HTML : TABLE, THEAD, TD, TR, TH sont autorisés (nécessite un rebake de tous les anciens messages contenant des tableaux)" post_undo_action_window_mins: "Nombre de minutes pendant lesquelles un utilisateur peut annuler une action sur un message (j'aime, signaler, etc.)" must_approve_users: "Les responsables doivent approuver les nouveaux utilisateurs afin qu'ils puissent accéder au site. ATTENTION : activer cette option sur un site en production suspendra l'accès des utilisateurs existants qui ne sont pas des responsables !" ga_tracking_code: "Google Analytics (ga.js) code de suivi, par exemple: UA-12345678-9; voir http://google.com/analytics" @@ -1157,7 +1155,6 @@ fr: characters: "doit inclure uniquement des chiffres, lettres et caractères de soulignement" unique: "doit être unique" blank: "doit être présent" - must_begin_with_alphanumeric: "doit commencer par une lettre ou un nombre" email: not_allowed: "n'est pas autorisé pour ce fournisseur de courriels. Merci d'utiliser une autre adresse." blocked: "n'est pas autorisé." diff --git a/config/locales/server.he.yml b/config/locales/server.he.yml index 4c34760d35..f63e60d745 100644 --- a/config/locales/server.he.yml +++ b/config/locales/server.he.yml @@ -387,7 +387,6 @@ he: please_continue: "המשך ל-%{site_name}" error: "הייתה שגאיה בעדכון כתובת הדואר האלקטרוני. אולי היא כבר בשימוש?" activation: - action: "הפעל/י את חשבונך" already_done: "סליחה, כתובת אישור החשבון הזו אינה זמינה יותר. אולי החשבון שלך כבר פעיל?" please_continue: "חשבונך החדש אושר; הנכם מועברים לעמוד הבית." continue_button: "המשך ל-%{site_name}" @@ -738,7 +737,6 @@ he: notify_mods_when_user_blocked: "If a user is automatically blocked, send a message to all moderators." flag_sockpuppets: "אם משתמש/ת חדשים מגיבים לנושא מכתובת IP זהה לזו של מי שהחל את הנושא, סמנו את הפרסומים של שניהם כספאם פוטנציאלי." traditional_markdown_linebreaks: "שימוש בשבירת שורות מסורתית בסימון, מה שדורש שני רווחים עוקבים למעבר שורה." - allow_html_tables: "אפשר להזין טבלאות ב-Markdown על ידי שימוש HTML tags, TABLE, THEAD, TD, TR, TH אפשריים (דורש הגדרה מחדש על כל הפרסומים הישנים שכוללים טבלאות). " post_undo_action_window_mins: "מספר הדקות בהן מתאפשר למשתמשים לבטל פעולות אחרות בפרסום (לייק, סימון, וכו')." must_approve_users: "על הצוות לאשר את כל המשתמשים החדשים לפני שהם מקבלים גישה לאתר. אזהרה: בחירה זו עבור אתר קיים תשלול גישה ממשתמשים קיימים שאינם מנהלים." ga_tracking_code: "Google analytics (ga.js) tracking code code, eg: UA-12345678-9; see http://google.com/analytics" @@ -1142,7 +1140,6 @@ he: characters: "חייב לכלול מספרים, אותיות ומקפים תחתונים בלבד." unique: "חייב להיות ייחודי" blank: "חייב להיות מלא" - must_begin_with_alphanumeric: "חייב להתחיל עם אות או מספר" email: not_allowed: "לא מורשה מכתובת הדואר האלקטרוני הזו. בבקשה השתמש בכתובת אחרת." blocked: "לא מורשה." @@ -1370,12 +1367,24 @@ he: לא ידוע לנו על חשבון משתמש/ת עם כתובת כזו. נסו לשלוח מכתובת דוא"ל אחרת, או צרו קשר עם אנשי הצוות שלנו. email_reject_empty: subject_template: "[%{site_name}] בעיית מייל -- ללא תוכן" + text_body_template: "אנחנו מתנצלים, אבל הודעת המייל שלך ל- %{destination} (titled %{former_title}) לא עבדה. \n\nלא מצאנו תוכן במייל שלך. אנא ודא שהתגובה שלך בראש המייל, אנחנו לא יכולים לעבד תגובות בין השורות. \n\nאם כן כללת תוכן, נצא שוב עם תוכן HTML בתוך המייל, לא רק טקסט רגיל. \n" + email_reject_parsing: + subject_template: "[%{site_name}] בעיית מייל -- תוכן לא זוהה." + text_body_template: "אנחנו מתנצלים, אבל הודעת המייל שלך ל- %{destination} (titled %{former_title}) לא עבדה. \n\nלא מצאנו תוכן במייל שלך. אנא ודא שהתגובה שלך בראש המייל, אנחנו לא יכולים לעבד תגובות בין השורות. \n" + email_reject_invalid_access: + subject_template: "[%{site_name}] בעיית מייל -- גישה לא תקינה" + text_body_template: | + אנו מצטערים, אבל הודעת הדוא"ל שלך אל %{destination} (titled %{former_title}) לא עברה. + + אין לחשבון שלך את רמון האמון הנדרשת כדי לפרסם נושאים חדשים בקטגוריה הזו. אם אתם חושבים שזוהי שגיאה, צרו קשר עם אחד מאנשי הצוות. email_reject_post_error: + subject_template: "[%{site_name}] בעיית מייל -- שגיאת פרסום" text_body_template: | אנו מצטערים, אך מסר הדוא"ל שלך ל-%{destination} (שכותרתו %{former_title}) לא עבר. סיבות אפשריות לעניין: מבנה מורכב, המסר ארוך מידי, המסר קצר מידי. אנא נסו שוב, או פרסמו באמצעות האתר עם הבעיה נמשכת. email_reject_post_error_specified: + subject_template: "[%{site_name}] בעיית מייל -- שגיאת פרסום" text_body_template: | אנחנו מצטערים, אבל הודעת הדוא"ל שלך אל %{destination} (titled %{former_title}) לא עברה. @@ -1392,16 +1401,37 @@ he: If you can correct the problem, please try again. email_reject_reply_key: + subject_template: "[%{site_name}] בעיית מייל -- תו תגובה לא מוכר" text_body_template: | אנו מצטערים, אבל הודעת הדוא"ל שלך אל %{destination} (titled %{former_title}) לא עברה. מפתח התגובה לא קיים או לא מוכר, כך שאיננו יודעים לאיזה פרסום המייל הזה אמור להגיב. אנא צרו קשר עם אנשי הצוות. email_reject_destination: + subject_template: "[%{site_name}] בעיית מייל -- כתובת יעד לא מזוהה" text_body_template: | מצטערים, אבל הודעת הדוא"ל שלך ל-%{destination} עם הכותרת %{former_title} לא הצליחה. לא הצלחנו לזהות כתובות יעד. אנא ודא שכתובת האתר נמצאת בשדה "אל:", ולא ב-"עותק" או "עותק מוחבא (CC / BCC), ושאתה שולח לכתובת אימייל שסופקה בידי הצוות. + email_reject_topic_not_found: + subject_template: "[%{site_name}] בעיית מייל -- לא נמצא נושא" + text_body_template: | + אנחנו מצטערים, אך הודעת הדוא"ל ששלחת אל %{destination} (בנושא %{former_title}) לא עברה. + + הנושאחושבים שהגבתם אליו כבר לא קיים, אולי הוא נמחק? אם אתם שמדובר בתקלה, צרו קשר עם איש/אשת צוות. + email_reject_topic_closed: + subject_template: "[%{site_name}] בעיית מייל -- נושא נסגר" + text_body_template: | + אנחנו מצטערים, אך הודעת הדוא"ל שלך אל %{destination} (בנושא %{former_title}) לא נשלחה. + + הנושא שהשבתם אליו נסגר ולכן לא ניתן להשיב לו יותר. אם אתם מאמינים שזוהי תקלה, אנא צרו קשר עם איש/אשת צוות. + email_reject_auto_generated: + subject_template: "[%{site_name}] בעיית מייל -- תגובה נוצרה אוטומטית" + text_body_template: | + אנחנו מצטערים, אך הודעת הדוא"ל ששלחת אל %{destination} (בנושא %{former_title}) לא עברה. + + המייל שלך סומן כ"נוצר אוטומטית", ולכן לא ניתן לקבל אותו. אם אתם מאמינים שמדובר בתקלה, צרו קשר עם איש/אשת צוות. email_error_notification: + subject_template: "[%{site_name}] בעיית מייל -- בעיית אימות POP" text_body_template: | התרחשה תקלת אימות בזמן שבדקנו הודעות מייל בשרת ה-POP. @@ -1455,6 +1485,10 @@ he: download_remote_images_disabled: subject_template: "הורדת תמונות מרחוק מנוטרלת" text_body_template: "האפשרות \"הורדת תמונות מרוחקות\" נוטרלה בגלל שכל שטח האכסון שמוקצה ל\"תמונות שהורדו מרחוק\" נוצל." + unsubscribe_link: | + להסרה מרשימת התפוצה, בקר ב [הגדרות משתמש/ת](%{user_preferences_url}). + + בשביל להפסיק לקבל התראות בנוגע לשיחה הזאת [לחץ כאן](%{unsubscribe_url}). subject_re: "תגובה: " subject_pm: "[PM] " user_notifications: @@ -1465,6 +1499,12 @@ he: reply_by_email: "To respond, reply to this email or visit %{base_url}%{url} in your browser." visit_link_to_respond: "To respond, visit %{base_url}%{url} in your browser." posted_by: "Posted by %{username} on %{post_date}" + user_invited_to_private_message_pm: + subject_template: "[%{site_name}] %{username} הזמין אותך להודעה '%{topic_title}'" + text_body_template: "\n%{username} הזמינ/ה אותך להודעה. \n\n> **%{topic_title}**\n>\n> %{topic_excerpt}\n\nב\n\n> %{site_title} -- %{site_description}\n\nתלחץ על הלינק בשביל לראות את ההודעה %{base_url}%{url}\n" + user_invited_to_topic: + subject_template: "[%{site_name}] %{username} הזמין אותך לנושא '%{topic_title}'" + text_body_template: "\n%{username} הזמינ/ה אותך לשיחה. \n\n> **%{topic_title}**\n>\n> %{topic_excerpt}\n\nב\n\n> %{site_title} -- %{site_description}\n\nתלחץ על הלינק בשביל לראות את ההודעה %{base_url}%{url}\n" user_replied: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | @@ -1558,7 +1598,9 @@ he: %{base_url}/users/authorize-email/%{email_token} signup_after_approval: subject_template: "You've been approved on %{site_name}!" + text_body_template: "ברוכים הבאים ל%{site_name}!\n\nחבר צוות אישר את החשבון שלך ב %{site_name}. \n\nלחץ על הקישור הבא לאשר והפעיל את החשבון החדש שלך:\n%{base_url}/users/activate-account/%{email_token}\n\nאם הלינק לא לחיץ, נסה להעתיק ולהדביק אותו לסרגל הכתובת בראש הדפדפן. \n\n%{new_user_tips}\n\nאנו מאמינים ב [civilized community behavior](%{base_url}/guidelines) בכל זמן.\n\nתהנה מהביקור!\n\n(אם אתה צריך ליצור קשר עם [staff members](%{base_url}/about) כחבר חדש, רק השב להודעה זאת. )\n\n" signup: + subject_template: "[%{site_name}] אשר את חשבונך החדש" text_body_template: | Welcome to %{site_name}! @@ -1586,7 +1628,11 @@ he: unauthorized: "Sorry, the file you are trying to upload is not authorized (authorized extensions: %{authorized_extensions})." pasted_image_filename: "Pasted image" store_failure: "Failed to store upload #%{upload_id} for user #%{user_id}." + file_missing: "סליחה, עליך לספק קובץ להעלות. " + attachments: + too_large: "מצטערים, הקובץ שאתם מנסים להעלות גדול מידי (הגודל המקסימלי המותר הוא %{max_size_kb}KB)." images: + too_large: "סליחה, אך התמונה שאתה מנסה להעלות גדולה מידי. (הגודל המקסימלי הוא %{max_size_kb}KB), אנא שנה את הגודל ונסה שנית." size_not_found: "Sorry, but we couldn't determine the size of the image. Maybe your image is corrupted?" flag_reason: sockpuppet: "A new user created a topic, and another new user at the same IP address replied. See the flag_sockpuppets site setting." @@ -1594,6 +1640,7 @@ he: email_log: no_user: "Can't find user with id %{user_id}" anonymous_user: "המשתמש הוא אנונימי" + suspended_not_pm: "המשתמש מושהה, לא הודעה" seen_recently: "User was seen recently" 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" @@ -1707,6 +1754,35 @@ he: א0 נחליט לשנות את מדיניות הפרטיות שלנו, נפרסם שינויים אלו בעמוד זה. מסמך זה מפורסם תחת רשיון CC-BY-SA. הוא עודכן לאחרונה ב-31 למאי, 2013. + static: + search_help: | +

      Tips

      +

      +

        +
      • Title matches are prioritized – when in doubt, search for titles
      • +
      • Unique, uncommon words will produce the best results
      • +
      • Try searching within a particular category, topic, or user
      • +
      +

      +

      Options

      +

      + + + + + + + +
      order:viewsorder:latest
      status:openstatus:closedstatus:archivedstatus:norepliesstatus:single_user
      category:foouser:foo
      in:likesin:postedin:watchingin:trackingin:private
      in:bookmarksin:first
      posts_count:nummin_age:daysmax_age:days
      +

      +

      + rainbows category:parks status:open order:latest will search for topics containing the word "rainbows" in the category "parks" that are not closed or archived, ordered by date of last post. +

      + badges: + long_descriptions: + autobiographer: "אות יוענק לך כשתמלא את פרופיל המשתמש שלך ותבחר תמונת פרופיל. לשתף את הקהילה לגבי מי אתה ובמה אתה מעוניין עוזר ליצור קהילה יותר טובה ומחוברת. \n" + first_like: | + אות מוענק בפעם הראשונה שאתה עושה "לייק" לפרסום בעזרת כפתור ה :heart: . לעשות לייק לפרסומים היא דרך מעולה לידע את חברך בקהילה שמה שהם פרסמו היה מעניין, שימושי או מגניב. חלוק את האהבה! admin_login: success: "דוא\"ל נשלח" error: "שגיאה!" diff --git a/config/locales/server.it.yml b/config/locales/server.it.yml index 70a3c1f163..32c0364930 100644 --- a/config/locales/server.it.yml +++ b/config/locales/server.it.yml @@ -357,7 +357,6 @@ it: please_continue: "Procedi su %{site_name}." error: "Si è verificato un errore durante la modifica del tuo indirizzo email. Forse l'indirizzo è già in uso?" activation: - action: "Attiva il tuo account" already_done: "Spiacenti, questo collegamento di attivazione non è più valido. Forse il tuo account è già attivo?" please_continue: "Il tuo nuovo account è confermato; verrai ora rediretto alla pagina iniziale." continue_button: "Procedi su %{site_name}." @@ -1005,7 +1004,6 @@ it: characters: "deve includere solo numeri, lettere e trattini bassi" unique: "deve essere univoco" blank: "deve essere presente" - must_begin_with_alphanumeric: "deve iniziare con una lettera o un numero" email: not_allowed: "non è permesso da quel fornitore di email. Per favore usa un altro indirizzo email." blocked: "non è permesso." diff --git a/config/locales/server.ja.yml b/config/locales/server.ja.yml index c792ed84c0..af85f5edee 100644 --- a/config/locales/server.ja.yml +++ b/config/locales/server.ja.yml @@ -327,7 +327,6 @@ ja: please_continue: "%{site_name}へ" error: "メールアドレスの更新に失敗しました。アドレスが既に使用されているかもしれません。" activation: - action: "アカウントを有効にする" already_done: "申し訳ありませんが、このアカウント認証リンクは無効です。既にアカウントがアクティブになっていませんか?" please_continue: "あなたのアカウントは確認されました。ホームにリダイレクトされます" continue_button: "%{site_name} へ" @@ -677,7 +676,6 @@ ja: notify_mods_when_user_blocked: "ユーザが自動的にブロックされた際に、すべてのモデレータにメッセージを送信する。" flag_sockpuppets: "トピックを作成したユーザーと同じIPアドレスで、新規ユーザーがトピックに回答した場合、両者を潜在的なスパムとしてフラグを立てるか" traditional_markdown_linebreaks: "Markdown の従来形式のラインブレーク (行の終わりにダブルスペース) を使う" - allow_html_tables: "Markdown でHTMLタグでのテーブルの入力を許可する、TABLE、THEAD、TD、TR、THタグがホワイトリストに登録されています(テーブルを含むすべての古い投稿の完全な再ベークが必要)" post_undo_action_window_mins: "ポストに対するアクション (「いいね!」、フラグ等) 取り消しを許可する時間 (秒)" must_approve_users: "スタッフはサイトへのアクセスが許可される前のすべての新規ユーザを承認する必要があります。警告:公開中のサイトでこれを有効にすると、既存の非スタッフユーザのアクセスを取り消すことになります!" ga_tracking_code: "Google analytics のトラッキングコード。例: UA-12345678-9; 参考 http://google.com/analytics" @@ -1055,7 +1053,6 @@ ja: characters: "は英数字のみである必要があります" unique: "はユニークである必要があります" blank: "は空であってはなりません" - must_begin_with_alphanumeric: "は英数字で始まる必要があります" email: not_allowed: "はこのメールプロバイダーを許可していません。他のメールアドレスを使用してください。" blocked: "は許可されていません。" diff --git a/config/locales/server.ko.yml b/config/locales/server.ko.yml index df459e28a5..c467025fbb 100644 --- a/config/locales/server.ko.yml +++ b/config/locales/server.ko.yml @@ -341,7 +341,6 @@ ko: please_continue: "%{site_name}으로 가기" error: "이메일 주소를 변경하는데 문제가 있습니다. 주소가 이미 사용되고 있나요?" activation: - action: "계정 활성화" already_done: "죄송합니다. 이 계정 확인 링크는 더 이상 유효하지 않습니다." please_continue: "계정이 활성화 되었습니다; 홈페이지로 이동합니다." continue_button: "%{site_name}으로 가기" @@ -1034,7 +1033,6 @@ ko: characters: "문자나 숫자만 사용해야 합니다." unique: "이미 사용중입니다." blank: "공백이 없어야 합니다." - must_begin_with_alphanumeric: "첫글자는 문자나 숫자로 시작해야 합니다." email: not_allowed: "이 이메일 제공업체는 허용되지 않습니다. 다른 이메일 제공 업체를 사용하세요." blocked: "는 허용되지 않습니다" diff --git a/config/locales/server.nb_NO.yml b/config/locales/server.nb_NO.yml index 8e231025e7..86e266e1ef 100644 --- a/config/locales/server.nb_NO.yml +++ b/config/locales/server.nb_NO.yml @@ -379,7 +379,6 @@ nb_NO: please_continue: "Fortsett til %{site_name}" error: "Det oppsto en feil ved endring av din epostadresse. Kanskje addressen allerede er i bruk?" activation: - action: "Aktiver din konto" already_done: "Beklager, denne bekreftelseslenken er ikke lenger gyldig. Kanskje er kontoen din allerede aktivert?" please_continue: "Din nye konto er registrert; du vil bli videresendt til hjemmesiden. " continue_button: "Fortsett til %{site_name}" @@ -743,7 +742,6 @@ nb_NO: long: "kan ikke være mer enn %{max} tegn" unique: "må være unik" blank: "Må være til stede" - must_begin_with_alphanumeric: "må begynne med en bokstav eller et tall" email: blocked: "er ikke tillatt." flags_reminder: diff --git a/config/locales/server.nl.yml b/config/locales/server.nl.yml index 1ed065b4c6..716af22365 100644 --- a/config/locales/server.nl.yml +++ b/config/locales/server.nl.yml @@ -400,7 +400,7 @@ nl: please_continue: "Ga verder naar %{site_name}" error: "Er ging iets mis bij het wijzigen van je e-mailadres. Wellicht is deze al in gebruik?" activation: - action: "Activeer je account" + action: "Klik hier om je account te activeren" already_done: "Sorry, de link om je account te activeren is niet langer geldig. Wellicht is je account al geactiveerd?" please_continue: "Je account is bevestigd en je wordt nu doorgestuurd naar de homepage." continue_button: "Ga verder naar %{site_name}" @@ -751,7 +751,6 @@ nl: notify_mods_when_user_blocked: "Als een gebruiker automatisch geblokkeerd is, stuur dan een bericht naar alle moderatoren." flag_sockpuppets: "Als een nieuwe gebruiker antwoord op een topic vanaf hetzelfde ip-adres als de nieuwe gebruiker die het topic opende, markeer dan beide berichten als potentiële spam." traditional_markdown_linebreaks: "Gebruik traditionele regeleinden in Markdown, welke 2 spaties aan het einde van een regel nodig heeft voor een regeleinde." - allow_html_tables: "Witte lijst toestaan voor tabellen die zijn ingevoerd met Markdown HTML tags, TABLE, THEAD, TD, TR, TH (vereist volledig rebaken van alle oude berichten die tabellen bevatten)" post_undo_action_window_mins: "Het aantal minuten waarin gebruikers hun recente acties op een bericht nog terug kunnen draaien (liken, markeren, etc)." must_approve_users: "Stafleden moeten alle nieuwe gebruikersaccounts goedkeuren voordat ze de site mogen bezoeken. OPGELET: als dit wordt aangezet voor een actieve site wordt alle toegang voor bestaande niet stafleden ingetrokken." ga_tracking_code: "Google analytics (ga.js) trackingcode, bijv. UA-12345678-9; zie: http://google.com/analytics" @@ -995,7 +994,6 @@ nl: characters: "mag alleen nummers, letters en underscores bevatten" unique: "moet uniek zijn" blank: "mag niet leeg zijn" - must_begin_with_alphanumeric: "moet met een letter of nummer beginnen" email: not_allowed: "is niet toegestaan vanaf die e-mailprovider. Gebruik een ander e-mailadres." blocked: "is niet toegestaan." @@ -1195,6 +1193,10 @@ nl: reply_by_email: "Beantwoord deze mail om te reageren op dit forumbericht, of ga naar %{base_url}%{url} in je browser." visit_link_to_respond: "Ga naar %{base_url}%{url} in je browser om te reageren." posted_by: "Geplaatst door %{username} op %{post_date}" + user_invited_to_private_message_pm: + subject_template: "[%{site_name}] %{username} nodigt je uit voor een bericht '%{topic_title}'" + user_invited_to_topic: + subject_template: "[%{site_name}] %{username} nodigt je uit voor een topic '%{topic_title}'" user_replied: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | @@ -1242,10 +1244,11 @@ nl: %{respond_instructions} digest: why: "Een korte samenvatting van %{site_link} sinds we je voor het laatst zagen op %{last_seen_at}." + subject_template: "[%{site_name}] Digest" new_activity: "Nieuwe reacties op je topics en berichten:" top_topics: "Populaire berichten" other_new_topics: "Populaire topics" - unsubscribe: "Deze samenvatting wordt door %{site_link} verstuurd als we je een tijdje niet gezien gebben op onze site. Mocht je dit uit willen zetten of je e-mailvoorkeur willen veranderen, %{unsubscribe_link}." + unsubscribe: "Deze samenvatting wordt door %{site_link} verstuurd als we je een tijdje niet gezien hebben op onze site. Mocht je dit uit willen zetten of je e-mailvoorkeur willen veranderen, %{unsubscribe_link}." click_here: "klik hier" from: "%{site_name} Digest" read_more: "Lees verder" @@ -1369,7 +1372,21 @@ nl: title: "Algemene Voorwaarden" privacy_topic: title: "Privacy Voorwaarden" + badges: + long_descriptions: + basic: | + Deze badge is aan je toegekend met het bereiken van trustlevel 1. Bedankt dat je een tijdje hebt rondgekeken, wat topics hebt gelezen en wat meer te weten bent gekomen over bedoeling van dit forum. De restricties voor nieuwe gebruikers gelden nu niet meer voor jou, en je kunt nu gebruik maken van alle basis communicatie mogelijkheden, zoals persoonlijke berichten, markeren, wijzigen van wiki's en de mogelijkheid om afbeeldingen en meerdere links in één bericht te plaatsen. + member: | + Deze badge is aan je toegekend met het bereiken van trustlevel 2. Bedankt voor je deelname gedurende enkele weken om op het forum actief te zijn. Je kunt nu persoonlijke uitnodigingen versturen vanuit je gebruikerspagina of een bepaalde topic, groepsberichten versturen en je hebt per dag meer likes tot je beschikking. + regular: | + Deze badge is aan je toegekend met het bereiken van trustlevel 3. Bedankt dat je al meerdere maanden deel uit maakt van ons forum, behoort tot de meest actieve lezers en regelmatig bijdraagt aan wat dit forum zo goed maakt. Je kunt nu de naam en categorie van topics aanpassen en gebruik maken van een speciale "lounge", krachtiger spam markeringen en nog veel meer likes per dag. + leader: | + Deze badge is aan je toegekend met het bereiken van trustlevel 4. Je bent door de staf uitgekozen voor een voortrekkersrol, om op dit forum een positief voorbeeld te geven in woord en gedrag. Je kunt nu alle berichten aanpassen, topics modereren door bijvoorbeeld pinnen, sluiten, verbergen, archiveren, splitsen en samenvoegen en je hebt een vrijwel onbeperkt aantal likes per dag. admin_login: success: "E-mail verstuurd" error: "Fout!" + email_input: "Beheerder E-mail" submit_button: "E-mail versturen" + performance_report: + initial_post_raw: Deze topic bevat dagelijkse performance rapporten van je site + initial_topic_title: Website performance rapporten diff --git a/config/locales/server.pl_PL.yml b/config/locales/server.pl_PL.yml index abf40f502b..7dab533c04 100644 --- a/config/locales/server.pl_PL.yml +++ b/config/locales/server.pl_PL.yml @@ -399,7 +399,6 @@ pl_PL: please_continue: "Przejdź do %{site_name}" error: "Podczas próby zmiany Twojego adresu email wystąpił błąd. Być może ten adres jest już używany?" activation: - action: "Aktywuj swoje konto" already_done: "Przepraszamy, ten link aktywujący konto jest już nieważny. Być może Twoje konto jest już aktywne ?" please_continue: "Twoje nowe konto zostało aktywowane, zostaniesz przekierowany na stronę główną." continue_button: "Przejdź do %{site_name}" @@ -887,7 +886,6 @@ pl_PL: characters: "może zawierać tylko litery, cyfry i podkreślenia" unique: "musi być unikalna" blank: "musi zostać podana" - must_begin_with_alphanumeric: "musi zaczynać się od litery lub cyfry" email: not_allowed: "nie jest dopuszczany od tego dostawcy poczty. Użyj innego adresu email." blocked: "is not allowed." diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml index 8b4c342ac1..d3404bc34c 100644 --- a/config/locales/server.pt.yml +++ b/config/locales/server.pt.yml @@ -410,7 +410,7 @@ pt: please_continue: "Continuar para %{site_name}" error: "Ocorreu um erro ao alterar o seu endereço de email. Talvez o endereço já esteja a ser utilizado?" activation: - action: "Ativar a sua conta" + action: "Clique aqui para ativar a sua conta" already_done: "Pedimos desculpa, esta hiperligação de confirmação já não está válida. Talvez a sua conta já esteja ativa?" please_continue: "A sua nova conta foi confirmada; será redirecionado para a página principal." continue_button: "Continuar para %{site_name}" @@ -761,7 +761,7 @@ pt: notify_mods_when_user_blocked: "Se um utilizador for bloqueado de forma automática, enviar uma mensagem a todos os moderadores." flag_sockpuppets: "Se um novo utilizador responde a um tópico a partir do mesmo endereço IP do novo utilizador que iniciou o tópico, sinalizar ambas as mensagens como potencial spam." traditional_markdown_linebreaks: "Utilize tradicionais quebras de linha no Markdown, que requer dois espaços no final para uma quebra de linha." - allow_html_tables: "Permitir que sejam inseridas tabelas no Markdown utilizando tags HTML. TABLE, THEAD, TD,TR,TH fazem parte da lista branca (requer que todas as mensagens antigas que contém tabelas sejam refeitas)" + allow_html_tables: "Permitir inserção de tabelas em Markdown utilizando tags HTML, TABLE,THEAD, TD, TR,TH fazem parte da lista branca (requer que todas as mensagens antigas que contém tabelas sejam refeitas)" post_undo_action_window_mins: "Número de minutos durante o qual os utilizadores têm permissão para desfazer ações numa mensagem (gostos, sinalizações, etc)." must_approve_users: "O pessoal deve aprovar todas as novas contas de utilizador antes destas terem permissão para aceder ao sítio. AVISO: ativar isto para um sítio ativo irá revogar o acesso aos utilizadores existentes que não fazem parte do pessoal!" ga_tracking_code: "Código de acompanhamento do Google Analytics (ga.js), ex: UA-12345678-9; ver http://google.com/analytics" @@ -1020,6 +1020,7 @@ pt: enable_cdn_js_debugging: "Permitir que /logs exiba erros próprios ao adicionar permissões de origem-cruzada em todos os js incluídos." show_create_topics_notice: "Se o sítio tem menos de 5 tópicos públicos, mostrar um aviso pedindo aos administradores a criação de mais tópicos." delete_drafts_older_than_n_days: Eliminar rascunhos mais antigos que (n) days. + show_logout_in_header: "Mostrar Terminar Sessão no menu suspenso do utilizador no cabeçalho" vacuum_db_days: "Executar VACUUM FULL ANALYZE para reclamar espaço na Base de Dados após a migração (configurar a 0 para desativar)" prevent_anons_from_downloading_files: "Previna que utilizadores anónimos descarreguem anexos. AVISO: isto irá fazer com que quaisquer atributos (que não sejam imagens) publicados como anexos não funcionem." slug_generation_method: "Escolha um método de geração slug. 'encoded' irá gerar sequências de caracteres com código percentual. 'none' irá desativar slug por completo." @@ -1165,7 +1166,10 @@ pt: characters: "pode incluir apenas números, letras e sublinhados" unique: "tem que ser único" blank: "tem que estar preenchido" - must_begin_with_alphanumeric: "tem que começar com uma letra ou com um número" + must_begin_with_alphanumeric: "tem que começar com uma letra ou número ou um sublinhado" + must_end_with_alphanumeric: "tem que terminar com uma letra ou número" + must_not_contain_two_special_chars_in_seq: "não deve conter uma sequência de 2 ou mais caracteres especiais (.-_)" + must_not_contain_confusing_suffix: "não deve conter um sufixo confuso tal como .json ou .png etc." email: not_allowed: "este provedor de emails não é permitido. Por favor utilize outro endereço de email." blocked: "não é permitido." diff --git a/config/locales/server.pt_BR.yml b/config/locales/server.pt_BR.yml index 71e821d53d..fd902d6b8a 100644 --- a/config/locales/server.pt_BR.yml +++ b/config/locales/server.pt_BR.yml @@ -382,7 +382,6 @@ pt_BR: please_continue: "Continuar no %{site_name}" error: "Houve um erro ao alterar o seu endereço de email. Talvez o endereço já esteja sendo utilizado?" activation: - action: "Ativar sua conta" already_done: "Desculpe, este link de confirmação não está mais válido. Talvez a sua conta já esteja ativa?" please_continue: "Sua conta agora está confirmada; você vai ser redirecionado para a página inicial." continue_button: "Continuar no %{site_name}" @@ -1093,7 +1092,6 @@ pt_BR: characters: "deve incluir apenas números, letras e sublinhados" unique: "tem que ser único" blank: "tem que ser preenchido" - must_begin_with_alphanumeric: "tem de começar com uma letra ou um número" email: not_allowed: "este provedor de emails não é permitido. Por favor utilize outro endereço de email." blocked: "não é permitido." diff --git a/config/locales/server.ro.yml b/config/locales/server.ro.yml index 557b272c85..b5699e8439 100644 --- a/config/locales/server.ro.yml +++ b/config/locales/server.ro.yml @@ -369,7 +369,6 @@ ro: please_continue: "Continuă cu %{site_name}" error: "S-a semnalat o eroare la schimbarea adresei de Email. Poate adresa deja e folosită?" activation: - action: "Activați contul dvs" already_done: "Ne pare rău, această adresă pentru confirmarea contului nu mai este valabilă. Poate contul dvs este deja activ?" please_continue: "Noul dvs cont este confirmat, iar acum sunteți autentificat." continue_button: "Continuă cu %{site_name}" @@ -859,7 +858,6 @@ ro: characters: "trebuie să includă doar numere, litere și underscor-uri" unique: "trebuie să fie unice" blank: "trebuie să fie completat" - must_begin_with_alphanumeric: "trebuie să înceapă cu o literă sau un număr" email: not_allowed: "nu este permis din partea acelui furnizor de servicii email. Vă rugăm folosiți altă adresă email." blocked: "nu este permis." diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml index f180bf5f28..a85605706c 100644 --- a/config/locales/server.ru.yml +++ b/config/locales/server.ru.yml @@ -439,7 +439,6 @@ ru: please_continue: "Перейти на %{site_name}" error: "При смене электронного адреса произошла ошибка. Возможно, этот адрес уже используется?" activation: - action: "Активируйте вашу учетную запись" already_done: "Извините, ссылка на активацию учетной записи устарела. Возможно, ваша учетная запись уже активирована?" please_continue: "Ваша новая учетная запись успешно активирована, вы будете перенаправлены на главную страницу." continue_button: "Перейти на %{site_name}" @@ -1153,7 +1152,6 @@ ru: characters: "должно состоять только из цифр и латинских букв" unique: "должно быть уникально" blank: "необходимо заполнить" - must_begin_with_alphanumeric: "должно начинаться с буквы или цифры" email: not_allowed: "недопустимый почтовый домен. Пожалуйста, используйте другой адрес." blocked: "не разрешено." diff --git a/config/locales/server.sq.yml b/config/locales/server.sq.yml index f4b7e304c7..ecdeeddea6 100644 --- a/config/locales/server.sq.yml +++ b/config/locales/server.sq.yml @@ -401,7 +401,6 @@ sq: please_continue: "Vazhdo tek %{site_name}" error: "There was an error changing your email address. Perhaps the address is already in use?" activation: - action: "Aktivizoni llogarinë tuaj" already_done: "Sorry, this account confirmation link is no longer valid. Perhaps your account is already active?" please_continue: "Your new account is confirmed; you will be redirected to the home page." continue_button: "Vazhdo tek %{site_name}" @@ -751,7 +750,6 @@ sq: notify_mods_when_user_blocked: "If a user is automatically blocked, send a message to all moderators." flag_sockpuppets: "If a new user replies to a topic from the same IP address as the new user who started the topic, flag both of their posts as potential spam." traditional_markdown_linebreaks: "Use traditional linebreaks in Markdown, which require two trailing spaces for a linebreak." - allow_html_tables: "Allow tables to be entered in Markdown using HTML tags, TABLE, THEAD, TD, TR, TH are whiteliseted (requires full rebake on all old posts containing tables)" 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!" ga_tracking_code: "Google analytics (ga.js) tracking code code, eg: UA-12345678-9; see http://google.com/analytics" @@ -1137,7 +1135,6 @@ sq: characters: "must only include numbers, letters and underscores" unique: "must be unique" blank: "must be present" - must_begin_with_alphanumeric: "must begin with a letter or number" email: not_allowed: "is not allowed from that email provider. Please use another email address." blocked: "is not allowed." diff --git a/config/locales/server.sv.yml b/config/locales/server.sv.yml index 66a524fe21..28d96fba60 100644 --- a/config/locales/server.sv.yml +++ b/config/locales/server.sv.yml @@ -364,7 +364,6 @@ sv: please_continue: "Fortsätt till %{site_name}" error: "Det uppstod ett fel med ändringen av din e-postadress. Adressen kanske redan används?" activation: - action: "Aktivera ditt konto" already_done: "Tyvärr, denna kontoaktiveringslänk är inte längre giltig. Kanske är ditt konto redan aktiverat?" please_continue: "Ditt nya konto är verifierat; du kommer att skickas till startsidan." continue_button: "Fortsätt till %{site_name}" @@ -813,7 +812,6 @@ sv: characters: "får endast innehålla nummer, bokstäver och understreck" unique: "måste vara unikt" blank: "måste finnas" - must_begin_with_alphanumeric: "måste börja med en bokstav eller siffra" email: not_allowed: "är inte en tillåten e-postleverantör. Vänligen använd en annan e-postadress." blocked: "är inte tillåtet." diff --git a/config/locales/server.te.yml b/config/locales/server.te.yml index 34e33b9609..2a40ce4212 100644 --- a/config/locales/server.te.yml +++ b/config/locales/server.te.yml @@ -321,7 +321,6 @@ te: please_continue: "%{site_name} కు కొనసాగండి" error: "మీ ఈమెయిల్ చిరునామా మార్చడంలో దోషం. బహుశా ఆ చిరునామా ఈసరికే వాడుకలో ఉందా?" activation: - action: "మీ ఖాతాను చేతనం చేయండి" already_done: "క్షమించాలి. ఖాతా ధృవపరుచు లంకె కాలాతీతమైంది. బహుశా మీ ఖాతా ఇప్పటికే చేతనమై ఉందేమో?" please_continue: "మీ ఖాతా ధృవపర్చబడింది. మీరిప్పుడు తొలిపుటకు మళ్లించబడతారు." continue_button: "%{site_name} కు కొనసాగండి." @@ -632,7 +631,6 @@ te: characters: "కేవలం సంఖ్యలు, అక్షరాలు మరియు అండర్ స్కోరు మాత్రమే ఉండాలి. " unique: "ఏకైకంగా ఉండాలి" blank: "తప్పనిసరిగా ఉండాలి" - must_begin_with_alphanumeric: "తప్పనిసరిగా సంఖ్యతోగాని, అక్షరంతోగాని మొదలవ్వాలి" email: not_allowed: "ఆ ఈమెయిల్ ప్రొవైడరును అనుమంతిచుటలేదు. దయచేసి మరో ఈమెయిల్ చిరునామా రాయండి" blocked: "అనుమతించుటలేదు" diff --git a/config/locales/server.tr_TR.yml b/config/locales/server.tr_TR.yml index fbdf744d95..a1b49d2fea 100644 --- a/config/locales/server.tr_TR.yml +++ b/config/locales/server.tr_TR.yml @@ -7,7 +7,7 @@ tr_TR: dates: - short_date_no_year: "D MMM" + short_date_no_year: "G AAA" short_date: "D MMM, YYYY" long_date: "MMMM D, YYYY h:mma" title: "Discourse" @@ -61,6 +61,12 @@ tr_TR: other: '%{count} hata bu %{model} kaydının alınmasını engelledi' embed: load_from_remote: "Gönderi yüklenirken bir hata oluştu." + site_settings: + min_username_length_exists: "En kısa kullanıcı adının üstünde en küçük kullanıcı adı uzunluğu ayarlayamazsınız." + min_username_length_range: "En büyük değerin üstünde en küçük değer ayarlayamazsınız." + max_username_length_exists: "En uzun kullanıcı adının altında en büyük kullanıcı adı uzunluğu ayarlayamazsınız." + max_username_length_range: "En küçük değerin altında en büyük değer ayarlayamazsınız." + default_categories_already_selected: "Bir başka listede kullanılan bir kategoriyi seçemezsiniz." bulk_invite: file_should_be_csv: "Yüklenen dosya csv veya txt formatında olmalı. " backup: @@ -148,17 +154,25 @@ tr_TR: education: until_posts: other: "%{count} gönderi" - new-topic: "%{site_name} &mdash sitesine hoşgeldiniz; **yeni bir sohbet başlattığınız için teşekkür ederiz! ** \n\n- Başlık konunuz yüksek sesle okuduğunuzda ilgi çekici duyuluyor mu? İyi bir özet mi?\n\n- Bununla kim ilgilenirdi? Neden önemli? Ne gibi cevaplar almayı diliyorsunuz?\n\n- Diğerlerinin de kolayca *bulabilmesi * için konunuzun içerisinde sıkça kullanılan kelimeler bulundurun. Konunuzun benzer konularla gruplanması için bir kategori seçin. \n\nDaha fazlası için, [topluluk yönergelerimize bakın](/guidelines). Bu panel sadece ilk gönderinizde %{education_posts_text} gözükür.\n" + new-topic: "%{site_name} forumuna hoşgeldiniz — **yeni bir sohbet başlattığınız için teşekkür ederiz! **\n\n- Yüksek sesle okuduğunuzda başlık ilgi çekici geliyor mu? İyi bir özet mi?\n\n- Bununla kim ilgilenirdi? Neden önemli? Ne gibi cevaplar almayı diliyorsunuz?\n\n- Diğerlerinin de kolayca *bulabilmesi * için konunuzun içerisine sıkça kullanılan kelimeler dahil edin. Konunuzun benzer konularla gruplanması için bir kategori seçin. \n\nDaha fazlası için, [topluluk yönergelerimize bakın](/guidelines). Bu panel sadece ilk %{education_posts_text} için görünür.\n" new-reply: | - %{site_name} &mdash sitesine hoşgeldiniz; katılımınız için teşekkür ederiz! + %{site_name} forumuna hoşgeldiniz — **katılımınız için teşekkür ederiz!** - - Cevabınız sohbetin gelişmesine yardımcı oluyor mu? + - Cevabınız sohbeti bir şekilde geliştiriyor mu? - Topluluğun diğer üyelerine karşı nazik olun. - - Yapıcı eleştirici her zaman kabul edilir, ama fikirleri eleştirin, insanları değil. + - Yapıcı eleştirici her zaman kabul edilir, ama *fikirleri* eleştirin, insanları değil. - Daha fazlası için, [topluluk yönergelerine bakın](/guidelines). Bu yazı sadece ilk gönderinizde %{education_posts_text} gözükür. + Daha fazlası için, [topluluk yönergelerine bakın](/guidelines). Bu yazı sadece ilk %{education_posts_text} için görünür. + avatar: | + ### Hesabının bir resmi olsun istemez misin? + + Birkaç konu ve cevap gönderdin, fakat profil resmin senin kadar eşsiz değil - sadece bir harf. + + Hiç **[kullanıcı profiline girmeyi](%{profile_path})** ve seni ifade eden bir resim yüklemeyi düşündün mü? + + Herkes eşsiz bir profil resmine sahip olduğunda tartışmaları takip etmek ve tartışmalarda ilgi çekici insanlar bulmak daha kolaydır! sequential_replies: "### Birden fazla gönderiyi aynı anda cevaplayabilirsin \n\nAynı konuya ardı ardına cevaplar yazmak yerine, lütfen önceki gönderilerden alıntı veya @isim referansları içeren tek bir cevap yaz. \n\nHerhangi bir yazıyı seçince çıkan alıntılayarak cevapla butonuna tıklayarak alıntı ekleyebilir, bir önceki cevabınızı düzenleyebilirsiniz. \n\nAz sayıda derinlemesine cevaplardan oluşan konuların okunması, çok fazla kısa ve tekil cevaplardan oluşan konulardan herkes için daha kolay oluyor.\n" dominating_topic: "###Sohbete başkalarının katılmasına izin verin\n\nBu konunun sizin için önemli olduğunu görüyoruz – buradaki cevapların %{percent}% oranından daha fazlasını siz göndermişsiniz. \n\nDiğerlerinin de kendi fikirlerini paylaşmaları için onlara yeteri kadar zaman tanıdığınıza emin misiniz?\n" too_many_replies: "### Bu konu için cevap limitinizi doldurdunuz\n\nÜzgünüz, ancak geçici olarak, yeni kullanıcılar aynı konu içinde en fazla %{newuser_max_replies_per_topic} cevap yazabiliyorlar. \n\nYeni bir cevap yazmak yerine, önceki cevaplarınızı düzenlemeyi, ya da başka konulara göz atmayı düşünün.\n" @@ -319,6 +333,7 @@ tr_TR: almost_x_years: other: "yaklaşık %{count} yıl önce" password_reset: + no_token: "Üzgünüz, bu şifre değiştirme bağlantısı çok eski. Yeni bir bağlantı almak için lütfen 'Giriş Yap' tuşuna basın ve 'Parolamı unuttum'u seçin." choose_new: "Lütfen yeni bir parola seçin" choose: "Lütfen parola seçin" update: 'Parolayı Güncelle' @@ -332,7 +347,7 @@ tr_TR: please_continue: "%{site_name} adresine devam edin" error: "E-posta adresiniz değiştirilirken bir hata oluştu. Bu adres zaten kullanımda olabilir." activation: - action: "Hesabınızı etkinleştirin" + action: "Hesabınızı etkinleştirmek için buraya tıklayın" already_done: "Üzgünüz, hesap doğrulama linki artık geçerli değil. Hesabınız zaten etkin olabilir mi?" please_continue: "Hesabınız doğrulandı; şimdi ana sayfaya yönlendirileceksiniz." continue_button: "%{site_name} adresine devam edin" @@ -558,6 +573,7 @@ tr_TR: host_names_warning: "config/database.yml dosyasınızda, bilgisayar adı olarak varsayılan değer olan \"localhost\" ayarlı. Değeri, sitenizin bilgisayar adını kullanacak biçimde güncelleyiniz." gc_warning: 'Sunucunuz, Ruby''nin varsayılan çöp toplama ayarlarını kullanıyor ki bu size en iyi performansı vermeyecektir. Performansı ayarı için şu konuyu okuyun: Discourse için Ruby on Rails Ayarları.' sidekiq_warning: 'Sidekiq çalışmıyor. E-posta yollamak gibi gibi birçok asenkron görev sidekiq''in işidir. En az bir tane sidekiq süreci çalıştırdığınızdan emin olun. Sidekiq ile ilgili bilgi burada.' + queue_size_warning: 'Kuyruğa eklenmiş işlerin sayısı fazla: %{queue_size}. Bu Sidekiq işlem(ler)indeki bir sorunu işaret ediyor olabilir, ya da daha fazla Sidekiq işçisi eklemeniz gerekiyor olabilir.' memory_warning: 'Sunucunuz toplam 1GB''tan az bellek ile çalışıyor. En az 1GB bellek tavsiye edilmektedir.' google_oauth2_config_warning: 'Sunucu Google OAuth2 (enable_google_oauth2_logins) ile üyelik oluşturulması ve giriş yapılmasına elveriyor, fakat the kullanıcı IDsi and gizli kullanıcı değerleri henüz ayarlanmamış. Site Ayarları sayfasına gidin ve ayarları güncelleyin. Daha fazla bilgi için bu yönetmeliğe bakın.' facebook_config_warning: 'Sunucu Facebook (enable_facebook_logins) ile üyelik oluşturulması ve giriş yapılmasına izin veriyor, fakat app ID ve gizli app değerleri henüz ayarlanmamış. Site Ayarları sayfasına gidin ve ayarları güncelleyin. Daha fazla bilgi için bu yönetmeliğe bakın.' @@ -648,6 +664,11 @@ tr_TR: post_excerpt_maxlength: "Gönderi alıntısının / özetinin en fazla uzunluğu." post_onebox_maxlength: "Kutulanmış bir Discourse gönderisinin en fazla karakter uzunluğu" onebox_domains_whitelist: "Kutulamaya izin verilen alan adları listesi; bu alan adları OpenGraph ya da oEmbed desteklemeliler. http://iframely.com/debug adresinden test edebilirsiniz." + logo_url: "Sitenin üst solundaki logo resmi, geniş dikdörtgen şeklinde olmalıdır. Boş bırakılırsa site başlığı gösterilecektir." + digest_logo_url: "Sitenin e-posta özetinin üstünde kullanılan diğer logo resmi. Geniş dikdörtgen şeklinde olmalıdır. Boş bırakılırsa `logo_url` kullanılacaktır." + logo_small_url: "Sitenin üst solundaki küçük logo resmi, kare şeklinde olmalıdır, aşağıya doğru kaydırılırken görünür. Boş bırakılırsa bir ev oyması gösterilecektir." + favicon_url: "Site simgesi, bilgi için http://en.wikipedia.org/wiki/Favicon adresine bakınız, bir CDN ile doğru şekilde çalışmak için png olmalıdır." + mobile_logo_url: "Mobil sitenin üst solunda kullanılan sabit konumlu logo resmi. Kare şeklinde olmalıdır. Boş bırakılırsa, `logo_url` kullanılacaktır. Örneğin: http://example.com/uploads/default/logo.png" apple_touch_icon_url: "Apple dokunmatik cihazları için kullanılan ikon. Önerilen boyut; 144 x 144 pixel." notification_email: "Tüm önemli sistem e-postaları için kullanılacak olan gönderen e-posta adresi. E-postaların başarıyla ulaşması için buraya girilen alan adının SPF, DKIM ve reverse PTR kayıtlarının doğru yapılması lazım." email_custom_headers: "Sınırlandırılmış özel e-posta başlıkları listesi" @@ -677,7 +698,7 @@ tr_TR: notify_mods_when_user_blocked: "Eğer bir kullanıcı otomatik olarak engellendiyse, tüm moderatörlere mesaj yolla." flag_sockpuppets: "Eğer, yeni kullanıcı konuya, konuyu başlatan yeni kullanıcı ile aynı IP adresinden cevap yazarsa, her iki gönderiyi de potansiyel spam olarak işaretle. " traditional_markdown_linebreaks: "Markdown'da, satır sonundan önce yazının sağında iki tane boşluk gerektiren, geleneksel satır sonu metodunu kullan kullan." - allow_html_tables: "Tabloların HTML etiketleri kullanılarak Markdown'da girilebilmesine izin ver. TABLE, THEAD, TD, TR ve TH beyaz listeye alınmıştır. (Tüm tablo içeren eski gönderilerde full rebake yapılmasını gerektirir)" + allow_html_tables: "Çizelgelerin HTML etiketleri kullanılarak Markdown ile oluşturulmasına izin verin, TABLE, THEAD, TD, TR, TH kabul edilir (çizelge içeren tüm eski gönderilerin yenilenmesini gerektirir)" post_undo_action_window_mins: "Kullanıcıya tanınan, bir gönderide yapılan yeni aksiyonları (beğenme, bayraklama, vs) geri alabilme dakika süresi" must_approve_users: "Siteye erişimlerine izin verilmeden önce tüm yeni kullanıcı hesaplarının görevliler tarafından onaylanması gerekir. UYARI: yayındaki bir site için bunu etkinleştirmek görevli olmayan hesapların erişimini iptal edecek." ga_tracking_code: "Google analytics (ga.js) takip kodu, ör: UA-12345678-9; bakınız http://google.com/analytics" @@ -720,6 +741,8 @@ tr_TR: invite_passthrough_hours: "Daha önce kabul edilmiş davetiye anahtarının kullanım süresi, saat olarak" invite_only: "Halka açık kayıt sistemi devre dışı bırakıldı, tüm yeni kullanıcıların bir üye ya da görevli tarafından davet edilmesi gerekir. " login_required: "Bu sitede içerik görüntülenebilmesi için kimlik doğrulamayı zorunlu kıl, isimsiz girişe izin verme." + min_username_length: "Karakter olarak en küçük kullanıcı adı uzunluğu." + max_username_length: "Karakter olarak en büyük kullanıcı adı uzunluğu." reserved_usernames: "Üyelik için izin verilmeyen kullanıcı adları." min_password_length: "En az parola uzunluğu." block_common_passwords: "En çok kullanılan 10,000 parola arasında yer alan parolalara izin verme." @@ -749,6 +772,8 @@ tr_TR: github_client_secret: "Github doğrulaması için gereken, https://github.com/settings/applications adresinde kayıtlı client secret" allow_restore: "Geri almaya izin ver. Tüm sitedeki verileri değiştirebilir! Bir yedeklemeyi geri yüklemeyi planlamıyorsanız devre dışı bırakın." maximum_backups: "Diskte tutulacak en fazla yedek sayısı. Eski yedekler otomatik olarak silinir." + automatic_backups_enabled: "Yedek sıklığında tanımlandığı gibi yedekleri otomatik çalıştır" + backup_frequency: "Hangi sıklıkta bir site yedeği oluştururuz, gün olarak." enable_s3_backups: "Tamamlanınca yedeklemeleri S3'e yükle. ÖNEMLİ: Dosyalar ayarında doğru S3 girilmesini gerektirir" s3_backup_bucket: "Yedeklemelerin yüklenmesi için uzak biriktirme yeri. UYARI: Özel bir biriktirme yeri olduğundan emin olun" active_user_rate_limit_secs: "'last_seen_at' alanını ne kadar sıklıkta güncelliyoruz, saniye olarak" @@ -856,6 +881,10 @@ tr_TR: num_flaggers_to_close_topic: "Bir konunun moderatör müdahalesi için otomatik olarak durdurulmadan önce alması gereken en az tekil bayrak sayısı" num_flags_to_close_topic: "Bir konunun moderatör müdahalesi için otomatik olarak durdurulmadan önce alması gereken en az etkin bayrak sayısı " auto_respond_to_flag_actions: "Bir bayrağı kaldırırken otomatik cevaplamayı etkinleştir" + min_first_post_typing_time: "Milisaniye olarak bir kullanıcının ilk gönderisini yazarken geçmesi gereken en küçük süre, bu süre geçmezse gönderi otomatik olarak onaylanma kuyruğuna girer. Devre dışı bırakmak için 0'a ayarlayın (tavsiye edilmez)." + auto_block_fast_typers_on_first_post: "min_first_post_typing_time'ı karşılamayan kullanıcıları otomatik olarak engelle." + auto_block_fast_typers_max_trust_level: "Hızlı yazıcıları otomatik engellemek için en büyük güven seviyesi." + auto_block_first_post_regex: "Eğer eşleşirse kullanıcının ilk gönderisinin engellenmesi ve onaylanma kuyruğuna gitmesine neden olan harf büyüklüğü duyarsız düzenli ifade. Örnek: reklam|a[bc]a düzenli ifadesi reklam, aba ya da aca içeren ilk gönderilerin engellenmesi ve onaylanma kuyruğuna gitmesine neden olacaktır. Sadece ilk gönderiler için geçerlidir." reply_by_email_enabled: "Konulara e-posta üzerinden cevap yazmayı etkinleştir." reply_by_email_address: "Email ile cevapla özelliği için gelen e-posta adresi şablonu, örnek: %{reply_key}@reply.example.com or replies+%{reply_key}@example.com" disable_emails: "Discourse'un herhangi bir e-posta göndermesine izin verme" @@ -868,6 +897,7 @@ tr_TR: pop3_polling_host: "POP3 üzerinden e-postaların sorgulanacağı sunucu" pop3_polling_username: "E-postaların sorgulanacağı POP3 hesabının kullanıcı adı." pop3_polling_password: "E-postaların sorgulanacağı POP3 hesabının parolası." + log_mail_processing_failures: "Bütün e-posta işleme hatalarını http://yoursitename.com/logs adresine logla." email_in: "Kullanıcıların e-posta aracılığıyla yeni konu oluşturabilmesine izin ver (pop3 sorgulaması gerektirir). Adresleri her kategorinin \"Ayarlar\" sekmesinden düzenleyin." email_in_min_trust: "Bir kullanıcının e-posta aracılığı ile yeni konu oluşturabilmesi için sahip olması gereken en az güven seviyesi." email_prefix: "E-postaların konu bölümünü belirten [etiket]. Boş bırakılırsa 'title' yazacak." @@ -879,6 +909,8 @@ tr_TR: username_change_period: "Kayıt sonrası, kullanıcı adınının değiştirilebileceği gün sayısı. (Kullanıcı adının değiştirilebilmesini devre dışı bırakmak için 0 girin)" email_editable: "Kullanıcıların kayıt olduktan sonra e-posta adreslerini değiştirmesine izin ver." logout_redirect: "Çıkış yaptıktan sonra tarayıcının yönlendirileceği sayfa, ÖRN: (http://somesite.com/logout)" + allow_uploaded_avatars: "Kullanıcıların özel profil resimleri yüklemelerine izin ver." + allow_animated_avatars: "Kullanıcıların hareketli gif profil resimleri kullanmalarına izin ver. UYARI: Bu ayarı değiştirdikten sonra avatars:refresh rake görevini çalıştırın." allow_animated_thumbnails: "Animasyonlu giflerin, animasyonlu küçük resmini oluşturur." default_avatars: "Yeni kullanıcılar için, onlar değiştirene kadar, varsayılan olarak kullanılacak avatarların URL'leri." automatically_download_gravatars: "Hesap oluşturma veya e-posta değişikliği esnasında kullanıcılar için Gravatarları indir" @@ -886,6 +918,7 @@ tr_TR: digest_min_excerpt_length: "Özet e-postalarında, gönderi alıntılarında olması gereken en az karakter sayısı." suppress_digest_email_after_days: "Siteye (n) günden fazla süredir uğramayan kullanıcılar için özet e-posta gönderimini durdur" disable_digest_emails: "Tüm kullanıcılar için özet e-postalarını devre dışı bırak." + detect_custom_avatars: "Kullanıcıların özel profil resimleri yükleyip yüklemediklerini kontrol et ya da etme." max_daily_gravatar_crawls: "Discourse'un gün içinde özel avatarlar için Gravatar'ı en fazla kaç kere kontrol edeceği." public_user_custom_fields: "Kullanıcıların için, herkes tarafından görüntülenebilir özel alanların beyaz listesi." staff_user_custom_fields: "Kullanıcıların için, sadece görevlilere görüntülenebilir özel alanların beyaz listesi." @@ -924,6 +957,7 @@ tr_TR: enable_cdn_js_debugging: "/logs 'ların asli hataları tüm js içeriklerine crossorigin izinleri ekleyerek göstermesine izin ver." show_create_topics_notice: "Eğer sitede herkese açık konu sayısı 5'den az ise, adminden yeni konular oluşturmasını isteyen bir uyarı mesajı göster. " delete_drafts_older_than_n_days: (n) günden eski taslakları sil. + show_logout_in_header: "Başlık çubuğundaki kullanıcı açılır listesinde oturumu kapatı göster" vacuum_db_days: "Geçiş sonra DB alanı geri kazanmak için TAM VAKUM ANALİZİ'ni çalıştırın (devre dışı bırakmak için 0 girin)" prevent_anons_from_downloading_files: "Anonim kullanıcıların eklenti indirebilmesini önle. DİKKAT: Bu ayar, eklenti olarak gönderilen resim-dışı site içeriklerinin de çalışmasını engelleyebilir." slug_generation_method: "Slug üretim yöntemi seçin. 'kodlanmış' seçeneği yüzde kodlamalı metin oluşturur. 'hiçbiri' seçeneği slug'ı devre dışı bırakır." @@ -933,6 +967,21 @@ tr_TR: approve_post_count: "Yeni bir kullanıcıdan onaylanması gereken gönderi sayısı" approve_unless_trust_level: "Bu güven seviyesi altındaki kullanıcılardan gelen gönderilerin onaylanması gerekir" notify_about_queued_posts_after: "Bu kadar saat geçmesine rağmen hala incelenmemiş konular varsa, iletişim adresine e-posta gönder. Devre dışı bırakmak için 0 girin." + default_email_digest_frequency: "Öntanımlı olarak kullanıcılar hangi sıklıkta özet e-postalar alırlar." + default_email_private_messages: "Öntanımlı olarak birisi bir kullanıcıya mesaj 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." + default_email_always: "Öntanımlı olarak kullanıcı etkin olsa bile e-posta bildirimi gönder." + default_other_new_topic_duration_minutes: "Bir konunun yeni sayıldığı genel öntanımlı dakika sayısı, kullanıcılar geçersiz kılabilir (her zaman için -1, son ziyaret için -2)" + default_other_auto_track_topics_after_msecs: "Bir konu otomatik olarak takip edilmeden önce geçen genel öntanımlı milisaniye sayısı, kullanıcılar geçersiz kılabilir (her zaman için 0, asla için -1)" + default_other_external_links_in_new_tab: "Harici linkleri, öntanımlı olarak, yeni bir sekmede aç." + default_other_enable_quoting: "Vurgulanmış yazılar için alıntı ile cevaplamayı, öntanımlı olarak, etkinleştir." + default_other_dynamic_favicon: "Tarayıcı simgesinde, öntanımlı olarak, yeni/güncellenmiş konu sayısını göster." + default_other_disable_jump_reply: "Kullanıcılar cevapladıktan sonra, öntanımlı olarak, onların gönderilerine atlama." + default_other_edit_history_public: "Gönderi değişikliklerini, öntanımlı olarak, herkese açık yap." + default_categories_watching: "Öntanımlı olarak, izlenen kategorilerin listesi." + default_categories_tracking: "Öntanımlı olarak, takip edilen kategorilerin listesi." + default_categories_muted: "Öntanımlı olarak, sesi kısılan kategorilerin listesi." errors: invalid_email: "Geçersiz e-posta adresi." invalid_username: "Bu kullanıcı adı ile bir kullanıcı bulunmuyor." @@ -979,6 +1028,11 @@ tr_TR: redirected_to_top_reasons: new_user: "Hoşgeldiniz! Bunlar en popüler yeni gönderiler." not_seen_in_a_month: "Hoşgeldiniz! Bir süredir yoktunuz. Bunlar sizin yokluğunuzda en popüler olan konular." + move_posts: + new_topic_moderator_post: + other: "%{count} gönderi yeni bir konu için ayıklandı: %{topic_link}" + existing_topic_moderator_post: + other: "%{count} gönderi var olan bir konu içinde birleştirildi: %{topic_link}" change_owner: post_revision_text: "Sahiplik %{old_user} hesabından %{new_user} hesabına aktarıldı" deleted_user: "silinmiş kullanıcı" @@ -1041,7 +1095,10 @@ tr_TR: characters: "sadece rakam, harf ve altçizgi bulundurabilir " unique: "özgün olmalı" blank: "bulunmalı" - must_begin_with_alphanumeric: "harf ya da sayı ile başlamalı" + must_begin_with_alphanumeric: "bir harf, rakam ya da alt çizgi ile başlamalı" + must_end_with_alphanumeric: "bir harf ya da rakam ile bitmeli" + must_not_contain_two_special_chars_in_seq: "2 ya da daha fazla uzunlukta özel karakter dizisi (.-_) içermemeli" + must_not_contain_confusing_suffix: ".json ya da .png gibi kafa karıştırıcı bir son ek içermemeli" email: not_allowed: "için o e-posta sağlayıcısına izin verilmiyor. Lütfen başka bir email adresi kullanın. " blocked: "için izin yok." @@ -1067,6 +1124,13 @@ tr_TR: Bu davet güvenilir bir kullanıcı tarafından gönderilmiştir. O nedenle giriş yapmanız gerekmeyecek. invite_password_instructions: subject_template: "%{site_name} hesabınız için parola oluşturun" + text_body_template: | + %{site_name}'e olan davetiyeni kabul ettiğin için teşekkür ederiz -- hoşgeldin! + + Şimdi bir şifre seçmek için şu bağlantıya tıklayın: + %{base_url}/users/password-reset/%{email_token} + + (Eğer yukarıdaki bağlantının süresi dolmuşsa e-posta adresinizle giriş yaparken "Parolamı unuttum" bağlantısına tıklayınız.) test_mailer: subject_template: "[%{site_name}] E-posta Ulaştırma Testi" text_body_template: "Bu aşağıdaki adresten gönderilen bir test e-postasıdır.\n\n[**%{base_url}**][0]\n\nE-postaların ulaştırılması karışık bir meseledir. Öncelikle dikkat etmeniz gereken bir kaç önemli nokta:\n\n- Site ayarlarınızda 'bildiri e-postaları' için gönderen adresini doğru ayarladığınıza emin olun. **Yolladığınız e-postalarda \"gönderen\" adresi olarak belirlediğiniz alan adı, e-postalarınızın doğrulanacağı alan adıdır.**\n\n- E-posta başlıklarındaki önemli ipuçlarını yakalayabilmek için e-posta istemcinizde e-postaların kaynak kodunu nasıl görüntüleyebileceğinizi öğrenin. Gmail'da, her e-postanın sağ üstündeki açılır menüden \"show original\" opsiyonuna tıklayabilirsiniz.\n\n- **ÖNEMLİ:** ISP'nizde e-posta yollamak için kullanıdığınız alan adlarıyla IP adreslerinin eşleşmesini sağlayacak bir reverse DNS kaydı var mı? Buradan [reverse PTR kayıtlarınızı test edin][2]. Eğer ISP'niz doğru reverse DNS pointer kaydı girmezse, büyük ihtimal e-postalarınızın hiç biri yerine ulaşmayacaktır.\n\n- Alan adınızın [SPF kaydı][8] doğru mu? Buradan [SPF kaydınızı test edin][1]. SPF için doğru resmi kayıt tipinin TXT olduğunu unutmayın. \n\n- Alan adınızın [DKIM kaydı][3] doğru mu? Bu e-postaların ulaştırılabilirliğini ciddi şekilde artıracaktır. Buradan [DKIM kaydınızı test edin][7].\n\n- Kendi e-posta sunucunuzu kullanıyorsanız, e-posta sunucunuzun IPlerinin [hiç bir e-posta karalistesine][4] alınmadığına emin olun. Sunucunuzun, kesinlikle, HELO mesajında DNS olarak çözümlenen tam tanımlanmış bilgisayar adı da gönderdiğinden emin olun. Göndermemesi, e-postanızın bir çok e-posta servisi tarafından reddedilmesine sebep olacaktır. \n\n(En kolayı, küçük topluluklar için rahat rahat yetecek sayıda bedava email yollama paketleri içeren, [Mandrill][md] veya [Mailgun][mg] veya [Mailjet][mj]'te ücretsiz hesap açmak. Tabi, gene, DNS ayarlarınızda SPF ve DKIM kayıtlarını oluşturmanız gerekecek!) \n\nUmarız bu e-posta ulaştırma testini başarıyla atlatmışsınızdır. \n\nİyi şanslar, \n\n[Discourse](http://www.discourse.org)'tan arkadaşlarınız \n\n[0]: %{base_url} \n[1]: http://www.kitterman.com/spf/validate.html\ @@ -1299,6 +1363,10 @@ tr_TR: download_remote_images_disabled: subject_template: "Uzaktaki resimlerin indirilmesi devre dışı bırakıldı" text_body_template: "`download_remote_images_to_local` ayarı harddisk alanı limiti `download_remote_images_threshold` aşıldığı için devre dışı bırakıldı." + unsubscribe_link: | + Bu e-postaların üyeliklerinden çıkmak için [user preferences](%{user_preferences_url})'i ziyaret edin. + + Bu belirli konuda bildirim almayı durdurmak için [buraya tıklayın](%{unsubscribe_url}). subject_re: "Cvp:" subject_pm: "[ÖM]" user_notifications: @@ -1479,6 +1547,7 @@ tr_TR: unauthorized: "Üzgünüz, yüklemeye çalıştığınız dosya izinli değil (authorized extensions: %{authorized_extensions})." pasted_image_filename: "Yapıştırılan resim" store_failure: "#%{user_id} kullanıcısı için yükleme #%{upload_id} kaydedilemedi." + file_missing: "Affedersiniz, yükleme için bir dosya sağlamalısınız." attachments: too_large: "Üzgünüz, yüklemeye çalıştığınız dosya çok büyük (en fazla %{max_size_kb}KB olabilir)." images: @@ -1834,24 +1903,80 @@ tr_TR: If we decide to change our privacy policy, we will post those changes on this page. This document is CC-BY-SA. It was last updated May 31, 2013. + static: + search_help: | +

      İpuçları

      +

      +

        +
      • Başlık eşlemelerine öncelik verilir – şüpheli durumlarda, başlıklar için aramalara
      • +
      • Eşsiz, alışılmamış kelimeler en iyi sonucu üretecektir
      • +
      • Belirli bir kategori, konu ya da kullanıcı içinde aramayı deneyin
      • +
      +

      +

      Seçenekler

      +

      + + + + + + + +
      order:viewsorder:latest
      status:openstatus:closedstatus:archivedstatus:norepliesstatus:single_user
      category:foouser:foo
      in:likesin:postedin:watchingin:trackingin:private
      in:bookmarksin:first
      posts_count:nummin_age:daysmax_age:days
      +

      +

      + gökkuşağı category:parklar status:open order:latest "parklar" kategorisi içinde "gökkuşağı" kelimesini içeren, kapatılmamış ya da arşivlenmemiş ve son gönderinin tarihine göre sıralanmış konuları arayacaktır. +

      badges: long_descriptions: autobiographer: | Bu rozet profilini doldurduğunda ve profil resmini seçtiğinde verilir. Kim olduğun ve nelerle ilgilendiğin hakkında topluluğa daha fazla bilgi vermen daha iyi ve daha yakın bir topluluğun oluşmasına yardım eder. + first_like: | + Bu rozet :heart: tuşunu kullanarak bir gönderiyi ilk kez beğendiğinizde verilir. Gönderileri beğenmek hemcins topluluk üyelerinin ilgi çekici, faydalı, havalı ve eğlenceli nelerin gönderildiğini bilmelerini sağlamak için mükemmel bir yoldur. Sevgiyi paylaş! + first_link: | + Bu rozet bir cevapta başka bir konuya bir bağlantı verdiğinde verilir. Konuları bağlamak, her iki yönde konular arasındaki ilişkiyi göstererek, hemcins okuyucuların ilgi çekici ve ilgili konuşmaları bulmalarına yardım eder. + first_quote: | + Bu rozet bir cevapta ilk kez bir gönderiden alıntı yaptığınızda verilir. Cevabında önceki gönderilerin ilgili bölümlerinden alıntı yapmak tartışmaların odaklanmış halde ve konu içinde tutulmasına yardımcı olur. + first_share: | + Bu rozet paylaş tuşunu kullanarak bir cevaba ya da konuya ilk kez bir bağlantı paylaştığında verilir. Bağlantıları paylaşmak dünyanın geri kalanıyla yapılan ilginç tartışmalarla gösteriş yapmak ve topluluğunu büyütmek için mükemmel bir yoldur. + read_guidelines: | + Bu rozet topluluk kurallarını okunmasına verilir. Bu basit kuralları benimsemek ve paylaşmak güvenli, eğlenceli ve sürdürülebilir bir topluluğun inşa edilmesine yardım eder. + reader: | + Bu rozet uzun bir konuyu okumaya verilir. Okumak temeldir. Yakından okumak tartışmayı takip etmene ve daha iyi, daha eksiksiz cevaplar yazmana yardım eder. + editor: | + Bu rozet gönderini değiştirmene verilir. Geliştirmek, küçük hataları gidermek ya da unuttuğun bir şeyi eklemek için gönderilerini değiştirmekten hiçbir zaman çekinme. + first_flag: | + Bu rozet bir gönderinin işaretlenmesine verilir. İşaretleme, topluluğunun sağlığı için çok önemlidir. Yönetici incelemesi gereken herhangi bir gönderi görürseniz lütfen işaretlemekten çekinmeyin. Ayrıca hemcins kullanıcılara mesajlar göndermek için işaretleme diyaloğunu da kullanabilirsiniz. + nice_share: | + Bu rozet bir gönderide paylaşılan bir bağlantının 25 yabancı ziyaretçi tarafından ziyaret edilmesine verilir. Tebrikler! İlginç tartışmalara bağlantıları arkadaşlarla paylaşmak topluluğumuzu büyütmek için harika bir yoldur. + welcome: | + Bu rozet bir gönderide ilk beğeninizi aldığınızda verilir. Tebrikler, hemcins topluluk üyelerinin ilginç, havalı ya da kullanışlı bulduğu bir şeyler gönderdin. + anniversary: | + Bu rozet en az bir gönderiye sahip olunan bir yıllık üyeliğe verilir. Buralarda olup topluluğumuza katkıda bulunduğunuz için teşekkür ederiz. good_share: | - Bu rozet 300 yabancı ziyaretçi tarafından ziyaret edilen bir gönderi bağlantısının paylaşılmasına verilir. İyi iş! Bir sürü yeni kişiye ilgi çekici bir tartışma gösterdin ve böylece çoğalmamıza yardımcı oldun. + Bu rozet bir gönderide paylaşılan bir bağlantı, 300 yabancı ziyaretçi tarafından ziyaret edildiğinde verilir. İyi iş! Bir sürü yeni kişiye ilgi çekici bir tartışma gösterdin ve böylece büyümemize yardımcı oldun. + great_share: | + Bu rozet bir gönderide paylaşılan bir bağlantının 100 yabancı ziyaretçi tarafından ziyaret edilmesine verilir. Vay be! Bu topluluk için ilginç bir tartışmayı yeni kocaman bir izleyici kitlesine terfi ettirdin ve büyümemize önemli bir katkı sağladın. nice_post: | - Bu rozet 10 beğeni alan bir cevabın yazılmasına verilir. İyi iş! + Bu rozet bir cevap 10 beğeni aldığında verilir. İyi iş! nice_topic: | - Bu rozet 10 beğeni alan bir konunun yaratılmasına verilir. İyi iş! + Bu rozet bir konu 10 beğeni aldığında verilir. İyi iş! good_post: | - Bu rozet 25 beğeni alan bir cevabın yazılmasına verilir. İyi iş! + Bu rozet bir cevap 25 beğeni aldığında verilir. İyi iş! good_topic: | - Bu rozet 25 beğeni alan bir konunun yaratılmasına verilir. İyi iş! + Bu rozet bir konu 25 beğeni aldığında verilir. İyi iş! great_post: | - Bu rozet 50 beğeni alan bir gönderinin yaratılmasına verilir. Vay be! + Bu rozet bir gönderi 50 beğeni aldığında verilir. Vay be! great_topic: | - Bu rozet 50 beğeni alan bir cevabın yazılmasına verilir. Vay be! + Bu rozet bir cevap 50 beğeni aldığında verilir. Vay be! + basic: | + Bu rozet güven seviyesi 1'e ulaştığında verilir. Bir süredir buralarda olduğun ve topluluğumuzun ne hakkında olduğu konusunda birkaç konu okuduğun için teşekkür ederiz. Yeni kullanıcı kısıtlamaların kaldırıldı ve kişisel mesajlaşma, işaretleme, wiki düzenleme, resim ve birden çok bağlantı gönderme yeteneği gibi ana topluluk yeteneklerini aldın. + member: | + Bu rozet güven seviyesi 2'ye ulaştığında verilir. Haftalar boyunca buralarda olup katkıda bulunduğun için teşekkür ederiz. Artık kullanıcı sayfandan ya da konularından kişisel davetler gönderebilir, grup mesajları oluşturabilir ve günlük birkaç daha fazla beğeni ekleyebilirsin. + regular: | + Bu rozet güven seviyesi 3'e ulaştığında verilir. Aylardır topluluğumuzun düzenli bir parçası, bu topluluğu harika yapan en etkin okuyucu ve güvenilir katkıcılardan biri olduğun için teşekkür ederiz. Artık konuların kategorilerini ve isimlerini değiştirebilirsin, özel bir lobi alanına erişebilir, daha güçlü spam işaretleri koyabilir ve günlük çok daha fazla beğeni ekleyebilirsin. + leader: | + Bu rozet güven seviyesi 4'e ulaştığında verilir. Eylem ve kelimelerin ile topluluk için olumlu bir izlenim bırakman sebebiyle forum kadrosu tarafından bu topluluğun bir önderi seçildin. Artık tüm gönderileri düzenleyebilir, sabitleme, kapatma, listelememe, arşivleme, ayırma ve birleştirme gibi yönetici eylemlerini yapabilir ve günlük yığınca beğeni ekleyebilirsiniz. admin_login: success: "E-posta Gönderildi" error: "Hata!" diff --git a/config/locales/server.uk.yml b/config/locales/server.uk.yml index 32b8a6a94c..046b6fe39a 100644 --- a/config/locales/server.uk.yml +++ b/config/locales/server.uk.yml @@ -144,7 +144,6 @@ uk: confirmed: "Адресу вашої електронної скриньки оновлено." error: "Під час зміни адреси Вашої електронної скриньки трапилася помилка. Можливо, ця адреса вже використовується?" activation: - action: "Активувати ваш обліковий запис" already_done: "Sorry, this account confirmation link is no longer valid. Perhaps your account is already active?" welcome_to: "Ласкаво просимо до сайта %{site_name}!" approval_required: "A moderator must manually approve your new account before you can access this forum. You'll get an email when your account is approved!" @@ -426,7 +425,6 @@ uk: characters: "must only include numbers, letters and underscores" unique: "має бути унікальним" blank: "має бути наявним" - must_begin_with_alphanumeric: "must begin with a letter or number" email: not_allowed: "is not allowed from that email provider. Please use another email address." blocked: "не допускається." diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml index 5ee7766ef2..9bbeceab28 100644 --- a/config/locales/server.zh_CN.yml +++ b/config/locales/server.zh_CN.yml @@ -373,7 +373,7 @@ zh_CN: please_continue: "转入到 %{site_name}" error: "在修改你的电子邮箱地址时出现了错误,可能此邮箱已经在论坛中使用了?" activation: - action: "激活你的帐号" + action: "点击这儿激活你的账户" already_done: "抱歉,此帐号激活链接已经失效。可能你的帐号已经被激活了?" please_continue: "你的新帐号已激活;即将转到主页。" continue_button: "转入到 %{site_name}" @@ -724,7 +724,7 @@ zh_CN: notify_mods_when_user_blocked: "如果一个用户被自动封禁了,发送一个消息给所有管理员。" flag_sockpuppets: "如果一个新用户开始了一个主题,并且同时另一个新用户以同一个 IP 在该主题回复,他们所有的帖子都将被自动标记为垃圾。" traditional_markdown_linebreaks: "在 Markdown 中使用传统换行符,即用两个尾随空格来换行" - allow_html_tables: "允许 Markdown 中输入表格的 HTML 表情,TABLE、THEAD、TD、TR、TD 等(需要重制包含表格标签的旧帖子)" + allow_html_tables: "允许在输入 Markdown 时输入表格 HTML 标签,TABLE、THEAD、TD、TR、TH 将被白名单(需要对所有包含表格的老帖子做彻底的 rebake)" post_undo_action_window_mins: "允许用户在帖子上进行撤销操作(赞、标记等)所需等待的间隔分钟数" must_approve_users: "新用户在被允许访问站点前需要由职员批准。警告:在运行的站点中启用将解除所有非职员用户的访问权限!" ga_tracking_code: "Google 分析追踪代码(ga.js),例如:UA-12345678-9。参考 http://google.com/analytics" @@ -983,6 +983,7 @@ zh_CN: enable_cdn_js_debugging: "为包含的 js 启动跨源访问 /logs 权限以显示合适的错误。" show_create_topics_notice: "如果站点只有少于 5 篇的公开帖子时,显示一条请管理员创建帖子的提示。" delete_drafts_older_than_n_days: 删除超过 n 天得草稿。 + show_logout_in_header: "在顶栏的用户下拉菜单中显示登出" vacuum_db_days: "在数据库迁移后使用完整扫描回收数据库空间(设置 0 为禁用)" prevent_anons_from_downloading_files: "禁止匿名用户下载附件。警告:这将禁止他们访问任何发表在帖子中的非图片资源。" slug_generation_method: "选择一个链接生成方式。“encoded”将生成以百分号编码的链接。“none”将禁用自定义链接,只生成默认链接。" @@ -1120,7 +1121,10 @@ zh_CN: characters: "必须只包含字母、数字和下划线" unique: "必须是唯一的" blank: "必须存在" - must_begin_with_alphanumeric: "必须以字母或数字开头" + must_begin_with_alphanumeric: "必须以一个字母或数字或下划线开头" + must_end_with_alphanumeric: "必须以一个字母或数字结尾" + must_not_contain_two_special_chars_in_seq: "必须不包括连续的 2 个或更多的特殊字符(.-_)" + must_not_contain_confusing_suffix: "必须不包含奇怪的后缀,例如 .json 或 .png 等等。" email: not_allowed: "本站不允许使用该邮箱服务商提供的电子邮箱,请使用其它邮箱地址。" blocked: "不被允许。" diff --git a/config/locales/server.zh_TW.yml b/config/locales/server.zh_TW.yml index 8d7e32ecfe..14f16f4751 100644 --- a/config/locales/server.zh_TW.yml +++ b/config/locales/server.zh_TW.yml @@ -294,7 +294,6 @@ zh_TW: please_continue: "繼續連接至 %{site_name}" error: "修改你的電郵位址時發生錯誤,可能此郵箱已有人使用了。" activation: - action: "啟用您的帳號" already_done: "抱歉,此帳號啟用連結已經失效。可能你的帳號已經啟用了。" please_continue: "你的新帳號已啟用;即將轉到主頁。" continue_button: "繼續連接至 %{site_name}" @@ -853,7 +852,6 @@ zh_TW: characters: "必須只包含字母和數位" unique: "必須是唯一的" blank: "必須存在" - must_begin_with_alphanumeric: "必須以字母或數位開頭" email: not_allowed: "本站不允許使用該郵箱服務商提供的電子郵箱,請使用其它郵箱位址。" blocked: "不被允許。" From d5a2029026c943412e449c70a287bf6d7caf3e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 7 Sep 2015 18:52:53 +0200 Subject: [PATCH 1145/1435] FIX: category permissions weren't properly loaded when /categories is the homepage FIX: don't scope to a specific category when creating a new topic from /categories --- .../components/category-chooser.js.es6 | 61 ++++++++----------- .../controllers/discovery/categories.js.es6 | 3 + .../discourse/models/category-list.js.es6 | 4 +- app/models/category_list.rb | 8 ++- lib/guardian/topic_guardian.rb | 2 +- 5 files changed, 37 insertions(+), 41 deletions(-) diff --git a/app/assets/javascripts/discourse/components/category-chooser.js.es6 b/app/assets/javascripts/discourse/components/category-chooser.js.es6 index 57a7ed9697..df61b75dde 100644 --- a/app/assets/javascripts/discourse/components/category-chooser.js.es6 +++ b/app/assets/javascripts/discourse/components/category-chooser.js.es6 @@ -1,5 +1,7 @@ import ComboboxView from 'discourse/components/combo-box'; import { categoryBadgeHTML } from 'discourse/helpers/category-link'; +import computed from 'ember-addons/ember-computed-decorators'; +import { observes, on } from 'ember-addons/ember-computed-decorators'; export default ComboboxView.extend({ classNames: ['combobox category-combobox'], @@ -8,46 +10,34 @@ export default ComboboxView.extend({ valueBinding: Ember.Binding.oneWay('source'), castInteger: true, - content: function() { - let scopedCategoryId = this.get('scopedCategoryId'); - + @computed("scopedCategoryId", "categories") + content(scopedCategoryId, categories) { // Always scope to the parent of a category, if present if (scopedCategoryId) { const scopedCat = Discourse.Category.findById(scopedCategoryId); scopedCategoryId = scopedCat.get('parent_category_id') || scopedCat.get('id'); } - return this.get('categories').filter(function(c) { - if (scopedCategoryId && (c.get('id') !== scopedCategoryId) && (c.get('parent_category_id') !== scopedCategoryId)) { - return false; - } - return c.get('permission') === Discourse.PermissionType.FULL && !c.get('isUncategorizedCategory'); + return categories.filter(c => { + if (scopedCategoryId && c.get('id') !== scopedCategoryId && c.get('parent_category_id') !== scopedCategoryId) { return false; } + if (c.get('isUncategorizedCategory')) { return false; } + return c.get('permission') === Discourse.PermissionType.FULL; }); - }.property('scopedCategoryId', 'categories'), + }, - _setCategories: function() { + @on("init") + @observes("site.sortedCategories") + _updateCategories() { + const categories = Discourse.SiteSettings.fixed_category_positions_on_create ? + Discourse.Category.list() : + Discourse.Category.listByActivity(); + this.set('categories', categories); + }, - if (!this.get('categories')) { - this.set('automatic', true); - } - - this._updateCategories(); - - }.on('init'), - - _updateCategories: function() { - - if (this.get('automatic')) { - this.set('categories', - Discourse.SiteSettings.fixed_category_positions_on_create ? - Discourse.Category.list() : Discourse.Category.listByActivity() - ); - } - }.observes('automatic', 'site.sortedCategories'), - - none: function() { + @computed("rootNone") + none(rootNone) { if (Discourse.User.currentProp('staff') || Discourse.SiteSettings.allow_uncategorized_topics) { - if (this.get('rootNone')) { + if (rootNone) { return "category.none"; } else { return Discourse.Category.findUncategorized(); @@ -55,10 +45,9 @@ export default ComboboxView.extend({ } else { return 'category.choose'; } - }.property(), + }, comboTemplate(item) { - let category; // If we have no id, but text with the uncategorized name, we can use that badge. @@ -79,16 +68,14 @@ export default ComboboxView.extend({ result = categoryBadgeHTML(Discourse.Category.findById(parentCategoryId), {link: false}) + " " + result; } - result += " × " + category.get('topic_count') + ""; + result += ` × ${category.get('topic_count')}`; const description = category.get('description'); // TODO wtf how can this be null?; if (description && description !== 'null') { - result += '
      ' + - description.substr(0,200) + - (description.length > 200 ? '…' : '') + - '
      '; + result += `
      ${description.substr(0, 200)}${description.length > 200 ? '…' : ''}
      `; } + return result; } diff --git a/app/assets/javascripts/discourse/controllers/discovery/categories.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/categories.js.es6 index ba9877d485..c4d639f361 100644 --- a/app/assets/javascripts/discourse/controllers/discovery/categories.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery/categories.js.es6 @@ -6,6 +6,9 @@ export default DiscoveryController.extend({ withLogo: Em.computed.filterBy('model.categories', 'logo_url'), showPostsColumn: Em.computed.empty('withLogo'), + // this makes sure the composer isn't scoping to a specific category + category: null, + actions: { refresh() { diff --git a/app/assets/javascripts/discourse/models/category-list.js.es6 b/app/assets/javascripts/discourse/models/category-list.js.es6 index fcbe90bd07..b5e7097054 100644 --- a/app/assets/javascripts/discourse/models/category-list.js.es6 +++ b/app/assets/javascripts/discourse/models/category-list.js.es6 @@ -34,7 +34,7 @@ CategoryList.reopenClass({ }, listForParent(store, category) { - return Discourse.ajax('/categories.json?parent_category_id=' + category.get('id')).then((result) => { + return Discourse.ajax(`/categories.json?parent_category_id=${category.get("id")}`).then(result => { return Discourse.CategoryList.create({ categories: this.categoriesFrom(store, result), parentCategory: category @@ -44,7 +44,7 @@ CategoryList.reopenClass({ list(store) { const getCategories = () => Discourse.ajax("/categories.json"); - return PreloadStore.getAndRemove("categories_list", getCategories).then((result) => { + return PreloadStore.getAndRemove("categories_list", getCategories).then(result => { return Discourse.CategoryList.create({ categories: this.categoriesFrom(store, result), can_create_category: result.category_list.can_create_category, diff --git a/app/models/category_list.rb b/app/models/category_list.rb index 2e32369fda..1472a27501 100644 --- a/app/models/category_list.rb +++ b/app/models/category_list.rb @@ -78,10 +78,16 @@ class CategoryList end if latest_post_only? - @categories = @categories.includes(:latest_post => {:topic => :last_poster} ) + @categories = @categories.includes(latest_post: { topic: :last_poster }) end @categories = @categories.to_a + + allowed_topic_create = Set.new(Category.topic_create_allowed(@guardian).pluck(:id)) + @categories.each do |category| + category.permission = CategoryGroup.permission_types[:full] if allowed_topic_create.include?(category.id) + end + if @options[:parent_category_id].blank? subcategories = {} to_delete = Set.new diff --git a/lib/guardian/topic_guardian.rb b/lib/guardian/topic_guardian.rb index 6543208b53..8c03cc3e38 100644 --- a/lib/guardian/topic_guardian.rb +++ b/lib/guardian/topic_guardian.rb @@ -15,7 +15,7 @@ module TopicGuardian def can_create_topic_on_category?(category) can_create_topic?(nil) && - (!category || Category.topic_create_allowed(self).where(:id => category.id).count == 1) + (!category || Category.topic_create_allowed(self).where(id: category.id).count == 1) end def can_create_post_on_topic?(topic) From e37dd5a393bc11dcfb0725928fbe11a01f45c071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 7 Sep 2015 19:33:04 +0200 Subject: [PATCH 1146/1435] FIX: don't reload the page when clicking the number in a notification on mobile --- app/assets/javascripts/discourse/templates/header.hbs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/header.hbs b/app/assets/javascripts/discourse/templates/header.hbs index ae0ec11d40..2c11cae568 100644 --- a/app/assets/javascripts/discourse/templates/header.hbs +++ b/app/assets/javascripts/discourse/templates/header.hbs @@ -45,10 +45,10 @@ loginAction="showLogin" title="user.avatar.header_title"}} {{#if currentUser.unread_notifications}} - {{currentUser.unread_notifications}} + {{currentUser.unread_notifications}} {{/if}} {{#if currentUser.unread_private_messages}} - {{currentUser.unread_private_messages}} + {{currentUser.unread_private_messages}} {{/if}} {{/header-dropdown}} {{/if}} From e13ed241223a05ee4c4c1fc1044c1e00571043cd Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 8 Sep 2015 11:03:51 +1000 Subject: [PATCH 1147/1435] FEATURE: on mobile take users to full page search UX: improve styling on full page search page FEATURE: allow search context in full page search FEATURE: visited color link for full page search FIX: broken search help on fulls page search page FEATURE: allow preload store to return a null FEATURE: "mobileAction" for the header buttons --- .../components/header-dropdown.js.es6 | 6 ++ .../discourse/components/search-menu.js.es6 | 15 +--- .../controllers/full-page-search.js.es6 | 72 ++++++++++++++++--- .../discourse/controllers/header.js.es6 | 13 ++++ .../{search-for-term.js.es6 => search.js.es6} | 17 ++++- .../discourse/routes/full-page-search.js.es6 | 21 ++++-- .../discourse/templates/full-page-search.hbs | 13 +++- .../discourse/templates/header.hbs | 1 + .../discourse/views/choose-topic.js.es6 | 2 +- app/assets/javascripts/main_include.js | 2 +- app/assets/javascripts/preload_store.js | 2 +- .../stylesheets/common/base/search.scss | 14 ++++ app/assets/stylesheets/mobile.scss | 1 + app/assets/stylesheets/mobile/search.scss | 16 +++++ app/controllers/search_controller.rb | 51 ++++++++++--- 15 files changed, 203 insertions(+), 43 deletions(-) rename app/assets/javascripts/discourse/lib/{search-for-term.js.es6 => search.js.es6} (84%) create mode 100644 app/assets/stylesheets/mobile/search.scss diff --git a/app/assets/javascripts/discourse/components/header-dropdown.js.es6 b/app/assets/javascripts/discourse/components/header-dropdown.js.es6 index 0712306338..02843970ac 100644 --- a/app/assets/javascripts/discourse/components/header-dropdown.js.es6 +++ b/app/assets/javascripts/discourse/components/header-dropdown.js.es6 @@ -13,6 +13,12 @@ export default Ember.Component.extend({ actions: { toggle() { + + if (Discourse.Mobile.mobileView && this.get('mobileAction')) { + this.sendAction('mobileAction'); + return; + } + if (this.siteSettings.login_required && !this.currentUser) { this.sendAction('loginAction'); } else { diff --git a/app/assets/javascripts/discourse/components/search-menu.js.es6 b/app/assets/javascripts/discourse/components/search-menu.js.es6 index 4786747d99..8a4f2ddece 100644 --- a/app/assets/javascripts/discourse/components/search-menu.js.es6 +++ b/app/assets/javascripts/discourse/components/search-menu.js.es6 @@ -1,4 +1,4 @@ -import searchForTerm from 'discourse/lib/search-for-term'; +import {searchForTerm, searchContextDescription} from 'discourse/lib/search'; import DiscourseURL from 'discourse/lib/url'; import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; import showModal from 'discourse/lib/show-modal'; @@ -48,18 +48,7 @@ export default Ember.Component.extend({ @computed('searchService.searchContext') searchContextDescription(ctx) { - if (ctx) { - switch(Em.get(ctx, 'type')) { - case 'topic': - return I18n.t('search.context.topic'); - case 'user': - return I18n.t('search.context.user', {username: Em.get(ctx, 'user.username')}); - case 'category': - return I18n.t('search.context.category', {category: Em.get(ctx, 'category.name')}); - case 'private_messages': - return I18n.t('search.context.private_messages'); - } - } + return searchContextDescription(Em.get(ctx, 'type'), Em.get(ctx, 'user.username') || Em.get(ctx, 'category.name')); }, @observes('searchService.searchContextEnabled') 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 7a5f031d2b..8efc5c6adf 100644 --- a/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 +++ b/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 @@ -1,30 +1,65 @@ -import { translateResults } from "discourse/lib/search-for-term"; +import { translateResults, searchContextDescription } from "discourse/lib/search"; +import showModal from 'discourse/lib/show-modal'; +import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; +import Category from 'discourse/models/category'; export default Ember.Controller.extend({ needs: ["application"], loading: Em.computed.not("model"), - queryParams: ["q"], + queryParams: ["q", "context_id", "context", "skip_context"], q: null, selected: [], + context_id: null, + context: null, - modelChanged: function() { + @computed('skip_context', 'context') + searchContextEnabled: { + get(skip,context){ + return (!skip && context) || skip === "false"; + }, + set(val) { + this.set('skip_context', val ? "false" : "true" ) + } + }, + + @computed('context', 'context_id') + searchContextDescription(context, id){ + var name = id; + if (context === 'category') { + var category = Category.findById(id); + if (!category) {return;} + + name = category.get('name'); + } + return searchContextDescription(context, name); + }, + + @computed('q') + searchActive(q){ + return q && q.length > 0; + }, + + @observes('model') + modelChanged() { if (this.get("searchTerm") !== this.get("q")) { this.set("searchTerm", this.get("q")); } - }.observes("model"), + }, - qChanged: function() { + @observes('q') + qChanged() { const model = this.get("model"); if (model && this.get("model.q") !== this.get("q")) { this.set("searchTerm", this.get("q")); this.send("search"); } - }.observes("q"), + }, - _showFooter: function() { + @observes('loading') + _showFooter() { this.set("controllers.application.showFooter", !this.get("loading")); - }.observes("loading"), + }, canBulkSelect: Em.computed.alias('currentUser.staff'), @@ -32,9 +67,19 @@ export default Ember.Controller.extend({ this.set("q", this.get("searchTerm")); this.set("model", null); - Discourse.ajax("/search", { data: { q: this.get("searchTerm") } }).then(results => { + var args = { q: this.get("searchTerm") }; + + const skip = this.get("skip_context"); + if ((!skip && this.get('context')) || skip==="false"){ + args.search_context = { + type: this.get('context'), + id: this.get('context_id') + }; + } + + Discourse.ajax("/search", { data: args }).then(results => { this.set("model", translateResults(results) || {}); - this.set("model.q", this.get("q")); + // this.set("model.q", this.get("q")); }); }, @@ -51,6 +96,13 @@ export default Ember.Controller.extend({ this.search(); }, + showSearchHelp() { + // TODO: dupe code should be centralized + Discourse.ajax("/static/search_help.html", { dataType: 'html' }).then((model) => { + showModal('searchHelp', { model }); + }); + }, + search() { this.search(); } diff --git a/app/assets/javascripts/discourse/controllers/header.js.es6 b/app/assets/javascripts/discourse/controllers/header.js.es6 index 813e9e717e..4d40efbbea 100644 --- a/app/assets/javascripts/discourse/controllers/header.js.es6 +++ b/app/assets/javascripts/discourse/controllers/header.js.es6 @@ -1,3 +1,5 @@ +import DiscourseURL from 'discourse/lib/url'; + const HeaderController = Ember.Controller.extend({ topic: null, showExtraInfo: null, @@ -18,6 +20,17 @@ const HeaderController = Ember.Controller.extend({ actions: { + fullPageSearch() { + const searchService = this.container.lookup('search-service:main'); + const context = searchService.get('searchContext'); + var params = ""; + + if (context) { + params = `?context=${context.type}&context_id=${context.id}`; + } + + DiscourseURL.routeTo('/search' + params); + }, toggleMenuPanel(visibleProp) { this.toggleProperty(visibleProp); this.appEvents.trigger('dropdowns:closeAll'); diff --git a/app/assets/javascripts/discourse/lib/search-for-term.js.es6 b/app/assets/javascripts/discourse/lib/search.js.es6 similarity index 84% rename from app/assets/javascripts/discourse/lib/search-for-term.js.es6 rename to app/assets/javascripts/discourse/lib/search.js.es6 index 2d029fdf9e..d39ed0d7ab 100644 --- a/app/assets/javascripts/discourse/lib/search-for-term.js.es6 +++ b/app/assets/javascripts/discourse/lib/search.js.es6 @@ -86,4 +86,19 @@ function searchForTerm(term, opts) { return promise; } -export default searchForTerm; +const searchContextDescription = function(type, name){ + if (type) { + switch(type) { + case 'topic': + return I18n.t('search.context.topic'); + case 'user': + return I18n.t('search.context.user', {username: name}); + case 'category': + return I18n.t('search.context.category', {category: name}); + case 'private_messages': + return I18n.t('search.context.private_messages'); + } + } +}; + +export { searchForTerm, searchContextDescription }; diff --git a/app/assets/javascripts/discourse/routes/full-page-search.js.es6 b/app/assets/javascripts/discourse/routes/full-page-search.js.es6 index 482ebf1fc5..8aeedefdde 100644 --- a/app/assets/javascripts/discourse/routes/full-page-search.js.es6 +++ b/app/assets/javascripts/discourse/routes/full-page-search.js.es6 @@ -1,15 +1,24 @@ -import { translateResults } from "discourse/lib/search-for-term"; +import { translateResults } from "discourse/lib/search"; export default Discourse.Route.extend({ - queryParams: { q: {} }, + queryParams: { q: {}, "context-id": {}, context: {} }, model(params) { return PreloadStore.getAndRemove("search", function() { - return Discourse.ajax("/search", { data: { q: params.q } }); + if (params.q && params.q.length > 2) { + var args = { q: params.q }; + if (params.context_id && !args.skip_context) { + args.search_context = { + type: params.context, + id: params.context_id + } + } + return Discourse.ajax("/search", { data: args }); + } else { + return null; + } }).then(results => { - const model = translateResults(results) || {}; - model.q = params.q; - return model; + return (results && translateResults(results)) || {}; }); }, diff --git a/app/assets/javascripts/discourse/templates/full-page-search.hbs b/app/assets/javascripts/discourse/templates/full-page-search.hbs index 832e68148e..f0278b6407 100644 --- a/app/assets/javascripts/discourse/templates/full-page-search.hbs +++ b/app/assets/javascripts/discourse/templates/full-page-search.hbs @@ -9,11 +9,22 @@ {{/if}}
      +{{#if context}} +
      + +
      +{{/if}} + {{#conditional-loading-spinner condition=loading}} {{#unless model.posts}}

      - {{i18n "search.no_results"}} {{i18n "search.search_help"}} + {{#if searchActive}} + {{i18n "search.no_results"}} + {{/if}} + {{i18n "search.search_help"}}

      {{/unless}} diff --git a/app/assets/javascripts/discourse/templates/header.hbs b/app/assets/javascripts/discourse/templates/header.hbs index 2c11cae568..04c87250f9 100644 --- a/app/assets/javascripts/discourse/templates/header.hbs +++ b/app/assets/javascripts/discourse/templates/header.hbs @@ -23,6 +23,7 @@ {{#header-dropdown iconId="search-button" icon="search" toggleVisible=searchVisible + mobileAction="fullPageSearch" loginAction="showLogin" title="search.title"}} {{/header-dropdown}} diff --git a/app/assets/javascripts/discourse/views/choose-topic.js.es6 b/app/assets/javascripts/discourse/views/choose-topic.js.es6 index da4cbac2f4..cb2a346614 100644 --- a/app/assets/javascripts/discourse/views/choose-topic.js.es6 +++ b/app/assets/javascripts/discourse/views/choose-topic.js.es6 @@ -1,5 +1,5 @@ import debounce from 'discourse/lib/debounce'; -import searchForTerm from 'discourse/lib/search-for-term'; +import { searchForTerm } from 'discourse/lib/search'; export default Ember.View.extend({ templateName: 'choose_topic', diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js index 5ea3455611..797cab8658 100644 --- a/app/assets/javascripts/main_include.js +++ b/app/assets/javascripts/main_include.js @@ -35,7 +35,7 @@ //= require_tree ./discourse/mixins //= require ./discourse/lib/ajax-error //= require ./discourse/lib/markdown -//= require ./discourse/lib/search-for-term +//= require ./discourse/lib/search //= require ./discourse/lib/user-search //= require ./discourse/lib/export-csv //= require ./discourse/lib/autocomplete diff --git a/app/assets/javascripts/preload_store.js b/app/assets/javascripts/preload_store.js index b4b0e8b0de..4911d4e54a 100644 --- a/app/assets/javascripts/preload_store.js +++ b/app/assets/javascripts/preload_store.js @@ -42,7 +42,7 @@ window.PreloadStore = { var result = finder(); // If the finder returns a promise, we support that too - if (result.then) { + if (result && result.then) { result.then(function(result) { return resolve(result); }, function(result) { diff --git a/app/assets/stylesheets/common/base/search.scss b/app/assets/stylesheets/common/base/search.scss index ec75223b5d..83e82ecf23 100644 --- a/app/assets/stylesheets/common/base/search.scss +++ b/app/assets/stylesheets/common/base/search.scss @@ -18,10 +18,20 @@ top: -3px; margin-right: 4px; } + a.search-link:visited .topic-title { + color: scale-color($tertiary, $lightness: 15%); + } .search-link { .topic-statuses, .topic-title { font-size: 1.25em; } + + .topic-statuses { + float: none; + display: inline-block; + color: $primary; + font-size: 1.0em; + } } .blurb { font-size: 1.0em; @@ -50,3 +60,7 @@ .search-footer { margin-bottom: 30px; } + +.panel-body-contents .search-context label { + float: left; +} diff --git a/app/assets/stylesheets/mobile.scss b/app/assets/stylesheets/mobile.scss index 984da0c668..8434f8c731 100644 --- a/app/assets/stylesheets/mobile.scss +++ b/app/assets/stylesheets/mobile.scss @@ -19,6 +19,7 @@ @import "mobile/history"; @import "mobile/directory"; @import "mobile/menu-panel"; +@import "mobile/search"; /* These files doesn't actually exist, they are injected by DiscourseSassImporter. */ diff --git a/app/assets/stylesheets/mobile/search.scss b/app/assets/stylesheets/mobile/search.scss new file mode 100644 index 0000000000..0438c41c05 --- /dev/null +++ b/app/assets/stylesheets/mobile/search.scss @@ -0,0 +1,16 @@ +.search button.btn-primary, .search button.btn { + float: none; +} + +.search.row { + margin-top: 5px; +} + +.search.row input.search { + height: 25px; + width: 69%; +} + +.fps-search-context { + margin-bottom: 15px; +} diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 4446a0af99..0b857278f0 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -9,7 +9,20 @@ class SearchController < ApplicationController end def show - search = Search.new(params[:q], type_filter: 'topic', guardian: guardian, include_blurbs: true, blurb_length: 300) + search_args = { + type_filter: 'topic', + guardian: guardian, + include_blurbs: true, + blurb_length: 300 + } + + context, type = lookup_search_context + if context + search_args[:search_context] = context + search_args[:type_filter] = type if type + end + + search = Search.new(params[:q], search_args) result = search.execute serializer = serialize_data(result, GroupedSearchResultSerializer, result: result) @@ -34,7 +47,29 @@ class SearchController < ApplicationController search_args[:include_blurbs] = params[:include_blurbs] == "true" if params[:include_blurbs].present? search_args[:search_for_id] = true if params[:search_for_id].present? + context,type = lookup_search_context + if context + search_args[:search_context] = context + search_args[:type_filter] = type if type + end + + search = Search.new(params[:term], search_args.symbolize_keys) + result = search.execute + render_serialized(result, GroupedSearchResultSerializer, result: result) + end + + protected + + def lookup_search_context + + return if params[:skip_context] == "true" + search_context = params[:search_context] + unless search_context + if (context = params[:context]) && (id = params[:context_id]) + search_context = {type: context, id: id} + end + end if search_context.present? raise Discourse::InvalidParameters.new(:search_context) unless SearchController.valid_context_types.include?(search_context[:type]) @@ -43,23 +78,21 @@ class SearchController < ApplicationController # A user is found by username context_obj = nil if ['user','private_messages'].include? search_context[:type] - context_obj = User.find_by(username_lower: params[:search_context][:id].downcase) + context_obj = User.find_by(username_lower: search_context[:id].downcase) else klass = search_context[:type].classify.constantize - context_obj = klass.find_by(id: params[:search_context][:id]) + context_obj = klass.find_by(id: search_context[:id]) end + type_filter = nil if search_context[:type] == 'private_messages' - search_args[:type_filter] = 'private_messages' + type_filter = 'private_messages' end guardian.ensure_can_see!(context_obj) - search_args[:search_context] = context_obj - end - search = Search.new(params[:term], search_args.symbolize_keys) - result = search.execute - render_serialized(result, GroupedSearchResultSerializer, result: result) + [context_obj, type_filter] + end end end From 0c5189fa2aed03fd6375d310acba78052bf8ff7a Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 8 Sep 2015 15:25:00 +1000 Subject: [PATCH 1148/1435] SECURITY: fix possible XSS expanding quotes --- .../javascripts/discourse/views/post.js.es6 | 9 +- app/assets/javascripts/vendor.js | 1 - .../javascripts/jquery.ba-replacetext.js | 129 ------------------ 3 files changed, 4 insertions(+), 135 deletions(-) delete mode 100644 vendor/assets/javascripts/jquery.ba-replacetext.js diff --git a/app/assets/javascripts/discourse/views/post.js.es6 b/app/assets/javascripts/discourse/views/post.js.es6 index bc555c6783..ef1869e90a 100644 --- a/app/assets/javascripts/discourse/views/post.js.es6 +++ b/app/assets/javascripts/discourse/views/post.js.es6 @@ -145,11 +145,10 @@ const PostView = Discourse.GroupedView.extend(Ember.Evented, { topicId = parseInt(topicId, 10); Discourse.ajax("/posts/by_number/" + topicId + "/" + postId).then(function (result) { - // slightly double escape the cooked html to prevent jQuery from unescaping it - const escaped = result.cooked.replace(/&[^gla]/, "&"); - const parsed = $(escaped); - parsed.replaceText(originalText, "" + originalText + ""); - $blockQuote.showHtml(parsed, 'fast', finished); + const div = $("
      "); + div.html(result.cooked); + div.highlight(originalText, {caseSensitive: true, element: 'span', className: 'highlighted'}); + $blockQuote.showHtml(div, 'fast', finished); }); } else { // Hide expanded quote diff --git a/app/assets/javascripts/vendor.js b/app/assets/javascripts/vendor.js index 5c0788f8c7..1a52785b5f 100644 --- a/app/assets/javascripts/vendor.js +++ b/app/assets/javascripts/vendor.js @@ -22,7 +22,6 @@ //= require div_resizer //= require caret_position //= require favcount.js -//= require jquery.ba-replacetext.js //= require jquery.ba-resize.min.js //= require jquery.color.js //= require jquery.cookie.js diff --git a/vendor/assets/javascripts/jquery.ba-replacetext.js b/vendor/assets/javascripts/jquery.ba-replacetext.js deleted file mode 100644 index c6b60c57c1..0000000000 --- a/vendor/assets/javascripts/jquery.ba-replacetext.js +++ /dev/null @@ -1,129 +0,0 @@ -/*! - * jQuery replaceText - v1.1 - 11/21/2009 - * http://benalman.com/projects/jquery-replacetext-plugin/ - * - * Copyright (c) 2009 "Cowboy" Ben Alman - * Dual licensed under the MIT and GPL licenses. - * http://benalman.com/about/license/ - */ - -// Script: jQuery replaceText: String replace for your jQueries! -// -// *Version: 1.1, Last updated: 11/21/2009* -// -// Project Home - http://benalman.com/projects/jquery-replacetext-plugin/ -// GitHub - http://github.com/cowboy/jquery-replacetext/ -// Source - http://github.com/cowboy/jquery-replacetext/raw/master/jquery.ba-replacetext.js -// (Minified) - http://github.com/cowboy/jquery-replacetext/raw/master/jquery.ba-replacetext.min.js (0.5kb) -// -// About: License -// -// Copyright (c) 2009 "Cowboy" Ben Alman, -// Dual licensed under the MIT and GPL licenses. -// http://benalman.com/about/license/ -// -// About: Examples -// -// This working example, complete with fully commented code, illustrates one way -// in which this plugin can be used. -// -// replaceText - http://benalman.com/code/projects/jquery-replacetext/examples/replacetext/ -// -// About: Support and Testing -// -// Information about what version or versions of jQuery this plugin has been -// tested with, and what browsers it has been tested in. -// -// jQuery Versions - 1.3.2, 1.4.1 -// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome, Opera 9.6-10.1. -// -// About: Release History -// -// 1.1 - (11/21/2009) Simplified the code and API substantially. -// 1.0 - (11/21/2009) Initial release - -(function($){ - '$:nomunge'; // Used by YUI compressor. - - // Method: jQuery.fn.replaceText - // - // Replace text in specified elements. Note that only text content will be - // modified, leaving all tags and attributes untouched. The new text can be - // either text or HTML. - // - // Uses the String prototype replace method, full documentation on that method - // can be found here: - // - // https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Objects/String/Replace - // - // Usage: - // - // > jQuery('selector').replaceText( search, replace [, text_only ] ); - // - // Arguments: - // - // search - (RegExp|String) A RegExp object or substring to be replaced. - // Because the String prototype replace method is used internally, this - // argument should be specified accordingly. - // replace - (String|Function) The String that replaces the substring received - // from the search argument, or a function to be invoked to create the new - // substring. Because the String prototype replace method is used internally, - // this argument should be specified accordingly. - // text_only - (Boolean) If true, any HTML will be rendered as text. Defaults - // to false. - // - // Returns: - // - // (jQuery) The initial jQuery collection of elements. - - $.fn.replaceText = function( search, replace, text_only ) { - return this.each(function(){ - var node = this.firstChild, - val, - new_val, - - // Elements to be removed at the end. - remove = []; - - // Only continue if firstChild exists. - if ( node ) { - - // Loop over all childNodes. - do { - - // Only process text nodes. - if ( node.nodeType === 3 ) { - - // The original node value. - val = node.nodeValue; - - // The new value. - new_val = val.replace( search, replace ); - - // Only replace text if the new value is actually different! - if ( new_val !== val ) { - - if ( !text_only && / Date: Tue, 8 Sep 2015 16:11:21 +1000 Subject: [PATCH 1149/1435] FEATURE: select all / deselect all on search page --- .../controllers/full-page-search.js.es6 | 16 +++++++++++++++- .../discourse/templates/full-page-search.hbs | 9 +++++++++ app/assets/stylesheets/common/base/search.scss | 14 ++++++++++++++ config/locales/client.en.yml | 2 ++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 b/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 index 8efc5c6adf..3641329f83 100644 --- a/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 +++ b/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 @@ -79,12 +79,26 @@ export default Ember.Controller.extend({ Discourse.ajax("/search", { data: args }).then(results => { this.set("model", translateResults(results) || {}); - // this.set("model.q", this.get("q")); }); }, actions: { + selectAll() { + this.get('selected').addObjects(this.get('model.posts').map(r => r.topic)); + // Doing this the proper way is a HUGE pain, + // we can hack this to work by observing each on the array + // in the component, however, when we select ANYTHING, we would force + // 50 traversals of the list + // This hack is cheap and easy + $('.fps-result input[type=checkbox]').prop('checked', true); + }, + + clearAll() { + this.get('selected').clear() + $('.fps-result input[type=checkbox]').prop('checked', false); + }, + toggleBulkSelect() { this.toggleProperty('bulkSelectEnabled'); this.get('selected').clear(); diff --git a/app/assets/javascripts/discourse/templates/full-page-search.hbs b/app/assets/javascripts/discourse/templates/full-page-search.hbs index f0278b6407..db60849ff9 100644 --- a/app/assets/javascripts/discourse/templates/full-page-search.hbs +++ b/app/assets/javascripts/discourse/templates/full-page-search.hbs @@ -9,6 +9,15 @@ {{/if}}
      +{{#if model.posts}} + {{#if bulkSelectEnabled}} + + {{/if}} +{{/if}} + {{#if context}}
      diff --git a/app/assets/javascripts/admin/templates/logs/site_customization_change_modal.hbs b/app/assets/javascripts/admin/templates/logs/site_customization_change_modal.hbs index 2c995713b7..556f6310db 100644 --- a/app/assets/javascripts/admin/templates/logs/site_customization_change_modal.hbs +++ b/app/assets/javascripts/admin/templates/logs/site_customization_change_modal.hbs @@ -1,10 +1,10 @@
      {{/if}} diff --git a/app/assets/javascripts/discourse/templates/mobile/discovery/categories.hbs b/app/assets/javascripts/discourse/templates/mobile/discovery/categories.hbs index 349cabd1a9..dc7db78da8 100644 --- a/app/assets/javascripts/discourse/templates/mobile/discovery/categories.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/discovery/categories.hbs @@ -30,7 +30,7 @@ {{#unless t.canClearPin}}{{i18n 'read_more'}}{{/unless}} {{/if}} {{#if t.canClearPin}} - {{i18n 'topic.clear_pin.title'}} + {{i18n 'topic.clear_pin.title'}} {{/if}}
      {{/if}} @@ -60,7 +60,7 @@
      {{number c.topics_week}}
      {{i18n 'week'}}
      {{#if controller.canEdit}} - {{i18n 'category.edit'}} + {{i18n 'category.edit'}} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs index 7a02b2a778..22cbceea9c 100644 --- a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs @@ -38,7 +38,7 @@

      {{footerMessage}} - {{#if model.can_create_topic}}{{i18n 'topic.suggest_create_topic'}}{{/if}} + {{#if model.can_create_topic}}{{i18n 'topic.suggest_create_topic'}}{{/if}}

      {{else}} {{#if top}} diff --git a/app/assets/javascripts/discourse/templates/modal/not-activated.hbs b/app/assets/javascripts/discourse/templates/modal/not-activated.hbs index 4266126c8d..f8df2639e9 100644 --- a/app/assets/javascripts/discourse/templates/modal/not-activated.hbs +++ b/app/assets/javascripts/discourse/templates/modal/not-activated.hbs @@ -3,7 +3,7 @@ {{{i18n 'login.sent_activation_email_again' currentEmail=currentEmail}}} {{else}} {{{i18n 'login.not_activated' sentTo=sentTo}}} - {{i18n 'login.resend_activation_email'}} + {{i18n 'login.resend_activation_email'}} {{/if}}
      diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 36c1f56dd3..6c30944c10 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -958,7 +958,7 @@ en: private_messages: "Search messages" hamburger_menu: "go to another topic list or category" - new_item: "New!" + new_item: "new" go_back: 'go back' not_logged_in_user: 'user page with summary of current activity and preferences' current_user: 'go to your user page' From 2922cc3036a4a28f55d50a2777f35fcf66944596 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Wed, 9 Sep 2015 16:05:02 -0400 Subject: [PATCH 1176/1435] UX: mobile topic list: move post count to right side of topic title --- .../templates/mobile/list/topic_list_item.raw.hbs | 7 +++++-- app/assets/stylesheets/mobile/topic-list.scss | 10 +++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs b/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs index 972acfa16a..01937421ae 100644 --- a/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs @@ -1,5 +1,5 @@ -

    + {{plugin-outlet "header-before-dropdowns"}} + {{user-menu visible=userMenuVisible logoutAction="logout"}} + {{hamburger-menu visible=hamburgerVisible showKeyboardAction="showKeyboardShortcutsHelp"}} + {{search-menu visible=searchVisible}}
    {{#if showExtraInfo}} diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss index 9eb96fb380..33d69271cc 100644 --- a/app/assets/stylesheets/common/base/menu-panel.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -1,18 +1,15 @@ .menu-panel.slide-in { position: fixed; + // positions are relative to the .d-header .panel div right: 0; top: 0; - - .panel-body { - position: absolute; - top: 3px; - bottom: 37px; - width: 97%; - } } .menu-panel.drop-down { position: absolute; + // positions are relative to the .d-header .panel div + top: 100%; // directly underneath .panel + right: -10px; // 10px to the right of .panel - adjust as needed } .menu-panel { @@ -20,7 +17,6 @@ box-shadow: 0 2px 2px rgba(0,0,0, .25); background-color: $secondary; z-index: 1100; - overflow: none; padding: 0.5em; width: 300px; From 1f8328feb52224c94b1b6d0e3fb2a5b298604fac Mon Sep 17 00:00:00 2001 From: scossar Date: Thu, 10 Sep 2015 09:49:54 -0700 Subject: [PATCH 1194/1435] add code that was accidentally deleted --- app/assets/stylesheets/common/base/menu-panel.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss index 33d69271cc..85a9869a4f 100644 --- a/app/assets/stylesheets/common/base/menu-panel.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -3,6 +3,13 @@ // positions are relative to the .d-header .panel div right: 0; top: 0; + + .panel-body { + position: absolute; + top: 3px; + bottom: 37px; + width: 97%; + } } .menu-panel.drop-down { From 9224afacaf78e116a9e525511f5866ae64a20a0e Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 10 Sep 2015 14:01:23 -0400 Subject: [PATCH 1195/1435] FIX: Don't put the FAQ as "New" if there is a custom `faq_url` If we do this, it's impossible to ever mark it as read. --- .../discourse/components/hamburger-menu.js.es6 | 6 ++++++ .../discourse/templates/components/hamburger-menu.hbs | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/discourse/components/hamburger-menu.js.es6 b/app/assets/javascripts/discourse/components/hamburger-menu.js.es6 index a3c0777488..910b8d661a 100644 --- a/app/assets/javascripts/discourse/components/hamburger-menu.js.es6 +++ b/app/assets/javascripts/discourse/components/hamburger-menu.js.es6 @@ -2,6 +2,12 @@ import computed from 'ember-addons/ember-computed-decorators'; export default Ember.Component.extend({ classNames: ['hamburger-panel'], + @computed('currentUser.read_faq') + prioritizeFaq(readFaq) { + // If it's a custom FAQ never prioritize it + return Ember.isEmpty(this.siteSettings.faq_url) && !readFaq; + }, + @computed() showKeyboardShortcuts() { return !Discourse.Mobile.mobileView && !this.capabilities.touch; diff --git a/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs b/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs index ee75d18a76..fe7050955b 100644 --- a/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs +++ b/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs @@ -1,5 +1,5 @@ {{#menu-panel visible=visible}} - {{#unless currentUser.read_faq}} + {{#if prioritizeFaq}} {{#menu-links}}
  • {{#d-link path=faqUrl class="faq-link"}} @@ -8,7 +8,7 @@ {{/d-link}}
  • {{/menu-links}} - {{/unless}} + {{/if}} {{#if currentUser.staff}} {{#menu-links}} @@ -81,9 +81,9 @@ {{#menu-links omitRule="true"}}
  • {{d-link route="about" class="about-link" label="about.simple_title"}}
  • - {{#if currentUser.read_faq}} + {{#unless prioritizeFaq}}
  • {{d-link path=faqUrl class="faq-link" label="faq"}}
  • - {{/if}} + {{/unless}} {{#if showKeyboardShortcuts}}
  • {{d-link action="keyboardShortcuts" class="keyboard-shortcuts-link" label="keyboard_shortcuts_help.title"}}
  • From c3a5ddac8cf835e59b400121df8fc5d3589b3a51 Mon Sep 17 00:00:00 2001 From: Erlend Sogge Heggen Date: Thu, 10 Sep 2015 20:43:36 +0200 Subject: [PATCH 1196/1435] Repurposing CONTRIBUTING.md into a link portal, 2nd attempt - Slight changes to the CLA paragraph, making it slightly easier to digest. - Added brief synopsis of the Discourse Development Contribution Guidelines doc - Replaced Bug Report, Feature Request and Contributing (commits) section with outgoing links The aim of this change is to reduce the maintenance burden, since more detailed information about contribution guidelines is more naturally documented and maintained on Discourse Meta. --- CONTRIBUTING.md | 134 ++++++------------------------------------------ 1 file changed, 16 insertions(+), 118 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f00d5d078e..f59bf39dcb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,129 +1,27 @@ # Contributing to Discourse -## Before You Start +## Important note for Developers -Anyone wishing to contribute to the **[Discourse/Discourse](https://github.com/discourse/discourse)** project **MUST read & sign the [Electronic Discourse Forums Contribution License Agreement](http://www.discourse.org/cla)**. The Discourse team is legally prevented from accepting any pull requests from users who have not signed the CLA first. +Anyone wishing to contribute to the [github/discourse/discourse](https://github.com/discourse/discourse) project **must read & sign our [Contribution License Agreement](http://www.discourse.org/cla)**. The Discourse team is legally prevented from accepting any pull requests from users who have not signed the CLA first. -## Reporting Bugs +For more information on -1. Always update to the most recent master release; the bug may already be resolved. +- how to set up your development environment +- first-time project suggestions +- code conventions +- step-by-step guide for GitHub commits -2. Search for similar issues on the [Discourse meta forum][m]; it may already be an identified problem. +**please read our [Discourse Development Contribution Guidelines](https://meta.discourse.org/t/discourse-development-contribution-guidelines/3823)** -3. Make sure you can reproduce your problem on our sandbox at [try.discourse.org](http://try.discourse.org) +## Everything Else -4. If this is a bug or problem that **requires any kind of extended discussion -- open [a topic on meta][m] about it**. +There are many other ways to contribute to Discourse besides code. We've outlined the most common ones below. -5. If possible, submit a Pull Request with a failing test. If you'd rather take matters into your own hands, fix the bug yourself (jump down to the "Contributing (Step-by-step)" section). +- [Reporting Bugs](https://meta.discourse.org/t/how-to-make-bug-reports-for-discourse/33070) +- [Requesting Features](https://meta.discourse.org/t/how-to-request-new-features-for-discourse/32986) +- [Translation](https://meta.discourse.org/t/contribute-a-translation-to-discourse/14882) +- Documentation (TBA) -6. When the bug is fixed, we will do our best to update the Discourse topic. +For anything else, just start a new topic on [Meta](https://meta.discourse.org/) and let us know what you're interested in working on. -## Requesting New Features - -1. Do not submit a feature request on GitHub; all feature requests on GitHub will be closed. Instead, visit the **[Discourse meta forum, features category](http://meta.discourse.org/category/feature)**, and search this list for similar feature requests. It's possible somebody has already asked for this feature or provided a pull request that we're still discussing. - -2. Provide a clear and detailed explanation of the feature you want and why it's important to add. The feature must apply to a wide array of users of Discourse; for smaller, more targeted "one-off" features, you might consider writing a plugin for Discourse. You may also want to provide us with some advance documentation on the feature, which will help the community to better understand where it will fit. - -3. If you're a Rock Star programmer, build the feature yourself (refer to the "Contributing (Step-by-step)" section below). - -## Contributing (Step-by-step) - -1. Clone the Repo: - - git clone git://github.com/discourse/discourse.git - -2. Create a new Branch: - - cd discourse - git checkout -b new_discourse_branch - - > Please keep your code clean: one feature or bug-fix per branch. If you find another bug, you want to fix while being in a new branch, please fix it in a separated branch instead. - -3. Code - * Adhere to common conventions you see in the existing code - * Include tests, and ensure they pass - * Search to see if your new functionality has been discussed on [the Discourse meta forum](http://meta.discourse.org), and include updates as appropriate - -4. Follow the Coding Conventions - * two spaces, no tabs - * no trailing whitespaces, blank lines should have no spaces - * use spaces around operators, after commas, colons, semicolons, around `{` and before `}` - * no space after `(`, `[` or before `]`, `)` - * use Ruby 1.9 hash syntax: prefer `{ a: 1 }` over `{ :a => 1 }` - * prefer `class << self; def method; end` over `def self.method` for class methods - * prefer `{ ... }` over `do ... end` for single-line blocks, avoid using `{ ... }` for multi-line blocks - * avoid `return` when not required - - > However, please note that **pull requests consisting entirely of style changes are not welcome on this project**. Style changes in the context of pull requests that also refactor code, fix bugs, improve functionality *are* welcome. - -5. Commit - - For every commit please write a short (max 72 characters) summary in the first line followed with a blank line and then more detailed descriptions of the change. Use markdown syntax for simple styling. - - **NEVER leave the commit message blank!** Provide a detailed, clear, and complete description of your commit! - - -6. Update your branch - - ``` - git fetch origin - git rebase origin/master - ``` - -7. Fork - - ``` - git remote add mine git@github.com:/discourse.git - ``` - -8. Push to your remote - - ``` - git push mine new_discourse_branch - ``` - -9. Issue a Pull Request - - Before submitting a pull-request, clean up the history, go over your commits and squash together minor changes and fixes into the corresponding commits. You can squash commits with the interactive rebase command: - - ``` - git fetch origin - git checkout new_discourse_branch - git rebase origin/master - git rebase -i - - < the editor opens and allows you to change the commit history > - < follow the instructions on the bottom of the editor > - - git push -f mine new_discourse_branch - ``` - - - In order to make a pull request, - * Navigate to the Discourse repository you just pushed to (e.g. https://github.com/your-user-name/discourse) - * Click "Pull Request". - * Write your branch name in the branch field (this is filled with "master" by default) - * Click "Update Commit Range". - * Ensure the changesets you introduced are included in the "Commits" tab. - * Ensure that the "Files Changed" incorporate all of your changes. - * Fill in some details about your potential patch including a meaningful title. - * Click "Send pull request". - - Thanks for that -- we'll get to your pull request ASAP, we love pull requests! - -10. Responding to Feedback - - The Discourse team may recommend adjustments to your code. Part of interacting with a healthy open-source community requires you to be open to learning new techniques and strategies; *don't get discouraged!* Remember: if the Discourse team suggest changes to your code, **they care enough about your work that they want to include it**, and hope that you can assist by implementing those revisions on your own. - - > Though we ask you to clean your history and squash commit before submitting a pull-request, please do not change any commits you've submitted already (as other work might be build on top). - -## Translations - -Translators can do their work in our [Transifex project](https://www.transifex.com/projects/p/discourse-org/). For more information, please see these how-to topics: - -* [Contributing a translation to Discourse](https://meta.discourse.org/t/contribute-a-translation-to-discourse/14882) -* [How to add a new language](https://meta.discourse.org/t/how-to-add-a-new-language/14970) - - - -[m]: http://meta.discourse.org +*Thanks for contributing!* From ef787b3828f81bbf100ff02eccac549edb9ded83 Mon Sep 17 00:00:00 2001 From: Erlend Sogge Heggen Date: Thu, 10 Sep 2015 20:46:36 +0200 Subject: [PATCH 1197/1435] GitHub link was missing the .com The whole point of adding github.com to the link in the first place was to leave no room for misinterpretation. --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f59bf39dcb..4ad0aec099 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Important note for Developers -Anyone wishing to contribute to the [github/discourse/discourse](https://github.com/discourse/discourse) project **must read & sign our [Contribution License Agreement](http://www.discourse.org/cla)**. The Discourse team is legally prevented from accepting any pull requests from users who have not signed the CLA first. +Anyone wishing to contribute to the [github.com/discourse/discourse](https://github.com/discourse/discourse) project **must read & sign our [Contribution License Agreement](http://www.discourse.org/cla)**. The Discourse team is legally prevented from accepting any pull requests from users who have not signed the CLA first. For more information on From ad481b3427a90cacc726a2d78b9d430d615dadb6 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Fri, 11 Sep 2015 00:16:37 +0530 Subject: [PATCH 1198/1435] FIX: permalinks like read.php should work --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index d2adc0cfdb..66125570f4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -409,7 +409,7 @@ Discourse::Application.routes.draw do end Discourse.filters.each do |filter| - get "#{filter}" => "list##{filter}" + get "#{filter}" => "list##{filter}", constraints: { format: /(json|html)/ } get "c/:category/l/#{filter}" => "list#category_#{filter}", as: "category_#{filter}" get "c/:category/none/l/#{filter}" => "list#category_none_#{filter}", as: "category_none_#{filter}" get "c/:parent_category/:category/l/#{filter}" => "list#parent_category_category_#{filter}", as: "parent_category_category_#{filter}" From 28cd0361d66269e0164886062f27366a54ab9054 Mon Sep 17 00:00:00 2001 From: Erlend Sogge Heggen Date: Thu, 10 Sep 2015 20:49:03 +0200 Subject: [PATCH 1199/1435] Proper long form for CLA Seems it's most commonly spelled out as "*Contributor* License Agreement", not *Contribution*. --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4ad0aec099..98aa05e4ac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Important note for Developers -Anyone wishing to contribute to the [github.com/discourse/discourse](https://github.com/discourse/discourse) project **must read & sign our [Contribution License Agreement](http://www.discourse.org/cla)**. The Discourse team is legally prevented from accepting any pull requests from users who have not signed the CLA first. +Anyone wishing to contribute to the [github.com/discourse/discourse](https://github.com/discourse/discourse) project **must read & sign our [Contributor License Agreement](http://www.discourse.org/cla)**. The Discourse team is legally prevented from accepting any pull requests from users who have not signed the CLA first. For more information on From 20c8bb04943204ce79366961759048a5ced964bd Mon Sep 17 00:00:00 2001 From: scossar Date: Thu, 10 Sep 2015 11:46:02 -0700 Subject: [PATCH 1200/1435] remove hardcoded left: auto --- app/assets/javascripts/discourse/components/menu-panel.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/components/menu-panel.js.es6 b/app/assets/javascripts/discourse/components/menu-panel.js.es6 index 8a5b2ea275..02304afee7 100644 --- a/app/assets/javascripts/discourse/components/menu-panel.js.es6 +++ b/app/assets/javascripts/discourse/components/menu-panel.js.es6 @@ -54,7 +54,7 @@ export default Ember.Component.extend({ } $panelBody.height('100%'); - this.$().css({ left: "auto", top: (menuTop) + "px", height }); + this.$().css({ top: menuTop + "px", height }); $('body').removeClass('drop-down-visible'); } From b68be6c5deac5e723b4a60f8f17db04f5d8b1671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Thu, 10 Sep 2015 21:56:51 +0200 Subject: [PATCH 1201/1435] update onebox --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 44397f9df5..e33b7a10e3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -209,7 +209,7 @@ GEM omniauth-twitter (1.0.1) multi_json (~> 1.3) omniauth-oauth (~> 1.0) - onebox (1.5.24) + onebox (1.5.25) moneta (~> 0.8) multi_json (~> 1.11) mustache From 0c5fb207e912bb2b2386824d75a38f2d09237130 Mon Sep 17 00:00:00 2001 From: Kane York Date: Thu, 10 Sep 2015 13:04:25 -0700 Subject: [PATCH 1202/1435] FIX: Fix behavior of category reorder dialog --- .../controllers/reorder-categories.js.es6 | 79 ++++++++++--------- .../templates/modal/reorder-categories.hbs | 31 ++++---- config/locales/client.en.yml | 2 + 3 files changed, 58 insertions(+), 54 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/reorder-categories.js.es6 b/app/assets/javascripts/discourse/controllers/reorder-categories.js.es6 index 0c7ab22cae..f33b5e02a1 100644 --- a/app/assets/javascripts/discourse/controllers/reorder-categories.js.es6 +++ b/app/assets/javascripts/discourse/controllers/reorder-categories.js.es6 @@ -5,6 +5,8 @@ import { popupAjaxError } from 'discourse/lib/ajax-error'; import computed from "ember-addons/ember-computed-decorators"; import Ember from 'ember'; +const SortableArrayProxy = Ember.ArrayProxy.extend(Ember.SortableMixin); + export default Ember.Controller.extend(ModalFunctionality, Ember.Evented, { @computed("site.categories") @@ -13,24 +15,23 @@ export default Ember.Controller.extend(ModalFunctionality, Ember.Evented, { return categories.map(c => bufProxy.create({ content: c })); }, - // uses propertyDidChange() - @computed('categoriesBuffered') - categoriesGrouped(cats) { - const map = {}; - cats.forEach((cat) => { - const p = cat.get('position') || 0; - if (!map[p]) { - map[p] = {pos: p, cats: [cat]}; - } else { - map[p].cats.push(cat); + categoriesOrdered: function() { + return SortableArrayProxy.create({ + sortProperties: ['content.position'], + content: this.get('categoriesBuffered') + }); + }.property('categoriesBuffered'), + + showFixIndices: function() { + const cats = this.get('categoriesOrdered'); + const len = cats.get('length'); + for (let i = 0; i < len; i++) { + if (cats.objectAt(i).get('position') !== i) { + return true; } - }); - const result = []; - Object.keys(map).map(p => parseInt(p)).sort((a,b) => a-b).forEach(function(pos) { - result.push(map[pos]); - }); - return result; - }, + } + return false; + }.property('categoriesOrdered.@each.position'), showApplyAll: function() { let anyChanged = false; @@ -38,29 +39,22 @@ export default Ember.Controller.extend(ModalFunctionality, Ember.Evented, { return anyChanged; }.property('categoriesBuffered.@each.hasBufferedChanges'), - saveDisabled: Ember.computed.alias('showApplyAll'), + @computed('showApplyAll', 'showFixIndices') + saveDisabled(a, b) { + return a || b; + }, moveDir(cat, dir) { - const grouped = this.get('categoriesGrouped'), - curPos = cat.get('position'), - curGroupIdx = binarySearch(grouped, curPos, "pos"), - curGroup = grouped[curGroupIdx]; - - if (curGroup.cats.length === 1 && ((dir === -1 && curGroupIdx !== 0) || (dir === 1 && curGroupIdx !== (grouped.length - 1)))) { - const nextGroup = grouped[curGroupIdx + dir], - nextPos = nextGroup.pos; - cat.set('position', nextPos); - } else { - cat.set('position', curPos + dir); + const cats = this.get('categoriesOrdered'); + const curIdx = cats.indexOf(cat); + const curPos = cat.get('position'); + const desiredIdx = curIdx + dir; + if (desiredIdx >= 0 && desiredIdx < cats.get('length')) { + cat.set('position', cat.get('position') + dir); + const otherCat = cats.objectAt(desiredIdx); + otherCat.set('position', cat.get('position') - dir); + this.send('commit'); } - cat.applyBufferedChanges(); - Ember.run.next(this, () => { - this.propertyDidChange('categoriesGrouped'); - Ember.run.schedule('afterRender', this, () => { - this.set('scrollIntoViewId', cat.get('id')); - this.trigger('scrollIntoView'); - }); - }); }, actions: { @@ -72,13 +66,22 @@ export default Ember.Controller.extend(ModalFunctionality, Ember.Evented, { this.moveDir(cat, 1); }, + fixIndices() { + const cats = this.get('categoriesOrdered'); + const len = cats.get('length'); + for (let i = 0; i < len; i++) { + cats.objectAt(i).set('position', i); + } + this.send('commit'); + }, + commit() { this.get('categoriesBuffered').forEach(bc => { if (bc.get('hasBufferedChanges')) { bc.applyBufferedChanges(); } }); - this.propertyDidChange('categoriesGrouped'); + this.propertyDidChange('categoriesBuffered'); }, saveOrder() { diff --git a/app/assets/javascripts/discourse/templates/modal/reorder-categories.hbs b/app/assets/javascripts/discourse/templates/modal/reorder-categories.hbs index 7a5b3172b6..b23f257292 100644 --- a/app/assets/javascripts/discourse/templates/modal/reorder-categories.hbs +++ b/app/assets/javascripts/discourse/templates/modal/reorder-categories.hbs @@ -6,28 +6,27 @@ Position Category - {{#each categoriesGrouped as |group|}} - - {{#each group.cats as |cat|}} - - - {{number-field number=cat.position}} - {{d-button class="no-text" action="moveUp" actionParam=cat icon="arrow-up"}} - {{d-button class="no-text" action="moveDown" actionParam=cat icon="arrow-down"}} - {{#if cat.hasBufferedChanges}} - {{d-button class="no-text" action="commit" icon="check"}} - {{/if}} - - {{category-badge cat allowUncategorized="true"}} - - {{/each}} - + {{#each categoriesOrdered as |cat|}} + + + {{number-field number=cat.position}} + {{d-button class="no-text" action="moveUp" actionParam=cat icon="arrow-up"}} + {{d-button class="no-text" action="moveDown" actionParam=cat icon="arrow-down"}} + {{#if cat.hasBufferedChanges}} + {{d-button class="no-text" action="commit" icon="check"}} + {{/if}} + + {{category-badge cat allowUncategorized="true"}} + {{/each}}

    {{/unless}} + {{#if model.posts}} +
    +
    + + {{{i18n "search.result_count" count=resultCount term=noSortQ}}} + +
    +
    + + {{i18n "search.sort_by"}} + + {{combo-box value=sortOrder content=sortOrders castInteger="true"}} +
    +
    + {{/if}} + {{#each model.posts as |result|}}
    @@ -65,6 +81,13 @@ {{/highlight-text}} {{/if}}
    + {{#if showLikeCount}} + {{#if result.like_count}} + + {{/if}} + {{/if}}
    {{/each}} diff --git a/app/assets/stylesheets/common/base/search.scss b/app/assets/stylesheets/common/base/search.scss index d77d57ea12..a22ad9d0cc 100644 --- a/app/assets/stylesheets/common/base/search.scss +++ b/app/assets/stylesheets/common/base/search.scss @@ -1,5 +1,10 @@ .fps-result { + .like-count { + color: dark-light-choose(scale-color($primary, $lightness: 40%), scale-color($secondary, $lightness: 60%)); + .fa { color: $love; font-size: 12px; } + } + .badge-wrapper span.badge-category { max-width: inherit; } @@ -78,3 +83,37 @@ .panel-body-contents .search-context label { float: left; } + +.search-title { + + .term { + font-weight: bold; + } + + position: relative; + .result-count { + float: left; + span { + line-height: 28px; + height: 28px; + display: inline-block; + } + margin-bottom: 4px; + } + margin: 10px 0 15px; + max-width: 675px; + border-bottom: 3px solid dark-light-diff($primary, $secondary, 90%, -75%); + width: 100%; + .sort-by { + .desc { + margin-right: 5px; + } + select { + margin-bottom: 0; + width: auto; + min-width: 150px; + } + float: right; + margin-bottom: 4px; + } +} diff --git a/app/serializers/search_post_serializer.rb b/app/serializers/search_post_serializer.rb index 27fc4cdae7..614ba8b790 100644 --- a/app/serializers/search_post_serializer.rb +++ b/app/serializers/search_post_serializer.rb @@ -2,6 +2,8 @@ class SearchPostSerializer < PostSerializer has_one :topic, serializer: TopicListItemSerializer + attributes :like_count + attributes :blurb def blurb options[:result].blurb(object) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 63f225130e..444eff15e0 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -968,8 +968,16 @@ en: image_link: "link your image will point to" search: + sort_by: "Sort by" + relevance: "Relevance" + latest_post: "Latest Post" + most_viewed: "Most Viewed" + most_liked: "Most Liked" select_all: "Select All" clear_all: "Clear All" + result_count: + one: "1 result for \"{{term}}\"" + other: "{{count}} results for \"{{term}}\"" title: "search topics, posts, users, or categories" no_results: "No results found." no_more_results: "No more results found." From 664e2209e53c920d6cc688af1144ed1ca90301d0 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 18 Sep 2015 17:16:37 +1000 Subject: [PATCH 1376/1435] FIX: order latest was broken --- lib/search.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/search.rb b/lib/search.rb index cc9267dd1c..354755a02c 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -522,11 +522,13 @@ class Search def aggregate_search(opts = {}) + min_or_max = @order == :latest ? "max" : "min" + post_sql = posts_query(@limit, aggregate_search: true, private_messages: opts[:private_messages]) - .select('topics.id', 'min(post_number) post_number') - .group('topics.id') - .to_sql + .select('topics.id', "#{min_or_max}(post_number) post_number") + .group('topics.id') + .to_sql # double wrapping so we get correct row numbers post_sql = "SELECT *, row_number() over() row_number FROM (#{post_sql}) xxx" From 01782affe220b975f0afcc9629e8766455a28b69 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 18 Sep 2015 00:40:00 -0700 Subject: [PATCH 1377/1435] clearer call to action in embed CSS styles --- app/assets/stylesheets/embed.css.scss | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/embed.css.scss b/app/assets/stylesheets/embed.css.scss index 9eee091348..a225c31be7 100644 --- a/app/assets/stylesheets/embed.css.scss +++ b/app/assets/stylesheets/embed.css.scss @@ -57,7 +57,7 @@ article.post { .cooked { - padding: 10px 0 20px 0; + padding: 10px 0; margin-left: 65px; word-wrap: break-word; word-break: break-word; @@ -115,21 +115,24 @@ img.emoji { } header.discourse { - padding: 10px 10px 20px 10px; + padding-left: 10px; + padding-bottom: 8px; font-size: 1.286em; - border-bottom: 1px solid #ddd; + border-bottom: 3px solid #ddd; } footer { font-size: 1.286em; + margin-top: 15px; .logo { - margin-right: 10px; - margin-top: 10px; + margin-top: -10px; } .button { - margin: 10px 0 0 10px; + color: white; + padding: 6px 8px; + background-color: #0088cc; } } From 681334701dc76f710556990ad2aa45295ed1703d Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 18 Sep 2015 17:49:57 +1000 Subject: [PATCH 1378/1435] FIX: Stop aggregating likes --- lib/search.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/search.rb b/lib/search.rb index 354755a02c..248948219f 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -524,11 +524,19 @@ class Search min_or_max = @order == :latest ? "max" : "min" - post_sql = posts_query(@limit, aggregate_search: true, + post_sql = + if @order == :likes + # likes are a pain to aggregate so skip + posts_query(@limit, private_messages: opts[:private_messages]) + .select('topics.id', "post_number") + .to_sql + else + posts_query(@limit, aggregate_search: true, private_messages: opts[:private_messages]) .select('topics.id', "#{min_or_max}(post_number) post_number") .group('topics.id') .to_sql + end # double wrapping so we get correct row numbers post_sql = "SELECT *, row_number() over() row_number FROM (#{post_sql}) xxx" From 59d8466d6877a5f89310e16b2109a1f764c07601 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 18 Sep 2015 01:24:40 -0700 Subject: [PATCH 1379/1435] Delete INSTALL-alternatives.md --- docs/INSTALL-alternatives.md | 85 ------------------------------------ 1 file changed, 85 deletions(-) delete mode 100644 docs/INSTALL-alternatives.md diff --git a/docs/INSTALL-alternatives.md b/docs/INSTALL-alternatives.md deleted file mode 100644 index cad612d43b..0000000000 --- a/docs/INSTALL-alternatives.md +++ /dev/null @@ -1,85 +0,0 @@ -> # Warning: This Guide is Deprecated -> We only support Docker based installs now. Please see [our **official install guide**](https://github.com/discourse/discourse/blob/master/docs/INSTALL.md) for supported install instructions. - -# Alternative Install Options - -Here lie some alternative installation options for Discourse. They're not the -recommended way of doing things, hence they're a bit out of the way. - -Oh, and dragons. Lots of dragons. - -## Web Server Alternative: apache2 - -If you instead want to use apache2 to serve the static pages: - - # Run these commands as your normal login (e.g. "michael") - # If you don't have apache2 yet - sudo apt-get install apache2 - - # Edit your site details in a new apache2 config file - sudo vim /etc/apache2/sites-available/your-domain.com - - # Put these info inside and change accordingly - - - ServerName your-domain.com - ServerAlias www.your-domain.com - - DocumentRoot /srv/www/apps/discourse/public - - - AllowOverride all - Options -MultiViews - - - # Custom log file locations - ErrorLog /srv/www/apps/discourse/log/error.log - CustomLog /srv/www/apps/discourse/access.log combined - - - # Install the Passenger Phusion gem and run the install - gem install passenger - passenger-install-apache2-module - - # Next, we "create" a new apache2 module, passenger - sudo vim /etc/apache2/mods-available/passenger.load - - # Inside paste (change the user accodingly) - LoadModule passenger_module /home/YOUR-USER/.rvm/gems/ruby-2.0.0-p0/gems/passenger-4.0.2/libout/apache2/mod_passenger.so - - # Now the passenger module configuration - sudo vim /etc/apache2/mods-available/passenger.conf - - # Inside, paste (change the user accodingly) - PassengerRoot /home/YOUR-USER/.rvm/gems/ruby-2.0.0-p0/gems/passenger-4.0.2 - PassengerDefaultRuby /home/YOUR-USER/.rvm/wrappers/ruby-2.0.0-p0/ruby - - # Now activate them all - - sudo a2ensite your-domain.com - sudo a2enmod passenger - sudo service apache2 reload - sudo service apache2 restart - -If you get any errors starting or reloading apache, please check the paths above - Ruby 2.0 should be there if you are using RVM, but it could get tricky. - -## RVM Alternative: Systemwide installation - -Taken from http://rvm.io/, the commands below installs RVM and users in the 'rvm' group have access to modify state: - - # Run these commands as your normal login (e.g. "michael") \curl -s -S -L https://get.rvm.io | sudo bash -s stable - sudo adduser $USER rvm - newgrp rvm - . /etc/profile.d/rvm.sh - rvm requirements - - # Build and install ruby - rvm install 2.0.0 - gem install bundler - -When creating the `discourse` user, add him/her/it to the RVM group: - - # Run these commands as your normal login (e.g. "michael") - sudo adduser discourse rvm - -RVM will be located in `/usr/local/rvm` directory instead of `/home/discourse/.rvm`, so update the crontab line respectively. From 2627b651c32f7c7c7c697fdb50b8f5b535a07453 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 18 Sep 2015 01:24:51 -0700 Subject: [PATCH 1380/1435] Delete INSTALL-ubuntu.md --- docs/INSTALL-ubuntu.md | 401 ----------------------------------------- 1 file changed, 401 deletions(-) delete mode 100644 docs/INSTALL-ubuntu.md diff --git a/docs/INSTALL-ubuntu.md b/docs/INSTALL-ubuntu.md deleted file mode 100644 index 24f0fa940b..0000000000 --- a/docs/INSTALL-ubuntu.md +++ /dev/null @@ -1,401 +0,0 @@ -# Warning: This Guide is Deprecated -We only support Docker based installs now. Please see [our **official install guide**](https://github.com/discourse/discourse/blob/master/docs/INSTALL.md) for supported install instructions. - -> # Discourse Ubuntu Install Guide - -> ## Recommended Server Hardware - -> - 2 GB of RAM -> - 2 GB of swap -> - 2 processor cores - -> With 2 GB of memory and dual cores, you can run two instances of the thin server (`NUM_WEBS=2`), and easily host anything but the largest of forums. - -> 1 GB of memory, 3 GB of swap and a single core CPU are the minimums for a steady state, running Discourse forum – but it's simpler to just throw a bit more hardware at the problem if you can, particularly during the install. - -> ## Install Ubuntu Server 12.04 LTS (or later) with the package groups: - -> Yes, you can in theory pick the distro of your choice, but to keep this guide sane, we're picking one, and it's Ubuntu. Feel free to substitute the distro of your choice, the steps are mostly the same. - -> ![screenshot of package group selection screen](https://raw.github.com/discourse/discourse-docimages/master/install/ubuntu%20-%20install%20-%20software%20selection.png) - -> * Basic Ubuntu server -> * OpenSSH server -> * Mail server -> * PostgreSQL database (9.1+) - -> You may be working on an already-installed or automatically deployed system, in which case you can install them afterwards: - -> # Run these commands as your normal login (e.g. "michael") -> sudo apt-get update && sudo apt-get -y upgrade -> sudo apt-get install tasksel -> sudo tasksel install openssh-server -> sudo tasksel install mail-server -> sudo tasksel install postgresql-server - -> If the above installation is stuck, please use the following method instead ([detail](https://bugs.launchpad.net/ubuntu/+source/debconf/+bug/141601)): - -> apt-get install postgresql-server^ - -> ### Configure the mail server: - -> ![screenshot of mail server type configuration screen](https://raw.github.com/discourse/discourse-docimages/master/install/ubuntu%20-%20install%20-%20mail_1%20system%20type.png) - -> In our example setup, we're going to configure as a 'Satellite system', forwarding all mail to our egress servers for delivery. You'll probably want to do that unless you're handling mail on the same machine as the Discourse software. - -> ![screenshot of mail name configuration screen](https://raw.github.com/discourse/discourse-docimages/master/install/ubuntu%20-%20install%20-%20mail_2%20mailname.png) - -> You probably want to configure your 'mail name' to be the base name of your domain. Note that this does not affect any email sent out by Discourse itself, just unqualified mail generated by systems programs. - -> ![screenshot of relay host configuration screen](https://raw.github.com/discourse/discourse-docimages/master/install/ubuntu%20-%20install%20-%20mail_3%20relayconfig.png) - -> If you have a mail server responsible for handling the egress of email from your network, enter it here. Otherwise, leave it blank. - -> ## Additional system packages - -> Install necessary packages: - -> # Run these commands as your normal login (e.g. "michael") -> sudo apt-get -y install build-essential libssl-dev libyaml-dev git libtool libxslt-dev libxml2-dev libpq-dev gawk curl pngcrush imagemagick python-software-properties - -> # If you're on Ubuntu >= 12.10, change: -> # python-software-properties to software-properties-common - -> ## Caching: Redis - -> Redis is a networked, in memory key-value store cache. Without the Redis caching layer, we'd have to go to the database a lot more often for common information and the site would be slower as a result. - -> Be sure to install the latest stable Redis, as the package in the distro may be a bit old: - -> sudo apt-add-repository -y ppa:rwky/redis -> sudo apt-get update -> sudo apt-get install redis-server - -> ## Web Server: nginx - -> nginx is used for: - -> * reverse proxy (i.e. load balancer) -> * static asset serving (since you don't want to do that from ruby) -> * anonymous user cache - -> At Discourse, we recommend the latest version of nginx (we like the new and -> shiny). To install on Ubuntu: - -> # Run these commands as your normal login (e.g. "michael") -> # Remove any existing versions of nginx -> sudo apt-get remove '^nginx.*$' - -> # Setup a sources.list.d file for the nginx repository -> cat << 'EOF' | sudo tee /etc/apt/sources.list.d/nginx.list -> deb http://nginx.org/packages/ubuntu/ precise nginx -> deb-src http://nginx.org/packages/ubuntu/ precise nginx -> EOF - -> # Add nginx key -> curl http://nginx.org/keys/nginx_signing.key | sudo apt-key add - - -> # install nginx -> sudo apt-get update && sudo apt-get -y install nginx - -> ## Install Ruby with RVM - -> ### RVM : Single-user installation - -> We recommend installing RVM isolated to a single user's environment. - -> ## Discourse setup - -> Create Discourse user: - -> # Run these commands as your normal login (e.g. "michael") -> sudo adduser --shell /bin/bash --gecos 'Discourse application' discourse -> sudo install -d -m 755 -o discourse -g discourse /var/www/discourse - -> Give Postgres database rights to the `discourse` user: - -> # Run these commands as your normal login (e.g. "michael") -> sudo -u postgres createuser -s discourse -> # If you will be using password authentication on your database, only -> # necessary if the database will be on a remote host -> sudo -u postgres psql -c "alter user discourse password 'todayisagooddaytovi';" - -> Change to the 'discourse' user: - -> # Run this command as your normal login (e.g. "michael"), further commands should be run as 'discourse' -> sudo su - discourse - -> Install RVM - -> # As 'discourse' -> # Install RVM -> \curl -s -S -L https://get.rvm.io | bash -s stable - -> # Refresh your profile -> . ~/.rvm/scripts/rvm - -> Install missing packages - -> # Install necessary packages for building ruby (this will only work if -> # you've given discourse sudo permissions, which is *not* the default) -> # rvm requirements - -> # NOTE: rvm will tell you which packages you (or your sysadmin) need -> # to install during this step. As discourse does not have sudo -> # permissions (likely the case), run: - -> rvm --autolibs=read-fail requirements - -> # For instance, if prompted with `libreadline6-dev libsqlite3-dev sqlite3 autoconf' etc -> # Install the missing packages with this command, run as your user: -> # sudo apt-get install libreadline6-dev libsqlite3-dev sqlite3 autoconf libgdbm-dev libncurses5-dev bison libffi-dev -> # Repeat the autolibs test until you see "Requirements installation successful" - - -> Build and install ruby - -> rvm install 2.0.0 - -> # Use installed ruby as default -> rvm use 2.0.0 --default - -> # Install bundler -> gem install bundler - -> Continue with Discourse installation - -> # Pull down the latest code -> # Now would be a great time to consider [forking](https://help.github.com/articles/fork-a-repo), if want to work from your own copy of discourse -> #If you don't need to customize your installation, and want less hassle upgrading clone from Discourse's repo -> git clone git://github.com/discourse/discourse.git /var/www/discourse -> cd /var/www/discourse - -> # To run on the most recent numbered release instead of bleeding-edge: -> #git checkout latest-release - -> # Install necessary gems -> bundle install --deployment --without test - -> _If you have errors building the native extensions, ensure you have sufficient free system memory. 1GB with no swap isn't enough, we recommend having 2GB as a minimum._ - -> Configure Discourse: - -> # Run these commands as the discourse user -> cd /var/www/discourse/config -> cp discourse_quickstart.conf discourse.conf -> cp discourse.pill.sample discourse.pill - -> Editing /var/www/discourse/config/discourse.conf: - -> Database/Hostname: -> - change database username/password if appropriate -> - change `hostname` to the name you'll use to access the Discourse site, e.g. "forum.example.com" - -> Redis: -> - no changes if this is the only application using redis, but have a look - -> E-mail: -> - browse through all the settings and be sure to add your mail server SMTP settings so outgoing mail can be sent (we recommend [Mandrill](https://mandrillapp.com)) -> - If your users will come from "internal" [private unroutable IPs](https://en.wikipedia.org/wiki/Private_network) like 10.x.x.x or 192.168.x.x please [see this topic](http://meta.discourse.org/t/all-of-my-internal-users-show-as-coming-from-127-0-0-1/6607). - -> Editing: /var/www/discourse/config/discourse.pill -> - change application name from 'discourse' if necessary -> - Ensure appropriate Bluepill.application line is uncommented - -> Initialize the database: - -> # Run these commands as the discourse user -> # The database name here should match the production one in database.yml -> cd /var/www/discourse -> createdb discourse_prod -> RUBY_GC_MALLOC_LIMIT=90000000 RAILS_ENV=production bundle exec rake db:migrate -> RUBY_GC_MALLOC_LIMIT=90000000 RAILS_ENV=production bundle exec rake assets:precompile - -> Not english? Set the default language as appropriate: - -> # Run these commands as the discourse user -> cd /var/www/discourse -> RAILS_ENV=production bundle exec rails c -> SiteSetting.default_locale = 'fr' - -> # Not sure if your locale is supported? Check at the rails console: -> LocaleSiteSetting.values -> => ["cs", "da", "de", "en", "es", "fr", "id", "it", "nb_NO", "nl", "pt", "ru", "sv", "zh_CN", "zh_TW"] - -> ## nginx setup - -> # Run these commands as your normal login (e.g. "michael") -> sudo cp /var/www/discourse/config/nginx.global.conf /etc/nginx/conf.d/local-server.conf -> sudo cp /var/www/discourse/config/nginx.sample.conf /etc/nginx/conf.d/discourse.conf - -> If Discourse will be the only site served by nginx, disable the nginx default -> site: - -> - `sudo mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.disabled` -> - Otherwise, only `server_name`s configured below in `discourse.conf` will be passed to Discourse. - -> Edit /etc/nginx/conf.d/discourse.conf - -> - edit `server_name`. Example: `server_name cain.discourse.org test.cain.discourse.org;` -> - change socket count depending on your NUM_WEB count -> - change socket paths if Discourse is installed to a different location -> - modify root location if Discourse is installed to a different location - -> Reload nginx by running - -> # Run as your normal login (e.g. "michael") -> sudo /etc/init.d/nginx reload - -> ## Bluepill setup - -> Configure Bluepill: - -> # Run these commands as the discourse user -> gem install bluepill -> echo 'alias bluepill="NOEXEC_DISABLE=1 bluepill --no-privileged -c ~/.bluepill"' >> ~/.bash_aliases -> rvm wrapper $(rvm current) bootup bluepill -> rvm wrapper $(rvm current) bootup bundle - -> Start Discourse: - -> # Run these commands as the discourse user -> RUBY_GC_MALLOC_LIMIT=90000000 RAILS_ROOT=/var/www/discourse RAILS_ENV=production NUM_WEBS=2 bluepill --no-privileged -c ~/.bluepill load /var/www/discourse/config/discourse.pill - -> Add the Bluepill startup to crontab. - -> # Run these commands as the discourse user -> crontab -e - -> Add the following lines: - -> @reboot RUBY_GC_MALLOC_LIMIT=90000000 RAILS_ROOT=/var/www/discourse RAILS_ENV=production NUM_WEBS=2 /home/discourse/.rvm/bin/bootup_bluepill --no-privileged -c ~/.bluepill load /var/www/discourse/config/discourse.pill - -> ## Log rotation setup - -> # Disabled for now - log rotation isn't *quite* complete -> #0 0 * * * /usr/sbin/logrotate /var/www/discourse/config/logrotate.conf - -> ## Email setup - -> IMPORTANT: Discourse relies heavily on email. If your email configuration is not correct, you will effectively have a broken forum. -> Please, head over to our [Mail Setup Guide](https://github.com/discourse/discourse/blob/master/docs/INSTALL-email.md) to find out more information on how to properly setup emails. - -> Congratulations! You've got Discourse installed and running! - -> ## Administrator account - -> Now make yourself an administrator account. Browse to your Discourse instance -> and create an account by logging in normally, then run the commands: - -> # Run these commands as the discourse user -> cd /var/www/discourse -> RAILS_ENV=production bundle exec rails c - -> # Administratorize yourself: -> # (in rails console) -> > me = User.find_by_username_or_email('myemailaddress@me.com') -> > me.activate # use this in case you haven't configured your mail server and therefore can't receive the activation mail. -> > me.admin = true -> > me.save - -> # Mark yourself as the 'system user': -> # (in rails console) -> > SiteSetting.site_contact_username = me.username - -> At this point we recommend you start going through the various items in the -> [Discourse Admin Quick Start Guide](https://github.com/discourse/discourse/wiki/The-Discourse-Admin-Quick-Start-Guide) -> to further prepare your site for users. - -> ## Site localization - -> Custom assets such as images should be placed somewhere under: - -> /var/www/discourse/public/ - -> For example, create a `local` directory and place it into: - -> /var/www/discourse/public/uploads/local/michael.png - -> The corresponding site setting is: - -> logo_small_url: /uploads/local/michael.png - -> ## Updating Discourse - -> # Run these commands as the discourse user -> bluepill stop -> bluepill quit -> # Back up your install -> DATESTAMP=$(TZ=UTC date +%F-%T) -> pg_dump --no-owner --clean discourse_prod | gzip -c > ~/discourse-db-$DATESTAMP.sql.gz -> tar cfz ~/discourse-dir-$DATESTAMP.tar.gz -C /var/www discourse -> # get the latest Discourse code -> cd /var/www/discourse -> git checkout master -> git pull -> git fetch --tags -> # To run on the latest numbered release instead of bleeding-edge: -> #git checkout latest-release -> # -> # Follow the section below titled: -> # "Check sample configuration files for new settings" -> # -> bundle install --without test --deployment -> RUBY_GC_MALLOC_LIMIT=90000000 RAILS_ENV=production bundle exec rake db:migrate -> RUBY_GC_MALLOC_LIMIT=90000000 RAILS_ENV=production bundle exec rake assets:precompile -> # restart bluepill -> crontab -l -> # Here, run the command to start bluepill. -> # Get it from the crontab output above. - -> ### Check sample configuration files for new settings - -> Check the sample configuration files provided in the repo with the ones being used for additional recommended settings and merge those in: - -> # Run these commands as the discourse user -> cd /var/www/discourse -> diff -u config/discourse_defaults.conf config/discourse.conf - -> #### Example 1 - -> $ diff -u config/discourse.pill.sample config/discourse.pill -> --- config/discourse.pill.sample 2013-07-15 17:38:06.501507001 +0000 -> +++ config/discourse.pill 2013-07-05 06:38:27.133506896 +0000 -> @@ -46,7 +46,7 @@ - -> app.working_dir = rails_root -> sockdir = "#{rails_root}/tmp/sockets" -> - File.directory? sockdir or FileUtils.mkdir_p sockdir -> + File.directory? sockdir or Dir.mkdir sockdir -> num_webs.times do |i| -> app.process("thin-#{i}") do |process| - -> This change reflects us switching to using `FileUtils.mkdir_p` instead of `Dir.mkdir`. - -> #### Example 2 - -> $ diff -u config/nginx.sample.conf /etc/nginx/conf.d/discourse.conf -> --- config/nginx.sample.conf 2013-07-15 17:38:06.521507000 +0000 -> +++ /etc/nginx/conf.d/discourse.conf 2013-07-15 17:52:46.649507024 +0000 -> @@ -12,17 +12,18 @@ -> gzip_min_length 1000; -> gzip_types application/json text/css application/x-javascript; - -> - server_name enter.your.web.hostname.here; -> + server_name webtier.discourse.org; - -> sendfile on; - -> keepalive_timeout 65; -> - client_max_body_size 2m; -> location / { -> root /home/discourse/discourse/public; - -> This change reflects a change in placeholder information plus (importantly) -> adding the `client_max_body_size 2m;` directive to the nginx configuration. -> This change should also be made to your production file. - -> ## Security - -> We take security very seriously at Discourse, and all our code is 100% open source and peer reviewed. -> Please read [our security guide](https://github.com/discourse/discourse/blob/master/docs/SECURITY.md) for an overview of security measures in Discourse. From 20739b7fba7af97037596e574e30cf16e7dfca7c Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 18 Sep 2015 01:25:09 -0700 Subject: [PATCH 1381/1435] Delete INSTALL-email.md --- docs/INSTALL-email.md | 162 ------------------------------------------ 1 file changed, 162 deletions(-) delete mode 100644 docs/INSTALL-email.md diff --git a/docs/INSTALL-email.md b/docs/INSTALL-email.md deleted file mode 100644 index 58d1202ba6..0000000000 --- a/docs/INSTALL-email.md +++ /dev/null @@ -1,162 +0,0 @@ -> # Warning: This Guide is Deprecated -> We only support Docker based installs now. Please see [our **official install guide**](https://github.com/discourse/discourse/blob/master/docs/INSTALL.md) for supported install instructions. - -# Discourse Mail Setup Guide - -After following INSTALL-ubuntu.md your mailer settings should still be set. - -Out-of-the-box Discourse is configured to deliver mail locally via sendmail. -That's great. Leave that there as we're going to try to get the mail to postfix -ASAP so postfix do it's job and process the mail for delivery. - -## Email is IMPORTANT - -Email notifications are core to the Discourse experience. We want your users to receive notifications as soon as possible so they can contribute to the conversation. - -If sending email isn't something to which you want to devote your time, don't -worry about it. There are [companies](http://mandrill.com/) that dedicate -theirs to doing one thing very well - ensuring that mail to your users gets -delivered. - -## Sending Email Through GMail - -Don't do it! GMail is not intended for sending out bulk notifications. Your email setup [will break](http://webapps.stackexchange.com/q/44768/12456). - -## Sending Email Through Mandrill - -### Create an account -We're going to use [Mandrill](http://mandrill.com/) as our email delivery -provider. - -![mandrill email signup](https://raw.github.com/discourse/discourse-docimages/master/email/email%20-%20mandrill%20signup.png) - -1. Create an account at http://mandrill.com/ (click on 'SIGN UP') - -1. I filled out the 'Tell Us A Little About Yourself' survey. They are -providing us a free service, after all! - -### Create an API key -I'm pleased with Mandrill's setup - this is the Right Way to do things. - -![mandrill email signup](https://raw.github.com/discourse/discourse-docimages/master/email/email%20-%20mandrill%20getsmtpcreds.png) - -1. Click 'Get SMTP Credentials' - -![mandrill email signup](https://raw.github.com/discourse/discourse-docimages/master/email/email%20-%20mandrill%20addapikey.png) - -1. Note that you can use 'any valid API key' as your password. Click '+ Add API Key' to create one. - -![mandrill email signup](https://raw.github.com/discourse/discourse-docimages/master/email/email%20-%20mandrill%20editapikey1.png) - -1. Click 'Edit' to document for what we'll be using this key. - -![mandrill email signup](https://raw.github.com/discourse/discourse-docimages/master/email/email%20-%20mandrill%20editapikey2.png) - -1. Since we'll only be using this key for sending email and *nothing else*, check 'Only Allow This Key To Use Certain API Calls' and select only Messages / Send and Messages/ Send-Raw. Send-Raw must be selected or Discourse won't be able to send email. - - -![mandrill email signup](https://raw.github.com/discourse/discourse-docimages/master/email/email%20-%20mandrill%20editapikey3.png) - -1. Optionally, restrict this key to the public static IP address of your server. - -1. Click 'Save' - -### Configure Postfix for Mandrill - -Thank you Mandrill for providing an excellent [guide on configuring Postfix to use Mandrill](http://help.mandrill.com/entries/23060367-Can-I-configure-Postfix-to-send-through-Mandrill-). - -Additional notes on this document: - -* Ubuntu has an `/etc/postfix/sasl` directory. Create a password file in there. - -* Make sure you put your **API KEY** into this password file, not your **ACCOUNT PASSWORD** - -* You may already have configured a `relayhost` earlier in the installation. If this machine is sending out ANY emails other than Discourse-generated notifications, follow the instructions in 'Relay only certain emails through Mandrill'. - -After configuring postfix as per Mandrill's instructions, reload postfix with `sudo postfix reload`. - -### Send test email - -![discourse admin setting](https://raw.github.com/discourse/discourse-docimages/master/email/email%20-%20discourse%20admin.png) - -Now we send a test email. Login to your Discourse installation and click on the ≡ (aka congruence/hamburger/etc), then 'Admin'. - -![discourse admin setting](https://raw.github.com/discourse/discourse-docimages/master/email/email%20-%20discourse%20emailtest.png) - -Click on `Email`, `Settings`, then type your email address into the test box and click `send test email`. - -Within moments, you should have email in your Inbox. - -### OH NOES I DIDN'T GET MY EMAIL TEST! - -Follow the trail. First of all, did the email get to Postfix? Check `/var/log/mail.log`: - - Jun 24 01:24:59 discoursetest postfix/pickup[25387]: 7CBF280294C: uid=1001 from= - Jun 24 01:24:59 discoursetest postfix/cleanup[25829]: 7CBF280294C: message-id=<51c7d82b6f878_8ef3d7802c10139@discoursetest.mail> - Jun 24 01:24:59 discoursetest postfix/qmgr[25386]: 7CBF280294C: from=, size=5884, nrcpt=1 (queue active) - -Looks good! Wait, why is the email coming *from* `info@discourse.org`? That's a -problem we'll fix below. - - Jun 24 01:25:04 discoursetest postfix/smtp[25831]: 7CBF280294C: SASL authentication failed; server smtp.mandrillapp.com[54.235.146.179] said: 435 4.7.8 Error: authentication failed: - Jun 24 01:25:10 discoursetest postfix/smtp[25831]: 7CBF280294C: SASL authentication failed; server smtp.mandrillapp.com[54.234.14.176] said: 435 4.7.8 Error: authentication failed: - Jun 24 01:25:13 discoursetest postfix/smtp[25831]: 7CBF280294C: SASL authentication failed; server smtp.mandrillapp.com[50.16.10.62] said: 435 4.7.8 Error: authentication failed: - Jun 24 01:25:20 discoursetest postfix/smtp[25831]: 7CBF280294C: to=, relay=smtp.mandrillapp.com[54.235.146.152]:25, delay=21, delays=0.07/0.01/21/0, dsn=4.7.8, status=deferred (SASL authentication failed; server smtp.mandrillapp.com[54.235.146.152] said: 435 4.7.8 Error: authentication failed: ) - -The above errors are caused by using an incorrect API key in your sasl passwords file. Fix that (edit `/etc/postfix/sasl/passwd`, run `sudo postmap` on it, then `postqueue -f` to restart the queue). - - Jun 24 01:30:30 discoursetest postfix/smtp[25861]: table hash:/etc/postfix/sasl/passwd(0,lock|fold_fix) has changed -- restarting - Jun 24 01:30:31 discoursetest postfix/smtp[25872]: 7CBF280294C: to=, relay=smtp.mandrillapp.com[54.234.14.176]:25, delay=332, delays=331/0.01/1.2/0.17, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as C515B6380D3) - -That's better! Our test message made it to Mandrill. Let's check Outbound Activity in Mandrill: - - -![mandrill email confirmation](https://raw.github.com/discourse/discourse-docimages/master/email/email%20-%20mandrill%20emailconfirm.png) - -If you see this, the email was accepted by Mandrill and delivered to the -destination. You need to check your spam filter now. - -If you don't see anything in Mandrill, ensure that the API key is enabled for -'Send-Raw' permission. Mandrill appears to silently drop the email if that's -not set. - -### Configure notification email addresses - -Login to Discourse, go to the Admin page and select 'Settings'. - -Filter with the string 'system'. - -* Ensure that `site_contact_username` is set to an email address for an appropriate "owner" of the site -* Set `notification_email` to 'noreply@', 'nobody@' as appropriate. - -Filter with the string 'contact_email' - -* Ensure `contact_email` is set appropriately. - -### SPF and DKIM records - -Login to Mandrill and click on ⚙ (Settings)-> Sending Domains - -If your domain isn't listed, add it. It'll probably show this: - -![mandrill missing dkim settings](https://raw.github.com/discourse/discourse-docimages/master/email/email%20-%20mandrill%20missingdkim.png) - -Click 'View DKIM/SPF setup instructions'. - -Follow the instructions there. - -When DNS is properly configured, you should be able to click on 'Test DNS Settings' and Mandrill will confirm they are setup properly: - -![mandrill good dkim settings](https://raw.github.com/discourse/discourse-docimages/master/email/email%20-%20mandrill%20gooddkim.png) - -### Mandrill Options - -Login to Mandrill and click on ⚙ (Settings)-> Sending Options - -* 'Track Clicks' is enabled by default. This rewrites links in email messages to bounce off the mandrillapp.com domain for click tracking. Disable it here if you don't want that: - -![mandrill rewriting emails](https://raw.github.com/discourse/discourse-docimages/master/email/email%20-%20mandrill%20rewriting.png) - -* If you do use it, setting up a 'Tracking Domain' is a very good idea to avoid erroneous scam warnings: - -![mandrill tbird warning](https://raw.github.com/discourse/discourse-docimages/master/email/email%20-%20mandrill%20tbirdwarning.png) From cc622f206ef6d2d1baf3404a99181f9d6ac6033e Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 18 Sep 2015 01:27:47 -0700 Subject: [PATCH 1382/1435] Delete install-HEROKU.md --- docs/install-HEROKU.md | 363 ----------------------------------------- 1 file changed, 363 deletions(-) delete mode 100644 docs/install-HEROKU.md diff --git a/docs/install-HEROKU.md b/docs/install-HEROKU.md deleted file mode 100644 index d7c5c8b42e..0000000000 --- a/docs/install-HEROKU.md +++ /dev/null @@ -1,363 +0,0 @@ -# Warning: This Guide is Deprecated -We only support Docker based installs now. Please see [our **official install guide**](https://github.com/discourse/discourse/blob/master/docs/INSTALL.md) for supported install instructions. - ------ - -# Basic Heroku deployment - -This guide takes you through the steps for deploying Discourse to the [Heroku](http://www.heroku.com/) cloud application platform. If you're unfamiliar with Heroku, [read this first](https://devcenter.heroku.com/articles/quickstart). The basic deployment of Discourse requires several services that will cost you money. In addition to the [750 free Dyno hours](https://devcenter.heroku.com/articles/usage-and-billing) provided by Heroku, the application requires one additional process to be running for the Sidekiq queue ($34 monthly), and a Redis database plan that supports a minimum of 2 databases (average $10 monthly). - -For details on how to reduce the monthly cost of your application, see [Advanced Heroku deployment](#advanced-heroku-deployment). - -## Clone Discourse - -If you haven't already, download Discourse and create a new branch for your Heroku configuration. - - git clone git@github.com:discourse/discourse.git - cd discourse - git checkout -b heroku - - -## Deploy to Heroku - -1. Create the heroku app. This automatically creates a git remote called heroku. - - heroku create your-app-name - -1. Add postgres addon (command below adds the free dev version) - - heroku addons:add heroku-postgresql - -1. Add a suitable Redis provider from [Heroku add-ons](https://addons.heroku.com/), (this service will cost you money). - - heroku addons:add openredis:micro - -1. Configure your app to connect to redis - - heroku config:get OPENREDIS_URL - heroku config:set REDIS_PROVIDER_URL= - - heroku config:set DISCOURSE_REDIS_HOST= - heroku config:set DISCOURSE_REDIS_PORT= - heroku config:set DISCOURSE_REDIS_PASSWORD= - heroku config:set DISCOURSE_REDIS_DB= - -1. Run bundler - - bundle install - -1. Generate a secret token in the terminal. - - rake secret - -1. Push the secret to the stored heroku environment variables, this will now be available to your app globally. - - heroku config:add SECRET_TOKEN= - -1. Precompile assets. - - There are two options for precompilation. Either precompile locally, **before each deploy** or enable [Heroku's experimental user-env-compile](https://devcenter.heroku.com/articles/labs-user-env-compile) feature and Heroku will precompile your assets for you. - - 1. **Option 1:** Let Heroku compile your assets - - This is the default for new apps, and seems to work fine. - - 2. **Option 2:** Precompile locally. - - bundle exec rake assets:precompile - - **Notice:** We don't use Foreman to start precompilation, as this would precompile in the development environment. Instead, rake assets:precompile runs in the production environment by default, as it should. - - If Rails complains that the SECRET_TOKEN is not set, you can pass this to the environment by prefixing it to the rake method call. - - SECRET_TOKEN=5310bc16ef6ecfd0... bundle exec rake assets:precompile - - **Tip:** OSX/Linux users can set/unset environment variables in their shell. - - # Set var - export SECRET_TOKEN=5310bc16ef6ecfd0... - # Unset var - unset SECRET_TOKEN - - When precompiling locally make sure to alter the .gitignore file to allow the public/assets folder into version control. - - *.gitignore* - - ```diff - - public/assets - + # public/assets - ``` - - Also, you'll need to add a commit to get the precompiled assets onto Heroku. - - ```bash - git add public/assets - git push heroku heroku:master - ``` - -1. Tell rails to serve your compiled assets - - heroku config:set DISCOURSE_SERVE_STATIC_ASSETS=true - -1. Push your heroku branch to Heroku. - - git push heroku heroku:master - -1. Migrate and seed the database. - - heroku run rake db:migrate db:seed_fu - - You should now be able to visit your app at `http://.herokuapp.com` - -## Configure the deployed application - -1. Log into the app, using your preferred auth provider. - -2. Connect to the Heroku console to make the first user an Admin. - - heroku run console - -3. Enter the following commands. -```ruby - u = User.first - u.admin = true - u.approved = true - u.save -``` - -4. In Discourse admin settings, set `force_hostname` to your applications Heroku domain. - - This step is required for Discourse to properly form links sent with account confirmation emails and password resets. The auto detected application url would point to an Amazon AWS instance. - - Since you can't log in yet, you can set `force_hostname` in the console. -```ruby - SiteSetting.create(:name => 'force_hostname', :data_type =>1, :value=>'yourappnamehere.herokuapp.com') -``` - -5. Start Sidekiq. - - In the [Heroku dashboard](https://dashboard.heroku.com/apps), select your app and you will see the separate processes that have been created for your application under Resources. You will only need to start the sidekiq process for your application to run properly. The worker process has been generated as a Rails default and can be ignored. As you can see **the Sidekiq process costs $34 monthly** to run. If you want to reduce this cost, check out [Advanced Heroku deployment](#advanced-heroku-deployment). - - Click on the check-box next to the Sidekiq process and click Apply Changes - - ##### Your Discourse application should now be functional. However, you will still need to [configure mail](#email) functionality and file storage for uploaded images. For some examples of doing this within Heroku, see [Heroku add-on examples](#heroku-add-on-examples). - -6. [Optional] Increase Garbage collection limit - -When you start up your app, the admin dashboard will complain "Your server is using default ruby garbage collection parameters" - -``` - heroku config:add RUBY_GC_MALLOC_LIMIT=90000000 -``` - -## Running the application locally - -Using Foreman to start the application allows you to mimic the way the application is started on Heroku. It loads environment variables via the .env file and instantiates the application using the Procfile. In the .env sample file, we have set `RAILS_ENV='development'`, this makes the Rails environment variable available globally, and is required when starting this application using Foreman. - -Create a .env file from the sample. - - cp .env.sample .env - -### Foreman commands: - - -##### Create the database - - foreman run rake db:create - -##### Migrate and seed the database - - foreman run rake db:migrate db:seed_fu - -##### Start the application using Foreman - - foreman run rails server - -##### Use Rails console, with pry - - foreman run rails console - -##### Prepare the test database - - foreman run rake db:test:prepare - -##### Run tests - - foreman run rake autospec - - -# Heroku add-on examples - -## Email - -##### Mandrill example - -1. Add the [Mandrill by MailChimp](https://devcenter.heroku.com/articles/mandrill) add-on from the [Heroku add-ons](https://addons.heroku.com/) page, or install from the command line using: - - heroku addons:add mandrill:starter - -2. Configure the smtp settings in the production environment config file. - - *config/environments/production.rb* - - ```ruby - - config.action_mailer.delivery_method = :sendmail - - config.action_mailer.sendmail_settings = {arguments: '-i'} - - + config.action_mailer.delivery_method = :smtp - + config.action_mailer.smtp_settings = { - + :port => '587', - + :address => 'smtp.mandrillapp.com', - + :user_name => ENV['MANDRILL_USERNAME'], - + :password => ENV['MANDRILL_APIKEY'], - + :domain => 'heroku.com', - + :authentication => :plain - + } - ``` - -## S3 (for file uploads) - -You can't upload files to heroku, you need to use a service like S3. - -Here are the [instructions for setting up S3](https://meta.discourse.org/t/setting-up-file-and-image-uploads-to-s3/7229/23) - -You can run this from ```heroku run console``` to test if everything is configured correctly. - -```ruby -bucket_name = Discourse.store.send :s3_bucket -bucket = Discourse.store.send :get_or_create_directory, bucket_name - -f = File.open('README.md') -bucket = Discourse.store.send :upload, f, 'test.txt' -``` - -@adamloving: I think there is still a file size issue. I was testing with a 2MB file that wasn't big enough to trigger the "file too big" Discourse error message, but didn't make it to S3 either. - - -## Load Testing - -##### Blitz example - -1. Add the [Blitz](https://addons.heroku.com/blitz) add-on from the [Heroku add-ons](https://addons.heroku.com/) page, or install from the command line using: - - heroku addons:add blitz:250 - -You can now run basic load tests against your instalation. Here's an example query with the rush of users scaling from 1 to 250 over 60 seconds. The timeout (-T) is set to 30 seconds, as after this Heroku will kill a process and return an error anyway. - - -p 1-250:60 -T 30000 http://YOUR-APP-NAME.herokuapp.com/ - -##### loader.io example - -1. Add the [loader.io](https://addons.heroku.com/loaderio) add-on from the [Heroku add-ons](https://addons.heroku.com/) page, or install from the command line using: - - heroku addons:add loaderio:test - -loader.io is still in beta, so you mileage may vary, but the tests are free for now. -They currently require you verify your domain. A simple way to do this is to add a hard coded static route to `config.routes.rb` using the loaderio verification key. You'll see the key the first time you try to run a load test. - -*config/routes.rb* - -```diff -Discourse::Application.routes.draw do -+ match "/loaderio-xxxxxxxxxxxxxxxxxxxx", :to => proc {|env| [200, {}, ["/loaderio-xxxxxxxxxxxxxxxxxxxx"]] } - ... -end -``` - -# Advanced Heroku deployment - -## Autoscaler - -Adding the [Autoscaler Gem](https://github.com/JustinLove/autoscaler) can help you better manage the running cost of your application by scaling down the Sidekiq worker process when not in use. This could save up to $34 per month depending on your usage levels. - -##### Whilst this Gem has the potential to save you money, it in no way guarantees it. Use of this Gem should be combined with careful monitoring of your applications processes and usage alerts where necessary. - -1. Push your Heroku API key and app name to Heroku. - - heroku config:add HEROKU_API_KEY= HEROKU_APP= - -2. Add the Autoscaler Gem to the Gemfile. - - *Gemfile* - - ```ruby - gem 'autoscaler', require: false - ``` -3. Modify the Sidekiq config file to use the Autoscaler middleware in production. - - - *config/initializers/sidekiq.rb* - - ```ruby - sidekiq_redis = { url: $redis.url, namespace: 'sidekiq' } - - if Rails.env.production? - - require 'autoscaler/sidekiq' - require 'autoscaler/heroku_scaler' - - Sidekiq.configure_server do |config| - config.redis = sidekiq_redis - config.server_middleware do |chain| - chain.add(Autoscaler::Sidekiq::Server, Autoscaler::HerokuScaler.new('sidekiq'), 60) - end - end - - - Sidekiq.configure_client do |config| - config.redis = sidekiq_redis - config.client_middleware do |chain| - chain.add Autoscaler::Sidekiq::Client, 'default' => Autoscaler::HerokuScaler.new('sidekiq') - end - end - - else - - Sidekiq.configure_server { |config| config.redis = sidekiq_redis } - Sidekiq.configure_client { |config| config.redis = sidekiq_redis } - - end - - ``` - -## S3 CDN - -Heroku Cedar stack does not support Nginx as a caching layer, so you may want to host your static assets in a CDN so you're not hitting your rails app for every asset request. - -This can be done simply using the [Asset Sync](https://github.com/rumblelabs/asset_sync) gem. - -You'll need an Amazon S3 account set up with a bucket configured with your app name (appname-assets), and a separate user with write access to that bucket. You can create the new user in Account > Security Credentials. See [AWS best practices](http://docs.aws.amazon.com/IAM/latest/UserGuide/IAMBestPractices.html) for more details. - -**Caveat:** This example relies on the app being deployed using the `heroku labs:enable user-env-compile` method detailed above. For instructions on manual compilation, please refer to the [Asset Sync](https://github.com/rumblelabs/asset_sync) gem readme. - -1. Add the Asset Sync Gem to the Gemfile under assets. - - *Gemfile* - - ```diff - group :assets do - ... - + gem 'asset_sync' - end - ``` - -2. Update production.rb to use the asset host. - - *config/environments/production.rb* - - ```diff - - # config.action_controller.asset_host = "http://YOUR_CDN_HERE" - + config.action_controller.asset_host = "//#{ENV['FOG_DIRECTORY']}.s3.amazonaws.com" - ``` - -3. Get the access keys that were created for the new user and push the S3 configs to Heroku. - - heroku config:set FOG_PROVIDER=AWS AWS_ACCESS_KEY_ID=xxx AWS_SECRET_ACCESS_KEY=yyy FOG_DIRECTORY=appname-assets - -4. Push the Gzip config setting to Heroku. This tells asset sync to upload Gzipped files where available. - - heroku config:add ASSET_SYNC_GZIP_COMPRESSION=true - -Now commit your changes to Git and push to Heroku. - -If you open Chrome's Inspector, click on Network and refresh the page, your assets should now be showing an amazonaws.com url. Please refer to the [Asset Sync](https://github.com/rumblelabs/asset_sync) gem readme for more configuration options, or to use another CDN such as AWS CloudFront for better performance. From f37f6f7673f850ca3dfc0d6a666403d06ada6ed0 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 18 Sep 2015 02:44:13 -0700 Subject: [PATCH 1383/1435] minor embed style tweak --- app/assets/stylesheets/embed.css.scss | 4 ++++ config/locales/server.en.yml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/embed.css.scss b/app/assets/stylesheets/embed.css.scss index a225c31be7..73dc8105e8 100644 --- a/app/assets/stylesheets/embed.css.scss +++ b/app/assets/stylesheets/embed.css.scss @@ -119,6 +119,10 @@ header.discourse { padding-bottom: 8px; font-size: 1.286em; border-bottom: 3px solid #ddd; + + .button { + float:right; + } } footer { diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index e21f642ef9..1a91ce928d 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -719,7 +719,7 @@ en: s3_config_warning: 'The server is configured to upload files to s3, but at least one the following setting is not set: s3_access_key_id, s3_secret_access_key or s3_upload_bucket. Go to
    the Site Settings and update the settings. See "How to set up image uploads to S3?" to learn more.' s3_backup_config_warning: 'The server is configured to upload backups to s3, but at least one the following setting is not set: s3_access_key_id, s3_secret_access_key or s3_backup_bucket. Go to the Site Settings and update the settings. See "How to set up image uploads to S3?" to learn more.' image_magick_warning: 'The server is configured to create thumbnails of large images, but ImageMagick is not installed. Install ImageMagick using your favorite package manager or download the latest release.' - failing_emails_warning: 'There are %{num_failed_jobs} email jobs that failed. Check your config/discourse.conf file and ensure that the mail server settings are correct. See the failed jobs in Sidekiq.' + failing_emails_warning: 'There are %{num_failed_jobs} email jobs that failed. Check your app.yml and ensure that the mail server settings are correct. See the failed jobs in Sidekiq.' default_logo_warning: "Set the graphic logos for your site. Update logo_url, logo_small_url, and favicon_url in Site Settings." contact_email_missing: "Enter a site contact email address so you can be reached for urgent matters regarding your site. Update it in Site Settings." contact_email_invalid: "The site contact email is invalid. Update it in Site Settings." From 7abd4687e2d80956d97a006c3a8b2411e1823b72 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Fri, 18 Sep 2015 18:29:57 +0530 Subject: [PATCH 1384/1435] FIX: redirect to original URL when logging in via OAuth --- app/assets/javascripts/discourse/controllers/login.js.es6 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/controllers/login.js.es6 b/app/assets/javascripts/discourse/controllers/login.js.es6 index 824c3110f0..3297e1c051 100644 --- a/app/assets/javascripts/discourse/controllers/login.js.es6 +++ b/app/assets/javascripts/discourse/controllers/login.js.es6 @@ -184,7 +184,12 @@ export default Ember.Controller.extend(ModalFunctionality, { // Reload the page if we're authenticated if (options.authenticated) { - if (window.location.pathname === Discourse.getURL('/login')) { + const destinationUrl = $.cookie('destination_url'); + if (self.get('loginRequired') && destinationUrl) { + // redirect client to the original URL + $.cookie('destination_url', null); + window.location.href = destinationUrl; + } else if (window.location.pathname === Discourse.getURL('/login')) { window.location.pathname = Discourse.getURL('/'); } else { window.location.reload(); From ee2d5cb67cd03da8b7a806d2ee5bdffd661fe3b1 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 18 Sep 2015 11:16:57 -0400 Subject: [PATCH 1385/1435] Added links to plugin tutorials --- docs/PLUGINS.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/PLUGINS.md b/docs/PLUGINS.md index 854e855f21..9b0c34bb7e 100644 --- a/docs/PLUGINS.md +++ b/docs/PLUGINS.md @@ -3,6 +3,18 @@ If you just want to get some plugins for your Discourse instance, check out [the plugin category](https://meta.discourse.org/category/extensibility/plugin) at meta. This is the most up to date place for plugin discussion and listing. -# Discourse Plugin Architecture +# Discourse Plugin Tutorials + +* [Part One: Getting Started](https://meta.discourse.org/t/beginners-guide-to-creating-discourse-plugins/30515) + +* [Part Two: Plugin Outlets](https://meta.discourse.org/t/beginners-guide-to-creating-discourse-plugins-part-2-plugin-outlets/31001) + +* [Part Three: Custom Site Settings](https://meta.discourse.org/t/beginners-guide-to-creating-discourse-plugins-part-3-custom-settings/31115) + +* [Part Four: Git Setup](https://meta.discourse.org/t/beginners-guide-to-creating-discourse-plugins-part-4-git-setup/31272) + +* [Part Five: Admin Interfaces](https://meta.discourse.org/t/beginners-guide-to-creating-discourse-plugins-part-5-admin-interfaces/31761) + +* [Part Six: Acceptance Tests](https://meta.discourse.org/t/beginner-s-guide-to-creating-discourse-plugins-part-6-acceptance-tests/32619) + -**Note: This is a work in progress!** From 619d5b1bc1b23a8422a5f601de889c19c0eb959f Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Fri, 18 Sep 2015 18:21:27 +0200 Subject: [PATCH 1386/1435] FIX: Load fallback locales in Sidekiq jobs --- app/jobs/base.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/jobs/base.rb b/app/jobs/base.rb index 5441f26e87..a187dfb682 100644 --- a/app/jobs/base.rb +++ b/app/jobs/base.rb @@ -149,6 +149,7 @@ module Jobs begin RailsMultisite::ConnectionManagement.establish_connection(db: db) I18n.locale = SiteSetting.default_locale + I18n.fallbacks.ensure_loaded! begin execute(opts) rescue => e From c1a9e32b48e64c2d6adf7d2b1c9543d8a79f4f93 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 18 Sep 2015 12:48:43 -0400 Subject: [PATCH 1387/1435] FIX: When recovering a post, it should recreate user actions --- app/models/user_action_observer.rb | 4 +-- lib/post_destroyer.rb | 6 ++++ spec/components/post_destroyer_spec.rb | 44 ++++++++++++++++++-------- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/app/models/user_action_observer.rb b/app/models/user_action_observer.rb index 34e6fcabed..a98ab14320 100644 --- a/app/models/user_action_observer.rb +++ b/app/models/user_action_observer.rb @@ -8,7 +8,7 @@ class UserActionObserver < ActiveRecord::Observer when (model.is_a?(Topic)) log_topic(model) when (model.is_a?(Post)) - log_post(model) + UserActionObserver.log_post(model) end end @@ -43,7 +43,7 @@ class UserActionObserver < ActiveRecord::Observer end end - def log_post(model) + def self.log_post(model) # first post gets nada return if model.is_first_post? diff --git a/lib/post_destroyer.rb b/lib/post_destroyer.rb index 818f376635..394b6ccc43 100644 --- a/lib/post_destroyer.rb +++ b/lib/post_destroyer.rb @@ -58,6 +58,7 @@ class PostDestroyer topic = Topic.with_deleted.find @post.topic_id topic.recover! if @post.is_first_post? topic.update_statistics + recover_user_actions DiscourseEvent.trigger(:post_recovered, @post, @opts, @user) end @@ -166,6 +167,11 @@ class PostDestroyer end end + def recover_user_actions + # TODO: Use a trash concept for `user_actions` to avoid churn and simplify this? + UserActionObserver.log_post(@post) + end + def remove_associated_replies post_ids = PostReply.where(reply_id: @post.id).pluck(:post_id) diff --git a/spec/components/post_destroyer_spec.rb b/spec/components/post_destroyer_spec.rb index 53e8015cb4..a5a91c4c75 100644 --- a/spec/components/post_destroyer_spec.rb +++ b/spec/components/post_destroyer_spec.rb @@ -142,6 +142,28 @@ describe PostDestroyer do end end + describe "recovery and user actions" do + it "recreates user actions" do + reply = create_post(topic: post.topic) + author = reply.user + + post_action = author.user_actions.where(action_type: UserAction::REPLY, target_post_id: reply.id).first + expect(post_action).to be_present + + PostDestroyer.new(moderator, reply).destroy + + # User Action is removed + post_action = author.user_actions.where(action_type: UserAction::REPLY, target_post_id: reply.id).first + expect(post_action).to be_blank + + PostDestroyer.new(moderator, reply).recover + + # On recovery, the user action is recreated + post_action = author.user_actions.where(action_type: UserAction::REPLY, target_post_id: reply.id).first + expect(post_action).to be_present + end + end + describe 'basic destroying' do it "as the creator of the post, doesn't delete the post" do @@ -170,23 +192,19 @@ describe PostDestroyer do context "as a moderator" do it "deletes the post" do + author = post.user + + post_count = author.post_count + history_count = UserHistory.count + PostDestroyer.new(moderator, post).destroy + expect(post.deleted_at).to be_present expect(post.deleted_by).to eq(moderator) - end - it "updates the user's post_count" do - author = post.user - expect { - PostDestroyer.new(moderator, post).destroy - author.reload - }.to change { author.post_count }.by(-1) - end - - it "creates a new user history entry" do - expect { - PostDestroyer.new(moderator, post).destroy - }.to change { UserHistory.count}.by(1) + author.reload + expect(author.post_count).to eq(post_count - 1) + expect(UserHistory.count).to eq(history_count + 1) end end From e7af8d22399b3c53e998d9d29d75852840731b49 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 18 Sep 2015 13:11:42 -0400 Subject: [PATCH 1388/1435] FIX: Queued Posts should be ordered by `created_at` --- app/controllers/queued_posts_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/queued_posts_controller.rb b/app/controllers/queued_posts_controller.rb index bfa1391213..d13de97081 100644 --- a/app/controllers/queued_posts_controller.rb +++ b/app/controllers/queued_posts_controller.rb @@ -8,7 +8,7 @@ class QueuedPostsController < ApplicationController state = QueuedPost.states[(params[:state] || 'new').to_sym] state ||= QueuedPost.states[:new] - @queued_posts = QueuedPost.visible.where(state: state).includes(:topic, :user) + @queued_posts = QueuedPost.visible.where(state: state).includes(:topic, :user).order(:created_at) render_serialized(@queued_posts, QueuedPostSerializer, root: :queued_posts, From 40934e595a246d2ff1d8aabfe9bb361dc74e897a Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 18 Sep 2015 13:25:09 -0400 Subject: [PATCH 1389/1435] FIX: Some RSS feeds do unsafe redirects There are people who have RSS feeds set up that do HTTPS -> HTTP redirects which throw errors. Since RSS feeds are all configured by admins I think it's OK if they allow an unsafe redirect as the content is public anyway. This will reduce many server side errors. --- app/jobs/scheduled/poll_feed.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/scheduled/poll_feed.rb b/app/jobs/scheduled/poll_feed.rb index 920c2161a9..8c93910def 100644 --- a/app/jobs/scheduled/poll_feed.rb +++ b/app/jobs/scheduled/poll_feed.rb @@ -66,7 +66,7 @@ module Jobs private def rss - SimpleRSS.parse open(@feed_url) + SimpleRSS.parse open(@feed_url, allow_redirections: :all) end end From d062d64278841001cc9682ad6f87b1a513a66db9 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 18 Sep 2015 15:07:31 -0400 Subject: [PATCH 1390/1435] FIX: Sometimes posts can't be created. Don't raise errors in that case. --- app/models/post_action.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/post_action.rb b/app/models/post_action.rb index d1875b6e95..abbe3c5df2 100644 --- a/app/models/post_action.rb +++ b/app/models/post_action.rb @@ -238,7 +238,7 @@ SQL end end - PostCreator.new(user, opts).create.id + PostCreator.new(user, opts).create.try(:id) end def self.act(user, post, post_action_type_id, opts = {}) From 32fd5bc696ffc92e0f23b5b4c5bb878b00d9dcc0 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 18 Sep 2015 12:18:54 -0700 Subject: [PATCH 1391/1435] minor fixes to embed HTML --- app/views/embed/comments.html.erb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/embed/comments.html.erb b/app/views/embed/comments.html.erb index 72974665ff..93a702fcc9 100644 --- a/app/views/embed/comments.html.erb +++ b/app/views/embed/comments.html.erb @@ -16,7 +16,7 @@ <%- end %>
    - +

    @@ -36,7 +36,6 @@ <%- end %>

    -
    <%- end %> From 8489118811b511b1de0e0a68994fc99bc919b4f0 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 18 Sep 2015 15:36:40 -0400 Subject: [PATCH 1392/1435] FIX: Cropped embedding footer --- app/assets/stylesheets/embed.css.scss | 10 +++------- app/views/embed/comments.html.erb | 1 - 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/embed.css.scss b/app/assets/stylesheets/embed.css.scss index 73dc8105e8..acf5fc3031 100644 --- a/app/assets/stylesheets/embed.css.scss +++ b/app/assets/stylesheets/embed.css.scss @@ -119,7 +119,7 @@ header.discourse { padding-bottom: 8px; font-size: 1.286em; border-bottom: 3px solid #ddd; - + .button { float:right; } @@ -127,16 +127,12 @@ header.discourse { footer { font-size: 1.286em; - margin-top: 15px; - - .logo { - margin-top: -10px; - } - + margin-top: 0.5em; .button { color: white; padding: 6px 8px; background-color: #0088cc; + display: inline-block; } } diff --git a/app/views/embed/comments.html.erb b/app/views/embed/comments.html.erb index 93a702fcc9..8c68d54af0 100644 --- a/app/views/embed/comments.html.erb +++ b/app/views/embed/comments.html.erb @@ -35,7 +35,6 @@ <%- end %> <%- end %>
    - <%- end %> From c1d321369b6592c51b3826ea26d9938e9d0169f4 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 18 Sep 2015 15:37:56 -0400 Subject: [PATCH 1393/1435] We don't need to allow `track` and `meter` --- .../javascripts/defer/html-sanitizer-bundle.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/app/assets/javascripts/defer/html-sanitizer-bundle.js b/app/assets/javascripts/defer/html-sanitizer-bundle.js index 7e524149a3..7529153a85 100644 --- a/app/assets/javascripts/defer/html-sanitizer-bundle.js +++ b/app/assets/javascripts/defer/html-sanitizer-bundle.js @@ -867,11 +867,6 @@ html4.ATTRIBS = { 'legend::align': 0, 'li::type': 0, 'li::value': 0, - 'meter::high': 0, - 'meter::low': 0, - 'meter::max': 0, - 'meter::min': 0, - 'meter::value': 0, 'ol::compact': 0, 'ol::reversed': 0, 'ol::start': 0, @@ -880,10 +875,6 @@ html4.ATTRIBS = { 'pre::width': 0, 'q::cite': 1, 'source::type': 0, - 'track::default': 0, - 'track::kind': 0, - 'track::label': 0, - 'track::srclang': 0, 'ul::compact': 0, 'ul::type': 0, }; @@ -957,7 +948,6 @@ html4.ELEMENTS = { 'legend': 0, 'li': 1, 'link': 274, - 'meter': 0, 'nav': 0, 'nobr': 0, 'noembed': 276, @@ -990,7 +980,6 @@ html4.ELEMENTS = { 'time': 0, 'title': 280, 'tr': 273, - 'track': 2, 'tt': 0, 'u': 0, 'ul': 0, @@ -1068,7 +1057,6 @@ html4.ELEMENT_DOM_INTERFACES = { 'map': 'HTMLMapElement', 'menu': 'HTMLMenuElement', 'meta': 'HTMLMetaElement', - 'meter': 'HTMLMeterElement', 'nav': 'HTMLElement', 'nobr': 'HTMLElement', 'noembed': 'HTMLElement', @@ -1106,7 +1094,6 @@ html4.ELEMENT_DOM_INTERFACES = { 'time': 'HTMLTimeElement', 'title': 'HTMLTitleElement', 'tr': 'HTMLTableRowElement', - 'track': 'HTMLTrackElement', 'tt': 'HTMLElement', 'u': 'HTMLElement', 'ul': 'HTMLUListElement', From 49fc4571b03da44000b0f382e99fd263cb4d371b Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Sat, 19 Sep 2015 00:42:26 +0530 Subject: [PATCH 1394/1435] Update Translations --- config/locales/client.ar.yml | 72 +++- config/locales/client.it.yml | 119 ++++++- config/locales/client.ko.yml | 10 +- config/locales/client.tr_TR.yml | 8 +- config/locales/client.zh_CN.yml | 4 +- config/locales/server.ar.yml | 608 +++++++++++++++++++++++++++++++- config/locales/server.de.yml | 3 + config/locales/server.it.yml | 10 +- config/locales/server.ko.yml | 102 +++--- 9 files changed, 853 insertions(+), 83 deletions(-) diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index 11dc28c3e5..95a879dd0c 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -316,6 +316,7 @@ ar: saved: "تم الحفظ!" upload: "رفع" uploading: "يتم الرفع..." + uploading_filename: "تحديث {{filename}}..." uploaded: "اكتمل الرفع!" enable: "تمكين" disable: "تعطيل" @@ -418,6 +419,9 @@ ar: mods_and_admins: "فقط المدراء والمشرفون" members_mods_and_admins: "فقط اعضاء المجموعة والمشرفون والمدراء" everyone: "الكل" + trust_levels: + title: "مستوى الثقة يمنح تلقائيا للأعضاء عندما يضيفون:" + none: "لا شيء" user_action_groups: '1': "الإعجابات المعطاة" '2': "الإعجابات المستلمة" @@ -439,8 +443,12 @@ ar: category: "تصنيف" reorder: title: "إعادة ترتيب الفئات" + title_long: "إعادة تنظيم قائمة الفئة" + fix_order: "تثبيت الأماكن" + fix_order_tooltip: "ليس كل الفئات لديها رقم مكان فريد، ربما يسبب نتيجة غير متوقعة." save: "حفظ الترتيب" apply_all: "تطبيق" + position: "مكان" posts: "مشاركات" topics: "مواضيع" latest: "آخر" @@ -668,6 +676,7 @@ ar: title: "دعوة" user: "المستخدمين المدعويين" sent: "تم الإرسال" + none: "لا توجد دعوات معلقة لعرضها." truncated: "اظهار اوائل {{count}} المدعويين" redeemed: "دعوات مستخدمة" redeemed_tab: "محررة" @@ -687,6 +696,8 @@ ar: days_visited: "أيام الزيارة" account_age_days: "عمر العضوية بالأيام" create: "ارسال دعوة" + generate_link: "انسخ رابط الدعوة" + generated_link_message: '

    رابط الدعوة منح بنجاح!

    رابط الدعوة صالح فقط لعنوان البريد الإلكتروني هذا: %{invitedEmail}

    ' bulk_invite: none: "لم تقم بدعوة اي احد حتى الان. تستطيع ارسال دعوة , أو ارسال عدة دعوات عن طريقuploading a bulk invite file." text: "الدعوة من ملف" @@ -743,6 +754,7 @@ ar: read_only_mode: enabled: "وضع القراءة فقط مفعل. يمكنك إكمال تصفح الموقع لكن التفاعلات قد لا تعمل." login_disabled: "تسجيل الدخول معطل لأن الموقع في خالة القراءة فقط" + too_few_topics_notice: "دعونا نبدأ هذه المناقشة! يوجد حاليا %{currentTopics} / %{requiredTopics} مواضيع و %{currentPosts} / %{requiredPosts} مشاركات. الزائرون الجدد بحاجة لبعض المحادثات للقراءة والرد عليها." learn_more: "تعلم المزيد..." year: 'سنة' year_desc: 'المواضيع المكتوبة خلال 365 يوم الماضية' @@ -845,6 +857,7 @@ ar: composer: emoji: "تعبيرات: ابتسامة" add_warning: "هذا تحذير رسمي" + add_whisper: "هذا هو الهمس المرئي فقط للمشرفين." posting_not_on_topic: "أي موضوع تود الرد عليه؟" saving_draft_tip: "جار الحفظ..." saved_draft_tip: "تم الحفظ" @@ -871,7 +884,7 @@ ar: title_placeholder: "ما هو الموضوع المراد مناقشته في جملة واحدة ؟" edit_reason_placeholder: "لمذا تريد التعديل ؟" show_edit_reason: "(اضف سبب التعديل)" - reply_placeholder: "اكتب هنا . استعمل Markdown او BBCode أو HTML للتنسيق . قم بسحب الصورة أو لصقها لرفعها." + reply_placeholder: "أكتب هنا. استخدم Markdown, BBCode, أو HTML للتشكيل. اسحب أو الصق الصور." view_new_post: "الاطلاع على أحدث مشاركاتك" saving: "يتم الحفظ..." saved: "تم الحفظ" @@ -932,6 +945,20 @@ ar: moved_post: "

    {{username}} نقل{{description}}

    " linked: "

    {{username}} {{description}}

    " granted_badge: "

    استحق'{{description}}'

    " + alt: + mentioned: "مؤشرة بواسطة" + quoted: "مقتبسة بواسطة" + replied: "مجاب" + posted: "مشاركة بواسطة" + edited: "تم تعديل مشاركتك بواسطة" + liked: "تم الإعجاب بمشاركتك" + private_message: "رسالة خاصة من" + invited_to_private_message: "تمت الدعوة لرسالة خاصة من " + invited_to_topic: "تمت الدعوة لموضوع من " + invitee_accepted: "قبلت الدعوة بواسطة" + moved_post: "مشاركتك نقلت بواسطة" + linked: "رابط لمشاركتك" + granted_badge: "تم منح الشارة" popup: mentioned: '{{username}} أشار لك في "{{topic}}" - {{site_title}}' quoted: '{{username}} نقل لك في "{{topic}}" - {{site_title}}' @@ -949,11 +976,13 @@ ar: local_tip: "إختر صور من جهازك ." local_tip_with_attachments: "اضغط لاختيار صورة أو ملف من جهازك ({{authorized_extensions}})" hint: "(تستطيع أيضا أن تسحب و تفلت ملف أو صورة في المحرر لرفعه)" - hint_for_supported_browsers: "تستطيع أيضاً سحب و إسقاط الصور او الملفات من جهازك الى المحرر لرفعها" + hint_for_supported_browsers: "يمكنك أيضا سحبوإفلات أو لصق الصور إلى المحرر" uploading: "يتم الرفع" select_file: "تحديد ملف" image_link: "رابط ستشير له الصورة" search: + select_all: "أختر الكل" + clear_all: "إلغ إختيار الكل" title: "البحث في المواضيع أو الردود أو الأعضاء أو التصنيفات" no_results: "لم يتم العثور على نتائج للبحث" no_more_results: "لا يوجد نتائج إضافية ." @@ -965,6 +994,8 @@ ar: category: "البحث في التصنيف \"{{category}}\"" topic: "بحث في هذا الموضوع" private_messages: "البحث في الرسائل الخاصة" + hamburger_menu: "أذهب لقائمة موضوع أخر أو فئة" + new_item: "جديد" go_back: 'الرجوع' not_logged_in_user: 'صفحة المستخدم مع ملخص عن نشاطه و إعداداته' current_user: 'الذهاب إلى صفحتك الشخصية' @@ -1188,6 +1219,7 @@ ar: unpin: "ازالة هذا الموضوع من أعلى هذه الفئة {{categoryLink}}." unpin_until: "ازالة هذا الموضوع من أعلى فئة {{categoryLink}} أو إنتظر حتى %{until} " pin_note: "المستخدمون يستطعون إزالة تثبيت الموضوع بشكل خاص بهم." + pin_validation: "التاريخ مطلوب لتثبيت هذا الموضوع." already_pinned: zero: "ﻻيوجد مواضيع مثبتة في {{categoryLink}}." one: "المواضيع المثيتة حالياً في {{categoryLink}} : \n1." @@ -1285,6 +1317,7 @@ ar: action: "تغيير الطابع الزمني" invalid_timestamp: "الطابع الزمني لا يمكن أن يكون في المستقبل." error: "هناك خطأ في نغيير الطابع الزمني للموضوع." + instructions: "رجاء أختر الطابع الزمني الجديد للموضوع. المشاركات في الموضوع ستكون محدثة لنفس الوقت المختلف." multi_select: select: 'تحديد' selected: 'محدد ({{count}})' @@ -1372,6 +1405,7 @@ ar: no_value: "لا ، حافظ عليها" yes_value: "نعم متأكد." via_email: "وصلت هذه المشاركة من خلال الإيميل" + whisper: "هذه المشاركة همسة خاصة للمشرفين" wiki: about: "هذه المشاركة عبارة عن ويكي بمعنى أنها متاحة للمستخدمين العاديين لتحريرها ، " archetypes: @@ -1608,6 +1642,7 @@ ar: topic_template: "إطار الموضوع" delete: 'حذف الصنف' create: 'قسم جديد' + create_long: 'أنشئ فئة جديدة' save: 'حفظ القسم' slug: 'عنوان التصنيف/Slug' slug_placeholder: '(اختياري) خط تحت عنوان الموقع' @@ -1630,6 +1665,7 @@ ar: change_in_category_topic: "تعديل الوصف" already_used: 'هذا اللون تم استخدامه سابقا في تصنيف آخر' security: "الأمن" + special_warning: "تحذير: هذه الفئة هي فئة قبل التصنيف وإعدادات الحماية لا يمكن تعديلها. إذا لم تكن تريد استخدام هذه الفئة، احذفها بدلا من تطويعها لأغراض أخرى." images: "الصور" auto_close_label: "الإغلاق التلقائي للمواضيع بعد:" auto_close_units: "ساعات" @@ -1637,6 +1673,7 @@ ar: email_in_allow_strangers: "قبول بريد إلكتروني من مستخدمين لا يملكون حسابات" email_in_disabled: "إضافة مواضيع جديدة من خلال البريد الإلكتروني موقف في الوقت الحالي من خلال إعدادات الموقع. لتفعيل إضافة مواضيع جديدة من خلال البريد الإلكتروني," email_in_disabled_click: 'قم بتفعيل خيار "email in" في الإعدادات' + suppress_from_homepage: "كتم هذه الفئة من الصفحة الرئيسية" allow_badges_label: "السماح بالحصول على الشارات في هذا التصنيف" edit_permissions: "تعديل الصلاحيات" add_permission: "اضف صلاحية" @@ -1708,6 +1745,8 @@ ar: help: "هذا الموضوع مغلق, لن يتم قبول اي رد " archived: help: "هذا الموضوع مؤرشف,لن تستطيع أن تعدل عليه" + locked_and_archived: + help: "هذا الموضوع مغلق و مؤرشف; لم يعد يقبل ردود جديدة أو لا يمكن تغيره." unpinned: title: "غير مثبت" help: "هذا الموضوع غير مثبت بالنسبة لك, سيتم عرضه بالترتيب العادي" @@ -2027,7 +2066,6 @@ ar: enabled: "مفعل؟" is_enabled: "Y" not_enabled: "N" - cant_disable: "-" change_settings: "تغيير الاعدادت" change_settings_short: "الاعدادات" howto: "كيف اثبت اضافة؟" @@ -2199,6 +2237,7 @@ ar: sent_test: "اٌرسلت!" delivery_method: "طريقة التسليم" preview_digest: "معاينة الخلاصة." + preview_digest_desc: "معاينة محتوى رسائل البريد الإلكتروني الملخص المرسلة للأعضاء الغير متاحين." refresh: "تحديث" format: "التنسيق" html: "html" @@ -2638,7 +2677,9 @@ ar: image: "صورة" delete_confirm: "هل أنت متأكد من انك تريد حذف هذا :%{name}: الوجه التعبيري ؟" embedding: + get_started: "إذا أردت تضمين Discourse في موقع اخر، أبدأ بإضافة مضيف." confirm_delete: "هل انت متأكد من انك تريد حذف هذا المضيف ؟" + sample: "استخدم كود HTML التالي لموقعك لإنشاء وتضمين موضوع discourse. استبدل REPLACE_ME مع canonical URL لصفحة قمت بتضمينها فيه." title: "تضمين" host: "أسمع بالمضيفين" edit: "تعديل" @@ -2646,6 +2687,18 @@ ar: add_host: "أضف مضيف" settings: "تضمين إعدادات" feed_settings: "إعدادات التغذية " + feed_description: " توفير مغذي RSS/ATOM لموقعك سيطور قدرة Discourse على استيراد المحتوى الخاص بك." + crawling_settings: "إعدادات المتقدم ببطء." + crawling_description: "عندما ينشأ Discourse مواضيع لمشاركتك، إذا لم يتوفر مغذي RSS/ATOM سيحاول تحليل محتواك من HTML الخاص بك. أحيانا يمكن أن يكون تحديا استخراج محتواك، لذا نمنحك القدرة لتحديد قواعد CSS لجعل الاستخراج أسهل." + embed_by_username: "اسم العضو للموضوع المنشأ" + embed_post_limit: "أقصى عدد مشاركات مضمنة" + embed_username_key_from_feed: "مفتاح لسحب اسم عضو discourse من المغذي" + embed_truncate: "بتر المشاركات المضمنة" + embed_whitelist_selector: "منتقي CSS للعناصر التي تسمح في التضمينات." + embed_blacklist_selector: "منتقي CSS للعناصر التي حذفت من التضمينات." + feed_polling_enabled: "استورد المشاركات عبر RSS/ATOM" + feed_polling_url: "URL مغذي RSS/ATOM يتقدم ببطء." + save: "أحفظ الإعدادات المضمنة" permalink: title: "الرابط الثابت" url: "رابط" @@ -2676,6 +2729,8 @@ ar: categories: 'g, c الفئات' top: 'g, t الأعلى' bookmarks: 'g, b الإشارات المرجعية' + profile: 'g, p ملف التعريف' + messages: 'g, m الرسائل' navigation: title: 'المتصفح' jump: '# الذهاب الى الموضوع #' @@ -2687,12 +2742,14 @@ ar: title: 'التطبيقات' create: 'c انشاء موضوع جديد' notifications: 'n فتح الإشعارات' + hamburger_menu: '= فتح قائمة hamburger' user_profile_menu: 'pأفتح قائمة المستخدم' show_incoming_updated_topics: '. عرض المواضيع المحدثة' search: '/ البحث' help: 'pأفتح قائمة المستخدم' dismiss_new_posts: 'تجاهل جديد / المشاركات x, r' dismiss_topics: 'x, t رفض المواضيع' + log_out: 'shift+z shift+z تسجيل خروج' actions: title: 'إجراءات' bookmark_topic: 'f تبديل علامة مرجعية الموضوع' @@ -2832,6 +2889,15 @@ ar: reader: name: قارئ description: قراءة أكثر من 100 تعليق في الموضوع + popular_link: + name: رابط مشهور + description: شارك رابط خارجي بـ 50 نقرة على الأقل. + hot_link: + name: الرابط الساخن + description: شارك الرابط الخارجي بـ 300 نقرة على الأقل. + famous_link: + name: رابط مشهور + description: شارك الرابط الخارجي بـ 1000 نقرة على الأقل google_search: |

    ابحث في قوقل

    diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index aa3b25d03c..5062c55c3b 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -220,6 +220,7 @@ it: saved: "Salvato!" upload: "Carica" uploading: "In caricamento..." + uploading_filename: "Sto caricando {{filename}}..." uploaded: "Caricato!" enable: "Attiva" disable: "Disattiva" @@ -306,6 +307,9 @@ it: mods_and_admins: "Solo i moderatori e gli amministratori" members_mods_and_admins: "Solo i membri del gruppo, i moderatori e gli amministratori" everyone: "Tutti" + trust_levels: + title: "Livello di esperienza automaticamente assegnato ai membri quando vengono aggiunti:" + none: "Nessuno" user_action_groups: '1': "Mi piace - Assegnati" '2': "Mi piace - Ricevuti" @@ -325,6 +329,14 @@ it: all_subcategories: "tutte" no_subcategory: "nessuno" category: "Categoria" + reorder: + title: "Riordina Categorie" + title_long: "Riorganizza l'elenco di categorie" + fix_order: "Posizioni Fisse" + fix_order_tooltip: "Non tutte le categorie hanno un numero di posizionamento univoco, ciò potrebbe causare risultati inattesi." + save: "Salva Ordinamento" + apply_all: "Applica" + position: "Posizione" posts: "Messaggi" topics: "Argomenti" latest: "Più recenti" @@ -367,6 +379,7 @@ it: private_messages: "Messaggi" activity_stream: "Attività" preferences: "Opzioni" + expand_profile: "Espandi" bookmarks: "Segnalibri" bio: "Su di me" invited_by: "Invitato Da" @@ -380,8 +393,8 @@ it: perm_denied_expl: "Hai negato il permesso per le notifiche. Usa il browser per abilitare le notifiche, poi premi il bottone quando hai finito. (Per il desktop: l'icona più a sinistra sulla barra degli indirizzi. Mobile: 'Informazioni sul sito'.)" disable: "Disabilita Notifiche" currently_enabled: "(attualmente attivate)" - enable: "Abilita le notifiche" - currently_disabled: "(attualmente disabilitata)" + enable: "Abilita Notifiche" + currently_disabled: "(attualmente disabilitate)" each_browser_note: "Nota: devi modificare questa impostazione per ogni browser che utilizzi." dismiss_notifications: "Imposta tutti come Letti" dismiss_notifications_tooltip: "Imposta tutte le notifiche non lette come lette " @@ -456,7 +469,7 @@ it: upload_title: "Carica la tua foto" upload_picture: "Carica Immagine" image_is_not_a_square: "Attenzione: abbiamo ritagliato l'immagine; la larghezza e l'altezza non erano uguali." - cache_notice: "Hai cambiato correttamente la tua immagine di progilo ma, a causa del meccanismo di caching del browser, potrebbe essere necessario un po' di tempo prima di poterla visualizzare." + cache_notice: "Hai cambiato correttamente la tua immagine di profilo ma potrebbe volerci un po' prima di vederla apparire a causa della cache del browser." change_profile_background: title: "Sfondo Profilo" instructions: "Gli sfondi del profilo saranno centrati e avranno per difetto un'ampiezza di 850px." @@ -523,10 +536,10 @@ it: label: "Considera un argomento \"nuovo\" se" not_viewed: "non l'ho ancora letto" last_here: "è stato creato dopo la mia ultima visita" - after_1_day: "creati nell'ultima giornata" - after_2_days: "creati negli ultimi 2 giorni" - after_1_week: "creati nell'ultima settimana" - after_2_weeks: "creati nelle ultime 2 settimane" + after_1_day: "creato nell'ultimo giorno" + after_2_days: "creato negli ultimi 2 giorni" + after_1_week: "creato nell'ultima settimana" + after_2_weeks: "creato nelle ultime 2 settimane" auto_track_topics: "Segui automaticamente gli argomenti che leggo" auto_track_options: never: "mai" @@ -543,12 +556,15 @@ it: title: "Inviti" user: "Utente Invitato" sent: "Spedito" + none: "Non ci sono inviti in sospeso da visualizzare." truncated: "Mostro i primi {{count}} inviti." redeemed: "Inviti Accettati" redeemed_tab: "Riscattato" + redeemed_tab_with_count: "Riscattato ({{count}})" redeemed_at: "Accettato" pending: "Inviti in sospeso" - pending_tab: "Pendente" + pending_tab: "In sospeso" + pending_tab_with_count: "In sospeso ({{count}})" topics_entered: "Argomenti Letti" posts_read_count: "Messaggi Letti" expired: "L'invito è scaduto." @@ -560,6 +576,8 @@ it: days_visited: "Presenza (giorni)" account_age_days: "Età dell'utente in giorni" create: "Invia un Invito" + generate_link: "Copia il collegamento di invito" + generated_link_message: '

    Il link di invito è stato generato con successo!

    Il link sarà disponibile solo per l''email: %{invitedEmail}

    ' bulk_invite: none: "Non hai ancora invitato nessuno qui. Puoi inviare inviti individuali, o invitare un gruppo di persone caricando un file di invito di massa." text: "Invito di Massa da File" @@ -616,6 +634,7 @@ it: read_only_mode: enabled: "La modalità di sola lettura è attiva. Puoi continuare a navigare nel sito ma le interazioni potrebbero non funzionare." login_disabled: "L'accesso è disabilitato quando il sito è in modalità di sola lettura." + too_few_topics_notice: "Cominciamo a discutere! Ci sono al momento %{currentTopics} / %{requiredTopics} argomenti e %{currentPosts} / %{requiredPosts} messaggi. I nuovi visitatori vogliono qualche discussione da leggere e a cui rispondere." learn_more: "per saperne di più..." year: 'all''anno' year_desc: 'argomenti creati negli ultimi 365 giorni' @@ -714,6 +733,7 @@ it: composer: emoji: "Emoji :smile:" add_warning: "Questo è un avvertimento ufficiale." + add_whisper: "Questo è un sussurro visibile solo ai moderatori" posting_not_on_topic: "A quale argomento vuoi rispondere?" saving_draft_tip: "salvataggio..." saved_draft_tip: "salvato" @@ -740,6 +760,7 @@ it: title_placeholder: "In breve, di cosa tratta questo argomento?" edit_reason_placeholder: "perché stai scrivendo?" show_edit_reason: "(aggiungi motivo della modifica)" + reply_placeholder: "Scrivi qui. Per formattare il testo usa Markdown, BBCode o HTML. Trascina o incolla le immagini." view_new_post: "Visualizza il tuo nuovo messaggio." saving: "Salvataggio..." saved: "Salvato!" @@ -800,6 +821,20 @@ it: moved_post: "

    {{username}} ha spostato {{description}}

    " linked: "

    {{username}} {{description}}

    " granted_badge: "

    Guadagnato '{{description}}'

    " + alt: + mentioned: "Menzionato da" + quoted: "Citato da" + replied: "Risposto" + posted: "Messaggio da" + edited: "Modifica il tuo messaggio da" + liked: "Ha assegnato un \"Mi piace\" al tuo messaggio" + private_message: "Messaggio privato da" + invited_to_private_message: "Invitato ad una conversazione privata da" + invited_to_topic: "Invitato ad un argomento da" + invitee_accepted: "Invito accettato da" + moved_post: "Il tuo messaggio è stato spostato da" + linked: "Collegamento al tuo messaggio" + granted_badge: "Targhetta assegnata" popup: mentioned: '{{username}} ti ha menzionato in "{{topic}}" - {{site_title}}' quoted: '{{username}} ti ha citato in "{{topic}}" - {{site_title}}' @@ -814,13 +849,15 @@ it: from_the_web: "Dal web" remote_tip: "collegamento all'immagine" remote_tip_with_attachments: "collegamento ad un'immagine o a un file ({{authorized_extensions}})" - local_tip: "seleziona immagina dal tuo dispositivo" + local_tip: "seleziona immagini dal tuo dispositivo" local_tip_with_attachments: "seleziona immagini o file dal tuo dispositivo ({{authorized_extensions}})" hint: "(puoi anche trascinarle nell'editor per caricarle)" uploading: "In caricamento" select_file: "Seleziona File" image_link: "collegamento a cui la tua immagine punterà" search: + select_all: "Seleziona Tutto" + clear_all: "Cancella Tutto" title: "cerca argomenti, messaggi, utenti o categorie" no_results: "Nessun risultato trovato." no_more_results: "Nessun altro risultato trovato." @@ -832,6 +869,8 @@ it: category: "Cerca nella categoria \"{{category}}\"" topic: "Cerca in questo argomento" private_messages: "Cerca messaggi" + hamburger_menu: "vai ad un'altra lista di argomenti o categoria" + new_item: "nuovo" go_back: 'indietro' not_logged_in_user: 'pagina utente con riassunto delle attività correnti e delle impostazioni' current_user: 'vai alla pagina utente' @@ -882,6 +921,7 @@ it: topic: unsubscribe: stop_notifications: "Da ora riceverai meno notifiche su {{title}}" + change_notification_state: "Lo stato delle tue notifiche è" filter_to: "{{post_count}} suoi messaggi" create: 'Nuovo Argomento' create_long: 'Crea un nuovo Argomento' @@ -975,8 +1015,10 @@ it: title: "Seguito" description: "Per questo argomento apparirà un conteggio delle nuove risposte. Riceverai una notifica se qualcuno menziona il tuo @nome o ti risponde." regular: + title: "Normale" description: "Riceverai una notifica se qualcuno menziona il tuo @nome o ti risponde." regular_pm: + title: "Normale" description: "Riceverai una notifica se qualcuno menziona il tuo @nome o ti risponde." muted_pm: title: "Silenziato" @@ -1024,6 +1066,7 @@ it: unpin: "Rimuovi questo argomento dalla cima della categoria {{categoryLink}}." unpin_until: "Rimuovi questo argomento dalla cima della categoria {{categoryLink}} o attendi fino al %{until}." pin_note: "Gli utenti possono spuntare gli argomenti individualmente per loro stessi." + pin_validation: "È richiesta una data per appuntare questo argomento" already_pinned: zero: "Non ci sono argomenti puntati in {{categoryLink}}." one: "Argomenti attualmente puntati in {{categoryLink}}: 1." @@ -1103,8 +1146,9 @@ it: change_timestamp: title: "Cambia Timestamp" action: "cambia timestamp" - invalid_timestamp: "Il timestamp non può essere nel futuro" - error: "Errore durante la modifica del timestamp dell'argomento" + invalid_timestamp: "Il timestamp non può essere nel futuro." + error: "Errore durante la modifica del timestamp dell'argomento." + instructions: "Seleziona un nuovo timestamp per l'argomento. I messaggi nell'argomento saranno aggiornati senza modificare l'intervallo di tempo" multi_select: select: 'scegli' selected: 'selezionati ({{count}})' @@ -1168,6 +1212,7 @@ it: no_value: "No, mantienilo" yes_value: "Si, abbandona" via_email: "questo messaggio è arrivato via email" + whisper: "questo messaggio è un sussurro privato per i moderatori" wiki: about: "questo messaggio è una guida; gli utenti base possono modificarla" archetypes: @@ -1328,6 +1373,7 @@ it: topic_template: "Modello di argomento" delete: 'Elimina Categoria' create: 'Crea Categoria' + create_long: 'Crea una nuova categoria' save: 'Salva Categoria' slug: 'Abbreviazione di categoria' slug_placeholder: '(Facoltativo) parole-sillabate per URL' @@ -1357,6 +1403,7 @@ it: email_in_allow_strangers: "Accetta email da utenti anonimi senza alcun account" email_in_disabled: "Le Impostazioni Sito non permettono di creare nuovi argomenti via email. Per abilitare la creazione di argomenti via email," email_in_disabled_click: 'abilita l''impostazione "email entrante".' + suppress_from_homepage: "Elimina questa categoria dalla homepage" allow_badges_label: "Permetti che le targhette vengano assegnate in questa categoria" edit_permissions: "Modifica Permessi" add_permission: "Aggiungi Permesso" @@ -1424,6 +1471,8 @@ it: help: "Questo argomento è chiuso; non sono ammesse nuove risposte" archived: help: "Questo argomento è archiviato; è bloccato e non può essere modificato" + locked_and_archived: + help: "Questo argomento è chiuso e archiviato; non sono ammesse nuove risposte e non può essere modificato" unpinned: title: "Spuntato" help: "Questo argomento è per te spuntato; verrà mostrato con l'ordinamento di default" @@ -2214,6 +2263,7 @@ it: backups: "Backup" login: "Accesso" plugins: "Plugin" + user_preferences: "Preferenze utente" badges: title: Targhette new_badge: Nuova Targhetta @@ -2287,8 +2337,29 @@ it: name: "Nome" image: "Immagine" delete_confirm: "Sicuro di voler cancellare l'emoji :%{name}:?" + embedding: + get_started: "Se lo desideri, puoi incorporare Discourse in un altro sito web. Comincia aggiungo il suo nome host" + confirm_delete: "Sei sicuro di volere cancellare questo host?" + sample: "Utilizza il seguente codice HTML nel tuo sito per creare e incorporare gli argomenti di Discourse. Sostituisci REPLACE_ME con l'URL canonical della pagina in cui lo stai incorporando." + title: "Incorporamento" + host: "Host abilitati" + edit: "modifica" + add_host: "Aggiungi host" + settings: "Impostazioni di incorporamento" + feed_settings: "Impostazioni dei feed" + feed_description: "Aggiungendo un feed RSS/AROM al tuo sito, migliori l'importazione dei tuoi contenuti da parte di Discourse" + crawling_settings: "Impostazioni del crawler" + crawling_description: "Quando Discourse crea gli argomenti per i messaggi, se non è presente nessun feed RSS/ATOM, cercherà di estrarre il contenuto dal codice HTML. Il contenuto può risultate a volte ostico da estrarre e, per semplificare il processo, forniamo la possibilità di specificare le regole CSS." + embed_by_username: "Nome utente per la creazione dell'argomento" + embed_post_limit: "Numero massimo di messaggi da includere" + embed_truncate: "Abbrevia i messaggi incorporati" + embed_whitelist_selector: "Selettore CSS per gli elementi da includere negli embed" + embed_blacklist_selector: "Selettore CSS per gli elementi da rimuovere dagli embed" + feed_polling_enabled: "Importa gli articoli via RSS/ATOM" + feed_polling_url: "URL del feed RSS/ATOM da recuperare" + save: "Salva Impostazioni Inclusione" permalink: - title: "Link permanenti" + title: "Collegamenti permanenti" url: "URL" topic_id: "ID dell'argomento" topic_title: "Argomento" @@ -2297,7 +2368,7 @@ it: category_id: "ID della categoria" category_title: "Categoria" external_url: "URL esterna" - delete_confirm: Sei sicuro di voler cancellare questo link permanente? + delete_confirm: Sei sicuro di voler cancellare questo collegamento permanente? form: label: "Nuovo:" add: "Aggiungi" @@ -2317,6 +2388,8 @@ it: categories: 'g, c Categorie' top: 'g, t Alto' bookmarks: 'g, b Segnalibri' + profile: 'g, p Profilo' + messages: 'g, m Messaggi' navigation: title: 'Navigazione' jump: '# Vai al messaggio numero' @@ -2328,12 +2401,14 @@ it: title: 'Applicazione' create: 'c Crea un nuovo argomento' notifications: 'n Apri le notifiche' + hamburger_menu: '= Apri il menu hamburger' user_profile_menu: 'p Apri il menu del profilo utente' show_incoming_updated_topics: '. Mostra argomenti aggiornati' search: '/ Cerca' help: '? Apri l''aiuto per le scorciatoie da tastiera' dismiss_new_posts: 'x, r Chiudi Nuovo/Messaggi' dismiss_topics: 'x, t Chiudi Argomenti' + log_out: 'shift+z shift+z Esci' actions: title: 'Azioni' bookmark_topic: 'f Aggiungi/togli argomento nei segnalibri' @@ -2461,3 +2536,21 @@ it: reader: name: Lettore description: Letto tutti i messaggi in un argomento con più di 100 messaggi + popular_link: + name: Collegamento Popolare + description: Ha pubblicato un collegamento esterno con almeno 50 clic + hot_link: + name: Collegamento Caldo + description: Ha pubblicato un collegamento esterno con almeno 300 clic + famous_link: + name: Collegamento Famoso + description: Ha pubblicato un collegamento esterno con almeno 1000 clic + google_search: | +

    Cerca con Google

    +

    +

    +

    diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml index 95dce22062..9a0deba625 100644 --- a/config/locales/client.ko.yml +++ b/config/locales/client.ko.yml @@ -339,7 +339,7 @@ ko: preferences: "환경 설정" bookmarks: "북마크" bio: "내 소개" - invited_by: "에 의해 초대되었습니다." + invited_by: "(이)가 초대했습니다." trust_level: "신뢰도" notifications: "알림" desktop_notifications: @@ -352,7 +352,7 @@ ko: currently_enabled: "(현재 활성화됨)" enable: "알림 활성화" currently_disabled: "(현재 비활성화됨)" - each_browser_note: "노트: 사용하시는 모든 브라우저에서 이 세팅을 변경해야합니다." + each_browser_note: "노트: 사용하시는 모든 브라우저에서 이 설정을 변경해야합니다." dismiss_notifications: "모두 읽음으로 표시" dismiss_notifications_tooltip: "읽지 않은 알림을 모두 읽음으로 표시" disable_jump_reply: "댓글을 작성했을 때, 새로 작성한 댓글로 화면을 이동하지 않습니다." @@ -377,9 +377,9 @@ ko: muted_categories: "알림 끄기" muted_categories_instructions: "이 카테고리들에 새로 작성되는 새로운 글타래에 대한 알림이 오지 않도록 합니다. '읽지 않은'탭에서도 보이지 않게 됩니다." delete_account: "내 계정 삭제" - delete_account_confirm: "영구적으로 계정을 삭제해도 되겠습니까? 이 작업은 되돌릴 수 없습니다." - deleted_yourself: "성공적으로 계정이 삭제 되었습니다." - delete_yourself_not_allowed: "지금은 계정을 삭제할 수 없습니다. 관리자에게 계정을 삭제해달라고 연락해보세요." + delete_account_confirm: "정말로 계정을 삭제할까요? 이 작업은 되돌릴 수 없습니다." + deleted_yourself: "계정이 삭제 되었습니다." + delete_yourself_not_allowed: "지금은 계정을 삭제할 수 없습니다. 관리자에게 연락해 주세요." unread_message_count: "메시지" admin_delete: "삭제" users: "사용자" diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml index 30038eda2b..4f6b03da58 100644 --- a/config/locales/client.tr_TR.yml +++ b/config/locales/client.tr_TR.yml @@ -315,10 +315,10 @@ tr_TR: latest_by: "son gönderen" toggle_ordering: "sıralama kontrolünü aç/kapa" subcategories: "Alt kategoriler" - topic_stats: "Yeni konu sayısı" + topic_stats: "Yeni konuların sayısı." topic_stat_sentence: other: "%{unit} beri %{count} yeni konu." - post_stats: "Yeni gönderi sayısı" + post_stats: "Yeni gönderilerin sayısı." post_stat_sentence: other: "%{unit} beri %{count} yeni gönderi." ip_lookup: @@ -344,7 +344,7 @@ tr_TR: mute: "Sustur" edit: "Ayarları Düzenle" download_archive: "Gönderilerimi İndir" - new_private_message: "Yeni esaj" + new_private_message: "Yeni Mesaj" private_message: "Mesaj" private_messages: "Mesajlar" activity_stream: "Aktivite" @@ -599,7 +599,7 @@ tr_TR: fixed: "Sayfayı Yükle" close: "Kapat" assets_changed_confirm: "Bu site yeni versiyona güncellendi. Son hali için sayfayı yenilemek ister misiniz?" - logout: "Oturumunuz kapatılmış." + logout: "Çıkış yapıldı." refresh: "Yenile" read_only_mode: enabled: "Salt-okunur modu etkin. Siteyi gezmeye devam edebilirsiniz fakat etkileşimler çalışmayabilir." diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index 934eba5bd8..5a65f94f8d 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -726,10 +726,10 @@ zh_CN: create_pm: "消息" title: "或者按下 Ctrl + 回车" users_placeholder: "添加一个用户" - title_placeholder: "简述此讨论内容是关于什么?" + title_placeholder: "用简要的一句话解释要讨论什么" edit_reason_placeholder: "编辑理由" show_edit_reason: "(添加编辑理由)" - reply_placeholder: "在这输入。使用 Markdown、BBCode 或 HTML 格式化内容。拖拽或粘贴图片。" + reply_placeholder: "正文。使用 Markdown、BBCode 或 HTML 格式化内容。拖拽或粘贴图片。" view_new_post: "浏览你的新帖子。" saving: "保存中..." saved: "已保存!" diff --git a/config/locales/server.ar.yml b/config/locales/server.ar.yml index 1c7340b9c4..35a7e28576 100644 --- a/config/locales/server.ar.yml +++ b/config/locales/server.ar.yml @@ -785,8 +785,10 @@ ar: content_types: education_new_reply: title: "مستخدم جديد للتعليم: الردود الأولى" + description: "ينبثق الدليل بطريقه اوتوماتيكيه فوق المؤلف عندما يبدا المستخدمون الجدد بكتابه اول ردين جديدين لهم " education_new_topic: title: "مستخدم جديد للتعليم: موضوعات الأولى" + description: "ينبثق الدليل بطريقه اوتوماتيكيه للمؤلف عندما يبدا المستخدمون الجدد بكتابه اول ردين جديدين لهم " usage_tips: title: "توجيه مستخدم الجديد" description: "الإرشادات والمعلومات الأساسية للمستخدمين الجدد. " @@ -795,6 +797,7 @@ ar: description: "هذه الرسالة تُرسل تلقائيًا لجميع أعضائنا الجدد حال اشتراكهم" welcome_invite: title: "مرحباً: عضو مدعو" + description: "ترسل رسالة أليا لكل الأعضاء المدعوين حديثا عندما يقبلون دعوة من عضو أخر مشارك." login_required_welcome_message: title: "مطلوب لتسجيل الدخول : رسالة ترحيب" description: "رسالة الترحيب التي تظهر للأعضاء المسجلين خروجهم عندما يفعلون إعداد \"تسجيل الدخول مطلوب\"" @@ -806,6 +809,7 @@ ar: description: "HTML الذي سيدرج داخل علامات ." top: title: "أعلى الصفحة" + description: "HTML التي ستكون مضافة في الأعلى لكل صفحة ( بعد رأس الصفحة، أو قبل الملاحة أو عنوان الموضوع)." bottom: title: "أسفل الصفحة" description: "HTML التي ستضاف قبل علامة ." @@ -822,20 +826,44 @@ ar: max_topic_title_length: "الحد الأعلى المسموح به لطول عنوان موضوع في الأحرف" min_private_message_title_length: "الحد الأدنى المسموح به لطول عنوان لرسالة في الأحرف" min_search_term_length: "الحد الأدنى الصالح لطول مصطلح في الأحرف" + allow_uncategorized_topics: "اسمح بتصميم النقاشات من غير فئات. تحذير: اذا كان هناك نقاشات غير مصنفه، يجب عليك تصنيفها قبل اغلاق هذا " uncategorized_description: "الوصف للفئة غير المصنفة. اتركه فارغا لعدم الوصف." allow_duplicate_topic_titles: "اسمح بالمواضيع المماثلة والعناوين المكررة." unique_posts_mins: "كمية الدقائق التي يمكن للعضو قبلها إنشاء مشاركة مع نفس المحتوى مجددا" + educate_until_posts: "عندما يبدا المستخدم كتابه منشورهم الاول.، اظهر انبثاق لوحه تعليمات المستخدم الجديد في المؤلف" title: "الاسم لهذا الموقع، كأنه يستخدم علامة العنوان." site_description: "صف هذا الموقع بجملة واحدة باستخدام علامة الوصف meta." + contact_email: "عنوان البريد الإلكتروني للاتصال الرئيسي المسؤول لهذا الموقع. تستخدم للإشعارات الحرجة كالتبليغات الغير معالجة، وكذلك عن / حول نموذج الاتصال للأمور المستعجلة." + contact_url: "اتصل بـ URL لهذا الموقع. استخدم في / حول نموذج الاتصال للأمور المستعجلة." + queue_jobs: "مطورين فقط ! تحذير ! افتراضياً، اذا تم تعطيل قوائم الانتظار في sidkiq سيتعطل الموقع." + crawl_images: "جلب الصور من الروابط لادراج الأبعاد الصحيحة." + download_remote_images_to_local: "تحويل الصور البعيدة إلى صور محلية بواسطة تحميلها؛ بإستثناء الصور التالفة" + download_remote_images_threshold: "ادني مساحه للقرص ضروربه لتحميل الصور البعيده محليا ( في المئه) " + disabled_image_download_domains: "الصور البعيدة لن يتم تنزيلها من هذا المجال. قائمة Pipe-delimited." + ninja_edit_window: "ل(n) ثواني بعد النشر، التعديل الذي سيتم لن يصنع نسخه جديده في القائمه التاريخيه للمنشورات" + post_edit_time_limit: "الكاتب يتستطيع تعديل و مسح مشاركته لعده (n) دقائق بعد النشر. اضبطه للعدد0 للابد" + edit_history_visible_to_public: "اسمح لاي شخص ان يرى النسخ السابقه للمنشورات المعدله. عندما يتم تعطيلها.،فقط اعضاء فريق العمل يمكنهم رؤيتها " + delete_removed_posts_after: "المشاركات التي تم إزالتها عن طريق الكتاب سيتم مسحها اوتوماتيكياً بعد(n) ساعات ة. اذا تم ضبطها علي 0، سيتم مسح المشاركه فوراً" max_image_width: "أقصى عرض للصور المصغرة في مشاركة" max_image_height: "أقصى ارتفاع للصور المصغرة في مشاركة" category_featured_topics: "عدد المواضيع المعروضة لكل فئة من صفحة الفئات /categories .بعد تغير هذه القيمة, قد تستغرق صفحة الفئات 15 دقيقة لتُحَدّث." show_subcategory_list: "اعرض قائمة الفئات الفرعية بدلاً من قائمة المواضيع عند ادخال فئة ما." fixed_category_positions: "إذا تم التحقق, ستتمكن من ترتيب الفئات على شكل المطلوب. وإذا لم يتم التحقق, ستسرد الفئات على حسب الفعالية." + fixed_category_positions_on_create: "إذا تحققت، سيحفظ ترتيب الفئة في موضوع الحوار المنشأ (يتطلب fixed_category_positions)." + add_rel_nofollow_to_user_content: "أضف rel nofollow لجميع محتوى المستخدم المتقدم، باستثناء الروابط الداخلية (بما في ذلك المجالات الأصل). إذا غيرت هذا، يجب عليك عمل rebake لكل المشاركات بـ: \"rake posts:rebake\"" + exclude_rel_nofollow_domains: "قائمه النطاقات حيت لا اتباع لا يجب ان تضاف للروابط. tld.com سوف يقوم تلقائيا بالسماح sub.tld.com كذلك مثل الاصغر , يجب عليك اضافه النطاق الاعلى مستوي الخاص بالموقع لمساعده زحف الويب ليجاد جميع المحتويات.اذا كان باقي اجزاء موقعك في نطاقات اخرى , اضفهم ايضا" post_excerpt_maxlength: "الحد الأقصى لطول وظيفة مقتطف / ملخص." post_onebox_maxlength: "الحد الأقصى لطول مشاركة oneboxed Discourse بالأحرف." + onebox_domains_whitelist: "قائمة بالمجالات للسماح روابط تفصيلي؛ وينبغي دعم هذه المجالات OpenGraph أو oEmbed. اختبار لهم في http://iframely.com/debug" logo_url: "صورة الشعار في الجزء العلوي الأيسر من موقع الويب الخاص بك، يجب أن تكون على شكل مستطيل واسع. إذا كان سيتم إظهار غادر نص عنوان موقع على بياض." + digest_logo_url: "صورة الشعار بديلة تستخدم في الجزء العلوي من البريد الإلكتروني موقعك. يجب أن تكون على شكل مستطيل واسع. إذا كان سيتم استخدام ترك فارغا `logo_url`." logo_small_url: "صورة الشعار صغيرة في الجزء العلوي الأيسر من موقع الويب الخاص بك، يجب أن تكون على شكل مربع، وينظر عند التمرير لأسفل. وإذا ترك فارغا أن أظهرت الصورة الرمزية المنزل." + favicon_url: "الأيقونة الخاصة بموقعك, شاهد http://en.wikipedia.org/wiki/Favicon, لتعمل بشكل صحيح ضمن CDN يجب ان تكون بصيغة png." + mobile_logo_url: "صورة الشعار الثابتة ستظهر في الجزء العلوي الأيسر في نسخة الجوال من موقعك. يجب أن تكون مربعة. إذا تُركت فارغة، سيتم استخدام الـ `logo_url`. على سبيل المثال: http://example.com/uploads/default/logo.png" + apple_touch_icon_url: "الأيقونات المستخدمة لأجهزة اللمس Apple. يستحسن حجم 144px - 144px." + email_custom_headers: "قائمة قناة محددة لرؤوس بريد إلكتروني مخصص." + email_subject: "لتخصيص تنسيق عناوين رسائل البريد الالكتروني القياسية. انظر https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801" + use_https: "يجب ان يكون رابط url كامل للموقع(Discourse.base_url) يكون http او https؟ لا تشغل هذا الا اذا تم ضبطه و يعمل!" summary_score_threshold: "الحد الأدنى للنقاط المطلوبة لتضمين مشاركة في \"تلخيص هذا الموضوع\"" summary_posts_required: "الحد الأدنى للمشاركات في الموضوع قبل \"تلخيص هذا الموضوع\" مفعل." summary_likes_required: "الحد الأدنى للإعجابات في الموضوع قبل \"تلخيص هذا الموضوع\" مفعل." @@ -843,14 +871,33 @@ ar: summary_max_results: "الحد الأقصى للمشاركات العائدة بـ 'ملخص هذا الموضوع'" enable_private_messages: "يسمح مستوى الثقة 1 للمستخدمين بإنشاء والرد على الرسائل" enable_long_polling: "ناقل الرسالة المستخدم للاشعارات يمكن أن يستخدم التصويت الطويل." + long_polling_interval: "المدة الزمنية التي سينتظرها الخادم قبل الرد على العملاء في حالة عدم وجود أية بيانات لارسالها (للأعضاء المسجلين فقط)" + polling_interval: "عندما لا يكون التصويت طويلا، عدد المرات التي يجب تسجيل عملاء التصويت فيها بالملي ثانية." anon_polling_interval: "المرات التي يجب الاستعلام فيها عن العملاء المجهولين بالملي ثانية." + background_polling_interval: "كم ينبغي أن يكون عدد مرات الاستعلام عن العملاء في المللي ثانية ( عندما يكون الاطار في الخلفية )" + flags_required_to_hide_post: "عدد التبليغات التي تسبب إخفاء المشاركة آليا وترسل PM للعضو (0 لتعطيلها)" cooldown_minutes_after_hiding_posts: "عدد الدقائق يجب على المستخدم الانتظار قبل أن يتمكنوا من تعديل موضوع مخفي عن طريق رفع الأعلام المجتمع" max_topics_in_first_day: "الحد الأقصى لعدد الموضوعات يسمح للمستخدم لإنشاء في يومهم الأول على الموقع" max_replies_in_first_day: "الحد الأقصى لعدد الردود يسمح للمستخدم لإنشاء في يومهم الأول على الموقع" + tl2_additional_likes_per_day_multiplier: "زيادة حد الإعجابات باليوم لـ لـمستوى الثقة 2 (عضو) بالضرب بهذا الرقم" + tl3_additional_likes_per_day_multiplier: "زيادة حد الإعجابات باليوم لـ لـمستوى الثقة 3 (منتظم) بالضرب بهذا الرقم" + tl4_additional_likes_per_day_multiplier: "زيادة حد الإعجابات باليوم لـمستوى الثقة 4 (قائد) بالضرب بهذا الرقم" + num_flags_to_block_new_user: "اذا كانت مشاركات المستخدم الجديد حصلت علي هذا الكم من اعلامات البريد الغير هام من num_users_to_block_new_user اختلاف المستخدمين،اخفي جميع منشوراتهم و امنع جميع منشوراتهم المستقبليه.0 للالغاء" notify_mods_when_user_blocked: "إذا تم حظر المستخدم تلقائيا، وإرسال رسالة الى جميع المشرفين." flag_sockpuppets: "إذا رد أحد المستخدمين جديد إلى موضوع من عنوان IP نفسه باسم المستخدم الجديد الذي بدأ هذا الموضوع، علم كل من مناصبهم كدعاية المحتملين." + traditional_markdown_linebreaks: "استعمل السطور التالفه التقليديه في Markdown, التي تتطلب مساحتين بيضاوين للسطور التالفه" + allow_html_tables: "السماح للجداول لتكون مُدخلة بـMarkdown بإستخدام علامات HTML, و TABLE, THEAD, TD, TR, TH تكون قائمة بيضاء (تتطلب إعادة انشاء كامل لكل المشاركات القديمة التي تحتوي على جداول)" + post_undo_action_window_mins: "عدد الدقائق التي يسمح فيها للأعضاء بالتراجع عن آخر إجراءاتهم على المنشور (إعجاب، اشارة، إلخ...)" must_approve_users: "يجب أن الموظفين يوافق على جميع حسابات المستخدم الجديدة قبل أن يتم السماح لهم للوصول إلى الموقع. تحذير: تمكين هذا لموقع الحية إلغاء وصول المستخدمين الحاليين غير الموظفين!" + ga_tracking_code: "تحليلات Google (ga.js) تتعقب رمز الشيفرة، مثال: UA-12345678-9; انظر http://google.com/analytics" + ga_domain_name: "تحليلات Google (ga.js) تتعقب اسم المجال، مثال: mysite.com; انظر http://google.com/analytics" + ga_universal_tracking_code: "تحليلات Google العالمية (analytics.js) تتعقب رمز الشيفرة، مثال: UA-12345678-9; انظر http://google.com/analytics" + ga_universal_domain_name: "تحليلات Google العالمية (analytics.js) تتعقب اسم المجال، مثال: mysite.com; انظر http://google.com/analytics" + enable_escaped_fragments: "تراجع لـGoogle's Ajax-Crawling API إذا لم يكشف عن webcrawler. أنظر https://support.google.com/webmasters/answer/174992?hl=en" + enable_noscript_support: "فعل دعم محرك بحث webcrawler عبر علامة غير نصية." allow_moderators_to_create_categories: "السماح للمشرفين إنشاء قسم جديد" + cors_origins: "اسمح بالأصول للطلبات عبر المنشأ (CORS). كل أصل يجب أن يتضمن http:// أو https://. متغير env لـ DISCOURSE_ENABLE_CORS يجب أن يعين إلى true ليعمل CORS." + top_menu: "حدد الأدوات التي تظهر في ملاحة الصفحة الرئيسية، وما ترتيبها. مثال الأخير|الجديد|غير مقروء|فئات|أعلى|مقروء|مشارك|مفضلات" post_menu: "تحديد العناصر التي تظهر في القائمة آخر، وبأي ترتيب. مثال مثل | تحرير | العلم | حذف | سهم | المرجعية | الرد" post_menu_hidden_items: "عناصر القائمة لإخفاء افتراضيا في القائمة آخر ما لم يتم النقر على القطع التوسع جرا." share_links: "تحديد العناصر التي تظهر على الحوار سهم، وبأي ترتيب." @@ -866,7 +913,10 @@ ar: topics_per_period_in_top_page: "عدد من أهم الموضوعات هو مبين في موجز أعلى الافتراضية المواضيع." redirect_users_to_top_page: "إعادة توجيه تلقائيا للمستخدمين الجدد وغائبة لمدة طويلة إلى أعلى الصفحة." show_email_on_profile: "إظهار بريدك المستخدم على صفحته الشخصية (مرئية فقط لأنفسهم والموظفين)" + email_token_valid_hours: "نسيت كلمه السر/ تنشيط رموز الحساب صالحه ل(n) ساعات" + email_token_grace_period_hours: "نسيت كلمه السر/ تنشيط رموز الحساب لا تزال صالحة لفترة سماح بالـ(n) ساعات بعد استبدالها." enable_badges: "تفعيل نظام الشارات." + enable_whispers: "اسمح للأعضاء بهمس المشرفين." allow_index_in_robots_txt: "تحديد في ملف robots.txt أن يسمح هذا الموقع ليتم فهرستها من قبل محركات البحث على شبكة الإنترنت." email_domains_blacklist: "قائمة pipe-delimited المجالات البريد الإلكتروني الذي لا يسمح للمستخدمين تسجيل حسابات مع. مثال: mailinator.com | trashmail.net" email_domains_whitelist: "قائمة pipe-delimited من مجالات البريد الإلكتروني التي يجب على المستخدمين تسجيل حسابات مع. تحذير: لن يسمح للمستخدمين مع مجالات البريد الإلكتروني الأخرى غير المذكورة هنا!" @@ -876,13 +926,23 @@ ar: new_version_emails: "إرسال بريد إلكتروني إلى عنوان contact_email عندما نسخة جديدة من ديسكورس هو متاح." port: "DEVELOPER فقط! تحذير! استخدام هذا المنفذ HTTP بدلا من الافتراضي من المنفذ 80. المغادرة ابحث عن الافتراضي من 80." force_hostname: "DEVELOPER فقط! تحذير! تحديد اسم المضيف في URL. اتركه فارغا لالافتراضي." + invite_expiry_days: "مدة صلاحية مفاتيح دعوة عضو، بالأيام." + invite_passthrough_hours: "كم المدة التي يمكن للعضو أن يستخدم مفتاح دعوة محرر سابقا لتسجيل الدخول، في الساعات" + invite_only: "تم التعطيل التسجيل العام، تسجيل الأعضاء الجدد يجب أن يكون بدعوة من الأعضاء أو الطاقم." + login_required: "أصلح التوثيق لقراءة الاتصال على هذا الموقع، لا تسمع بالوصول المجهول." min_username_length: "الحد الأدنى لطول اسم العضو في الأحرف." max_username_length: "الحد الأعلى لطول اسم العضو في الأحرف." reserved_usernames: "الأعضاء الغير مسموح لها بالتسجيل." min_password_length: "أقل طول لكلمة المرور" block_common_passwords: "لا تسمح لكلمات المرور المسجلة في قائمة كلمات المرور الشائعةز" + enable_sso: " اسمح تسجيل دخول واحد عبر موقع خارجي(تحذير :عنوان بريد المستخدم *يجب* ان يتم التحقق من صحته عبر الموقع الخارجي)" sso_url: "نقطة نهاية URL الدخول الموحد" + sso_secret: "السلسلة السرية استخدمت لتشفير مصادقة معلومات SSO, كن متأكداً أنها 10 حروف أو أكثر" + sso_overrides_email: "يتجاوز البريد الاكتوني المحلي مع بريد موقع خارجي من حمولة SSO عند كل تسجيل دخول, ويمنع التتغيرات المحلية. (تحذير: ممكن يحدث تعارض بسبب الاختلاف طول اسم المستخدم/المتطلبات)" + sso_overrides_username: "يتجاوز \"اسم المستخدم المحلي\" مع \"اسم مستخدم موقع خارجي\" من حمولة SSO عند كل تسجيل دخول, ويمنع التغيرات المحلية. (تحذير: ممكن يحدث تعارض بسبب الاختلاف طول اسم المستخدم/المتطلبات)" + sso_overrides_name: "يتجاوز الاسم المحلي مع اسم موقع خارجي من حمولة SSO عند كل تسجيل دخول, ويمنع التغيرات المحلية. " sso_not_approved_url: "إعادة التوجيه لم توافق على حسابات SSO لهذا URL" + enable_local_logins: "فعل اسم مستخدم و كلمه مرور محليه تعتمد علي الحسابات(ملاحظه: يجب ان تفعل بالدعوات لتعمل) " allow_new_registrations: "السماح تسجيل مستخدم جديد. إلغاء هذا لمنع أي شخص من إنشاء حساب جديد." enable_yahoo_logins: "تفعيل مصادقة ياهو" enable_google_oauth2_logins: "تمكين مصادقة جوجل Oauth2. هذا هو أسلوب المصادقة التي تؤيد جوجل حاليا. يتطلب مفتاح وسرا." @@ -902,11 +962,12 @@ ar: automatic_backups_enabled: "فعل النسخ الإحتياطي التلقائي بشكل متكرر كما هو محدد." backup_frequency: "كمية النسخ الإحتياطية المتكررة التي أنشأناها، في اليوم." enable_s3_backups: "تحميل النسخ الاحتياطي لS3 عند اكتماله. هام: يتطلب اعتماد S3 صالحة دخلت في إعدادات الملفات." + s3_backup_bucket: "الرفع عن بعد لإجراء نسخ إحتياطية. تحذير : تأكد من أنه رفع خاص." active_user_rate_limit_secs: "كيف في كثير من الأحيان نقوم بتحديث حقل 'last_seen_at، في ثوان" verbose_localization: "شاهد تلميحات الترجمة الممتدة في واجهة المستخدم." previous_visit_timeout_hours: "متى زيارة تستمر قبل أن تنظر فيه الزيارة \"السابقة، في ساعات" rate_limit_create_topic: "بعد إنشاء الموضوع، يجب على المستخدمين الانتظار (n) ثانية قبل إنشاء موضوع آخر." - rate_limit_create_post: "بعد نشر، يجب على المستخدمين الانتظار (n) ثانية قبل إنشاء وظيفة أخرى." + rate_limit_create_post: "بعد نشر، يجب على المستخدمين الانتظار (n) ثانية قبل إنشاء منشور أخر." rate_limit_new_user_create_topic: "بعد إنشاء الموضوع، يجب على المستخدمين الانتظار (n) ثانية قبل إنشاء موضوع آخر." rate_limit_new_user_create_post: "بعد نشر، يجب على المستخدمين الانتظار (n) ثانية قبل إنشاء وظيفة أخرى." max_likes_per_day: "أقصى عدد للإعجابات لكل عضو باليوم." @@ -924,10 +985,13 @@ ar: purge_deleted_uploads_grace_period_days: "يتم مسح فترة سماح (بالأيام) قبل تحميل حذفه." purge_unactivated_users_grace_period_days: "يتم حذف فترة سماح (بالأيام) قبل المستخدم الذي لم تنشيط حساباتهم." enable_s3_uploads: "وضع الإضافات على تخزين الأمازون S3. هام: يتطلب اعتماد S3 صالحة (على حد سواء الوصول معرف مفتاح ومفتاح الوصول السري)." + s3_use_iam_profile: 'إستعمل وظيفة AWS EC2 IAM لإسترجاع المفاتيح. ملاحظة : التمكين سوف يتجاوز " مفتاح معرف الوصول ل s3 " و "مفتاح الوصول السري ل s3" الإعدادات.' s3_access_key_id: "معرف مفتاح دخول امازون S3 سيستخدم لرفع الصور." s3_secret_access_key: "مفتاح دخول أمازون السري S3 سيستخدم لرفع الصور." s3_region: "اسم منطقة أمازون S3 سيستخدم لرفع الصور." avatar_sizes: "قائمة أحجام الرمزية إنشاؤه تلقائيا." + external_system_avatars_enabled: "استخدم خدمات الهه النظام الخارجي " + external_system_avatars_url: "عنوان URL لخدمة الصور الرمزية النظام الخارجي. الاستعمال المسموح بها {username} {first_letter} {color} {size}" enable_flash_video_onebox: "تمكين التضمين من swf و FLV (أدوبي فلاش) وصلات في مربع واحد. تحذير: قد يعرض المخاطر الأمنية." default_invitee_trust_level: "مستوى الثقة الإفتراضي (0-4) للأعضاء المدعوين." default_trust_level: "مستوى الثقة الإفتراضي (0-4) للأعضاء المدعوين.تحذير! التغيرات ستضعك تحت خطر البريد الغير هام." @@ -941,30 +1005,141 @@ ar: tl2_requires_likes_received: "كمية الإعجابات التي يجب على العضو إرسالها قبل ترقيته لمستوى الثقة 2." tl2_requires_likes_given: "كمية الإعجابات التي يجب على العضو جمعها قبل ترقيته لمستوى الثقة 2." tl2_requires_topic_reply_count: "كمية مواضيع العضو التي يجب الرد عليها قبل الترقية لمستوى الثقة 2." + tl3_requires_days_visited: "أقل عدد من الأيام التي يجب على المستخدم زيارة الموقع في آخر مئةيوم للتأهل لمستوى الثقة 3. (0 حتى 100)" + tl3_requires_topics_replied_to: "أقل عدد من المواضيع التي يجب على المستخدم الرد عليها في آخر 100 يوم للتأهل لمستوى الثقة 3. (0 أو أكثر)" + tl3_requires_topics_viewed: "نسبة الموضوعات التي تم إنشاؤها في آخر 100 يوم، تجعل المستخدم مؤهلًا لعرضه للترقية إلى مستوى الثقة الثالث3 . (من 0 الى 100)" + tl3_requires_topics_viewed_all_time: "أقل عدد من المواضيع التي يجب على المستخدم مشاهدتها للتأهل لمستوى الثقة 3." + tl3_requires_posts_read_all_time: "أقل عدد من المشاركات يجب على المستحدم قرائتها للتأهل لمستوى الثقة 3 ." + tl3_requires_max_flagged: "يجب علي المستخدم عدم الحصول علي اكثر من x مشاركات مؤشره من x مستخدمين مختلفين في اخر 100 يوم لليترقى للمستوي 3 من الثقه , عندما x تساوي قيمه (صفر او اكثر)" + tl3_promotion_min_duration: "أقل عدد من الأيام التي تكون الترقية إلى مستوى الثقة 3 قد أنتهت قبل عودة المستخدم لمستوى الثقة 2." + tl3_requires_likes_given: "أقل عدد من الإعجابات التي يجب أن تُمنح في آخر 100 يوم للتأهل لمستوى الثقة 3." + tl3_requires_likes_received: "أقل عدد من الإعجابات التي يجب أن تُتلقى في آخر 100 يوم للتأهل لمستوى الثقة 3." + tl3_links_no_follow: "لا تحذف rel=nofollow من روابط المشاركة بواسطة أعضاء مستوى الثقة 3." min_trust_to_create_topic: "أدنى مستوى ثقة مطلوب لإنشاء موضوع جديد." + min_trust_to_edit_wiki_post: "الحد الأدنى لمستوى الثقة المطلوب لتعديل مشاركة معلمة كويكي." newuser_max_links: "عدد الروابط التي يمكن للمستخدم الجديد إضافتها للمشاركة." newuser_max_images: "عدد الصور التي يمكن للمستخدم الجديد إضافتها للمشاركة." newuser_max_attachments: "عدد المرفقات التي يمكن للمستخدم الجديد إضافتها للمشاركة." + newuser_max_mentions_per_post: "أقصى عدد لاشعارات @name العضو الجديد يمكنه استخدامها في المشاركة." + newuser_max_replies_per_topic: "أقصى عدد من الردود التي يمكن للمستخدم الجديد الرد بها على موضوع واحد حتى يتم الرد من شخص آخر عليه." + max_mentions_per_post: "أقصى عدد لاشعارات @name أي شخص يمكنه استخدامها في المشاركة." + create_thumbnails: "أنشئ الصور المصغرة وصندوق الصور المضيء الكبيرة جدا لتناسب المشاركة." + email_time_window_mins: "انتظر (n) دقائق قبل ارسال اي بريد اشعاري , لتعطي المستخدم فرصه تعدل و انهاء منشوراته." + email_posts_context: "كمية الردود السابقة التي ضمنت كسياق في إشعارات البريد الإلكتروني." + flush_timings_secs: "عدد تكرارات التي نقوم بدفع وقت البيانات للخادم، بالثانية." title_max_word_length: "الحد الأقصى المسموح لطول كلمة، بالأحرف، في عنوان الموضوع." + title_min_entropy: "أدنى إنتروبي (حروف فريدة، عدد غير إنجليزي للكثير) مطلوب لعنوان الموضوع." + body_min_entropy: "أدنى إنتروبي (حروف فريدة، عدد غير إنجليزي للكثير) مطلوب لصلب المشاركة." + min_title_similar_length: "الحد الأدنى لطول العنوان قبل أن يتم البحث عن مواضيع مشابهة." + min_body_similar_length: "الحد الادنى لطول المشاركة قبل أن يتم البحث عن مواضيع مشابه." + category_colors: "قائمة قيمة الألوان الست عشرية مسموحة في الفئات." category_style: "النمط المرئي لفئة الشارات." + max_image_size_kb: "الحجم الاقص لتحميل صوره بالكيلو بايت.هذا يجب ضبطه في nginx (client_max_body_size) /اباتشي او البروكسي" + max_attachment_size_kb: "أقصى حجم لتحميل المرفقات الملفات بالكيلوبايت. وهذا يجب أن يتم تكوينه في nginx (client_max_body_size) / أباتشي أو الوكيل. " + authorized_extensions: "قائمة بالملفات المسموح رفعها (أستخدم '*' للسماح لجميع الأنواع)" + max_similar_results: "كم عدد المواضيع المتشابهه لتظهر للمحرر اثناء انشاء موضوع جديد.المقارنه تعتمد علي العنوان و المحتوي " + topic_views_heat_low: "بعد هذه المشاهدات العديدة، مجال هذه المشاهدات بارزة قليلا." + topic_views_heat_medium: "بعد هذه المشاهدات العديدة، مجال هذه المشاهدات بارزة باعتدال." + topic_views_heat_high: "بعد هذه المشاهدات العديدة، مجال هذه المشاهدات بارزة بقوة." + cold_age_days_low: "بعد هذه الأيام الكثيرة من المحادثة, تاريخ النشاط الأخير ضعيف قليلا." + cold_age_days_medium: "بعد هذه الأيام الكثيرة من المحادثة, تاريخ النشاط الأخير ضعيف نسبياً." + cold_age_days_high: "بعد هذه الأيام الكثيرة من المحادثة, تاريخ النشاط الأخير ضعيف جداً." + history_hours_low: "تعديل المشاركة مع العديد من الساعات له مؤشر تحرير بارز قليلا." + history_hours_medium: "تعديل المشاركة مع العديد من الساعات له مؤشر تحرير بارز باعتدال." + history_hours_high: "تعديل المشاركة مع العديد من الساعات له مؤشر تحرير بارز بقوة." + topic_post_like_heat_low: "بعد الاعجابات: نسبه تفوق هذه النسبه,سلط الضوء قليلا علي حقل المنشور" + topic_post_like_heat_medium: "بعد الاعجابات: نسبه تفوق هذه النسبه,سلط الضوء باعتدال علي حقل المنشور" + topic_post_like_heat_high: "بعد likes:post تفوق هذه النسبه,سلط الضوء بقوه علي حقل المنشور" + faq_url: "إذا كانت لديك معلومات حول إضافة أخرى تريد استخدامها, فضلًا قم بتوفير العنوان كاملاً هنا." + tos_url: "اذا كان لديك وثيقه شروط خدمه مستضيفها في مكان اخر و تريد استخدمها, وفر url الكامل هنا" + privacy_policy_url: "اذا كان لديك وثيقه سياسه خصوصيه مستضيفها في مكان ما و نريد استخدمها. وفر url الكامل هنا" + newuser_spam_host_threshold: "كم عدد مرات التي يستطيع فيها المستخدم نشر رابط في نفس المضيف ضمن newuser_spam_host_posts قبل ان يعتبر غير هام" + staff_like_weight: "كم عدد مرات الترجيح الاضافيه لمنح اعجابات الطاقم " + topic_view_duration_hours: "قم بعد موضوع جديد مرة لكل IP/عضو كل N ساعات" + max_new_accounts_per_registration_ip: "اذا كان هناك بالفعل (N) مستوي ثقه الحسابات 0 من هذا IP ( و لم يكن عضو في الطاقم او TL2 او اعلى), توقف عن قبول تسجيلات الدخول الجديده من هذا IP" + min_ban_entries_for_roll_up: "When clicking the Roll up button, will create a new subnet ban entry if there are at least (N) entries." + max_age_unmatched_emails: "احذف عرض عدد مرات ادخال البريد الالكتروني الغير متطابق بعد (N) ايام." + max_age_unmatched_ips: "احذف عرض عدد مرات ادخال IP الغير متطابق بعد (N)ايام." + num_flaggers_to_close_topic: "ادني عدد من تبليغات المميزه المطلوبه ليتم ايقاف الموضوع تلفائيا ليدخل" + num_flags_to_close_topic: "ادني عدد من التبليغات النشطه المطلوبه ليتم توقيف الموضوع تلقائيا ليدخل" auto_respond_to_flag_actions: "تمكين الرد التلقائي عند التخلص من التبليغ." + auto_block_fast_typers_on_first_post: "اوتوماتيكيا احظر المستخدمين الذين لا يقابلون \nدقيقه_اول_منشور_كتابه_زمن" auto_block_fast_typers_max_trust_level: "الحد الأعلى لمستوى الثقة لأنواع الحظر التلقائي السريع." reply_by_email_enabled: "تفعيل الرد على الموضوع بواسطة البريد الالكتروني" + disable_emails: "منع Discourse من إرسال أي نوع من رسائل البريد الإلكتروني" + strip_images_from_short_emails: "شريط الصور من البريد الإلكتروني لها حجم أقل من 2800 بايت" short_email_length: "طول أقصر بريد الكتروني بـ Bytes." + pop3_polling_enabled: "تصويت عبرPOP3 لردود البريد الإلكتروني." + pop3_polling_ssl: "أستخدم SSL عند الأتصال بمخدم POP3.(مُستحسن)" + pop3_polling_period_mins: "الفترة دقائق بين التحقق من حساب POP3 للبريد الإلكتروني. ملاحظة : تتطلب إعادة تشغيل." + pop3_polling_port: "المنفذ للتصويت عن طريق حساب POP3." + pop3_polling_host: "المضيف للتصويت عن طريق البريد الإلكتروني عبر POP3." + pop3_polling_username: "اسم العضو لحساب POP3 للتصويت بالبريد الإلكتروني." + pop3_polling_password: "كلمة المرور لحساب POP3 للتصويت بالبريد الإلكتروني." + log_mail_processing_failures: " سجل كل فشل عمليات البريد الإلكتروني الي http://yourtsitename.com/logs" + email_in: "اسمح للمستخدمين بنشر المواضيع الجديده عن طريق البريد الالكتروني (يتطلب تصويت pop3 ). كون العناوين في الضبط صفحه لكل فئه " + email_in_min_trust: "أدنى مستوى ثقة يحتاجه العضو للسماح له بنشر موضوع جديد عبر البريد الألكتروني." + email_prefix: "[الوسم] يُستخدم في عنون رسالة البريد الألكتروني. سيكون إفتراضياً 'العنوان' في حال عدم تعيينه." + minimum_topics_similar: "عدد المواضيع التي يجب أن تكون موجودة قبل عرض المواضيع المشابهة عند إنشاء موضوع جديد." + delete_user_max_post_age: "لا تسمح بحذف المستخدمين الذين منشورهم الاول اقدم من (x) ايام" + delete_all_posts_max: "العدد الاقص للمشاركات التي يمكن مسحها مره واحده بزر مسح كل المشاركات.اذا المستخدم لديه اكثر من هذا العدد من المشاركات, المشاركات لا يمكن مسح جميعها مره واحدهو المستخدم لا يمكن حذفه" + username_change_period: "عدد الايام بعد التسجيل التي يستطيع فيها الحسابات بتغير اسم المستخدم الخاص بهم(0 لكي لا تسمح بتغير اسم المستخدم)" + email_editable: "يُسمح للأعضاء بتغيير بريدهم الألكتروني بعد التسجيل." + logout_redirect: "الموقع لاعاده توجيه المتصفح بعد تسجيل الخروج EG: (http://somesite.com/logout)" + allow_uploaded_avatars: "اسمح للمستخدمين بتحميل صور شخصيه مخصصه" + allow_animated_avatars: "يُسمح للمستخدمين باستخدام صور متحركة GIF كصورة شخصية.\nتحذير:يُمنع استخدام صور رمزية خليعة:سيتم تحديث الصورة فور تغييرها." + allow_animated_thumbnails: "يولد الصور المصغرة المتحركة للرسوم المتحركة. " + default_avatars: "سيتم استخدام عناوين URL للصور الرمزية بشكل افتراضيًا للأعضاء الجدد حتى يغيروا صورهم." + automatically_download_gravatars: "تحميل Gravatars للأعضاء عند تغيرهم البريد الإلكتروني الخاص بهم أو عند إنشائهم لحساب آخر." + digest_topics: "العدد الاقصي من المواضيع لعرضها في مضمون البريد الإلكتروني " + digest_min_excerpt_length: "الحد الادني من مشاركه مقتطفات خلاصه البريد الاكتروني,في الشخصيات" + suppress_digest_email_after_days: "احفظ خلاصه الرسائل الالكترونيه للمستخدمين الذين لم تتم رويتهم في الموقع لاكثر من (N) ايام" + disable_digest_emails: "عطل ملخص رسائل البريد الإلكتروني لكل الأعضاء." + detect_custom_avatars: "سواء او لا تفقد اذا كان المستخدم قام بتحميل صور شخصيه مخصصه" + max_daily_gravatar_crawls: "العدد الاقصي من مرات Dicourse سوف يتفقد Gravatar لصور رمزيه مخصصه في اليوم" + public_user_custom_fields: "حقول قائمه بيضاء مخصصه للمستدم يمكن رويتها علنا" + staff_user_custom_fields: "حقول القائمه البيضاء المخصصه للمستخدم يمكن اراها للطاقم" + enable_user_directory: "وفر دليل المستخدمين للتصفح" + allow_anonymous_posting: "اسمح للمستخدمين ان يغيروا لوضع المخفي" + anonymous_posting_min_trust_level: "أدنى مستوى ثقة للسماح بنشر مشاركات مجهولة." + anonymous_account_duration_minutes: "للحمايه الهويه اصنع حساب مجهول كل n دقائق لكل مستخدم.\nمثال:اذا ضبط 600 , بعد انقضي 600 دقيقه من اخر منشور و غير المستخدم للمجهول.تم انشاء حساب مجهول." + allow_profile_backgrounds: "اسمح للأعضاء برفع خلفيات ملف التعريف." + sequential_replies_threshold: "عدد مشاركات عضو في صف واحد في موضوع قبل يجري تذكير حول الردود متسلسلة كثيرة جداً." + enable_mobile_theme: "اجهزه الموبايل تستخدم ثيم مناسب للجوال,امكانيه التحويل للموقع الكامل .الغي هذا اذا اردت استخدام انماط سريعه الاستجابه" + daily_performance_report: "تحليل سجلات NGINX يومي ونشر موضوع \"قاقم فقط\" مع التفاصيل" + suppress_uncategorized_badge: "لا تظهر الشارة للمواضيع غير المصنفة في قائمة الموضوع." + disable_edit_notifications: "لتعطيل تحرير الاشعارات بواسطة العضو النظام عندما يكون نشطاً 'download_remote_images_to_local'." full_name_required: "الإسم الكامل مطلوب وهو ضروري لإكمال الحساب " enable_names: "عرض الاسم الكامل للعضو , بطاقة العضو , ورسائل البريد الالكتروني , تعطيل عرض الاسم في اي مكان " display_name_on_posts: "عرض الاسم الكامل للعضو على التعليقات بالاضافة الى @username." + show_time_gap_days: "إذا تم إجراء وظيفتين في حد كثير من الأيام، عرض الفجوة الزمنية في الموضوع." invites_per_page: "الدعوات الافتراضية تظهر في صفحة العضو" + default_code_lang: "تسليط الضوء علي تركيب جمله اللغه البرمجيه يطبق الي مكعبات اكواد GitHub (lang-auto, ruby, python etc.)" + feed_polling_enabled: "فقط التضمين : تضمين مغذي RSS/ATOM كمشاركات." + feed_polling_url: "فقط التضمين : URL لمغذي RSS/ATOM مضمن." + embed_by_username: "اسم مستخدم Discourse للعضو الذي ينشأ المواضيع المضمنة." + embed_username_key_from_feed: "مفتاح لسحب اسم مستخدم discourse من المغذي." embed_truncate: "اقتطاع الوظائف المدمجة." embed_post_limit: "أقصى عدد للمشاركات المضمنة." + embed_whitelist_selector: "منتقي CSS للعناصر التي تسمح في التضمينات." + embed_blacklist_selector: "منتقي CSS للعناصر التي حذفت من التضمينات." + enable_cdn_js_debugging: "السماح/logs لعرض أخطاء المناسبة عن طريق إضافة تتضمن تحليل عرض كل شبيبة." + show_create_topics_notice: "إذا كان الموقع يحتوي على أقل من 5 مواضيع عامة , إظهار إشعار مطالبة المسؤولين إنشاء بعض المواضيع." delete_drafts_older_than_n_days: حذف المسودات مضى عليها أكثر من (ن) يوما. + show_logout_in_header: "عرض تسجيل الخروج في القائمة المنسدلة للمستخدم في رأس الصفحة." + vacuum_db_days: "شغل التحليل الكامل للمساحة لاستعادة مساحة DB بعد الهجرات (ضع 0 للإغلاق)" enable_emoji: "تمكين الرموز التعبيرية " - emoji_set: "كيف تريد الرموز التعبيرية الخاصة بك؟" + emoji_set: "كيف تريد أن تكون الرموز التعبيرية الخاصة بك؟" enforce_square_emoji: "أجبر نسبة جانب المربع لكل الرموز التعبيرية." approve_post_count: "كمية مشاركات العضو الجديد التي يجب أن تتم الموافقة عليها" approve_unless_trust_level: "مشاركات للأعضاء أدنى من مستوى الثقة هذا يجب أن تتم الموافقة عليها." default_email_digest_frequency: "عدد المرات التي يتلقى الأعضاء فيها ملخص لبريدهم الإلكتروني إفتراضيا." + default_email_private_messages: "أرسل بريد إلكتروني عندما يراسل شخص ما العضو إفتراضيا." + default_email_direct: "ارسل بريد الكتروني عندما يقوم احدهم بالرد/الاقتباس الي/ذكر او دعوه مستخدم افتراضيا" default_email_mailing_list_mode: "ارسل بريد إلكتروني لكل مشاركة جديدة افتراضيا." + default_email_always: "أرسل إشعار بريد إلكتروني حتى عندما يكون العضو متاح إفتراضيا." + default_other_new_topic_duration_minutes: "الشروط العالمية الافتراضية لموضوع يعتبر جديد." + default_other_auto_track_topics_after_msecs: "الوقت العالمي الافتراضي قبل الموضوع متعقب آليا." default_other_external_links_in_new_tab: "أفتح الروابط الخارجية في تبويب جديد إفتراضيا." default_other_enable_quoting: "فعل إقتباس الردود لتحديد النص إفتراضيا." default_other_dynamic_favicon: "إعرض عدد المواضيع الجديدة/الحديثة في أيقونة المتصفح إفتراضيا." @@ -1019,6 +1194,21 @@ ar: redirected_to_top_reasons: new_user: "مرحبا بكم في مجتمعنا! هذه هي المواضيع الأكثر شعبية الأخيرة." not_seen_in_a_month: "مرحبا بعودتك! لم نرك منذ مدة. هذه أكثر المواضيع شعبية عندما كنت بعيدا." + move_posts: + new_topic_moderator_post: + zero: "المشاركه التي انقسمت الي موضوع جديد: %{topic_link}" + one: "المشاركه التي انقسمت الي موضوع جديد : %{topic_link}" + two: "%{count} المشاركات التي انقسمت الي موضوع جديد: %{topic_link] " + few: "%{count} المشاركات التي انقسمت الي موضوع جديد: %{topic_link] " + many: "%{count} المشاركات التي انقسمت الي موضوع جديد: %{topic_link] " + other: "%{count} المشاركات التي انقسمت الي موضوع جديد: %{topic_link] " + existing_topic_moderator_post: + zero: "المشاركه التي اندمجت الي موضوع واحد %{topic_link}" + one: "المشاركه التي اندمجت الي موضوع واحد %{topic_link}" + two: "%{count} المشاركات التي اندمجت الي مشاركه واحده %{topic_link}" + few: "%{count} المشاركات التي اندمجت الي مشاركه واحده %{topic_link}" + many: "%{count} المشاركات التي اندمجت الي مشاركه واحده %{topic_link}" + other: "%{count} المشاركات التي اندمجت الي مشاركه واحده %{topic_link}" change_owner: post_revision_text: "نقل الملكية من %{old_user} إلى %{new_user}" deleted_user: "عضو محذوف" @@ -1031,13 +1221,59 @@ ar: archived_disabled: "هذا الموضوع غير مؤرشف الأن.لم يجمد، و تستطيع أن تعدل عليه." closed_enabled: "تم اغلاق هذا الموضوع لا يسمح باضافة ردود جديدة" closed_disabled: "تم فتح هذا الموضوع الان ويسمح باضافة ردود جديدة " + autoclosed_enabled_days: + zero: "هذا الموضوع تم اغلاقه تلقائيا بعد 1 يوم .الردود الجديده غير متوفره بعد الان." + one: "هذا الموضوع تم اغلاقه تلقائيا بعد 1 يوم .الردود الجديده غير متوفره بعد الان." + two: "هذا الموضوع تم اغلاقه تلقائيا بعد %{count} ايام . الردود الجديده غير مسموحه بعد الان." + few: "هذا الموضوع تم اغلاقه تلقائيا بعد %{count} ايام . الردود الجديده غير مسموحه بعد الان." + many: "هذا الموضوع تم اغلاقه تلقائيا بعد %{count} ايام . الردود الجديده غير مسموحه بعد الان." + other: "هذا الموضوع تم اغلاقه تلقائيا بعد %{count} ايام . الردود الجديده غير مسموحه بعد الان." + autoclosed_enabled_hours: + zero: "هذا الموضوع تم اغلاقه تلقائيا بعد 1 ساعه. الردود الجديده غير مسموحه بعد الان." + one: "هذا الموضوع تم اغلاقه تلقائيا بعد 1 ساعه. الردود الجديده غير مسموحه بعد الان." + two: "هذا الموضوع تم اغلاقه تلقائيا بعد %{count} ساعات. الردود الجديده غير مسموحه" + few: "هذا الموضوع تم اغلاقه تلقائيا بعد %{count} ساعات. الردود الجديده غير مسموحه" + many: "هذا الموضوع تم اغلاقه تلقائيا بعد %{count} ساعات. الردود الجديده غير مسموحه" + other: "هذا الموضوع تم اغلاقه تلقائيا بعد %{count} ساعات. الردود الجديده غير مسموحه" + autoclosed_enabled_minutes: + zero: "هذا الموضوع تم غلاقه تلقائيا بعد 1 دقيقه . الردود الجديده غير مسموحه بعد الان." + one: "هذا الموضوع تم غلاقه تلقائيا بعد 1 دقيقه . الردود الجديده غير مسموحه بعد الان." + two: "هذا الموضوع تم تم اغلاقه تلقائيا بعد %{count} دقائق . الردود الجديده غير مسموحه بعد الان." + few: "هذا الموضوع تم اغلاقه تلقائيا بعد %{count} دقائق. الردود الجديده غير مسموحه بعد الان." + many: "هذا الموضوع تم اغلاقه تلقائيا بعد %{count} دقائق. الردود الجديده غير مسموحه بعد الان " + other: "هذا الموضوع تم اغلاقه تلقائيا بعد %{count} دقائق. الردود الجديده غير مسموحه بعد الان" + autoclosed_enabled_lastpost_days: + zero: "هذا الموضوع تم اغلاقه تلقائيا 1 يوم بعد اخر رد . الردود الجديده غير مسموحه بعد الان." + one: "هذا الموضوع تم اغلاقه تلقائيا 1 يوم بعد اخر رد . الردود الجديده غير مسموحه بعد الان." + two: "هذا الموضوع تم اغلاقه تلقائيا %{count} ايام بعد اخر رد. الردود الجديده غير مسموحه." + few: "هذا الموضوع تم اغلاقه تلقائيا %{count} ايام بعد اخر رد. الردود الجديده غير مسموحه." + many: "هذا الموضوع تم اغلاقه تلقائيا %{count} ايام بعد اخر رد. الردود الجديده غير مسموحه." + other: "هذا الموضوع تم اغلاقه تلقائيا %{count} ايام بعد اخر رد. الردود الجديده غير مسموحه." + autoclosed_enabled_lastpost_hours: + zero: "هذا الموضوع تم اغلاقه تلقائيا ب 1 ساعه بعد اخر رد.الردود الجديده غير مسموحه بعد الان." + one: "هذا الموضوع تم اغلاقه تلقائيا ب 1 ساعه بعد اخر رد.الردود الجديده غير مسموحه بعد الان." + two: "هذا الموضوع تم اغلاقه تلقائيا بعد %{count} ساعات بعد اخر ردز الردود الجديده غير مسموحه بعد الان." + few: "هذا الموضوع تم اغلاقه تلقائيا بعد %{count} ساعات بعد اخر ردز الردود الجديده غير مسموحه بعد الان." + many: "هذا الموضوع تم اغلاقه تلقائيا بعد %{count} ساعات بعد اخر ردز الردود الجديده غير مسموحه بعد الان." + other: "هذا الموضوع تم اغلاقه تلقائيا بعد %{count} ساعات بعد اخر ردز الردود الجديده غير مسموحه بعد الان." + autoclosed_enabled_lastpost_minutes: + zero: "هذا الموضوع تم اغلاقه تلقائيا 1 دقيقه بعتد الرد الاخير.الردود الجديده غير مسموحه بعد الان" + one: "هذا الموضوع تم اغلاقه تلقائيا 1 دقيقه بعتد الرد الاخير.الردود الجديده غير مسموحه بعد الان" + two: "هذا الموضوع تم اغلاقه تلقائيا %{count} دقائق بعد اخر ردز الردود الجديده غير مسموحه." + few: "هذا الموضوع تم اغلاقه تلقائيا %{count} دقائق بعد اخر ردز الردود الجديده غير مسموحه." + many: "هذا الموضوع تم اغلاقه تلقائيا %{count} دقائق بعد اخر ردز الردود الجديده غير مسموحه." + other: "هذا الموضوع تم اغلاقه تلقائيا %{count} دقائق بعد اخر ردز الردود الجديده غير مسموحه." autoclosed_disabled: "تم فتح هذا الموضوع الان ويسمح باضافة ردود جديدة " autoclosed_disabled_lastpost: "تم فتح هذا الموضوع الان ويسمح باضافة ردود جديدة " pinned_enabled: "هذا الموضوع الآن مقيد. سوف تظهر في الجزء العلوي من فئة لها حتى أنها متغيرة من الموظفين لكل فرد، أو بواسطة المستخدمين انفسهم." pinned_disabled: "هذا الموضوع يتم الآن إزالة. لن يظهر في الجزء العلوي من هذه الفئة." + pinned_globally_enabled: "هذا الموضوع معلّم الآن. سوف يظهر في أعلى كل صفحة حتى يرفض من قبل المستخدم." + pinned_globally_disabled: "هذا الموضوع الان غير مثبت . لن يكون موجودا في اعلى الفئه لمده طويله" visible_enabled: "هذا الموضوع مدرج الأن. سيظهر في قائمة المواضيع." login: + not_approved: "لم تتم عملية الموافقة على حسابك حتى الآن. سيتم إعلامك عبر البريد الإلكتروني الخاص بك عندما يكون حسابك جاهزًا لتسجيل الدخول." incorrect_username_email_or_password: "اسم المستخدم او كلمة المرور او البريد الالكتروني غير صحيح" + wait_approval: "شكرًا على التسجيل. سيتم إبلاغك عندما تتم عملية الموافقة على حسابك." active: "حسابك مفعل وجاهز للاستخدام." not_allowed_from_ip_address: "ﻻ يمكنك تسجيل الدخول كـ %{username} من هذا الـIP" admin_not_allowed_from_ip_address: "لا يمكنك تسجيل الدخول كمدير من خلال هذا العنوان الرقمي - IP." @@ -1050,6 +1286,7 @@ ar: omniauth_error_unknown: "حدث خطأ ما في معالجة دخولك، الرجاء إعادة المحاولة." new_registrations_disabled: "لا يُسمح بتسجيل حساب جديد بهذا الوقت " password_too_long: "الحد الاقصى لكلمة المرور 200 حرف" + email_too_long: "عنوان البريد الذي ادخلته طويل جدًا. يجب ان لا يزيد عدد الأحرف عن 254 حرفًا, ويجب أن تكون أسماء النطاقات أكثر من 253 حرفًا." reserved_username: "اسم المستخدم ذلك غير مسموح." missing_user_field: "لم تُكمل كافة الحقول المطلوبة " close_window: "تم التحقق . اغلق هذه النافذة للإستمرار" @@ -1061,6 +1298,8 @@ ar: characters: "يجب ان تحتوي الارقام والاحرف الصغيرة فقط " unique: "يجب أن يكون فريدا" blank: "يجب أن يكون موجود" + must_begin_with_alphanumeric: "يجب البدء بحرف أو رقم أو _" + must_end_with_alphanumeric: "يجب الإنتهاء بحرف أو رقم أو _" must_not_contain_two_special_chars_in_seq: "يجب أن لا يحتوي على تسلسل من 2 أو رموز خاصة (.-_)" must_not_contain_confusing_suffix: "يجب أن لا تحتوي على ملحقة مربكة مثل : json. أو png. الخ." email: @@ -1068,22 +1307,112 @@ ar: blocked: "غير مسموح" ip_address: blocked: "لا يُسمح بتسجيل جديد من عنوان ip الخاص بك " + max_new_accounts_per_registration_ip: "وفقًا لعنوان IP الخاص بك فقد تم حظر التسجيل (لقد وصلت للحد الأقصى المسموح به) تواصل مع أحد المشرفين." invite_mailer: subject_template: "%{invitee_name} دعاك إلى '%{topic_title}' على %{site_domain_name}" invite_forum_mailer: subject_template: "%{invitee_name} قام بدعوتك للإنضمام إلى %{site_domain_name}" invite_password_instructions: subject_template: "تعيين كلمة مرور %{site_name} حسابك" + text_body_template: | + شكرا لقبول دعوتك إلى %{site_name} -- أهلا بك! + + انقر فوق هذا الرابط لاختيار كلمة مرور الآن: + %{base_url}/users/password-reset/%{email_token} + + (إذا انتهت مدة صلاحية الرابط أعلاه، اختر "نسيت كلمة المرور" عند تسجيل الدخول مع عنوان البريد الإلكتروني الخاص بك.) test_mailer: subject_template: "[%{site_name}] بريد الكتروني بهدف الاختبار" + text_body_template: | + هذا بريد إختبار إلكتروني من + + [**%{base_url}**][0] + + أهداف البريد معقدة. هنا بعض الأشياء المهمة يجب عليك مراجعتها أولا: + + - كن *متأكد* لوضع `إشعارات البريد الإلكتروني` من: عنوان صحيح في إعدادات موقعك. **النطاق المخصص في أو "من" عنوان رسائل البريد الإلكتروني التي أرسلتها سيتم التحقق تجاه مجال بريدك الإلكتروني**. + + - معرفة كيفية عرض المصدر الخام للبريد الإلكتروني في عميل بريدك , لذا يمكنك البحث في رؤوس صفحات البريد الإلكتروني للأدلة المهمة. في Gmail, خيار "إظهار الأصلية" في القائمة المنسدلة في أعلى اليمين لكل بريد إلكتروني. + + - **مهم جدا:** هل الـ ISP الخاص بك له سجل DNS عكسي مدخل لربط أسماء النطاقات وعناوين IP التي أرسلت بريد منها؟ [اختبر سجل PTR العكسي][2] هنا. إذا كان ISP الخاص بك لم يدخل سجل مؤشر DNS العكسي المناسب, من غير المحتمل جدا تسليم أي من بريدك الإلكتروني. + + - هل مجالك لـ[سجل SPF][8] صحيح؟ [أختبر سجل SPF الخاص بك][1] هنا. لاحظ أن TXT هو نوع سجل رسمي صحيح لـSPF. + + - Iهل مجالك لـ [DKIM سجل][3] صحيح؟ هذا سيحسن بشكل كبير أهداف البريد الإلكتروني. [أختبر سجل DKIM الخاص بك][7] هنا. + + - إذا شغلت خادم البريد الخاص بك, راجع للتأكد من IPs لخادم بريدك [ليست في أي قوائم سوداء][4]. أيضا تحقق من إرسال تأكيد مضيف كامل التأهيل الذي يحل في DNS في رسالته الترحيبية. إذا كان لا, سيسبب رفض بريدك الإلكتروني من عدة خدمات. + + (الطريقة *السهلة* هي إنشاء حساب مجاني في [Mandrill][md] أو [Mailgun][mg] أو [Mailjet][mj], التي لديها عطاء مجاني والبريد مجاني والخطط وستكون جيدة للإتصالات القصيرة. لا تزال بحاجة لإعداد سجلات SPF و DKIM في DNS الخاص بك, رغم ذلك!) + + نأمل منك أن يلقي إختبار أهداف البريد الإلكتروني الموافقة! + + حظ جيد, + + أصدقائك في [Discourse](http://www.discourse.org) + + [0]: %{base_url} + [1]: http://www.kitterman.com/spf/validate.html + [2]: http://mxtoolbox.com/ReverseLookup.aspx + [3]: http://www.dkim.org/ + [4]: http://whatismyipaddress.com/blacklist-check + [7]: http://dkimcore.org/tools/dkimrecordcheck.html + [8]: http://www.openspf.org/SPF_Record_Syntax + [md]: http://mandrill.com + [mg]: http://www.mailgun.com/ + [mj]: https://www.mailjet.com/pricing new_version_mailer: subject_template: "[%{site_name}] يوجد اصدار جديد , تحديث متوفر" + text_body_template: | + نسخة جديدة من [Discourse](http://www.discourse.org) متوفرة. + + نسختك: %{installed_version} + النسخة الجديدة: **%{new_version}** + + ربما تريد أن: + + - ما الجدي في [GitHub changelog](https://github.com/discourse/discourse/commits/master). + + - التحديث من متصفحك من [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade). + + - زيارة [meta.discourse.org](http://meta.discourse.org) للأخبار, والمناقشة, ودعم Discourse. new_version_mailer_with_notes: subject_template: "[%{site_name}] التحديث متوفر" + text_body_template: | + نسخة جديدة من [Discourse](http://www.discourse.org) متوفرة. + + نسختك: %{installed_version} + النسخة الجديدة: **%{new_version}** + + ربما تريد أن: + + - ما الجدي في [GitHub changelog](https://github.com/discourse/discourse/commits/master). + + - التحديث من متصفحك من [%{base_url}/admin/upgrade](%{base_url}/admin/upgrade). + + - زيارة [meta.discourse.org](http://meta.discourse.org) للأخبار, والمناقشة, ودعم Discourse. + + ### ملاحظات الإصدارة + + %{notes} flags_reminder: please_review: "يرجى مراجعة ذلك " post_number: "مشاركة" how_to_disable: 'يمكنك تعطيل أو تغيير تكرار تذكير البريد الإلكتروني عبر "إشعار حول الإعلامات بعد" الإعدادات.' + queued_posts_reminder: + subject_template: + zero: "[%{site_name}] %{count} المشاركات التي تنتظر معينتها" + one: "[%{site_name}] 1 المشاركات التي تنتظر معينتها " + two: "[%{site_name}] 1 المشاركات التي تنتظر معينتها" + few: "[%{site_name}] %{count} المشاركات التي يجب معينتها" + many: "[%{site_name}] %{count}المشاركات التي يجب معينتها" + other: "{site_name}] %{count}%] المشاركات التي تنتظر معينتها " + text_body_template: | + مرحبا, + + هناك مشاركات من المستخدمين الجدد تنتظر ليتم معينتها .[يمكن قبولهم او رفضهم هنا] (%{base_url}/queued-posts). + flag_reasons: + off_topic: "تم تاشير مشاركتك **خارج الموضوع**: المجتمع يشعر بانها لا تلائم الموضوع , حاليا كتعريف بالعنوان و المشاركه الاولى" + inappropriate: " مشاركتك **غير ملائم**: المجتمع يشعر بانها لا تلائم الموضوع . [our community guidelines](/guidelines)." flags_dispositions: agreed: "شكراً لإعلامنا سننظر في الأمر " agreed_and_deleted: "شكراً لإعلامنا سنتحقق من المشكلة ونزيل المشاركة" @@ -1094,26 +1423,145 @@ ar: system_messages: post_hidden: subject_template: "تم إخفاء المنشور بسبب حظر المجتمع" + usage_tips: + text_body_template: | + هنا بعض الخطوات السريعة لتبدأ: + + ##قراءة + + لقراءة المزيد, **استمر بالتمرير لأسفل!** + + الردود الجديدة والمواضيع الجديدة ونحو ذلك, ستظهر تلقائياً -دون الحاجة لتحديث الصفحة. + + ## التصفح + + -للبحث, صفحة المستخدم الخاصة بك, أو القائمة , استخدم **أيقونة الأزرار في الجزء العلوي الأيمن**. + + -اختيار عنوان موضوع سيأخذك دائماً الى **الرد التالي غير المقروء ** في الموضوع. + للدخول أول أو آخر الموضوع, اختر رقم الرد أو آخِر تاريخ. + + + + - عند قراءة موضوع, اختر شريط التقدم من الزر الأيمن لعناصر تحكم في التنقل كاملة. انتقل للأعلى بسرعة باختيار عنوان الموضوع. أضغط ? للحصول على قائمة باختصارات لوحة المفاتيح السريعة جدا. + + + + ## الرد + + - للرد على **الموضوع بشكل عام**, استخدم في الجزء السفلي جدا من الموضوع. + + - للرد على **شخص مخصص**, استخدم على مشاركاتهم. + + - للرد **بموضوع جديد**, استخدم إلى يمين المشاركة. كلا المواضيع القديمة والجديدة سيرتبطون مع بعضهم تلقائيا. + + لإدخال إقتباس, حدد النص الذي تريد إقتباسه, ثم أضغط أي زر رد. كرر للإقتباسات المتعددة! + + + + لإشعار شخص عن ردك, أشر بأسمائهم. أكتب `@` للبدء باختيار اسم العضو. + + + + لاستخدام [معيار الرسوم التعبيرية](http://www.emoji.codes/), أكتب فقط `:` لوصلها بالاسم, أو استخدم الوجوه التقليدية الضاحكة `;)` + + + + لتوليد اختصار لرابط, ألصقه في السطر بمفرده: + + + + ## التفاعل + + هناك أزرار التفاعل في أسفل كل مشاركة: + + + + لتسمح لشخص ما تعرفه الذي تستمتع وتقدر مشاركاتهم, استخدم زر **إعجاب** . شارك ما يعجبك! + + اذا رايت خطأ في منشور شخص ما .قم بتعديله شخصيا, أو [العاملون لدينا](%{base_url}/about), أعرف حول ذلك بزر **التبليغ** . يمكنك أيضا **مشاركة** رابط لمشاركة, أو **تفضيل** ليكون مرجع لك لاحقا على صفحة العضو. + + + ##تنبيهات + + + عندما يرد عليك احد,اقتبس منشورك,او ذكر اسم المستخدم خاصتك, هنالك رقم سوف يظهر فورا في الجزء العلوي الايمن من الصفحه. استعمله لتصل **لتنبيهاتك** + + + + + + لا تخف اذا نسيت ردا-سوف يصلك بريد الكتروني بكل التنبيهات التي وصلتك عندما كنت بعيدا. + + + ##تفضيلاتك + + + كل المواضيع في اقل من **يومين مضت** تعتبر جديدة. + + + اي موضوع كنت **شاركت فيه بنشاط**(بصنعه,الردود, او القراءه لفتره ممتده ) سوفي تعقب تلقائيا. + + + سوف ترى مؤشر الرقم الجديد الغير مقروى و الازرق بعد هذا الموضوع مباشره: + + + + + يمكنك تغيير إشعاراتك لأي موضوع عبر لوحة التحكم بالإشعارات في أسفل الموضوع. + + + + يمكنك أيضا تعيين حالة الإشعار لكل فئة, إذا أردت أن ترى كل موضوع جديد في فئة خاصة. + + لتغيير أي من هذه الإعدادات, أنظر [تفضيلات المستخدم الخاصة بك](%{base_url}/my/preferences). + + ## ثقة المجتمع + + يمكنك المشاركة هنا, ومع مرور الوقت ستكسب ثقة المجتمع, كن عضو صالح, وسيتم رفع قيود العضو الجديد. لترفع [مستوى الثقة] بما فيه الكفاية (https://meta.discourse.org/t/what-do-user-trust-levels-do/4924), عليك اكتساب قدرات جديدة لمساعدتنا في إدارة مجتمعنا معا. welcome_user: subject_template: "مرحبا بك في %{site_name}!" welcome_invite: subject_template: "مرحبا بك في %{site_name}!" backup_succeeded: subject_template: "اكتملت عملية النسخ الإحتياطي بنجاح" + text_body_template: "تم النسخ الاحتياطي بنجاح .\nزيارة [admin > backup section](%{base_url}/admin/backups) لتحميل النسخةالاحتياطية الجديدة." backup_failed: subject_template: "فشل النسخ الإحتياطي" + text_body_template: | + فشل النسخ الاحتياطي. + + هنا السجل: + + ``` + %{logs} + ``` restore_succeeded: subject_template: "أكتملت الإستعادة بنجاح" text_body_template: "الاستعادة نجحت" restore_failed: subject_template: "فشل استعادة" + text_body_template: | + فشل عملية الاستعادة. + + هذا السجل: + + ``` + %{logs} + ``` bulk_invite_succeeded: subject_template: "دعوة العضو الجماعية تمت بنجاح" text_body_template: "ملف دعوة العضو الجماعية الخاص بك تمت معالجته، %{sent} دعوات مرسلة." bulk_invite_failed: subject_template: "دعوة العضو الجماعية تمت مع وجود أخطاء" + text_body_template: "تمت معالجة ملف دعوات العضو , وتدعو %{sent} بالبريد مع %{failed} error(s). \n\nهنا السجل \n \n```\n%{logs}\n```\n" csv_export_succeeded: subject_template: "اكتمل تصدير البيانات" + text_body_template: | + تصدير البيانات الخاصة بك بنجاح! :dvd: + + %{file_name} (%{file_size}) + + رابط التحميل أعلاه ستكون صالحة لمدة 48 ساعة. csv_export_failed: subject_template: "فشل تصدير البيانات" text_body_template: "نحن آسفون، لكنه فشل تصدير البيانات الخاصة بك. يرجى التحقق من السجلات أو اتصل بأحد المشرفين." @@ -1160,6 +1608,12 @@ ar: subject_template: "[%{site_name}] بريد الكتروني -- خطأ مصادقة POP" too_many_spam_flags: subject_template: "حساب جديد مقفول" + text_body_template: | + مرحبا, + + هذه رساله اليه من %{site_name} لاعلاماك ان مشاركتك تم اخفاها اليا لانها كانت مؤشره من قبل المجتمع. + كجراء وقائي تم حظر حسابك من انشاء ردود جديده او مواضيع حتي يقوم اعضاء الطاقم بمراجعه حسابك. + للمعلومات الاضافيه.الرجاء مراجعه [قوانين مجتمعنا] (%{base_url}/guidelines). blocked_by_staff: subject_template: "الحساب مُعطل" user_automatically_blocked: @@ -1168,6 +1622,13 @@ ar: subject_template: "العضو الجديد %{username} تم حجب مشاركاته بسبب تكرار الروابط " unblocked: subject_template: "الحساب مُفعل " + text_body_template: |+ + مرحبا, + + هذه رساله اليه من %{site_name} لاعلامك انه قد تم فك حظر حسابك بعد مراجعه الطاقم + + يمكنك الان انشاء ردود جديده و مواضيع مجددا. + pending_users_reminder: subject_template: zero: "لا يوجد أعضاء تنتظر الموافقة." @@ -1176,18 +1637,52 @@ ar: few: "%{count} أعضاء تنتظر الموافقة." many: "%{count} أعضاء تنتظر الموافقة." other: "%{count} أعضاء تنتظر الموافقة." + text_body_template: | + هناك تسجيلات مستخدمين جديده تنظر لكي يتم قبولها (او رفضها) قبل ان يمكنهم الوصول لهذه المدونه. + [الرجاء مراجعنهم في قسم المدراء](%{base_url}/admin/users/list/pending). download_remote_images_disabled: subject_template: "الغاء تفعيل تحميل الصور عن بعد " + text_body_template: "تم تعطيل الإعداد 'download_remote_images_to_local' لأنه تم الوصول إلى حد مساحة القرص في 'download_remote_images_threshold'." subject_re: "اعادة " subject_pm: "[مسائًا]" user_notifications: previous_discussion: "الردود السابقة " unsubscribe: title: "غير مشترك " + description: "لست مهتما في تلقي هذه الرسائل الالكترونيه؟ لا مشكله! اضغط تحت ليتم الغاء اشتركك فورا:" + reply_by_email: "للاستجابه, رد علي هذه الرساله او قم بزياره%{base_url}%{url}% في متصفحك" visit_link_to_respond: "للرد، قم بزيارة %{base_url}%{url} في متصفحك." posted_by: "مشاركة بواسطة %{username} على %{post_date}" user_invited_to_private_message_pm: subject_template: "[%{site_name}] %{username} دعاك لرسالة '%{topic_title}'" + text_body_template: |2 + + %{username} دعوتك إلى رسالة + + > **%{topic_title}** + > + > %{topic_excerpt} + + at + + > %{site_title} -- %{site_description} + + يرجى زيارة هذا الرابط لعرض الرسالة: %{base_url}%{url} + user_invited_to_topic: + subject_template: "[%{site_name}] %{username} دعوتك للموضوع '%{topic_title}'" + text_body_template: |2 + + %{username} دعوتك للنقاش + + > **%{topic_title}** + > + > %{topic_excerpt} + + at + + > %{site_title} -- %{site_description} + + الرجاء زيارة هذا الرابط لعرض الرسالة: %{base_url}%{url} user_replied: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | @@ -1234,10 +1729,12 @@ ar: --- %{respond_instructions} digest: + why: "ملخص المؤجز من %{site_link} منذ زيارتك الاخيره في %{last_seen_at}" subject_template: "[%{site_name}] الخلاصة" new_activity: "يوجد نشاط جديد في المواضيع والمشاركات الخاصة بك:" top_topics: "اشهر المشاركات " other_new_topics: "اشهر المواضيع " + unsubscribe: "يتم إرسال هذا الملخص من %{site_link} لإلغاء الاشتراك %{unsubscribe_link}." click_here: "أنقر هنا" from: "%{site_name} الخلاصة" read_more: "قراءة المزيد" @@ -1249,12 +1746,41 @@ ar: subject_template: "[%{site_name}] ضع كلمة المرور" admin_login: subject_template: "[%{site_name}] تسجيل الدخول" + text_body_template: | + شخص طلب تسجيل الدخول إلى الحساب الخاص بك في [%{site_name}](%{base_url}). + + إذا كنت لم تتقدم بهذا الطلب، يمكنك تجاهل هذه الرسالة الإلكترونية بأمان. + + اضغط على الرابط التالي للدخول: + %{base_url}/users/admin-login/%{email_token} account_created: subject_template: "[%{site_name}] حسابك الجديد" + text_body_template: | + تم إنشاء حساب جديد بالنسبة لك في %{site_name} + + انقر على الرابط التالي لاختيار كلمة مرور لحسابك الجديد: + %{base_url}/users/password-reset/%{email_token} authorize_email: subject_template: "[%{site_name}] تأكيد البريد الإلكتروني الجديد " + text_body_template: | + قم بتاكيد عنوان بريدك الالكتروني لـ %{site_name} عن طريق الضغط علي الرابط التالي: + %{base_url}/users/authorize-email/%{email_token} signup_after_approval: subject_template: "قد وافقت على %{site_name}!" + text_body_template: | + مرحبا بك الي %{site_name}! + + اعضاء الطاقم قاموا بالموافقه علي حسابك علي %{site_name} + + اضغط الرابط التالي للتاكيد و لتفعيل حسابك الجديد + base_url}/users/activate-account/%{email_token}% + اذا كان الرابط في الاعلى غير مضغوط , حاول نسخه و لصقه في ششريط العناوين الالكتروني في متصفح الويب خاصتك. + {new_user_tips}% + نح نؤمن في [سلوك المجتمع الراقي(base_url}/guidelines}%) في كل الاوقات + + تمتع ببقائك! + + (اذا احتجت للاتصال مع [افراد الطاقم]({base_url}/about}%)كمستخدم جديد , فقط قم بالرد علي هذه الرساله). signup: subject_template: "[%{site_name}] تأكيد حسابك الجديد " page_not_found: @@ -1264,24 +1790,34 @@ ar: see_more: "المزيد" search_title: "البحث في الموقع" search_google: "جوجل" + login_required: + welcome_message: | + #[أهلاً بكم %{title}](#مرحبًا) + الحساب مطلوب. الرجاء انشاء حساب او تسجيل الدخول للأستمرار. terms_of_service: title: "شروط الخدمة" + signup_form_message: 'لقد قرأت و أوافق على بنود الخدمة.' deleted: 'حذف' upload: edit_reason: "تحميل نسخ محلية للصور" unauthorized: "المعذرة، الملف الذي تحاول رفعه غير مسموح به (الامتدادات المسموح بها هي : %{authorized_extensions})." pasted_image_filename: "لصق الصورة " + store_failure: "فشل حفظ التحميل #%{upload_id} للعضو #%{user_id}." file_missing: "عذرا، يجب عليك توفير ملف للرفع." attachments: too_large: "نعتذر، الملف الذي تريد رفعه كبير جداً ( الحد الاقصى {max_size_kb} كيلوبايت )" images: too_large: "نعتذر، الصورة الذي تريد رفعها كبيرة جداً ( الحد الاقصى هو %{max_size_kb} كيلوبايت )،يرجى اعادة تغيير حجمها ثم حاول مرة اخرى." size_not_found: "نعتذر، لكننا لا يمكن تحديد حجم الصورة. ربما صورتك تالفة؟" + flag_reason: + sockpuppet: "عضو جديد إنشاء موضوع , وعضو أخر جديد في نفس عنوان IP . شاهد flag_sockpuppets إعداد الموقع" + spam_hosts: "حاول هذا العضو الجديد إنشاء مشاركات متعددة لها روابط بنفس المجال. شاهد newuser_spam_host_threshold إعداد الموقع." email_log: no_user: "لا يمكنك إيجاد عضو بواسطة id %{user_id}" anonymous_user: "المستخدم مجهول" suspended_not_pm: "عضو موقوف، ليست رسالة" seen_recently: "تم رؤية هذا المستخدم مسبقاً" + post_not_found: "لا يمكن إيجاد مشاركة مع معرف %{post_id}" notification_already_read: "تم قراءة هذه الاشعارات " topic_nil: "مشاركة.موضوع صفر" post_deleted: "تم حذف الموضوع من قبل كاتبه " @@ -1289,6 +1825,7 @@ ar: already_read: "المستخدم قرأ هذه المشاركة " message_blank: "رسالة فارغة" message_to_blank: "رسالة فارغة " + text_part_body_blank: "text_part.body فارغ" body_blank: "محتوى فارغ " color_schemes: base_theme_name: "قاعدة" @@ -1307,8 +1844,71 @@ ar: title: "شروط الخدمة" privacy_topic: title: "سياسة الخصوصية" + body: "\n\n## [ما هي البيانات التي نقوم بجمعها؟](#جمع)\n\nنحن نجمع معلومات عنك عندما تسجل في موقعنا، وعندما تشارك في المنتدى عن طريق القراءة أو الكتابة، ونقوم بتقييم ما تم مشاركته هنا .\n\n\nيمكنك زيارة موقعنا بدون تسجيل، ولكن عند التسجيل في موقعنا، سيطلب منك ادخال اسمك وعنوان بريدك الالكتروني، وسيتم التحقق من بريدك الالكتروني بإرسال رسالة الى بريدك الالكتروني تحتوي على رابط خاص، عند الضغط على هذا الرابط، نحن سنعرف أنك مالك البريد الالكتروني المتحكم فيه.\n\nعندما تقوم بالتسجيل في موقعنا والنشر،\ + \ سنقوم بتسجيل عنوان IP الذي نشرت منه، أيضا قد تحتفظ سجلات الخادم عناوين IP جميع الطلبات من الخادم.\n\n\n\n## [في ماذا نستخدم المعلومات الخاصة بك؟](#استخدام)\n\nيمكننا استخدام المعلومات التي نجمعها عنك بإحدى الطرق التالية:\n\n* لتخصيص تجربتك و mdahs؛ المعلومات الخاصة بك تساعدنا على الاستجابة بشكل أفضل لاحتياجاتك الفردية.\n* لتحسين موقعنا و mdash ؛ نسعى باستمرار لتحسين ما يعرضه الموقع استنادًا الى المعلومات والتغذية الراجعة - الملاحظات - التي نتلقاها منك.\n* لتحسين خدمة العملاء و mdash ؛ المعلومات\ + \ الخاصة بك تساعدنا على الاستجابة بشكل أكثر فعالية لطلبات الدعم وخدمة العملاء.\n* لإرسال رسائل البريد الالكتروني الدورية و mdash ؛ سنستخدم عنوان البريد الالكتروني الذي وفرته لنا لإرسال معلومات إليك، الاشعارات التي تطلبها حول التحديثات في المواضيع، أو الاستجابة لاسم المستخدم الخاص بك، كالرد على الاستفسارات، او الطلبات أو اسئلة اخرى.\n\n\n\n## [كيف نقوم بحماية معلوماتك ؟](#حماية)\n\nنقوم بتطبيق مجموعة من الإجراءات الأمنية للمحافظة على أمن وسلامة معلوماتك الشخصية، عندما تقوم بإرسال أو إدخال\ + \ أو الدخول لمعلوماتك الشخصية.\n\n\n\n## [ما هي سياسة الاحتفاظ بالبيانات الخاصة بك ؟](#الاحتفاظ-بالبيانات)\n\nسوف نبذل جهونا بإخلاص للتالي :\n\n* الاحتفاظ بسجلات عناوين IP لجميع الطلبات لهذا الخادم لمدة لا تزيد عن 90 يومًا.\n* الاحتفاظ بعناوين IP الخاصة بالمستخدمين المسجلين ومنشوراتهم لمدة لا تزيد عن 5 سنوات.\n\n\n\n## [هل نستخدم الكعكات - cookies - ملفات تعريف الارتباط ؟ ](#كعكات)\n\nنعم، الكعكات هي عبارة عن ملفات صغيرة يقوم الموقع أو مزود الخدمة بنقلها الى\ + \ القرص الصلب لحاسبك من خلال متصفحك (اذا سمحتم بذلك)، هذه الكعكات تعرف الموقع على متصفحك، فن كان لديك حساب مسجل، سيتم ربطه مع حسابك.\n\nنحن نستخدم ملفات تعريف الارتباط - cookies - لفهم وحفظ التفضيلات الخاصة بك للزيارات في المستقبل، وجمع البيانات العامة حول حركة المرور والتفاعل في الموقع حتى نتمكن من تقديم تجربة وأدوات افضل في المستقبل، نحن قد نتعاقد مع مقدمي خدمات من الطرف الثالث لمساعدتنا في تحسين فهمنا لزوار الموقع، مقدمي الخدمات لا يسمح لهم بإستخدام المعلومات التي تم جمعها نيابة عنا، إلّا لمساعدتنا في سلوكنا\ + \ وتحسين أعمالنا.\n\n\n\n## [هل نقوم بالإفصاح عن أي معلومات لأطراف خارجية؟](#إفصاح)\n\nنحن لا نبيع، ولا نتاجر أو ننقل المعلومات الشخصية الى أطراف خارجية. وهذا لا يشمل الطرف الخاجية الموثوق بها والتي تساعد في تشغيل موقعنا، واجراء أعمالنا، أو تقديم الخدمات لكم، طالما أن تلك الأطراف موافقة للحفاظ على سرية المعلومات، يجوز لنا الافراج عن معوماتك الشخصية عندما نرى أنه هو المناسب للإمتثال للقانون، مع فرض سياسة موقعنا، أو حماية حقوقنا أو حقوق الآخرين، أو حقوق الملكية، أو السلامة. ومع ذلك، يمكننا\ + \ تقديم معلومات الزائرين دون تحديد الهوية، الى أطراف أخرى للتسويق والاعلان، أو غيرها من الاستخدامات.\n\n\n\n## [روابط الطرف الثالث](#الطرف-الثالث)\n\nأحيانًا، نفترض، أنه عند وجود مواقع طرف ثالث تقجم خدمات أو منتجات على موقعنا، كان لدى مواقع الطرف الثالث هذه، سياسات خصوصية منفصلة ومستقلة، لن يكون لدينا أية مسؤولية عن محتوى وأنشطة هذه المواقع المرتبطة، ومع ذلك، نحن نسعى إلى حماية موقعنا وسلامته، ونرحب بأي ملاحظات حول هذه المواقع. \n\n\n\n## [الالتزام بقانون حماية\ + \ خصوصية الأطفال على الانترنت ](#coppa)\n\nموقعنا والمنتجات والخدمات موجهه للأشخاص اللذين لا تقل أعمارهم عن 13 سنة، إذا كان هذا الخادم في الولايات المتحدة الأمريكية، من شروط COPPA\n ([Children's Online Privacy Protection Act](https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act))\nعدم استخدم هذا الموقع.\n\n\n\n## [سياسة الخصوصية على الانترنت](#متصل)\n\nتنطبق سياسة الخصوصية على الانترنت فقط على المعلومات التي يتم جمعها من خلال موقعنا، ولاتنطبق على المعلومات التي يتم جمعها\ + \ أثناء عدم الاتصال.\n\n\n\n## [الموافقة](#الموافقة)\n\nبإستخدام موقعنا، أنت توافق على سياسة الخصوصية لموقعنا.\n\n\n\n## [تغييرات على سياسة الخصوصية](#تغييرات)\n\nإذا قمنا بتغيير سياسة الخصوصية، سوف نقوم بنشر هذه التغييرات في هذه الصفحة.\n\nهذه الوثيقة هي نسخة من CC-BY-SA تم تحديثها في 31 مايو عام 2013م .\n" + static: + search_help: | +

    نصائح

    +

    +

      +
    • العناوين المتطابقة لها أولوية وحركة; عند الشك, ابحث عن العناوين
    • +
    • وحيد, الكلمات الغير مألوفة ستعطي أفضل نتائج
    • +
    • جرب البحث ضمن فئة خاصة, موضوع, أو عضو
    • +
    +

    +

    الخيارات

    +

    + + + + + + + +
    order:viewsorder:latest
    status:openstatus:closedstatus:archivedstatus:norepliesstatus:single_user
    category:foouser:foo
    in:likesin:postedin:watchingin:trackingin:private
    in:bookmarksin:first
    posts_count:nummin_age:daysmax_age:days
    +

    +

    + rainbows category:parks status:open order:latest سيبحث عن المواضيع المحتوية كلمة "rainbows" في الفئة "parks" التي لم تغلق أو تؤرشف, رتبت بالبيانات أخر المشاركة. +

    badges: long_descriptions: + autobiographer: | + منحت هذه الشارة لتعبئتك صفحة المستخدم الشخصية واختيار صورة شخصية. السماح للمجتمع معرفة المزيد حولك وماذاترغب يجعل المجتمع أكثر ارتباطاً + first_like: | + منحت هذه الشارة لك لإعجابك للمرة الأولى بالمشاركة بواسطة :heart: button. الإعجاب بالمشاركات وسيلة رائعة تسمح لرفاقك في المجتمع معرفة أن المشاركة كانت مثيرة للاهتمام ومفيدة وجيدة، أو ممتعة. شارك ما يعجبك! + first_link: | + منحت لك هذه الشارة لأنك قمت أول مرة بوضع رابط لموضوع آخر في الردود. المواضيع المترابطة تساعد القراء المتابعين على إيجاد المواضيع المتعلقة بحوارهم ، ويظهر الصلة بين المواضيع. + first_quote: |+ + منحت هذه الشارة لإقتباسك المرة الأولى منشورًا في ردك. نقلًا عن الأجزاء ذات الصلة من المنشورات السابقة في ردودك يساعد على إبقاء المناقشات مرتكزة وحول موضوع المنشور. + + first_share: | + منحت هذه الشارة لمشاركتك لأول مرة رابط الرد أو المنشور باستعمال زر المشاركة. مشاركة الروابط وسيلة رائعة لإظهار النقاشات المهمة للآخرين وتنمية المجتمع الخاص بك. + read_guidelines: | + منحت هذه الشارة لأنك قرأت مبادئ و توجيهات المجتمع . اتباع هذه المبادئ والتوجيهات البسيطة تساعد على بناء مجتمع آمن وممتع دائمًا. + reader: | + منحت هذه الشارة لقراءتك موضوع طويل. القراءة أمر اساسي، القراءة عن كثب تساعدك على متابعة الحوار و كتابة ردود أفضل ومتكاملة. + editor: | + منحت هذه الشارة لتحريرك مشاركتك، لا تترد في تحرير مشاركاتك في أي وقت لتحسينها، وإصلاح الأخطاء الصغيرة، أو إضافة شئ نسيته. + first_flag: | + منحت هذه الشارة لتبليغك عن منشور، و التبليغ ذو أهمية بالغة لصحة مجتمعك، اذا لاحظت أي منشور يتطلب متابعة المشرف رجاءًا لا تتردد في الابلاغ عنه + يمكنك أيضًا استخدام مربع الابلاغ لإرسال رسالة الى المستخدمين الآخرين. + nice_share: | + منحت هذه الشارة لزيارة 25 شخصاً لرابط المشاركة الذي نشرته، عمل رائع ! مشاركة روابط النقاشات المثيرة للاهتمام مع أصدقاءك وسيلة ممتازة لتنمية مجتمعنا. + welcome: | + منحت هذه الشارة لأنك تلقيت علامة الإعجاب الأولى على منشورك، لقد وجد رفاقك أن منشورك مثيرٌ للاهتمام وممتعا، أو مفيدًا ! + anniversary: | + منحت هذه الشارة لأنك كنت عضوًا لمدة سنة، لقد نشرت منشورًا واحدًا على الأقل في هذه السنة. شكرًا لمساهمتك في مجتمعنا ! + good_share: | + منحت هذه الشارة لمشاركتك رابط المنشور الذي زاره 300 زائر، نجاح باهر! لقد عرضت نقاشًا مهما لـالكثير من الأشخاص الجدد وساعدت على تنمية مجتمعنا. + great_share: | + منحت هذه الشارة لمشاركتك رابط المنشور الذي زاره 100زائر، عمل رائع! لقد شجعت على مناقشة مهمة لجمهور جديد ضخم لهذا المجتمع وساعدت على تنميته بشكل كبير. nice_post: | هذه الشارة تمنح لوجود رد حصل على 10 إعجابات. عمل جميل! nice_topic: | @@ -1321,6 +1921,10 @@ ar: هذه الشارة تمنح لوجود رد حصل على 50 إعجاب. ياللعجب! great_topic: | هذه الشارة تمنح لوجود رد حصل على 50 إعجاب. ياللعجب! + basic: | + منحت هذه الشارة لأنك وصلت مستوى الثقة الأول 1 ، شكرًا لتعليقاتك وقراءتك عدد من المواضيع لتتعرف على مجتمعنا، وقد تم رفع جميع قيود المستخدم الجديد عنك، و مُنحت الامكانيات الأسياسة في المجتمع، مثل الرسائل الشخصية، و الإبلاغ، و تحرير الويكي والقدرة على نشر الصور والروابط المتعددة. + member: | + منحت هذه الشارة لأنك وصلت مستوى الثقة الثاني 2، شكرًا لمشاركتك لأسابيع منذ انضمامك لمجتعنا، يمكنك الآن إرسال دعوات للأعضاء من صفحتك الشخصية أو من المواضيع، إنشاء مجموعة الرسائل، وإعطاء علامات إعجاب أكثر في اليوم الواحد. regular: |+ يتم منح هذه الشارة عندما تصل لمستوى الثقة 3. شكرا لكونك جزءا منضبطاً من مجتمعنا على مدى أشهر، أحد القراء الأكثر نشاطا ومساهمة معتمدا، لما يجعل هذا المجتمع عظيم. يمكنك الآن إعادة تصنيف وإعادة تسمية الموضوعات، والوصول إلى الاستراحة الخاصة وأقدر على الإعلام بالبريد المزعج، وغيرها كثير من الإعجابات لكل يوم. diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml index fd3f07f885..48aeb5e7be 100644 --- a/config/locales/server.de.yml +++ b/config/locales/server.de.yml @@ -1560,6 +1560,9 @@ de: title: "Nutzungsbedingungen" privacy_topic: title: "Datenschutzrichtlinie" + static: + search_help: "

    Tipps

    \n

    \n

      \n
    • Suchtreffer in Titeln haben Vorrang – im Zweifel einfach nach Titeln suchen
    • \n
    • Eindeutige, seltene Wörter erzielen die besten Suchergebnisse
    • \n
    • Versuche, innerhalb einer bestimmten Kategorie, eines bestimmten Themas oder Benutzers zu suchen
    • \n
    \n

    \n

    Optionen

    \n

    \n\n\n\n\n\n\n\n
    order:viewsorder:latest
    status:openstatus:closedstatus:archivedstatus:norepliesstatus:single_user
    category:foouser:foo
    in:likesin:postedin:watchingin:trackingin:private
    in:bookmarksin:first
    posts_count:nummin_age:daysmax_age:days
    \n

    \n

    \nBeispiel: Regenbogen category:Parks status:open order:latest sucht nach Themen, die das Wort \"Regenbogen\" in der Kategorie \"Parks\" enthalten und die nicht geschlossen oder archiviert sind; die Treffer werden nach dem Datum des letzten Beitrags sortiert. \n

    \n" admin_login: success: "E-Mail gesendet" error: "Fehler!" diff --git a/config/locales/server.it.yml b/config/locales/server.it.yml index cba295e53a..3549720cda 100644 --- a/config/locales/server.it.yml +++ b/config/locales/server.it.yml @@ -66,7 +66,8 @@ it: embed: load_from_remote: "Si è verificato un errore nel caricamento del messaggio." site_settings: - min_username_length_range: "Non puoi impostare il minimo al di sopra del massimo" + min_username_length_range: "Non puoi impostare il minimo più grande del massimo." + default_categories_already_selected: "Non puoi selezionare una categoria usata in un'altra lista." bulk_invite: file_should_be_csv: "Il file caricato deve essere in formato csv o txt." backup: @@ -740,6 +741,8 @@ it: invite_passthrough_hours: "Per quanto tempo un utente può usare una chiave d'invito per fare login, in ore" invite_only: "La registrazione pubblica è disabilitata, tutti i nuovi utenti devono essere invitati esplicitamente da altri membri o dallo staff." login_required: "E' richiesta l'autenticazione per leggere contenuti su questo sito, disabilita l'accesso anonimo." + min_username_length: "Lunghezza minima del nome utente in caratteri." + max_username_length: "Lunghezza massima del nome utente in caratteri. " reserved_usernames: "Nomi utente ai quali non è permesso l'accesso." min_password_length: "Minima lunghezza della password." block_common_passwords: "Non permettere password che sono nelle 10.000 password più comuni." @@ -765,6 +768,7 @@ it: github_client_secret: "Client secret per autenticazione Github, come registrata su https://github.com/settings/applications" allow_restore: "Abilita il ripristino, che sostituisce TUTTI i dati del sito! Lascia a falso a meno che non hai intenzione di ripristinare un backup." maximum_backups: "Il numero massimo di backup da mantenere sul disco. I backup più vecchi vengono automaticamente cancellati." + automatic_backups_enabled: "Esegui backup automatici come definito nella frequenza di backup" backup_frequency: "Con che frequenza, in giorni, effettuiamo il backup del sito." enable_s3_backups: "Carica i backup su S3 quando completati. IMPORTANTE: richiede che siano inserite valide credenziali S3 nelle impostazioni File." s3_backup_bucket: "Il bucket remoto che contiene i backup. ATTENZIONE: assicurati che sia un bucket privato." @@ -794,10 +798,10 @@ it: s3_access_key_id: "La access key id Amazon S3 che verrà usata per caricare le immagini." s3_secret_access_key: "La access key secret Amazon S3 che verrà usata per caricare le immagini." s3_region: "La region name Amazon S3 che verrà usata per caricare le immagini." - avatar_sizes: "Elenco delle dimensioni degli avatar, generate automaticamente" + avatar_sizes: "Elenco delle dimensioni degli avatar, generate automaticamente." enable_flash_video_onebox: "Attiva l'inserimento di collegamenti swf e flv (Adobe Flash) in onebox. ATTENZIONE: comporta rischi per la sicurezza." default_invitee_trust_level: "Livello di esperienza (0-4) assegnato di default agli utenti invitati." - default_trust_level: "Livello di fiducia (da 0 a 4) per tutti i nuovi utenti. ATTENZIONE! Modificare questa opzione ti espone ad un serio rischio di ricevere posta indesiderata." + default_trust_level: "Livello di esperienza (da 0 a 4) assegnato ai nuovi utenti. ATTENZIONE! Modificare questa opzione ti espone ad un serio rischio di ricevere posta indesiderata." tl1_requires_topics_entered: "Quanti argomenti deve inserire un utente per essere promosso a livello di esperienza 1." tl1_requires_read_posts: "Quanti messaggi deve leggere un utente per essere promosso al livello di esperienza 1." tl1_requires_time_spent_mins: "Per quanti minuti un utente deve leggere i messaggi per essere promosso al livello di esperienza 1." diff --git a/config/locales/server.ko.yml b/config/locales/server.ko.yml index 4dd56b4d6b..7d12548042 100644 --- a/config/locales/server.ko.yml +++ b/config/locales/server.ko.yml @@ -229,17 +229,17 @@ ko: no_info_me: "
    프로필이 현재 비어있습니다. 지금 작성하시겠습니까?
    " no_info_other: "
    %{name}님께서는 아직 프로필을 작성하지 않으셨습니다
    " vip_category_name: "라운지" - vip_category_description: "신뢰 등급 3 혹은 그 이상의 회원만 입장가능한 카테고리" + vip_category_description: "회원등 3 혹은 그 이상의 회원만 입장가능한 카테고리" meta_category_name: "사이트 피드백" meta_category_description: "이 사이트의 운영 방식에 대해 건의하고 토론하는 곳입니다." staff_category_name: "스태프" staff_category_description: "스태프들의 논의를 위한 비공개 카테고리입니다. 관리자 및 운영자에게만 보입니다." - assets_topic_body: "이 글타래는 스태프에게만 보이는 영구적인 글타래입니다. 사이트 디자인을 위한 이미지를 저장할 수 있습니다. 지우지 마세요!\n\n다음처럼 하세요:\n\n1. 이 글타래에 답글을 작성하세요.\n2. 로고, 파비콘 등으로 사용하고 싶은 모든 이미지들을 업로드한다. (포스트 에디터 툴바의 업로드 아이콘을 사용하거나, 드래그 앤 드롭으로 이미지를 올려주세요.)\n3. 답글을 제출합니다.\n4. 답글의 업로드된 이미지를 우클릭하거나, 수정 버튼을 눌러 이미지의 경로를 확인하세요. 해당 이미지의 경로를 복사하세요.\n5. 복사한 이미지 경로를 [basic settings](/admin/site_settings/category/required)에 붙여넣으세요.\n\n\n다른 유형의 이미지를 사용해야한다면 [file settings](/admin/site_settings/category/files)의 `authorized_extensions` 를 수정하세요." + assets_topic_body: "이 글타래는 스태프에게만 보이는 영구적인 글타래입니다. 사이트 디자인을 위한 이미지를 저장할 수 있습니다. 지우지 마세요!\n\n다음처럼 하세요:\n\n1. 이 글타래에 답글을 작성하세요.\n2. 로고, 파비콘 등으로 사용하고 싶은 모든 이미지들을 업로드한다. (글 에디터 툴바의 업로드 아이콘을 사용하거나, 드래그 앤 드롭으로 이미지를 올려주세요.)\n3. 답글을 제출합니다.\n4. 답글의 업로드된 이미지를 우클릭하거나, 수정 버튼을 눌러 이미지의 경로를 확인하세요. 해당 이미지의 경로를 복사하세요.\n5. 복사한 이미지 경로를 [basic settings](/admin/site_settings/category/required)에 붙여넣으세요.\n\n\n다른 유형의 이미지를 사용해야한다면 [file settings](/admin/site_settings/category/files)의 `authorized_extensions` 를 수정하세요." lounge_welcome: title: "라운지에 오신 것을 환영합니다." body: |2 - 축하합니다! :confetti_ball: 당신의 신뢰도가 **지도자** (신뢰도 3등급)로 올라갔기 때문에 이 글타래를 볼 수 있게 되었습니다. 이제부터 아래의 행동들을 할 수 있습니다 … * 글타래를 제목을 수정할 수 있습니다 * 글타래의 카테고리를 수정할 수 있습니다 * 링크가 follow로 처리됩니다 ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) 가 제거됩니다) * 신뢰도 3 이상인 사용자가 접근할 수 있는 비공개 카테고리인 라운지에 접근할 수 있습니다 * 좋아요와 신고의 가중치가 높아집니다 [정규 멤버 목록](/badges/3/regular)을 확인할 수 있습니다. 인사하는 것을 잊지마세요. 이 커뮤니티의 중요한 역할을 해주신 것에 감사드립니다! (신뢰도에 관한 더 자세한 정보를 보시려면, [이 글타래를 확인하세요][trust]. 기준에 맞는 사람만 정규 회원으로 자격이 유지됩니다.) [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + 축하합니다! :confetti_ball: 당신의 회원등급이 **지도자** (회원등급 3등급)로 올라갔기 때문에 이 글타래를 볼 수 있게 되었습니다. 이제부터 아래의 행동들을 할 수 있습니다 … * 글타래를 제목을 수정할 수 있습니다 * 글타래의 카테고리를 수정할 수 있습니다 * 링크가 follow로 처리됩니다 ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) 가 제거됩니다) * 회원등급 3 이상인 사용자가 접근할 수 있는 비공개 카테고리인 라운지에 접근할 수 있습니다 * 좋아요와 신고의 가중치가 높아집니다 [정규 멤버 목록](/badges/3/regular)을 확인할 수 있습니다. 인사하는 것을 잊지마세요. 이 커뮤니티의 중요한 역할을 해주신 것에 감사드립니다! (회원등급에 관한 더 자세한 정보를 보시려면, [이 글타래를 확인하세요][trust]. 기준에 맞는 사람만 정규 회원으로 자격이 유지됩니다.) [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "'%{category}' 카테고리의 설명" replace_paragraph: "[이 첫번째 문단을 당신의 새로운 카테고리의 짧은 설명으로 바꿔주세요. 이 지침은 카테고리 선택 화면에 나타날 것입니다. 200자 이하로 적어주세요.]" @@ -266,7 +266,7 @@ ko: title: "정규 회원" elder: title: "지도자" - 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" + 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} 동안 기다리시고 다시 시도해 주세요." @@ -458,8 +458,8 @@ ko: xaxis: "일" yaxis: "새로운 즐겨찾기 수" users_by_trust_level: - title: "신뢰도당 사용자 수" - xaxis: "신뢰도" + title: "회원등급당 사용자 수" + xaxis: "회원등급" yaxis: "사용자 수" emails: title: "이메일 보냄" @@ -607,10 +607,10 @@ ko: delete_old_hidden_posts: "30일이 지난 숨겨진 글은 자동으로 삭제됩니다." default_locale: "Discourse 인스턴스가 사용하는 기본 언어 (ISO 639-1 Code)" allow_user_locale: "사용자에게 자신이 원하는 언어를 선택 허용" - min_post_length: "포스트의 최소 글자 수" + min_post_length: "글의 최소 글자 수" min_first_post_length: "첫 글 (내용)의 최소 길이" min_private_message_post_length: "메세지 내용의 최소 길이" - max_post_length: "포스트의 최대 글자 수" + max_post_length: "글의 최대 글자 수" min_topic_title_length: "글타래 제목의 최소 글자 수" max_topic_title_length: "글타래 제목의 최대 글자 수" min_private_message_title_length: "메세지 제목의 최소 길이" @@ -618,8 +618,8 @@ ko: allow_uncategorized_topics: "카테고리 없이 게시 허용. 주의: 카테고리 없는 글은 이 항목을 비활성화에 하기전에 카테고리 설정을 해야 합니다." uncategorized_description: "'카테고리 없음' 카테고리의 설명, 설명을 쓰지 않으려면 빈칸으로 놔둔다." allow_duplicate_topic_titles: "같은 제목의 동일한 글타래 허용" - unique_posts_mins: "같은 컨텐츠를 다시 포스트 할 수 있는 기간(분)" - educate_until_posts: "새로운 사용자가 포스트를 작성할 시 포스트 작성 방법에 대한 교육패널을 보여주는데, 해당 패널이 보여지는 초기 포스트 개수" + unique_posts_mins: "같은 컨텐츠를 다시 글 할 수 있는 기간(분)" + educate_until_posts: "새로운 사용자가 글를 작성할 시 글 작성 방법에 대한 교육패널을 보여주는데, 해당 패널이 보여지는 초기 글 개수" title: "타이틀 태그에 쓰일 이 사이트의 이름" site_description: "이 사이트를 한 문장으로 설명해 주세요. 설명 메타태그에 사용됩니다." contact_email: "문의 답변 관련 중요 책임자 이메일주소. 접수안된 신고, 긴급사항으로 /about 양식에서 보낸 메일 등 중요한 알림에 사용 됩니다. " @@ -642,7 +642,7 @@ ko: add_rel_nofollow_to_user_content: "사용자 생성 컨텐츠에 대해서 rel nofollow 를 설정함. parent domain을 포함한 internal link는 예외임. 이 설정을 바꾸려면 모든 baked markdown을 \"rake posts:rebake\" 명령으로 변경해주서야 함" exclude_rel_nofollow_domains: "nofollow 도메인 리스트에 넣으면 링크에 추가가 안됩니다. tld.com은 자동으로 sub.tld.com도 포함합니다. 최소한, 이 사이트의 top-level 도메인은 허가해서 웹크롤러가 모든 자료를 검색할 수 있게 해야합니다. 다른 도메인으로도 이 사이트가 들어가진다면 그것도 추가해야 합니다." post_excerpt_maxlength: "글 인용에 허용되는 최대 글자수" - post_onebox_maxlength: "onebox가 적용된 Discourse 포스트에 허용되는 최대 글자수" + post_onebox_maxlength: "onebox가 적용된 Discourse 글에 허용되는 최대 글자수" onebox_domains_whitelist: "onebox 사용을 허가할 도메일 리스트; 이 도메인은 OpenGraph나 oEmbed를 지원하여야합니다. 테스트: http://iframely.com/debug" apple_touch_icon_url: "애플 디바이스는 144px의 아이콘을 사용함. 144px X 144px 사이즈를 추천함" notification_email: "The from: 이 이메일 주소는 모든 기본 시스템 메일을 보내는데 사용됩니다. 여기에 명시된 도메인은 SPF, DKIM가 적용되어 있어야하며, reverse PTR 레코드가 제대로 설정되어 있어야 메일이 도착 할 수 있습니다." @@ -654,7 +654,7 @@ ko: summary_likes_required: "하나의 글타래에 대하여 요약본 보기 모드가 활성화되기 전까지 요구되는 최소 좋아요 수" summary_percent_filter: "요약본 보기를 클릭시, 글 중에 몇 %의 상위 글을 보여줄 것인가?" summary_max_results: "이 주제에 대한 요약 글 최대 갯수" - enable_private_messages: "신뢰도 1인 유저들에게 메세지 작성과 메세지 답변을 허용하기" + 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: "보낼 데이터가 없을 때 응답 전에 서버가 기다려야하는 시간 (로그인된 유저 전용)" @@ -775,47 +775,47 @@ ko: s3_cdn_url: "s3 에셋에 사용될 CDN URL (예: https://cdn.somewhere.com). 경고: 이 설정 뒤에는 모든 글을 다시 구워야rebake 합니다." avatar_sizes: "자동 생성 아바타 사이즈 목록" enable_flash_video_onebox: "swf, flv(어도비 플래쉬)링크를 embed 할 수 있도록 함. 주의: 보안에 대한 위험성을 알려주는 것이 좋다." - default_invitee_trust_level: "사용자를 초대하기 위한 기본 신뢰도(0-4)" - default_trust_level: "모든 신규가입자의 기본 신뢰도 (0-4). 경고: 변경시에 스팸 문제가 생길 가능성이 높습니다." - tl1_requires_topics_entered: "새로운 사용자가 신뢰받는 사용자-1 이 되기 위해 들어가봐야되는 글타래의 개수" - tl1_requires_read_posts: "새로운 사용자가 신뢰받는 사용자-1 가 되기 위해 읽어야 하는 포스트 개수" - tl1_requires_time_spent_mins: "새로운 사용자가 신뢰받는 사용자-1 이 되기 위해 몇 분 동안 포스트를 읽어야 하는지." - tl2_requires_topics_entered: "기본 사용자가 자주오는 사용자-2 가 되기 위해 들어가 봐야 되는 글타래 갯수" - tl2_requires_read_posts: "기본 사용자가 자주오는 사용자-2 가 되기 위해 읽어야 하는 글타래 갯수" - tl2_requires_time_spent_mins: "기본 사용자가 자주오는 사용자-2 가 되기 위해 몇 분 동안 포스트를 읽어야 하는지." - tl2_requires_days_visited: "기본 사용자가 자주오는 사용자-2 가 되기 위해 몇 일을 방문해야 하는지." - tl2_requires_likes_received: "기본 사용자가 자주오는 사용자-2 가 되기 위해 받아야 하는 좋아요 갯수" - tl2_requires_likes_given: "기본 사용자가 자주오는 사용자-2 가 되기 위해 눌러야하는 좋아요 수" - tl2_requires_topic_reply_count: "기본 사용자가 자주오는 사용자-2 가 되기 위해 달아야 하는 덧글 수" + default_invitee_trust_level: "사용자를 초대하기 위한 기본 회원등급(0-4)" + default_trust_level: "모든 신규가입자의 기본 회원등급 (0-4). 경고: 변경시에 스팸 문제가 생길 가능성이 높습니다." + tl1_requires_topics_entered: "새회원이 회원등급-1이 되기 위해 봐야 하는 글타래 갯수" + tl1_requires_read_posts: "새회원이 회원등급-1이 되기 위해 읽어야 하는 글 갯수" + tl1_requires_time_spent_mins: "새회원이 회원등급-1 이 되기 위해 몇 분 동안 글을 읽어야 하는지." + tl2_requires_topics_entered: "회원등급-2가 되기 위해 봐야 하는 글타래 갯수" + tl2_requires_read_posts: "회원등급-2가 되기 위해 읽어야 하는 글타래 갯수" + tl2_requires_time_spent_mins: "회원등급-2가 가 되기 위해 몇 분 동안 글을 읽어야 하는지." + tl2_requires_days_visited: "회원등급-2가 되기 위해 며칠을 방문해야 하는지." + tl2_requires_likes_received: "회원등급-2가 되기 위해 받아야 하는 좋아요 횟수" + tl2_requires_likes_given: "회원등급-2가 되기 위해 눌러야하는 좋아요 횟수" + tl2_requires_topic_reply_count: "기회원등급-2가 되기 위해 달아야 하는 댓글 갯수" tl3_requires_days_visited: "VIP 사용자-3 이 되기 위해 지난 100일 동안 최소한 사이트에 방문해야 하는 수 (0 ~ 100)" tl3_requires_topics_replied_to: "VIP 사용자-3 이 되기 위해 지난 100일 동안 답글을 달아야하는 최소 글타래수(0 ~ )" tl3_requires_topics_viewed: "VIP 사용자-3 이 되기 위해 지난 100일 동안 생성된 전체 글타래 중 본 글타래의 비율(%, 0 ~ 100)" - tl3_requires_posts_read: "VIP 사용자-3 이 되기 위해 지난 100일 동안 생성된 전체 포스트 중 본 포스트의 비율(%, 0 ~ 100)" + tl3_requires_posts_read: "VIP 사용자-3 이 되기 위해 지난 100일 동안 생성된 전체 글 중 본 글의 비율(%, 0 ~ 100)" tl3_requires_topics_viewed_all_time: "VIP 사용자-3 이 되기 위해 꼭 보아야 하는 글타래의 전체 개수" tl3_requires_posts_read_all_time: "VIP 사용자-3 이 되기 위해 꼭 보아야 하는 글타래의 전체 개수" - tl3_requires_max_flagged: "사용자가 VIP 사용자-3 이 되기 위해 지난 100일동안 서로 다른 사용자에게 최소한으로 받지 말아야하는 신고된 포스트 수(0 ~ )" - tl3_promotion_min_duration: "신뢰도가 2로 떨어진 후 다시 VIP 사용자-3 이 될 수 있는 최소 일 수" + tl3_requires_max_flagged: "사용자가 VIP 사용자-3 이 되기 위해 지난 100일동안 서로 다른 사용자에게 최소한으로 받지 말아야하는 신고된 글 수(0 ~ )" + tl3_promotion_min_duration: "회원등급이 2로 떨어진 후 다시 VIP 사용자-3 이 될 수 있는 최소 일 수" tl3_requires_likes_given: "VIP 사용자-3 이 되기 위해 지난 100일 동안 해야 할 좋아요 수" tl3_requires_likes_received: "VIP 사용자-3 이 되기 위해 지난 100일 동안 받아야 할 좋아요 수" tl3_links_no_follow: "VIP 사용자-3 의 글에 있는 링크에서 rel=nofollow 를 제거하지 마시오." - min_trust_to_create_topic: "새로운 글타래를 생성하기 위한 최소 신뢰도" - min_trust_to_edit_wiki_post: "위키로 설정된 포스트를 수정할 수 있는 최소 신뢰도" - newuser_max_links: "새로운 사용자가 포스트에 붙일 수 있는 최대 링크 개수" - newuser_max_images: "새로운 사용자가 포스트에 붙일 수 있는 최대 이미지 개수" + min_trust_to_create_topic: "새로운 글타래를 생성하기 위한 최소 회원등급" + min_trust_to_edit_wiki_post: "위키로 설정된 글 수정할 수 있는 최소 회원등급" + newuser_max_links: "새로운 사용자가 글에 붙일 수 있는 최대 링크 개수" + newuser_max_images: "새로운 사용자가 글에 붙일 수 있는 최대 이미지 개수" newuser_max_attachments: "새로운 사용자가 포트에 붙일 수 있는 최대 첨부파일 개수" - newuser_max_mentions_per_post: "새료운 사용자가 포스트에 사용할 수 있는 최대 @name 알림 개수" + newuser_max_mentions_per_post: "새료운 사용자가 글에 사용할 수 있는 최대 @name 알림 개수" newuser_max_replies_per_topic: "다른 사용자가 답글을 달기 전, 새로운 사용자가 개별 글타래에 달 수 있는 최대 답글 개수" - max_mentions_per_post: "포스트 당 사용할 수 있는 최대 @name 알림의 수" - create_thumbnails: "포스트의 너무 큰 크기의 이미지는 thumnails와 lightbox를 만든다." - email_time_window_mins: "알림 메일을 보내기 전 대기 기간(분), 사용자에게 포스트의 변경하고 완료할 수 있는 기회를 준다." + max_mentions_per_post: "글 당 사용할 수 있는 최대 @name 알림의 수" + create_thumbnails: "글의 너무 큰 크기의 이미지는 thumnails와 lightbox를 만든다." + email_time_window_mins: "알림 메일을 보내기 전 대기 기간(분), 사용자에게 글의 변경하고 완료할 수 있는 기회를 준다." email_posts_context: "알림메일의 내용에 추가할 기존 답글 수" flush_timings_secs: "사용자의 이용 시간 데이터를 서버로 보내는 기간(초)" title_max_word_length: "글타래 제목안에 단어들의 최대 길이" title_min_entropy: "글타래 제목에 필요한 최소 엔트로피(서로 다른 글자들이 몇개 존재해야하는지)" - body_min_entropy: "포스트 본문에 필요한 최소 엔트로피(서로 다른 글자들이 몇개 존재해야하는지)" + body_min_entropy: "글 본문에 필요한 최소 엔트로피(서로 다른 글자들이 몇개 존재해야하는지)" title_fancy_entities: "글타래 제목에 일반 ASCII 문자로 만든 기호들을 보기 좋은 HTML로 변환시켜준다. 참고: SmartyPants http://daringfireball.net/projects/smartypants/" min_title_similar_length: "유사 글타래에 대한 체크가 있기 전, 최소 제목 글자 수" - min_body_similar_length: "유사 글타래에 대한 체크가 있기 전, 포스트의 최소 본문 글자 수" + min_body_similar_length: "유사 글타래에 대한 체크가 있기 전, 글의 최소 본문 글자 수" category_colors: "허용된 카테고리에 사용될 hexadecimal 색상 값의 리스트" category_style: "카테고리 뱃지 시각 스타일" max_image_size_kb: "최대 이미지 업로드 사이즈(kB). 이 설정은 꼭 nginx / apache와 proxy에도 적용해야 합니다." @@ -839,11 +839,11 @@ ko: tos_url: "이용약관이 있으면 전체 URL을 적어주세요." privacy_policy_url: "개인정보 보호가 있으면 전체 URL을 적어주세요." newuser_spam_host_threshold: "스팸인가 결정하는 새로운 사용자가 게시한 같은 링크 수 `newuser_spam_host_posts`" - white_listed_spam_host_domains: "스팸 호스트 테스트로부터 제외할 도메인 리스트. 새로운 사용자는 새로운 사용자는 헤당 도메인에 대한 링크를 포함하는 포스트를 생성하는데 제한되지 않는다." + white_listed_spam_host_domains: "스팸 호스트 테스트로부터 제외할 도메인 리스트. 새로운 사용자는 새로운 사용자는 헤당 도메인에 대한 링크를 포함하는 글를 생성하는데 제한되지 않는다." staff_like_weight: "스태프가 좋아요를 눌렀을 때 부여할 가산점" topic_view_duration_hours: "N 시간마다 IP/User 별로 새 글타래 조회수를 셉니다." levenshtein_distance_spammer_emails: "스패머 메일을 체크할 때, 허용할 다른 글자 개수(fuzzy match)" - max_new_accounts_per_registration_ip: "(n) 신뢰도의 0개의 계정(스태프도 tl2 이상도 아닌 계정만)이 이 IP에 있으면, 새로운 회원가입 방지." + max_new_accounts_per_registration_ip: "(n) 회원등급의 0개의 계정(스태프도 tl2 이상도 아닌 계정만)이 이 IP에 있으면, 새로운 회원가입 방지." min_ban_entries_for_roll_up: "Roll up 버튼을 눌렀을 때, 적어도 (N)개의 엔트리가 있다면 새 subnet ban 엔트리를 만듭니다." max_age_unmatched_emails: "(N)일 뒤에 안 맞는 막힌 이메일 접근을 지웁니다." max_age_unmatched_ips: "(N)일 뒤에 안 맞는 막힌 IP 접근을 지웁니다." @@ -863,12 +863,12 @@ ko: pop3_polling_username: "이메일 설문조사를 위한 POP3 사용자 계정 이름" pop3_polling_password: "이메일 설문조사를 위한 POP3 사용자 비밀번호" email_in: "이메일을 통해 새로운 글타래를 포스팅할 수 있도록 허가한다.(POP3 polling 필요) 각 카테고리의 \"Setting\" 탭에서 주소를 설정합니다." - email_in_min_trust: "이메일을 통해 새 글타래를 포스팅 할 수 있는 최소 사용자 신뢰도" + email_in_min_trust: "이메일을 통해 새 글타래를 포스팅 할 수 있는 최소 사용자 회원등급" email_prefix: "이메일 제목에 쓰일 [라벨]. 설정하지 않으면 기본적으로 'title(필수 설정의)' 이 된다." email_site_title: "사이트에서 전송되는 이메일의 보내는 이를 사이트 이름으로 설정. 설정하지 않으면 'title(필수 설정의)'이 된다. 만약 'title'에 보내는 이에 허용되지 않는 글자가 포함되어 있으면 이 설정이 사용된다." minimum_topics_similar: "새로운 글타래를 작성할 때, 유사한 글타래들을 보여주기 위해 존재해야 할 최소 글타래 개수" relative_date_duration: "절대적 날짜(9월 3일) 대신 상대적 날짜(7일 전)가 사용될 일 수 (글타래가 작성된 이후부터)" - delete_user_max_post_age: "(x)일 이전의 첫 포스트가 있는 유저는 삭제할 수 없음." + delete_user_max_post_age: "(x)일 이전의 첫 글가 있는 유저는 삭제할 수 없음." delete_all_posts_max: "전체 글 지우기 버튼을 통해 한번에 삭제할 수 있는 최대 글 수. 만약 사용자가 이것보다 많은 글을 가지고 있으면 한번에 삭제 할 수 없다." username_change_period: "등록 후 사용자 이름 최소 유지 일 수(0은 사용자 이름 변경을 막음)" email_editable: "등록 후 이메일 주소를 바꿀수 있는 있음" @@ -877,7 +877,7 @@ ko: default_avatars: "신규가입자가 받게될 기본 아바타 URL" automatically_download_gravatars: "사용자가 계정을 만들거나 이메일을 변경하자마자 Gravatar를 다운로드합니다." digest_topics: "요약 이메일에서 보여질 최대 글타래 개수" - digest_min_excerpt_length: "요약 이메일에서 최소 포스트 발췌 수" + digest_min_excerpt_length: "요약 이메일에서 최소 글 발췌 수" suppress_digest_email_after_days: "(n)일동안 사이트에서 보지 못한 사용자에게는 이메일 요약을 보내지 않습니다." disable_digest_emails: "모든 유저들 이메일 다이제스트 못하게 하기" max_daily_gravatar_crawls: "하루에 Discourse가 커스텀 아파타를 위해 Gravatar를 체크하는 최대 횟수" @@ -885,7 +885,7 @@ ko: staff_user_custom_fields: "스태프가 쓸 수 있는 공개 커스텀 필드 목록" enable_user_directory: "전체 유저 둘러보기 목록 제공" allow_anonymous_posting: "익명 모드 허용" - anonymous_posting_min_trust_level: "익명 게시 할 수 있는 최소 신뢰도" + anonymous_posting_min_trust_level: "익명 게시 할 수 있는 최소 회원등급" anonymous_account_duration_minutes: "한 익명이 익명계정을 만들 때 빨리 만드는 걸 막을 분 단위 시간 예: 600으로 정해놓으면 마지막 게시하고 익명 전환 후로 600분이 지나는 즉시 새 계정이 만들어 집니다." allow_profile_backgrounds: "사용자에게 프로필 배경 이미지 업로드를 허용합니다." sequential_replies_threshold: "한 유저가 한 글타래에 너무 연속으로 답글 달았다고 알리게 될 연속 글 수" @@ -897,10 +897,10 @@ ko: disable_edit_notifications: "'download_remote_images_to_local'가 활성화되있으면 시스템 사용자에 의한 수정 알림을 비활성화합니다." full_name_required: "사용자 정보에서 실명은 필수입니다." enable_names: "사용자의 실명을 프로필, 사용자카드, 이메일에서 보여줍니다. 실명을 아무데서도 안보이게 하려면 비활성화하세요." - display_name_on_posts: "포스트에 @username 뿐만 아니라 사용자의 전체 이름도 보여준다." + display_name_on_posts: "글에 @username 뿐만 아니라 사용자의 전체 이름도 보여준다." show_time_gap_days: "두 글의 일차를 보여주기 위해 두 글이 떨어져 있어야 할 일수" invites_per_page: "사용자 페이지에 방문자를 표시합니다." - short_progress_text_threshold: "글타래의 포스트 개수가 이 값을 넘어서면, 포스트 프로그래스바는 오직 현재 포스트 넘버만 보여준다. 만약 포스트 프로그래스바의 넓이는 변경하면, 이 숫자도 변경해야합니다." + short_progress_text_threshold: "글타래의 글 개수가 이 값을 넘어서면, 글 프로그래스바는 오직 현재 글 넘버만 보여준다. 만약 글 프로그래스바의 넓이는 변경하면, 이 숫자도 변경해야합니다." default_code_lang: "기본 programming language syntax highlighting은 GitHub code blocks이 적용된다. (lang-auto, ruby, python etc.)" warn_reviving_old_topic_age: "이 값보다 오래된 글타래에 답글을 달면, 오래된 토론이라는 것을 상기시키기 위해 주의 알림이 보여진다. 비활성화 값음 0 이다. " autohighlight_all_code: "형식이 지정되기 전의(되지 않은) 모든 코드 블럭들에 대해, 사용자가 언어를 지정하지 않아도 강제로 code highlighting이 적용된다." @@ -910,7 +910,7 @@ ko: embed_by_username: "embed 글타래를 작성한 Discourse 사용자 아이디 " embed_username_key_from_feed: "feed에서 Discourse 아이디 가져올 때 쓰는 Key" embed_truncate: "임베드된 글 비우기" - embed_post_limit: "포스트 개수가 최대치입니다." + embed_post_limit: "글 개수가 최대치입니다." embed_whitelist_selector: "embed에서 보여져야 할 DOM element 선택하는 CSS 셀렉터" embed_blacklist_selector: "embed에서 지워져야 할 DOM element 선택하는 CSS 셀렉터" notify_about_flags_after: "이 시간동안 처리되지 않은 신고가 들어와 있으면, contact_email 주소로 이메일을 보냅니다. 0으로 넣으면 비활성화됩니다." @@ -924,7 +924,7 @@ ko: emoji_set: "어떤 emoji가 좋은가요?" enforce_square_emoji: "모든 emoji를 정사각형 비율로 강제로 바꿉니다." approve_post_count: "신규가입자가 올릴 수 있는 글 수" - approve_unless_trust_level: "허가 받고 글 올려야 하는 유저들 최저 신뢰도" + approve_unless_trust_level: "허가 받고 글 올려야 하는 유저들 최저 회원등급" notify_about_queued_posts_after: "글이 리뷰가 되길 기다리면서 이 시간이 지나면 문의처 메일로 연락을 보냅니다. 0으로 두면 보내지 않습니다." errors: invalid_email: "유효하지 않은 이메일 주소입니다." @@ -1159,7 +1159,7 @@ ko: text_body_template: | 안녕하세요, - 새 유저가 쓴 포스트가 심의를 기다리고 있습니다. [여기서 승인 또는 거절합니다](%{base_url}/queued-posts). + 새 유저가 쓴 글가 심의를 기다리고 있습니다. [여기서 승인 또는 거절합니다](%{base_url}/queued-posts). flag_reasons: off_topic: "내가 올린 글타래가 **주제 벗어난 이야기**으로 신고 되었습니다. 사람들이 이 글타래의 제목이랑 글 내용이 안 맞는다고 느끼나 봅니다. " inappropriate: "내가 올린 글타래가 **부적절**로 신고 되었습니다. 사람들이 이 글타래가 [가이드라인](/guidelines)에서 벗어나 공격적, 모욕적, 폭력적이라고 느낍니다." @@ -1274,11 +1274,11 @@ ko: subject_template: "데이터 추출 실패" text_body_template: "죄송합니다. 데이터 추출에 실패하였습니다. 로그를 확인하시거나 관리자에게 문의해주세요." email_reject_trust_level: - subject_template: "[%{site_name}] 이메일 문제 -- 신뢰도 충족 안됨." + subject_template: "[%{site_name}] 이메일 문제 -- 회원등급 충족 안됨." text_body_template: | 죄송합니다, 이메일 메세지 %{destination} (titled %{former_title})가 안 됐습니다. - 새 글타래를 작성하는 데 필요한 신뢰도가 없습니다. 오류라고 생각되면 스태프에게 연락주세요. + 새 글타래를 작성하는 데 필요한 회원등급이 없습니다. 오류라고 생각되면 스태프에게 연락주세요. email_reject_no_account: subject_template: "[%{site_name}] 이메일 문제 -- 모르는 계정" text_body_template: | @@ -1304,7 +1304,7 @@ ko: text_body_template: | 죄송합니다, 이메일 메세지 %{destination} (titled %{former_title})가 안 됐습니다. - 이 카테고리에서 새 글타래를 작성하는 데 필요한 신뢰도가 없습니다. 오류라고 생각되면 스태프에게 연락주세요. + 이 카테고리에서 새 글타래를 작성하는 데 필요한 회원등급이 없습니다. 오류라고 생각되면 스태프에게 연락주세요. email_reject_post_error: subject_template: "[%{site_name}] 이메일 문제 -- 게시 오류" text_body_template: | From 71bac0c342631f8baf6652c377fd993d47134966 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 18 Sep 2015 16:12:41 -0400 Subject: [PATCH 1395/1435] Add page title and charset to embedded HTML --- app/views/embed/comments.html.erb | 5 +++-- app/views/layouts/embed.html.erb | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/views/embed/comments.html.erb b/app/views/embed/comments.html.erb index 8c68d54af0..511d401fe3 100644 --- a/app/views/embed/comments.html.erb +++ b/app/views/embed/comments.html.erb @@ -39,13 +39,14 @@ <%- end %> <% if @topic_view.topic.posts_count > 0 %> -