diff --git a/.tx/config b/.tx/config index 1377a12a5..718e97b54 100644 --- a/.tx/config +++ b/.tx/config @@ -1,6 +1,6 @@ [main] host = https://www.transifex.com -lang_map = es_ES: es, fr_FR: fr, ko_KR: ko, pt_PT: pt, vi_VN: vi +lang_map = es_ES: es, fr_FR: fr, ko_KR: ko, pt_PT: pt, sk_SK: sk, vi_VN: vi [discourse-org.clientenyml] file_filter = config/locales/client..yml diff --git a/Gemfile b/Gemfile index 0b130eb94..b131b3d79 100644 --- a/Gemfile +++ b/Gemfile @@ -46,11 +46,11 @@ gem 'active_model_serializers', '~> 0.8.3' gem 'onebox' gem 'ember-rails' -gem 'ember-source', '1.12.1' +gem 'ember-source', '1.12.2' gem 'barber' gem 'babel-transpiler' -gem 'message_bus' +gem 'message_bus', '2.0.0.beta.2' gem 'rails_multisite' @@ -83,7 +83,9 @@ gem 'omniauth-twitter' gem 'omniauth-github-discourse', require: 'omniauth-github' gem 'omniauth-oauth2', require: false -gem 'omniauth-google-oauth2' + +# this removes the dependency on 'addressable' +gem 'omniauth-google-oauth2', git: 'git://github.com/zquestz/omniauth-google-oauth2.git', ref: 'b492c4bb8286d35' gem 'oj' gem 'pg' gem 'pry-rails', require: false @@ -183,7 +185,7 @@ begin gem 'stackprof', require: false, platform: [:mri_21, :mri_22, :mri_23] gem 'memory_profiler', require: false, platform: [:mri_21, :mri_22, :mri_23] rescue Bundler::GemfileError - begin + begin STDERR.puts "You are running an old version of bundler, please upgrade bundler ASAP, if you are using Discourse docker, rebuild your container." gem 'stackprof', require: false, platform: [:mri_21, :mri_22] gem 'memory_profiler', require: false, platform: [:mri_21, :mri_22] diff --git a/Gemfile.lock b/Gemfile.lock index 201cc46b3..38a9d9266 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,54 +1,65 @@ +GIT + remote: git://github.com/zquestz/omniauth-google-oauth2.git + revision: b492c4bb8286d35ae1168d7f2e5c57769bfe45a0 + ref: b492c4bb8286d35 + specs: + omniauth-google-oauth2 (0.3.0) + jwt (~> 1.0) + multi_json (~> 1.3) + omniauth (>= 1.1.1) + omniauth-oauth2 (>= 1.3.1) + GEM remote: https://rubygems.org/ specs: - actionmailer (4.2.5) - actionpack (= 4.2.5) - actionview (= 4.2.5) - activejob (= 4.2.5) + actionmailer (4.2.5.1) + actionpack (= 4.2.5.1) + actionview (= 4.2.5.1) + activejob (= 4.2.5.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.5) - actionview (= 4.2.5) - activesupport (= 4.2.5) + actionpack (4.2.5.1) + actionview (= 4.2.5.1) + activesupport (= 4.2.5.1) rack (~> 1.6) rack-test (~> 0.6.2) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.5) - activesupport (= 4.2.5) + actionview (4.2.5.1) + activesupport (= 4.2.5.1) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) active_model_serializers (0.8.3) activemodel (>= 3.0) - activejob (4.2.5) - activesupport (= 4.2.5) + activejob (4.2.5.1) + activesupport (= 4.2.5.1) globalid (>= 0.3.0) - activemodel (4.2.5) - activesupport (= 4.2.5) + activemodel (4.2.5.1) + activesupport (= 4.2.5.1) builder (~> 3.1) - activerecord (4.2.5) - activemodel (= 4.2.5) - activesupport (= 4.2.5) + activerecord (4.2.5.1) + activemodel (= 4.2.5.1) + activesupport (= 4.2.5.1) arel (~> 6.0) - activesupport (4.2.5) + activesupport (4.2.5.1) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - annotate (2.6.10) - activerecord (>= 3.2, <= 4.3) + annotate (2.7.0) + activerecord (>= 3.2, < 6.0) rake (~> 10.4) arel (6.0.3) - aws-sdk (2.1.29) - aws-sdk-resources (= 2.1.29) - aws-sdk-core (2.1.29) + aws-sdk (2.2.9) + aws-sdk-resources (= 2.2.9) + aws-sdk-core (2.2.9) jmespath (~> 1.0) - aws-sdk-resources (2.1.29) - aws-sdk-core (= 2.1.29) - babel-source (5.8.19) + aws-sdk-resources (2.2.9) + aws-sdk-core (= 2.2.9) + babel-source (5.8.34) babel-transpiler (0.7.0) babel-source (>= 4.0, < 6) execjs (~> 2.0) @@ -62,7 +73,7 @@ GEM binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) builder (3.2.2) - byebug (6.0.2) + byebug (8.2.1) certified (1.0.0) coderay (1.1.0) concurrent-ruby (1.0.0) @@ -89,15 +100,15 @@ GEM ember-source (>= 1.1.0) jquery-rails (>= 1.0.17) railties (>= 3.1) - ember-source (1.12.1) + ember-source (1.12.2) erubis (2.7.0) eventmachine (1.0.8) excon (0.45.4) execjs (2.6.0) - exifr (1.2.3.1) + exifr (1.2.4) fabrication (2.9.8) fakeweb (1.3.0) - faraday (0.9.1) + faraday (0.9.2) multipart-post (>= 1.2, < 3) fast_blank (1.0.0) fast_stack (0.1.0) @@ -115,15 +126,15 @@ GEM thor (~> 0.19.1) fspath (2.1.1) gctools (0.2.3) - given_core (3.5.4) + given_core (3.7.1) sorcerer (>= 0.3.7) globalid (0.3.6) activesupport (>= 4.1.0) guess_html_encoding (0.0.11) - hashie (3.4.2) - highline (1.7.7) + hashie (3.4.3) + highline (1.7.8) hike (1.2.3) - hiredis (0.6.0) + hiredis (0.6.1) htmlentities (4.3.4) http-cookie (1.0.2) domain_name (~> 0.5) @@ -142,12 +153,12 @@ GEM railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (1.8.3) - jwt (1.5.1) + jwt (1.5.2) kgio (2.10.0) librarian (0.1.2) highline thor (~> 0.15) - libv8 (3.16.14.11) + libv8 (3.16.14.13) listen (0.7.3) logster (1.0.1) loofah (2.0.3) @@ -155,28 +166,28 @@ GEM lru_redux (1.1.0) mail (2.6.3) mime-types (>= 1.16, < 3) - memory_profiler (0.9.4) - message_bus (1.1.1) + memory_profiler (0.9.6) + message_bus (2.0.0.beta.2) rack (>= 1.1.3) redis metaclass (0.0.4) method_source (0.8.2) mime-types (2.99) mini_portile2 (2.0.0) - minitest (5.8.3) + minitest (5.8.4) mocha (1.1.0) metaclass (~> 0.0.1) - mock_redis (0.15.2) + mock_redis (0.15.4) moneta (0.8.0) - msgpack (0.6.2) + msgpack (0.7.4) multi_json (1.11.2) multi_xml (0.5.5) multipart-post (2.0.0) mustache (1.0.2) netrc (0.11.0) - nokogiri (1.6.7.1) + nokogiri (1.6.7.2) mini_portile2 (~> 2.0.0.rc2) - nokogumbo (1.4.1) + nokogumbo (1.4.7) nokogiri oauth (0.4.7) oauth2 (1.0.0) @@ -185,18 +196,15 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (~> 1.2) - oj (2.12.14) - omniauth (1.2.2) + oj (2.14.3) + omniauth (1.3.1) hashie (>= 1.2, < 4) - rack (~> 1.0) - omniauth-facebook (2.0.1) + rack (>= 1.0, < 3) + omniauth-facebook (3.0.0) omniauth-oauth2 (~> 1.2) omniauth-github-discourse (1.1.2) omniauth (~> 1.0) omniauth-oauth2 (~> 1.1) - omniauth-google-oauth2 (0.2.5) - omniauth (> 1.0) - omniauth-oauth2 (~> 1.1) omniauth-oauth (1.1.0) oauth omniauth (~> 1.0) @@ -209,7 +217,7 @@ GEM omniauth-twitter (1.2.1) json (~> 1.3) omniauth-oauth (~> 1.1) - onebox (1.5.31) + onebox (1.5.33) moneta (~> 0.8) multi_json (~> 1.11) mustache @@ -217,9 +225,9 @@ GEM openid-redis-store (0.0.2) redis ruby-openid - pg (0.18.3) - progress (3.1.0) - pry (0.10.1) + pg (0.18.4) + progress (3.1.1) + pry (0.10.3) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) @@ -227,10 +235,10 @@ GEM pry (>= 0.9.10, < 0.11.0) pry-rails (0.3.4) pry (>= 0.9.10) - puma (2.14.0) - r2 (0.2.5) + puma (2.15.3) + r2 (0.2.6) rack (1.6.4) - rack-mini-profiler (0.9.7) + rack-mini-profiler (0.9.8) rack (>= 1.1.3) rack-openid (1.3.1) rack (>= 1.1.0) @@ -239,16 +247,16 @@ GEM rack rack-test (0.6.3) rack (>= 1.0) - rails (4.2.5) - actionmailer (= 4.2.5) - actionpack (= 4.2.5) - actionview (= 4.2.5) - activejob (= 4.2.5) - activemodel (= 4.2.5) - activerecord (= 4.2.5) - activesupport (= 4.2.5) + rails (4.2.5.1) + actionmailer (= 4.2.5.1) + actionpack (= 4.2.5.1) + actionview (= 4.2.5.1) + activejob (= 4.2.5.1) + activemodel (= 4.2.5.1) + activerecord (= 4.2.5.1) + activesupport (= 4.2.5.1) bundler (>= 1.3.0, < 2.0) - railties (= 4.2.5) + railties (= 4.2.5.1) sprockets-rails rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) @@ -256,21 +264,21 @@ GEM activesupport (>= 4.2.0.beta, < 5.0) nokogiri (~> 1.6.0) rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.0.2) + rails-html-sanitizer (1.0.3) loofah (~> 2.0) rails-observers (0.1.2) activemodel (~> 4.0) rails_multisite (1.0.3) - railties (4.2.5) - actionpack (= 4.2.5) - activesupport (= 4.2.5) + railties (4.2.5.1) + actionpack (= 4.2.5.1) + activesupport (= 4.2.5.1) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) raindrops (0.15.0) - rake (10.4.2) + rake (10.5.0) rake-compiler (0.9.5) rake - rb-fsevent (0.9.6) + rb-fsevent (0.9.7) rb-inotify (0.9.5) ffi (>= 0.5.0) rbtrace (0.4.7) @@ -296,16 +304,16 @@ GEM rspec-expectations (3.2.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.2.0) - rspec-given (3.5.4) - given_core (= 3.5.4) - rspec (>= 2.12) + rspec-given (3.7.1) + given_core (= 3.7.1) + rspec (>= 2.14.0) rspec-html-matchers (0.7.0) nokogiri (~> 1) rspec (~> 3) rspec-mocks (3.2.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.2.0) - rspec-rails (3.2.1) + rspec-rails (3.2.3) actionpack (>= 3.0, < 4.3) activesupport (>= 3.0, < 4.3) railties (>= 3.0, < 4.3) @@ -319,10 +327,10 @@ GEM ruby-readability (0.7.0) guess_html_encoding (>= 0.0.4) nokogiri (>= 1.6.0) - sanitize (4.0.0) + sanitize (4.0.1) crass (~> 1.0.2) nokogiri (>= 1.4.4) - nokogumbo (= 1.4.1) + nokogumbo (~> 1.4.1) sass (3.2.19) sass-rails (4.0.5) railties (>= 4.0.0, < 5.0) @@ -336,17 +344,16 @@ GEM shoulda-context (~> 1.0, >= 1.0.1) shoulda-matchers (>= 1.4.1, < 3.0) shoulda-context (1.2.1) - shoulda-matchers (2.7.0) + shoulda-matchers (2.8.0) activesupport (>= 3.0.0) - sidekiq (4.0.1) + sidekiq (4.0.2) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) - json (~> 1.0) redis (~> 3.2, >= 3.2.1) sidekiq-statistic (1.2.0) sidekiq (>= 3.3.4, < 5) simple-rss (1.3.1) - simplecov (0.10.0) + simplecov (0.11.1) docile (~> 1.1.0) json (~> 1.8) simplecov-html (~> 0.10.0) @@ -382,7 +389,7 @@ GEM thread_safe (0.3.5) tilt (1.4.1) timecop (0.8.0) - trollop (2.1.1) + trollop (2.1.2) tzinfo (1.2.2) thread_safe (~> 0.1) uglifier (2.7.2) @@ -390,8 +397,8 @@ GEM json (>= 1.8.0) unf (0.1.4) unf_ext - unf_ext (0.0.6) - unicorn (4.9.0) + unf_ext (0.0.7.1) + unicorn (5.0.1) kgio (~> 2.6) rack raindrops (~> 0.7) @@ -412,7 +419,7 @@ DEPENDENCIES discourse-qunit-rails discourse_email_parser ember-rails - ember-source (= 1.12.1) + ember-source (= 1.12.2) excon fabrication (= 2.9.8) fakeweb (~> 1.3.0) @@ -433,7 +440,7 @@ DEPENDENCIES lru_redux mail memory_profiler - message_bus + message_bus (= 2.0.0.beta.2) mime-types minitest mocha @@ -445,7 +452,7 @@ DEPENDENCIES omniauth omniauth-facebook omniauth-github-discourse - omniauth-google-oauth2 + omniauth-google-oauth2! omniauth-oauth2 omniauth-openid omniauth-twitter diff --git a/README.md b/README.md index 683411485..02ff312d2 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Plus *lots* of Ruby Gems, a complete list of which is at [/master/Gemfile](https ## Contributing -[![Build Status](https://travis-ci.org/discourse/discourse.svg)](https://travis-ci.org/discourse/discourse) +[![Build Status](https://api.travis-ci.org/discourse/discourse.svg?branch=master)](https://travis-ci.org/discourse/discourse) [![Code Climate](https://codeclimate.com/github/discourse/discourse.svg)](https://codeclimate.com/github/discourse/discourse) Discourse is **100% free** and **open source**. We encourage and support an active, healthy community that diff --git a/app/assets/javascripts/admin/controllers/admin-email-all.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-all.js.es6 deleted file mode 100644 index 5f456c108..000000000 --- a/app/assets/javascripts/admin/controllers/admin-email-all.js.es6 +++ /dev/null @@ -1,3 +0,0 @@ -import AdminEmailSkippedController from "admin/controllers/admin-email-skipped"; - -export default AdminEmailSkippedController.extend(); diff --git a/app/assets/javascripts/admin/controllers/admin-email-incomings.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-incomings.js.es6 new file mode 100644 index 000000000..deb7f0771 --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-email-incomings.js.es6 @@ -0,0 +1,11 @@ +import IncomingEmail from 'admin/models/incoming-email'; + +export default Ember.Controller.extend({ + loadMore() { + return IncomingEmail.findAll(this.get("filter"), this.get("model.length")) + .then(incoming => { + if (incoming.length < 50) { this.get("model").set("allLoaded", true); } + this.get("model").addObjects(incoming); + }); + } +}); diff --git a/app/assets/javascripts/admin/controllers/admin-email-logs.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-logs.js.es6 new file mode 100644 index 000000000..2506a0cd9 --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-email-logs.js.es6 @@ -0,0 +1,11 @@ +import EmailLog from 'admin/models/email-log'; + +export default Ember.Controller.extend({ + loadMore() { + return EmailLog.findAll(this.get("filter"), this.get("model.length")) + .then(logs => { + if (logs.length < 50) { this.get("model").set("allLoaded", true); } + this.get("model").addObjects(logs); + }); + } +}); diff --git a/app/assets/javascripts/admin/controllers/admin-email-received.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-received.js.es6 new file mode 100644 index 000000000..69ebd5e4c --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-email-received.js.es6 @@ -0,0 +1,9 @@ +import AdminEmailIncomingsController from 'admin/controllers/admin-email-incomings'; +import debounce from 'discourse/lib/debounce'; +import IncomingEmail from 'admin/models/incoming-email'; + +export default AdminEmailIncomingsController.extend({ + filterIncomingEmails: debounce(function() { + IncomingEmail.findAll(this.get("filter")).then(incomings => this.set("model", incomings)); + }, 250).observes("filter.{from,to,subject}") +}); diff --git a/app/assets/javascripts/admin/controllers/admin-email-rejected.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-rejected.js.es6 new file mode 100644 index 000000000..317a669cd --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-email-rejected.js.es6 @@ -0,0 +1,9 @@ +import AdminEmailIncomingsController from 'admin/controllers/admin-email-incomings'; +import debounce from 'discourse/lib/debounce'; +import IncomingEmail from 'admin/models/incoming-email'; + +export default AdminEmailIncomingsController.extend({ + filterIncomingEmails: debounce(function() { + IncomingEmail.findAll(this.get("filter")).then(incomings => this.set("model", incomings)); + }, 250).observes("filter.{from,to,subject,error}") +}); diff --git a/app/assets/javascripts/admin/controllers/admin-email-sent.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-sent.js.es6 index 6ea640672..d73d640ad 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-sent.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-sent.js.es6 @@ -1,12 +1,9 @@ +import AdminEmailLogsController from 'admin/controllers/admin-email-logs'; import debounce from 'discourse/lib/debounce'; import EmailLog from 'admin/models/email-log'; -export default Ember.Controller.extend({ - +export default AdminEmailLogsController.extend({ filterEmailLogs: debounce(function() { - var self = this; - EmailLog.findAll(this.get("filter")).then(function(logs) { - self.set("model", logs); - }); - }, 250).observes("filter.user", "filter.address", "filter.type", "filter.reply_key") + EmailLog.findAll(this.get("filter")).then(logs => this.set("model", logs)); + }, 250).observes("filter.{user,address,type,reply_key}") }); diff --git a/app/assets/javascripts/admin/controllers/admin-email-skipped.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-skipped.js.es6 index b392ea8e9..ae75d1871 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-skipped.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-skipped.js.es6 @@ -1,8 +1,9 @@ +import AdminEmailLogsController from 'admin/controllers/admin-email-logs'; import debounce from 'discourse/lib/debounce'; +import EmailLog from 'admin/models/email-log'; -export default Ember.Controller.extend({ +export default AdminEmailLogsController.extend({ filterEmailLogs: debounce(function() { - const EmailLog = require('admin/models/email-log').default; EmailLog.findAll(this.get("filter")).then(logs => this.set("model", logs)); - }, 250).observes("filter.user", "filter.address", "filter.type", "filter.skipped_reason") + }, 250).observes("filter.{user,address,type,skipped_reason}") }); diff --git a/app/assets/javascripts/admin/controllers/admin-reports.js.es6 b/app/assets/javascripts/admin/controllers/admin-reports.js.es6 index d11e67305..bdafff9f0 100644 --- a/app/assets/javascripts/admin/controllers/admin-reports.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-reports.js.es6 @@ -1,5 +1,6 @@ import { exportEntity } from 'discourse/lib/export-csv'; import { outputExportResult } from 'discourse/lib/export-result'; +import Report from 'admin/models/report'; export default Ember.Controller.extend({ viewMode: 'table', @@ -20,9 +21,9 @@ export default Ember.Controller.extend({ var q; this.set("refreshing", true); if (this.get('categoryId') === "all") { - q = Discourse.Report.find(this.get("model.type"), this.get("startDate"), this.get("endDate")); + q = Report.find(this.get("model.type"), this.get("startDate"), this.get("endDate")); } else { - q = Discourse.Report.find(this.get("model.type"), this.get("startDate"), this.get("endDate"), this.get("categoryId")); + q = Report.find(this.get("model.type"), this.get("startDate"), this.get("endDate"), this.get("categoryId")); } q.then(m => this.set("model", m)).finally(() => this.set("refreshing", false)); }, diff --git a/app/assets/javascripts/admin/controllers/modals/admin-edit-badge-groupings.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-edit-badge-groupings.js.es6 index 74c4f79e6..6c0f6b9cc 100644 --- a/app/assets/javascripts/admin/controllers/modals/admin-edit-badge-groupings.js.es6 +++ b/app/assets/javascripts/admin/controllers/modals/admin-edit-badge-groupings.js.es6 @@ -2,15 +2,13 @@ export default Ember.Controller.extend({ needs: ['modal'], modelChanged: function(){ - - var grouping = Em.Object.extend({}); - - var model = this.get('model'); - var copy = Em.A(); + const model = this.get('model'); + const copy = Em.A(); + const store = this.store; if(model){ model.forEach(function(o){ - copy.pushObject(grouping.create(o)); + copy.pushObject(store.createRecord('badge-grouping', o)); }); } @@ -18,8 +16,8 @@ export default Ember.Controller.extend({ }.observes('model'), moveItem: function(item, delta){ - var copy = this.get('workingCopy'); - var index = copy.indexOf(item); + const copy = this.get('workingCopy'); + const index = copy.indexOf(item); if (index + delta < 0 || index + delta >= copy.length){ return; } @@ -50,14 +48,14 @@ export default Ember.Controller.extend({ item.set("editing", false); }, add: function(){ - var obj = Em.Object.create({editing: true, name: "Enter Name"}); + const obj = this.store.createRecord('badge-grouping', {editing: true, name: I18n.t('admin.badges.badge_grouping')}); this.get('workingCopy').pushObject(obj); }, saveAll: function(){ - var self = this; + const self = this; var items = this.get('workingCopy'); - var groupIds = items.map(function(i){return i.get("id") || -1;}); - var names = items.map(function(i){return i.get("name");}); + const groupIds = items.map(function(i){return i.get("id") || -1;}); + const names = items.map(function(i){return i.get("name");}); Discourse.ajax('/admin/badges/badge_groupings',{ data: {ids: groupIds, names: names}, @@ -66,14 +64,13 @@ export default Ember.Controller.extend({ items = self.get("model"); items.clear(); data.badge_groupings.forEach(function(g){ - items.pushObject(Em.Object.create(g)); + items.pushObject(self.store.createRecord('badge-grouping', g)); }); self.set('model', null); self.set('workingCopy', null); self.send('closeModal'); },function(){ - // TODO we can do better - bootbox.alert("Something went wrong"); + bootbox.alert(I18n.t('generic_error')); }); } } diff --git a/app/assets/javascripts/admin/models/admin-user.js.es6 b/app/assets/javascripts/admin/models/admin-user.js.es6 index a4341d09d..267d951e5 100644 --- a/app/assets/javascripts/admin/models/admin-user.js.es6 +++ b/app/assets/javascripts/admin/models/admin-user.js.es6 @@ -9,6 +9,8 @@ const AdminUser = Discourse.User.extend({ customGroups: Em.computed.filter("groups", (g) => !g.automatic && Group.create(g)), automaticGroups: Em.computed.filter("groups", (g) => g.automatic && Group.create(g)), + canViewProfile: Ember.computed.or("active", "staged"), + generateApiKey() { const self = this; return Discourse.ajax("/admin/users/" + this.get('id') + "/generate_api_key", { @@ -264,6 +266,7 @@ const AdminUser = Discourse.User.extend({ }, unblock() { + this.set('blockingUser', true); return Discourse.ajax('/admin/users/' + this.id + '/unblock', { type: 'PUT' }).then(function() { @@ -275,14 +278,33 @@ const AdminUser = Discourse.User.extend({ }, block() { - return Discourse.ajax('/admin/users/' + this.id + '/block', { - type: 'PUT' - }).then(function() { - window.location.reload(); - }).catch(function(e) { - var error = I18n.t('admin.user.block_failed', { error: "http: " + e.status + " - " + e.body }); - bootbox.alert(error); - }); + const user = this, + message = I18n.t("admin.user.block_confirm"); + + const performBlock = function() { + user.set('blockingUser', true); + return Discourse.ajax('/admin/users/' + user.id + '/block', { + type: 'PUT' + }).then(function() { + window.location.reload(); + }).catch(function(e) { + var error = I18n.t('admin.user.block_failed', { error: "http: " + e.status + " - " + e.body }); + bootbox.alert(error); + user.set('blockingUser', false); + }); + }; + + const buttons = [{ + "label": I18n.t("composer.cancel"), + "class": "cancel", + "link": true + }, { + "label": '' + I18n.t('admin.user.block_accept'), + "class": "btn btn-danger", + "callback": function() { performBlock(); } + }]; + + bootbox.dialog(message, buttons, { "classes": "delete-user-modal" }); }, sendActivationEmail() { diff --git a/app/assets/javascripts/admin/models/email-log.js.es6 b/app/assets/javascripts/admin/models/email-log.js.es6 index ce7d8a242..2b19eeff4 100644 --- a/app/assets/javascripts/admin/models/email-log.js.es6 +++ b/app/assets/javascripts/admin/models/email-log.js.es6 @@ -4,7 +4,7 @@ const EmailLog = Discourse.Model.extend({}); EmailLog.reopenClass({ - create: function(attrs) { + create(attrs) { attrs = attrs || {}; if (attrs.user) { @@ -14,16 +14,15 @@ EmailLog.reopenClass({ return this._super(attrs); }, - findAll: function(filter) { + findAll(filter, offset) { filter = filter || {}; - var status = filter.status || "all"; + offset = offset || 0; + + const status = filter.status || "sent"; filter = _.omit(filter, "status"); - return Discourse.ajax("/admin/email/" + status + ".json", { data: filter }).then(function(logs) { - return _.map(logs, function (log) { - return EmailLog.create(log); - }); - }); + return Discourse.ajax(`/admin/email/${status}.json?offset=${offset}`, { data: filter }) + .then(logs => _.map(logs, log => EmailLog.create(log))); } }); diff --git a/app/assets/javascripts/admin/models/incoming-email.js.es6 b/app/assets/javascripts/admin/models/incoming-email.js.es6 new file mode 100644 index 000000000..677fbebbc --- /dev/null +++ b/app/assets/javascripts/admin/models/incoming-email.js.es6 @@ -0,0 +1,29 @@ +import AdminUser from 'admin/models/admin-user'; + +const IncomingEmail = Discourse.Model.extend({}); + +IncomingEmail.reopenClass({ + + create(attrs) { + attrs = attrs || {}; + + if (attrs.user) { + attrs.user = AdminUser.create(attrs.user); + } + + return this._super(attrs); + }, + + findAll(filter, offset) { + filter = filter || {}; + offset = offset || 0; + + const status = filter.status || "received"; + filter = _.omit(filter, "status"); + + return Discourse.ajax(`/admin/email/${status}.json?offset=${offset}`, { data: filter }) + .then(incomings => _.map(incomings, incoming => IncomingEmail.create(incoming))); + } +}); + +export default IncomingEmail; diff --git a/app/assets/javascripts/admin/routes/admin-badges.js.es6 b/app/assets/javascripts/admin/routes/admin-badges.js.es6 index 592743766..5efa86491 100644 --- a/app/assets/javascripts/admin/routes/admin-badges.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-badges.js.es6 @@ -1,4 +1,5 @@ import Badge from 'discourse/models/badge'; +import BadgeGrouping from 'discourse/models/badge-grouping'; export default Discourse.Route.extend({ _json: null, @@ -13,14 +14,19 @@ export default Discourse.Route.extend({ setupController: function(controller, model) { var json = this._json, - triggers = []; + triggers = [], + badgeGroupings = []; _.each(json.admin_badges.triggers,function(v,k){ triggers.push({id: v, name: I18n.t('admin.badges.trigger_type.'+k)}); }); + json.badge_groupings.forEach(function(badgeGroupingJson) { + badgeGroupings.push(BadgeGrouping.create(badgeGroupingJson)); + }); + controller.setProperties({ - badgeGroupings: json.badge_groupings, + badgeGroupings: badgeGroupings, badgeTypes: json.badge_types, protectedSystemFields: json.admin_badges.protected_system_fields, badgeTriggers: triggers, diff --git a/app/assets/javascripts/admin/routes/admin-email-all.js.es6 b/app/assets/javascripts/admin/routes/admin-email-all.js.es6 deleted file mode 100644 index be310b9c3..000000000 --- a/app/assets/javascripts/admin/routes/admin-email-all.js.es6 +++ /dev/null @@ -1,2 +0,0 @@ -import AdminEmailLogs from 'admin/routes/admin-email-logs'; -export default AdminEmailLogs.extend({ status: "all" }); diff --git a/app/assets/javascripts/admin/routes/admin-email-incomings.js.es6 b/app/assets/javascripts/admin/routes/admin-email-incomings.js.es6 new file mode 100644 index 000000000..7eefb322c --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-email-incomings.js.es6 @@ -0,0 +1,14 @@ +import IncomingEmail from 'admin/models/incoming-email'; + +export default Discourse.Route.extend({ + + model() { + return IncomingEmail.findAll({ status: this.get("status") }); + }, + + setupController(controller, model) { + controller.set("model", model); + controller.set("filter", { status: this.get("status") }); + } + +}); diff --git a/app/assets/javascripts/admin/routes/admin-email-index.js.es6 b/app/assets/javascripts/admin/routes/admin-email-index.js.es6 index 9e8aa7d89..1b75e39f6 100644 --- a/app/assets/javascripts/admin/routes/admin-email-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-email-index.js.es6 @@ -1,11 +1,11 @@ import EmailSettings from 'admin/models/email-settings'; export default Discourse.Route.extend({ - model: function() { + model() { return EmailSettings.find(); }, - renderTemplate: function() { + renderTemplate() { this.render('admin/templates/email_index', { into: 'adminEmail' }); } }); diff --git a/app/assets/javascripts/admin/routes/admin-email-logs.js.es6 b/app/assets/javascripts/admin/routes/admin-email-logs.js.es6 index f6cbd90ea..27791bcae 100644 --- a/app/assets/javascripts/admin/routes/admin-email-logs.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-email-logs.js.es6 @@ -1,27 +1,14 @@ import EmailLog from 'admin/models/email-log'; -/** - Handles routes related to viewing email logs. - - @class AdminEmailSentRoute - @extends Discourse.Route - @namespace Discourse - @module Discourse -**/ export default Discourse.Route.extend({ - model: function() { + model() { return EmailLog.findAll({ status: this.get("status") }); }, - setupController: function(controller, model) { + setupController(controller, model) { controller.set("model", model); - // resets the filters controller.set("filter", { status: this.get("status") }); - }, - - renderTemplate: function() { - this.render("admin/templates/email_" + this.get("status"), { into: "adminEmail" }); } }); diff --git a/app/assets/javascripts/admin/routes/admin-email-received.js.es6 b/app/assets/javascripts/admin/routes/admin-email-received.js.es6 new file mode 100644 index 000000000..4bea62c1e --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-email-received.js.es6 @@ -0,0 +1,2 @@ +import AdminEmailIncomings from 'admin/routes/admin-email-incomings'; +export default AdminEmailIncomings.extend({ status: "received" }); diff --git a/app/assets/javascripts/admin/routes/admin-email-rejected.js.es6 b/app/assets/javascripts/admin/routes/admin-email-rejected.js.es6 new file mode 100644 index 000000000..ed662e211 --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-email-rejected.js.es6 @@ -0,0 +1,2 @@ +import AdminEmailIncomings from 'admin/routes/admin-email-incomings'; +export default AdminEmailIncomings.extend({ status: "rejected" }); diff --git a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 index 406a00117..3626ea48d 100644 --- a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 @@ -8,9 +8,10 @@ export default { }); this.resource('adminEmail', { path: '/email'}, function() { - this.route('all'); this.route('sent'); this.route('skipped'); + this.route('received'); + this.route('rejected'); this.route('previewDigest', { path: '/preview-digest' }); }); diff --git a/app/assets/javascripts/admin/templates/badges-show.hbs b/app/assets/javascripts/admin/templates/badges-show.hbs index 2a6b74f16..173b8c60d 100644 --- a/app/assets/javascripts/admin/templates/badges-show.hbs +++ b/app/assets/javascripts/admin/templates/badges-show.hbs @@ -2,25 +2,22 @@
- {{input type="text" name="name" value=buffered.name}} + {{#if readOnly}} + {{input type="text" name="name" value=buffered.displayName disabled=true}} + {{else}} + {{input type="text" name="name" value=buffered.name}} + {{/if}}
- {{#if showDisplayName}} -
- {{i18n 'admin.badges.display_name'}} - {{buffered.displayName}} -
- {{/if}} -
- - {{input type="text" name="name" value=buffered.icon}} + + {{input type="text" name="icon" value=buffered.icon}}

{{i18n 'admin.badges.icon_help'}}

- - {{input type="text" name="name" value=buffered.image}} + + {{input type="text" name="image" value=buffered.image}}

{{i18n 'admin.badges.icon_help'}}

@@ -40,7 +37,7 @@ value=buffered.badge_grouping_id content=badgeGroupings optionValuePath="content.id" - optionLabelPath="content.name"}} + optionLabelPath="content.displayName"}}   diff --git a/app/assets/javascripts/admin/templates/email-received.hbs b/app/assets/javascripts/admin/templates/email-received.hbs new file mode 100644 index 000000000..95e0cb2dc --- /dev/null +++ b/app/assets/javascripts/admin/templates/email-received.hbs @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + {{#each email in model}} + + + + + + + {{else}} + + {{/each}} + + + +{{conditional-loading-spinner condition=view.loading}} diff --git a/app/assets/javascripts/admin/templates/email-rejected.hbs b/app/assets/javascripts/admin/templates/email-rejected.hbs new file mode 100644 index 000000000..6e055ffb2 --- /dev/null +++ b/app/assets/javascripts/admin/templates/email-rejected.hbs @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + {{#each email in model}} + + + + + + + + {{else}} + + {{/each}} + + + +{{conditional-loading-spinner condition=view.loading}} diff --git a/app/assets/javascripts/admin/templates/email_sent.hbs b/app/assets/javascripts/admin/templates/email-sent.hbs similarity index 93% rename from app/assets/javascripts/admin/templates/email_sent.hbs rename to app/assets/javascripts/admin/templates/email-sent.hbs index 0287492f1..4e4f5dbf7 100644 --- a/app/assets/javascripts/admin/templates/email_sent.hbs +++ b/app/assets/javascripts/admin/templates/email-sent.hbs @@ -1,4 +1,4 @@ - +
@@ -37,3 +37,5 @@ {{/each}} + +{{conditional-loading-spinner condition=view.loading}} diff --git a/app/assets/javascripts/admin/templates/email_skipped.hbs b/app/assets/javascripts/admin/templates/email-skipped.hbs similarity index 94% rename from app/assets/javascripts/admin/templates/email_skipped.hbs rename to app/assets/javascripts/admin/templates/email-skipped.hbs index c2c6d261a..d983b0937 100644 --- a/app/assets/javascripts/admin/templates/email_skipped.hbs +++ b/app/assets/javascripts/admin/templates/email-skipped.hbs @@ -1,4 +1,4 @@ - +
@@ -37,3 +37,5 @@ {{/each}} + +{{conditional-loading-spinner condition=view.loading}} diff --git a/app/assets/javascripts/admin/templates/email.hbs b/app/assets/javascripts/admin/templates/email.hbs index e14d8ba4b..1a7d5bbfe 100644 --- a/app/assets/javascripts/admin/templates/email.hbs +++ b/app/assets/javascripts/admin/templates/email.hbs @@ -1,10 +1,11 @@ {{#admin-nav}} {{nav-item route='adminEmail.index' label='admin.email.settings'}} - {{nav-item route='adminEmail.all' label='admin.email.all'}} + {{nav-item route='adminEmail.previewDigest' label='admin.email.preview_digest'}} + {{nav-item route='adminCustomizeEmailTemplates' label='admin.email.templates'}} {{nav-item route='adminEmail.sent' label='admin.email.sent'}} {{nav-item route='adminEmail.skipped' label='admin.email.skipped'}} - {{nav-item route='adminEmail.previewDigest' label='admin.email.preview_digest'}} - {{nav-item route='adminCustomizeEmailTemplates' label='admin.customize.email_templates.title'}} + {{nav-item route='adminEmail.received' label='admin.email.received'}} + {{nav-item route='adminEmail.rejected' label='admin.email.rejected'}} {{/admin-nav}}
diff --git a/app/assets/javascripts/admin/templates/email_all.hbs b/app/assets/javascripts/admin/templates/email_all.hbs deleted file mode 100644 index c2c6d261a..000000000 --- a/app/assets/javascripts/admin/templates/email_all.hbs +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - {{#each l in model}} - - - - - - - - {{else}} - - {{/each}} - -
{{i18n 'admin.email.time'}}{{i18n 'admin.email.user'}}{{i18n 'admin.email.to_address'}}{{i18n 'admin.email.email_type'}}{{i18n 'admin.email.skipped_reason'}}
{{i18n 'admin.email.logs.filters.title'}}{{text-field value=filter.user placeholderKey="admin.email.logs.filters.user_placeholder"}}{{text-field value=filter.address placeholderKey="admin.email.logs.filters.address_placeholder"}}{{text-field value=filter.type placeholderKey="admin.email.logs.filters.type_placeholder"}}{{text-field value=filter.skipped_reason placeholderKey="admin.email.logs.filters.skipped_reason_placeholder"}}
{{format-date l.created_at}} - {{#if l.user}} - {{#link-to 'adminUser' l.user}}{{avatar l.user imageSize="tiny"}}{{/link-to}} - {{#link-to 'adminUser' l.user}}{{l.user.username}}{{/link-to}} - {{else}} - — - {{/if}} - {{l.to_address}}{{l.email_type}}{{l.skipped_reason}}
{{i18n 'admin.email.logs.none'}}
diff --git a/app/assets/javascripts/admin/templates/modal/admin_edit_badge_groupings.hbs b/app/assets/javascripts/admin/templates/modal/admin_edit_badge_groupings.hbs index 1dd273e0f..f2a6581bb 100644 --- a/app/assets/javascripts/admin/templates/modal/admin_edit_badge_groupings.hbs +++ b/app/assets/javascripts/admin/templates/modal/admin_edit_badge_groupings.hbs @@ -5,15 +5,15 @@
  • {{#if wc.editing}} {{input value=wc.name}} - + {{else}} - {{wc.name}} + {{wc.displayName}} {{/if}}
    - - - - + + + +
  • {{/each}} diff --git a/app/assets/javascripts/admin/templates/user-index.hbs b/app/assets/javascripts/admin/templates/user-index.hbs index 32190c967..5c1c402d4 100644 --- a/app/assets/javascripts/admin/templates/user-index.hbs +++ b/app/assets/javascripts/admin/templates/user-index.hbs @@ -1,11 +1,13 @@
    - {{#if model.active}} + {{#if model.canViewProfile}} {{#link-to 'user' model class="btn"}} {{fa-icon "user"}} {{i18n 'admin.user.show_public_profile'}} {{/link-to}} + {{/if}} + {{#if model.active}} {{#if model.can_impersonate}} - {{i18n 'admin.user.block_explanation'}} - {{/if}} + {{#conditional-loading-spinner size="small" condition=model.blockingUser}} + {{#if model.blocked}} + + {{i18n 'admin.user.block_explanation'}} + {{else}} + + {{i18n 'admin.user.block_explanation'}} + {{/if}} + {{/conditional-loading-spinner}}
    + +
    +
    {{i18n 'admin.user.staged'}}
    +
    {{model.staged}}
    +
    {{i18n 'admin.user.stage_explanation'}}
    +
    diff --git a/app/assets/javascripts/admin/views/admin-email-incomings.js.es6 b/app/assets/javascripts/admin/views/admin-email-incomings.js.es6 new file mode 100644 index 000000000..6360f857b --- /dev/null +++ b/app/assets/javascripts/admin/views/admin-email-incomings.js.es6 @@ -0,0 +1,14 @@ +import LoadMore from "discourse/mixins/load-more"; + +export default Ember.View.extend(LoadMore, { + loading: false, + eyelineSelector: ".email-list tr", + + actions: { + loadMore() { + if (this.get("loading") || this.get("model.allLoaded")) { return; } + this.set("loading", true); + return this.get("controller").loadMore().then(() => this.set("loading", false)); + } + } +}); diff --git a/app/assets/javascripts/admin/views/admin-email-logs.js.es6 b/app/assets/javascripts/admin/views/admin-email-logs.js.es6 new file mode 100644 index 000000000..6360f857b --- /dev/null +++ b/app/assets/javascripts/admin/views/admin-email-logs.js.es6 @@ -0,0 +1,14 @@ +import LoadMore from "discourse/mixins/load-more"; + +export default Ember.View.extend(LoadMore, { + loading: false, + eyelineSelector: ".email-list tr", + + actions: { + loadMore() { + if (this.get("loading") || this.get("model.allLoaded")) { return; } + this.set("loading", true); + return this.get("controller").loadMore().then(() => this.set("loading", false)); + } + } +}); diff --git a/app/assets/javascripts/admin/views/admin-email-received.js.es6 b/app/assets/javascripts/admin/views/admin-email-received.js.es6 new file mode 100644 index 000000000..da0d8de15 --- /dev/null +++ b/app/assets/javascripts/admin/views/admin-email-received.js.es6 @@ -0,0 +1,5 @@ +import AdminEmailIncomingsView from "admin/views/admin-email-incomings"; + +export default AdminEmailIncomingsView.extend({ + templateName: "admin/templates/email-received" +}); diff --git a/app/assets/javascripts/admin/views/admin-email-rejected.js.es6 b/app/assets/javascripts/admin/views/admin-email-rejected.js.es6 new file mode 100644 index 000000000..b89fe8d46 --- /dev/null +++ b/app/assets/javascripts/admin/views/admin-email-rejected.js.es6 @@ -0,0 +1,5 @@ +import AdminEmailIncomingsView from "admin/views/admin-email-incomings"; + +export default AdminEmailIncomingsView.extend({ + templateName: "admin/templates/email-rejected" +}); diff --git a/app/assets/javascripts/admin/views/admin-email-sent.js.es6 b/app/assets/javascripts/admin/views/admin-email-sent.js.es6 new file mode 100644 index 000000000..d007a7964 --- /dev/null +++ b/app/assets/javascripts/admin/views/admin-email-sent.js.es6 @@ -0,0 +1,5 @@ +import AdminEmailLogsView from "admin/views/admin-email-logs"; + +export default AdminEmailLogsView.extend({ + templateName: "admin/templates/email-sent" +}); diff --git a/app/assets/javascripts/admin/views/admin-email-skipped.js.es6 b/app/assets/javascripts/admin/views/admin-email-skipped.js.es6 new file mode 100644 index 000000000..e3c446709 --- /dev/null +++ b/app/assets/javascripts/admin/views/admin-email-skipped.js.es6 @@ -0,0 +1,5 @@ +import AdminEmailLogsView from "admin/views/admin-email-logs"; + +export default AdminEmailLogsView.extend({ + templateName: "admin/templates/email-skipped" +}); diff --git a/app/assets/javascripts/discourse/components/category-chooser.js.es6 b/app/assets/javascripts/discourse/components/category-chooser.js.es6 index 1611fcbe5..110785db1 100644 --- a/app/assets/javascripts/discourse/components/category-chooser.js.es6 +++ b/app/assets/javascripts/discourse/components/category-chooser.js.es6 @@ -6,9 +6,9 @@ import PermissionType from 'discourse/models/permission-type'; export default ComboboxView.extend({ classNames: ['combobox category-combobox'], - overrideWidths: true, dataAttributes: ['id', 'description_text'], valueBinding: Ember.Binding.oneWay('source'), + overrideWidths: true, castInteger: true, @computed("scopedCategoryId", "categories") @@ -22,7 +22,6 @@ export default ComboboxView.extend({ return categories.filter(c => { if (scopedCategoryId && c.get('id') !== scopedCategoryId && c.get('parent_category_id') !== scopedCategoryId) { return false; } if (c.get('isUncategorizedCategory')) { return false; } - if (c.get('contains_messages')) { return false; } return c.get('permission') === PermissionType.FULL; }); }, diff --git a/app/assets/javascripts/discourse/components/category-group.js.es6 b/app/assets/javascripts/discourse/components/category-group.js.es6 index 887e4ad7b..4daca78a6 100644 --- a/app/assets/javascripts/discourse/components/category-group.js.es6 +++ b/app/assets/javascripts/discourse/components/category-group.js.es6 @@ -1,18 +1,19 @@ import { categoryBadgeHTML } from 'discourse/helpers/category-link'; +import Category from 'discourse/models/category'; export default Ember.Component.extend({ _initializeAutocomplete: function() { const self = this, template = this.container.lookup('template:category-group-autocomplete.raw'), - regexp = new RegExp("href=['\"]" + Discourse.getURL('/c/') + "([^'\"]+)"); + regexp = new RegExp(`href=['\"]${Discourse.getURL('/c/')}([^'\"]+)`); this.$('input').autocomplete({ items: this.get('categories'), single: false, allowAny: false, dataSource(term){ - return Discourse.Category.list().filter(function(category){ + return Category.list().filter(function(category){ const regex = new RegExp(term, "i"); return category.get("name").match(regex) && !_.contains(self.get('blacklist') || [], category) && @@ -22,7 +23,7 @@ export default Ember.Component.extend({ onChangeItems(items) { const categories = _.map(items, function(link) { const slug = link.match(regexp)[1]; - return Discourse.Category.findSingleBySlug(slug); + return Category.findSingleBySlug(slug); }); Em.run.next(() => self.set("categories", categories)); }, diff --git a/app/assets/javascripts/discourse/components/composer-editor.js.es6 b/app/assets/javascripts/discourse/components/composer-editor.js.es6 index 51a7a8fe1..9dbd7f25d 100644 --- a/app/assets/javascripts/discourse/components/composer-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-editor.js.es6 @@ -1,6 +1,7 @@ import userSearch from 'discourse/lib/user-search'; import { default as computed, on } from 'ember-addons/ember-computed-decorators'; import { linkSeenMentions, fetchUnseenMentions } from 'discourse/lib/link-mentions'; +import { linkSeenCategoryHashtags, fetchUnseenCategoryHashtags } from 'discourse/lib/link-category-hashtags'; export default Ember.Component.extend({ classNames: ['wmd-controls'], @@ -111,13 +112,19 @@ export default Ember.Component.extend({ $preview.scrollTop(desired + 50); }, - _renderUnseen: function($preview, unseen) { - fetchUnseenMentions($preview, unseen, this.siteSettings).then(() => { + _renderUnseenMentions: function($preview, unseen) { + fetchUnseenMentions($preview, unseen).then(() => { linkSeenMentions($preview, this.siteSettings); this._warnMentionedGroups($preview); }); }, + _renderUnseenCategoryHashtags: function($preview, unseen) { + fetchUnseenCategoryHashtags(unseen).then(() => { + linkSeenCategoryHashtags($preview); + }); + }, + _warnMentionedGroups($preview) { Ember.run.scheduleOnce('afterRender', () => { this._warnedMentions = this._warnedMentions || []; @@ -386,11 +393,17 @@ export default Ember.Component.extend({ // Paint mentions const unseen = linkSeenMentions($preview, this.siteSettings); if (unseen.length) { - Ember.run.debounce(this, this._renderUnseen, $preview, unseen, 500); + Ember.run.debounce(this, this._renderUnseenMentions, $preview, unseen, 500); } this._warnMentionedGroups($preview); + // Paint category hashtags + const unseenHashtags = linkSeenCategoryHashtags($preview); + if (unseenHashtags.length) { + Ember.run.debounce(this, this._renderUnseenCategoryHashtags, $preview, unseenHashtags, 500); + } + const post = this.get('composer.post'); let refresh = false; diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6 index ceaad81bb..83d9b5ce6 100644 --- a/app/assets/javascripts/discourse/components/d-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/d-editor.js.es6 @@ -2,6 +2,8 @@ import loadScript from 'discourse/lib/load-script'; import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators'; import { showSelector } from "discourse/lib/emoji/emoji-toolbar"; +import Category from 'discourse/models/category'; +import { SEPARATOR as categoryHashtagSeparator } from 'discourse/lib/category-hashtags'; // Our head can be a static string or a function that returns a string // based on input (like for numbered lists). @@ -41,7 +43,7 @@ function Toolbar() { id: 'italic', group: 'fontStyles', shortcut: 'I', - perform: e => e.applySurround('*', '*', 'italic_text') + perform: e => e.applySurround('_', '_', 'italic_text') }); this.addButton({id: 'link', group: 'insertions', shortcut: 'K', action: 'showLinkModal'}); @@ -175,7 +177,11 @@ export default Ember.Component.extend({ @on('didInsertElement') _startUp() { - this._applyEmojiAutocomplete(); + const container = this.get('container'), + $editorInput = this.$('.d-editor-input'); + + this._applyEmojiAutocomplete(container, $editorInput); + this._applyCategoryHashtagAutocomplete(container, $editorInput); loadScript('defer/html-sanitizer-bundle').then(() => this.set('ready', true)); @@ -243,14 +249,49 @@ export default Ember.Component.extend({ Ember.run.debounce(this, this._updatePreview, 30); }, - _applyEmojiAutocomplete() { + _applyCategoryHashtagAutocomplete(container, $editorInput) { + const template = container.lookup('template:category-group-autocomplete.raw'); + + $editorInput.autocomplete({ + template: template, + key: '#', + transformComplete(category) { + return Category.slugFor(category, categoryHashtagSeparator); + }, + dataSource(term) { + return Category.search(term); + }, + triggerRule(textarea, opts) { + const result = Discourse.Utilities.caretRowCol(textarea); + const row = result.rowNum; + var col = result.colNum; + var line = textarea.value.split("\n")[row - 1]; + + if (opts && opts.backSpace) { + col = col - 1; + line = line.slice(0, line.length - 1); + + // Don't trigger autocomplete when backspacing into a `#category |` => `#category|` + if (/^#{1}\w+/.test(line)) return false; + } + + if (col < 6) { + // Don't trigger autocomplete when ATX-style headers are used + return (line.slice(0, col) !== "#".repeat(col)); + } else { + return true; + } + } + }); + }, + + _applyEmojiAutocomplete(container, $editorInput) { if (!this.siteSettings.enable_emoji) { return; } - const container = this.container; const template = container.lookup('template:emoji-selector-autocomplete.raw'); const self = this; - this.$('.d-editor-input').autocomplete({ + $editorInput.autocomplete({ template: template, key: ":", diff --git a/app/assets/javascripts/discourse/components/date-picker.js.es6 b/app/assets/javascripts/discourse/components/date-picker.js.es6 index 3f7c446f5..d1c8e4fce 100644 --- a/app/assets/javascripts/discourse/components/date-picker.js.es6 +++ b/app/assets/javascripts/discourse/components/date-picker.js.es6 @@ -3,22 +3,24 @@ import loadScript from "discourse/lib/load-script"; import { on } from "ember-addons/ember-computed-decorators"; export default Em.Component.extend({ - tagName: "input", - classNames: ["date-picker"], + classNames: ["date-picker-wrapper"], _picker: null, @on("didInsertElement") _loadDatePicker() { - const input = this.$()[0]; + const input = this.$(".date-picker")[0]; loadScript("/javascripts/pikaday.js").then(() => { - this._picker = new Pikaday({ + let default_opts = { field: input, + container: this.$()[0], format: "YYYY-MM-DD", defaultDate: moment().add(1, "day").toDate(), minDate: new Date(), - onSelect: date => this.set("value", moment(date).format("YYYY-MM-DD")), - }); + onSelect: date => this.set("value", moment(date).format("YYYY-MM-DD")) + }; + + this._picker = new Pikaday(Object.assign(default_opts, this._opts())); }); }, @@ -27,4 +29,8 @@ export default Em.Component.extend({ this._picker = null; }, + _opts() { + return null; + } + }); diff --git a/app/assets/javascripts/discourse/components/home-logo.js.es6 b/app/assets/javascripts/discourse/components/home-logo.js.es6 index d2deabb8a..1cd706d09 100644 --- a/app/assets/javascripts/discourse/components/home-logo.js.es6 +++ b/app/assets/javascripts/discourse/components/home-logo.js.es6 @@ -4,10 +4,15 @@ import { setting } from 'discourse/lib/computed'; export default Ember.Component.extend({ classNames: ["title"], - linkUrl: function() { - return Discourse.getURL('/'); + targetUrl: function() { + // For overriding by customizations + return '/'; }.property(), + linkUrl: function() { + return Discourse.getURL(this.get('targetUrl')); + }.property('targetUrl'), + showSmallLogo: function() { return !Discourse.Mobile.mobileView && this.get("minimized"); }.property("minimized"), @@ -27,7 +32,7 @@ export default Ember.Component.extend({ e.preventDefault(); - DiscourseURL.routeTo('/'); + DiscourseURL.routeTo(this.get('targetUrl')); return false; } }); diff --git a/app/assets/javascripts/discourse/components/notification-item.js.es6 b/app/assets/javascripts/discourse/components/notification-item.js.es6 index 66520e002..8eef09c0c 100644 --- a/app/assets/javascripts/discourse/components/notification-item.js.es6 +++ b/app/assets/javascripts/discourse/components/notification-item.js.es6 @@ -29,7 +29,9 @@ export default Ember.Component.extend({ badgeSlug = badgeName.replace(/[^A-Za-z0-9_]+/g, '-').toLowerCase(); } - return Discourse.getURL('/badges/' + badgeId + '/' + badgeSlug); + var username = it.get('data.username'); + username = username ? "?username=" + username.toLowerCase() : ""; + return Discourse.getURL('/badges/' + badgeId + '/' + badgeSlug + username); } const topicId = it.get('topic_id'); diff --git a/app/assets/javascripts/discourse/components/post-menu.js.es6 b/app/assets/javascripts/discourse/components/post-menu.js.es6 index 2b3e28e72..ef63cd9d2 100644 --- a/app/assets/javascripts/discourse/components/post-menu.js.es6 +++ b/app/assets/javascripts/discourse/components/post-menu.js.es6 @@ -349,6 +349,21 @@ const PostMenuComponent = Ember.Component.extend(StringBuffer, { this.sendAction('toggleBookmark', post); }, + // Wiki button + buttonForWiki(post) { + if (!post.get('can_wiki')) return; + + if (post.get('wiki')) { + return new Button('wiki', 'post.controls.unwiki', 'pencil-square-o', {className: 'wiki wikied'}); + } else { + return new Button('wiki', 'post.controls.wiki', 'pencil-square-o', {className: 'wiki'}); + } + }, + + clickWiki(post) { + this.sendAction('toggleWiki', post); + }, + buttonForAdmin() { if (!Discourse.User.currentProp('canManageTopic')) { return; } return new Button('admin', 'post.controls.admin', 'wrench'); @@ -357,10 +372,7 @@ const PostMenuComponent = Ember.Component.extend(StringBuffer, { renderAdminPopup(post, buffer) { if (!Discourse.User.currentProp('canManageTopic')) { return; } - const isWiki = post.get('wiki'), - wikiIcon = iconHTML('pencil-square-o'), - wikiText = isWiki ? I18n.t('post.controls.unwiki') : I18n.t('post.controls.wiki'), - isModerator = post.get('post_type') === this.site.get('post_types.moderator_action'), + const isModerator = post.get('post_type') === this.site.get('post_types.moderator_action'), postTypeIcon = iconHTML('shield'), postTypeText = isModerator ? I18n.t('post.controls.revert_to_regular') : I18n.t('post.controls.convert_to_moderator'), rebakePostIcon = iconHTML('cog'), @@ -373,7 +385,6 @@ const PostMenuComponent = Ember.Component.extend(StringBuffer, { const html = '
    ' + '

    ' + I18n.t('admin_title') + '

    ' + '
      ' + - '
    • ' + wikiIcon + wikiText + '
    • ' + (Discourse.User.currentProp('staff') ? '
    • ' + postTypeIcon + postTypeText + '
    • ' : '') + '
    • ' + rebakePostIcon + rebakePostText + '
    • ' + (post.hidden ? '
    • ' + unhidePostIcon + unhidePostText + '
    • ' : '') + @@ -393,10 +404,6 @@ const PostMenuComponent = Ember.Component.extend(StringBuffer, { }); }, - clickToggleWiki() { - this.sendAction('toggleWiki', this.get('post')); - }, - clickTogglePostType() { this.sendAction("togglePostType", this.get("post")); }, diff --git a/app/assets/javascripts/discourse/components/small-action.js.es6 b/app/assets/javascripts/discourse/components/small-action.js.es6 index 54ef41b9f..567436348 100644 --- a/app/assets/javascripts/discourse/components/small-action.js.es6 +++ b/app/assets/javascripts/discourse/components/small-action.js.es6 @@ -1,4 +1,5 @@ -import { relativeAge } from 'discourse/lib/formatter'; +import { autoUpdatingRelativeAge } from 'discourse/lib/formatter'; +import computed from 'ember-addons/ember-computed-decorators'; const icons = { 'closed.enabled': 'lock', @@ -13,16 +14,20 @@ const icons = { 'pinned_globally.disabled': 'thumb-tack unpinned', 'visible.enabled': 'eye', 'visible.disabled': 'eye-slash', - 'split_topic': 'sign-out' + 'split_topic': 'sign-out', + 'invited_user': 'plus-circle', + 'removed_user': 'minus-circle' }; -export function actionDescription(actionCode, createdAt) { +export function actionDescription(actionCode, createdAt, username) { return function() { const ac = this.get(actionCode); if (ac) { const dt = new Date(this.get(createdAt)); - const when = relativeAge(dt, {format: 'medium-with-ago'}); - return I18n.t(`action_codes.${ac}`, {when}).htmlSafe(); + const when = autoUpdatingRelativeAge(dt, { format: 'medium-with-ago' }); + const u = this.get(username); + const who = u ? `@${u}` : ""; + return I18n.t(`action_codes.${ac}`, { who, when }).htmlSafe(); } }.property(actionCode, createdAt); } @@ -31,18 +36,19 @@ export default Ember.Component.extend({ layoutName: 'components/small-action', // needed because `time-gap` inherits from this classNames: ['small-action'], - description: actionDescription('actionCode', 'post.created_at'), + description: actionDescription('actionCode', 'post.created_at', 'post.action_code_who'), - icon: function() { - return icons[this.get('actionCode')] || 'exclamation'; - }.property('actionCode'), + @computed("actionCode") + icon(actionCode) { + return icons[actionCode] || 'exclamation'; + }, actions: { - edit: function() { + edit() { this.sendAction('editPost', this.get('post')); }, - delete: function() { + delete() { this.sendAction('deletePost', this.get('post')); } } diff --git a/app/assets/javascripts/discourse/components/stream-item.js.es6 b/app/assets/javascripts/discourse/components/stream-item.js.es6 index 562ac59f0..d81366bec 100644 --- a/app/assets/javascripts/discourse/components/stream-item.js.es6 +++ b/app/assets/javascripts/discourse/components/stream-item.js.es6 @@ -4,7 +4,7 @@ import { actionDescription } from "discourse/components/small-action"; export default Ember.Component.extend({ classNameBindings: [":item", "item.hidden", "item.deleted", "moderatorAction"], moderatorAction: propertyEqual("item.post_type", "site.post_types.moderator_action"), - actionDescription: actionDescription("item.action_code", "item.created_at"), + actionDescription: actionDescription("item.action_code", "item.created_at", "item.username"), actions: { removeBookmark(userAction) { diff --git a/app/assets/javascripts/discourse/components/user-badge.js.es6 b/app/assets/javascripts/discourse/components/user-badge.js.es6 index c7fce8b1a..0db738d41 100644 --- a/app/assets/javascripts/discourse/components/user-badge.js.es6 +++ b/app/assets/javascripts/discourse/components/user-badge.js.es6 @@ -3,5 +3,13 @@ export default Ember.Component.extend({ showGrantCount: function() { return this.get('count') && this.get('count') > 1; - }.property('count') + }.property('count'), + + badgeUrl: function(){ + // NOTE: I tried using a link-to helper here but the queryParams mean it fails + var username = this.get('user.username_lower') || ''; + username = username !== '' ? "?username=" + username : ''; + return this.get('badge.url') + username; + }.property("badge", "user") + }); diff --git a/app/assets/javascripts/discourse/controllers/badges/show.js.es6 b/app/assets/javascripts/discourse/controllers/badges/show.js.es6 index a5a899f64..afe640bc6 100644 --- a/app/assets/javascripts/discourse/controllers/badges/show.js.es6 +++ b/app/assets/javascripts/discourse/controllers/badges/show.js.es6 @@ -1,17 +1,33 @@ import UserBadge from 'discourse/models/user-badge'; export default Ember.Controller.extend({ + queryParams: ['username'], noMoreBadges: false, userBadges: null, needs: ["application"], + user: function(){ + if (this.get("username")) { + return this.get('userBadges')[0].get('user'); + } + }.property("username"), + + grantCount: function() { + if (this.get("username")) { + return this.get('userBadges.grant_count'); + } else { + return this.get('model.grant_count'); + } + }.property('username', 'model', 'userBadges'), + actions: { loadMore() { const self = this; const userBadges = this.get('userBadges'); UserBadge.findByBadgeId(this.get('model.id'), { - offset: userBadges.length + offset: userBadges.length, + username: this.get('username'), }).then(function(result) { userBadges.pushObjects(result); if(userBadges.length === 0){ @@ -22,11 +38,12 @@ export default Ember.Controller.extend({ }, layoutClass: function(){ + var user = this.get("user") ? " single-user" : ""; var ub = this.get("userBadges"); if(ub && ub[0] && ub[0].post_id){ - return "user-badge-with-posts"; + return "user-badge-with-posts" + user; } else { - return "user-badge-no-posts"; + return "user-badge-no-posts" + user; } }.property("userBadges"), @@ -34,7 +51,7 @@ export default Ember.Controller.extend({ if (this.get('noMoreBadges')) { return false; } if (this.get('userBadges')) { - return this.get('model.grant_count') > this.get('userBadges.length'); + return this.get('grantCount') > this.get('userBadges.length'); } else { return false; } diff --git a/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 b/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 index 00d551bd8..fa958a84d 100644 --- a/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 +++ b/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 @@ -4,9 +4,15 @@ export default Ember.Controller.extend(ModalFunctionality, { // You need a value in the field to submit it. submitDisabled: function() { - return Ember.isEmpty(this.get('accountEmailOrUsername').trim()) || this.get('disabled'); + return Ember.isEmpty((this.get('accountEmailOrUsername') || '').trim()) || this.get('disabled'); }.property('accountEmailOrUsername', 'disabled'), + onShow: function() { + if ($.cookie('email')) { + this.set('accountEmailOrUsername', $.cookie('email')); + } + }, + actions: { submit: function() { var self = this; diff --git a/app/assets/javascripts/discourse/controllers/invite.js.es6 b/app/assets/javascripts/discourse/controllers/invite.js.es6 index d22217b56..fe6c83af4 100644 --- a/app/assets/javascripts/discourse/controllers/invite.js.es6 +++ b/app/assets/javascripts/discourse/controllers/invite.js.es6 @@ -6,6 +6,7 @@ export default Ember.Controller.extend(ModalFunctionality, { // If this isn't defined, it will proxy to the user model on the preferences // page which is wrong. emailOrUsername: null, + inviteIcon: "envelope", isAdmin: function(){ return Discourse.User.currentProp("admin"); @@ -88,8 +89,10 @@ export default Ember.Controller.extend(ModalFunctionality, { if (Ember.isEmpty(this.get('emailOrUsername'))) { return I18n.t('topic.invite_reply.to_topic_blank'); } else if (Discourse.Utilities.emailValid(this.get('emailOrUsername'))) { + this.set("inviteIcon", "envelope"); return I18n.t('topic.invite_reply.to_topic_email'); } else { + this.set("inviteIcon", "hand-o-right"); return I18n.t('topic.invite_reply.to_topic_username'); } } diff --git a/app/assets/javascripts/discourse/controllers/preferences.js.es6 b/app/assets/javascripts/discourse/controllers/preferences.js.es6 index f23f342e0..1efdaff99 100644 --- a/app/assets/javascripts/discourse/controllers/preferences.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences.js.es6 @@ -32,8 +32,8 @@ export default Ember.Controller.extend(CanCheckEmails, { } }, - cannotDeleteAccount: Em.computed.not('can_delete_account'), - deleteDisabled: Em.computed.or('saving', 'deleting', 'cannotDeleteAccount'), + cannotDeleteAccount: Em.computed.not('currentUser.can_delete_account'), + deleteDisabled: Em.computed.or('model.isSaving', 'deleting', 'cannotDeleteAccount'), canEditName: setting('enable_names'), diff --git a/app/assets/javascripts/discourse/controllers/preferences/email.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/email.js.es6 index 83262902d..099e4a1e6 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/email.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/email.js.es6 @@ -9,10 +9,10 @@ export default Ember.Controller.extend({ newEmailEmpty: Em.computed.empty('newEmail'), saveDisabled: Em.computed.or('saving', 'newEmailEmpty', 'taken', 'unchanged'), - unchanged: propertyEqual('newEmailLower', 'email'), + unchanged: propertyEqual('newEmailLower', 'currentUser.email'), newEmailLower: function() { - return this.get('newEmail').toLowerCase(); + return this.get('newEmail').toLowerCase().trim(); }.property('newEmail'), saveButtonText: function() { @@ -26,10 +26,10 @@ export default Ember.Controller.extend({ this.set('saving', true); return this.get('content').changeEmail(this.get('newEmail')).then(function() { self.set('success', true); - }, function(data) { + }, function(e) { self.setProperties({ error: true, saving: false }); - if (data.responseJSON && data.responseJSON.errors && data.responseJSON.errors[0]) { - self.set('errorMessage', data.responseJSON.errors[0]); + if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors && e.jqXHR.responseJSON.errors[0]) { + self.set('errorMessage', e.jqXHR.responseJSON.errors[0]); } else { self.set('errorMessage', I18n.t('user.change_email.error')); } @@ -38,5 +38,3 @@ export default Ember.Controller.extend({ } }); - - diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index 5722d2f5d..e5f5d19d3 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -6,6 +6,7 @@ import Quote from 'discourse/lib/quote'; import { popupAjaxError } from 'discourse/lib/ajax-error'; import computed from 'ember-addons/ember-computed-decorators'; import Composer from 'discourse/models/composer'; +import DiscourseURL from 'discourse/lib/url'; export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { needs: ['header', 'modal', 'composer', 'quote-button', 'topic-progress', 'application'], @@ -17,8 +18,8 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { queryParams: ['filter', 'username_filters', 'show_deleted'], loadedAllPosts: Em.computed.or('model.postStream.loadedAllPosts', 'model.postStream.loadingLastPost'), enteredAt: null, - firstPostExpanded: false, retrying: false, + firstPostExpanded: false, adminMenuVisible: false, showRecover: Em.computed.and('model.deleted', 'model.details.can_recover'), @@ -89,11 +90,14 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { this.set('selectedReplies', []); }.on('init'), - @computed("model.isPrivateMessage", "model.category_id") - showCategoryChooser(isPrivateMessage, categoryId) { - const category = Discourse.Category.findById(categoryId); - const containsMessages = category && category.get("contains_messages"); - return !isPrivateMessage && !containsMessages; + showCategoryChooser: Ember.computed.not("model.isPrivateMessage"), + + gotoInbox(name) { + var url = '/users/' + this.get('currentUser.username_lower') + '/messages'; + if (name) { + url = url + '/group/' + name; + } + DiscourseURL.routeTo(url); }, actions: { @@ -109,12 +113,19 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { this.deleteTopic(); }, + archiveMessage() { - this.get('model').archiveMessage(); + const topic = this.get('model'); + topic.archiveMessage().then(()=>{ + this.gotoInbox(topic.get("inboxGroupName")); + }); }, moveToInbox() { - this.get('model').moveToInbox(); + const topic = this.get('model'); + topic.moveToInbox().then(()=>{ + this.gotoInbox(topic.get("inboxGroupName")); + }); }, // Post related methods diff --git a/app/assets/javascripts/discourse/controllers/user-activity.js.es6 b/app/assets/javascripts/discourse/controllers/user-activity.js.es6 index 8cecec3db..59c6b6dd8 100644 --- a/app/assets/javascripts/discourse/controllers/user-activity.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-activity.js.es6 @@ -1,3 +1,5 @@ +import { exportUserArchive } from 'discourse/lib/export-csv'; + export default Ember.Controller.extend({ userActionType: null, needs: ["application", "user"], @@ -14,6 +16,21 @@ export default Ember.Controller.extend({ showFooter = this.get("model.statsCountNonPM") <= this.get("model.stream.itemsLoaded"); } this.set("controllers.application.showFooter", showFooter); - }.observes("userActionType", "model.stream.itemsLoaded") + }.observes("userActionType", "model.stream.itemsLoaded"), + + actions: { + exportUserArchive() { + bootbox.confirm( + I18n.t("admin.export_csv.user_archive_confirm"), + I18n.t("no_value"), + I18n.t("yes_value"), + function(confirmed) { + if (confirmed) { + exportUserArchive(); + } + } + ); + } + } }); diff --git a/app/assets/javascripts/discourse/controllers/user-badges.js.es6 b/app/assets/javascripts/discourse/controllers/user-badges.js.es6 index 3ceae1ec1..76b2a05cd 100644 --- a/app/assets/javascripts/discourse/controllers/user-badges.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-badges.js.es6 @@ -1,4 +1,6 @@ export default Ember.ArrayController.extend({ + needs: ["user"], + user: Em.computed.alias("controllers.user.model"), sortProperties: ['badge.badge_type.sort_order', 'badge.name'], orderBy: function(ub1, ub2){ var sr1 = ub1.get('badge.badge_type.sort_order'); diff --git a/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 b/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 index c51bf4072..eb8d13c72 100644 --- a/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 @@ -3,14 +3,23 @@ import Topic from 'discourse/models/topic'; export default Ember.Controller.extend({ - needs: ["user-topics-list"], + needs: ["user-topics-list", "user"], pmView: false, - + viewingSelf: Em.computed.alias("controllers.user.viewingSelf"), isGroup: Em.computed.equal('pmView', 'groups'), selected: Em.computed.alias('controllers.user-topics-list.selected'), bulkSelectEnabled: Em.computed.alias('controllers.user-topics-list.bulkSelectEnabled'), + mobileView: function() { + return Discourse.Mobile.mobileView; + }.property(), + + showNewPM: function(){ + return this.get('controllers.user.viewingSelf') && + Discourse.User.currentProp('can_send_private_messages'); + }.property('controllers.user.viewingSelf'), + @computed('selected.@each', 'bulkSelectEnabled') hasSelection(selected, bulkSelectEnabled){ return bulkSelectEnabled && selected && selected.length > 0; diff --git a/app/assets/javascripts/discourse/controllers/user-summary.js.es6 b/app/assets/javascripts/discourse/controllers/user-summary.js.es6 new file mode 100644 index 000000000..b8c414521 --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/user-summary.js.es6 @@ -0,0 +1,13 @@ +export default Ember.Controller.extend({ + needs: ['user'], + user: Em.computed.alias('controllers.user.model'), + moreTopics: function(){ + return this.get('model.topics').length > 5; + }.property('model'), + moreReplies: function(){ + return this.get('model.replies').length > 5; + }.property('model'), + moreBadges: function(){ + return this.get('model.badges').length > 5; + }.property('model') +}); diff --git a/app/assets/javascripts/discourse/controllers/user-topics-list.js.es6 b/app/assets/javascripts/discourse/controllers/user-topics-list.js.es6 index 73a763d47..e4d428a4f 100644 --- a/app/assets/javascripts/discourse/controllers/user-topics-list.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-topics-list.js.es6 @@ -14,9 +14,4 @@ export default Ember.Controller.extend({ } }, - showNewPM: function(){ - return this.get('controllers.user.viewingSelf') && - Discourse.User.currentProp('can_send_private_messages'); - }.property('controllers.user.viewingSelf') - }); diff --git a/app/assets/javascripts/discourse/controllers/user.js.es6 b/app/assets/javascripts/discourse/controllers/user.js.es6 index b06b5a9cb..3971558f8 100644 --- a/app/assets/javascripts/discourse/controllers/user.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user.js.es6 @@ -1,4 +1,3 @@ -import { exportUserArchive } from 'discourse/lib/export-csv'; import CanCheckEmails from 'discourse/mixins/can-check-emails'; import computed from 'ember-addons/ember-computed-decorators'; import UserAction from 'discourse/models/user-action'; @@ -89,17 +88,5 @@ export default Ember.Controller.extend(CanCheckEmails, { .then(user => user.destroy({deletePosts: true})); }, - exportUserArchive() { - bootbox.confirm( - I18n.t("admin.export_csv.user_archive_confirm"), - I18n.t("no_value"), - I18n.t("yes_value"), - function(confirmed) { - if (confirmed) { - exportUserArchive(); - } - } - ); - } } }); diff --git a/app/assets/javascripts/discourse/dialects/category_hashtag_dialect.js b/app/assets/javascripts/discourse/dialects/category_hashtag_dialect.js new file mode 100644 index 000000000..f7a7f48d1 --- /dev/null +++ b/app/assets/javascripts/discourse/dialects/category_hashtag_dialect.js @@ -0,0 +1,23 @@ +/** + Supports Discourse's category hashtags (#category-slug) for automatically + generating a link to the category. +**/ +Discourse.Dialect.inlineRegexp({ + start: '#', + matcher: /^#([\w-:]{1,50})/i, + spaceOrTagBoundary: true, + + emitter: function(matches) { + var slug = matches[1], + hashtag = matches[0], + attributeClass = 'hashtag', + categoryHashtagLookup = this.dialect.options.categoryHashtagLookup, + result = categoryHashtagLookup && categoryHashtagLookup(slug); + + if (result && result[0] === "category") { + return ['a', { class: attributeClass, href: result[1] }, '#', ["span", {}, slug]]; + } else { + return ['span', { class: attributeClass }, hashtag]; + } + } +}); diff --git a/app/assets/javascripts/discourse/dialects/mention_dialect.js b/app/assets/javascripts/discourse/dialects/mention_dialect.js index 20af912c6..3848ae150 100644 --- a/app/assets/javascripts/discourse/dialects/mention_dialect.js +++ b/app/assets/javascripts/discourse/dialects/mention_dialect.js @@ -5,22 +5,23 @@ **/ 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}[A-Za-z0-9\_])/, + // NOTE: since we can't use SiteSettings here (they loads later in process) + // we are being less strict to account for more cases than allowed + matcher: /^@(\w[\w.-]{0,59})\b/i, wordBoundary: true, emitter: function(matches) { - var username = matches[1], + var mention = matches[0].trim(), + name = matches[1], mentionLookup = this.dialect.options.mentionLookup; - var type = mentionLookup && mentionLookup(username.substr(1)); + var type = mentionLookup && mentionLookup(name); if (type === "user") { - return ['a', {'class': 'mention', href: Discourse.getURL("/users/") + username.substr(1).toLowerCase()}, username]; + return ['a', {'class': 'mention', href: Discourse.getURL("/users/") + name.toLowerCase()}, mention]; } else if (type === "group") { - return ['a', {'class': 'mention-group', href: Discourse.getURL("/groups/") + username.substr(1)}, username]; + return ['a', {'class': 'mention-group', href: Discourse.getURL("/groups/") + name}, mention]; } else { - return ['span', {'class': 'mention'}, username]; + return ['span', {'class': 'mention'}, mention]; } } }); @@ -34,10 +35,10 @@ Discourse.Dialect.on("parseNode", function(event) { var parent = path[path.length - 1]; // If the parent is an 'a', remove it if (parent && parent[0] === 'a') { - var username = node[2]; + var name = node[2]; node.length = 0; node[0] = "__RAW"; - node[1] = username; + node[1] = name; } } diff --git a/app/assets/javascripts/discourse/helpers/category-link.js.es6 b/app/assets/javascripts/discourse/helpers/category-link.js.es6 index 9f7d3f4ed..1155a2b7b 100644 --- a/app/assets/javascripts/discourse/helpers/category-link.js.es6 +++ b/app/assets/javascripts/discourse/helpers/category-link.js.es6 @@ -9,6 +9,16 @@ function categoryStripe(color, classes) { return ""; } +/** + Generates category badge HTML + + @param {Object} category The category to generate the badge for. + @param {Object} opts + @param {String} [opts.url] The url that we want the category badge to link to. + @param {Boolean} [opts.allowUncategorized] If false, returns an empty string for the uncategorized category. + @param {Boolean} [opts.link] If false, the category badge will not be a link. + @param {Boolean} [opts.hideParaent] If true, parent category will be hidden in the badge. +**/ export function categoryBadgeHTML(category, opts) { opts = opts || {}; @@ -21,7 +31,7 @@ export function categoryBadgeHTML(category, opts) { var description = get(category, 'description_text'), restricted = get(category, 'read_restricted'), - url = Discourse.getURL("/c/") + Discourse.Category.slugFor(category), + url = opts.url ? opts.url : Discourse.getURL("/c/") + Discourse.Category.slugFor(category), href = (opts.link === false ? '' : url), tagName = (opts.link === false || opts.link === "false" ? 'span' : 'a'), extraClasses = (opts.extraClasses ? (' ' + opts.extraClasses) : ''), @@ -50,6 +60,7 @@ export function categoryBadgeHTML(category, opts) { ">"; var name = escapeExpression(get(category, 'name')); + if (restricted) { html += iconHTML('lock') + " " + name; } else { diff --git a/app/assets/javascripts/discourse/helpers/raw.js.es6 b/app/assets/javascripts/discourse/helpers/raw.js.es6 index c420c76bd..fbef46ff0 100644 --- a/app/assets/javascripts/discourse/helpers/raw.js.es6 +++ b/app/assets/javascripts/discourse/helpers/raw.js.es6 @@ -24,6 +24,5 @@ registerUnbound('raw', function(templateName, params) { Ember.warn('Could not find raw template: ' + templateName); return; } - return renderRaw(this, template, templateName, params); }); diff --git a/app/assets/javascripts/discourse/helpers/register-unbound.js.es6 b/app/assets/javascripts/discourse/helpers/register-unbound.js.es6 index 86e096904..6fa9237a0 100644 --- a/app/assets/javascripts/discourse/helpers/register-unbound.js.es6 +++ b/app/assets/javascripts/discourse/helpers/register-unbound.js.es6 @@ -8,9 +8,9 @@ function resolveParams(ctx, options) { if (options.hashTypes) { Ember.keys(hash).forEach(function(k) { const type = options.hashTypes[k]; - if (type === "STRING") { + if (type === "STRING" || type === "StringLiteral") { params[k] = hash[k]; - } else if (type === "ID") { + } else if (type === "ID" || type === "PathExpression") { params[k] = get(ctx, hash[k], options); } }); @@ -23,7 +23,7 @@ function resolveParams(ctx, options) { export default function registerUnbound(name, fn) { const func = function(property, options) { - if (options.types && options.types[0] === "ID") { + if (options.types && (options.types[0] === "ID" || options.types[0] === "PathExpression")) { property = get(this, property, options); } diff --git a/app/assets/javascripts/discourse/lib/autocomplete.js.es6 b/app/assets/javascripts/discourse/lib/autocomplete.js.es6 index 78690f624..5aa01e495 100644 --- a/app/assets/javascripts/discourse/lib/autocomplete.js.es6 +++ b/app/assets/javascripts/discourse/lib/autocomplete.js.es6 @@ -282,6 +282,14 @@ export default function(options) { }, 50); }); + const checkTriggerRule = (opts) => { + if (options.triggerRule) { + return options.triggerRule(me[0], opts); + } else { + return true; + } + }; + $(this).on('keypress.autocomplete', function(e) { var caretPosition, term; @@ -289,7 +297,7 @@ export default function(options) { if (options.key && e.which === options.key.charCodeAt(0)) { caretPosition = Discourse.Utilities.caretPosition(me[0]); var prevChar = me.val().charAt(caretPosition - 1); - if (!prevChar || allowedLettersRegex.test(prevChar)) { + if (checkTriggerRule() && (!prevChar || allowedLettersRegex.test(prevChar))) { completeStart = completeEnd = caretPosition; updateAutoComplete(options.dataSource("")); } @@ -343,7 +351,7 @@ export default function(options) { stopFound = prev === options.key; if (stopFound) { prev = me[0].value[c - 1]; - if (!prev || allowedLettersRegex.test(prev)) { + if (checkTriggerRule({ backSpace: true }) && (!prev || allowedLettersRegex.test(prev))) { completeStart = c; caretPosition = completeEnd = initial; term = me[0].value.substring(c + 1, initial); @@ -351,7 +359,7 @@ export default function(options) { return true; } } - prevIsGood = /[a-zA-Z\.]/.test(prev); + prevIsGood = /[a-zA-Z\.-]/.test(prev); } } diff --git a/app/assets/javascripts/discourse/lib/category-hashtags.js.es6 b/app/assets/javascripts/discourse/lib/category-hashtags.js.es6 new file mode 100644 index 000000000..da2be1209 --- /dev/null +++ b/app/assets/javascripts/discourse/lib/category-hashtags.js.es6 @@ -0,0 +1,5 @@ +export const SEPARATOR = ":"; + +export function replaceSpan($elem, categorySlug, categoryLink) { + $elem.replaceWith(`#${categorySlug}`); +}; diff --git a/app/assets/javascripts/discourse/lib/click-track.js.es6 b/app/assets/javascripts/discourse/lib/click-track.js.es6 index a0e8264b7..627318a83 100644 --- a/app/assets/javascripts/discourse/lib/click-track.js.es6 +++ b/app/assets/javascripts/discourse/lib/click-track.js.es6 @@ -1,5 +1,10 @@ import DiscourseURL from 'discourse/lib/url'; +export function isValidLink($link) { + return ($link.hasClass("track-link") || + $link.closest('.hashtag,.badge-category,.onebox-result,.onebox-body').length === 0); +}; + export default { trackClick(e) { // cancel click if triggered as part of selection. @@ -32,8 +37,7 @@ export default { var $badge = $('span.badge', $link); if ($badge.length === 1) { // don't update counts in category badge nor in oneboxes (except when we force it) - if ($link.hasClass("track-link") || - $link.closest('.badge-category,.onebox-result,.onebox-body').length === 0) { + if (isValidLink($link)) { var html = $badge.html(); if (/^\d+$/.test(html)) { $badge.html(parseInt(html, 10) + 1); } } diff --git a/app/assets/javascripts/discourse/lib/ember_compat_handlebars.js b/app/assets/javascripts/discourse/lib/ember_compat_handlebars.js index a6666cc6c..e69c5421a 100644 --- a/app/assets/javascripts/discourse/lib/ember_compat_handlebars.js +++ b/app/assets/javascripts/discourse/lib/ember_compat_handlebars.js @@ -68,19 +68,32 @@ RawHandlebars.JavaScriptCompiler.prototype.compiler = RawHandlebars.JavaScriptCompiler; RawHandlebars.JavaScriptCompiler.prototype.namespace = "Discourse.EmberCompatHandlebars"; + function replaceGet(ast) { + var visitor = new Handlebars.Visitor(); + visitor.mutating = true; - RawHandlebars.Compiler.prototype.mustache = function(mustache) { - if ( !(mustache.params.length || mustache.hash)) { - - var id = new Handlebars.AST.IdNode([{ part: 'get' }]); - mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, mustache.escaped); - } - - return Handlebars.Compiler.prototype.mustache.call(this, mustache); - }; + visitor.MustacheStatement = function(mustache) { + if (!(mustache.params.length || mustache.hash)) { + mustache.params[0] = mustache.path; + mustache.path = { + type: "PathExpression", + data: false, + depth: mustache.path.depth, + parts: ["get"], + original: "get", + loc: mustache.path.loc, + strict: true, + falsy: true + }; + } + return Handlebars.Visitor.prototype.MustacheStatement.call(this, mustache); + }; + visitor.accept(ast); + } RawHandlebars.precompile = function(value, asObject) { var ast = Handlebars.parse(value); + replaceGet(ast); var options = { knownHelpers: { @@ -96,9 +109,10 @@ return new RawHandlebars.JavaScriptCompiler().compile(environment, options, undefined, asObject); }; - RawHandlebars.compile = function(string) { var ast = Handlebars.parse(string); + replaceGet(ast); + // this forces us to rewrite helpers var options = { data: true, stringParams: true }; var environment = new RawHandlebars.Compiler().compile(ast, options); diff --git a/app/assets/javascripts/discourse/lib/link-category-hashtags.js.es6 b/app/assets/javascripts/discourse/lib/link-category-hashtags.js.es6 new file mode 100644 index 000000000..53b3a8c2a --- /dev/null +++ b/app/assets/javascripts/discourse/lib/link-category-hashtags.js.es6 @@ -0,0 +1,51 @@ +import { replaceSpan } from 'discourse/lib/category-hashtags'; + +const validCategoryHashtags = {}; +const checkedCategoryHashtags = []; +const testedKey = 'tested'; +const testedClass = `hashtag-${testedKey}`; + +function updateFound($hashtags, categorySlugs) { + Ember.run.schedule('afterRender', () => { + $hashtags.each((index, hashtag) => { + const categorySlug = categorySlugs[index]; + const link = validCategoryHashtags[categorySlug]; + const $hashtag = $(hashtag); + + if (link) { + replaceSpan($hashtag, categorySlug, link); + } else if (checkedCategoryHashtags.indexOf(categorySlug) !== -1) { + $hashtag.addClass(testedClass); + } + }); + }); +}; + +export function linkSeenCategoryHashtags($elem) { + const $hashtags = $(`span.hashtag:not(.${testedClass})`, $elem); + const unseen = []; + + if ($hashtags.length) { + const categorySlugs = $hashtags.map((_, hashtag) => $(hashtag).text().substr(1)); + if (categorySlugs.length) { + _.uniq(categorySlugs).forEach((categorySlug) => { + if (checkedCategoryHashtags.indexOf(categorySlug) === -1) { + unseen.push(categorySlug); + } + }); + } + updateFound($hashtags, categorySlugs); + } + + return unseen; +}; + +export function fetchUnseenCategoryHashtags(categorySlugs) { + return Discourse.ajax("/category_hashtags/check", { data: { category_slugs: categorySlugs } }) + .then((response) => { + response.valid.forEach((category) => { + validCategoryHashtags[category.slug] = category.url; + }); + checkedCategoryHashtags.push.apply(checkedCategoryHashtags, categorySlugs); + }); +} diff --git a/app/assets/javascripts/discourse/lib/markdown.js b/app/assets/javascripts/discourse/lib/markdown.js index 4b9e3ce1e..29616a7e2 100644 --- a/app/assets/javascripts/discourse/lib/markdown.js +++ b/app/assets/javascripts/discourse/lib/markdown.js @@ -239,6 +239,7 @@ Discourse.Markdown.whiteListTag('a', 'class', 'attachment'); Discourse.Markdown.whiteListTag('a', 'class', 'onebox'); Discourse.Markdown.whiteListTag('a', 'class', 'mention'); Discourse.Markdown.whiteListTag('a', 'class', 'mention-group'); +Discourse.Markdown.whiteListTag('a', 'class', 'hashtag'); Discourse.Markdown.whiteListTag('a', 'target', '_blank'); Discourse.Markdown.whiteListTag('a', 'rel', 'nofollow'); @@ -251,6 +252,7 @@ Discourse.Markdown.whiteListTag('div', 'class', 'title'); Discourse.Markdown.whiteListTag('div', 'class', 'quote-controls'); Discourse.Markdown.whiteListTag('span', 'class', 'mention'); +Discourse.Markdown.whiteListTag('span', 'class', 'hashtag'); Discourse.Markdown.whiteListTag('aside', 'class', 'quote'); Discourse.Markdown.whiteListTag('aside', 'data-*'); diff --git a/app/assets/javascripts/discourse/lib/utilities.js b/app/assets/javascripts/discourse/lib/utilities.js index 72cd08816..fa1f063f0 100644 --- a/app/assets/javascripts/discourse/lib/utilities.js +++ b/app/assets/javascripts/discourse/lib/utilities.js @@ -143,6 +143,19 @@ Discourse.Utilities = { return String(text).trim(); }, + // Determine the row and col of the caret in an element + caretRowCol: function(el) { + var caretPosition = Discourse.Utilities.caretPosition(el); + var rows = el.value.slice(0, caretPosition).split("\n"); + var rowNum = rows.length; + + var colNum = caretPosition - rows.splice(0, rowNum - 1).reduce(function(sum, row) { + return sum + row.length + 1; + }, 0); + + return { rowNum: rowNum, colNum: colNum}; + }, + // Determine the position of the caret in an element caretPosition: function(el) { var r, rc, re; @@ -249,7 +262,11 @@ Discourse.Utilities = { return ''; } else if (!Discourse.SiteSettings.prevent_anons_from_downloading_files && (/\.(mov|mp4|webm|ogv|mp3|ogg|wav)$/i).test(upload.original_filename)) { // is Audio/Video - return "http://" + Discourse.BaseUrl + upload.url; + if (Discourse.CDN) { + return Discourse.CDN.startsWith('//') ? "http:" + Discourse.getURLWithCDN(upload.url) : Discourse.getURLWithCDN(upload.url); + } else { + return "http://" + Discourse.BaseUrl + upload.url; + } } else { return '' + upload.original_filename + ' (' + I18n.toHumanSize(upload.filesize) + ')'; } diff --git a/app/assets/javascripts/discourse/models/badge-grouping.js.es6 b/app/assets/javascripts/discourse/models/badge-grouping.js.es6 index 605d4d6d2..04574c431 100644 --- a/app/assets/javascripts/discourse/models/badge-grouping.js.es6 +++ b/app/assets/javascripts/discourse/models/badge-grouping.js.es6 @@ -8,7 +8,7 @@ export default RestModel.extend({ return this.get('name').toLowerCase().replace(/\s/g, '_'); }, - @computed + @computed('name') displayName() { const i18nKey = `badges.badge_grouping.${this.get('i18nNameKey')}.name`; return I18n.t(i18nKey, {defaultValue: this.get('name')}); diff --git a/app/assets/javascripts/discourse/models/badge.js.es6 b/app/assets/javascripts/discourse/models/badge.js.es6 index 1e4d79433..6e3d852c7 100644 --- a/app/assets/javascripts/discourse/models/badge.js.es6 +++ b/app/assets/javascripts/discourse/models/badge.js.es6 @@ -5,6 +5,10 @@ const Badge = RestModel.extend({ newBadge: Em.computed.none('id'), + url: function() { + return Discourse.getURL(`/badges/${this.get('id')}/${this.get('slug')}`); + }.property(), + /** @private @@ -159,7 +163,7 @@ Badge.reopenClass({ let badges = []; if ("badge" in json) { badges = [json.badge]; - } else { + } else if (json.badges) { badges = json.badges; } badges = badges.map(function(badgeJson) { @@ -207,4 +211,3 @@ Badge.reopenClass({ }); export default Badge; - diff --git a/app/assets/javascripts/discourse/models/category.js.es6 b/app/assets/javascripts/discourse/models/category.js.es6 index ed82eb978..1a8261dd1 100644 --- a/app/assets/javascripts/discourse/models/category.js.es6 +++ b/app/assets/javascripts/discourse/models/category.js.es6 @@ -86,8 +86,7 @@ const Category = RestModel.extend({ allow_badges: this.get('allow_badges'), custom_fields: this.get('custom_fields'), topic_template: this.get('topic_template'), - suppress_from_homepage: this.get('suppress_from_homepage'), - contains_messages: this.get("contains_messages"), + suppress_from_homepage: this.get('suppress_from_homepage') }, type: this.get('id') ? 'PUT' : 'POST' }); @@ -205,14 +204,14 @@ Category.reopenClass({ return _uncategorized; }, - slugFor(category) { + slugFor(category, separator = "/") { if (!category) return ""; const parentCategory = Em.get(category, 'parentCategory'); let result = ""; if (parentCategory) { - result = Category.slugFor(parentCategory) + "/"; + result = Category.slugFor(parentCategory) + separator; } const id = Em.get(category, 'id'), @@ -285,6 +284,64 @@ Category.reopenClass({ reloadById(id) { return Discourse.ajax(`/c/${id}/show.json`); + }, + + search(term, opts) { + var limit = 5; + + if (opts) { + if (opts.limit === 0) { + return []; + } else if (opts.limit) { + limit = opts.limit; + } + } + + const emptyTerm = (term === ""); + let slugTerm = term; + + if (!emptyTerm) { + term = term.toLowerCase(); + slugTerm = term; + term = term.replace(/-/g, " "); + } + + const categories = Category.listByActivity(); + const length = categories.length; + var i; + var data = []; + + const done = () => { + return data.length === limit; + }; + + for (i = 0; i < length && !done(); i++) { + const category = categories[i]; + if ((emptyTerm && !category.get('parent_category_id')) || + (!emptyTerm && + (category.get('name').toLowerCase().indexOf(term) === 0 || + category.get('slug').toLowerCase().indexOf(slugTerm) === 0))) { + + data.push(category); + } + } + + if (!done()) { + for (i = 0; i < length && !done(); i++) { + const category = categories[i]; + + if (!emptyTerm && + (category.get('name').toLowerCase().indexOf(term) > 0 || + category.get('slug').toLowerCase().indexOf(slugTerm) > 0)) { + + if (data.indexOf(category) === -1) data.push(category); + } + } + } + + return _.sortBy(data, (category) => { + return category.get('read_restricted'); + }); } }); diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6 index f0bc7eaca..cc094dc08 100644 --- a/app/assets/javascripts/discourse/models/composer.js.es6 +++ b/app/assets/javascripts/discourse/models/composer.js.es6 @@ -67,17 +67,16 @@ const Composer = RestModel.extend({ creatingPrivateMessage: Em.computed.equal('action', PRIVATE_MESSAGE), notCreatingPrivateMessage: Em.computed.not('creatingPrivateMessage'), - @computed("privateMessage", "archetype.hasOptions", "categoryId") - showCategoryChooser(isPrivateMessage, hasOptions, categoryId) { + @computed("privateMessage", "archetype.hasOptions") + showCategoryChooser(isPrivateMessage, hasOptions) { const manyCategories = Discourse.Category.list().length > 1; - const category = Discourse.Category.findById(categoryId); - const containsMessages = category && category.get("contains_messages"); - return !isPrivateMessage && !containsMessages && (hasOptions || manyCategories); + return !isPrivateMessage && (hasOptions || manyCategories); }, - privateMessage: function(){ - return this.get('creatingPrivateMessage') || this.get('topic.archetype') === 'private_message'; - }.property('creatingPrivateMessage', 'topic'), + @computed("creatingPrivateMessage", "topic") + privateMessage(creatingPrivateMessage, topic) { + return creatingPrivateMessage || (topic && topic.get('archetype') === 'private_message'); + }, topicFirstPost: Em.computed.or('creatingTopic', 'editingFirstPost'), diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index c7c867946..24d0638a5 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -424,8 +424,12 @@ const Topic = RestModel.extend({ this.set("archiving", true); var promise = Discourse.ajax(`/t/${this.get('id')}/archive-message`, {type: 'PUT'}); - promise.then(()=>this.set('message_archived', true)) - .finally(()=>this.set('archiving', false)); + promise.then((msg)=> { + this.set('message_archived', true); + if (msg && msg.group_name) { + this.set('inboxGroupName', msg.group_name); + } + }).finally(()=>this.set('archiving', false)); return promise; }, @@ -434,8 +438,12 @@ const Topic = RestModel.extend({ this.set("archiving", true); var promise = Discourse.ajax(`/t/${this.get('id')}/move-to-inbox`, {type: 'PUT'}); - promise.then(()=>this.set('message_archived', false)) - .finally(()=>this.set('archiving', false)); + promise.then((msg)=> { + this.set('message_archived', false); + if (msg && msg.group_name) { + this.set('inboxGroupName', msg.group_name); + } + }).finally(()=>this.set('archiving', false)); return promise; } diff --git a/app/assets/javascripts/discourse/models/user-badge.js.es6 b/app/assets/javascripts/discourse/models/user-badge.js.es6 index 8f938298d..a99c997d6 100644 --- a/app/assets/javascripts/discourse/models/user-badge.js.es6 +++ b/app/assets/javascripts/discourse/models/user-badge.js.es6 @@ -48,7 +48,7 @@ UserBadge.reopenClass({ if ("user_badge" in json) { userBadges = [json.user_badge]; } else { - userBadges = json.user_badges; + userBadges = (json.user_badge_info && json.user_badge_info.user_badges) || json.user_badges; } userBadges = userBadges.map(function(userBadgeJson) { @@ -73,6 +73,10 @@ UserBadge.reopenClass({ if ("user_badge" in json) { return userBadges[0]; } else { + if (json.user_badge_info) { + userBadges.grant_count = json.user_badge_info.grant_count; + userBadges.username = json.user_badge_info.username; + } return userBadges; } }, diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6 index 2dbeea576..eff6ef3d7 100644 --- a/app/assets/javascripts/discourse/models/user.js.es6 +++ b/app/assets/javascripts/discourse/models/user.js.es6 @@ -10,6 +10,7 @@ import UserBadge from 'discourse/models/user-badge'; import UserActionStat from 'discourse/models/user-action-stat'; import UserAction from 'discourse/models/user-action'; import Group from 'discourse/models/group'; +import Topic from 'discourse/models/topic'; const User = RestModel.extend({ @@ -355,6 +356,38 @@ const User = RestModel.extend({ }); } }); + }, + + summary() { + return Discourse.ajax(`/users/${this.get("username_lower")}/summary.json`) + .then(json => { + const topicMap = {}; + + json.topics.forEach(t => { + topicMap[t.id] = Topic.create(t); + }); + + const badgeMap = {}; + Badge.createFromJson(json).forEach(b => { + badgeMap[b.id] = b; + }); + const summary = json["user_summary"]; + + summary.replies.forEach(r => { + r.topic = topicMap[r.topic_id]; + r.url = r.topic.urlForPostNumber(r.post_number); + r.createdAt = new Date(r.created_at); + }); + + summary.topics = summary.topic_ids.map(id => topicMap[id]); + + summary.badges = summary.badges.map(ub => { + const badge = badgeMap[ub.badge_id]; + badge.count = ub.count; + return badge; + }); + return summary; + }); } }); diff --git a/app/assets/javascripts/discourse/routes/app-route-map.js.es6 b/app/assets/javascripts/discourse/routes/app-route-map.js.es6 index 6aa420a88..f8c278db1 100644 --- a/app/assets/javascripts/discourse/routes/app-route-map.js.es6 +++ b/app/assets/javascripts/discourse/routes/app-route-map.js.es6 @@ -42,6 +42,7 @@ export default function() { this.route('parentCategory', { path: '/c/:slug' }); this.route('categoryNone', { path: '/c/:slug/none' }); this.route('category', { path: '/c/:parentSlug/:slug' }); + this.route('categoryWithID', { path: '/c/:parentSlug/:slug/:id' }); // homepage this.route(Discourse.Utilities.defaultHomepage(), { path: '/' }); @@ -57,11 +58,13 @@ export default function() { // User routes this.resource('users'); this.resource('user', { path: '/users/:username' }, function() { + this.route('summary'); this.resource('userActivity', { path: '/activity' }, function() { this.route('topics'); this.route('replies'); this.route('likesGiven', {path: 'likes-given'}); this.route('bookmarks'); + this.route('pending'); }); this.resource('userNotifications', {path: '/notifications'}, function(){ diff --git a/app/assets/javascripts/discourse/routes/badges-show.js.es6 b/app/assets/javascripts/discourse/routes/badges-show.js.es6 index 42bc89e9a..74e04c644 100644 --- a/app/assets/javascripts/discourse/routes/badges-show.js.es6 +++ b/app/assets/javascripts/discourse/routes/badges-show.js.es6 @@ -2,6 +2,11 @@ import UserBadge from 'discourse/models/user-badge'; import Badge from 'discourse/models/badge'; export default Discourse.Route.extend({ + queryParams: { + username: { + refreshModel: true + } + }, actions: { didTransition() { this.controllerFor("badges/show")._showFooter(); @@ -24,10 +29,13 @@ export default Discourse.Route.extend({ } }, - afterModel(model) { - return UserBadge.findByBadgeId(model.get("id")).then(userBadges => { + afterModel(model,transition) { + const username = transition.queryParams && transition.queryParams.username; + + return UserBadge.findByBadgeId(model.get("id"), {username}).then(userBadges => { this.userBadges = userBadges; }); + }, titleToken() { diff --git a/app/assets/javascripts/discourse/routes/discovery-category-with-id.js.es6 b/app/assets/javascripts/discourse/routes/discovery-category-with-id.js.es6 new file mode 100644 index 000000000..c36b4bae7 --- /dev/null +++ b/app/assets/javascripts/discourse/routes/discovery-category-with-id.js.es6 @@ -0,0 +1,11 @@ +import Category from 'discourse/models/category'; + +export default Discourse.Route.extend({ + model: function(params) { + return Category.findById(params.id); + }, + + redirect: function(model) { + this.transitionTo(`/c/${Category.slugFor(model)}`); + } +}); diff --git a/app/assets/javascripts/discourse/routes/user-statistics.js.es6 b/app/assets/javascripts/discourse/routes/user-statistics.js.es6 deleted file mode 100644 index 610a10489..000000000 --- a/app/assets/javascripts/discourse/routes/user-statistics.js.es6 +++ /dev/null @@ -1,2 +0,0 @@ -export default Discourse.Route.extend({ -}); diff --git a/app/assets/javascripts/discourse/routes/user-summary.js.es6 b/app/assets/javascripts/discourse/routes/user-summary.js.es6 new file mode 100644 index 000000000..4b24ecc6a --- /dev/null +++ b/app/assets/javascripts/discourse/routes/user-summary.js.es6 @@ -0,0 +1,5 @@ +export default Discourse.Route.extend({ + model() { + return this.modelFor("User").summary(); + } +}); diff --git a/app/assets/javascripts/discourse/templates/badges/show.hbs b/app/assets/javascripts/discourse/templates/badges/show.hbs index 4b245ee2f..7cfada243 100644 --- a/app/assets/javascripts/discourse/templates/badges/show.hbs +++ b/app/assets/javascripts/discourse/templates/badges/show.hbs @@ -9,7 +9,9 @@
      {{user-badge badge=model}}
      {{{model.displayDescriptionHtml}}}
      -
      {{i18n 'badges.granted' count=model.grant_count}}
      + {{#unless user}} +
      {{i18n 'badges.granted' count=grantCount}}
      + {{/unless}}
      {{i18n 'badges.allow_title'}} {{{view.allowTitle}}}
      {{i18n 'badges.multiple_grant'}} {{{view.multipleGrant}}}
      @@ -22,23 +24,48 @@
    {{/if}} + {{#if user}} + + {{/if}} + {{#if userBadges}}
    {{#each ub in userBadges}}
    - {{#link-to 'user' ub.user classNames="badge-info"}} - {{avatar ub.user imageSize="large"}} -
    - {{ub.user.username}} - {{format-date ub.granted_at}} -
    - {{/link-to}} + {{#if user}} + {{format-date ub.granted_at}} + {{else}} + {{#link-to 'user' ub.user classNames="badge-info"}} + {{avatar ub.user imageSize="large"}} +
    + {{ub.user.username}} + {{format-date ub.granted_at}} +
    + {{/link-to}} + {{/if}} {{#if ub.post_number}} {{{ub.topic.fancyTitle}}} {{/if}}
    {{/each}} + + {{#unless canLoadMore}} + {{#if user}} + {{i18n 'badges.more_with_badge'}} + {{/if}} + {{/unless}} +
    {{conditional-loading-spinner condition=canLoadMore}} diff --git a/app/assets/javascripts/discourse/templates/components/date-picker.hbs b/app/assets/javascripts/discourse/templates/components/date-picker.hbs new file mode 100644 index 000000000..7d9e48080 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/date-picker.hbs @@ -0,0 +1 @@ + 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 1a7fa9b42..01aeb2d15 100644 --- a/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs +++ b/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs @@ -20,18 +20,13 @@
    {{#if emailInEnabled}} -
    - -
    +
    + diff --git a/app/assets/javascripts/discourse/templates/modal/invite.hbs b/app/assets/javascripts/discourse/templates/modal/invite.hbs index a5ccb897f..0a016002d 100644 --- a/app/assets/javascripts/discourse/templates/modal/invite.hbs +++ b/app/assets/javascripts/discourse/templates/modal/invite.hbs @@ -28,7 +28,7 @@ {{#if model.finished}} {{d-button class="btn-primary" action="closeModal" label="close"}} {{else}} - {{d-button icon="envelope" action="createInvite" class="btn-primary" disabled=disabled label=buttonTitle}} + {{d-button icon=inviteIcon action="createInvite" class="btn-primary" disabled=disabled label=buttonTitle}} {{#if showCopyInviteButton}} {{d-button icon="link" action="generateInvitelink" class="btn-primary" disabled=disabledCopyLink label='user.invited.generate_link'}} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/user-card.hbs b/app/assets/javascripts/discourse/templates/user-card.hbs index 6c0762d22..9b8bf40bb 100644 --- a/app/assets/javascripts/discourse/templates/user-card.hbs +++ b/app/assets/javascripts/discourse/templates/user-card.hbs @@ -68,7 +68,7 @@ {{#if showBadges}}
    {{#each ub in user.featured_user_badges}} - {{user-badge badge=ub.badge}} + {{user-badge badge=ub.badge user=user}} {{/each}} {{#if showMoreBadges}} {{#link-to 'user.badges' user class="btn more-user-badges"}} diff --git a/app/assets/javascripts/discourse/templates/user/activity.hbs b/app/assets/javascripts/discourse/templates/user/activity.hbs index 01bc42ee1..e94b740c9 100644 --- a/app/assets/javascripts/discourse/templates/user/activity.hbs +++ b/app/assets/javascripts/discourse/templates/user/activity.hbs @@ -1,5 +1,5 @@
    -
    diff --git a/app/assets/javascripts/discourse/views/create-account.js.es6 b/app/assets/javascripts/discourse/views/create-account.js.es6 index da0e28158..964c8f8ec 100644 --- a/app/assets/javascripts/discourse/views/create-account.js.es6 +++ b/app/assets/javascripts/discourse/views/create-account.js.es6 @@ -6,9 +6,14 @@ export default ModalBodyView.extend({ classNames: ['create-account'], _setup: function() { - // allows the submission the form when pressing 'ENTER' on *any* text input field - // but only when the submit button is enabled + // Allows submitting the form when pressing 'ENTER' on *any* text input field + // but only when the submit button is enabled. const createAccountController = this.get('controller'); + + if ($.cookie('email')) { + createAccountController.set('accountEmail', $.cookie('email')); + } + Em.run.schedule('afterRender', function() { $("input[type='text'], input[type='password']").keydown(function(e) { if (createAccountController.get('submitDisabled') === false && e.keyCode === 13) { diff --git a/app/assets/javascripts/discourse/views/login.js.es6 b/app/assets/javascripts/discourse/views/login.js.es6 index f2b61efb5..656c476ba 100644 --- a/app/assets/javascripts/discourse/views/login.js.es6 +++ b/app/assets/javascripts/discourse/views/login.js.es6 @@ -15,8 +15,13 @@ export default ModalBodyView.extend({ // Get username and password from the browser's password manager, // if it filled the hidden static login form: - loginController.set('loginName', $('#hidden-login-form input[name=username]').val()); - loginController.set('loginPassword', $('#hidden-login-form input[name=password]').val()); + var prefillUsername = $('#hidden-login-form input[name=username]').val(); + if (prefillUsername) { + loginController.set('loginName', prefillUsername); + loginController.set('loginPassword', $('#hidden-login-form input[name=password]').val()); + } else if ($.cookie('email')) { + loginController.set('loginName', $.cookie('email')); + } Em.run.schedule('afterRender', function() { $('#login-account-password, #login-account-name').keydown(function(e) { diff --git a/app/assets/javascripts/discourse/views/post.js.es6 b/app/assets/javascripts/discourse/views/post.js.es6 index 28c4fcbe7..21535c72d 100644 --- a/app/assets/javascripts/discourse/views/post.js.es6 +++ b/app/assets/javascripts/discourse/views/post.js.es6 @@ -3,6 +3,7 @@ import { number } from 'discourse/lib/formatter'; import DiscourseURL from 'discourse/lib/url'; import { default as computed, on } from 'ember-addons/ember-computed-decorators'; import { fmt } from 'discourse/lib/computed'; +import { isValidLink } from 'discourse/lib/click-track'; const DAY = 60 * 50 * 1000; @@ -192,8 +193,7 @@ const PostView = Discourse.GroupedView.extend(Ember.Evented, { if (valid) { // don't display badge counts on category badge & oneboxes (unless when explicitely stated) - if ($link.hasClass("track-link") || - $link.closest('.badge-category,.onebox-result,.onebox-body').length === 0) { + if (isValidLink($link)) { $link.append("" + number(lc.clicks) + ""); } } diff --git a/app/assets/javascripts/locales/sk.js.erb b/app/assets/javascripts/locales/sk.js.erb new file mode 100644 index 000000000..42603f0b4 --- /dev/null +++ b/app/assets/javascripts/locales/sk.js.erb @@ -0,0 +1,9 @@ +//= depend_on 'client.sk.yml' +//= require locales/i18n +<%= JsLocaleHelper.output_locale(:sk) %> + +I18n.pluralizationRules['sk'] = function (n) { + if (n == 1) return "one"; + if (n >= 2 && n <= 4) return "few"; + return "other"; +}; diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index 7ef592b19..85e9ac30e 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -1532,7 +1532,7 @@ button.ru { @media all and (max-width : 850px) { - html:not(.mobile-view) .nav-stacked { + html:not(.mobile-view) .admin-content .nav-stacked { .glyph {width: auto; position: relative;} > li > a {padding: 13px} } @@ -1606,10 +1606,9 @@ and (max-width : 500px) { border-bottom: 1px solid #dfdfdf; } .actions { - font-size: 1.214em; float: right; - a { - margin-left: 5px; + .btn { + padding: 3px 6px; } } } @@ -1769,6 +1768,30 @@ table#user-badges { } } +// Emails + +.email-list { + .filters input { + width: 100%; + } + .time { + width: 50px; + } + .username div { + max-width: 180px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .addresses p { + margin: 2px 0; + max-width: 200px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +} + // Mobile specific styles // Mobile view text-inputs need some padding .mobile-view .admin-contents { diff --git a/app/assets/stylesheets/common/base/_topic-list.scss b/app/assets/stylesheets/common/base/_topic-list.scss index d0a66d7a8..612d43c8a 100644 --- a/app/assets/stylesheets/common/base/_topic-list.scss +++ b/app/assets/stylesheets/common/base/_topic-list.scss @@ -241,15 +241,17 @@ ol.category-breadcrumb { h2 { float: left; margin: 5px 0 10px; - .top-date-string { - color: dark-light-choose(scale-color($primary, $lightness: 60%), scale-color($secondary, $lightness: 40%)); - font-weight: normal; font-size: 0.7em; - text-transform: uppercase; } } + .top-date-string { + color: dark-light-choose(scale-color($primary, $lightness: 60%), scale-color($secondary, $lightness: 40%)); + font-weight: normal; + text-transform: uppercase; + } + button { outline: 0; background: transparent; @@ -262,6 +264,7 @@ ol.category-breadcrumb { @include unselectable; + font-size: 1.2em; border: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); padding: 5px; background: $secondary; @@ -277,9 +280,15 @@ ol.category-breadcrumb { li { margin: 0; padding: 0; - a { + font-weight: bold; + a, a:visited { display: block; padding: 5px; + color: $primary; + } + .top-date-string { + font-weight: normal; + font-size: 0.8em; } &:hover { background-color: dark-light-diff($highlight, $secondary, 50%, -70%); diff --git a/app/assets/stylesheets/common/base/topic-admin-menu.scss b/app/assets/stylesheets/common/base/topic-admin-menu.scss index 651c7ba20..667496f92 100644 --- a/app/assets/stylesheets/common/base/topic-admin-menu.scss +++ b/app/assets/stylesheets/common/base/topic-admin-menu.scss @@ -33,6 +33,15 @@ } } +.date-picker-wrapper { + display: inline-block; + position: relative; +} + +.pika-single { + position: absolute !important; +} + .modal-body.feature-topic { padding: 5px; max-height: 500px; diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss index fc931a22c..7ac426abb 100644 --- a/app/assets/stylesheets/common/base/topic-post.scss +++ b/app/assets/stylesheets/common/base/topic-post.scss @@ -156,9 +156,6 @@ aside.quote { background-color: dark-light-diff($tertiary, $secondary, 85%, -65%); } } -.wiki .topic-body { - background-color: dark-light-diff($wiki, $secondary, 95%, -50%); -} // this ensures consistent top margin on topic posts even if the first line of a post // is a top-margin-less element like a list or image. diff --git a/app/assets/stylesheets/common/base/user-badges.scss b/app/assets/stylesheets/common/base/user-badges.scss index a7d25e877..8f4d7a21b 100644 --- a/app/assets/stylesheets/common/base/user-badges.scss +++ b/app/assets/stylesheets/common/base/user-badges.scss @@ -187,6 +187,58 @@ } } +.show-badge .badge-user-info { + margin-left: 2%; + .earned { + margin-top: 15px; + font-size: 1.3em; + } + .username { + margin-top: 5px; + display: block; + color: dark-light-choose(scale-color($primary, $lightness: 30%), scale-color($secondary, $lightness: 70%)); + } +} + +.show-badge .single-user { + margin-left: 2%; + padding-bottom: 20px; + .load-more { + padding-top: 30px; + display: block; + font-size: 1.2em; + } +} + +.show-badge .single-user .badge-user { + padding-left: 0; + + text-align: left; + display: block; + margin: 20px 0; + .badge-info { + display: none; + } + .date { + display: inline-block; + font-size: 1.1em; + margin-left: 10px; + } + .post-link { + font-size: 1.3em; + width: 500px; + margin: 0; + padding: 0; + } + width: 800px; + + &:after { + content: ""; + clear: both; + display: table; + } +} + .long-description.banner { width: 88%; margin-bottom: 20px; diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss index fac2284a6..8ec1047f4 100644 --- a/app/assets/stylesheets/common/base/user.scss +++ b/app/assets/stylesheets/common/base/user.scss @@ -149,3 +149,61 @@ } } +.top-section { + display: inline-block; + width: 45%; + max-width: 500px; + padding-right: 20px; + vertical-align: top; + margin-bottom: 30px; + .more { + display: block; + margin-top: 10px; + color: dark-light-choose(scale-color($primary, $lightness: 40%), scale-color($secondary, $lightness: 60%)); + } + h3 { + margin-bottom: 15px; + } + .relative-date { + color: lighten($primary, 40%); + font-size: 0.8em; + margin-left: 5px; + } + .like-count { + margin-left: 5px; + } + ul { + list-style-type: none; + padding: 0; + margin: 0; + li { + margin: 0; + padding: 8px 0; + .fa-heart { + margin-left: 3px; + } + } + } + + dt,dd { + float:left; + } + dd { + min-width: 80px; + text-align: right; + } + dt { + clear: left; + min-width: 100px; + color: dark-light-choose(scale-color($primary, $lightness: 25%), scale-color($secondary, $lightness: 75%)); + } +} + +@media all +and (max-width : 600px) { + .top-section { + width: 90%; + } +} + + diff --git a/app/assets/stylesheets/common/components/badges.css.scss b/app/assets/stylesheets/common/components/badges.css.scss index 3f5239d1f..afe7ebd9b 100644 --- a/app/assets/stylesheets/common/components/badges.css.scss +++ b/app/assets/stylesheets/common/components/badges.css.scss @@ -30,7 +30,7 @@ color: $primary !important; padding: 3px; vertical-align: text-top; - margin-top: -3px; //vertical alignment fix + margin-top: -2px; //vertical alignment fix display: inline-block; overflow: hidden; text-overflow: ellipsis; @@ -38,21 +38,18 @@ .extra-info-wrapper & { color: $header-primary !important; } - } + } - .badge-category-parent-bg, .badge-category-bg { - display: inline-block; - padding: 1px; - - &:before { - content: "\a0"; - } - - } + .badge-category-parent-bg, .badge-category-bg { + display: inline-block; + padding: 1px; + &:before { + content: "\a0"; + } + } } - &.bullet { //bullet category style display: inline-flex; align-items: baseline; @@ -71,31 +68,30 @@ .extra-info-wrapper & { color: $header-primary !important; } - } + } .badge-category-parent-bg, .badge-category-bg { - width: 10px; - height: 10px; - margin-right: 5px; - display: inline-block; - line-height: 1; + width: 10px; + height: 10px; + margin-right: 5px; + display: inline-block; + line-height: 1; - &:before { - content: "\a0"; - } - } + &:before { + content: "\a0"; + } + } - span { - &.badge-category-parent-bg { //subcategory style - width: 5px; - margin-right: 0; - & + .badge-category-bg { - width: 5px; - } - } - } - - } + span { + &.badge-category-parent-bg { //subcategory style + width: 5px; + margin-right: 0; + & + .badge-category-bg { + width: 5px; + } + } + } + } &.box { //box category style (apply custom widths to the wrapper, not the children) @@ -134,6 +130,30 @@ } } +@mixin cooked-badge-bullet($length, $offset:0px) { + .badge-wrapper.bullet { + span { + position: relative; + + &.badge-category-bg { + width: $length; + height: $length; + top: $offset; + } + + &.badge-category-parent-bg { + width: $length / 2; + height: $length; + top: $offset; + + & + .badge-category-bg { + width: $length / 2; + } + } + } + } +} + // Category badge dropdown // -------------------------------------------------- @@ -190,6 +210,8 @@ line-height: 1; } .badge-wrapper { + box-sizing: border-box; + &.bar { padding: 5px 0; width: 100%; diff --git a/app/assets/stylesheets/common/components/hashtag.scss b/app/assets/stylesheets/common/components/hashtag.scss new file mode 100644 index 000000000..3cea8a783 --- /dev/null +++ b/app/assets/stylesheets/common/components/hashtag.scss @@ -0,0 +1,14 @@ +a.hashtag { + color: black; + font-weight: bold; + + &:visited, &:hover { + color: black; + } + + &:hover { + span { + text-decoration: underline; + } + } +} diff --git a/app/assets/stylesheets/desktop/compose.scss b/app/assets/stylesheets/desktop/compose.scss index 6edf84153..6e7666916 100644 --- a/app/assets/stylesheets/desktop/compose.scss +++ b/app/assets/stylesheets/desktop/compose.scss @@ -96,7 +96,7 @@ } .search-link { .fa, .blurb { - color: dark-light-choose(scale-color($tertiary, $lightness: -40%), scale-color($tertiary, $lightness: 40%)); + color: dark-light-choose(scale-color($primary, $lightness: 45%), scale-color($secondary, $lightness: 55%)); } } .badge-wrapper { diff --git a/app/assets/stylesheets/desktop/header.scss b/app/assets/stylesheets/desktop/header.scss index 0ce668056..ff74c6714 100644 --- a/app/assets/stylesheets/desktop/header.scss +++ b/app/assets/stylesheets/desktop/header.scss @@ -27,6 +27,7 @@ and (max-width : 570px) { } .search-link .blurb { + color: dark-light-choose(scale-color($primary, $lightness: 45%), scale-color($secondary, $lightness: 55%)); display: block; word-wrap: break-word; font-size: 11px; diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss index 0e1b71954..42f2676b0 100644 --- a/app/assets/stylesheets/desktop/topic-post.scss +++ b/app/assets/stylesheets/desktop/topic-post.scss @@ -174,6 +174,10 @@ nav.post-controls { box-shadow: none; } + &.wikied { + color: green; + } + &.bookmark {padding: 8px 11px; } .read-icon { @@ -1026,3 +1030,16 @@ and (max-width : 767px) { } } + +@media all +and (max-height: 700px) { + + .post-menu-area { + margin-bottom: 0px; + margin-top: -18px; + } + + .topic-body { + padding: 5px 11px 2px; + } +} diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss index 56e196f73..eb11d0af8 100644 --- a/app/assets/stylesheets/desktop/user.scss +++ b/app/assets/stylesheets/desktop/user.scss @@ -79,11 +79,27 @@ margin-top: 18px; } +.user-table { + margin-top: 30px; + width: 100%; + display: table; + .wrapper { + display: table-row; + } +} + +.user-navigation .nav-stacked .glyph { + display: none; + // float: right; + // display: block; + // position: static; +} + .user-navigation { - width: 21.62%; - margin-right: 1.8018%; - float: left; - margin-top: 20px; + display: table-cell; + vertical-align: top; + width: 170px; + padding-right: 30px; h3 { color: $primary; @@ -161,9 +177,9 @@ } .user-right { - width: 75%; - float: right; + max-width: 700px; margin-top: 20px; + display: table-cell; } .user-content { @@ -654,13 +670,18 @@ } .user-main .nav-stacked { + &.notification-list { + padding-top: 40px; + } + &.activity-list { + padding-top: 20px; + } background-color: transparent; - margin-top: 20px; > li { border-bottom: none; > a { - padding: 8px 8px 8px 30px; + padding: 8px 13px; color: dark-light-choose(scale-color($primary, $lightness: 40%), scale-color($secondary, $lightness: 40%)); } } @@ -671,8 +692,13 @@ background-color: transparent; } li > a.active:after { - border-left-color: none; + display: none; + } + + li.archive { + padding-left: 15px; } } + diff --git a/app/assets/stylesheets/mobile/topic-list.scss b/app/assets/stylesheets/mobile/topic-list.scss index 7f70b260f..01f656b6e 100644 --- a/app/assets/stylesheets/mobile/topic-list.scss +++ b/app/assets/stylesheets/mobile/topic-list.scss @@ -77,6 +77,7 @@ td { padding: 7px 0; color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%)); + max-width: 300px; } a.title {color: $primary;} @@ -404,6 +405,7 @@ td .main-link { display: inline-block; a.title { padding: 5px 10px 5px 0; + word-wrap: break-word; } } .topic-list { diff --git a/app/assets/stylesheets/mobile/topic-post.scss b/app/assets/stylesheets/mobile/topic-post.scss index c0ffdc235..05ae0a664 100644 --- a/app/assets/stylesheets/mobile/topic-post.scss +++ b/app/assets/stylesheets/mobile/topic-post.scss @@ -58,6 +58,7 @@ button { margin:10px 0 10px 0; } &.has-like {color: $love;} + &.wikied { color: green; } .read-icon { &:before { font-family: "FontAwesome"; diff --git a/app/assets/stylesheets/mobile/topic.scss b/app/assets/stylesheets/mobile/topic.scss index b32607a7e..75b772802 100644 --- a/app/assets/stylesheets/mobile/topic.scss +++ b/app/assets/stylesheets/mobile/topic.scss @@ -23,6 +23,7 @@ a { color: $primary; vertical-align: middle; + word-wrap: break-word; } } .title-category-wrapper { diff --git a/app/controllers/admin/email_controller.rb b/app/controllers/admin/email_controller.rb index 5ae6a3205..4f9837e8b 100644 --- a/app/controllers/admin/email_controller.rb +++ b/app/controllers/admin/email_controller.rb @@ -17,11 +17,6 @@ class Admin::EmailController < Admin::AdminController end end - def all - email_logs = filter_email_logs(EmailLog.all, params) - render_serialized(email_logs, EmailLogSerializer) - end - def sent email_logs = filter_email_logs(EmailLog.sent, params) render_serialized(email_logs, EmailLogSerializer) @@ -32,6 +27,16 @@ class Admin::EmailController < Admin::AdminController render_serialized(email_logs, EmailLogSerializer) end + def received + incoming_emails = filter_incoming_emails(IncomingEmail, params) + render_serialized(incoming_emails, IncomingEmailSerializer) + end + + def rejected + incoming_emails = filter_incoming_emails(IncomingEmail.errored, params) + render_serialized(incoming_emails, IncomingEmailSerializer) + end + def preview_digest params.require(:last_seen_at) params.require(:username) @@ -49,13 +54,33 @@ class Admin::EmailController < Admin::AdminController private def filter_email_logs(email_logs, params) - email_logs = email_logs.limit(50).includes(:user).order("email_logs.created_at desc").references(:user) - email_logs = email_logs.where("users.username LIKE ?", "%#{params[:user]}%") if params[:user].present? - email_logs = email_logs.where("email_logs.to_address LIKE ?", "%#{params[:address]}%") if params[:address].present? - email_logs = email_logs.where("email_logs.email_type LIKE ?", "%#{params[:type]}%") if params[:type].present? - email_logs = email_logs.where("email_logs.reply_key LIKE ?", "%#{params[:reply_key]}%") if params[:reply_key].present? - email_logs = email_logs.where("email_logs.skipped_reason LIKE ?", "%#{params[:skipped_reason]}%") if params[:skipped_reason].present? - email_logs.to_a + email_logs = email_logs.includes(:user) + .references(:user) + .order(created_at: :desc) + .offset(params[:offset] || 0) + .limit(50) + + email_logs = email_logs.where("users.username ILIKE ?", "%#{params[:user]}%") if params[:user].present? + email_logs = email_logs.where("email_logs.to_address ILIKE ?", "%#{params[:address]}%") if params[:address].present? + email_logs = email_logs.where("email_logs.email_type ILIKE ?", "%#{params[:type]}%") if params[:type].present? + email_logs = email_logs.where("email_logs.reply_key ILIKE ?", "%#{params[:reply_key]}%") if params[:reply_key].present? + email_logs = email_logs.where("email_logs.skipped_reason ILIKE ?", "%#{params[:skipped_reason]}%") if params[:skipped_reason].present? + + email_logs + end + + def filter_incoming_emails(incoming_emails, params) + incoming_emails = incoming_emails.includes(:user, { post: :topic }) + .order(created_at: :desc) + .offset(params[:offset] || 0) + .limit(50) + + incoming_emails = incoming_emails.where("from_address ILIKE ?", "%#{params[:from]}%") if params[:from].present? + incoming_emails = incoming_emails.where("to_addresses ILIKE ? OR cc_addresses ILIKE ?", "%#{params[:to]}%") if params[:to].present? + incoming_emails = incoming_emails.where("subject ILIKE ?", "%#{params[:subject]}%") if params[:subject].present? + incoming_emails = incoming_emails.where("error ILIKE ?", "%#{params[:error]}%") if params[:error].present? + + incoming_emails end def delivery_settings diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index ee03ac705..dacf04175 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -216,7 +216,7 @@ class Admin::UsersController < Admin::AdminController def block guardian.ensure_can_block_user! @user - UserBlocker.block(@user, current_user) + UserBlocker.block(@user, current_user, keep_posts: true) render nothing: true end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 97c3b55a1..05f850e30 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -251,8 +251,8 @@ class ApplicationController < ActionController::Base user = if params[:username] username_lower = params[:username].downcase username_lower.gsub!(/\.json$/, '') - find_opts = {username_lower: username_lower} - find_opts[:active] = true unless opts[:include_inactive] + find_opts = { username_lower: username_lower } + find_opts[:active] = true unless opts[:include_inactive] || current_user.try(:staff?) User.find_by(find_opts) elsif params[:external_id] external_id = params[:external_id].gsub(/\.json$/, '') diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index 5e6a25f1b..be98b2089 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -178,15 +178,14 @@ class CategoriesController < ApplicationController :position, :email_in, :email_in_allow_strangers, - :contains_messages, :suppress_from_homepage, :parent_category_id, :auto_close_hours, :auto_close_based_on_last_post, :logo_url, :background_url, - :allow_badges, :slug, + :allow_badges, :topic_template, :custom_fields => [params[:custom_fields].try(:keys)], :permissions => [*p.try(:keys)]) diff --git a/app/controllers/category_hashtags_controller.rb b/app/controllers/category_hashtags_controller.rb new file mode 100644 index 000000000..ca4a1f700 --- /dev/null +++ b/app/controllers/category_hashtags_controller.rb @@ -0,0 +1,16 @@ +class CategoryHashtagsController < ApplicationController + before_filter :ensure_logged_in + + def check + category_slugs = params[:category_slugs] + category_slugs.each(&:downcase!) + + ids = category_slugs.map { |category_slug| Category.query_from_hashtag_slug(category_slug).try(:id) } + + valid_categories = Category.secured(guardian).where(id: ids).map do |category| + { slug: category.hashtag_slug, url: category.url_with_id } + end.compact + + render json: { valid: valid_categories } + end +end diff --git a/app/controllers/email_controller.rb b/app/controllers/email_controller.rb index ca8255334..4e0ff301a 100644 --- a/app/controllers/email_controller.rb +++ b/app/controllers/email_controller.rb @@ -11,6 +11,7 @@ class EmailController < ApplicationController def unsubscribe @user = DigestUnsubscribeKey.user_for_key(params[:key]) + RateLimiter.new(@user, "unsubscribe_via_email", 3, 1.day).performed! unless @user && @user.staff? # Don't allow the use of a key while logged in as a different user if current_user.present? && (@user != current_user) @@ -23,7 +24,12 @@ class EmailController < ApplicationController return end - @user.update_column(:email_digests, false) + if params[:from_all] + @user.update_columns(email_digests: false, email_direct: false, email_private_messages: false, email_always: false) + else + @user.update_column(:email_digests, false) + end + @success = true end diff --git a/app/controllers/list_controller.rb b/app/controllers/list_controller.rb index 07fec8d6a..a5aa226c0 100644 --- a/app/controllers/list_controller.rb +++ b/app/controllers/list_controller.rb @@ -54,15 +54,17 @@ class ListController < ApplicationController list_opts.merge!(options) if options user = list_target_user - if filter == :latest && params[:category].blank? - 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])) + if params[:category].blank? + if filter == :latest + list_opts[:no_definitions] = true + end + if filter.to_s == current_homepage + list_opts[:exclude_category_ids] = get_excluded_category_ids(list_opts[:category]) + end 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) if Discourse.anonymous_filters.include?(filter) @@ -165,7 +167,7 @@ class ListController < ApplicationController 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])) + top_options[:exclude_category_ids] = get_excluded_category_ids(top_options[:category]) end user = list_target_user @@ -224,14 +226,22 @@ class ListController < ApplicationController def set_category slug_or_id = params.fetch(:category) parent_slug_or_id = params[:parent_category] + id = params[:id].to_i parent_category_id = nil if parent_slug_or_id.present? parent_category_id = Category.query_parent_category(parent_slug_or_id) - redirect_or_not_found and return if parent_category_id.blank? + redirect_or_not_found and return if parent_category_id.blank? && !id end @category = Category.query_category(slug_or_id, parent_category_id) + + # Redirect if we have `/c/:parent_category/:category/:id` + if id + category = Category.find_by_id(id) + (redirect_to category.url, status: 301) && return if category + end + redirect_or_not_found and return if !@category @description_meta = @category.description_text diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 2b172ed8c..553e0e036 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -96,14 +96,6 @@ class PostsController < ApplicationController def create - if !is_api? && current_user.blocked? - - # error has parity with what user would get if they posted when blocked - # and it went through post creator - render json: {errors: [I18n.t("topic_not_found")]}, status: 422 - return - end - @manager_params = create_params @manager_params[:first_post_checks] = !is_api? @@ -305,9 +297,9 @@ class PostsController < ApplicationController end def wiki - guardian.ensure_can_wiki! - post = find_post_from_params + guardian.ensure_can_wiki!(post) + post.revise(current_user, { wiki: params[:wiki] }) render nothing: true diff --git a/app/controllers/static_controller.rb b/app/controllers/static_controller.rb index c2d4d6f17..fc2bbe09f 100644 --- a/app/controllers/static_controller.rb +++ b/app/controllers/static_controller.rb @@ -6,8 +6,10 @@ class StaticController < ApplicationController skip_before_filter :check_xhr, :redirect_to_login_if_required skip_before_filter :verify_authenticity_token, only: [:cdn_asset, :enter, :favicon] + PAGES_WITH_EMAIL_PARAM = ['login', 'password_reset', 'signup'] + def show - return redirect_to(path '/') if current_user && params[:id] == 'login' + return redirect_to(path '/') if current_user && (params[:id] == 'login' || params[:id] == 'signup') map = { "faq" => {redirect: "faq_url", topic_id: "guidelines_topic_id"}, @@ -44,6 +46,10 @@ class StaticController < ApplicationController return end + if PAGES_WITH_EMAIL_PARAM.include?(@page) && params[:email] + cookies[:email] = { value: params[:email], expires: 1.day.from_now } + end + file = "static/#{@page}.#{I18n.locale}" file = "static/#{@page}.en" if lookup_context.find_all("#{file}.html").empty? file = "static/#{@page}" if lookup_context.find_all("#{file}.html").empty? diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index b049438b6..bc53a2e95 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -281,6 +281,9 @@ class TopicsController < ApplicationController def toggle_archive_message(archive) topic = Topic.find(params[:id].to_i) + + group_id = nil + group_ids = current_user.groups.pluck(:id) if group_ids.present? allowed_groups = topic.allowed_groups @@ -289,6 +292,7 @@ class TopicsController < ApplicationController GroupArchivedMessage.where(group_id: id, topic_id: topic.id).destroy_all if archive + group_id = id GroupArchivedMessage.create!(group_id: id, topic_id: topic.id) end end @@ -302,7 +306,12 @@ class TopicsController < ApplicationController end end - render nothing: true + if group_id + name = Group.find_by(id: group_id).try(:name) + render_json_dump(group_name: name) + else + render nothing: true + end end def bookmark @@ -347,7 +356,7 @@ class TopicsController < ApplicationController topic = Topic.find_by(id: params[:topic_id]) guardian.ensure_can_remove_allowed_users!(topic) - if topic.remove_allowed_user(params[:username]) + if topic.remove_allowed_user(current_user, params[:username]) render json: success_json else render json: failed_json, status: 422 diff --git a/app/controllers/user_badges_controller.rb b/app/controllers/user_badges_controller.rb index 397ce503f..5f1499f91 100644 --- a/app/controllers/user_badges_controller.rb +++ b/app/controllers/user_badges_controller.rb @@ -1,16 +1,28 @@ class UserBadgesController < ApplicationController def index - params.permit [:granted_before, :offset] + params.permit [:granted_before, :offset, :username] badge = fetch_badge_from_params user_badges = badge.user_badges.order('granted_at DESC, id DESC').limit(96) user_badges = user_badges.includes(:user, :granted_by, badge: :badge_type, post: :topic) + grant_count = nil + + if params[:username] + user_id = User.where(username_lower: params[:username].downcase).pluck(:id).first + user_badges = user_badges.where(user_id: user_id) if user_id + grant_count = user_badges.count + end + if offset = params[:offset] user_badges = user_badges.offset(offset.to_i) end - render_serialized(user_badges, UserBadgeSerializer, root: "user_badges", include_long_description: true) + user_badges = UserBadges.new(user_badges: user_badges, + username: params[:username], + grant_count: grant_count) + + render_serialized(user_badges, UserBadgesSerializer, root: :user_badge_info, include_long_description: true) end def username @@ -28,7 +40,7 @@ class UserBadgesController < ApplicationController .includes(post: :topic) .includes(:granted_by) - render_serialized(user_badges, DetailedUserBadgeSerializer, root: "user_badges") + render_serialized(user_badges, DetailedUserBadgeSerializer, root: :user_badges) end def create diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 3e510bb25..3a9f77942 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -23,6 +23,7 @@ class UsersController < ApplicationController :send_activation_email, :authorize_email, :password_reset, + :confirm_email_token, :admin_login] def index @@ -175,6 +176,13 @@ class UsersController < ApplicationController end end + def summary + user = fetch_user_from_params + summary = UserSummary.new(user, guardian) + serializer = UserSummarySerializer.new(summary, scope: guardian) + render_json_dump(serializer) + end + def invited inviter = fetch_user_from_params offset = params[:offset].to_i || 0 @@ -355,7 +363,12 @@ class UsersController < ApplicationController expires_now if EmailToken.valid_token_format?(params[:token]) - @user = EmailToken.confirm(params[:token]) + if request.put? + @user = EmailToken.confirm(params[:token]) + else + email_token = EmailToken.confirmable(params[:token]) + @user = email_token.try(:user) + end if @user session["password-#{params[:token]}"] = @user.id @@ -387,6 +400,12 @@ class UsersController < ApplicationController render layout: 'no_ember' end + def confirm_email_token + expires_now + EmailToken.confirm(params[:token]) + render json: success_json + end + def logon_after_password_reset message = if Guardian.new(@user).can_access_forum? # Log in the user @@ -460,10 +479,11 @@ class UsersController < ApplicationController RateLimiter.new(user, "change-email-hr-#{request.remote_ip}", 6, 1.hour).performed! RateLimiter.new(user, "change-email-min-#{request.remote_ip}", 3, 1.minute).performed! + EmailValidator.new(attributes: :email).validate_each(user, :email, lower_email) + return render_json_error(user.errors.full_messages) if user.errors[:email].present? + # Raise an error if the email is already in use - if User.find_by_email(lower_email) - raise Discourse::InvalidParameters.new(:email) - end + return render_json_error(I18n.t('change_email.error')) if User.find_by_email(lower_email) email_token = user.email_tokens.create(email: lower_email) Jobs.enqueue( diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 2c60a6251..d8b9634dc 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -146,12 +146,9 @@ module ApplicationHelper end end - if opts[:read_time] + if opts[:read_time] && opts[:read_time] > 0 && opts[:like_count] && opts[:like_count] > 0 result << tag(:meta, name: 'twitter:label1', value: I18n.t("reading_time")) result << tag(:meta, name: 'twitter:data1', value: "#{opts[:read_time]} mins 🕑") - end - - if opts[:like_count] result << tag(:meta, name: 'twitter:label2', value: I18n.t("likes")) result << tag(:meta, name: 'twitter:data2', value: "#{opts[:like_count]} ❤") end diff --git a/app/jobs/base.rb b/app/jobs/base.rb index 1ccbdbdee..427a72007 100644 --- a/app/jobs/base.rb +++ b/app/jobs/base.rb @@ -197,6 +197,11 @@ module Jobs class Scheduled < Base extend Scheduler::Schedule + + def perform(*args) + return if Discourse.readonly_mode? + super + end end def self.enqueue(job_name, opts={}) @@ -233,30 +238,23 @@ module Jobs end def self.cancel_scheduled_job(job_name, params={}) - jobs = scheduled_for(job_name, params) - return false if jobs.empty? - jobs.each { |job| job.delete } - true + scheduled_for(job_name, params).each(&:delete) end def self.scheduled_for(job_name, params={}) + params = params.with_indifferent_access job_class = "Jobs::#{job_name.to_s.camelcase}" Sidekiq::ScheduledSet.new.select do |scheduled_job| - if scheduled_job.klass == 'Sidekiq::Extensions::DelayedClass' - job_args = YAML.load(scheduled_job.args[0]) - job_args_class, _, (job_args_params, *) = job_args - if job_args_class.to_s == job_class && job_args_params - matched = true - params.each do |key, value| - unless job_args_params[key] == value - matched = false - break - end + if scheduled_job.klass.to_s == job_class + matched = true + job_params = scheduled_job.item["args"][0].with_indifferent_access + params.each do |key, value| + if job_params[key] != value + matched = false + break end - matched - else - false end + matched else false end diff --git a/app/jobs/scheduled/badge_grant.rb b/app/jobs/scheduled/badge_grant.rb index 5f218f911..dc5def068 100644 --- a/app/jobs/scheduled/badge_grant.rb +++ b/app/jobs/scheduled/badge_grant.rb @@ -10,7 +10,7 @@ module Jobs def execute(args) return unless SiteSetting.enable_badges - Badge.all.each do |b| + Badge.enabled.find_each do |b| begin BadgeGranter.backfill(b) rescue => ex diff --git a/app/jobs/scheduled/poll_mailbox.rb b/app/jobs/scheduled/poll_mailbox.rb index 552833731..2d141e3b1 100644 --- a/app/jobs/scheduled/poll_mailbox.rb +++ b/app/jobs/scheduled/poll_mailbox.rb @@ -1,6 +1,3 @@ -# -# Connects to a mailbox and checks for replies -# require 'net/pop' require_dependency 'email/receiver' require_dependency 'email/sender' @@ -10,6 +7,7 @@ module Jobs class PollMailbox < Jobs::Scheduled every SiteSetting.pop3_polling_period_mins.minutes sidekiq_options retry: false + include Email::BuildEmailHelper def execute(args) @@ -17,53 +15,42 @@ module Jobs poll_pop3 if SiteSetting.pop3_polling_enabled? end - def handle_mail(mail) + def process_popmail(popmail) begin - mail_string = mail.pop + mail_string = popmail.pop Email::Receiver.new(mail_string).process rescue => e handle_failure(mail_string, e) - ensure - mail.delete end end def handle_failure(mail_string, e) Rails.logger.warn("Email can not be processed: #{e}\n\n#{mail_string}") if SiteSetting.log_mail_processing_failures + message_template = case e + when Email::Receiver::EmptyEmailError then :email_reject_empty + when Email::Receiver::NoBodyDetectedError then :email_reject_empty + when Email::Receiver::NoMessageIdError then :email_reject_no_message_id + when Email::Receiver::AutoGeneratedEmailError then :email_reject_auto_generated + when Email::Receiver::InactiveUserError then :email_reject_inactive_user + when Email::Receiver::BadDestinationAddress then :email_reject_bad_destination_address + when Email::Receiver::StrangersNotAllowedError then :email_reject_strangers_not_allowed + when Email::Receiver::InsufficientTrustLevelError then :email_reject_insufficient_trust_level + when Email::Receiver::ReplyUserNotMatchingError then :email_reject_reply_user_not_matching + when Email::Receiver::TopicNotFoundError then :email_reject_topic_not_found + when Email::Receiver::TopicClosedError then :email_reject_topic_closed + when Email::Receiver::InvalidPost then :email_reject_invalid_post + when ActiveRecord::Rollback then :email_reject_invalid_post + when Email::Receiver::InvalidPostAction then :email_reject_invalid_post_action + when Discourse::InvalidAccess then :email_reject_invalid_access + end + template_args = {} - case e - when Email::Receiver::UserNotSufficientTrustLevelError - message_template = :email_reject_trust_level - when Email::Receiver::UserNotFoundError - message_template = :email_reject_no_account - when Email::Receiver::EmptyEmailError - message_template = :email_reject_empty - when Email::Receiver::EmailUnparsableError - message_template = :email_reject_parsing - when Email::Receiver::EmailLogNotFound - message_template = :email_reject_reply_key - when Email::Receiver::BadDestinationAddress - message_template = :email_reject_destination - when Email::Receiver::TopicNotFoundError - message_template = :email_reject_topic_not_found - when Email::Receiver::TopicClosedError - message_template = :email_reject_topic_closed - when Email::Receiver::AutoGeneratedEmailError - message_template = :email_reject_auto_generated - when Discourse::InvalidAccess - message_template = :email_reject_invalid_access - when ActiveRecord::Rollback - message_template = :email_reject_post_error - when Email::Receiver::InvalidPost - if e.message.length < 6 - message_template = :email_reject_post_error - else - message_template = :email_reject_post_error_specified - template_args[:post_error] = e.message - end - else - message_template = nil + + # there might be more information available in the exception + if message_template == :email_reject_invalid_post && e.message.size > 6 + message_template = :email_reject_invalid_post_specified + template_args[:post_error] = e.message end if message_template @@ -81,19 +68,16 @@ module Jobs end def poll_pop3 - connection = Net::POP3.new(SiteSetting.pop3_polling_host, SiteSetting.pop3_polling_port) - connection.enable_ssl if SiteSetting.pop3_polling_ssl + pop3 = Net::POP3.new(SiteSetting.pop3_polling_host, SiteSetting.pop3_polling_port) + pop3.enable_ssl if SiteSetting.pop3_polling_ssl - connection.start(SiteSetting.pop3_polling_username, SiteSetting.pop3_polling_password) do |pop| - unless pop.mails.empty? - pop.each { |mail| handle_mail(mail) } + pop3.start(SiteSetting.pop3_polling_username, SiteSetting.pop3_polling_password) do |pop| + pop.delete_all do |p| + process_popmail(p) end - pop.finish end rescue Net::POPAuthenticationError => e Discourse.handle_job_exception(e, error_context(@args, "Signing in to poll incoming email")) - rescue Net::POPError => e - Discourse.handle_job_exception(e, error_context(@args, "Generic POP error")) end end diff --git a/app/mailers/rejection_mailer.rb b/app/mailers/rejection_mailer.rb index 1c1a7558b..2821f9abf 100644 --- a/app/mailers/rejection_mailer.rb +++ b/app/mailers/rejection_mailer.rb @@ -3,13 +3,23 @@ require_dependency 'email/message_builder' class RejectionMailer < ActionMailer::Base include Email::BuildEmailHelper - DISALLOWED_TEMPLATE_ARGS = [:to, :from, :base_url, + DISALLOWED_TEMPLATE_ARGS = [:to, + :from, + :base_url, :user_preferences_url, - :include_respond_instructions, :html_override, - :add_unsubscribe_link, :respond_instructions, - :style, :body, :post_id, :topic_id, :subject, - :template, :allow_reply_by_email, - :private_reply, :from_alias] + :include_respond_instructions, + :html_override, + :add_unsubscribe_link, + :respond_instructions, + :style, + :body, + :post_id, + :topic_id, + :subject, + :template, + :allow_reply_by_email, + :private_reply, + :from_alias] # Send an email rejection message. # diff --git a/app/mailers/subscription_mailer.rb b/app/mailers/subscription_mailer.rb new file mode 100644 index 000000000..c36228713 --- /dev/null +++ b/app/mailers/subscription_mailer.rb @@ -0,0 +1,14 @@ +require_dependency 'email/message_builder' + +class SubscriptionMailer < ActionMailer::Base + include Email::BuildEmailHelper + + def confirm_unsubscribe(user, opts={}) + unsubscribe_key = DigestUnsubscribeKey.create_key_for(user) + build_email user.email, + template: "unsubscribe_mailer", + site_title: SiteSetting.title, + site_domain_name: Discourse.current_hostname, + confirm_unsubscribe_link: "#{Discourse.base_url}/unsubscribe/#{unsubscribe_key}?from_all=true" + end +end diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb index acc2ff7ed..947735eaa 100644 --- a/app/mailers/user_notifications.rb +++ b/app/mailers/user_notifications.rb @@ -307,6 +307,7 @@ class UserNotifications < ActionMailer::Base context: context, username: username, add_unsubscribe_link: !user.staged, + add_unsubscribe_via_email_link: user.mailing_list_mode, unsubscribe_url: post.topic.unsubscribe_url, allow_reply_by_email: allow_reply_by_email, use_site_subject: use_site_subject, diff --git a/app/models/badge_grouping.rb b/app/models/badge_grouping.rb index 3512806d6..f1201e721 100644 --- a/app/models/badge_grouping.rb +++ b/app/models/badge_grouping.rb @@ -9,7 +9,7 @@ class BadgeGrouping < ActiveRecord::Base has_many :badges def system? - id && id < 5 + id && id <= 5 end def default_position=(pos) diff --git a/app/models/category.rb b/app/models/category.rb index bd5e85914..2eab836c0 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -5,6 +5,7 @@ class Category < ActiveRecord::Base include Positionable include HasCustomFields + include CategoryHashtag belongs_to :topic, dependent: :destroy belongs_to :topic_only_relative_url, @@ -75,6 +76,7 @@ class Category < ActiveRecord::Base scoped_to_permissions(guardian, [:create_post, :full]) end } + delegate :post_template, to: 'self.class' # permission is just used by serialization @@ -230,7 +232,7 @@ SQL end # only allow to use category itself id. new_record doesn't have a id. unless new_record? - match_id = /(\d+)-category/.match(self.slug) + match_id = /^(\d+)-category/.match(self.slug) errors.add(:slug, :invalid) if match_id && match_id[1] && match_id[1] != self.id.to_s end end @@ -398,8 +400,8 @@ SQL @@url_cache.clear end - def full_slug - url[3..-1].gsub("/", "-") + def full_slug(separator = "-") + url[3..-1].gsub("/", separator) end def url @@ -416,6 +418,10 @@ SQL url end + def url_with_id + self.parent_category ? "#{url}/#{self.id}" : "#{Discourse.base_uri}/c/#{self.id}-#{self.slug}" + end + # If the name changes, try and update the category definition topic too if it's # an exact match def rename_category_definition diff --git a/app/models/category_user.rb b/app/models/category_user.rb index cd7b719af..2922b6858 100644 --- a/app/models/category_user.rb +++ b/app/models/category_user.rb @@ -108,3 +108,8 @@ end # user_id :integer not null # notification_level :integer not null # +# Indexes +# +# idx_category_users_u1 (user_id,category_id,notification_level) UNIQUE +# idx_category_users_u2 (category_id,user_id,notification_level) UNIQUE +# diff --git a/app/models/concerns/category_hashtag.rb b/app/models/concerns/category_hashtag.rb new file mode 100644 index 000000000..8f2d807fb --- /dev/null +++ b/app/models/concerns/category_hashtag.rb @@ -0,0 +1,23 @@ +module CategoryHashtag + extend ActiveSupport::Concern + + SEPARATOR = ":".freeze + + class_methods do + def query_from_hashtag_slug(category_slug) + parent_slug, child_slug = category_slug.split(SEPARATOR, 2) + + category = Category.where(slug: parent_slug, parent_category_id: nil) + + if child_slug + Category.where(slug: child_slug, parent_category_id: category.pluck(:id).first).first + else + category.first + end + end + end + + def hashtag_slug + full_slug(SEPARATOR) + end +end diff --git a/app/models/directory_item.rb b/app/models/directory_item.rb index 61fc7e400..680b35c08 100644 --- a/app/models/directory_item.rb +++ b/app/models/directory_item.rb @@ -13,7 +13,12 @@ class DirectoryItem < ActiveRecord::Base end def self.period_types - @types ||= Enum.new(:all, :yearly, :monthly, :weekly, :daily, :quarterly) + @types ||= Enum.new(all: 1, + yearly: 2, + monthly: 3, + weekly: 4, + daily: 5, + quarterly: 6) end def self.refresh! @@ -70,6 +75,26 @@ class DirectoryItem < ActiveRecord::Base new_topic_type: UserAction::NEW_TOPIC, reply_type: UserAction::REPLY, regular_post_type: Post.types[:regular] + + if period_type == :all + exec_sql < d.likes_given OR + s.likes_received <> d.likes_received OR + s.topic_count <> d.topic_count OR + s.post_count <> d.post_count + ) + +SQL + end end end end diff --git a/app/models/email_token.rb b/app/models/email_token.rb index a7eb036f9..d08c09161 100644 --- a/app/models/email_token.rb +++ b/app/models/email_token.rb @@ -44,7 +44,7 @@ class EmailToken < ActiveRecord::Base def self.confirm(token) return unless valid_token_format?(token) - email_token = EmailToken.where("token = ? and expired = FALSE AND ((NOT confirmed AND created_at >= ?) OR (confirmed AND created_at >= ?))", token, EmailToken.valid_after, EmailToken.confirm_valid_after).includes(:user).first + email_token = confirmable(token) return if email_token.blank? user = email_token.user @@ -59,12 +59,17 @@ class EmailToken < ActiveRecord::Base user.save! end end + # redeem invite, if available return User.find_by(email: Email.downcase(user.email)) if Invite.redeem_from_email(user.email).present? user rescue ActiveRecord::RecordInvalid # If the user's email is already taken, just return nil (failure) end + + def self.confirmable(token) + EmailToken.where("token = ? and expired = FALSE AND ((NOT confirmed AND created_at >= ?) OR (confirmed AND created_at >= ?))", token, EmailToken.valid_after, EmailToken.confirm_valid_after).includes(:user).first + end end # == Schema Information diff --git a/app/models/embeddable_host.rb b/app/models/embeddable_host.rb index ee51105bc..98eb59a2f 100644 --- a/app/models/embeddable_host.rb +++ b/app/models/embeddable_host.rb @@ -1,5 +1,5 @@ class EmbeddableHost < ActiveRecord::Base - validates_format_of :host, :with => /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,7}(:[0-9]{1,5})?(\/.*)?\Z/i + validate :host_must_be_valid belongs_to :category before_validation do @@ -21,6 +21,14 @@ class EmbeddableHost < ActiveRecord::Base record_for_host(host).present? end + private + + def host_must_be_valid + if host !~ /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,7}(:[0-9]{1,5})?(\/.*)?\Z/i && + host !~ /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/ + errors.add(:host, I18n.t('errors.messages.invalid')) + end + end end # == Schema Information diff --git a/app/models/group.rb b/app/models/group.rb index 925c3d709..a508335a7 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -442,8 +442,11 @@ end # primary_group :boolean default(FALSE), not null # title :string(255) # grant_trust_level :integer +# incoming_email :string +# has_messages :boolean default(FALSE), not null # # Indexes # -# index_groups_on_name (name) UNIQUE +# index_groups_on_incoming_email (incoming_email) UNIQUE +# index_groups_on_name (name) UNIQUE # diff --git a/app/models/group_archived_message.rb b/app/models/group_archived_message.rb index 7c346518e..b32092cb3 100644 --- a/app/models/group_archived_message.rb +++ b/app/models/group_archived_message.rb @@ -2,3 +2,18 @@ class GroupArchivedMessage < ActiveRecord::Base belongs_to :user belongs_to :topic end + +# == Schema Information +# +# Table name: group_archived_messages +# +# id :integer not null, primary key +# group_id :integer not null +# topic_id :integer not null +# created_at :datetime +# updated_at :datetime +# +# Indexes +# +# index_group_archived_messages_on_group_id_and_topic_id (group_id,topic_id) UNIQUE +# diff --git a/app/models/group_mention.rb b/app/models/group_mention.rb index 65faeb6f0..30eb647eb 100644 --- a/app/models/group_mention.rb +++ b/app/models/group_mention.rb @@ -2,3 +2,19 @@ class GroupMention < ActiveRecord::Base belongs_to :post belongs_to :group end + +# == Schema Information +# +# Table name: group_mentions +# +# id :integer not null, primary key +# post_id :integer +# group_id :integer +# created_at :datetime +# updated_at :datetime +# +# Indexes +# +# index_group_mentions_on_group_id_and_post_id (group_id,post_id) UNIQUE +# index_group_mentions_on_post_id_and_group_id (post_id,group_id) UNIQUE +# diff --git a/app/models/group_user.rb b/app/models/group_user.rb index e3129ebc4..ddd0de85f 100644 --- a/app/models/group_user.rb +++ b/app/models/group_user.rb @@ -61,12 +61,13 @@ end # # Table name: group_users # -# id :integer not null, primary key -# group_id :integer not null -# user_id :integer not null -# created_at :datetime not null -# updated_at :datetime not null -# owner :boolean default(FALSE), not null +# id :integer not null, primary key +# group_id :integer not null +# user_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# owner :boolean default(FALSE), not null +# notification_level :integer default(3), not null # # Indexes # diff --git a/app/models/incoming_email.rb b/app/models/incoming_email.rb new file mode 100644 index 000000000..1ba3fb88c --- /dev/null +++ b/app/models/incoming_email.rb @@ -0,0 +1,32 @@ +class IncomingEmail < ActiveRecord::Base + belongs_to :user + belongs_to :topic + belongs_to :post + + scope :errored, -> { where.not(error: nil) } +end + +# == Schema Information +# +# Table name: incoming_emails +# +# id :integer not null, primary key +# user_id :integer +# topic_id :integer +# post_id :integer +# raw :text +# error :text +# message_id :text +# from_address :text +# to_addresses :text +# cc_addresses :text +# subject :text +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_incoming_emails_on_created_at (created_at) +# index_incoming_emails_on_error (error) +# index_incoming_emails_on_message_id (message_id) +# diff --git a/app/models/notification.rb b/app/models/notification.rb index f9611d2a6..eababfdc8 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -27,16 +27,34 @@ class Notification < ActiveRecord::Base end def self.types - @types ||= Enum.new( - :mentioned, :replied, :quoted, :edited, :liked, :private_message, - :invited_to_private_message, :invitee_accepted, :posted, :moved_post, - :linked, :granted_badge, :invited_to_topic, :custom, :group_mentioned - ) + @types ||= Enum.new(mentioned: 1, + replied: 2, + quoted: 3, + edited: 4, + liked: 5, + private_message: 6, + invited_to_private_message: 7, + invitee_accepted: 8, + posted: 9, + moved_post: 10, + linked: 11, + granted_badge: 12, + invited_to_topic: 13, + custom: 14, + group_mentioned: 15) end def self.mark_posts_read(user, topic_id, post_numbers) - Notification.where(user_id: user.id, topic_id: topic_id, post_number: post_numbers, read: false).update_all "read = 't'" - user.publish_notifications_state + count = Notification + .where(user_id: user.id, + topic_id: topic_id, + post_number: post_numbers, + read: false) + .update_all("read = 't'") + + user.publish_notifications_state if count > 0 + + count end def self.interesting_after(min_date) diff --git a/app/models/post.rb b/app/models/post.rb index ade5e6525..746a7a33b 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -67,26 +67,33 @@ class Post < ActiveRecord::Base delegate :username, to: :user def self.hidden_reasons - @hidden_reasons ||= Enum.new( - :flag_threshold_reached, - :flag_threshold_reached_again, - :new_user_spam_threshold_reached, - :flagged_by_tl3_user - ) + @hidden_reasons ||= Enum.new(flag_threshold_reached: 1, + flag_threshold_reached_again: 2, + new_user_spam_threshold_reached: 3, + flagged_by_tl3_user: 4) end def self.types - @types ||= Enum.new(:regular, :moderator_action, :small_action, :whisper) + @types ||= Enum.new(regular: 1, + moderator_action: 2, + small_action: 3, + whisper: 4) end def self.cook_methods - @cook_methods ||= Enum.new(:regular, :raw_html, :email) + @cook_methods ||= Enum.new(regular: 1, + raw_html: 2, + email: 3) end def self.find_by_detail(key, value) includes(:post_details).find_by(post_details: { key: key, value: value }) end + def whisper? + post_type == Post.types[:whisper] + end + def add_detail(key, value, extra = nil) post_details.build(key: key, value: value, extra: extra) end @@ -352,6 +359,10 @@ class Post < ActiveRecord::Base publish_change_to_clients!(:acted) end + def full_url + "#{Discourse.base_url}#{url}" + end + def url if topic Post.url(topic.slug, topic.id, post_number) diff --git a/app/models/post_action_type.rb b/app/models/post_action_type.rb index 2f4829c34..48935123b 100644 --- a/app/models/post_action_type.rb +++ b/app/models/post_action_type.rb @@ -17,14 +17,14 @@ class PostActionType < ActiveRecord::Base end def types - @types ||= Enum.new(:bookmark, - :like, - :off_topic, - :inappropriate, - :vote, - :notify_user, - :notify_moderators, - :spam) + @types ||= Enum.new(bookmark: 1, + like: 2, + off_topic: 3, + inappropriate: 4, + vote: 5, + notify_user: 6, + notify_moderators: 7, + spam: 8) end def auto_action_flag_types diff --git a/app/models/post_analyzer.rb b/app/models/post_analyzer.rb index 70f1bf012..cf20921cc 100644 --- a/app/models/post_analyzer.rb +++ b/app/models/post_analyzer.rb @@ -52,8 +52,11 @@ class PostAnalyzer cooked_stripped.css("code").remove cooked_stripped.css(".onebox").remove - results = cooked_stripped.to_html.scan(PrettyText.mention_matcher) - @raw_mentions = results.uniq.map { |un| un.first.downcase.gsub!(/^@/, '') } + @raw_mentions = cooked_stripped.to_html + .scan(PrettyText.mention_matcher) + .flatten + .map(&:downcase) + .uniq end # from rack ... compat with ruby 2.2 diff --git a/app/models/s3_region_site_setting.rb b/app/models/s3_region_site_setting.rb index 991813f96..24710f89a 100644 --- a/app/models/s3_region_site_setting.rb +++ b/app/models/s3_region_site_setting.rb @@ -6,7 +6,7 @@ class S3RegionSiteSetting < EnumSiteSetting end def self.values - @values ||= valid_values.sort.map { |x| { name: x, value: x } } + @values ||= valid_values.sort.map { |x| { name: "s3.regions.#{x.tr("-", "_")}", value: x } } end def self.valid_values @@ -19,7 +19,13 @@ class S3RegionSiteSetting < EnumSiteSetting 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', - 'sa-east-1'] + 'ap-northeast-2', + 'sa-east-1' + ] + end + + def self.translate_names? + true end private_class_method :valid_values diff --git a/app/models/top_topic.rb b/app/models/top_topic.rb index 4181c39bc..29f206ea3 100644 --- a/app/models/top_topic.rb +++ b/app/models/top_topic.rb @@ -44,7 +44,12 @@ class TopTopic < ActiveRecord::Base end def self.sorted_periods - ascending_periods ||= Enum.new(:daily, :weekly, :monthly, :quarterly, :yearly, :all) + ascending_periods ||= Enum.new(daily: 1, + weekly: 2, + monthly: 3, + quarterly: 4, + yearly: 5, + all: 6) end def self.sort_orders @@ -142,6 +147,15 @@ class TopTopic < ActiveRecord::Base def self.compute_top_score_for(period) + log_views_multiplier = SiteSetting.top_topics_formula_log_views_multiplier.to_f + log_views_multiplier = 2 if log_views_multiplier == 0 + + first_post_likes_multiplier = SiteSetting.top_topics_formula_first_post_likes_multiplier.to_f + first_post_likes_multiplier = 0.5 if first_post_likes_multiplier == 0 + + least_likes_per_post_multiplier = SiteSetting.top_topics_formula_least_likes_per_post_multiplier.to_f + least_likes_per_post_multiplier = 3 if least_likes_per_post_multiplier == 0 + if period == :all top_topics = "( SELECT t.like_count all_likes_count, @@ -162,11 +176,11 @@ class TopTopic < ActiveRecord::Base WITH top AS ( SELECT CASE WHEN #{time_filter} THEN 0 - ELSE log(GREATEST(#{period}_views_count, 1)) * 2 + - #{period}_op_likes_count * 0.5 + + ELSE log(GREATEST(#{period}_views_count, 1)) * #{log_views_multiplier} + + #{period}_op_likes_count * #{first_post_likes_multiplier} + CASE WHEN #{period}_likes_count > 0 AND #{period}_posts_count > 0 THEN - LEAST(#{period}_likes_count / #{period}_posts_count, 3) + LEAST(#{period}_likes_count / #{period}_posts_count, #{least_likes_per_post_multiplier}) ELSE 0 END + CASE WHEN topics.posts_count < 10 THEN diff --git a/app/models/topic.rb b/app/models/topic.rb index 5f470c509..7a370a926 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -27,7 +27,7 @@ class Topic < ActiveRecord::Base attr_accessor :allowed_user_ids def self.max_sort_order - 2**31 - 1 + @max_sort_order ||= (2 ** 31) - 1 end def featured_users @@ -208,7 +208,7 @@ class Topic < ActiveRecord::Base def cancel_auto_close_job if (auto_close_at_changed? && !auto_close_at_was.nil?) || (auto_close_user_id_changed? && auto_close_at) self.auto_close_started_at ||= Time.zone.now if auto_close_at - Jobs.cancel_scheduled_job(:close_topic, { topic_id: id }) + Jobs.cancel_scheduled_job(:close_topic, topic_id: id) end end @@ -514,6 +514,12 @@ class Topic < ActiveRecord::Base true end + def add_small_action(user, action_code, who=nil) + custom_fields = {} + custom_fields["action_code_who"] = who if who.present? + add_moderator_post(user, nil, post_type: Post.types[:small_action], action_code: action_code, custom_fields: custom_fields) + end + def add_moderator_post(user, text, opts=nil) opts ||= {} new_post = nil @@ -524,7 +530,8 @@ class Topic < ActiveRecord::Base no_bump: opts[:bump].blank?, skip_notifications: opts[:skip_notifications], topic_id: self.id, - skip_validations: true) + skip_validations: true, + custom_fields: opts[:custom_fields]) new_post = creator.create increment!(:moderator_posts_count) if new_post.persisted? @@ -543,7 +550,6 @@ class Topic < ActiveRecord::Base def change_category_to_id(category_id) return false if private_message? - return false if category.try(:contains_messages) new_category_id = category_id.to_i # if the category name is blank, reset the attribute @@ -557,11 +563,12 @@ class Topic < ActiveRecord::Base changed_to_category(cat) end - def remove_allowed_user(username) + def remove_allowed_user(removed_by, username) if user = User.find_by(username: username) topic_user = topic_allowed_users.find_by(user_id: user.id) if topic_user topic_user.destroy + add_small_action(removed_by, "removed_user", user.username) return true end end @@ -575,6 +582,8 @@ class Topic < ActiveRecord::Base # If the user exists, add them to the message. user = User.find_by_username_or_email(username_or_email) if user && topic_allowed_users.create!(user_id: user.id) + # Create a small action message + add_small_action(invited_by, "invited_user", user.username) # Notify the user they've been invited user.notifications.create(notification_type: Notification.types[:invited_to_private_message], @@ -909,8 +918,10 @@ class Topic < ActiveRecord::Base sql = < 0 - end TIME_TO_FIRST_RESPONSE_SQL ||= <<-SQL diff --git a/app/models/topic_user.rb b/app/models/topic_user.rb index c49f13a43..25298af1f 100644 --- a/app/models/topic_user.rb +++ b/app/models/topic_user.rb @@ -17,21 +17,22 @@ class TopicUser < ActiveRecord::Base # Enums def notification_levels - @notification_levels ||= Enum.new(:muted, :regular, :tracking, :watching, start: 0) + @notification_levels ||= Enum.new(muted: 0, + regular: 1, + tracking: 2, + watching: 3) end def notification_reasons - @notification_reasons ||= Enum.new( - :created_topic, - :user_changed, - :user_interacted, - :created_post, - :auto_watch, - :auto_watch_category, - :auto_mute_category, - :auto_track_category, - :plugin_changed - ) + @notification_reasons ||= Enum.new(created_topic: 1, + user_changed: 2, + user_interacted: 3, + created_post: 4, + auto_watch: 5, + auto_watch_category: 6, + auto_mute_category: 7, + auto_track_category: 8, + plugin_changed: 9) end def auto_track(user_id, topic_id, reason) diff --git a/app/models/translation_override.rb b/app/models/translation_override.rb index a433605e8..3046832e6 100644 --- a/app/models/translation_override.rb +++ b/app/models/translation_override.rb @@ -22,3 +22,19 @@ class TranslationOverride < ActiveRecord::Base end end + +# == Schema Information +# +# Table name: translation_overrides +# +# id :integer not null, primary key +# locale :string not null +# translation_key :string not null +# value :string not null +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_translation_overrides_on_locale_and_translation_key (locale,translation_key) UNIQUE +# diff --git a/app/models/user.rb b/app/models/user.rb index 0c2d99896..3d56abf4f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -169,8 +169,7 @@ class User < ActiveRecord::Base def self.suggest_name(email) return "" if email.blank? - name = email.split(/[@\+]/)[0].gsub(".", " ") - name.titleize + email[/\A[^@]+/].tr(".", " ").titleize end def self.find_by_username_or_email(username_or_email) @@ -620,7 +619,7 @@ class User < ActiveRecord::Base user_badges.select('distinct badge_id').count end - def featured_user_badges + def featured_user_badges(limit=3) user_badges .joins(:badge) .order("CASE WHEN badges.id = (SELECT MAX(ub2.badge_id) FROM user_badges ub2 @@ -630,7 +629,7 @@ class User < ActiveRecord::Base .includes(:user, :granted_by, badge: :badge_type) .where("user_badges.id in (select min(u2.id) from user_badges u2 where u2.user_id = ? group by u2.badge_id)", id) - .limit(3) + .limit(limit) end def self.count_by_signup_date(start_date, end_date) @@ -1091,6 +1090,7 @@ end # edit_history_public :boolean default(FALSE), not null # trust_level_locked :boolean default(FALSE), not null # staged :boolean default(FALSE), not null +# automatically_unpin_topics :boolean default(TRUE) # # Indexes # diff --git a/app/models/user_action.rb b/app/models/user_action.rb index 320b94d07..2fa8490eb 100644 --- a/app/models/user_action.rb +++ b/app/models/user_action.rb @@ -242,10 +242,13 @@ SQL action.save! user_id = hash[:user_id] - update_like_count(user_id, hash[:action_type], 1) topic = Topic.includes(:category).find_by(id: hash[:target_topic_id]) + if topic && !topic.private_message? + update_like_count(user_id, hash[:action_type], 1) + end + # move into Topic perhaps group_ids = nil if topic && topic.category && topic.category.read_restricted diff --git a/app/models/user_archived_message.rb b/app/models/user_archived_message.rb index b24c26b54..3f7c94e47 100644 --- a/app/models/user_archived_message.rb +++ b/app/models/user_archived_message.rb @@ -2,3 +2,18 @@ class UserArchivedMessage < ActiveRecord::Base belongs_to :user belongs_to :topic end + +# == Schema Information +# +# Table name: user_archived_messages +# +# id :integer not null, primary key +# user_id :integer not null +# topic_id :integer not null +# created_at :datetime +# updated_at :datetime +# +# Indexes +# +# index_user_archived_messages_on_user_id_and_topic_id (user_id,topic_id) UNIQUE +# diff --git a/app/models/user_badges.rb b/app/models/user_badges.rb new file mode 100644 index 000000000..187ee1bc9 --- /dev/null +++ b/app/models/user_badges.rb @@ -0,0 +1,12 @@ +# view model for user badges +class UserBadges + alias :read_attribute_for_serialization :send + + attr_accessor :user_badges, :username, :grant_count + + def initialize(opts={}) + @user_badges = opts[:user_badges] + @username = opts[:username] + @grant_count = opts[:grant_count] + end +end diff --git a/app/models/user_email_observer.rb b/app/models/user_email_observer.rb index e19c51e2f..67bcc5d6f 100644 --- a/app/models/user_email_observer.rb +++ b/app/models/user_email_observer.rb @@ -42,18 +42,24 @@ class UserEmailObserver < ActiveRecord::Observer private + EMAILABLE_POST_TYPES ||= Set.new [Post.types[:regular], Post.types[:whisper]] + def enqueue(type, delay=default_delay) - return unless notification.user.email_direct? && (notification.user.active? || notification.user.staged?) + return unless notification.user.email_direct? + return unless notification.user.active? || notification.user.staged? + return unless EMAILABLE_POST_TYPES.include? notification.post.try(:post_type) Jobs.enqueue_in(delay, - :user_email, - type: type, - user_id: notification.user_id, - notification_id: notification.id) + :user_email, + type: type, + user_id: notification.user_id, + notification_id: notification.id) end def enqueue_private(type, delay=default_delay) - return unless notification.user.email_private_messages? && (notification.user.active? || notification.user.staged?) + return unless notification.user.email_private_messages? + return unless notification.user.active? || notification.user.staged? + return unless EMAILABLE_POST_TYPES.include? notification.post.try(:post_type) Jobs.enqueue_in(delay, :user_email, diff --git a/app/models/user_history.rb b/app/models/user_history.rb index 978ca6ee3..252b5cc5b 100644 --- a/app/models/user_history.rb +++ b/app/models/user_history.rb @@ -16,35 +16,37 @@ class UserHistory < ActiveRecord::Base before_save :set_admin_only def self.actions - @actions ||= Enum.new(:delete_user, - :change_trust_level, - :change_site_setting, - :change_site_customization, - :delete_site_customization, - :change_site_text, - :checked_for_custom_avatar, # not used anymore - :notified_about_avatar, - :notified_about_sequential_replies, - :notified_about_dominating_topic, - :suspend_user, - :unsuspend_user, - :facebook_no_email, - :grant_badge, - :revoke_badge, - :auto_trust_level_change, - :check_email, - :delete_post, - :delete_topic, - :impersonate, - :roll_up, - :change_username, - :custom, - :custom_staff, - :anonymize_user, - :reviewed_post, - :change_category_settings, - :delete_category, - :create_category) + @actions ||= Enum.new(delete_user: 1, + change_trust_level: 2, + change_site_setting: 3, + change_site_customization: 4, + delete_site_customization: 5, + checked_for_custom_avatar: 6, # not used anymore + notified_about_avatar: 7, + notified_about_sequential_replies: 8, + notified_about_dominating_topic: 9, + suspend_user: 10, + unsuspend_user: 11, + facebook_no_email: 12, + grant_badge: 13, + revoke_badge: 14, + auto_trust_level_change: 15, + check_email: 16, + delete_post: 17, + delete_topic: 18, + impersonate: 19, + roll_up: 20, + change_username: 21, + custom: 22, + custom_staff: 23, + anonymize_user: 24, + reviewed_post: 25, + change_category_settings: 26, + delete_category: 27, + create_category: 28, + change_site_text: 29, + block_user: 30, + unblock_user: 31) end # Staff actions is a subset of all actions, used to audit actions taken by staff users. @@ -70,7 +72,9 @@ class UserHistory < ActiveRecord::Base :reviewed_post, :change_category_settings, :delete_category, - :create_category] + :create_category, + :block_user, + :unblock_user] end def self.staff_action_ids diff --git a/app/models/user_stat.rb b/app/models/user_stat.rb index 3eb8ab981..21590b9c8 100644 --- a/app/models/user_stat.rb +++ b/app/models/user_stat.rb @@ -30,8 +30,11 @@ class UserStat < ActiveRecord::Base (SELECT pt.user_id, COUNT(*) AS c FROM users AS u - INNER JOIN post_timings AS pt ON pt.user_id = u.id - WHERE u.last_seen_at > :seen_at + JOIN post_timings AS pt ON pt.user_id = u.id + JOIN topics t ON t.id = pt.topic_id + WHERE u.last_seen_at > :seen_at AND + t.archetype = 'regular' AND + t.deleted_at IS NULL GROUP BY pt.user_id) AS X WHERE X.user_id = user_stats.user_id AND X.c <> posts_read_count diff --git a/app/models/user_summary.rb b/app/models/user_summary.rb new file mode 100644 index 000000000..2525a82b5 --- /dev/null +++ b/app/models/user_summary.rb @@ -0,0 +1,49 @@ +# ViewModel used on Summary tab on User page + +class UserSummary + + MAX_FEATURED_BADGES = 10 + MAX_TOPICS = 6 + + alias :read_attribute_for_serialization :send + + def initialize(user, guardian) + @user = user + @guardian = guardian + end + + def topics + Topic + .secured(@guardian) + .listable_topics + .where(user: @user) + .order('like_count desc, created_at asc') + .includes(:user, :category) + .limit(MAX_TOPICS) + end + + def replies + Post + .secured(@guardian) + .where(user: @user) + .where('post_number > 1') + .where('topics.archetype <> ?', Archetype.private_message) + .order('posts.like_count desc, posts.created_at asc') + .includes(:user, {topic: :category}) + .references(:topic) + .limit(MAX_TOPICS) + end + + def badges + @user.featured_user_badges(MAX_FEATURED_BADGES) + end + + def user_stat + @user.user_stat + end + + delegate :likes_given, :likes_received, :days_visited, + :posts_read_count, :topic_count, :post_count, + to: :user_stat + +end diff --git a/app/models/username_validator.rb b/app/models/username_validator.rb index 891958b16..1ef41e17e 100644 --- a/app/models/username_validator.rb +++ b/app/models/username_validator.rb @@ -36,6 +36,8 @@ class UsernameValidator errors.empty? end + CONFUSING_EXTENSIONS ||= /\.(js|json|css|htm|html|xml|jpg|jpeg|png|gif|bmp|ico|tif|tiff|woff)$/i + private def username_exist? @@ -61,36 +63,36 @@ class UsernameValidator def username_char_valid? return unless errors.empty? - if username =~ /[^A-Za-z0-9_\.\-]/ + if username =~ /[^\w.-]/ 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] =~ /\W/ 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_]/ + 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,}/ + 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|ico)/i - self.errors << I18n.t(:'user.username.must_not_contain_confusing_suffix') + if username =~ CONFUSING_EXTENSIONS + self.errors << I18n.t(:'user.username.must_not_end_with_confusing_suffix') end end end diff --git a/app/serializers/admin_detailed_user_serializer.rb b/app/serializers/admin_detailed_user_serializer.rb index 29a93fc2c..ea7bfbabb 100644 --- a/app/serializers/admin_detailed_user_serializer.rb +++ b/app/serializers/admin_detailed_user_serializer.rb @@ -56,10 +56,6 @@ class AdminDetailedUserSerializer < AdminUserSerializer scope.can_anonymize_user?(object) end - def moderator - object.moderator - end - def topic_count object.topics.count end @@ -72,18 +68,10 @@ class AdminDetailedUserSerializer < AdminUserSerializer object.suspend_record.try(:acting_user) end - def tl3_requirements - object.tl3_requirements - end - def include_tl3_requirements? object.has_trust_level?(TrustLevel[2]) end - def user_fields - object.user_fields - end - def include_user_fields? object.user_fields.present? end diff --git a/app/serializers/admin_user_list_serializer.rb b/app/serializers/admin_user_list_serializer.rb index df4678813..ba3953e5b 100644 --- a/app/serializers/admin_user_list_serializer.rb +++ b/app/serializers/admin_user_list_serializer.rb @@ -23,7 +23,8 @@ class AdminUserListSerializer < BasicUserSerializer :suspended_till, :suspended, :blocked, - :time_read + :time_read, + :staged [:days_visited, :posts_read_count, :topics_entered, :post_count].each do |sym| attributes sym @@ -46,7 +47,7 @@ class AdminUserListSerializer < BasicUserSerializer def can_impersonate scope.can_impersonate?(object) end - + def last_emailed_at return nil if object.last_emailed_at.blank? object.last_emailed_at @@ -56,7 +57,7 @@ class AdminUserListSerializer < BasicUserSerializer return nil if object.last_emailed_at.blank? AgeWords.age_words(Time.now - object.last_emailed_at) end - + def last_seen_at return nil if object.last_seen_at.blank? object.last_seen_at @@ -71,10 +72,6 @@ class AdminUserListSerializer < BasicUserSerializer return nil if object.user_stat.time_read.blank? AgeWords.age_words(object.user_stat.time_read) end - - def created_at - object.created_at - end def created_at_age AgeWords.age_words(Time.now - object.created_at) diff --git a/app/serializers/badge_grouping_serializer.rb b/app/serializers/badge_grouping_serializer.rb index 7331a610c..79abba78c 100644 --- a/app/serializers/badge_grouping_serializer.rb +++ b/app/serializers/badge_grouping_serializer.rb @@ -1,3 +1,7 @@ class BadgeGroupingSerializer < ApplicationSerializer - attributes :id, :name, :description, :position + attributes :id, :name, :description, :position, :system + + def system + object.system? + end end diff --git a/app/serializers/basic_category_serializer.rb b/app/serializers/basic_category_serializer.rb index 2bf18d856..c229b94fa 100644 --- a/app/serializers/basic_category_serializer.rb +++ b/app/serializers/basic_category_serializer.rb @@ -11,16 +11,15 @@ class BasicCategorySerializer < ApplicationSerializer :description, :description_text, :topic_url, + :logo_url, + :background_url, :read_restricted, :permission, :parent_category_id, :notification_level, - :logo_url, - :background_url, :can_edit, :topic_template, - :has_children, - :contains_messages + :has_children def include_parent_category_id? parent_category_id diff --git a/app/serializers/incoming_email_serializer.rb b/app/serializers/incoming_email_serializer.rb new file mode 100644 index 000000000..53916e24c --- /dev/null +++ b/app/serializers/incoming_email_serializer.rb @@ -0,0 +1,32 @@ +class IncomingEmailSerializer < ApplicationSerializer + + attributes :id, + :created_at, + :from_address, + :to_addresses, + :cc_addresses, + :subject, + :error, + :post_url + + has_one :user, serializer: BasicUserSerializer, embed: :objects + + def post_url + object.post.url + end + + def include_post_url? + object.post.present? + end + + def to_addresses + return if object.to_addresses.blank? + object.to_addresses.split(";") + end + + def cc_addresses + return if object.cc_addresses.blank? + object.cc_addresses.split(";") + end + +end diff --git a/app/serializers/post_serializer.rb b/app/serializers/post_serializer.rb index 259c781d2..6e0ea158f 100644 --- a/app/serializers/post_serializer.rb +++ b/app/serializers/post_serializer.rb @@ -38,6 +38,7 @@ class PostSerializer < BasicPostSerializer :can_edit, :can_delete, :can_recover, + :can_wiki, :link_counts, :read, :user_title, @@ -62,7 +63,8 @@ class PostSerializer < BasicPostSerializer :user_custom_fields, :static_doc, :via_email, - :action_code + :action_code, + :action_code_who def initialize(object, opts) super(object, opts) @@ -129,6 +131,10 @@ class PostSerializer < BasicPostSerializer scope.can_recover_post?(object) end + def can_wiki + scope.can_wiki?(object) + end + def display_username object.user.try(:name) end @@ -313,6 +319,14 @@ class PostSerializer < BasicPostSerializer object.action_code.present? end + def action_code_who + post_custom_fields["action_code_who"] + end + + def include_action_code_who? + include_action_code? && action_code_who.present? + end + private def post_actions diff --git a/app/serializers/user_action_serializer.rb b/app/serializers/user_action_serializer.rb index d9609ff9b..965f4e61e 100644 --- a/app/serializers/user_action_serializer.rb +++ b/app/serializers/user_action_serializer.rb @@ -31,7 +31,7 @@ class UserActionSerializer < ApplicationSerializer def excerpt cooked = object.cooked || PrettyText.cook(object.raw) - PrettyText.excerpt(cooked, 300, keep_emojis: true) if cooked + PrettyText.excerpt(cooked, 300, keep_emoji_images: true) if cooked end def avatar_template diff --git a/app/serializers/user_badge_serializer.rb b/app/serializers/user_badge_serializer.rb index cb6d2341b..9a5e6d9eb 100644 --- a/app/serializers/user_badge_serializer.rb +++ b/app/serializers/user_badge_serializer.rb @@ -1,9 +1,14 @@ class UserBadgeSerializer < ApplicationSerializer + + class UserSerializer < BasicUserSerializer + attributes :name, :moderator, :admin + end + attributes :id, :granted_at, :count, :post_id, :post_number has_one :badge - has_one :user, serializer: BasicUserSerializer, root: :users - has_one :granted_by, serializer: BasicUserSerializer, root: :users + has_one :user, serializer: UserSerializer, root: :users + has_one :granted_by, serializer: UserSerializer, root: :users has_one :topic, serializer: BasicTopicSerializer def include_count? diff --git a/app/serializers/user_badges_serializer.rb b/app/serializers/user_badges_serializer.rb new file mode 100644 index 000000000..178438b17 --- /dev/null +++ b/app/serializers/user_badges_serializer.rb @@ -0,0 +1,4 @@ +class UserBadgesSerializer < ApplicationSerializer + has_many :user_badges, embed: :objects + attributes :grant_count, :username +end diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index 06abd889a..480fbd9c6 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -229,7 +229,7 @@ class UserSerializer < BasicUserSerializer end def bio_excerpt - object.user_profile.bio_excerpt(350 , { keep_newlines: true, keep_emojis: true }) + object.user_profile.bio_excerpt(350 , { keep_newlines: true, keep_emoji_images: true }) end def include_suspend_reason? diff --git a/app/serializers/user_summary_serializer.rb b/app/serializers/user_summary_serializer.rb new file mode 100644 index 000000000..4be285f4a --- /dev/null +++ b/app/serializers/user_summary_serializer.rb @@ -0,0 +1,18 @@ +class UserSummarySerializer < ApplicationSerializer + class TopicSerializer < BasicTopicSerializer + attributes :like_count, :slug, :created_at + end + + class ReplySerializer < ApplicationSerializer + attributes :post_number, :like_count, :created_at + has_one :topic, serializer: TopicSerializer + end + + has_many :topics, serializer: TopicSerializer + has_many :replies, serializer: ReplySerializer, embed: :object + has_many :badges, serializer: UserBadgeSerializer, embed: :object + + attributes :likes_given, :likes_received, :posts_read_count, + :days_visited, :topic_count, :post_count + +end diff --git a/app/services/badge_granter.rb b/app/services/badge_granter.rb index b6d964a65..7e237703d 100644 --- a/app/services/badge_granter.rb +++ b/app/services/badge_granter.rb @@ -44,7 +44,7 @@ class BadgeGranter I18n.with_locale(@user.effective_locale) do notification = @user.notifications.create( notification_type: Notification.types[:granted_badge], - data: { badge_id: @badge.id, badge_name: @badge.display_name, badge_slug: @badge.slug }.to_json) + data: { badge_id: @badge.id, badge_name: @badge.display_name, badge_slug: @badge.slug, username: @user.username}.to_json) user_badge.update_attributes notification_id: notification.id end end diff --git a/app/services/spam_rule/auto_block.rb b/app/services/spam_rule/auto_block.rb index 2a5fa9dd6..8a6cea9f3 100644 --- a/app/services/spam_rule/auto_block.rb +++ b/app/services/spam_rule/auto_block.rb @@ -18,7 +18,8 @@ class SpamRule::AutoBlock def block? @user.blocked? or - (!@user.has_trust_level?(TrustLevel[1]) and + (!@user.staged? and + !@user.has_trust_level?(TrustLevel[1]) and SiteSetting.num_flags_to_block_new_user > 0 and SiteSetting.num_users_to_block_new_user > 0 and num_spam_flags_against_user >= SiteSetting.num_flags_to_block_new_user and diff --git a/app/services/spam_rule/flag_sockpuppets.rb b/app/services/spam_rule/flag_sockpuppets.rb index b6223df72..4c96a0687 100644 --- a/app/services/spam_rule/flag_sockpuppets.rb +++ b/app/services/spam_rule/flag_sockpuppets.rb @@ -21,6 +21,8 @@ class SpamRule::FlagSockpuppets !first_post.user.staff? && !@post.user.staff? && + !first_post.user.staged? && + !@post.user.staged? && @post.user != first_post.user && @post.user.ip_address == first_post.user.ip_address && @post.user.new_user? && diff --git a/app/services/staff_action_logger.rb b/app/services/staff_action_logger.rb index c07665510..a6354ecb2 100644 --- a/app/services/staff_action_logger.rb +++ b/app/services/staff_action_logger.rb @@ -134,10 +134,8 @@ class StaffActionLogger })) end - def log_site_text_change(subject, new_text, old_text, opts={}) + def log_site_text_change(subject, new_text=nil, old_text=nil, opts={}) raise Discourse::InvalidParameters.new(:subject) unless subject.present? - raise Discourse::InvalidParameters.new(:new_text) unless new_text.present? - raise Discourse::InvalidParameters.new(:old_text) unless old_text.present? UserHistory.create( params(opts).merge({ action: UserHistory.actions[:change_site_text], subject: subject, @@ -280,6 +278,22 @@ class StaffActionLogger })) end + def log_block_user(user, opts={}) + raise Discourse::InvalidParameters.new(:user) unless user + UserHistory.create( params(opts).merge({ + action: UserHistory.actions[:block_user], + target_user_id: user.id + })) + end + + def log_unblock_user(user, opts={}) + raise Discourse::InvalidParameters.new(:user) unless user + UserHistory.create( params(opts).merge({ + action: UserHistory.actions[:unblock_user], + target_user_id: user.id + })) + end + private def params(opts=nil) diff --git a/app/services/user_blocker.rb b/app/services/user_blocker.rb index 9a8dd7493..1ff9983b7 100644 --- a/app/services/user_blocker.rb +++ b/app/services/user_blocker.rb @@ -13,11 +13,12 @@ class UserBlocker end def block - hide_posts + hide_posts unless @opts[:keep_posts] unless @user.blocked? @user.blocked = true if @user.save SystemMessage.create(@user, @opts[:message] || :blocked_by_staff) + StaffActionLogger.new(@by_user).log_block_user(@user) if @by_user end else false @@ -34,6 +35,7 @@ class UserBlocker @user.blocked = false if @user.save SystemMessage.create(@user, :unblocked) + StaffActionLogger.new(@by_user).log_unblock_user(@user) if @by_user end end diff --git a/app/views/email/_post.html.erb b/app/views/email/_post.html.erb index 48da134ce..75510c19a 100644 --- a/app/views/email/_post.html.erb +++ b/app/views/email/_post.html.erb @@ -1,4 +1,4 @@ - +
    '; - } var arr = []; + if (opts.isEmpty) { + if (opts.showDaysInNextAndPreviousMonths) { + arr.push('is-outside-current-month'); + } else { + return ''; + } + } if (opts.isDisabled) { arr.push('is-disabled'); } @@ -417,7 +427,7 @@ return; } - if (!hasClass(target.parentNode, 'is-disabled')) { + if (!hasClass(target, 'is-disabled')) { if (hasClass(target, 'pika-button') && !hasClass(target, 'is-empty')) { self.setDate(new Date(target.getAttribute('data-pika-year'), target.getAttribute('data-pika-month'), target.getAttribute('data-pika-day'))); if (opts.bound) { @@ -472,7 +482,7 @@ return; } if (hasMoment) { - date = moment(opts.field.value, opts.format); + date = moment(opts.field.value, opts.format, opts.formatStrict); date = (date && date.isValid()) ? date.toDate() : null; } else { @@ -638,9 +648,7 @@ this.setMinDate(opts.minDate); } if (opts.maxDate) { - setToStartOfDay(opts.maxDate); - opts.maxYear = opts.maxDate.getFullYear(); - opts.maxMonth = opts.maxDate.getMonth(); + this.setMaxDate(opts.maxDate); } if (isArray(opts.yearRange)) { @@ -828,6 +836,7 @@ this._o.minDate = value; this._o.minYear = value.getFullYear(); this._o.minMonth = value.getMonth(); + this.draw(); }, /** @@ -835,7 +844,11 @@ */ setMaxDate: function(value) { + setToStartOfDay(value); this._o.maxDate = value; + this._o.maxYear = value.getFullYear(); + this._o.maxMonth = value.getMonth(); + this.draw(); }, setStartRange: function(value) @@ -967,6 +980,11 @@ before += 7; } } + var previousMonth = month === 0 ? 11 : month - 1, + nextMonth = month === 11 ? 0 : month + 1, + yearOfPreviousMonth = month === 0 ? year - 1 : year, + yearOfNextMonth = month === 11 ? year + 1 : year, + daysInPreviousMonth = getDaysInMonth(yearOfPreviousMonth, previousMonth); var cells = days + before, after = cells; while(after > 7) { @@ -979,24 +997,41 @@ isSelected = isDate(this._d) ? compareDates(day, this._d) : false, isToday = compareDates(day, now), isEmpty = i < before || i >= (days + before), + dayNumber = 1 + (i - before), + monthNumber = month, + yearNumber = year, isStartRange = opts.startRange && compareDates(opts.startRange, day), isEndRange = opts.endRange && compareDates(opts.endRange, day), isInRange = opts.startRange && opts.endRange && opts.startRange < day && day < opts.endRange, isDisabled = (opts.minDate && day < opts.minDate) || (opts.maxDate && day > opts.maxDate) || (opts.disableWeekends && isWeekend(day)) || - (opts.disableDayFn && opts.disableDayFn(day)), - dayConfig = { - day: 1 + (i - before), - month: month, - year: year, + (opts.disableDayFn && opts.disableDayFn(day)); + + if (isEmpty) { + if (i < before) { + dayNumber = daysInPreviousMonth + dayNumber; + monthNumber = previousMonth; + yearNumber = yearOfPreviousMonth; + } else { + dayNumber = dayNumber - days; + monthNumber = nextMonth; + yearNumber = yearOfNextMonth; + } + } + + var dayConfig = { + day: dayNumber, + month: monthNumber, + year: yearNumber, isSelected: isSelected, isToday: isToday, isDisabled: isDisabled, isEmpty: isEmpty, isStartRange: isStartRange, isEndRange: isEndRange, - isInRange: isInRange + isInRange: isInRange, + showDaysInNextAndPreviousMonths: opts.showDaysInNextAndPreviousMonths }; row.push(renderDay(dayConfig)); diff --git a/script/import_scripts/jive.rb b/script/import_scripts/jive.rb index bb5cedd85..c64e87a66 100644 --- a/script/import_scripts/jive.rb +++ b/script/import_scripts/jive.rb @@ -6,7 +6,7 @@ require File.expand_path(File.dirname(__FILE__) + "/base.rb") class ImportScripts::Jive < ImportScripts::Base BATCH_SIZE = 1000 - CATEGORY_IDS = [2023,2003,2004,2042,2036,2029] # categories that should be skipped + CATEGORY_IDS = [2023,2003,2004,2042,2036,2029] # categories that should be imported def initialize(path) @path = path @@ -127,7 +127,7 @@ class ImportScripts::Jive < ImportScripts::Base puts "", "importing groups..." rows = [] - csv_parse("group") do |row| + csv_parse("groups") do |row| rows << {id: row.groupid, name: row.name} end @@ -142,14 +142,13 @@ class ImportScripts::Jive < ImportScripts::Base count = 0 users = [] - total = total_rows("user") + total = total_rows("users") - csv_parse("user") do |row| + csv_parse("users") do |row| id = row.userid - # append "-x" at the end of each email - email = "#{row.email}-x" + email = "#{row.email}" # fake it if row.email.blank? || row.email !~ /@/ @@ -174,7 +173,7 @@ class ImportScripts::Jive < ImportScripts::Base created_at: created_at, last_seen_at: last_seen_at, active: is_activated.to_i == 1, - approved: is_activated.to_i == 1 + approved: true } count += 1 @@ -203,7 +202,7 @@ class ImportScripts::Jive < ImportScripts::Base def import_categories rows = [] - csv_parse("community") do |row| + csv_parse("communities") do |row| next unless CATEGORY_IDS.include?(row.communityid.to_i) rows << {id: row.communityid, name: "#{row.name} (#{row.communityid})"} end @@ -279,7 +278,7 @@ class ImportScripts::Jive < ImportScripts::Base topic_map = {} thread_map = {} - csv_parse("message") do |thread| + csv_parse("messages") do |thread| next unless CATEGORY_IDS.include?(thread.containerid.to_i) @@ -288,6 +287,37 @@ class ImportScripts::Jive < ImportScripts::Base thread_map[thread.threadid] = thread.messageid + #IMAGE UPLOADER + if thread.imagecount + Dir.foreach("/var/www/discourse/script/import_scripts/jive/img/#{thread.messageid}") do |item| + next if item == '.' or item == '..' or item == '.DS_Store' + photo_path = "/var/www/discourse/script/import_scripts/jive/img/#{thread.messageid}/#{item}" + upload = create_upload(thread.userid, photo_path, File.basename(photo_path)) + if upload.persisted? + puts "Image upload is successful for #{photo_path}, new path is #{upload.url}!" + thread.body.gsub!(item,upload.url) + else + puts "Error: Image upload is not successful for #{photo_path}!" + end + end + end + + #ATTACHMENT UPLOADER + if thread.attachmentcount + Dir.foreach("/var/www/discourse/script/import_scripts/jive/attach/#{thread.messageid}") do |item| + next if item == '.' or item == '..' or item == '.DS_Store' + attach_path = "/var/www/discourse/script/import_scripts/jive/attach/#{thread.messageid}/#{item}" + upload = create_upload(thread.userid, attach_path, File.basename(attach_path)) + if upload.persisted? + puts "Attachment upload is successful for #{attach_path}, new path is #{upload.url}!" + thread.body.gsub!(item,upload.url) + thread.body << "

    #{attachment_html(upload,item)}" + else + puts "Error: Attachment upload is not successful for #{attach_path}!" + end + end + end + topic_map[thread.messageid] = { id: thread.messageid, topic_id: thread.messageid, @@ -301,7 +331,7 @@ class ImportScripts::Jive < ImportScripts::Base end end - total = total_rows("message") + total = total_rows("messages") posts = [] count = 0 @@ -310,12 +340,44 @@ class ImportScripts::Jive < ImportScripts::Base count+=1 end - csv_parse("message") do |thread| + csv_parse("messages") do |thread| # post next unless CATEGORY_IDS.include?(thread.containerid.to_i) if thread.parentmessageid + + #IMAGE UPLOADER + if thread.imagecount + Dir.foreach("/var/www/discourse/script/import_scripts/jive/img/#{thread.messageid}") do |item| + next if item == '.' or item == '..' or item == '.DS_Store' + photo_path = "/var/www/discourse/script/import_scripts/jive/img/#{thread.messageid}/#{item}" + upload = create_upload(thread.userid, photo_path, File.basename(photo_path)) + if upload.persisted? + puts "Image upload is successful for #{photo_path}, new path is #{upload.url}!" + thread.body.gsub!(item,upload.url) + else + puts "Error: Image upload is not successful for #{photo_path}!" + end + end + end + + #ATTACHMENT UPLOADER + if thread.attachmentcount + Dir.foreach("/var/www/discourse/script/import_scripts/jive/attach/#{thread.messageid}") do |item| + next if item == '.' or item == '..' or item == '.DS_Store' + attach_path = "/var/www/discourse/script/import_scripts/jive/attach/#{thread.messageid}/#{item}" + upload = create_upload(thread.userid, attach_path, File.basename(attach_path)) + if upload.persisted? + puts "Attachment upload is successful for #{attach_path}, new path is #{upload.url}!" + thread.body.gsub!(item,upload.url) + thread.body << "

    #{attachment_html(upload,item)}" + else + puts "Error: Attachment upload is not successful for #{attach_path}!" + end + end + end + row = { id: thread.messageid, topic_id: thread_map["#{thread.threadid}"], diff --git a/script/import_scripts/nabble.rb b/script/import_scripts/nabble.rb index 78e484975..ccf8d495e 100644 --- a/script/import_scripts/nabble.rb +++ b/script/import_scripts/nabble.rb @@ -1,5 +1,38 @@ require File.expand_path(File.dirname(__FILE__) + "/base.rb") require 'pg' +require_relative 'base/uploader' + +=begin + if you want to create mock users for posts made by anonymous participants, + run the following SQL prior to importing. + +-- first attribute any anonymous posts to existing users (if any) + +UPDATE node +SET owner_id = p.user_id, anonymous_name = NULL +FROM ( SELECT lower(name) AS name, user_id FROM user_ ) p +WHERE p.name = lower(node.anonymous_name) + AND owner_id IS NULL; + +-- then create mock users + +INSERT INTO user_ (email, name, joined, registered) + SELECT lower(anonymous_name) || '@dummy.com', MIN(anonymous_name), MIN(when_created), MIN(when_created) + FROM node + WHERE anonymous_name IS NOT NULL + GROUP BY lower(anonymous_name); + +-- then move these posts to the new users +-- (yes, this is the same query as the first one indeed) + +UPDATE node +SET owner_id = p.user_id, anonymous_name = NULL +FROM ( SELECT lower(name) AS name, user_id FROM user_ ) p +WHERE p.name = lower(node.anonymous_name) + AND owner_id IS NULL; + +=end + class ImportScripts::Nabble < ImportScripts::Base # CHANGE THESE BEFORE RUNNING THE IMPORTER @@ -15,6 +48,7 @@ class ImportScripts::Nabble < ImportScripts::Base @tagmap = [] @td = PG::TextDecoder::TimestampWithTimeZone.new @client = PG.connect(dbname: DB_NAME) + @uploader = ImportScripts::Uploader.new end def execute @@ -42,17 +76,45 @@ class ImportScripts::Nabble < ImportScripts::Base next if all_records_exist? :users, users.map {|u| u["user_id"].to_i} - create_users(users, total: total_count, offset: offset) do |user| + create_users(users, total: total_count, offset: offset) do |row| { - id: user["user_id"], - email: user["email"] || (SecureRandom.hex << "@domain.com"), - created_at: Time.zone.at(@td.decode(user["joined"])), - name: user["name"] + id: row["user_id"], + email: row["email"] || (SecureRandom.hex << "@domain.com"), + created_at: Time.zone.at(@td.decode(row["joined"])), + name: row["name"], + post_create_action: proc do |user| + import_avatar(user, row["user_id"]) + end } end end end + def import_avatar(user, org_id) + filename = 'avatar' + org_id.to_s + path = File.join('/tmp/nab', filename) + res = @client.exec("SELECT content FROM file_avatar WHERE name='avatar100.png' AND user_id = #{org_id} LIMIT 1") + return if res.ntuples() < 1 + + binary = res[0]['content'] + File.open(path, 'wb') { |f| + f.write(PG::Connection.unescape_bytea(binary)) + } + + upload = @uploader.create_upload(user.id, path, filename) + + if upload.persisted? + user.import_mode = false + user.create_user_avatar + user.import_mode = true + user.user_avatar.update(custom_upload_id: upload.id) + user.update(uploaded_avatar_id: upload.id) + else + Rails.logger.error("Could not persist avatar for user #{user.username}") + end + + end + def parse_email(msg) receiver = Email::Receiver.new(msg, skip_sanity_check: true) mail = Mail.read_from_string(msg) @@ -87,6 +149,8 @@ class ImportScripts::Nabble < ImportScripts::Base create_posts(topics, total: topic_count, offset: offset) do |t| raw = body_from(t) next unless raw + raw = process_content(raw) + raw = process_attachments(raw, t['node_id']) { id: t['node_id'], title: t['subject'], @@ -94,7 +158,7 @@ class ImportScripts::Nabble < ImportScripts::Base created_at: Time.zone.at(@td.decode(t["when_created"])), category: CATEGORY_ID, raw: raw, - cook_method: Post.cook_methods[:email] } + cook_method: Post.cook_methods[:regular] } end end end @@ -105,6 +169,60 @@ class ImportScripts::Nabble < ImportScripts::Base puts "Skipped #{p['node_id']}" end + def process_content(txt) + txt.gsub! /\/, '[quote="\1"]' + txt.gsub! /\<\/quote\>/, '[/quote]' + txt.gsub!(/\(.*?)\<\/raw\>/m) do |match| + c = Regexp.last_match[1].indent(4); + "\n#{c}\n" + end + + # lines starting with # are comments, not headings, insert a space to prevent markdown + txt.gsub! /\n#/m, ' #' + + # in the languagetool forum, quite a lot of XML was not marked as raw + # so we treat ... and ... as raw + + # uncomment below if you want to use this + + #txt.gsub!(/(.*?<\/rule>)/m) do |match| + # c = Regexp.last_match[2].indent(4); + # "\n #{c}\n" + #end + #txt.gsub!(/(.*?<\/category>)/m) do |match| + # c = Regexp.last_match[2].indent(4); + # "\n #{c}\n" + #end + txt + end + + def process_attachments(txt, postid) + txt.gsub!(//m) do |match| + basename = Regexp.last_match[1] + fn = File.join('/tmp/nab', basename) + + binary = @client.exec("SELECT content FROM file_node WHERE name='#{basename}' AND node_id = #{postid}")[0]['content'] + File.open(fn, 'wb') { |f| + f.write(PG::Connection.unescape_bytea(binary)) + } + upload = @uploader.create_upload(0, fn, basename) + @uploader.embedded_image_html(upload) + end + + txt.gsub!(/(.*?)<\/nabble_a>/m) do |match| + basename = Regexp.last_match[1] + fn = File.join('/tmp/nab', basename) + + binary = @client.exec("SELECT content FROM file_node WHERE name='#{basename}' AND node_id = #{postid}")[0]['content'] + File.open(fn, 'wb') { |f| + f.write(PG::Connection.unescape_bytea(binary)) + } + upload = @uploader.create_upload(0, fn, basename) + @uploader.attachment_html(upload, basename) + end + txt + end + def import_replies puts "", "creating topic replies" @@ -143,15 +261,31 @@ class ImportScripts::Nabble < ImportScripts::Base raw = body_from(p) next unless raw + raw = process_content(raw) + raw = process_attachments(raw, id) { id: id, topic_id: topic_id, user_id: user_id_from_imported_user_id(p['owner_id']) || Discourse::SYSTEM_USER_ID, created_at: Time.zone.at(@td.decode(p["when_created"])), raw: raw, - cook_method: Post.cook_methods[:email] } + cook_method: Post.cook_methods[:regular] } end end end end +class String + def indent(count, char = ' ') + gsub(/([^\n]*)(\n|$)/) do |match| + last_iteration = ($1 == "" && $2 == "") + line = "" + line << (char * count) unless last_iteration + line << $1 + line << $2 + line + end + end +end + + ImportScripts::Nabble.new.perform diff --git a/script/import_scripts/phpbb3/importers/attachment_importer.rb b/script/import_scripts/phpbb3/importers/attachment_importer.rb index e41ca7a12..37f7695c9 100644 --- a/script/import_scripts/phpbb3/importers/attachment_importer.rb +++ b/script/import_scripts/phpbb3/importers/attachment_importer.rb @@ -22,7 +22,7 @@ module ImportScripts::PhpBB3 filename = CGI.unescapeHTML(row[:real_filename]) upload = @uploader.create_upload(user_id, path, filename) - if upload.nil? || !upload.valid? + if upload.nil? || !upload.persisted? puts "Failed to upload #{path}" puts upload.errors.inspect if upload else diff --git a/script/import_scripts/phpbb3/importers/avatar_importer.rb b/script/import_scripts/phpbb3/importers/avatar_importer.rb index 3db8b7010..825bc445f 100644 --- a/script/import_scripts/phpbb3/importers/avatar_importer.rb +++ b/script/import_scripts/phpbb3/importers/avatar_importer.rb @@ -24,14 +24,15 @@ module ImportScripts::PhpBB3 filename = "avatar#{File.extname(path)}" upload = @uploader.create_upload(user.id, path, filename) - if upload.persisted? + if upload.present? && upload.persisted? user.import_mode = false user.create_user_avatar user.import_mode = true user.user_avatar.update(custom_upload_id: upload.id) user.update(uploaded_avatar_id: upload.id) else - Rails.logger.error("Could not persist avatar for user #{user.username}") + puts "Failed to upload avatar for user #{user.username}: #{path}" + puts upload.errors.inspect if upload end rescue SystemCallError => err Rails.logger.error("Could not import avatar for user #{user.username}: #{err.message}") diff --git a/script/import_scripts/phpbb3/importers/poll_importer.rb b/script/import_scripts/phpbb3/importers/poll_importer.rb index b3099f20c..e6bae5b40 100644 --- a/script/import_scripts/phpbb3/importers/poll_importer.rb +++ b/script/import_scripts/phpbb3/importers/poll_importer.rb @@ -22,6 +22,8 @@ module ImportScripts::PhpBB3 poll_text = get_poll_text(options, poll) extracted_poll = extract_default_poll(topic_id, poll_text) + return if extracted_poll.nil? + update_poll(extracted_poll, options, topic_id, poll) mapped_poll = { @@ -83,6 +85,9 @@ module ImportScripts::PhpBB3 extracted_polls.each do |poll| return poll if poll['name'] == @default_poll_name end + + puts "Failed to extract poll for topic id #{topic_id}. The poll text is:" + puts poll_text end # @param poll [ImportScripts::PhpBB3::Poll] diff --git a/script/import_scripts/phpbb3/support/smiley_processor.rb b/script/import_scripts/phpbb3/support/smiley_processor.rb index f79a24c46..1737e446c 100644 --- a/script/import_scripts/phpbb3/support/smiley_processor.rb +++ b/script/import_scripts/phpbb3/support/smiley_processor.rb @@ -65,7 +65,7 @@ module ImportScripts::PhpBB3 filename = File.basename(path) upload = @uploader.create_upload(Discourse::SYSTEM_USER_ID, path, filename) - if upload.nil? || !upload.valid? + if upload.nil? || !upload.persisted? puts "Failed to upload #{path}" puts upload.errors.inspect if upload html = nil diff --git a/script/import_scripts/vanilla_mysql.rb b/script/import_scripts/vanilla_mysql.rb index e472ce525..13237e7b8 100644 --- a/script/import_scripts/vanilla_mysql.rb +++ b/script/import_scripts/vanilla_mysql.rb @@ -7,6 +7,7 @@ class ImportScripts::VanillaSQL < ImportScripts::Base VANILLA_DB = "vanilla_mysql" TABLE_PREFIX = "GDN_" BATCH_SIZE = 1000 + CONVERT_HTML = true def initialize super @@ -29,6 +30,10 @@ class ImportScripts::VanillaSQL < ImportScripts::Base def import_users puts '', "creating users" + @user_is_deleted = false + @last_deleted_username = nil + username = nil + total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}User;").first['count'] batches(BATCH_SIZE) do |offset| @@ -42,21 +47,37 @@ class ImportScripts::VanillaSQL < ImportScripts::Base break if results.size < 1 - next if all_records_exist? :users, users.map {|u| u['UserID'].to_i} + next if all_records_exist? :users, results.map {|u| u['UserID'].to_i} create_users(results, total: total_count, offset: offset) do |user| next if user['Email'].blank? next if user['Name'].blank? + + if user['Name'] == '[Deleted User]' + # EVERY deleted user record in Vanilla has the same username: [Deleted User] + # Save our UserNameSuggester some pain: + @user_is_deleted = true + username = @last_deleted_username || user['Name'] + else + @user_is_deleted = false + username = user['Name'] + end + { id: user['UserID'], email: user['Email'], - username: user['Name'], + username: username, name: user['Name'], created_at: user['DateInserted'] == nil ? 0 : Time.zone.at(user['DateInserted']), bio_raw: user['About'], registration_ip_address: user['InsertIPAddress'], last_seen_at: user['DateLastActive'] == nil ? 0 : Time.zone.at(user['DateLastActive']), location: user['Location'], - admin: user['Admin'] == 1 } + admin: user['Admin'] == 1, + post_create_action: proc do |newuser| + if @user_is_deleted + @last_deleted_username = newuser.username + end + end } end end end @@ -169,17 +190,19 @@ class ImportScripts::VanillaSQL < ImportScripts::Base # [SAMP]...[/SAMP] raw.gsub!(/\[\/?samp\]/i, "`") - # replace all chevrons with HTML entities - # NOTE: must be done - # - AFTER all the "code" processing - # - BEFORE the "quote" processing - raw = raw.gsub(/`([^`]+)`/im) { "`" + $1.gsub("<", "\u2603") + "`" } - .gsub("<", "<") - .gsub("\u2603", "<") + unless CONVERT_HTML + # replace all chevrons with HTML entities + # NOTE: must be done + # - AFTER all the "code" processing + # - BEFORE the "quote" processing + raw = raw.gsub(/`([^`]+)`/im) { "`" + $1.gsub("<", "\u2603") + "`" } + .gsub("<", "<") + .gsub("\u2603", "<") - raw = raw.gsub(/`([^`]+)`/im) { "`" + $1.gsub(">", "\u2603") + "`" } - .gsub(">", ">") - .gsub("\u2603", ">") + raw = raw.gsub(/`([^`]+)`/im) { "`" + $1.gsub(">", "\u2603") + "`" } + .gsub(">", ">") + .gsub("\u2603", ">") + end # [URL=...]...[/URL] raw.gsub!(/\[url="?(.+?)"?\](.+)\[\/url\]/i) { "[#{$2}](#{$1})" } @@ -224,7 +247,8 @@ class ImportScripts::VanillaSQL < ImportScripts::Base end def mysql_query(sql) - @client.query(sql, cache_rows: false) + @client.query(sql) + # @client.query(sql, cache_rows: false) #segfault: cache_rows: false causes segmentation fault end end diff --git a/spec/components/concern/category_hashtag_spec.rb b/spec/components/concern/category_hashtag_spec.rb new file mode 100644 index 000000000..be60931f7 --- /dev/null +++ b/spec/components/concern/category_hashtag_spec.rb @@ -0,0 +1,26 @@ +require 'rails_helper' + +describe CategoryHashtag do + describe '#query_from_hashtag_slug' do + let(:parent_category) { Fabricate(:category) } + let(:child_category) { Fabricate(:category, parent_category: parent_category) } + + it "should return the right result for a parent category slug" do + expect(Category.query_from_hashtag_slug(parent_category.slug)) + .to eq(parent_category) + end + + it "should return the right result for a parent and child category slug" do + expect(Category.query_from_hashtag_slug("#{parent_category.slug}#{CategoryHashtag::SEPARATOR}#{child_category.slug}")) + .to eq(child_category) + end + + it "should return nil for incorrect parent category slug" do + expect(Category.query_from_hashtag_slug("random-slug")).to eq(nil) + end + + it "should return nil for incorrect parent and child category slug" do + expect(Category.query_from_hashtag_slug("random-slug#{CategoryHashtag::SEPARATOR}random-slug")).to eq(nil) + end + end +end diff --git a/spec/components/cooked_post_processor_spec.rb b/spec/components/cooked_post_processor_spec.rb index 2511927c3..89d0c51e0 100644 --- a/spec/components/cooked_post_processor_spec.rb +++ b/spec/components/cooked_post_processor_spec.rb @@ -344,7 +344,7 @@ describe CookedPostProcessor do it "uses schemaless url for uploads" do cpp.optimize_urls - expect(cpp.html).to match_html '

    Link

    Google

    ' + expect(cpp.html).to match_html '

    Link

    Google

    text.txt (20 Bytes)

    ' end context "when CDN is enabled" do @@ -352,13 +352,20 @@ describe CookedPostProcessor do it "does use schemaless CDN url for http uploads" do Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com") cpp.optimize_urls - expect(cpp.html).to match_html '

    Link

    Google

    ' + expect(cpp.html).to match_html '

    Link

    Google

    text.txt (20 Bytes)

    ' end it "does not use schemaless CDN url for https uploads" do Rails.configuration.action_controller.stubs(:asset_host).returns("https://my.cdn.com") cpp.optimize_urls - expect(cpp.html).to match_html '

    Link

    Google

    ' + expect(cpp.html).to match_html '

    Link

    Google

    text.txt (20 Bytes)

    ' + end + + it "does not use CDN when login is required" do + SiteSetting.login_required = true + Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com") + cpp.optimize_urls + expect(cpp.html).to match_html '

    Link

    Google

    text.txt (20 Bytes)

    ' end end diff --git a/spec/components/email/message_builder_spec.rb b/spec/components/email/message_builder_spec.rb index 5f8a1d763..8ff2dc511 100644 --- a/spec/components/email/message_builder_spec.rb +++ b/spec/components/email/message_builder_spec.rb @@ -181,6 +181,23 @@ describe Email::MessageBuilder do end + context "with unsubscribe_via_email_link true" do + let(:message_with_unsubscribe_via_email) { Email::MessageBuilder.new(to_address, + body: 'hello world', + add_unsubscribe_link: true, + add_unsubscribe_via_email_link: true, + unsubscribe_url: "/t/1234/unsubscribe") } + + it "can add an unsubscribe via email link" do + SiteSetting.stubs(:unsubscribe_via_email_footer).returns(true) + expect(message_with_unsubscribe_via_email.body).to match(/mailto:reply@#{Discourse.current_hostname}\?subject=unsubscribe/) + end + + it "does not add unsubscribe via email link without site setting set" do + expect(message_with_unsubscribe_via_email.body).to_not match(/mailto:reply@#{Discourse.current_hostname}\?subject=unsubscribe/) + end + end + end context "template_args" do diff --git a/spec/components/email/receiver_spec.rb b/spec/components/email/receiver_spec.rb index 796ce440d..1296cb991 100644 --- a/spec/components/email/receiver_spec.rb +++ b/spec/components/email/receiver_spec.rb @@ -1,791 +1,293 @@ -# -*- encoding : utf-8 -*- - -require 'rails_helper' -require 'email/receiver' +require "rails_helper" +require "email/receiver" describe Email::Receiver do before do - SiteSetting.reply_by_email_address = "reply+%{reply_key}@appmail.adventuretime.ooo" - SiteSetting.email_in = false - SiteSetting.title = "Discourse" + SiteSetting.email_in = true + SiteSetting.reply_by_email_address = "reply+%{reply_key}@bar.com" end - describe 'parse_body' do - def test_parse_body(mail_string) - Email::Receiver.new(nil).parse_body(Mail::Message.new mail_string) + def email(email_name) + fixture_file("emails/#{email_name}.eml") + end + + def process(email_name) + Email::Receiver.new(email(email_name)).process + end + + it "raises an EmptyEmailError when 'mail_string' is blank" do + expect { Email::Receiver.new(nil) }.to raise_error(Email::Receiver::EmptyEmailError) + expect { Email::Receiver.new("") }.to raise_error(Email::Receiver::EmptyEmailError) + end + + it "raises an NoMessageIdError when 'mail_string' is not an email" do + expect { Email::Receiver.new("wat") }.to raise_error(Email::Receiver::NoMessageIdError) + end + + it "raises an NoMessageIdError when 'mail_string' is missing the message_id" do + expect { Email::Receiver.new(email(:missing_message_id)) }.to raise_error(Email::Receiver::NoMessageIdError) + end + + it "raises an AutoGeneratedEmailError when the mail has no return path" do + expect { process(:no_return_path) }.to raise_error(Email::Receiver::AutoGeneratedEmailError) + end + + it "raises an AutoGeneratedEmailError when the mail is auto generated" do + expect { process(:auto_generated_precedence) }.to raise_error(Email::Receiver::AutoGeneratedEmailError) + expect { process(:auto_generated_header) }.to raise_error(Email::Receiver::AutoGeneratedEmailError) + end + + it "raises a NoBodyDetectedError when the body is blank" do + expect { process(:no_body) }.to raise_error(Email::Receiver::NoBodyDetectedError) + end + + it "raises an InactiveUserError when the sender is inactive" do + Fabricate(:user, email: "inactive@bar.com", active: false) + expect { process(:inactive_sender) }.to raise_error(Email::Receiver::InactiveUserError) + end + + skip "doesn't raise an InactiveUserError when the sender is staged" do + Fabricate(:user, email: "staged@bar.com", active: false, staged: true) + expect { process(:staged_sender) }.not_to raise_error + end + + it "raises a BadDestinationAddress when destinations aren't matching any of the incoming emails" do + expect { process(:bad_destinations) }.to raise_error(Email::Receiver::BadDestinationAddress) + end + + context "reply" do + + let(:reply_key) { "4f97315cc828096c9cb34c6f1a0d6fe8" } + let(:user) { Fabricate(:user, email: "discourse@bar.com") } + let(:topic) { create_topic(user: user) } + let(:post) { create_post(topic: topic, user: user) } + let!(:email_log) { Fabricate(:email_log, reply_key: reply_key, user: user, topic: topic, post: post) } + + it "raises a ReplyUserNotMatchingError when the email address isn't matching the one we sent the notification to" do + expect { process(:reply_user_not_matching) }.to raise_error(Email::Receiver::ReplyUserNotMatchingError) end - it "raises EmptyEmailError if the message is blank" do - expect { test_parse_body("") }.to raise_error(Email::Receiver::EmptyEmailError) + it "raises a TopicNotFoundError when the topic was deleted" do + topic.update_columns(deleted_at: 1.day.ago) + expect { process(:reply_user_matching) }.to raise_error(Email::Receiver::TopicNotFoundError) end - it "raises EmptyEmailError if the message is not an email" do - expect { test_parse_body("asdf" * 30) }.to raise_error(Email::Receiver::EmptyEmailError) + it "raises a TopicClosedError when the topic was closed" do + topic.update_columns(closed: true) + expect { process(:reply_user_matching) }.to raise_error(Email::Receiver::TopicClosedError) end - it "raises EmptyEmailError if there is no reply content" do - expect { test_parse_body(fixture_file("emails/no_content_reply.eml")) }.to raise_error(Email::Receiver::EmptyEmailError) + it "raises an InvalidPost when there was an error while creating the post" do + expect { process(:too_small) }.to raise_error(Email::Receiver::InvalidPost) end - skip "raises EmailUnparsableError if the headers are corrupted" do - expect { ; }.to raise_error(Email::Receiver::EmailUnparsableError) + it "raises an InvalidPost when there are too may mentions" do + SiteSetting.max_mentions_per_post = 1 + Fabricate(:user, username: "user1") + Fabricate(:user, username: "user2") + expect { process(:too_many_mentions) }.to raise_error(Email::Receiver::InvalidPost) end - it "can parse the html section" do - expect(test_parse_body(fixture_file("emails/html_only.eml"))).to eq("The EC2 instance - I've seen that there tends to be odd and " + - "unrecommended settings on the Bitnami installs that I've checked out.") + it "raises an InvalidPostAction when they aren't allowed to like a post" do + topic.update_columns(archived: true) + expect { process(:like) }.to raise_error(Email::Receiver::InvalidPostAction) end - it "supports a Dutch reply" do - expect(test_parse_body(fixture_file("emails/dutch.eml"))).to eq("Dit is een antwoord in het Nederlands.") + it "works" do + expect { process(:text_reply) }.to change { topic.posts.count } + expect(topic.posts.last.raw).to eq("This is a text reply :)") + expect(topic.posts.last.via_email).to eq(true) + expect(topic.posts.last.cooked).not_to match(/
    HTML reply ;)") + + expect { process(:hebrew_reply) }.to change { topic.posts.count } + expect(topic.posts.last.raw).to eq("שלום! מה שלומך היום?") + + expect { process(:chinese_reply) }.to change { topic.posts.count } + expect(topic.posts.last.raw).to eq("您好! 你今天好吗?") end - it "supports a Hebrew reply" do - I18n.stubs(:t).with('user_notifications.previous_discussion').returns('כלטוב') - - # The force_encoding call is only needed for the test - it is passed on fine to the cooked post - expect(test_parse_body(fixture_file("emails/hebrew.eml"))).to eq("שלום") + it "prefers text over html" do + expect { process(:text_and_html_reply) }.to change { topic.posts.count } + expect(topic.posts.last.raw).to eq("This is the *text* part.") end - it "supports a BIG5-encoded reply" do - # The force_encoding call is only needed for the test - it is passed on fine to the cooked post - expect(test_parse_body(fixture_file("emails/big5.eml"))).to eq("媽!我上電視了!") + it "removes the 'on , wrote' quoting line" do + expect { process(:on_date_contact_wrote) }.to change { topic.posts.count } + expect(topic.posts.last.raw).to eq("This is the actual reply.") end - it "removes 'via' lines if they match the site title" do - SiteSetting.title = "Discourse" - - expect(test_parse_body(fixture_file("emails/via_line.eml"))).to eq("Hello this email has content!") - end - - it "removes an 'on date wrote' quoting line" do - expect(test_parse_body(fixture_file("emails/on_wrote.eml"))).to eq("Sure, all you need to do is frobnicate the foobar and you'll be all set!") - end - - it "removes the 'Previous Discussion' marker" do - expect(test_parse_body(fixture_file("emails/previous.eml"))).to eq("This will not include the previous discussion that is present in this email.") + it "removes the 'Previous Replies' marker" do + expect { process(:previous_replies) }.to change { topic.posts.count } + expect(topic.posts.last.raw).to eq("This will not include the previous discussion that is present in this email.") end it "handles multiple paragraphs" do - expect(test_parse_body(fixture_file("emails/paragraphs.eml"))). - to eq( -"Is there any reason the *old* candy can't be be kept in silos while the new candy -is imported into *new* silos? - -The thing about candy is it stays delicious for a long time -- we can just keep -it there without worrying about it too much, imo. - -Thanks for listening." - ) + expect { process(:paragraphs) }.to change { topic.posts.count } + expect(topic.posts.last.raw).to eq("Do you like liquorice?\n\nI really like them. One could even say that I am *addicted* to liquorice. Anf if\nyou can mix it up with some anise, then I'm in heaven ;)") end - it "handles multiple paragraphs when parsing html" do - expect(test_parse_body(fixture_file("emails/html_paragraphs.eml"))). - to eq( -"Awesome! + describe 'Unsubscribing via email' do + let(:last_email) { ActionMailer::Base.deliveries.last } -Pleasure to have you here! + describe 'unsubscribe_subject.eml' do + it 'sends an email asking the user to confirm the unsubscription' do + expect { process("unsubscribe_subject") }.to change { ActionMailer::Base.deliveries.count }.by(1) + expect(last_email.to.length).to eq 1 + expect(last_email.from.length).to eq 1 + expect(last_email.from).to include "noreply@#{Discourse.current_hostname}" + expect(last_email.to).to include "discourse@bar.com" + expect(last_email.subject).to eq I18n.t(:"unsubscribe_mailer.subject_template").gsub("%{site_title}", SiteSetting.title) + end -:boom:" - ) - end + it 'does nothing unless unsubscribe_via_email is turned on' do + SiteSetting.stubs("unsubscribe_via_email").returns(false) + before_deliveries = ActionMailer::Base.deliveries.count + expect { process("unsubscribe_subject") }.to raise_error { Email::Receiver::BadDestinationAddress } + expect(before_deliveries).to eq ActionMailer::Base.deliveries.count + end + end - it "handles newlines" do - expect(test_parse_body(fixture_file("emails/newlines.eml"))). - to eq( -"This is my reply. -It is my best reply. -It will also be my *only* reply." - ) + describe 'unsubscribe_body.eml' do + it 'sends an email asking the user to confirm the unsubscription' do + expect { process("unsubscribe_body") }.to change { ActionMailer::Base.deliveries.count }.by(1) + expect(last_email.to.length).to eq 1 + expect(last_email.from.length).to eq 1 + expect(last_email.from).to include "noreply@#{Discourse.current_hostname}" + expect(last_email.to).to include "discourse@bar.com" + expect(last_email.subject).to eq I18n.t(:"unsubscribe_mailer.subject_template").gsub("%{site_title}", SiteSetting.title) + end + + it 'does nothing unless unsubscribe_via_email is turned on' do + SiteSetting.stubs(:unsubscribe_via_email).returns(false) + before_deliveries = ActionMailer::Base.deliveries.count + expect { process("unsubscribe_body") }.to raise_error { Email::Receiver::InvalidPost } + expect(before_deliveries).to eq ActionMailer::Base.deliveries.count + end + end end it "handles inline reply" do - expect(test_parse_body(fixture_file("emails/inline_reply.eml"))). - to eq( -"On Wed, Oct 8, 2014 at 11:12 AM, techAPJ wrote: - -> techAPJ -> November 28 -> -> Test reply. -> -> First paragraph. -> -> Second paragraph. -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> ------------------------------ -> Previous Replies codinghorror -> -> November 28 -> -> We're testing the latest GitHub email processing library which we are -> integrating now. -> -> https://github.com/github/email_reply_parser -> -> Go ahead and reply to this topic and I'll reply from various email clients -> for testing. -> ------------------------------ -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> -> To unsubscribe from these emails, visit your user preferences -> . -> - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog." - ) + expect { process(:inline_reply) }.to change { topic.posts.count } + expect(topic.posts.last.raw).to eq("On Tue, Jan 15, 2016 at 11:12 AM, Bar Foo wrote:\n\n> WAT November 28\n>\n> This is the previous post.\n\nAnd this is *my* reply :+1:") end - it "can retrieve the first part of multiple replies" do - expect(test_parse_body(fixture_file("emails/inline_mixed.eml"))).to eq( -"The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. - -> First paragraph. -> -> Second paragraph. - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown" - ) - + it "retrieves the first part of multiple replies" do + expect { process(:inline_mixed_replies) }.to change { topic.posts.count } + expect(topic.posts.last.raw).to eq("On Tue, Jan 15, 2016 at 11:12 AM, Bar Foo wrote:\n\n> WAT November 28\n>\n> This is the previous post.\n\nAnd this is *my* reply :+1:\n\n> This is another post.\n\nAnd this is **another** reply.") end - it "should not include previous replies" do - expect(test_parse_body(fixture_file("emails/previous_replies.eml"))).not_to match(/Previous Replies/) - end + it "strips signatures" do + expect { process(:iphone_signature) }.to change { topic.posts.count } + expect(topic.posts.last.raw).to eq("This is not the signature you're looking for.") - it "strips iPhone signature" do - expect(test_parse_body(fixture_file("emails/iphone_signature.eml"))).not_to match(/Sent from my iPhone/) - end - - it "strips regular signature" do - expect(test_parse_body(fixture_file("emails/signature.eml"))).not_to match(/Arpit/) + expect { process(:signature) }.to change { topic.posts.count } + expect(topic.posts.last.raw).to eq("You shall not sign!") end it "strips 'original message' context" do - expect(test_parse_body(fixture_file("emails/original_message_context.eml"))).not_to match(/Context/) + expect { process(:original_message) }.to change { topic.posts.count } + expect(topic.posts.last.raw).to eq("This is a reply :)") end - it "properly renders email reply from gmail web client" do - expect(test_parse_body(fixture_file("emails/gmail_web.eml"))). - to eq( -"### This is a reply from standard GMail in Google Chrome. + it "supports attachments" do + expect { process(:no_body_with_attachments) }.to change { topic.posts.count } + expect(topic.posts.last.raw).to match(//) - expect(Upload.find_by(sha1: upload_sha)).not_to eq(nil) - end - - describe 'Liking via email' do - let!(:reply_key) { '636ca428858779856c226bb145ef4fad' } - let(:replied_user_like_params) { { user: replying_user, post: post, post_action_type_id: PostActionType.types[:like] } } - let(:replied_user_like) { PostAction.find_by(replied_user_like_params) } - - describe "plus_one.eml" do - let!(:email_raw) { - fixture_file("emails/plus_one.eml") - .gsub("TO", "reply+#{reply_key}@appmail.adventuretime.ooo") - .gsub("FROM", replying_user_email) - } - - it "adds a user like to the post" do - expect { receiver.process }.to change { PostAction.count }.by(1) - expect(replied_user_like).to be_present - end - - it "does not create a duplicate like" do - PostAction.create(replied_user_like_params) - before_count = PostAction.count - expect { receiver.process }.to raise_error(Email::Receiver::InvalidPostAction) - expect(PostAction.count).to eq before_count - expect(replied_user_like).to be_present - end - - it "does not allow unauthorized happiness" do - post.trash! - before_count = PostAction.count - expect { receiver.process }.to raise_error(Email::Receiver::InvalidPostAction) - expect(PostAction.count).to eq before_count - expect(replied_user_like).to_not be_present - end - end - - describe "like.eml" do - let!(:email_raw) { - fixture_file("emails/like.eml") - .gsub("TO", "reply+#{reply_key}@appmail.adventuretime.ooo") - .gsub("FROM", replying_user_email) - } - - it 'adds a user like to the post' do - expect { receiver.process }.to change { PostAction.count }.by(1) - expect(replied_user_like).to be_present - end - end - end - end - - # === Failure Conditions === - - describe "too_short.eml" do - let!(:reply_key) { '636ca428858779856c226bb145ef4fad' } - let!(:email_raw) { - fixture_file("emails/too_short.eml") - .gsub("TO", "reply+#{reply_key}@appmail.adventuretime.ooo") - .gsub("FROM", replying_user_email) - .gsub("SUBJECT", "re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'") - } - - it "raises an InvalidPost error" do - SiteSetting.min_post_length = 5 - expect { receiver.process }.to raise_error(Email::Receiver::InvalidPost) - end - end - - describe "too_many_mentions.eml" do - let!(:reply_key) { '636ca428858779856c226bb145ef4fad' } - let!(:email_raw) { fixture_file("emails/too_many_mentions.eml") } - - it "raises an InvalidPost error" do - SiteSetting.max_mentions_per_post = 10 - (1..11).each do |i| - Fabricate(:user, username: "user#{i}").save - end - - expect { receiver.process }.to raise_error(Email::Receiver::InvalidPost) - end - end - - describe "auto response email replies should not be accepted" do - let!(:reply_key) { '636ca428858779856c226bb145ef4fad' } - let!(:email_raw) { fixture_file("emails/auto_reply.eml") } - it "raises a AutoGeneratedEmailError" do - expect { receiver.process }.to raise_error(Email::Receiver::AutoGeneratedEmailError) - end + it "ensures posts aren't dated in the future" do + expect { process(:from_the_future) }.to change { topic.posts.count } + expect(topic.posts.last.created_at).to be_within(1.minute).of(DateTime.now) end end - describe "posting reply to a closed topic" do - let(:reply_key) { raise "Override this in a lower describe block" } - let(:email_raw) { raise "Override this in a lower describe block" } - let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) } - let(:receiver) { Email::Receiver.new(email_raw) } - let(:topic) { Fabricate(:topic, closed: true) } - let(:post) { Fabricate(:post, topic: topic, post_number: 1) } - let(:replying_user_email) { 'jake@adventuretime.ooo' } - let(:replying_user) { Fabricate(:user, email: replying_user_email, trust_level: 2) } - let(:email_log) { EmailLog.new(reply_key: reply_key, - post: post, - post_id: post.id, - topic_id: topic.id, - email_type: 'user_posted', - user: replying_user, - user_id: replying_user.id, - to_address: replying_user_email - ) } + context "new message to a group" do - before do - email_log.save + let!(:group) { Fabricate(:group, incoming_email: "team@bar.com") } + + it "handles encoded display names" do + expect { process(:encoded_display_name) }.to change(Topic, :count) + + topic = Topic.last + expect(topic.private_message?).to eq(true) + expect(topic.allowed_groups).to include(group) + + user = topic.user + expect(user.staged).to eq(true) + expect(user.username).to eq("random.name") + expect(user.name).to eq("Случайная Имя") end - describe "should not create post" do - let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' } - let!(:email_raw) { fill_email(fixture_file("emails/valid_reply.eml"), replying_user_email, to) } - it "raises a TopicClosedError" do - expect { receiver.process }.to raise_error(Email::Receiver::TopicClosedError) - end - end - end - - describe "posting reply to a deleted topic" do - let(:reply_key) { raise "Override this in a lower describe block" } - let(:email_raw) { raise "Override this in a lower describe block" } - let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) } - let(:receiver) { Email::Receiver.new(email_raw) } - let(:deleted_topic) { Fabricate(:deleted_topic) } - let(:post) { Fabricate(:post, topic: deleted_topic, post_number: 1) } - let(:replying_user_email) { 'jake@adventuretime.ooo' } - let(:replying_user) { Fabricate(:user, email: replying_user_email, trust_level: 2) } - let(:email_log) { EmailLog.new(reply_key: reply_key, - post: post, - post_id: post.id, - topic_id: deleted_topic.id, - email_type: 'user_posted', - user: replying_user, - user_id: replying_user.id, - to_address: replying_user_email - ) } - - before do - email_log.save + it "invites everyone in the chain but emails configured as 'incoming' (via reply, group or category)" do + expect { process(:cc) }.to change(Topic, :count) + emails = Topic.last.allowed_users.pluck(:email) + expect(emails.size).to eq(3) + expect(emails).to include("someone@else.com", "discourse@bar.com", "wat@bar.com") end - describe "should not create post" do - let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' } - let!(:email_raw) { fill_email(fixture_file("emails/valid_reply.eml"), replying_user_email, to) } - it "raises a TopicNotFoundError" do - expect { receiver.process }.to raise_error(Email::Receiver::TopicNotFoundError) - end - end - end + it "associates email replies using both 'In-Reply-To' and 'References' headers" do + expect { process(:email_reply_1) }.to change(Topic, :count) - describe "posting reply as a inactive user" do - let(:reply_key) { raise "Override this in a lower describe block" } - let(:email_raw) { raise "Override this in a lower describe block" } - let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) } - let(:receiver) { Email::Receiver.new(email_raw) } - let(:topic) { Fabricate(:topic) } - let(:post) { Fabricate(:post, topic: topic, post_number: 1) } - let(:replying_user_email) { 'jake@adventuretime.ooo' } - let(:replying_user) { Fabricate(:user, email: replying_user_email, trust_level: 2, active: false) } - let(:email_log) { EmailLog.new(reply_key: reply_key, - post: post, - post_id: post.id, - topic_id: topic.id, - email_type: 'user_posted', - user: replying_user, - user_id: replying_user.id, - to_address: replying_user_email - ) } + topic = Topic.last - before do - email_log.save - end + expect { process(:email_reply_2) }.to change { topic.posts.count } + expect { process(:email_reply_3) }.to change { topic.posts.count } - describe "should not create post" do - let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' } - let!(:email_raw) { fill_email(fixture_file("emails/valid_reply.eml"), replying_user_email, to) } - it "raises a InactiveUserError" do - expect { receiver.process }.to raise_error(Email::Receiver::InactiveUserError) - end - end - end + # Why 5 when we only processed 3 emails? + # - 3 of them are indeed "regular" posts generated from the emails + # - The 2 others are "small action" posts automatically added because + # we invited 2 users (two@foo.com and three@foo.com) + expect(topic.posts.count).to eq(5) - describe "posting a new topic in a category" do - let(:category_destination) { raise "Override this in a lower describe block" } - let(:email_raw) { raise "Override this in a lower describe block" } - let(:allow_strangers) { false } - # ---- - let(:receiver) { Email::Receiver.new(email_raw) } - let(:user_email) { 'jake@adventuretime.ooo' } - let(:user) { Fabricate(:user, email: user_email, trust_level: 2)} - let(:category) { Fabricate(:category, email_in: category_destination, email_in_allow_strangers: allow_strangers) } - - before do - SiteSetting.email_in = true - user.save - category.save - end - - describe "too_short.eml" do - let!(:category_destination) { 'incoming+amazing@appmail.adventuretime.ooo' } - let(:email_raw) { - fixture_file("emails/too_short.eml") - .gsub("TO", category_destination) - .gsub("FROM", user_email) - .gsub("SUBJECT", "A long subject that passes the checks") - } - - it "does not create a topic if the post fails" do - before_topic_count = Topic.count - - expect { receiver.process }.to raise_error(Email::Receiver::InvalidPost) - - expect(Topic.count).to eq(before_topic_count) - end + # trash all but the 1st post + topic.ordered_posts[1..-1].each(&:trash!) + expect { process(:email_reply_4) }.to change { topic.posts.count } end end - def process_email(opts) - incoming_email = fixture_file("emails/valid_incoming.eml") - email = fill_email(incoming_email, opts[:from], opts[:to], opts[:body], opts[:subject], opts[:cc]) - Email::Receiver.new(email).process - end + context "new topic in a category" do - describe "with a valid email" do - let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" } - let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) } - let(:user_email) { "test@test.com" } - let(:user) { Fabricate(:user, email: user_email, trust_level: 2)} - let(:post) { create_post(user: user) } - - let(:valid_reply) { - reply = fixture_file("emails/valid_reply.eml") - fill_email(reply, user.email, to) - } - - let(:receiver) { Email::Receiver.new(valid_reply) } - let(:email_log) { EmailLog.new(reply_key: reply_key, - post_id: post.id, - topic_id: post.topic_id, - user_id: post.user_id, - post: post, - user: user, - email_type: 'test', - to_address: user.email - ) } - let(:reply_body) { -"I could not disagree more. I am obviously biased but adventure time is the -greatest show ever created. Everyone should watch it. - -- Jake out" } - - describe "with an email log" do - - it "extracts data" do - expect { receiver.process }.to raise_error(Email::Receiver::EmailLogNotFound) - - email_log.save! - receiver.process - - expect(receiver.body).to eq(reply_body) - expect(receiver.email_log).to eq(email_log) - end + let!(:category) { Fabricate(:category, email_in: "category@bar.com", email_in_allow_strangers: false) } + it "raises a StrangersNotAllowedError when 'email_in_allow_strangers' is disabled" do + expect { process(:stranger_not_allowed) }.to raise_error(Email::Receiver::StrangersNotAllowedError) end - end - - describe "with a valid email from a different user" do - let(:reply_key) { SecureRandom.hex(16) } - let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) } - let(:user) { Fabricate(:user, email: "test@test.com", trust_level: 2)} - let(:post) { create_post(user: user) } - let!(:email_log) { EmailLog.create(reply_key: reply_key, - post_id: post.id, - topic_id: post.topic_id, - user_id: post.user_id, - post: post, - user: user, - email_type: 'test', - to_address: user.email) } - - it "raises ReplyUserNotFoundError when user doesn't exist" do - reply = fill_email(fixture_file("emails/valid_reply.eml"), "unknown@user.com", to) - receiver = Email::Receiver.new(reply) - expect { receiver.process }.to raise_error(Email::Receiver::ReplyUserNotFoundError) + it "raises an InsufficientTrustLevelError when user's trust level isn't enough" do + SiteSetting.email_in_min_trust = 4 + Fabricate(:user, email: "insufficient@bar.com", trust_level: 3) + expect { process(:insufficient_trust_level) }.to raise_error(Email::Receiver::InsufficientTrustLevelError) end - it "raises ReplyUserNotMatchingError when user is not matching the reply key" do - another_user = Fabricate(:user, email: "existing@user.com") - reply = fill_email(fixture_file("emails/valid_reply.eml"), another_user.email, to) - receiver = Email::Receiver.new(reply) - expect { receiver.process }.to raise_error(Email::Receiver::ReplyUserNotMatchingError) - end - end + it "raises an InvalidAccess when the user is part of a readonly group" do + user = Fabricate(:user, email: "readonly@bar.com", trust_level: SiteSetting.email_in_min_trust) + group = Fabricate(:group) - describe "processes an email to a category" do - let(:to) { "some@email.com" } + group.add(user) + group.save - before do - SiteSetting.email_in = true - SiteSetting.email_in_min_trust = TrustLevel[4].to_s - end - - it "correctly can target categories" do - Fabricate(:category, email_in_allow_strangers: false, email_in: to) - - # no email in for user - expect{ - process_email(from: "cobb@dob.com", to: "invalid@address.com") - }.to raise_error(Email::Receiver::BadDestinationAddress) - - # valid target invalid user - expect{ - process_email(from: "cobb@dob.com", to: to) - }.to raise_error(Email::Receiver::UserNotFoundError) - - # untrusted - user = Fabricate(:user) - expect{ - process_email(from: user.email, to: to) - }.to raise_error(Email::Receiver::UserNotSufficientTrustLevelError) - - # trusted - user.trust_level = 4 - user.save - - process_email(from: user.email, to: to) - expect(user.posts.count).to eq(1) - - # email too short - message = nil - begin - process_email(from: user.email, to: to, body: "x", subject: "this is my new topic title") - rescue Email::Receiver::InvalidPost => e - message = e.message - end - - expect(e.message).to include("too short") - end - - - it "blocks user in restricted group from creating topic" do - to = "some@email.com" - - restricted_user = Fabricate(:user, trust_level: 4) - restricted_group = Fabricate(:group) - restricted_group.add(restricted_user) - restricted_group.save - - category = Fabricate(:category, email_in_allow_strangers: false, email_in: to) - category.set_permissions(restricted_group => :readonly) + category.set_permissions(group => :readonly) category.save - expect{ - process_email(from: restricted_user.email, to: to) - }.to raise_error(Discourse::InvalidAccess) + expect { process(:readonly) }.to raise_error(Discourse::InvalidAccess) end - end - - describe "processes an unknown email sender to category" do - let(:email_in) { "bob@bob.com" } - let(:user_email) { "#{SecureRandom.hex(32)}@foobar.com" } - let(:body) { "This is a new topic created\n\ninside a category ! :)" } - - before do - SiteSetting.email_in = true - SiteSetting.allow_staged_accounts = true - end - - it "rejects anon email" do - Fabricate(:category, email_in_allow_strangers: false, email_in: email_in) - - expect { - process_email(from: user_email, to: email_in, body: body) - }.to raise_error(Email::Receiver::UserNotFoundError) - end - - it "creates a topic for matching category" do - Fabricate(:category, email_in_allow_strangers: true, email_in: email_in) - process_email(from: user_email, to: email_in, body: body) - - staged_account = User.find_by_email(user_email) - expect(staged_account).to be - expect(staged_account.staged).to be(true) - expect(staged_account.posts.order(id: :desc).limit(1).pluck(:raw).first).to eq(body) - end - - end - - describe "processes an unknown email sender to group" do - let(:incoming_email) { "foo@bar.com" } - let(:user_email) { "#{SecureRandom.hex(32)}@foobar.com" } - let(:body) { "This is a message to\n\na group ;)" } - - before do - SiteSetting.email_in = true - SiteSetting.allow_staged_accounts = true - end - - it "creates a message for matching group" do - Fabricate(:group, incoming_email: incoming_email) - process_email(from: user_email, to: incoming_email, body: body) - - staged_account = User.find_by_email(user_email) - expect(staged_account).to be - expect(staged_account.name).to eq("Jake the Dog") - expect(staged_account.staged).to be(true) - - post = staged_account.posts.order(id: :desc).first - expect(post).to be - expect(post.raw).to eq(body) - expect(post.topic.private_message?).to eq(true) - end - - end - - describe "supports incoming mail in CC fields" do - - let(:incoming_email) { "foo@bar.com" } - let(:user_email) { "#{SecureRandom.hex(32)}@foobar.com" } - let(:body) { "This is a message to\n\na group via CC ;)" } - - before do - SiteSetting.email_in = true - SiteSetting.allow_staged_accounts = true - end - - it "creates a message for matching group" do - Fabricate(:group, incoming_email: incoming_email) - process_email(from: user_email, to: "some@email.com", body: body, cc: incoming_email) - - staged_account = User.find_by_email(user_email) - expect(staged_account).to be - expect(staged_account.staged).to be(true) - - post = staged_account.posts.order(id: :desc).first - expect(post).to be - expect(post.raw).to eq(body) - expect(post.topic.private_message?).to eq(true) + it "works" do + Fabricate(:user, email: "sufficient@bar.com", trust_level: SiteSetting.email_in_min_trust) + expect { process(:sufficient_trust_level) }.to change(Topic, :count) end end diff --git a/spec/components/enum_spec.rb b/spec/components/enum_spec.rb index 12033ae00..d7977e1ee 100644 --- a/spec/components/enum_spec.rb +++ b/spec/components/enum_spec.rb @@ -2,37 +2,51 @@ require 'rails_helper' require 'email' describe Enum do - let(:enum) { Enum.new(:jake, :finn, :princess_bubblegum, :peppermint_butler) } + let(:array_enum) { Enum.new(:jake, :finn, :princess_bubblegum, :peppermint_butler) } + let(:hash_enum) { Enum.new(jake: 1, finn: 2, princess_bubblegum: 3, peppermint_butler: 4) } describe ".[]" do it "looks up a number by symbol" do - expect(enum[:princess_bubblegum]).to eq(3) + expect(array_enum[:princess_bubblegum]).to eq(3) + expect(hash_enum[:princess_bubblegum]).to eq(3) end it "looks up a symbol by number" do - expect(enum[2]).to eq(:finn) + expect(array_enum[2]).to eq(:finn) + expect(hash_enum[2]).to eq(:finn) end end describe ".valid?" do it "returns true if a key exists" do - expect(enum.valid?(:finn)).to eq(true) + expect(array_enum.valid?(:finn)).to eq(true) + expect(hash_enum.valid?(:finn)).to eq(true) end it "returns false if a key does not exist" do - expect(enum.valid?(:obama)).to eq(false) + expect(array_enum.valid?(:obama)).to eq(false) + expect(hash_enum.valid?(:obama)).to eq(false) end end describe ".only" do it "returns only the values we ask for" do - expect(enum.only(:jake, :princess_bubblegum)).to eq({ jake: 1, princess_bubblegum: 3 }) + expect(array_enum.only(:jake, :princess_bubblegum)).to eq({ jake: 1, princess_bubblegum: 3 }) + expect(hash_enum.only(:jake, :princess_bubblegum)).to eq({ jake: 1, princess_bubblegum: 3 }) end end describe ".except" do it "returns everything but the values we ask to delete" do - expect(enum.except(:jake, :princess_bubblegum)).to eq({ finn: 2, peppermint_butler: 4 }) + expect(array_enum.except(:jake, :princess_bubblegum)).to eq({ finn: 2, peppermint_butler: 4 }) + expect(hash_enum.except(:jake, :princess_bubblegum)).to eq({ finn: 2, peppermint_butler: 4 }) + end + end + + context "allows to specify number of first enum member" do + it "number of first enum member should be 0 " do + start_enum = Enum.new(:jake, :finn, :princess_bubblegum, :peppermint_butler, start: 0) + expect(start_enum[:princess_bubblegum]).to eq(2) end end end diff --git a/spec/components/guardian_spec.rb b/spec/components/guardian_spec.rb index e5ae07942..35244c52a 100644 --- a/spec/components/guardian_spec.rb +++ b/spec/components/guardian_spec.rb @@ -7,6 +7,7 @@ describe Guardian do let(:user) { build(:user) } let(:moderator) { build(:moderator) } let(:admin) { build(:admin) } + let(:trust_level_2) { build(:user, trust_level: 2) } let(:trust_level_3) { build(:user, trust_level: 3) } let(:trust_level_4) { build(:user, trust_level: 4) } let(:another_admin) { build(:admin) } @@ -186,6 +187,22 @@ describe Guardian do expect(Guardian.new(user).can_send_private_message?(suspended_user)).to be_falsey end end + + context "author is blocked" do + before do + user.blocked = true + user.save + end + + it "returns true if target is staff" do + expect(Guardian.new(user).can_send_private_message?(admin)).to be_truthy + expect(Guardian.new(user).can_send_private_message?(moderator)).to be_truthy + end + + it "returns false if target is not staff" do + expect(Guardian.new(user).can_send_private_message?(another_user)).to be_falsey + end + end end describe 'can_reply_as_new_topic' do @@ -660,11 +677,34 @@ describe Guardian do it "doesn't allow new posts from admins" do expect(Guardian.new(admin).can_create?(Post, topic)).to be_falsey end - end + context "private message" do + let(:private_message) { Fabricate(:topic, archetype: Archetype.private_message, category_id: nil) } - end + before { user.save! } + + it "allows new posts by people included in the pm" do + private_message.topic_allowed_users.create!(user_id: user.id) + expect(Guardian.new(user).can_create?(Post, private_message)).to be_truthy + end + + it "doesn't allow new posts by people not invited to the pm" do + expect(Guardian.new(user).can_create?(Post, private_message)).to be_falsey + end + + it "allows new posts from blocked users included in the pm" do + user.update_attribute(:blocked, true) + private_message.topic_allowed_users.create!(user_id: user.id) + expect(Guardian.new(user).can_create?(Post, private_message)).to be_truthy + end + + it "doesn't allow new posts from blocked users not invited to the pm" do + user.update_attribute(:blocked, true) + expect(Guardian.new(user).can_create?(Post, private_message)).to be_falsey + end + end + end # can_create? a Post end @@ -1998,16 +2038,34 @@ describe Guardian do end describe 'can_wiki?' do + let(:post) { build(:post) } + it 'returns false for regular user' do - expect(Guardian.new(coding_horror).can_wiki?).to be_falsey + expect(Guardian.new(coding_horror).can_wiki?(post)).to be_falsey + end + + it "returns false when user does not satisfy trust level but owns the post" do + own_post = Fabricate(:post, user: trust_level_2) + expect(Guardian.new(trust_level_2).can_wiki?(own_post)).to be_falsey + end + + it "returns false when user satisfies trust level but tries to wiki someone else's post" do + SiteSetting.min_trust_to_allow_self_wiki = 2 + expect(Guardian.new(trust_level_2).can_wiki?(post)).to be_falsey + end + + it 'returns true when user satisfies trust level and owns the post' do + SiteSetting.min_trust_to_allow_self_wiki = 2 + own_post = Fabricate(:post, user: trust_level_2) + expect(Guardian.new(trust_level_2).can_wiki?(own_post)).to be_truthy end it 'returns true for admin user' do - expect(Guardian.new(admin).can_wiki?).to be_truthy + expect(Guardian.new(admin).can_wiki?(post)).to be_truthy end it 'returns true for trust_level_4 user' do - expect(Guardian.new(trust_level_4).can_wiki?).to be_truthy + expect(Guardian.new(trust_level_4).can_wiki?(post)).to be_truthy end end end diff --git a/spec/components/post_creator_spec.rb b/spec/components/post_creator_spec.rb index 2cdef4aef..01006058e 100644 --- a/spec/components/post_creator_spec.rb +++ b/spec/components/post_creator_spec.rb @@ -478,6 +478,10 @@ describe PostCreator do expect(unrelated.notifications.count).to eq(0) expect(post.topic.subtype).to eq(TopicSubtype.user_to_user) + # PMs do not increase post count or topic count + expect(post.user.user_stat.post_count).to eq(0) + expect(post.user.user_stat.topic_count).to eq(0) + # archive this message and ensure archive is cleared for all users on reply UserArchivedMessage.create(user_id: target_user2.id, topic_id: post.topic_id) @@ -490,6 +494,16 @@ describe PostCreator do expect(post.topic.topic_allowed_users.where(user_id: admin.id).count).to eq(1) expect(UserArchivedMessage.where(user_id: target_user2.id, topic_id: post.topic_id).count).to eq(0) + + # if another admin replies and is already member of the group, don't add them to topic_allowed_users + group = Fabricate(:group) + post.topic.topic_allowed_groups.create!(group: group) + admin2 = Fabricate(:admin) + group.add(admin2) + + PostCreator.create(admin2, raw: 'I am also an admin, and a mod', topic_id: post.topic_id) + + expect(post.topic.topic_allowed_users.where(user_id: admin2.id).count).to eq(0) end end @@ -595,12 +609,12 @@ describe PostCreator do describe "word_count" do it "has a word count" do - creator = PostCreator.new(user, title: 'some inspired poetry for a rainy day', raw: 'mary had a little lamb, little lamb, little lamb. mary had a little lamb') + creator = PostCreator.new(user, title: 'some inspired poetry for a rainy day', raw: 'mary had a little lamb, little lamb, little lamb. mary had a little lamb. Здравствуйте') post = creator.create - expect(post.word_count).to eq(14) + expect(post.word_count).to eq(15) post.topic.reload - expect(post.topic.word_count).to eq(14) + expect(post.topic.word_count).to eq(15) end end diff --git a/spec/components/post_revisor_spec.rb b/spec/components/post_revisor_spec.rb index 0c035fcea..4667ede9c 100644 --- a/spec/components/post_revisor_spec.rb +++ b/spec/components/post_revisor_spec.rb @@ -278,14 +278,14 @@ describe PostRevisor do describe 'with a new body' do let(:changed_by) { Fabricate(:coding_horror) } - let!(:result) { subject.revise!(changed_by, { raw: "lets update the body" }) } + let!(:result) { subject.revise!(changed_by, { raw: "lets update the body. Здравствуйте" }) } it 'returns true' do expect(result).to eq(true) end it 'updates the body' do - expect(post.raw).to eq("lets update the body") + expect(post.raw).to eq("lets update the body. Здравствуйте") end it 'sets the invalidate oneboxes attribute' do @@ -306,9 +306,9 @@ describe PostRevisor do end it "updates the word count" do - expect(post.word_count).to eq(4) + expect(post.word_count).to eq(5) post.topic.reload - expect(post.topic.word_count).to eq(4) + expect(post.topic.word_count).to eq(5) end context 'second poster posts again quickly' do diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb index f9cd29081..0cca316e1 100644 --- a/spec/components/pretty_text_spec.rb +++ b/spec/components/pretty_text_spec.rb @@ -243,9 +243,14 @@ HTML expect(PrettyText.excerpt("'", 500, text_entities: true)).to eq("'") end - it "should have an option to preserve emojis" do + it "should have an option to preserve emoji images" do emoji_image = "heart" - expect(PrettyText.excerpt(emoji_image, 100, { keep_emojis: true })).to match_html(emoji_image) + expect(PrettyText.excerpt(emoji_image, 100, { keep_emoji_images: true })).to match_html(emoji_image) + end + + it "should have an option to preserve emoji codes" do + emoji_code = ":heart:" + expect(PrettyText.excerpt(emoji_code, 100, { keep_emoji_codes: true })).to eq(":heart:") end end @@ -390,7 +395,7 @@ HTML end it "doesn't replace unicode emoji if emoji is disabled" do - SiteSetting.enable_emoji = false + SiteSetting.enable_emoji = false expect(PrettyText.cook("💣")).not_to match(/\:bomb\:/) end end diff --git a/spec/components/site_setting_extension_spec.rb b/spec/components/site_setting_extension_spec.rb index fad837036..04f362270 100644 --- a/spec/components/site_setting_extension_spec.rb +++ b/spec/components/site_setting_extension_spec.rb @@ -3,6 +3,23 @@ require_dependency 'site_setting_extension' require_dependency 'site_settings/local_process_provider' describe SiteSettingExtension do + + describe '#types' do + context "verify enum sequence" do + before do + @types = SiteSetting.types + end + + it "'string' should be at 1st position" do + expect(@types[:string]).to eq(1) + end + + it "'value_list' should be at 12th position" do + expect(@types[:value_list]).to eq(12) + end + end + end + let :provider_local do SiteSettings::LocalProcessProvider.new end diff --git a/spec/components/system_message_spec.rb b/spec/components/system_message_spec.rb index 3a0ffd8d7..dbbeff71e 100644 --- a/spec/components/system_message_spec.rb +++ b/spec/components/system_message_spec.rb @@ -4,22 +4,27 @@ require 'topic_subtype' describe SystemMessage do - let!(:admin) { Fabricate(:admin) } context 'send' do - let(:user) { Fabricate(:user) } - let(:system_message) { SystemMessage.new(user) } - let(:post) { system_message.create(:welcome_invite) } - let(:topic) { post.topic } - it 'should create a post correctly' do + + admin = Fabricate(:admin) + user = Fabricate(:user) + SiteSetting.site_contact_username = admin.username + system_message = SystemMessage.new(user) + post = system_message.create(:welcome_invite) + topic = post.topic + expect(post).to be_present expect(post).to be_valid expect(topic).to be_private_message expect(topic).to be_valid expect(topic.subtype).to eq(TopicSubtype.system_message) expect(topic.allowed_users.include?(user)).to eq(true) + expect(topic.allowed_users.include?(admin)).to eq(true) + + expect(UserArchivedMessage.where(user_id: admin.id, topic_id: topic.id).length).to eq(1) end end diff --git a/spec/components/trust_level_spec.rb b/spec/components/trust_level_spec.rb new file mode 100644 index 000000000..87f0b08cd --- /dev/null +++ b/spec/components/trust_level_spec.rb @@ -0,0 +1,19 @@ +require 'rails_helper' + +describe TrustLevel do + describe 'levels' do + context "verify enum sequence" do + before do + @levels = TrustLevel.levels + end + + it "'newuser' should be at 0 position" do + expect(@levels[:newuser]).to eq(0) + end + + it "'leader' should be at 4th position" do + expect(@levels[:leader]).to eq(4) + end + end + end +end diff --git a/spec/components/user_name_suggester_spec.rb b/spec/components/user_name_suggester_spec.rb index ca404ed4b..f6fde9899 100644 --- a/spec/components/user_name_suggester_spec.rb +++ b/spec/components/user_name_suggester_spec.rb @@ -56,15 +56,19 @@ describe UserNameSuggester do end it "removes leading character if it is not alphanumeric" do - expect(UserNameSuggester.suggest("_myname")).to eq('myname') + expect(UserNameSuggester.suggest(".myname")).to eq('myname') + end + + it "allows leading _" do + expect(UserNameSuggester.suggest("_myname")).to eq('_myname') end it "removes trailing characters if they are invalid" do expect(UserNameSuggester.suggest("myname!^$=")).to eq('myname') end - it "replace dots" do - expect(UserNameSuggester.suggest("my.name")).to eq('my_name') + it "allows dots in the middle" do + expect(UserNameSuggester.suggest("my.name")).to eq('my.name') end it "remove leading dots" do @@ -81,7 +85,7 @@ describe UserNameSuggester do end it 'should handle typical facebook usernames' do - expect(UserNameSuggester.suggest('roger.nelson.3344913')).to eq('roger_nelson_33') + expect(UserNameSuggester.suggest('roger.nelson.3344913')).to eq('roger.nelson.33') end end diff --git a/spec/controllers/category_hashtags_controller_spec.rb b/spec/controllers/category_hashtags_controller_spec.rb new file mode 100644 index 000000000..e5f151561 --- /dev/null +++ b/spec/controllers/category_hashtags_controller_spec.rb @@ -0,0 +1,46 @@ +require 'rails_helper' + +describe CategoryHashtagsController do + describe "check" do + describe "logged in" do + before do + log_in(:user) + end + + it 'only returns the categories that are valid' do + category = Fabricate(:category) + xhr :get, :check, category_slugs: [category.slug, 'none'] + + expect(JSON.parse(response.body)).to eq( + { "valid" => [{ "slug" => category.hashtag_slug, "url" => category.url_with_id }] } + ) + end + + it 'does not return restricted categories for a normal user' do + group = Fabricate(:group) + private_category = Fabricate(:private_category, group: group) + xhr :get, :check, category_slugs: [private_category.slug] + + expect(JSON.parse(response.body)).to eq({ "valid" => [] }) + end + + it 'returns restricted categories for an admin' do + admin = log_in(:admin) + group = Fabricate(:group) + group.add(admin) + private_category = Fabricate(:private_category, group: group) + xhr :get, :check, category_slugs: [private_category.slug] + + expect(JSON.parse(response.body)).to eq( + { "valid" => [{ "slug" => private_category.hashtag_slug, "url" => private_category.url_with_id }] } + ) + end + end + + describe "not logged in" do + it 'raises an exception' do + expect { xhr :get, :check, category_slugs: [] }.to raise_error(Discourse::NotLoggedIn) + end + end + end +end diff --git a/spec/controllers/email_controller_spec.rb b/spec/controllers/email_controller_spec.rb index 411d3ff14..f5c6e9180 100644 --- a/spec/controllers/email_controller_spec.rb +++ b/spec/controllers/email_controller_spec.rb @@ -39,9 +39,23 @@ describe EmailController do context '.unsubscribe' do - let(:user) { Fabricate(:user) } + let(:user) { Fabricate(:user, email_digests: true, email_direct: true, email_private_messages: true, email_always: true) } let(:key) { DigestUnsubscribeKey.create_key_for(user) } + context 'from confirm unsubscribe email' do + before do + get :unsubscribe, key: key, from_all: true + user.reload + end + + it 'unsubscribes from all emails' do + expect(user.email_digests).to eq false + expect(user.email_direct).to eq false + expect(user.email_private_messages).to eq false + expect(user.email_always).to eq false + end + end + context 'with a valid key' do before do get :unsubscribe, key: key diff --git a/spec/controllers/list_controller_spec.rb b/spec/controllers/list_controller_spec.rb index 0415074b9..e2de6b623 100644 --- a/spec/controllers/list_controller_spec.rb +++ b/spec/controllers/list_controller_spec.rb @@ -83,6 +83,26 @@ describe ListController do it { is_expected.to respond_with(:success) } end + context 'with a link that has a parent slug, slug and id in its path' do + let(:child_category) { Fabricate(:category, parent_category: category) } + + context "with valid slug" do + before do + xhr :get, :category_latest, parent_category: category.slug, category: child_category.slug, id: child_category.id + end + + it { is_expected.to redirect_to(child_category.url) } + end + + context "with invalid slug" do + before do + xhr :get, :category_latest, parent_category: 'random slug', category: 'random slug', id: child_category.id + end + + it { is_expected.to redirect_to(child_category.url) } + end + end + context 'another category exists with a number at the beginning of its name' do # One category has another category's id at the beginning of its name let!(:other_category) { Fabricate(:category, name: "#{category.id} name") } @@ -228,4 +248,30 @@ describe ListController do end + describe "categories suppression" do + let(:category_one) { Fabricate(:category) } + let(:sub_category) { Fabricate(:category, parent_category: category_one, suppress_from_homepage: true) } + let!(:topic_in_sub_category) { Fabricate(:topic, category: sub_category) } + + let(:category_two) { Fabricate(:category, suppress_from_homepage: true) } + let!(:topic_in_category_two) { Fabricate(:topic, category: category_two) } + + it "suppresses categories from the homepage" do + get SiteSetting.homepage, format: :json + expect(response).to be_success + + topic_titles = JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["title"] } + expect(topic_titles).not_to include(topic_in_sub_category.title, topic_in_category_two.title) + end + + it "does not suppress" do + get SiteSetting.homepage, category: category_one.id, format: :json + expect(response).to be_success + + topic_titles = JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["title"] } + expect(topic_titles).to include(topic_in_sub_category.title) + end + + end + end diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb index f7afc4254..14896e52d 100644 --- a/spec/controllers/posts_controller_spec.rb +++ b/spec/controllers/posts_controller_spec.rb @@ -408,7 +408,7 @@ describe PostsController do let(:post) {Fabricate(:post, user: user)} it "raises an error if the user doesn't have permission to wiki the post" do - Guardian.any_instance.expects(:can_wiki?).returns(false) + Guardian.any_instance.expects(:can_wiki?).with(post).returns(false) xhr :put, :wiki, post_id: post.id, wiki: 'true' @@ -416,7 +416,7 @@ describe PostsController do end it "can wiki a post" do - Guardian.any_instance.expects(:can_wiki?).returns(true) + Guardian.any_instance.expects(:can_wiki?).with(post).returns(true) xhr :put, :wiki, post_id: post.id, wiki: 'true' @@ -426,7 +426,7 @@ describe PostsController do it "can unwiki a post" do wikied_post = Fabricate(:post, user: user, wiki: true) - Guardian.any_instance.expects(:can_wiki?).returns(true) + Guardian.any_instance.expects(:can_wiki?).with(wikied_post).returns(true) xhr :put, :wiki, post_id: wikied_post.id, wiki: 'false' diff --git a/spec/controllers/topics_controller_spec.rb b/spec/controllers/topics_controller_spec.rb index b92a14817..69281fca3 100644 --- a/spec/controllers/topics_controller_spec.rb +++ b/spec/controllers/topics_controller_spec.rb @@ -898,18 +898,6 @@ describe TopicsController do end end - context 'when topic is in support category' do - let(:another_category) { Fabricate(:category) } - - it "cannot change the category of a topic that is in a support category" do - @topic.category = Fabricate(:category, contains_messages: true) - @topic.save! - xhr :put, :update, topic_id: @topic.id, slug: @topic.title, category_id: another_category.id - expect(response).not_to be_success - end - - end - context "allow_uncategorized_topics is false" do before do SiteSetting.stubs(:allow_uncategorized_topics).returns(false) diff --git a/spec/controllers/user_badges_controller_spec.rb b/spec/controllers/user_badges_controller_spec.rb index 3ad8840bc..0046f6140 100644 --- a/spec/controllers/user_badges_controller_spec.rb +++ b/spec/controllers/user_badges_controller_spec.rb @@ -12,9 +12,11 @@ describe UserBadgesController do xhr :get, :index, badge_id: badge.id expect(response.status).to eq(200) + parsed = JSON.parse(response.body) expect(parsed["topics"]).to eq(nil) - expect(parsed["user_badges"][0]["post_id"]).to eq(nil) + expect(parsed["badges"].length).to eq(1) + expect(parsed["user_badge_info"]["user_badges"][0]["post_id"]).to eq(nil) end end @@ -38,7 +40,7 @@ describe UserBadgesController do expect(response.status).to eq(200) parsed = JSON.parse(response.body) - expect(parsed["user_badges"].length).to eq(1) + expect(parsed["user_badge_info"]["user_badges"].length).to eq(1) end it 'includes counts when passed the aggregate argument' do diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 54f221b0f..651bf0308 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -242,11 +242,13 @@ describe UsersController do context 'when the new email address is taken' do let!(:other_user) { Fabricate(:coding_horror) } it 'raises an error' do - expect { xhr :put, :change_email, username: user.username, email: other_user.email }.to raise_error(Discourse::InvalidParameters) + xhr :put, :change_email, username: user.username, email: other_user.email + expect(response).to_not be_success end it 'raises an error if there is whitespace too' do - expect { xhr :put, :change_email, username: user.username, email: other_user.email + ' ' }.to raise_error(Discourse::InvalidParameters) + xhr :put, :change_email, username: user.username, email: other_user.email + ' ' + expect(response).to_not be_success end end @@ -254,10 +256,23 @@ describe UsersController do let!(:other_user) { Fabricate(:user, email: 'case.insensitive@gmail.com')} it 'raises an error' do - expect { xhr :put, :change_email, username: user.username, email: other_user.email.upcase }.to raise_error(Discourse::InvalidParameters) + xhr :put, :change_email, username: user.username, email: other_user.email.upcase + expect(response).to_not be_success end end + it 'raises an error when new email domain is present in email_domains_blacklist site setting' do + SiteSetting.email_domains_blacklist = "mailinator.com" + xhr :put, :change_email, username: user.username, email: "not_good@mailinator.com" + expect(response).to_not be_success + end + + it 'raises an error when new email domain is not present in email_domains_whitelist site setting' do + SiteSetting.email_domains_whitelist = "discourse.org" + xhr :put, :change_email, username: user.username, email: new_email + expect(response).to_not be_success + end + context 'success' do it 'has an email token' do @@ -326,6 +341,16 @@ describe UsersController do expect(user.auth_token).to_not eq old_token expect(user.auth_token.length).to eq 32 end + + it "doesn't invalidate the token when loading the page" do + user = Fabricate(:user, auth_token: SecureRandom.hex(16)) + email_token = user.email_tokens.create(email: user.email) + + get :password_reset, token: email_token.token + + email_token.reload + expect(email_token.confirmed).to eq(false) + end end context 'submit change' do @@ -361,6 +386,24 @@ describe UsersController do end end + describe '.confirm_email_token' do + let(:user) { Fabricate(:user) } + + it "token doesn't match any records" do + email_token = user.email_tokens.create(email: user.email) + get :confirm_email_token, token: SecureRandom.hex, format: :json + expect(response).to be_success + expect(email_token.reload.confirmed).to eq(false) + end + + it "token matches" do + email_token = user.email_tokens.create(email: user.email) + get :confirm_email_token, token: email_token.token, format: :json + expect(response).to be_success + expect(email_token.reload.confirmed).to eq(true) + end + end + describe '.admin_login' do let(:admin) { Fabricate(:admin) } let(:user) { Fabricate(:user) } @@ -1567,4 +1610,19 @@ describe UsersController do end + context '#summary' do + + it "generates summary info" do + user = Fabricate(:user) + create_post(user: user) + + xhr :get, :summary, username: user.username_lower + expect(response).to be_success + json = JSON.parse(response.body) + + expect(json["user_summary"]["topic_count"]).to eq(1) + expect(json["user_summary"]["post_count"]).to eq(1) + end + end + end diff --git a/spec/fabricators/post_fabricator.rb b/spec/fabricators/post_fabricator.rb index fcd53d92d..43c479fab 100644 --- a/spec/fabricators/post_fabricator.rb +++ b/spec/fabricators/post_fabricator.rb @@ -96,6 +96,7 @@ Fabricator(:post_with_uploads_and_links, from: :post) do Google +text.txt (20 Bytes) ' end diff --git a/spec/fixtures/emails/android_gmail.eml b/spec/fixtures/emails/android_gmail.eml deleted file mode 100644 index 21c5dde23..000000000 --- a/spec/fixtures/emails/android_gmail.eml +++ /dev/null @@ -1,177 +0,0 @@ -Delivered-To: reply@discourse.org -Return-Path: -MIME-Version: 1.0 -In-Reply-To: -References: - -Date: Fri, 28 Nov 2014 12:53:21 -0800 -Subject: Re: [Discourse Meta] [Lounge] Testing default email replies -From: Walter White -To: Discourse Meta -Content-Type: multipart/alternative; boundary=089e0149cfa485c6630508f173df - ---089e0149cfa485c6630508f173df -Content-Type: text/plain; charset=UTF-8 - -### this is a reply from Android 5 gmail - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. - -This is **bold** in Markdown. - -This is a link to http://example.com -On Nov 28, 2014 12:36 PM, "Arpit Jalan" wrote: - -> techAPJ -> November 28 -> -> Test reply. -> -> First paragraph. -> -> Second paragraph. -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> ------------------------------ -> Previous Replies codinghorror -> -> November 28 -> -> We're testing the latest GitHub email processing library which we are -> integrating now. -> -> https://github.com/github/email_reply_parser -> -> Go ahead and reply to this topic and I'll reply from various email clients -> for testing. -> ------------------------------ -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> -> To unsubscribe from these emails, visit your user preferences -> . -> - ---089e0149cfa485c6630508f173df -Content-Type: text/html; charset=UTF-8 -Content-Transfer-Encoding: quoted-printable - -

    ### this is a reply from Android 5 gmail

    -

    The quick brown fox jumps over the lazy dog. The quick brown= - fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. = -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over= - the lazy dog. The quick brown fox jumps over the lazy dog.

    -

    This is **bold** in Markdown.

    -

    This is a link to http://exam= -ple.com

    -
    On Nov 28, 2014 12:36 PM, "Arpit Jalan"= -; <info@discourse.org> wrot= -e:
    - -
    diff --git a/app/views/email/notification.html.erb b/app/views/email/notification.html.erb index 044de8352..681dc96af 100644 --- a/app/views/email/notification.html.erb +++ b/app/views/email/notification.html.erb @@ -19,7 +19,7 @@
    - + diff --git a/app/views/embed/comments.html.erb b/app/views/embed/comments.html.erb index 873229a99..29ab27723 100644 --- a/app/views/embed/comments.html.erb +++ b/app/views/embed/comments.html.erb @@ -10,17 +10,17 @@ <%- if @topic_view.posts.present? %> <%- @topic_view.posts.each do |post| %>
    - <%= link_to embed_post_date(post.created_at), post.url, title: post.created_at.strftime("%B %e, %Y %l:%M%P"), class: 'post-date', target: "_blank" %> + <%= link_to embed_post_date(post.created_at), post.full_url, title: post.created_at.strftime("%B %e, %Y %l:%M%P"), class: 'post-date', target: "_blank" %> <%- if post.reply_to_post.present? && !post.cooked.index('aside') %> - <%= link_to I18n.t('embed.in_reply_to', username: post.reply_to_post.username), post.reply_to_post.url, 'data-link-to-post' => post.reply_to_post.id.to_s, :class => 'in-reply-to' %> + <%= link_to I18n.t('embed.in_reply_to', username: post.reply_to_post.username), post.reply_to_post.full_url, 'data-link-to-post' => post.reply_to_post.id.to_s, :class => 'in-reply-to' %> <%- end %>
    - +

    - <%= post.user.username %> + <%= post.user.username %> <%- if post.user.title.present? %> <%= post.user.title %> <%- end %> @@ -29,9 +29,9 @@ <%- if post.reply_count > 0 %> <%- if post.reply_count == 1 %> - <%= link_to I18n.t('embed.replies', count: post.reply_count), post.url, 'data-link-to-post' => post.replies.first.id.to_s, :class => 'post-replies button' %> + <%= link_to I18n.t('embed.replies', count: post.reply_count), post.full_url, 'data-link-to-post' => post.replies.first.id.to_s, :class => 'post-replies button' %> <% else %> - <%= link_to I18n.t('embed.replies', count: post.reply_count), post.url, class: 'post-replies button', target: "_blank" %> + <%= link_to I18n.t('embed.replies', count: post.reply_count), post.full_url, class: 'post-replies button', target: "_blank" %> <%- end %> <%- end %>

    @@ -41,7 +41,7 @@ <% if @topic_view.topic.posts_count > 0 %>
    <%= link_to(image_tag(SiteSetting.logo_url, class: 'logo'), Discourse.base_url, target: '_blank') %> - <%= link_to(I18n.t('embed.continue'), @topic_view.posts.last.url, class: 'button', target: '_blank') %> + <%= link_to(I18n.t('embed.continue'), @topic_view.posts.last.full_url, class: 'button', target: '_blank') %> <%- if @posts_left > 0 %> <%= I18n.t('embed.more_replies', count: @posts_left) %> <%- end %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index fb45a9b9d..4d6575b77 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -46,10 +46,10 @@
    diff --git a/app/views/layouts/crawler.html.erb b/app/views/layouts/crawler.html.erb index e68f58beb..a68cfade2 100644 --- a/app/views/layouts/crawler.html.erb +++ b/app/views/layouts/crawler.html.erb @@ -27,10 +27,10 @@

    <%= t 'powered_by_html' %>

    diff --git a/app/views/static/signup.html.erb b/app/views/static/signup.html.erb new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/users/password_reset.html.erb b/app/views/users/password_reset.html.erb index c1597a541..93a5fd40c 100644 --- a/app/views/users/password_reset.html.erb +++ b/app/views/users/password_reset.html.erb @@ -48,6 +48,10 @@ <%end%> +<%- content_for(:no_ember_head) do %> + <%= script "ember_jquery" %> +<%- end %> + diff --git a/config/initializers/100-sidekiq.rb b/config/initializers/100-sidekiq.rb index bfac8028f..3940cdd4c 100644 --- a/config/initializers/100-sidekiq.rb +++ b/config/initializers/100-sidekiq.rb @@ -74,5 +74,3 @@ end Sidekiq.error_handlers.clear Sidekiq.error_handlers << SidekiqLogsterReporter.new - - diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index e3571d8ff..87dc245d4 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -402,6 +402,12 @@ ar: many: "%{count} عضوًا" other: "%{count} عضو" groups: + empty: + posts: "لا يوجد أي منشور لأي عضو من هذه المجموعة ." + members: "لا يوجد أعضاء في هذه المجموعة ." + mentions: "لا يوجد أعضاء في هذه المجموعة ." + messages: "لا توجد رسائل لهذه المجموعة." + topics: "لا يوجد أي منشور لأي عضو من هذه المجموعة ." add: "اضافة" selector_placeholder: "اضافة عضو" owner: "المالك" @@ -425,6 +431,19 @@ ar: trust_levels: title: "مستوى الثقة يمنح تلقائيا للأعضاء عندما يضيفون:" none: "لا شيء" + notifications: + watching: + title: "تحت المتابعة" + description: "سيتم إشعارك بأية رد على هذه الرسالة، وبعدد الردود الجديدة التي ستظهر ." + tracking: + title: "تتبع" + description: "سيتم إشعارك بأية رد على هذا الموضوع، وبعدد الردود الجديدة التي ستظهر." + regular: + title: "معتدل" + description: ".سيتم إشعارك إذا ذكر أحد ما @name أو رد على مشاركاتك" + muted: + title: "مكتوم" + description: "لن يتم إشعارك بأي جديد يخص هذا الموضوع ولن يظهرهذا الموضوع في قائمة المواضيع المنشورة مؤخراً." user_action_groups: '1': "الإعجابات المعطاة" '2': "الإعجابات المستلمة" @@ -434,7 +453,6 @@ ar: '6': "ردود" '7': "إشارات" '9': "إقتباسات" - '10': "تألقت" '11': "التعديلات" '12': "العناصر المرسلة" '13': "البريد الوارد" @@ -444,6 +462,7 @@ ar: all_subcategories: "جميع" no_subcategory: "لا شيء" category: "تصنيف" + category_list: "أعرض قائمة الأقسام." reorder: title: "إعادة ترتيب الفئات" title_long: "إعادة تنظيم قائمة الفئة" @@ -508,6 +527,7 @@ ar: invited_by: "مدعو بواسطة" trust_level: "مستوى الثقة" notifications: "الاشعارات" + statistics: "احصائيات" desktop_notifications: label: "إشعارات سطح المكتب" not_supported: "عذراً , الإشعارات غير مدعومة على هذا المتصفح " @@ -561,9 +581,14 @@ ar: warnings_received: "تحذيرات" messages: all: "الكل" - mine: "لي" - unread: "غير مقروء" + inbox: "البريد الوارد" + sent: "مرسلة" + archive: "الارشيف" groups: "مجموعاتي" + bulk_select: "إختيار رسائل" + move_to_inbox: "الذهاب إلى الرسائل الواردة" + failed_to_move: "فشل في نقل الرسائل المحددة (ربما يكون اتصالك ضعيفاً)" + select_all: "إختيار الكل" change_password: success: "(تم ارسال الرسالة)" in_progress: "(يتم ارسال رسالة)" @@ -1001,6 +1026,7 @@ ar: granted_badge: "تم منح الوسام" popup: mentioned: '{{username}} أشار لك في "{{topic}}" - {{site_title}}' + group_mentioned: '{{username}} ذكرك في "{{topic}}" - {{site_title}}' quoted: '{{username}} نقل لك في "{{topic}}" - {{site_title}}' replied: '{{username}} رد لك في "{{topic}}" - {{site_title}}' posted: '{{username}} شارك في "{{topic}}" - {{site_title}}' @@ -1108,6 +1134,12 @@ ar: create: 'موضوع جديد' create_long: 'كتابة موضوع جديد' private_message: 'أرسل رسالة خاصة' + archive_message: + help: 'انقل الرسالة لارشيفك.' + title: 'الارشيف' + move_to_inbox: + title: 'انقل للبريد الوارد.' + help: 'انقل الرسالة مرة آخرى للبريد الوارد.' list: 'المواضيع' new: 'موضوع جديد' unread: 'غير مقروء' @@ -1471,7 +1503,7 @@ ar: via_email: "وصلت هذه المشاركة من خلال الإيميل" whisper: "هذه المشاركة همسة خاصة للمشرفين" wiki: - about: "هذه المشاركة عبارة عن ويكي بمعنى أنها متاحة للمستخدمين العاديين لتحريرها ، " + about: "هذه المشاركة تعتبر ويكي." archetypes: save: 'حفظ الخيارات' controls: @@ -2270,6 +2302,7 @@ ar: email_templates: title: "قالب البريد الالكتروني " subject: "الموضوع" + multiple_subjects: "قالب البريد الإلكتروني هذا لديه موضوعات متعددة." body: "المحتوى" none_selected: "اختر قالب بريد الكتروني لتبدا بتعديله " revert: "اعاده التغيرات " @@ -2397,6 +2430,7 @@ ar: change_site_setting: "تغيير اعدادات الموقع" change_site_customization: "تخصيص الموقع" delete_site_customization: "حذف هذا التخصيص؟" + change_site_text: "تغيير نص الموقع." suspend_user: "حظر المستخدم" unsuspend_user: "رفع الحظر " grant_badge: "منح شارة" @@ -2616,7 +2650,7 @@ ar: unlock_trust_level: "فتح مستوى الثقة " tl3_requirements: title: "المتطلبات لمستوى الثقة 3." - table_title: "في آخر 100 يوم" + table_title: "في أخر %{time_period} أيام:" value_heading: "تصويت" requirement_heading: "متطلبات" visits: "الزيارات" @@ -2683,6 +2717,7 @@ ar: revert: "حفظ التعديلات" revert_confirm: "هل انت متاكد من انك تريد اعاده التغيرات؟ " go_back: "العودة إلى البحث" + recommended: "نوصيك بتخصيص النص التالي ليلائم احتياجاتك:" show_overriden: 'اظهر التجاوزات فقط' site_settings: show_overriden: 'تظهر فقط تجاوز' diff --git a/config/locales/client.bs_BA.yml b/config/locales/client.bs_BA.yml index c3f9d1c22..26cecdeee 100644 --- a/config/locales/client.bs_BA.yml +++ b/config/locales/client.bs_BA.yml @@ -267,7 +267,6 @@ bs_BA: members: "Članovi" posts: "Postovi" alias_levels: - title: "Who can use this group as an alias?" nobody: "Niko" only_admins: "Samo admini" mods_and_admins: "Samo moderatori i Admini" @@ -282,7 +281,6 @@ bs_BA: '6': "Odgovori" '7': "Spemenute" '9': "Citirane" - '10': "Označene" '11': "Izmjenjene" '12': "Poslato" '13': "Inbox" @@ -351,8 +349,6 @@ bs_BA: warnings_received: "warnings" messages: all: "Sve" - mine: "Moje" - unread: "Nepročitane" change_password: success: "(email poslat)" in_progress: "(šaljem email)" @@ -1603,7 +1599,6 @@ bs_BA: unlock_trust_level: "Unlock Trust Level" tl3_requirements: title: "Requirements for Trust Level 3" - table_title: "In the last 100 days:" value_heading: "Value" requirement_heading: "Requirement" visits: "Visits" @@ -1658,7 +1653,6 @@ bs_BA: text: 'Text Field' confirm: 'Confirmation' site_text: - none: "Choose a type of content to begin editing." title: 'Text Content' site_settings: show_overriden: 'Only show overridden' diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index ceeaa3490..55de3e400 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -350,7 +350,6 @@ cs: '6': "Odezva" '7': "Zmínění" '9': "Citace" - '10': "Oblíbené" '11': "Editace" '12': "Odeslané zprávy" '13': "Přijaté zprávy" @@ -470,8 +469,6 @@ cs: warnings_received: "varování" messages: all: "Všechny" - mine: "Moje" - unread: "Nepřečtené" groups: "Moje skupiny" change_password: success: "(email odeslán)" @@ -2315,7 +2312,6 @@ cs: unlock_trust_level: "Odemknout úroveň důvěryhodnosti" tl3_requirements: title: "Požadavky pro důvěryhodnost 3" - table_title: "Za posledních 100 dní:" value_heading: "Hodnota" requirement_heading: "Požadavek" visits: "Návštěv" diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index d56f8a2f6..12e4de9c2 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -322,7 +322,6 @@ da: '6': "Svar" '7': "Referencer" '9': "Citater" - '10': "Favoritter" '11': "Ændringer" '12': "Sendte indlæg" '13': "Indbakke" @@ -441,8 +440,6 @@ da: warnings_received: "advarsler" messages: all: "Alle" - mine: "Mine" - unread: "Ulæste" groups: "Mine grupper" change_password: success: "(e-mail sendt)" @@ -2239,7 +2236,6 @@ da: unlock_trust_level: "Lås tillidsniveau op" tl3_requirements: title: "Krav for fortrolighedsniveau 3" - table_title: "For de sidste 100 dage:" value_heading: "værdi" requirement_heading: "Obligatoriske" visits: "Besøg" diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index 51f1c4195..e7f3b937e 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -294,6 +294,12 @@ de: one: "1 Benutzer" other: "%{count} Benutzer" groups: + empty: + posts: "Es gibt keinen Beitrag von Mitgliedern dieser Gruppe." + members: "Diese Gruppe hat keine Mitglieder." + mentions: "Diese Gruppe wurde nicht erwähnt." + messages: "Es gibt keine Nachrichten für diese Gruppe." + topics: "Es gibt kein Thema von Mitgliedern dieser Gruppe." add: "Hinzufügen" selector_placeholder: "Mitglieder hinzufügen" owner: "Eigentümer" @@ -313,6 +319,19 @@ de: trust_levels: title: "Vertrauensstufe, die neuen Mitgliedern automatisch verliehen wird:" none: "keine" + notifications: + watching: + title: "Beobachten" + description: "Du wirst über jeden neuen Beitrag in jeder Nachricht benachrichtigt und die Anzahl der neuen Antworten wird angezeigt." + tracking: + title: "Verfolgen" + description: "Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder auf deinen Beitrag antwortet, und die Anzahl der neuen Antworten wird angezeigt." + regular: + title: "Normal" + description: "Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder dir antwortet." + muted: + title: "Stummgeschaltet" + description: "Du erhältst keine Benachrichtigungen im Zusammenhang mit Nachrichten in dieser Gruppe." user_action_groups: '1': "Abgegebene Likes" '2': "Erhaltene Likes" @@ -322,7 +341,6 @@ de: '6': "Antworten" '7': "Erwähnungen" '9': "Zitate" - '10': "Favoriten" '11': "Änderungen" '12': "Gesendete Objekte" '13': "Posteingang" @@ -332,6 +350,7 @@ de: all_subcategories: "alle" no_subcategory: "keine" category: "Kategorie" + category_list: "Kategorieliste anzeigen" reorder: title: "Kategorien neu sortieren" title_long: "Neustrukturierung der Kategorieliste" @@ -388,6 +407,7 @@ de: invited_by: "Eingeladen von" trust_level: "Vertrauensstufe" notifications: "Benachrichtigungen" + statistics: "Statistiken" desktop_notifications: label: "Desktop-Benachrichtigungen" not_supported: "Dieser Browser unterstützt leider keine Benachrichtigungen." @@ -441,9 +461,14 @@ de: warnings_received: "Warnungen" messages: all: "Alle" - mine: "Meine" - unread: "Ungelesen" + inbox: "Posteingang" + sent: "Gesendet" + archive: "Archiv" groups: "Meine Gruppen" + bulk_select: "Nachrichten auswählen" + move_to_inbox: "In Posteingang verschieben" + failed_to_move: "Die ausgewählten Nachrichten konnten nicht bewegt werden (vielleicht gibt es ein Netzwerkproblem)." + select_all: "Alle auswählen" change_password: success: "(E-Mail gesendet)" in_progress: "(E-Mail wird gesendet)" @@ -727,9 +752,9 @@ de: admin_not_allowed_from_ip_address: "Von dieser IP-Adresse darfst du dich nicht als Administrator anmelden." resend_activation_email: "Klicke hier, um eine neue Aktivierungsmail zu schicken." sent_activation_email_again: "Wir haben dir eine weitere E-Mail zur Aktivierung an {{currentEmail}} geschickt. Es könnte ein paar Minuten dauern, bis diese ankommt; sieh auch im Spam-Ordner nach." - to_continue: "Bitte einloggen" - preferences: "Du musst eingeloggt sein, um deine Benutzereinstellungen bearbeiten zu können." - forgot: "Ich kann mich an meine Konto-Daten nicht erinnern" + to_continue: "Melde dich bitte an" + preferences: "Du musst angemeldet sein, um deine Benutzereinstellungen bearbeiten zu können." + forgot: "Ich kann mich nicht an meine Zugangsdaten erinnern" google: title: "mit Google" message: "Authentifiziere mit Google (stelle sicher, dass keine Pop-up-Blocker aktiviert sind)" @@ -870,6 +895,7 @@ de: granted_badge: "Abzeichen erhalten" popup: mentioned: '{{username}} hat dich in "{{topic}}" - {{site_title}} erwähnt' + group_mentioned: '{{username}} hat dich in "{{topic}}" - {{site_title}} erwähnt' quoted: '{{username}} hat dich in "{{topic}}" - {{site_title}} zitiert' replied: '{{username}} hat dir in "{{topic}}" - {{site_title}} geantwortet' posted: '{{username}} hat in "{{topic}}" - {{site_title}} einen Beitrag verfasst' @@ -925,6 +951,7 @@ de: dismiss_read: "Blende alle ungelesenen Beiträge aus" dismiss_button: "Ignorieren..." dismiss_tooltip: "Nur die neuen Beiträge ignorieren oder Themen nicht mehr verfolgen" + also_dismiss_topics: "Diese Themen nicht mehr verfolgen, sodass mir diese nicht mehr als ungelesen angezeigt werden" dismiss_new: "Neue Themen ignorieren" toggle: "zu Massenoperationen auf Themen umschalten" actions: "Massenoperationen" @@ -971,6 +998,12 @@ de: create: 'Neues Thema' create_long: 'Ein neues Thema erstellen' private_message: 'Eine Unterhaltung beginnen' + archive_message: + help: 'Nachricht ins Archiv verschieben' + title: 'Archivieren' + move_to_inbox: + title: 'In Posteingang verschieben' + help: 'Nachricht in den Posteingang zurück verschieben' list: 'Themen' new: 'neues Thema' unread: 'ungelesen' @@ -1021,6 +1054,7 @@ de: auto_close_title: 'Automatisches Schließen' auto_close_save: "Speichern" auto_close_remove: "Dieses Thema nicht automatisch schließen" + auto_close_immediate: "Der letzte Beitrag in diesem Thema ist bereits %{hours} Stunden alt. Das Thema wird daher sofort geschlossen." progress: title: Themen-Fortschritt go_top: "Anfang" @@ -1141,6 +1175,7 @@ de: success: "Wir haben den Benutzer gebeten, sich an dieser Unterhaltung zu beteiligen." error: "Entschuldige, es gab einen Fehler beim Einladen des Benutzers." group_name: "Gruppenname" + controls: "Weitere Aktionen" invite_reply: title: 'Einladen' username_placeholder: "Benutzername" @@ -1258,7 +1293,7 @@ de: via_email: "dieser Beitrag ist per E-Mail eingetroffen" whisper: "Dieser Beitrag ist Privat für Moderatoren." wiki: - about: "dieser Beitrag ist ein Wiki; Anwärter können diesen bearbeiten" + about: "dieser Beitrag ist ein Wiki" archetypes: save: 'Speicheroptionen' controls: @@ -1445,6 +1480,7 @@ de: email_in_allow_strangers: "Akzeptiere E-Mails von nicht registrierten, anonymen Benutzern" email_in_disabled: "Das Erstellen von neuen Themen per E-Mail ist in den Website-Einstellungen deaktiviert. Um das Erstellen von neuen Themen per E-Mail zu erlauben," email_in_disabled_click: 'aktiviere die Einstellung „email in“.' + contains_messages: "Ändere diese Kategorie, sodass diese nur noch Nachrichten enthält." suppress_from_homepage: "Löse diese Kategorie von der Webseite." allow_badges_label: "Erlaube das Verleihen von Abzeichen in dieser Kategorie" edit_permissions: "Berechtigungen bearbeiten" @@ -1783,6 +1819,7 @@ de: primary_group: "Automatisch als primäre Gruppe festlegen" group_owners: Eigentümer add_owners: Eigentümer hinzufügen + incoming_email: "Benutzerdefinierte Adresse für eingehende E-Mails" incoming_email_placeholder: "E-Mail-Adresse eingeben" api: generate_master: "Master API Key erzeugen" @@ -1881,7 +1918,7 @@ de: button_title: "Einladungen versenden" customize: title: "Anpassen" - long_title: "Website-Anpassungen" + long_title: "Anpassungen" css: "CSS" header: "Kopfbereich" top: "Anfang" @@ -1915,10 +1952,11 @@ de: email_templates: title: "E-Mail-Vorlagen" subject: "Betreff" + multiple_subjects: "Diese E-Mail-Vorlage enthält mehrere Betreffzeilen." body: "Nachrichtentext" none_selected: "Wähle eine E-Mail-Vorlage aus, um diese zu bearbeiten." - revert: "Änderungen rückgängig machen" - revert_confirm: "Möchtest du wirklich die Änderungen rückgängig machen?" + revert: "Änderungen verwerfen" + revert_confirm: "Möchtest du wirklich deine Änderungen verwerfen?" css_html: title: "CSS/HTML" long_title: "CSS und HTML Anpassungen" @@ -2039,9 +2077,10 @@ de: delete_user: "Benutzer löschen" change_trust_level: "Vertrauensstufe ändern" change_username: "Benutzernamen ändern" - change_site_setting: "Website-Einstellungen ändern" - change_site_customization: "Website-Anpassungen ändern" - delete_site_customization: "Website-Anpassungen löschen" + change_site_setting: "Einstellungen ändern" + change_site_customization: "Anpassungen ändern" + delete_site_customization: "Anpassungen löschen" + change_site_text: "Text ändern" suspend_user: "Benutzer sperren" unsuspend_user: "Benutzer entsperren" grant_badge: "Abzeichen verleihen" @@ -2233,7 +2272,7 @@ de: unlock_trust_level: "Vertrauensstufe entsperren" tl3_requirements: title: "Anforderungen für Vertrauensstufe 3" - table_title: "In den letzten 100 Tagen:" + table_title: "In den letzten %{time_period} Tagen:" value_heading: "Wert" requirement_heading: "Anforderung" visits: "Aufrufe" @@ -2294,10 +2333,14 @@ de: confirm: 'Bestätigung' dropdown: "Dropdown-Liste" site_text: + description: "Du kannst jeden Text deines Forums anpassen. Benutze dazu die Suche:" + search: "Suche nach dem Text, den du bearbeiten möchtest" title: 'Textinhalt' edit: 'bearbeiten' - revert: "Änderungen rückgängig machen" + revert: "Änderungen verwerfen" + revert_confirm: "Möchtest du wirklich deine Änderungen verwerfen?" go_back: "Zurück zur Suche" + recommended: "Wir empfehlen, dass du den folgenden Text an deine Bedürfnisse anpasst:" show_overriden: 'Nur geänderterte Texte anzeigen' site_settings: show_overriden: 'Nur geänderterte Einstellungen anzeigen' diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 80f7a596f..b5091b084 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1,9 +1,9 @@ # encoding: utf-8 +# # This file contains content for the client portion of Discourse, sent out # to the Javascript app. # -# To work with us on translations, see: -# https://www.transifex.com/projects/p/discourse-org/ +# To work with us on translations, see: https://www.transifex.com/projects/p/discourse-org/ # # This is a "source" file, which is used by Transifex to get translations for other languages. # After this file is changed, it needs to be pushed by a maintainer to Transifex: @@ -12,8 +12,7 @@ # # Read more here: https://meta.discourse.org/t/contribute-a-translation-to-discourse/14882 # -# To validate this YAML file after you change it, please paste it into -# http://yamllint.com/ +# To validate this YAML file after you change it, please paste it into http://yamllint.com/ en: js: @@ -121,6 +120,8 @@ en: action_codes: split_topic: "split this topic %{when}" + invited_user: "invited %{who} %{when}" + removed_user: "removed %{who} %{when}" autoclosed: enabled: 'closed %{when}' disabled: 'opened %{when}' @@ -144,6 +145,20 @@ en: emails_are_disabled: "All outgoing email has been globally disabled by an administrator. No email notifications of any kind will be sent." + s3: + regions: + us_east_1: "US East (N. Virginia)" + us_west_1: "US West (N. California)" + us_west_2: "US West (Oregon)" + us_gov_west_1: "AWS GovCloud (US)" + eu_west_1: "EU (Ireland)" + eu_central_1: "EU (Frankfurt)" + ap_southeast_1: "Asia Pacific (Singapore)" + ap_southeast_2: "Asia Pacific (Sydney)" + ap_northeast_1: "Asia Pacific (Tokyo)" + ap_northeast_2: "Asia Pacific (Seoul)" + sa_east_1: "South America (Sao Paulo)" + edit: 'edit the title and category of this topic' not_implemented: "That feature hasn't been implemented yet, sorry!" no_value: "No" @@ -688,6 +703,23 @@ en: ok: "Your password looks good." instructions: "At least %{count} characters." + summary: + title: "Summary" + stats: "Stats" + topic_count: "Topics Created" + post_count: "Posts Created" + likes_given: "Likes Given" + likes_received: "Likes Received" + days_visited: "Days Visited" + posts_read_count: "Posts Read" + top_replies: "Top Replies" + top_topics: "Top Topics" + top_badges: "Top Badges" + more_topics: "More Topics" + more_replies: "More Replies" + more_badges: "More Badges" + + associated_accounts: "Logins" ip_address: title: "Last IP Address" @@ -733,7 +765,7 @@ en: logout: "You were logged out." refresh: "Refresh" read_only_mode: - enabled: "Read-only mode is enabled. You can continue to browse the site but interactions may not work." + enabled: "This site is in read only mode. Please continue to browse, but replying, likes, and other actions are disabled for now." login_disabled: "Login is disabled while the site is in read only mode." too_few_topics_and_posts_notice: "Let's get this discussion started! There are currently %{currentTopics} / %{requiredTopics} topics and %{currentPosts} / %{requiredPosts} posts. New visitors need some conversations to read and respond to." too_few_topics_notice: "Let's get this discussion started! There are currently %{currentTopics} / %{requiredTopics} topics. New visitors need some conversations to read and respond to." @@ -862,7 +894,7 @@ en: alt: 'Alt' composer: - emoji: "Emoji :smile:" + emoji: "Emoji :)" more_emoji: "more..." options: "Options" whisper: "whisper" @@ -1629,7 +1661,6 @@ 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.' - contains_messages: "Change this category to only contain messages." suppress_from_homepage: "Suppress this category from the homepage." allow_badges_label: "Allow badges to be awarded in this category" edit_permissions: "Edit Permissions" @@ -2190,14 +2221,17 @@ en: email: - title: "Email" + title: "Emails" settings: "Settings" - all: "All" + templates: "Templates" + preview_digest: "Preview Digest" sending_test: "Sending test Email..." error: "ERROR - %{server_error}" test_error: "There was a problem sending the test email. Please double-check your mail settings, verify that your host is not blocking mail connections, and try again." sent: "Sent" skipped: "Skipped" + received: "Received" + rejected: "Rejected" sent_at: "Sent At" time: "Time" user: "User" @@ -2207,7 +2241,6 @@ en: send_test: "Send Test Email" sent_test: "sent!" delivery_method: "Delivery Method" - preview_digest: "Preview Digest" preview_digest_desc: "Preview the content of the digest emails sent to inactive users." refresh: "Refresh" format: "Format" @@ -2216,6 +2249,19 @@ en: last_seen_user: "Last Seen User:" reply_key: "Reply Key" skipped_reason: "Skip Reason" + incoming_emails: + from_address: "From" + to_addresses: "To" + cc_addresses: "Cc" + subject: "Subject" + error: "Error" + none: "No incoming emails found." + filters: + from_placeholder: "from@example.com" + to_placeholder: "to@example.com" + cc_placeholder: "cc@example.com" + subject_placeholder: "Subject..." + error_placeholder: "Error" logs: none: "No logs found." filters: @@ -2280,6 +2326,8 @@ en: change_category_settings: "change category settings" delete_category: "delete category" create_category: "create category" + block_user: "block user" + unblock_user: "unblock user" screened_emails: title: "Screened Emails" description: "When someone tries to create a new account, the following email addresses will be checked and the registration will be blocked, or some other action performed." @@ -2384,6 +2432,7 @@ en: moderator: "Moderator?" admin: "Admin?" blocked: "Blocked?" + staged: "Staged?" show_admin_profile: "Admin" edit_title: "Edit Title" save_title: "Save Title" @@ -2448,9 +2497,12 @@ en: deactivate_failed: "There was a problem deactivating the user." unblock_failed: 'There was a problem unblocking the user.' block_failed: 'There was a problem blocking the user.' + block_confirm: 'Are you sure you want to block this user? They will not be able to create any new topics or posts.' + block_accept: 'Yes, block this user' deactivate_explanation: "A deactivated user must re-validate their email." suspended_explanation: "A suspended user can't log in." block_explanation: "A blocked user can't post or start topics." + stage_explanation: "A staged user can only post via email in specific topics." trust_level_change_failed: "There was a problem changing the user's trust level." suspend_modal_title: "Suspend User" trust_level_2_users: "Trust Level 2 Users" @@ -2742,6 +2794,10 @@ en: mark_watching: 'm, w Watch topic' badges: + earned_n_times: + one: "Earned this badge 1 time" + other: "Earned this badge %{count} times" + more_with_badge: "Others with this badge" title: Badges allow_title: "can be used as a title" multiple_grant: "can be awarded multiple times" diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index 8f11ce4d1..96f3b18b2 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -294,6 +294,12 @@ es: one: "1 usuario" other: "%{count} usuarios" groups: + empty: + posts: "No hay mensajes publicados por los miembros de este grupo." + members: "Este grupo no tiene miembros." + mentions: "No hay menciones de este grupo." + messages: "No hay mensajes para este grupo." + topics: "No hay temas de miembros de este grupo." add: "Añadir" selector_placeholder: "Añadir miembros" owner: "propietario" @@ -313,6 +319,19 @@ es: trust_levels: title: "Nivel de confianza entregado automáticamente a miembros cuando son añadidos:" none: "Ninguno" + notifications: + watching: + title: "Vigilando" + description: "e te notificará de cada nuevo post en este mensaje y se mostrará un contador de nuevos posts." + tracking: + title: "Siguiendo" + description: "Se te notificará si alguien menciona tu @nombre o te responde, y un contador de nuevos mensajes será mostrado." + regular: + title: "Normal" + description: "Se te notificará si alguien menciona tu @nombre o te responde." + muted: + title: "Silenciado" + description: "Nunca se te notificará de nada sobre temas en este grupo." user_action_groups: '1': "'Me gusta' Dados" '2': "'Me gusta' Recibidos" @@ -322,16 +341,16 @@ es: '6': "Respuestas" '7': "Menciones" '9': "Citas" - '10': "Favoritos" '11': "Ediciones" '12': "Elementos Enviados" '13': "Bandeja de entrada" '14': "Pendiente" categories: - all: "Todas las categorías" + all: "Categorías" all_subcategories: "todas" no_subcategory: "ninguna" category: "Categoría" + category_list: "Mostrar lista de categorías" reorder: title: "Reorganizar Categorías" title_long: "Reorganizar la lista de categorías" @@ -388,6 +407,7 @@ es: invited_by: "Invitado Por" trust_level: "Nivel de Confianza" notifications: "Notificaciones" + statistics: "Estadísticas" desktop_notifications: label: "Notificaciones de escritorio" not_supported: "Las notificaciones no están disponibles en este navegador. Lo sentimos." @@ -441,9 +461,14 @@ es: warnings_received: "avisos" messages: all: "Todos" - mine: "Míos" - unread: "No leídos" + inbox: "Bandeja de entrada" + sent: "Enviados" + archive: "Archivo" groups: "Mis grupos" + bulk_select: "Mensajes seleccionados" + move_to_inbox: "Mover a la bandeja de entrada" + failed_to_move: "No se han podido mover los mensajes seleccionados (puede haber problemas de conexión)" + select_all: "Seleccionar todo" change_password: success: "(e-mail enviado)" in_progress: "(enviando e-mail)" @@ -870,6 +895,7 @@ es: granted_badge: "Distintivo concedido" popup: mentioned: '{{username}} te mencionó en "{{topic}}" - {{site_title}}' + group_mentioned: '{{username}} te ha mencionado en "{{topic}}" - {{site_title}}' quoted: '{{username}} te citó en "{{topic}}" - {{site_title}}' replied: '{{username}} te respondió en "{{topic}}" - {{site_title}}' posted: '{{username}} publicó en "{{topic}}" - {{site_title}}' @@ -922,7 +948,7 @@ es: reset_read: "Restablecer leídos" delete: "Eliminar temas" dismiss: "Descartar" - dismiss_read: "Descartar todos los temas no leídos" + dismiss_read: "Descartar todos los temas sin leer" dismiss_button: "Descartar..." dismiss_tooltip: "Descartar solo los nuevos posts o dejar de seguir los temas" also_dismiss_topics: "Parar de seguir estos temas para que no aparezcan más en mis mensajes no leídos" @@ -943,7 +969,7 @@ es: read: "Todavía no has leído ningún tema." posted: "Todavía no has publicado en ningún tema." latest: "No hay temas recientes. Qué pena..." - hot: "No hay temas calientes nuevos." + hot: "No hay temas candentes nuevos." bookmarks: "No tienes temas guardados en marcadores todavía." category: "No hay temas en la categoría {{category}}." top: "No hay temas en el top más vistos." @@ -953,7 +979,7 @@ es: unread: '

    Tus temas sin leer aparecerán aquí.

    Por defecto, los temas son considerados no leídos y mostrán contadores de post sin leer 1 si:

    • Creaste el tema
    • Respondiste al tema
    • Leíste el tema durante más de 4 minutos

    O si has establecido específicamente el tema a Seguir o Vigilar en el control de notificaciones al pie de cada tema.

    Puedes cambiar esto en tus preferencias.

    ' bottom: latest: "No hay más temas recientes para leer." - hot: "No hay más temas calientes." + hot: "No hay más temas candentes." posted: "No hay más temas publicados." read: "No hay más temas leídos." new: "No hay más nuevos temas." @@ -970,9 +996,15 @@ es: create: 'Crear tema' create_long: 'Crear un nuevo tema' private_message: 'Empezar un mensaje' + archive_message: + help: 'Archivar mensaje' + title: 'Archivar' + move_to_inbox: + title: 'Mover a la bandeja de entrada' + help: 'Restaurar mensaje a la bandeja de entrada' list: 'Temas' new: 'nuevo tema' - unread: 'No leídos' + unread: 'sin leer' new_topics: one: '1 tema nuevo' other: '{{count}} temas nuevos' @@ -1020,6 +1052,7 @@ es: auto_close_title: 'Configuración de auto-cerrado' auto_close_save: "Guardar" auto_close_remove: "No Auto-Cerrar Este Tema" + auto_close_immediate: "El último mensaje tiene %{hours} horas de antigüedad, por lo que será cerrado inmediatamente." progress: title: avances go_top: "arriba" @@ -1140,6 +1173,7 @@ es: success: "Hemos invitado a ese usuario a participar en este hilo de mensajes." error: "Lo sentimos, hubo un error al invitar a ese usuario." group_name: "nombre del grupo" + controls: "Controles del tema" invite_reply: title: 'Invitar' username_placeholder: "nombre de usuario" @@ -1257,7 +1291,7 @@ es: via_email: "este post llegó por email" whisper: "esto post es un susurro privado para moderadores" wiki: - about: "Este post es tipo wiki, cualquier usuario registrado puede editarlo" + about: "este post es tipo wiki" archetypes: save: 'Guardar opciones' controls: @@ -1403,7 +1437,7 @@ es: category: can: 'puede… ' none: '(sin categoría)' - all: 'Todas las categorías' + all: 'Categorías' choose: 'Seleccionar una categoría…' edit: 'editar' edit_long: "Editar" @@ -1570,8 +1604,8 @@ es: other: "Recientes ({{count}})" help: "temas con posts recientes" hot: - title: "Popular" - help: "una selección de los temas más populares" + title: "Candente" + help: "una selección de los temas más candentes" read: title: "Leídos" help: "temas que ya has leído" @@ -1585,8 +1619,8 @@ es: unread: title: "Sin leer" title_with_count: - one: "Unread (1)" - other: "No leídos ({{count}})" + one: "Sin leer (1)" + other: "Sin leer ({{count}})" help: "temas que estás vigilando o siguiendo actualmente con posts no leídos" lower_title_with_count: one: "{{count}} sin leer" @@ -2048,6 +2082,7 @@ es: change_site_setting: "cambiar configuración del sitio" change_site_customization: "cambiar customización del sitio" delete_site_customization: "borrar customización del sitio" + change_site_text: "cambiar textos" suspend_user: "suspender usuario" unsuspend_user: "desbloquear usuario" grant_badge: "conceder distintivo" @@ -2239,7 +2274,7 @@ es: unlock_trust_level: "Desbloquear Nivel de Confianza" tl3_requirements: title: "Requerimientos para el nivel de confianza 3" - table_title: "En los últimos 100 días:" + table_title: "En los últimos %{time_period} días:" value_heading: "Valor" requirement_heading: "Requerimiento" visits: "Visitas" @@ -2618,7 +2653,7 @@ es: name: Enlace Popular description: Publicó un enlace externo con al menos 50 clicks hot_link: - name: Enlace Candente + name: Enlace popular description: Publicó un enlace externo con al menos 300 clicks famous_link: name: Enlace Famoso diff --git a/config/locales/client.fa_IR.yml b/config/locales/client.fa_IR.yml index 7e4e8efe7..d6cf6f3dd 100644 --- a/config/locales/client.fa_IR.yml +++ b/config/locales/client.fa_IR.yml @@ -267,6 +267,12 @@ fa_IR: total_rows: other: "%{count} کاربران" groups: + empty: + posts: "در این گروه هیچ پستی توسط کاربران " + members: "هیچ عضوی در این گروه وجود ندارد." + mentions: "هیچ کجا به این گروه اشاره‌ای نشده است." + messages: "پیامی در این گروه وجود ندارد." + topics: "در این گروه هیچ موضوعی توسط کاربران ارسال نشده." add: "افزودن" selector_placeholder: "افزودن عضو" owner: "مالک" @@ -285,6 +291,19 @@ fa_IR: trust_levels: title: "سطح اعتماد به صورت خودکار به اعضاء داده میشود وقتی که آن ها اضافه میشوند:" none: "هیچ کدام" + notifications: + watching: + title: "در حال مشاهده" + description: "در صورت ارسال شدن پست جدید در هر پیام یک اعلان برای شما ارسال می‌شود و تعداد پاسخ‌های جدید نمایش داده می‌شود." + tracking: + title: "ردگیری" + description: "در صورت اشاره شدن به @نام شما توسط اشخاص دیگر و یا دریافت پاسخ، اعلانی برای شما ارسال می‌شود و تعداد پاسخ‌های جدید نمایش داده می‌شود." + regular: + title: "معمولی" + description: "در صورتی که به @نام شما اشاره شود و یا پاسخی دریافت کنید اعلانی برای شما ارسال می‌شود." + muted: + title: "بی صدا شد" + description: "با ارسال شدن موضوعات جدید در این گروه شما اعلانی دریافت نمی‌کنید." user_action_groups: '1': "پسندهای داده شده" '2': "پسندهای دریافت شده" @@ -294,7 +313,6 @@ fa_IR: '6': "واکنش" '7': "اشاره‌ها" '9': "نقل‌قول‌ها" - '10': "ستاره‌دار" '11': "ویرایش‌ها" '12': "ارسال موارد" '13': "صندوق دریافت" @@ -304,6 +322,7 @@ fa_IR: all_subcategories: "همه" no_subcategory: "هیچی" category: "دسته بندی" + category_list: "نمایش لیست دسته‌بندی" reorder: title: "دوباره مرتب کردن دسته بندی ها" title_long: "سازماندهی مجدد فهرست دسته بندی ها" @@ -358,6 +377,7 @@ fa_IR: invited_by: "فراخوان از سوی" trust_level: "سطح اعتماد" notifications: "آگاه‌سازی‌ها" + statistics: "وضعیت" desktop_notifications: label: "اعلانات دسکتاپ" not_supported: "اعلانات بر روی این مرورگر پشتیبانی نمیشوند. با عرض پوزش." @@ -411,9 +431,14 @@ fa_IR: warnings_received: "هشدارها" messages: all: "همه" - mine: "خودم" - unread: "خوانده‌ نشده‌" + inbox: "صندوق دریافت" + sent: "ارسال شد" + archive: "بایگانی" groups: "گروه های من" + bulk_select: "انتخاب پیام‌ها" + move_to_inbox: "انتقال به صندوق دریافت" + failed_to_move: "انتقال پیام‌های انتخاب شده با اشکال مواجه شد (شاید اتصال شما در دسترس نیست)" + select_all: "انتخاب همه" change_password: success: "(ایمیل ارسال شد)" in_progress: "(فرستادن ایمیل)" @@ -837,6 +862,7 @@ fa_IR: granted_badge: "مدال اعطاء شد" popup: mentioned: '{{username}} mentioned you in "{{topic}}" - {{site_title}}' + group_mentioned: '{{username}} به شما در "{{topic}}" - {{site_title}} اشاره نمود' quoted: '{{username}} quoted you in "{{topic}}" - {{site_title}}' replied: '{{username}} replied to you in "{{topic}}" - {{site_title}}' posted: '{{username}} posted in "{{topic}}" - {{site_title}}' @@ -979,6 +1005,7 @@ fa_IR: auto_close_title: 'تنضیمات قفل خوکار' auto_close_save: "‌ذخیره" auto_close_remove: "این موضوع را خوکار قفل نکن" + auto_close_immediate: "زمان ارسال آخرین پست در موضوع برای %{hours} ساعت پیش است، بنابر این موضوع بلافاصله بسته می‌شود." progress: title: نوشته ی در حال اجرا go_top: "بالا" @@ -1097,6 +1124,7 @@ fa_IR: success: "ما آن کاربر را برای شرکت در این پیام دعوت کردیم." error: "با معذرت٬ یک خطا برای دعوت آن کاربر وجود داشت" group_name: "نام گروه" + controls: "مدیریت مبحث" invite_reply: title: 'دعوتنامه ' username_placeholder: "نام کاربری" @@ -1960,6 +1988,7 @@ fa_IR: change_site_setting: "تغییر تنظیمات سایت" change_site_customization: "تغییر سفارشی‌سازی سایت" delete_site_customization: "پاک‌کردن سفارشی‌سازی سایت" + change_site_text: "تغییر نوشته سایت" suspend_user: "کاربر تعلیق شده" unsuspend_user: "کابر تعلیق نشده" grant_badge: "اعطای مدال" @@ -2144,7 +2173,7 @@ fa_IR: unlock_trust_level: "باز کردن سطح اعتماد" tl3_requirements: title: "شرایط لازم برای سطح اعتماد 3." - table_title: "در ۱۰۰ روز اخیر:" + table_title: "در %{time_period} روز گذشته:" value_heading: "مقدار" requirement_heading: "نیازمندی‌ها" visits: "بازدیدها" diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index b88438aed..ee9487891 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -130,7 +130,7 @@ fi: disabled: 'poisti listauksista %{when}' topic_admin_menu: "ketjun ylläpitotoimet" emails_are_disabled: "Ylläpitäjä on estänyt kaiken lähtevän sähköpostiliikenteen. Mitään sähköposti-ilmoituksia ei lähetetä." - edit: 'muokkaa tämän ketjun otsikkoa ja aluetta' + edit: 'muokkaa ketjun otsikkoa ja aluetta' not_implemented: "Tätä toimintoa ei ole vielä toteutettu, pahoittelut!" no_value: "Ei" yes_value: "Kyllä" @@ -143,7 +143,7 @@ fi: admin_title: "Ylläpito" flags_title: "Liput" show_more: "näytä lisää" - show_help: "asetukset" + show_help: "ohjeet" links: "Linkit" links_lowercase: one: "linkki" @@ -231,7 +231,7 @@ fi: switch_from_anon: "Poistu anonyymitilasta" banner: close: "Sulje tämä banneri." - edit: "Muokkaa tätä banneria >>" + edit: "Muokkaa banneria >>" choose_topic: none_found: "Yhtään ketjua ei löydetty." title: @@ -266,9 +266,9 @@ fi: you_replied_to_post: "Sinä vastasit {{post_number}}" user_replied_to_topic: "{{user}} vastasi ketjuun" you_replied_to_topic: "Sinä vastasit ketjuun" - user_mentioned_user: "{{user}} mainitsi {{another_user}}" + user_mentioned_user: "{{user}} mainitsi käyttäjän {{another_user}}" user_mentioned_you: "{{user}} mainitsi sinut" - you_mentioned_user: "Sinä mainitsit {{another_user}}" + you_mentioned_user: "Sinä mainitsit käyttäjän {{another_user}}" posted_by_user: "Kirjoittaja {{user}}" posted_by_you: "Kirjoittaja sinä" sent_by_user: "Lähettäjä {{user}}" @@ -294,6 +294,12 @@ fi: one: "1 käyttäjä" other: "%{count} käyttäjää" groups: + empty: + posts: "Ryhmän jäsenet eivät ole kirjoittaneet viestejä." + members: "Kukaan ei kuulu tähän ryhmään." + mentions: "Ryhmää ei ole mainittu." + messages: "Tällä ryhmällä ei ole yksityistä ketjua." + topics: "Ryhmän jäsenet eivät ole aloittaneet ketjuja." add: "Lisää" selector_placeholder: "Lisää jäseniä" owner: "omistaja" @@ -313,6 +319,19 @@ fi: trust_levels: title: "Luottamustaso, joka annetaan automaattisesti lisättäessä tähän ryhmään:" none: "Ei mitään" + notifications: + watching: + title: "Tarkkaillut" + description: "Saat ilmoituksen uusista viesteistä jokaisessa viestiketjussa, ja uusien vastausten lukumäärä näytetään." + tracking: + title: "Seurannassa" + description: "Saat ilmoituksen, jos joku mainitsee @nimesi tai vastaa sinulle, ja uusien vastausten lukumäärä näytetään." + regular: + title: "Tavallinen" + description: "Saat ilmoituksen, jos joku mainitsee @nimesi tai vastaa sinulle." + muted: + title: "Vaimennetut" + description: "Et saa ilmoituksia uusista ketjuista tässä ryhmässä." user_action_groups: '1': "Annetut tykkäykset" '2': "Saadut tykkäykset" @@ -322,7 +341,6 @@ fi: '6': "Vastaukset" '7': "Viittaukset" '9': "Lainaukset" - '10': "Tähdelliset" '11': "Muokkaukset" '12': "Lähetetyt" '13': "Postilaatikko" @@ -330,8 +348,9 @@ fi: categories: all: "kaikki alueet" all_subcategories: "kaikki" - no_subcategory: "alueettomat" + no_subcategory: "vain pääalue" category: "Alue" + category_list: "Näytä alueet" reorder: title: "Järjestä alueet uudelleen" title_long: "Järjestä alueiden lista uudelleen" @@ -388,9 +407,10 @@ fi: invited_by: "Kutsuja" trust_level: "Luottamustaso" notifications: "Ilmoitukset" + statistics: "Tilastot" desktop_notifications: label: "Työpöytäilmoitukset" - not_supported: "Tämä selainen ei tue ilmoituksia, pahoittelut." + not_supported: "Tämä selain ei tue ilmoituksia, pahoittelut." perm_default: "Näytä ilmoituksia" perm_denied_btn: "Ei oikeuksia" perm_denied_expl: "Olet kieltänyt ilmoitusten näyttämisen. Salli ilmoitusten näyttäminen selaimen asetuksista ja klikkaa sen jälkeen painiketta. (Työpöytä: vasemmanpuoleisin kuvake osoiterivillä. Mobiili: 'Sivun tiedot'.)" @@ -434,16 +454,21 @@ fi: muted_topics_link: "Näytä vaimennetut ketjut" automatically_unpin_topics: "Poista ketjun kiinnitys automaattisesti, jos selaan keskustelun loppuun" staff_counters: - flags_given: "hyödyllisiä lippuja" + flags_given: "hyödyllistä liputusta" flagged_posts: "liputettuja viestejä" - deleted_posts: "poistettuja viestejä" + deleted_posts: "poistettua viestiä" suspensions: "hyllytyksiä" warnings_received: "varoituksia" messages: all: "Kaikki" - mine: "Omat" - unread: "Lukemattomat" + inbox: "Saapuneet" + sent: "Lähetetyt" + archive: "Arkisto" groups: "Omat ryhmäni" + bulk_select: "Valitse viestejä" + move_to_inbox: "Siirrä saapuneisiin" + failed_to_move: "Viestien siirto epäonnistui (vika saattaa olla internetyhteydessäsi)" + select_all: "Valitse kaikki" change_password: success: "(sähköposti lähetetty)" in_progress: "(lähettää sähköpostia)" @@ -672,7 +697,7 @@ fi: value_prop: "Kun luot tilin, muistamme mitä olet lukenut, jotta voit aina palata keskusteluissa takaisin oikeaan kohtaan. Saat myös ilmoituksia, täällä tai sähköpostilla, kun uusia viestejä kirjoitetaan. Voit myös tykätä viesteistä. :heartbeat:" summary: enabled_description: "Tarkastelet tiivistelmää tästä ketjusta, sen mielenkiintoisimpia viestejä käyttäjien toiminnan perusteella." - description: "Tässä kejussa on {{count}} viestiä." + description: "Tässä ketjussa on {{count}} viestiä." description_time: "Ketjussa on {{count}} viestiä, joiden arvioitu lukemisaika on {{readingTime}} minuuttia." enable: 'Näytä ketjun tiivistelmä' disable: 'Näytä kaikki viestit' @@ -870,11 +895,12 @@ fi: granted_badge: "Arvomerkki myönnetty" popup: mentioned: '{{username}} mainitsi sinut ketjussa "{{topic}}" - {{site_title}}' + group_mentioned: '{{username}} mainitsi sinut ketjussa "{{topic}}" - {{site_title}}' quoted: '{{username}} lainasi sinua ketjussa "{{topic}}" - {{site_title}}' replied: '{{username}} vastasi sinulle ketjussa "{{topic}}" - {{site_title}}' posted: '{{username}} vastasi ketjuun "{{topic}}" - {{site_title}}' private_message: '{{username}} lähetti sinulle yksityisviestin ketjussa "{{topic}}" - {{site_title}}' - linked: '{{username}} linkitti viestiisi aiheesta "{{topic}}" - {{site_title}}' + linked: '{{username}} linkitti viestiisi ketjusta "{{topic}}" - {{site_title}}' upload_selector: title: "Lisää kuva" title_with_attachments: "Lisää kuva tai tiedosto" @@ -970,6 +996,12 @@ fi: create: 'Uusi ketju' create_long: 'Luo uusi ketju' private_message: 'Luo viesti' + archive_message: + help: 'Siirrä viesti arkistoosi' + title: 'Arkistoi' + move_to_inbox: + title: 'Siirrä saapuneisiin' + help: 'Siirrä takaisin saapuneisiin.' list: 'Ketjut' new: 'uusi ketju' unread: 'lukemattomat' @@ -1008,7 +1040,7 @@ fi: toggle_information: "näytä/kätke ketjun tiedot" read_more_in_category: "Haluatko lukea lisää? Selaa muita ketjuja alueella {{catLink}} tai {{latestLink}}." read_more: "Haluatko lukea lisää? {{catLink}} tai {{latestLink}}." - read_more_MF: "Sinulla on { UNREAD, plural, =0 {} one { 1 lukematon } other { # lukematonta } } { NEW, plural, =0 {} one { {BOTH, select, true{ja } false { } other{}} 1 uusi ketju} other { {BOTH, select, true{ja } false { } other{}} # uutta kejua} } jäljellä, voit myös {CATEGORY, select, true {selata muita ketjuja alueella {catLink}} false {{latestLink}} other {}}" + read_more_MF: "Sinulla on { UNREAD, plural, =0 {} one { 1 lukematon } other { # lukematonta } } { NEW, plural, =0 {} one { {BOTH, select, true{ja } false { } other{}} 1 uusi ketju} other { {BOTH, select, true{ja } false { } other{}} # uutta ketjua} } jäljellä, voit myös {CATEGORY, select, true {selata muita ketjuja alueella {catLink}} false {{latestLink}} other {}}" browse_all_categories: Selaa keskustelualueita view_latest_topics: katsele tuoreimpia ketjuja suggest_create_topic: Jospa aloittaisit uuden ketjun? @@ -1020,6 +1052,7 @@ fi: auto_close_title: 'Automaattisen sulkemisen asetukset' auto_close_save: "Tallenna" auto_close_remove: "Älä sulje tätä ketjua automaattisesti" + auto_close_immediate: "Viimeisin viesti tässä ketjussa on jo %{hours} tuntia vanha, joten ketju suljetaan heti." progress: title: ketjun edistyminen go_top: "alkuun" @@ -1140,6 +1173,7 @@ fi: success: "Käyttäjä on kutsuttu osallistumaan tähän yksityiseen keskusteluun." error: "Pahoittelut, kutsuttaessa tapahtui virhe." group_name: "ryhmän nimi" + controls: "Ketjun hallinta" invite_reply: title: 'Kutsu' username_placeholder: "käyttäjätunnus" @@ -1257,7 +1291,7 @@ fi: via_email: "tämä viesti lähetettiin sähköpostitse" whisper: "tämä viesti on yksityinen kuiskaus valvojille" wiki: - about: "tämä viesti on wiki; peruskäyttäjät voivat muokata sitä" + about: "tämä viesti on wiki" archetypes: save: 'Tallennusasetukset' controls: @@ -1786,6 +1820,7 @@ fi: primary_group: "Aseta automaattisesti ensisijaiseksi ryhmäksi" group_owners: Omistajat add_owners: Lisää omistajia + incoming_email: "Saapuvan sähköpostin osoite" incoming_email_placeholder: "aseta sähköpostiosoite" api: generate_master: "Luo rajapinnan pääavain" @@ -2046,6 +2081,7 @@ fi: change_site_setting: "muuta sivuston asetusta" change_site_customization: "vaihda sivuston mukautusta" delete_site_customization: "poista sivuston mukautus" + change_site_text: "muutos sivuston teksteissä" suspend_user: "hyllytä käyttäjä" unsuspend_user: "poista hyllytys" grant_badge: "myönnä arvomerkki" @@ -2127,6 +2163,7 @@ fi: basic: 'Luottamustason 1 käyttäjät (Haastaja)' member: 'Luottamustason 2 käyttäjät (Konkari)' regular: 'Luottamustason 3 käyttäjät (Mestari)' + leader: 'Luottamustason 4 käyttäjät (Johtaja)' staff: "Henkilökunta" admins: 'Ylläpitäjät' moderators: 'Valvojat' @@ -2236,7 +2273,7 @@ fi: unlock_trust_level: "Avaa luottamustason lukitus" tl3_requirements: title: "Vaatimukset luottamustasolle 3." - table_title: "Edellisen 100 päivän aikana:" + table_title: "Edellisen %{time_period} päivän aikana:" value_heading: "Arvo" requirement_heading: "Vaatimus" visits: "Vierailua" @@ -2305,6 +2342,7 @@ fi: revert_confirm: "Oletko varma, että haluat kumota tekemäsi muutokset?" go_back: "Takaisin hakuun" recommended: "On suositeltavaa muokata seuraavaa tekstiä tarpeidesi mukaan:" + show_overriden: 'Näytä vain muokatut' site_settings: show_overriden: 'Näytä vain muokatut' title: 'Asetukset' @@ -2612,13 +2650,13 @@ fi: description: Luki kaikki viestit ketjusta, jossa on yli 100 viestiä popular_link: name: Suosittu linkki - description: Postasi linkin ulkoiselle sivustolle, joka sai vähintään 50 klikkausta. + description: Laittoi linkin ulkoiselle sivustolle, joka sai vähintään 50 klikkausta. hot_link: name: Kuuma linkki - description: Postasi linkin ulkoiselle sivustolle, joka sai vähintään 300 klikkausta. + description: Laittoi linkin ulkoiselle sivustolle, joka sai vähintään 300 klikkausta. famous_link: name: Kuuluisa linkki - description: Postasi linkin ulkoiselle sivustolle, joka sai vähintään 1000 klikkausta. + description: Laittoi linkin ulkoiselle sivustolle, joka sai vähintään 1000 klikkausta. google_search: |

    Etsi Googlella

    diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index b0e29c50b..67ed9b954 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -294,6 +294,12 @@ fr: one: "1 utilisateur" other: "%{count} utilisateurs" groups: + empty: + posts: "Il n'y a aucun message de la part des membres de ce groupe." + members: "Il n'y a aucun membre dans ce groupe." + mentions: "Il n'y a aucune mention de ce groupe." + messages: "Il n'y a aucun message pour ce groupe." + topics: "Il n'y a aucun sujet de la part des membres de ce groupe." add: "Ajouter" selector_placeholder: "Ajouter des membres" owner: "propriétaire" @@ -313,6 +319,19 @@ fr: trust_levels: title: "Niveau de confiance automatiquement attribué lorsque les membres sont ajoutés à :" none: "Aucun" + notifications: + watching: + title: "S'abonner" + description: "Vous serez notifié de chaque nouvelle réponse dans chaque message, et le nombre de nouvelles réponses sera affiché." + tracking: + title: "Suivre" + description: "Vous serez notifié si quelqu'un mentionne votre @pseudo ou vous répond, et le nombre de nouvelles réponses sera affiché." + regular: + title: "Normal" + description: "Vous serez notifié si quelqu'un mentionne votre @pseudo ou vous répond." + muted: + title: "Silencieux" + description: "Nous ne serez jamais notifié de quoi que ce soit à propos des nouveaux sujets dans ce groupe." user_action_groups: '1': "J'aime donnés" '2': "J'aime reçus" @@ -322,7 +341,6 @@ fr: '6': "Réponses" '7': "Mentions" '9': "Citations" - '10': "Favoris" '11': "Editions" '12': "Eléments envoyés" '13': "Boîte de réception" @@ -332,6 +350,7 @@ fr: all_subcategories: "toutes" no_subcategory: "aucune" category: "Catégorie" + category_list: "Afficher la liste des catégories" reorder: title: "Réordonner les catégories" title_long: "Réorganiser la liste des catégories" @@ -388,6 +407,7 @@ fr: invited_by: "Invité par" trust_level: "Niveau de confiance" notifications: "Notifications" + statistics: "Stats" desktop_notifications: label: "Notifications de bureau" not_supported: "Les notifications ne sont pas supportées avec ce navigateur. Désolé." @@ -441,9 +461,14 @@ fr: warnings_received: "avertissements" messages: all: "Tous" - mine: "Envoyés" - unread: "Non lus" + inbox: "Boîte de réception" + sent: "Envoyé" + archive: "Archiver" groups: "Mes groupes" + bulk_select: "Sélection des messages" + move_to_inbox: "Déplacer dans la boîte de réception" + failed_to_move: "Impossible de déplacer les messages sélectionnés (peut-être que votre connexion est coupée)" + select_all: "Sélectionner tout" change_password: success: "(courriel envoyé)" in_progress: "(courriel en cours d'envoi)" @@ -2243,7 +2268,6 @@ fr: unlock_trust_level: "Déverrouiller le niveau de confiance" tl3_requirements: title: "Pré-requis pour le niveau de confiance 3" - table_title: "Les 100 derniers jours :" value_heading: "Valeur" requirement_heading: "Pré-requis" visits: "Visites" diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index 6473380b6..50960d12c 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -304,7 +304,6 @@ he: members: "חברים" posts: "הודעות" alias_levels: - title: "מי יכול להשתמש בקבוצה זו ככינוי?" nobody: "אף אחד" only_admins: "רק מנהלים" mods_and_admins: "רק מנחים ומנהלים" @@ -322,7 +321,6 @@ he: '6': "תגובות" '7': "אזכורים" '9': "ציטוטים" - '10': "כוכבים" '11': "עריכות" '12': "פריטים שנשלחו" '13': "דואר נכנס" @@ -439,8 +437,6 @@ he: warnings_received: "אזהרות" messages: all: "הכל" - mine: "שלי" - unread: "לא נקראו" change_password: success: "(דואר אלקטרוני נשלח)" in_progress: "(שולח דואר אלקטרוני)" @@ -2194,7 +2190,6 @@ he: unlock_trust_level: "שחרור רמת אמון מנעילה" tl3_requirements: title: "דרישות עבור רמת אמון 3" - table_title: "במאה הימים האחרונים:" value_heading: "ערך" requirement_heading: "דרישה" visits: "ביקורים" @@ -2255,7 +2250,6 @@ he: confirm: 'אישור' dropdown: "נגלל" site_text: - none: "בחרו את סוג התוכן לתחילת עריכה." title: 'תוכן טקסטואלי' site_settings: show_overriden: 'הצג רק הגדרות ששונו' diff --git a/config/locales/client.id.yml b/config/locales/client.id.yml index 9d64819f1..6809b9a58 100644 --- a/config/locales/client.id.yml +++ b/config/locales/client.id.yml @@ -243,7 +243,6 @@ id: members: "Anggota" posts: "Post" alias_levels: - title: "Siapa yang dapat menggunakan grup ini sebagai alias?" nobody: "Tak seorangpun" only_admins: "Hanya admin" mods_and_admins: "Hanya moderator dan Admin" @@ -256,7 +255,6 @@ id: '4': "Topik" '7': "Disebutkan" '9': "Kutipan" - '10': "Bertanda Bintang" '11': "Edit" '12': "Item Terkirim" '13': "Kotak masuk" @@ -321,7 +319,6 @@ id: warnings_received: "peringatan" messages: all: "Semua" - unread: "Belum dibaca" change_password: success: "(surel terkirim)" in_progress: "(mengirimkan surel)" diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index 25a8b0cea..9c3327e99 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -294,6 +294,12 @@ it: one: "1 utente" other: "%{count} utenti" groups: + empty: + posts: "Non ci sono messaggi dai membri di questo gruppo." + members: "Non ci sono membri in questo gruppo." + mentions: "Non ci sono menzioni a questo gruppo." + messages: "Non ci sono messaggi per questo gruppo." + topics: "Non ci sono argomenti da membri di questo gruppo." add: "Aggiungi" selector_placeholder: "Aggiungi membri" owner: "proprietario" @@ -313,6 +319,19 @@ it: trust_levels: title: "Livello di esperienza automaticamente assegnato ai membri quando vengono aggiunti:" none: "Nessuno" + notifications: + watching: + title: "In osservazione" + description: "Verrai avvertito per ogni nuovo messaggio, e verrà mostrato il conteggio delle nuove risposte." + tracking: + title: "Seguendo" + description: "Verrai avvertito se qualcuno menziona il tuo @nome o ti risponde, e verrà mostrato un conteggio delle nuove risposte." + regular: + title: "Esperto" + description: "Verrai avvertito se qualcuno menziona il tuo @nome o ti risponde." + muted: + title: "Silenziato" + description: "Non verrai mai avvertito per i nuovi argomenti in questo gruppo." user_action_groups: '1': "Mi piace - Assegnati" '2': "Mi piace - Ricevuti" @@ -322,7 +341,6 @@ it: '6': "Risposte" '7': "Menzioni" '9': "Citazioni" - '10': "Preferiti" '11': "Modifiche" '12': "Inviati" '13': "Posta in arrivo" @@ -332,6 +350,7 @@ it: all_subcategories: "tutte" no_subcategory: "nessuno" category: "Categoria" + category_list: "Visualizza l'elenco delle categorie" reorder: title: "Riordina Categorie" title_long: "Riorganizza l'elenco di categorie" @@ -388,6 +407,7 @@ it: invited_by: "Invitato Da" trust_level: "Livello Esperienza" notifications: "Notifiche" + statistics: "Statistiche" desktop_notifications: label: "Notifiche Desktop" not_supported: "Spiacenti, le notifiche non sono supportate su questo browser." @@ -441,9 +461,14 @@ it: warnings_received: "avvisi" messages: all: "Tutti" - mine: "Miei" - unread: "Non letti" + inbox: "In arrivo" + sent: "Spediti" + archive: "Archiviati" groups: "I Miei Gruppi" + bulk_select: "Seleziona messaggi" + move_to_inbox: "Sposta in arrivo" + failed_to_move: "Errore nello spostare i messaggi selezionati (forse la tua connessione non è attiva)" + select_all: "Seleziona Tutti" change_password: success: "(email inviata)" in_progress: "(invio email in corso)" @@ -769,6 +794,7 @@ it: saved_local_draft_tip: "salvato in locale" similar_topics: "Il tuo argomento è simile a..." drafts_offline: "bozze offline" + group_mentioned: "Usando {{group}}, stai per mandare una notifica a {{count}} persone." error: title_missing: "Il titolo è richiesto" title_too_short: "Il titolo deve essere lungo almeno {{min}} caratteri" @@ -840,6 +866,7 @@ it: more: "visualizza le notifiche precedenti" total_flagged: "totale argomenti segnalati" mentioned: "

    {{username}} {{description}}

    " + group_mentioned: "

    {{username}} {{description}}

    " quoted: "

    {{username}} {{description}}

    " replied: "

    {{username}} {{description}}

    " posted: "

    {{username}} {{description}}

    " @@ -868,6 +895,7 @@ it: granted_badge: "Targhetta assegnata" popup: mentioned: '{{username}} ti ha menzionato in "{{topic}}" - {{site_title}}' + group_mentioned: '{{username}} ti ha menzionato in "{{topic}}" - {{site_title}}' quoted: '{{username}} ti ha citato in "{{topic}}" - {{site_title}}' replied: '{{username}} ti ha risposto in "{{topic}}" - {{site_title}}' posted: '{{username}} ha pubblicato in "{{topic}}" - {{site_title}}' @@ -923,6 +951,7 @@ it: dismiss_read: "Chiudi tutti i non letti" dismiss_button: "Chiudi..." dismiss_tooltip: "Chiudi solo gli ultimi messaggi o smetti di seguire gli argomenti" + also_dismiss_topics: "Smetti di tracciare questi topic così che non compaiano più come non letti" dismiss_new: "Chiudi Nuovo" toggle: "commuta la selezione multipla degli argomenti" actions: "Azioni Multiple" @@ -967,6 +996,12 @@ it: create: 'Nuovo Argomento' create_long: 'Crea un nuovo Argomento' private_message: 'Inizia a scrivere un messaggio' + archive_message: + help: 'Sposta il messaggio nel tuo archivio' + title: 'Archivio' + move_to_inbox: + title: 'Sposta in arrivo' + help: 'Sposta in arrivo' list: 'Argomenti' new: 'nuovo argomento' unread: 'non letto' @@ -1017,6 +1052,7 @@ it: auto_close_title: 'Impostazioni di auto-chiusura' auto_close_save: "Salva" auto_close_remove: "Non chiudere automaticamente questo argomento" + auto_close_immediate: "Il topic verrà chiuso immediatamente perché l'ultimo post risale a %{hours} ore fa." progress: title: Avanzamento dell'argomento go_top: "alto" @@ -1137,6 +1173,7 @@ it: success: "Abbiamo invitato l'utente a partecipare a questo messaggio." error: "Spiacenti, si è verificato un errore durante l'invito dell'utente." group_name: "nome gruppo" + controls: "Opzioni Argomento" invite_reply: title: 'Invita' username_placeholder: "nome utente" @@ -1159,7 +1196,7 @@ it: other: "{{count}} messaggi" cancel: "Rimuovi filtro" split_topic: - title: "Sposta in un Nuovo Argomento" + title: "Sposta in un nuovo argomento" action: "sposta in un nuovo argomento" topic_name: "Nome Nuovo Argomento" error: "Si è verificato un errore spostando il messaggio nel nuovo argomento." @@ -1254,7 +1291,7 @@ it: 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" + about: "questo post è una wiki" archetypes: save: 'Opzioni di salvataggio' controls: @@ -1780,6 +1817,7 @@ it: primary_group: "Imposta automaticamente come gruppo principale" group_owners: Proprietari add_owners: Aggiungi proprietari + incoming_email: "Indirizzo email personalizzato" incoming_email_placeholder: "inserisci l'indirizzo e-mail" api: generate_master: "Genera una Master API Key" @@ -1912,6 +1950,7 @@ it: email_templates: title: "Modelli e-mail" subject: "Oggetto" + multiple_subjects: "Questo template email ha più di un campo oggetto." body: "Corpo" none_selected: "Scegli un modello di e-mail per iniziare la modifica." revert: "Annulla Cambiamenti" @@ -2039,6 +2078,7 @@ it: change_site_setting: "modifica le impostazioni del sito" change_site_customization: "modifica la personalizzazione del sito" delete_site_customization: "cancella la personalizzazione del sito" + change_site_text: "cambia il testo del sito" suspend_user: "utente sospeso" unsuspend_user: "utente riattivato" grant_badge: "assegna targhetta" @@ -2230,7 +2270,7 @@ it: unlock_trust_level: "Sblocca Livello Esperienza" tl3_requirements: title: "Requisiti per Livello Esperienza 3" - table_title: "Negli ultimi 100 giorni:" + table_title: "Negli ultimi %{time_period} giorni:" value_heading: "Valore" requirement_heading: "Requisito" visits: "Visite" @@ -2291,9 +2331,15 @@ it: confirm: 'Conferma' dropdown: "A tendina" site_text: + description: "Puoi personalizzare qualsiasi testo del forum. Comincia effettuando una ricerca qui sotto:" search: "Trova il testo che vorresti modificare" title: 'Contenuto Testuale' edit: 'modifica' + revert: "Annulla i cambiamenti" + revert_confirm: "Sei sicuro di voler annullare i cambiamenti?" + go_back: "Torna alla ricerca" + recommended: "Consigliamo di personalizzare il testo seguente in modo da adattarlo alle necessità:" + show_overriden: 'Mostra solo le opzioni sovrascritte' site_settings: show_overriden: 'Mostra solo le opzioni sovrascritte' title: 'Impostazioni' @@ -2414,6 +2460,7 @@ it: crawling_description: "Quando Discourse crea gli argomenti per i tuoi 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_username_key_from_feed: "Chiave per ottenere il nome utente discourse dal feed" embed_truncate: "Tronca i messaggi incorporati" embed_whitelist_selector: "Selettore CSS per gli elementi da permettere negli embed" embed_blacklist_selector: "Selettore CSS per gli elementi da rimuovere dagli embed" diff --git a/config/locales/client.ja.yml b/config/locales/client.ja.yml index b70fbbfbf..cfa124e99 100644 --- a/config/locales/client.ja.yml +++ b/config/locales/client.ja.yml @@ -104,6 +104,7 @@ ja: admin_title: "管理者" flags_title: "フラグ" show_more: "もっと見る" + show_help: "オプション" links: "リンク" links_lowercase: other: "リンク集" @@ -244,13 +245,17 @@ ja: total_rows: other: "%{count} 人のユーザ" groups: + empty: + posts: "このグループのメンバーによる投稿はありません。" + add: "追加" + selector_placeholder: "メンバー追加" + owner: "所有者" visible: "このグループは全てのユーザに表示されています。" title: other: "グループ" members: "メンバー" posts: "ポスト" alias_levels: - title: "このグループを仮名として使えるユーザ" nobody: "無し" only_admins: "管理者のみ" mods_and_admins: "管理者とモデレータのみ" @@ -265,7 +270,6 @@ ja: '6': "レスポンス数" '7': "タグ付け" '9': "引用" - '10': "お気に入り" '11': "編集" '12': "アイテム送信" '13': "インボックス" @@ -275,6 +279,8 @@ ja: all_subcategories: "全てのサブカテゴリ" no_subcategory: "サブカテゴリなし" category: "カテゴリ" + reorder: + title: "カテゴリを並び替える" posts: "ポスト" topics: "トピック" latest: "最新ポスト" @@ -370,8 +376,6 @@ ja: warnings_received: "注意" messages: all: "すべて" - mine: "受信トレイ" - unread: "未読" change_password: success: "(メールを送信しました)" in_progress: "(メールを送信中)" @@ -1320,6 +1324,7 @@ ja: with_topics: "%{filter} トピック" with_category: "%{filter} %{category} トピック" latest: + title: "最新" help: "最新のトピック" hot: title: "ホット" @@ -1391,7 +1396,7 @@ ja: stale_data: "最近アップデートの確認が正しく動作していません。sidekiq が起動していることを確認してください。" version_check_pending: "まるでアップデート直後のようです。素晴らしい!" installed_version: "Installed" - latest_version: "Latest" + latest_version: "最新" problems_found: "Discourse のインストールにいくつか問題が発見されました:" last_checked: "最終チェック" refresh_problems: "更新" @@ -1935,7 +1940,6 @@ ja: unlock_trust_level: "トラストレベルをアンロック" tl3_requirements: title: "トラストレベル3の条件" - table_title: "過去100日に:" value_heading: "値" requirement_heading: "条件" visits: "訪問" @@ -1996,7 +2000,6 @@ ja: confirm: '確認' dropdown: "ドロップダウン" site_text: - none: "コンテンツの種類を選択してください" title: 'テキストコンテンツ' site_settings: show_overriden: '上書き部分のみ表示' diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml index f28bfc99e..991ea4e48 100644 --- a/config/locales/client.ko.yml +++ b/config/locales/client.ko.yml @@ -267,6 +267,12 @@ ko: total_rows: other: "%{count} 사용자" groups: + empty: + posts: "이 그룹의 구성원에 의해 작성된 게시물은 없습니다." + members: "이 그룹에는 구성원이 없습니다." + mentions: "이 그룹에 대한 언급이 없습니다." + messages: "이 그룹에 대한 메시지는 없습니다." + topics: "이 그룹의 구성원에 의해 작성된 주제글이 없습니다." add: "추가" selector_placeholder: "멤버 추가" owner: "소유자" @@ -276,7 +282,6 @@ ko: members: "멤버" posts: "게시글" alias_levels: - title: "누가 이 그룹을 별칭으로 사용할 수 있는가?" nobody: "0명" only_admins: "관리자 전용" mods_and_admins: "운영자 및 관리자만" @@ -293,7 +298,6 @@ ko: '6': "응답" '7': "멘션" '9': "인용" - '10': "즐겨찾기" '11': "편집" '12': "보낸 편지함" '13': "받은 편지함" @@ -407,8 +411,6 @@ ko: warnings_received: "경고" messages: all: "전체" - mine: "내가 보낸 메세지" - unread: "읽지 않음" change_password: success: "(이메일 전송)" in_progress: "(이메일 전송 중)" @@ -1717,6 +1719,10 @@ ko: color: "색" opacity: "투명도" copy: "복사" + email_templates: + none_selected: "편집하려는 이메일 템플릿을 선택하세요." + revert: "변경사항 취소" + revert_confirm: "정말로 변경사항을 되돌리시겠습니까?" css_html: title: "CSS/HTML" long_title: "CSS, HTML 사용자 정의" @@ -1809,6 +1815,7 @@ ko: ip_address: "IP" topic_id: "토픽 ID" post_id: "글 ID" + category_id: "카테고리 ID" delete: '삭제' edit: '편집' save: '저장' @@ -1839,6 +1846,7 @@ ko: change_site_setting: "사이트 설정 변경" change_site_customization: "사이트 커스텀화 변경" delete_site_customization: "사이트 커스텀화 삭제" + change_site_text: "site text 변경" suspend_user: "suspend user" unsuspend_user: "unsuspend user" grant_badge: "뱃지 부여" @@ -1849,6 +1857,9 @@ ko: impersonate: "대역" anonymize_user: "anonymize user" roll_up: "roll up IP blocks" + change_category_settings: "카테고리 설정 변경" + delete_category: "카테고리 지우기" + create_category: "카테고리 만들기" screened_emails: title: "블락된 이메일들" description: "누군가가 새로운 계정을 만들면 아래 이메일 주소는 체크되고 등록은 블락됩니다, 또는 다른 조치가 취해집니다." @@ -1913,6 +1924,9 @@ ko: pending: '검토가 필요한 사용자' newuser: '사용자 신뢰도 0 (새로운 사용자)' basic: '사용자 신뢰도 1 (초보 사용자)' + member: '사용자 신뢰도 2 (회원)' + regular: '사용자 신뢰도 3 (정규)' + leader: '사용자 신뢰도 4 (지도자)' staff: "스태프" admins: '관리자 사용자 목록' moderators: '운영자' @@ -2017,7 +2031,7 @@ ko: unlock_trust_level: "신뢰도 시스템 잠금 해제" tl3_requirements: title: "레벨 3 권한이 필요합니다." - table_title: "최근 100일 간:" + table_title: "지난 %{time_period} 일간" value_heading: "값" requirement_heading: "필수" visits: "방문수" @@ -2078,8 +2092,15 @@ ko: confirm: '확인' dropdown: "드롭다운" site_text: - none: "편집할 콘텐츠 유형을 선택하세요." + description: "포럼에 있는 그 어떤 텍스트도 수정이 가능합니다. 아래의 검색기능을 통해 시작하세요." + search: "편집하고 싶은 텍스트를 검색하세요." title: '텍스트 콘텐츠' + edit: '편집' + revert: "변경사항 취소" + revert_confirm: "정말로 변경사항을 되돌리시겠습니까?" + go_back: "검색으로 돌아가기" + recommended: "다음의 텍스트를 요구에 맞게 편집하는 것을 권장:" + show_overriden: 'Override 된 설정만 보여주기' site_settings: show_overriden: '수정된 것만 표시' title: '사이트 설정' @@ -2167,6 +2188,9 @@ ko: bad_count_warning: header: "주의!" text: "사라진 뱃지 샘플이 있습니다. 뱃지 query가 존재하지 않는 user ID나 post ID를 반환할 경우 발생합니다. 예상하지 못한 결과를 일으킬 수 있으니 query를 다시 한번 확인하세요." + no_grant_count: "할당된 뱃지가 없습니다." + grant_count: + other: "%{count}개의 뱃지가 할당됨." sample: "샘플:" grant: with: %{username} @@ -2191,8 +2215,11 @@ ko: settings: "Embedding 설정" feed_settings: "Feed 설정" crawling_settings: "클롤러 설정" + embed_post_limit: "글 개수가 최대치입니다" + embed_truncate: "임베드된 글 비우기" feed_polling_enabled: "RSS/ATOM으로 글 가져오기" feed_polling_url: "RSS/ATOM 피드로 긁을 URL" + save: "Embedding 설정 저장하기" permalink: title: "고유링크" url: "URL" diff --git a/config/locales/client.nb_NO.yml b/config/locales/client.nb_NO.yml index 227652f23..4e4d75bd4 100644 --- a/config/locales/client.nb_NO.yml +++ b/config/locales/client.nb_NO.yml @@ -298,7 +298,6 @@ nb_NO: members: "Medlemmer" posts: "Innlegg" alias_levels: - title: "Hvem kan benytte denne gruppen som alias?" nobody: "Ingen" only_admins: "Kun administratorer" mods_and_admins: "Kun moderatorer og administratorer" @@ -315,7 +314,6 @@ nb_NO: '6': "Svar" '7': "Omtalelser" '9': "Sitater" - '10': "Favoritter" '11': "Redigeringer" '12': "Sendte elementer" '13': "Innboks" @@ -419,8 +417,7 @@ nb_NO: warnings_received: "advarsler" messages: all: "Alle" - mine: "Mine" - unread: "Uleste" + groups: "Mine grupper" change_password: success: "(e-post sendt)" in_progress: "(sender e-post)" @@ -2075,7 +2072,6 @@ nb_NO: unlock_trust_level: "Lås opp tillitsnivå" tl3_requirements: title: "Krav til tillitsnivå 3" - table_title: "De siste 100 dagene:" value_heading: "Verdi" requirement_heading: "Krav" visits: "Besøk" @@ -2136,7 +2132,6 @@ nb_NO: confirm: 'Bekreftelse' dropdown: "Nedtrekk" site_text: - none: "Velg en innholdstype for å starte redigering." title: 'Tekstinnhold' site_settings: show_overriden: 'Bare vis overstyrte' diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index 4bbe2c72a..69982a54c 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -304,7 +304,6 @@ nl: members: "Leden" posts: "Berichten" alias_levels: - title: "Wie kan deze groep als alias gebruiken?" nobody: "Niemand" only_admins: "Alleen admins" mods_and_admins: "Alleen moderatoren and admins" @@ -322,7 +321,6 @@ nl: '6': "Reacties" '7': "Genoemd" '9': "Citaten" - '10': "Met ster" '11': "Wijzigingen" '12': "Verzonden items" '13': "Inbox" @@ -440,8 +438,6 @@ nl: warnings_received: "waarschuwingen" messages: all: "Alle" - mine: "Mijn" - unread: "Ongelezen" change_password: success: "(e-mail verzonden)" in_progress: "(e-mail wordt verzonden)" @@ -916,7 +912,6 @@ nl: dismiss_read: "Alle ongelezen afwijzen" dismiss_button: "Afwijzen..." dismiss_tooltip: "Alleen nieuwe posts afwijzen of stop het volgen van topics" - also_dismiss_topics: "Deze topics niet meer volgens? (Topics zullen niet meer verschijnen in het tabblad Ongelezen)" dismiss_new: "markeer nieuwe berichten als gelezen" toggle: "toggle bulkselectie van topics" actions: "Bulk Acties" @@ -2179,7 +2174,6 @@ nl: unlock_trust_level: "Deblokkeer Trust Level" tl3_requirements: title: "Vereisten voor Trust Level 3" - table_title: "In de afgelopen 100 dagen:" value_heading: "Waarde" requirement_heading: "Vereiste" visits: "Bezoeken" @@ -2240,7 +2234,6 @@ nl: confirm: 'Bevestiging' dropdown: "Uitklapbaar" site_text: - none: "Kies een type van inhoud om te beginnen met bewerken." title: 'Tekst Inhoud' site_settings: show_overriden: 'Bekijk alleen bewerkte instellingen' diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml index f4c59e70c..a2fe392fd 100644 --- a/config/locales/client.pl_PL.yml +++ b/config/locales/client.pl_PL.yml @@ -350,7 +350,6 @@ pl_PL: '6': "Odpowiedzi" '7': "Wzmianki" '9': "Cytaty" - '10': "Oznaczone" '11': "Edycje" '12': "Wysłane" '13': "Skrzynka odbiorcza" @@ -471,8 +470,6 @@ pl_PL: warnings_received: "otrzymanych ostrzeżeń" messages: all: "Wszystkie" - mine: "Moje" - unread: "Nieprzeczytane" groups: "Moje grupy" change_password: success: "(email wysłany)" @@ -2329,7 +2326,6 @@ pl_PL: unlock_trust_level: "Odblokuj poziom zaufania" tl3_requirements: title: "Wymagania dla osiągnięcia 3. poziomu zaufania" - table_title: "W ciągu ostatnich 100 dni:" value_heading: "Wartość" requirement_heading: "Wymaganie" visits: "Odwiedziny" diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index bdd38ec83..445ea3f3d 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -294,6 +294,12 @@ pt: one: "1 utilizador" other: "%{count} utilizadores" groups: + empty: + posts: "Não há nenhuma publicação feita por membros deste grupo." + members: "Não há nenhum membro neste grupo." + mentions: "Não há nenhuma menção deste grupo." + messages: "Não há nenhuma mensagem para este grupo." + topics: "Não há nenhum tópico feito por membros deste grupo." add: "Adicionar" selector_placeholder: "Adicionar membros" owner: "proprietário" @@ -313,6 +319,19 @@ pt: trust_levels: title: "Nível de confiança concedido automaticamente a membros quando são adicionados:" none: "Nenhum" + notifications: + watching: + title: "A vigiar" + description: "Será notificado de cada nova publicação em todas as mensagens, e uma contagem de novas respostas será exibida." + tracking: + title: "A Acompanhar" + description: "Será notificado se alguém mencionar o seu @nome ou lhe responder, e uma contagem de novas respostas será exibida." + regular: + title: "Habitual" + description: "Será notificado se alguém mencionar o seu @nome ou responder-lhe." + muted: + title: "Mudo" + description: "Não será notificado de nada relacionado com novos tópicos neste grupo." user_action_groups: '1': "Gostos Dados" '2': "Gostos Recebidos" @@ -322,7 +341,6 @@ pt: '6': "Respostas" '7': "Menções" '9': "Citações" - '10': "Favoritos" '11': "Edições" '12': "Itens Enviados" '13': "Caixa de Entrada" @@ -332,6 +350,7 @@ pt: all_subcategories: "todas" no_subcategory: "nenhuma" category: "Categoria" + category_list: "Exibir lista de categorias" reorder: title: "Re-organizar Categorias" title_long: "Re-organizar a lista de categorias" @@ -388,6 +407,7 @@ pt: invited_by: "Convidado Por" trust_level: "Nível de Confiança" notifications: "Notificações" + statistics: "Estatísticas" desktop_notifications: label: "Notificações de Desktop" not_supported: "Não são suportadas notificações neste navegador. Desculpe." @@ -441,9 +461,14 @@ pt: warnings_received: "avisos" messages: all: "Todas" - mine: "Minha" - unread: "Não lidas" + inbox: "Caixa de Entrada" + sent: "Enviado" + archive: "Arquivo" groups: "Os Meus Grupos" + bulk_select: "Selecionar mensagens" + move_to_inbox: "Mover para Caixa de Entrada" + failed_to_move: "Falha ao mover as mensagens selecionadas (talvez a sua rede esteja em baixo)" + select_all: "Selecionar Tudo" change_password: success: "(email enviado)" in_progress: "(a enviar email)" @@ -870,6 +895,7 @@ pt: granted_badge: "Distintivo concedido" popup: mentioned: '{{username}} mencionou-o em "{{topic}}" - {{site_title}}' + group_mentioned: '{{username}} mencionou-o em "{{topic}}" - {{site_title}}' quoted: '{{username}} citou-o em "{{topic}}" - {{site_title}}' replied: '{{username}} respondeu-lhe em "{{topic}}" - {{site_title}}' posted: '{{username}} publicou em "{{topic}}" - {{site_title}}' @@ -1020,6 +1046,7 @@ pt: auto_close_title: 'Configurações para Fechar Automaticamente' auto_close_save: "Guardar" auto_close_remove: "Não Fechar Este Tópico Automaticamente" + auto_close_immediate: "A última mensagem neste tópico já tem %{hours} horas, por isso o tópico será fechado imediatamente." progress: title: progresso do tópico go_top: "topo" @@ -1140,6 +1167,7 @@ pt: success: "Convidámos esse utilizador para participar nesta mensagem." error: "Pedimos desculpa, ocorreu um erro ao convidar esse utilizador." group_name: "nome do grupo" + controls: "Controlos de Tópico" invite_reply: title: 'Convidar' username_placeholder: "nome de utilizador" @@ -2048,6 +2076,7 @@ pt: change_site_setting: "alterar configurações do sítio" change_site_customization: "alterar personalização do sítio" delete_site_customization: "remover personalização do sítio" + change_site_text: "alterar texto do sítio" suspend_user: "utilizador suspenso" unsuspend_user: "utilizador não suspenso" grant_badge: "conceder distintivo" @@ -2239,7 +2268,7 @@ pt: unlock_trust_level: "Desbloquear Nível de Confiança" tl3_requirements: title: "Requisitos para o Nível de Confiança 3" - table_title: "Nos últimos 100 dias:" + table_title: "Nos últimos %{time_period} dias:" value_heading: "Valor" requirement_heading: "Requisito" visits: "Visitas" diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml index be81b9d88..83ebeb56a 100644 --- a/config/locales/client.pt_BR.yml +++ b/config/locales/client.pt_BR.yml @@ -321,7 +321,6 @@ pt_BR: '6': "Respostas" '7': "Menções" '9': "Citações" - '10': "Favoritos" '11': "Edições" '12': "Itens enviados" '13': "Caixa de Entrada" @@ -439,8 +438,6 @@ pt_BR: warnings_received: "avisos" messages: all: "Todas" - mine: "Minha" - unread: "Não lidas" groups: "Meus Grupos" change_password: success: "(email enviado)" @@ -2211,7 +2208,6 @@ pt_BR: unlock_trust_level: "Destravar Nível de Confiança" tl3_requirements: title: "Requisitos para o Nível de Confiança 3" - table_title: "Nos últimos 100 dias:" value_heading: "Valor" requirement_heading: "Requisito" visits: "Visitas" diff --git a/config/locales/client.ro.yml b/config/locales/client.ro.yml index ef9c18d97..9b9f16e1f 100644 --- a/config/locales/client.ro.yml +++ b/config/locales/client.ro.yml @@ -98,6 +98,15 @@ ro: one: "acum o zi" few: "acum %{count} zile" other: "acum %{count} zile" + later: + x_days: + one: "După 1 zi" + few: "După %{count} zile" + other: "După %{count} zile" + x_years: + one: "După 1 an" + few: "După %{count} ani" + other: "După %{count} ani" share: topic: 'distribuie adresă către această discuție' post: 'distribuie o adresă către postarea #%{postNumber}' @@ -113,11 +122,19 @@ ro: disabled: 'deschis %{when}' closed: enabled: 'inchis %{when}' + disabled: 'deschis %{when}' archived: enabled: 'arhivat %{when}' disabled: 'dezarhivat %{when}' pinned: enabled: 'Prins %{when}' + disabled: 'desprinse %{when}' + pinned_globally: + enabled: 'fixat global %{when}' + disabled: 'desprins %{when}' + visible: + enabled: 'listat %{when}' + disabled: 'retras %{when}' topic_admin_menu: "acțiuni subiect administrator" emails_are_disabled: "Trimiterea de emailuri a fost dezactivată global de către un administrator. Nu vor fi trimise notificări email de nici un fel." edit: 'editează titlul și categoria acestui subiect' @@ -215,15 +232,18 @@ ro: saved: "Salvat!" upload: "Încarcă" uploading: "Încărcare..." + uploading_filename: "Se încarcă {{filename}}..." uploaded: "Încărcat!" enable: "Activează" disable: "Dezactivează" undo: "Anulează acțiunea precedentă" - revert: "Rescrie acțiunea precedentă" + revert: "Refacere" failed: "Eșuat" switch_to_anon: "Mod anonim" + switch_from_anon: "Ieșire din mod anonim" banner: close: "Ignoră acest banner." + edit: "Editează acest banner >>" choose_topic: none_found: "Nu au fost găsite discuții." title: @@ -239,6 +259,10 @@ ro: edit: "Editează" cancel: "Anulează" view_pending: "vezi postările în aşteptare" + has_pending_posts: + one: "Această discuție are 1 postare în așteptare" + few: "Această discuție are 1 postare în așteptare" + other: "Această discuţie are {{count}} postări în aşteptare." confirm: "Salvează Schimbările" delete_prompt: "Sunteţi sigur că vreţi să ştergeţi %{username}? Această operaţiune va şterge toate postările, va bloca adresa de email şi adresa de IP." approval: @@ -285,6 +309,7 @@ ro: few: "%{count} utilizatori" other: "%{count} utilizatori" groups: + add: "Adăugați" visible: "Grupul este vizibil tuturor utilizatorilor" title: one: "grup" @@ -293,7 +318,6 @@ ro: members: "Membri" posts: "Postări" alias_levels: - title: "Cine poate folosii acest grup ca pseudonim?" nobody: "Nimeni" only_admins: "Doar Adminii" mods_and_admins: "Doar moderatorii și adminii" @@ -308,7 +332,6 @@ ro: '6': "Răspunsuri" '7': "Mențiuni" '9': "Citate" - '10': "Participări" '11': "Editări" '12': "Obiecte Trimise" '13': "Primite" @@ -425,8 +448,6 @@ ro: warnings_received: "avertizări" messages: all: "Toate" - mine: "Ale mele" - unread: "Necitite" change_password: success: "(email trimis)" in_progress: "(se trimite email)" @@ -1143,8 +1164,6 @@ ro: no_value: "Nu, pastrează" yes_value: "Da, abandonează" via_email: "acest post a sosit via email" - wiki: - about: "Acest post este un wiki; oricine poate edita" archetypes: save: 'Opțiuni de salvare' controls: @@ -1852,6 +1871,7 @@ ro: ip_address: "Adresa IP" topic_id: "ID Discuție" post_id: "ID Mesaj" + category_id: "ID categorie" delete: 'Șterge' edit: 'Editează' save: 'Salvează' @@ -2069,7 +2089,6 @@ ro: unlock_trust_level: "Deblochează Nivelul de Încredere" tl3_requirements: title: "Cerințe pentru nivelul 3 de încredere" - table_title: "În ultimele 100 de zile:" value_heading: "Valoarea" requirement_heading: "Cerințe" visits: "Vizite" @@ -2128,7 +2147,6 @@ ro: text: 'Câmp Text' confirm: 'Confirmare' site_text: - none: "Alege un tip de conținut pentru editare." title: 'Conținut' site_settings: show_overriden: 'Arată doar rescrierile' @@ -2227,6 +2245,23 @@ ro: name: "Nume" image: "Imagine" delete_confirm: "Sunteţi sigur că doriţi să ștergeți :%{name}: emoji?" + embedding: + save: "Salvați setările pentru embeding" + permalink: + title: "Link-uri" + url: "URL" + topic_id: "ID discuție" + topic_title: "Discuție" + post_id: "ID postare" + post_title: "Postare" + category_id: "ID categorie" + category_title: "Categorie" + external_url: "URL extern" + delete_confirm: Sigur doriți să ștergeți acest link ? + form: + label: "Nou:" + add: "Adăugați" + filter: "Căutare (URL sau URL extern)" lightbox: download: "descarcă" search_help: @@ -2242,6 +2277,7 @@ ro: categories: 'g apoi c Categorii' top: 'g, t Top' bookmarks: 'g, b Semne de carte' + messages: 'g, m Mesaje' navigation: title: 'Navigare' jump: '# Mergi la mesajul #' @@ -2253,31 +2289,33 @@ ro: title: 'Applicația' create: 'c Crează discuție nouă' notifications: 'n Deschide notificare' + hamburger_menu: '= Deschide meniul hamburger' user_profile_menu: 'p Deschide meniu utilizator' show_incoming_updated_topics: '. Arată discuţiile actualizate' search: '/ Caută' - help: '? Deschide ajutorul de scurtături de tastatură' - dismiss_new_posts: 'x, r Respinge Nou/Mesaj' - dismiss_topics: 'x, t Respinge Discuţia' + help: '? Vezi comenzi rapide disponibile' + dismiss_new_posts: 'x, r Respinge Nou/Mesaje' + dismiss_topics: 'x, t Respinge discuţiile' + log_out: 'shift+z shift+z Ieșire din cont' actions: title: 'Acțiuni' bookmark_topic: 'f Comută semnul de carte pentru discuţie' - pin_unpin_topic: 'shift+p Pin/Unpin topic' + pin_unpin_topic: 'shift+p Fixează/Desprinde discuția' share_topic: 'shift s distribuie discuție' share_post: 's Distribuie mesajul' - reply_as_new_topic: 't Răspunde că discuţie legată' + reply_as_new_topic: 't Răspunde că discuţie conexă' reply_topic: 'shift r Raspunde la discuție' reply_post: 'r Răspunde la postare' quote_post: 'q Citează mesajul' like: 'l Apreciează mesajul' - flag: '! Marchează mesajul' - bookmark: 'b Marchează cu semn de carte postarea' + flag: '! Reclamă mesajul' + bookmark: 'b Salvează postarea' edit: 'e Editează mesaj' delete: 'd Șterge mesaj' - mark_muted: 'm apoi m Marchează discuția ca silențios' - mark_regular: 'm apoi r Marchează discuția ca normală' - mark_tracking: 'm apoi t Marchează discuția ca urmărită' - mark_watching: 'm apoi w Marchează discuția ca privită' + mark_muted: 'm apoi m Treceți discuția în mod silențios' + mark_regular: 'm, r Discuție normală (implicită)' + mark_tracking: 'm, t Urmăriți discuția' + mark_watching: 'm, w Urmăriți discuția îndeaproape' badges: title: Insigne allow_title: "poate fi folosit ca titlu" @@ -2298,7 +2336,7 @@ ro: none: "" badge_grouping: getting_started: - name: Să începem + name: Cum începem community: name: Communitate trust_level: @@ -2306,22 +2344,23 @@ ro: other: name: Altele posting: - name: Scrie mesaj + name: Postând badge: editor: name: Editor - description: Primul mesaj editat + description: Prima postare editată basic_user: name: De baza - description: Acordată toate funcțiile esențiale + description: Vi se permite acces la toate funcțiile esențiale ale comunității member: name: Membru - description: Invitaţii Acordate + description: Permisiuni invitații regular: - name: Normal + name: Obișnuit + description: Vi se permit recategorisirea, redenumirea, link-urile urmărite și acces lounge leader: name: Lider - description: Acrodată recategorisește , redenumește, adrese urmărite și lounge + description: Vi se permit editarea globală, fixarea, arhivarea, despărțirea și combinarea welcome: name: Bine ai venit description: A primit o apreceiere @@ -2350,39 +2389,59 @@ ro: name: Discuţie Foarte Bună description: A primit 50 de aprecieri pentru o discuţie. Această insignă poate fi acordată de mai multe ori nice_share: - name: Drăguţ + name: Distribuire drăguță description: A împărţit un mesaj cu 25 utilizatori unici good_share: - name: Bun - description: A împărţit un mesaj cu 300 utilizatori unici + name: Distribuire bună + description: A distribuit un mesaj către 300 utilizatori unici great_share: - name: Perfect - description: A împărţit un mesaj cu 1000 utilizatori unici + name: Super distribuire + description: A distribuit un mesaj către 1000 utilizatori unici first_like: name: Prima apreciere description: A apreciat un mesaj first_flag: - name: Primul marcaj - description: A marcat un mesaj + name: Prima reclamație + description: A reclamat un mesaj promoter: name: Promotor description: A invitat un utilizator campaigner: name: Combatant + description: A invitat 3 membri simpli (nivel de incredere 1) champion: name: Campion + description: A invitat 5 membri (nivel de incredere 2) first_share: name: Primul description: A distribuit un mesaj first_link: - name: Prima adresă + name: Primul link description: A adăugat o adresă internă catre altă discuție first_quote: name: Primul citat description: A citat un alt utilizator read_guidelines: - name: Citește reguli de ajutor - description: Citește comune + name: Citește ghidul comunității + description: Citește ghidul comunității reader: - name: Cititorul - description: Citeşte fiecare mesaj dintr-o discuție cu mai mult de 100 de mesaje + name: Cititor + description: A citit fiecare mesaj dintr-o discuție cu mai mult de 100 de mesaje + popular_link: + name: Link popular + description: A postat un link extern cu cel puțin 50 de accesări + hot_link: + name: Link interesant + description: A postat un link extern cu cel puțin 300 de accesări + famous_link: + name: Link foarte popular + description: A postat un link extern cu cel puțin 1000 de accesări + google_search: | +

    Căutați cu Google

    +

    + + + + + +

    diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index 1d32d173e..d080a32d2 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -376,7 +376,6 @@ ru: '6': "Ответы" '7': "Упоминания" '9': "Цитаты" - '10': "Избранное" '11': "Изменения" '12': "Отправленные" '13': "Входящие" @@ -499,8 +498,10 @@ ru: warnings_received: "предупреждения" messages: all: "Все" - mine: "Мои" - unread: "Непрочитанные" + inbox: "Входящие" + sent: "Отправленные" + archive: "Архив" + select_all: "Выбрать все" change_password: success: "(письмо отправлено)" in_progress: "(отправка письма)" @@ -969,6 +970,7 @@ ru: current_user: 'перейти на вашу страницу пользователя' topics: bulk: + unlist_topics: "Исключить из списков" reset_read: "Сбросить прочтённые" delete: "Удалить темы" dismiss: "OK" @@ -1020,6 +1022,10 @@ ru: create: 'Создать Тему' create_long: 'Создать новую тему' private_message: 'Написать сообщение' + archive_message: + help: 'Переместить сообщение в архив' + move_to_inbox: + help: 'Переместить сообщение во входящие' list: 'Темы' new: 'новая тема' unread: 'непрочитанно' @@ -1323,8 +1329,6 @@ ru: yes_value: "Да, отказаться" via_email: "это сообщение пришло с почты" whisper: "Это внутреннее сообщение, т.е. оно видно только модераторам" - wiki: - about: "это вики-сообщение - любой пользователь может отредактировать его, чтобы улучшить, дополнить или исправить ошибки" archetypes: save: 'Параметры сохранения' controls: @@ -1583,6 +1587,7 @@ ru: submit_tooltip: "Отправить приватную отметку" take_action_tooltip: "Достигнуть порога жалоб не дожидаясь большего количества жалоб от сообщества" cant: "Извините, но вы не можете сейчас послать жалобу." + notify_staff: 'Уведомить администрацию' formatted_name: off_topic: "Это не по теме" inappropriate: "Это неприемлемо" @@ -1623,6 +1628,7 @@ ru: help: "Эта тема не закреплена; она будет отображаться в обычном порядке" pinned_globally: title: "Закреплена глобально" + help: "Эта тема закреплена глобально; она будет отображаться на главной и вверху своего раздела" pinned: title: "Закреплена" help: "Тема закреплена; она будет показана вверху соответствующего раздела" @@ -2350,7 +2356,6 @@ ru: unlock_trust_level: "Разморозить уровень доверия" tl3_requirements: title: "Требования для 3 уровня доверия" - table_title: "За последние 100 дней:" value_heading: "Значение" requirement_heading: "Требование" visits: "Посещений" @@ -2529,6 +2534,8 @@ ru: categories: 'g, c Разделы' top: 'g, t Вверх' bookmarks: 'g, b Закладки' + profile: 'Профиль' + messages: 'Сообщения' navigation: title: 'Навигация' jump: '# Перейти к сообщение №' @@ -2540,12 +2547,14 @@ ru: title: 'Приложение' 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: 'Выйти' actions: title: 'Действия' bookmark_topic: 'f Добавить в Избранное' diff --git a/config/locales/client.sk.yml b/config/locales/client.sk.yml new file mode 100644 index 000000000..97251a682 --- /dev/null +++ b/config/locales/client.sk.yml @@ -0,0 +1,2729 @@ +# encoding: utf-8 +# +# Never edit this file. It will be overwritten when translations are pulled from Transifex. +# +# To work with us on translations, join this project: +# https://www.transifex.com/projects/p/discourse-org/ + +sk: + js: + number: + format: + separator: "," + delimiter: "," + human: + storage_units: + format: '%n %u' + units: + byte: + one: bajt + few: bajtov + other: bajtov + gb: GB + kb: KB + mb: MB + tb: TB + short: + thousands: "{{number}}tis" + millions: "{{number}}mil" + dates: + time: "h:mm a" + long_no_year: "MMM D h:mm a" + long_no_year_no_time: "MMM D" + full_no_year_no_time: "MMMM Do" + long_with_year: "MMM D, YYYY h:mm a" + long_with_year_no_time: "MMM D, YYYY" + full_with_year_no_time: "MMMM Do, YYYY" + long_date_with_year: "MMM D, 'YY LT" + long_date_without_year: "MMM D, LT" + long_date_with_year_without_time: "MMM D, 'YY" + long_date_without_year_with_linebreak: "MMM D
    LT" + long_date_with_year_with_linebreak: "MMM D, 'YY
    LT" + tiny: + half_a_minute: "< 1m" + less_than_x_seconds: + one: "< 1s" + few: "< %{count}s" + other: "< %{count}s" + x_seconds: + one: "1s" + few: "1s" + other: "%{count}s" + less_than_x_minutes: + one: "< 1m" + few: "< 1m" + other: "< %{count}m" + x_minutes: + one: "1m" + few: "%{count}m" + other: "%{count}m" + about_x_hours: + one: "1h" + few: "%{count}h" + other: "%{count}h" + x_days: + one: "1d" + few: "%{count}d" + other: "%{count}d" + about_x_years: + one: "1r" + few: "%{count}r" + other: "%{count}r" + over_x_years: + one: "> 1r" + few: "> %{count}r" + other: "> %{count}r" + almost_x_years: + one: "1r" + few: "%{count}r" + other: "%{count}r" + date_month: "MMM D" + date_year: "MMM 'YY" + medium: + x_minutes: + one: "1 minúta" + few: "%{count} minúty" + other: "%{count} minút" + x_hours: + one: "1 hodina" + few: "%{count} hodiny" + other: "%{count} hodín" + x_days: + one: "1 deň" + few: "%{count} dni" + other: "%{count} dní" + date_year: "MMM D, 'YY" + medium_with_ago: + x_minutes: + one: "pred 1 minútou" + few: "pred %{count} minútami" + other: "pred %{count} minútami" + x_hours: + one: "pred 1 hodinou" + few: "pred %{count} hodinami" + other: "pred %{count} hodinami" + x_days: + one: "pred 1 dňom" + few: "pred %{count} dňami" + other: "pred %{count} dňami" + later: + x_days: + one: "1 deň neskôr" + few: "%{count} dni neskôr" + other: "%{count} dní neskôr" + x_months: + one: "1 mesiac neskôr" + few: "%{count} mesiace neskôr" + other: "%{count} mesiacov neskôr" + x_years: + one: "1 rok neskôr" + few: "%{count} roky neskôr" + other: "%{count} rokov neskôr" + share: + topic: 'zdieľaj odkaz na túto tému' + post: 'príspevok #%{postNumber}' + close: 'zatvoriť' + twitter: 'zdieľaj odkaz na Twitteri' + facebook: 'zdieľaj odkaz na Facebooku' + google+: 'zdieľaj odkaz na Google+' + email: 'pošli odkaz emailom' + action_codes: + split_topic: "rozdeľ tému %{when}" + autoclosed: + enabled: 'uzavreté %{when}' + disabled: 'otvorené %{when}' + closed: + enabled: 'uzavreté %{when}' + disabled: 'otvorené %{when}' + archived: + enabled: 'archivované %{when}' + disabled: 'odarchivované %{when}' + pinned: + enabled: 'pripnuné %{when}' + disabled: 'odopnuté %{when}' + pinned_globally: + enabled: 'globálne pripnuté %{when}' + disabled: 'odopnuté %{when}' + visible: + enabled: 'zverejnené %{when}' + disabled: 'stiahnuté %{when}' + topic_admin_menu: "akcie administrátora témy" + emails_are_disabled: "Odosielanie emailov bolo globálne vypnuté administrátorom. Žiadne emailové notifikácie nebudú odoslané." + edit: 'upraviť názov a kategóriu témy' + not_implemented: "Táto funkcia ešte bohužiaľ nie je implementovaná." + no_value: "Nie" + yes_value: "Áno" + generic_error: "Bohužiaľ nastala chyba." + generic_error_with_reason: "Nastala chyba: %{error}" + sign_up: "Registrácia" + log_in: "Prihlásenie" + age: "Vek" + joined: "Registovaný" + admin_title: "Administrácia" + flags_title: "Nahlásenie" + show_more: "zobraz viac" + show_help: "možnosti" + links: "Odkazy" + links_lowercase: + one: "odkaz" + few: "odkazy" + other: "odkazy" + faq: "FAQ" + guidelines: "Pokyny" + privacy_policy: "Ochrana súkromia" + privacy: "Súkromie" + terms_of_service: "Podmienky používania" + mobile_view: "Mobilná verzia" + desktop_view: "Desktop verzia" + you: "Vy" + or: "alebo" + now: "práve teraz" + read_more: 'čítaj ďalej' + more: "Viac" + less: "Menej" + never: "nikdy" + daily: "denne" + weekly: "týždenne" + every_two_weeks: "každé dva týždne" + every_three_days: "každé tri dni" + max_of_count: "najviac {{count}}" + alternation: "alebo" + character_count: + one: "1 znak" + few: "{{count}} znakov" + other: "{{count}} znakov" + suggested_topics: + title: "Odporúčané témy" + about: + simple_title: "O fóre" + title: "O %{title}" + stats: "Štatistiky stránky" + our_admins: "Naši admini" + our_moderators: "Naši moderátori" + stat: + all_time: "Za celú dobu" + last_7_days: "Posledných 7 dní" + last_30_days: "Posledných 30 dní" + like_count: "Páči sa mi" + topic_count: "Témy" + post_count: "Príspevky" + user_count: "Noví používatelia" + active_user_count: "Aktívni používatelia" + contact: "Kontaktujte nás" + contact_info: "V prípade kritickej chyby alebo naliehavej záležitosti nás prosím konaktujte na %{contact_info}." + bookmarked: + title: "Záložka" + clear_bookmarks: "Odstrániť záložku" + help: + bookmark: "Kliknutím vložíte záložku na prvý príspevok tejto témy" + unbookmark: "Kliknutím odstánite všetky záložky v tejto téme" + bookmarks: + not_logged_in: "pre pridanie záložky sa musíte prihlásiť" + created: "záložka bola pridaná" + not_bookmarked: "príspevok je prečítaný, kliknite pre pridanie záložky" + last_read: "toto je posledný prečítaný príspevok, kliknite pre pridanie záložky" + remove: "Odstrániť záložku" + confirm_clear: "Ste si istý že chcete odstrániť všetky záložky z tejto témy?" + topic_count_latest: + one: "1 nová alebo upravená téma." + few: "{{count}} nové alebo upravené témy." + other: "{{count}} nových alebo upravených tém." + topic_count_unread: + one: "1 neprečítaná téma." + few: "{{count}} neprečítané témy." + other: "{{count}} neprečítaných tém." + topic_count_new: + one: "1 nová téma." + few: "{{count}} nové témy." + other: "{{count}} nových tém." + click_to_show: "Kliknite pre zobrazenie." + preview: "náhľad" + cancel: "zrušiť" + save: "Uložiť zmeny" + saving: "Ukladám zmeny..." + saved: "Zmeny uložené." + upload: "Upload" + uploading: "Upload prebieha..." + uploading_filename: "Nahrávám {{filename}}..." + uploaded: "Upload úspešne dokončený." + enable: "Zapnúť" + disable: "Vypnúť" + undo: "Späť" + revert: "Vrátiť zmeny" + failed: "Nepodarilo sa" + switch_to_anon: "Anonymný mód" + switch_from_anon: "Opustiť anonymný mód" + banner: + close: "Zamietnuť tento banner." + edit: "Upraviť tento banner >>" + choose_topic: + none_found: "Nenašli sa žiadne témy." + title: + search: "Hľadaj tému podľa názvu, url alebo id:" + placeholder: "sem napíšte názov témy" + queue: + topic: "Téma:" + approve: 'Schváliť' + reject: 'Zamietnuť' + delete_user: 'Odstrániť používateľa' + title: "Vyžaduje schválenie" + none: "Žiadne príspevky na kontrolu." + edit: "Upraviť" + cancel: "Zrušiť" + view_pending: "zobraziť príspevky čakajúce na schválenie" + has_pending_posts: + one: "Téma má {{count}} príspevkov čakajúci na schválenie" + few: "Téma má {{count}} príspevky čakajúce na schválenie" + other: "Téma má {{count}} príspevkov čakajúcich na schválenie" + confirm: "Uložiť zmeny" + delete_prompt: "Ste si istý, že chcete vymazať %{username}? To odstráni všetky ich príspevky a zablokuje ich emailové a IP adresy." + approval: + title: "Príspevok vyžaduje schválenie" + description: "Váš príspevok sme obdžali, ale skôr než bude zverejnený musí byť schválený moderátorom. Prosíme o trpezlivosť." + pending_posts: + one: "Máte 1 neprečítaný príspevok." + few: "Máte {{count}} neprečítané príspevky." + other: "Máte {{count}} neprečítaných príspevkov." + ok: "OK" + user_action: + user_posted_topic: "{{user}} založil tému" + you_posted_topic: "Vy ste založili tému" + user_replied_to_post: "{{user}} odpovedal na {{post_number}}" + you_replied_to_post: "Vy ste odpovedali na {{post_number}}" + user_replied_to_topic: "{{user}} odpovedal na tému" + you_replied_to_topic: "Vy ste odpovedali natému" + user_mentioned_user: "{{user}} spomenul {{another_user}}" + user_mentioned_you: "{{user}} spomenul Vás" + you_mentioned_user: "Vy ste spomenuli {{another_user}}" + posted_by_user: "Príspevok od {{user}}" + posted_by_you: "Príspevok od Vás" + sent_by_user: "Poslané od {{user}}" + sent_by_you: "Poslané Vami" + directory: + filter_name: "filtrovať podľa používateľského mena" + title: "Používatelia" + likes_given: "Rozdané" + likes_received: "Prijaté" + topics_entered: "Navštívené" + topics_entered_long: "Navšívených tém" + time_read: "Čas strávený čítaním" + topic_count: "Témy" + topic_count_long: "Vytvorených tém" + post_count: "Odpovede" + post_count_long: "Odpovedí" + no_results: "Žiadne výsledky" + days_visited: "Návštev" + days_visited_long: "Navštívených dní" + posts_read: "Prečítané" + posts_read_long: "Prečítaných príspevkov" + total_rows: + one: "1 užívateľ" + few: "%{count} užívatelia" + other: "%{count} užívateľov" + groups: + add: "Pridať" + selector_placeholder: "Pridať členov" + owner: "vlastník" + visible: "Skupina je viditeľná všetkým používateľom" + title: + one: "skupina" + few: "skupiny" + other: "skupiny" + members: "Členovia" + posts: "Príspevky" + alias_levels: + title: "Kto môže poslať správu a @uváadzať túto skupinu?" + nobody: "Nikto" + only_admins: "Iba administrátori" + mods_and_admins: "Iba moderátori a administrátori" + members_mods_and_admins: "Iba členovia skupiny, moderátori a administrátori" + everyone: "Každý" + trust_levels: + title: "Stupeň dôvery automaticky pridelený členom po ich pridaní:" + none: "Žiadny" + user_action_groups: + '1': "Rozdaných 'páči sa mi'" + '2': "Obdržaných 'páči sa mi'" + '3': "Záložky" + '4': "Témy" + '5': "Odpovede" + '6': "Odozvy" + '7': "Zmienky" + '9': "Citácie" + '10': "Obľúbené" + '11': "Úpravy" + '12': "Odoslané správy" + '13': "Prijaté správy" + '14': "Čakajúce správy" + categories: + all: "všetky kategórie" + all_subcategories: "všetky" + no_subcategory: "žiadne" + category: "Kategória" + reorder: + title: "Usporiadať Kategórie" + title_long: "Usporiadať zoznam kategórií" + fix_order: "Pevné pozície" + fix_order_tooltip: "Nie všetky kategórie majú unikátne číslo pozície, čo môže zpôsobovať neočakávané výsledky." + save: "Ulož poradie" + apply_all: "Použi" + position: "Pozícia" + posts: "Príspevky" + topics: "Témy" + latest: "Najnovšie" + latest_by: "najnovšie podľa" + toggle_ordering: "zmeniť radenie" + subcategories: "Podkategórie" + topic_stats: "Počet nových tém." + topic_stat_sentence: + one: "%{count} nová téma za posledných %{unit}." + few: "%{count} nové témy za posledných %{unit}." + other: "%{count} nových tém za posledných %{unit}." + post_stats: "Počet nových príspevkov." + post_stat_sentence: + one: "%{count} nový príspevok za posledných %{unit}." + few: "%{count} nové príspevky za posledných %{unit}." + other: "%{count} nových príspevkov za posledných %{unit}." + ip_lookup: + title: Vyhľadávanie podľa IP adresy + hostname: Hostname + location: Lokácia + location_not_found: (neznáma) + organisation: Organizácia + phone: Telefón + other_accounts: "Ostatné účty s touto IP adresou:" + delete_other_accounts: "Zmazaných %{count}" + username: "používateľské meno" + trust_level: "Dôvera" + read_time: "čas strávený čítaním" + topics_entered: "založených tém" + post_count: "# príspevkov" + confirm_delete_other_accounts: "Ste si istý že chcete zmazať tieto účty?" + user_fields: + none: "(vyberte možnosť)" + user: + said: "{{username}}:" + profile: "Profil" + mute: "Ignorovať" + edit: "Upraviť nastavenia" + download_archive: "Stiahnutie mojich prípevkov" + new_private_message: "Nová správa" + private_message: "Správa" + private_messages: "Správy" + activity_stream: "Aktivita" + preferences: "Nastavenia" + expand_profile: "Rozbaľ" + bookmarks: "Záložky" + bio: "O mne" + invited_by: "Pozvaný od" + trust_level: "Stupeň dôvery" + notifications: "Upozornenia" + desktop_notifications: + label: "Upozornenia na pracovnej ploche" + not_supported: "Tento prehliadač nepodporuje upozornenia. Prepáčte." + perm_default: "Zapnúť upozornenia" + perm_denied_btn: "Prístup zamietnutý" + perm_denied_expl: "Notifikácie nie sú povolené. Povoľte notifikácie vo vašom prehliadači a potom kliknite na tlačidlo. (Desktop: ľavá krajná ikona v adresnom riadku. Mobil: 'Site info')" + disable: "Zakázať upozornenia" + currently_enabled: "(momentálne povolené)" + enable: "Povoliť upozornenia" + currently_disabled: "(momentálne nepovolené)" + each_browser_note: "Poznámka: Toto nastavenie musíte zmeniť v každom používanom prehliadači." + dismiss_notifications: "Označiť všetky ako prečítané" + dismiss_notifications_tooltip: "Označiť všetky neprečítané upozornenia ako prečítané" + disable_jump_reply: "Neskočiť na môj príspevok po odpovedi" + dynamic_favicon: "Zobraziť počet nových/upravených tém na ikone prehliadača" + edit_history_public: "Povoliť ostatným užívateľom zobrazenie všetkých verzií môjho príspevku" + external_links_in_new_tab: "Otvoriť všekty externé odkazy v novej záložke" + enable_quoting: "Umožniť odpoveď s citáciou z označeného textu" + change: "zmeniť" + moderator: "{{user}} je moderátor" + admin: "{{user}} je administrátor" + moderator_tooltip: "Tento používateľ je moderátor" + admin_tooltip: "Tento používateľ je administrátor" + blocked_tooltip: "Tento používateľ je zablokovaný" + suspended_notice: "Tento používateľ je suspendovaný do {{date}}" + suspended_reason: "Dôvod:" + github_profile: "Github" + mailing_list_mode: "Pošli mi email pri každom novom príspevku (pokiaľ neignorujem tému alebo kategóriu)" + watched_categories: "Sledované" + watched_categories_instructions: "Budete automaticky sledovať všetky nové témy v týchto kategóriách. Budete upozornený na všetky nové príspevky a témy, a zároveň bude vedľa témy zobrazený počet nových príspevkov." + tracked_categories: "Sledované" + tracked_categories_instructions: "Budete automaticky sledovať všetky nové témy v týchto kategóriách. Počet nových príspevkov sa bude zobraziť vedľa témy." + muted_categories: "Ignorovaný" + muted_categories_instructions: "Nebudete informovaní o udalostiach v nových témach týchto kategórií. Tieto témy sa zároveň nebudú zobrazovať v zozname posledných udalostí." + delete_account: "Vymazať môj účet" + delete_account_confirm: "Ste si istý, že chcete permanentne vymazať váš účet? Táto akcia je nenávratná." + deleted_yourself: "Váš účet bol úspešne vymazaný." + delete_yourself_not_allowed: "Momentálne nie je možné vymazať váš učet. Kontaktujte administrátora a ten vám ho vymaže." + unread_message_count: "Správy" + admin_delete: "Vymazať" + users: "Používatelia" + muted_users: "Ignorovaný" + muted_users_instructions: "Pozastaviť všetky notifikácie od týchto užívateľov." + muted_topics_link: "Zobraziť umlčané témy" + automatically_unpin_topics: "Automaticky zrušiť pripnutie témy ak sa dočítate na koniec." + staff_counters: + flags_given: "nápomocné značky" + flagged_posts: "označkované príspevky" + deleted_posts: "vymazané príspevky" + suspensions: "pozastavenia" + warnings_received: "upozornenia" + messages: + all: "Všetky" + mine: "Moje" + unread: "Neprečítané" + groups: "Moje skupiny" + change_password: + success: "(email odoslaný)" + in_progress: "(email sa odosiela)" + error: "(chyba)" + action: "Odoslať email na reset hesla" + set_password: "Nastaviť heslo" + change_about: + title: "Upraviť O mne" + error: "Pri úprave hodnoty nastala chyba" + change_username: + title: "Zmeniť užívateľské meno" + confirm: "Ak si zmeníte vaše užívateľské meno, všetky predchádajúce cítacie vašich príspevkov a @zmienok prestanú platiť. Ste si určite istý, že to chcete?" + taken: "Sorry, toto užívateľské meno je obsadené." + error: "Pri zmene užívateľského mena prišlo k chybe." + invalid: "Toto užívateľské meno nie je platné. Musí obsahovať iba znaky a čísla." + change_email: + title: "Zmeniť email" + taken: "Prepáčte, tento email je už obsadený." + error: "Pri zmene emailu nastala chyba. Je možné, že je email už použitý?" + success: "Na email sme odoslali správu. Nasledujte prosím inštrukcie pre potvrdenie." + change_avatar: + title: "Zmeniť váš profilový obrázok" + gravatar: "Gravatar, podľa" + gravatar_title: "Zmeňte váš avatar na webe Gravatar " + refresh_gravatar_title: "Obnoviť váš Gravatar" + letter_based: "Systém pridelil profilový obrázok" + uploaded_avatar: "Vlastný obrázok" + uploaded_avatar_empty: "Pridať vlastný obrázok" + upload_title: "Nahrať váš obrázok" + upload_picture: "Nahrať obrázok" + image_is_not_a_square: "Upozornenie: váš obrázok sme orezali; mal rozdielnu šírku a výšku" + cache_notice: "Váš profilový obrázok bol úspešne zmenený, ale jeho zobrazenie môže chvíľu trvať kvôli vyrovnávacej pamäti prehliadača." + change_profile_background: + title: "Pozadie profilu" + instructions: "Pozadie profilu bude vystredené a s predvolenou šírkou 850px." + change_card_background: + title: "Pozadie karty používateľa" + instructions: "Obrázky pozadia budú vystredené a s predvolenou šírkou 590px." + email: + title: "Email" + instructions: "Nikdy verejne nezobrazené" + ok: "Pošleme vám email pre potvrdenie" + invalid: "Zadajte prosím platný email" + authenticated: "Váš email bude autentifikovaný pomocou {{provider}}" + frequency_immediately: "Odošleme vám email ak ste neprečítali to, čo vám posielame emailom." + frequency: + one: "Odošleme vám email iba ak sme vás nevideli poslednú minútu" + few: "Odošleme vám email iba ak sme vás nevideli posledné {{count}} minúty." + other: "Odošleme vám email iba ak sme vás nevideli posledných {{count}} minút" + name: + title: "Meno" + instructions: "Vaše celé meno (nepovinné)" + instructions_required: "Vaše celé meno" + too_short: "Vaše meno je prikrátke" + ok: "Vaše meno je v poriadku" + username: + title: "Užívateľské meno" + instructions: "Unikátne, bez medzier, krátke" + short_instructions: "Ostatní vás môžu označiť ako @{{username}}" + available: "Vaše užívateľské meno je voľné" + global_match: "Email zodpovedá registrovanému užívateľskému menu" + global_mismatch: "Už zaregistrované. Skúste {{suggestion}}?" + not_available: "Nie je k dispozícii. Skúste {{suggestion}}?" + too_short: "Vaše užívateľské meno je prikrátke" + too_long: "Vaše užívateľské meno je pridlhé" + checking: "Kontrolujeme dostupnosť užívateľského meno" + enter_email: 'Užívateľské meno nájdené, zadajte zodpovedajúci email' + prefilled: "Email zodpovedá tomuto registrovanému užívateľskému menu" + locale: + title: "Jazyk rozhrania" + instructions: "Jazyk úžívateľského rozhrania. Zmení sa po obnovení stránky." + default: "(predvolené)" + password_confirmation: + title: "Heslo znova" + last_posted: "Posledný príspevok" + last_emailed: "Posledný odemailovaný" + last_seen: "Videný" + created: "Spojený" + log_out: "Odhlásiť sa" + location: "Poloha" + card_badge: + title: "Odznak karty užívateľa" + website: "Webová stránka" + email_settings: "Email" + email_digests: + title: "Ak to tu nenavštívim, pošlite mi emailový súhrn s novinkami:" + daily: "denne" + every_three_days: "každé tri dni" + weekly: "týždenne" + every_two_weeks: "každé dva týždne" + email_direct: "Pošlite mi email ak ma niekto cituje, odpovie na môj príspevok, spomenie moje @užívateľské meno alebo ma pozve do témy." + email_private_messages: "Pošlite mi email keď mi niekto pošle správu" + email_always: "Pošlite mi emailovú notifikáciu aj keď som aktívny na stránke" + other_settings: "Ostatné" + categories_settings: "Kategórie" + new_topic_duration: + label: "Považuj témy za nové keď" + not_viewed: "Ešte som ich nevidel" + last_here: "vytvorené odkedy som tu bol naposledy" + after_1_day: "vytvorené za posledný deň" + after_2_days: "vytvorené posledné 2 dni" + after_1_week: "vytvorené za posledný týždeň" + after_2_weeks: "vytvorené za posledné 2 týždne" + auto_track_topics: "Automaticky sleduj témy, do ktorých vstúpim" + auto_track_options: + never: "nikdy" + immediately: "ihneď" + after_30_seconds: "po 30 sekundách" + after_1_minute: "po 1 minúte" + after_2_minutes: "po 2 minútach" + after_3_minutes: "po 3 minútach" + after_4_minutes: "po 4 minútach" + after_5_minutes: "po 5 minútach" + after_10_minutes: "po 10 minútach" + invited: + search: "začni písať pre hľadanie pozvánok" + title: "Pozvánky" + user: "Pozvaný užívateľ" + sent: "Odoslané" + none: "Nemáte žiadne čakajúce pozvánky." + truncated: + one: "Zobrazuje sa prvá pozvánka." + few: "Zobrazujú sa prvé {{count}} pozvánky." + other: "Zobrazuje sa prvých {{count}} pozvánok." + redeemed: "Použité pozvánky" + redeemed_tab: "Použitá" + redeemed_tab_with_count: "Použité ({{count}})" + redeemed_at: "Použitá" + pending: "Čakajúce pozvánky" + pending_tab: "Čakajúca" + pending_tab_with_count: "Čakajúce ({{count}})" + topics_entered: "Zobrazených tém" + posts_read_count: "Prečítaných príspevkov" + expired: "Táto pozvánka vypršala." + rescind: "Odstrániť" + rescinded: "Pozvánka odstránená" + reinvite: "Poslať pozvánku znovu" + reinvited: "Poslať pozvánku znovu" + time_read: "Doba čítania" + days_visited: "Dní na stránke" + account_age_days: "Vek účtu v dňoch" + create: "Poslať Pozvánku" + generate_link: "Kopírovať Odkaz Pozvánky" + generated_link_message: '

    Odkaz pre pozvánku bol úspešne vytvorený!

    Odkaz je platný iba pre túto adresu: %{invitedEmail}

    ' + bulk_invite: + none: "Zatiaľ ste tu nikoho nepozvali. Môžete odosielať pozvánky individuálne alebo pozvať skupinu ľudí naraz pomocou nahratia súboru." + text: "Hromadná pozvánka zo súboru" + uploading: "Prebieha nahrávanie..." + success: "Súbor bol úspešne odoslaný. Keď sa nahrávanie dokončí, budete na to upozornený cez správu." + error: "Pri nahrávaní '{{filename}}' sa vyskytla chyba: {{message}}" + password: + title: "Heslo" + too_short: "Vaše heslo je príliš krátke." + common: "Toto heslo je príliš časté." + same_as_username: "Vaše heslo je rovnaké ako používateľské meno." + same_as_email: "Vaše heslo je rovnaké ako e-mail." + ok: "Vaše heslo je v poriadku." + instructions: "Minimálne %{count} znakov." + associated_accounts: "Prihlásenia" + ip_address: + title: "Posledná IP adresa" + registration_ip_address: + title: "IP adresa pri registrácii" + avatar: + title: "Profilový obrázok" + header_title: "profil, správy, záložky a nastavenia" + title: + title: "Názov" + filters: + all: "Všetky" + stream: + posted_by: "Autor príspevku" + sent_by: "Odoslané používateľom" + private_message: "správa" + the_topic: "téma" + loading: "Prebieha načítavanie..." + errors: + prev_page: "pri pokuse o načítanie" + reasons: + network: "Chyba siete" + server: "Chyba na serveri" + forbidden: "Prístup zamietnutý" + unknown: "Chyba" + not_found: "Stránka nenájdená" + desc: + network: "Skontrolujte, prosím, svoje pripojenie." + network_fixed: "Zdá sa, že sme späť." + server: "Kód chyby: {{status}}" + forbidden: "Nemáte oprávnenie na zobrazenie." + not_found: "Hopla, aplikácia sa pokúsila načítať adresu, ktorá neexistuje." + unknown: "Niečo sa pokazilo." + buttons: + back: "Späť" + again: "Skúsiť znova" + fixed: "Načítať stránku" + close: "Zatvoriť" + assets_changed_confirm: "Táto stránka bola práve aktualizovaná. Obnoviť na najnovšiu verziu?" + logout: "Boli ste odhlásený." + refresh: "Obnoviť" + read_only_mode: + enabled: "Je zapnuté mód na čítanie. Môžete prezerať stránku, ale akákoľvek interakcia nemusí fungovať." + login_disabled: "Keď je zapnutý mód iba na čítanie, prihlásenie nie je možné." + too_few_topics_and_posts_notice: "Začnime diskusiu! Je tu %{currentTopics} / %{requiredTopics} tém a %{currentPosts} / %{requiredPosts} príspevkov. Noví návštevníci potrebujú mať témy, ktoré môžu čítať a na ktoré budú reagovať." + too_few_topics_notice: "Začnime diskusiu! Je tu %{currentTopics} / %{requiredTopics} tém. Noví návštevníci potrebujú mať témy, ktoré môžu čítať a na ktoré budú reagovať." + too_few_posts_notice: "Začnime diskusiu! Je tu %{currentPosts} / %{requiredPosts} príspevkov. Noví návštevníci potrebujú mať témy, ktoré môžu čítať a na ktoré budú reagovať." + learn_more: "zistiť viac..." + year: 'rok' + year_desc: 'témy vytvorené za posledných 365 dní' + month: 'mesiac' + month_desc: 'témy vytvorené za posledných 30 dní' + week: 'týždeň' + week_desc: 'témy vytvorené za posledných 7 dní' + day: 'deň' + first_post: Prvý príspevok + mute: Ignorovať + unmute: Prestať ignorovať + last_post: Posledný príspevok + last_reply_lowercase: posledná odpoveď + replies_lowercase: + one: odpoveď + few: odpovede + other: odpovedí + signup_cta: + sign_up: "Registrovať sa" + hide_session: "Pripomenúť zajtra" + hide_forever: "nie, ďakujem" + hidden_for_session: "Fajn, opýtam sa vás to zajtra. Stále môžete na vytvorenie účtu použiť aj možnosť 'Prihlásenie'." + intro: "Zdravím! :heart_eyes: Vyzerá, že sa vám diskusia páči, ale stále nie ste prihlásený na svojom účte." + value_prop: "Keď si vytvoríte účet, zapamätáme si čo ste čítali, takže sa môžete vrátiť presne tam, kde ste prestali. Okrem toho dostanete upozornenie tu, aj na váš e-mail, vždy keď pribudnú nové príspevky. A môžete označiť príspevky ktoré sa vám páčia. :heartbeat:" + summary: + enabled_description: "Pozeráte sa na zhrnutie tejto témy: najzaujímavejšie príspevky podľa výberu komunity." + description: "Je tu {{count}} odpovedí." + description_time: "Je tu {{count}} odpovedí s priemerným časom čítania {{readingTime}} minút." + enable: 'Zhrnutie tejto témy' + disable: 'Zobraziť všetky príspevky' + deleted_filter: + enabled_description: "Táto téma obsahuje zmazané príspevky, ktoré boli skryté." + disabled_description: "Zmazané príspevky sa v téme zobrazujú." + enable: "Skryť zmazané príspevky" + disable: "Zobraziť zmazané príspevky" + private_message_info: + title: "Správa" + invite: "Pozvať ostatných..." + remove_allowed_user: "Skutočne chcete odstrániť {{name}} z tejto správy?" + email: 'Email' + username: 'Používateľské meno' + last_seen: 'Videné' + created: 'Vytvorené' + created_lowercase: 'vytvorené' + trust_level: 'Stupeň dôvery' + search_hint: 'používateľské meno, email alebo IP adresa' + create_account: + title: "Vytvoriť nový účet" + failed: "Niečo sa pokazilo, možno je tento e-mail už registrovaný, vyskúšajte odkaz pre zabudnuté heslo" + forgot_password: + title: "Obnovenie hesla" + action: "Zabudol som svoje heslo" + invite: "Napíšte vaše používateľské meno alebo e-mailovú adresu a pošleme vám e-mail pre obnovu hesla." + reset: "Obnoviť heslo" + complete_username: "Ak je účet priradený k používateľskému menu %{username}, čoskoro dostanete e-mail s pokynmi, ako si obnoviť svoje heslo." + complete_email: "Ak je účet priradený k %{email}, čoskoro dostanete e-mail s pokynmi, ako si obnoviť svoje heslo." + complete_username_found: "Našli sme účet priradený k používateľskému menu %{username}, čoskoro dostanete e-mail s pokynmi, ako si obnoviť svoje heslo." + complete_email_found: "Našli sme účet priradený k %{email}, čoskoro dostanete e-mail s pokynmi, ako si obnoviť svoje heslo." + complete_username_not_found: "Žiadny účet nemá priradené používateľské meno %{username}" + complete_email_not_found: "Žiadny účet nie je s e-mailom %{email}" + login: + title: "Prihlásenie" + username: "Používateľ" + password: "Heslo" + email_placeholder: "e-mail alebo používateľské meno" + caps_lock_warning: "Caps Lock je zapnutý" + error: "Neznáma chyba" + rate_limit: "Pred opätovným prihlásením chvíľku počkajte." + blank_username_or_password: "Zadajte prosím svoj e-mail alebo používateľské meno a heslo." + reset_password: 'Obnoviť heslo' + logging_in: "Prebieha prihlásenie..." + or: "Alebo" + authenticating: "Prebieha overovanie..." + awaiting_confirmation: "Váš účet čaká na aktiváciu. Ak chcete zaslať aktivačný e-mail znova, použite odkaz pre zabudnuté heslo." + awaiting_approval: "Váš účet zatiaľ nebol schválený členom tímu. Keď bude schválený, dostanete e-mail." + requires_invite: "Prepáčte, ale prístup k tomuto fóru majú iba pozvaní používatelia." + not_activated: "Systém vás nemôže prihlásiť. Na emailovú adresu {{sentTo}} sme vám poslali aktivačný email. Prosím, postupujte podľa inštrukcií na aktiváciu účtu, ktoré sú uvedené v tomto emaile." + not_allowed_from_ip_address: "Nie je možné prihlásenie z tejto IP adresy." + admin_not_allowed_from_ip_address: "Nie je možné prihlásenie ako admin z tejto IP adresy." + resend_activation_email: "Kliknite sem pre opätovné odoslanie aktivačného emailu." + sent_activation_email_again: "Odoslali sme vám ďalší aktivačný email na {{currentEmail}}. Môže trvať niekoľko minút kým príde; pre istotu si skontrolujte spamový priečinok." + to_continue: "Prosím, prihláste sa" + preferences: "Na zmenu používateľských nastavení musíte byť prihlásený." + forgot: "Nepamätám si detaily môjho účtu" + google: + title: "pomocou Google" + message: "Prihlásenie pomocou Google účtu (prosím uistite sa, že vyskakovacie okná sú povolené)" + google_oauth2: + title: "pomocou Google" + message: "Prihlásenie pomocou Google účtu (prosím uistite sa, že vyskakovacie okná sú povolené)" + twitter: + title: "pomocou Twitter účtu" + message: "Prihlásenie pomocou Twitter účtu (prosím uistite sa, že vyskakovacie okná sú povolené)" + facebook: + title: "pomocou stránky Facebook" + message: "Prihlásenie pomocou Facebook účtu (prosím uistite sa, že vyskakovacie okná sú povolené)" + yahoo: + title: "pomocou Yahoo" + message: "Prihlásenie pomocou Yahoo účtu (prosím uistite sa, že vyskakovacie okná sú povolené)" + github: + title: "pomocou GitHub" + message: "Prihlásenie pomocou GitHub účtu (prosím uistite sa, že vyskakovacie okná sú povolené)" + apple_international: "Apple/Medzinárodné" + google: "Google" + twitter: "Twitter" + emoji_one: "Emoji One" + shortcut_modifier_key: + shift: 'Shift' + ctrl: 'Ctrl' + alt: 'Alt' + composer: + emoji: "Emoji :smile:" + more_emoji: "viac ..." + options: "Možnosti" + whisper: "šepot" + add_warning: "Toto je oficiálne varovanie." + toggle_whisper: "Prepnúť šepot" + posting_not_on_topic: "Na akú tému chcete odpovedať?" + saving_draft_tip: "ukladám..." + saved_draft_tip: "uložené" + saved_local_draft_tip: "uložené lokálne" + similar_topics: "Vaša téma je podobná..." + drafts_offline: "offline koncepty" + group_mentioned: "Použitím tejto {{group}} , upozorníte {{count}} ľudí." + error: + title_missing: "Názov je povinný" + title_too_short: "Názov musí mať minimálne {{min}} znakov" + title_too_long: "Názov nesmie byť dlhší než {{max}} znakov" + post_missing: "Príspevok nesmie byť prázdny" + post_length: "Príspevok musí mať minimálne {{min}} znakov" + try_like: 'Skúsili ste tlačítko?' + category_missing: "Musíte vybrať kategóriu" + save_edit: "Uložiť úpravy" + reply_original: "Odpovedať na pôvodnú tému" + reply_here: "Odpovedať tu" + reply: "Odpovedať" + cancel: "Zrušiť" + create_topic: "Vytvoriť tému" + create_pm: "Správa" + title: "Alebo stlačte Ctrl+Enter" + users_placeholder: "Pridať používateľa" + title_placeholder: "O čom je této diskusia v jednej stručnej vete?" + edit_reason_placeholder: "prečo upravujete?" + show_edit_reason: "(pridajte dôvod úpravy)" + reply_placeholder: "Píšte sem. Formátujte pomocou Markdown, BBCode alebo HTML. Pretiahnite alebo vložte obrázky." + view_new_post: "Zobraziť nový príspevok." + saving: "Ukladanie" + saved: "Uložené!" + saved_draft: "Návrh príspevku rozpracovaný. Vyberte na obnovenie." + uploading: "Upload prebieha..." + show_preview: 'zobraziť náhľad »' + hide_preview: '» skryť náhľad' + quote_post_title: "Citovať celý príspevok" + bold_title: "Výrazne" + bold_text: "výrazný text" + italic_title: "Zdôraznene" + italic_text: "zdôraznený text" + link_title: "Hyperlink" + link_description: "tu zadaj popis odkazu" + link_dialog_title: "Vložte hyperlink" + link_optional_text: "nepovinný názov" + link_placeholder: "http://example.com \"voliteľný text\"" + quote_title: "Úvodzovky" + quote_text: "Úvodzovky" + code_title: "Preformátovaný text" + code_text: "Odsaďte preformátovaný text 4 medzerami" + upload_title: "Upload" + upload_description: "tu zadajte popis uploadu" + olist_title: "Číslované odrážky" + ulist_title: "Odrážky" + list_item: "Položka zoznamu" + heading_title: "Nadpis" + heading_text: "Nadpis" + hr_title: "Horizonálny oddeľovač" + help: "Nápoveda úprav pre Markdown" + toggler: "skryť alebo zobraziť panel úprav" + modal_ok: "OK" + modal_cancel: "Zrušiť" + cant_send_pm: "Ľutujeme, nemôžete poslať správu pre %{username}." + admin_options_title: "Nepovinné zamestnanecké nastavenia pre túto tému" + auto_close: + label: "Čas na automatické uzavretie témy:" + error: "Prosím zadajte platnú hodnotu." + based_on_last_post: "Nezatvárať pokým posledný príspevok v téme nie je takto starý." + all: + examples: 'Zadajte číslo v hodinách (24), absolútny čas (17:30) alebo časovú značku (2013-11-22 14:00).' + limited: + units: "(# hodín)" + examples: 'Zadajte počet hodín (24).' + notifications: + title: "oznámenia o zmienkach pomocou @name, odpovede na vaše príspevky a témy, správy atď." + none: "Notifikácie sa nepodarilo načítať" + more: "zobraziť staršie upozornenia" + total_flagged: "označených príspevkov celkom" + mentioned: "

    {{username}} {{description}}

    " + group_mentioned: "

    {{username}} {{description}}

    " + quoted: "

    {{username}} {{description}}

    " + replied: "

    {{username}} {{description}}

    " + posted: "

    {{username}} {{description}}

    " + edited: "

    {{username}} {{description}}

    " + liked: "

    {{username}} {{description}}

    " + private_message: "

    {{username}} {{description}}

    " + invited_to_private_message: "

    {{username}} {{description}}

    " + invited_to_topic: "

    {{username}} {{description}}

    " + invitee_accepted: "

    {{username}} prijal Vaše pozvanie

    " + moved_post: "

    {{username}} presunul {{description}}

    " + linked: "

    {{username}} {{description}}

    " + granted_badge: "

    Získal '{{description}}'

    " + alt: + mentioned: "Spomenutý od" + quoted: "Citovaný od" + replied: "Odpovedané" + posted: "Príspevok od" + edited: "Upravte Váš príspevok do" + liked: "Váš príspevok sa páčil" + private_message: "Súkromná správa od" + invited_to_private_message: "Pozvaný na súkromné správy od" + invited_to_topic: "Pozvaný k téme od" + invitee_accepted: "Pozvánka akceptovaná " + moved_post: "Váš príspevok bol presunutý " + linked: "Odkaz na váš príspevok" + granted_badge: "Priznaný odznak" + popup: + mentioned: '{{username}} vás spomenul v "{{topic}}" - {{site_title}}' + quoted: '{{username}} vás citoval v "{{topic}}" - {{site_title}}' + replied: '{{username}} vám odpovedal v "{{topic}}" - {{site_title}}' + posted: '{{username}} prispel v "{{topic}}" - {{site_title}}' + private_message: '{{username}} vám poslal súkromnú správu v "{{topic}}" - {{site_title}}' + linked: '{{username}} odkázal na váš príspevok z "{{topic}}" - {{site_title}}' + upload_selector: + title: "Pridať obrázok" + title_with_attachments: "Pridať obrázok alebo súbor" + from_my_computer: "Z počítača" + from_the_web: "Z webu" + remote_tip: "odkaz na obrázok" + remote_tip_with_attachments: "odkaz na obrázok alebo súbor {{authorized_extensions}}" + local_tip: "zvoľte obrázok z vášho počítača" + local_tip_with_attachments: "zvoľte obrázok alebo súbor z vášho počítača {{authorized_extensions}}" + hint: "(môžete taktiež potiahnuť a pustiť do editora pre nahratie)" + hint_for_supported_browsers: "môžete taktiež potiahnuť a pustiť alebo priložiť obrázky do editora" + uploading: "Nahrávanie" + select_file: "Zvoľte súbor" + image_link: "adresa na ktorú bude odkazovať váš obrázok" + search: + sort_by: "Zoradiť podľa" + relevance: "Relevancia" + latest_post: "Najnovší príspevok" + most_viewed: "Najviac prezerané" + most_liked: "Najviac sa páčia" + select_all: "Označ všetky" + clear_all: "Odznač všetky" + result_count: + one: "1 výsledok pre \"{{term}}\"" + few: "{{count}} výsledky pre \"{{term}}\"" + other: "{{count}} výsledkov pre \"{{term}}\"" + title: "hľadaj témy, príspevky, užívateľov, alebo kategórie" + no_results: "Žiadne výsledky" + no_more_results: "Nenašlo sa viac výsledkov" + search_help: Pomoc pri vyhľadávaní + searching: "Vyhľadávam....." + post_format: "#{{post_number}} podľa {{username}}" + context: + user: "Vyhľadávanie podľa @{{username}}" + category: "Vyhľadávanie podľa \"{{category}}\" kategórie" + topic: "Hľadaj v tejto téme" + private_messages: "Hľadaj správy" + hamburger_menu: "prejsť na iné témy, alebo kategórie" + new_item: "nový" + go_back: 'späť' + not_logged_in_user: 'užívateľská stránka so súhrnom aktivít a nastavení' + current_user: 'prejsť na Vašu uťívateľskú stránku' + topics: + bulk: + unlist_topics: "Dôverné témy" + reset_read: "Obnoviť prečítané" + delete: "Zmazať témy" + dismiss: "Zahodiť" + dismiss_read: "Zahodiť všetký neprečítané" + dismiss_button: "Zahadzujem....." + dismiss_tooltip: "Zahoď nové príspevky, alebo prestaň sledovať témy" + also_dismiss_topics: "Prestať sledovať tieto témy. Už sa nikdy nebudu ukazovať medzi neprečítanými" + dismiss_new: "Zahodiť. Nová" + toggle: "prepnuť hromadne vybrané témy" + actions: "Hromadné akcie" + change_category: "Zmeň kategóriu" + close_topics: "Uzavrieť tému" + archive_topics: "Archivuj témy" + notification_level: "Zmeň úroveň upozorňovania" + choose_new_category: "Vyberte pre tému novú kategóriu:" + selected: + one: "Označíli ste 1 tému." + few: "Označíli ste {{count}} tém.y" + other: "Označíli ste {{count}} tém." + none: + unread: "Nemáte neprečítanú tému" + new: "Nemáte žiadnu novú tému" + read: "Neprečítali ste ešte žiadnu tému." + posted: "Nanapísali ste ešte žiadnu tému." + latest: "Nie sú žiadne nové témy. To je smutné." + hot: "Nie sú žiadne horúce témy." + bookmarks: "Nemáte žiadne témy v záložke" + category: "V kategórii {{category}} nie je žiadna téma" + top: "Nie sú žiadne populárne témy." + search: "Nenašli sa žiadne výsledky" + educate: + new: '

    Tu sa zobrazí Vaša nová téma.

    V predvolenom nastavení sú témy považované za nové a zobrazia sa s príznakom nová pokiaľ boli vytvorené za posledné 2 dni.

    Môžte to zmeniť vo Vašich nastaveniach.

    ' + unread: '

    Tu sa zobrazia Vaše neprečítané témy.

    V predvolenom nastavení sú témy považované za nové a zobrazí sa počet neprečítaných1 Ak ste:

    • Vytvorili tému
    • Odpovedali na tému
    • Čítali tému viac ako 4 minúty

    Alebo ste nastavili na tému Sledovať alebo Pozorovať prostredníctvom ovládania upozornení na konci každej témy.

    Môžte to zmeniť vo Vašich nastaveniach.

    ' + bottom: + latest: "Nie je už viac najnovšich tém." + hot: "Nie je už viac horúcich tém" + posted: "Žiadne ďalšie témy na čítanie." + read: "Žiadne ďalšie prečítané témy." + new: "Žiadne nové témy." + unread: "Žiadne ďalšie neprečítané témy." + category: "Žiadne ďalšie témy v {{category}}." + top: "Nie je už viac poulárnych tém" + bookmarks: "Žiadne ďalšie témy v záložkách." + search: "Nenašlo sa viac výsledkov." + topic: + unsubscribe: + stop_notifications: "Teraz budete dostávať menej upozornení na {{title}}" + change_notification_state: "Váš súčasný stav upozornení je" + filter_to: "{{post_count}} príspevkov k téme" + create: 'Nová téma' + create_long: 'Vytvoriť novú tému' + private_message: 'Vytvoríť správu' + list: 'Témy' + new: 'nová téma' + unread: 'neprečítané' + new_topics: + one: '1 nová téma' + few: '{{count}} nové témy' + other: '{{count}} nových tém' + unread_topics: + one: '1 neprečítaná téma' + few: '{{count}} neprečítané témy' + other: '{{count}} neprečítaných tém' + title: 'Témy' + invalid_access: + title: "Téma je súkromná" + description: "Prepáčte, nemáte prístup k tejto téme!" + login_required: "Musíte sa prihlásiť, aby ste videli túto tému." + server_error: + title: "Tému sa nepodarilo načítať" + description: "Prepáčte, nepodarllo sa nám načítať túto tému, možno je problém s Vaším pripojením. Prosim skúste znova. Ak problém pretrváva, dajte nám vedieť" + not_found: + title: "Téma sa nenašla" + description: "Prepáčte, hľadaná téma nebola nájdená. Nebola odstránená moderátorom?" + total_unread_posts: + one: "máte 1 neprečítaný príspevok k tejto téme" + few: "máte {{count}} neprečítanépríspevky k tejto téme" + other: "máte {{count}} neprečítaných príspevkov k tejto téme" + unread_posts: + one: "máte 1 starší neprečítaný príspevok k tejto téme" + few: "máte {{count}} staršie neprečítané príspevky k tejto téme" + other: "máte {{count}} starších neprečítaných príspevkov k tejto téme" + new_posts: + one: "pribudol 1 nový príspevok odkedy ste čítali túto tému naposledy " + few: "pribudlo {{count}} nové príspevky odkedy ste čítali túto tému naposledy " + other: "pribudlo {{count}} nových príspevkov odkedy ste čítali túto tému naposledy " + likes: + one: "v tejto téme je jedo \"Páči sa\"" + few: "v tejto téme je {{count}} \"Páči sa\"" + other: "v tejto téme je {{count}} \"Páči sa\"" + back_to_list: "Naspäť na zoznam tém" + options: "Možnosti tém" + show_links: "zobrazovať odkazy v tejto téme" + toggle_information: "zmeniť detaily témy" + read_more_in_category: "Chcete si prečítať viac? Prezrite si témy v {{catLink}} alebo v {{latestLink}}." + read_more: "Chcete si prečítať viac? {{catLink}} alebo v {{latestLink}}." + read_more_MF: "Máte { UNREAD, plural, =0 {} one { 1 neprečítanú } other { # neprečítané } } { NEW, plural, =0 {} one { {BOTH, select, true{a} false { } other{}} 1 novú tému} other { {BOTH, select, true{a } false {} other{}} # nových tém} } na prečítanie, prípadne {CATEGORY, select, true {si pozrite iné témy v {catLink}} false {{latestLink}} other {}}" + browse_all_categories: Prezrieť všetky kategórie + view_latest_topics: zobraziť najnošie témy + suggest_create_topic: Čo tak vytvoriť novú tému? + jump_reply_up: prejsť na predchádzajúcu odpoveď + jump_reply_down: prejsť na nasledujúcu odpoveď + deleted: "Téma bola vymazaná" + auto_close_notice: "Táto téma bude automaticky uzavretá o %{timeLeft}." + auto_close_notice_based_on_last_post: "Táto téma bude uzavretá %{duration} po poslednej odpovedi." + auto_close_title: 'Nastavenia automatického zatvárania' + auto_close_save: "Uložiť" + auto_close_remove: "Neuzatvárať túto tému automaticky" + progress: + title: pozícia v téme + go_top: "na začiatok" + go_bottom: "na spodok" + go: "Choď" + jump_bottom: "choď na posledný príspevok" + jump_bottom_with_number: "choď na príspevok číslo %{post_number}" + total: Všetkých príspevkov + current: tento príspevok + position: "%{current} príspevok z %{total}" + notifications: + reasons: + '3_6': 'Budete dostávať upozornenia, pretože sa pozeráte na túto kategóriu.' + '3_5': 'Budete automaticky dostávať upozornenie pretože ste začali pozorovať túto tému.' + '3_2': 'Budete dostávať upozornenia, pretože sa pozeráte na túto tému.' + '3_1': 'Budete dostávať upozornenia, pretože ste vytvorili túto tému.' + '3': 'Budete dostávať upozornenia, pretože sa pozeráte na túto tému.' + '2_8': 'Budete dostávať upozornenia, pretože sledujete túto kategóriu.' + '2_4': 'Budete dostávať upozornenia, pretože ste zaslali odpoveď na túto tému.' + '2_2': 'Budete dostávať upozornenia, pretože sledujete túto tému.' + '2': 'Budete dostávať upozornenia, pretože ste čítali túto tému.' + '1_2': 'Budete upozornený ak niekto spomenie Vaše @meno alebo Vám odpovie.' + '1': 'Budete upozornený ak niekto spomenie Vaše @meno alebo Vám odpovie.' + '0_7': 'Ignorujete všetky upozornenia v tejto kategórii.' + '0_2': 'Ignorujete všetky upozornenia k tejto téme.' + '0': 'Ignorujete všetky upozornenia k tejto téme.' + watching_pm: + title: "Pozerať" + description: "Budete upozornený na každú novu dopoveť na túto správu, a zobrazí sa počet nových odpovedí" + watching: + title: "Pozerať" + description: "Budete upozornený na každú novu dopoveť na túto tému, a zobrazí sa počet nových odpovedí" + tracking_pm: + title: "Sledovať" + description: "Zobrazí počet nových odpovedí na túto správu. Budete upozornený ak niekto spomenie Vaše @meno alebo Vám odpovie." + tracking: + title: "Sledovať" + description: "Zobrazí počet nových odpovedí na túto tému. Budete upozornený ak niekto spomenie Vaše @meno alebo Vám odpovie." + regular: + title: "Bežný" + description: "Budete upozornený ak niekto spomenie Vaše @meno alebo Vám odpovie." + regular_pm: + title: "Bežný" + description: "Budete upozornený ak niekto spomenie Vaše @meno alebo Vám odpovie." + muted_pm: + title: "Stíšené" + description: "Nikdy nebudete upozornení na nič ohľadom tejto správy" + muted: + title: "Stíšené" + description: "Nikdy nebudete upozornení na nič ohľadom tejto témy, a nebude sa zobrazovať medzi najnovšími." + actions: + recover: "Obnoviť zmazanú tému" + delete: "Zmazať tému" + open: "Otvoriť tému" + close: "Uzavrieť tému" + multi_select: "Označ príspevky...." + auto_close: "Automaticky zatvor...." + pin: "Pripni tému...." + unpin: "Odopni tému...." + unarchive: "Zruš archiváciu témy" + archive: "Archívuj tému" + invisible: "Skyť" + visible: "Zobraziť" + reset_read: "Zrušiť načítané údaje" + feature: + pin: "Pripni tému" + unpin: "Odopni tému" + pin_globally: "Pripni tému globálne" + make_banner: "Banerová téma" + remove_banner: "Odstrániť banerovú tému" + reply: + title: 'Odpovedať' + help: 'vytvor odpoveď k tejto téme' + clear_pin: + title: "Zruš pripnutie" + help: "Zruší pripnutie tejto témy takže sa už viac nebude objavovať na vrchu Vášho zoznamu tém" + share: + title: 'Zdielaj' + help: 'zdieľaj odkaz na túto tému' + flag_topic: + title: 'Označ' + help: 'súkromne označiť túto tému do pozornosti, alebo na ňu poslať súkromne upozornenie' + success_message: 'Úspešne ste označili tému.' + feature_topic: + title: "Vyzdvihni túto tému" + pin: "Zobrazuj túto tému na vrchu {{categoryLink}} kategórie do" + confirm_pin: "Máte už {{count}} pripnutých tém. Príliš veľa pripnutých tém môže byť na príťaž pre nových a anonymných užívateľov. Ste si istý že chcete pripnúť ďalšiu tému v tejto kategórii?" + unpin: "Zruš túto tému z vrcholu kategórie {{categoryLink}} . " + unpin_until: "Zruš túto tému z vrcholu kategórie {{categoryLink}} , alebo počkaj do %{until}." + pin_note: "Užívatelia si môžu sami odopnúť tému " + pin_validation: "Dátum je vyžadovaný k pripnutiu tejto témy." + not_pinned: "V {{categoryLink}} nie sú pripnuté žiadne témy." + already_pinned: + one: "Téma pripnutá k {{categoryLink}}: 1" + few: "Témy pripnuté ku {{categoryLink}}: {{count}}" + other: "Tém pripnutých k {{categoryLink}}: {{count}}" + pin_globally: "Zobrazuj túto tému na vrchu všetkých zoznamov tém do" + confirm_pin_globally: "Máte už {{count}} globálne pripnutých tém. Príliš veľa pripnutých tém môže byť na príťaž pre nových a anonymných užívateľov. Ste si istý že chcete pripnúť ďalšiu globálnu tému?" + unpin_globally: "Zruš túto tému z vrcholu všetkých zoznamov tém. " + unpin_globally_until: "Zruš túto tému z vrcholu všetkých zoznamov tém, alebo počkaj do %{until}." + global_pin_note: "Užívatelia si môžu sami odopnúť tému." + not_pinned_globally: "Nie sú pripnuté žiadne globálne témy." + already_pinned_globally: + one: "Globálne pripnutá téma : 1" + few: "Globálne pripnuté témy : {{count}}" + other: "Globálne pripnutých tém : {{count}}" + make_banner: "Spraviť z tejto témy baner, ktorý sa zobrazí navrchu každej stránky." + remove_banner: "Odstrániť baner, ktorý sa zobrazuje navrchu každej stránky." + banner_note: "Užívatelia môžu banner kedykoľvek zrušiť. Bannerom môže byť v jednom momente len jedna téma." + no_banner_exists: "Neexistuje žiadna banerová téma." + banner_exists: "Banerová téma je aktuálne nastavená." + inviting: "Pozývam..." + automatically_add_to_groups_optional: "Táto pozvánka obsahuje taktiež prístup do týchto skupín: ( nepovinné, iba správca) " + automatically_add_to_groups_required: "Táto pozvánka obsahuje taktiež prístup do týchto skupín: ( Povinné, iba správca) " + invite_private: + title: 'Pozvať do konverzácie' + email_or_username: "Email, alebo užívateľské meno pozvaného" + email_or_username_placeholder: "emailova adresa alebo uťívateľské meno" + action: "Pozvi" + success: "Pozvali sme tohoto uťívateľa aby sa podieľal na tejto správe" + error: "Prepáčte, pri pozývaní tohto užívateľa nastala chyba." + group_name: "názov skupiny" + invite_reply: + title: 'Pozvi' + username_placeholder: "používateľské meno" + action: 'Pošli pozvánku' + help: 'pozvite ostatných k tejto téme prostredníctvom emailu, alebo upozornení' + to_forum: "Pošleme krátký email dovoľujúci Vášmu priateľovi okamžité pripojenie kliknutím na odkaz bez potreby prihlasovania" + sso_enabled: "Zadajte uťívateľské meno osoby, ktorú by ste radi pozvali k tejto téme" + to_topic_blank: "Zadajte uťívateľské meno alebo email osoby, ktorú by ste radi pozvali k tejto téme" + to_topic_email: "Zadali ste emailovú adresu. Pošleme pozvánku ktorá umožní Vášmu priateľovi okamžitú odpoveď k tejto téme." + to_topic_username: "Zadali ste užívateľské meno. Pošleme mu pozvánku s odkazom na túto tému." + to_username: "Zadajte užívateľské meno osoby, ktorú chcete pozvať. Pošleme mu pozvánku s odkazom na túto tému." + email_placeholder: 'name@example.com' + success_email: "Poslali sme email s pozvánkou na {{emailOrUsername}}. Upozorníme vas keď bude pozvánka použítá. Svoje pozvánky môžte sledovať v tabuľke pozvánok vo svojom užívateľskom profile." + success_username: "Pozvali sme tohoto uťívateľa aby sa podieľal na tejto téme." + error: "Prepáčte, Nepodarilo sa nám pozvať túto osobu. Nebola už náhodou pozvaná ? (Počet opakovaných pozvánok je obmedzený)" + login_reply: 'Príhláste sa ak chcete odpovedať' + filters: + n_posts: + one: "1 príspevok" + few: "{{count}} príspevky" + other: "{{count}} príspevkov" + cancel: "Zruš filter" + split_topic: + title: "Presuň na novú tému" + action: "presuň na novú tému" + topic_name: "Názov novej témy" + error: "Nastala chyba pri presune príspevku na novú tému." + instructions: + one: "Vytvárate novú tému do ktorej bude vložený príspevok, ktorý ste označili. " + few: "Vytvárate novú tému do ktorej bude vložených {{count}} príspevkov, ktoré ste označili. " + other: "Vytvárate novú tému do ktorej budú vložené {{count}} príspevky, ktoré ste označili. " + merge_topic: + title: "Presuň do existujúcej témy." + action: "presuň do existujúcej témy" + error: "Nastala chyba pri presune príspevku do tejto témy." + instructions: + one: "Prosím vyberte tému do ktorej chcete presunúť tento príspevok." + few: "Prosím vyberte tému do ktorej chcete presunúť tieto {{count}} príspevky." + other: "Prosím vyberte tému do ktorej chcete presunúť týchto {{count}} príspevkov." + change_owner: + title: "Zmeň vlástníka príspevkov" + action: "zmeň vlastníka" + error: "Nastala chyba pri zmene vlastníka príspevkov." + label: "Príspevky nového vlastníka" + placeholder: "užívateľske meno nového vlastnika" + instructions: + one: "Prosím vyberte nového vlastníka príspevku vytvoreného {{old_user}}." + few: "Prosím vyberte nového vlastníka {{count}} príspevkov vytvorených {{old_user}}." + other: "Prosím vyberte nového vlastníka {{count}} príspevkov vytvorených {{old_user}}." + instructions_warn: "Poznámka: Žiadne upozornenie o tomto príspevku nebude spätne zaslané novým užívateľom
    Upozornenie: Momentálne nie sú prenášané žiadne dáta vťahujúce sa k príspevku na nových užívateľov. Používajte opatrne." + change_timestamp: + title: "Nastavte časovú značku" + action: "nastavte časovú značku" + invalid_timestamp: "Časová značka nemôže byť v budúcnosti. " + error: "Nastala chyba pri zmene časovej značky témy." + instructions: "Prosím vyberte novú časovú značku témy. Príspevky k téme budu aktualizované so zachovaním časoveho rozdielu." + multi_select: + select: 'označ' + selected: 'označených ({{count}})' + select_replies: 'označ +odpovede' + delete: zmaž označené + cancel: zrušiť výber + select_all: označ všetko + deselect_all: odznač všetko + description: + one: Označili ste 1 príspevok + few: Označili ste {{count}} príspevky + other: Označili ste {{count}} príspevkov + post: + reply: " {{replyAvatar}} {{usernameLink}}" + reply_topic: " {{link}}" + quote_reply: "citovať odpoveď" + edit: "Upravujete {{link}} {{replyAvatar}} {{username}}" + edit_reason: "Dôvod:" + post_number: "príspevok {{number}}" + last_edited_on: "príspevok naposledy upravený" + reply_as_new_topic: "Odpoveď ako súvisiaca téma" + continue_discussion: "Pokračovanie diskusie z {{postLink}}:" + follow_quote: "prejsť na citovaný príspevok" + show_full: "Zobraziť celý príspevok" + show_hidden: 'Zobraziť skrytý obsah.' + deleted_by_author: + one: "(príspevky stiahnuté autorom budú automaticky zmazané za jednu hodinu pokiaľ nie sú označené)" + few: "(príspevky stiahnuté autorom budú automaticky zmazané za %{count} hodiny pokiaľ nie sú označené)" + other: "(príspevky stiahnuté autorom budú automaticky zmazané za %{count} hodín pokiaľ nie sú označené)" + expand_collapse: "rozbaliť/zbaliť" + gap: + one: "zobraziť skrytú odpoveď" + few: "zobraziť {{count}} skryté odpovede" + other: "zobraziť {{count}} skrytých odpovedí" + more_links: "ešte {{count}} " + unread: "Príspevok je neprečítaný." + has_replies: + one: "{{count}} Odpoveď" + few: "{{count}} Odpovede" + other: "{{count}} Odpovedí" + has_likes: + one: "{{count}} \"Páči sa\"" + few: "{{count}} \"Páči sa\"" + other: "{{count}} \"Páči sa\"" + has_likes_title: + one: "Tento príspevok sa páčil jedej osobe" + few: "Tento príspevok sa páčil {{count}} ľuďom" + other: "Tento príspevok sa páčil {{count}} ľuďom" + has_likes_title_only_you: "tento príspevok sa Vám páči" + has_likes_title_you: + one: "Tento príspevok sa páčil Vám a jednej ďalšej osobe" + few: "Tento príspevok sa páčil Vám a ďalším {{count}} ľuďom" + other: "Tento príspevok sa páčil Vám a ďalším {{count}} ľuďom" + errors: + create: "Ľutujeme, pri vytváraní príspevku nastala chyba. Prosím, skúste znovu." + edit: "Ľutujeme, pri úprave príspevku nastala chyba. Prosím, skúste znovu." + upload: "Ľutujeme, pri nahrávaní súboru nastala chyba. Prosím, skúste znovu." + attachment_too_large: "Ľutujeme, súbor, ktorý sa pokúšate nahrať, je príliš veľký (maximálna veľkosť je {{max_size_kb}}kb)." + file_too_large: "Ľutujeme, súbor, ktorý sa pokúšate nahrať je príliš veľký (maximálna veľkosť je {{max_size_kb}}kb)" + too_many_uploads: "Ľutujeme, ale naraz je možné nahrať len jeden súbor." + too_many_dragged_and_dropped_files: "Prepáčte, naraz môžte presunúť maximálne 10 súborov." + upload_not_authorized: "Ľutujeme, súbor, ktorý sa pokúšate nahrať nemá povolenú príponu (povolené prípony sú: {{authorized_extensions}})." + image_upload_not_allowed_for_new_user: "Ľutujeme, noví použivatelia nemôžu nahrávať obrázky." + attachment_upload_not_allowed_for_new_user: "Ľutujeme, noví používatelia nemôžu nahrávať prílohy." + attachment_download_requires_login: "Ľutujeme, pre stiahnutie príloh musíte byť prihlásený." + abandon: + confirm: "Ste si istý, že chcete zahodiť tento príspevok?" + no_value: "Nie, ponechať." + yes_value: "Áno, zahodiť." + via_email: "tento príspevok prišiel emailom" + whisper: "tento príspevok je súkromným šepotom pre moderátorov" + wiki: + about: "toto je wiki príspevok; základní používatelia ho môžu upravovať" + archetypes: + save: 'Uložiť možnosti' + controls: + reply: "vytvorte odpoveď na tento príspevok" + like: "páči sa mi tento príspevok" + has_liked: "tento príspevok sa Vám páči" + undo_like: "zruš \"Páči sa\"" + edit: "Editovať tento príspevok." + edit_anonymous: "Ľutujeme, ale pre úpravu príspevku je potrebné sa prihlásiť." + flag: "súkromne označiť tento príspevok do pozornosti, alebo naň poslať súkromne upozornenie" + delete: "odstrániť tento príspevok" + undelete: "vrátiť späť odstránenie príspevku" + share: "zdieľať odkaz na tento príspevok" + more: "Viac" + delete_replies: + confirm: + one: "Chcete tiež odstrániť {{count}} priamu reakciu na tento príspevok?" + few: "Chcete tiež odstrániť {{count}} priame reakcie na tento príspevok?" + other: "Chcete tiež odstrániť {{count}} priamych reakcií na tento príspevok?" + yes_value: "Áno, odstrániť aj reakcie." + no_value: "Nie, len tento príspevok." + admin: "akcie administrátora príspevku" + wiki: "Spraviť Wiki" + unwiki: "Odstrániť Wiki" + convert_to_moderator: "Pridať farbu personálu" + revert_to_regular: "Odobrať farbu personálu" + rebake: "Pregenerovať HTML" + unhide: "Odokryť" + change_owner: "Zmeniť vlastníctvo" + actions: + flag: 'Označ' + defer_flags: + one: "Zrušiť označenie" + few: "Zrušiť označenia" + other: "Zrušiť označenia" + it_too: + off_topic: "Tiež označ" + spam: "Tiež označ" + inappropriate: "Tiež označ" + custom_flag: "Tiež označ" + bookmark: "Tiež vytvoriť záložku" + like: "Tiež sa mi páči" + vote: "Tiež hlasujem za" + undo: + off_topic: "Zruš označenie" + spam: "Zruš označenie" + inappropriate: "Zruš označenie" + bookmark: "Vrátiť záložku späť" + like: "Zruš \"Páči sa\"" + vote: "Zruš hlasovanie" + people: + off_topic: "{{icons}} to označíl ako mimo tému" + spam: "{{icons}} to označíl ako spam" + spam_with_url: "{{icons}} to označíl ako spam" + inappropriate: "{{icons}} to označíl ako nevhodné" + notify_moderators: "{{icons}} upozornil moderátorov" + notify_moderators_with_url: "{{icons}} upozornil moderátorov" + notify_user: "{{icons}} poslal správu" + notify_user_with_url: "{{icons}} poslal správu " + bookmark: "{{icons}} si na to vytvoril záložku" + like: "Páčilo sa to {{icons}}" + vote: "{{icons}} hlasoval za" + by_you: + off_topic: "Označíli ste to ako mimo tému" + spam: "Označíli ste to ako spam" + inappropriate: "Označíli ste to ako nevhodné" + notify_moderators: "Označíli ste to pre moderátora" + notify_user: "Poslali ste správu užívateľovi " + bookmark: "Vytvorili ste si záložku na tento príspevok" + like: "Páči sa Vám to" + vote: "Hlasoval ste za tento príspevok" + by_you_and_others: + off_topic: + one: "Vy a 1 ďalšia osoba to označílo ako mimo tému" + few: "Vy a ďalšie {{count}} osoby to označíli ako mimo tému" + other: "Vy a ďalších {{count}} osôb to označílo ako mimo tému" + spam: + one: "Vy a 1 ďalšia osoba to označíla ako spam" + few: "Vy a ďalšie {{count}} osoby to označíli ako spam" + other: "Vy a ďalších {{count}} osôb to označílo ako spam" + inappropriate: + one: "Vy a jedna ďalšia osoba to označila ako nevhodné" + few: "Vy a ďalšie {{count}} osoby to označili ako nevhodné" + other: "Vy a ďalších {{count}} osôb to označilo ako nevhodné" + notify_moderators: + one: "Vy a jedna ďalšia osoba to označila na moderovanie" + few: "Vy a ďalšie {{count}} osoby to označili na moderovanie" + other: "Vy a ďalších {{count}} osôb to označilo na moderovanie" + notify_user: + one: "Vy a jedna ďalšia osoba poslala správu tomuto užívateľovi" + few: "Vy a ďalšie {{count}} osoby poslali správu tomuto užívateľovi" + other: "Vy a ďalších {{count}} osôb poslalo správu tomuto užívateľovi" + bookmark: + one: "Vy a jedna ďalšia osoba si vytvorilo záložku na tento príspevok" + few: "Vy a ďalšie {{count}} osoby si vytvorili záložku na tento príspevok" + other: "Vy a ďalších {{count}} osôb si vytvorilo záložku na tento príspevok" + like: + one: "Páči sa to Vám a jendej ďalšej osobe" + few: "Páči sa to Vám a ďalším {{count}} osobám" + other: "Páči sa to Vám a ďalším {{count}} osobám" + vote: + one: "Vy a jenda ďalšia osoba hlasovala za tento príspevok" + few: "Vy a ďalšie {{count}} osoby hlasovalo za tento príspevok" + other: "Vy a ďalších {{count}} osôb hlasovalo za tento príspevok" + by_others: + off_topic: + one: "1 osoba to označíla ako mimo tému" + few: "{{count}} osoby to označíli ako mimo tému" + other: "{{count}} osôb to označílo ako mimo tému" + spam: + one: "1 osoba to označíla ako spam" + few: "{{count}} osoby to označíli ako spam" + other: "{{count}} osôb to označílo ako spam" + inappropriate: + one: "1 osoba to označila ako nevhodné" + few: "{{count}} osoby to označili ako nevhodné" + other: "{{count}} osôb to označilo ako nevhodné" + notify_moderators: + one: "1 osoba to označila na moderovanie" + few: "{{count}} osoby to označili na moderovanie" + other: "{{count}} osôb to označilo na moderovanie" + notify_user: + one: "1 osoba poslala správu tomuto užívateľovi" + few: "{{count}} osoby poslali správu tomuto užívateľovi" + other: "{{count}} osôb poslalo správu tomuto užívateľovi" + bookmark: + one: "1 osoba si vytvorila záložku na tento príspevok" + few: "{{count}} osoby si vytvorili záložku na tento príspevok" + other: "{{count}} osôb si vytvorilo záložku na tento príspevok" + like: + one: " {{count}} osobe sa to páčilo" + few: " {{count}} osobám sa to páčilo" + other: " {{count}} osobám sa to páčilo" + vote: + one: "1 osoba hlasovala za tento príspevok" + few: "{{count}} osoby hlasovali za tento príspevok" + other: "{{count}} osôb hlasovalo za tento príspevok" + delete: + confirm: + one: "Ste si istý že chcete zmazať tento príspevok?" + few: "Ste si istý že chcete zmazať všetky tieto príspevky?" + other: "Ste si istý že chcete zmazať všetky tieto príspevky?" + revisions: + controls: + first: "Prvá revízia" + previous: "Predchádzajúca revízia" + next: "Ďalšia revízia" + last: "Posledná revízia" + hide: "Skriť revíziu" + show: "Ukáza revíziu" + comparing_previous_to_current_out_of_total: "{{previous}} {{current}} / {{total}}" + displays: + inline: + title: "Zobraz výstup vrátane pridaného a zmazaného v riadku" + button: ' HTML' + side_by_side: + title: "Zobraziť rozdiely v generovanom výstupe vedľa seba" + button: ' HTML' + side_by_side_markdown: + title: "Zobraziť rozdiely v pôvodnom zdroji vedľa seba" + button: ' Neupravený' + category: + can: 'môže … ' + none: '(Bez kategórie)' + all: 'Všetky kategórie' + choose: 'Vyber kategóriu…' + edit: 'uprav' + edit_long: "Upraviť" + view: 'Prezerať témy v kategórii' + general: 'Všeobecné' + settings: 'Nastavenia' + topic_template: "Formulár témy" + delete: 'Odstrániť kategóriu' + create: 'Nová kategória' + create_long: 'Vytvoriť novú kategóriu' + save: 'Uložiť kategóriu' + slug: 'URL kategórie' + slug_placeholder: '(Voliteľné) pomlčkou-prerušované-slová pre url' + creation_error: Nastala chyba počas vytvárania kategórie. + save_error: Nastala chyba počas ukladania kategórie + name: "Názov kategórie" + description: "Popis" + topic: "kategória témy" + logo: "Logo kategórie" + background_image: "Pozadie kategórie" + badge_colors: "Farby odznakov" + background_color: "Farba pozadia" + foreground_color: "Farba popredia" + name_placeholder: "Maximálne jedno dve slová" + color_placeholder: "Ľubovoľná farba stránky" + delete_confirm: "Ste si istý že chcete zmazať túto kategóriu?" + delete_error: "Nastala chyba počas mazania kategórie" + list: "Zoznam kategórií" + no_description: "Prosím, pridajte popis k tejto kategórii." + change_in_category_topic: "Uprav popis" + already_used: 'Táto farba je už použitá inou kategóriou' + security: "Bezpečnosť" + special_warning: "Upozornenie: Toto je preddefinovaná kategória a jej bezpečnostné nastavenia sa nedajú upraviť. Pokiaľ si neželáte použiť túto kategóriu, neupravujte ju, ale zmažte." + images: "Obrázky" + auto_close_label: "Automaticky uzavrieť tému po:" + auto_close_units: "hodinách" + email_in: "Vlastná e-mailová adresa pre príchodziu poštu:" + email_in_allow_strangers: "Prijímať emaily od anonymných užívateľov bez účtu" + email_in_disabled: "Vkladanie nových tém cez email je zablokované v Nastaveniach stránky. Ak chcete povoliť vkladanie nových téme cez email," + email_in_disabled_click: 'povoľte nastavenie "email in"' + contains_messages: "Zmeň túto kategóriu tak, aby obsahovala len správy." + suppress_from_homepage: "Pozastaviť kategóriu z domovskej stránky." + allow_badges_label: "Povoliť získavanie odznakov v tejto kategórii" + edit_permissions: "Upraviť práva" + add_permission: "Pridať práva" + this_year: "tento rok" + position: "pozícia" + default_position: "Predvolená pozícia" + position_disabled: "Kategórie budú zobrazené podľa aktivity. Pre možnosť ovládania poradia kategórií v zozname," + position_disabled_click: 'povoľte možnosť "pevné poradie kategórií"' + parent: "Nadradená kategória" + notifications: + watching: + title: "Pozerať" + description: "Budete automaticky pozerať všetky nové témy v týchto kategóriách. Budete upozornený na všetky nové príspevky vo všetkých témach. Zároveň bude zobrazený počet nových odpovedí." + tracking: + title: "Sledovať" + description: "Budete automaticky sledovať všetky nové témy v týchto kategóriách. Budete upozornený ak niekto uvedie vaše @meno alebo Vám odpovie. Zároveň bude zobrazený počet nových odpovedí." + regular: + title: "Bežný" + description: "Budete upozornený ak niekto spomenie Vaše @meno alebo Vám odpovie." + muted: + title: "Stíšené" + description: "Nikdy nebudete informovaní o udalostiach v nových témach týchto kategórií. Tieto témy sa zároveň nebudú zobrazovať v zozname posledných udalostí." + flagging: + title: 'Ďakujeme, že pomáhate udržiavať slušnosť v našej komunite!' + private_reminder: 'Označenia sú súkromné viditeľné iba pre personál' + action: 'Označ príspevok' + take_action: "Vykonať akciu" + notify_action: 'Správa' + delete_spammer: "Zmazať spammera" + delete_confirm: "Idete vymazať %{posts} príspevky a %{topics} témy tohto užívateľa, zmazať jeho účet, zakázať prihlásenia z jeho IP adresy %{ip_address}, a pridať jeho email %{email} na zoznam trvalo zakázaných. Ste si istý, že tento užívateľ je skutočne spamer?" + yes_delete_spammer: "Áno, zmazať spammera" + ip_address_missing: "(nedostupné)" + hidden_email_address: "(skryté)" + submit_tooltip: "Odoslať súkromné označenie" + take_action_tooltip: "Dosiahnuť okamžite limit označení, namiesto čakania na ďalšie označenia od komunity" + cant: "Ľutujeme, ale tento príspevok sa teraz nedá označiť ." + notify_staff: 'Notifikovať redakciu' + formatted_name: + off_topic: "Je to mimo témy" + inappropriate: "Je to nevhodné" + spam: "Je to spam" + custom_placeholder_notify_user: "Buďte konkrétny, buďte konštruktívny a buďte vždy milý." + custom_placeholder_notify_moderators: "Dajte nám vedieť, z čoho konkrétne máte obavy, a priložte príslušné odkazy a príklady, ak je to možné." + custom_message: + at_least: "zadajte aspoň {{n}} znakov" + more: "zostáva ešte {{n}} ..." + left: "{{n}} zostáva" + flagging_topic: + title: "Ďakujeme, že pomáhate udržiavať slušnosť v našej komunite!" + action: "Označ príspevok" + notify_action: "Správa" + topic_map: + title: "Zhrnutie článku" + participants_title: "Častí prispievatelia" + links_title: "Populárne odkazy" + links_shown: "ukázať všetkých {{totalLinks}} odkazov..." + clicks: + one: "%{count} kilk" + few: "%{count} kliky" + other: "%{count} klikov" + topic_statuses: + warning: + help: "Toto je oficiálne varovanie." + bookmarked: + help: "Vytvorili ste si záložku na túto tému" + locked: + help: "Táto téma je už uzavretá. Nové odpovede už nebudú akceptované" + archived: + help: "Táto téma je archivovaná. Už sa nedá meniť. " + locked_and_archived: + help: "Táto téma je už uzavretá a archivovaná. Nové odpovede ani zmeny už nebudú akceptované " + unpinned: + title: "Odopnuté" + help: "Túto tému ste odopli. Bude zobrazená v bežnom poradí." + pinned_globally: + title: "Globálne pripnuté" + help: "Tento príspevok je globálne uprednostnený. Zobrazí sa na začiatku v: zozname posledných článkov a vo svojej kategórii." + pinned: + title: "Pripnutý" + help: "Túto tému ste pripli. Bude zobrazená na vrchole svojej kategórie" + invisible: + help: "Táto téma je skrytá. Nebude zobrazená v zozname tém a prístup k nej bude možný len prostrednictvom priameho odkazu na ňu" + posts: "Príspevky" + posts_lowercase: "príspevky" + posts_long: "v tejto téme je {{number}} príspevkov" + posts_likes_MF: | + Táto téma obsahuje {count, plural, one {1 odpoveď} other {# odpovedí}} {ratio, select, + low {s vysokým pomerom "Páči sa" na príspevok} + med {s veľmi vysokým pomerom "Páči sa" na príspevok} + high {s extrémne vysokým pomerom "Páči sa" na príspevok} + other {}} + original_post: "Pôvodný príspevok" + views: "Zobrazenia" + views_lowercase: + one: "zobrazenie" + few: "zobrazenia" + other: "zobrazení" + replies: "Odpovede" + views_long: "táto téma bola prezeraná {{number}} krát " + activity: "Aktivita" + likes: "Páči sa mi" + likes_lowercase: + one: "\"Páči sa\"" + few: "\"Páči sa\"" + other: "\"Páči sa\"" + likes_long: "v tejto téme je {{number}} \"Páči sa\"" + users: "Používatelia" + users_lowercase: + one: "užívateľ" + few: "užívatelia" + other: "užívatelia" + category_title: "Kategória" + history: "História" + changed_by: "od {{author}}" + raw_email: + title: "Neupravený email" + not_available: "Nedostupné!" + categories_list: "Zoznam kategórií" + filters: + with_topics: "%{filter} témy" + with_category: "%{filter} %{category} témy" + latest: + title: "Najnovšie" + title_with_count: + one: "Posledný (1)" + few: "Posledné ({{count}})" + other: "Posledných ({{count}})" + help: "témy s nedávnymi príspevkami" + hot: + title: "Horúca" + help: "výber najhorúcejších tém" + read: + title: "Prečítaná" + help: "prečítané témy, zoradené podľa času ich prečítania" + search: + title: "Hľadať" + help: "hľadaj vo všetkych témach" + categories: + title: "Kategórie" + title_in: "Kategória - {{categoryName}}" + help: "všetky témy zoskupené podľa kategórie" + unread: + title: "Neprečítané" + title_with_count: + one: "Neprečítaná (1)" + few: "Neprečítané ({{count}})" + other: "Neprečítaných ({{count}})" + help: "témy ktorých neprečítané príspevky v súčastnosti pozeráte alebo sledujete " + lower_title_with_count: + one: "1 neprečítaná" + few: "{{count}} neprečítané" + other: "{{count}} neprečítaných" + new: + lower_title_with_count: + one: "1 nová" + few: "{{count}} nové" + other: "{{count}} nových" + lower_title: "nový" + title: "Nový" + title_with_count: + one: "Nová (1)" + few: "Nové ({{count}})" + other: "Nových ({{count}})" + help: "témy vytvorené za posledných pár dní" + posted: + title: "Moje príspevky" + help: "témy s vašimi príspevkami" + bookmarks: + title: "Záložky" + help: "témy, ktoré máte v záložkách" + category: + title: "{{categoryName}}" + title_with_count: + one: "{{categoryName}} ({{count}})" + few: "{{categoryName}} ({{count}})" + other: "{{categoryName}} ({{count}})" + help: "najnovšie témy v kategórii {{categoryName}}" + top: + title: "Vrch" + help: "najaktívnejšie témy za posledný rok, mesiac, týždeň, alebo deň" + all: + title: "Za celú dobu" + yearly: + title: "Ročne" + quarterly: + title: "Štvrťročne" + monthly: + title: "Mesačne" + weekly: + title: "Týždenne" + daily: + title: "Denne" + all_time: "Za celú dobu" + this_year: "Rok" + this_quarter: "Štvrťrok" + this_month: "Mesiac" + this_week: "Týždeň" + today: "Dnes" + other_periods: "pozri hore" + browser_update: 'Ľutujeme, Váš prehliadač je príliš starý na prácu na tejto stránke. Prosím aktualizujte Váš prehliedač.' + permission_types: + full: "Vytvor / Odpovedz / Zobraz" + create_post: "Odpovedz / Zobraz" + readonly: "Zobraz" + admin_js: + type_to_filter: "zadajte, čo chcete filtrovať ..." + admin: + title: 'Administrátor Discourse' + moderator: 'Moderátor' + dashboard: + title: "Ovládací panel" + last_updated: "Dashboard naposledy aktualizovaný:" + version: "Verzia" + up_to_date: "Máte nainštalovanú najnovšiu verziu!" + critical_available: "Je dostupná kritická aktualizácia." + updates_available: "Aktualizácie sú k dispozícii." + please_upgrade: "Prosím aktualizujte!" + no_check_performed: "Neprebehlo zisťovanie aktualizácií. Uistite sa že je spustený sidekiq." + stale_data: "V poslednej dobe neprebehlo zisťovanie aktualizácií. Uistite sa že je spustený sidekiq." + version_check_pending: "Zdá sa že ste nedávno aktualizovali. Fantastické!" + installed_version: "Nainštalované" + latest_version: "Najnovšie" + problems_found: "Boli zistené nejaké problémy s Vašou inštaláciou Discourse." + last_checked: "Naposledy overené" + refresh_problems: "Obnoviť" + no_problems: "Nenašli sa žiadne problémy." + moderators: 'Moderátori:' + admins: 'Administrátori:' + blocked: 'Zablokované:' + suspended: 'Odobraté:' + private_messages_short: "Správy" + private_messages_title: "Správy" + mobile_title: "Mobil" + space_free: "{{size}} voľné" + uploads: "nahraté" + backups: "zálohy" + traffic_short: "Vyťaženie" + traffic: "Požiadavky webových aplikácií" + page_views: "Požiadavky API" + page_views_short: "Požiadavky API" + show_traffic_report: "Zobraziť detaily vyťaženia" + reports: + today: "Dnes" + yesterday: "Včera" + last_7_days: "Posledných 7 dní" + last_30_days: "Posledných 30 dní" + all_time: "Za celú dobu" + 7_days_ago: "Pred 7 dňami" + 30_days_ago: "Pred 30 dňami" + all: "Všetky" + view_table: "tabuľka" + view_chart: "stĺpcový graf" + refresh_report: "Obnoviť report" + start_date: "Od" + end_date: "Do" + commits: + latest_changes: "Najnov3ie zmeny. Prosime aktualizujte čo najčastejšie!" + by: "podľa" + flags: + title: "Označenia" + old: "Staré" + active: "Aktívny" + agree: "Súhlasiť" + agree_title: "Akceptovať toto označenie ako platné a správne" + agree_flag_modal_title: "Súhlasiť a ...." + agree_flag_hide_post: "Súhlasiť (skryť príspevok a poslať súkromnú správu)" + agree_flag_hide_post_title: "Skryť tento príspevok a automaticky poslať súkromnú správu s výzvou na úpravu príspevku." + agree_flag_restore_post: "Súhlasiť (obnoviť príspevok)" + agree_flag_restore_post_title: "Obnoviť tento príspevok" + agree_flag: "Súhlasiť s označením" + agree_flag_title: "Súhlasiť s označením, ale nemeníť príspevok" + defer_flag: "Odložiť" + defer_flag_title: "Zrušíť označenie. Žiadna akcia nie je nateraz potrebná." + delete: "Odstrániť" + delete_title: "Zmazať príspevok na ktorý označenie odkazuje ." + delete_post_defer_flag: "Zmazať príspevok a zrušiť označenie" + delete_post_defer_flag_title: "Zmazať prípspevok; ak ide o prvý príspevok, zmazať aj tému" + delete_post_agree_flag: "Zmazať príspevok a súhlasiť s označením" + delete_post_agree_flag_title: "Zmazať prípspevok; ak ide o prvý príspevok, zmazať aj tému" + delete_flag_modal_title: "Zmazať a..." + delete_spammer: "Zmazať spammera" + delete_spammer_title: "Zmazať užívateľa aj všetky príspevky a témy ktoré vytvoril." + disagree_flag_unhide_post: "Nesúhlasiť (odkryť príspevok)" + disagree_flag_unhide_post_title: "Zrušíť všetky označenia z príspevku a znova odkryť príspevok" + disagree_flag: "Nesúhlasiť" + disagree_flag_title: "Zamietnuť toto označenie ako neplatné, alebo nesprávne" + clear_topic_flags: "Hotovo" + clear_topic_flags_title: "Téma bola preskúmaná a problémy boli vyriešené. Kliknite Hotovo pre zrušenie označení." + more: "(viac odpovedí...)" + dispositions: + agreed: "odsúhlasené" + disagreed: "neodsúhlasené" + deferred: "odložené" + flagged_by: "Označené " + resolved_by: "Vyriešené" + took_action: "Prijal opatrenia" + system: "Systém" + error: "Niečo sa pokazilo" + reply_message: "Odpovedať" + no_results: "Žiadne označenia." + topic_flagged: "Táto téma bola označená. " + visit_topic: "Navšťívte tému pre prijatie opatrení" + was_edited: "Príspevok bol upravený po prvom označení" + previous_flags_count: "Tento príspevok bol už označený {{count}} krát." + summary: + action_type_3: + one: "mimo tému" + few: "mimo tému x{{count}}" + other: "mimo tému x{{count}}" + action_type_4: + one: "nevhodný" + few: "nevhodné x{{count}}" + other: "nevhodných x{{count}}" + action_type_6: + one: "vlastná" + few: "vlastné x{{count}}" + other: "vlastných x{{count}}" + action_type_7: + one: "vlastný" + few: "vlastné x{{count}}" + other: "vlastných x{{count}}" + action_type_8: + one: "spam" + few: "spam x{{count}}" + other: "spam x{{count}}" + groups: + primary: "Hlavná skupina" + no_primary: "(bez hlavnej skupiny)" + title: "Skupiny" + edit: "Upraviť skupiny" + refresh: "Obnoviť" + new: "Nový" + selector_placeholder: "zadať používateľské meno" + name_placeholder: "Názov skupiny, bez medzier, rovnaké pravidlá ako pre uťívateľa" + about: "Tu upravíte Vaše členstvo v skupinách a mená" + group_members: "Členovia skupiny" + delete: "Odstrániť" + delete_confirm: "Zmazať túto skupinu?" + delete_failed: "Nepodarilo sa zmazať skupinu. Pokiaľ je skupina automatická, nemôže byť zrušená." + delete_member_confirm: "Odstrániť '%{username}' zo skupiny '%{group}'?" + delete_owner_confirm: "Odobrať vlastnícke práva %{username}'?" + name: "Meno" + add: "Pridať" + add_members: "Pridať členov" + custom: "Vlastné" + bulk_complete: "Užívatelia boli pridaní do skupiny." + bulk: "Hromadné pridanie do skupiny" + bulk_paste: "Vlož zoznam používateľov alebo emailov, jeden na riadok:" + bulk_select: "(vyberte skupinu)" + automatic: "Automaticky" + automatic_membership_email_domains: "Užívatelia, ktorí sa zaregistrovali s emailovou doménou uvedenou v zozname budú automaticky pridaní do tejto skupiny. " + automatic_membership_retroactive: "Použi pravidlo rovnakej emailovej domény pre pridanie registrovaných užívateľov" + default_title: "Štandardné označenie pre všetkých používateľov v tejto skupine" + primary_group: "Automaticky nastav ako hlavnú skupinu" + group_owners: Vlastníci + add_owners: Pridať vlastníkov + incoming_email: "Vlastná e-mailová adresa pre príchodziu poštu" + incoming_email_placeholder: "zadajte emailovú adresu" + api: + generate_master: "Vygenerovať Master API kľúč" + none: "V súčasnosti neexistujú žiadne aktívne API kľúče." + user: "Používateľ" + title: "API" + key: "API kľúč" + generate: "Generovať" + regenerate: "Obnov" + revoke: "Zrušiť" + confirm_regen: "Ste si istý, že chcete nahradiť tento API kľúč novým?" + confirm_revoke: "Ste si istý, že chcete obnoviť tento kľúč?" + info_html: "Váš API kľúč Vám umožní vytváranie a aktualizovanie tém prostredníctvom volaní JSON." + all_users: "Všetci používatelia" + note_html: "Držte tento kľúč v tajnosti, všetci užívatelia ktorí ho vlastnia môžu vytvárať ľubovoľné príspevky pod ľubovoľným užívateľským menom. " + plugins: + title: "Pluginy" + installed: "Nainštalované pluginy" + name: "Meno" + none_installed: "Nemáte nainštalované žiadne pluginy." + version: "Verzia" + enabled: "Povolené?" + is_enabled: "A" + not_enabled: "N" + change_settings: "Zmeniť nastavenia" + change_settings_short: "Nastavenia" + howto: "Ako nainštalujem pluginy?" + backups: + title: "Zálohy" + menu: + backups: "Zálohy" + logs: "Logy" + none: "Nie je dostupná žiadna záloha." + read_only: + enable: + title: "Povoliť mód len na čítanie." + label: "Povoliť mód len na čítanie." + confirm: "Ste si istý, že chcete povoliť mód len na čítanie?" + disable: + title: "Zakázať mód len na čítanie" + label: "Zakázať mód len na čítanie" + logs: + none: "Zatiaľ žiadne logy..." + columns: + filename: "Názov súboru" + size: "Veľkosť" + upload: + label: "Upload" + title: "Nahrať zálohu do tejto inštancie" + uploading: "Upload prebieha..." + success: "'{{filename}}' bol úspešne nahratý." + error: "Počas nahrávania '{{filename}}' nastala chyba: {{message}}" + operations: + is_running: "Operácia práve prebieha..." + failed: " Zlyhalo vykonanie {{operation}} . Prosím skontrolujte logy. " + cancel: + label: "Zrušiť" + title: "Zrušiť prebiehajúcu operáciu" + confirm: "Ste si istý, že chcete zrušiť prebiehajúcu operáciu?" + backup: + label: "Záloha" + title: "Vytvoriť zálohu" + confirm: "Prajete si spustiť novú zálohu?" + without_uploads: "Áno (nezahŕňať súbory)" + download: + label: "Stiahnuť" + title: "Stiahnuť zálohu" + destroy: + title: "Odstrániť zálohu" + confirm: "Ste si istý, že chcete odstrániť túto zálohu?" + restore: + is_disabled: "Obnovenie je vypnuté na Nastaveniach stránky." + label: "Obnoviť" + title: "Obnoviť zálohu" + confirm: "Ste si istý, že chcete obnoviť túto zálohu?" + rollback: + label: "Vrátiť späť" + title: "Vrátiť databázu do predchádzajúceho funkčného stavu" + confirm: "Ste si istý, že chcete vrátiť databázu do predchádzajúceho funkčńeho stavu?" + export_csv: + user_archive_confirm: "Ste si istý, že si chcete stiahnut svoje príspevky?" + success: "Export bol spustený, o jeho skončení budete informovaný správou." + failed: "Export zlyhal. Skontrolujte prosím logy." + rate_limit_error: "Príspevky možu byť stiahnuté len raz za deň. Skúste opäť zajtra." + button_text: "Export" + button_title: + user: "Exportovať celý zoznam používateľov v CSV formáte." + staff_action: "Exportovať celý log akcií redakcie v CSV formáte." + screened_email: "Exportovať celý zobrazený zoznam emailov v CSV formáte." + screened_ip: "Exportovať celý zobrazený zoznam IP adries v CSV formáte." + screened_url: "Exportovať celý zobrazený zoznam URL adries v CSV formáte." + export_json: + button_text: "Export" + invite: + button_text: "Poslať pozvánky" + button_title: "Poslať pozvánky" + customize: + title: "Upraviť" + long_title: "Úpravy webu" + css: "CSS" + header: "Hlavička" + top: "Vrch" + footer: "Päta" + embedded_css: "Vnorené CSS" + head_tag: + text: "" + title: "HTML, ktoré bude vložené pred tag" + body_tag: + text: "" + title: "HTML, ktoré bude vložené pred tag" + override_default: "Nevkladať štandardné štýly" + enabled: "Povolené?" + preview: "náhľad" + undo_preview: "zmazať náhľad" + rescue_preview: "predvolený štýl" + explain_preview: "Nastaviť na stránke vlastné štýly" + explain_undo_preview: "Vrátiť sa k akruálnym vlastným štýlom" + explain_rescue_preview: "Nastavit na stránke štandardné štýly" + save: "Uložiť" + new: "Nový" + new_style: "Nový štýl" + import: "Import" + import_title: "Vyberte súbor alebo vložte text" + delete: "Odstrániť" + delete_confirm: "Zmazať túto úpravu?" + about: "Upraviť CSS štýly a HTML hlavičky na stránke. Začnite pridaním úpravy." + color: "Farba" + opacity: "Nepriesvitnosť" + copy: "Kopírovať" + email_templates: + title: "Emailové šablóny" + subject: "Predmet" + multiple_subjects: "Táto emailova šablóna obsahuje viac predmetov" + body: "Telo" + none_selected: "Vyberte šablénu emailu pre začatie úpravy." + revert: "Vrátiť zmeny" + revert_confirm: "Ste si istý, že chcete vrátiť vykonané zmeny späť?" + css_html: + title: "CSS/HTML" + long_title: "Úpravy CSS a HTML" + colors: + title: "Farby" + long_title: "Farebné schémy" + about: "Upravte farby použité na stránke bez použitia CSS. Začnite pridaním schémy." + new_name: "Nová farebná schéma" + copy_name_prefix: "Kópia" + delete_confirm: "Zmazať túto farebnú schému?" + undo: "späť" + undo_title: "Zrušiť zmeny farby a vrátiť sa k predchádzajucej uloženej verzii. " + revert: "vrátiť zmeny" + revert_title: "Nastaviť východziu farebnú schému Discourse. " + primary: + name: 'primárny' + description: 'Väčšina textov, ikony, a okraje.' + secondary: + name: 'sekundárny' + description: 'Hlavná farba pozadia a farba textu niektorých ovládacích prvkov.' + tertiary: + name: 'terciárny' + description: 'Odkazy, nejaké tlačidlá, upozornenia a zvýrazňovacie farby.' + quaternary: + name: "štvrťročne" + description: "Navigačné odkazy." + header_background: + name: "pozadie hlavičky" + description: "Farba pozadia hlavičky stránky." + header_primary: + name: "hlavné záhlavie" + description: "Texty a ikony v záhlaví stránky." + highlight: + name: 'zvýraznenie' + description: 'Farba pozadia zvýrazneného prvku na stránke, napríklad príspevku alebo témy.' + danger: + name: 'nebezpečenstvo' + description: 'Zvýrazňovacia farba pre akcie ako napríklad mazanie príspevkov a tém.' + success: + name: 'úspech' + description: 'Použitá pre úspešne vykonané akcie.' + love: + name: 'obľúbené' + description: "Farba tlačidla \"Páči sa\"" + wiki: + name: 'wiki' + description: "Základná farba pozadia wiki príspevkov." + email: + title: "Email" + settings: "Nastavenia" + all: "Všetky" + sending_test: "Odosielam testovací email..." + error: "CHYBA - %{server_error}" + test_error: "Pri posielaní testovacieho emailu nastala chyba. Prosím preverte Vaše emailové nastavenia, overte si, že váš hostiteľ neblokuje emailové spojenia a skúste znova." + sent: "Odoslané" + skipped: "Preskočené" + sent_at: "Odoslané" + time: "Čas" + user: "Používateľ" + email_type: "Typ emailu" + to_address: "Adresát" + test_email_address: "testovacia emailová adresa" + send_test: "Odoslať testovací email" + sent_test: "odoslané!" + delivery_method: "Spôsob doručenia" + preview_digest: "Súhrn" + preview_digest_desc: "Náhľad obsahu súhrnných emailov zaslaných neaktívnym užívateľom." + refresh: "Obnoviť" + format: "Formát" + html: "html" + text: "text" + last_seen_user: "Posledný videný užívateľ" + reply_key: "Tlačidlo odpovedať" + skipped_reason: "Preskočiť zdôvodnenie" + logs: + none: "Nenašli sa žiadne logy." + filters: + title: "Filter" + user_placeholder: "používateľské meno" + address_placeholder: "meno@príklad.com" + type_placeholder: "súhrn, registácia..." + reply_key_placeholder: "tlačidlo odpovedať" + skipped_reason_placeholder: "dôvod" + logs: + title: "Logy" + action: "Akcia" + created_at: "Vytvorené" + last_match_at: "Posledný zodpovedajúci" + match_count: "Zodpovedá" + ip_address: "IP" + topic_id: "ID témy" + post_id: "ID príspevku" + category_id: "ID kategórie" + delete: 'Odstrániť' + edit: 'Upraviť' + save: 'Uložiť' + screened_actions: + block: "blokovať" + do_nothing: "nerob nič" + staff_actions: + title: "Akcie personálu" + instructions: "Vyberte uťívateľské meno a akcie na filtrovanie zoznamu. Kliknite na profilovú fotku pre navigáciu na užívateľské stránky." + clear_filters: "Ukázať všetko" + staff_user: "Člen redakcie" + target_user: "Cieľový používateľ" + subject: "Predmet" + when: "Kedy" + context: "Kontext" + details: "Detaily" + previous_value: "Predchádzajúci" + new_value: "Nový" + diff: "Rozdiel" + show: "Zobraziť" + modal_title: "Detaily" + no_previous: "Neexistuje predchádzajúca hodnota" + deleted: "Žiadna nová hodnota. Záznam bol vymazaný." + actions: + delete_user: "odstrániť používateľa" + change_trust_level: "zmeniť stupeň dôvery" + change_username: "zmeniť používateľské meno" + change_site_setting: "zmeniť nastavenia webu" + change_site_customization: "zmeniť úpravy webu" + delete_site_customization: "zmazať úpravy webu" + suspend_user: "zruš práva užívateľovi" + unsuspend_user: "obnov práva užívateľovi" + grant_badge: "udeliť odznak" + revoke_badge: "odobrať odznak" + check_email: "skontrolovať email" + delete_topic: "odstrániť tému" + delete_post: "odstrániť príspevok" + impersonate: "privlastniť" + anonymize_user: "anonymizovať používateľa" + roll_up: "zbaliť IP bloky" + change_category_settings: "zmeniť nastavenia kategórie" + delete_category: "odstrániť kategóriu" + create_category: "vytvoriť kategóriu" + screened_emails: + title: "Kontrolované emaily" + description: "Keď niekto skúsi vytvoriť nový účet, nasledujúce emailove adresy budú preverené a registrácia bude zablokovaná, alebo bude vykonaná nejaka iná akcia. " + email: "Emailové adresy" + actions: + allow: "Povoliť" + screened_urls: + title: "Kontrolované URL adresy" + description: "URL adresy v tomto zozname boli použité v príspevkoch užívateľov, ktorí boli identifikovaní ako spameri." + url: "URL" + domain: "Doména" + screened_ips: + title: "Kontrolované IP adresy" + description: 'IP adresy pod dohľadom. Použi "Povoľ" pre povolenie IP adries.' + delete_confirm: "Ste si istý, že chcete zrušiť pravidlo pre %{ip_address}?" + roll_up_confirm: "Ste si istý, že chcete zosumarizovať bežne kontrolované IP do podsietí?" + rolled_up_some_subnets: "Zakázané IP adresy boli úspešne zosumarizované do podsietí: %{subnets}." + rolled_up_no_subnet: "Nebolo čo zbaliť." + actions: + block: "Blokovať" + do_nothing: "Povoliť" + allow_admin: "Povoliť admin" + form: + label: "Nový:" + ip_address: "IP adresy" + add: "Pridať" + filter: "Hľadať" + roll_up: + text: "Zbaliť" + title: "Vytvorí novú podsieť zakázaných záznamov pokiaľ existuje aspoň 'min_ban_entries_for_roll_up' záznamov" + logster: + title: "Chybové Logy" + impersonate: + title: "Privlastniť" + help: "Použite tento nástroj na privlastnenie si užívateľského účtu na účely debugovania. Po skončení sa budete musieť odhlásiť." + not_found: "Tento používateľ sa nenašiel." + invalid: "Ľutujeme, nesmiete si privlatniť tohto užívateľa." + users: + title: 'Používatelia' + create: 'Pridať admin používateľa' + last_emailed: "Posledný odemailovaný" + not_found: "Prepáčte, toto užívateľské meno sa nenachádza v našom systéme." + id_not_found: "Prepáčte, toto užívateľské id sa nenachádza v našom systéme." + active: "Aktívny" + show_emails: "Ukázať Emaily" + nav: + new: "Nový" + active: "Aktívny" + pending: "Čakajúca" + staff: 'Zamestnanci' + suspended: 'Odobrate práva' + blocked: 'Zablokovaný' + suspect: 'Podozrivý' + approved: "Schválený?" + approved_selected: + one: "schváliť užívateľa" + few: "schváliť ({{count}}) užívateľov " + other: "schváliť ({{count}}) užívateľov " + reject_selected: + one: "zamietnuť užívateľa" + few: "zamietnuť ({{count}}) užívateľov " + other: "zamietnuť ({{count}}) užívateľov " + titles: + active: 'Aktívni používatelia' + new: 'Noví používatelia' + pending: 'Užívatelia čakajúci na kontrolu' + newuser: 'Užívatelia na Stupni dôvery 0 (Noví užívatelia)' + basic: 'Užívatelia na Stupni dôvery 1 (Bežný užívateľ)' + member: 'Užívatelia na Stupni dôvery 2 (Člen)' + regular: 'Užívatelia na Stupni dôvery 3 (Stály člen)' + leader: 'Užívatelia na Stupni dôvery 4 (Vodca)' + staff: "Zamestnanci" + admins: 'Admin používatelia' + moderators: 'Moderátori' + blocked: 'Zablokovaní užívatelia' + suspended: 'Užívatelia s odobratými právami' + suspect: 'Podozriví užívatelia' + reject_successful: + one: "Úspešne zamietnutý užívateľ" + few: "Úspešne zamietnutí %{count} užívatelia" + other: "Úspešne zamietnutých %{count} užívateľov" + reject_failures: + one: "Nepodarilo sa zamietnuť 1 užívateľa" + few: "Nepodarilo sa zamietnuť %{count} užívateľov" + other: "Nepodarilo sa zamietnuť %{count} užívateľov" + not_verified: "Neoverený" + check_email: + title: "Odhaliť emailovú adresu tohto používateľa" + text: "Zobraziť" + user: + suspend_failed: "Niečo sa pokazilo pri odoberaní práv tomuto užívateľovi {{error}}" + unsuspend_failed: "Niečo sa pokazilo pri obnovovaní práv tomuto užívateľovi {{error}}" + suspend_duration: "Ako dlho budú užívateľovi odobrate práva?" + suspend_duration_units: "(dni)" + suspend_reason_label: "Prečo mu odoberáte práva? Tento text sa zobrazí každému na stránke profilu užívateľa a bude zobrazený užívateľovi pri pokuse o prihlásenie. Buďte strucný." + suspend_reason: "Dôvod" + suspended_by: "Práva odobraté" + delete_all_posts: "Zmazať všetky príspevky" + delete_all_posts_confirm: "Chystáte sa zmazať %{posts} príspevkov a %{topics} tém. Ste si istý?" + suspend: "Odobrať" + unsuspend: "Obnoviť" + suspended: "Odobrate práva?" + moderator: "Moderátor?" + admin: "Admin?" + blocked: "Blokovaný?" + show_admin_profile: "Admin" + edit_title: "Upraviť názov" + save_title: "Uložiť názov" + refresh_browsers: "Vynútiť refresh browsera." + refresh_browsers_message: "Správa odoslaná všetkým klientom!" + show_public_profile: "Ukázať verejný profil" + impersonate: 'Privlastniť' + ip_lookup: "Vyhľadávanie IP" + log_out: "Odhlásiť sa" + logged_out: "Užívateľ bol odhlásený na všetkých zariadeniach" + revoke_admin: 'Odobrať admin' + grant_admin: 'Udeliť admin' + revoke_moderation: 'Odobrať moderovanie' + grant_moderation: 'Udeliť moderovanie' + unblock: 'Odblokovať' + block: 'Blokovať' + reputation: Reputácia + permissions: Práva + activity: Aktivita + like_count: '"Páči sa" Rozdané / Prijaté' + last_100_days: 'za posledných 100 dní' + private_topics_count: Súkromné témy + posts_read_count: Prečítané príspevky + post_count: Vytvorené príspevky + topics_entered: Zobrazených tém + flags_given_count: Rozdané označenia + flags_received_count: Prijaté označenia + warnings_received_count: Prijaté varovania + flags_given_received_count: 'Rozdané a prijaté označenia' + approve: 'Schváliť' + approved_by: "schválený" + approve_success: "Uťívateľ schválený a bol zaslaný email s aktivačnými inštrukciami" + approve_bulk_success: "Úspech! Všetci vybraní uťívateľia boli schválení a oboznáamení." + time_read: "Doba Čítania" + anonymize: "Anonymizovať používateľa" + anonymize_confirm: "Ste si istý že chcete zmeniť tento účet na anonymný? Zmeni to užívateľské meno, email a zmažú sa všetky informácie z profilu. " + anonymize_yes: "Áno, zmeň tento účet na anonymný" + anonymize_failed: "Nastala chyba pri anonymizovaní účtu." + delete: "Odstrániť používateľa" + delete_forbidden_because_staff: "Správcovia a moderátori nemôžu byť vymazaní." + delete_posts_forbidden_because_staff: "Nedá sa zmazať príspevky správcov a moderátorov." + delete_forbidden: + one: "Užívatelia nemôžu byť vymazaní ak majú príspevky. Najprv zmažte príspevky až potom užívateľa. (Príspevky staršie ako %{count} deň nemožno zmazať)" + few: "Užívatelia nemôžu byť vymazaní ak majú príspevky. Najprv zmažte príspevky až potom užívateľa. (Príspevky staršie ako %{count} dni nemožno zmazať)" + other: "Užívatelia nemôžu byť vymazaní ak majú príspevky. Najprv zmažte príspevky až potom užívateľa. (Príspevky staršie ako %{count} dní nemožno zmazať)" + cant_delete_all_posts: + one: "Nepodarilo sa zmazať všetky príspevky. Niektoré príspevky sú staršie ako %{count} deň. (Nastavenie delete_user_max_post_age )" + few: "Nepodarilo sa zmazať všetky príspevky. Niektoré príspevky sú staršie ako %{count} dni. (Nastavenie delete_user_max_post_age )" + other: "Nepodarilo sa zmazať všetky príspevky. Niektoré príspevky sú staršie ako %{count} dní. (Nastavenie delete_user_max_post_age )" + cant_delete_all_too_many_posts: + one: "Nedá sa zmazať všetky píspevky, pretože užívateľ má viac ako 1 príspevok. (delete_all_posts_max)" + few: "Nedá sa zmazať všetky píspevky, pretože užívateľ má viac ako %{count} príspevky. (delete_all_posts_max)" + other: "Nedá sa zmazať všetky píspevky, pretože užívateľ má viac ako %{count} príspevkov. (delete_all_posts_max)" + delete_confirm: "Ste si ISTÝ, že chcete zmazať tohoto užívateľa? Už sa to nedá obnoviť!" + delete_and_block: "Zazať a zablokovať tento email a IP adresu" + delete_dont_block: "Iba vymazať" + deleted: "Používateľ bol vymazaný." + delete_failed: "Počas vymazávania používateľa nastala chyba. Pred vymazaním používateľa sa uistite, že všetky jeho príspevky sú zmazané." + send_activation_email: "Poslať aktivačný email." + activation_email_sent: "Aktivačný emial bol odoslaný." + send_activation_email_failed: "Počas odosielania ďalšieho aktivačného emailu nastala chyba. %{error}" + activate: "Aktivovať účet" + activate_failed: "Počas aktivácie používateľa nastala chyba." + deactivate_account: "Deaktivovať účet" + deactivate_failed: "Počas deaktivácie používateľa nastala chyba." + unblock_failed: 'Nastala chyba pri odblokovaní užívateľa.' + block_failed: 'Nastala chyba pri zablokovaní užívateľa.' + deactivate_explanation: "Deaktivovaý užívateľ musí znovu overiť svoj email" + suspended_explanation: "Suspendovaní užívatelia sa nemôžu prihlasovať." + block_explanation: "Zablokovaní uťívatelia nemôžu zakladať témy ani pridávať príspevky." + trust_level_change_failed: "Nastala chyba pri zmene úrovne dôveryhodnosti užívateľa." + suspend_modal_title: "Zruš práva užívateľovi" + trust_level_2_users: "Užívatelia na 2 Stupni dôvery" + trust_level_3_requirements: "Požiadavky pre 3 stupeň" + trust_level_locked_tip: "stupeň dôvery je zamknutý, systém užívateľovi stupeň nezvýši ani nezníži " + trust_level_unlocked_tip: "stupeň dôvery je odomknutý, systém môže užívateľovi stupeň zvýšiť alebo znížiť" + lock_trust_level: "Zamknúť stupeň dôvery" + unlock_trust_level: "Odomknúť stupeň dôvery" + tl3_requirements: + title: "Požiadavky pre stupeň dôvery 3" + table_title: "Za posledných 100 dní:" + value_heading: "Hodnota" + requirement_heading: "Požiadavka" + visits: "Návštev" + days: "dní" + topics_replied_to: "Témy na ktoré odpovedal" + topics_viewed: "Zobrazených tém" + topics_viewed_all_time: "Videné témy (za celú dobu)" + posts_read: "Prečítané príspevky" + posts_read_all_time: "Prečítaných príspevkov (za celú dobu)" + flagged_posts: "Označené príspevky" + flagged_by_users: "Užívatelia, ktorí označili" + likes_given: "Rozdaných 'páči sa mi'" + likes_received: "Obdržaných 'páči sa mi'" + likes_received_days: "Obdržaných 'páči sa mi' na jednotlivé dni" + likes_received_users: "Obdržaných 'páči sa mi' na jednotlivých užívateľov" + qualifies: "Spĺňa požiadavky pre stupeň dôvery 3" + does_not_qualify: "Nespĺňa požiadavky pre stupeň dôvery 3" + will_be_promoted: "Bude čoskoro povýšený" + will_be_demoted: "Čoskoro bude degradovaný" + on_grace_period: "V súčastnosti je v povyšovacej skúšobnej dobe, nebude degradovaný." + locked_will_not_be_promoted: "Stupeň dôvery je zamknutý. Nikdy nebude povýšený." + locked_will_not_be_demoted: "Stupeň dôvery je zamknutý. Nikdy nebude degradovaný" + sso: + title: "Jednotné prihlásenie" + external_id: "Externé ID" + external_username: "Používateľské meno" + external_name: "Meno" + external_email: "Email" + external_avatar_url: "URL profilovej fotky" + user_fields: + title: "Užívateľské polia" + help: "Pridaj polia, ktoré môžu užívatelia vyplniť" + create: "Vytvor užívateľske pole" + untitled: "Bez názvu" + name: "Názov poľa" + type: "Typ poľa" + description: "Popis poľa" + save: "Uložiť" + edit: "Upraviť" + delete: "Odstrániť" + cancel: "Zrušiť" + delete_confirm: "Ste si istý, že chcete zmazať toto užívateľské pole?" + options: "Možnosti" + required: + title: "Požadované pri registrácii?" + enabled: "povinné" + disabled: "nepovinné" + editable: + title: "Upravovateľné po registrácii?" + enabled: "upravovateľné " + disabled: "neupravovateľné " + show_on_profile: + title: "Ukázať na verejnom profile?" + enabled: "zobrazené na profile" + disabled: "nezobrazené na profile" + field_types: + text: 'Textové pole' + confirm: 'Potvrdenie' + dropdown: "Zoznam" + site_text: + description: "Môžete prispôsobiť hociktorý text na Vašom fóre. Prosím začnite hľadaním nižšie:" + search: "Hľadajte text, ktorý chcete upraviť" + title: 'Textový obsah' + edit: 'uprav' + revert: "Vrátiť zmeny" + revert_confirm: "Ste si istý, že chcete vrátiť vykonané zmeny späť?" + go_back: "Návrat na vyhľadávanie" + recommended: "Odporúčame prispôsobenie nasledujúceho textu podľa vašich potrieb:" + show_overriden: 'Ukázať iba zmenené' + site_settings: + show_overriden: 'Ukázať iba zmenené' + title: 'Nastavenia' + reset: 'zrušiť' + none: 'žiadne' + no_results: "Žiadne výsledky" + clear_filter: "Vyčistiť" + add_url: "pridaj URL" + add_host: "pridať hostiteľa" + categories: + all_results: 'Všetky' + required: 'Povinné' + basic: 'Základné nastavenia' + users: 'Používatelia' + posting: 'Prispievam' + email: 'Email' + files: 'Súbory' + trust: 'Stupne dôvery' + security: 'Bezpečnosť' + onebox: "Onebox" + seo: 'SEO' + spam: 'Spam' + rate_limits: 'Limity a obmedzenia' + developer: 'Vývojár' + embedding: "Vkladám" + legal: "Právne záležitosti" + uncategorized: 'Ostatné' + backups: "Zálohy" + login: "Prihlásenie" + plugins: "Pluginy" + user_preferences: "Užívateľské Nastavenia" + badges: + title: Odznaky + new_badge: Nový odznak + new: Nový + name: Meno + badge: Odznak + display_name: Zobrazované meno + description: Popis + badge_type: Typ odznaku + badge_grouping: Skupina + badge_groupings: + modal_title: Zoskupovanie odznakov + granted_by: Pridelené užívateľom + granted_at: Pridelené na + reason_help: (Odkaze na príspevok, alebo tému) + save: Uložiť + delete: Odstrániť + delete_confirm: Ste si istý, že chcete zmazať tento odznak? + revoke: Zrušiť + reason: Dôvod + expand: Rozbaliť … + revoke_confirm: Ste si istý, že chcete obnoviť tento odznak? + edit_badges: Upraviť odznaky + grant_badge: Prideliť odznaky + granted_badges: Pridelené odznaky + grant: Prideliť + no_user_badges: "%{name} nebol pridelený žiaden odznak." + no_badges: Nie sú žiadne odznaky, ktoré môžu byť pridelené. + none_selected: "Vyberte odznak, aby ste mohli začať" + allow_title: Povoliť použitie odznaku namiesto názvu + multiple_grant: Môže byť pridelené viacnásobne + listable: Zobraziť odznak na stránke verejných odznakov + enabled: Povoliť odznak + icon: Ikona + image: Obrázok + icon_help: "Použi buď font z triedy Awesome, alebo URL na obrázok" + query: Požiadavka na Odznak (SQL) + show_posts: Zobraziť príspevok o pridelení odznaku na stránke odznakov + trigger: Spúšťač + trigger_type: + none: "Obnovovať denne" + post_action: "Keď užívateľ zareaguje na príspevok" + post_revision: "Keď užívateľ vytvorí príspevok" + trust_level_change: "Keď užívateľ zmení stupeň dôvery" + user_change: "Keď je užívateľ vytvorený, alebo upravený" + preview: + link_text: "Prezerať pridelené odznaky" + plan_text: "Náhľad na plán požiadaviek" + modal_title: "Požiadavka na Odznak Prezeranie" + sql_error_header: "Nastala chyba s požiadavkou." + error_help: "Pozrite si nasledujäce odkazy pre pomoc s dopytovacími odznakmi." + bad_count_warning: + header: "UPOZORNENIE!" + text: "Chýbajú ukážky práv. Toto sa stane, ak dotaz na odznak vráti ID používateľa alebo príspevku, ktorý neexistuje. Toto môže zapríčiniť neskoršie neočakávané výsledky - prosíme znovu overte Váš dotaz." + no_grant_count: "Žiadne odznaky na pridelenie." + grant_count: + one: "1 odznak na pridelenie" + few: "%{count} odznaky na pridelenie" + other: "%{count} odznakov na pridelenie" + sample: "Vzor:" + grant: + with: %{username} + with_post: %{username} za príspevok v %{link} + with_post_time: %{username} za príspevok v %{link} v čase %{time} + with_time: %{username} v čase %{time} + emoji: + title: "Emoji" + help: "Pridaj nové emoji, ktoré bude dostupné pre všetkých (TIP: môžete pretiahnuť viac súborov naraz)" + add: "Pridaj nové Emoji" + name: "Meno" + image: "Obrázok" + delete_confirm: "Ste si istý, že chcete zmazať: %{name}: emoji?" + embedding: + get_started: "Pokiaľ chcete vložiť Discourse na inú stránku, začnite pridaním jej hostiteľa." + confirm_delete: "Ste si istý, že chcete zmazať tohoto hostiteľa?" + sample: "Použite nasledovný HTML kód vo Vašej stránke pre vytvorenie vloženej témy Discourse. Nahraďte REPLACE_ME kanonickou URL adresou stránky, do ktorej to vkladáte." + title: "Vkladám" + host: "Povolení hostitelia" + edit: "uprav" + category: "Prispievať do kategórií" + add_host: "Pridať hostiteľa" + settings: "Nastavenia vkladania" + feed_settings: "Nastavenie zdrojov" + feed_description: "Zadaním RSS/ATOM kanálu Vašich stránok zlepší schopnosť Discourse vladať Váš obsah." + crawling_settings: "Nastavenia vyhľadávača" + crawling_description: "Ak Discourse vytvorí tému pre Váš príspevok a neexistuje žiadny RSS/ATOM kanál tak sa pokúsime získať Váš obsah z HTML. Získanie obsahu môže byt niekedy výzva a preto poskytujeme možnosť špecifikovať CSS pravidlá na uľahčenie získania obsahu." + embed_by_username: "Užívateľské meno pre vytváranie tém" + embed_post_limit: "Maximálny počet vložených príspevkov" + embed_username_key_from_feed: "Kľúč na získanie užívateľského mena discourse zo zdroja" + embed_truncate: "Skrátiť vložené príspevky" + embed_whitelist_selector: "CSS selector pre elementy ktoré je možné vkladať" + embed_blacklist_selector: "CSS selector pre elementy ktoré nie je možné vkladať" + feed_polling_enabled: "importovať príspevky cez RSS/ATOM" + feed_polling_url: "URL adresa zdroja RSS/ATOM na preskúmanie" + save: "Uložiť Nastavenia vkladania" + permalink: + title: "Trvalé odkazy" + url: "URL" + topic_id: "IT témy" + topic_title: "Témy" + post_id: "ID príspevku" + post_title: "Príspevok" + category_id: "ID kategórie" + category_title: "Kategória" + external_url: "Externá URL" + delete_confirm: Ste si istý, že chcete zmazať tento trvalý odkaz? + form: + label: "Nový:" + add: "Pridať" + filter: "Hľadať (URL alebo externá URL)" + lightbox: + download: "stiahnuť" + search_help: + title: 'Pomoc pri vyhľadávaní' + keyboard_shortcuts_help: + title: 'Klávesové skratky' + jump_to: + title: 'Preskočiť na' + home: 'g, h Domov' + latest: 'g, l Najnovšie' + new: 'g, n Nové' + unread: 'g, u Neprečítané' + categories: 'g, c Kategórie' + top: 'g, t Hore' + bookmarks: 'g, b Záložky' + profile: 'g, p Profil' + messages: 'g, m Správy' + navigation: + title: 'Navigácia' + jump: '# Choď na príspevok #' + back: 'u Späť' + up_down: 'k/j Presuň označené ↑ ↓' + open: 'o or EnterOtvoriť zvolenú tému' + next_prev: 'shift+j/shift+k Nasledujúca/predchádzajúca sekcia' + application: + title: 'Aplikácia' + create: 'c Vytvoriť novú tému' + notifications: 'n Otvor upozornenia' + hamburger_menu: '= Otvoriť hamburger menu' + user_profile_menu: 'p Otvor užívateľské menu' + show_incoming_updated_topics: '. Zobraz aktualizované témy' + search: '/ Hľadať' + help: '? Pomoc s klávesovými skratkami' + dismiss_new_posts: 'x, r Zahodiť Nové/Príspevky' + dismiss_topics: 'x, t Zahodiť témy' + log_out: 'shift+z shift+z Odhlásiť sa' + actions: + title: 'Akcie' + bookmark_topic: 'f Zmeniť tému záložky' + pin_unpin_topic: 'shift+p Pripnúť/Odopnúť tému' + share_topic: 'shift+s Zdielať tému' + share_post: 's Zdielať príspevok' + reply_as_new_topic: 't Odpoveď ako súvisiaca téma' + reply_topic: 'shift+r Odpovedať na tému' + reply_post: 'r Odpovedať na príspevok' + quote_post: 'q Citovať príspevok' + like: 'l Označiť príspevok "Páči sa"' + flag: '! Označiť príspevok ' + bookmark: 'b Pridať príspevok do záložiek' + edit: 'e Editovat príspevok' + delete: 'd Zmazať príspevok' + mark_muted: 'm, m Umlčať tému' + mark_regular: 'm, r Obyčajná (preddefinovaná) téma' + mark_tracking: 'm, w Sledovať tému' + mark_watching: 'm, w Sledovať tému' + badges: + title: Odznaky + allow_title: "môže byť použitý ako názov" + multiple_grant: "Môže byť ocenené viacnásobne" + badge_count: + one: "1 Odznak" + few: "%{count} Odznaky" + other: "%{count} Odznakov" + more_badges: + one: "+1 Viac" + few: "+%{count} Viac" + other: "+%{count} Viac" + granted: + one: "1 povolené" + few: "%{count} povolené" + other: "%{count} povolených" + select_badge_for_title: Vyberte odznak, ktorý chcete použiť ako Váš titul + none: "" + badge_grouping: + getting_started: + name: Začíname + community: + name: Komunita + trust_level: + name: Stupeň dôvery + other: + name: Ostatné + posting: + name: Prispievam + badge: + editor: + name: Editor + description: Úprava prvého príspevku + basic_user: + name: Základné + description: Povolené všetky základné funkcie komunity + member: + name: Člen + description: Povolené pozývanie + regular: + name: Bežný + description: Povolené zmeny kategóriií, premenovávanie, odkazy a lóža + leader: + name: Vodca + description: Pridelené globálna editácia, pripnutia, uzatváranie, archivovanie, rozdeľovanie a spájanie + welcome: + name: Vitajte + description: Prijal "Páči sa" + autobiographer: + description: Vyplnený užívateľský profil + anniversary: + name: Výročie + description: Aktívny člen, napísal aspoň jeden príspevok za rok + nice_post: + name: Pekný príspevok + description: Príspevok získal 10 "Páči sa". Tento odznak môže byť pridelený viac krát + good_post: + name: Dobrý príspevok + description: Príspevok získal 25 "Páči sa". Tento odznak môže byť pridelený viac krát + great_post: + name: Vynikajúci príspevok + description: Príspevok získal 50 "Páči sa". Tento odznak môže byť pridelený viac krát + nice_topic: + name: Pekná téma + description: Téma získala 10 "Páči sa". Tento odznak môže byť pridelený viac krát + good_topic: + name: Dobrá téma + description: Téma získala 25 "Páči sa". Tento odznak môže byť pridelený viac krát + great_topic: + name: Vynikajúca téma + description: Téma získala 50 "Páči sa". Tento odznak môže byť pridelený viac krát + nice_share: + name: Pekné zdieľanie + description: Zdieľaný príspevok s 25 jedinečnými návštevami + good_share: + name: Dobré zdieľanie + description: Zdieľaný príspevok s 300 jedinečnými návštevami + great_share: + name: Výborné zdieľanie + description: Zdieľaný príspevok s 1000 jedinečnými návštevami + first_like: + name: Prvé "Páči sa mi" + description: Príspevok, ktorý sa páčil + first_flag: + name: Prvé označenie + description: Označený príspevok + promoter: + name: Propagátor + description: pozval užívateľa + campaigner: + description: Pozval 3 základných užívateľov (stupeň dôvery 1) + champion: + name: Šampión + description: Pozval 5 členov (stupeň dôvery 2) + first_share: + name: Prvé zdieľanie + description: Zdieľal príspevok + first_link: + name: Prvý odkaz + description: Pridaný vnútorný odkaz na ďalšiu tému + first_quote: + name: Prvý citát + description: Citoval užívateľa + read_guidelines: + name: 'Prečítať pravidlá ' + description: Prečítať pravidlá komunity + reader: + name: Čitateľ + description: Prečítať každý príspevok v téme, ktorá má viac ako 100 príspevkov + popular_link: + name: Populárny odkaz + description: Príspevok s externým odkazom s najmenej 50 kliknutiami + hot_link: + name: Horúci odkaz + description: Príspevok s externým odkazom s najmenej 300 kliknutiami + famous_link: + name: Skvelý odkaz + description: Príspevok s externým odkazom s najmenej 1000 kliknutiami + google_search: | +

    Vyhľadávať pomocou Google

    +

    +

    +

    diff --git a/config/locales/client.sq.yml b/config/locales/client.sq.yml index a66e2fe87..50bd3fe12 100644 --- a/config/locales/client.sq.yml +++ b/config/locales/client.sq.yml @@ -271,6 +271,8 @@ sq: one: "1 user" other: "%{count} users" groups: + add: "Shto" + selector_placeholder: "Shto anëtarë" visible: "Grupi është i dukshëm për të gjithë përdoruesit" title: one: "grupë" @@ -278,7 +280,6 @@ sq: members: "Anëtarë" posts: "Postime" alias_levels: - title: "Kush mund ta përdori këtë grup si një nofkë?" nobody: "Asnjëri" only_admins: "Vetëm adminët" mods_and_admins: "Vetëm moderatorët dhe Adminët" @@ -293,7 +294,6 @@ sq: '6': "Responses" '7': "Përmendje" '9': "Citim" - '10': "Shënuar" '11': "Redaktuar" '12': "Sent Items" '13': "Inbox" @@ -303,6 +303,8 @@ sq: all_subcategories: "të gjitha" no_subcategory: "asnjë" category: "Kategori" + reorder: + apply_all: "Apliko" posts: "Postime" topics: "Tema" latest: "Të fundit" @@ -345,6 +347,7 @@ sq: private_messages: "Mesazhet" activity_stream: "Aktiviteti" preferences: "Preferencat" + expand_profile: "Shpalos" bookmarks: "Të Preferuarat" bio: "Rreth meje" invited_by: "Të ftuar nga unë" @@ -400,8 +403,6 @@ sq: warnings_received: "paralajmërimet" messages: all: "Të gjithë" - mine: "Mine" - unread: "Palexuar" change_password: success: "(email u dërgua)" in_progress: "(duke dërguar emailin)" @@ -500,6 +501,8 @@ sq: auto_track_options: never: "asnjëherë" immediately: "menjëherë" + after_30_seconds: "pas 30 sekonda" + after_1_minute: "pas 1 minute" invited: search: "shkruaj për të kërkuar ftesat..." title: "Ftesa" @@ -559,6 +562,7 @@ sq: server: "Gabim në Server" forbidden: "Ndalohet Hyrja" unknown: "Gabim" + not_found: "Faqja nuk u gjet" desc: network: "Ju lutemi, kontrolloni lidhjen me Internetin." network_fixed: "Duket sikur u ktheve." @@ -592,6 +596,8 @@ sq: replies_lowercase: one: përgjigje other: përgjigje + signup_cta: + hide_forever: "jo faleminderit" summary: enabled_description: "You're viewing a summary of this topic: the most interesting posts as determined by the community." description: "Janë {{count}} përgjigje." @@ -649,6 +655,7 @@ sq: admin_not_allowed_from_ip_address: "You can't log in as admin from that IP address." resend_activation_email: "Click here to send the activation email again." sent_activation_email_again: "We sent another activation email to you at {{currentEmail}}. It might take a few minutes for it to arrive; be sure to check your spam folder." + to_continue: "Ju lutem, Identifikohuni" google: title: "me Google" message: "Authenticating with Google (make sure pop up blockers are not enabled)" @@ -671,8 +678,14 @@ sq: google: "Google" twitter: "Twitter" emoji_one: "Emoji One" + shortcut_modifier_key: + shift: 'Shift' + ctrl: 'Ctrl' + alt: 'Alt' composer: emoji: "Emoji :smile:" + more_emoji: "më shumë..." + options: "Opsione" add_warning: "This is an official warning." posting_not_on_topic: "Which topic do you want to reply to?" saving_draft_tip: "duke e ruajtur..." @@ -701,6 +714,7 @@ sq: edit_reason_placeholder: "pse jeni duke e redaktuar?" show_edit_reason: "(arsye redaktimit)" view_new_post: "Shikoni postimin tuaj te ri." + saving: "Duke e ruajtur" saved: "U Ruajt!" saved_draft: "Post draft in progress. Select to resume." uploading: "Duke nga ngarkuar..." @@ -729,6 +743,8 @@ sq: hr_title: "Horizontal Rule" help: "Markdown Editing Help" toggler: "hide or show the composer panel" + modal_ok: "OK" + modal_cancel: "Anulo" admin_options_title: "Optional staff settings for this topic" auto_close: label: "Auto-close topic time:" @@ -757,6 +773,9 @@ sq: moved_post: "

    {{username}} moved {{description}}

    " linked: "

    {{username}} {{description}}

    " granted_badge: "

    Earned '{{description}}'

    " + alt: + quoted: "Cituar nga" + posted: "Postim nga" popup: mentioned: '{{username}} mentioned you in "{{topic}}" - {{site_title}}' quoted: '{{username}} quoted you in "{{topic}}" - {{site_title}}' @@ -776,6 +795,7 @@ sq: select_file: "Select File" image_link: "link your image will point to" search: + sort_by: "Rendit sipas" title: "search topics, posts, users, or categories" no_results: "Nuk i gjet asnjë rezultat." no_more_results: "No more results found." @@ -787,6 +807,7 @@ sq: category: "Kërko tek kategoria \"{{category}}\"" topic: "Kërko tek kjo temë" private_messages: "Search messages" + new_item: "e re" go_back: 'kthehu mbrapa' not_logged_in_user: 'user page with summary of current activity and preferences' current_user: 'go to your user page' @@ -924,8 +945,10 @@ sq: title: "Tracking" description: "A count of new replies will be shown for this topic. You will be notified if someone mentions your @name or replies to you. " regular: + title: "Normal" description: "You will be notified if someone mentions your @name or replies to you." regular_pm: + title: "Normal" description: "You will be notified if someone mentions your @name or replies to you." muted_pm: title: "Muted" @@ -1296,6 +1319,7 @@ sq: tracking: title: "Tracking" regular: + title: "Normal" description: "You will be notified if someone mentions your @name or replies to you." muted: title: "Muted" @@ -1406,9 +1430,11 @@ sq: title_in: "Category - {{categoryName}}" help: "all topics grouped by category" unread: + title: "Palexuar " help: "topics you are currently watching or tracking with unread posts" new: lower_title: "e re" + title: "I Ri" help: "topics created in the last few days" posted: title: "Postimet e Mia" @@ -2018,7 +2044,6 @@ sq: unlock_trust_level: "Unlock Trust Level" tl3_requirements: title: "Requirements for Trust Level 3" - table_title: "Në 100 ditët e fundit:" value_heading: "Vlera" requirement_heading: "Requirement" visits: "Vizita" @@ -2079,8 +2104,8 @@ sq: confirm: 'Confirmation' dropdown: "Dropdown" site_text: - none: "Choose a type of content to begin editing." title: 'Text Content' + edit: 'redakto' site_settings: show_overriden: 'Only show overridden' title: 'Rregullimet' @@ -2180,6 +2205,8 @@ sq: name: "Name" image: "Imazh" delete_confirm: "Are you sure you want to delete the :%{name}: emoji?" + embedding: + edit: "redakto" permalink: title: "Permalinks" url: "URL" diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index e409e6baa..0476af986 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -136,6 +136,7 @@ sv: admin_title: "Admin" flags_title: "Flaggningar" show_more: "visa mer" + show_help: "alternativ" links: "Länkar" links_lowercase: one: "länk" @@ -302,6 +303,11 @@ sv: everyone: "Alla" trust_levels: none: "Inga" + notifications: + regular: + title: "Normal" + muted: + title: "Tystad" user_action_groups: '1': "Gillningar givna" '2': "Gillningar mottagna" @@ -311,7 +317,6 @@ sv: '6': "Svar" '7': "Omnämnanden" '9': "Citat" - '10': "Stjärnmärkt" '11': "Redigeringar" '12': "Skickade föremål" '13': "Inkorg" @@ -321,6 +326,7 @@ sv: all_subcategories: "alla" no_subcategory: "ingen" category: "Kategori" + category_list: "Visa kategori-lista" reorder: title: "Sortera kategorier" title_long: "Sortera litan av katergorier" @@ -415,9 +421,13 @@ sv: warnings_received: "varningar" messages: all: "Alla" - mine: "Mina" - unread: "Olästa" + inbox: "Inkorg" + sent: "Skickat" + archive: "Arkiv" groups: "Mina Grupper" + bulk_select: "Välj meddelanden" + move_to_inbox: "Flytta till inkorg" + select_all: "Markera alla" change_password: success: "(e-brev skickat)" in_progress: "(skickar e-brev)" @@ -975,8 +985,10 @@ sv: title: "Följer" description: "En räknare över antal nya svar visas för detta ämne. Du notifieras om någon nämner ditt @namn eller svarar dig." regular: + title: "Normal" description: "Du kommer att få en notifiering om någon nämner ditt @namn eller svarar dig." regular_pm: + title: "Normal" description: "Du kommer att notifieras om någon nämner ditt @namn eller svarar dig." muted_pm: title: "tystade" @@ -1338,6 +1350,7 @@ sv: tracking: title: "Följer" regular: + title: "Normal" description: "Du notifieras om någon nämner ditt @namn eller svarar på ditt inlägg." muted: title: "Tystad" @@ -2049,7 +2062,6 @@ sv: unlock_trust_level: "Lås upp förtroendenivå" tl3_requirements: title: "Krav för Förtroendenivå 3" - table_title: "Under de senaste 100 dagarna:" value_heading: "värde" requirement_heading: "krav" visits: "besök" diff --git a/config/locales/client.te.yml b/config/locales/client.te.yml index 0ccd1919f..a8a122f57 100644 --- a/config/locales/client.te.yml +++ b/config/locales/client.te.yml @@ -220,7 +220,6 @@ te: members: "సభ్యులు" posts: "టపాలు" alias_levels: - title: "ఈ గుంపును మారుపేరుతో ఎవరు వాడవచ్చు?" nobody: "ఎవరూకాదు" only_admins: "కేవలం అధికారులే" mods_and_admins: "కేవలం అధికారులు మరియు నిర్వాహకులు మాత్రమే" @@ -233,7 +232,6 @@ te: '4': "విషయాలు" '7': "ప్రస్తావనలు" '9': "కోట్ లు" - '10': "నక్షత్రపు" '11': "సవరణలు" '12': "పంపిన అంశాలు" '13': "ఇన్ బాక్స్" @@ -318,8 +316,6 @@ te: warnings_received: "హెచ్చరికలు" messages: all: "అన్నీ" - mine: "నావి" - unread: "చదవని" change_password: success: "(ఈమెయిల్ పంపిన)" in_progress: "(ఈమెయిల్ పంపుతోన్నాం)" @@ -1726,7 +1722,6 @@ te: unlock_trust_level: "నమ్మకపు స్థాయిని వదిలేయి" tl3_requirements: title: "నమ్మకపు స్థాయి 3 అవసరాలు" - table_title: "గత 100 రోజుల్లో:" value_heading: "విలువ" requirement_heading: "అవసరం" visits: "సందర్శనాలు" @@ -1785,7 +1780,6 @@ te: text: 'పాఠ్య క్షేత్రం' confirm: 'ఖాయము' site_text: - none: "సవరణను ప్రారంభించడానికి విషయం రకాన్ని ఎంచుకోండి." title: 'పాఠ్య కాంటెంటు' site_settings: show_overriden: 'ప్రాబల్యం ఉన్న వాటిని మాత్రమే చూపించు' diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml index 097ea23c9..e5801a246 100644 --- a/config/locales/client.tr_TR.yml +++ b/config/locales/client.tr_TR.yml @@ -267,6 +267,12 @@ tr_TR: total_rows: other: "%{count} kullanıcı" groups: + empty: + posts: "Bu grubun üyelerinden mesaj yok." + members: "Bu grupta üye yok." + mentions: "Bu gruptan söz edilmemiş." + messages: "Bu grup için bir mesaj yok." + topics: "Bu grubun üyelerinden konu yok." add: "Ekle" selector_placeholder: "Üye ekle" owner: "sahip" @@ -285,6 +291,16 @@ tr_TR: trust_levels: title: "Eklendiklerinde üyelere otomatik olarak güven seviyesi verilir:" none: "Hiç" + notifications: + watching: + title: "Gözleniyor" + tracking: + title: "Takip ediliyor" + regular: + title: "Normal" + muted: + title: "Susturuldu" + description: "Bu gruptan herhangi yeni konuyla ilgili asla bildirim almayacaksınız" user_action_groups: '1': "Verilen Beğeniler" '2': "Alınan Beğeniler" @@ -294,7 +310,6 @@ tr_TR: '6': "Yanıtlar" '7': "Bahsedenler" '9': "Alıntılar" - '10': "Yıldızlılar" '11': "Düzenlemeler" '12': "Yollanmış ögeler" '13': "Gelen Kutusu" @@ -304,6 +319,7 @@ tr_TR: all_subcategories: "hepsi" no_subcategory: "hiçbiri" category: "Kategori" + category_list: "Kategori ekranı listesi" reorder: title: "Kategorileri Yeniden Sırala" title_long: "Kategori listesini yeniden yapılandır" @@ -358,6 +374,7 @@ tr_TR: invited_by: "Tarafından Davet Edildi" trust_level: "Güven Seviyesi" notifications: "Bildirimler" + statistics: "istatistikler" desktop_notifications: label: "Masaüstü Bildirimleri" not_supported: "Bildirimler bu tarayıcıda desteklenmiyor. Üzgünüz." @@ -411,9 +428,14 @@ tr_TR: warnings_received: "uyarılar" messages: all: "Hepsi" - mine: "Benimkiler" - unread: "Okunmamışlar" + inbox: "Gelen Kutusu" + sent: "Gönderildi" + archive: " Arşiv" groups: "Gruplarım" + bulk_select: "Mesajları seçin" + move_to_inbox: "Gelen kutusuna taşı" + failed_to_move: "Seçilen mesajları taşımak başarısız oldu (muhtemelen ağınız çöktü)" + select_all: "Tümünü seç" change_password: success: "(e-posta gönderildi)" in_progress: "(e-posta yollanıyor)" @@ -837,6 +859,7 @@ tr_TR: granted_badge: "Rozet alındı" popup: mentioned: '{{username}}, "{{topic}}" başlıklı konuda sizden bahsetti - {{site_title}}' + group_mentioned: '{{username}} sizden bahsetti "{{topic}}" - {{site_title}}' quoted: '{{username}}, "{{topic}}" başlıklı konuda sizden alıntı yaptı - {{site_title}}' replied: '{{username}}, "{{topic}}" başlıklı konuda size cevap verdi - {{site_title}}' posted: '{{username}}, "{{topic}}" başlıklı konuya yazdı - {{site_title}}' @@ -935,6 +958,12 @@ tr_TR: create: 'Yeni Konu' create_long: 'Yeni bir konu oluştur' private_message: 'Mesajlaşma başlat' + archive_message: + help: 'Mesajı arşivine taşı' + title: ' Arşiv' + move_to_inbox: + title: 'Gelen kutusuna taşı' + help: 'Mesajı yeniden gelen kutusuna taşı' list: 'Konular' new: 'yeni konu' unread: 'okunmamış' @@ -979,6 +1008,7 @@ tr_TR: auto_close_title: 'Otomatik Kapatma Ayarları' auto_close_save: "Kaydet" auto_close_remove: "Bu Konuyu Otomatik Olarak Kapatma" + auto_close_immediate: "Son konudaki mesaj %{hours} saat olmuş, bu yüzden konu hemen kapanacak" progress: title: konu gidişatı go_top: "en üst" @@ -1097,6 +1127,7 @@ tr_TR: success: "O kullanıcıyı bu mesajlaşmaya davet ettik." error: "Üzgünüz, kullanıcı davet edilirken bir hata oluştu." group_name: "grup adı" + controls: "Konu Kontrolleri" invite_reply: title: 'Davet Et' username_placeholder: "kullanıcıadı" @@ -1202,8 +1233,6 @@ tr_TR: yes_value: "Evet, vazgeç" via_email: "bu gönderi e-posta ile iletildi" whisper: "bu gönderi yöneticiler için özel bir fısıltıdır" - wiki: - about: "bu gönderi bir wiki; acemi kullanıcılar düzenleyebilir" archetypes: save: 'Seçenekleri kaydet' controls: @@ -2144,7 +2173,7 @@ tr_TR: unlock_trust_level: "Güvenlik Seviyesi Kilidini Aç" tl3_requirements: title: "Güven Seviyesi 3 için Gerekenler" - table_title: "Son 100 günde:" + table_title: "Son %{time_period} günde" value_heading: "Değer" requirement_heading: "Gereksinim" visits: "Ziyaretler" diff --git a/config/locales/client.uk.yml b/config/locales/client.uk.yml index 1a1f7383f..b404fb923 100644 --- a/config/locales/client.uk.yml +++ b/config/locales/client.uk.yml @@ -212,7 +212,6 @@ uk: members: "Учасники" posts: "Дописи" alias_levels: - title: "Хто може використовувати цю групу як аліас?" nobody: "Ніхто" only_admins: "Лише адміністратори" mods_and_admins: "Лише модератори та адміністратори" @@ -226,7 +225,6 @@ uk: '5': "Відповіді" '7': "Згадки" '9': "Цитати" - '10': "Позначені зірочкою" '11': "Редагування" '12': "Надіслані" '13': "Вхідні" @@ -313,8 +311,6 @@ uk: warnings_received: "попередження" messages: all: "Всі" - mine: "Мої" - unread: "Непрочитані" change_password: success: "(лист надіслано)" in_progress: "(надсилання листа)" @@ -1309,7 +1305,6 @@ uk: trust_level_3_requirements: "Вимоги для Рівня довіри 3" tl3_requirements: title: "Вимоги для Рівня довіри 3" - table_title: "Протягом останніх 100 днів:" value_heading: "Значення" requirement_heading: "Вимога" visits: "Відвідини" diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index df7235496..582f0a9b3 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -267,6 +267,12 @@ zh_CN: total_rows: other: "%{count} 位用户" groups: + empty: + posts: "此小组成员没有回复" + members: "此小组没有成员" + mentions: "此小组没有通知" + messages: "此小组没有消息" + topics: "此小组成员没有主题帖" add: "添加" selector_placeholder: "添加成员" owner: "所有者" @@ -285,6 +291,15 @@ zh_CN: trust_levels: title: "当这些用户加入时,信任等级将自动赋予给他们:" none: "无" + notifications: + watching: + title: "关注" + tracking: + title: "追踪" + regular: + title: "普通" + muted: + title: "忽略" user_action_groups: '1': "给赞" '2': "被赞" @@ -294,7 +309,6 @@ zh_CN: '6': "回应" '7': "提到" '9': "引用" - '10': "星标" '11': "编辑" '12': "发送条目" '13': "收件箱" @@ -304,6 +318,7 @@ zh_CN: all_subcategories: "全部" no_subcategory: "无" category: "分类" + category_list: "显示分类列表" reorder: title: "重排序分类" title_long: "重新排序分类列表" @@ -358,6 +373,7 @@ zh_CN: invited_by: "邀请者为" trust_level: "用户级别" notifications: "通知" + statistics: "统计" desktop_notifications: label: "桌面通知" not_supported: "通知功能暂不支持该浏览器。抱歉。" @@ -411,9 +427,13 @@ zh_CN: warnings_received: "警告" messages: all: "所有" - mine: "我的" - unread: "未读" + inbox: "收件箱" + sent: "已发送" + archive: "存档" groups: "我的小组" + bulk_select: "选择消息" + move_to_inbox: "移动到收件箱" + select_all: "全选" change_password: success: "(电子邮件已发送)" in_progress: "(正在发送电子邮件)" @@ -837,6 +857,7 @@ zh_CN: granted_badge: "勋章授予" popup: mentioned: '{{username}}在“{{topic}}”提到了你 - {{site_title}}' + group_mentioned: '{{username}}在“{{topic}}”提到了你 - {{site_title}}' quoted: '{{username}}在“{{topic}}”引用了你的帖子 - {{site_title}}' replied: '{{username}}在“{{topic}}”回复了你 - {{site_title}}' posted: '{{username}}在“{{topic}}”中发布了帖子 - {{site_title}}' @@ -935,6 +956,12 @@ zh_CN: create: '新主题' create_long: '创建一个新主题' private_message: '发送消息' + archive_message: + help: '移动消息到存档' + title: '存档' + move_to_inbox: + title: '移动到收件箱' + help: '移动消息到收件箱' list: '主题' new: '新主题' unread: '未读' @@ -1097,6 +1124,7 @@ zh_CN: success: "我们已经邀请了该用户加入这个消息交流。" error: "抱歉,在邀请该用户时发生了错误。" group_name: "群组名" + controls: "主题控制" invite_reply: title: ' 邀请' username_placeholder: "用户名" @@ -1203,7 +1231,7 @@ zh_CN: via_email: "通过电子邮件发送的帖子" whisper: "这个帖子是只对版主可见的密语" wiki: - about: "这个帖子是维基;基础用户能编辑它" + about: "这个帖子是维基" archetypes: save: '保存选项' controls: @@ -1960,6 +1988,7 @@ zh_CN: change_site_setting: "更改站点设置" change_site_customization: "更改站点自定义" delete_site_customization: "删除站点自定义" + change_site_text: "更改站点文字" suspend_user: "封禁用户" unsuspend_user: "解禁用户" grant_badge: "授予徽章" @@ -2144,7 +2173,7 @@ zh_CN: unlock_trust_level: "解锁信任等级" tl3_requirements: title: "3 级信任等级的需求" - table_title: "在过去的 100 天中:" + table_title: "在最近%{time_period}天:" value_heading: "价值" requirement_heading: "需求" visits: "访问" @@ -2314,7 +2343,7 @@ zh_CN: title: "Emoji" help: "增加所有人可用的 emoji。(高端技巧:一次性拖进多个文件)" add: "增加新的 Emoji" - name: "姓名" + name: "名称" image: "图片" delete_confirm: "你确定要删除 :%{name}: emoji 么?" embedding: diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml index 946575eb5..72807fe6c 100644 --- a/config/locales/client.zh_TW.yml +++ b/config/locales/client.zh_TW.yml @@ -90,11 +90,22 @@ zh_TW: google+: '在 Google+ 分享此連結' email: '以電子郵件分享此連結' action_codes: - split_topic: "切分此討論話題 %{when}" + split_topic: "於 %{when} 切分此討論話題" autoclosed: - enabled: '關閉 %{when}' + enabled: '於 %{when} 關閉' + disabled: '於 %{when} 開啟' closed: - enabled: '關閉 %{when}' + enabled: '於 %{when} 關閉' + disabled: '於 %{when} 開啟' + archived: + enabled: '於 %{when} 封存' + disabled: '於 %{when} 解除封存' + pinned: + enabled: '於 %{when} 置頂' + disabled: '於 %{when} 解除置頂' + pinned_globally: + enabled: '於 %{when} 全局置頂' + disabled: '於 %{when} 解除置頂' topic_admin_menu: "討論話題管理員操作" emails_are_disabled: "管理員已經停用了所有外寄郵件功能。通知信件都不會寄出。" edit: '編輯此討論話題的標題與分類' @@ -110,6 +121,7 @@ zh_TW: admin_title: "管理員" flags_title: "投訴" show_more: "顯示更多" + show_help: "選項" links: "連結" links_lowercase: other: "鏈結" @@ -181,6 +193,7 @@ zh_TW: saved: "儲存完畢!" upload: "上傳" uploading: "正在上傳..." + uploading_filename: "{{filename}} 上傳中..." uploaded: "上傳完畢!" enable: "啟用" disable: "停用" @@ -188,8 +201,10 @@ zh_TW: revert: "回復" failed: "失敗" switch_to_anon: "匿名模式" + switch_from_anon: "登出匿名模式" banner: close: "關閉此橫幅" + edit: "編輯此橫幅 >>" choose_topic: none_found: "未找到任何討論話題。" title: @@ -205,6 +220,8 @@ zh_TW: edit: "編輯" cancel: "取消" view_pending: "觀看等待審核的貼文" + has_pending_posts: + other: "本主題仍有 {{count}}篇貼文等待審核" confirm: "儲存變更" delete_prompt: "您確定要刪除 %{username} 這個帳號嗎?這會同時將該帳號的所有貼文一併刪除,並封鎖他的電子郵件與 IP。" approval: @@ -256,12 +273,14 @@ zh_TW: members: "成員" posts: "文章" alias_levels: + title: "誰可以在這個群組發送訊息和使用@提到" nobody: "沒有" only_admins: "只有管理員" mods_and_admins: "只有板主以及管理員" members_mods_and_admins: "只有群組成員、板主以及管理員" everyone: "所有人" trust_levels: + title: "當這些成員加入時自動提升信任等級:" none: "無" user_action_groups: '1': "已按讚" @@ -272,7 +291,6 @@ zh_TW: '6': "回應" '7': "提到" '9': "引用" - '10': "收藏" '11': "編輯" '12': "送出的項目" '13': "收件匣" @@ -283,7 +301,12 @@ zh_TW: no_subcategory: "無" category: "分類" reorder: + title: "重新排序分類" + title_long: "重新排序分類列表" + fix_order: "固定位置" + fix_order_tooltip: "並非所有的分類皆有唯一的位置參數, 可能會有出乎意料之外的結果." save: "儲存順序" + position: "位置" posts: "貼文" topics: "標題" latest: "最近" @@ -311,6 +334,8 @@ zh_TW: topics_entered: "已閱讀的討論話題" post_count: "# 文章" confirm_delete_other_accounts: "你確定要刪除這些帳號?" + user_fields: + none: "(選擇一個選項)" user: said: "{{username}}:" profile: "基本資料" @@ -374,8 +399,6 @@ zh_TW: warnings_received: "警告" messages: all: "全部" - mine: "我的" - unread: "未讀" groups: "我的群組" change_password: success: "( 寄出的郵件 )" @@ -1955,7 +1978,6 @@ zh_TW: unlock_trust_level: "解鎖信任等級" tl3_requirements: title: "信任等級 3 之條件" - table_title: "在過去100天內:" value_heading: "價值" requirement_heading: "要求" visits: "訪問" @@ -2021,6 +2043,7 @@ zh_TW: revert: "恢復變更" revert_confirm: "你確定要撤回這個改動?" go_back: "回到搜尋" + show_overriden: '只顯示修改過的項目' site_settings: show_overriden: '只顯示修改過的項目' title: '設定' @@ -2131,6 +2154,7 @@ zh_TW: settings: "嵌入設定" crawling_settings: "爬蟲設定" feed_polling_enabled: "匯入帖子藉由 RSS/ATOM" + save: "儲存崁入設定" permalink: title: "固定連結" url: "網址" @@ -2161,6 +2185,7 @@ zh_TW: categories: 'g, c 分類' top: 'g, t 頂端' bookmarks: 'g, b 書籤' + profile: 'g, p 個人檔案' messages: 'g, m 私人訊息' navigation: title: '導航' @@ -2288,6 +2313,7 @@ zh_TW: name: 說客 champion: name: 鬥士 + description: 已邀請 5 位成員 ( 信任等級 2 ) first_share: name: 首個分享 description: 分享文章 @@ -2310,4 +2336,5 @@ zh_TW: name: 熱門連結 description: 張貼的外部連結有最少 300 次的點擊 famous_link: + name: 出色連結 description: 張貼的外部連結有最少 1000 次的點擊 diff --git a/config/locales/server.ar.yml b/config/locales/server.ar.yml index b25d143d8..dd2e7b318 100644 --- a/config/locales/server.ar.yml +++ b/config/locales/server.ar.yml @@ -106,6 +106,8 @@ ar: not_found: "تعذر العثور على رابط العنوان المطلوب" invalid_access: "لا تمتلك صلاحيات لعرض المطلوب " read_only_mode_enabled: "الموقع في وضع القراءة فقط. عُطّلت التفاعلات." + reading_time: "وقت القراءة" + likes: "الإعجابات" too_many_replies: zero: "يمكن للأعضاء الجدد الرد كما يحلو لهم في كل موضوع." one: "آسفون، فلا يمكن للأعضاء الجدد سوى الرد مرة واحدة في نفس الموضوع." @@ -607,6 +609,9 @@ ar: email_body: "%{link}\n\n%{message}" notify_moderators: title: "شيء آخر " + description: 'يحتاج هذا المنشور إجراء من الطاقم لسبب آخر لم يُذكر أعلاه.' + long_form: ' بَلِّغ عن هذا لتنبيه الطاقم ' + email_title: 'المنشور في "%{title}" يتطلب تنبيه الطاقم.' email_body: "%{link}\n\n%{message}" bookmark: title: 'المفضلة' @@ -631,6 +636,7 @@ ar: long_form: 'ترفع علم هذا عن صورة غير ملائمة' notify_moderators: title: "شيء آخر " + description: 'هذا الموضوع يتطلب اهتمام الطاقم العام معتمداً على التوجيهات, شروط الخدمة, أو لسبب آخر لم يذكر أعلاه.' long_form: 'علم هذا لتنبيه المراقب' email_title: 'الموضوع "%{title}" يتطلب موافقة المشرف' email_body: "%{link}\n\n%{message}" @@ -1028,7 +1034,7 @@ 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_time_period: "الفترة الزمنية لمتطلبات مستور الثقة 3." tl3_requires_topics_replied_to: "أقل عدد من المواضيع التي يجب على المستخدم الرد عليها في آخر 100 يوم للتأهل لمستوى الثقة 3. (0 أو أكثر)" tl3_requires_topics_viewed: "نسبة الموضوعات التي تم إنشاؤها في آخر 100 يوم، تجعل المستخدم مؤهلًا لعرضه للترقية إلى مستوى الثقة الثالث3 . (من 0 الى 100)" tl3_requires_posts_read: "النسبه المئويه للمنشورات التي تم انشاءها في اخر 100 يوم ,التي يحتاج المستخدم لمعينتها تؤهله للترقيه للمستوي الثقه 3. (0 الي 100)" @@ -1103,6 +1109,7 @@ ar: disable_emails: "منع Discourse من إرسال أي نوع من رسائل البريد الإلكتروني" strip_images_from_short_emails: "شريط الصور من البريد الإلكتروني لها حجم أقل من 2800 بايت" short_email_length: "طول أقصر بريد الكتروني بـ Bytes." + display_name_on_email_from: "أعرض الاسماء كاملة في البريد الالكتروني من المجال." pop3_polling_enabled: "تصويت عبرPOP3 لردود البريد الإلكتروني." pop3_polling_ssl: "أستخدم SSL عند الأتصال بمخدم POP3.(مُستحسن)" pop3_polling_period_mins: "الفترة دقائق بين التحقق من حساب POP3 للبريد الإلكتروني. ملاحظة : تتطلب إعادة تشغيل." @@ -1141,9 +1148,9 @@ ar: anonymous_account_duration_minutes: "للحمايه الهويه اصنع حساب مجهول كل n دقائق لكل مستخدم.\nمثال:اذا ضبط 600 , بعد انقضي 600 دقيقه من اخر منشور و غير المستخدم للمجهول.تم انشاء حساب مجهول." hide_user_profiles_from_public: "قم بإلغاء الصفحة الشخصية و حقيبة المستخدم و بطاقة المستخدم للمستخدمين المجهولين" allow_profile_backgrounds: "اسمح للأعضاء برفع خلفيات ملف التعريف." - sequential_replies_threshold: "عدد مشاركات عضو في صف واحد في موضوع قبل يجري تذكير حول الردود متسلسلة كثيرة جداً." enable_mobile_theme: "اجهزه الموبايل تستخدم ثيم مناسب للجوال,امكانيه التحويل للموقع الكامل .الغي هذا اذا اردت استخدام انماط سريعه الاستجابه" dominating_topic_minimum_percent: "ما هي النسبه المئويه للمشاركات التي يجب علي المستخدم عملها في الموضوع قبل تلقيه لتنبيه عن خروجه لنطاق الموضوع" + disable_avatar_education_message: "عطل الرسالة التعليمية لتغيير الصورة الرمزية." daily_performance_report: "تحليل سجلات NGINX يومي ونشر موضوع \"قاقم فقط\" مع التفاصيل" suppress_uncategorized_badge: "لا تظهر الوسام للمواضيع غير المصنفة في قائمة الموضوع." permalink_normalizations: "يتم تطبيق التعابير القياسية قبل مطابقة صور الروابط الثابتة، على سبيل المثال: /(\\/topic.*)\\?.*/\\1 سوف تقطع الاستعلامات من الموضوع. التنسيق هو : التعبير القياسي+النص المستخدم \\ إلخ.. للوصول للقطع" @@ -1212,6 +1219,7 @@ ar: invalid_string_max: "يجب ان لا تكون الاحرف اكثر من %{max} " invalid_reply_by_email_address: "القيمة يجب أن تحتوي '%{reply_key}' وتختلف عن إشعار البريد الإلكتروني." notification_types: + group_mentioned: "%{group_name} ذكرت في %{link}" mentioned: "%{display_username} ذكرك في %{link}" liked: "%{display_username} أعجب بمشاركتك في %{link}" replied: "%{display_username} رد على مشاركتك في %{link}" @@ -1772,24 +1780,8 @@ ar: لمزيد من الإرشادات، يرجى الرجوع إلى موقعنا على [community guidelines](%{base_url}/guidelines). user_automatically_blocked: subject_template: "العضو الجديد %{username} تم حجبه بسبب تبليغات عليه في المجتمع" - text_body_template: | - هذه رسالة تلقائية. - - المستخدم الجديد [%{username}](%{base_url}%{user_url})تم حظره تلقائيا بسبب عدة بالغات من عدة مستخدمين %{username}'s post(s). - - رجاءًا [استعراض البلاغات](%{base_url}/admin/flags).إذا تم حظر %{username} بشكل خاطئ من المشاركة، أنقر فوق رفع الحظر [صفحة الإدارة](%{base_url}%{user_url}). - - يمكن تغيير هذا الحد من اعدادات الموقع `block_new_user` . spam_post_blocked: subject_template: "العضو الجديد %{username} تم حجب مشاركاته بسبب تكرار الروابط " - text_body_template: | - هذه رسالة تلقائية. - - عضو جديد [%{username}](%{base_url}%{user_url}) حاول إنشاء المشاركات متعددة مع روابط إلى %{domains},ولكن هذه المشاركات تم حظرها لتجنب البريد المزعج. المستخدم لا يزال قادراً على أنشاء مشاركات جديدة التي لم تصل إلى %{domains}. - - من فضلك [review the user](%{base_url}%{user_url}). - - هذا يمكن تعديلها عن طريق `` newuser_spam_host_threshold` وإعدادات موقع white_listed_spam_host_domains`. unblocked: subject_template: "الحساب مُفعل " text_body_template: |+ @@ -1820,6 +1812,8 @@ ar: 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}'" @@ -1835,6 +1829,21 @@ ar: > %{site_title} -- %{site_description} + يرجى زيارة هذا الرابط لعرض الرسالة: %{base_url}%{url} + user_invited_to_private_message_pm_staged: + subject_template: "[%{site_name}] %{username} دعاك للرسالة '%{topic_title}'" + text_body_template: |2 + + %{username} دعاك لرسالة + + > **%{topic_title}** + > + > %{topic_excerpt} + + في + + > %{site_title} -- %{site_description} + يرجى زيارة هذا الرابط لعرض الرسالة: %{base_url}%{url} user_invited_to_topic: subject_template: "[%{site_name}] %{username} دعوتك للموضوع '%{topic_title}'" @@ -1854,6 +1863,8 @@ ar: user_replied: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1863,6 +1874,8 @@ ar: user_quoted: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1872,6 +1885,8 @@ ar: user_mentioned: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1881,6 +1896,8 @@ ar: user_group_mentioned: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1890,6 +1907,8 @@ ar: user_posted: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1899,10 +1918,20 @@ ar: user_posted_pm: subject_template: "[%{site_name}] [مساءً] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} + --- + %{respond_instructions} + user_posted_pm_staged: + subject_template: "%{optional_re}%{topic_title}" + text_body_template: |2 + + %{message} + --- %{respond_instructions} digest: diff --git a/config/locales/server.bs_BA.yml b/config/locales/server.bs_BA.yml index dab1427b5..51bfc944b 100644 --- a/config/locales/server.bs_BA.yml +++ b/config/locales/server.bs_BA.yml @@ -16,8 +16,6 @@ bs_BA: loading: "Loading" powered_by_html: 'Powered by Discourse, best viewed with JavaScript enabled' log_in: "Log In" - via: "%{username} via %{site_name}" - is_reserved: "is reserved" purge_reason: "Automatically deleted due to being old and unverified" disable_remote_images_download_reason: "Remote images download was disabled because there wasn't enough disk space available." errors: &errors @@ -235,15 +233,11 @@ bs_BA: description: 'Ovaj post sadrži tekst koji je uvredljiv i pogrdan. I kao takav kosi se sa našim pravilima korišćenja.' long_form: 'opomeni kao neprikladno' notify_user: - description: 'Ovaj post sadrži materijal o kojem želim da raspravljam sa osobom direktno, privatno i bez opomene.' long_form: 'korisnik obavješten' email_title: 'Tvoj post u temi "%{title}"' email_body: "%{link}\n\n%{message}" notify_moderators: title: "Obavijesti Moderatore" - description: 'Ovaj post treba prijaviti moderatoru jer se kosi sa pravilima korišćenja, ili drugim razlogom ne navedenim gore.' - long_form: 'obavješten moderator' - email_title: 'Postu u temi "%{title}" treba pažnju moderatora' email_body: "%{link}\n\n%{message}" bookmark: title: 'Sačuvaj' @@ -268,7 +262,6 @@ bs_BA: long_form: 'opomenuto kao neprikladno' notify_moderators: title: "Obavijesti Moderatore" - description: 'Ovu temu treba prijaviti moderatoru jer se kosi sa pravilima korišćenja, ili drugim razlogom ne navedenim gore.' long_form: 'moderator obaviješten' email_title: 'Ova tema "%{title}" treba pažnju moderatora' email_body: "%{link}\n\n%{message}" @@ -392,37 +385,6 @@ bs_BA: consumer_email_warning: "Your site is configured to use Gmail (or another consumer email service) to send email. Gmail limits how many emails you can send. Consider using an email service provider like mandrill.com to ensure email deliverability." site_contact_username_warning: "The site_contact_username setting is blank. Please update it in the Site Settings. Set it to the username of an admin user who should be the sender of system messages." notification_email_warning: "The notification_email setting is blank. Please update it in the Site Settings." - content_types: - education_new_reply: - title: "New User Education: First Replies" - description: "Pop up just-in-time guidance automatically displayed above the composer when new users begin typing their first two new replies." - 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: - title: "New User Guidance" - description: "Guidance and essential information for new users." - welcome_user: - title: "Welcome: New User" - description: "A private message automatically sent to all new users when they sign up." - welcome_invite: - title: "Welcome: Invited User" - description: "A private message automatically sent to all new invited users when they accept the invitation from another user to participate." - login_required_welcome_message: - title: "Login Required: Welcome Message" - description: "Welcome message that is displayed to logged out users when the 'login required' setting is enabled." - login_required: - title: "Login Required: Homepage" - description: "The text displayed for unauthorized users when login is required on the site." - head: - title: "HTML head" - description: "HTML that will be inserted inside the tags." - top: - title: "Top of the pages" - description: "HTML that will be added at the top of every page (after the header, before the navigation or the topic title)." - bottom: - title: "Bottom of the pages" - description: "HTML that will be added at the bottom of every page." site_settings: censored_words: "Words that will be automatically replaced with ■■■■" delete_old_hidden_posts: "Auto-delete any hidden posts that stay hidden for more than 30 days." @@ -661,7 +623,6 @@ bs_BA: max_daily_gravatar_crawls: "Maximum number of times Discourse will check Gravatar for custom avatars in a day" public_user_custom_fields: "A whitelist of custom fields for a user that can be shown publically." allow_profile_backgrounds: "Allow users to upload profile backgrounds." - sequential_replies_threshold: "Number posts a user has to make in a row in a topic before being reminded about too many sequential replies. " enable_mobile_theme: "Mobile devices use a mobile-friendly theme, with the ability to switch to the full site. Disable this if you want to use a custom stylesheet that is fully responsive." dominating_topic_minimum_percent: "What percentage of posts a user has to make in a topic before being reminded about overly dominating a topic." suppress_uncategorized_badge: "Don't show the badge for uncategorized topics in topic lists." @@ -685,7 +646,6 @@ bs_BA: notify_about_flags_after: "If there are flags that haven't been handled after this many hours, send an email to the contact_email. Set to 0 to disable." enable_cdn_js_debugging: "Allow /logs to display proper errors by adding crossorigin permissions on all js includes." show_create_topics_notice: "If the site has fewer than 5 public topics, show a notice asking admins to create some topics." - vacuum_db_days: "Run VACUUM FULL ANALYZE to reclaim DB space after migrations (set to 0 to disable)" prevent_anons_from_downloading_files: "Prevent anonymous users from downloading files. WARNING: this will prevent any site assets posted as attachments from working." errors: invalid_email: "Invalid email address." @@ -1033,24 +993,8 @@ bs_BA: For additional guidance, please refer to our [community guidelines](%{base_url}/guidelines). user_automatically_blocked: subject_template: "New user %{username} blocked due to community flags" - text_body_template: | - This is an automated message. - - The new user [%{username}](%{base_url}%{user_url}) was automatically blocked because multiple users flagged %{username}'s post(s). - - Please [review the flags](%{base_url}/admin/flags). If %{username} was incorrectly blocked from posting, click the unblock button on [the admin page for this user](%{base_url}%{user_url}). - - This threshold can be changed via the `block_new_user` site settings. spam_post_blocked: subject_template: "New user %{username} posts blocked due to repeated links" - text_body_template: | - This is an automated message. - - The new user [%{username}](%{base_url}%{user_url}) tried to create multiple posts with links to %{domains}, but those posts were blocked to avoid spam. The user is still able to create new posts that do not link to %{domains}. - - Please [review the user](%{base_url}%{user_url}). - - This can be modified via the `newuser_spam_host_threshold` and `white_listed_spam_host_domains` site settings. unblocked: subject_template: "Account unblocked" text_body_template: | @@ -1074,8 +1018,6 @@ bs_BA: unsubscribe: title: "Odjavi se" description: "Niste zainteresovani za ove email-e? Nema problema! Kliknite ovdje da se odjavite:" - reply_by_email: "Da odgovorite, reply to this email or visit %{base_url}%{url} in your browser." - visit_link_to_respond: "Da odgovorite, posjetite %{base_url}%{url} u vašem browseru." posted_by: "Odgovoreno %{username} dana %{post_date}" user_invited_to_private_message_pm: subject_template: "[%{site_name}] %{username} invited you to a private message '%{topic_title}'" @@ -1085,49 +1027,14 @@ bs_BA: Please visit this link to view the topic: %{base_url}%{url} user_replied: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_quoted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted_pm: subject_template: "[%{site_name}] [PM] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} digest: why: "Ukratko šta se dešavalo na %{site_link} od vaše zadnje posjete %{last_seen_at}." subject_template: "[%{site_name}] Pregled za %{date}" diff --git a/config/locales/server.cs.yml b/config/locales/server.cs.yml index 4ef52c16e..2269a88b9 100644 --- a/config/locales/server.cs.yml +++ b/config/locales/server.cs.yml @@ -769,54 +769,14 @@ cs: Prosíme klikněte na tento odkaz pro zobrazení konverzace: %{base_url}%{url} user_replied: subject_template: "[%{site_name}] %{username} vás citoval v '%{topic_title}'" - text_body_template: | - %{username} vás citoval v '%{topic_title}' na %{site_name}: - - --- - %{message} - - --- - Prosím navšivte tento odkaz, chcete-li odpovědět: %{base_url}%{url} user_quoted: subject_template: "[%{site_name}] %{username} vás citoval v '%{topic_title}'" - text_body_template: | - %{username} vás citoval v '%{topic_title}' na %{site_name}: - - --- - %{message} - - --- - Prosím navšivte tento odkaz, chcete-li odpovědět: %{base_url}%{url} user_mentioned: subject_template: "[%{site_name}] %{username} vás citoval v '%{topic_title}'" - text_body_template: | - %{username} vás citoval v '%{topic_title}' na %{site_name}: - - --- - %{message} - - --- - Prosím navšivte tento odkaz, chcete-li odpovědět: %{base_url}%{url} user_posted: subject_template: "[%{site_name}] %{username} vás citoval v '%{topic_title}'" - text_body_template: | - %{username} vás citoval v '%{topic_title}' na %{site_name}: - - --- - %{message} - - --- - Prosím navšivte tento odkaz, chcete-li odpovědět: %{base_url}%{url} user_posted_pm: subject_template: "[%{site_name}] [PM] %{topic_title}" - text_body_template: | - %{username} vás citoval v '%{topic_title}' na %{site_name}: - - --- - %{message} - - --- - Prosím navšivte tento odkaz, chcete-li odpovědět: %{base_url}%{url} digest: new_activity: "Nová aktivita ve vašich tématech a příspěvcích:" top_topics: "Populární příspěvky" diff --git a/config/locales/server.da.yml b/config/locales/server.da.yml index 7f78c3ef7..dd05eed3c 100644 --- a/config/locales/server.da.yml +++ b/config/locales/server.da.yml @@ -740,49 +740,14 @@ da: posted_by: "Oprettet af %{username} den %{post_date}" user_replied: subject_template: "[%{site_name}] %{username} svarede på dit indlæg i emnet '%{topic_title}'" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_quoted: subject_template: "[%{site_name}] %{username} citerede dig i emnet '%{topic_title}'" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted: subject_template: "[%{site_name}] %{subject_prefix}%{username} skrev et indlæg i emnet '%{topic_title}'" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted_pm: subject_template: "[%{site_name}] [PM] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} digest: why: "Et kort resume af %{site_link} siden dit sidste besøg %{last_seen_at}" subject_template: "Sammenfatning af [%{site_name}]" diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml index 80c92ec0b..157c2fa0c 100644 --- a/config/locales/server.de.yml +++ b/config/locales/server.de.yml @@ -90,6 +90,8 @@ de: not_found: "Die angeforderte URL oder Ressource konnte nicht gefunden werden." invalid_access: "Du hast nicht die Erlaubnis, die angeforderte Ressource zu betrachten." read_only_mode_enabled: "Die Seite befindet sich im Nur-Lesen Modus. Änderungen sind deaktiviert." + reading_time: "Lesezeit" + likes: "Likes" too_many_replies: one: "Entschuldigung, aber neue Benutzer sind vorübergehend auf eine Antwort pro Thema beschränkt." other: "Entschuldigung, aber neue Benutzer sind vorübergehend auf %{count} Antworten pro Thema beschränkt." @@ -157,6 +159,8 @@ de: errors: can_not_modify_automatic: "Du kannst eine automatisch verwaltete Gruppe nicht verändern." member_already_exist: "'%{username}' ist bereits Mitglied dieser Gruppe." + invalid_domain: "'%{domain}' ist keine gültige Domain." + invalid_incoming_email: "'%{incoming_email}' ist keine gültige E-Mail-Adresse." default_names: everyone: "jeder" admins: "admins" @@ -427,11 +431,16 @@ de: description: 'Dieser Beitrag enthält Inhalte, die eine vernünftige Person als anstößig, beleidigend oder unsere Richtlinien verletzend auffassen würde.' long_form: 'dies als unangemessen gemeldet' notify_user: + title: 'Schreibe @{{username}} eine Nachricht' + description: 'Ich möchte mit dieser Person direkt und persönlich über ihren Beitrag reden.' long_form: 'angeschriebener Benutzer' email_title: 'Dein Beitrag in „%{title}“' email_body: "%{link}\n\n%{message}" notify_moderators: title: "Irgendetwas anderes" + description: 'Auf diesen Beitrag sollte aus einem oben nicht aufgeführten Grund ein Moderator aufmerksam gemacht werden.' + long_form: 'hat dies den Moderatoren gemeldet' + email_title: 'Ein Beitrag in "%{title}" sollte von einem Moderator begutachtet werden' email_body: "%{link}\n\n%{message}" bookmark: title: 'Lesezeichen' @@ -456,6 +465,7 @@ de: long_form: 'als unangemessen gemeldet' notify_moderators: title: "Irgendetwas anderes" + description: 'Dieser Beitrag muss von einem Mitarbeiter begutachtet werden, da er entweder nicht mit den Richtlinien oder den Nutzungsbedingungen in Einklang zu bringen ist, oder aus sonstigen oben nicht genannten Gründen.' long_form: ' hast dies den Moderatoren gemeldet' email_title: 'Das Thema "%{title}" benötigt die Aufmerksamkeit eines Moderators' email_body: "%{link}\n\n%{message}" @@ -660,6 +670,7 @@ de: max_topic_title_length: "Maximale zulässige Titellänge von Themen in Zeichen." min_private_message_title_length: "Minimale zulässige Titellänge von Nachrichten in Zeichen." min_search_term_length: "Minimale zulässige Länge der Suche in Zeichen." + search_tokenize_chinese_japanese_korean: "Zwinge die Suche, Chinesisch, Japanisch und Koreanisch zu erkennen, auch wenn die Seite keine dieser Sprachen nutzt" allow_uncategorized_topics: "Erlaube Themen ohne Kategorie zu erstellen. ACHTUNG: Falls es unkategorisierte Themen gibt, musst du sie neu kategorisieren, bevor du diese Option abschaltest." uncategorized_description: "Beschreibung der Kategorie für unkategorisierte Themen. Leer lassen, wenn keine Beschreibung erwünscht ist." allow_duplicate_topic_titles: "Erlaube Themen mit identischen und doppelten Titeln." @@ -853,7 +864,7 @@ de: tl2_requires_likes_received: "Die Anzahl der Likes, die ein Benutzer erhalten muss, bevor er in die Vertrauensstufe 2 befördert wird." tl2_requires_likes_given: "Die Anzahl der Likes, die ein Benutzer vergeben muss, bevor er in die Vertrauensstufe 2 befördert wird." tl2_requires_topic_reply_count: "Die Anzahl der Beiträge, auf die ein Benutzer antworten muss, bevor er in die Vertrauensstufe 2 befördert wird." - tl3_requires_days_visited: "Die Mindestanzahl an Tagen, an denen ein Benutzer in den letzten 100 Tagen die Seite besucht haben muss, um die Vertrauensstufe 3 erreichen zu können. (0 bis 100)" + tl3_time_period: "Zeitraum für die Bedingungen für Vertrauensstufe 3" tl3_requires_topics_replied_to: "Die Mindestanzahl an Themen, auf die ein Benutzer in den letzten 100 Tagen geantwortet haben muss, um die Vertrauensstufe 3 erreichen zu können. (0 oder mehr)" tl3_requires_topics_viewed: "Prozentualer Anteil aller Themen der letzten 100 Tage, die ein Nutzer mindestens gelesen haben muss, um die Vertrauensstufe Anführer (3) erreichen zu können. (0 bis 100)" tl3_requires_posts_read: "Prozentualer Anteil aller Beiträge der letzten 100 Tage, die ein Nutzer mindestens gelesen haben muss, um die Vertrauensstufe Anführer (3) erreichen zu können. (0 bis 100)" @@ -873,6 +884,7 @@ de: newuser_max_mentions_per_post: "Maximale Anzahl der @Namens-Erwähnungen, die neue Benutzer in Beiträgen nutzen dürfen." newuser_max_replies_per_topic: "Maximale Anzahl an Antworten, die ein neuer Benutzer in einem einzigen Thema geben darf, bevor jemand auf diese antwortet." max_mentions_per_post: "Maximale Anzahl der @Namens-Erwähnungen, die jemand in einem Beitrag nutzen kann." + max_users_notified_per_group_mention: "Maximale Anzahl an Nutzern die benachrichtigt werden wenn eine Gruppe erwähnt wird (wird die Grenze erreicht, werden keine Nutzer benachrichtigt)" create_thumbnails: "Erzeuge ein Vorschaubild und eine Lightbox für Bilder, die zu groß sind, um in einem Beitrag angezeigt zu werden." email_time_window_mins: "Warte (n) Minuten, bevor Nutzern eine Hinweis-E-Mail geschickt wird, um ihnen Gelegenheit zu geben, ihre Beiträge abschließend bearbeiten zu können." email_posts_context: "Anzahl der Antworten welche als Konext einer Notifikations-Mail hinzugefügt werden." @@ -928,6 +940,7 @@ de: disable_emails: "Discourse daran hindern, jegliche Art von Emails zu verschicken" strip_images_from_short_emails: "Entferne Bilder aus E-Mails kleiner als 2800 Bytes." short_email_length: "Kurze E-Mail-Länge in Bytes" + display_name_on_email_from: "Zeige vollständige Namen im Absender-Feld von E-Mails" pop3_polling_enabled: "E-Mail-Antworten über POP3 abholen." pop3_polling_ssl: "SSL für die Verbindung zum POP3-Server verwenden. (Empfohlen)" pop3_polling_period_mins: "Intervall in Minuten zum Abholen neuer E-Mails vom POP3-Konto. HINWEIS: benötigt Neustart." @@ -966,9 +979,9 @@ de: anonymous_account_duration_minutes: "Um die Anonymität der virtuellen anonymen Nutzer zu erhalten, erzeuge ein neues anonymes Konto alle N Minuten je Benutzer. Beispiel: wenn dies auf 600 gesetzt ist wird ein neues anonymes Konto erzeugt, wenn ein Benutzer in den anonymen Modus wechselt UND mindestens 600 Minuten seit der letzten anonymen Nachricht dieses Nutzers vergangen sind." hide_user_profiles_from_public: "Deaktiviert Benutzerkarten, Nutzerprofile und das Benutzerverzeichnis für anonyme Nutzer." allow_profile_backgrounds: "Erlaube Benutzern Profilhintergründe hochzuladen." - sequential_replies_threshold: "Anzahl von Beiträgen, die ein Benutzer in einem Thema am Stück schreiben darf, bevor er eine Erinnerung bezüglich zu vieler aufeinanderfolgender Antworten erhält." enable_mobile_theme: "Mobilgeräte verwenden eine mobile Darstellung mit der Möglichkeit zur vollständigen Seite zu wechseln. Deaktiviere diese Option, wenn du ein eigenes Full-Responsive-Stylesheet verwenden möchtest." dominating_topic_minimum_percent: "Anteil der Nachrichten eines Themas in Prozent, die ein einzelner Nutzer verfassen darf, bevor dieser Nutzer darauf hingewiesen wird, dass er dieses Thema dominiert." + disable_avatar_education_message: "Weise Benutzer nicht darauf hin, dass sie ihren Avatar ändern können" daily_performance_report: "Analysiere die NGINX-Logs täglich. Poste anschließend eine Zusammenfassung als Beitrag, welcher nur für Moderatoren oder Administratoren zugänglich ist." suppress_uncategorized_badge: "Zeige kein Abzeichen für unkategorisierte Themen in der Themenliste." permalink_normalizations: "Wende den folgenden regulären Ausdruck an, bevor Permanentlinks überprüft werden, beispielsweise wird /(\\/topic.*)\\?.*/\\1 Query Strings aus Themen-Routen entfernen. Format: RegEx+String, benutze \\1 usw. um auf Gruppierungen zuzugreifen." @@ -1585,24 +1598,8 @@ de: Weitere Informationen hierzu findest du in unseren [Richtlinien](%{base_url}/guidelines). user_automatically_blocked: subject_template: "Neuer Benutzer %{username} wegen Gemeinschaftsrichtlinien blockiert." - text_body_template: | - Dies ist eine automatische Nachricht. - - Der neue Benutzer [%{username}](%{base_url}%{user_url}) wurde automatisch gesperrt, da mehrere Benutzer die Beiträge von %{username} gemeldet haben. - - Bitte [überprüfe die Meldungen](%{base_url}/admin/flags). Falls %{username} fälschlicherweise gesperrt wurde, drücke den 'Entsperren'-Knopf auf der [Administrationsseite für den Benutzer](%{base_url}%{user_url}). - - Der Schwellwert kann über die Seiteneinstellung `block_new_user` angepasst werden. spam_post_blocked: subject_template: "Beiträge des neuen Benutzers ${username} wegen mehrfacher Verlinkung blockiert" - text_body_template: | - Dies ist eine automatische Nachricht. - - Der neue Benutzer [%{username}](%{base_url}%{user_url}) hat versucht mehrere Beiträge mit Links zu %{domains} zu erstellen. Diese Beiträge wurden aber blockiert, um Spam zu vermeiden. Der Benutzer kann immer noch neue Beiträge erstellen, die nicht auf %{domains} verlinken. - - Bitte [überprüfe den Benutzer](%{base_url}%{user_url}). - - Dieses Verhalten kann durch die Seiteneinstellungen `newuser_spam_host_threshold` und `white_listed_spam_host_domains` geändert werden. unblocked: subject_template: "Blockierung des Kontos aufgehoben" text_body_template: | @@ -1622,6 +1619,8 @@ de: download_remote_images_disabled: subject_template: "Download von externen Bildern deaktiviert" text_body_template: "Die `download_remote_images_to_local` Einstellung wurde deaktiviert, da das Speicherplatz Limit von `download_remote_images_threshold` erreicht wurde." + unsubscribe_link: | + Um keine Benachrichtigungen mehr zu diesem Thema zu erhalten, bitte [hier klicken](%{unsubscribe_url}). Du kannst den Empfang von Benachrichtigungen per E-Mail auch in deinen [Profil-Eigenschaften](%{user_preferences_url}) deaktivieren. subject_re: "Re: " subject_pm: "[PN]" user_notifications: @@ -1662,58 +1661,16 @@ de: eingeladen. Bitte besuche diesen Link um das Thema zu lesen: %{base_url}%{url} user_replied: subject_template: "[%{site_name}] %{username} hat auf deinen Beitrag '%{topic_title}' geantwortet" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_quoted: subject_template: "[%{site_name}] %{username} hat Dich in '%{topic_title}' zitiert" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{username} hat Dich in '%{topic_title}' erwähnt" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_group_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted: subject_template: "[%{site_name}] %{subject_prefix}%{username} hat auf '%{topic_title}' geantwortet" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted_pm: subject_template: "[%{site_name}] [PM] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} digest: why: "Eine kurze Zusammenfassung von %{site_link} seit deinem letzten Besuch am %{last_seen_at}" subject_template: "[%{site_name}] Übersicht" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 258770c83..4e8c0196c 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -945,7 +945,9 @@ en: verbose_localization: "Show extended localization tips in the UI" previous_visit_timeout_hours: "How long a visit lasts before we consider it the 'previous' visit, in hours" - allow_staged_accounts: "[BETA] Automatically create staged accounts for incoming emails." + top_topics_formula_log_views_multiplier: "value of log views multiplier (n) in top topics formula: `log(views_count) * (n) + op_likes_count * 0.5 + LEAST(likes_count / posts_count, 3) + 10 + log(posts_count)`" + top_topics_formula_first_post_likes_multiplier: "value of first post likes multiplier (n) in top topics formula: `log(views_count) * 2 + op_likes_count * (n) + LEAST(likes_count / posts_count, 3) + 10 + log(posts_count)`" + top_topics_formula_least_likes_per_post_multiplier: "value of least likes per post multiplier (n) in top topics formula: `log(views_count) * 2 + op_likes_count * 0.5 + LEAST(likes_count / posts_count, (n)) + 10 + log(posts_count)`" rate_limit_create_topic: "After creating a topic, users must wait (n) seconds before creating another topic." rate_limit_create_post: "After posting, users must wait (n) seconds before creating another post." @@ -1017,6 +1019,8 @@ en: min_trust_to_edit_wiki_post: "The minimum trust level required to edit post marked as wiki." + min_trust_to_allow_self_wiki: "The minimum trust level required to make user's own post wiki." + min_trust_to_send_messages: "The minimum trust level required to create new private messages." newuser_max_links: "How many links a new user can add to a post." @@ -1104,6 +1108,9 @@ en: short_email_length: "Short email length in Bytes" display_name_on_email_from: "Display full names on email from fields" + unsubscribe_via_email: "Allow users to unsubscribe from emails by sending an email with 'unsubscribe' in the subject or body" + unsubscribe_via_email_footer: "Attach an unsubscribe link to the footer of sent emails" + pop3_polling_enabled: "Poll via POP3 for email replies." pop3_polling_ssl: "Use SSL while connecting to the POP3 server. (Recommended)" pop3_polling_period_mins: "The period in minutes between checking the POP3 account for email. NOTE: requires restart." @@ -1371,7 +1378,7 @@ en: 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 or an underscore" 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." + must_not_end_with_confusing_suffix: "must not end with 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." @@ -1379,6 +1386,17 @@ en: blocked: "New registrations are not allowed from your IP address." max_new_accounts_per_registration_ip: "New registrations are not allowed from your IP address (maximum limit reached). Contact a staff member." + unsubscribe_mailer: + subject_template: "Confirm you no longer want to receive email updates from %{site_title}" + text_body_template: | + Someone (possibly you?) requested to no longer send email updates from %{site_domain_name} to this address. + If you with to confirm this, please click this link: + + %{confirm_unsubscribe_link} + + + If you want to continue receiving email updates, you may ignore this email. + invite_mailer: subject_template: "%{invitee_name} invited you to '%{topic_title}' on %{site_domain_name}" text_body_template: | @@ -1582,7 +1600,15 @@ en: - To reply with **a new topic**, use to the right of the post. Both old and new topics will be automatically linked together. - To insert a quote, select the text you wish to quote, then press any Reply button. Repeat for multiple quotes! + Your reply can be formatted using simple HTML, BBCode, or [Markdown](http://commonmark.org/help/): + + This is **bold**. + This is bold. + This is [b]bold[/b]. + + Want to learn Markdown? [Take our fun 10 minute interactive tutorial!](http://commonmark.org/help/tutorial/) + + To insert a quote, select the text you wish to quote, then press any Reply button. Repeat for multiple quotes. @@ -1734,13 +1760,34 @@ en: subject_template: "Data export failed" text_body_template: "We're sorry, but your data export failed. Please check the logs or contact a staff member." - email_reject_trust_level: + email_reject_insufficient_trust_level: subject_template: "[%{site_name}] Email issue -- Insufficient Trust Level" text_body_template: | We're sorry, but your email message to %{destination} (titled %{former_title}) didn't work. Your account does not have the required trust level to post new topics to this email address. If you believe this is in error, contact a staff member. + email_reject_inactive_user: + subject_template: "[%{site_name}] Email issue -- Inactive User" + text_body_template: | + We're sorry, but your email message to %{destination} (titled %{former_title}) didn't work. + + Your account associated with this email address is not activated. Please activate your account before sending emails in. + + email_reject_reply_user_not_matching: + subject_template: "[%{site_name}] Email issue -- Reply User Not Matching" + text_body_template: | + We're sorry, but your email message to %{destination} (titled %{former_title}) didn't work. + + The original notification was not sent to this email address. Try sending from a different email address, or contact a staff member. + + email_reject_no_message_id: + subject_template: "[%{site_name}] Email issue -- No Message Id" + text_body_template: | + We're sorry, but your email message to %{destination} (titled %{former_title}) didn't work. + + There was no Message-Id header in the email. Try sending from a different email address, or contact a staff member. + email_reject_no_account: subject_template: "[%{site_name}] Email issue -- Unknown Account" text_body_template: | @@ -1771,14 +1818,21 @@ en: Your account does not have the privileges to post new topics in that category. If you believe this is in error, contact a staff member. - email_reject_post_error: + email_reject_strangers_not_allowed: + subject_template: "[%{site_name}] Email issue -- Invalid Access" + text_body_template: | + We're sorry, but your email message to %{destination} (titled %{former_title}) didn't work. + + The category you sent this email to does not allow emails from unrestricted accounts. If you believe this is in error, contact a staff member. + + email_reject_invalid_post: subject_template: "[%{site_name}] Email issue -- Posting error" text_body_template: | We're sorry, but your email message to %{destination} (titled %{former_title}) didn't work. Some possible causes are: complex formatting, message too large, message too small. Please try again, or post via the website if this continues. - email_reject_post_error_specified: + email_reject_invalid_post_specified: subject_template: "[%{site_name}] Email issue -- Posting error" text_body_template: | We're sorry, but your email message to %{destination} (titled %{former_title}) didn't work. @@ -1789,6 +1843,14 @@ en: If you can correct the problem, please try again. + email_reject_invalid_post_action: + subject_template: "[%{site_name}] Email issue -- Invalid Post Action" + text_body_template: | + We're sorry, but your email message to %{destination} (titled %{former_title}) didn't work. + + The Post Action was not recognized. Please try again, or post via the website if this continues. + + email_reject_reply_key: subject_template: "[%{site_name}] Email issue -- Unknown Reply Key" text_body_template: | @@ -1796,12 +1858,12 @@ en: The provided reply key is invalid or unknown, so we don't know what this email is in reply to. Contact a staff member. - email_reject_destination: + email_reject_bad_destination_address: subject_template: "[%{site_name}] Email issue -- Unknown To: Address" text_body_template: | We're sorry, but your email message to %{destination} (titled %{former_title}) didn't work. - None of the destination addresses are recognized. Please make sure that the site address is in the To: line (not Cc: or Bcc:), and that you are sending to the correct email address provided by staff. + None of the destination addresses are recognized. Please make sure that you are sending to the correct email address provided by staff. email_reject_topic_not_found: subject_template: "[%{site_name}] Email issue -- Topic Not Found" @@ -1896,7 +1958,10 @@ en: text_body_template: "The `download_remote_images_to_local` setting was disabled because the disk space limit at `download_remote_images_threshold` was reached." unsubscribe_link: | - To stop receiving notifications for this particular topic, [click here](%{unsubscribe_url}). To unsubscribe from these emails, change your [user preferences](%{user_preferences_url}). + To stop receiving notifications for this particular topic, [click here](%{unsubscribe_url}). To unsubscribe from these emails, change your [user preferences](%{user_preferences_url}) + + unsubscribe_via_email_link: | + or, [click here](mailto:reply@%{hostname}?subject=unsubscribe) to unsubscribe via email. subject_re: "Re: " subject_pm: "[PM] " @@ -1908,8 +1973,8 @@ en: description: "Not interested in getting these emails? No problem! Click below to unsubscribe instantly:" header_instructions: '' - reply_by_email: "To respond, reply to this email or [visit the topic](%{base_url}%{url})." - visit_link_to_respond: "To respond, [visit the topic](%{base_url}%{url})." + reply_by_email: "[Visit Topic](%{base_url}%{url}) or reply to this email to respond." + visit_link_to_respond: "[Visit Topic](%{base_url}%{url}) to respond." posted_by: "Posted by %{username} on %{post_date}" @@ -1973,6 +2038,18 @@ en: --- %{respond_instructions} + user_replied_pm: + subject_template: "[%{site_name}] [PM] %{topic_title}" + text_body_template: | + %{header_instructions} + + %{message} + + %{context} + + --- + %{respond_instructions} + user_quoted: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml index b7adb5b53..c4073d9ac 100644 --- a/config/locales/server.es.yml +++ b/config/locales/server.es.yml @@ -90,6 +90,8 @@ es: not_found: "No se ha podido encontrar la URL o recurso solicitado." invalid_access: "No tienes permiso para ver el recurso solicitado." read_only_mode_enabled: "El sitio está en modo sólo lectura. Las interacciones están deshabilitadas." + reading_time: "Tiempo de lectura" + likes: "Me gusta" too_many_replies: one: "Lo sentimos, pero los usuarios nuevos están limitados a 1 respuesta en el mismo tema." other: "Lo sentimos, pero los usuarios nuevos están limitados a %{count} respuestas en el mismo tema." @@ -128,7 +130,7 @@ es: other: "Lo sentimos, los nuevos usuarios solo pueden poner %{count} enlaces en un post." spamming_host: "Lo sentimos, no puedes publicar un enlace a esa web." user_is_suspended: "A los usuarios suspendidos no se les permite publicar." - topic_not_found: "Algo ha salido mal. Tal vez este tema fue cerrado o eliminado mientras estabas mirando en él?" + topic_not_found: "Algo ha salido mal. ¿Tal vez este tema ha sido cerrado o eliminado mientras estabas lo estabas mirando?" just_posted_that: "es demasiado parecido a lo que has publicado recientemente" has_already_been_used: "ya ha sido utilizado" invalid_characters: "contiene caracteres no válidos" @@ -147,7 +149,7 @@ es: private_message_abbrev: "Msj" rss_description: latest: "Temas recientes" - hot: "Temas calientes" + hot: "Temas candentes" posts: "Últimos posts" too_late_to_edit: "Ese post fue publicado hace demasiado tiempo. No puede ser editado ni eliminado." excerpt_image: "imagen" @@ -679,6 +681,7 @@ es: max_topic_title_length: "Extensión máxima del título de los temas, en número de caracteres" min_private_message_title_length: "Extensión mínima del título de los temas en mensajes, en número de caracteres" min_search_term_length: "Extensión mínima de una búsqueda válida, en número de caracteres" + search_tokenize_chinese_japanese_korean: "Forzar la búsqueda a tokenizar Chino/Japonés/Coreano incluso en sitios que no basados en esos idiomas" allow_uncategorized_topics: "Permitir la creación de temas sin categoría. AVISO: Si ya hay algún tema sin categoría, debes recategorizarlo antes de activar esta opción." uncategorized_description: "La descripción de la categoría Sin categoría. Déjalo en blanco para no añadir descripción." allow_duplicate_topic_titles: "Permitir temas con títulos idénticos, duplicados." @@ -830,6 +833,7 @@ es: active_user_rate_limit_secs: "Con qué frecuencia actualizaremos el campo 'last_seen_at', en segundos" verbose_localization: "Mostrar sugerencias de localización extendida en la interface de usuario " previous_visit_timeout_hours: "Cuanto tiempo debe pasar antes de que una visita sea considerada la 'visita previa', en horas" + allow_staged_accounts: "[BETA] Crear cuentas provisionales para los emails entrantes." rate_limit_create_topic: "Después de crear un tema, los usuarios deben esperar (n) segundos antes de crear otro tema." rate_limit_create_post: "Después de publicar un post, los usuarios deben esperar (n) segundos antes de crear otro post." rate_limit_new_user_create_topic: "Después de crear un tema, los nuevos usuarios deben esperar (n) segundos antes de crear otro tema." @@ -872,7 +876,8 @@ es: tl2_requires_likes_received: "¿Cuántos 'me gusta' un usuario debe de recibir antes de promoverlo a nivel de confianza 2?" tl2_requires_likes_given: "¿Cuántos 'me gusta' un usuario debe de dar antes de promoverlo a nivel de confianza 2?" tl2_requires_topic_reply_count: "¿Cuántos temas un usuario debe de contestar antes de promoverlo a nivel de confianza 2?" - tl3_requires_days_visited: "El número mínimo de días que un usuario necesito haber visitado el sitio en los últimos 100 días para calificar a promoción de nivel de confianza 3. (0 a 100)" + tl3_time_period: "Tiempo para requisitos del nivel de confianza 3" + tl3_requires_days_visited: "Mínimo número de días que un usuario necesita haber visitado el sitio en los últimos 100 para poder ser promocionado a nivel de confianza 3. Establece un valor superior para desactivar la posibilidad de subir a nivel de confianza 3. (De 0 a 100)" tl3_requires_topics_replied_to: "El número mínimo de temas que un usuario necesito haber respondido en los últimos 100 días para calificar a promoción de nivel de confianza 3. (0 o superior)." tl3_requires_topics_viewed: "El porcentaje de temas creados en los últimos 100 días que un usuario necesito haber visto para calificar a promoción de nivel de confianza 3. (0 a 100)" tl3_requires_posts_read: "El porcentaje de posts creados en los últimos 100 días que un usuario necesito haber visto para calificar a promoción de nivel de confianza 3. (0 a 100)" @@ -948,6 +953,7 @@ es: disable_emails: "Impedir que Discourse envié cualquier tipo de e-mail." strip_images_from_short_emails: "Remover imágenes de e-mails que tengan un tamaño menor a 2800 Bytes" short_email_length: "e-mail corto longitud en Bytes" + display_name_on_email_from: "Mostrar nombres completos en los campos de remitente de emails" pop3_polling_enabled: "Poll vía POP3 para respuestas de e-mail." pop3_polling_ssl: "Usar SSL mientras se conecta al servidor POP3. (Recomendado)" pop3_polling_period_mins: "El período en minutos entre revisiones de correo de la cuenta POP3. NOTA: requiere reiniciar." @@ -986,16 +992,18 @@ es: anonymous_account_duration_minutes: "Para proteger el anonimato, crear una nueva cuenta anónima cada N minutos para cada usuario. Ejemplo: si se establece en 600, tan pronto como pasen 600 minutos desde el último post Y el usuario cambie a anónimo, se creará una nueva cuenta anónima." hide_user_profiles_from_public: "Desactiva las tarjetas de usuario, los perfiles y el directorio de usuarios para usuarios anónimos." allow_profile_backgrounds: "Permitir a los usuarios subir sus propios fondos de perfil personalizados." - sequential_replies_threshold: "Número de posts consecutivos que un usuario publicará en un mismo tema hasta mostrarle un recordatorio sobre demasiadas repuestas seguidas." + sequential_replies_threshold: "Número de mensajes que un usuario tiene que publicar seguidos antes de que se le recuerde sobre demasiadas respuestas consecutivas." enable_mobile_theme: "Los dispositivos móviles utilizan un tema adaptado, con la habilidad de cambiar al estilo de sitio completo. Deshabilita esta opción si quieres utilizar una plantilla personalizada que sea completamente adaptable." dominating_topic_minimum_percent: "Qué porcentaje de posts tiene que publicar un usuario en un mismo tema hasta mostrarle un recordatorio acerca de dominar en demasía un tema." + disable_avatar_education_message: "Desactivar mensaje incentivador para que los usuarios se cambien su foto de perfil." daily_performance_report: "Analizar registros de NGINX diariamente y publicar un tema para Administradores con los detalles" suppress_uncategorized_badge: "No mostrar la etiqueta de los temas sin categoría en la lista de temas." permalink_normalizations: "Aplicar la siguiente expresion regular antes de los enlaces, por ejemplo: /(\\/topic.*)\\?.*/\\1 despojará las cadenas de consulta de las rutas de los temas. El formato es regex+string use \\1 etc. para acceder a capturas" global_notice: "Mostrar una noticia de URGENCIA o EMERGENCIA para todos los visitantes, deja este campo en blanco para ocultarla (se puede utilizar código HTML)" disable_edit_notifications: "Inhabilitar editar notificaciones por el usuario system cuando 'download_remote_images_to_local' este activo." automatically_unpin_topics: "Quitar destacado automáticamente cuando el usuario llega al final del tema." - full_name_required: "Su nombre completo es un campo obligatorio de un perfil de usuario." + read_time_word_count: "Número de palabras por minuto para calcular el tiempo de lectura estimado." + full_name_required: "El nombre completo es un campo obligatorio del perfil de usuario." enable_names: "Mostrar el nombre completo del usuario en su perfil, tarjeta de usuario y emails. Desactiva esta opción para ocultar el nombre completo en todas partes." display_name_on_posts: "Mostrar el nombre completo de un usuario en sus posts, además de su @usuario." show_time_gap_days: "Si entre dos publicaciones han pasado este número de días, mostrar el lapso de tiempo en el tema." @@ -1004,7 +1012,7 @@ es: default_code_lang: "Lenguaje de programación por defecto para aplicar el resaltado de la sintaxis en los bloques de código de GitHub (lang-auto, ruby, python etc.)" warn_reviving_old_topic_age: "Cuando alguien publica en un tema cuya última respuesta fue hace este número de días o más, se le mostrará un aviso para desalentar el hecho de revivir una antigua discusión. Deshabilita esta opción introduciendo el valor 0." autohighlight_all_code: "Forzar el resaltado de código a los bloques de código preformateado cuando no se especifique el lenguaje del código." - highlighted_languages: "Incluye reglas resaltadas de sintaxis. (Advertencia: incluyendo demasiadas lenguages puede afectar al rendimiento) ver: https://highlightjs.org/static/demo/ para una demostración" + highlighted_languages: "Incluye reglas resaltadas de sintaxis. (Advertencia: incluyendo demasiados lenguajes puede afectar al rendimiento) ver: https://highlightjs.org/static/demo/ para una demostración" feed_polling_enabled: "SOLO PARA EMBEBER: embeber feeds RSS/ATOM como posts." feed_polling_url: "SOLO PARA EMBEBER: URL de los feeds RSS/ATOM a embeber." embed_by_username: "Nombre de usuario en Discourse del que crea los temas embebidos." @@ -1018,6 +1026,7 @@ es: enable_cdn_js_debugging: "Permitir /logs mostrar los errores correctamente, añadiendo permisos crossorigin en todas las inclusiones de js" show_create_topics_notice: "Si el sitio tiene menos de 5 temas abiertos al público, mostrar un aviso pidiendo a los administradores crear más temas." delete_drafts_older_than_n_days: Eliminar borradores de más de (n) días de antigüedad. + vacuum_db_days: "Ejecutar VACUUM ANALYZE para reclamar espacio en la base de datos después de las migraciones. (Poner en 0 para inhabilitar)" prevent_anons_from_downloading_files: "Impedir que los usuarios anónimos descarguen archivos. ADVERTENCIA: Esto impedirá que funcione cualquier recurso del sitio publicado como adjunto." slug_generation_method: "Elegir un método de generación de slug. 'encoded' generará cadenas con código porciento. 'none' hara que no se genere slug." enable_emoji: "Habilitar emoji" @@ -1056,6 +1065,10 @@ es: invalid_string_min: "Debe contener como mínimo %{min} caracteres. " invalid_string_max: "No debe exceder los %{max} caracteres. " invalid_reply_by_email_address: "El valor debe contener '%{reply_key}' y debe ser diferente del email de notificación." + pop3_polling_host_is_empty: "Debes establecer un host de 'pop3 polling' antes de activar el polling POP3." + pop3_polling_username_is_empty: "Debes establecer un usuario de 'pop3 polling' antes de activar el polling POP3." + pop3_polling_password_is_empty: "Debes establecer una contraseña de 'pop3 polling' antes de activar el polling POP3." + pop3_polling_authentication_failed: "La autenticación POP3 falló. Por favor, verifica tus credenciales pop3." notification_types: group_mentioned: "%{group_name} ha sido mencionado en %{link}" mentioned: "%{display_username} te ha mencionado en %{link}" @@ -1503,7 +1516,7 @@ es: Como medida precautoria, tu nueva cuenta ha sido bloqueada para crear nuevas respuestas o temas hasta que un miembro del staff pueda revisar tu cuenta. - Para orientación adicional, por favor revise nuestras [reglas de comunidad](%{base_url}/guidelines). + Para orientación adicional, por favor revisa nuestras [reglas de comunidad](%{base_url}/guidelines). blocked_by_staff: subject_template: "Cuenta bloqueada" text_body_template: | @@ -1511,20 +1524,27 @@ es: Esto es un mensaje automatizado de %{site_name} para informatarte que tu cuenta ha sido bloqueada por un miembro del staff. - Para orientación adicional, por favor revise nuestras [reglas de comunidad](%{base_url}/guidelines). + Para orientación adicional, por favor revisa nuestras [reglas de comunidad](%{base_url}/guidelines). user_automatically_blocked: subject_template: "El nuevo usuario %{username} ha sido bloqueado tras ser reportado por la comunidad" text_body_template: | - Éste es un mensaje automático. + Este es un mensaje automático. - El nuevo usuario [%{username}](%{base_url}%{user_url}) ha sido bloqueado automáticamente porque múltiples usuarios han reportado el/los post(s) de %{username}. + El usuario nuevo [%{username}](%{user_url}) ha sido automáticamente bloqueado porque varios usuarios han reportado los mensajes de %{username}. - Por favor, [revisa los informes](%{base_url}/admin/flags). Si %{username} ha sido bloqueado incorrectamente, clica el botón de desbloquear en [la página de administración para este usuario](%{base_url}%{user_url}). + Por favor, [mira los reportes](%{base_url}/admin/flags). Si %{username} hubiera sido bloqueado incorrectamente, click haz clic en el botón de desbloquear en la [página de administración del usuario](%{user_url}). - El umbral puede ser cambiado vía configuración del sitio en `block_new_user`. + Esto puede ser cambiado con el ajuste `block_new_user`. spam_post_blocked: subject_template: "El nuevo usuario %{username} tiene posts bloqueados debido a repetición de enlaces." - text_body_template: "Este es un mensaje automático. \n\nEl usuario [%{username}](%{base_url}%{user_url}) intento crear multiples posts con links hacia %{domains}, pero dichos posts fueron bloqueados para evitar el spam. El usuario aun esta habilitado para crear nuevos posts que no contengan links hacia %{domains}.\n\nPor favor [revise el usuario] (%{base_url}%{user_url}).\n\nEsto puede ser modificado por medio de las opciones de configuración `newuser_spam_host_threshold` y `white_listed_spam_host_domains`.\n" + text_body_template: | + Este es un mensaje automático. + + El usuario nuevo [%{username}](%{user_url}) ha intentado crear múltiples temas con enlaces a %{domains}, pero han sido bloqueados para evitar spam. El usuario todavía puede crear temas, siempre y cuando no contengan enlaces a %{domains}. + + Por favor, [echa un vistazo al usuario](%{user_url}). + + Esto puede ser modificado con los ajustes`newuser_spam_host_threshold` y`white_listed_spam_host_domains`. unblocked: subject_template: "Cuenta desbloqueada" text_body_template: | @@ -1544,11 +1564,8 @@ es: download_remote_images_disabled: subject_template: "Inhabilitar la descarga de imágenes remotas" text_body_template: "La opción `download_remote_images_to_local` ha sido inhabilitada porque se ha llegado al límite de espacio en disco configurado en `download_remote_images_threshold`." - unsubscribe_link: |+ - Para parar de recibir notificaciones para este tema en particular, [haz clic aquí](%{unsubscribe_url}). - - Para darte de baja de estos emails, cambia tus [preferencias de usuario](%{user_preferences_url}). - + unsubscribe_link: | + Para no recibir más notificaciones de este tema en particular, [haz clic aquí](%{unsubscribe_url}). Para desuscribirte , cambia tus [preferencias de usuario](%{user_preferences_url}). subject_re: "Re:" subject_pm: "[MP]" user_notifications: @@ -1556,8 +1573,8 @@ es: unsubscribe: title: "Darse de baja" description: "¿No estás interesado en recibir estos emails? ¡No hay problema! Haz clic abajo para darte de baja de forma instantánea:" - reply_by_email: "Para contestar, responde a este email o [visita el tema](%{base_url}%{url}) en tu navegador." - visit_link_to_respond: "Para contestar, [visita el tema](%{base_url}%{url}) en tu navegador." + reply_by_email: "Para responder, responde a este mensaje o [visita el tema](%{base_url}%{url})." + visit_link_to_respond: "Para responder, [visita el tema](%{base_url}%{url})." posted_by: "Publicado por %{username} el %{post_date}" user_invited_to_private_message_pm: subject_template: "[%{site_name}] %{username} te ha invitado a un hilo de mensajes '%{topic_title}'" @@ -1574,6 +1591,21 @@ es: > %{site_title} -- %{site_description} Por favor visita este enlace para verlo: %{base_url}%{url} + user_invited_to_private_message_pm_staged: + subject_template: "[%{site_name}] %{username} te ha invitado a un mensaje '%{topic_title}'" + text_body_template: |2 + + %{username} te ha invitado a un mensaje + + > **%{topic_title}** + > + > %{topic_excerpt} + + at + + > %{site_title} -- %{site_description} + + Por favor, visita este enlace para ver el mensaje: %{base_url}%{url} user_invited_to_topic: subject_template: "[%{site_name}] %{username} te ha invitado a un tema '%{topic_title}'" text_body_template: |2 @@ -1592,6 +1624,8 @@ es: user_replied: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1601,6 +1635,8 @@ es: user_quoted: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1610,6 +1646,8 @@ es: user_mentioned: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1619,6 +1657,8 @@ es: user_group_mentioned: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1628,20 +1668,31 @@ es: user_posted: subject_template: "[%{site_name}] %{subject_prefix}%{username} publicó en '%{topic_title}'" text_body_template: | - %{username} publicó en '%{topic_title}' en %{site_name}: + %{header_instructions} - --- - %{message} - - --- - Por favor visita este enlace para contestar: %{base_url}%{url} - user_posted_pm: - subject_template: "[%{site_name}] [MP] %{topic_title}" - text_body_template: | %{message} %{context} + --- + %{respond_instructions} + user_posted_pm: + subject_template: "[%{site_name}] [MP] %{topic_title}" + text_body_template: | + %{header_instructions} + + %{message} + + %{context} + + --- + %{respond_instructions} + user_posted_pm_staged: + subject_template: "%{optional_re}%{topic_title}" + text_body_template: |2 + + %{message} + --- %{respond_instructions} digest: @@ -1688,7 +1739,7 @@ es: text_body_template: | Una nueva cuenta ha sido creada por ti en %{site_name} - Da clic en el siguiente enlace para escoger una contraseña para tu nueva cuenta: + Pulsa en el siguiente enlace para escoger una contraseña para tu nueva cuenta: %{base_url}/users/password-reset/%{email_token} authorize_email: subject_template: "[%{site_name}] Confirma tu nueva dirección de email" diff --git a/config/locales/server.fa_IR.yml b/config/locales/server.fa_IR.yml index 5822bee95..60f0a87fa 100644 --- a/config/locales/server.fa_IR.yml +++ b/config/locales/server.fa_IR.yml @@ -894,7 +894,6 @@ fa_IR: anonymous_posting_min_trust_level: "حداقل سطح اعتماد برای فعال سازی نوشته گذاشتن در حالت ناشناس" anonymous_account_duration_minutes: "برای محافظت از ناشناس ماندن یک حساب کاربری نانشناس بساز هر N دقیقه. برای مثال: اگر به 600 تنظیم شد٬‌ به محض اینکه 600 دقیقه از نوشته گذشت و کاربر سوئیچ شد به چند لحظه بعد٬‌ حساب کاربری جدید ناشناس ساخته می شود. " allow_profile_backgrounds: "به کاربر اجازه بده تا پس زمینه نمایه خود را آپلود کنند." - sequential_replies_threshold: "تعداد پست هایی که کاربر باید بسازد در یک ردیف در یک جستار قبل از یاآوری درباره پاسخ های پی در پی. " enable_mobile_theme: "دستگاه های موبایلی که از تم دوستانه موبایل استفاده می کنن٬‌ با قابلیت جابجایی به حالت تمام سایت. این را از کار بیانداز اگر می خواهی از شیوه سفارشی استفاده کنی که بطور کامل پاسخگو است." dominating_topic_minimum_percent: " پست های یک کاربر چند درصد باید درست کنند در یک جستار قبل از اینکه یادآوری بشن درباره بیش از اندازه بودن آن در یک جستار. " daily_performance_report: "تحلیل لاگ‌های NGINX و ارسال یک پست شامل جزئیات ویژه مدیران" @@ -1241,19 +1240,8 @@ fa_IR: text_body_template: "سلام٬ \n\n\nاین یک پیام خودکار است از طرف %{site_name} برای اطلاع در جهت اینکه حساب کاربری شما توسط یکی از مدیران مدیران مسدود شده است.\n\nبرای راهنمایی بیشتر٬ لطفا مراجعه کنید به [دستورالعمل انجمن](%{base_url}/guidelines).\n" user_automatically_blocked: subject_template: "کاربر جدید%{username} مسدود شد بخاطر پرچم های انجمن" - text_body_template: "این یک پیام خودکار است. \n\n\nکاربر جدید [%{username}](%{base_url}%{user_url}) بطور خودکار مسدود شد زیرا چند کاربر متفاوت %{username}'s این نوشته(ها) را پرچم گزاری کرده اند.\n\n\nلطفا [پرچم ها را بررسی کن](%{base_url}/admin/flags). اگر %{username} به اشتباه مسدود شده بود لطفا به روی کلید رفع مسدود کلیک کنید[مدیرصفحه برای این کاربر](%{base_url}%{user_url}). \n\n\nاین آستانه می تواند تغییر کند از طریق `block_new_user` تنظیمات سایت.\n" spam_post_blocked: subject_template: "کاربر جدید%{username} نوشته مسدود شده است بدلیل پیوند های تکراری" - text_body_template: | - این یک پیام خودکار است. - - کاربرجدید [%{username}](%{base_url}%{user_url}) تلاش کرد برای ساخت چند نوشته با پیوندهایی به %{domains}, ولی آن نوشته ها مسدود شده اند برای جلوگیری از هرزنامه. کاربر هنوز قادر به ساخت نوشته های جدید است که به پیوندی به این %{domains} ندارد. - - - لطفا [کاربر را بررسی کنید](%{base_url}%{user_url}). - - - این می تواند تغییر کند با `newuser_spam_host_threshold` و `white_listed_spam_host_domains` site settings. unblocked: subject_template: "حساب کاربری رفع انسداد شد " text_body_template: "سلام \n\nاین یک پیام خودکار هست از طرف %{site_name} برای تایید باز شدن حساب کاربری شما بعد از بررسی مدیران. \n\n\nشما می توانید دوباره پاسخ ها و جستارهای جدید را بوجود بیاورید.\n" @@ -1283,49 +1271,14 @@ fa_IR: text_body_template: "\n%{username} به گفتگو دعوت شده است\n\n\n> **%{topic_title}**\n\n>\n\n> %{topic_excerpt}\n\nat \n\n\n> %{site_title} -- %{site_description}\n\n\nلطفا از این پیوند بازدید کنید تا پیام را ببینید: %{base_url}%{url}\n" user_replied: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - {message}% - - {context}% - - --- - {respond_instructions}% user_quoted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted_pm: subject_template: "[%{site_name}] [PM] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} digest: why: "یک گزارش کوتاه از %{site_link} از آخرین باری که بازدید کردید در %{last_seen_at}" subject_template: "[%{site_name}] خلاصه" diff --git a/config/locales/server.fi.yml b/config/locales/server.fi.yml index d8940eff0..cb520fa05 100644 --- a/config/locales/server.fi.yml +++ b/config/locales/server.fi.yml @@ -90,6 +90,8 @@ fi: not_found: "Pyydettyä osoitetta tai resurssia ei löytynyt." invalid_access: "Sinulla ei ole oikeutta nähdä pyydettyä resurssia." read_only_mode_enabled: "Sivusto on vain luku-tilassa. Vuorovaikutteiset toiminnot ovat poissa käytöstä." + reading_time: "Lukuaika" + likes: "Tykkäykset" too_many_replies: one: "Pahoittelut, uudet käyttäjät voivat kirjoittaa yhden vastauksen samaan ketjuun." other: "Pahoittelut, uudet käyttäjät voivat kirjoittaa %{count} vastausta samaan ketjuun." @@ -106,6 +108,14 @@ fi: replies: one: "1 kommentti" other: "%{count} kommenttia" + no_mentions_allowed: "Pahoittelut, et voi mainita muita käyttäjiä." + too_many_mentions: + one: "Pahoittelut, voit mainita viestissä vain yhden käyttäjän." + other: "Pahoittelut, voit mainita viestissä vain %{count} käyttäjää." + no_mentions_allowed_newuser: "Pahoittelut, uusi käyttäjä ei voi mainita muita käyttäjiä." + too_many_mentions_newuser: + one: "Pahoittelut, uusi käyttäjä voi mainita viestissä vain yhden käyttäjän." + other: "Pahoittelut, uusi käyttäjä voi mainita viestissä vain %{count} käyttäjää." no_images_allowed: "Pahoittelut, uusi käyttäjä ei voi liittää kuvia viestiin." too_many_images: one: "Pahoittelut, uusi käyttäjä voi liittää vain yhden kuvan viestiin." @@ -289,7 +299,7 @@ fi: errors: uncategorized_parent: "Alueettomia ei voi asettaa toisen alueen alle" self_parent: "Alue ei voi olla itsensä ylempi alue." - depth: "Et voi laittaa alempia alueita sisäkkäin" + depth: "Alue ei voi olla tytäralueen tytäralue" email_in_already_exist: "Sisääntulevan sähköpostin osoite '%{email_in}' on jo käytössä alueella '%{category_name}'." cannot_delete: uncategorized: "Alueettomat-aluetta ei voi poistaa" @@ -449,6 +459,8 @@ fi: notify_moderators: title: "Jotain muuta" description: 'Valvojan tulee huomioida tämä viesti muusta kuin yllä listatusta syystä.' + long_form: 'liputti tämän valvojien nähtäväksi' + email_title: 'Valvojien tulisi huomioida viesti ketjussa "%{title}" ' email_body: "%{link}\n\n%{message}" bookmark: title: 'Kirjanmerkki' @@ -473,6 +485,7 @@ fi: long_form: 'liputti tämän asiattomaksi' notify_moderators: title: "Jotain muuta" + description: 'Valvojan tulee huomioida tämä ketju palstan sääntöjen, palveluehtojen tai muun syyn vuoksi.' long_form: 'liputit tämän valvojille tiedoksi' email_title: 'Viestiketju"%{title}" kaipaa valvojan huomiota' email_body: "%{link}\n\n%{message}" @@ -698,7 +711,7 @@ fi: max_image_width: "Esikatselukuvan suurin sallittu leveys viestissä" max_image_height: "Esikatselukuvan suurin sallittu korkeus viestissä" category_featured_topics: "Näytettävien ketjujen lukumäärä alueittan Alueet-sivulla. Arvon muuttamisen jälkeen voi kestää jopa 15 minuuttia sivun päivittymiseen." - show_subcategory_list: "Näytä sisempien alueiden lista ketjulistauksen sijaan alueelle saavuttaessa." + show_subcategory_list: "Näytä lista tytäralueista ketjulistauksen sijaan alueelle saavuttaessa." fixed_category_positions: "Jos tämä on valittuna, voit muokata alueiden järjestystä. Jos tätä ei valita, alueet järjetsetään aktiivisuuden mukaan." fixed_category_positions_on_create: "Jos tämä on valittuna, alueiden järjestys pysyy samana uuden ketjun luomisen dialogissa (edellyttää fixed_category_positions)" add_rel_nofollow_to_user_content: "Lisää rel nofollow kaikkeen käyttäjien lähettämään sisältöön, paitsi sivuston sisäisiin linkkeihin (sisältäen ylemmät verkkotunnukset). Jos muutat asetusta, sinun täytyy rakentaa viestit uudelleen komennolla: \"rake posts:rebake\"" @@ -765,8 +778,8 @@ fi: suppress_reply_when_quoting: "Älä näytä vastauksena-painiketta viestin yläreunassa, kun viestissä on lainaus." max_reply_history: "Maksimimäärä vastauksia, jotka avataan klikattaessa 'vastauksena' painiketta" experimental_reply_expansion: "Piilota välilliset vastaukset, kun 'vastauksena' avataan (kokeellinen)" - topics_per_period_in_top_summary: "Kejujen lukumäärä, joka näytetään oletuksena Huiput-listauksissa." - topics_per_period_in_top_page: "Kejujen lukumäärä, joka näytetään laajennetussa Huiput-listauksessa." + topics_per_period_in_top_summary: "Ketjujen lukumäärä, joka näytetään oletuksena Huiput-listauksissa." + topics_per_period_in_top_page: "Ketjujen lukumäärä, joka näytetään laajennetussa Huiput-listauksessa." redirect_users_to_top_page: "Ohjaa uudet ja kauan poissa olleet käyttäjät automaattisesti huiput-sivulle." top_page_default_timeframe: "Huiput-sivun oletusaikajakso." show_email_on_profile: "Näytä käyttäjän sähköpostiosoite profiilissa (näkyy vain käyttäjälle itselleen ja henkilökunnalle)" @@ -828,6 +841,7 @@ fi: active_user_rate_limit_secs: "Kuinka usein 'last_seen_at' kenttä päivitetään, sekunneissa" verbose_localization: "Näytä laajennetut lokalisointitiedot käyttöliittymässä" previous_visit_timeout_hours: "Kuinka kauan vierailun on täytynyt kestää, jotta se lasketaan 'edelliseksi' vierailuksi, tunneissa" + allow_staged_accounts: "[BETA] Luo automaattiset tunnukset vastaanotettujen sähköpostien mukaisesti." rate_limit_create_topic: "Ketjun luomisen jälkeen käyttäjän täytyy odottaa (n) sekuntia voidakseen luoda uuden ketjun." rate_limit_create_post: "Viestin luomisen jälkeen käyttäjän täytyy odottaa (n) sekuntia voidakseen luoda uuden viestin." rate_limit_new_user_create_topic: "Ketjun luomisen jälkeen uuden käyttäjän täytyy odottaa (n) sekuntia voidakseen luoda uuden ketjun." @@ -870,7 +884,8 @@ fi: tl2_requires_likes_received: "Kuinka monta tykkäystä käyttäjän täytyy saada ennen ylentämistä luottamustasolle 2." tl2_requires_likes_given: "Kuinka monta tykkäystä käyttäjän täytyy antaa ennen ylentämistä luottamustasolle 2." tl2_requires_topic_reply_count: "Kuinka moneen ketjuun käyttäjän täytyy vastata ennen ylentämistä luottamustasolle 2." - tl3_requires_days_visited: "Monenako päivänä vähintään käyttäjän täytyy olla vieraillut sivustolla viimeisen 100 päivän aikana voidakseen saavuttaa luottamustason 3. (asteikko 0-100)" + tl3_time_period: "Luottamustason 3 vaatimuksiin liittyvän ajanjakson pituus" + tl3_requires_days_visited: "Minimimäärä päiviä, joina käyttäjän täytyy olla vieraillut sivustolla viimeisen 100 päivän aikana voidakseen saavuttaa luottamustason 3. Poistaaksesi ylennykset aseta arvo korkeammaksi, kuin lt3 aikaraja, (0 tai korkeampi)" tl3_requires_topics_replied_to: "Moneenko ketjuun vähintään käyttäjän täytyy olla vastannut viimeisen 100 päivän aikana voidakseen saavuttaa luottamustason 3. (0 tai korkeampi)" tl3_requires_topics_viewed: "Montako prosenttia viimeisen 100 päivän aikana luoduista ketjuista käyttäjän täytyy olla katsellut voidakseen saavuttaa luottamustason 3. (asteikko 0-100)" tl3_requires_posts_read: "Montako prosenttia viimeisen 100 päivän aikana luoduista viesteistä käyttäjän täytyy olla katsellut voidakseen saavuttaa luottamustason 3. (asteikko 0-100)" @@ -946,6 +961,7 @@ fi: disable_emails: "Estä Discoursea lähettämästä mitään sähköpostia" strip_images_from_short_emails: "Poista kuvat sähköposteista, joiden koko on alle 2800 tavua" short_email_length: "Lyhyen sähköpostin pituus tavuissa" + display_name_on_email_from: "Näytä sähköpostien lähettäjinä käyttäjien koko nimet" pop3_polling_enabled: "Pollaa sähköpostivastaukset POP3:lla." pop3_polling_ssl: "Käytä SSL-salausta yhdistettäessä POP3-palvelimeen. (Suositellaan)" pop3_polling_period_mins: "Tiheys minuuteissa kuinka usein POP3 tililtä tarkastetaan uudet sähköpostit. HUOM: vaatii uudelleenkäynnistyksen." @@ -987,11 +1003,13 @@ fi: sequential_replies_threshold: "Kuinka monen peräkkäisen viestin jälkeen yhdessä ketjussa käyttäjää muistutetaan peräkkäisistä vastauksista." enable_mobile_theme: "Mobiililaitteet käyttävät erillistä teemaa ja kahden välillä on mahdollista vaihtaa. Poista asetus käytöstä, jos haluat käyttää omaa tyylitiedostoa, joka on mukautuva eri laitteille." dominating_topic_minimum_percent: "Kuinka monta prosenttia ketjun viesteistä käyttäjän täytyy kirjoittaa, ennen kuin tulee muistutetuksi ketjun dominoinnista." + disable_avatar_education_message: "Piilota opastusviesti, joka näytetään kun profiilikuvaa vaihdetaan." daily_performance_report: "Analysoi NGINX lokit päivittäin ja julkaise vain henkilökunnalle näkyvä ketju yksityiskohdista" suppress_uncategorized_badge: "Älä näytä alueettomille ketjuille tunnusta ketjujen listauksissa." global_notice: "Näytä KIIREELLISESTÄ HÄTÄTAPAUKSESTA kertova banneri kaikilla sivuilla kaikille käyttäjille, vaihda tyhjäksi piilottaaksesi sen (HTML sallittu)." disable_edit_notifications: "Poista muokkausilmoitukset system-käyttäjältä, kun 'download_remote_images_to_local' on asetettu." automatically_unpin_topics: "Poista ketjun kiinnitys automaattisesti, kun käyttäjä on sen lopussa." + read_time_word_count: "Sanamäärä minuutissa, jota käytetään lukuajan arviointiin." full_name_required: "Koko nimi on käyttäjäprofiilin vaadittu kohta" enable_names: "Näytä käyttäjän koko nimi profiilissa, käyttäjäkortissa ja sähköposteissa. Poista käytöstä piilottaaksesi koko nimen kaikkialla." display_name_on_posts: "Näytä käyttäjän pitkä nimi viesteissä @nimen lisäksi." @@ -1015,6 +1033,7 @@ fi: enable_cdn_js_debugging: "Salli /logs näyttää kunnolliset virheilmoitukset lisäämällä crossorigin permissions kaikkiin sisällytettyihin js-kirjastoihin." show_create_topics_notice: "Jos palstalla on vähemmän kuin 5 julkista ketjua, huomauta ylläpitäjiä ketjujen luonnista." delete_drafts_older_than_n_days: Poista yli (n) päivää vanhat luonnokset. + vacuum_db_days: "Aja VACUUM ANALYZE vapauttaaksesi tietokannan viemää tilaa migraatioiden jälkeen (aseta 0 poistaaksesi käytöstä)" prevent_anons_from_downloading_files: "Estä kirjautumattomia käyttäjiä lataamasta liitetiedostoja. VAROITUS: tämä estää viestin liitettyjen muiden tiedostojen, kuin kuvien käyttämisen sivustolla." slug_generation_method: "Valitse polkulyhenteen luomisen metodi. 'encoded' käyttää prosenttikoodausta, 'none' poistaa polkulyhenteet käytöstä." enable_emoji: "Ota emoji käyttöön" @@ -1053,7 +1072,12 @@ fi: invalid_string_min: "Täytyy olla vähintään %{min} merkkiä." invalid_string_max: "Ei saa olla yli %{max} merkkiä." invalid_reply_by_email_address: "Arvon täytyy sisältää '%{reply_key}' ja olla eri, kuin ilmoitusten sähköpostiosoite." + pop3_polling_host_is_empty: "Sinun täytyy asettaa 'pop3 polling host' ennen POP3-pollauksen ottamista käyttöön." + pop3_polling_username_is_empty: "Sinun täytyy asettaa 'pop3 polling username' ennen POP3-pollauksen ottamista käyttöön." + pop3_polling_password_is_empty: "Sinun täytyy asettaa 'pop3 polling password' ennen POP3-pollauksen ottamista käyttöön." + pop3_polling_authentication_failed: "POP3 autentikaatio epäonnistui. Tarkasta pop3 kirjautumistiedot." notification_types: + group_mentioned: "%{group_name} mainittiin täällä %{link}" mentioned: "%{display_username} viittasi sinuun viestissä %{link}" liked: "%{display_username} tykkäsi viestistäsi %{link}" replied: "%{display_username} vastasi viestiisi %{link}" @@ -1605,21 +1629,21 @@ fi: text_body_template: | Tämä on automaattinen viesti. - Uusi käyttäjätili [%{username}](%{base_url}%{user_url}) on automaattisesti estetty, koska usea käyttäjä liputti käyttäjän %{username} viestin tai viestejä. + Uusi käyttäjätili [%{username}](%{user_url}) estettiin, koska monet liputtivat käyttäjän %{username} viestin tai viestejä. - Ole hyvä ja [tarkasta liput](%{base_url}/admin/flags). Jos %{username} on estetty virheellisesti, klikkaa poista esto -painiketta [käyttäjän hallinnointisivulta](%{base_url}%{user_url}). + [Tarkastele liputettuja viestejä](%{base_url}/admin/flags). Jos käyttäjä %{username} estettiin aiheettomasti, klikkaa poista esto -painiketta [tähän käyttäjään liittyvällä ylläpitäjän sivulla](%{user_url}). - Tätä kynnysarvoa voi muuttaa vaihtamalla asetuksen `block_new_user` arvoa. + Kynnysarvoa voi muuttaa säätämällä asetuksen `block_new_user` arvoa sivuston asetuksissa. spam_post_blocked: subject_template: "Uuden käyttäjän %{username} viestit on estetty toistuvien linkkien vuoksi" text_body_template: | - Tämä on automaattinen viesti + Tämä on automaattisesti luotu viesti. - Uusi käyttäjä [%{username}](%{base_url}%{user_url}) yritti luoda useita viestejä, jotka sisälsivät linkkejä osoitteeseen %{domains}, mutta nämä viestit estettiin roskapostin välttämiseksi. Käyttäjä voi edelleen luoda uusia viestejä, joissa ei ole linkkejä osoitteeseen %{domains}. + Uusi käyttäjä [%{username}](%{user_url}) yritti luoda useita viestejä, jotka sisälsivät linkkejä osoitteeseen %{domains}, mutta jotka estettiin roskapostin välttämiseksi. Käyttäjä voi edelleen luoda viesteijä, joissa ei ole linkkejä näihin osoitteisiin %{domains}. - Ole hyvä ja [tarkasta käyttäjä](%{base_url}%{user_url}). + [Tarkasta käyttäjätili](%{user_url}). - Tätä toimintoa voi muokata vaihtamalla asetuksia `newuser_spam_host_threshold` ja `white_listed_spam_host_domains` sivuston asetuksissa. + Tätä toimintoa voi muokata vaihtamalla asetuksia `newuser_spam_host_threshold` `white_listed_spam_host_domains` sivuston asetuksissa. unblocked: subject_template: "Tili avattu" text_body_template: | @@ -1640,9 +1664,7 @@ fi: subject_template: "Linkattujen kuvien lataaminen on otettu pois käytöstä" text_body_template: "Asetus `download_remote_images_to_local` on otettu pois käytöstä, koska vapaan tilan rajoitus `download_remote_images_threshold` saavutettiin." unsubscribe_link: | - Jos et enää halua ilmoituksia tästä ketjusta, [klikkaa tätä](%{unsubscribe_url}). - - Jos et halua näitä viestejä, muuta [tilisi asetuksia](%{user_preferences_url}). + Jos et enää halua ilmoituksia tästä ketjusta, [klikkaa tätä](%{unsubscribe_url}). Jos et halua sähköposti-ilmoituksia, muuta [tilisi asetuksia](%{user_preferences_url}). subject_re: "VS:" subject_pm: "[YV]" user_notifications: @@ -1668,6 +1690,21 @@ fi: > %{site_title} -- %{site_description} Seuraa tätä linkkiä nähdäksesi viestin: %{base_url}%{url} + user_invited_to_private_message_pm_staged: + subject_template: "[%{site_name}] %{username} kutsui sinut yksityiseen keskusteluun '%{topic_title}'" + text_body_template: |2 + + %{username} kutsui sinut yksityiseen keskusteluun + + > **%{topic_title}** + > + > %{topic_excerpt} + + sivustolla + + > %{site_title} -- %{site_description} + + Klikkaa tätä linkkiä nähdäksesi viestin: %{base_url}%{url} user_invited_to_topic: subject_template: "[%{site_name}] %{username} kutsui sinut viestiin '%{topic_title}'" text_body_template: |2 @@ -1686,6 +1723,8 @@ fi: user_replied: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1695,6 +1734,8 @@ fi: user_quoted: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1703,19 +1744,31 @@ fi: %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: |+ + text_body_template: | + %{header_instructions} + %{message} %{context} --- %{respond_instructions} - user_group_mentioned: subject_template: "[%{site_name}] %{topic_title}" + text_body_template: | + %{header_instructions} + + %{message} + + %{context} + + --- + %{respond_instructions} user_posted: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1725,10 +1778,20 @@ fi: user_posted_pm: subject_template: "[%{site_name}] [YV] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} + --- + %{respond_instructions} + user_posted_pm_staged: + subject_template: "%{optional_re}%{topic_title}" + text_body_template: |2 + + %{message} + --- %{respond_instructions} digest: @@ -1972,6 +2035,34 @@ fi: title: "Käyttöehdot" privacy_topic: title: "Rekisteriseloste" + static: + search_help: | +

    Vinkkejä

    +

    +

      +
    • Otsikon vastaavuudet ovat etusijalla – jos olet epävarma, etsi otsikkoa
    • +
    • Parhaat tulokset saa uniikeilla ja epätavallisilla sanoilla
    • +
    • Voit kokeilla etsiä tietyn alueen, ketjun tai käyttäjän viesteistä
    • +
    +

    +

    Options

    +

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

    +

    Esimerkkejä

    +

    +

      +
    • rainbows category:parks status:open order:latest etsii alueelta "parks" ketjuja, joissa esiintyy sana "rainbows" ja joita ei suljettu tai arkistoitu, ja esittää ylimmäisinä ne ketjut, joihin on viimeksi kirjoitettu.
    • +
    • rainbows category:"parks and gardens" in:bookmarks etsii alueelta "parks and gardens" kirjanmerkkeihin lisäämiäsi ketjuja, joissa esiintyy sana "rainbows".
    • +
    +

    badges: long_descriptions: autobiographer: | @@ -2016,7 +2107,7 @@ fi: great_topic: | Tämä arvomerkki myönnetään, kun avaamasi ketju saa 50 tykkäystä. Vau! basic: | - Tämä arvomerkki myönnetään, kun saavutat luottamustason 1. Kiitos kun olet ollut täällä hetken ja lukenut muutamaa ketjua oppiaksesi yhteisöstämme. Uuden käyttäjän rajoituksia on löysätty ja sinulla on käytössäsi kaikki perustoiminnot kuten yksityisviestit, liputtaminen, wiki-viestien muokkaaminen, kuvien liittäminen ja useamman linkin asettaminen. + Tämä arvomerkki myönnetään, kun saavutat luottamustason 1. Kiitos kun olet ollut täällä hetken ja lukenut muutamaa ketjua oppiaksesi yhteisöstämme. Uuden käyttäjän rajoituksia on löysätty ja sinulla on käytössäsi kaikki perustoiminnot kuten yksityisviestit, liputtaminen, wiki-viestien muokkaaminen, kuvien liittäminen ja useamman linkin laittaminen. member: | Tämä arvomerkki myönnetään, kun saavutat luottamustason 2. Kiitos, kun olet ollut täällä useamman viikon ajan ja kasvanut yhteisömme jäseneksi. Voit nyt lähettää henkilökohtaisia kutsuja käyttäjäsivultasi ja yksittäisistä ketjuista, lähettää ryhmäviestejä ja tykätä muutaman kerran enemmän päivää kohden. regular: | diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml index 261694543..07cf42d58 100644 --- a/config/locales/server.fr.yml +++ b/config/locales/server.fr.yml @@ -970,7 +970,6 @@ fr: anonymous_posting_min_trust_level: "Le niveau de confiance minimum pour passer en mode anonyme." anonymous_account_duration_minutes: "Pour protéger l'anonymat, créer un nouveau compte anonyme tous les N minutes pour chaque utilisateur. Exemple: si 600 est choisi, dès 600 minutes après le dernier message ET que l'utilisateur passe en mode anonyme, un nouveau compte anonyme lui sera crée." allow_profile_backgrounds: "Autoriser les utilisateurs à envoyer des arrières-plans de profil." - sequential_replies_threshold: "Nombre de messages qu'un utilisateur doit poster d'affilé dans un sujet avant d'être rappelé à l'ordre pour multiple réponses." enable_mobile_theme: "Les appareils mobiles utilisent un thème adapté aux mobiles, avec la possibilité de passer à la totalité du site. Désactivez cette option si vous voulez utiliser une feuille de style personnalisée qui répond à tous les types de client." dominating_topic_minimum_percent: "Quel est le pourcentage de messages un utilisateur doit poster dans un sujet avant d'être rappelé à l'ordre pour laissé la communauté répondre." daily_performance_report: "Analyser les logs de NGINX quotidiennement et poster un sujet Responsables Uniquement avec les détails" @@ -1566,24 +1565,8 @@ fr: Pour plus d'informations, merci de vous en référer au [règlement de la communauté](%{base_url}/guidelines). user_automatically_blocked: subject_template: "Nouvel utilisateur %{username} bloqué à cause de signalements de la communauté" - text_body_template: | - Ceci est un message automatique. - - Le nouvel utilisateur [%{username}](%{base_url}%{user_url}) a été bloqué automatiquement par plusieurs utilisateurs ayant signalés ses messages. - - Merci de [vérifier les signalements](%{base_url}/admin/flags). Si %{username} a été bloqué injustement, cliquez sur le bouton débloquer sur la [page d'administration de cet utilisateur](%{base_url}%{user_url}). - - Cette limite peut être changée par le paramètre du site `block_new_user`. spam_post_blocked: subject_template: "Les messages du nouvel utilisateur %{username} sont bloqués pour des liens répétés" - text_body_template: | - Ceci est un message automatique. - - Le nouvel utilisateur [%{username}](%{base_url}%{user_url}) tente de créer de multiples messages avec des liens vers %{domains}, mais que ces messages ont été bloqués pour éviter le spam. Cet utilisateur est toujours capable de créer des messages qui ne contiennent pas de liens vers %{domains}. - - Merci de [vérifier cet utilisateur](%{base_url}%{user_url}). - - Ceci peut être modifier via les options `newuser_spam_host_threshold` et white_listed_spam_host_domains`. unblocked: subject_template: "Compte débloqué" text_body_template: | @@ -1619,51 +1602,16 @@ fr: text_body_template: "\n%{username} vous a invité à une conversation\n\n> **%{topic_title}**\n>\n> %{topic_excerpt}\n\nsur \n\n> %{site_title} -- %{site_description}\n\nVeuillez suivre ce lien pour voir le message: %{base_url}%{url}\n" user_replied: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_quoted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_group_mentioned: subject_template: "[%{site_name}] %{topic_title}" user_posted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted_pm: subject_template: "[%{site_name}] [MP] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} digest: why: "Voici un bref résumé de ce qu'il s'est passé sur %{site_link} depuis votre dernière visite le %{last_seen_at}" subject_template: "[%{site_name}] Résumé" diff --git a/config/locales/server.he.yml b/config/locales/server.he.yml index 7883b8579..f6a7fe506 100644 --- a/config/locales/server.he.yml +++ b/config/locales/server.he.yml @@ -24,8 +24,6 @@ he: loading: "טוען" powered_by_html: 'מונע ע"י Discourse, פועל מיטבית עם Javascript' log_in: "התחברות" - via: "%{username} עם %{site_name}" - is_reserved: "שמור" purge_reason: "נמחק באופן אוטומטי כחשבון נטוש ולא פעיל" disable_remote_images_download_reason: "הורדת תמונות מרחוק נחסמה בשל היעדר מספיק שטח אכסון פנוי." anonymous: "אנונימי" @@ -415,15 +413,11 @@ he: description: 'פרסום זה מכיל תוכן שאדם סביר היה רואה כפוגעני, מתעלל או הפרה של כללי הקהילה .' long_form: 'דוגלל כלא ראוי' notify_user: - description: 'פרסום זה מכיל משהו שארצה לשוחח עליו עם האדם הזה באופן ישיר ופרטי. אין צורך בסימון.' long_form: 'הודעה נשלחה למשתמש/ת' email_title: 'הפרסום שלךב"%{title}"' email_body: "%{link}\n\n%{message}" notify_moderators: title: "משהו אחר" - description: 'פרסום זה דורש את תשומת ליבו של מנחה מסיבה שאינה מצויינת למעלה.' - long_form: 'זה סומן לתשומת הלב של מנחה' - email_title: 'הודעה ב-"%{title}" דורשת תשומת לב של מנהל' email_body: "%{link}\n\n%{message}" bookmark: title: 'מועדפים' @@ -448,7 +442,6 @@ he: long_form: 'דוגלל כלא ראוי' notify_moderators: title: "משהו אחר" - description: 'נושא זה דורש התייחסות של מנחה (moderator) כללי, על פי הכללים המנחים , TOS, או סיבה אחרת שאינה מופיעה כאן.' long_form: 'זה סומן לתשומת הלב של מנחה' email_title: 'הנושא "%{title}" דורש תשומת לב של מנהל' email_body: "%{link}\n\n%{message}" @@ -639,37 +632,6 @@ he: consumer_email_warning: "Your site is configured to use Gmail (or another consumer email service) to send email. Gmail limits how many emails you can send. Consider using an email service provider like mandrill.com to ensure email deliverability." site_contact_username_warning: "הזינו שם חשבון ידידותי של אי/אשת צות ממנו ישלחו הודעות אוטומטיות חשובות. עדכנו את site_contact_username בהגדרות האתר." notification_email_warning: "מיילים ליידוע אינם נשלחים מכתובת תקינה בדומיין שלכם; משלוח דוא\"ל יהיה בעייתי ולא אמין. אנא כוונו את כתובת המייל למשלוח התראות לכתובת מקומית תקינה בהגדרות האתר." - content_types: - education_new_reply: - title: "הדרכת משתמש חדש: תגובות ראשונות" - description: "Pop up just-in-time guidance automatically displayed above the composer when new users begin typing their first two new replies." - education_new_topic: - title: "הדרכת משתמש חדש: נושאים ראשונים" - description: "Pop up just-in-time guidance automatically displayed above the composer when new users begin typing their first two new topics." - usage_tips: - title: "הדרכה למשתמש/ת חדשים" - description: "הדרכה ומידע חשוב למשתמשים חדשים." - welcome_user: - title: "Welcome: New User" - description: "הודעה שנשלחת באופן אוטומטי לכל המשתמשים החדשים כאשר הם נרשמים." - welcome_invite: - title: "Welcome: Invited User" - description: "הודעה שנשלחת באופן אוטומטי לכל המשתמשים המוזמנים כאשר הם מקבלים את ההזמנה ממשתמש אחר." - login_required_welcome_message: - title: "Login Required: Welcome Message" - description: "Welcome message that is displayed to logged out users when the 'login required' setting is enabled." - login_required: - title: "Login Required: Homepage" - description: "The text displayed for unauthorized users when login is required on the site." - head: - title: "HTML head" - description: "HTML that will be inserted inside the tags." - top: - title: "Top of the pages" - description: "HTML that will be added at the top of every page (after the header, before the navigation or the topic title)." - bottom: - title: "Bottom of the pages" - description: "קוד HTML שיתווסף לפני התגית ." site_settings: censored_words: "מלים שיוחלפו באופן אוטומטי ב- ■■■■" delete_old_hidden_posts: "מחיקת אוטומטית של פרסומים מוסתרים שנותרים מוסתרים במשך יותר מ-30 יום." @@ -977,7 +939,6 @@ he: anonymous_posting_min_trust_level: "רמת האמון המינמלית הנדרשת כדי לאפשר פרסום אנונימי" anonymous_account_duration_minutes: "בכדי להגן על האנונימות צרו חשבון אנונימי כל N דקות לכ משתמש. לדוגמה: אם מכוון ל-600, כאשר יעברו 600 דקות מהפרסום האחרון והמשתמש/ת יחליפו לזהות אנונימית, חשבון אנונימי חדש יווצר." allow_profile_backgrounds: "אפשרו למשתמשים להעלות רקעים לפרופיל." - sequential_replies_threshold: "מספר הפרסומים שעל משתמש להכין ברצף בפרסום לפני שיקבל תזכורת בנוגע ליותר מידי תגובות עוקבות." enable_mobile_theme: "Mobile devices use a mobile-friendly theme, with the ability to switch to the full site. Disable this if you want to use a custom stylesheet that is fully responsive." dominating_topic_minimum_percent: "איזה אחוז מהפרסומים משתמש צריך לייצר בנושא לפני שיקבל תזכורת לגבי שליטת/שתלטנות יתר על הנושא." daily_performance_report: "Analyze NGINX logs daily and post a Staff Only topic with details" @@ -1008,7 +969,6 @@ he: enable_cdn_js_debugging: "אפשרו ל-/logs להציג שגיאות בצורה נכונה באמצעות הוספת הרשאות לגישה בין אתרים (crossorigin permissions) בכל ה-js הכלולים." show_create_topics_notice: "אם לאתר פחות מ-5 נושאים פומביים, הציגו הודעה המבקשת מן המנהלים/מנהלות ליצור עוד נושאים." delete_drafts_older_than_n_days: Delete drafts older than (n) days. - vacuum_db_days: "הריצו VACUUM FULL ANALYZE כדי להשיב שטח בסיס הנתונים אחרי העברות (כוונו ל-0 כדי לנטרל)" prevent_anons_from_downloading_files: "מונע ממשתמשים אנונימיים להוריד צרופות (attachments). אזהרה: דבר זה ימנע מכל משאב שאינו תמונה ופורסם כצרופה לעבוד." slug_generation_method: "Choose a slug generation method. 'encoded' will generate percent encoding string. 'none' will disable slug at all." enable_emoji: "אפשרו emoji" @@ -1468,16 +1428,8 @@ he: להדרכה נוספת, אנא פנו ל[כללי ההתנהגות הקהיליתיים](%{base_url}/guidelines) שלנו. user_automatically_blocked: subject_template: "המשתמש/ת החדשים %{username} נחסמו בשל התראות מהקהילה." - text_body_template: | - זהו מסר שנשלח בצורה אוטמטית. - המשתמש/ת החדש/ה [%{username}](%{base_url}%{user_url}) נחסמו באופן אוטומטי בשל כמה התראות בנוגע להודעה/הודעות שפרסמ/ה. - - אנא [סרקו את ההתראות] (%{base_url}/admin/flags). אם %{username נחסם בטעות מפרסום הודעות, הקישו על הכפתור "ביטול חסימה" ש[בעמוד האדמין עבור משתמש/ת אלה](%{base_url}%{user_url}). - - ניתן לשנות את המנגנון הזה דרך הגדרות 'חסימת_משתמש_חדש' שבאתר. spam_post_blocked: subject_template: "הודעות של המשתמש/ת החדש/ה %{username} נחסמו בשל קישורים חוזרים." - text_body_template: "זוהי הודעה שנשלחה באופן אוטומטי.\nהמשתמש/ת החדש/ה [%{username}](%{base_url}%{user_url}) ניסו ליצור כמה הודעות עם קישור ל-%{domains}, אבל הודעות אלו נחסמו כדי למנוע דואר זבל (spam). המשתמש/ת עדיין יכול/ה ליצור הודעות חדשות שלא מקשרות ל-%{domains}.\n\nאנא [סקרו את המשתמש/ת] (%{base_url}%{user_url}). \nהגדרות אלו ניתנו שינוי דרך ההגדרות 'משתמשיםחדשים_ספאם_סף'.\n" unblocked: subject_template: "Account unblocked" text_body_template: | @@ -1497,10 +1449,6 @@ he: download_remote_images_disabled: subject_template: "הורדת תמונות מרחוק מנוטרלת" text_body_template: "האפשרות \"הורדת תמונות מרוחקות\" נוטרלה בגלל שכל שטח האכסון שמוקצה ל\"תמונות שהורדו מרחוק\" נוצל." - unsubscribe_link: | - להסרה מרשימת התפוצה, בקר ב [הגדרות משתמש/ת](%{user_preferences_url}). - - בשביל להפסיק לקבל התראות בנוגע לשיחה הזאת [לחץ כאן](%{unsubscribe_url}). subject_re: "תגובה: " subject_pm: "[PM] " user_notifications: @@ -1508,8 +1456,6 @@ he: unsubscribe: title: "Unsubscribe" description: "Not interested in getting these emails? No problem! Click below to unsubscribe instantly:" - 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}'" @@ -1519,49 +1465,14 @@ he: 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: | - %{message} - - %{context} - - --- - %{respond_instructions} user_quoted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted_pm: subject_template: "[%{site_name}] [PM] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} digest: why: "סיכום קצר של %{site_link} מאז ביקורך האחרון ב-%{last_seen_at}" subject_template: "[%{site_name}] תקציר" diff --git a/config/locales/server.it.yml b/config/locales/server.it.yml index 8d049f6d7..e1c37e6c4 100644 --- a/config/locales/server.it.yml +++ b/config/locales/server.it.yml @@ -90,6 +90,8 @@ it: not_found: "L'URL o la risorsa richiesta non sono stati trovati." invalid_access: "Non hai il permesso di vedere la risorsa richiesta." read_only_mode_enabled: "Il sito è in modalità di sola lettura. Le interazioni sono disabilitate." + reading_time: "Tempo di lettura" + likes: "Mi piace" too_many_replies: one: "Spiacenti, ma i nuovi utenti possono inserire solo 1 risposta nello stesso argomento." other: "Spiacenti, ma i nuovi utenti possono inserire solo %{count} risposte nello stesso argomento." @@ -564,7 +566,9 @@ it: xaxis: "Giorno" yaxis: "Richieste API Totali" page_view_logged_in_mobile_reqs: + title: "Richieste API registrate" xaxis: "Giorno" + yaxis: "Richieste API da mobile registrate" page_view_anon_mobile_reqs: title: "Richieste anonime API" xaxis: "Giorno" @@ -611,6 +615,7 @@ it: host_names_warning: "Il tuo file config/database.yml usa l'hostname di default: localhost. Aggiornalo con l'hostname del tuo sito." gc_warning: 'Il tuo server usa i parametri di garbage collection di default di ruby, che potrebbero non garantirti le migliori prestazioni. Leggi questo argomento sulla taratura delle prestazioni: Tuning Ruby and Rails for Discourse.' sidekiq_warning: 'Sidekiq non è in esecuzione. Molte attività, come l''invio di email, sono eseguite in maniera asincrona da sidekiq. Assicurati che almeno un processo sidekiq sia in esecuzione. Leggi altro su sidekiq qui.' + queue_size_warning: 'Il numero di job in coda è %{queue_size}, il che è alto. Questo potrebbe indicare un problema con i processi Sidekiq, o potrebbe essere necessario aggiungere altri worker Sidekiq.' memory_warning: 'Il tuo server gira con meno di 1 GB di memoria. Si raccomanda almeno 1 GB di memoria.' google_oauth2_config_warning: 'Il server è configurato per permettere iscrizioni e login con Google Oauth2 (enable_google_oauth2_logins), ma il client id e il client secret non sono impostati. Vai nelle Impostazioni del sito e aggiorna le impostazioni. Leggi questa guida per saperne di più.' facebook_config_warning: 'Il server è configurato per accettare iscrizioni e login con Facebook (enable_facebook_logins), tuttavia i parametri app id e secret non sono stati impostati. Vai alle Impostazioni e aggiorna i campi interessati. Leggi questa guida per saperne di più.' @@ -619,6 +624,7 @@ it: s3_config_warning: 'Il server è configurato per caricare file su s3, ma almeno uno dei seguenti parametri non è impostato: s3_access_key_id, s3_secret_access_key, s3_upload_bucket. Vai nelle Impostazioni Sito e aggiorna i parametri. Leggi anche "How to set up image uploads to S3?" per saperne di più.' s3_backup_config_warning: 'Il server è configurato per caricare i backup su S3, ma almeno una delle seguenti impostazioni non è impostata: s3_access_key_id, s3_secret_access_key, s3_backup_bucket. Vai nelle Impostazioni Sito e aggiorna le impostazioni. Leggi anche "How to set up image uploads to S3?" per saperne di più.' image_magick_warning: 'Il server è configurato per creare miniature di immagini grandi, ma ImageMagick non è installato. Installa ImageMagick usando il tuo package manager preferito o scarica la versione più recente.' + failing_emails_warning: 'Ci sono %{num_failed_jobs} email job falliti. Controlla il tuo app.yml e assicurati che le impostazioni del server mail siano corrette. Vedi i job falliti in Sidekiq.' default_logo_warning: "Non hai personalizzato il logo del tuo sito. Aggiorna logo_url, logo_small_url e favicon_url nelle Impostazioni Sito." contact_email_missing: "Inserisci un indirizzo email di contatto per il sito, in modo da essere raggiungibile per questioni urgenti riguardanti il sito. Aggiornalo nelle Impostazioni Sito." contact_email_invalid: "La email di contatto non è valida. Aggiornala nelle Impostazioni Sito." @@ -627,6 +633,7 @@ it: consumer_email_warning: "Il tuo sito è configurato per usare Gmail (o un altro servizio email consumer) per inviare le email. Gmail limita il numero di email che puoi inviare. Considera l'utilizzo di un fornitore di servizi come mandrill.com per assicurare l'invio delle email." site_contact_username_warning: "Inserisci il nome di un membro dello staff da cui inviare messaggi importanti in maniera automatica. Aggiorna site_contact_username nelle Impostazioni Sito." notification_email_warning: "Le email di notifica non vengono inviate da un indirizzo email valido del tuo dominio; l'invio di email sarà inaffidabile. Definisci notification_email con un indirizzo email valido nelle Impostazioni Sito." + subfolder_ends_in_slash: "L'impostazione della sottocartella è errata; DISCOURSE_RELATIVE_URL_ROOT finisce con uno slash." site_settings: censored_words: "Parole che saranno automaticamente sostituite con ■■■■" delete_old_hidden_posts: "Cancella automaticamente tutti i messaggi nascosti che restano nascosti per più di 30 giorni." @@ -640,6 +647,7 @@ it: max_topic_title_length: "Numero massimo di caratteri per i titoli degli argomenti" min_private_message_title_length: "Numero minimo di caratteri per un messaggio" min_search_term_length: "Numero minimo di caratteri per le parole cercate" + search_tokenize_chinese_japanese_korean: "Attiva la tokenizzazione dei caratteri Cinesi/Giapponesi/Coreani nella ricerca anche sui siti non CJK" allow_uncategorized_topics: "Permetti la creazione di argomenti senza categoria. ATTENZIONE: se ci sono argomenti senza categoria, devi ricategorizzarli prima di disabilitare questa opzione." uncategorized_description: "La descrizione della categoria \"Non classificato\". Lascia vuoto per nessuna descrizione." allow_duplicate_topic_titles: "Permetti più argomenti con lo stesso identico titolo" @@ -669,6 +677,9 @@ it: post_excerpt_maxlength: "Lunghezza massima dell'estratto / riassunto di un messaggio." post_onebox_maxlength: "Lunghezza massima in caratteri di un messaggio Discourse in Onebox." onebox_domains_whitelist: "Lista di domini per i quali consentire la funzione di onebox; questi domini devono supportare OpenGraph o oEmbed. Testali su http://iframely.com/debug" + logo_url: "L'immagine nel logo in alto a sinistra del sito, dovrebbe essere larga e di forma rettangolare. Se non impostata, verrà usato il testo del titolo del sito." + digest_logo_url: "Il logo alternativo usato in cima alla email di riepilogo. Dovrebbe essere a forma di un ampio rettangolo. Se lasciato vuoto, sarà utilizzato il campo `logo_url`." + logo_small_url: "Il piccolo logo in alto a sinistra del tuo sito, dovrebbe essere a forma di quadrato. Se non impostato, verrà mostrata l'icona di una casa." apple_touch_icon_url: "Icona usata per dispositivi touch Apple. La dimensione consigliata è 144 x 144 pixel." notification_email: "L'indirizzo presente nel campo from: usato per inviare tutte le email essenziali di sistema. Il dominio indicato deve avere i record SPF, DKIM e reverse PTR impostati correttamente perché l'email arrivi." email_custom_headers: "Una lista di intestazioni email personalizzate delimitata da una barra verticale (pipe |)" @@ -809,7 +820,6 @@ it: tl2_requires_likes_received: "Quanti \"Mi piace\" deve ricevere un utente per essere promosso al livello di esperienza 2." tl2_requires_likes_given: "Quanti \"Mi piace\" deve dare un utente per essere promosso al livello di esperienza 2." tl2_requires_topic_reply_count: "A quanti argomenti deve rispondere un utente per essere promosso al livello di esperienza 2." - tl3_requires_days_visited: "Per quanti giorni un utente deve aver visitato il sito negli ultimi 100 giorni per essere promosso al livello di esperienza 3 (da 0 a 100)." tl3_requires_topics_replied_to: "Numero minimo di argomenti cui un utente deve aver risposto negli ultinmi 100 giorni per essere promosso al livello di esperienza 3 (0 o maggiore)." tl3_requires_topics_viewed: "Percentuale di argomenti creati negli ultimi 100 giorni che un utente deve leggere per essere promosso al livello di esperienza 3 (da 0 a 100)." tl3_requires_posts_read: "Percentuale di messaggi creati negli ultimi 100 giorni che un utente deve leggere per essere promosso al livello di esperienza 3 (da 0 a 100)." @@ -835,6 +845,7 @@ it: title_max_word_length: "La lunghezza massima di una parola, in caratteri, nel titolo di un argomento." title_min_entropy: "Minima entropia (caratteri unici) richiesti come titolo di un argomento." body_min_entropy: "Minima entropia (caratteri unici) richiesti come corpo di un messaggio." + allow_uppercase_posts: "Permetti l'uso di tutti caratteri maiuscoli nel titolo di argomento o nel corpo di un messaggio. " title_fancy_entities: "Converti caratteri ASCII comuni in HTML nei titoli dell'argomento, tipo SmartyPants http://daringfireball.net/projects/smartypants/" min_title_similar_length: "Lunghezza minima di un titolo che attiva il controllo su argomenti simili." min_body_similar_length: "Lunghezza minima del corpo di un messaggio che attiva il controllo su argomenti simili." @@ -1136,25 +1147,8 @@ it: Per maggiori dettagli, fai riferimento alle nostre [linee guida della community](%{base_url}/guidelines). user_automatically_blocked: subject_template: "Nuovo utente %{username} bloccato a causa delle segnalazioni" - text_body_template: | - Questo è un messaggio automatico. - - Il nuovo utente [%{username}](%{base_url}%{user_url}) è stato automaticamente bloccato perché più utenti hanno contrassegnato i messaggi di %{username}. - - Per favore [controlla le segnalazioni](%{base_url}/admin/flags). Se %{username} è stato bloccato per errore, clicca sul tasto di sblocco nella [pagina di amministrazione di questo utente](%{base_url}%{user_url}). - - La soglia può essere modificata tramite l'impostazione del sito `block_new_user`. spam_post_blocked: subject_template: "Il messaggi del nuovo utente %{username} sono stati bloccati a causa dell'invio di ripetuti collegamenti." - text_body_template: |+ - Questo è un messaggio automatico. - - Il nuovo utente [%{username}](%{base_url}%{user_url}) ha creato diversi messaggi con collegamenti a %{domains}, ma tali messaggi sono stati bloccati per evitare spam. L'utente è ancora abilitato alla creazione di messaggi ma senza collegamenti verso %{domains}. - - Per favore [controlla questo utente](%{base_url}%{user_url}). - - Questa soglia può essere modificata tramite l'impostazione `newuser_spam_host_threshold` e `white_listed_spam_host_domains` del sito. - unblocked: subject_template: "Account sbloccato" text_body_template: | @@ -1185,49 +1179,14 @@ it: subject_template: "[%{site_name}] %{username} ti invita a partecipare all'argomento '%{topic_title}'" user_replied: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_quoted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted_pm: subject_template: "[%{site_name}] [PM] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} digest: why: "Un breve sommario di %{site_link} dalla tua ultima visita il %{last_seen_at}" subject_template: "[%{site_name}] Riepilogo" diff --git a/config/locales/server.ja.yml b/config/locales/server.ja.yml index 8d0745d03..739b3988c 100644 --- a/config/locales/server.ja.yml +++ b/config/locales/server.ja.yml @@ -16,8 +16,6 @@ ja: loading: "読み込み中" powered_by_html: 'Powered by Discourse, best viewed with JavaScript enabled' log_in: "ログイン" - via: "%{username} via %{site_name}" - is_reserved: "は予約されています" purge_reason: "アクティブでないアカウントは放棄されたとして削除されました" disable_remote_images_download_reason: "ディスク容量が不足しているため、リモートでの画像ダウンロードは無効になっています。" anonymous: "匿名" @@ -318,15 +316,11 @@ ja: description: 'この投稿は、誹謗中傷、恫喝、名誉毀損、わいせつ、犯罪行為など他人を不快にさせる内容を含んでいる。' long_form: '不適切フラグをたてる' notify_user: - description: 'この投稿やトピックを書いた人に、直接メッセージを送る。(フラグを付けません)' long_form: 'メッセージが送られたユーザ' email_title: '「%{title}」にの投稿' email_body: "%{link}\n\n%{message}" notify_moderators: title: "その他" - description: 'このポストは、記載されていない別の理由でモデレータの注意が必要です' - long_form: 'モデレータへの注意としてフラグを立てる' - email_title: '"%{title}" のポストに関する管理人の確認が必要とする' email_body: "%{link}\n\n%{message}" bookmark: title: 'ブックマーク' @@ -351,7 +345,6 @@ ja: long_form: '不適切フラグをたてる' notify_moderators: title: "その他" - description: 'この投稿やトピックは不適切な可能性があるため、管理人による確認を必要とする。' long_form: 'モデレータへの注意としてフラグを立てる' email_title: 'トピック"%{title}" は不適切な可能性があるため、管理人による確認を必要とする。' email_body: "%{link}\n\n%{message}" @@ -537,37 +530,6 @@ ja: consumer_email_warning: "サイトはメール送信に Gmail (または他のカスタムメールサービス) を利用するように設定されています。Gmail で送信可能なメール数には制限があります。メールを確実に送信するために mandrill.com などのメールサービスプロバイダーの利用を検討してください。" site_contact_username_warning: "重要な自動メッセージを送信するフレンドリースタッフユーザーアカウントの名前を入力してください。サイト設定 のsite_contact_username を更新してください。" notification_email_warning: "通知用メールがあなたのドメインで有効なメールアドレスから送信されていません。メール配信が不安定になり、信頼性が低くなります。\nサイトの設定で更新してください" - content_types: - education_new_reply: - title: "新規ユーザ支援: 初めての回答" - description: "新規ユーザが回答の入力を開始した際に自動的にポップアップするガイダンスの内容 (最初の2回のみ表示)。" - education_new_topic: - title: "新規ユーザ支援: 初めてのポスト" - description: "新規ユーザが新規トピックの作成を行った際に自動的にポップアップするガイダンスに内容 (最初の2回のみ表示)。" - usage_tips: - title: "新規ユーザガイダンス" - description: "新規ユーザのための重要な情報とガイダンス" - welcome_user: - title: "ウェルカムメッセージ: 新規ユーザ" - description: "サインアップ時にすべての新規ユーザに自動送信されるメッセージ。" - welcome_invite: - title: "ウェルカムメッセージ: 招待ユーザ" - description: "他のユーザからの招待を受け入れたときにすべての新しい招待ユーザに自動送信されるメッセージ" - login_required_welcome_message: - title: "ウェルカムメッセージ: 要ログイン" - description: "'login required' 設定有効時に、ユーザに対してログインを促すメッセージの内容。" - login_required: - title: "ホームページ: 要ログイン" - description: "要ログイン設定の際に、未ログインユーザに対して表示されるメッセージの内容。" - head: - title: "HTML head" - description: " タグの中に挿入されるHTML" - top: - title: "ページトップ" - description: "全ページのトップに追加される HTML (ヘッダとナビゲーション/トピックタイトルの間に表示されます)" - bottom: - title: "ページボトム" - description: "タグの前に挿入されるHTML" site_settings: censored_words: "自動的に ■■■■ で置換されます" delete_old_hidden_posts: "30日以上非表示になっているポストを自動で削除します" @@ -866,7 +828,6 @@ ja: anonymous_posting_min_trust_level: "匿名の投稿を行うための最小のトラストレベル" anonymous_account_duration_minutes: "匿名性を守るため、N 分毎に匿名ユーザーを作成しなおします。 例 : 600を設定すると匿名ユーザへの変更と最後の投稿から600分経過していた場合、匿名アカウントが作成されます" allow_profile_backgrounds: "プロフィール背景のアップロードを許可" - sequential_replies_threshold: "ユーザに連続回答が多すぎることをリマインドする、連続ポスト投稿のしきい値" enable_mobile_theme: "モバイル端末にモバイル向けテーマ (通常サイト用テーマにスイッチ可能) を利用する。レスポンシブなスタイルシートを使用する場合はこの設定を無効にしてください。" dominating_topic_minimum_percent: "ユーザがトピックを占拠しているとリマインドを行う、ポスト投稿支配率 " daily_performance_report: "日別のNGINXのログを解析し、Staff Onlyトピックへ詳細を投稿する" @@ -896,7 +857,6 @@ ja: enable_cdn_js_debugging: "全てのJSにcrossorigin権限を追加することで、/logsに適切なエラーを表示することを許可する" show_create_topics_notice: "サイトにpublicなトピックが5よりも少ない場合、トピックを作成するための通知が管理者に表示される" delete_drafts_older_than_n_days: (n) 日間経過したドラフトを削除 - vacuum_db_days: "DB領域を再利用するため、migration後にVACUUM FULL ANALYZEを実行する(0を設定すると無効化)" prevent_anons_from_downloading_files: "匿名ユーザーが添付ファイルをダウンロードするのを防止。警告: 画像ではなく、添付ファイルとして投稿された全てのファイルが対象です" slug_generation_method: "slugを生成するメソッドを選択。 'encode'はパーセントエンコードされた文字列を生成します。 'none'は、slugを無効にします" enable_emoji: "絵文字を有効にする" @@ -1381,25 +1341,8 @@ ja: 詳細については[コミュニティガイドライン](%{base_url}/guidelines)を参照してください。 user_automatically_blocked: subject_template: "コミュニティフラッグによって新たにブロックされたユーザ%{username}" - text_body_template: |+ - これは自動送信メッセージです。 - - 複数のユーザが%{username}'のポストにフラグを立てているため、新規ユーザ[%{username}](%{base_url}%{user_url})は自動的にブロックされました。 - - フラグを確認してください(%{base_url}/admin/flags)。もし%{username}が間違ってブロックされた場合は、 このユーザの管理ページ(%{base_url}%{user_url})でブロック解除ボタンをクリックしてください。 - - この閾値はサイト設定の`block_new_user`で変更できます。 - spam_post_blocked: subject_template: "同一リンクの連続投稿による新規ユーザ %{username} のブロック" - text_body_template: | - これは自動送信メッセージです。 - - 新規ユーザ[%{username}](%{base_url}%{user_url})は、%{domains}へのリンクを複数投稿しようとしましたが、スパムを避けるために投稿をブロックしました。ユーザは%{domains}のリンクを含まない投稿であれば引き続き作成可能です。 - - ユーザを確認してください。(%{base_url}%{user_url}) - - この設定はサイト設定の `newuser_spam_host_threshold` と `white_listed_spam_host_domains` で変更できます。 unblocked: subject_template: "アカウントのブロック解除" text_body_template: | @@ -1425,8 +1368,6 @@ ja: unsubscribe: title: "解除" description: "メールに興味がありませんか? 下のリンクをクリックすると、即座にメール解除ができます:" - reply_by_email: "回答するにはこのメールに返信するか、ブラウザで %{base_url}%{url} にアクセスしてください。" - visit_link_to_respond: "回答するにはブラウザで %{base_url}%{url} にアクセスしてください。" posted_by: "%{post_date} に %{username} が投稿" user_invited_to_private_message_pm: subject_template: "[%{site_name}] %{username} がメッセージにあなたを招待しました '%{topic_title}'" @@ -1435,49 +1376,14 @@ ja: subject_template: "[%{site_name}] %{username} がトピックにあなたを招待しました '%{topic_title}'" user_replied: subject_template: "[%{site_name}] '%{topic_title}' 内のあなたのポストに新たな回答が投稿されました" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_quoted: subject_template: "[%{site_name}] '%{topic_title}' で %{username} があなたを引用しました" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] '%{topic_title}' で %{username} があなたをタグ付けしました" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted: subject_template: "[%{site_name}] '%{topic_title}' に %{subject_prefix}個の新しいポストが投稿されました" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted_pm: subject_template: "[%{site_name}] [プライベートメッセージ] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} digest: why: "あなたが最後にアクセスした %{last_seen_at} 以降の %{site_link} のまとめです" subject_template: "[%{site_name}] ダイジェスト" diff --git a/config/locales/server.ko.yml b/config/locales/server.ko.yml index 51ef2e975..b19908133 100644 --- a/config/locales/server.ko.yml +++ b/config/locales/server.ko.yml @@ -16,8 +16,6 @@ ko: loading: "로딩중" powered_by_html: 'Powered by Discourse, best viewed with JavaScript enabled' log_in: "로그인" - via: "%{site_name}의 %{username}" - is_reserved: "예약됨" purge_reason: "비활성화된 계정은 자동적으로 삭제됩니다." disable_remote_images_download_reason: "디스크저장공간이 부족하여 원격 이미지 다운로드 기능이 비활성화 되었습니다." anonymous: "익명" @@ -330,15 +328,11 @@ ko: description: '이 글은 다른 사용자들에게 공격적이나 모욕적 또는 침해적인 글을 담고 있습니다.' long_form: '부적절함으로 신고하기' notify_user: - description: '이 글에 내가 작성자와 직접 대화하고 싶은 내용이 있습니다. 신고에서 제외해주세요.' long_form: '메세지한 유저' email_title: '"%{title}" 내의 당신의 글' email_body: "%{link}\n\n%{message}" notify_moderators: title: "뭔가 다른것" - description: '이 글은 리스트에 없는 이유로 관리자의 주의가 필요합니다.' - long_form: '관리자의 주의를 위해 신고' - email_title: '"%{title}" 글에 대한 운영자의 확인이 필요합니다' email_body: "%{link}\n\n%{message}" bookmark: title: '북마크' @@ -365,7 +359,6 @@ ko: long_form: '부적절함으로 신고하였습니다.' notify_moderators: title: "뭔가 다른것" - description: '이 글은 가이드라인, 이용약관, 또는 다른 이유와 관련해서 운영자의 주의가 필요합니다.' long_form: '관리자의 주의를 위해 신고' email_title: '글타래 "%{title}" 은 운영자의 확인이 필요합니다' email_body: "%{link}\n\n%{message}" @@ -538,37 +531,6 @@ ko: consumer_email_warning: "이메일 전송을 위해 Gmail(또는 다른 커스텀 이메일 서비스)을 사용하고 있습니다. Gmail의 이메일 전송 제한 을 읽어보세요. mandrill.com을 이메일 서비스로 설정하는 것을 고려해보세요." site_contact_username_warning: "중요 자동 메세지를 보낼 스태프 사용자 계정의 이름을 적으세요. 사이트 설정에서 site_contact_username을 업데이트하세요." notification_email_warning: "알람 메일이 올바른 이 사이트의 도메인 내 이메일 주소로 전송되지 않고 있습니다; 이메일 전송이 안정적이지 않을 것입니다. 사이트 설정에서 올바른 도메인의 이메일 주소로 정해주세요." - content_types: - education_new_reply: - title: "새로운 사용자 교육: 첫번째 답글들" - description: "새로운 사용자가 첫 두개의 답글을 작성하면 자동 팝업됩니다." - education_new_topic: - title: "새로운 사용자 교육: 첫번째 글타래들" - description: "새로운 사용자가 첫 두개의 글타래를 작성하면 자동 팝업됩니다." - usage_tips: - title: "새로운 사용자 가이드" - description: "새로운 사용자를 위한 가이드와 팁." - welcome_user: - title: "환영: 새로운 사용자" - description: "새 가입자에게 전달할 자동 메세지" - welcome_invite: - title: "환영: 초대된 사용자" - description: "사용자가 다른 사람이 보낸 초대장에 승인했을 때 에게 전달 될 자동 메세지" - login_required_welcome_message: - title: "로그인 필요: Welcome Message" - description: "'login required' 설정이 활성화 되어있으면 웰컴 메시지가 로그인 되지 않은 사용자들에게 보여집니다." - login_required: - title: "로그인 필요: 홈페이지" - description: "이 텍스트는 로그인이 필요하지만 인증되지 않은 사용자들에게 보여집니다." - head: - title: "HTML head" - description: " 태그 사이에 추가할 HTML" - top: - title: "페이지 상단" - description: "모든 페이지 상단에 추가할 HTML(헤더 아래, 네비게이션이나 글타래 제목 위)." - bottom: - title: "페이지 하단" - description: "모든 페이지 하단에 추가할 HTML" site_settings: censored_words: "단어는 자동적으로 `■■■■` 로 대체 됩니다." delete_old_hidden_posts: "30일이 지난 숨겨진 글은 자동으로 삭제됩니다." @@ -853,7 +815,6 @@ ko: anonymous_posting_min_trust_level: "익명 게시 할 수 있는 최소 회원등급" anonymous_account_duration_minutes: "한 익명이 익명계정을 만들 때 빨리 만드는 걸 막을 분 단위 시간 예: 600으로 정해놓으면 마지막 게시하고 익명 전환 후로 600분이 지나는 즉시 새 계정이 만들어 집니다." allow_profile_backgrounds: "사용자에게 프로필 배경 이미지 업로드를 허용합니다." - sequential_replies_threshold: "한 유저가 한 글타래에 너무 연속으로 답글 달았다고 알리게 될 연속 글 수" enable_mobile_theme: "모바일 디바이스는 모바일 환경에 친화적인 테마를 사용합니다, 그리고 PC용 화면으로 전환할 수 있습니다. 만약 커스텀 스타일 시트를 사용한다면 이것을 비활성화 시키세요." dominating_topic_minimum_percent: "한 글타래에서 한 사용자의 영향력을 결정하는 글 수의 퍼센트" daily_performance_report: "NGINX 일별 로그를 분석하고 상세정보를 스태프들에게 글타래 게시" @@ -882,7 +843,6 @@ ko: enable_cdn_js_debugging: "/logs에서 적절한 모든 js를 포함해 crossorigin 권한 오류가 뜨도록 합니다." show_create_topics_notice: "이 사이트에 5개 이하의 공개 글타래가 있으면, 관리자에게 글타래 좀 만들라고 알려줍니다." delete_drafts_older_than_n_days: (n) 일지난 임시 보관글 삭제하기 - vacuum_db_days: "VACUUM FULL ANALYZE를 실행해서 통합 후 DB 공간 재확인 (0으로 설정하면 비활성화)" prevent_anons_from_downloading_files: "익명 사용자가 다운로드를 받지 못하게 합니다. 경고: 이미지 파일 외 모든 첨부파일이 다운로드 받지 못하게 됩니다." slug_generation_method: "slug 생성 방식. 'encoded'는 퍼센트 기호로 인코딩해서 생성합니다. 'none'은 slug 자체를 비활성화합니다.." enable_emoji: "emoji 활성화" @@ -1320,24 +1280,8 @@ ko: 추가적인 조언은 [community guidelines](%{base_url}/guidelines)에서 확인하실 수 있습니다. user_automatically_blocked: subject_template: "%{username} 신규가입자가 커뮤니티 신고로 블락됨" - text_body_template: | - 자동 생성 메세지입니다. - - 새 사용자 [%{username}](%{base_url}%{user_url})님이 여러 사용자가 %{username}님의 글(들)을 신고하여 자동으로 블락되었습니다. - - [신고 접수](%{base_url}/admin/flags) 확인 바랍니다. %{username}님이 잘못 블락 되었으면, [사용자 관리 페이지](%{base_url}%{user_url})에서 블락해제해 주세요. - - `block_new_user` 사이트 설정을 변경해 쓰레솔드를 바꿀 수 있습니다. spam_post_blocked: subject_template: "%{username} 신규가입자가 연속 링크로 블락됨" - text_body_template: | - 자동 생성 메세지입니다. - - 새 사용자 [%{username}](%{base_url}%{user_url})님이 %{domains} 링크로 여러 글을 올려서 스팸이 의심되어 블락되었습니다. 다만, 이 사용자는 %{domains} 링크가 없는 글은 계속 올릴 수 있습니다. - - [사용자 관리 페이지](%{base_url}%{user_url})에서 확인 바랍니다. - - 이는 `newuser_spam_host_threshold`와 `white_listed_spam_host_domains` 사이트 설정으로 변경할 수 있습니다. unblocked: subject_template: "계정 블락 해제" text_body_template: | @@ -1363,8 +1307,6 @@ ko: 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}' 글타래에 초대했습니다" @@ -1398,49 +1340,14 @@ ko: 메세지 링크: %{base_url}%{url} user_replied: subject_template: "[%{site_name}] '%{topic_title}'에 답글이 달렸습니다" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_quoted: subject_template: "[%{site_name}] %{username}님께서 '%{topic_title}' 글타래에 당신을 인용하였습니다" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{username}님께서 '%{topic_title}'에서 당신을 언급하였습니다" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted: subject_template: "[%{site_name}] %{subject_prefix} '%{topic_title}'에 새로운 글이 등록되었습니다" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted_pm: subject_template: "[%{site_name}] [PM] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} digest: why: "%{last_seen_at}부터 지금까지 %{site_link} 사이트 근황" subject_template: "[%{site_name}] 근황" diff --git a/config/locales/server.nb_NO.yml b/config/locales/server.nb_NO.yml index e0dda1606..a3bdb502f 100644 --- a/config/locales/server.nb_NO.yml +++ b/config/locales/server.nb_NO.yml @@ -16,8 +16,6 @@ nb_NO: loading: "Laster" powered_by_html: 'Drevet av Discourse, best nyttet med JavaScript på' log_in: "Logg inn" - via: "%{username} via %{site_name}" - is_reserved: "er reservert" purge_reason: "Automatisk slettet som som forlatt, uaktivert konto" disable_remote_images_download_reason: "Nedlasting av bilder ble deaktivert grunnet mangel på tilgjengelig diskplass." anonymous: "Anonym" @@ -253,6 +251,8 @@ nb_NO: title: "ny bruker" basic: title: " bruker" + member: + title: "medlem" rate_limiter: too_many_requests: "Vi har en daglig begrensning for hvor mange ganger den handlingen kan utføres. Venligst vent %{time_left} før du prøver igjen." hours: @@ -371,15 +371,11 @@ nb_NO: description: 'Dette innlegget har innhold som for en fornuftig person vil kunne være fornærmende, nedverdigende eller brudd på retningslinjene til dette nettsamfunnet.' long_form: 'markerte dette som upassende' notify_user: - description: 'Dette innlegget inneholder noe jeg ønsker å ta opp med vedkommende direkte på privaten. Oppretter ikke en markering.' long_form: 'sendt melding til bruker' email_title: 'Ditt innlegg i "%{title}"' email_body: "%{link}\n\n%{message}" notify_moderators: title: "Noe annet" - description: 'Dette innlegget trenger moderator oppmerksomhet for en annen grunn enn listet over.' - long_form: 'merket for moderators oppmerksomhet' - email_title: 'Et innlegg i "%{title}" krever moderator oppmerksomhet' email_body: "%{link}\n\n%{message}" bookmark: title: 'Bokmerke' @@ -404,7 +400,6 @@ nb_NO: long_form: 'markerte dette som upassende' notify_moderators: title: "Noe annet" - description: 'Dette emnet krever moderators oppmerksomhet basert på retningslinjene, TOS, eller en annen grunn ikke listet over. ' long_form: 'Markert for mederering' email_title: 'Emne "%{title}" krever moderator oppmerksomhet' email_body: "%{link}\n\n%{message}" @@ -440,6 +435,8 @@ nb_NO: title: "nye brukere" xaxis: "Dag" yaxis: "Antall nye brukere" + profile_views: + xaxis: "Dag" topics: title: "Emner" xaxis: "Dag" @@ -571,32 +568,6 @@ nb_NO: host_names_warning: "Din config/database.yml fil bruker standard localhost hostnavn. Venligst legg inn ditt ønskede hostnavn" memory_warning: 'Serveren din kjører med mindre enn 1 GB med minne. Minst 1 GB RAM er anbefalt.' title_nag: "Oppgi navnet på nettstedet. Oppdater tittelen under Nettstedinstillinger." - content_types: - education_new_reply: - title: "Ny bruker utdanning: Første svar" - education_new_topic: - title: "Ny bruker utdanning: Første Emne" - usage_tips: - title: "Ny Bruker Guide" - description: "Brukerveiledning og nyttig informasjon for nye brukere" - welcome_user: - title: "Velkommen: Ny Bruker" - description: "En melding som automatisk sendes til alle nye brukere når de oppretter konto." - welcome_invite: - title: "Velkommen: Invitert Bruker" - login_required_welcome_message: - title: "Pålogging påkrevd: Velkommen Melding" - login_required: - title: "Pålogging påkrevd: Hjemmeside" - description: "Tekst som vil bli vist uautoriserte brukere når logg inn er påkrevd." - head: - title: "HTML head" - description: "HTML som vil bli satt inn i klammene. " - top: - title: "Toppen av siden" - bottom: - title: "Bunnen av siden" - description: "HTML som vil bli lagt til før tag. " site_settings: censored_words: "Ord som automatisk vil bli erstattet med ■■■■" delete_old_hidden_posts: "Auto-slett skjulte innlegg som er skjult i mer enn 30 dager." diff --git a/config/locales/server.nl.yml b/config/locales/server.nl.yml index 65e7b9233..72024c882 100644 --- a/config/locales/server.nl.yml +++ b/config/locales/server.nl.yml @@ -18,8 +18,6 @@ nl: loading: "Laden" powered_by_html: 'Powered by Discourse, werkt het beste met JavaScript ingeschakeld' log_in: "Inloggen" - via: "%{username} via %{site_name}" - is_reserved: "is gereserveerd" purge_reason: "Automatisch verwijderd, account is nooit geactiveerd" disable_remote_images_download_reason: "Het downloaden van plaatjes is uitgeschakeld omdat er niet genoeg schijfruimte beschikbaar is." anonymous: "Anoniem" @@ -430,16 +428,11 @@ nl: description: 'Dit bericht bevat inhoud dat iemand als beledigend, discriminerend of kwetsend kan ervaren. Ook kan het een overtreding van de regels zijn.' long_form: 'heeft dit als ongepast gemeld' notify_user: - title: 'Neem contact op met @{{username}} via privébericht' - description: 'Dit bericht bevat iets waarover ik je graag persoonlijk en privé wil spreken. Dit zet geen vlag.' long_form: 'bericht verstuurd aan gebruiker' email_title: 'Je bericht in ''%{title}''' email_body: "%{link}\n\n%{message}" notify_moderators: title: "Iets anders" - description: 'Dit bericht heeft de aandacht van een moderator nodig om een andere reden dan hier boven gespecificeerd.' - long_form: 'heeft dit gemarkeerd voor moderatie' - email_title: 'Graag aandacht van een moderator voor een bericht in ''%{title}''' email_body: "%{link}\n\n%{message}" bookmark: title: 'Favoriet' @@ -464,7 +457,6 @@ nl: long_form: 'markeerde dit als ongepast' notify_moderators: title: "Iets anders" - description: 'Dit topic moet door een moderator bekeken worden. Dit vanwege de regels, voorwaarden of vanwege een andere reden.' long_form: 'heeft dit gemarkeerd voor moderatie' email_title: 'De topic "%{title}" moet door een moderator worden bekeken' email_body: "%{link}\n\n%{message}" @@ -656,37 +648,6 @@ nl: site_contact_username_warning: "Vul de naam in van een vriendelijke staf gebruikersaccount om belangrijke geautomatiseerde berichten vandaan te verzenden. Verander site_contact_username in Site Instellingen." notification_email_warning: "Notificatie e-mails worden niet verzonden van een geldig email-adres op je domein; email bezorging zal onberekenbaar en onbetrouwbaar zijn. Verander a.u.b. notification_email naar een geldig lokaal email adres in Site Instellingen." subfolder_ends_in_slash: "Je submap setup is onjuist; de DISCOURSE_RELATIVE_URL_ROOT eindigt in een schuine streep." - content_types: - education_new_reply: - title: "Uitleg voor nieuw lid: Eerste reacties" - description: "Popup verschijnt automatisch als een nieuw lid zijn eerste twee reacties schrijft." - education_new_topic: - title: "Uitleg voor nieuwe leden: Eerste topics" - description: "Popup verschijnt automatisch als een nieuw lid zijn eerste twee topics schrijft." - usage_tips: - title: "Hulp voor nieuwe leden" - description: "Hulp en essentiële informatie voor nieuwe leden." - welcome_user: - title: "Welkom: Nieuw lid" - description: "Een boodschap welke automatisch naar alle nieuwe gebruikers verzonden wordt als ze zich inschrijven." - welcome_invite: - title: "Welkom: Uitgenodigd lid" - description: "Een boodschap welke automatisch naar alle nieuw uitgenodigde gebruikers wordt verzonden als ze ingaan op een uitnodiging van een andere gebruiker om deel te nemen." - login_required_welcome_message: - title: "Inloggen vereist: Welkomstbericht" - description: "Welcomstbericht dat getoond wordt aan uitgelogde gebruikers als 'login required' is ingesteld." - login_required: - title: "Inloggen nodig: Homepage" - description: "De tekst die getoond wordt als men moet inloggen om naar het forum te kunnen gaan." - head: - title: "HTML head" - description: "HTML wordt ingevoegd in de tags." - top: - title: "Bovenkant van de pagina's" - description: "HTML dat aan de bovenkant van alle pagina's wordt toegevoegd (na de header, voor de navigatie of topictitel)." - bottom: - title: "Onderkant van de pagina's" - description: "HTML welke toegevoegd wordt voor de tag." site_settings: censored_words: "Woorden welke automatisch vervangen zullen worden met ■■■■" delete_old_hidden_posts: "Auto-delete alle verborgen berichten die meer dan 30 dagen verborgen zijn." @@ -1005,7 +966,6 @@ nl: anonymous_account_duration_minutes: "Ter bescherming van anonimiteit, maak een nieuw anonieme account aan elke N minuten voor elke gebruiker. Bijvoorbeeld: als ingesteld op 600 dan zal een nieuwe anonieme account worden aangemaakt als er 600 minuten zijn verstreken na het laatste bericht EN de gebruiker schakelt om naar anon." hide_user_profiles_from_public: "Uitschakelen gebruikerskaarten, gebruikersprofielen en het gebruikersoverzicht voor anonieme gebruikers." allow_profile_backgrounds: "Gebruikers mogen een profielachtergrond instellen." - sequential_replies_threshold: "Aantal reacties dat een gebruiker achter elkaar moet maken in een topic voordat ze worden herinnerd aan te veel opeenvolgende reacties." enable_mobile_theme: "Mobiele apparaten gebruiken een mobiel-vriendelijke theme met de mogelijkheid te schakelen naar de volledige site. Schakel deze optie uit als je een eigen stylesheet wil gebruiken die volledig responsive is." dominating_topic_minimum_percent: "Welk percentage van de berichten een gebruiker moet maken in een onderwerp voordat ze worden herinnerd aan het te veel domineren van een topic." daily_performance_report: "Analyseer elke dag NGINX logs en post een Alleen Voor Medewerkers topic met de details" @@ -1037,7 +997,6 @@ nl: enable_cdn_js_debugging: "Laat /logs juiste errors weergeven door crossorigin toestemmingen toe te voegen op alle js includes." show_create_topics_notice: "Als de site minder dan 5 publieke topics heeft, toon dan een melding waarin admins gevraagd wordt om een aantal topics te creëren." delete_drafts_older_than_n_days: Verwijder concepten ouder dan (n) dagen. - vacuum_db_days: "Draai VACUUM FULL ANALYZE om DB ruimte terug te winnen na migraties (stel op 0 in om uit te schakelen)" prevent_anons_from_downloading_files: "Voorkom dat anonieme gebruikers bijlagen mogen downloaden. WAARSCHUWING: hierdoor zullen website onderdelen, anders dan afbeeldingen, die zijn gepost als bijlage niet langer werken." slug_generation_method: "Kies een slug generate methode. 'encoded' zal een percentage encoderen string genereren. 'none' zal slug helemaal uitschakelen." enable_emoji: "Inschakelen emoji" @@ -1405,24 +1364,8 @@ nl: Kijk voor verdere uitleg in de [regels](%{base_url}/guidelines). user_automatically_blocked: subject_template: "Nieuwe gebruiker %{username} geblokkeerd wegens meldingen van andere gebruikers" - text_body_template: | - Dit is een automatisch gegenereerd bericht. - - De nieuwe gebruiker [%{username}](%{base_url}%{user_url}) is automatisch geblokkeerd, omdat meerdere gebruikers de bericht(en) van %{username} gemarkeerd hebben. - - Kijk hier voor [de markeringen](%{base_url}/admin/flags). Als %{username} ten onrechte geblokkeerd is, klik dan op de deblokkeerdknop op [de beheerpagina van deze gebruiker](%{base_url}%{user_url}). - - De gevoeligheid kan worden gewijzigd via de `block_new_user` site instellingen. spam_post_blocked: subject_template: "Berichten van nieuwe gebruiker %{username} geblokkeerd vanwege herhaalde links" - text_body_template: | - Dit is een automatisch gegenereerd bericht. - - Nieuwe gebruiker [%{username}](%{base_url}%{user_url}) probeerde meerdere berichten te plaatsen met links naar %{domains}, maar deze berichten zijn geblokkeerd om spam tegen te gaan. De gebruiker kan nog steeds nieuwe berichten plaatsen die niet linken naar %{domains}. - - Bekijk de gebruiker [hier](%{base_url}%{user_url}). - - De drempel hiervoor kan ingesteld worden met de `newuser_spam_host_threshold` en de `white_listed_spam_host_domains` site instellingen. unblocked: subject_template: "Account gedeblokkeerd" text_body_template: | @@ -1448,8 +1391,6 @@ nl: unsubscribe: title: "Uitschrijven" description: "Niet geïnteresseerd in deze e-mails? Geen probleem! Klik hieronder om direct uitgeschreven te worden:" - 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}'" @@ -1457,49 +1398,14 @@ nl: subject_template: "[%{site_name}] %{username} nodigt je uit voor een topic '%{topic_title}'" user_replied: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_quoted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted_pm: subject_template: "[%{site_name}] [PM] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{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" diff --git a/config/locales/server.pl_PL.yml b/config/locales/server.pl_PL.yml index 538cd3d75..b4d014a44 100644 --- a/config/locales/server.pl_PL.yml +++ b/config/locales/server.pl_PL.yml @@ -24,8 +24,6 @@ pl_PL: loading: "Ładowanie" powered_by_html: 'Zasilane przez Discourse, najlepiej oglądać z włączonym JavaScriptem' log_in: "Logowanie" - via: "%{username} z %{site_name}" - is_reserved: "jest zarezerwowana" purge_reason: "Automatycznie usunięto jako porzucone, nieaktywne konto" disable_remote_images_download_reason: "Pobieranie zewnętrznych grafik zostało wyłączone z uwagi na niską ilość wolnego miejsca na dysku." anonymous: "Anonim" @@ -435,15 +433,11 @@ pl_PL: description: 'Ten wpis zawiera treści które umiarkowana osoba może uznać za wulgarne, obraźliwe lub naruszające wytyczne społeczności.' long_form: 'oflagowano jako niewłaściwe' notify_user: - description: 'Ten wpis zawiera coś o czym chciałbym porozmawiać z tą osobą bezpośrednio i prywatnie. Bez oflagowania.' long_form: 'użytkownik został powiadomiony' email_title: 'Twój wpis w "%{title}"' email_body: "%{link}\n\n%{message}" notify_moderators: title: "Coś innego" - description: 'Ten post wymaga uwagi moderatora z powodu nie uwzględnionego na liście powyżej.' - long_form: 'oznaczył to dla uwagi moderatora' - email_title: 'Wpis w "%{title}" wymaga uwagi moderatorów' email_body: "%{link}\n\n%{message}" bookmark: title: 'Zakładka' @@ -468,7 +462,6 @@ pl_PL: long_form: 'oflagowano jako niewłaściwe' notify_moderators: title: "Coś innego" - description: 'Ten temat wymaga interwencji moderatora z uwagi na niezgodność z wytycznymi społeczności, warunkami użytkowania lub z innego, niewymienionego tu powodu.' long_form: 'oznaczył to dla uwagi moderatora' email_title: 'Temat "%{title}" wymaga uwagi moderatora' email_body: "%{link}\n\n%{message}" @@ -658,37 +651,6 @@ pl_PL: consumer_email_warning: "Twój serwis jest skonfigurowany by używać Gmaila (lub innego konsumenckiego serwisu poczty) do wysyłania emaili. Gmail ogranicza jak dużo wiadomości możesz wysłać. Rozważ wykorzystanie dostawcy serwisu pocztowego jak mandrill.com by zagwarantować dostarczanie poczty." site_contact_username_warning: "Wprowadź nazwę użytkownika zespołu, aby wysłać ważny szablon automatycznej wiadomości. Zaktualizuj site_contact_username w Ustawieniach Strony " notification_email_warning: "Emaile z powiadomieniami nie zostały wysłane z prawidłowego adresu email na twojej domenie; dostawca usługi email może być niekonsekwentny i nierzetelny. Ustaw prawidłowy email w notification_email w Ustawieniach Strony." - content_types: - education_new_reply: - title: "Edukacja nowych użytkowników: Pierwsze odpowiedzi" - description: "Okienko z wskazówkami automatycznie wyświetlane nad panelem pisania gdy nowi użytkownicy zaczynają pisać swoje pierwsze 2 odpowiedzi." - education_new_topic: - title: "Edukacja nowych użytkowników: Pierwsze tematy" - description: "Okienko z wskazówkami automatycznie wyświetlane nad panelem pisania gdy nowi użytkownicy rozpoczynają swoje pierwsze 2 tematy." - usage_tips: - title: "Wiadomość powitalna" - description: "Przewodnik i inne informacje dla nowych użytkowników." - welcome_user: - title: "Przywitanie: Nowy użytkownik" - description: "Wiadomość wysyłana automatycznie do wszystkich nowych użytkowników zaraz po ich rejestracji." - welcome_invite: - title: "Przywitanie: Zaproszony użytkownik" - description: "Wiadomość wysyłana automatycznie do nowych użytkowników po zaakceptowaniu zaproszenia od innego użytkownika." - login_required_welcome_message: - title: "Wymagane logowanie: wiadomość powitalna" - description: "Wiadomość powitalna wyświetlana niezalogowanym użytkownikom, gdy ustawienie 'login required' jest włączone." - login_required: - title: "Login Required: Homepage" - description: "The text displayed for unauthorized users when login is required on the site." - head: - title: "Nagłówki HTML" - description: "Kod HTML jaki zostanie umieszczony między tagami ." - top: - title: "Nagłówek każdej strony" - description: "Kod HTML jaki zostanie dołączony na górze każdej strony (po nagłówku, przed nawigacją lub tytułem tematu)." - bottom: - title: "Stopka każdej strony" - description: "Kod HTML, który zostanie załączony przed tagiem ." site_settings: censored_words: "Wskazane słowa będą automatycznie zamieniane na ■■■■" delete_old_hidden_posts: "Automatycznie kasuj wpisy ukryte dłużej niż 30 dni." @@ -1177,54 +1139,17 @@ pl_PL: unsubscribe: title: "Wypisz" description: "Nie chcesz otrzymywać tych emaili? Nie ma problemu! Kliknij poniżej by wypisać się natychmiast:" - reply_by_email: "Aby odpowiedzieć odpisz na ten email lub otwórz %{base_url}%{url} w swojej przeglądarce." - visit_link_to_respond: "To respond, visit %{base_url}%{url} in your browser." posted_by: "Dodany przez %{username} w dniu %{post_date}" user_replied: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_quoted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted_pm: subject_template: "[%{site_name}] [PW] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} digest: subject_template: "[%{site_name}] Podsumowanie" new_activity: "Nowa aktywność w twoich tematach i wpisach:" diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml index 1abf5a5d0..a1d614894 100644 --- a/config/locales/server.pt.yml +++ b/config/locales/server.pt.yml @@ -90,6 +90,8 @@ pt: not_found: "O URL ou recurso pedido não foi encontrado." invalid_access: "Não tem permissões para visualizar o recurso pedido." read_only_mode_enabled: "Este sítio encontra-se no modo só de leitura. As interações estão desativadas." + reading_time: "Tempo de leitura" + likes: "Gostos" too_many_replies: one: "Pedimos desculpa mas novos utilizadores estão temporariamente limitados a 1 resposta no mesmo tópico." other: "Pedimos desculpa mas novos utilizadores estão temporariamente limitados a %{count} respostas no mesmo tópico." @@ -843,6 +845,7 @@ pt: active_user_rate_limit_secs: "Qual a frequência de atualização do campo 'última vez visto em', em segundos." verbose_localization: "Mostrar extensas dicas de localização na IU" previous_visit_timeout_hours: "Quanto tempo dura uma visita antes de a considerarmos como 'visita anterior', em horas." + allow_staged_accounts: "[BETA] Criar automaticamente contas encenadas para emails recebidos." rate_limit_create_topic: "Após a criação de um tópico, os utilizadores devem esperar (n) segundos antes de criarem um novo tópico." rate_limit_create_post: "Após a publicação, os utilizadores devem esperar (n) segundos antes de criarem outra mensagem." rate_limit_new_user_create_topic: "Após a criação de um tópico, os novos utilizadores devem esperar (n) segundos antes de criarem um novo tópico." @@ -885,6 +888,7 @@ pt: tl2_requires_likes_received: "Quantos gostos um utilizador deve receber antes de ser promovido para o Nível de Confiança 2." tl2_requires_likes_given: "Quantos gostos um utilizador deve atribuir antes de ser promovido para o Nível de Confiança 2." tl2_requires_topic_reply_count: "Quantos tópicos um utilizador deve responder antes de ser promovido para o Nível de Confiança 2." + tl3_time_period: "Período de tempo de requisitos do Nível de Confiança 3" tl3_requires_days_visited: "Número mínimo de dias que o utilizador necessita de ter visitado o sítio nos últimos 100 dias para se qualificar à promoção para o Nível de Confiança 3. (0 a 100)" tl3_requires_topics_replied_to: "Número mínimo de tópicos que o utilizador necessita de ter respondido nos últimos 100 dias para se qualificar à promoção para o Nível de Confiança 3. (0 ou mais)" tl3_requires_topics_viewed: "Percentagem de tópicos criados nos últimos 100 dias que o utilizador precisa de ter visto para se qualificar à promoção para o Nível de Confiança 3. (0 a 100)" @@ -961,6 +965,7 @@ pt: disable_emails: "Impedir que Discourse envie qualquer tipo de emails" strip_images_from_short_emails: "Remover imagens de emails cujo tamanho seja inferior a 2800 Bytes" short_email_length: "Comprimento de email curto, em Bytes" + display_name_on_email_from: "Exibir nomes completos em campos do formulário de email" pop3_polling_enabled: "Solicitação através de POP3 para respostas de emails" pop3_polling_ssl: "Utilize SSL ao ligar a um servidor POP3. (Recomendado)" pop3_polling_period_mins: "Período em minutos entre a verificação da conta POP3 para o email. NOTA: requer reinicialização." @@ -999,9 +1004,10 @@ pt: anonymous_account_duration_minutes: "Para proteger o anonimato crie uma nova conta anónima a cada N minutos para cada utilizador. Exemplo: se configurado para 600, assim que passarem 600 minutos desde a última mensagem E o utilizador altere para anónimo, uma nova conta anónima é criada." hide_user_profiles_from_public: "Desativar cartões de utilizador, perfis de utilizador e diretoria de utilizadores para utilizadores anónimos." allow_profile_backgrounds: "Permitir que os utilizadores carreguem fundos de perfil." - sequential_replies_threshold: "Número de mensagens que um utilizador tem que fazer em linha num tópico antes de ser relembrado acerca de demasiadas respostas sequenciais." + sequential_replies_threshold: "Número de mensagens que um utilizador tem que fazer em linha num tópico antes de ser lembrado acerca de demasiadas respostas sequenciais." enable_mobile_theme: "Os dispositivos móveis usam um tema mobile-friendly, com a possibilidade de mudar para o sítio completo. Desative isto se quer usar um estilo personalizado que é totalmente responsivo." dominating_topic_minimum_percent: "Que percentagem de mensagens um utilizador tem que fazer num tópico antes de ser relembrado sobre dominar demasiado um tópico." + disable_avatar_education_message: "Desativar mensagem educativa para alteração de avatar." daily_performance_report: "Analise os logs diários NGINX e publique um tópico visível apenas para o pessoal com detalhes" suppress_uncategorized_badge: "Não mostrar distintivos para tópicos sem categoria nas listagens de tópicos." permalink_normalizations: "Utilize a seguinte expressão regular antes de combinar hiperligações permanentes, por exemplo: /(\\/topic.*)\\?.*/\\1 irá retirar sequências de consulta de rotas de tópicos. O formato é regex+sequência utilize \\1 etc. para aceder a capturas." @@ -1070,6 +1076,10 @@ pt: invalid_string_min: "Deve ser pelo menos %{min} caracteres." invalid_string_max: "Não deve ser mais que %{max} caracteres." invalid_reply_by_email_address: "O valor deve conter '%{reply_key}' e ser diferente do email de notificação." + pop3_polling_host_is_empty: "Deve configurar um 'pop3 polling host' antes de ativar o polling POP3." + pop3_polling_username_is_empty: "Deve configurar um 'nome de utilizador de polling pop3' antes de ativar o polling POP3." + pop3_polling_password_is_empty: "Deve configurar uma 'palavra-passe de polling pop3' antes de ativar o polling POP3." + pop3_polling_authentication_failed: "Autenticação POP3 falhada. Por favor verifique as suas credenciais pop3." notification_types: group_mentioned: "%{group_name} foi mencionado em %{link}" mentioned: "%{display_username} mencionou-o em %{link}" @@ -1465,21 +1475,14 @@ pt: text_body_template: | Esta é uma mensagem automática. - O novo utilizador [%{username}](%{base_url}%{user_url}) foi automaticamente bloqueado devido a múltiplos utilizadores terem sinalizado a(s) mensagen(s) de %{username}. + O novo utilizador [%{username}](%{user_url}) foi automaticamente bloqueado porque múltiplos utilizadores marcaram mensagem(ns) de %{username}. - Por favor [reveja as sinalizações](%{base_url}/admin/flags). Se %{username} foi incorretamente bloqueado de publicar, carregue no botão de desbloqueio em [a página de administração para este utilizador](%{base_url}%{user_url}). + Por favor [reveja as sinalizações](%{base_url}/admin/flags). Se %{username} foi incorretamente bloqueado de publicar, carregue no botão de desbloqueio em [a página de administração para este utilizador](%{user_url}) Este limite pode ser alterado através de `block_new_user` nas configurações do sítio. spam_post_blocked: subject_template: "Novas mensagens do novo utilizador %{username} foram bloqueadas devido a hiperligações repetidas" - text_body_template: | - Esta é uma mensagem automática. - - O novo utilizador [%{username}](%{base_url}%{user_url}) tentou criar múltiplas mensagens com hiperligações para %{domains}, mas estas foram bloqueadas para evitar spam. O utilizador ainda pode criar novas mensagens sem hiperligação a %{domains}. - - Por favor [reveja o utilizador](%{base_url}%{user_url}). - - Isto pode ser modificado através de `newuser_spam_host_threshold` e `white_listed_spam_host_domains`nas configurações do sítio. + text_body_template: "Esta é uma mensagem automática.\n\nO novo utilizador [%{username}](%{user_url}) tentou criar múltiplas mensagens com hiperligações para %{domains}, mas estas foram bloqueadas para evitar spam. O utilizador ainda pode criar novas mensagens sem hiperligação a %{domains}. \n\nPor favor [reveja o utilizador](%{user_url}).\n\nIsto pode ser modificado através de `newuser_spam_host_threshold` e `white_listed_spam_host_domains` nas configurações do sítio.\n" unblocked: subject_template: "Conta desbloqueada" text_body_template: | @@ -1500,9 +1503,7 @@ pt: subject_template: "Descarregamento de imagens remotas desativado" text_body_template: "A configuração `download_remote_images_to_local` foi desativada porque o limite de espaço em disco em `download_remote_images_threshold` foi alcançado." unsubscribe_link: | - Para parar de receber notificações para este tópico em particular, [clique aqui](%{unsubscribe_url}). - - Para cancelar a subscrição destes emails, altere as suas [preferências de utilizador](%{user_preferences_url}). + Para parar de receber notificações para este tópico em particular, [clique aqui](%{unsubscribe_url}). Para cancelar a subscrição destes emails, altere as suas [preferências de utilizador](%{user_preferences_url}). subject_re: "Re:" subject_pm: "[MP]" user_notifications: @@ -1510,8 +1511,8 @@ pt: unsubscribe: title: "Cancelar a Subscrição" description: "Não está interessado em receber estes emails? Não há problema! Clique em baixo para cancelar a subscrição instantaneamente:" - reply_by_email: "Para reagir, responda a este email ou [visite o tópico](%{base_url}%{url}) no seu navegador de internet." - visit_link_to_respond: "Para reagir, [visite o tópico](%{base_url}%{url}) no seu navegador de internet." + reply_by_email: "Para reagir, responda a este email ou [visite o tópico](%{base_url}%{url})." + visit_link_to_respond: "Para reagir, [visite o tópico](%{base_url}%{url})." posted_by: "Publicado por %{username} em %{post_date}" user_invited_to_private_message_pm: subject_template: "[%{site_name}] %{username} convidou-o para uma mensagem '%{topic_title}'" @@ -1528,6 +1529,22 @@ pt: > %{site_title} -- %{site_description} Por favor visite esta hiperligação para visualizar a mensagem: %{base_url}%{url} + user_invited_to_private_message_pm_staged: + subject_template: "[%{site_name}] %{username} convidou-o para uma mensagem '%{topic_title}'" + text_body_template: |2+ + + %{username} convidou-o para uma mensagem + + > **%{topic_title}** + > + > %{topic_excerpt} + + em + + > %{site_title} -- %{site_description} + + Por favor visite esta hiperligação para ver a mensagem: %{base_url}%{url} + user_invited_to_topic: subject_template: "[%{site_name}] %{username} convidou-o para um tópico '%{topic_title}'" text_body_template: |2 @@ -1546,6 +1563,8 @@ pt: user_replied: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1555,6 +1574,8 @@ pt: user_quoted: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1564,6 +1585,8 @@ pt: user_mentioned: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1573,6 +1596,8 @@ pt: user_group_mentioned: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1582,6 +1607,8 @@ pt: user_posted: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1591,10 +1618,20 @@ pt: user_posted_pm: subject_template: "[%{site_name}] [MP] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} + --- + %{respond_instructions} + user_posted_pm_staged: + subject_template: "%{optional_re}%{topic_title}" + text_body_template: |2 + + %{message} + --- %{respond_instructions} digest: diff --git a/config/locales/server.pt_BR.yml b/config/locales/server.pt_BR.yml index e17d51fef..bc3323572 100644 --- a/config/locales/server.pt_BR.yml +++ b/config/locales/server.pt_BR.yml @@ -946,7 +946,6 @@ pt_BR: anonymous_account_duration_minutes: "Para proteger anonimidade cria uma conta anônima a cada N minutos para cada usuário. Exemplo: se configurado para 600, assim que passar 600 minutos da última mensagem E o usuário trocar para anônimo, uma nova conta anônima será criada." hide_user_profiles_from_public: "Desativar cartões de usuários, perfis de usuário e diretório de usuário para usuários anônimos." allow_profile_backgrounds: "Permitir usuários de fazerem upload de backgrounds para o perfil" - sequential_replies_threshold: "Número de posts que um usuário precisa fazer em seguida num tópico antes de ser relembrado sobre muitas respostas sequenciais." enable_mobile_theme: "Os dispositivos móveis usam um tema mobile-friendly, com a possibilidade de mudar para o site completo. Desative isso se você quiser usar um estilo personalizado que é totalmente responsivo." dominating_topic_minimum_percent: "Qual o percentual de postagens que um usuário precisa fazer em um tópico antes de ser relembrado sobre ser excessivamente dominante em um tópico." daily_performance_report: "Analiza logs do NGINX diariamente e cria um tópico na categoria Somente Equipe com os detalhes." @@ -1412,24 +1411,8 @@ pt_BR: text_body_template: "Olá, \n\nEsta é uma mensagem automática do %{site_name} para informá-lo de que a sua conta foi bloqueada por um membro da administração. \n\nPara informações adicionais, por favor consulte as nossas [diretrizes da comunidade](%{base_url}/guidelines).\n" user_automatically_blocked: subject_template: "Novo usuário %{username} foi bloqueado devido a denúncias da comunidade" - text_body_template: | - Essa é uma mensagem automática. - - O novo usuário [%{username}](%{base_url}%{user_url}) foi automaticamente bloqueado por ter recebido múltiplas sinalizações %{username}'s postagem(ns). - - Por favor [reveja as sinalzações](%{base_url}/admin/flags). Se %{username} foi bloqueado injustamente, clique no botão de desbloquear na [página de administração desse usuário](%{base_url}%{user_url}). - - Este limite pode ser alterado através da configuração `block_new_user` do site. spam_post_blocked: subject_template: "Novo usuário %{username} teve postagem bloqueada por repetidos links" - text_body_template: | - Essa é uma mensagem automática. - - O novo usuário [%{username}](%{base_url}%{user_url}) tentou criar múltiplas postagens com link para %{domains}, mas essas postagens foram bloqueadas para evitar spam. O usuário ainda está apto a criar novas postagens que não tenham links para %{domains}. - - Por favor, [reveja esse usuário](%{base_url}%{user_url}). - - Isso pode ser modificado na configuração `newuser_spam_host_threshold` e `white_listed_spam_host_domains` do site. unblocked: subject_template: "Conta desbloqueada" text_body_template: | @@ -1453,7 +1436,6 @@ pt_BR: unsubscribe: title: "Desinscrever" description: "Não está interessado em receber estes emails? Não tem problema! Clique em baixo para se desinscrever instantaneamente:" - visit_link_to_respond: "Para responder, [visite o tópico](%{base_url}%{url}) em seu navegador." posted_by: "Postado por %{username} em %{post_date}" user_invited_to_private_message_pm: subject_template: "[%{site_name}] %{username} convidou você para uma mensagem '%{topic_title}'" @@ -1487,55 +1469,16 @@ pt_BR: Por favor visitar o link para visualizar a mensagem: %{base_url}%{url} user_replied: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{username} respondeu à sua postagem no tópico '%{topic_title}' de %{site_name}: - - --- - %{message} - - --- - %{respond_instructions} user_quoted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{username} citou você no tópico '%{topic_title}' de %{site_name}: - - --- - %{message} - - --- - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{username} mencionou você no tópico '%{topic_title}' de %{site_name}: - - --- - %{message} - - --- - %{respond_instructions} user_group_mentioned: subject_template: "[%{site_name}] %{topic_title}" user_posted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{username} postou no tópico '%{topic_title}' de %{site_name}: - - --- - %{message} - - --- - %{respond_instructions} user_posted_pm: subject_template: "[%{site_name}] [PM] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} digest: why: "Um breve resumo de %{site_link} desde que viu pela última vez em %{last_seen_at}." subject_template: "Resumo [%{site_name}]" diff --git a/config/locales/server.ro.yml b/config/locales/server.ro.yml index 55987cec2..3e4c62cbe 100644 --- a/config/locales/server.ro.yml +++ b/config/locales/server.ro.yml @@ -16,8 +16,6 @@ ro: loading: "Încarcă" powered_by_html: 'Prin intermediul Discourse, Vizualizare optimă cu JavaScript activat' log_in: "Autentificare" - via: "%{username} prin %{site_name}" - is_reserved: "este rezervat" purge_reason: "Cont automat şters ca abandonat, neactivat" disable_remote_images_download_reason: "Descărcarea de imagini la distanţă a fost dezactivată deoarece nu mai era spaţiu pe disc suficient." anonymous: "Anonim" @@ -358,14 +356,10 @@ ro: description: 'Această postare are conținut pe care o persoană normală l-ar numii ofesator, abuziv, sau o violare a regulilor comune.' long_form: 'marcat ca necorespunzător' notify_user: - description: 'Această postare conține ceva despre care aș vrea să discut direct și privat cu acestă persoană.' email_title: 'Despre postarea dvs din "%{title}"' email_body: "%{link}\n\n%{message}" notify_moderators: title: "Notifică moderatorii" - description: 'Această postare necesită atenția generală a moderatorilor bazat pe guidelines, TOS, sau pentru un alt motiv ne-listat mai sus.' - long_form: 'moderatori notificați' - email_title: 'O postare din "%{title}" necesită atenția moderatorilor' email_body: "%{link}\n\n%{message}" bookmark: title: 'Semn de carte' @@ -390,7 +384,6 @@ ro: long_form: 'marcat ca necorespunzător' notify_moderators: title: "Notifică moderatori" - description: 'Această discuție necesită atenția generală a moderatorilor bazat pe guidelines, TOS, sau pentru un alt motiv ne-listat mai sus.' long_form: 'moderatori notificați' email_title: 'discuția "%{title}" necesită atenția moderatorilor' email_body: "%{link}\n\n%{message}" @@ -537,35 +530,6 @@ ro: site_description_missing: "Opțiunea descriere_site nu este completată. Scrieți o scurtă descriere a forum-ului în setările site-ului." consumer_email_warning: "Site-ul dvs e configurat să folosească Gmail(sau alt serviciu de email) pentru a trimite emailuri. Gmail limitează numărul de email-uri trimise. Folosiți un provider de mailuri ca mandrill.com pentru a asigura trimiterea mail-urilor." notification_email_warning: "Opțiunea notificare_email este goală. Vă rugăm modificați-o în Setările site-ului." - content_types: - education_new_reply: - title: "Insturirea noului utilizator: Primele răspunsuri" - description: "Apar ghidările automat deasupra spațiului de compus când noul utilizator începe să scrie primele două răspunsuri." - education_new_topic: - title: "Insturirea noului utilizator: Primele Discuții" - description: "Apar ghidările automat deasupra spațiului de compus când noul utilizator începe să scrie primele două discuții." - usage_tips: - title: "Sfaturile noului utilizator" - description: "Sfaturile comune, informațiile esențiale forumului și intruirile cheie pentru utilizatorii noi." - welcome_user: - title: "Bine ai venit: Utilizator nou" - welcome_invite: - title: "Bine ai venit: Utilizator invitat" - login_required_welcome_message: - title: "Autentificare necesară: Mesaj de întâmpinare" - description: "Mesaj de întâmpinare afișat tuturor utilizatorilor delogați când setarea 'Autentificare necesară' este activată." - login_required: - title: "Autentificare necesară: Pagina principală" - description: "Textul afișat pentru utilizatorii neautorizați când autentificarea e necesară în site." - head: - title: "Cap HTML" - description: "HTML inserat între tagurile ." - top: - title: "Capătul paginilor" - description: "HTML ce va fi adăugat în capătul fiecărei pagini (după header, îainte de navigarea către titlul discuției)." - bottom: - title: "Sfârșitul paginilor" - description: "HTML ce va fi adăugat la sfârșitul fiecărei pagini." site_settings: default_locale: "Limba oficială a acestei instanțe de discurs (ISO 639-1 Code)" allow_user_locale: "Permite utilizatorilor să aleagă preferința de limbă pentru interfață" @@ -735,7 +699,6 @@ ro: digest_min_excerpt_length: "Numărul minimum de extrase din postări din email-ul rezumat, în caractere." max_daily_gravatar_crawls: "Numărul maxim de verificări făcute de Discourse pentru existența unui gravatar preferențial într-o zi" allow_profile_backgrounds: "Permite utilizatorilor să încarce fundaluri de profil." - sequential_replies_threshold: "Numărul de postări la rând într-o discuție până să-i fie amintit utilizatorului că sunt prea multe răspunsuri secvențiale. " enable_mobile_theme: "Dispozitivele mobile folosesc o temă cu abilitatea de a schimba la întregul site. Dezactivați pentru a folosii un foiae de stil preferențiala ce este total compatibilă." dominating_topic_minimum_percent: "Ce procent din postări într-o discuție trebuie să facă un utilizator înainte de ai fi amintit că domină discuția." suppress_uncategorized_badge: "Nu arăta insigna pentru discuțiile fără categorie în lista de discuții." @@ -758,7 +721,6 @@ ro: notify_about_flags_after: "Dacă sunt marcaje ce nu au fost aranjate după atâtea ore, Trimite un email la email-ul de contact . Setați 0 pentru a dezactiva." enable_cdn_js_debugging: "Permite /rapoarte să afișeze erori adăugând permisiune crossorigin tuturor js-urilor incluse." show_create_topics_notice: "Dacă site-ul are mai puțin de 5 discuții publice, afișează o notificare ce cere adminilor să creeze discuții." - vacuum_db_days: "Pornește ANALIZĂ COMPLETĂ VACUUM pentru a re-obține spațiul DB după migrări (setează 0 pt dezactivare)" enable_emoji: "Activează emoji" errors: invalid_email: "Adresa de email invalidă." @@ -956,24 +918,8 @@ ro: Pentru ajutor adițional, faceți referie la [regulile de comunitate](%{base_url}).. user_automatically_blocked: subject_template: "Noul utilizator %{username} a fost blocat datorită marcajelor date de comunitate" - text_body_template: | - Acesta este un mesaj automat. - - Noul utilizator [%{username}](%{base_url}%{user_url}) a fost automat blocat fiindcă mai mulți utilizatori au marcat postările lui %{username}. - - Vă rugăm [revizuiți marcajele](%{base_url}/admin/flags). Dacă %{username} a fost incorect blocat în a posta, faceți click pe butonul de deblocare pe [pagina de admin a acestui utilizator](%{base_url}%{user_url}). - - Acest prag poate fi schimbat prin setarea `block_new_user` a site-ului . spam_post_blocked: subject_template: "Utilizatorul nou %{username} are postările blocate datorită adreselor repetate" - text_body_template: | - Acesta este un mesaj automat. - - Noul utilizator [%{username}](%{base_url}%{user_url}) a încercat să creeze postări multiple cu adrese către %{domains}, dar aceste postări au fost blocate pentru a evita spamul. Acest utilizator este capabil încă să creeze postări ce nu conțin adrese către %{domains}. - - Vă rugăm [revizionati utilizatorul](%{base_url}%{user_url}). - - Aceasta poate fi modificat prin setările `newuser_spam_host_threshold` și `white_listed_spam_host_domains` din site. unblocked: subject_template: "Cont deblocat" text_body_template: | @@ -997,54 +943,17 @@ ro: unsubscribe: title: "Dezabonare" description: "Nu sunteți interesat în a primii aceste email-uri? Nicio problemă! Faceți clic dedesubt pentru a vă dezabona imediat:" - reply_by_email: "Pentru a răspunde, răspundeți la acest email sau vizitați %{base_url}%{url} în browser-ul dvs." - visit_link_to_respond: "Pentru a răspunde, vizitați %{base_url}%{url} în browserul dvs." posted_by: "Postat de %{username} pe data %{post_date}" user_replied: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_quoted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted_pm: subject_template: "[%{site_name}] [MP] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} digest: why: "Un sumar scurt al %{site_link} de ultima oară când ați fost prezent %{last_seen_at}." subject_template: "Rezumatul [%{site_name}]" diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml index 928d435f3..87f3961ed 100644 --- a/config/locales/server.ru.yml +++ b/config/locales/server.ru.yml @@ -273,6 +273,12 @@ ru: title: "новый пользователь" basic: title: "базовый пользователь" + member: + title: "участник" + regular: + title: "активный" + leader: + title: "лидер" change_failed_explanation: "Вы пытаетесь понизить пользователя %{user_name} до уровня доверия '%{new_trust_level}'. Однако, его уровень доверия уже '%{current_trust_level}'. %{user_name} останется с уровнем доверия '%{current_trust_level}'. Если вы все же хотите понизить пользователя, заблокируйте вначале уровень доверия." rate_limiter: too_many_requests: "Вы повторяете действие слишком часто. Пожалуйста, подождите %{time_left} до следующей попытки." @@ -447,11 +453,14 @@ ru: description: 'Это сообщение может быть оскорбительным или нарушает правила поведения.' long_form: 'отметить как неуместное' notify_user: + title: 'Отправить @{{username}} сообщение' + description: 'Я хочу пообщаться приватно с этим человеком о его посте.' long_form: 'оповещаемый пользователь' email_title: 'Ваше сообщение в теме "%{title}"' email_body: "%{link}\n\n%{message}\n" notify_moderators: title: "Другое" + description: 'Этот пост требует внимания администрации по другой причине.' email_body: "%{link}\n\n%{message}\n" bookmark: title: 'Добавить в закладки' @@ -725,7 +734,7 @@ ru: max_replies_in_first_day: "Максимальное количество ответов, которое пользователь может сделать в первый день на сайте" tl2_additional_likes_per_day_multiplier: "Увеличить лимит лайков в день для tl2 (member) до" tl3_additional_likes_per_day_multiplier: "Увеличить лимит лайков в день для tl3 (member) до" - tl4_additional_likes_per_day_multiplier: "Увеличить лимит лайков в день для tl4 (member) до" + tl4_additional_likes_per_day_multiplier: "Увеличить лимит лайков в день для tl4 (leader) до" num_flags_to_block_new_user: "Если сообщения нового пользователя получат данное количество жалоб на спам от num_users_to_block_new_user различных пользователей, скрыть все сообщения этого пользователя и отказать ему в публикации новых сообщений. 0 отключает данную функцию." num_users_to_block_new_user: "Если сообщения нового пользователя получат num_flags_to_block_new_user жалоб на спам от такого количества различных пользователей, скрыть все сообщения этого пользователя и отказать ему в публикации новых сообщений. 0 отключает данную функцию." notify_mods_when_user_blocked: "Отправить сообщение всем модераторам, если пользователь заблокирован автоматически." @@ -843,7 +852,6 @@ ru: tl2_requires_likes_received: "Сколько симпатий пользователь должен получить для продвижения до уровня доверия 2." tl2_requires_likes_given: "Сколько симпатий пользователь должен выразить для продвижения до уровня доверия 2." tl2_requires_topic_reply_count: "В скольких темах пользователь должен ответить для продвижения до уровня доверия 2." - tl3_requires_days_visited: "Минимальное количество дней посещения сайта пользователем за последние 100 дней, необходимое для продвижения до уровня доверия 3. (от 0 до 100)" tl3_requires_topics_replied_to: "Минимальное количество тем, в которых пользователь должен ответить за последние 100 дней, для возможности продвижения до уровня доверия 3. (0 и больше)" tl3_requires_topics_viewed: "Какой процент тем созданных за последние 100 дней, должен просмотреть пользовательзователь для повышения уровня доверия до 3. (0 to 100)" tl3_requires_posts_read: "Какой процент сообщений написанных за последние 100 дней, должен просмотреть пользовательзователь для повышения уровня доверия до 3. (0 to 100)" @@ -945,7 +953,6 @@ ru: anonymous_posting_min_trust_level: "Минимальный уровень доверия для возможности создавать темы от имени анонимного пользователя." anonymous_account_duration_minutes: "Защита от создания анонимных аккаунтов каждые N минут для каждого пользователя. Пример: Если установлено 600, означает что должно пройти 600 минут с момента последнего поста юзера И юзер вышел из системы, для того чтобы он смог создать новоый аккаунт." allow_profile_backgrounds: "Разрешить пользователям загружать фоновые картинки для своих страниц профиля." - sequential_replies_threshold: "Количество сообщений, отправленных пользователем подряд в одной теме, прежде чем ему будет показано предупреждение о слишком частых ответах." enable_mobile_theme: "Мобильные устройства используют адаптированную тему с возможностью переключения в обычный вид. Отключите данную настройку если вы хотите использовать собственный стиль для мобильных устройств." dominating_topic_minimum_percent: "Количество сообщений, отправленных пользователем подряд в одной теме, прежде чем ему будет показано предупреждение о слишком частых ответах." daily_performance_report: "Ежедневно анализировать логи сервера NGINX и отправлять сообщение с результатами анализа в тему, видимую только персоналу." @@ -1471,24 +1478,8 @@ ru: Получить дополнительную информацию вы можете в нашей [инструкции пользователя](%{base_url}/guidelines). user_automatically_blocked: subject_template: "Пользователь %{username} заблокирован участниками сообщества" - text_body_template: | - This is an automated message. - - The new user [%{username}](%{base_url}%{user_url}) was automatically blocked because multiple users flagged %{username}'s post(s). - - Please [review the flags](%{base_url}/admin/flags). If %{username} was incorrectly blocked from posting, click the unblock button on [the admin page for this user](%{base_url}%{user_url}). - - This threshold can be changed via the `block_new_user` site settings. spam_post_blocked: subject_template: "Сообщения нового пользователя %{username} заблокированы из-за повторяющихся ссылок" - text_body_template: | - Это автоматическое уведомление. - - Новый пользователь [%{username}](%{base_url}%{user_url}) пытается создать множество постов с ссылками на %{domains}, но система заблокировала эти посты как спам. Пользователь все еще может создавать посты не содержащие ссылки на %{domains}. - - Пожайлуйста [проверьте пользователя](%{base_url}%{user_url}). - - Решить данный вопрос можно также добавив домен в настройки `newuser_spam_host_threshold` и `white_listed_spam_host_domains` . unblocked: subject_template: "Учетная запись разблокирована" text_body_template: | @@ -1548,49 +1539,14 @@ ru: Просмотреть сообщение вы можете по ссылке: %{base_url}%{url} user_replied: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_quoted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted_pm: subject_template: "[%{site_name}] [Личное сообщение] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} digest: why: "Сводка обсуждений на сайте %{site_link} с момента вашего последнего визита %{last_seen_at}" subject_template: "Cводка новостей сайта [%{site_name}]" diff --git a/config/locales/server.sk.yml b/config/locales/server.sk.yml new file mode 100644 index 000000000..4e982d938 --- /dev/null +++ b/config/locales/server.sk.yml @@ -0,0 +1,1128 @@ +# encoding: utf-8 +# +# Never edit this file. It will be overwritten when translations are pulled from Transifex. +# +# To work with us on translations, join this project: +# https://www.transifex.com/projects/p/discourse-org/ + +sk: + dates: + short_date_no_year: "MMM D" + short_date: "YYYY MMM D" + long_date: "YYYY MMM D h:mma" + datetime_formats: &datetime_formats + formats: + short: "%Y-%m-%d" + short_no_year: "%B %-d" + date_only: "%Y, %B %-d" + date: + month_names: [null, Január, Február, Marec, Apríl, Máj, Jún, Júl, August, September, Október, November, December] + <<: *datetime_formats + title: "DIscourse" + topics: "Témy" + posts: "príspevky" + loading: "Načítava sa" + powered_by_html: 'Systém beží na Discourse, najlepšie funguje so zapnutým JavaScriptom' + log_in: "Prihlásenie" + purge_reason: "Automaticky vymazaný ako opustený, neaktivovaný účet" + disable_remote_images_download_reason: "Sťahovanie vzdialených obrázkov je vypnuté kvôli nedostatku diskového priestoru." + anonymous: "Anonymný" + errors: &errors + format: '%{attribute} %{message}' + messages: + too_long_validation: "je obmedzený na %{max} znakov, zadali ste %{length}." + invalid_boolean: "Neplatná logická hodnota." + taken: "je už použité" + accepted: sa musí akceptovať + blank: nesmie byť prázdne + present: musí byť prázdne + confirmation: "sa nerovná %{attribute}" + empty: nesmie byť prázdne + equal_to: sa musí rovnať %{count} + even: musí byť párne + exclusion: je rezervované + greater_than: musí byť viac ako %{count} + greater_than_or_equal_to: musí byť väčšie alebo rovné %{count} + has_already_been_used: "je už použité" + inclusion: nie je v zozname + invalid: je nesprávne + is_invalid: "je nesprávne; skúste opísať lepšie" + less_than: musí byť menej ako %{count} + less_than_or_equal_to: musí byť menej alebo rovné %{count} + not_a_number: nie je číslo + not_an_integer: musí byť číslo + odd: musí byť nepárne + record_invalid: 'Validácia zlyhala s chybami: %{errors}' + restrict_dependent_destroy: + one: "Nedá sa zmazať záznam pretože existuje %{record} závislých záznamov" + many: "Záznam nemôže byť zmazaný z dôvodu zavislosti na zázname: %{record} " + too_long: + one: príliš dlhé (maximálne 1 znak) + few: príliiš dlhé (maximum je %{count} znaky) + other: príliiš dlhé (maximum je %{count} znakov) + too_short: + one: príliiš krátke (minimum je 1 znak) + few: príliiš krátke (minimum je %{count} znaky) + other: príliiš krátke (minimum je %{count} znakov) + wrong_length: + one: nesprávna dĺžka (musí byť 1 znak) + few: nesprávna dĺžka (musí byť %{count} znaky) + other: nesprávna dĺžka (musí byť %{count} znakov) + other_than: "musí byť iný než %{count}" + template: + body: 'Nastal problém s nasledujúcimi položkami:' + header: + one: Uloženie %{model} zlyhalo kôli chybe + few: Uloženie %{model} zlyhalo kôli %{count} chybám + other: 'Uloženie %{model} zlyhalo kôli %{count} chybám ' + embed: + load_from_remote: "Nastala chyba pri načítaní príspevku" + site_settings: + min_username_length_exists: "Nemôžete nastaviť minimálnu dĺžku používateľského mena viac ako najkratšie používateľské meno." + min_username_length_range: "Nemôžete nastaviť minimum viac ako maximum." + max_username_length_exists: "Nemôžete nastaviť maximálnu dĺžku používateľského mena kratšiu ako najdlhšie používateľské meno." + max_username_length_range: "Nemôžete nastaviť maximum menšie ako minimum." + default_categories_already_selected: "Nemôžete vybrať kategóriu použitú v inom zozname." + s3_upload_bucket_is_required: "Nemôžete nahrávať na S3 pokiaľ ste nezadali 's3_upload_bucket'." + bulk_invite: + file_should_be_csv: "Nahrávaný súbor by mal byť vo formáte csv alebo txt." + backup: + operation_already_running: "Prebieha spracovanie inej operácie. Momentálne sa nová úloha nedá spustiť. " + backup_file_should_be_tar_gz: "Záložný súbor musí byť archív .tar.gz" + not_enough_space_on_disk: "Na disku nie je dosť miesta na uloženie zálohy" + not_logged_in: "K tejto akcii musíte byť prihlásený." + not_found: "Požadovaná URL alebo zdroj sa nenašiel" + invalid_access: "Nemáte oprávnenie na zobrazenie požadovaných údajov!" + read_only_mode_enabled: "Táto stránka je v móde na čítanie. Zapisovanie je vypnuté" + too_many_replies: + one: "Lutujeme, noví užívatelia majú dočasne obmedzený počet príspevkov na jeden v rámci jednej témy." + few: "Lutujeme, noví užívatelia majú dočasne obmedzený počet príspevkov na %{count} v rámci jednej témy." + other: "Lutujeme, noví užívatelia majú dočasne obmedzený počet príspevkov na %{count} v rámci jednej témy." + embed: + start_discussion: "Začať diskusiu" + continue: "Pokračovať v diskusii" + more_replies: + one: "1 ďalšia odpoveď" + few: "%{count} ďalšie odpovede" + other: "%{count} ďalších odpovedí" + loading: "Nahrávanie Diskusie ..." + permalink: "Trvalý odkaz" + imported_from: "Toto je sprievodná diskusia k pôvodnej téme na %{link}" + in_reply_to: "▶ %{username}" + replies: + one: "1 odpoveď" + few: "%{count} odpovede" + other: "%{count} odpovedí" + no_mentions_allowed: "Ľutujeme, nesmiete menovať iných užívateľov" + too_many_mentions: + one: "Ľutujeme, v príspevku môžte menovat maximálne jedného užívateľa." + few: "Ľutujeme, v príspevku môžte menovat maximálne %{count} užívatelov." + other: "Ľutujeme, v príspevku môžte menovat maximálne %{count} užívatelov." + no_mentions_allowed_newuser: "Ľutujeme, noví užívatelia nesmú zmieňovať iných uživateľov" + too_many_mentions_newuser: + one: "Ľutujeme, noví užívatelia môžu menovat v príspevku maximálne jedného užívateľa." + few: "Ľutujeme, noví užívatelia môžu menovat v príspevku maximálne %{count} užívatelov." + other: "Ľutujeme, noví užívatelia môžu menovat v príspevku maximálne %{count} užívatelov." + no_images_allowed: "Ľutujeme, noví užívatelia nemôžu vkladať obrázky do príspevkov." + too_many_images: + one: "Ľutujeme, noví užívatelia môžu vložiť maximálne jeden obrázok do príspevku." + few: "Ľutujeme, noví užívatelia môžu vložiť maximálne %{count} obrázky do príspevku." + other: "Ľutujeme, noví užívatelia môžu vložiť maximálne %{count} obrázkov do príspevku." + no_attachments_allowed: "Ľutujeme, noví užívatelia nemôžu vkladať prílohy do príspevkov." + too_many_attachments: + one: "Ľutujeme, noví užívatelia môžu vložiť maximálne jednu prílohu do príspevku." + few: "Ľutujeme, noví užívatelia môžu vložiť maximálne %{count} prílohy do príspevku." + other: "Ľutujeme, noví užívatelia môžu vložiť maximálne %{count} príloh do príspevku." + no_links_allowed: "Ľutujeme, noví užívatelia nemôžu vkladať odkazy do príspevkov." + too_many_links: + one: "Ľutujeme, noví užívatelia môžu vložiť maximálne jeden odkaz do príspevku." + few: "Ľutujeme, noví užívatelia môžu vložiť maximálne %{count} odkazy do príspevku." + other: "Ľutujeme, noví užívatelia môžu vložiť maximálne %{count} odkazov do príspevku." + spamming_host: "Prepáčte, nemôžte publikovať odkazy na tento zdroj" + user_is_suspended: "Suspendovaní užívatelia nemôžu vkladať príspevky" + topic_not_found: "Niečo sa pokazilo. Téma mohla byť napríklad uzavretá, alebo zmazaná kým ste ju prezerali." + just_posted_that: "je to príliš podobné Vášmu predchádzajúcemu príspevku" + has_already_been_used: "je už použité" + invalid_characters: "obsahuje neplatné znaky" + is_invalid: "je nesprávne; skúste opísať lepšie" + next_page: "nasledujúca stránka →" + prev_page: "← predchádzajúca stránka" + page_num: "Stránka %{num}" + home_title: "Domov" + topics_in_category: " '%{category}' tém v tejto kategórii" + rss_posts_in_topic: "RSS čítačka na %{topic}'" + rss_topics_in_category: "RSS čítačka na tému v kategórii: '%{category}'" + author_wrote: "%{author} napísal:" + num_posts: "Príspevky:" + num_participants: "Prispievatelia:" + read_full_topic: "Čítať celý príspevok" + private_message_abbrev: "Správa" + rss_description: + latest: "Najnovšie témy" + hot: "Horúce témy" + posts: "Najnovšie príspevky" + too_late_to_edit: "Tento príspevok bol vytvorený príliš dávno. Už nemôže byť upravovaný či zmazaný" + excerpt_image: "Obrázok" + queue: + delete_reason: "Zmazané moderátorom" + groups: + errors: + can_not_modify_automatic: "Nemôžte upravovať automatickú skupinu" + member_already_exist: "'%{username}' už je členom tejto skupiny." + invalid_domain: "'%{domain}' nie je platnou doménou." + invalid_incoming_email: "'%{incoming_email}' je neplatná emailová addresa." + default_names: + everyone: "všetci" + admins: "administrátori" + moderators: "moderátori" + staff: "zamestnanci" + trust_level_0: "stupen_dovery_0" + trust_level_1: "stupen_dovery_1" + trust_level_2: "stupen_dovery_2" + trust_level_3: "stupen_dovery_3" + trust_level_4: "stupen_dovery_4" + education: + until_posts: + one: "jeden príspevok" + few: "%{count} príspevky" + other: "%{count} príspevkov" + new-topic: | + Víitajte na %{site_name} — **Ďakujeme za založenie novej konverzácie!** + + - Znie titulok zaujímavo ak ho čítate nahlas ? Zodpovedá obsahu ? + + - Koho by to mohlo zaujímať ? Prečo je to dôležité ? Akú reakciu očakávate ? + + - Vložte často používane slová vo vašej téme aby ju ostatní vedeli "nájsť". Pre zlúčenie vašej témy so súvisiacimi témami vyberte kategóriu . + + Pre viac informácií, [pozri návody](/guidelines). Tento panel sa zobrazí iba raz %{education_posts_text}. + new-reply: | + Vitajte na %{site_name} — **Ďakujeme za príspevok!** + + - Vylepší vaša odpoveď nejakým spôsobom diskusiu ? + + - Buďte ohľaduplní k ostatným členom komunity. + + - Konštruktívna kritika je vítana, avšak kritizujte "nápady", nie ľudí. + + Pre viac informácií, [pozri návody](/guidelines). Tento panel sa zobrazí iba raz %{education_posts_text}. + avatar: | + ### Čo tak pridať fotku k svojmu účtu? + + Už ste pridali zopár tém a odpovedi, ale Váš profil nie je taký jedinečný ako Vy -- Je to len písmeno. + + Zvážili ste **[pozrieť váš profil](%{profile_path})** a pridať obrázok , ktorý Vás vystihuje? + + Je jednoduchšie sledovať diskusie a nájsť zaujímave osoby v konverzácii pokiaľ ma každý z nich v profile jedinečný obrázok ! + sequential_replies: | + ### Zvážte možnosť odpovedať na viacero príspevkov naraz + + Namiesto množstva postupných odpovedí na tému prosím zvážte použitie jednej odpovede, ktorá bude obsahovať citácie z predchádzajúcich príspevkov, alebo odkazy na @meno + + Ak chcete dať časť svojho vloženého príspevku do úvodzoviek, stačí označiť text a stlačiť tlačidlo quote reply , ktoré sa následne objaví. + + Pre všetkých je jednoduchšie čítať témy, ktoré majú menšiu hĺbku vnorených odpovedi v porovnaní s množstvom malých individuálnych odpovedí. + dominating_topic: | + ### Umožnite ostatným nech sa zapoja do debaty + + Táto téma je pre Vás zjavne dôležitá – prispeli ste do nej viac než %{percent}% odpovedí. + + Ste si istí, že posktujete dostatok času aj ostatným aby prezentovali svoj názor ? + too_many_replies: | + ### Dosiahli ste limit počtu odpovedí na túto tému + + Ľutujeme, noví užívatelia môžu dočasne vložiť len %{newuser_max_replies_per_topic} odpovedí na jednu tému. + + Namiesto nového príspevku zvážte možnosť úpravy predchádzajúcich odpovedí, alebo skúste inú tému. + reviving_old_topic: | + ### Oživiť túto tému ? + + Posledná odpoveď k tejto téme je staršia ako %{days} dní. Vaša odpoveď posunie tému na vrchol tém a upozorní všetkých, ktorí sa zapojili do diskusie. + + Ste si istí, že chcete pokračovať v tejto starej diskusii? + activerecord: + attributes: + category: + name: "Názov kategórie" + post: + raw: "Telo" + user_profile: + bio_raw: "O mne" + errors: + models: + topic: + attributes: + base: + warning_requires_pm: "Výstraha môže byť pripojená len k súkromným spravam" + too_many_users: "Výstraha môže byť zaslaná len jednej osobe" + cant_send_pm: "Ľutujeme, nemôžte zaslať súkromnú správu tomuto užívateľovi" + no_user_selected: "Musíte zadať existujúceho užívateľa" + user: + attributes: + password: + common: "Toto je jedno z 10000 najbežnejších hesiel. Prosím použite bezpečnejšie heslo" + same_as_username: "je také isté ako vaše užívateľské meno. Prosím použite bezpečnejšie heslo" + same_as_email: "je také isté ako Váš email. Prosím použite bezpečnejšie heslo" + ip_address: + signup_not_allowed: "Registrácia z tohto účtu nie je povolená-" + color_scheme_color: + attributes: + hex: + invalid: "nesprávna farba" + <<: *errors + user_profile: + no_info_me: "
    Položka \"O mne\" vo Vašom profile je prázdna, želáte si ju doplníť?
    " + no_info_other: "
    %{name} nevložil zatiaľ nič do profilu \"O mne\"
    " + vip_category_name: "Salón" + vip_category_description: "Kategória výhradne pre členov s dôveryhodnosťou 3 a vyššou" + meta_category_name: "Podnety pre tvorcov stránky" + meta_category_description: "Diskusia o stránke, organizácii, ako funguje a ako ju môžme vylepšit" + staff_category_name: "Zamestnanci" + staff_category_description: "Súkromna kategória pre zamestnaneckú diskusiu. Témy su viditeľné len pre správcov a moderátorov diskusie" + assets_topic_body: "Toto je permanentná téma, viditeľná len pre redaktorov, na uchovávanie obrázkov a súborov použitých v dizajne tohto webu. Nezmazávajte ju!\n\n\nTu je návod:\n\n\n1. Odpovedzte na túto tému.\n2. Nahrajte sem všetky obrázky, ktoré si prajete použit pre logá, favikony a pod. (Použite ikonku „nahrať“ v paneli nástrojov editora, alebo obrázky potiahnite alebo „vložte“)\n3. Odošlite svoju odpoveď.\n4. Pre získanie adresy k nahratým obrázkom kliknite pravým tlačítkom na obrázky vo svojom novom príspevku, alebo kliknite na ikonku úpravy príspevku a následne si adresu skopírujte. \n5. Cesty k obrázkom vložte do [základného nastavenia](/admin/site_settings/category/required).\n\n\nAk potrebujete povoliť nahratie pre iné typy súborov, upravte `authorized_extensions` v [nastavení súborov](/admin/site_settings/category/files)." + lounge_welcome: + title: "Vítajte v Salóne" + category: + topic_prefix: "O kategórii: %{category} " + errors: + uncategorized_parent: "Nekategorizovaná nemôže mať nadradenú kategóriu" + self_parent: "Podkategória si nemôže byť zároveň kategóriou " + depth: "Nemôžte umiestniť podkategóriu pod inú podkategóriu" + email_in_already_exist: "Prichádzajúca emailová adresa '%{email_in}' sa už používa pre kategóriu '%{category_name}'." + cannot_delete: + uncategorized: "Nemôžte vymazať nekategorizované" + has_subcategories: "Nemôžte vymazať kategóriu pretože obsahuje podkategórie" + topic_exists: + one: "Nemôžte vymazať kategóriu pretože obsahuje tému %{topic_link}." + few: "Nemôžte vymazať kategóriu pretože obsahuje %{count} témy. Najstaršia téma je %{topic_link}." + other: "Nemôžte vymazať kategóriu pretože obsahuje %{count} tém. Najstaršia téma je %{topic_link}." + topic_exists_no_oldest: "Nemôžte vymazať túto kategóriu pretože obsahuje %{count} tém." + trust_levels: + newuser: + title: "nový používateľ" + basic: + title: "základný používateľ" + member: + title: "člen" + regular: + title: "bežný" + leader: + title: "vodca" + change_failed_explanation: "Pokúsili ste sa znížiť úroveň %{user_name} na '%{new_trust_level}'. Ale jeho/jej aktuálna úroveň už je '%{current_trust_level}'. %{user_name} zostane na úrovni '%{current_trust_level}' - Ak chcete znížiť úroveň používateľa, najskôr uzamknite úroveň" + rate_limiter: + slow_down: "Vykonali ste túto akciu príliš veľa krát, skúste neskôr" + too_many_requests: "Na vykonávanie tejto akcie máme nastavený denný limit. Prosím počkajte %{time_left} než skúsite znovu." + by_type: + first_day_replies_per_day: "Dosiahli ste maximálny počet odpovedí stanovený pre nového používateľa v jeho prvý deň. Prosím čakajte %{time_left} , než skúsite znova" + first_day_topics_per_day: "Dosiahli ste maximálny počet tém stanovený pre nového používateľa v jeho prvý deň. Prosím čakajte %{time_left} , než skúsite znova" + create_topic: "Výtvárate témy príliš rýchlo. Prosím čakajte %{time_left} , než skúsite znova" + create_post: "Odpovedáte príliš rýchlo. Prosím čakajte %{time_left} , než skúsite znova" + topics_per_day: "Dosiahli ste maximálny počet nových tém na tento deň. Prosím čakajte %{time_left} , než skúsite znova" + pms_per_day: "Dosiahli ste maximálny počet správ na tento deň. Prosím čakajte %{time_left} , než skúsite znova" + create_like: "Dosiahli ste maximálny počet \"Páči sa\" na tento deň. Prosím čakajte %{time_left} , než skúsite znova" + create_bookmark: "Dosiahli ste maximálny počet záložiek na tento deň. Prosím čakajte %{time_left} , než skúsite znova" + edit_post: "Dosiahli ste maximálny počet úprav na tento deň. Prosím čakajte %{time_left} , než skúsite znova" + hours: + one: "1 hodina" + few: "%{count} hodiny" + other: "%{count} hodín" + minutes: + one: "1 minútu" + few: "%{count} minúty" + other: "%{count} minút" + seconds: + one: " 1 sekundu" + few: "%{count} sekundy" + other: "%{count} sekúnd" + datetime: + distance_in_words: + half_a_minute: "< 1m" + less_than_x_seconds: + one: "< 1s" + few: "< %{count}s" + other: "< %{count}s" + x_seconds: + one: "1s" + few: "%{count}s" + other: "%{count}s" + less_than_x_minutes: + one: "< 1m" + few: "< %{count}m" + other: "< %{count}m" + x_minutes: + one: "1m" + few: "%{count}m" + other: "%{count}m" + about_x_hours: + one: "1h" + few: "%{count}h" + other: "%{count}h" + x_days: + one: "1d" + few: "%{count}d" + other: "%{count}d" + about_x_months: + one: " 1mes" + few: "%{count}mes" + other: "%{count}mes" + x_months: + one: "1mes" + few: "%{count}mes" + other: "%{count}mes" + about_x_years: + one: "1r" + few: "%{count}r" + other: "%{count}r" + over_x_years: + one: "> 1r" + few: "> %{count}r" + other: "> %{count}r" + almost_x_years: + one: "1r" + few: "%{count}r" + other: "%{count}r" + distance_in_words_verbose: + half_a_minute: "práve teraz" + less_than_x_seconds: + one: "práve teraz" + few: "práve teraz" + other: "práve teraz" + x_seconds: + one: "pred sekundou" + few: "pred %{count} sekundami" + other: "pred %{count} sekundami" + less_than_x_minutes: + one: "menej ako pred minútou" + few: "menej ako pred %{count} minútami" + other: "menej ako pred %{count} minútami" + x_minutes: + one: "pred minútou" + few: "pred %{count} minútami" + other: "pred %{count} minútami" + about_x_hours: + one: "pred hodinou" + few: "pred %{count} hodinami" + other: "pred %{count} hodinami" + x_days: + one: "včera" + few: "pred %{count} dňami" + other: "pred %{count} dňami" + about_x_months: + one: "približne pred mesiacom" + few: "približne pred %{count} mesiacmi" + other: "približne pred %{count} mesiacmi" + x_months: + one: "pred mesiacom" + few: "pred %{count} mesiacmi" + other: "pred %{count} mesiacmi" + about_x_years: + one: "približne pred rokom" + few: "približne pred %{count} rokmi" + other: "približne pred %{count} rokmi" + over_x_years: + one: "pred vyše rokom" + few: "pred vyše %{count} rokmi" + other: "pred vyše %{count} rokmi" + almost_x_years: + one: "pred takmer rokom" + few: "pred takmer %{count} rokmi" + other: "pred takmer %{count} rokmi" + password_reset: + no_token: "Prepáčte, táto linka na zmenu hesla je už príliš stará. Stlačte tlačitlo Prihlásiť a použite \"Zabudol som heslo\" pre vytvorenie novej linky" + choose_new: "Prosim zadajte nové heslo" + choose: "Prosím zadajte heslo" + update: 'Aktualizujte heslo' + save: 'Nastavte heslo' + title: 'Obnoviť heslo' + success: "Heslo bolo úspešne zmenené a ste prihlasený do systému. " + success_unapproved: "Heslo bolo úspešne zmenené." + continue: "Pokračujte na %{site_name}" + change_email: + confirmed: "Vaša emailova adresa bola aktualizovaná" + please_continue: "Pokračujte na %{site_name}" + error: "Nastala chyba pri aktualizácii emailu. Nieje už náhodou použitý?" + activation: + action: "Pre aktiváciu účtu kliknite sem" + already_done: "Prepáčte, táto potvrdzovacia linka je už neplatná. Nie je už účet aktivny ?" + please_continue: "Váš nový účet je potvrdený. Budete presmerovaní na domovskú stránku." + continue_button: "Pokračujte na %{site_name}" + welcome_to: "Vitajte na %{site_name}!" + approval_required: "Moderátor musí manuálne povoliť Váš prístup na toto fórum. O schválení prístupu budete upovedomení emailom." + missing_session: "Nevieme zistiť či Váš účet bol vytvorený, prosím uistite sa že máte v prehliadači povolené cookies." + post_action_types: + off_topic: + title: 'Mimo témy' + description: 'Tento príspevok nesúvisí s danou témou popísanou v názve a prvom príspevku a pravdepodobne by mal byť presunutý niekam inam' + long_form: 'toto je označené ako "Mimo témy"' + spam: + title: 'Spam' + description: 'Tento príspevok je reklama. Nijako nesúvisí s danou témou, má propagačný charakter.' + long_form: 'označiť toto ako spam' + email_title: '"%{title}" bol označený ako spam' + email_body: "%{link}\n\n%{message}" + inappropriate: + title: 'Nevhodné' + description: 'Obsah tohto príspevku môže byť nektorými osobami považovaný za urážlivý, hanlivý, alebo porušujúci pravidlá slušného správania.' + long_form: 'toto označ ako nevhodné' + notify_user: + title: 'Poslať @{{username}} správu' + description: 'Chcem sa s touto osobou rozprávať o jeho príspevku priamo a súkromne.' + long_form: 'úživateľovi bola zaslaná správa' + email_title: 'Váš príspevok v "%{title}"' + email_body: "%{link}\n\n%{message}" + notify_moderators: + title: "Niečo iné" + description: 'Tento príspevok vyžaduje pozornosť obsluhy z iného dôvodu ako je uvedené vyššie.' + long_form: 'označené do pozornosti obsluhy' + email_title: 'Príspevok "%{title}" vyžaduje pozornosť obsluhy' + email_body: "%{link}\n\n%{message}" + bookmark: + title: 'Záložka' + description: 'Vytvor záložku na tento príspevok' + long_form: 'záložka na tento príspevok bola vytvorená ' + like: + title: 'Páči sa mi' + description: 'Páči sa mi tento príspevok' + long_form: 'páčilo sa' + vote: + title: 'Hlasovať' + description: 'Hlasovať pre tento príspevok' + long_form: 'hlasoval pre tento príspevok' + topic_flag_types: + spam: + title: 'Spam' + description: 'Tento príspevok je reklama. Nijako nesúvisí s danou stránkou, má propagačný charakter.' + long_form: 'označiť toto ako spam' + inappropriate: + title: 'Nevhodné' + description: 'Táto téma môže byť nektorými osobami považovaná za urážlivú, hanlivú, alebo porušujúcu pravidlá slušného správania.' + long_form: 'toto označ ako nevhodné' + notify_moderators: + title: "Niečo iné" + description: 'Táto téma vyžaduje pozornosť obsluhy na základe pravidiel, TOS, alebo z iného dôvodu ako je uvedené vyššie.' + long_form: 'označené do pozornosti moderátora' + email_title: 'Téma "%{title}" vyžaduje pozornosť moderátora' + email_body: "%{link}\n\n%{message}" + flagging: + you_must_edit: '

    Váš príspevok bol označený komunitou. Prosim pozrite si Vaše správy.

    ' + user_must_edit: '

    Tento príspevok bol označený komunitou a je dočasne skrytý

    ' + archetypes: + regular: + title: "Bežná téma" + banner: + title: "Banerová téma" + message: + make: "Téma je odteraz baner. Bude sa zobrazovať navrchu každej stránky, pokiaľ ju používateľ nezavrie." + remove: "Téma odteraz nie je baner. Už sa nebude viac zobrazovat navrchu každej stránky." + unsubscribed: + title: 'Odhlásiť' + description: "Boli ste odhlásený. Už vás znovu nebudeme kontaktovať." + oops: "V prípade že ste to tak nemysleli, kliknite nižšie" + error: "Chyba pri odhlasovaní" + preferences_link: "Zo súhrnných emailov sa môžete odhlásiť na stránke nastavení" + different_user_description: "Aktuálne ste prihlásený ako iný používateľ než ten, komu bol zaslaný súhrnný email. Prosíme odhláste sa a skúste znovu." + not_found_description: "Prepáčte, nedokázali sme Vás odhlásiť. Je možné, že platnosť odkazu vo Vašom emaile vypršala." + resubscribe: + action: "Znovu-prihlásiť" + title: "Znovu-prihlásený!" + description: "Boli ste znovu prihlásený." + reports: + visits: + title: "Používateľské návštevy" + xaxis: "Deň" + yaxis: "Počet návštev" + signups: + title: "Noví používatelia" + xaxis: "Deň" + yaxis: "Počet nových používateľov" + profile_views: + title: "Prezreté používateľské profily" + xaxis: "Deň" + yaxis: "Počet prezretých používateľských profilov" + topics: + title: "Témy" + xaxis: "Deň" + yaxis: "Počet nových tém" + posts: + title: "Príspevky" + xaxis: "Deň" + yaxis: "Počet nových príspevkov" + likes: + title: "Páči sa mi" + xaxis: "Deň" + yaxis: "Počet nových páči sa mi" + flags: + title: "Označenia" + xaxis: "Deň" + yaxis: "Počet označení" + bookmarks: + title: "Záložky" + xaxis: "Deň" + yaxis: "Počet nových záložiek" + starred: + title: "Obľúbené" + xaxis: "Deň" + yaxis: "Počet nových ohviezdičkovaných tém" + users_by_trust_level: + title: "Užívatelia podľa stupňa dôvery" + xaxis: "Stupeň dôvery" + yaxis: "Počet použivateľov" + emails: + title: "Emaily odoslané" + xaxis: "Deň" + yaxis: "Počet emailov" + user_to_user_private_messages: + title: "Užívateľ užívateľovi" + xaxis: "Deň" + yaxis: "Počet správ" + system_private_messages: + title: "Systém" + xaxis: "Deň" + yaxis: "Počet správ" + moderator_warning_private_messages: + title: "Upozornenie od moderátora" + xaxis: "Deň" + yaxis: "Počet správ" + notify_moderators_private_messages: + title: "Upozorniť moderátora" + xaxis: "Deň" + yaxis: "Počet správ" + notify_user_private_messages: + title: "Upozorniť užívateľa" + xaxis: "Deň" + yaxis: "Počet správ" + top_referrers: + title: "Najlepší referenti" + xaxis: "Používateľ" + num_clicks: "Klinutia" + num_topics: "Témy" + top_traffic_sources: + title: "Najlepšie zdroje" + xaxis: "Doména" + num_clicks: "Klinutia" + num_topics: "Témy" + num_users: "Používatelia" + top_referred_topics: + title: "Najlepšie témy" + xaxis: "Témy" + num_clicks: "Klinutia" + page_view_anon_reqs: + title: "Anonymný" + xaxis: "Deň" + yaxis: "Anonymné požiadavky API" + page_view_logged_in_reqs: + title: "Prihlásený" + xaxis: "Deň" + yaxis: "Zaznamenané API požiadavky" + page_view_crawler_reqs: + title: "Webové prehľadávače" + xaxis: "Deň" + yaxis: "API dotazy web robota" + page_view_total_reqs: + title: "Celkovo" + xaxis: "Deň" + yaxis: "Všetky API požiadavky" + page_view_logged_in_mobile_reqs: + title: "Zaznamenané API požiadavky" + xaxis: "Deň" + yaxis: "Zaznamenané API požiadavky cez mobil" + page_view_anon_mobile_reqs: + title: "Anonymné požiadavky API" + xaxis: "Deň" + yaxis: "Zaznamenané API požiadavky cez mobil" + http_background_reqs: + title: "Pozadie" + xaxis: "Deň" + yaxis: "Požiadavky použité na aktívne aktualizácie a sledovanie" + http_2xx_reqs: + title: "Status 2xx (OK)" + xaxis: "Deň" + yaxis: "Úspešné požiadavky (Status 2xx)" + http_3xx_reqs: + title: "HTTP 3xx (Presmerovanie)" + xaxis: "Deň" + yaxis: "Presmerované požiadavky (Status 3xx)" + http_4xx_reqs: + title: "HTTP 4xx (Chyba na strane klienta)" + xaxis: "Deň" + yaxis: "Chyby na strane klienta (Status 4xx)" + http_5xx_reqs: + title: "HTTP 5xx (Chyba na strane servera)" + xaxis: "Deň" + yaxis: "Chyby na strane servera (Status 5xx)" + http_total_reqs: + title: "Celkovo" + xaxis: "Deň" + yaxis: "celkový počet žiadostí" + time_to_first_response: + title: "Čas do prvej odpovede" + xaxis: "Deň" + yaxis: "Priemerný čas (hodiny)" + topics_with_no_response: + title: "Témy bez odozvy" + xaxis: "Deň" + yaxis: "Celkovo" + mobile_visits: + title: "Používateľské návštevy" + xaxis: "Deň" + yaxis: "Počet návštev" + dashboard: + rails_env_warning: "Váš server beží v %{env} móde" + ruby_version_warning: "Používate Ruby 2.0.0, ktorá má známe problémy. Aktualizujte prosím na záplatu 247, alebo novšiu." + host_names_warning: "Váš súbor config/database.yml používa lokálne meno hostiteľa. Aktualizujte ho menom Vašej stránky." + gc_warning: 'Váš server používa východzie nastavenia ruby garbage collectora, ktorý neposktytuje najlepší výkon. Prečítajte si tento príspevok ohľadom ladenia výkonu Ladenie Ruby and Rails pre Discourse.' + sidekiq_warning: 'Sidekiq neni spustený. Množstvo úloh, ako napríklad posielanie emailov, je vykonávaných asynchrónne prostrednictvom sidekiq. Prosim zabezpečte aby bol spustený aspoň jeden proces sidekiq Viac o Sidekiq.' + queue_size_warning: 'Počet úloh v zásobníku je %{queue_size}, čo je dosť veľa. To môže byť príznakom problému s procesom (procesmi) Sidekiq, alebo by ste mali spustiť viac Sidekiq procesov.' + memory_warning: 'Váš server beží s menej než 1 GB pamäte. Odporúča sa minimálne 1GB. ' + google_oauth2_config_warning: 'Server má nakonfigurovanú podporu registrácie a prihlásenia pomocou Google OAuth2 (enable_google_oauth2_logins), ale identifikačné údaje klienta a jeho tajné hodnoty nie sú nastavené. Navštívte Nastavenia stránky a aktualizujte nastavenia. Chcete vedieť viac? Pozrite si tento návod.' + facebook_config_warning: 'Server má nakonfigurovanú podporu registrácie a prihlásenia pomocou Facebooku (enable_facebook_logins), ale údaje "app Id" a tajné hodnoty nie sú nastavené. Navštívte Nastavenia stránky a aktualizujte nastavenia. Chcete vedieť viac? Pozrite si tento návod.' + twitter_config_warning: 'Server má nakonfigurovanú podporu registrácie a prihlásenia pomocou Twitteru (enable_twitter_logins), ale kľúč a tajné hodnoty nie sú nastavené. Navštívte Nastavenia stránky a aktualizujte nastavenia. Chcete vedieť viac? Pozrite si tento návod.' + github_config_warning: 'Server má nakonfigurovanú podporu registrácie a prihlásenia pomocou GitHub (enable_github_logins), ale identifikačné údaje klienta a jeho tajné hodnoty nie sú nastavené.. Navštívte Nastavenia stránky a aktualizujte nastavenia.Chcete vedieť viac? Pozrite si tento návod.' + s3_config_warning: 'Server má nakonfigurované nahrávanie súborov na S3, ale minimálne jedna z nasledujúcich hodnôt nie je nastavená: s3_access_key_id, s3_secret_access_key, alebo s3_upload_bucket. Navštívte Nastavenia stránky a aktualizujte nastavenia. Chcete vedieť viac? Pozrite si návod ako nastaviť nahrávanie súborov na S3.' + s3_backup_config_warning: 'Server má nakonfigurované nahrávanie záloh na S3, ale minimálne jedna z nasledujúcich hodnôt nie je nastavená: s3_access_key_id, s3_secret_access_key, alebo s3_backup_bucket. Navštívte Nastavenia stránky a aktualizujte nastavenia. Chcete vedieť viac? Pozrite si návod ako nastaviť nahrávanie súborov na S3.' + image_magick_warning: 'Server je nakonfigurovaný na vytváranie náhľadov z veľkých obrázkov, ale ImageMagick nie je nainštalovaný. Nainštalujte ImageMagick pomocou Vášho správcu balíčkov, alebo Stiahnite najnovšiu verziu.' + failing_emails_warning: 'Existuje %{num_failed_jobs} neuspešných emailých pokusov. Skontrolujte Váš app.yml a uistite sa, že nastavenia email serveru máte správne. Pozrieť neúspešné pokusy v Sidekiq.' + default_logo_warning: "Nastavte grafické logo Vašej stránky. Aktualizujte logo_url, logo_small_url, a favicon_url na Ňastaveniach stránky." + contact_email_missing: "Vložte kontaktnú email adresu aby ste mohli byť kontaktovaný v urgentných prípadov týkajúcich sa stránok. Vložte na Stránke nastavení." + contact_email_invalid: "Kontaktný email na stránke je neplatný. Aktualizujte ho v Nastavenia stránky." + title_nag: "Zadajte meno stránky. Aktualizujte ho v Nastavenia stránky." + site_description_missing: "Vložte jednovetný popis Vašej stránky, ktorý sa zobrazí vo výsledkoch vyhľadávania. Aktualizujte ho v Nastavenia stránky." + consumer_email_warning: "Vaše stránky sú nastavené tak aby na posielanie emailov používali Gmail (alebo iné služby pre spotrebiteľov). Gmail obmedzuje počet odoslaných emailov. Na zaistenie doručiteľonosti emailov zvážte použitie emailového poskytovateľa ako napríklad mandrill.com." + site_contact_username_warning: "Vložte názov účtu kolegu z personálu pre zasielanie dôležitých automatických správ. Aktualizujte kontakt v Nastavenia stránky." + notification_email_warning: "Notifikačné emaily sú posielané z neplatnej emailovej adresy vo Vašej doméne. Doručovanie emailov môže byť chybné a nespoľahlivé. Prosím nastavte platnú lokálnu emailovú adresu \"notification_email\" v Nastavenia stránky." + subfolder_ends_in_slash: "Vaše nastavenie podadresára je chybné, DISCOURSE_RELATIVE_URL_ROOT je ukončené lomítkom." + site_settings: + censored_words: "Slová, ktoré budu automatický nahradené znakmi ■■■■" + delete_old_hidden_posts: "Automatické zmazanie príspevkov, ktoré ostali skryté viac ako 30 dní" + default_locale: "Predvolený jazyk tejto inštancie Discourse je (ISO 639-1 Code)" + allow_user_locale: "Povoliť užívateľom zvoliť si vlastný jazyk" + min_post_length: "Minimálny povolený počet znakov v príspevkoch" + min_first_post_length: "Minimálny povolený počet znakov pre prvý príspevok (obsah témy)" + min_private_message_post_length: "Minimálny povolený počet znakov v správe" + max_post_length: "Maximálny povolený počet znakov v príspevkoch" + min_topic_title_length: "Minimálny povolený počet znakov v názve témy" + max_topic_title_length: "Maximálny povolený počet znakov v názve témy" + min_private_message_title_length: "Minimálny povolený počet znakov v správe" + min_search_term_length: "Minimálny povolený počet znakov vo vyhľadávaní" + allow_uncategorized_topics: "Pvoliť vytváranie tém bez kategórií. UPOZORNENIE: Pokiaľ existujú nekategorizované témy, musíte ich zaradiť do kategórii skôr než túto možnosť vypnete." + uncategorized_description: "Popis nekategorizovanej kategórie. Nechajte prázdne pre žiadny popis.." + allow_duplicate_topic_titles: "Povoliť témy s rovnakými, duplikovanými názvami" + unique_posts_mins: "Koľko minút musí byť medzi dvomi rovnakými príspevkami od jedného užívateľa." + educate_until_posts: "Ked užívateľ začne písať svojich prvých (n) príspevkov, zobraz sprievodcu vzdelávania pre nového užívateľa. " + title: "Meno stránok, tak ako je použité v značke title." + site_description: "Popíšte stránky v jednej vete, tak ako sa popisujú v tagu meta." + contact_email: "Emailová adresa kľúčovej kontaktnej osoby zodpovednej za túto stránku. Používa sa pri kritických situáciach, ako napríklad neošetrené návestia a tiež pri kontaktnom formulári pre dôležité prípady." + contact_url: "Kontaktná URL adresa pre túto stránku. Použitá tiež pri kontaktnom formulári pre dôležité prípady." + queue_jobs: "IBA VÝVOJÁRI VAROVANIE! Štandardne, požiadavky fronty v sidekiq. Ak je vypnuté, vaše stránky budú rozbité." + crawl_images: "Načítať obrázky z URL pre vloženie správnej výšky a šírky." + download_remote_images_to_local: "Stiahnuť obrázky zo vzdialených zdrojov na lokálne. Zabráni poškodeniu obrázkov. " + download_remote_images_threshold: "Minimálne miesto na lokálnom disku potrebné na stiahnutie obrázkov zo vzdialených zdrojov (v percentách)" + disabled_image_download_domains: "Nikdy nesťahovať obrázky z týchto domén. Zoznam oddeleny pipou \"|\"." + editing_grace_period: "Nevytvárať nové verzie editovaných príspevkov do (n) sekúnd po odoslaní príspevku." + post_edit_time_limit: "Autor môže upravovať alebo mazať svoje príspevky do (n) minút po ich odoslaní. Nastavte 0 pre nekonečno" + edit_history_visible_to_public: "Povoliť všetkým zobrazenie predchádzajúcich verzií upraveného príspevku. Pokiaľ je to vypnuté, verzie vidí iba obslužný personál" + delete_removed_posts_after: "Príspevky zmazané autorom budú automaticky zmazané po (n) hodinách. Pokiaľ je nastavená 0, príspevky budú zmazané okamžite." + max_image_width: "Maximálna šírka náhľadu obrázkov v príspevku" + max_image_height: "Maximálna výška náhľadu obrázkov v príspevku" + category_featured_topics: "Počet zobrazených tém na jednu kategóriu na stránke /katgórie. Zmena hodnoty sa na stránkach prejaví do 15 minút." + show_subcategory_list: "Zobraz zoznam podkategórií namiesto zoznamu tém po vstupe do menu kategórií" + fixed_category_positions: "Ak je to označené, budete môcť zoraďovať kategórie v pevnom poradí. Ak je neoznačené, kategórie budu zoradené podlľa aktivity." + fixed_category_positions_on_create: "Ak je označené, triedenie kategórií bude spravované v dialógu na vytváranie témy (vyžaduje fixed_category_positions)." + add_rel_nofollow_to_user_content: "Pridať rel nofollow ku všetkému zaslanému používateľskému kontextu s výnimkou interných odkazov (zahrňuje rodičovskú doménu). Ak to zmeníte, musíte vykonať prípaz rebake na všetky príspevky pomocou: \"rake posts:rebake\"" + exclude_rel_nofollow_domains: "Zoznam domén, na ktorých odkazy nebude pridávaný nofollow . tld.com automaticky umožní sub.tld.com. Aby ste umožnili internetovým vyhľadávačom nájsť kompletný obsah mali by ste pridať minimálne doménu najvyššej úrovne týchto stránok. Ak je nejaká časť stránok na iných doménach, pridajte ich tiež." + post_excerpt_maxlength: "Maximálna dĺžka príspevku okrem zhrnutia." + post_onebox_maxlength: "Maximálna dĺžka vloženého Discourse príspevku v znakoch." + onebox_domains_whitelist: "Zoznam domén, pre ktoré je umožnené vkladanie. Tieto domény by mali podporovať OpenGraph alebo oEmbed. Overte ich pomocou http://iframely.com/debug" + logo_url: "Obrázok loga na ľavej hornej časti stránky, mal by byť tvaru širokého obdĺžnika. Ak nebude uvedený, zobrazí sa nadpis stránok." + digest_logo_url: "Obrázok loga na začiatku sumarizačného emailu stránok. Mal by byť tvaru širokého obdĺžnika. Ak nebude uvedený, použije sa logo_url." + logo_small_url: "Malý obrázok loga na ľavej hornej strane stránok, mal by mať tvar štvorca a bude zobrazený pri skrolovaní nadol. Ak nebude uvedený, zobrazí sa piktogram domu." + favicon_url: "Favicon pre Vaše stránky, pozrite http://en.wikipedia.org/wiki/Favicon. Aby fungoval správne s CDN, musí byť vo formáte png" + mobile_logo_url: "Obrázok loga zobrazený na fixnej pozícií v ľavom hornom rohu mobilnej verzie. Mal byť mať tvar štvorca. Ak nie je uvedené, použije sa `logo_url`. Napríklad: http://example.com/uploads/default/logo.png" + apple_touch_icon_url: "Ikona použitá na zariadeniach Apple touch. Doporučovaná veľkosť je 144px na 144px." + notification_email: "Odosieľateľ: email adresa použitá na zasielanie všetkých systémových emailov. Uvedená doména musí mať správne nastavené záznamy SPF, DKIM a opačný PTR aby email dorazil." + email_custom_headers: "Zoznam voliteľných emailových hlavičiek oddelených |" + email_subject: "Voliteľný formát predmetu emailu pre štandardné emaily. Pozrite https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801" + use_https: "Má byť url stránok (Discourse.base_url) http alebo https? NEZAPÍNAJTE MOŽNOSŤ AK HTTPS NIE JE NASTAVENÉ A FUNKČNÉ!" + summary_score_threshold: "Minimálne požadované skóre príspevku na to aby bol zahrnutý do 'Sumarizuj túto tému'" + summary_posts_required: "Minimálny počet príspevkou predtým ako je zapnuté 'Sumarizuj túto tému'" + summary_likes_required: "Minimálny počet páči sa mi predtým ako je zapnuté 'Sumarizuj túto tému'" + summary_percent_filter: "Ak používateľ klikne na 'Sumarizuj túto tému', zobraz najlepších % príspevkov" + summary_max_results: "Maximálny počet príspevkov vrátených pomocou 'Sumarizuj túto tému'" + enable_private_messages: "Umožni používateľom s úrovňou dôvery 1 (nastaviteľné pomocou min úroveň dôvery na posielanie správ) vytvárať správy a odpovedať na ne" + enable_long_polling: "Zbernica správ pre upozornenia môže používať techniku long-polling" + long_polling_base_url: "Base URL used for long polling (when a CDN is serving dynamic content, be sure to set this to origin pull) eg: http://origin.site.com" + long_polling_interval: "Ako dlho má server čakať pred odpovedaním klientom ak nie sú na zaslanie žiadne dáta (iba pre prihlásených používateľov)" + polling_interval: "Ak sa nepoužíva technika long-polling, ako často majú byť klienti dotazovaný v milisekundách" + anon_polling_interval: "Ako často majú byť dotazovaný anonymný klienti v milisekundách" + background_polling_interval: "Ako často majú byť dotazovaný klienti v milisekundách (ak je okno v pozadí)" + flags_required_to_hide_post: "Počet značiek ktoré automaticky príspevok skryjú a používateľovi bude zaslaná správa (0 pre nikdy)" + cooldown_minutes_after_hiding_posts: "Počet minút koľko musí používateľ počkať pred tým než môže upraviť príspevok skytý pomocou komunitných značiek" + max_topics_in_first_day: "Maximálny počet tém, ktoré používateľ môže vytvoriť v jeho prvý deň ." + max_replies_in_first_day: "Maximálny povolený počet odpovedí, ktoré môže používateľa vytvoriť v jeho prvý deň." + tl2_additional_likes_per_day_multiplier: "Zvýšiť počet páči sa mi na deň pre úroveň dôvery 2 (člen) vynásobením týmto číslom" + tl3_additional_likes_per_day_multiplier: "Zvýšiť počet páči sa mi na deň pre úroveň dôvery 3 (bežný) vynásobením týmto číslom" + tl4_additional_likes_per_day_multiplier: "Zvýšiť počet páči sa mi na deň pre úroveň dôvery 4 (vedúci) vynásobením týmto číslom" + num_flags_to_block_new_user: "Ak príspevky nového používateľa dostanú takéto množstvo spamových vlajok od num_users_to_block_new_user rôznych používateľov, skry všetky jeho príspevky a zabráň vytváraniu príspevkov v budúcnosti. 0 na vypnutie." + num_users_to_block_new_user: "Ak príspevky nového používateľa dostanú num_flags_to_block_new_user spamových vlajok od takéhoto množstva rôznych používateľov, skry všetky jeho príspevky a zabráň vytváraniu príspevkov v budúcnosti. 0 na vypnutie." + notify_mods_when_user_blocked: "Ak je používateľ automaticky zablokovaný, pošli správu všetkým moderátorom." + flag_sockpuppets: "Ak nový používateľ odpovedá na tému z rovnakej IP adresy, ako nový používateľ, ktorý danú tému vytvoril, označ oba ich príspevky ako potencionálny spam." + post_undo_action_window_mins: "Počet minút počas ktorých môžu užívatelia zrušiť poslednú akciu na príspevku (\"Páči sa\", označenie, atď..)." + must_approve_users: "Obsluha musí povoliť účty všetkým novým užívateľom skôr než im bude povolený prístup na stránku. UPOZORNENIE: zapnutie na živej stránke spôsobí zrušenie prístupu pre existujúcich používateľov, okrem obsluhy!" + pending_users_reminder_delay: "Upozorni moderátora ak nový užívateľ čaká na schválenie dlhšie ako tento počet hodín. Nastavte -1 pre vypnutie upozornenia." + allow_moderators_to_create_categories: "Povoliť moderátorom vytváranie nových kategórií" + use_admin_ip_whitelist: "Správcovia sa môťu prihlásiť iba pokiaľ pristupujú z adresy uvedenej v zozname kontrolovaných IP adries (Admin > Logs > Screened Ips)" + post_menu_hidden_items: "Položky vo východzích nastaveniach menu schované pri príspevkovom menu, dokiaľ neni kliknuté na rozbaľovaciu elipsu." + share_links: "Uveďte, ktoré položky sa maju zobraziť v zdieľacom dialógu a v akom poradí." + track_external_right_clicks: "Sledovať externé odkazy na ktore sa klkne pravým tlačidlom (npar. otvorenie v novej záložke) Vypnuté vo východzích nastaveniach, pretože to prepisuje URL adresy" + site_contact_username: "Platné uťívateľské meno obsluhy, ktorá bude posielať automatizované správy. Pokiaľ je vynechané, použije sa východzí systémový účet. " + send_welcome_message: "Poslač všetkým novým užívateľom uvítaciu správu s príručkou ako začať. " + suppress_reply_directly_below: "Nezobrazovať rozbaľovacie tlačidlo s počtom odpovedí ak je k príspevku len jedna odpoveď priamo pod ním." + suppress_reply_directly_above: "Nezobrazovať rozbaľovacie tlačidlo v-odpovedi-na ak je len jedna odpoveď priamo nad príspevkom." + suppress_reply_when_quoting: "Nezobrazovať rozbaľovacie tlačidlo v-odpovedi-na ak príspevok cituje odpoveď." + max_reply_history: "Maximálny počet odpovedí ktoré sa zobrazia po rozbalení v-odpovedi-na" + topics_per_period_in_top_summary: "Zobrazovaný počet najlepších tém vo východzom zozname najlepších tém." + topics_per_period_in_top_page: "Zobrazovaný počet najlepších tém vzozname po rozbalení 'Ukáž viac' najlepších tém." + redirect_users_to_top_page: "Automaticky presmeruj nových a dlho neprihlásených užívateľov na hlavnú stránku. " + top_page_default_timeframe: "Východzí interval pre presmerovanie na hlavnú stránku." + show_email_on_profile: "Zobraziť užívateľov email na ich profile (viditeľné len nimi samotnými a obsluhou)" + email_token_valid_hours: "Platnosť tokenov pre Zabudnuté heslo a aktiváciu účtu v (n) hodinách." + email_token_grace_period_hours: "Platnosť tokenov pre Zabudnuté heslo a aktiváciu účtu v (n) hodinách po ich uplatnení." + enable_badges: "Povoliť systém odznakov" + enable_whispers: "Povoliť súkromnú konverzáciu pre redakciu vrámci témy. (experimantálne)" + log_out_strict: "Pri odhlasovaní odhlásiť VŠETKY sessiony používateľa, na všetkých zariadeniach." + port: "POZOR! LEN PRE VÝVOJÁROV! Použiť tento HTTP port namiesto predvoleného portu 80. Nechajte prázdne pre port 80." + force_hostname: "POZOR! LEN PRE VÝVOJÁROV! V URL zadajte hostname. Ak chcete použiť predvolené, nechajte políčko prázdne." + min_username_length: "Minimálna dĺžka používateľského mena v znakoch." + max_username_length: "Maximálna dĺžka používateľského mena v znakoch." + reserved_usernames: "Používateľské mená, ktorých registrácia nie je povolená." + min_password_length: "Minimálna dĺžka hesla." + block_common_passwords: "Nepovoliť heslá, ktoré sú v zozname 10 000 najbežnejších hesiel." + sso_url: "URL pre single sign on." + enable_local_logins: "Povoliť pouťívanie lokálnych účtov a hesiel. (Poznámka: musí to byť zapnuté pre fungovanie pozvánok)" + allow_new_registrations: "Povoliť registráciu nových užívateľov. Odznačte to ak chcete zabrániť vytváraniu nových účtov. " + enable_signup_cta: "Zobraz oznámenie pre navrátilých anonymných užívateľov s výzvou na registráciu účtu. " + enable_yahoo_logins: "Povoliť autentifikáciu pomocou Yahoo." + enable_google_oauth2_logins: "Povoliť Google Oauth2 autentifikáciu. Táto métoda je podporovaná Googlom. Vyžaduje kľúč a šifru." + google_oauth2_client_id: "Client ID Vašej Google aplikácie." + google_oauth2_client_secret: "Client secret Vašej Google aplikácie." + enable_twitter_logins: "Povoliť autentifikáciu cez Twitter, vyžaduje twitter_consmer_key a twiiter_consumer_secret" + twitter_consumer_key: "Consumer key pre Twitter autentifikáciu, registrovaný na http://dev.twitter.com" + twitter_consumer_secret: "Consumer secret pre Twitter autentifikáciu, registrované na http://dev.twitter.com" + enable_facebook_logins: "Povoliť autentifikáciu pomocou Facebook, vyžaduje facebook_app_id a facebook_app_secret." + facebook_app_id: "App id pre Facebook autentifikáciu, registrované na https://developers.facebook.com/apps" + facebook_app_secret: "App secret pre Facebook autentifikáciu, registrované na https://developers.facebook.com/apps" + enable_github_logins: "Povoliť autentifikáciu cez Github, vyžaduje github_client_id a github_client_secret" + github_client_id: "Client id pre Github autentifikáciu, registrované na https://github.com/settings/applications" + github_client_secret: "Client secret pre Github autentifikáciu, registrované na https://github.com/settings/applications" + allow_restore: "Umožniť obnovu, ktorá nahradí VŠETKY data na stránkach! Ponechajte prázdne okrem prípadu ak chcete obnoviť zo zálohy." + maximum_backups: "Maximálny počet záloh udržiavaných na disku. Staršie zálohy budu automaticky vymazané" + automatic_backups_enabled: "Spustiť automatické zálohy podľa nastavenia intervalu záloh" + backup_frequency: "Ako často máme vytvárať zálohu stránok, v dňoch." + enable_s3_backups: "Po dokončení nahrať zálohy na S3. DÔLEŽITÉ: požaduje správne nastavenie prístupových údajov na S3 v menu Súbory." + s3_backup_bucket: "Vzdialený S3 bucket na uloženie záloh. VAROVANIE: Uistite sa, že ide o súkromný S3 bucket." + backup_time_of_day: "Čas v UTC, kedy sa má spustiť záloha." + backup_with_uploads: "Do pravidelných záloh ulož i nahrané súbory. Pri vypnutej voľbe sa uloží iba záloha databázy." + active_user_rate_limit_secs: "Ako často máme aktualizovať pole 'naposledy videný o', v sekundách" + verbose_localization: "Zobraziť rozšírené lokalizačné tipy v rozhraní" + previous_visit_timeout_hours: "Ako dlho trvá návšteva predtým, ako ju pokladáme za 'predchádzajúcu' návštevu, v hodinách" + rate_limit_create_topic: "Po vytvorení témy musí používateľ počkať (n) sekúnd pred vytvorením ďalšej témy." + rate_limit_create_post: "Po odoslaní prísprevku používateľ počkať (n) sekúnd pred vytvorením ďalšieho príspevku." + rate_limit_new_user_create_topic: "Po vytvorení témy musí používateľ počkať (n) sekúnd pred vytvorením ďalšej témy." + rate_limit_new_user_create_post: "Po odoslaní príspevku používateľ počkať (n) sekúnd pred vytvorením ďalšieho príspevku." + max_likes_per_day: "Maximálny počet páči sa mi používateľa denne." + max_flags_per_day: "Maximálny počet príznakov používateľa denne." + max_bookmarks_per_day: "Maximálny počet záložiek na jedného používateľa denne." + max_edits_per_day: "Maximálny počet úprav používateľa denne." + max_topics_per_day: "Maximálny počet tém používateľa denne." + max_private_messages_per_day: "Maximálny počet správ ktoré môže používateľ denne vytvoriť." + max_invites_per_day: "Maximálny počet pozvánok ktoré môže používateľ denne zaslať." + max_topic_invitations_per_day: "Maximálny počet pozvánok do tém ktoré môže používateľ denne zaslať." + suggested_topics: "Zobrazovaný počet navrhovaných tém na konci témy." + limit_suggested_to_category: "V navrhovaných témach zobraziť iba témy z aktuálnej kategórie." + clean_up_uploads: "Na zamedzenie nezákonného uloženia odstrániť opustené nahrané súbory na ktoré nevedie žiadny odkaz. VAROVANIE: pred zapnutím tohoto nastavenie možno chcete zálohovať Váš adresár s nahranými súbormi." + clean_orphan_uploads_grace_period_hours: "Doba (v hodinách) pred odstránením opustených nahraných súborov." + purge_deleted_uploads_grace_period_days: "Doba (v dňoch) pred úplným vymazaním odstráneného nahraného súboru." + purge_unactivated_users_grace_period_days: "Doba (v dňoch) pred tým, než je užívateľ, ktorý si neaktivoval svoj účet vymazaný." + enable_s3_uploads: "Ukladať nahrávané súbory na úložište S3. DÔLEŽITÉ: vyžaduje platné prístupové údaje S3 (id prístupového kľúča (access key id) a tajný prístupový kľúč (secret access key))." + s3_use_iam_profile: 'Použiť rolu AWS EC2 IAM na načítanie kľúčov. POZNÁMKA: zapnutie prepíše nastavený "s3 prístupový kľúč id (s3 access key id)" a "s3 tajný prístupový kľúč (s3 secret access key)".' + s3_upload_bucket: "Amazon S3 bucket name, do ktorého budú nahraté súbory. POZOR: musí byť malými písmenami, žiadne bodky a žiadne podtržítka." + s3_access_key_id: "Amazon S3 access key id, ktoré bude použité pre nahrávanie obrázkov." + s3_secret_access_key: "Amazon S3 secret access key, ktorý bude použitý pre nahrávanie obrázkov." + s3_region: "Amazon S3 region name, ktorý bude použitý pre nahrávanie súborov." + s3_cdn_url: "CDN URL ktoré sa použije na všetky dáta v s3 (napríklad https://cdn.niekde.com). VAROVANIE: po zmene tohoto nastavenia musíte použiť príkaz rebake na všetky staré príspevky." + avatar_sizes: "Zoznam automaticky generovaných veľkostí avatarov." + external_system_avatars_enabled: "Použiť externú avatar službu." + external_system_avatars_url: "URL externej avatar služby. Dostupné náhrady sú {username} {first_letter} {color} {size}" + default_opengraph_image_url: "URL štandardného opengraph obrázku." + enable_flash_video_onebox: "Povolenia vkladania odkazov na swf a flv (Adobe Flash) v onebox. VAROVANIE: môže vniesť bezpečnostné riziká." + default_invitee_trust_level: "Predvolená úroveň dôvery (0-4) pre pozvaných používateľov." + default_trust_level: "Východzí stupeň dôvery (0-4) pre všekých nových užívateľov. UPOZORNENIE: Zmena nastavenia Vás môže vystaviť riziku spamovania. " + tl1_requires_topics_entered: "Koľko tém musí nový užívateľ zadať pred povýšením na stupeň dôvery 1." + tl1_requires_read_posts: "Koľko príspevkov musí nový užívateľ prečítať pred povýšením na stupeň dôvery 1." + tl1_requires_time_spent_mins: "Koľko minút musí nový užívateľ čítať príspevky pred povýšením na stupeň dôvery 1." + tl2_requires_topics_entered: "Koľko tém musí nový užívateľ zadať pred povýšením na stupeň dôvery 2." + tl2_requires_read_posts: "Koľko príspevkov musí nový užívateľ prečítať pred povýšením na stupeň dôvery 2." + tl2_requires_time_spent_mins: "Koľko minút musí nový užívateľ čítať príspevky pred povýšením na stupeň dôvery 2." + tl2_requires_days_visited: "Koľko dní musí nový užívateľ navštevovať stránku pred povýšením na stupeň dôvery 2." + tl2_requires_likes_received: "Koľko \"Páči sa\" musí nový užívateľ dostať pred povýšením na stupeň dôvery 2." + tl2_requires_likes_given: "Koľko \"Páči sa\" musí nový užívateľ rozdať pred povýšením na stupeň dôvery 2." + tl2_requires_topic_reply_count: "Na koľko tém musí nový užívateľ odpovedať pred povýšením na stupeň dôvery 2." + tl3_requires_days_visited: "Minimálny počet navštevných dní, ktoré musí mať užívateľ za posledných 100 dní potrebných pre kvalifikáciu na dosiahnute 3 levelu dôvery. (0 až 100)" + tl3_requires_topics_replied_to: "Minimálny počet tém, na ktoré musí mať užívateľ odpovedať za posledných 100 dní potrebných pre kvalifikáciu na dosiahnute 3 levelu dôvery. (0 a viac)" + tl3_requires_topics_viewed: "Percento tém ktoré musí mať užívateľ pozreté, za posledných 100 dní pre kvalifikáciu na dosiahnute 3 levelu dôvery. (0 až 100)" + tl3_requires_posts_read: "Percento príspevkov, ktoré musí mať užívateľ pozreté za posledných 100 dní pre kvalifikáciu na dosiahnute 3 levelu dôvery. (0 až 100)" + tl3_requires_topics_viewed_all_time: "Minimálny počet všetkých tém, ktoré musí mať užívateľ pozreté pre kvalifikáciu na dosiahnute 3 levelu dôvery. " + tl3_requires_posts_read_all_time: "Minimálny počet všetkých príspevkov, ktoré musí mať užívateľ prečítané pre kvalifikáciu na 3 level dôvery. " + tl3_promotion_min_duration: "MInimálny počet dní po pridelení 3 levelu dôvery než môže byť užívateľ degradovaný späť na 3 level dôvery." + tl3_requires_likes_given: "Minimálny počet \"Páči sa\", ktoré musia byť udelené za posledných 100 dní pre kvalifikáciu na dosiahnutie 3 levelu dôvery. " + tl3_requires_likes_received: "Minimálny počet \"Páči sa\", ktoré musia byť prijaté za posledných 100 dní pre kvalifikáciu na dosiahnutie 3 levelu dôvery. " + tl3_links_no_follow: "Neodstraňujte rel = nofollow z odkazov pridaných užívateľmi s úrovňou dôveryhodnosti 3 ." + min_trust_to_create_topic: "MInimálna úroveň dôvery na vytvorenie novej témy." + min_trust_to_edit_wiki_post: "MInimálna úroveň dôvery na úpravu wiki príspevku." + min_trust_to_send_messages: "MInimálna úroveň dôvery na vytvorenie novej osobnej správy." + newuser_max_links: "Koľko odkazov môže nový používateľ pridať do príspevku." + newuser_max_images: "Koľko obrázkov môže nový používateľ pridať do príspevku." + newuser_max_attachments: "Koľko príloh môže nový používateľ pridať do príspevku." + newuser_max_mentions_per_post: "Maximálny počet notifikácií typu @meno môže nový užívateľ použiť v príspevku." + newuser_max_replies_per_topic: "Maximálny počet odpovedí ktoré môže nový používateľ pridať do jednej témy pokiaľ na ne niekto neodpovie." + max_mentions_per_post: "Maximálný počet notifikácií typu @meno, ktoré môže ktokoľvek použiť v rámci jedného príspevku." + max_users_notified_per_group_mention: "Maximálny počet používateľov, ktorý môžu dostať notifikácie, v prípade notifikovania celej skupiny (ak sa dosiahne maximum, žiadne ďalšie notifikácie nebudú zaslané)" + create_thumbnails: "Vytvor náhľad a okraje pre obrázoky, ktoré sú príliš veľké aby sa zmestili do príspevku." + title_max_word_length: "Maximálna povolená dĺžka slov v názve témy, v znakoch." + allow_uppercase_posts: "Povoliť kapitálky v názve témy, alebo tele príspevku." + search: + types: + category: 'Kategórie' + user: 'Používatelia' + login: + admin_not_allowed_from_ip_address: "Nie je možné prihlásenie ako admin z tejto IP adresy." + system_messages: + welcome_user: + subject_template: "Vitajte na %{site_name}!" + welcome_invite: + subject_template: "Vitajte na %{site_name}!" + subject_re: "Re:" + subject_pm: "[PM]" + user_notifications: + previous_discussion: "Prechádzajúce odpovede" + unsubscribe: + title: "Odhlásiť" + user_replied: + subject_template: "[%{site_name}] %{topic_title}" + text_body_template: | + %{message} + + %{context} + + --- + %{respond_instructions} + user_quoted: + subject_template: "[%{site_name}] %{topic_title}" + text_body_template: | + %{message} + + %{context} + + --- + %{respond_instructions} + user_mentioned: + subject_template: "[%{site_name}] %{topic_title}" + text_body_template: | + %{message} + + %{context} + + --- + %{respond_instructions} + user_group_mentioned: + subject_template: "[%{site_name}] %{topic_title}" + text_body_template: | + %{message} + + %{context} + + --- + %{respond_instructions} + user_posted: + subject_template: "[%{site_name}] %{topic_title}" + text_body_template: | + %{message} + + %{context} + + --- + %{respond_instructions} + user_posted_pm: + subject_template: "[%{site_name}] [PM] %{topic_title}" + text_body_template: | + %{message} + + %{context} + + --- + %{respond_instructions} + digest: + top_topics: "Populárne príspevky" + other_new_topics: "Populárne témy" + click_here: "kliknite tu" + set_password: + subject_template: "[%{site_name}] Nastaviť heslo" + admin_login: + subject_template: "[%{site_name}] Prihlásenie" + account_created: + subject_template: "[%{site_name}] Váš nový účet" + authorize_email: + subject_template: "[%{site_name}] Potvrďte Vašu novú email adresu" + signup_after_approval: + subject_template: "Váš účet bol schválený na %{site_name}!" + signup: + subject_template: "[%{site_name}] Potvrďte Váš nový účet" + page_not_found: + title: "Stránka ktorú požadujete neexistuje alebo je súkromná." + popular_topics: "Populárne" + recent_topics: "Nedávne" + see_more: "Viac" + search_title: "Prehľadať tieto stránky" + search_google: "Google" + terms_of_service: + title: "Podmienky používania" + signup_form_message: 'Prečítal som si Podmienky používania a súhlasím s nimi.' + upload: + pasted_image_filename: "Vložený obrázok" + file_missing: "Ľutujeme, ale musíte poskytnúť súbor na nahranie." + email_log: + anonymous_user: "Používateľ je anonymný" + seen_recently: "Používateľ bol nedávno online" + already_read: "používateľ si už prečítal tento príspevok" + message_blank: "správa je prázdna" + body_blank: "telo je prázdne" + color_schemes: + base_theme_name: "Základ" + about: "O stránke" + guidelines: "Pravidlá" + privacy: "Súkromie" + edit_this_page: "Upraviť túto stránku" + csv_export: + boolean_yes: "Áno" + boolean_no: "Nie" + guidelines_topic: + title: "FAQ/Pravidlá" + body: | + + + ## [Toto je miesto pre civilizovanú diskusiu](#civilized) + + Prosíme majte k tejto diskusii rešpekt podobný ako k verejnému parku. My, sme tiež zdieľaný zdroj — miesto na zdieľanie znalostí, vedomostí a záujmov pomocou nepretržitej diskusie. + + Toto nie sú jednoznačné pravidlá, skôr návod ľudskému úsudku naše komunity. Použite tieto pravidlá na udržanie prehľadnú a svetlého miesta pre civilizovanú verejnú diskusiu. + + + + ## [Vylepšujte diskusiu](#improve) + + Pomôžte nám tvoriť toto skvelé miesto akýmkoľvek snahou o zlepšenie diskuzie, stačí drobnosť. Ak nie ste si istý že Váš príspevok posúva diskuziu, premyslite si čo chcete povedať a skúste neskôr. + + Diskutované témy sú nám blízke a chceme, aby ste sa správali tak, akoby Vám boli blízke tiež. Rešpektujte témy a diskutujúcich dokonca i v prípade, ak nesúhlasíte s tým, čo bolo povedané. + + Jedným zo spôsobov ako vylepšiť diskusiu je objavenie tej ktoré už prebieha. Prosíme, venujte trochu času zoznámeniu sa s témami predtým, ako začnete odpovedať alebo začínať svoje vlastné a budete mať vačšiu šancu stretnúť tých, ktorý s Vami zdieľajú záujmy. + + + + ## [Buďte príjemný i keď nesúhlasíte](#agreeable) + + Môžete mať túžbu odpovedať na niečo s čím nesúhlasíte. To je v poriadku. Ale pamätajte _kritizujte myšienky a nie ľudí_. Prosíme, vyvarujte sa: + + * Posmeškom. + * Útokom na konkrétnu osobu. + * Odpovediam na tón príspevku miesto skutočného obsahu. + * Impulzívnym reakciám. + + Namiesto toho poskytnite logické proti argumenty, ktoré zlepšia konverzáciu. + + + + ## [Vaša účasť sa počíta](#participate) + + Konverzácie, ktoré tu máme nastavujú tón pre všetkých. Pomôžte nám ovplyvniť budúcnosť tejto komunity zapojením sa do diskusií ktoré z tohoto fóra robia zaujímavé miesto a vyhýbaním sa tým ktoré to nerobia. + + Discourse poskytuje nástroje, ktoré komunite spoločne umožňujú identifikovať najlepšie (a najhoršie) príspevky, záložky, páči sa mi, značky, odpovede, úpravy a podobne. Použite ich na vylepšenie Vašej skúsenosti i skúsenosti kohokoľvek iného. + + Poďme náš park zanechať v lepšom stave než v akom sme ho našli. + + + + ## [Ak vidíte problém označte ho](#flag-problems) + + Moderátori majú špeciálnu autoritu, sú zodpovedný za fórum. Ale Vy tiež. S Vašou pomocou sa moderátori môžu stať sprostredkovateľmi a nie iba školníkmi alebo políciou. + + Ak vidíte nevhodné chovanie, neodpovedajte. Reakciou naň ho posiľujete, míňate svoju energiu a strácate čas nás všetkých. _Oba ho označte_. Ak sa zíde viačej značiek, budú prijaté opatrenia, či už automatické alebo zásahom moderátora. + + Na správu komunity majú moderátori vyhradené práva na odstránenie akéhokoľvek obsahu a používateľského účtu kedukoľvek z akéhokoľvek dôvodu. Moderátori žiadnym spôsobom nekontrolujú príspevky, moderátori a vlastníci stránky nepreberajú žiadku zodpovednosť za obsah publikovaný komunitou. + + + + ## [Buďte civilizovaný](#be-civil) + + Nič nesabotuje zdravú diskusiu tak ako hrubosť: + + * Buďte zdvorilí, Neuverejňujte nič, čo by múdra osoba považovala za urážlivé, znevažujúce alebo nenávistné. + * Udržujte poriadok. Neuverejnujte nič nemravné alebo explicitne sexuálne. + * Rešpektujte iných. Neprenasledujte alebo nezarmucujte ostatných, nenapodobnujte ľudí, alebo nezverejňujte ich súkromné informácie. + * Rešpektujte naše fórum. Neuverejňujte spam alebo iným spôsobom nevandalizujte fórum. + + Toto nie sú jednoznačné pravidlá s presnými definiciami — vyhnite sa čo i len _náznaku_ akejkoľvek z týchto vecí. Ak nie ste si istý, spýtajte sa, či by Váš príspevok bol zverejnený na hlavnej strane New York Times. + + Toto je verejné fórum a diskusie sú indexované vyhľadávačmi. Udržujte jazyk, odkazy a obrázky bezpečné pre rodinu a priateĺov. + + + + ## [Udržujte poriadok](#keep-tidy) + + Vynaložte snahu dať veci na správne miesto, tak môžeme stráviť viac času diskusiou a menej udržiavaním poriadku. Takže: + + * Nezačínajte tému v nesprávnej kategórií. + * Neuverejňujte rovnaké veci vo viacetých témach. + * Nepublikujte príspevky bez obsahu. + * Nepresmerujte tému zmenou uprostred. + * Nepodpisujte sa — každý príspevok má pripojený odkaz na Váš profil. + + Namiesto poslania “+1” or “Súhlasím”, radšej použite tlačítko Páči sa mi. Namiesto radikálnej zmeny témy v priebehu diskusie radšej odpovedzte spojenou témou. + + + + ## [Uverejňujte iba svoj obsah](#stealing) + + Bez dovolenia nemôžete publikovať nič digitálne, čo patrí niekomu inému. Nemôžete publikovať popis, odkazy alebo metódy na kradnutie niekoho intelektuálneho vlastníctva (softvéru, videa, audia, obrázkov), alebo porušovať akýkoľvek iný zákon. + + + + ## [Založené na Vás](#power) + + Táto stránka je prevádzkovaná [miestnym priateľským personálom](/about) a *Vami*, komunitou. Ak máte akékoľvek ďalšie otázky ako by tu veci mali fungovať, založte novú tému v [kategórií spätná väzba](/c/site-feedback) a poďme diskutovať! Ak ste narazili na kritický alebo urgentný problém ktorý nemôže byť riešený pomocou tém v meta alebo príznakom, kontaktujte nás pomocou [stránky personálu](/about). + + + + ## [Podmienky](#tos) + + Áno, právnický žargón je nudný, ale musíme chrániť seba – tým následne i Vás a vaše dáta – voči nepriateľským ľuďom. Máme [Podmienky](/tos) popisujúce Vaše (a naše ) správanie a práva súvisiace s obsahom, súkromým a legislatívou. K používaniu tejto služby musíte dodžiavať naše [Pravidlá](/tos). + tos_topic: + title: "Podmienky používania" + badges: + long_descriptions: + basic: | + Tento odznak je udelený ak dosiahnete stupeň dóvery 1. Ďakujeme za Váš čas , prečítanie niekoľkých tém a zoznámenie sa s tým o čom je naša komunita. Odstránili sme obmedzenia pre nového používateľa a odteraz máte všetky základné práva, ako je zasielanie osobných správ, značkovanie, wiki úpravy a možnosť publikovať obrázky a viaceré odkazy. + admin_login: + success: "Email odoslaný" + error: "Chyba !" + email_input: "Email administrátora" + submit_button: "Poslať email" + discourse_hub: + access_token_problem: "Povedzte administrátorovi: Prosím aktualizujte nastavenia stránky, aby obsahovali správny discourse_org_access_key." + performance_report: + initial_post_raw: Táto téma obsahuje denné reporty rýchlosti Vašich stránok. + initial_topic_title: Reporty rýchlosti stránok + time: + <<: *datetime_formats + activemodel: + errors: + <<: *errors diff --git a/config/locales/server.sq.yml b/config/locales/server.sq.yml index 42e086a9e..efb40284a 100644 --- a/config/locales/server.sq.yml +++ b/config/locales/server.sq.yml @@ -16,8 +16,6 @@ sq: loading: "Loading" powered_by_html: 'Mundësuar nga Discourse, për një eksperience më të mirë aktivizoni JavaScript' log_in: "Identifikohu" - via: "%{username} \tnëpër %{site_name}" - is_reserved: "është i rezervuar" purge_reason: "Automatically deleted as abandoned, unactivated account" disable_remote_images_download_reason: "Remote images download was disabled because there wasn't enough disk space available." anonymous: "Anonim" @@ -392,15 +390,11 @@ sq: description: 'This post contains content that a reasonable person would consider offensive, abusive, or a violation of our community guidelines.' long_form: 'flagged this as inappropriate' notify_user: - description: 'This post contains something I want to talk to this person directly and privately about. Does not cast a flag.' long_form: 'messaged user' email_title: 'Postimi juaj në "%{title}"' email_body: "%{link}\n\n%{message}" notify_moderators: title: "Diçka tjetër" - description: 'This post requires moderator attention for another reason not listed above.' - long_form: 'shënoje për vëmendjen e moderatorëve' - email_title: 'A post in "%{title}" requires moderator attention' email_body: "%{link}\n\n%{message}" bookmark: title: 'Bookmark' @@ -425,7 +419,6 @@ sq: long_form: 'flagged this as inappropriate' notify_moderators: title: "Diçka Tjetër" - description: 'This topic requires general moderator attention based on the guidelines, TOS, or for another reason not listed above.' long_form: 'shënoje për vëmendjen e moderatorëve' email_title: 'The topic "%{title}" requires moderator attention' email_body: "%{link}\n\n%{message}" @@ -611,37 +604,6 @@ sq: consumer_email_warning: "Your site is configured to use Gmail (or another consumer email service) to send email. Gmail limits how many emails you can send. Consider using an email service provider like mandrill.com to ensure email deliverability." site_contact_username_warning: "Enter the name of a friendly staff user account to send important automated messages from. Update site_contact_username in Site Settings." notification_email_warning: "Notification emails are not being sent from a valid email address on your domain; email delivery will be erratic and unreliable. Please set notification_email to a valid local email address in Site Settings." - content_types: - education_new_reply: - title: "New User Education: First Replies" - description: "Pop up just-in-time guidance automatically displayed above the composer when new users begin typing their first two new replies." - 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: - title: "New User Guidance" - description: "Guidance and essential information for new users." - welcome_user: - title: "Welcome: New User" - description: "A message automatically sent to all new users when they sign up." - welcome_invite: - title: "Welcome: Invited User" - description: "A message automatically sent to all new invited users when they accept the invitation from another user to participate." - login_required_welcome_message: - title: "Login Required: Welcome Message" - description: "Welcome message that is displayed to logged out users when the 'login required' setting is enabled." - login_required: - title: "Login Required: Homepage" - description: "The text displayed for unauthorized users when login is required on the site." - head: - title: "HTML head" - description: "HTML that will be inserted inside the tags." - top: - title: "Top of the pages" - description: "HTML that will be added at the top of every page (after the header, before the navigation or the topic title)." - bottom: - title: "Bottom of the pages" - description: "HTML that will be added before the tag." site_settings: censored_words: "Fjalët do të zhvendosen automatikisht me ■■■■" delete_old_hidden_posts: "Auto-delete any hidden posts that stay hidden for more than 30 days." @@ -814,9 +776,9 @@ sq: enable_s3_uploads: "Place uploads on Amazon S3 storage. IMPORTANT: requires valid S3 credentials (both access key id & secret access key)." s3_use_iam_profile: 'Use AWS EC2 IAM role to retrieve keys. NOTE: enabling will override "s3 access key id" and "s3 secret access key" settings.' s3_upload_bucket: "The Amazon S3 bucket name that files will be uploaded into. WARNING: must be lowercase, no periods, no underscores." - s3_access_key_id: "The Amazon S3 access key id that will be used to upload images." - s3_secret_access_key: "The Amazon S3 secret access key that will be used to upload images." - s3_region: "The Amazon S3 region name that will be used to upload images." + s3_access_key_id: "The Amazon S3 access key id that will be used to upload images and/or backups." + s3_secret_access_key: "The Amazon S3 secret access key that will be used for uploading." + s3_region: "The Amazon S3 region name that will be used for uploading." s3_cdn_url: "The CDN URL to use for all s3 assets (for example: https://cdn.somewhere.com). WARNING: after changing this setting you must rebake all old posts." avatar_sizes: "List of automatically generated avatar sizes." enable_flash_video_onebox: "Enable embedding of swf and flv (Adobe Flash) links in oneboxes. WARNING: may introduce security risks." @@ -940,7 +902,6 @@ sq: anonymous_posting_min_trust_level: "Minimum trust level required to enable anonymous posting" anonymous_account_duration_minutes: "To protect anonymity create a new anonymous account every N minutes for each user. Example: if set to 600, as soon as 600 minutes elapse from last post AND user switches to anon, a new anonymous account is created." allow_profile_backgrounds: "Allow users to upload profile backgrounds." - sequential_replies_threshold: "Number posts a user has to make in a row in a topic before being reminded about too many sequential replies. " enable_mobile_theme: "Mobile devices use a mobile-friendly theme, with the ability to switch to the full site. Disable this if you want to use a custom stylesheet that is fully responsive." dominating_topic_minimum_percent: "What percentage of posts a user has to make in a topic before being reminded about overly dominating a topic." daily_performance_report: "Analyze NGINX logs daily and post a Staff Only topic with details" @@ -970,7 +931,6 @@ sq: enable_cdn_js_debugging: "Allow /logs to display proper errors by adding crossorigin permissions on all js includes." show_create_topics_notice: "If the site has fewer than 5 public topics, show a notice asking admins to create some topics." delete_drafts_older_than_n_days: Delete drafts older than (n) days. - vacuum_db_days: "Run VACUUM FULL ANALYZE to reclaim DB space after migrations (set to 0 to disable)" prevent_anons_from_downloading_files: "Prevent anonymous users from downloading attachments. WARNING: this will prevent any non-image site assets posted as attachments from working." slug_generation_method: "Choose a slug generation method. 'encoded' will generate percent encoding string. 'none' will disable slug at all." enable_emoji: "Enable emoji" @@ -1534,24 +1494,8 @@ sq: For additional guidance, please refer to our [community guidelines](%{base_url}/guidelines). user_automatically_blocked: subject_template: "New user %{username} blocked due to community flags" - text_body_template: | - This is an automated message. - - The new user [%{username}](%{base_url}%{user_url}) was automatically blocked because multiple users flagged %{username}'s post(s). - - Please [review the flags](%{base_url}/admin/flags). If %{username} was incorrectly blocked from posting, click the unblock button on [the admin page for this user](%{base_url}%{user_url}). - - This threshold can be changed via the `block_new_user` site settings. spam_post_blocked: subject_template: "New user %{username} posts blocked due to repeated links" - text_body_template: | - This is an automated message. - - The new user [%{username}](%{base_url}%{user_url}) tried to create multiple posts with links to %{domains}, but those posts were blocked to avoid spam. The user is still able to create new posts that do not link to %{domains}. - - Please [review the user](%{base_url}%{user_url}). - - This can be modified via the `newuser_spam_host_threshold` and `white_listed_spam_host_domains` site settings. unblocked: subject_template: "Account unblocked" text_body_template: | @@ -1578,8 +1522,6 @@ sq: unsubscribe: title: "Unsubscribe" description: "Not interested in getting these emails? No problem! Click below to unsubscribe instantly:" - 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} invited you to a message '%{topic_title}'" @@ -1613,49 +1555,14 @@ sq: Please visit this link to view the message: %{base_url}%{url} user_replied: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_quoted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted_pm: subject_template: "[%{site_name}] [PM] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} digest: why: "A brief summary of %{site_link} since your last visit on %{last_seen_at}" subject_template: "[%{site_name}] Digest" diff --git a/config/locales/server.sv.yml b/config/locales/server.sv.yml index c9c0d693c..460620ab1 100644 --- a/config/locales/server.sv.yml +++ b/config/locales/server.sv.yml @@ -24,8 +24,6 @@ sv: loading: "Laddar" powered_by_html: 'Drivs av Discourse, visas bäst med JavaScript påslaget' log_in: "Logga in" - via: "%{username} via %{site_name}" - is_reserved: "är reserverat" purge_reason: "Automatiskt raderat i och med att kontot var övergivet och icke-aktiverat" disable_remote_images_download_reason: "Fjärrbilds nedladdning är inaktiverad eftersom det inte fanns tillräckligt mycket lagringsutrymme tillgängligt." anonymous: "Anonym" @@ -85,6 +83,7 @@ sv: not_found: "Den efterfrågade URL:en eller resursen kan inte hittas." invalid_access: "Du har inte behörighet att visa den efterfrågade resursen." read_only_mode_enabled: "Webbplatsen är i read only läge. Interaktioner är inaktiverade." + reading_time: "Lästid" too_many_replies: one: "Vi är ledsna men nya användare är tillfälligt begränsade till 1 svar i samma diskussion." other: "Vi är ledsna men nya användare är tillfälligt begränsade till &{count} svar i samma ämne." @@ -386,15 +385,10 @@ sv: description: 'Detta inläggs innehåll är inkluderar saker som som en förnuftig person skulle betrakta som stötande, kränkande eller överträdelse av våra community riktlinjerr.' long_form: 'flagga detta som olämpligt' notify_user: - title: 'Kontakta @{{username}} via privat meddelande' - description: 'Detta inlägg innehåller något som jag, direkt och privat, vill tala med denna person om. Detta ger ingen flagga.' email_title: 'Du postade i "%{title}"' email_body: "%{link}\n\n%{message}" notify_moderators: title: "Övrigt" - description: 'Detta inlägg kräver en moderators uppmärksamhet av ett annat skäl än de som nämns ovan.' - long_form: 'flaggade det här för granskning av moderator' - email_title: 'Ett inlägg i "%{title}" kräver uppmärksamhet från en moderator' email_body: "%{link}\n\n%{message}" bookmark: title: 'Bokmärk' @@ -419,7 +413,6 @@ sv: long_form: 'flaggad som olämplig' notify_moderators: title: "Annat" - description: 'Detta ämne kräver en moderators uppmärksamhet baserat på riktlinjerna, användarvillkoren, eller an annat skäl som inte nämns ovan.' long_form: 'flaggade det här för granskning av moderator' email_title: 'Ämnet "%{title}" kräver moderators uppmärksamhet' email_body: "%{link}\n\n%{message}" @@ -454,6 +447,7 @@ sv: xaxis: "Dag" yaxis: "Antalet nya användare" profile_views: + title: "Användarprofilvisningar" xaxis: "Dag" topics: title: "Ämnen" @@ -566,7 +560,9 @@ sv: xaxis: "Dag" yaxis: "Totalt" mobile_visits: + title: "Användarbesök" xaxis: "Dag" + yaxis: "Antal besök" dashboard: rails_env_warning: "Your server is running in %{env} mode." ruby_version_warning: "Du använder en version av Ruby 2.0.0 som är känd för att ha problem. Uppgradera till patch-nivå 247 eller senare." @@ -581,36 +577,6 @@ sv: 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: - title: "New User Education: First Replies" - description: "Pop up just-in-time guidance automatically displayed above the composer when new users begin typing their first two new replies." - 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 ■■■■" delete_old_hidden_posts: "Auto-radera alla dolda inlägg som varit dolda i mer än 30 dagar." @@ -983,8 +949,6 @@ sv: unsubscribe: title: "Unsubscribe" description: "Inte intresserad av att få dessa mejl? Inga problem! Klicka nedan för att avprenumerera." - reply_by_email: "För att svara, svara på detta mejl eller besök %{base_url}%{url} i din webbläsare." - visit_link_to_respond: "För att svara, besök %{base_url}%{url} i din webbläsare." posted_by: "Postat av %{username} den %{post_date}" user_invited_to_private_message_pm: subject_template: "[%{site_name}] %{username} har bjudit in dig till en privat konversation '%{topic_title}'" @@ -992,40 +956,12 @@ sv: subject_template: "[%{site_name}] %{username} har bjudit in dig till ett ämne '%{topic_title}'" user_replied: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_quoted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} digest: new_activity: "Ny aktivitet i dina ämnen och inlägg:" top_topics: "Populära inlägg" diff --git a/config/locales/server.te.yml b/config/locales/server.te.yml index e56c83489..a7198c203 100644 --- a/config/locales/server.te.yml +++ b/config/locales/server.te.yml @@ -16,8 +16,6 @@ te: loading: "లోడవుతోంది" powered_by_html: ' డిస్కౌర్స్ చేత శక్తివంతం చేయబడింది, జావాస్క్రిప్ట్ చేతనం చేస్తే బాగా కనిపిస్తుంది. ' log_in: "లాగిన్" - via: "%{site_name} నుండి %{username}" - is_reserved: "రక్షితము" purge_reason: "వదిలేసినదిగా స్వీయంగా కనుగొను, చేతనం చేయని ఖాతా" disable_remote_images_download_reason: "సుదూర బొమ్మల దిగుమతి అచేతనమైంది ఎందుకంటే డిస్క్ జాగా తక్కువగా ఉంది." errors: &errors @@ -313,14 +311,10 @@ te: description: 'ఈ టపాలో విషయం కొంతమందికి అభ్యంతరకరమైనది, అగౌరవపరిచేది లేదా మా కమ్యునిటీ మార్గదర్శకాలకు లోబడినది కాదు.' long_form: 'దీన్ని అసమంజసమైనదిగా కేతనించాము' notify_user: - description: 'ఈ టపా నేను సభ్యునితో వ్యక్తిగతంగా మాట్లాడాలనుకున్న విషయం కలిగి ఉంది. కేతనం అవ్వసరంలేదు.' email_title: '"%{title}" లో మీ టపా' email_body: "%{link}\n\n%{message}" notify_moderators: title: "వేరే ఏదో" - description: 'ఇక్కడ ప్రదర్శిచని వేరే కారణంగా ఈ టపా నిర్వాహకుని దృష్టికి తీసుకెళ్లాలి' - long_form: 'దీన్ని నిర్వాహకుని దృష్టికి కేతనించాము' - email_title: '"%{title}" లోని ఒక టపా నిర్వాహకుని చర్య కోసం వేచిఉంది. ' email_body: "%{link}\n\n%{message}" bookmark: title: 'పేజీక' @@ -345,7 +339,6 @@ te: long_form: 'దీన్ని అసమంజసమైనదిగా కేతనించారు' notify_moderators: title: "వేరే ఏదో" - description: 'మార్గదర్శకాల ఆధారంగా ఈ అంశానికి సాధారణ పరిశీలకుల శ్రధ్ధ అవసరం, TOS, లేదా పైన జాబితాలో పేర్కొనబడని మరొక కారణం కావచ్చు.' long_form: 'దీన్ని నిర్వాహకుల దృష్టికి తెచ్చారు' email_title: '"%{title}" విషయం నిర్వాహకుల దృష్టిలో ఉంది.' email_body: "%{link}\n\n%{message}" @@ -489,35 +482,6 @@ te: title_nag: "సైట్ పేరు నమోదు చేయండి.శీర్షికను సైట్ సెట్టింగ్స్‌ లో అప్‌డేట్ చేయండి" site_description_missing: "శోధన ఫలితాల్లో కనిపించడానికి మీ సైట్ యొక్క ఏకవాక్య వివరణ నమోదు చేయండి.సైట్ వివరణను సైట్ సెట్టింగ్స్‌ లో అప్‌డేట్ చేయండి." notification_email_warning: "ఈ-మెయిల్ ప్రకటనలు మీ డొమైన్ లో ఒక చెల్లుబాటు అయ్యే ఈ-మెయిల్ చిరునామా నుండి పంపలేదు :ఈ-మెయిల్ బట్వాడా అనిశ్చిత మరియు నమ్మలేనిదిగా ఉంటుంది.దయచేసి ప్రకటనను ఉంచండి_ఒక చెల్లుబాటు అయ్యే స్థానిక ఈమెయిల్ చిరునామాకు ఈ-మెయిల్ చేయండిసైట్ సెట్టింగ్స్ లో." - content_types: - education_new_reply: - title: "కొత్త వాడుకరి విద్య: మొదటి ప్రత్యుత్తరాలు" - description: "వినియోగదారులు వారి మొదటి రెండు కొత్త ప్రత్యుత్తరాలు టైప్ చేయడం ప్రారంభించిన సమయంలో పాప్‌అప్ మార్గదర్శకత్వం స్వయంచాలకంగా కంపోజర్ పైన కనిపిస్తుంది. " - education_new_topic: - title: "కొత్త వాడుకరి విద్య: మొదటి విషయాలు" - description: "వినియోగదారులు వారి మొదటి రెండు కొత్త విషయాలు టైప్ చేయడం ప్రారంభించిన సమయంలో పాప్‌అప్ మార్గదర్శకత్వం స్వయంచాలకంగా కంపోజర్ పైన కనిపిస్తుంది. " - usage_tips: - title: "కొత్త వాడుకరి మార్గదర్శకత్వం" - description: "కొత్త వాడుకరుల కోసం మార్గదర్శకత్వం మరియు అవసరమైన సమాచారం" - welcome_user: - title: "సుస్వాగతం: కొత్త సభ్యుడు" - welcome_invite: - title: "స్వాగతం : ఆహ్వానించబడిన వాడుకరి" - login_required_welcome_message: - title: "లాగిన్ అవసరం: స్వాగత సందేశం" - description: "'లాగిన్ అవసరం' సెట్టింగ్ ప్రారంభించబడి ఉన్నప్పుడు లాగ్ అవుట్ వినియోగదారులకు స్వాగతసందేశం ప్రదర్శించబడుతుంది." - login_required: - title: "లాగిన్ అవసరం: హోమ్‌పేజ్" - description: "లాగిన్ సైట్లో అవసరం ఉన్నప్పుడు టెక్స్ట్ అనధికార వినియోగదారులకు ప్రదర్శించబడుతుంది." - head: - title: "HTML హెడ్" - description: "ట్యాగ్‌ల లోపల HTML ప్రవేశపెట్టబడింది." - top: - title: "పుటల యొక్క పై భాగంలో" - description: "HTML ప్రతి పేజీ ఎగువన చేర్చబడుతుంది(హెడర్ తర్వాత , దిశా నిర్దేశనానికి ముందు లేదా శీర్షికలో)" - bottom: - title: "పుటల అడుగు భాగం" - description: "ట్యాగ్ చెయ్యక ముందే HTML ను కలుపుతారు." site_settings: censored_words: "వీటితో పదాలు స్వయంచాలకంగా భర్తీ చేయబడతాయి ■■■■" delete_old_hidden_posts: "30 రోజుల కంటే ఎక్కువ దాగివున్న టపాలు స్వయంసిధ్ధంగా తొలగింపబడతాయి." diff --git a/config/locales/server.tr_TR.yml b/config/locales/server.tr_TR.yml index 6d023e345..d4ef2f17f 100644 --- a/config/locales/server.tr_TR.yml +++ b/config/locales/server.tr_TR.yml @@ -86,6 +86,8 @@ tr_TR: not_found: "İstenilen URL ya da kaynak bulunamadı." invalid_access: "İstenilen kaynağı görüntüleyebilmeniz için izniniz yok." read_only_mode_enabled: "Bu site sadece okuma modunda. Etkileşimler etkisizleştirildi." + reading_time: "Okuma Zamanı" + likes: "Beğeniler" too_many_replies: other: "Üzgünüz, yeni kullanıcılar geçici olarak aynı konu içinde sadece %{count} cevap ile sınırlılar. " embed: @@ -817,7 +819,6 @@ tr_TR: tl2_requires_likes_received: "Bir kullanıcının güven seviyesi 2'ye yükseltilmeden önce alması gereken beğeni sayısı." tl2_requires_likes_given: "Bir kullanıcının güven seviyesi 2'ye yükseltilmeden önce vermesi gereken beğeni sayısı." tl2_requires_topic_reply_count: "Bir kullanıcının güven seviyesi 2'ye yükseltilmeden önce cevaplaması gereken konu sayısı." - tl3_requires_days_visited: "Bir kullanıcının güven seviyesi 3'e yükseltimeye hak kazanması için, son 100 günde siteyi ziyaret etmesi gereken en az gün sayısı. (0 - 100)" tl3_requires_topics_replied_to: "Bir kullanıcının güven seviyesi 3'e yükseltilmeye hak kazanması için son 100 günde cevap yazması gereken en az konu sayısı. (0 ya da daha fazla)" tl3_requires_topics_viewed: "Bir kulanıcının güven seviyesi 3'e yükseltilmeye hak kazanması için, son 100 gün içinde oluşturulmuş konulardan görüntülemesi gereken yüzde oranı. (0 ile 100 arası)" tl3_requires_posts_read: "Bir kulanıcının güven seviyesi 3'e yükseltilmeye hak kazanması için, son 100 gün içinde oluşturulmuş gönderilerden görüntülemesi gereken yüzde oranı. (0 ile 100 arası)" @@ -931,7 +932,6 @@ tr_TR: anonymous_account_duration_minutes: "Anonimliği koruyabilmek için, her kulanıcı için her N dakikada bir yeni bir anonim hesap oluştur. Örnek: 600'e ayarlanırsa, son gönderi üzerinden 600 dakika geçer geçmez VE kullanıcı anonim moddaysa, yeni bir anonim hesap oluşturulur." hide_user_profiles_from_public: "Anonim kullanıcılar için kullanıcı kartlarını, kullanıcı profillerini ve kullanıcı dizinini devre dışı bırak" allow_profile_backgrounds: "Kullanıcıların profillerine arkaplan eklemesine izin ver." - sequential_replies_threshold: "Kullanıcının ardarda çok fazla cevap gönderdiğine dair uyarı alması için, bir konuda üstüste yapması gereken gönderi sayısı. " enable_mobile_theme: "Mobil cihazlar mobil uyumlu temayı kullanır, dilerse masaüstü görünüme geçebilirler. Eğer özel, duyarlı bir stil kullanıyorsanız bunu devredışı bırakın." dominating_topic_minimum_percent: "Konuyu domine ettiğine dair uyarı almadan önce konudaki gönderilerin yüzde kaçının kullanıcıya ait olması gerekir." daily_performance_report: "NGINX kayıtlarını analiz edip detaylı bir şekilde günlük Yetkili kategorisinde bir konu içerisinde paylaş." @@ -1340,17 +1340,8 @@ tr_TR: Daha fazla bilgi için lütfen [topluluk yönergelerine](%{base_url}/guidelines) bakın. user_automatically_blocked: subject_template: "Topluluk bayrakları nedeniyle yeni kullanıcı %{username} engellendi" - text_body_template: | - Bu bir otomatik mesajdır. - - Yeni kullanıcı [%{username}](%{base_url}%{user_url}), gönderisi(leri) farklı kullanıcılar tarafından bayrakladığı için otomatik olarak engellendi. - - Lütfen [bayrakları inceleyin](%{base_url}/admin/flags). %{username} adlı kullancının haksız yere gönderi oluşturabilmesi engellendiyse, [bu kullanıcıya ait admin sayfasında](%{base_url}%{user_url}) engeli kaldır butonuna tıklayın. - - Bu tür engellemeleri tetikleyen eşik, site ayarları sayfasında `block_new_user` bölümünden değiştirilebilir. spam_post_blocked: subject_template: "Üst üste aynı bağlantıların paylaşılmasından ötürü %{username} adlı yeni kullanıcının gönderileri engelledi" - text_body_template: "Bu bir otomatik mesajdır.\n\n[%{username}](%{base_url}%{user_url}) adlı yeni kullanıcı %{domains} sitelerine bağlantı içeren birçok farklı gönderi oluşturmaya çalıştı, ancak bu gönderiler spam yaratmamaları için engellendi. Kullanıcı hala %{domains} sitelerine bağlantılı içermeyen yeni gönderiler oluşturabiliyor. \n\nLütfen [kullanıcıyı inceleyin](%{base_url}%{user_url}).\n\nBu tür engellemeleri tetikleyen `newuser_spam_host_threshold` ve `white_listed_spam_host_domains` değerleri site ayarları sayfasından değiştirilebilir.\n" unblocked: subject_template: "Hesabın engeli kaldırıldı" text_body_template: | @@ -1366,13 +1357,6 @@ 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 konu hakkında bildirim almak istemiyorsanız, [buraya tıklayın](%{unsubscribe_url}). - - - Bu e-postaları almak istemiyorsanız [kullanıcı tercihlerinizden](%{user_preferences_url}) değiştirin. - - subject_re: "Cvp:" subject_pm: "[ÖM]" user_notifications: @@ -1380,8 +1364,6 @@ tr_TR: unsubscribe: title: "Aboneliği İptal Et" description: "Bu e-postalarla ilgilenmiyor musunuz? Sorun değil! Aşağıya tıklayarak aboneliğinizi hemen iptal edebilirsiniz:" - reply_by_email: "Karşılık vermek için, bu e-postayı cevaplayın ya da [visit the topic](%{base_url}%{url}) sayfasını internet tarayıcınızda ziyaret edin." - visit_link_to_respond: "Karşılık vermek için, [visit the topic](%{base_url}%{url}) adresini tarayıcınızda ziyaret edin." posted_by: "%{post_date} tarihinde %{username} tarafından gönderildi" user_invited_to_private_message_pm: subject_template: "[%{site_name}] %{username} sizi bir mesaja davet etti '%{topic_title}'" @@ -1415,69 +1397,16 @@ tr_TR: Mesajı görüntülemek için lütfen bu bağlantıyı ziyaret edin: %{base_url}%{url} user_replied: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_quoted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - - %{context} - - - --- - - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - - %{context} - - --- - - %{respond_instructions} user_group_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - - %{context} - - - --- - - %{respond_instructions} user_posted_pm: subject_template: "[%{site_name}] [ÖM] %{topic_title}" - text_body_template: | - %{message} - - - %{context} - - - --- - - %{respond_instructions} digest: why: "%{last_seen_at} tarihindeki son girişinizden beri %{site_link} sitesine olanların kısa bir özeti" subject_template: "[%{site_name}] Özet" diff --git a/config/locales/server.uk.yml b/config/locales/server.uk.yml index 93e45e59e..c8a331265 100644 --- a/config/locales/server.uk.yml +++ b/config/locales/server.uk.yml @@ -15,8 +15,6 @@ uk: posts: "дописи" loading: "Завантаження" powered_by_html: 'Створено за допомогою технології Discourse, бажано переглядати з увімкненим JavaScript' - via: "%{username} через %{site_name}" - is_reserved: "is reserved" backup: operation_already_running: "An operation is currently running. Can't start a new job right now." backup_file_should_be_tar_gz: "The backup file should be a .tar.gz archive." @@ -140,7 +138,6 @@ uk: notify_user: email_body: "%{link}\n\n%{message}" notify_moderators: - email_title: 'Допис у темі "%{title}" потребує уваги модератора' email_body: "%{link}\n\n%{message}" bookmark: title: 'Лишити закладку' @@ -261,31 +258,6 @@ uk: 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.' 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.' consumer_email_warning: "Your site is configured to use Gmail (or another consumer email service) to send 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: - title: "Навчання новачка: Перші відповіді" - description: "Спливні настанови, що одразу автоматично відображаються над редактором, коли новий користувач починає вводити свої перші дві нові відповіді." - education_new_topic: - title: "Навчання новачка: Перші теми" - description: "Спливні настанови, що одразу автоматично відображаються над редактором, коли новий користувач починає вводити свої перші дві нові теми." - welcome_user: - title: "Вітання: Новий користувач" - welcome_invite: - title: "Вітання: Запрошений користувач" - login_required_welcome_message: - title: "Потрібно увійти: Вітальне повідомлення" - description: "Вітальне повідомлення, що відображається для користувачів, що вийшли, коли налаштування 'потрібен вхід' увімкнено." - login_required: - title: "Потрібно увійти: Головна сторінка" - description: "Текст, що відображається для неавторизованих користувачів, коли потрібен вхід на сайт." - head: - title: "HTML head" - description: "HTML-код, що буде вставлятися всередину теґів ." - top: - title: "Верхній колонтитул сторінок" - description: "HTML-код, що буде додаватися на початку кожної сторінки (після заголовка, перед навігацією або назвою теми)." - bottom: - title: "Нижній колонтитул сторінок" site_settings: default_locale: "Мова за замовчуванням для цього екземпляра Discourse (Код ISO 639-1)" allow_user_locale: "Allow users to choose their own language interface preference" @@ -449,54 +421,17 @@ uk: unsubscribe: title: "Відпісатися" description: "Не зацікавлені в отриманні цих листів? Немає проблем! Натисніть нижче, щоб відписатися назавжди:" - reply_by_email: "Щоб відповісти, надішліть відповідь на цей лист або відвідайте %{base_url}%{url} у своєму веб-оглядачі." - visit_link_to_respond: "Щоб відповісти, відвідайте %{base_url}%{url} у своєму веб-оглядачі." posted_by: "Опубліковано %{username} %{post_date}" user_replied: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_quoted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_mentioned: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted: subject_template: "[%{site_name}] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} user_posted_pm: subject_template: "[%{site_name}] [ПП] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} digest: new_activity: "Нова активність у ваших темах та дописах:" top_topics: "Популярні дописи" diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml index bf7b1c7c4..d9506c7f1 100644 --- a/config/locales/server.zh_CN.yml +++ b/config/locales/server.zh_CN.yml @@ -86,6 +86,8 @@ zh_CN: not_found: "请求的 URL 或资源未找到。" invalid_access: "你没有权限查看请求的资源。" read_only_mode_enabled: "这个站点正处于只读模式。交互功能已禁用。" + reading_time: "阅读时间" + likes: "赞" too_many_replies: other: "我们非常抱歉,但是新用户被临时限制为在同一个主题只能回复 %{count} 次" embed: @@ -840,7 +842,7 @@ zh_CN: tl2_requires_likes_received: "一个初级用户升级到信任等级2所需要获得的赞数。" tl2_requires_likes_given: "一个初级用户升级到信任等级2所需要给出的赞数。" tl2_requires_topic_reply_count: "一个初级用户升级到信任等级2所需要回复的主题数量。" - tl3_requires_days_visited: "在最近 100 天内升至信任等级3所需的访问站点的天数。(0到100)" + tl3_time_period: "3级信任等级时间期" tl3_requires_topics_replied_to: "在最近 100 天内升至信任等级3所需的回复主题的最小数量。(0或更高)" tl3_requires_topics_viewed: "在最近 100 天内升至信任等级3所需的创建主题的百分比。(0到100)" tl3_requires_posts_read: "在最近 100 天内升信任等级3所需的创建帖子的百分比。(0到100)" @@ -915,6 +917,7 @@ zh_CN: disable_emails: "禁止 Discourse 发送任何邮件" strip_images_from_short_emails: "从邮件中除去小于 2800 比特的图片" short_email_length: "短邮件地址长度(以比特作为单位)" + display_name_on_email_from: "在Email栏显示全名" pop3_polling_enabled: "轮询 POP3 收取邮件回复。" pop3_polling_ssl: "连接至 POP3 服务器时使用 SSL。(推荐)" pop3_polling_period_mins: "查询用于邮件的 POP3 账户的间隔(以分钟计)。注意:需要重新启动。" @@ -953,7 +956,6 @@ zh_CN: anonymous_account_duration_minutes: "为了匿名性,为每个用户每 N 分钟创建一个匿名账户。例如:如果设置为 600,只要发帖后 600 分钟到了,并且用户切换至了匿名模式,就会创建一个新的匿名账户。" hide_user_profiles_from_public: "对来访用户关闭关闭用户信息卡,用户资料和用户目录。" allow_profile_backgrounds: "允许用户上传个人资料背景图片。" - sequential_replies_threshold: "用户可以在一个主题内多次回复而不被提醒连续发送多个回复的通知。" enable_mobile_theme: "为移动设备启用移动友好的主题,但也能切换回完整站点。如果你想要使用自定义的响应式主题请禁用它。" dominating_topic_minimum_percent: "用户在主题中的帖子占到多少百分比时使得用户主导话题。" daily_performance_report: "每日分析 NGINX 日志并且发布详情主题到职员才能看到的主题" @@ -1547,24 +1549,8 @@ zh_CN: 欲查看额外的指导,请查看我们的[社群指引](%{base_url}/guidelines)。 user_automatically_blocked: subject_template: "因标记而被封禁的新用户 %{username}" - text_body_template: | - 这是一封自动发出的邮件。 - - 新用户 [%{username}](%{base_url}%{user_url}) 已因多位用户标记 %{username} 的帖子而被自动封禁。 - - 请[审核这些标记](%{base_url}/admin/flags)。如果 %{username} 被不正确地封禁了编辑功能,点击[该用户的管理页面](%{base_url}%{user_url})上的解封按钮。 - - 该阈值可以通过站点设置中的 `block_new_user` 更改。 spam_post_blocked: subject_template: "新用户 %{username} 因重复发布链接而被禁止发表相关帖子" - text_body_template: | - 这是一封自动发出的邮件。 - - 新用户 [%{username}](%{base_url}%{user_url}) 已因创建多个链接至 %{domains} 的帖子而被封禁以防止垃圾信息。但是用户仍能够发表不包含到 %{domains} 的帖子。 - - 请[审核这些用户](%{base_url}%{user_url})。 - - 该阈值可以通过站点设置中的 `newuser_spam_host_threshold` 和 `white_listed_spam_host_domains` 更改。 unblocked: subject_template: "账户解封 unblocked" text_body_template: | @@ -1590,7 +1576,8 @@ zh_CN: unsubscribe: title: "取消订阅" description: "不再对这些邮件感兴趣?没问题!点击下面按钮来立即取消订阅:" - visit_link_to_respond: "在浏览器中访问[visit the topic](%{base_url}%{url}),以回复。" + reply_by_email: "回复邮件或访问[visit the topic](%{base_url}%{url}),以回复。" + visit_link_to_respond: "回复,[visit the topic](%{base_url}%{url})。" posted_by: "%{username}发表于%{post_date}" user_invited_to_private_message_pm: subject_template: "[%{site_name}] %{username} 邀请你加入消息交流:'%{topic_title}'" @@ -1607,6 +1594,8 @@ zh_CN: > %{site_title} -- %{site_description} 请访问 %{base_url}%{url} 来查看该主题。 + user_invited_to_private_message_pm_staged: + subject_template: "[%{site_name}] %{username} 邀请你加入消息交流:'%{topic_title}'" user_invited_to_topic: subject_template: "[%{site_name}] %{username} 邀请你至主题:'%{topic_title}'" text_body_template: |2 @@ -1625,6 +1614,8 @@ zh_CN: user_replied: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1634,6 +1625,8 @@ zh_CN: user_quoted: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1643,6 +1636,8 @@ zh_CN: user_mentioned: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1652,6 +1647,8 @@ zh_CN: user_group_mentioned: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1661,6 +1658,8 @@ zh_CN: user_posted: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} @@ -1670,10 +1669,20 @@ zh_CN: user_posted_pm: subject_template: "[%{site_name}] [PM] %{topic_title}" text_body_template: | + %{header_instructions} + %{message} %{context} + --- + %{respond_instructions} + user_posted_pm_staged: + subject_template: "%{optional_re}%{topic_title}" + text_body_template: |2 + + %{message} + --- %{respond_instructions} digest: diff --git a/config/locales/server.zh_TW.yml b/config/locales/server.zh_TW.yml index 5b7e7cc7a..93a50b5e7 100644 --- a/config/locales/server.zh_TW.yml +++ b/config/locales/server.zh_TW.yml @@ -746,7 +746,6 @@ zh_TW: public_user_custom_fields: "用戶可設定公開顯示的自定欄位白名單。" staff_user_custom_fields: "用戶可設定只給管理員顯示的自定欄位白名單。" allow_profile_backgrounds: "允許使用者上傳個人檔案背景圖片" - sequential_replies_threshold: "用戶可以在一個主題內多次回覆而不被提醒連續發送多個回覆的通知。" suppress_uncategorized_badge: "不在\"無分類\"的話題列表內顯示徽章。" invites_per_page: "默認在用戶頁顯示的邀請。" autohighlight_all_code: "即使未顯示特定語言,仍為所有預編排程式套用程式碼顏色提示" @@ -894,24 +893,8 @@ zh_TW: subject_template: "帳號已被封鎖" user_automatically_blocked: subject_template: "新用戶 %{username} 由於社群的投訴已被封鎖" - text_body_template: | - 這是一則自動產生的訊息。 - - 由於文章受到多位用戶的投訴,新用戶 [%{username}](%{base_url}%{user_url}) 已被自動封鎖。 - - 請[審查那些投訴](%{base_url}/admin/flags)。如果 %{username} 是被錯誤地封鎖,請在[該用戶的管理頁面](%{base_url}%{user_url})中點擊解鎖按鈕。 - - 此門檻可在 `block_new_user` 網站設定中更改。 spam_post_blocked: subject_template: "新用戶 %{username} 由於重複貼連結,文章已被封鎖" - text_body_template: | - 這是一則自動產生的訊息。 - - 新用戶 [%{username}](%{base_url}%{user_url}) 曾嘗試發表多篇內含連結至 %{domains} 的文章。為避免垃圾內容,那些文章已被封鎖。該用戶仍然可以發表不含連結至 %{domains} 的新文章。 - - 請[審查該用戶](%{base_url}%{user_url})。 - - 相關設定可在 `newuser_spam_host_threshold` 和 `white_listed_spam_host_domains` 網站設定中更改。 unblocked: subject_template: "帳號解除封鎖" pending_users_reminder: @@ -931,53 +914,14 @@ zh_TW: subject_template: "[%{site_name}] %{username} 邀請你參與討論 '%{topic_title}'" user_replied: subject_template: "[%{site_name}] %{username} 在 '%{topic_title}' 討論話題回覆了你的文章" - text_body_template: | - %{username} 在 %{site_name} 的 '%{topic_title}' 討論話題中回覆了你的文章: - - --- - %{message} - - --- - 請瀏覽 %{base_url}%{url} 來回覆。 user_quoted: subject_template: "[%{site_name}] %{username} 在 '%{topic_title}' 討論話題引用了你的內容" - text_body_template: | - %{username} 在 %{site_name} 的 '%{topic_title}' 討論話題中引用了你的內容: - - --- - %{message} - - --- - 請瀏覽 %{base_url}%{url} 來回覆。 user_mentioned: subject_template: "[%{site_name}] %{username} 在 '%{topic_title}' 討論話題提及了你" - text_body_template: | - %{username} 在 %{site_name} 的 '%{topic_title}' 討論話題中提及到了你: - - --- - %{message} - - --- - 請瀏覽 %{base_url}%{url} 來回覆。 user_posted: subject_template: "[%{site_name}] %{subject_prefix}%{username} 在 '%{topic_title}' 討論話題發表了文章" - text_body_template: | - %{username} 在 %{site_name} 的 '%{topic_title}' 討論話題中發表了文章: - - --- - %{message} - - --- - 請瀏覽 %{base_url}%{url} 來回覆。 user_posted_pm: subject_template: "[%{site_name}] [PM] %{topic_title}" - text_body_template: | - %{message} - - %{context} - - --- - %{respond_instructions} digest: why: "在你上一次於 %{last_seen_at} 訪問後,在 %{site_link} 上的摘要。" new_activity: "在你的討論話題和文章裡的動態:" diff --git a/config/nginx.sample.conf b/config/nginx.sample.conf index 930b2c161..86e05d98e 100644 --- a/config/nginx.sample.conf +++ b/config/nginx.sample.conf @@ -91,6 +91,9 @@ server { root $public; add_header ETag ""; + # auth_basic on; + # auth_basic_user_file /etc/nginx/htpasswd; + location ~* assets/.*\.(eot|ttf|woff|woff2|ico)$ { expires 1y; add_header Cache-Control public; @@ -135,7 +138,6 @@ server { # # proxy_set_header DOES NOT inherit, by design, we must repeat it, # otherwise headers are not set correctly -# proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -214,8 +216,22 @@ server { proxy_cache_key $uri; proxy_cache_valid 200 7d; proxy_cache_valid 404 1m; + proxy_set_header Connection ""; proxy_pass https://avatars.discourse.org/; + break; + } + + # we need buffering off for message bus + location /message-bus/ { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $thescheme; + proxy_http_version 1.1; + proxy_buffering off; + proxy_pass http://discourse; + break; } # this means every file in public is tried first diff --git a/config/routes.rb b/config/routes.rb index 638396dd3..daf6ba56b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -119,9 +119,10 @@ Discourse::Application.routes.draw do resources :email, constraints: AdminConstraint.new do collection do post "test" - get "all" get "sent" get "skipped" + get "received" + get "rejected" get "preview-digest" => "email#preview_digest" post "handle_mail" end @@ -254,7 +255,7 @@ Discourse::Application.routes.draw do get "guidelines" => "static#show", id: "guidelines", as: 'guidelines' get "tos" => "static#show", id: "tos", as: 'tos' get "privacy" => "static#show", id: "privacy", as: 'privacy' - get "signup" => "list#latest" + get "signup" => "static#show", id: "signup" get "login-preferences" => "static#show", id: "login" get "users/admin-login" => "users#admin_login" @@ -266,6 +267,7 @@ Discourse::Application.routes.draw do get "users/search/users" => "users#search_users" get "users/account-created/" => "users#account_created" get "users/password-reset/:token" => "users#password_reset" + get "users/confirm-email-token/:token" => "users#confirm_email_token", constraints: { format: 'json' } put "users/password-reset/:token" => "users#password_reset" get "users/activate-account/:token" => "users#activate_account" put "users/activate-account/:token" => "users#perform_account_activation", as: 'perform_activate_account' @@ -300,16 +302,19 @@ Discourse::Application.routes.draw do put "users/:username/preferences/card-badge" => "users#update_card_badge", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/staff-info" => "users#staff_info", constraints: {username: USERNAME_ROUTE_FORMAT} + get "users/:username/summary" => "users#summary", constraints: {username: USERNAME_ROUTE_FORMAT} + get "users/:username/invited" => "users#invited", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/invited_count" => "users#invited_count", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/invited/:filter" => "users#invited", constraints: {username: USERNAME_ROUTE_FORMAT} post "users/action/send_activation_email" => "users#send_activation_email" + get "users/:username/summary" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/activity" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/activity/:filter" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/badges" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/notifications" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/notifications/:filter" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} - get "users/:username/pending" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} + get "users/:username/activity/pending" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT} delete "users/:username" => "users#destroy", constraints: {username: USERNAME_ROUTE_FORMAT} # The external_id constraint is to allow periods to be used in the value without becoming part of the format. ie: foo.bar.json get "users/by-external/:external_id" => "users#show", constraints: {external_id: /[^\/]+/} @@ -425,11 +430,12 @@ Discourse::Application.routes.draw do get "c/:parent_category/:category.rss" => "list#category_feed", format: :rss get "c/:category" => "list#category_latest" get "c/:category/none" => "list#category_none_latest" - get "c/:parent_category/:category" => "list#parent_category_category_latest" + get "c/:parent_category/:category/(:id)" => "list#parent_category_category_latest", constraints: { id: /\d+/ } get "c/:category/l/top" => "list#category_top", as: "category_top" get "c/:category/none/l/top" => "list#category_none_top", as: "category_none_top" get "c/:parent_category/:category/l/top" => "list#parent_category_category_top", as: "parent_category_category_top" + get "category_hashtags/check" => "category_hashtags#check" TopTopic.periods.each do |period| get "top/#{period}" => "list#top_#{period}" diff --git a/config/site_settings.yml b/config/site_settings.yml index ff7f6173c..b4d132179 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -105,7 +105,7 @@ basic: post_menu: client: true type: list - default: 'like-count|like|share|flag|edit|bookmark|delete|admin|reply' + default: 'like-count|like|share|flag|edit|bookmark|wiki|delete|admin|reply' choices: - like-count - like @@ -116,10 +116,11 @@ basic: - bookmark - admin - reply + - wiki post_menu_hidden_items: client: true type: list - default: 'bookmark|edit|delete|admin' + default: 'bookmark|edit|wiki|delete|admin' choices: - like - edit @@ -129,6 +130,7 @@ basic: - bookmark - admin - reply + - wiki share_links: client: true type: list @@ -325,9 +327,11 @@ users: show_email_on_profile: client: true default: false - email_token_valid_hours: 24 + email_token_valid_hours: + default: 24 + min: 1 email_token_grace_period_hours: 0 - purge_unactivated_users_grace_period_days: 7 + purge_unactivated_users_grace_period_days: 14 public_user_custom_fields: type: list default: '' @@ -531,6 +535,10 @@ email: short_email_length: 2800 display_name_on_email_from: default: true + unsubscribe_via_email: + default: true + unsubscribe_via_email_footer: + default: false files: max_image_size_kb: 3072 @@ -623,6 +631,9 @@ trust: min_trust_to_edit_wiki_post: default: 1 enum: 'TrustLevelSetting' + min_trust_to_allow_self_wiki: + default: 3 + enum: 'TrustLevelSetting' min_trust_to_send_messages: default: 1 enum: 'TrustLevelSetting' @@ -792,6 +803,15 @@ developer: verbose_localization: default: false client: true + top_topics_formula_log_views_multiplier: + default: 2 + min: 0 + top_topics_formula_first_post_likes_multiplier: + default: 0.5 + min: 0 + top_topics_formula_least_likes_per_post_multiplier: + default: 3 + min: 0 migrate_to_new_scheme: hidden: true default: false @@ -799,8 +819,6 @@ developer: default: 500 client: true hidden: true - allow_staged_accounts: - default: false embedding: feed_polling_enabled: diff --git a/db/migrate/20151109124147_drop_group_managers.rb b/db/migrate/20151109124147_drop_group_managers.rb index 2e197cfb5..41e07141c 100644 --- a/db/migrate/20151109124147_drop_group_managers.rb +++ b/db/migrate/20151109124147_drop_group_managers.rb @@ -10,6 +10,6 @@ class DropGroupManagers < ActiveRecord::Migration end def down - raise ActiveRecord::IrriversableMigration + raise ActiveRecord::IrreversibleMigration end end diff --git a/db/migrate/20160108051129_fix_incorrect_user_history.rb b/db/migrate/20160108051129_fix_incorrect_user_history.rb new file mode 100644 index 000000000..2a9cbdf0f --- /dev/null +++ b/db/migrate/20160108051129_fix_incorrect_user_history.rb @@ -0,0 +1,34 @@ +class FixIncorrectUserHistory < ActiveRecord::Migration + def up + # see https://meta.discourse.org/t/old-user-suspension-reasons-have-gone-missing/3730 + # we had a window of 21 days where all user history records with action > 5 were off by one + # + # to correct we are doing this https://meta.discourse.org/t/enums-that-are-used-in-tables-need-to-be-stable/37622 + # + # This migration hunts for date stuff started going wrong and date it started being good and corrects the data + + + # this is a :auto_trust_level_change mislabled as :check_email + # impersonate that was actually delete topic + condition = < 5 AND id >= #{first_wrong_id} AND id <= #{last_wrong_id}") + + execute("INSERT INTO user_histories(action, acting_user_id, details, created_at, updated_at) + VALUES (22, -1, '#{msg}', current_timestamp, current_timestamp)") + end + end + + def down + end +end diff --git a/db/migrate/20160110053003_archive_system_messages_with_no_replies.rb b/db/migrate/20160110053003_archive_system_messages_with_no_replies.rb new file mode 100644 index 000000000..da9b1d71c --- /dev/null +++ b/db/migrate/20160110053003_archive_system_messages_with_no_replies.rb @@ -0,0 +1,24 @@ +class ArchiveSystemMessagesWithNoReplies < ActiveRecord::Migration + def up + # backdate archival of system messages send on behalf of site_contact_user + execute <]+)>', 'im'), '') + , users.email + , array_to_string(regexp_matches(array_to_string(regexp_matches(posts.raw_email, '^to:.+$', 'im'), ''), '[^<\s"''(]+@[^>\s"'')]+'), '') + , topics.title + FROM posts + JOIN topics ON posts.topic_id = topics.id + JOIN users ON posts.user_id = users.id + WHERE posts.user_id IS NOT NULL + AND posts.topic_id IS NOT NULL + AND posts.via_email = 't' + AND posts.raw_email ~* 'Message-Id' + ORDER BY posts.id; + SQL + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/discourse.sublime-project b/discourse.sublime-project index 20854867b..6ec5f18c5 100644 --- a/discourse.sublime-project +++ b/discourse.sublime-project @@ -16,9 +16,7 @@ { "path": "script" }, { "path": "spec" }, { "path": "vendor" }, - { "path": "test", - "folder_exclude_patterns": ["fixtures"] - } + { "path": "test" }, ], "settings": { diff --git a/docs/INSTALL-cloud.md b/docs/INSTALL-cloud.md index d3387f1df..0aa946fa9 100644 --- a/docs/INSTALL-cloud.md +++ b/docs/INSTALL-cloud.md @@ -124,8 +124,7 @@ Commands: stop: Stop a running container restart: Restart a container destroy: Stop and remove a container - enter: Use nsenter to enter a container - ssh: Start a bash shell in a running container + enter: Enter a container using docker exec logs: Docker logs for container bootstrap: Bootstrap a container for the config based on a template rebuild: Rebuild a container (destroy old, bootstrap, start new) diff --git a/lib/backup_restore/backuper.rb b/lib/backup_restore/backuper.rb index 0b3b26656..b55d73590 100644 --- a/lib/backup_restore/backuper.rb +++ b/lib/backup_restore/backuper.rb @@ -258,7 +258,7 @@ module BackupRestore end log "Gzipping archive..." - `gzip #{tar_filename}` + `gzip -5 #{tar_filename}` end def after_create_hook diff --git a/lib/cooked_post_processor.rb b/lib/cooked_post_processor.rb index 8b984b78f..147824d82 100644 --- a/lib/cooked_post_processor.rb +++ b/lib/cooked_post_processor.rb @@ -276,6 +276,14 @@ class CookedPostProcessor end def optimize_urls + # when login is required, attachments can't be on the CDN + if SiteSetting.login_required + @doc.css("a.attachment[href]").each do |a| + href = a["href"].to_s + a["href"] = UrlHelper.schemaless UrlHelper.absolute(href, nil) if UrlHelper.is_local(href) + end + end + %w{href data-download-href}.each do |selector| @doc.css("a[#{selector}]").each do |a| href = a["#{selector}"].to_s diff --git a/lib/disk_space.rb b/lib/disk_space.rb index 18f4d1a9f..7a9734843 100644 --- a/lib/disk_space.rb +++ b/lib/disk_space.rb @@ -57,7 +57,7 @@ class DiskSpace protected def self.free(path) - `df -Pk #{path} | awk 'NR==2 {print $4 * 1024;}'`.to_i + `df -Pk #{path} | awk 'NR==2 {print $4;}'`.to_i * 1024 end def self.used(path) diff --git a/lib/email/message_builder.rb b/lib/email/message_builder.rb index b1cf2bd57..f5d1ed58a 100644 --- a/lib/email/message_builder.rb +++ b/lib/email/message_builder.rb @@ -63,8 +63,16 @@ module Email if @opts[:add_unsubscribe_link] unsubscribe_link = PrettyText.cook(I18n.t('unsubscribe_link', template_args), sanitize: false).html_safe html_override.gsub!("%{unsubscribe_link}", unsubscribe_link) + + if SiteSetting.unsubscribe_via_email_footer && @opts[:add_unsubscribe_via_email_link] + unsubscribe_via_email_link = PrettyText.cook(I18n.t('unsubscribe_via_email_link', hostname: Discourse.current_hostname), sanitize: false).html_safe + html_override.gsub!("%{unsubscribe_via_email_link}", unsubscribe_via_email_link) + else + html_override.gsub!("%{unsubscribe_via_email_link}", "") + end else html_override.gsub!("%{unsubscribe_link}", "") + html_override.gsub!("%{unsubscribe_via_email_link}", "") end header_instructions = @template_args[:header_instructions] @@ -103,6 +111,9 @@ module Email if @opts[:add_unsubscribe_link] body << "\n" body << I18n.t('unsubscribe_link', template_args) + if SiteSetting.unsubscribe_via_email_footer && @opts[:add_unsubscribe_via_email_link] + body << I18n.t('unsubscribe_via_email_link', hostname: Discourse.current_hostname) + end end body diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb index bb2ad455a..65788fe00 100644 --- a/lib/email/receiver.rb +++ b/lib/email/receiver.rb @@ -1,144 +1,201 @@ -require_dependency 'new_post_manager' -require_dependency 'email/html_cleaner' -require_dependency 'post_action_creator' +require_dependency "new_post_manager" +require_dependency "post_action_creator" +require_dependency "email/html_cleaner" module Email class Receiver - include ActionView::Helpers::NumberHelper + class ProcessingError < StandardError; end + class EmptyEmailError < ProcessingError; end + class NoMessageIdError < ProcessingError; end + class AutoGeneratedEmailError < ProcessingError; end + class NoBodyDetectedError < ProcessingError; end + class InactiveUserError < ProcessingError; end + class BadDestinationAddress < ProcessingError; end + class StrangersNotAllowedError < ProcessingError; end + class InsufficientTrustLevelError < ProcessingError; end + class ReplyUserNotMatchingError < ProcessingError; end + class TopicNotFoundError < ProcessingError; end + class TopicClosedError < ProcessingError; end + class InvalidPost < ProcessingError; end + class InvalidPostAction < ProcessingError; end - class ProcessingError < StandardError; end - class EmailUnparsableError < ProcessingError; end - class EmptyEmailError < ProcessingError; end - class UserNotFoundError < ProcessingError; end - class UserNotSufficientTrustLevelError < ProcessingError; end - class BadDestinationAddress < ProcessingError; end - class TopicNotFoundError < ProcessingError; end - class TopicClosedError < ProcessingError; end - class AutoGeneratedEmailError < ProcessingError; end - class EmailLogNotFound < ProcessingError; end - class InvalidPost < ProcessingError; end - class ReplyUserNotFoundError < ProcessingError; end - class ReplyUserNotMatchingError < ProcessingError; end - class InactiveUserError < ProcessingError; end - class InvalidPostAction < ProcessingError; end - - attr_reader :body, :email_log - - def initialize(raw, opts=nil) - @raw = raw - @opts = opts || {} + def initialize(mail_string) + raise EmptyEmailError if mail_string.blank? + @raw_email = mail_string + @mail = Mail.new(@raw_email) + raise NoMessageIdError if @mail.message_id.blank? end def process - raise EmptyEmailError if @raw.blank? - - @message = Mail.new(@raw) - - raise AutoGeneratedEmailError if @message.header.to_s =~ /auto-(replied|generated)/ - - @body = parse_body(@message) - - # 'smtp_envelope_to' is a combination of: to, cc and bcc fields - # prioriziting the `:reply` types - dest_infos = @message.smtp_envelope_to - .map { |to_address| check_address(to_address) } - .compact - .sort do |a, b| - if a[:type] == :reply && b[:type] != :reply - 1 - elsif a[:type] != :reply && b[:type] == :reply - -1 - else - 0 - end - end - - raise BadDestinationAddress if dest_infos.empty? - - from = @message[:from].address_list.addresses.first - user_email = from.address - user_name = from.display_name - - user = User.find_by_email(user_email) - raise InactiveUserError if user.present? && !user.active && !user.staged - - # TODO: take advantage of all the "TO"s - dest_info = dest_infos[0] - case dest_info[:type] - when :group - group = dest_info[:obj] - - if user.blank? - if SiteSetting.allow_staged_accounts - user = create_staged_account(user_email, user_name) - else - wrap_body_in_quote(user_email) - user = Discourse.system_user - end - end - - create_new_topic(user, archetype: Archetype.private_message, target_group_names: [group.name]) - when :category - category = dest_info[:obj] - - if user.blank? && category.email_in_allow_strangers - if SiteSetting.allow_staged_accounts - user = create_staged_account(user_email) - else - wrap_body_in_quote(user_email) - user = Discourse.system_user - end - end - - raise UserNotFoundError if user.blank? - raise UserNotSufficientTrustLevelError.new(user) unless category.email_in_allow_strangers || user.has_trust_level?(TrustLevel[SiteSetting.email_in_min_trust.to_i]) - - create_new_topic(user, category: category.id) - when :reply - @email_log = dest_info[:obj] - - raise EmailLogNotFound if @email_log.blank? - raise TopicNotFoundError if Topic.find_by_id(@email_log.topic_id).nil? - raise TopicClosedError if Topic.find_by_id(@email_log.topic_id).closed? - raise ReplyUserNotFoundError if user.blank? - raise ReplyUserNotMatchingError if @email_log.user_id != user.id - - if post_action_type = post_action_for(@body) - create_post_action(@email_log, post_action_type) - else - create_reply(@email_log) - end - end - - rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e - raise EmailUnparsableError.new(e) + @incoming_email = find_or_create_incoming_email + process_internal + rescue => e + @incoming_email.update_columns(error: e.to_s) + raise end - def create_staged_account(email, name=nil) - User.create( - email: email, - username: UserNameSuggester.suggest(name.presence || email), - name: name.presence || User.suggest_name(email), - staged: true, - ) + def find_or_create_incoming_email + IncomingEmail.find_or_create_by(message_id: @mail.message_id) do |incoming_email| + incoming_email.raw = @raw_email + incoming_email.subject = @mail.subject + incoming_email.from_address = @mail.from.first.downcase + incoming_email.to_addresses = @mail.to.map(&:downcase).join(";") if @mail.to.present? + incoming_email.cc_addresses = @mail.cc.map(&:downcase).join(";") if @mail.cc.present? + end + end + + def process_internal + raise AutoGeneratedEmailError if is_auto_generated? + + body = select_body || "" + + raise NoBodyDetectedError if body.blank? && !@mail.has_attachments? + + user = find_or_create_user(from) + + @incoming_email.update_columns(user_id: user.id) + + raise InactiveUserError if !user.active && !user.staged + + if action = subscription_action_for(body, @mail.subject) + message = SubscriptionMailer.send(action, user) + Email::Sender.new(message, :subscription).send + elsif post = find_related_post + create_reply(user: user, raw: body, post: post, topic: post.topic) + else + destination = destinations.first + + raise BadDestinationAddress if destination.blank? + + case destination[:type] + when :group + group = destination[:obj] + create_topic(user: user, raw: body, title: @mail.subject, archetype: Archetype.private_message, target_group_names: [group.name], skip_validations: true) + when :category + category = destination[:obj] + + raise StrangersNotAllowedError if user.staged? && !category.email_in_allow_strangers + raise InsufficientTrustLevelError if !user.has_trust_level?(SiteSetting.email_in_min_trust) + + create_topic(user: user, raw: body, title: @mail.subject, category: category.id) + when :reply + email_log = destination[:obj] + + raise ReplyUserNotMatchingError if email_log.user_id != user.id + + create_reply(user: user, raw: body, post: email_log.post, topic: email_log.post.topic) + end + end + end + + def is_auto_generated? + @mail.return_path.blank? || + @mail[:precedence].to_s[/list|junk|bulk|auto_reply/] || + @mail.header.to_s[/auto-(submitted|replied|generated)/] + end + + def select_body + text = nil + html = nil + + if @mail.multipart? + text = fix_charset(@mail.text_part) + html = fix_charset(@mail.html_part) + elsif @mail.content_type.to_s["text/html"] + html = fix_charset(@mail) + else + text = fix_charset(@mail) + end + + # prefer text over html + if text.present? + text_encoding = text.encoding + text = DiscourseEmailParser.parse_reply(text) + text = try_to_encode(text, text_encoding) + return text if text.present? + end + + # clean the html if that's all we've got + if html.present? + html_encoding = html.encoding + html = Email::HtmlCleaner.new(html).output_html + html = DiscourseEmailParser.parse_reply(html) + html = try_to_encode(html, html_encoding) + return html if html.present? + end + end + + def fix_charset(mail_part) + return nil if mail_part.blank? || mail_part.body.blank? + + string = mail_part.body.to_s + + # TODO (use charlock_holmes to properly detect encoding) + + # 1) use the charset provided + if mail_part.charset.present? + fixed = try_to_encode(string, mail_part.charset) + return fixed if fixed.present? + end + + # 2) default to UTF-8 + try_to_encode(string, "UTF-8") + end + + def try_to_encode(string, encoding) + string.encode("UTF-8", encoding) + rescue Encoding::InvalidByteSequenceError, Encoding::UndefinedConversionError + nil + end + + def from + @from ||= @mail[:from].address_list.addresses.first + end + + def find_or_create_user(address_field) + # decode the address field + address_field.decoded + # extract email and name + email = address_field.address.downcase + name = address_field.display_name.try(:to_s) + username = UserNameSuggester.sanitize_username(name) if name.present? + + User.find_or_create_by(email: email) do |user| + user.username = UserNameSuggester.suggest(username.presence || email) + user.name = name.presence || User.suggest_name(email) + user.staged = true + end + end + + def destinations + [ @mail.destinations, + [@mail[:x_forwarded_to]].flatten.compact.map(&:decoded), + [@mail[:delivered_to]].flatten.compact.map(&:decoded), + ].flatten + .select(&:present?) + .uniq + .lazy + .map { |d| check_address(d) } + .drop_while(&:blank?) end def check_address(address) # only check for a group/category when 'email_in' is enabled if SiteSetting.email_in group = Group.find_by_email(address) - return { address: address, type: :group, obj: group } if group + return { type: :group, obj: group } if group category = Category.find_by_email(address) - return { address: address, type: :category, obj: category } if category + return { type: :category, obj: category } if category end + # reply match = reply_by_email_address_regex.match(address) if match && match[1].present? email_log = EmailLog.for(match[1]) - return { address: address, type: :reply, obj: email_log } + return { type: :reply, obj: email_log } if email_log end end @@ -147,173 +204,103 @@ module Email .gsub(Regexp.escape("%{reply_key}"), "([[:xdigit:]]{32})") end - def parse_body(message) - body = select_body(message) - encoding = body.encoding - raise EmptyEmailError if body.strip.blank? - - body = discourse_email_trimmer(body) - raise EmptyEmailError if body.strip.blank? - - body = DiscourseEmailParser.parse_reply(body) - raise EmptyEmailError if body.strip.blank? - - body.force_encoding(encoding).encode("UTF-8") + def group_incoming_emails_regex + @group_incoming_emails_regex ||= Regexp.union Group.pluck(:incoming_email).select(&:present?).uniq end - def select_body(message) - html = nil - - if message.multipart? - text = fix_charset message.text_part - # prefer text over html - return text if text - html = fix_charset message.html_part - elsif message.content_type =~ /text\/html/ - html = fix_charset message - end - - if html - body = HtmlCleaner.new(html).output_html - else - body = fix_charset message - end - - return body if @opts[:skip_sanity_check] - - # Certain trigger phrases that means we didn't parse correctly - if body =~ /Content\-Type\:/ || body =~ /multipart\/alternative/ || body =~ /text\/plain/ - raise EmptyEmailError - end - - body + def category_email_in_regex + @category_email_in_regex ||= Regexp.union Category.pluck(:email_in).select(&:present?).uniq end - # Force encoding to UTF-8 on a Mail::Message or Mail::Part - def fix_charset(object) - return nil if object.nil? + def find_related_post + message_ids = [@mail.in_reply_to, extract_references] + message_ids.flatten! + message_ids.select!(&:present?) + message_ids.uniq! + return if message_ids.empty? - if object.charset - object.body.decoded.force_encoding(object.charset.gsub(/utf8/i, "UTF-8")).encode("UTF-8").to_s - else - object.body.to_s + Post.where(id: IncomingEmail.where(message_id: message_ids).select(:post_id)) + .order(created_at: :desc) + .first + end + + def extract_references + if Array === @mail.references + @mail.references + elsif @mail.references.present? + @mail.references.split(/[\s,]/).map { |r| r.sub(/^$/, "") } end - rescue - nil end - REPLYING_HEADER_LABELS = ['From', 'Sent', 'To', 'Subject', 'In-Reply-To', 'Cc', 'Bcc', 'Date'] - REPLYING_HEADER_REGEX = Regexp.union(REPLYING_HEADER_LABELS.map { |lbl| "#{lbl}:" }) - - def line_is_quote?(l) - l =~ /\A\s*\-{3,80}\s*\z/ || - l =~ Regexp.new("\\A\\s*" + I18n.t('user_notifications.previous_discussion') + "\\s*\\Z") || - (l =~ /via #{SiteSetting.title}(.*)\:$/) || - # This one might be controversial but so many reply lines have years, times and end with a colon. - # Let's try it and see how well it works. - (l =~ /\d{4}/ && l =~ /\d:\d\d/ && l =~ /\:$/) || - (l =~ /On [\w, ]+\d+.*wrote:/) + def likes + @likes ||= Set.new ["+1", I18n.t('post_action_types.like.title').downcase] end - def discourse_email_trimmer(body) - lines = body.scrub.lines.to_a - range_start = 0 - range_end = 0 - - # If we started with a quote, skip it - lines.each_with_index do |l, idx| - break unless line_is_quote?(l) or l =~ /^>/ or l.blank? - range_start = idx + 1 + def subscription_action_for(body, subject) + return unless SiteSetting.unsubscribe_via_email + if ([subject, body].compact.map(&:to_s).map(&:downcase) & ['unsubscribe']).any? + :confirm_unsubscribe end - - lines[range_start..-1].each_with_index do |l, idx| - break if line_is_quote?(l) - - # Headers on subsequent lines - break if (0..2).all? { |off| lines[idx+off] =~ REPLYING_HEADER_REGEX } - # Headers on the same line - break if REPLYING_HEADER_LABELS.count { |lbl| l.include? lbl } >= 3 - range_end = range_start + idx - end - - lines[range_start..range_end].join.strip - end - - private - - def wrap_body_in_quote(user_email) - @body = "[quote=\"#{user_email}\"]\n#{@body}\n[/quote]" - end - - def create_post_action(email_log, type) - PostActionCreator.new(email_log.user, email_log.post).perform(type) - rescue Discourse::InvalidAccess, PostAction::AlreadyActed => e - raise InvalidPostAction.new(e) end def post_action_for(body) - if ['+1', I18n.t('post_action_types.like.title').downcase].include? body.downcase + if likes.include?(body.strip.downcase) PostActionType.types[:like] end end - def create_reply(email_log) - create_post_with_attachments(email_log.user, - raw: @body, - topic_id: email_log.topic_id, - reply_to_post_number: email_log.post.post_number) + def create_topic(options={}) + create_post_with_attachments(options) end - def create_new_topic(user, topic_options={}) - topic_options[:raw] = @body - topic_options[:title] = @message.subject + def create_reply(options={}) + raise TopicNotFoundError if options[:topic].nil? || options[:topic].trashed? + raise TopicClosedError if options[:topic].closed? - result = create_post_with_attachments(user, topic_options) - topic_id = result.post.present? ? result.post.topic_id : nil - - EmailLog.create( - email_type: "topic_via_incoming_email", - to_address: user.email, - topic_id: topic_id, - user_id: user.id, - ) - - result + if post_action_type = post_action_for(options[:raw]) + create_post_action(options[:user], options[:post], post_action_type) + else + options[:topic_id] = options[:post].try(:topic_id) + options[:reply_to_post_number] = options[:post].try(:post_number) + create_post_with_attachments(options) + end end - def create_post_with_attachments(user, post_options={}) - options = { - cooking_options: { traditional_markdown_linebreaks: true }, - }.merge(post_options) - - raw = options[:raw] + def create_post_action(user, post, type) + PostActionCreator.new(user, post).perform(type) + rescue PostAction::AlreadyActed + # it's cool, don't care + rescue Discourse::InvalidAccess => e + raise InvalidPostAction.new(e) + end + def create_post_with_attachments(options={}) # deal with attachments - @message.attachments.each do |attachment| + @mail.attachments.each do |attachment| tmp = Tempfile.new("discourse-email-attachment") begin # read attachment File.open(tmp.path, "w+b") { |f| f.write attachment.body.decoded } # create the upload for the user - upload = Upload.create_for(user.id, tmp, attachment.filename, tmp.size) + upload = Upload.create_for(options[:user].id, tmp, attachment.filename, tmp.size) if upload && upload.errors.empty? # try to inline images - if attachment.content_type.start_with?("image/") - if raw =~ /\[image: Inline image \d+\]/ - raw.sub!(/\[image: Inline image \d+\]/, attachment_markdown(upload)) - next - end + if attachment.content_type.start_with?("image/") && options[:raw][/\[image: .+ \d+\]/] + options[:raw].sub!(/\[image: .+ \d+\]/, attachment_markdown(upload)) + else + options[:raw] << "\n#{attachment_markdown(upload)}\n" end - raw << "\n#{attachment_markdown(upload)}\n" end ensure - tmp.close! + tmp.try(:close!) rescue nil end end - options[:raw] = raw + post_options = { + cooking_options: { traditional_markdown_linebreaks: true }, + }.merge(options) - create_post(user, options) + create_post(post_options) end def attachment_markdown(upload) @@ -324,20 +311,58 @@ module Email end end - def create_post(user, options) - # Mark the reply as incoming via email + def create_post(options={}) options[:via_email] = true - options[:raw_email] = @raw + options[:raw_email] = @raw_email - manager = NewPostManager.new(user, options) + # ensure posts aren't created in the future + options[:created_at] = [@mail.date, DateTime.now].min + + manager = NewPostManager.new(options[:user], options) result = manager.perform - if result.errors.present? - raise InvalidPost, result.errors.full_messages.join("\n") - end + raise InvalidPost, result.errors.full_messages.join("\n") if result.errors.any? - result + if result.post + @incoming_email.update_columns(topic_id: result.post.topic_id, post_id: result.post.id) + if result.post.topic && result.post.topic.private_message? + add_other_addresses(result.post.topic, options[:user]) + end + end + end + + def add_other_addresses(topic, sender) + %i(to cc bcc).each do |d| + if @mail[d] && @mail[d].address_list && @mail[d].address_list.addresses + @mail[d].address_list.addresses.each do |address_field| + begin + email = address_field.address.downcase + if should_invite?(email) + user = find_or_create_user(address_field) + if can_invite?(topic, user) + topic.topic_allowed_users.create!(user_id: user.id) + topic.add_small_action(sender, "invited_user", user.username) + end + end + rescue ActiveRecord::RecordInvalid + # don't care if user already allowed + end + end + end + end + end + + def should_invite?(email) + email !~ reply_by_email_address_regex && + email !~ group_incoming_emails_regex && + email !~ category_email_in_regex + end + + def can_invite?(topic, user) + !topic.topic_allowed_users.where(user_id: user.id).exists? && + !topic.topic_allowed_groups.where("group_id IN (SELECT group_id FROM group_users WHERE user_id = ?)", user.id).exists? end end + end diff --git a/lib/email/styles.rb b/lib/email/styles.rb index 6f0dd6c2b..67447a10a 100644 --- a/lib/email/styles.rb +++ b/lib/email/styles.rb @@ -89,6 +89,7 @@ module Email style('hr', 'background-color: #ddd; height: 1px; border: 1px;') style('.rtl', 'direction: rtl;') style('td.body', 'padding-top:5px;', colspan: "2") + style('.whisper td.body', 'font-style: italic; color: #9c9c9c;') correct_first_body_margin correct_footer_style reset_tables @@ -206,11 +207,20 @@ module Email end def correct_footer_style + footernum = 0 @fragment.css('.footer').each do |element| element['style'] = "color:#666;" + linknum = 0 element.css('a').each do |inner| - inner['style'] = "color:#666;" + # we want the first footer link to be specially highlighted as IMPORTANT + if footernum == 0 and linknum == 0 + inner['style'] = "background-color: #006699; color:#ffffff; border-top: 4px solid #006699; border-right: 6px solid #006699; border-bottom: 4px solid #006699; border-left: 6px solid #006699; display: inline-block;" + else + inner['style'] = "color:#666;" + end + linknum += 1 end + footernum += 1 end end diff --git a/lib/enum.rb b/lib/enum.rb index bbdc3fe82..7db1845dc 100644 --- a/lib/enum.rb +++ b/lib/enum.rb @@ -1,19 +1,28 @@ class Enum < Hash # Public: Initialize an enum. # - # members - the array of enum members. May contain a hash of options: - # :start - the number of first enum member. Defaults to 1. + # members - Array of enum members or Hash of enum members. + # Array of enum members may also contain a hash of options: + # :start - the number of first enum member. Defaults to 1. # # Examples # - # FRUITS = Enum.new(:apple, :orange, :kiwi) + # FRUITS = Enum.new(:apple, :orange, :kiwi) # array + # FRUITS = Enum.new(:apple, :orange, :kiwi, start: 0) # array + # FRUITS = Enum.new(apple: 1, orange: 2, kiwi: 3) # hash + def initialize(*members) super({}) - options = members.extract_options! - start = options.fetch(:start) { 1 } - - update Hash[members.zip(start..members.count + start)] + if members[0].is_a?(Hash) + # hash + update Hash[members[0]] + else + # array + options = members.extract_options! + start = options.fetch(:start) { 1 } + update Hash[members.zip(start..members.count + start)] + end end # Public: Access the number/value of member. diff --git a/lib/excerpt_parser.rb b/lib/excerpt_parser.rb index 95e9cf1de..acfa88b88 100644 --- a/lib/excerpt_parser.rb +++ b/lib/excerpt_parser.rb @@ -13,7 +13,8 @@ class ExcerptParser < Nokogiri::XML::SAX::Document @text_entities = options[:text_entities] == true @markdown_images = options[:markdown_images] == true @keep_newlines = options[:keep_newlines] == true - @keep_emojis = options[:keep_emojis] == true + @keep_emoji_images = options[:keep_emoji_images] == true + @keep_emoji_codes = options[:keep_emoji_codes] == true @start_excerpt = false end @@ -48,11 +49,14 @@ class ExcerptParser < Nokogiri::XML::SAX::Document def start_element(name, attributes=[]) case name when "img" - attributes = Hash[*attributes.flatten] - if @keep_emojis && attributes["class"] == 'emoji' - return include_tag(name, attributes) + if attributes["class"] == 'emoji' + if @keep_emoji_images + return include_tag(name, attributes) + elsif @keep_emoji_codes + return characters(attributes["alt"]) + end end # If include_images is set, include the image in markdown diff --git a/lib/freedom_patches/arel_patch.rb b/lib/freedom_patches/arel_patch.rb deleted file mode 100644 index 4c0ae309b..000000000 --- a/lib/freedom_patches/arel_patch.rb +++ /dev/null @@ -1,6 +0,0 @@ -# https://github.com/rails/arel/pull/206 -class Arel::Table - def hash - @name.hash - end -end diff --git a/lib/guardian.rb b/lib/guardian.rb index 78dcee9cd..92c09f2ed 100644 --- a/lib/guardian.rb +++ b/lib/guardian.rb @@ -256,7 +256,9 @@ class Guardian @user.username == SiteSetting.site_contact_username || @user == Discourse.system_user) && # Can't send PMs to suspended users - (is_staff? || target.is_a?(Group) || !target.suspended?) + (is_staff? || target.is_a?(Group) || !target.suspended?) && + # Blocked users can only send PM to staff + (!@user.blocked? || target.staff?) end def can_see_emails? diff --git a/lib/guardian/post_guardian.rb b/lib/guardian/post_guardian.rb index 2933cf194..722c01748 100644 --- a/lib/guardian/post_guardian.rb +++ b/lib/guardian/post_guardian.rb @@ -73,7 +73,7 @@ module PostGuardian # Creating Method def can_create_post?(parent) - !SpamRule::AutoBlock.block?(@user) && ( + (!SpamRule::AutoBlock.block?(@user) || (!!parent.try(:private_message?) && parent.allowed_users.include?(@user))) && ( !parent || !parent.category || Category.post_create_allowed(self).where(:id => parent.category.id).count == 1 @@ -173,8 +173,9 @@ module PostGuardian is_admin? end - def can_wiki? - is_staff? || @user.has_trust_level?(TrustLevel[4]) + def can_wiki?(post) + return false unless authenticated? + is_staff? || @user.has_trust_level?(TrustLevel[4]) || (@user.has_trust_level?(SiteSetting.min_trust_to_allow_self_wiki) && is_my_own?(post)) end def can_change_post_type? diff --git a/lib/onebox/engine/discourse_local_onebox.rb b/lib/onebox/engine/discourse_local_onebox.rb index 5fd48971b..cac4503d6 100644 --- a/lib/onebox/engine/discourse_local_onebox.rb +++ b/lib/onebox/engine/discourse_local_onebox.rb @@ -63,7 +63,7 @@ module Onebox topic = post.topic slug = Slug.for(topic.title) - excerpt = post.excerpt(SiteSetting.post_onebox_maxlength) + excerpt = post.excerpt(SiteSetting.post_onebox_maxlength, { keep_emoji_codes: true }) excerpt.gsub!("\n"," ") # hack to make it render for now excerpt.gsub!("[/quote]", "[quote]") diff --git a/lib/post_creator.rb b/lib/post_creator.rb index 4d2b158b8..f7151442b 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -172,7 +172,7 @@ class PostCreator def self.before_create_tasks(post) set_reply_info(post) - post.word_count = post.raw.scan(/\w+/).size + post.word_count = post.raw.scan(/[[:word:]]+/).size post.post_number ||= Topic.next_post_number(post.topic_id, post.reply_to_post_number.present?) cooking_options = post.cooking_options || {} @@ -262,10 +262,14 @@ class PostCreator end def ensure_in_allowed_users - return unless @topic.private_message? + return unless @topic.private_message? && @topic.id unless @topic.topic_allowed_users.where(user_id: @user.id).exists? - @topic.topic_allowed_users.create!(user_id: @user.id) + unless @topic.topic_allowed_groups.where('group_id IN ( + SELECT group_id FROM group_users where user_id = ? + )',@user.id).exists? + @topic.topic_allowed_users.create!(user_id: @user.id) + end end end @@ -350,8 +354,10 @@ class PostCreator @user.user_stat.first_post_created_at = @post.created_at end - @user.user_stat.post_count += 1 - @user.user_stat.topic_count += 1 if @post.is_first_post? + unless @post.topic.private_message? + @user.user_stat.post_count += 1 + @user.user_stat.topic_count += 1 if @post.is_first_post? + end # We don't count replies to your own topics if !@opts[:import_mode] && @user.id != @topic.user_id diff --git a/lib/post_revisor.rb b/lib/post_revisor.rb index e08186420..873be66e0 100644 --- a/lib/post_revisor.rb +++ b/lib/post_revisor.rb @@ -229,7 +229,7 @@ class PostRevisor end @post.last_editor_id = @editor.id - @post.word_count = @fields[:raw].scan(/\w+/).size if @fields.has_key?(:raw) + @post.word_count = @fields[:raw].scan(/[[:word:]]+/).size if @fields.has_key?(:raw) @post.self_edits += 1 if self_edit? remove_flags_and_unhide_post diff --git a/lib/pretty_text.rb b/lib/pretty_text.rb index aefd4bed0..d952373ab 100644 --- a/lib/pretty_text.rb +++ b/lib/pretty_text.rb @@ -48,6 +48,14 @@ module PrettyText end end + def category_hashtag_lookup(category_slug) + if category = Category.query_from_hashtag_slug(category_slug) + ['category', category.url_with_id] + else + nil + end + end + def get_topic_info(topic_id) return unless Fixnum === topic_id # TODO this only handles public topics, secured one do not get this @@ -65,7 +73,7 @@ module PrettyText @ctx_init = Mutex.new def self.mention_matcher - Regexp.new("(\@[a-zA-Z0-9_]{#{User.username_length.begin},#{User.username_length.end}})") + Regexp.new("\\W@(\\w{#{SiteSetting.min_username_length},#{SiteSetting.max_username_length}})\\b") end def self.app_root @@ -207,6 +215,7 @@ module PrettyText context.eval("Discourse.Emoji.applyCustomEmojis();") context.eval('opts["mentionLookup"] = function(u){return helpers.mention_lookup(u);}') + context.eval('opts["categoryHashtagLookup"] = function(c){return helpers.category_hashtag_lookup(c);}') context.eval('opts["lookupAvatar"] = function(p){return Discourse.Utilities.avatarImg({size: "tiny", avatarTemplate: helpers.avatar_template(p)});}') context.eval('opts["getTopicInfo"] = function(i){return helpers.get_topic_info(i)};') baked = context.eval('Discourse.Markdown.markdownConverter(opts).makeHtml(raw)') diff --git a/lib/scheduler/manager.rb b/lib/scheduler/manager.rb index 2f2b42d4a..78d1de7fa 100644 --- a/lib/scheduler/manager.rb +++ b/lib/scheduler/manager.rb @@ -10,7 +10,6 @@ module Scheduler class Manager attr_accessor :random_ratio, :redis - class Runner def initialize(manager) @mutex = Mutex.new @@ -157,7 +156,6 @@ module Scheduler lock do schedule_info(klass).schedule! end - end def remove(klass) @@ -203,8 +201,8 @@ module Scheduler def schedule_next_job(hostname=nil) (key, due), _ = redis.zrange Manager.queue_key(hostname), 0, 0, withscores: true - return unless key + if due.to_i <= Time.now.to_i klass = get_klass(key) unless klass diff --git a/lib/sidekiq/pausable.rb b/lib/sidekiq/pausable.rb index 3ef356c83..c7cd9b592 100644 --- a/lib/sidekiq/pausable.rb +++ b/lib/sidekiq/pausable.rb @@ -68,15 +68,13 @@ end # server middleware that will reschedule work whenever Sidekiq is paused class Sidekiq::Pausable - attr_reader :delay - def initialize(delay = 5.seconds) @delay = delay end def call(worker, msg, queue) if Sidekiq.paused? - worker.class.perform_in(delay, *msg['args']) + worker.class.perform_in(@delay, *msg['args']) else yield end diff --git a/lib/site_setting_extension.rb b/lib/site_setting_extension.rb index 7b85f9c6e..13392a22a 100644 --- a/lib/site_setting_extension.rb +++ b/lib/site_setting_extension.rb @@ -21,18 +21,18 @@ module SiteSettingExtension end def types - @types ||= Enum.new(:string, - :time, - :fixnum, - :float, - :bool, - :null, - :enum, - :list, - :url_list, - :host_list, - :category_list, - :value_list) + @types ||= Enum.new(string: 1, + time: 2, + fixnum: 3, + float: 4, + bool: 5, + null: 6, + enum: 7, + list: 8, + url_list: 9, + host_list: 10, + category_list: 11, + value_list: 12) end def mutex diff --git a/lib/system_message.rb b/lib/system_message.rb index 5e4a82df8..d38b5113f 100644 --- a/lib/system_message.rb +++ b/lib/system_message.rb @@ -23,12 +23,22 @@ class SystemMessage title = I18n.t("system_messages.#{type}.subject_template", params) raw = I18n.t("system_messages.#{type}.text_body_template", params) - PostCreator.create(Discourse.site_contact_user, + creator = PostCreator.new(Discourse.site_contact_user, title: title, raw: raw, archetype: Archetype.private_message, target_usernames: @recipient.username, - subtype: TopicSubtype.system_message) + subtype: TopicSubtype.system_message, + skip_validations: true) + + post = creator.create + if creator.errors.present? + raise StandardError, creator.errors.to_s + end + + UserArchivedMessage.create!(user: Discourse.site_contact_user, topic: post.topic) + + post end def create_from_system_user(type, params = {}) diff --git a/lib/tasks/assets.rake b/lib/tasks/assets.rake index 030e4db2e..040f70e2b 100644 --- a/lib/tasks/assets.rake +++ b/lib/tasks/assets.rake @@ -122,7 +122,7 @@ def compress_ruby(from,to) data = File.read("#{assets_path}/#{from}") uglified, map = Uglifier.new(comments: :none, - screw_ie8: false, + screw_ie8: true, source_filename: File.basename(from), output_filename: File.basename(to) ) @@ -135,7 +135,7 @@ end def gzip(path) STDERR.puts "gzip #{path}" - STDERR.puts `gzip -f -c -9 #{path} > #{path}.gz` + STDERR.puts `gzip -f -c -7 #{path} > #{path}.gz` end def compress(from,to) diff --git a/lib/tasks/emails.rake b/lib/tasks/emails.rake new file mode 100644 index 000000000..1ab9c71d3 --- /dev/null +++ b/lib/tasks/emails.rake @@ -0,0 +1,56 @@ +def process_popmail(popmail) + begin + mail_string = popmail.pop + Email::Receiver.new(mail_string).process + rescue + putc "!" + else + putc "." + end +end + +desc "use this task to import a mailbox into Disourse" +task "emails:import" => :environment do + begin + unless SiteSetting.email_in + puts "ERROR: you should enable the 'email_in' site setting before running this task" + exit(1) + end + + address = ENV["ADDRESS"].presence || "pop.gmail.com" + port = (ENV["PORT"].presence || 995).to_i + ssl = (ENV["SSL"].presence || "1") == "1" + username = ENV["USERNAME"].presence + password = ENV["PASSWORD"].presence + + if username.blank? + puts "ERROR: expecting USERNAME= rake emails:import" + exit(2) + elsif password.blank? + puts "ERROR: expecting PASSWORD= rake emails:import" + exit(3) + end + + RateLimiter.disable + + mails_left = 1 + pop3 = Net::POP3.new(address, port) + pop3.enable_ssl if ssl + + while mails_left > 0 + pop3.start(username, password) do |pop| + pop.delete_all do |p| + process_popmail(p) + end + mails_left = pop.n_mails + end + end + + puts "Done" + rescue Net::POPAuthenticationError + puts "AUTH EXCEPTION: please make sure your credentials are correct." + exit(10) + ensure + RateLimiter.enable + end +end diff --git a/lib/topic_creator.rb b/lib/topic_creator.rb index c4791a2f7..9ca18af01 100644 --- a/lib/topic_creator.rb +++ b/lib/topic_creator.rb @@ -193,6 +193,6 @@ class TopicCreator end def check_can_send_permission!(topic, obj) - rollback_with!(topic, :cant_send_pm) unless @guardian.can_send_private_message?(obj) + rollback_with!(topic, :cant_send_pm) unless @opts[:skip_validations] || @guardian.can_send_private_message?(obj) end end diff --git a/lib/topic_query.rb b/lib/topic_query.rb index cde31fffd..c837c429c 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -199,7 +199,6 @@ class TopicQuery end def prioritize_pinned_topics(topics, options) - pinned_clause = options[:category_id] ? "topics.category_id = #{options[:category_id].to_i} AND" : "pinned_globally AND " pinned_clause << " pinned_at IS NOT NULL " if @user diff --git a/lib/topic_view.rb b/lib/topic_view.rb index 7db989d45..9072610f5 100644 --- a/lib/topic_view.rb +++ b/lib/topic_view.rb @@ -16,6 +16,10 @@ class TopicView 20 end + def self.default_post_custom_fields + @default_post_custom_fields ||= ["action_code_who"] + end + def self.post_custom_fields_whitelisters @post_custom_fields_whitelisters ||= Set.new end @@ -25,7 +29,8 @@ class TopicView end def self.whitelisted_post_custom_fields(user) - post_custom_fields_whitelisters.map { |w| w.call(user) }.flatten.uniq + wpcf = default_post_custom_fields + post_custom_fields_whitelisters.map { |w| w.call(user) } + wpcf.flatten.uniq end def initialize(topic_id, user=nil, options={}) diff --git a/lib/user_name_suggester.rb b/lib/user_name_suggester.rb index 4819a3f87..934035ed6 100644 --- a/lib/user_name_suggester.rb +++ b/lib/user_name_suggester.rb @@ -35,10 +35,15 @@ module UserNameSuggester def self.sanitize_username(name) name = ActiveSupport::Inflector.transliterate(name) - name = name.gsub(/^[^[:alnum:]]+|\W+$/, "") - .gsub(/\W+/, "_") - .gsub(/^\_+/, '') - .gsub(/[\-_\.]{2,}/, "_") + # 1. replace characters that aren't allowed with '_' + name.gsub!(UsernameValidator::CONFUSING_EXTENSIONS, "_") + name.gsub!(/[^\w.-]/, "_") + # 2. removes unallowed leading characters + name.gsub!(/^\W+/, "") + # 3. removes unallowed trailing characters + name.gsub!(/[^A-Za-z0-9]+$/, "") + # 4. unify special characters + name.gsub!(/[-_.]{2,}/, "_") name end diff --git a/lib/validators/password_validator.rb b/lib/validators/password_validator.rb index cb7cb457e..015f08f12 100644 --- a/lib/validators/password_validator.rb +++ b/lib/validators/password_validator.rb @@ -10,7 +10,7 @@ class PasswordValidator < ActiveModel::EachValidator record.errors.add(attribute, :too_short, count: SiteSetting.min_password_length) elsif record.username.present? && value == record.username record.errors.add(attribute, :same_as_username) - elsif record.username.present? && value == record.email + elsif record.email.present? && value == record.email record.errors.add(attribute, :same_as_email) elsif SiteSetting.block_common_passwords && CommonPasswords.common_password?(value) record.errors.add(attribute, :common) diff --git a/lib/version.rb b/lib/version.rb index f5a521ac6..0e13f016b 100644 --- a/lib/version.rb +++ b/lib/version.rb @@ -5,7 +5,7 @@ module Discourse MAJOR = 1 MINOR = 5 TINY = 0 - PRE = 'beta7' + PRE = 'beta9' STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/plugins/poll/assets/javascripts/controllers/poll.js.es6 b/plugins/poll/assets/javascripts/controllers/poll.js.es6 index 1a9c3943d..576bf7433 100644 --- a/plugins/poll/assets/javascripts/controllers/poll.js.es6 +++ b/plugins/poll/assets/javascripts/controllers/poll.js.es6 @@ -15,7 +15,7 @@ export default Ember.Controller.extend({ showResultsDisabled: Em.computed.equal("poll.voters", 0), hideResultsDisabled: Em.computed.or("isClosed", "post.topic.closed", "post.topic.archived"), - @computed("model", "vote") + @computed("model", "vote", "model.voters", "model.options", "model.status") poll(poll, vote) { if (poll) { const options = _.map(poll.get("options"), o => Em.Object.create(o)); diff --git a/plugins/poll/assets/javascripts/initializers/extend-for-poll.js.es6 b/plugins/poll/assets/javascripts/initializers/extend-for-poll.js.es6 index 2e086fbc5..f96da54dd 100644 --- a/plugins/poll/assets/javascripts/initializers/extend-for-poll.js.es6 +++ b/plugins/poll/assets/javascripts/initializers/extend-for-poll.js.es6 @@ -1,4 +1,7 @@ import PostView from "discourse/views/post"; +import TopicController from "discourse/controllers/topic"; +import Post from "discourse/models/post"; + import { on } from "ember-addons/ember-computed-decorators"; function createPollView(container, post, poll, vote) { @@ -6,7 +9,7 @@ function createPollView(container, post, poll, vote) { view = container.lookup("view:poll"); controller.set("vote", vote); - controller.setProperties({ model: Em.Object.create(poll), post }); + controller.setProperties({ model: poll, post }); view.set("controller", controller); return view; @@ -17,13 +20,39 @@ export default { initialize(container) { - const messageBus = container.lookup("message-bus:main"); + Post.reopen({ + // we need a proper ember object so it is bindable + pollsChanged: function(){ + const polls = this.get("polls"); + if (polls) { + this._polls = this._polls || {}; + _.map(polls, (v,k) => { + const existing = this._polls[k]; + if (existing) { + this._polls[k].setProperties(v); + } else { + this._polls[k] = Em.Object.create(v); + } + }); + this.set("pollsObject", this._polls); + } + }.observes("polls") + }); - // listen for back-end to tell us when a post has a poll - messageBus.subscribe("/polls", data => { - const post = container.lookup("controller:topic").get('model.postStream').findLoadedPost(data.post_id); - // HACK to trigger the "postViewUpdated" event - Em.run.next(() => post.set("cooked", post.get("cooked") + " ")); + TopicController.reopen({ + subscribe(){ + this._super(); + this.messageBus.subscribe("/polls/" + this.get("model.id"), msg => { + const post = this.get('model.postStream').findLoadedPost(msg.post_id); + if (post) { + post.set('polls', msg.polls); + } + }); + }, + unsubscribe(){ + this.messageBus.unsubscribe('/polls/*'); + this._super(); + } }); // overwrite polls @@ -32,12 +61,16 @@ export default { @on("postViewInserted", "postViewUpdated") _createPollViews($post) { const post = this.get("post"), - polls = post.get("polls"), votes = post.get("polls_votes") || {}; + post.pollsChanged(); + const polls = post.get("pollsObject"); + // don't even bother when there's no poll if (!polls) { return; } + // TODO inject cleanly into + // clean-up if needed this._cleanUpPollViews(); @@ -55,23 +88,11 @@ export default { pollViews[pollName] = pollView; }); - messageBus.subscribe(`/polls/${this.get("post.id")}`, results => { - if (results && results.polls) { - _.forEach(results.polls, poll => { - if (pollViews[poll.name]) { - pollViews[poll.name].get("controller").set("model", Em.Object.create(poll)); - } - }); - } - }); - this.set("pollViews", pollViews); }, @on("willClearRender") _cleanUpPollViews() { - messageBus.unsubscribe(`/polls/${this.get("post.id")}`); - if (this.get("pollViews")) { _.forEach(this.get("pollViews"), v => v.destroy()); } diff --git a/plugins/poll/assets/javascripts/lib/even-round.js.es6 b/plugins/poll/assets/javascripts/lib/even-round.js.es6 index 3dcc57861..0395f1f16 100644 --- a/plugins/poll/assets/javascripts/lib/even-round.js.es6 +++ b/plugins/poll/assets/javascripts/lib/even-round.js.es6 @@ -5,11 +5,13 @@ function sumsUpTo100(percentages) { export default (percentages) => { const sumOfDecimals = Math.ceil(percentages.map(a => a % 1).reduce((a, b) => a + b)); - // compensate error by adding 1 to the first n items - for (let i = 0; i < sumOfDecimals; i++) { - percentages[i] = ++percentages[i]; - // quit early when there is a rounding issue - if (sumsUpTo100(percentages)) break; + // compensate error by adding 1 to the first n "non-zero" items + for (let i = 0, max = percentages.length; i < sumOfDecimals && i < max; i++) { + if (percentages[i] > 0) { + percentages[i] = ++percentages[i]; + // quit early when there is a rounding issue + if (sumsUpTo100(percentages)) break; + } } return percentages.map(p => Math.floor(p)); }; diff --git a/plugins/poll/config/locales/client.fi.yml b/plugins/poll/config/locales/client.fi.yml index e686f8e6a..0ac4206e5 100644 --- a/plugins/poll/config/locales/client.fi.yml +++ b/plugins/poll/config/locales/client.fi.yml @@ -10,7 +10,7 @@ fi: poll: voters: one: "äänestäjä" - other: "äänestäjät" + other: "äänestäjää" total_votes: one: "ääni" other: "ääntä" diff --git a/plugins/poll/config/locales/client.ro.yml b/plugins/poll/config/locales/client.ro.yml index 0176f9589..82b8d8830 100644 --- a/plugins/poll/config/locales/client.ro.yml +++ b/plugins/poll/config/locales/client.ro.yml @@ -8,9 +8,29 @@ ro: js: poll: + voters: + one: "participant" + few: "participanți" + other: "participanți" + total_votes: + one: "un vot" + few: "total voturi" + other: "total voturi" average_rating: "Media: %{average}." multiple: help: + at_least_min_options: + one: "Trebuie să selectați cel puțin 1 opțiune." + few: "Trebuie să selectați cel puțin %{count} opțiuni." + other: "Trebuie să selectați cel puțin %{count} opțiuni." + up_to_max_options: + one: "Puteţi alege cel mult o %{count} opţiune." + few: "Puteţi selecta cel mult 1 opţiune." + other: "Puteți selecta până la %{count} opțiuni." + x_options: + one: "Trebuie să selectați 1 opțiune." + few: "Trebuie să selectați %{count} opțiuni." + other: "Trebuie să alegeți %{count} opțiuni." between_min_and_max_options: "Puteţi alege între %{min} şi %{max} opţiuni." cast-votes: title: "Exprimaţi-vă votul" @@ -26,8 +46,8 @@ ro: label: "Deschis" confirm: "Sunteţi sigur că doriţi să deschideţi acest sondaj?" close: - title: "Închide sondaj" - label: "Închis" + title: "Închideți sondajul" + label: "Închideți" confirm: "Sunteţi sigur că vreţi să închideţi acest sondaj?" error_while_toggling_status: "A apărut o eroare în timpul schimbării stării acestui sondaj." error_while_casting_votes: "A apărut o eroare în timpul exprimării votului dvs." diff --git a/plugins/poll/config/locales/client.sk.yml b/plugins/poll/config/locales/client.sk.yml new file mode 100644 index 000000000..2ae88650f --- /dev/null +++ b/plugins/poll/config/locales/client.sk.yml @@ -0,0 +1,53 @@ +# encoding: utf-8 +# +# Never edit this file. It will be overwritten when translations are pulled from Transifex. +# +# To work with us on translations, join this project: +# https://www.transifex.com/projects/p/discourse-org/ + +sk: + js: + poll: + voters: + one: "volič" + few: "voliči" + other: "voliči" + total_votes: + one: "hlas celkom" + few: "hlasy celkom" + other: "hlasov celkom" + average_rating: "Priemerné hodnotenie: %{average}." + multiple: + help: + at_least_min_options: + one: "Musíte si vybrať minimálne %{count} možnosť." + few: "Musíte si vybrať minimálne %{count} možnosti." + other: "Musíte si vybrať minimálne %{count} možností." + up_to_max_options: + one: "Môžete si vybrať maximálne %{count} možnosť." + few: "Môžete si vybrať maximálne %{count} možnosti." + other: "Môžete si vybrať maximálne %{count} možností." + x_options: + one: "Musíte si vybrať %{count} možnosť." + few: "Musíte si vybrať %{count} možnosti." + other: "Musíte si vybrať %{count} možností." + between_min_and_max_options: "Môžete si vybrať medzi možnosťami %{min}%{max}." + cast-votes: + title: "Hlasovať" + label: "Hlasuj teraz!" + show-results: + title: "Zobraz výsledky hlasovania" + label: "Zobraz výsledky" + hide-results: + title: "Návrat na odovzdané hlasy" + label: "Skyť výsledky" + open: + title: "Zahájiť hlasovanie" + label: "Zahájiť" + confirm: "Ste si istý, že chcete zahájiť toto hlasovanie?" + close: + title: "Zatvoriť hlasovanie" + label: "Zatvoriť" + confirm: "Ste si istý, že chcete zatvoriť toto hlasovanie?" + error_while_toggling_status: "Pri zmene stavu hlasovania sa vyskytla chyba." + error_while_casting_votes: "Pri hlasovaní sa vyskytla chyba." diff --git a/plugins/poll/config/locales/server.ro.yml b/plugins/poll/config/locales/server.ro.yml index 31d1c4401..e0ca3634c 100644 --- a/plugins/poll/config/locales/server.ro.yml +++ b/plugins/poll/config/locales/server.ro.yml @@ -5,4 +5,37 @@ # To work with us on translations, join this project: # https://www.transifex.com/projects/p/discourse-org/ -ro: {} +ro: + site_settings: + poll_enabled: "Permiți utilizatorilor să creeze sondaje?" + poll_maximum_options: "Numărul maxim admis de opțiuni într-un sondaj" + poll: + multiple_polls_without_name: "Există mai multe sondaje fără nume. Folosește atributul 'name' pentru a identifica sondajele proprii" + multiple_polls_with_same_name: "Există mai multe sondaje cu același nume: %{name}. Folosește atributul 'name' pentru a identifica sondajele proprii." + default_poll_must_have_at_least_2_options: "Sondajul trebuie să aibă cel puțin 2 opțiuni." + named_poll_must_have_at_least_2_options: "Sondajul numit %{name} trebuie să aibă cel puțin 2 opțiuni." + default_poll_must_have_less_options: + one: "Sondajul trebuie să aibă mai puțin de 1 opțiune." + few: "Sondajul trebuie să conțină cel mult %{count} opțiuni." + other: "Sondajul trebuie să aibă mai puțin de %{count} opțiuni." + named_poll_must_have_less_options: + one: "Sondajul numit %{name} trebuie să aibă cel mult 1 opțiune." + few: "Sondajul numit %{name} trebuie să aibă mai puțin de %{count} opțiuni." + other: "Sondajul numit %{name} trebuie să aibă cel mult %{count} opțiuni." + default_poll_must_have_different_options: "Sondajul trebuie să aibă opțiuni diferite." + named_poll_must_have_different_options: "Sondajul numit %{name} trebuie să aibă opțiuni diferite." + default_poll_with_multiple_choices_has_invalid_parameters: "Sondajul cu opțiuni multiple are parametri invalizi." + named_poll_with_multiple_choices_has_invalid_parameters: "Sondajul numit %{name} cu opțiuni multiple are parametri invalizi." + requires_at_least_1_valid_option: "Trebuie să selectezi cel puțin 1 valoare validă." + cannot_change_polls_after_5_minutes: "Nu poți adăuga, elimina sau redenumi sondaje după primele 5 minute." + op_cannot_edit_options_after_5_minutes: "Nu poți adăuga sau elimina opțiuni ale unui sondaj după primele 5 minute. Te rugăm contactează un moderator pentru a edita o opțiune din sondaj." + staff_cannot_add_or_remove_options_after_5_minutes: "Nu poți adăuga sau elimina opțiuni ale unui sondaj după primele 5 minute. Ar trebui să închizi discuția și să creezi alta în loc." + no_polls_associated_with_this_post: "Nu există sondaje asociate acestei postări." + no_poll_with_this_name: "Nu există nici un sondaj cu numele %{name} asociat acestei postări." + post_is_deleted: "Postările șterse nu se pot modifica." + topic_must_be_open_to_vote: "Discuția trebuie să fie activă pentru a se putea vota." + poll_must_be_open_to_vote: "Sondajul trebuie să fie deschis pentru a se putea vota." + topic_must_be_open_to_toggle_status: "Discuția trebuie să fie activă pentru a se schimba statutul." + only_staff_or_op_can_toggle_status: "Doar un administrator sau autorul postării inițiale pot schimba statusul unui sondaj." + email: + link_to_poll: "Click pentru afișarea sondajului." diff --git a/plugins/poll/config/locales/server.sk.yml b/plugins/poll/config/locales/server.sk.yml new file mode 100644 index 000000000..665bc70c7 --- /dev/null +++ b/plugins/poll/config/locales/server.sk.yml @@ -0,0 +1,41 @@ +# encoding: utf-8 +# +# Never edit this file. It will be overwritten when translations are pulled from Transifex. +# +# To work with us on translations, join this project: +# https://www.transifex.com/projects/p/discourse-org/ + +sk: + site_settings: + poll_enabled: "Umožnit používaťeľom vytvárať hlasovania?" + poll_maximum_options: "Maximálny počet povolených možností v hlasovaní." + poll: + multiple_polls_without_name: "Existujú viaceré hlasovania bez mena. Použite atribút 'meno', aby ste hlasovanie jednoznačne rozlíšili." + multiple_polls_with_same_name: "Existujú viaceré hlasovania s rovnakým menom: %{name}. Použite atribút 'meno', aby ste hlasovanie jednoznačne rozlíšili." + default_poll_must_have_at_least_2_options: "Hlasovanie musí mať minimálne 2 možnosti." + named_poll_must_have_at_least_2_options: "Hlasovanie s názvom %{name} musí mať minimálne 2 možnosti." + default_poll_must_have_less_options: + one: "Hlasovanie musí mať menej ako jednu možnosť." + few: "Hlasovanie musí mať menej ako %{count} možnosti." + other: "Hlasovanie musí mať menej ako %{count} možnosti." + named_poll_must_have_less_options: + one: "Hlasovanie s názvom %{name} musí mať menej ako 1 možnost." + few: "Hlasovanie s názvom %{name} musí mať menej ako %{count} možnosti." + other: "Hlasovanie s názvom %{name} musí mať menej ako %{count} možností." + default_poll_must_have_different_options: "Hlasovanie musí mať rôzne možnosti." + named_poll_must_have_different_options: "Hlasovanie s názvom %{name} musí mať rôzne možnosti." + default_poll_with_multiple_choices_has_invalid_parameters: "Hlasovanie s viac možnosťami má nesprávne parametre." + named_poll_with_multiple_choices_has_invalid_parameters: "Hlasovanie s názvom %{name} s viac možnosťami má nesprávne parametre." + requires_at_least_1_valid_option: "Musite vybrať aspoň 1 platnú možnosť." + cannot_change_polls_after_5_minutes: "Po prvých 5 minútach už nemôžete pridať, odstrániť alebo premenovať hlasovanie." + op_cannot_edit_options_after_5_minutes: "Po prvých 5 minútach už nemôžete pridať alebo odstrániť možnosti hlasovania. Ak potrebujete upraviť možnosti hlasovania prosím kontaktujte moderátora." + staff_cannot_add_or_remove_options_after_5_minutes: "Po prvých 5 minútach už nemôžete pridať alebo odstrániť možnosti hlasovania. Miesto toho by ste mali uzavrieť túto tému a miesto nej vytvoriť inú." + no_polls_associated_with_this_post: "S týmto príspevkom nie sú spojené žiadne hlasovania." + no_poll_with_this_name: "S týmto príspevkom nie je spojené žiadne hlasovanie s názvom %{name}." + post_is_deleted: "Na vymazanom príspevku nie je možné vykonať." + topic_must_be_open_to_vote: "Na odovzdanie hlasu téma otvorená." + poll_must_be_open_to_vote: "Na odovzdanie hlasu musí byť hlasovanie otvorené." + topic_must_be_open_to_toggle_status: "Na zmenu stavu musí byť téma otvorená." + only_staff_or_op_can_toggle_status: "Zmeniť stav hlasovania môže iba zamestnanec alebo pôvodný prispievateľ." + email: + link_to_poll: "Kliknite na zobrazenie hlasovania." diff --git a/plugins/poll/plugin.rb b/plugins/poll/plugin.rb index 18306e86c..45fb14e63 100644 --- a/plugins/poll/plugin.rb +++ b/plugins/poll/plugin.rb @@ -81,7 +81,7 @@ after_initialize do post.custom_fields[VOTES_CUSTOM_FIELD]["#{user_id}"] ||= {} post.custom_fields[VOTES_CUSTOM_FIELD]["#{user_id}"][poll_name] = options - post.custom_fields[VOTES_CUSTOM_FIELD].each do |user_id, user_votes| + post.custom_fields[VOTES_CUSTOM_FIELD].each do |_, user_votes| next unless votes = user_votes[poll_name] votes.each { |option| all_options[option] += 1 } poll["voters"] += 1 if (available_options & votes.to_set).size > 0 @@ -92,7 +92,7 @@ after_initialize do post.custom_fields[POLLS_CUSTOM_FIELD] = polls post.save_custom_fields(true) - MessageBus.publish("/polls/#{post_id}", { polls: polls }) + MessageBus.publish("/polls/#{post.topic_id}", { post_id: post_id, polls: polls }) return [poll, options] end @@ -128,7 +128,7 @@ after_initialize do post.save_custom_fields(true) - MessageBus.publish("/polls/#{post_id}", { polls: polls }) + MessageBus.publish("/polls/#{post.topic_id}", {post_id: post.id, polls: polls }) polls[poll_name] end @@ -350,7 +350,7 @@ after_initialize do post.save_custom_fields(true) # publish the changes - MessageBus.publish("/polls/#{post.id}", { polls: polls }) + MessageBus.publish("/polls/#{post.topic_id}", { post_id: post.id, polls: polls }) end end else @@ -370,7 +370,9 @@ after_initialize do # tells the front-end we have a poll for that post on(:post_created) do |post| next if post.is_first_post? || post.custom_fields[POLLS_CUSTOM_FIELD].blank? - MessageBus.publish("/polls", { post_id: post.id }) + MessageBus.publish("/polls/#{post.topic_id}", { + post_id: post.id, + polls: post.custom_fields[POLLS_CUSTOM_FIELD]}) end add_to_serializer(:post, :polls, false) { post_custom_fields[POLLS_CUSTOM_FIELD] } diff --git a/public/403.ko.html b/public/403.ko.html index cbf427be1..2e6cb00d5 100644 --- a/public/403.ko.html +++ b/public/403.ko.html @@ -20,7 +20,7 @@

    403

    해당 내용을 보실 수 없습니다.

    -

    Discourse 403 오류 페이지로 대체됩니다.

    +

    Discourse 자체 403 오류 페이지로 대체됩니다.

    diff --git a/public/403.sk.html b/public/403.sk.html new file mode 100644 index 000000000..eca597310 --- /dev/null +++ b/public/403.sk.html @@ -0,0 +1,26 @@ + + +Na toto nemáte oprávnenie (403) + + + + +
    +

    403

    +

    Nemáte oprávnenie na zobrazenie týchto údajov!

    + +

    Toto bude nahradené vlastnou Discourse 403 stránkou.

    +
    + + diff --git a/public/422.sk.html b/public/422.sk.html new file mode 100644 index 000000000..36e3684c1 --- /dev/null +++ b/public/422.sk.html @@ -0,0 +1,25 @@ + + +Požadovaná zmena bola zamietnutá (422) + + + + + +
    +

    Požadovaná zmena bola zamietnutá.

    +

    Možno ste skúšali zmeniť niečo k čomu nemáte prístup.

    +
    + + diff --git a/public/500.sk.html b/public/500.sk.html new file mode 100644 index 000000000..625a96497 --- /dev/null +++ b/public/500.sk.html @@ -0,0 +1,12 @@ + + +Oops - Chyba 500 + + + +

    Oops

    +

    V softvéri poháňajúcom toto diskusné fórum došlo k neočakávanej chybe. Ospravedlňujeme sa za spôsobené nepríjemnosti.

    +

    Podrobnosti o chybe boli zaznamenané a vygenerovalo sa automatické upozornenie. Pozrieme sa na to.

    +

    Nie je potrebná žiadna ďalšia akcia. Ak však chyba pretrváva, môžete poskytnúť dodatočné informácie, vrátane krokov vedúcich k navodeniu chyby založením diskusnej témy v meta kategórií.

    + + diff --git a/public/503.sk.html b/public/503.sk.html new file mode 100644 index 000000000..eae9f0922 --- /dev/null +++ b/public/503.sk.html @@ -0,0 +1,11 @@ + + +Na stránke sa práve vykonáva údržba. + + + +

    Stránka je momentálne nedostupná z dôvodu plánovanej údržby

    +

    Skúste prosím znova o niekoľko minút.

    +

    Ospravedlňujeme sa za spôsobené nepríjemnosti.

    + + diff --git a/public/javascripts/pikaday.js b/public/javascripts/pikaday.js index c0596d22d..bc0c64474 100644 --- a/public/javascripts/pikaday.js +++ b/public/javascripts/pikaday.js @@ -202,6 +202,9 @@ // first day of week (0: Sunday, 1: Monday etc) firstDay: 0, + // the default flag for moment's strict date parsing + formatStrict: false, + // the minimum/earliest date that can be selected minDate: null, // the maximum/latest date that can be selected @@ -230,6 +233,9 @@ // Render the month after year in the calendar title showMonthAfterYear: false, + // Render days of the calendar grid that fall in the next or previous month + showDaysInNextAndPreviousMonths: false, + // how many months are visible numberOfMonths: 1, @@ -274,10 +280,14 @@ renderDay = function(opts) { - if (opts.isEmpty) { - return '
    - - - - - - - - - -
    - - - techAPJ
    - November 28 -
    -

    Test reply.

    - -

    First paragraph.

    - -

    Second paragraph.

    -
    - - -
    -

    To respond, reply to this email or visit https:/= -/meta.discourse.org/t/testing-default-email-replies/22638/3 in your bro= -wser.

    -
    -
    -

    Previous Replies

    - - - - - - - - - - - -
    - - - codinghorror - November 28 -
    -

    We're testing the latest GitHub emai= -l processing library which we are integrating now.

    - -

    https://github.com/github/email_reply_parser

    - -

    Go ahead and reply to this topic and I&#= -39;ll reply from various email clients for testing.

    -
    - - -
    - -
    -

    To respond, reply to this email or visit https://met= -a.discourse.org/t/testing-default-email-replies/22638/3 in your browser= -.

    -
    -
    -

    To unsubscribe from these emails, visit your user preferences.

    -
    - - - ---089e0149cfa485c6630508f173df-- diff --git a/spec/fixtures/emails/attachment.eml b/spec/fixtures/emails/attachment.eml deleted file mode 100644 index f25c3d1a4..000000000 --- a/spec/fixtures/emails/attachment.eml +++ /dev/null @@ -1,351 +0,0 @@ -Message-ID: <51C22E52.1030509@darthvader.ca> -Date: Wed, 19 Jun 2013 18:18:58 -0400 -From: Anakin Skywalker -User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:17.0) Gecko/20130510 Thunderbird/17.0.6 -MIME-Version: 1.0 -To: Han Solo via Death Star -Subject: Re: [Death Star] [PM] re: Regarding your post in "Site Customization - not working" -References: <51d23d33f41fb_5f4e4b35d7d60798@xwing.mail> -In-Reply-To: <51d23d33f41fb_5f4e4b35d7d60798@xwing.mail> -Content-Type: multipart/mixed; boundary=047d7b45041e19c68004eb9f3de8 - ---047d7b45041e19c68004eb9f3de8 -Content-Type: multipart/alternative; boundary=047d7b45041e19c67b04eb9f3de6 - ---047d7b45041e19c67b04eb9f3de6 -Content-Type: text/plain; charset=ISO-8859-1 - -here is an image attachment - - -On Tue, Nov 19, 2013 at 5:11 PM, Neil wrote: - -> Neil -> November 19 -> -> Actually, deleting a spammer does what it's supposed to. It does mark the -> topic as deleted. -> -> That topic has id 11002, and you're right that the user was deleted. -> -> @eviltrout Any idea why it showed up in -> suggested topics? -> -> To respond, reply to this email or visit -> http://meta.discourse.org/t/spam-post-pops-back-up-in-suggested-topics/11005/5in your browser. -> ------------------------------ -> Previous Replies Neil -> November 19 -> -> Looks like a bug when deleting a spammer. I'll look at it. -> riking -> November 19 -> -> codinghorror: -> -> I can't even find that topic by name. -> -> In that case, I'm fairly certain someone used the 'Delete Spammer' -> function on the user, which would explain your inability to find it - it's -> gone. -> -> I'm raising this because, well, it's gone and shouldn't be showing up. And -> even if it was hanging around, it should be invisible to me, and not -> showing up in Suggested Topics. -> codinghorror -> November 19 -> -> Hmm, that's interesting -- can you have a look @eviltrout? -> I can't even find that topic by name. -> riking -> November 19 -> -> I'm one of the users who flagged this particular spam post, and it was -> promptly deleted/hidden, but it just popped up in the Suggested Topics box: -> -> Pasted image1125x220 27.7 KB -> -> -> We may want to recheck the suppression on these. -> ------------------------------ -> -> To respond, reply to this email or visit -> http://meta.discourse.org/t/spam-post-pops-back-up-in-suggested-topics/11005/5in your browser. -> -> To unsubscribe from these emails, visit your user preferences -> . -> - ---047d7b45041e19c67b04eb9f3de6 -Content-Type: text/html; charset=ISO-8859-1 -Content-Transfer-Encoding: quoted-printable - -
    here is an image attachment


    On Tue, Nov 19, 2013 at 5:11 PM, Neil = -<info@discourse.org> wrote:
    -
    - - - - - - - - -
    - - - Neil<= -/a>
    -No= -vember 19 -
    -

    Actually, deleting a spammer does what it's s= -upposed to. It does mark the topic as deleted.

    - -

    That topic has id 11002, and you're right tha= -t the user was deleted.

    - -

    @eviltrout Any idea why it showed up in suggested topics?

    -
    -
    -

    To respond, reply to this email or visit http://meta.discourse.org/t/spam-post-pops-back= --up-in-suggested-topics/11005/5 in your browser.

    - -
    -
    -

    Previous Replies

    - - - - - - - - - -
    - - - Neil<= -/a>
    -No= -vember 19 -

    Looks= - like a bug when deleting a spammer. I'll look at it.

    - - - - - - - - -
    - - - rik= -ing
    -No= -vember 19 -
    -

    -
    -codinghorror:
    -

    I can't even find that topic by n= -ame.

    - -

    In that case, I'm fairly certain someone used= - the 'Delete Spammer' function on the user, which would explain you= -r inability to find it - it's gone.

    - -

    I'm raising this because, well, it's gone= - and shouldn't be showing up. And even if it was hanging around, it sho= -uld be invisible to me, and not showing up in Suggested Topics.

    -
    - - - - - - - - -
    - - - codinghorror
    -No= -vember 19 -

    Hmm, = -that's interesting -- can you have a look @eviltrout? I can't even find that topic by= - name.

    -
    - - - - - - - - -
    - - - rik= -ing
    -No= -vember 19 -
    -

    I'm one of the users who flagged this particu= -lar spam post, and it was promptly deleted/hidden, but it just popped up in= - the Suggested Topics box:

    - -

    - - -

    We may want to recheck the suppression on these.<= -/p> -

    -
    -
    -

    To respond, reply to this email or visit http://meta.discourse.org/t/spam-post-pops-back-up-= -in-suggested-topics/11005/5 in your browser.

    - -
    -
    -

    To unsubscribe from these emails, visit your user pre= -ferences.

    -
    -

    - ---047d7b45041e19c67b04eb9f3de6-- ---047d7b45041e19c68004eb9f3de8 -Content-Type: image/png; name="bricks.png" -Content-Disposition: attachment; filename="bricks.png" -Content-Transfer-Encoding: base64 -X-Attachment-Id: f_ho8uteve0 - -iVBORw0KGgoAAAANSUhEUgAAASEAAAB+CAIAAADk0DDaAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ -bWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdp -bj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6 -eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEz -NDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJo -dHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlw -dGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAv -IiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RS -ZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpD -cmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNl -SUQ9InhtcC5paWQ6MDYxQjcyOUUzMDM1MTFFM0JFRTFBOTQ1RUY4QUU4MDIiIHhtcE1NOkRvY3Vt -ZW50SUQ9InhtcC5kaWQ6MDYxQjcyOUYzMDM1MTFFM0JFRTFBOTQ1RUY4QUU4MDIiPiA8eG1wTU06 -RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowNjFCNzI5QzMwMzUxMUUzQkVF -MUE5NDVFRjhBRTgwMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowNjFCNzI5RDMwMzUxMUUz -QkVFMUE5NDVFRjhBRTgwMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1w -bWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pm2fyz0AAAyISURBVHja7F2/i11FFL6rL12aBdlGRDCF -EQmEbVJtChfSJJDGRkgZBBsVUhgQ7NSkCKiFVUr/AUGbhW1MlWaJBAkWVsFmG0HshMT7duJk9szc -uefOjzPn3vd9xfL2/bh35rtnznfOuXNnth7c/6ID2Lh261vO13669wm4SsZ7H3396gmePXu2OkH/ -Yr4Mv4IrCgAYY8Am4vnz51sn8EVsXth68P7eYq7Kj4cP3H+v79fq2tWDX/u/d25/7n/08/3PzIvb -u3vLs3sxhh/vXrOvb9/50v1o77W/X340B5IXMsbsta931eN24I6uRQ4wd3SJkUwYnqkLQ6wIAHWx -gn/Nx3ff3Ov/njvbWFcXFibESdZw3aFjAKBDx46Ofk/42e7u2/3f4G8jH5XF07+O7es3tnfSThps -beRNA/PRmd1rxrlGkMNDf8a2DLskJzOcRrJ5/7czb/Z/fzk8qESyjBlDxwBAZT4WGd/1/CtxLcaz -ZiLYWvOmezpXxMQwxKQYwzIkK2S4LMnQMQCorGMm4C7irhp6nUzPHfSs7un6176jffT4cULSuGkM -+1mWq5b2jDlqRpJGdWNsFqNLxqrstfejxEzjA8l+LBpkm+DihQucmodyhhErAoCOmkcvx4t3xsG4 -RaZEbgOeZZNMwu9u+P7EkkiGjgGADh2LDH21Ehd0Wvz82E/VqiLOsE6JizM8iWSZ2n0TM4aOAYAO -HUvzDW0RbNhoa8ld0Ui2cPHCBU7JCwz7DDPzMc7dEf0krzqAESsCIBmxIgAsN1YUSKMlU/9N8KxD -+b02hvn3oDWbMXQMADZMxyIOtUnqn1lTVluuWAzD+kmGjgGAeh2rcfMu7YDCd8PFKss10qRkhiV1 -Q7J2X8+Mpe+PuRcpOCEgp59lOWry1GCRfgVJdg+STFRxK4yTLFnzSCCZacaIFQGgcqworP5FvKlM -YFBwvuIGkszscny+Ij9WlJ/SyY+8oGMAUFnHZIa+tpnjRVrCn68o0PFFMqztdGkkQ8cAQCQfI87A -X0lGlZtJW4gmx9Mnr5lDGuyenawko82RJ5OczLCflfHriprNGDoGAOL5WD/63QX7tU1USV7oq2FH -yKmNf7Ukq2V4RiRrNuOVf+3LLsSrYXTlI7l2TwLUSgvxahhdNRhmRuMkQNVmxogVAUBEx9yh7zoz -STc2quwFHVKTdX7sc/WtGB4NUMsynH/AqXOpdJoxdAwAKuuYGwc3SXj0TL2NIFi7n+pfWyU8c2E4 -p6mazRg6BgAi+ZgbRIpF2yRDKIhRuRhdMJTTu8v7VyY9dpFAcr4nJhlCDZKTGS4uNTrNOLBXLeeU -beuhVefm8Q8bma/4ZLt756+XRyMkM0+xVJL5x4zU7nuGe1iSNZsxYkUAqBwrBoXbf1Os2F3E/cg0 -NeJle//qPyRLGkZiLcmJ83MhOVK7d8OEIZKDZizTcjwHDQCCOubGtfHbdpNSVc6+UuYL1/f33JRx -RttwRfKxvv2mI4Ze63pHb4zySWZuj9Z/gTDczWc3uUik4OqSJZljxt2UslYRM4aOAUBlHTNDPxJN -EmEx/wbfPBUcHy2fu4iXPeiOu22aPAyR7Eu3JTl4ITaH4QjWDPfYZjHc1oxXoxHL0DtumyIJJWl3 -8CHF0QZkJqxFbsj4ExE4aw0Er32wj3GG48Unsg4Zh2T/dHb05iy9mBnnE5KZ8xWHSK5nxt3Ak6DB -IyNWBADBmkca+P6YfPr08JS8vFD/kGc69au8+dTJP89xz5kkT2J4iGTTfkuy35jgNZJkOIdkYTM+ -RdeYGUPHAKCyjpV1BqXwZHs8nxGG8VsHR+u/r1+6sX7rdM3jj3/WPvjc2eNgR9QyrJPkqzfvBqtK -PcmEYf0kQ8cAoC62rr4FEibAKJipd333zb2hr/m+FphKskWwrjgjhrfO7+zgonLw8ae3bPRirrp5 -Jz7YgEm4vH/F/df4srmTjFgRAKBjOkQsqGAG7kdAvoL18jU0h2aOJEPHAKCyjn34wY2hz9xomIC4 -GfPNtJ1FyW8jJ423Ie7/cnpnvmzyAZIw1OtdPsnkXO4P7Uf1Llm9CxE5sqtywdSulJlN6iB0DAAq -69j3X92ND8rgqHXdwNBvR4e7+4W4L0xug+/5gv5s9Mi9g/QVLO5TM3vHVJtI++OdCrJX8JKNXohS -ZjYaZYiZ2dChoGMAUFnHvn1LS13xzM1bHH/z7kOU79Lx26XxLOXf+7jdl8uwa8Ar5sqsZPk482R1 -WRyZS3vSxKAo//nwh/Xfrru9u7e8a+Mv0FeD5O7EQ5GRZvHz/c/s600guR7Dj1DzAIDGsaIrbmlb -0dnFRsh+oaOyaX5lHa3RXNe/Xul2hprK34+UNM9/TY5vWz70acdexMZvedpWdP6pO/aq8f3X/Mjc -kkwY7pK21Q0yk8Yh+UICwzkkB814lGTXjKFjANBOx0aH/qjX4bwZdADGy3b/zwR1J1nb54KC25O6 -p+AIy1TxKQjOhmCZDEdIdlMyc+vWkuw+eRXcZdeehcleK5KVmDF0DABa6FiRhZzS3K3rAOzjDEwd -S0gXJ31UFkUWckpzt1bH3MlHHB3LbJiwrNUz4yE7CZrxKnigIovIBkkcqjQII3KB6117clXESN4o -hmXM2C/hRPaMR6wIAHWxqudaguVO88I9XbKaNdn3tZJrzyfZDxDs6XLihSb7vupk2Cd51IxNKA4d -AwARHavtVIJ3ISO5L//hnFn4VwGSh4gdKuEwl7kGyUN3g4LTGAjMcwDQMQCYrY4FnUHatKNMzGhP -syLulkNy2hPQINl9zTTjVUFC+UUIX3+rItIYzZYxtEULM34jYSRB8cVn5kiyjBkjVgQAlbFicHzz -d/4cFVmxJb40xzYJJPOfAzDL18ksDKqW5GQznhQrQscAYBE1j0ggG4QpemJV0KokAzIMQ8cAQETH -ZCo/m+BZI0wG64StGC5eu1fCsCozNjf6Vw2z0syqveZyRXA4geTaI00bw5h3DwAS4I6xzH24p6IX -2UlLw+e4wxpdS3ColVqiRHOKd61neC4kQ8cAoC64tfuykW6TJ3OL9MtNA4LTmpKJKp5LzJpkwrB7 -kByimpgxdAwAdOhYmqcfQsE5wcLzTYtIlgDD3dic4EnPjwncdQiS3LCqWcSM8Rw0AMxTx4r4Hm3P -QQs7coEuT5oNLNB3bc+/FGmJmRK4GurtpNPkEBRcbIQfyQTT4rRF8MWMLG21n2SSgwxPmncfNImE -RfAlh7EeM0asCACCsWLyQl8NJT64IHvaJh1imfTCSNY230qPGWMuFQAI6ljD9UAjixhPjZ5rLHat -wb+2YtgnucZi10rChBokG0DHAEBExwoO+iJF2KlPQFv/2mRaLTMJLEVykzK3q2AaSK7KcCmSTz0/ -1hCZlWX3h/LBmJ45gVMZnjTPw/62STA2X5IRKwKAYKxYMK0cXcuS4wKnPgnvxmnMXuS74d5pTT1v -keoIh+FRkgUYztc6PwgXq44UNGPoGACI61i9uXlFDvtk+8VmquZoZCIP8xRti871ihalGO66XJKb -l/U1mDHmUgFAIx2LD9Pm/qn3r/5DsqRtJNqWLHYX8fFtSXYVjJDsNoykJWIkFwlVZBg2+dhK59CP -VJbtmDEhjWsW8fs2/HoAcx3z/gvX9/dIUj6XLYLiDNuOWJI5DE+qB3BINp8Skme3CRNiRQAQjxWV -46A77jwFi0QCJPc1XjD45kv/fbT8Cx+p3a8Z7sEmmQiLZXjoQrzQug0gGTUPABDUsZwYt8gMJrub -06iXjTtXP/UayiLcLkeydvtmcJo/swH2+JkM55BMJvsw51KVJTnOcDcwzX8Sw6rMGPkYAIjomOsP -MudT5/ycOA/+jFX3hmmRNkf8Mfn06eEpz/cijQm5/+DPhUkmDE+aS2Xv+xdpc5zhU3QdUgG3JA8x -rMqMMZcKAATzseboncfB0dp/XL151//0j3/W7uHc2WNfwQq624Igt5WUMLzWgf9Jvnjyphsp9CQT -hn2SM6OGGgxrI9kw/PqlGy/HmG+prRAcXaMjjTDepDtPumOOKeghuY9hgtvicBgGyXGY0WXoRawI -ABLYOr+jYk6KWVGV1Dy6icvZAqMMu/7VAnvbFzdjN0yAjgHA0mseZukO4lnNv70zMI4BjrZgjOA7 -WqhZcZJde4aOAUDlfOz7r+6SYdd7OPJv51Si3AQp6CD9Hw65TytW/tCPwz9y/FyRb7r/Tu3pEFHx -/g7pCbOR8SP7Le/DBNI7v+Uckl2VC2YdkQMmXAi/zfGm+t8hJ2U2tdQldr/5nwADACLM1IGrPYuL -AAAAAElFTkSuQmCC ---047d7b45041e19c68004eb9f3de8-- diff --git a/spec/fixtures/emails/auto_generated_header.eml b/spec/fixtures/emails/auto_generated_header.eml new file mode 100644 index 000000000..74e0e9d4f --- /dev/null +++ b/spec/fixtures/emails/auto_generated_header.eml @@ -0,0 +1,8 @@ +Return-Path: +From: Foo Bar +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <3@foo.bar.mail> +Auto-Submitted: auto-generated +Mime-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit diff --git a/spec/fixtures/emails/auto_generated_precedence.eml b/spec/fixtures/emails/auto_generated_precedence.eml new file mode 100644 index 000000000..bc82e2b3c --- /dev/null +++ b/spec/fixtures/emails/auto_generated_precedence.eml @@ -0,0 +1,8 @@ +Return-Path: +From: Foo Bar +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <2@foo.bar.mail> +Precedence: list +Mime-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit diff --git a/spec/fixtures/emails/auto_reply.eml b/spec/fixtures/emails/auto_reply.eml deleted file mode 100644 index 7999c8d78..000000000 --- a/spec/fixtures/emails/auto_reply.eml +++ /dev/null @@ -1,21 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: reply+636ca428858779856c226bb145ef4fad@appmail.adventuretime.ooo -Message-ID: -Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -Auto-Submitted: auto-generated -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - -Test reply to Discourse email digest diff --git a/spec/fixtures/emails/bad_destinations.eml b/spec/fixtures/emails/bad_destinations.eml new file mode 100644 index 000000000..5690e6677 --- /dev/null +++ b/spec/fixtures/emails/bad_destinations.eml @@ -0,0 +1,11 @@ +Return-Path: +From: Foo Bar +To: wat@bar.com +Cc: foofoo@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <9@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. diff --git a/spec/fixtures/emails/big5.eml b/spec/fixtures/emails/big5.eml deleted file mode 100644 index 4a7b20824..000000000 --- a/spec/fixtures/emails/big5.eml +++ /dev/null @@ -1,26 +0,0 @@ - -Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org -Received: by 10.194.216.104 with SMTP id op8csp80593wjc; - Wed, 24 Jul 2013 07:59:14 -0700 (PDT) -Return-Path: -References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -From: Walter White -In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -Mime-Version: 1.0 (1.0) -Date: Wed, 24 Jul 2013 15:59:10 +0100 -Message-ID: <4597127794206131679@unknownmsgid> -Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' -To: walter via Discourse -Content-Type: multipart/alternative; boundary=20cf301cc47ada510404f040b262 - ---20cf301cc47ada510404f040b262 -Content-Type: text/plain; charset=Big5 -Content-Transfer-Encoding: base64 - -tv2hSafapFe5cbX4pEahSQ0K ---20cf301cc47ada510404f040b262 -Content-Type: text/html; charset=Big5 -Content-Transfer-Encoding: base64 - -PGRpdiBkaXI9Imx0ciI+tv2hSafapFe5cbX4pEahSTxicj48L2Rpdj4NCg== ---20cf301cc47ada510404f040b262-- diff --git a/spec/fixtures/emails/bottom_reply.eml b/spec/fixtures/emails/bottom_reply.eml deleted file mode 100644 index 5fc992971..000000000 --- a/spec/fixtures/emails/bottom_reply.eml +++ /dev/null @@ -1,160 +0,0 @@ -Received: by 10.107.19.29 with SMTP id b29csp111716ioj; - Wed, 30 Jul 2014 17:52:05 -0700 (PDT) -X-Received: by 10.194.238.6 with SMTP id vg6mr11340975wjc.24.1406767925330; - Wed, 30 Jul 2014 17:52:05 -0700 (PDT) -Received: from localhost (localhost [127.0.0.1]) - by bendel.debian.org (Postfix) with QMQP - id 18F5C417; Thu, 31 Jul 2014 00:52:04 +0000 (UTC) -Old-Return-Path: -X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on bendel.debian.org -X-Spam-Level: -X-Spam-Status: No, score=-25.9 required=4.0 tests=FOURLA,LDOSUBSCRIBER, - LDO_WHITELIST,MURPHY_DEBIAN_MESSAGE,PGPSIGNATURE autolearn=unavailable - version=3.3.2 -X-Original-To: lists-debian-ctte@bendel.debian.org -Delivered-To: lists-debian-ctte@bendel.debian.org -Received: from localhost (localhost [127.0.0.1]) - by bendel.debian.org (Postfix) with ESMTP id CE6CDEE - for ; Thu, 31 Jul 2014 00:51:52 +0000 (UTC) -X-Virus-Scanned: at lists.debian.org with policy bank en-lt -X-Amavis-Spam-Status: No, score=-11.9 tagged_above=-10000 required=5.3 - tests=[BAYES_00=-2, FOURLA=0.1, LDO_WHITELIST=-5, PGPSIGNATURE=-5] - autolearn=ham -Received: from bendel.debian.org ([127.0.0.1]) - by localhost (lists.debian.org [127.0.0.1]) (amavisd-new, port 2525) - with ESMTP id SB451DwGZCOe for ; - Thu, 31 Jul 2014 00:51:47 +0000 (UTC) -X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_BL_NJABL=-1.5 CL_IP_EQ_HELO_IP=-2 (check from: .debian. - helo: .becquer.dodds. - helo-domain: .dodds.) FROM/MX_MATCHES_NOT_HELO(DOMAIN)=0; rate: -5 -Received: from becquer.dodds.net (becquer.dodds.net [207.224.24.209]) - by bendel.debian.org (Postfix) with ESMTP id 8E89A2B4 - for ; Thu, 31 Jul 2014 00:51:47 +0000 (UTC) -Received: from virgil.dodds.net (unknown [192.168.15.59]) - by becquer.dodds.net (Postfix) with ESMTPA id 9B0A9256EB - for ; Wed, 30 Jul 2014 17:51:19 -0700 (PDT) -Received: by virgil.dodds.net (Postfix, from userid 1000) - id 942FB60199; Wed, 30 Jul 2014 17:51:15 -0700 (PDT) -Date: Wed, 30 Jul 2014 17:51:15 -0700 -From: Jake -To: incoming+amazing@appmail.adventuretime.ooo -Subject: Re: Next Debian CTTE IRC Meeting at date -d'Thu Jul 31 17:00:00 UTC - 2014' -Message-ID: <20140731005115.GA19044@virgil.dodds.net> -Mail-Followup-To: debian-ctte@lists.debian.org -References: <20140730213924.GA12356@teltox.donarmstrong.com> -MIME-Version: 1.0 -Content-Type: multipart/signed; micalg=pgp-sha256; - protocol="application/pgp-signature"; boundary="qMm9M+Fa2AknHoGS" -Content-Disposition: inline -In-Reply-To: <20140730213924.GA12356@teltox.donarmstrong.com> -User-Agent: Mutt/1.5.23 (2014-03-12) -X-Debian-Message: Signature check passed for Debian member -X-Rc-Virus: 2007-09-13_01 -X-Rc-Spam: 2008-11-04_01 -Resent-Message-ID: -Resent-From: debian-ctte@lists.debian.org -X-Mailing-List: archive/latest/4791 -X-Loop: debian-ctte@lists.debian.org -List-Id: -List-Post: -List-Help: -List-Subscribe: -List-Unsubscribe: -Precedence: list -Resent-Sender: debian-ctte-request@lists.debian.org -Resent-Date: Thu, 31 Jul 2014 00:52:04 +0000 (UTC) - - ---qMm9M+Fa2AknHoGS -Content-Type: text/plain; charset=us-ascii -Content-Disposition: inline -Content-Transfer-Encoding: quoted-printable - -On Wed, Jul 30, 2014 at 02:39:24PM -0700, Don Armstrong wrote: -> The next Debian CTTE IRC meeting is at=20 - -> date -d 'Thu Jul 31 17:00:00 UTC 2014' on irc.debian.org in -> #debian-ctte. - -> The current meeting agenda is here, and more up-to-date ones may be -> found in the git repository. - -> #startmeeting - -> #topic Who is here? - -> #topic Next Meeting? - -> #topic #717076 Decide between libjpeg-turbo and libjpeg8 et al. - -This has been voted on; should probably be removed from the agenda, someone -just needs to confirm the vote results and get it on the website. (AIUI the -archive has already begun moving on accordingly.) - -> #topic #636783 constitution: super-majority bug - -> #topic #636783 constitution: casting vote - -> #topic #636783 constitution: minimum discussion period - -> #topic #636783 constitution: TC member retirement/rollover - -> #topic #636783 constitution: TC chair retirement/rollover - -> #topic #681419 Depends: foo | foo-nonfree - -> #topic #741573 menu systems and mime-support - -> #topic #746715 init system fallout - -Also voted and just needs to be confirmed. - -> #topic #750135 Maintainer of aptitude package ->=20 -> #topic #752400 Advice on util-linux - -This has been closed by mutual agreement of the people involved and doesn't -require any action from the TC. Removed from the agenda. - -There's also bug #744246, which was assigned to the TC at my request but -without any preamble so it may have escaped notice. However, that situation -has been evolving constructively among the related parties (apparently, an -informal comment from a member of the release team was mistaken for a -release team position, so that's now being revisited), so I don't believe -this is anything we need to put on the agenda for tomorrow despite being on -the open bug list. - ---=20 -Steve Langasek Give me a lever long enough and a Free OS -Debian Developer to set it on, and I can move the world. -Ubuntu Developer http://www.debian.org/ -slangasek@ubuntu.com vorlon@debian.org - ---qMm9M+Fa2AknHoGS -Content-Type: application/pgp-signature; name="signature.asc" -Content-Description: Digital signature - ------BEGIN PGP SIGNATURE----- -Version: GnuPG v1 - -iQIcBAEBCAAGBQJT2ZMDAAoJEFaNMPMhshM9GLsP/244S3wtYZEeVgJWIdB5PE0A -sZVezEA692y++/0oVZVecwV67yBOyfSjPPetdAph2UDMRtfurwxfj2BkbOFA2+Y6 -++MErbmC3V7IGpd/L/fFGdXgvMQT2MNBpw0fnMA7bLpNjCvoj+Hr1HXRUcWoJSlj -WmHWwWSTVRcHg8a3iWYJzY6XfLyEEgHlahrlKvJExsTx/9mc1qg7g8KGdnhzHFBl -ttdH2fxpAk/624dReCcw5RKmOLfZ1HsEl9XcVe1cb4K+MDaQiXmoEK5v3xaNz1tS -NK5v2D5gDs229zoxKzQnnzOPLHxqI5E0L9PpI/mu4T9z7H2bHR3U5BvhnT99t5uw -ydf2cZNGY0uFCV3Rvn07BfAIW5WSXhOfN/5IymRKmdhjsTiwZ/wFjFrK8tVjtERu -yeyA7RIYiblGCEKYIYLWSxhoXeEdmAdfp6EA2/IA1CpgMB+ZdSfaeMgFY7xosgmG -ax3NTnaKyhr1QEUJ2gjAwHnKjuGbRVDAinYrSvP0o8Bh9sAs2BN2negWBCZVwwkN -S9hWTjVqsBmpaPOt5SEDwDo9O9dfzkmaamDsxOuUEz9F7v5jYg0mxA/WbogGty9R -vOMKxdxRkzflL/CferVbkzL/EkZRDfWDp9SleZggrpz7miiNDbS7jdRzJ4EttmJ8 -gHBAVrOzcnbIPOIkk9pw -=KXIu ------END PGP SIGNATURE----- - ---qMm9M+Fa2AknHoGS-- - - --- -To UNSUBSCRIBE, email to debian-ctte-REQUEST@lists.debian.org -with a subject of "unsubscribe". Trouble? Contact listmaster@lists.debian.org -Archive: https://lists.debian.org/20140731005115.GA19044@virgil.dodds.net diff --git a/spec/fixtures/emails/boundary.eml b/spec/fixtures/emails/boundary.eml deleted file mode 100644 index 1250fe498..000000000 --- a/spec/fixtures/emails/boundary.eml +++ /dev/null @@ -1,61 +0,0 @@ - -MIME-Version: 1.0 -Received: by 10.64.14.41 with HTTP; Wed, 19 Jun 2013 06:29:41 -0700 (PDT) -In-Reply-To: <51c19490e928a_13442dd8ae892548@tree.mail> -References: <51c19490e928a_13442dd8ae892548@tree.mail> -Date: Wed, 19 Jun 2013 09:29:41 -0400 -Delivered-To: finn@adventuretime.ooo -Message-ID: -Subject: Re: [Adventure Time] jake mentioned you in 'peppermint butler is - missing' -From: Finn the Human -To: jake via Adventure Time -Content-Type: multipart/alternative; boundary=001a11c206a073876a04df81d2a9 - ---001a11c206a073876a04df81d2a9 -Content-Type: text/plain; charset=ISO-8859-1 - -I'll look into it, thanks! - - -On Wednesday, June 19, 2013, jake via Discourse wrote: - -> jake mentioned you in 'peppermint butler is missing' on Adventure -> Time: -> ------------------------------ -> -> yeah, just noticed this cc @jake -> ------------------------------ -> -> Please visit this link to respond: -> http://adventuretime.ooo/t/peppermint-butler-is-missing/7628/2 -> -> To unsubscribe from these emails, visit your user preferences -> . -> - ---001a11c206a073876a04df81d2a9 -Content-Type: text/html; charset=ISO-8859-1 -Content-Transfer-Encoding: quoted-printable - -I'll look into it, thanks!

    On Wednesday, June 19, 2= -013, jake via Adventure Time wrote:

    sa= -m mentioned you in 'Duplicate message are shown in profile' on Adve= -nture Time

    - - -

    yeah, just noticed this cc @eviltrout

    - -

    Please visit this link to respond: http= -://adventuretime.ooo/t/peppermint-butler-is-missing/7628/2 - - -

    To unsubscribe from these emails, visit your user preferences.

    -
    - ---001a11c206a073876a04df81d2a9-- diff --git a/spec/fixtures/emails/cc.eml b/spec/fixtures/emails/cc.eml new file mode 100644 index 000000000..c54363b90 --- /dev/null +++ b/spec/fixtures/emails/cc.eml @@ -0,0 +1,12 @@ +Return-Path: +From: Foo Bar +To: someone@else.com +CC: team@bar.com, wat@bar.com, reply+d400310beeae61d785c2ac6a2aacb210@bar.com +Subject: The more, the merrier +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <30@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +It is more fun with more people. diff --git a/spec/fixtures/emails/chinese_reply.eml b/spec/fixtures/emails/chinese_reply.eml new file mode 100644 index 000000000..befb3b1cb --- /dev/null +++ b/spec/fixtures/emails/chinese_reply.eml @@ -0,0 +1,10 @@ +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <17@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain; charset=Big5 +Content-Transfer-Encoding: base64 + +5oKo5aW977yBIOS9oOS7iuWkqeWlveWQl++8nw== diff --git a/spec/fixtures/emails/dutch.eml b/spec/fixtures/emails/dutch.eml deleted file mode 100644 index 7be08dc49..000000000 --- a/spec/fixtures/emails/dutch.eml +++ /dev/null @@ -1,20 +0,0 @@ - -Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org -Received: by 10.194.216.104 with SMTP id op8csp80593wjc; - Wed, 24 Jul 2013 07:59:14 -0700 (PDT) -Return-Path: -References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -From: Walter White -In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -Mime-Version: 1.0 (1.0) -Date: Wed, 24 Jul 2013 15:59:10 +0100 -Message-ID: <4597127794206131679@unknownmsgid> -Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' -To: walter via Discourse -Content-Type: multipart/alternative; boundary=001a11c20edc15a39304e2432790 - -Dit is een antwoord in het Nederlands. - -Op 18 juli 2013 10:23 schreef Sander Datema het volgende: - -Dit is de originele post. \ No newline at end of file diff --git a/spec/fixtures/emails/email_reply_1.eml b/spec/fixtures/emails/email_reply_1.eml new file mode 100644 index 000000000..fdf4a5f2f --- /dev/null +++ b/spec/fixtures/emails/email_reply_1.eml @@ -0,0 +1,12 @@ +Return-Path: +From: One +To: team@bar.com +Cc: two@foo.com, three@foo.com +Subject: Testing email threading +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <34@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit + +This is email reply **1**. diff --git a/spec/fixtures/emails/email_reply_2.eml b/spec/fixtures/emails/email_reply_2.eml new file mode 100644 index 000000000..fde2e0339 --- /dev/null +++ b/spec/fixtures/emails/email_reply_2.eml @@ -0,0 +1,13 @@ +Return-Path: +From: Two +To: one@foo.com +Cc: team@bar.com, three@foo.com +Subject: RE: Testing email threading +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <35@foo.bar.mail> +In-Reply-To: <34@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit + +This is email reply **2**. diff --git a/spec/fixtures/emails/email_reply_3.eml b/spec/fixtures/emails/email_reply_3.eml new file mode 100644 index 000000000..3b4eb6d0c --- /dev/null +++ b/spec/fixtures/emails/email_reply_3.eml @@ -0,0 +1,14 @@ +Return-Path: +From: Three +To: one@foo.com +Cc: team@bar.com, two@foo.com +Subject: RE: Testing email threading +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <36@foo.bar.mail> +In-Reply-To: <35@foo.bar.mail> +References: <34@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit + +This is email reply **3**. diff --git a/spec/fixtures/emails/email_reply_4.eml b/spec/fixtures/emails/email_reply_4.eml new file mode 100644 index 000000000..4c54d0084 --- /dev/null +++ b/spec/fixtures/emails/email_reply_4.eml @@ -0,0 +1,15 @@ +Return-Path: +From: One +To: two@foo.com +Cc: team@bar.com, three@foo.com +Subject: RE: Testing email threading +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <37@foo.bar.mail> +In-Reply-To: <36@foo.bar.mail> +References: <34@foo.bar.mail> + <35@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit + +This is email reply **4**. diff --git a/spec/fixtures/emails/empty.eml b/spec/fixtures/emails/empty.eml deleted file mode 100644 index 85bebc662..000000000 --- a/spec/fixtures/emails/empty.eml +++ /dev/null @@ -1,21 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo -Message-ID: -Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - - - diff --git a/spec/fixtures/emails/encoded_display_name.eml b/spec/fixtures/emails/encoded_display_name.eml new file mode 100644 index 000000000..cf31f703e --- /dev/null +++ b/spec/fixtures/emails/encoded_display_name.eml @@ -0,0 +1,11 @@ +Return-Path: +From: =?UTF-8?B?0KHQu9GD0YfQsNC50L3QsNGP?= =?UTF-8?B?INCY0LzRjw==?= +To: team@bar.com +Subject: I need help +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <29@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Будьте здоровы! diff --git a/spec/fixtures/emails/from_the_future.eml b/spec/fixtures/emails/from_the_future.eml new file mode 100644 index 000000000..6e14c442e --- /dev/null +++ b/spec/fixtures/emails/from_the_future.eml @@ -0,0 +1,10 @@ +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Wed, 01 Jan 3000 00:00:00 +0100 +Message-ID: <4@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit + +Back to the future! diff --git a/spec/fixtures/emails/gmail_web.eml b/spec/fixtures/emails/gmail_web.eml deleted file mode 100644 index 8bb838357..000000000 --- a/spec/fixtures/emails/gmail_web.eml +++ /dev/null @@ -1,181 +0,0 @@ -Delivered-To: reply@discourse.org -Return-Path: -MIME-Version: 1.0 -In-Reply-To: -References: - -Date: Fri, 28 Nov 2014 12:36:49 -0800 -Subject: Re: [Discourse Meta] [Lounge] Testing default email replies -From: Walter White -To: Discourse Meta -Content-Type: multipart/alternative; boundary=001a11c2e04e6544f30508f138ba - ---001a11c2e04e6544f30508f138ba -Content-Type: text/plain; charset=UTF-8 - -### This is a reply from standard GMail in Google Chrome. - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. - -Here's some **bold** text in Markdown. - -Here's a link http://example.com - -On Fri, Nov 28, 2014 at 12:35 PM, Arpit Jalan wrote: - -> techAPJ -> November 28 -> -> Test reply. -> -> First paragraph. -> -> Second paragraph. -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> ------------------------------ -> Previous Replies codinghorror -> -> November 28 -> -> We're testing the latest GitHub email processing library which we are -> integrating now. -> -> https://github.com/github/email_reply_parser -> -> Go ahead and reply to this topic and I'll reply from various email clients -> for testing. -> ------------------------------ -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> -> To unsubscribe from these emails, visit your user preferences -> . -> - ---001a11c2e04e6544f30508f138ba -Content-Type: text/html; charset=UTF-8 -Content-Transfer-Encoding: quoted-printable - -
    ### This is a reply from standard GMail in Google Chr= -ome.

    The quick brown fox jumps over the lazy dog. = -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over= - the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown= - fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. = -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over= - the lazy dog.=C2=A0

    Here's some **bold** text= - in Markdown.

    Here's a link http://example.com
    <= -br>
    On Fri, Nov 28, 2014 at 12:35 PM, Arpit Jalan= - <info@discourse.org> wrote:
    - - - - - - - - - - - -
    - - - techAPJ
    - November 28 -
    -

    Test reply.

    - -

    First paragraph.

    - -

    Second paragraph.

    -
    - - -
    -

    To respond, reply to this email or visit https:/= -/meta.discourse.org/t/testing-default-email-replies/22638/3 in your bro= -wser.

    -
    -
    -

    Previous Replies

    - - - - - - - - - - - -
    - - - codinghorror - November 28 -
    -

    We're testing the latest GitHub emai= -l processing library which we are integrating now.

    - -

    https://github.com/github/email_reply_parser

    - -

    Go ahead and reply to this topic and I&#= -39;ll reply from various email clients for testing.

    -
    - - -
    - -
    -

    To respond, reply to this email or visit https://met= -a.discourse.org/t/testing-default-email-replies/22638/3 in your browser= -.

    -
    -
    -

    To unsubscribe from these emails, visit your user preferences.

    -
    -
    -

    - ---001a11c2e04e6544f30508f138ba-- diff --git a/spec/fixtures/emails/hebrew.eml b/spec/fixtures/emails/hebrew.eml deleted file mode 100644 index f4c8f01ad..000000000 --- a/spec/fixtures/emails/hebrew.eml +++ /dev/null @@ -1,17 +0,0 @@ - -Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org -Received: by 10.194.216.104 with SMTP id op8csp80593wjc; - Wed, 24 Jul 2013 07:59:14 -0700 (PDT) -Return-Path: -References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -From: Walter White -In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -Mime-Version: 1.0 (1.0) -Date: Wed, 24 Jul 2013 15:59:10 +0100 -Message-ID: <4597127794206131679@unknownmsgid> -Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' -To: walter via Discourse -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: base64 - -16nXnNeV150= \ No newline at end of file diff --git a/spec/fixtures/emails/hebrew_reply.eml b/spec/fixtures/emails/hebrew_reply.eml new file mode 100644 index 000000000..450e2dea2 --- /dev/null +++ b/spec/fixtures/emails/hebrew_reply.eml @@ -0,0 +1,10 @@ +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <16@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: base64 + +16nXnNeV150hINee15Qg16nXnNeV157XmiDXlNeZ15XXnT8= diff --git a/spec/fixtures/emails/html_only.eml b/spec/fixtures/emails/html_only.eml deleted file mode 100644 index db88f2c38..000000000 --- a/spec/fixtures/emails/html_only.eml +++ /dev/null @@ -1,93 +0,0 @@ - -Delivered-To: walter@breakingbad.com -Received: by 10.64.13.41 with SMTP id m9csp29769iec; - Thu, 20 Jun 2013 08:53:22 -0700 (PDT) -X-Received: by 10.252.23.9 with SMTP id p9mr4055675lag.4.1371743601980; - Thu, 20 Jun 2013 08:53:21 -0700 (PDT) -Received: from mail-la0-x229.google.com (mail-la0-x229.google.com [2a00:1450:4010:c03::229]) - by mx.google.com with ESMTPS id u4si430203lae.48.2013.06.20.08.53.20 - for - (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); - Thu, 20 Jun 2013 08:53:21 -0700 (PDT) -X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; - d=google.com; s=20120113; - h=x-forwarded-to:x-forwarded-for:delivered-to:x-return-path - :content-type:mime-version:content-transfer-encoding:x-mailer - :message-id:date:subject:from:in-reply-to:to:resent-date:resent-from - :resent-to:resent-subject:resent-message-id:resent-user-agent - :x-scanned-by:x-gm-message-state; - bh=9O67r74ofh9WkEaKTRB/frQ3MKOtQlbCac2mz0/MiyY=; - b=YVAo2/JDMP53RxDmqDEKNcEMtggtfaVyq2DoseZ6vBAfB7G6NtHC9ZEkRs4oGhk6LU - fnyAPe0wnz5d9WINoMAuuTRIhplLxzcqysduSnAJAQ2qqR7mFBnlj9wJeVEKltNwmUME - nPwxsf8go20VBzrZCtECPedcLi60wbl32NCXVn0qwt2LvKiy6ktSS5Xgb4zY8i4dfXAP - 6Y5gu32boooWIb9DkH1TJkn3C0RrEugNlw/DUnXrnkFefgxWF3pt/zcoW/wYRyikOdx+ - smBClgR9my6QmsS2KsQrMvWJZUva7fddTiZ6FC22e4hW+8Wha0RaZOZu5O7hjg6G4/1g - IEyg== -X-Received: by 10.112.55.9 with SMTP id n9mr5916187lbp.5.1371743600857; - Thu, 20 Jun 2013 08:53:20 -0700 (PDT) -X-Forwarded-To: walter@breakingbad.com -X-Forwarded-For: walter@breakingbad.com -Delivered-To: walter@breakingbad.com -Content-Type: text/html; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: quoted-printable -X-Mailer: BlackBerry Email (10.1.0.1720) -Message-ID: <20130619231548.6307981.74194.2379@breakingbad.com> -Date: Wed, 19 Jun 2013 19:15:48 -0400 -Subject: Re: [Discourse Meta] [PM] re: Regarding your post in "Site - Customization not working" -From: aaron@breakingbad.com -In-Reply-To: <51c238655a394_5f4e3ce6690667bd@tiefighter2.mail> -To: reply+20c1b0a8bd1a63c0163cc7e7641ca06b@appmail.adventuretime.ooo -ReSent-Date: Thu, 20 Jun 2013 11:53:08 -0400 (EDT) -ReSent-From: Aaron -ReSent-Subject: Re: [Discourse Meta] [PM] re: Regarding your post in "Site - Customization not working" -X-Gm-Message-State: ALoCoQl1BtN83rAX7At808XAPv1yCqUK3Du2IvK7eCyY3jsI77u4e5cak28307pYYHAo1JlO/Eu9 - -
    The EC2 instance - I've seen that th= -ere tends to be odd and unrecommended settings on the Bitnami installs that= - I've checked out.
    = - = -

    = - = -
    = - = - = -
    = -From: Grizzly B via Discourse Meta
    Sent: Wednesday, J= -une 19, 2013 19:02
    To: aaron@breakingbad.com
    = -Reply To: Grizzly B via Discourse Meta
    Subject: [Disc= -ourse Meta] [PM] re: Regarding your post in "Site Customization
    not wor= -king"

    Grizzly B just sent you a private message

    - -

    Log in to our EC2 instance -or- log into a new DigitalOcean instanc= -e?

    - -

    Please visit this link to respond: http://= -meta.discourse.org/t/regarding-your-post-in-site-customization-not-working/= -7641/5

    - -

    To unsubscribe from these emails, visit your user preferences.

    -
    diff --git a/spec/fixtures/emails/html_paragraphs.eml b/spec/fixtures/emails/html_paragraphs.eml deleted file mode 100644 index 3fe37fb8b..000000000 --- a/spec/fixtures/emails/html_paragraphs.eml +++ /dev/null @@ -1,205 +0,0 @@ - -MIME-Version: 1.0 -Received: by 10.25.161.144 with HTTP; Tue, 7 Oct 2014 22:17:17 -0700 (PDT) -X-Originating-IP: [117.207.85.84] -In-Reply-To: <5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail> -References: - <5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail> -Date: Wed, 8 Oct 2014 10:47:17 +0530 -Delivered-To: arpit@techapj.com -Message-ID: -Subject: Re: [Discourse] [Meta] Welcome to techAPJ's Discourse! -From: Arpit Jalan -To: Discourse -Content-Type: multipart/alternative; boundary=001a114119d8f4e46e0504e26d5b - ---001a114119d8f4e46e0504e26d5b -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: quoted-printable - -Awesome! - -Pleasure to have you here! - -:boom: - -On Wed, Oct 8, 2014 at 10:46 AM, ajalan -wrote: - -> ajalan -> -> October 8 -> -> Nice to be here! Thanks! [image: smile] -> -> To respond, reply to this email or visit -> http://discourse.techapj.com/t/welcome-to-techapjs-discourse/35/2 -> -> in your browser. -> ------------------------------ -> Previous Replies techAPJ -> -> October 8 -> -> Welcome to techAPJ's Discourse! -> ------------------------------ -> -> To respond, reply to this email or visit -> http://discourse.techapj.com/t/welcome-to-techapjs-discourse/35/2 -> -> in your browser. -> -> To unsubscribe from these emails, visit your user preferences -> -> . -> - ---001a114119d8f4e46e0504e26d5b -Content-Type: text/html; charset=UTF-8 -Content-Transfer-Encoding: quoted-printable - -
    Awesome!

    Pleasure to have you here!

    :boom:

    On Wed, Oct 8, 2014 at 10:46 AM, ajalan <info@unconfigured.discourse.org> wrote:
    - - - - - - - - - - - -
    - - - ajalan
    - October 8 -

    Nice to be here! Thanks! 3D"smile"

    - - -
    -

    To respond, reply to this email or visit http://discourse= -.techapj.com/t/welcome-to-techapjs-discourse/35/2 in your browser.

    -
    -
    -

    Previous Replies

    - - - - - - - - - - - -
    - - - techAPJ
    - October 8 -

    Welcome to techAPJ's Discourse!

    - - -
    - -
    -

    To respond, reply to this email or visit http://discourse.tec= -hapj.com/t/welcome-to-techapjs-discourse/35/2 in your browser.

    -
    -
    -

    To unsubscribe from these emails, visit your user preferences.

    -
    -
    - -
    = -
    - ---001a114119d8f4e46e0504e26d5b-- diff --git a/spec/fixtures/emails/html_reply.eml b/spec/fixtures/emails/html_reply.eml new file mode 100644 index 000000000..28e5feff8 --- /dev/null +++ b/spec/fixtures/emails/html_reply.eml @@ -0,0 +1,10 @@ +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <18@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +
    This is a HTML reply ;)
    diff --git a/spec/fixtures/emails/inactive_sender.eml b/spec/fixtures/emails/inactive_sender.eml new file mode 100644 index 000000000..d22e95078 --- /dev/null +++ b/spec/fixtures/emails/inactive_sender.eml @@ -0,0 +1,9 @@ +Return-Path: +From: Foo Bar +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <8@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. diff --git a/spec/fixtures/emails/inline_attachment.eml b/spec/fixtures/emails/inline_attachment.eml new file mode 100644 index 000000000..52188b2a7 --- /dev/null +++ b/spec/fixtures/emails/inline_attachment.eml @@ -0,0 +1,76 @@ +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <28@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: multipart/related; boundary=001a114b2eccff183a052998ec68 + +--001a114b2eccff183a052998ec68 +Content-Type: multipart/alternative; boundary=001a114b2eccff1836052998ec67 + +--001a114b2eccff1836052998ec67 +Content-Type: text/plain; charset=UTF-8 + +Before + +[image: 内嵌图片 1] + +After + +--001a114b2eccff1836052998ec67 +Content-Type: text/html; charset=UTF-8 + +
    Before

    内嵌图片 1
    +

    After
    + +--001a114b2eccff1836052998ec67-- +--001a114b2eccff183a052998ec68 +Content-Type: image/png; name="logo.png" +Content-Disposition: inline; filename="logo.png" +Content-Transfer-Encoding: base64 +Content-ID: +X-Attachment-Id: ii_1525434659ddb4cb + +iVBORw0KGgoAAAANSUhEUgAAAPQAAABCCAMAAABXYgukAAABhlBMVEUAAAAjHyAjHyAjHyAjHyAj +HyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAAqVAAru/lGyTxXCL/+a5UHiHZGyTq +NyPtRCM7HyHwWCLrPCOEHSL95Z31g0W1HCMgs1wAqnghKC0Aq5YJirsAqm6oHCMPb5QArccUXnsS +Z4hAvWj80ouP1oXoKyR4HSL0eTz4q2j+76XsQCOf24vpLyNsHiJgHiEYTGHyZiucHSPzcDTf76IL +ga7qMyMwuGIArdEaQ1T7yIJwzHn2l1f6vnovHyDuTCPNHCQfMTr3oV/wVCL5tHEHk8gArKDnJyTv +UCIAruUWVW4ArLMQrlYNeKEEnNVQwm0cOkcAq4KA0X/mHyQAqVoArKkAq4wCpeIArdvnIyT1jU4A +qmQUXV3v9KikRSHP6pwwIyBHHiHKUSLxaDTtSCMCpLpgx3PaHyTtSSy/5Zetw3b83JSVSiT0i1eg +MSI+qVa2JCMrbVbiX0Ygs2YArL2LkGApkWQLf2kSZmpFMZD0AAAAEHRSTlMAEFCAv8+vQCBw72CP +358w5xEcGAAABxJJREFUeF7lmuW/uzoMh1eKrUApbMfd9efu7u5u193d739+gbHQBtg49x3b99U5 +H7asD0mTNNAoE9GobohUukmtxqDLbhoCi5nOICNruigWc+0BRXY80UN8ELEtQ/QWo4OGTFzRX4Y/ +UMy+ISqpOUi7mYmK4oPDLKpLJ4PBzMVOZAwEdVNgje6dmZ+fX5vZ+1mhrwcwto+stTKNnRgdxH3t +q0B7r6W4d/fEirivHR24HE48Ja47yO+NX22nujlxuDU/ialrXq9NmWUmQd7TIQZNTX9zBCczGYCy +mjVrmoxyIgnrDvLJS5d3jUSa3XXr/fbU+Ayipvi2mTUMbmAeT5AvJcCgy3P3fkbUNrTs6QWrNtAU +Mb+cSJBnR7B27fv4U5TBkQlaG0czKW3HzPfiwAYvK9r/OXI1hq5hWzI5FkF/GTHPHRgp1vc/qq5G +WcGpC7QnQPMR88E4tEdK9ctvMjQjKYAB+bxufcloXKoQM9ah4ENAllxLzFodQ1wBWos29M12e99I +Ly0sHius1cSCXF6n6J6MHD3dbh+f7Qm9O/hqSaIm9e26IXVPtdu3RnrqVLB4GsV37eQo0X0Ggrtc +Z4Nz5wXIRQB129JjSbm60A/6YrC8gjZ13aQD85UouqOuBODKN/VqeF2A0nunxwK3E4fqOm3mU5vf +pKZu0qafB/BpZASuVJVNTd4kqW03Mq3lEK3YsEs1AtAsK1hJvbrRF/q7IAjXcVOmK+MU2rWqKwi2 +m/2aR5UUSKgnXcEdMgBkHX76ey4kFr/rPqZYtnlGyC0wA9BJ7v6hCvTGbQGyctDEQKMGIFDEpEsa +U65oVaE5lBBfsuBl0dJkQpZLiqCPj1SCDpfKoOFvnOlsQ2CZBNiQeDXolMhMmUHML5t2GiQHfbU9 +Vwn6Tpjl7yaGdgrPnzYTeRmQTLF4BWiQhm+08BAzokbQ+ytCbwIAxdCmCuDIIY/llg7caXVoFoUR +2jk+OkoZOkSCrkBfqQi9O4beKodOQ86yKAPmzJme61gaTUOdOXIMeC5tuh4ESDXoZsN2YIhBLY0D +Mxg2nE5Gy7wg5Dp9sAr0xXhPb5WHd7ZmYqTM4AnPgswFaZaDIeXhkl4BGjKGlX3FYt0dzZWdAvve +U6DXWtNVoM+q0FYxNE+Auow891yEGLzzDwE76tqEXQXalqGFlrClzETguR18Sh7wt/ZUgD4VBMH9 +cmjY057clTPYamV9MM1NNWgFaKOhAArdyhm2c4+uXBn6Sutl+0aVLf1BGD4ob04sITC21aNP5zCN +QLdIrwBtSjgY2+0Y1iV56Z1Sp7/3+peshSBYDqWS1UDQcvr2NMWbfnkfbBbch97QEA24Ipo2GC6R +Aj3aGu/bnBwKguBcGKIHeaUdGSc4H2ExHN2AWh0aMgHUhOrQYu3lVJ829KOFJLpXlEqLe2/CUTPQ +C1rsAJrkoUG+h3qDqtCTY9M3+tar4GEo9SYahk6kZStw+0B7O4C2CqBBhDIpu1aHFmNnTvbpxhJH +S0dLgqDzb2bZVfa0LkOb0Eo6laGV/kPoAM2tvJCnWxOXejF/8iiIi3T4GJjN8qU7DBbm98jeLq4s +UGAlShVa2RUYG8zR0iGHCn20NTXbj3lZcbRTBE1IZwUeLN7Lu7r7t5brIczMtKWeT90e0EQpFBYY +tvpBzxye68e8GobSCIEVBqlhEFgA1Ft00HXgfTQGjMqyGcnYvA4RgQJelL3TO2MAKoPvpvK5XQA9 +P3GhR7FKmO+H4ZMlgKZF0LzbY5sAbTN1cGBzdJKC+mqbimkDakB2IndlaGBODRAPoKna7xOanINy +0GNflHefT4OUOXwmQDaGhtbCoFSXFkYhNnRKuaG8fOhlB0DdQA+MHEhNOmRjH0MDKqdU2kiEde1S +zWpylk2wlDx291YZ8tdBrOcx8wN0HEbQrkDye7QKDvQVWBbQYOmovBed1j3IezkxW4U++u0BqE0v +Dp0C4t3bCfKr16HKzEgBNMdrhHWVzke0wrtRco3ZBdD4tintb36+oOSxn9KMdXEhhny0/fTFi+3t +R0FHb2I3vz2Gh3758C5+y47o5WTY18xBhyW0aAyNDXC5bGJmBP3rbEq8+ubhu403r4JMq69j5PD2 +deTDokRGMTOeTObHw8RUrti43uNBJ4aGW4r7AV/Ho0gM/XtE/MfinY37t1fWtx6H4cbr5cVIz+/8 ++S6M9XYTxxmGhlkzDGpkEQfYPI7qp9X9DuMJF56JY3t6Ism+ZaLpNjYsPNfP1+nJv/7+59+t891T +4/X1t6GsJ5tLaN9I8q1Yvvo/eBl/EK7kL9mNIpFO+9hHZR+y8W+KXjq2vtIBf3J681luXFlfYc6h +eOV7GJkb/5d5+KCbjaGDZlZjiKChwA8RNEx1hwcanvIPGbThAPKQQBvQuQ4FtKeb8GrOQOg/pxLS +uIDrr6oAAAAASUVORK5CYII= +--001a114b2eccff183a052998ec68-- diff --git a/spec/fixtures/emails/inline_mixed.eml b/spec/fixtures/emails/inline_mixed.eml deleted file mode 100644 index f8be9c7a5..000000000 --- a/spec/fixtures/emails/inline_mixed.eml +++ /dev/null @@ -1,37 +0,0 @@ -In-Reply-To: -References: - <5434ced4ee0f9_663fb0b5f76070593b@discourse-app.mail> -Date: Mon, 1 Dec 2014 20:48:40 +0530 -Delivered-To: someone@googlemail.com -Subject: Re: [Discourse] [Meta] Testing reply via email -From: Walter White -To: Discourse -Content-Type: multipart/alternative; boundary=20cf30363f8522466905092920a6 - ---20cf30363f8522466905092920a6 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: quoted-printable - -On Wed, Oct 8, 2014 at 11:12 AM, techAPJ wrote: - -> techAPJ -> November 28 -> -> Test reply. -> -> First paragraph. -> -> Second paragraph. - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. - -> First paragraph. -> -> Second paragraph. - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown diff --git a/spec/fixtures/emails/inline_mixed_replies.eml b/spec/fixtures/emails/inline_mixed_replies.eml new file mode 100644 index 000000000..05c299e96 --- /dev/null +++ b/spec/fixtures/emails/inline_mixed_replies.eml @@ -0,0 +1,19 @@ +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <24@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 + +On Tue, Jan 15, 2016 at 11:12 AM, Bar Foo wrote: + +> WAT November 28 +> +> This is the previous post. + +And this is *my* reply :+1: + +> This is another post. + +And this is **another** reply. diff --git a/spec/fixtures/emails/inline_reply.eml b/spec/fixtures/emails/inline_reply.eml index 39625a225..b65518e2a 100644 --- a/spec/fixtures/emails/inline_reply.eml +++ b/spec/fixtures/emails/inline_reply.eml @@ -1,60 +1,15 @@ - -MIME-Version: 1.0 -In-Reply-To: -References: - <5434ced4ee0f9_663fb0b5f76070593b@discourse-app.mail> -Date: Mon, 1 Dec 2014 20:48:40 +0530 -Delivered-To: someone@googlemail.com -Subject: Re: [Discourse] [Meta] Testing reply via email -From: Walter White -To: Discourse -Content-Type: multipart/alternative; boundary=20cf30363f8522466905092920a6 - ---20cf30363f8522466905092920a6 +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <23@foo.bar.mail> +Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: quoted-printable -On Wed, Oct 8, 2014 at 11:12 AM, techAPJ -wrote: +On Tue, Jan 15, 2016 at 11:12 AM, Bar Foo wrote: -> techAPJ -> November 28 -> -> Test reply. -> -> First paragraph. -> -> Second paragraph. -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> ------------------------------ -> Previous Replies codinghorror -> -> November 28 -> -> We're testing the latest GitHub email processing library which we are -> integrating now. -> -> https://github.com/github/email_reply_parser -> -> Go ahead and reply to this topic and I'll reply from various email clients -> for testing. -> ------------------------------ -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> -> To unsubscribe from these emails, visit your user preferences -> . +> WAT November 28 > +> This is the previous post. -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. - ---20cf30363f8522466905092920a6-- +And this is *my* reply :+1: diff --git a/spec/fixtures/emails/insufficient_trust_level.eml b/spec/fixtures/emails/insufficient_trust_level.eml new file mode 100644 index 000000000..4800b432b --- /dev/null +++ b/spec/fixtures/emails/insufficient_trust_level.eml @@ -0,0 +1,11 @@ +Return-Path: +From: Foo Bar +To: category@bar.com +Subject: This is a topic from a complete stranger +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <32@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Hey, this is a topic from a complete stranger ;) diff --git a/spec/fixtures/emails/ios_default.eml b/spec/fixtures/emails/ios_default.eml deleted file mode 100644 index 8d4d58feb..000000000 --- a/spec/fixtures/emails/ios_default.eml +++ /dev/null @@ -1,136 +0,0 @@ -Delivered-To: reply@discourse.org -Return-Path: -From: Walter White -Content-Type: multipart/alternative; - boundary=Apple-Mail-B41C7F8E-3639-49B0-A5D5-440E125A7105 -Content-Transfer-Encoding: 7bit -Mime-Version: 1.0 (1.0) -Subject: Re: [Discourse Meta] [Lounge] Testing default email replies -Date: Fri, 28 Nov 2014 12:41:41 -0800 -References: -In-Reply-To: -To: Discourse Meta -X-Mailer: iPhone Mail (12B436) - - ---Apple-Mail-B41C7F8E-3639-49B0-A5D5-440E125A7105 -Content-Type: text/plain; - charset=us-ascii -Content-Transfer-Encoding: quoted-printable - -### this is a reply from iOS default mail - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over t= -he lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fo= -x jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The q= -uick brown fox jumps over the lazy dog. The quick brown fox jumps over the l= -azy dog.=20 - -Here's some **bold** markdown text. - -Here's a link http://example.com - - -> On Nov 28, 2014, at 12:35 PM, Arpit Jalan wrote: ->=20 ->=20 -> techAPJ -> November 28 -> Test reply. ->=20 -> First paragraph. ->=20 -> Second paragraph. ->=20 -> To respond, reply to this email or visit https://meta.discourse.org/t/test= -ing-default-email-replies/22638/3 in your browser. ->=20 -> Previous Replies ->=20 -> codinghorror -> November 28 -> We're testing the latest GitHub email processing library which we are inte= -grating now. ->=20 -> https://github.com/github/email_reply_parser ->=20 -> Go ahead and reply to this topic and I'll reply from various email clients= - for testing. ->=20 -> To respond, reply to this email or visit https://meta.discourse.org/t/test= -ing-default-email-replies/22638/3 in your browser. ->=20 -> To unsubscribe from these emails, visit your user preferences. - ---Apple-Mail-B41C7F8E-3639-49B0-A5D5-440E125A7105 -Content-Type: text/html; - charset=utf-8 -Content-Transfer-Encoding: 7bit - -
    ### this is a reply from iOS default mail

    The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. 

    Here's some **bold** markdown text.

    Here's a link http://example.com


    On Nov 28, 2014, at 12:35 PM, Arpit Jalan <info@discourse.org> wrote:

    - - - - - - - - - - - -
    - - - techAPJ
    - November 28 -
    -

    Test reply.

    - -

    First paragraph.

    - -

    Second paragraph.

    -
    - - -
    -

    To respond, reply to this email or visit https://meta.discourse.org/t/testing-default-email-replies/22638/3 in your browser.

    -
    -
    -

    Previous Replies

    - - - - - - - - - - - -
    - - - codinghorror
    - November 28 -
    -

    We're testing the latest GitHub email processing library which we are integrating now.

    - -

    https://github.com/github/email_reply_parser

    - -

    Go ahead and reply to this topic and I'll reply from various email clients for testing.

    -
    - - -
    - -
    -

    To respond, reply to this email or visit https://meta.discourse.org/t/testing-default-email-replies/22638/3 in your browser.

    -
    -
    -

    To unsubscribe from these emails, visit your user preferences.

    -
    -
    -
    ---Apple-Mail-B41C7F8E-3639-49B0-A5D5-440E125A7105-- diff --git a/spec/fixtures/emails/iphone_signature.eml b/spec/fixtures/emails/iphone_signature.eml index d314ad1f1..79183b4a3 100644 --- a/spec/fixtures/emails/iphone_signature.eml +++ b/spec/fixtures/emails/iphone_signature.eml @@ -1,29 +1,11 @@ -Delivered-To: test@mail.com -Return-Path: -From: Walter White -Content-Type: multipart/alternative; - boundary=Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 -Content-Transfer-Encoding: 7bit -Mime-Version: 1.0 (1.0) -Subject: Re: Signature in email replies! -Date: Thu, 23 Oct 2014 14:43:49 +0530 -References: <1234@mail.gmail.com> -In-Reply-To: <1234@mail.gmail.com> -To: Arpit Jalan -X-Mailer: iPhone Mail (12A405) +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <25@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +This is not the signature you're looking for. ---Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 -Content-Type: text/plain; - charset=us-ascii -Content-Transfer-Encoding: 7bit - -This post should not include signature. - -Sent from my iPhone - -> On 23-Oct-2014, at 9:45 am, Arpit Jalan wrote: -> -> Signature in email replies! - ---Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 +Sent from my iMachine diff --git a/spec/fixtures/emails/like.eml b/spec/fixtures/emails/like.eml index 3bd9ae0fa..39829fcaa 100644 --- a/spec/fixtures/emails/like.eml +++ b/spec/fixtures/emails/like.eml @@ -1,37 +1,10 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: FROM -To: TO -Message-ID: -Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <13@foo.bar.mail> Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 +Content-Type: text/plain Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 -LIKE - - -On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta - wrote: -> -> -> -> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: -> -> --- -> hey guys everyone knows adventure time sucks! -> -> --- -> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 -> -> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). -> ++1 diff --git a/spec/fixtures/emails/missing_message_id.eml b/spec/fixtures/emails/missing_message_id.eml new file mode 100644 index 000000000..03405bed3 --- /dev/null +++ b/spec/fixtures/emails/missing_message_id.eml @@ -0,0 +1,5 @@ +From: Foo Bar +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Mime-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit diff --git a/spec/fixtures/emails/multiple_destinations.eml b/spec/fixtures/emails/multiple_destinations.eml deleted file mode 100644 index 6d31bbf19..000000000 --- a/spec/fixtures/emails/multiple_destinations.eml +++ /dev/null @@ -1,40 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: finn@adventuretime.ooo, reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo -Message-ID: -Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - -I could not disagree more. I am obviously biased but adventure time is the -greatest show ever created. Everyone should watch it. - -- Jake out - - -On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta - wrote: -> -> -> -> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: -> -> --- -> hey guys everyone knows adventure time sucks! -> -> --- -> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 -> -> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). -> \ No newline at end of file diff --git a/spec/fixtures/emails/newlines.eml b/spec/fixtures/emails/newlines.eml deleted file mode 100644 index cf03b9d18..000000000 --- a/spec/fixtures/emails/newlines.eml +++ /dev/null @@ -1,84 +0,0 @@ -In-Reply-To: -Date: Wed, 8 Oct 2014 10:36:19 +0530 -Delivered-To: walter.white@googlemail.com -Subject: Re: [Discourse] Welcome to Discourse -From: Walter White -To: Discourse -Content-Type: multipart/alternative; boundary=bcaec554078cc3d0c10504e24661 - ---bcaec554078cc3d0c10504e24661 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: quoted-printable - -This is my reply. -It is my best reply. -It will also be my *only* reply. - -On Wed, Oct 8, 2014 at 10:33 AM, ajalan -wrote: - -> ajalan -> -> October 8 -> -> Awesome! Thank You! [image: +1] -> -> To respond, reply to this email or visit -> http://discourse.techapj.com/t/welcome-to-discourse/8/2 -> -> in your browser. -> ------------------------------ -> Previous Replies system -> -> October 8 -> -> The first paragraph of this pinned topic will be visible as a welcome -> message to all new visitors on your homepage. It's important! -> -> *Edit this* into a brief description of your community: -> -> - Who is it for? -> - What can they find here? -> - Why should they come here? -> - Where can they read more (links, resources, etc)? -> -> You may want to close this topic via the wrench icon at the upper right, -> so that replies don't pile up on an announcement. -> ------------------------------ -> -> To respond, reply to this email or visit -> http://discourse.techapj.com/t/welcome-to-discourse/8/2 -> -> in your browser. -> -> To unsubscribe from these emails, visit your user preferences -> -> . -> - ---bcaec554078cc3d0c10504e24661 diff --git a/spec/fixtures/emails/no_body.eml b/spec/fixtures/emails/no_body.eml new file mode 100644 index 000000000..02afbe733 --- /dev/null +++ b/spec/fixtures/emails/no_body.eml @@ -0,0 +1,7 @@ +Return-Path: +From: Foo Bar +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <5@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit diff --git a/spec/fixtures/emails/no_body_with_attachments.eml b/spec/fixtures/emails/no_body_with_attachments.eml new file mode 100644 index 000000000..7e768d118 --- /dev/null +++ b/spec/fixtures/emails/no_body_with_attachments.eml @@ -0,0 +1,75 @@ +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <6@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: multipart/mixed; + boundary="--==_mimepart_56990c8d3f66c_7cb53ffbb98602004746e"; + charset=UTF-8 +Content-Transfer-Encoding: 7bit + + +----==_mimepart_56990c8d3f66c_7cb53ffbb98602004746e +Content-Type: image/png; + charset=UTF-8; + filename=logo.png +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; + filename=logo.png +Content-ID: <56990c92d6c64_7cb53ffbb986020047616@foo.bar.mail> + +iVBORw0KGgoAAAANSUhEUgAAAPQAAABCCAMAAABXYgukAAABhlBMVEUAAAAj +HyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAj +HyAAqVAAru/lGyTxXCL/+a5UHiHZGyTqNyPtRCM7HyHwWCLrPCOEHSL95Z31 +g0W1HCMgs1wAqnghKC0Aq5YJirsAqm6oHCMPb5QArccUXnsSZ4hAvWj80ouP +1oXoKyR4HSL0eTz4q2j+76XsQCOf24vpLyNsHiJgHiEYTGHyZiucHSPzcDTf +76ILga7qMyMwuGIArdEaQ1T7yIJwzHn2l1f6vnovHyDuTCPNHCQfMTr3oV/w +VCL5tHEHk8gArKDnJyTvUCIAruUWVW4ArLMQrlYNeKEEnNVQwm0cOkcAq4KA +0X/mHyQAqVoArKkAq4wCpeIArdvnIyT1jU4AqmQUXV3v9KikRSHP6pwwIyBH +HiHKUSLxaDTtSCMCpLpgx3PaHyTtSSy/5Zetw3b83JSVSiT0i1egMSI+qVa2 +JCMrbVbiX0Ygs2YArL2LkGApkWQLf2kSZmpFMZD0AAAAEHRSTlMAEFCAv8+v +QCBw72CP358w5xEcGAAABxJJREFUeF7lmuW/uzoMh1eKrUApbMfd9efu7u5u +193d739+gbHQBtg49x3b99U5H7asD0mTNNAoE9GobohUukmtxqDLbhoCi5nO +ICNruigWc+0BRXY80UN8ELEtQ/QWo4OGTFzRX4Y/UMy+ISqpOUi7mYmK4oPD +LKpLJ4PBzMVOZAwEdVNgje6dmZ+fX5vZ+1mhrwcwto+stTKNnRgdxH3tq0B7 +r6W4d/fEirivHR24HE48Ja47yO+NX22nujlxuDU/ialrXq9NmWUmQd7TIQZN +TX9zBCczGYCymjVrmoxyIgnrDvLJS5d3jUSa3XXr/fbU+Ayipvi2mTUMbmAe +T5AvJcCgy3P3fkbUNrTs6QWrNtAUMb+cSJBnR7B27fv4U5TBkQlaG0czKW3H +zPfiwAYvK9r/OXI1hq5hWzI5FkF/GTHPHRgp1vc/qq5GWcGpC7QnQPMR88E4 +tEdK9ctvMjQjKYAB+bxufcloXKoQM9ah4ENAllxLzFodQ1wBWos29M12e99I +Ly0sHius1cSCXF6n6J6MHD3dbh+f7Qm9O/hqSaIm9e26IXVPtdu3RnrqVLB4 +GsV37eQo0X0GgrtcZ4Nz5wXIRQB129JjSbm60A/6YrC8gjZ13aQD85UouqOu +BODKN/VqeF2A0nunxwK3E4fqOm3mU5vfpKZu0qafB/BpZASuVJVNTd4kqW03 +Mq3lEK3YsEs1AtAsK1hJvbrRF/q7IAjXcVOmK+MU2rWqKwi2m/2aR5UUSKgn +XcEdMgBkHX76ey4kFr/rPqZYtnlGyC0wA9BJ7v6hCvTGbQGyctDEQKMGIFDE +pEsaU65oVaE5lBBfsuBl0dJkQpZLiqCPj1SCDpfKoOFvnOlsQ2CZBNiQeDXo +lMhMmUHML5t2GiQHfbU9Vwn6Tpjl7yaGdgrPnzYTeRmQTLF4BWiQhm+08BAz +okbQ+ytCbwIAxdCmCuDIIY/llg7caXVoFoUR2jk+OkoZOkSCrkBfqQi9O4be +KodOQ86yKAPmzJme61gaTUOdOXIMeC5tuh4ESDXoZsN2YIhBLY0DMxg2nE5G +y7wg5Dp9sAr0xXhPb5WHd7ZmYqTM4AnPgswFaZaDIeXhkl4BGjKGlX3FYt0d +zZWdAvveU6DXWtNVoM+q0FYxNE+Auow891yEGLzzDwE76tqEXQXalqGFlrCl +zETguR18Sh7wt/ZUgD4VBMH9cmjY057clTPYamV9MM1NNWgFaKOhAArdyhm2 +c4+uXBn6Sutl+0aVLf1BGD4ob04sITC21aNP5zCNQLdIrwBtSjgY2+0Y1iV5 +6Z1Sp7/3+peshSBYDqWS1UDQcvr2NMWbfnkfbBbch97QEA24Ipo2GC6RAj3a +Gu/bnBwKguBcGKIHeaUdGSc4H2ExHN2AWh0aMgHUhOrQYu3lVJ829KOFJLpX +lEqLe2/CUTPQC1rsAJrkoUG+h3qDqtCTY9M3+tar4GEo9SYahk6kZStw+0B7 +O4C2CqBBhDIpu1aHFmNnTvbpxhJHS0dLgqDzb2bZVfa0LkOb0Eo6laGV/kPo +AM2tvJCnWxOXejF/8iiIi3T4GJjN8qU7DBbm98jeLq4sUGAlShVa2RUYG8zR +0iGHCn20NTXbj3lZcbRTBE1IZwUeLN7Lu7r7t5brIczMtKWeT90e0EQpFBYY +tvpBzxye68e8GobSCIEVBqlhEFgA1Ft00HXgfTQGjMqyGcnYvA4RgQJelL3T +O2MAKoPvpvK5XQA9P3GhR7FKmO+H4ZMlgKZF0LzbY5sAbTN1cGBzdJKC+mqb +imkDakB2IndlaGBODRAPoKna7xOanINy0GNflHefT4OUOXwmQDaGhtbCoFSX +FkYhNnRKuaG8fOhlB0DdQA+MHEhNOmRjH0MDKqdU2kiEde1SzWpylk2wlDx2 +91YZ8tdBrOcx8wN0HEbQrkDye7QKDvQVWBbQYOmovBed1j3IezkxW4U++u0B +qE0vDp0C4t3bCfKr16HKzEgBNMdrhHWVzke0wrtRco3ZBdD4tintb36+oOSx +n9KMdXEhhny0/fTFi+3tR0FHb2I3vz2Gh3758C5+y47o5WTY18xBhyW0aAyN +DXC5bGJmBP3rbEq8+ubhu403r4JMq69j5PD2deTDokRGMTOeTObHw8RUrti4 +3uNBJ4aGW4r7AV/Ho0gM/XtE/MfinY37t1fWtx6H4cbr5cVIz+/8+S6M9XYT +xxmGhlkzDGpkEQfYPI7qp9X9DuMJF56JY3t6Ism+ZaLpNjYsPNfP1+nJv/7+ +59+t891T4/X1t6GsJ5tLaN9I8q1Yvvo/eBl/EK7kL9mNIpFO+9hHZR+y8W+K +Xjq2vtIBf3J681luXFlfYc6heOV7GJkb/5d5+KCbjaGDZlZjiKChwA8RNEx1 +hwcanvIPGbThAPKQQBvQuQ4FtKeb8GrOQOg/pxLSuIDrr6oAAAAASUVORK5C +YII= + +----==_mimepart_56990c8d3f66c_7cb53ffbb98602004746e-- diff --git a/spec/fixtures/emails/no_content_reply.eml b/spec/fixtures/emails/no_content_reply.eml deleted file mode 100644 index 95eb2055c..000000000 --- a/spec/fixtures/emails/no_content_reply.eml +++ /dev/null @@ -1,34 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo -Message-ID: -Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - -On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta - wrote: -> -> -> -> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: -> -> --- -> hey guys everyone knows adventure time sucks! -> -> --- -> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 -> -> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). -> \ No newline at end of file diff --git a/spec/fixtures/emails/no_return_path.eml b/spec/fixtures/emails/no_return_path.eml new file mode 100644 index 000000000..3fae2e6bc --- /dev/null +++ b/spec/fixtures/emails/no_return_path.eml @@ -0,0 +1,7 @@ +From: Foo Bar +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <1@foo.bar.mail> +Precedence: list +Mime-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit diff --git a/spec/fixtures/emails/on_date_contact_wrote.eml b/spec/fixtures/emails/on_date_contact_wrote.eml new file mode 100644 index 000000000..33f0527d9 --- /dev/null +++ b/spec/fixtures/emails/on_date_contact_wrote.eml @@ -0,0 +1,19 @@ +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <20@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 + +This is the actual reply. + +On Tue, Jan 14, 2016 at 0:42 AM, Bar Foo wrote: + +> This is the previous email. +> And it had +> +> a lot +> +> +> of lines ;) diff --git a/spec/fixtures/emails/on_wrote.eml b/spec/fixtures/emails/on_wrote.eml deleted file mode 100644 index feb59bd27..000000000 --- a/spec/fixtures/emails/on_wrote.eml +++ /dev/null @@ -1,277 +0,0 @@ - -MIME-Version: 1.0 -Received: by 10.107.9.17 with HTTP; Tue, 9 Sep 2014 16:18:19 -0700 (PDT) -In-Reply-To: <540f16d4c08d9_4a3f9ff6d61890391c@tiefighter4-meta.mail> -References: - <540f16d4c08d9_4a3f9ff6d61890391c@tiefighter4-meta.mail> -Date: Tue, 9 Sep 2014 16:18:19 -0700 -Delivered-To: kanepyork@gmail.com -Message-ID: -Subject: Re: [Discourse Meta] Badge icons - where to find them? -From: Kane York -To: Discourse Meta -Content-Type: multipart/alternative; boundary=001a11c34c389e728f0502aa26a0 - ---001a11c34c389e728f0502aa26a0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: quoted-printable - -Sure, all you need to do is frobnicate the foobar and you'll be all set! - -On Tue, Sep 9, 2014 at 8:03 AM, gordon_ryan wrote: - -> gordon_ryan -> September 9 -> -> @riking - willing to step by -> step of the custom icon method for an admittedly ignorant admin? Seriousl= -y -> confused. -> -> Or anyone else who knows how to do this [image: smiley] -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/badge-icons-where-to-find-them/18058/9 in -> your browser. -> ------------------------------ -> Previous Replies riking -> July 25 -> -> Check out the "HTML Head" section in the "Content" tab of the admin panel= -. -> meglio -> July 25 -> -> How will it load the related custom font? -> riking -> July 25 -> -> Here's an example of the styles that FA applies. I'll use fa-heart"> as the example. -> -> .fa { -> display: inline-block; -> font-family: FontAwesome; -> font-style: normal; -> font-weight: normal; -> line-height: 1; -> -webkit-font-smoothing: antialiased; -> -moz-osx-font-smoothing: grayscale; -> } -> .fa-heart:before { -> content: "\f004"; -> } -> -> So you could do this in your site stylesheet: -> -> .fa-custom-burger:before { -> content: "\01f354"; -> font-family: inherit; -> } -> -> And get =F0=9F=8D=94 as your badge icon when you enter custom-burger. -> ------------------------------ -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/badge-icons-where-to-find-them/18058/9 in -> your browser. -> -> To unsubscribe from these emails, visit your user preferences -> . -> - ---001a11c34c389e728f0502aa26a0 -Content-Type: text/html; charset=UTF-8 -Content-Transfer-Encoding: quoted-printable - -
    Sure, all you need to do is frobnicate the foobar and you'll be all s= -et!


    = -
    On Tue, Sep 9, 2014 at 8:03 AM, gordon_ryan = -<info@discourse.org> wrote:
    - - - - - - - - - - - - -
    - - - gordon_ryan
    - September 9 -
    -

    @riking- willing to step by step of the custom icon me= -thod for an admittedly ignorant admin? Seriously confused.

    - -

    Or anyone else who knows how to do this = -3D"smiley"

    -
    - - -
    -

    To respond, reply to this email or visit https:= -//meta.discourse.org/t/badge-icons-where-to-find-them/18058/9 in your b= -rowser.

    -
    -
    -

    Previous Replies

    - - - - - - - - - - - -
    - - - riking
    - July 25 -

    Check out the "HTML Head" section in the "Content&= -quot; tab of the admin panel.

    - - - - - - - - - - - -
    - - - meglio
    - July 25 -

    How will it load the related custom font?

    - - - - - - - - - - - -
    - - - riking
    - July 25 -
    -

    Here's an example of the styles that= - FA applies. I'll use <i class=3D"fa fa-heart"></i> as the e= -xample.

    - -

    -
    .fa {
    -  display: inline-block;
    -  font-family: FontAwesome;
    -  font-style: normal;
    -  font-weight: normal;
    -  line-height: 1;
    -  -webkit-font-smoothing: antialiased;
    -  -moz-osx-font-smoothing: grayscale;
    -}
    -.fa-heart:before {
    -  content: "\f004";
    -}
    - -

    So you could do this in your site styles= -heet:

    - -

    -
    .fa-custom-burger:before {
    -  content: "\01f354";
    -  font-family: inherit;
    -}
    - -

    And get =F0=9F=8D=94 as your badge icon = -when you enter cus= -tom-burger.

    -
    - - -
    - -
    -

    To respond, reply to this email or visit https://me= -ta.discourse.org/t/badge-icons-where-to-find-them/18058/9 in your brows= -er.

    -
    -
    -

    To unsubscribe from these emails, visit your user preferences.

    -
    -
    -

    - ---001a11c34c389e728f0502aa26a0-- \ No newline at end of file diff --git a/spec/fixtures/emails/original_message.eml b/spec/fixtures/emails/original_message.eml new file mode 100644 index 000000000..8dbc5d182 --- /dev/null +++ b/spec/fixtures/emails/original_message.eml @@ -0,0 +1,12 @@ +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <27@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 + +This is a reply :) + +---Original Message--- +This part should not be included diff --git a/spec/fixtures/emails/original_message_context.eml b/spec/fixtures/emails/original_message_context.eml deleted file mode 100644 index 31088c16e..000000000 --- a/spec/fixtures/emails/original_message_context.eml +++ /dev/null @@ -1,30 +0,0 @@ -Delivered-To: test@mail.com -Return-Path: -From: Walter White -Content-Type: multipart/alternative; - boundary=Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 -Content-Transfer-Encoding: 7bit -Mime-Version: 1.0 (1.0) -Subject: Re: Signature in email replies! -Date: Thu, 23 Oct 2014 14:43:49 +0530 -References: <1234@mail.gmail.com> -In-Reply-To: <1234@mail.gmail.com> -To: Arpit Jalan -X-Mailer: iPhone Mail (12A405) - - ---Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 -Content-Type: text/plain; - charset=us-ascii -Content-Transfer-Encoding: 7bit - -This post should not include signature. -----Original Message---- - -Context here. - -> On 23-Oct-2014, at 9:45 am, Arpit Jalan wrote: -> -> Signature in email replies! - ---Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 diff --git a/spec/fixtures/emails/outlook.eml b/spec/fixtures/emails/outlook.eml deleted file mode 100644 index fb1f590a3..000000000 --- a/spec/fixtures/emails/outlook.eml +++ /dev/null @@ -1,188 +0,0 @@ - -MIME-Version: 1.0 -Received: by 10.25.161.144 with HTTP; Tue, 7 Oct 2014 22:17:17 -0700 (PDT) -X-Originating-IP: [117.207.85.84] -In-Reply-To: <5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail> -References: - <5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail> -Date: Wed, 8 Oct 2014 10:47:17 +0530 -Delivered-To: arpit@techapj.com -Message-ID: -Subject: Re: [Discourse] [Meta] Welcome to techAPJ's Discourse! -From: Arpit Jalan -To: Discourse Accept-Language: en-US -Content-Language: en-US -X-MS-Has-Attach: -X-MS-TNEF-Correlator: -x-originating-ip: [134.68.31.227] -Content-Type: multipart/alternative; - boundary="_000_B0DFE1BEB3739743BC9B639D0E6BC8FF217A6341IUMSSGMBX104ads_" -MIME-Version: 1.0 - ---_000_B0DFE1BEB3739743BC9B639D0E6BC8FF217A6341IUMSSGMBX104ads_ -Content-Type: text/plain; charset="utf-8" -Content-Transfer-Encoding: base64 - -TWljcm9zb2Z0IE91dGxvb2sgMjAxMA0KDQpGcm9tOiBtaWNoYWVsIFttYWlsdG86dGFsa0BvcGVu -bXJzLm9yZ10NClNlbnQ6IE1vbmRheSwgT2N0b2JlciAxMywgMjAxNCA5OjM4IEFNDQpUbzogUG93 -ZXIsIENocmlzDQpTdWJqZWN0OiBbUE1dIFlvdXIgcG9zdCBpbiAiQnVyZ2VyaGF1czogTmV3IHJl -c3RhdXJhbnQgLyBsdW5jaCB2ZW51ZSINCg0KDQptaWNoYWVsPGh0dHA6Ly9jbC5vcGVubXJzLm9y -Zy90cmFjay9jbGljay8zMDAzOTkwNS90YWxrLm9wZW5tcnMub3JnP3A9ZXlKeklqb2liR2xaYTFW -MGVYaENZMDFNUlRGc1VESm1ZelZRTTBabGVqRTRJaXdpZGlJNk1Td2ljQ0k2SW50Y0luVmNJam96 -TURBek9Ua3dOU3hjSW5aY0lqb3hMRndpZFhKc1hDSTZYQ0pvZEhSd2N6cGNYRnd2WEZ4Y0wzUmhi -R3N1YjNCbGJtMXljeTV2Y21kY1hGd3ZkWE5sY25OY1hGd3ZiV2xqYUdGbGJGd2lMRndpYVdSY0lq -cGNJbVExWW1Nd04yTmtORFJqWkRRNE1HTTRZVGcyTXpsalpXSTFOemd6WW1ZMlhDSXNYQ0oxY214 -ZmFXUnpYQ0k2VzF3aVlqaGtPRGcxTWprNU56ZG1aalkxWldZeU5URTNPV1JpTkdZeU1XSTNOekZq -TnpoalpqaGtPRndpWFgwaWZRPg0KT2N0b2JlciAxMw0KDQpodHRwczovL3RhbGsub3Blbm1ycy5v -cmcvdC9idXJnZXJoYXVzLW5ldy1yZXN0YXVyYW50LWx1bmNoLXZlbnVlLzY3Mi8zPGh0dHA6Ly9j -bC5vcGVubXJzLm9yZy90cmFjay9jbGljay8zMDAzOTkwNS90YWxrLm9wZW5tcnMub3JnP3A9ZXlK -eklqb2lVRVJJU1VOeVIzbFZNRGRCVlZocFduUjNXV3g0TVdOc1RXNVpJaXdpZGlJNk1Td2ljQ0k2 -SW50Y0luVmNJam96TURBek9Ua3dOU3hjSW5aY0lqb3hMRndpZFhKc1hDSTZYQ0pvZEhSd2N6cGNY -Rnd2WEZ4Y0wzUmhiR3N1YjNCbGJtMXljeTV2Y21kY1hGd3ZkRnhjWEM5aWRYSm5aWEpvWVhWekxX -NWxkeTF5WlhOMFlYVnlZVzUwTFd4MWJtTm9MWFpsYm5WbFhGeGNMelkzTWx4Y1hDOHpYQ0lzWENK -cFpGd2lPbHdpWkRWaVl6QTNZMlEwTkdOa05EZ3dZemhoT0RZek9XTmxZalUzT0ROaVpqWmNJaXhj -SW5WeWJGOXBaSE5jSWpwYlhDSmlOelppWWprMFpURmlOekk1WlRrMlpUUmxaV000TkdSbU1qUTRN -RE13WWpZeVlXWXlNR00wWENKZGZTSjk+DQoNCkxvb2tzIGxpa2UgeW91ciByZXBseS1ieS1lbWFp -bCB3YXNuJ3QgcHJvY2Vzc2VkIGNvcnJlY3RseSBieSBvdXIgc29mdHdhcmUuIENhbiB5b3UgbGV0 -IG1lIGtub3cgd2hhdCB2ZXJzaW9uL09TIG9mIHdoYXQgZW1haWwgcHJvZ3JhbSB5b3UncmUgdXNp -bmc/IFdlIHdpbGwgd2FudCB0byB0cnkgdG8gZml4IHRoZSBidWcuIDpzbWlsZToNCg0KVGhhbmtz -IQ0KDQoNCl9fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fDQoNClRvIHJlc3BvbmQsIHJl -cGx5IHRvIHRoaXMgZW1haWwgb3IgdmlzaXQgaHR0cHM6Ly90YWxrLm9wZW5tcnMub3JnL3QveW91 -ci1wb3N0LWluLWJ1cmdlcmhhdXMtbmV3LXJlc3RhdXJhbnQtbHVuY2gtdmVudWUvNjc0LzE8aHR0 -cDovL2NsLm9wZW5tcnMub3JnL3RyYWNrL2NsaWNrLzMwMDM5OTA1L3RhbGsub3Blbm1ycy5vcmc/ -cD1leUp6SWpvaWVYaDJWbnBGTUhSMU1uRm5aRWR1TlhFd01GcFFPVlp0VFZvNElpd2lkaUk2TVN3 -aWNDSTZJbnRjSW5WY0lqb3pNREF6T1Rrd05TeGNJblpjSWpveExGd2lkWEpzWENJNlhDSm9kSFJ3 -Y3pwY1hGd3ZYRnhjTDNSaGJHc3ViM0JsYm0xeWN5NXZjbWRjWEZ3dmRGeGNYQzk1YjNWeUxYQnZj -M1F0YVc0dFluVnlaMlZ5YUdGMWN5MXVaWGN0Y21WemRHRjFjbUZ1ZEMxc2RXNWphQzEyWlc1MVpW -eGNYQzgyTnpSY1hGd3ZNVndpTEZ3aWFXUmNJanBjSW1RMVltTXdOMk5rTkRSalpEUTRNR000WVRn -Mk16bGpaV0kxTnpnelltWTJYQ0lzWENKMWNteGZhV1J6WENJNlcxd2lZamMyWW1JNU5HVXhZamN5 -T1dVNU5tVTBaV1ZqT0RSa1pqSTBPREF6TUdJMk1tRm1NakJqTkZ3aVhYMGlmUT4gaW4geW91ciBi -cm93c2VyLg0KDQpUbyB1bnN1YnNjcmliZSBmcm9tIHRoZXNlIGVtYWlscywgdmlzaXQgeW91ciB1 -c2VyIHByZWZlcmVuY2VzPGh0dHA6Ly9jbC5vcGVubXJzLm9yZy90cmFjay9jbGljay8zMDAzOTkw -NS90YWxrLm9wZW5tcnMub3JnP3A9ZXlKeklqb2lkVXh1V2xnNVZGYzBPV1pXUzBZNGJGZExkbWx5 -V0dzeFRWOXpJaXdpZGlJNk1Td2ljQ0k2SW50Y0luVmNJam96TURBek9Ua3dOU3hjSW5aY0lqb3hM -RndpZFhKc1hDSTZYQ0pvZEhSd2N6cGNYRnd2WEZ4Y0wzUmhiR3N1YjNCbGJtMXljeTV2Y21kY1hG -d3ZiWGxjWEZ3dmNISmxabVZ5Wlc1alpYTmNJaXhjSW1sa1hDSTZYQ0prTldKak1EZGpaRFEwWTJR -ME9EQmpPR0U0TmpNNVkyVmlOVGM0TTJKbU5sd2lMRndpZFhKc1gybGtjMXdpT2x0Y0ltSTRNV1V3 -WmpBMU5EWTVORE0wTnpneU0yRm1NakEyTmpGalpqYzNaR05pTjJOaFl6ZG1NakpjSWwxOUluMD4u -DQoNCg== - ---_000_B0DFE1BEB3739743BC9B639D0E6BC8FF217A6341IUMSSGMBX104ads_ -Content-Type: text/html; charset="utf-8" -Content-Transfer-Encoding: base64 - -PGh0bWwgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiIHhtbG5zOm89InVy -bjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9mZmljZSIgeG1sbnM6dz0idXJuOnNjaGVt -YXMtbWljcm9zb2Z0LWNvbTpvZmZpY2U6d29yZCIgeG1sbnM6bT0iaHR0cDovL3NjaGVtYXMubWlj -cm9zb2Z0LmNvbS9vZmZpY2UvMjAwNC8xMi9vbW1sIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv -VFIvUkVDLWh0bWw0MCI+DQo8aGVhZD4NCjxtZXRhIGh0dHAtZXF1aXY9IkNvbnRlbnQtVHlwZSIg -Y29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PXV0Zi04Ij4NCjxtZXRhIG5hbWU9IkdlbmVyYXRv -ciIgY29udGVudD0iTWljcm9zb2Z0IFdvcmQgMTQgKGZpbHRlcmVkIG1lZGl1bSkiPg0KPCEtLVtp -ZiAhbXNvXT48c3R5bGU+dlw6KiB7YmVoYXZpb3I6dXJsKCNkZWZhdWx0I1ZNTCk7fQ0Kb1w6KiB7 -YmVoYXZpb3I6dXJsKCNkZWZhdWx0I1ZNTCk7fQ0Kd1w6KiB7YmVoYXZpb3I6dXJsKCNkZWZhdWx0 -I1ZNTCk7fQ0KLnNoYXBlIHtiZWhhdmlvcjp1cmwoI2RlZmF1bHQjVk1MKTt9DQo8L3N0eWxlPjwh -W2VuZGlmXS0tPjxzdHlsZT48IS0tDQovKiBGb250IERlZmluaXRpb25zICovDQpAZm9udC1mYWNl -DQoJe2ZvbnQtZmFtaWx5OkNhbGlicmk7DQoJcGFub3NlLTE6MiAxNSA1IDIgMiAyIDQgMyAyIDQ7 -fQ0KQGZvbnQtZmFjZQ0KCXtmb250LWZhbWlseTpUYWhvbWE7DQoJcGFub3NlLTE6MiAxMSA2IDQg -MyA1IDQgNCAyIDQ7fQ0KLyogU3R5bGUgRGVmaW5pdGlvbnMgKi8NCnAuTXNvTm9ybWFsLCBsaS5N -c29Ob3JtYWwsIGRpdi5Nc29Ob3JtYWwNCgl7bWFyZ2luOjBpbjsNCgltYXJnaW4tYm90dG9tOi4w -MDAxcHQ7DQoJZm9udC1zaXplOjEyLjBwdDsNCglmb250LWZhbWlseToiVGltZXMgTmV3IFJvbWFu -Iiwic2VyaWYiO30NCmE6bGluaywgc3Bhbi5Nc29IeXBlcmxpbmsNCgl7bXNvLXN0eWxlLXByaW9y -aXR5Ojk5Ow0KCWNvbG9yOmJsdWU7DQoJdGV4dC1kZWNvcmF0aW9uOnVuZGVybGluZTt9DQphOnZp -c2l0ZWQsIHNwYW4uTXNvSHlwZXJsaW5rRm9sbG93ZWQNCgl7bXNvLXN0eWxlLXByaW9yaXR5Ojk5 -Ow0KCWNvbG9yOnB1cnBsZTsNCgl0ZXh0LWRlY29yYXRpb246dW5kZXJsaW5lO30NCnANCgl7bXNv -LXN0eWxlLXByaW9yaXR5Ojk5Ow0KCW1zby1tYXJnaW4tdG9wLWFsdDphdXRvOw0KCW1hcmdpbi1y -aWdodDowaW47DQoJbXNvLW1hcmdpbi1ib3R0b20tYWx0OmF1dG87DQoJbWFyZ2luLWxlZnQ6MGlu -Ow0KCWZvbnQtc2l6ZToxMi4wcHQ7DQoJZm9udC1mYW1pbHk6IlRpbWVzIE5ldyBSb21hbiIsInNl -cmlmIjt9DQpzcGFuLkVtYWlsU3R5bGUxOA0KCXttc28tc3R5bGUtdHlwZTpwZXJzb25hbC1yZXBs -eTsNCglmb250LWZhbWlseToiQ2FsaWJyaSIsInNhbnMtc2VyaWYiOw0KCWNvbG9yOiMxRjQ5N0Q7 -fQ0KLk1zb0NocERlZmF1bHQNCgl7bXNvLXN0eWxlLXR5cGU6ZXhwb3J0LW9ubHk7DQoJZm9udC1m -YW1pbHk6IkNhbGlicmkiLCJzYW5zLXNlcmlmIjt9DQpAcGFnZSBXb3JkU2VjdGlvbjENCgl7c2l6 -ZTo4LjVpbiAxMS4waW47DQoJbWFyZ2luOjEuMGluIDEuMGluIDEuMGluIDEuMGluO30NCmRpdi5X -b3JkU2VjdGlvbjENCgl7cGFnZTpXb3JkU2VjdGlvbjE7fQ0KLS0+PC9zdHlsZT48IS0tW2lmIGd0 -ZSBtc28gOV0+PHhtbD4NCjxvOnNoYXBlZGVmYXVsdHMgdjpleHQ9ImVkaXQiIHNwaWRtYXg9IjEw -MjYiIC8+DQo8L3htbD48IVtlbmRpZl0tLT48IS0tW2lmIGd0ZSBtc28gOV0+PHhtbD4NCjxvOnNo -YXBlbGF5b3V0IHY6ZXh0PSJlZGl0Ij4NCjxvOmlkbWFwIHY6ZXh0PSJlZGl0IiBkYXRhPSIxIiAv -Pg0KPC9vOnNoYXBlbGF5b3V0PjwveG1sPjwhW2VuZGlmXS0tPg0KPC9oZWFkPg0KPGJvZHkgbGFu -Zz0iRU4tVVMiIGxpbms9ImJsdWUiIHZsaW5rPSJwdXJwbGUiPg0KPGRpdiBjbGFzcz0iV29yZFNl -Y3Rpb24xIj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTEu -MHB0O2ZvbnQtZmFtaWx5OiZxdW90O0NhbGlicmkmcXVvdDssJnF1b3Q7c2Fucy1zZXJpZiZxdW90 -Oztjb2xvcjojMUY0OTdEIj5NaWNyb3NvZnQgT3V0bG9vayAyMDEwPG86cD48L286cD48L3NwYW4+ -PC9wPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCI+PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxMS4wcHQ7 -Zm9udC1mYW1pbHk6JnF1b3Q7Q2FsaWJyaSZxdW90OywmcXVvdDtzYW5zLXNlcmlmJnF1b3Q7O2Nv -bG9yOiMxRjQ5N0QiPjxvOnA+Jm5ic3A7PC9vOnA+PC9zcGFuPjwvcD4NCjxwIGNsYXNzPSJNc29O -b3JtYWwiPjxiPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTAuMHB0O2ZvbnQtZmFtaWx5OiZxdW90 -O1RhaG9tYSZxdW90OywmcXVvdDtzYW5zLXNlcmlmJnF1b3Q7Ij5Gcm9tOjwvc3Bhbj48L2I+PHNw -YW4gc3R5bGU9ImZvbnQtc2l6ZToxMC4wcHQ7Zm9udC1mYW1pbHk6JnF1b3Q7VGFob21hJnF1b3Q7 -LCZxdW90O3NhbnMtc2VyaWYmcXVvdDsiPiBtaWNoYWVsIFttYWlsdG86dGFsa0BvcGVubXJzLm9y -Z10NCjxicj4NCjxiPlNlbnQ6PC9iPiBNb25kYXksIE9jdG9iZXIgMTMsIDIwMTQgOTozOCBBTTxi -cj4NCjxiPlRvOjwvYj4gUG93ZXIsIENocmlzPGJyPg0KPGI+U3ViamVjdDo8L2I+IFtQTV0gWW91 -ciBwb3N0IGluICZxdW90O0J1cmdlcmhhdXM6IE5ldyByZXN0YXVyYW50IC8gbHVuY2ggdmVudWUm -cXVvdDs8bzpwPjwvbzpwPjwvc3Bhbj48L3A+DQo8cCBjbGFzcz0iTXNvTm9ybWFsIj48bzpwPiZu -YnNwOzwvbzpwPjwvcD4NCjxkaXY+DQo8dGFibGUgY2xhc3M9Ik1zb05vcm1hbFRhYmxlIiBib3Jk -ZXI9IjAiIGNlbGxzcGFjaW5nPSIwIiBjZWxscGFkZGluZz0iMCI+DQo8dGJvZHk+DQo8dHI+DQo8 -dGQgdmFsaWduPSJ0b3AiIHN0eWxlPSJwYWRkaW5nOjBpbiAwaW4gMGluIDBpbiI+PC90ZD4NCjx0 -ZCBzdHlsZT0icGFkZGluZzowaW4gMGluIDBpbiAwaW4iPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIg -c3R5bGU9Im1hcmdpbi1ib3R0b206MTguNzVwdCI+PGEgaHJlZj0iaHR0cDovL2NsLm9wZW5tcnMu -b3JnL3RyYWNrL2NsaWNrLzMwMDM5OTA1L3RhbGsub3Blbm1ycy5vcmc/cD1leUp6SWpvaWJHbFph -MVYwZVhoQ1kwMU1SVEZzVURKbVl6VlFNMFpsZWpFNElpd2lkaUk2TVN3aWNDSTZJbnRjSW5WY0lq -b3pNREF6T1Rrd05TeGNJblpjSWpveExGd2lkWEpzWENJNlhDSm9kSFJ3Y3pwY1hGd3ZYRnhjTDNS -aGJHc3ViM0JsYm0xeWN5NXZjbWRjWEZ3dmRYTmxjbk5jWEZ3dmJXbGphR0ZsYkZ3aUxGd2lhV1Jj -SWpwY0ltUTFZbU13TjJOa05EUmpaRFE0TUdNNFlUZzJNemxqWldJMU56Z3pZbVkyWENJc1hDSjFj -bXhmYVdSelhDSTZXMXdpWWpoa09EZzFNams1TnpkbVpqWTFaV1l5TlRFM09XUmlOR1l5TVdJM056 -RmpOemhqWmpoa09Gd2lYWDBpZlEiIHRhcmdldD0iX2JsYW5rIj48Yj48c3BhbiBzdHlsZT0iZm9u -dC1zaXplOjEwLjBwdDtmb250LWZhbWlseTomcXVvdDtUYWhvbWEmcXVvdDssJnF1b3Q7c2Fucy1z -ZXJpZiZxdW90Oztjb2xvcjojMDA2Njk5O3RleHQtZGVjb3JhdGlvbjpub25lIj5taWNoYWVsPC9z -cGFuPjwvYj48L2E+PGJyPg0KPHNwYW4gc3R5bGU9ImZvbnQtc2l6ZTo4LjVwdDtmb250LWZhbWls -eTomcXVvdDtUYWhvbWEmcXVvdDssJnF1b3Q7c2Fucy1zZXJpZiZxdW90Oztjb2xvcjojOTk5OTk5 -Ij5PY3RvYmVyIDEzPC9zcGFuPg0KPG86cD48L286cD48L3A+DQo8L3RkPg0KPC90cj4NCjx0cj4N -Cjx0ZCBjb2xzcGFuPSIyIiBzdHlsZT0icGFkZGluZzozLjc1cHQgMGluIDBpbiAwaW4iPg0KPHAg -Y2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1hcmdpbi1ib3R0b206MTguNzVwdCI+PGEgaHJlZj0i -aHR0cDovL2NsLm9wZW5tcnMub3JnL3RyYWNrL2NsaWNrLzMwMDM5OTA1L3RhbGsub3Blbm1ycy5v -cmc/cD1leUp6SWpvaVVFUklTVU55UjNsVk1EZEJWVmhwV25SM1dXeDRNV05zVFc1Wklpd2lkaUk2 -TVN3aWNDSTZJbnRjSW5WY0lqb3pNREF6T1Rrd05TeGNJblpjSWpveExGd2lkWEpzWENJNlhDSm9k -SFJ3Y3pwY1hGd3ZYRnhjTDNSaGJHc3ViM0JsYm0xeWN5NXZjbWRjWEZ3dmRGeGNYQzlpZFhKblpY -Sm9ZWFZ6TFc1bGR5MXlaWE4wWVhWeVlXNTBMV3gxYm1Ob0xYWmxiblZsWEZ4Y0x6WTNNbHhjWEM4 -elhDSXNYQ0pwWkZ3aU9sd2laRFZpWXpBM1kyUTBOR05rTkRnd1l6aGhPRFl6T1dObFlqVTNPRE5p -WmpaY0lpeGNJblZ5YkY5cFpITmNJanBiWENKaU56WmlZamswWlRGaU56STVaVGsyWlRSbFpXTTRO -R1JtTWpRNE1ETXdZall5WVdZeU1HTTBYQ0pkZlNKOSI+PGI+PHNwYW4gc3R5bGU9ImNvbG9yOiMw -MDY2OTk7dGV4dC1kZWNvcmF0aW9uOm5vbmUiPmh0dHBzOi8vdGFsay5vcGVubXJzLm9yZy90L2J1 -cmdlcmhhdXMtbmV3LXJlc3RhdXJhbnQtbHVuY2gtdmVudWUvNjcyLzM8L3NwYW4+PC9iPjwvYT4N -CjxvOnA+PC9vOnA+PC9wPg0KPHAgc3R5bGU9Im1hcmdpbi10b3A6MGluIj5Mb29rcyBsaWtlIHlv -dXIgcmVwbHktYnktZW1haWwgd2Fzbid0IHByb2Nlc3NlZCBjb3JyZWN0bHkgYnkgb3VyIHNvZnR3 -YXJlLiBDYW4geW91IGxldCBtZSBrbm93IHdoYXQgdmVyc2lvbi9PUyBvZiB3aGF0IGVtYWlsIHBy -b2dyYW0geW91J3JlIHVzaW5nPyBXZSB3aWxsIHdhbnQgdG8gdHJ5IHRvIGZpeCB0aGUgYnVnLiA6 -c21pbGU6PG86cD48L286cD48L3A+DQo8cCBzdHlsZT0ibWFyZ2luLXRvcDowaW4iPlRoYW5rcyE8 -bzpwPjwvbzpwPjwvcD4NCjwvdGQ+DQo8L3RyPg0KPC90Ym9keT4NCjwvdGFibGU+DQo8ZGl2IGNs -YXNzPSJNc29Ob3JtYWwiIGFsaWduPSJjZW50ZXIiIHN0eWxlPSJ0ZXh0LWFsaWduOmNlbnRlciI+ -DQo8aHIgc2l6ZT0iMSIgd2lkdGg9IjEwMCUiIGFsaWduPSJjZW50ZXIiPg0KPC9kaXY+DQo8ZGl2 -Pg0KPHA+PHNwYW4gc3R5bGU9ImNvbG9yOiM2NjY2NjYiPlRvIHJlc3BvbmQsIHJlcGx5IHRvIHRo -aXMgZW1haWwgb3IgdmlzaXQgPGEgaHJlZj0iaHR0cDovL2NsLm9wZW5tcnMub3JnL3RyYWNrL2Ns -aWNrLzMwMDM5OTA1L3RhbGsub3Blbm1ycy5vcmc/cD1leUp6SWpvaWVYaDJWbnBGTUhSMU1uRm5a -RWR1TlhFd01GcFFPVlp0VFZvNElpd2lkaUk2TVN3aWNDSTZJbnRjSW5WY0lqb3pNREF6T1Rrd05T -eGNJblpjSWpveExGd2lkWEpzWENJNlhDSm9kSFJ3Y3pwY1hGd3ZYRnhjTDNSaGJHc3ViM0JsYm0x -eWN5NXZjbWRjWEZ3dmRGeGNYQzk1YjNWeUxYQnZjM1F0YVc0dFluVnlaMlZ5YUdGMWN5MXVaWGN0 -Y21WemRHRjFjbUZ1ZEMxc2RXNWphQzEyWlc1MVpWeGNYQzgyTnpSY1hGd3ZNVndpTEZ3aWFXUmNJ -anBjSW1RMVltTXdOMk5rTkRSalpEUTRNR000WVRnMk16bGpaV0kxTnpnelltWTJYQ0lzWENKMWNt -eGZhV1J6WENJNlcxd2lZamMyWW1JNU5HVXhZamN5T1dVNU5tVTBaV1ZqT0RSa1pqSTBPREF6TUdJ -Mk1tRm1NakJqTkZ3aVhYMGlmUSI+DQo8Yj48c3BhbiBzdHlsZT0iY29sb3I6IzAwNjY5OTt0ZXh0 -LWRlY29yYXRpb246bm9uZSI+aHR0cHM6Ly90YWxrLm9wZW5tcnMub3JnL3QveW91ci1wb3N0LWlu -LWJ1cmdlcmhhdXMtbmV3LXJlc3RhdXJhbnQtbHVuY2gtdmVudWUvNjc0LzE8L3NwYW4+PC9iPjwv -YT4gaW4geW91ciBicm93c2VyLjxvOnA+PC9vOnA+PC9zcGFuPjwvcD4NCjwvZGl2Pg0KPGRpdj4N -CjxwPjxzcGFuIHN0eWxlPSJjb2xvcjojNjY2NjY2Ij5UbyB1bnN1YnNjcmliZSBmcm9tIHRoZXNl -IGVtYWlscywgdmlzaXQgeW91ciA8YSBocmVmPSJodHRwOi8vY2wub3Blbm1ycy5vcmcvdHJhY2sv -Y2xpY2svMzAwMzk5MDUvdGFsay5vcGVubXJzLm9yZz9wPWV5SnpJam9pZFV4dVdsZzVWRmMwT1da -V1MwWTRiRmRMZG1seVdHc3hUVjl6SWl3aWRpSTZNU3dpY0NJNkludGNJblZjSWpvek1EQXpPVGt3 -TlN4Y0luWmNJam94TEZ3aWRYSnNYQ0k2WENKb2RIUndjenBjWEZ3dlhGeGNMM1JoYkdzdWIzQmxi -bTF5Y3k1dmNtZGNYRnd2YlhsY1hGd3ZjSEpsWm1WeVpXNWpaWE5jSWl4Y0ltbGtYQ0k2WENKa05X -SmpNRGRqWkRRMFkyUTBPREJqT0dFNE5qTTVZMlZpTlRjNE0ySm1ObHdpTEZ3aWRYSnNYMmxrYzF3 -aU9sdGNJbUk0TVdVd1pqQTFORFk1TkRNME56Z3lNMkZtTWpBMk5qRmpaamMzWkdOaU4yTmhZemRt -TWpKY0lsMTlJbjAiPg0KPGI+PHNwYW4gc3R5bGU9ImNvbG9yOiMwMDY2OTk7dGV4dC1kZWNvcmF0 -aW9uOm5vbmUiPnVzZXIgcHJlZmVyZW5jZXM8L3NwYW4+PC9iPjwvYT4uPG86cD48L286cD48L3Nw -YW4+PC9wPg0KPC9kaXY+DQo8L2Rpdj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiPjxpbWcgYm9yZGVy -PSIwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBpZD0iX3gwMDAwX2kxMDI2IiBzcmM9Imh0dHA6Ly9j -bC5vcGVubXJzLm9yZy90cmFjay9vcGVuLnBocD91PTMwMDM5OTA1JmFtcDtpZD1kNWJjMDdjZDQ0 -Y2Q0ODBjOGE4NjM5Y2ViNTc4M2JmNiI+PG86cD48L286cD48L3A+DQo8L2Rpdj4NCjwvYm9keT4N -CjwvaHRtbD4NCg== - ---_000_B0DFE1BEB3739743BC9B639D0E6BC8FF217A6341IUMSSGMBX104ads_-- diff --git a/spec/fixtures/emails/paragraphs.cooked b/spec/fixtures/emails/paragraphs.cooked deleted file mode 100644 index 2d4472210..000000000 --- a/spec/fixtures/emails/paragraphs.cooked +++ /dev/null @@ -1,7 +0,0 @@ -

    Is there any reason the old candy can't be be kept in silos while the new candy -is imported into new silos?

    - -

    The thing about candy is it stays delicious for a long time -- we can just keep -it there without worrying about it too much, imo.

    - -

    Thanks for listening.

    diff --git a/spec/fixtures/emails/paragraphs.eml b/spec/fixtures/emails/paragraphs.eml index 2d5b5283f..7fb2bd373 100644 --- a/spec/fixtures/emails/paragraphs.eml +++ b/spec/fixtures/emails/paragraphs.eml @@ -1,42 +1,12 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo -Message-ID: -Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <22@foo.bar.mail> Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 +Content-Type: text/plain; charset=UTF-8 -Is there any reason the *old* candy can't be be kept in silos while the new candy -is imported into *new* silos? +Do you like liquorice? -The thing about candy is it stays delicious for a long time -- we can just keep -it there without worrying about it too much, imo. - -Thanks for listening. - -On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta - wrote: -> -> -> -> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: -> -> --- -> hey guys everyone knows adventure time sucks! -> -> --- -> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 -> -> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). -> +I really like them. One could even say that I am *addicted* to liquorice. Anf if +you can mix it up with some anise, then I'm in heaven ;) diff --git a/spec/fixtures/emails/plus_one.eml b/spec/fixtures/emails/plus_one.eml deleted file mode 100644 index a3255e969..000000000 --- a/spec/fixtures/emails/plus_one.eml +++ /dev/null @@ -1,37 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: FROM -To: TO -Message-ID: -Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - -+1 - - -On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta - wrote: -> -> -> -> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: -> -> --- -> hey guys everyone knows adventure time sucks! -> -> --- -> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 -> -> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). -> diff --git a/spec/fixtures/emails/previous.eml b/spec/fixtures/emails/previous.eml deleted file mode 100644 index 24ac5a63d..000000000 --- a/spec/fixtures/emails/previous.eml +++ /dev/null @@ -1,38 +0,0 @@ - -Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org -Received: by 10.194.216.104 with SMTP id op8csp80593wjc; - Wed, 24 Jul 2013 07:59:14 -0700 (PDT) -Return-Path: -References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -From: Walter White -In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -Mime-Version: 1.0 (1.0) -Date: Wed, 24 Jul 2013 15:59:10 +0100 -Message-ID: <4597127794206131679@unknownmsgid> -Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' -To: walter via Discourse -Content-Type: multipart/alternative; boundary=001a11c20edc15a39304e2432790 - -This will not include the previous discussion that is present in this email. - ------------------------------ -Previous discussion -skylerwhite July 24 - -This is a reply. - fring July 24 - -This is an older reply. - hank_schrader July 24 - -Of course another reply here. - walterwhite July 24 - - ------------------------------ - -To respond, reply to this email or visit -http://discourse.org/t/crystal-blue/5043/10in -your browser. - -To unsubscribe from these emails, visit your user -preferences -. diff --git a/spec/fixtures/emails/previous_replies.eml b/spec/fixtures/emails/previous_replies.eml index 3fd74482c..8b4cbc7e7 100644 --- a/spec/fixtures/emails/previous_replies.eml +++ b/spec/fixtures/emails/previous_replies.eml @@ -1,180 +1,23 @@ -Delivered-To: reply@discourse.org -MIME-Version: 1.0 -In-Reply-To: -References: - -Date: Fri, 28 Nov 2014 12:55:32 -0800 -Subject: Re: [Discourse Meta] [Lounge] Testing default email replies -From: Walter White -To: Discourse Meta -Content-Type: multipart/alternative; boundary=001a1137c9285318bb0508f17bc5 - ---001a1137c9285318bb0508f17bc5 +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <21@foo.bar.mail> +Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 -### this is a reply from iOS Gmail app +This will not include the previous discussion that is present in this email. -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy -dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps -over the lazy dog. +--- +*Previous Replies* -This is **bold** text in Markdown. +This is previous reply #1. -This is a link to http://example.com +Posted by foo bar on 01/15/2016 -On Friday, November 28, 2014, Arpit Jalan wrote: +--- +[Visit the Topic](https://bar.com/t/wat/1/1) to respond. -> techAPJ -> November 28 -> -> Test reply. -> -> First paragraph. -> -> Second paragraph. -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> ------------------------------ -> Previous Replies codinghorror -> -> November 28 -> -> We're testing the latest GitHub email processing library which we are -> integrating now. -> -> https://github.com/github/email_reply_parser -> -> Go ahead and reply to this topic and I'll reply from various email clients -> for testing. -> ------------------------------ -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> -> To unsubscribe from these emails, visit your user preferences -> . -> - ---001a1137c9285318bb0508f17bc5 -Content-Type: text/html; charset=UTF-8 -Content-Transfer-Encoding: quoted-printable - -### this is a reply from iOS Gmail app

    The quick brown f= -ox jumps over the lazy dog.=C2=A0The quick brown fox jumps over the lazy dog.=C2=A0The quic= -k brown fox jumps over the lazy dog.=C2=A0The quick brown fox jumps over th= -e lazy dog.=C2=A0The quick brown fox jumps over the lazy dog.=C2=A0The quic= -k brown fox jumps over the lazy dog.=C2=A0The quick brown fox jumps over th= -e lazy dog.=C2=A0

    This is **bold** text in Markdown.

    This is a link to http://example.com

    On Friday, November 28, 2014, Arpit Jalan <
    info@discourse.org> wrote:
    - - - - - - - - - - - -
    - - - techAPJ
    - November 28 -
    -

    Test reply.

    - -

    First paragraph.

    - -

    Second paragraph.

    -
    - - -
    -

    To respond, reply to this email or visit https:/= -/meta.discourse.org/t/testing-default-email-replies/22638/3 in your bro= -wser.

    -
    -
    -

    Previous Replies

    - - - - - - - - - - - -
    - - - codinghorror - November 28 -
    -

    We're testing the latest GitHub emai= -l processing library which we are integrating now.

    - -

    https://github.com/github/email_reply_parser

    - -

    Go ahead and reply to this topic and I&#= -39;ll reply from various email clients for testing.

    -
    - - -
    - -
    -

    To respond, reply to this email or visit https://met= -a.discourse.org/t/testing-default-email-replies/22638/3 in your browser= -.

    -
    -
    -

    To unsubscribe from these emails, visit your user preferences.

    -
    -
    -
    - ---001a1137c9285318bb0508f17bc5-- +To stop receiving notifications for this particular topic, [click here](h= +ttps://bar.com/t/wat/1/unsubscribe). To unsubscribe from these emails, ch= +ange your [user preferences](https://bar.com/my/preferences). diff --git a/spec/fixtures/emails/readonly.eml b/spec/fixtures/emails/readonly.eml new file mode 100644 index 000000000..7572bff66 --- /dev/null +++ b/spec/fixtures/emails/readonly.eml @@ -0,0 +1,11 @@ +Return-Path: +From: Foo Bar +To: category@bar.com +Subject: This is a topic from a restricted user +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <33@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Hey, this is a topic from a restricted user ;) diff --git a/spec/fixtures/emails/reply_user_matching.eml b/spec/fixtures/emails/reply_user_matching.eml new file mode 100644 index 000000000..caead8467 --- /dev/null +++ b/spec/fixtures/emails/reply_user_matching.eml @@ -0,0 +1,10 @@ +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <11@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. diff --git a/spec/fixtures/emails/reply_user_not_matching.eml b/spec/fixtures/emails/reply_user_not_matching.eml new file mode 100644 index 000000000..c6523f966 --- /dev/null +++ b/spec/fixtures/emails/reply_user_not_matching.eml @@ -0,0 +1,10 @@ +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <10@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. diff --git a/spec/fixtures/emails/signature.eml b/spec/fixtures/emails/signature.eml index 01a0dd787..5352f48a2 100644 --- a/spec/fixtures/emails/signature.eml +++ b/spec/fixtures/emails/signature.eml @@ -1,29 +1,12 @@ -Delivered-To: test@mail.com -Return-Path: -From: Walter White -Content-Type: multipart/alternative; - boundary=Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 -Content-Transfer-Encoding: 7bit -Mime-Version: 1.0 (1.0) -Subject: Re: Signature in email replies! -Date: Thu, 23 Oct 2014 14:43:49 +0530 -References: <1234@mail.gmail.com> -In-Reply-To: <1234@mail.gmail.com> -To: Arpit Jalan -X-Mailer: iPhone Mail (12A405) +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <26@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +You shall not sign! ---Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 -Content-Type: text/plain; - charset=us-ascii -Content-Transfer-Encoding: 7bit - -This post should not include signature. - -----Arpit - -> On 23-Oct-2014, at 9:45 am, Arpit Jalan wrote: -> -> Signature in email replies! - ---Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 +--- +Foo Bar diff --git a/spec/fixtures/emails/staged_sender.eml b/spec/fixtures/emails/staged_sender.eml new file mode 100644 index 000000000..6ee856614 --- /dev/null +++ b/spec/fixtures/emails/staged_sender.eml @@ -0,0 +1,9 @@ +Return-Path: +From: Foo Bar +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <9@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. diff --git a/spec/fixtures/emails/stranger_not_allowed.eml b/spec/fixtures/emails/stranger_not_allowed.eml new file mode 100644 index 000000000..1464e8ddd --- /dev/null +++ b/spec/fixtures/emails/stranger_not_allowed.eml @@ -0,0 +1,11 @@ +Return-Path: +From: Foo Bar +To: category@bar.com +Subject: This is a topic from a complete stranger +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <31@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Hey, this is a topic from a complete stranger ;) diff --git a/spec/fixtures/emails/sufficient_trust_level.eml b/spec/fixtures/emails/sufficient_trust_level.eml new file mode 100644 index 000000000..cee56f11b --- /dev/null +++ b/spec/fixtures/emails/sufficient_trust_level.eml @@ -0,0 +1,11 @@ +Return-Path: +From: Foo Bar +To: category@bar.com +Subject: This is a topic from a know user +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <33@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Hey, this is a topic from a known user ;) diff --git a/spec/fixtures/emails/text_and_html_reply.eml b/spec/fixtures/emails/text_and_html_reply.eml new file mode 100644 index 000000000..5fb87780a --- /dev/null +++ b/spec/fixtures/emails/text_and_html_reply.eml @@ -0,0 +1,19 @@ +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <19@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: multipart/alternative; boundary=001a11469b1296cf8d052963bde5 + +--001a11469b1296cf8d052963bde5 +Content-Type: text/plain; charset=UTF-8 + +This is the *text* part. + +--001a11469b1296cf8d052963bde5 +Content-Type: text/html; charset=UTF-8 + +
    This is the html part.
    + +--001a11469b1296cf8d052963bde5-- diff --git a/spec/fixtures/emails/text_reply.eml b/spec/fixtures/emails/text_reply.eml new file mode 100644 index 000000000..1d295a3f2 --- /dev/null +++ b/spec/fixtures/emails/text_reply.eml @@ -0,0 +1,10 @@ +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <15@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit + +This is a text reply :) diff --git a/spec/fixtures/emails/too_many_mentions.eml b/spec/fixtures/emails/too_many_mentions.eml index 9cc7b75c9..6940955f9 100644 --- a/spec/fixtures/emails/too_many_mentions.eml +++ b/spec/fixtures/emails/too_many_mentions.eml @@ -1,31 +1,10 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: reply+636ca428858779856c226bb145ef4fad@appmail.adventuretime.ooo -Message-ID: -Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <14@foo.bar.mail> Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 +Content-Type: text/plain Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - -@user1 -@user2 -@user3 -@user4 -@user5 -@user6 -@user7 -@user8 -@user9 -@user10 -@user11 \ No newline at end of file +@user1 @user2 diff --git a/spec/fixtures/emails/too_short.eml b/spec/fixtures/emails/too_short.eml deleted file mode 100644 index 69f597697..000000000 --- a/spec/fixtures/emails/too_short.eml +++ /dev/null @@ -1,21 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: TO -Message-ID: -Subject: SUBJECT -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - - -ok diff --git a/spec/fixtures/emails/too_small.eml b/spec/fixtures/emails/too_small.eml new file mode 100644 index 000000000..fbc5c8d88 --- /dev/null +++ b/spec/fixtures/emails/too_small.eml @@ -0,0 +1,10 @@ +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Fri, 15 Jan 2016 00:12:43 +0100 +Message-ID: <12@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain +Content-Transfer-Encoding: 7bit + +Ok! diff --git a/spec/fixtures/emails/unsubscribe_body.eml b/spec/fixtures/emails/unsubscribe_body.eml new file mode 100644 index 000000000..1ae876edb --- /dev/null +++ b/spec/fixtures/emails/unsubscribe_body.eml @@ -0,0 +1,10 @@ +Return-Path: +From: Foo Bar +To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com +Date: Thu, 13 Jun 2013 17:03:48 -0400 +Message-ID: <55@foo.bar.mail> +Mime-Version: 1.0 +Content-Type: text/plain; +Content-Transfer-Encoding: 7bit + +UNSUBSCRIBE diff --git a/spec/fixtures/emails/unsubscribe_subject.eml b/spec/fixtures/emails/unsubscribe_subject.eml new file mode 100644 index 000000000..84b89079b --- /dev/null +++ b/spec/fixtures/emails/unsubscribe_subject.eml @@ -0,0 +1,11 @@ +Return-Path: +From: Foo Bar +To: reply@bar.com +Date: Thu, 13 Jun 2013 17:03:48 -0400 +Message-ID: <56@foo.bar.mail> +Subject: UnSuBScRiBe +Mime-Version: 1.0 +Content-Type: text/plain; +Content-Transfer-Encoding: 7bit + +I've basically had enough of your mailing list and would very much like it if you went away. diff --git a/spec/fixtures/emails/valid_incoming.cooked b/spec/fixtures/emails/valid_incoming.cooked deleted file mode 100644 index 2bf358253..000000000 --- a/spec/fixtures/emails/valid_incoming.cooked +++ /dev/null @@ -1,5 +0,0 @@ -

    Hey folks,

    - -

    I was thinking. Wouldn't it be great if we could post topics via email? Yes it would!

    - -

    Jakie

    diff --git a/spec/fixtures/emails/valid_incoming.eml b/spec/fixtures/emails/valid_incoming.eml deleted file mode 100644 index cad475d66..000000000 --- a/spec/fixtures/emails/valid_incoming.eml +++ /dev/null @@ -1,26 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: -Cc: -Message-ID: -Subject: We should have a post-by-email-feature. -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - -Hey folks, - -I was thinking. Wouldn't it be great if we could post topics via email? Yes it would! - -Jakie - diff --git a/spec/fixtures/emails/valid_reply.cooked b/spec/fixtures/emails/valid_reply.cooked deleted file mode 100644 index 4bce79ad1..000000000 --- a/spec/fixtures/emails/valid_reply.cooked +++ /dev/null @@ -1,4 +0,0 @@ -

    I could not disagree more. I am obviously biased but adventure time is the -greatest show ever created. Everyone should watch it.

    - -
    • Jake out
    diff --git a/spec/fixtures/emails/valid_reply.eml b/spec/fixtures/emails/valid_reply.eml deleted file mode 100644 index 786b1d851..000000000 --- a/spec/fixtures/emails/valid_reply.eml +++ /dev/null @@ -1,40 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: FROM -To: TO -Message-ID: -Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - -I could not disagree more. I am obviously biased but adventure time is the -greatest show ever created. Everyone should watch it. - -- Jake out - - -On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta - wrote: -> -> -> -> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: -> -> --- -> hey guys everyone knows adventure time sucks! -> -> --- -> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 -> -> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). -> diff --git a/spec/fixtures/emails/via_line.eml b/spec/fixtures/emails/via_line.eml deleted file mode 100644 index 0b2947ff9..000000000 --- a/spec/fixtures/emails/via_line.eml +++ /dev/null @@ -1,25 +0,0 @@ - -Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org -Received: by 10.194.216.104 with SMTP id op8csp80593wjc; - Wed, 24 Jul 2013 07:59:14 -0700 (PDT) -Return-Path: -References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -From: Walter White -In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -Mime-Version: 1.0 (1.0) -Date: Wed, 24 Jul 2013 15:59:10 +0100 -Message-ID: <4597127794206131679@unknownmsgid> -Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' -To: walter via Discourse -Content-Type: multipart/alternative; boundary=001a11c20edc15a39304e2432790 - -Hello this email has content! - -codinghorror via Discourse wrote: -> [codinghorror] codinghorror -> -> August 7 -> -> It wouldn't be great at the moment for 100% email, since there's no -> way to be notified of new topics via email. (you can get notified of -> new replies to your posts and topics via email, and reply to them.) diff --git a/spec/fixtures/emails/windows_8_metro.eml b/spec/fixtures/emails/windows_8_metro.eml deleted file mode 100644 index 67d204af5..000000000 --- a/spec/fixtures/emails/windows_8_metro.eml +++ /dev/null @@ -1,173 +0,0 @@ -Delivered-To: reply@discourse.org -Return-Path: -MIME-Version: 1.0 -From: -To: - =?utf-8?Q?Discourse_Meta?= - -Subject: - =?utf-8?Q?Re:_[Discourse_Meta]_[Lounge]_Testing_default_email_replies?= -Importance: Normal -Date: Fri, 28 Nov 2014 21:29:10 +0000 -In-Reply-To: -References: - , -Content-Type: multipart/alternative; - boundary="_866E2678-BB4F-4DD8-BE18-81B04AD8D1BC_" - ---_866E2678-BB4F-4DD8-BE18-81B04AD8D1BC_ -Content-Transfer-Encoding: base64 -Content-Type: text/plain; charset="utf-8" - -IyMjIHJlcGx5IGZyb20gZGVmYXVsdCBtYWlsIGNsaWVudCBpbiBXaW5kb3dzIDguMSBNZXRybw0K -DQoNClRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWlj -ayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gg -anVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0 -aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cu -IFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBi -cm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVt -cHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUg -bGF6eSBkb2cuDQoNCg0KVGhpcyBpcyBhICoqYm9sZCoqIHdvcmQgaW4gTWFya2Rvd24NCg0KDQpU -aGlzIGlzIGEgbGluayBodHRwOi8vZXhhbXBsZS5jb20NCiANCg0KDQoNCg0KDQpGcm9tOiBBcnBp -dCBKYWxhbg0KU2VudDog4oCORnJpZGF54oCOLCDigI5Ob3ZlbWJlcuKAjiDigI4yOOKAjiwg4oCO -MjAxNCDigI4xMuKAjjrigI4zNeKAjiDigI5QTQ0KVG86IGplZmYgYXR3b29kDQoNCg0KDQoNCg0K -DQogdGVjaEFQSg0KTm92ZW1iZXIgMjggDQoNClRlc3QgcmVwbHkuDQoNCkZpcnN0IHBhcmFncmFw -aC4NCg0KU2Vjb25kIHBhcmFncmFwaC4NCg0KDQoNClRvIHJlc3BvbmQsIHJlcGx5IHRvIHRoaXMg -ZW1haWwgb3IgdmlzaXQgaHR0cHM6Ly9tZXRhLmRpc2NvdXJzZS5vcmcvdC90ZXN0aW5nLWRlZmF1 -bHQtZW1haWwtcmVwbGllcy8yMjYzOC8zIGluIHlvdXIgYnJvd3Nlci4NCg0KDQoNClByZXZpb3Vz -IFJlcGxpZXMNCg0KIGNvZGluZ2hvcnJvcg0KTm92ZW1iZXIgMjggDQoNCldlJ3JlIHRlc3Rpbmcg -dGhlIGxhdGVzdCBHaXRIdWIgZW1haWwgcHJvY2Vzc2luZyBsaWJyYXJ5IHdoaWNoIHdlIGFyZSBp -bnRlZ3JhdGluZyBub3cuDQoNCmh0dHBzOi8vZ2l0aHViLmNvbS9naXRodWIvZW1haWxfcmVwbHlf -cGFyc2VyDQoNCkdvIGFoZWFkIGFuZCByZXBseSB0byB0aGlzIHRvcGljIGFuZCBJJ2xsIHJlcGx5 -IGZyb20gdmFyaW91cyBlbWFpbCBjbGllbnRzIGZvciB0ZXN0aW5nLg0KDQoNCg0KDQoNClRvIHJl -c3BvbmQsIHJlcGx5IHRvIHRoaXMgZW1haWwgb3IgdmlzaXQgaHR0cHM6Ly9tZXRhLmRpc2NvdXJz -ZS5vcmcvdC90ZXN0aW5nLWRlZmF1bHQtZW1haWwtcmVwbGllcy8yMjYzOC8zIGluIHlvdXIgYnJv -d3Nlci4NCg0KDQpUbyB1bnN1YnNjcmliZSBmcm9tIHRoZXNlIGVtYWlscywgdmlzaXQgeW91ciB1 -c2VyIHByZWZlcmVuY2VzLg== - ---_866E2678-BB4F-4DD8-BE18-81B04AD8D1BC_ -Content-Transfer-Encoding: base64 -Content-Type: text/html; charset="utf-8" - -CjxodG1sPgo8aGVhZD4KPG1ldGEgbmFtZT0iZ2VuZXJhdG9yIiBjb250ZW50PSJXaW5kb3dzIE1h -aWwgMTcuNS45NjAwLjIwNjA1Ij4KPHN0eWxlIGRhdGEtZXh0ZXJuYWxzdHlsZT0idHJ1ZSI+PCEt -LQpwLk1zb0xpc3RQYXJhZ3JhcGgsIGxpLk1zb0xpc3RQYXJhZ3JhcGgsIGRpdi5Nc29MaXN0UGFy -YWdyYXBoIHsKbWFyZ2luLXRvcDowaW47Cm1hcmdpbi1yaWdodDowaW47Cm1hcmdpbi1ib3R0b206 -MGluOwptYXJnaW4tbGVmdDouNWluOwptYXJnaW4tYm90dG9tOi4wMDAxcHQ7Cn0KcC5Nc29Ob3Jt -YWwsIGxpLk1zb05vcm1hbCwgZGl2Lk1zb05vcm1hbCB7Cm1hcmdpbjowaW47Cm1hcmdpbi1ib3R0 -b206LjAwMDFwdDsKfQpwLk1zb0xpc3RQYXJhZ3JhcGhDeFNwRmlyc3QsIGxpLk1zb0xpc3RQYXJh -Z3JhcGhDeFNwRmlyc3QsIGRpdi5Nc29MaXN0UGFyYWdyYXBoQ3hTcEZpcnN0LCAKcC5Nc29MaXN0 -UGFyYWdyYXBoQ3hTcE1pZGRsZSwgbGkuTXNvTGlzdFBhcmFncmFwaEN4U3BNaWRkbGUsIGRpdi5N -c29MaXN0UGFyYWdyYXBoQ3hTcE1pZGRsZSwgCnAuTXNvTGlzdFBhcmFncmFwaEN4U3BMYXN0LCBs -aS5Nc29MaXN0UGFyYWdyYXBoQ3hTcExhc3QsIGRpdi5Nc29MaXN0UGFyYWdyYXBoQ3hTcExhc3Qg -ewptYXJnaW4tdG9wOjBpbjsKbWFyZ2luLXJpZ2h0OjBpbjsKbWFyZ2luLWJvdHRvbTowaW47Cm1h -cmdpbi1sZWZ0Oi41aW47Cm1hcmdpbi1ib3R0b206LjAwMDFwdDsKbGluZS1oZWlnaHQ6MTE1JTsK -fQotLT48L3N0eWxlPjwvaGVhZD4KPGJvZHkgZGlyPSJsdHIiPgo8ZGl2IGRhdGEtZXh0ZXJuYWxz -dHlsZT0iZmFsc2UiIGRpcj0ibHRyIiBzdHlsZT0iZm9udC1mYW1pbHk6ICdDYWxpYnJpJywgJ1Nl -Z29lIFVJJywgJ01laXJ5bycsICdNaWNyb3NvZnQgWWFIZWkgVUknLCAnTWljcm9zb2Z0IEpoZW5n -SGVpIFVJJywgJ01hbGd1biBHb3RoaWMnLCAnc2Fucy1zZXJpZic7Zm9udC1zaXplOjEycHQ7Ij48 -ZGl2IHN0eWxlPSJmb250LXNpemU6IDE0cHQ7Ij4jIyMgcmVwbHkgZnJvbSBkZWZhdWx0IG1haWwg -Y2xpZW50IGluIFdpbmRvd3MgOC4xIE1ldHJvPC9kaXY+PGRpdiBzdHlsZT0iZm9udC1zaXplOiAx -NHB0OyI+PGJyPjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZTogMTRwdDsiPlRoZSBxdWljayBi -cm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVt -cHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUg -bGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRo -ZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93 -biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMg -b3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6 -eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuPC9kaXY+ -PGRpdiBzdHlsZT0iZm9udC1zaXplOiAxNHB0OyI+PGJyPjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQt -c2l6ZTogMTRwdDsiPlRoaXMgaXMgYSAqKmJvbGQqKiB3b3JkIGluIE1hcmtkb3duPC9kaXY+PGRp -diBzdHlsZT0iZm9udC1zaXplOiAxNHB0OyI+PGJyPjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6 -ZTogMTRwdDsiPlRoaXMgaXMgYSBsaW5rIDxhIGhyZWY9Imh0dHA6Ly9leGFtcGxlLmNvbSI+aHR0 -cDovL2V4YW1wbGUuY29tPC9hPjxicj4mbmJzcDs8L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6 -IDE0cHQ7Ij48YnI+PC9kaXY+PGRpdiBzdHlsZT0icGFkZGluZy10b3A6IDVweDsgYm9yZGVyLXRv -cC1jb2xvcjogcmdiKDIyOSwgMjI5LCAyMjkpOyBib3JkZXItdG9wLXdpZHRoOiAxcHg7IGJvcmRl -ci10b3Atc3R5bGU6IHNvbGlkOyI+PGRpdj48Zm9udCBmYWNlPSIgJ0NhbGlicmknLCAnU2Vnb2Ug -VUknLCAnTWVpcnlvJywgJ01pY3Jvc29mdCBZYUhlaSBVSScsICdNaWNyb3NvZnQgSmhlbmdIZWkg -VUknLCAnTWFsZ3VuIEdvdGhpYycsICdzYW5zLXNlcmlmJyIgc3R5bGU9J2xpbmUtaGVpZ2h0OiAx -NXB0OyBsZXR0ZXItc3BhY2luZzogMC4wMmVtOyBmb250LWZhbWlseTogIkNhbGlicmkiLCAiU2Vn -b2UgVUkiLCAiTWVpcnlvIiwgIk1pY3Jvc29mdCBZYUhlaSBVSSIsICJNaWNyb3NvZnQgSmhlbmdI -ZWkgVUkiLCAiTWFsZ3VuIEdvdGhpYyIsICJzYW5zLXNlcmlmIjsgZm9udC1zaXplOiAxMnB0Oyc+ -PGI+RnJvbTo8L2I+Jm5ic3A7PGEgaHJlZj0ibWFpbHRvOmluZm9AZGlzY291cnNlLm9yZyIgdGFy -Z2V0PSJfcGFyZW50Ij5BcnBpdCBKYWxhbjwvYT48YnI+PGI+U2VudDo8L2I+Jm5ic3A74oCORnJp -ZGF54oCOLCDigI5Ob3ZlbWJlcuKAjiDigI4yOOKAjiwg4oCOMjAxNCDigI4xMuKAjjrigI4zNeKA -jiDigI5QTTxicj48Yj5Ubzo8L2I+Jm5ic3A7PGEgaHJlZj0ibWFpbHRvOmphdHdvb2RAY29kaW5n -aG9ycm9yLmNvbSIgdGFyZ2V0PSJfcGFyZW50Ij5qZWZmIGF0d29vZDwvYT48L2ZvbnQ+PC9kaXY+ -PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdiBkaXI9IiI+PGRpdj4KCjx0YWJsZSB0YWJpbmRleD0i -LTEiIHN0eWxlPSJtYXJnaW4tYm90dG9tOiAyNXB4OyIgYm9yZGVyPSIwIiBjZWxsc3BhY2luZz0i -MCIgY2VsbHBhZGRpbmc9IjAiPgogIDx0Ym9keT4KICAgIDx0cj4KICAgICAgPHRkIHN0eWxlPSJ3 -aWR0aDogNTVweDsgdmVydGljYWwtYWxpZ246IHRvcDsiPgogICAgICAgIDxpbWcgd2lkdGg9IjQ1 -IiBoZWlnaHQ9IjQ1IiB0YWJpbmRleD0iLTEiIHN0eWxlPSJtYXgtd2lkdGg6IDEwMCU7IiBzcmM9 -Imh0dHBzOi8vbWV0YS1kaXNjb3Vyc2UuZ2xvYmFsLnNzbC5mYXN0bHkubmV0L3VzZXJfYXZhdGFy -L21ldGEuZGlzY291cnNlLm9yZy90ZWNoYXBqLzQ1LzMyODEucG5nIiBkYXRhLW1zLWltZ3NyYz0i -aHR0cHM6Ly9tZXRhLWRpc2NvdXJzZS5nbG9iYWwuc3NsLmZhc3RseS5uZXQvdXNlcl9hdmF0YXIv -bWV0YS5kaXNjb3Vyc2Uub3JnL3RlY2hhcGovNDUvMzI4MS5wbmciPgogICAgICA8L3RkPgogICAg -ICA8dGQ+CiAgICAgICAgPGEgc3R5bGU9J2NvbG9yOiByZ2IoNTksIDg5LCAxNTIpOyBmb250LWZh -bWlseTogImx1Y2lkYSBncmFuZGUiLHRhaG9tYSx2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7IGZv -bnQtc2l6ZTogMTNweDsgZm9udC13ZWlnaHQ6IGJvbGQ7IHRleHQtZGVjb3JhdGlvbjogbm9uZTsn -IGhyZWY9Imh0dHBzOi8vbWV0YS5kaXNjb3Vyc2Uub3JnL3VzZXJzL3RlY2hhcGoiIHRhcmdldD0i -X3BhcmVudCI+dGVjaEFQSjwvYT48YnI+CiAgICAgICAgPHNwYW4gc3R5bGU9J3RleHQtYWxpZ246 -IHJpZ2h0OyBjb2xvcjogcmdiKDE1MywgMTUzLCAxNTMpOyBwYWRkaW5nLXJpZ2h0OiA1cHg7IGZv -bnQtZmFtaWx5OiAibHVjaWRhIGdyYW5kZSIsdGFob21hLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJp -ZjsgZm9udC1zaXplOiAxMXB4Oyc+Tm92ZW1iZXIgMjg8L3NwYW4+CiAgICAgIDwvdGQ+CiAgICA8 -L3RyPgogICAgPHRyPgogICAgICA8dGQgc3R5bGU9InBhZGRpbmctdG9wOiA1cHg7IiBjb2xzcGFu -PSIyIj4KPHAgc3R5bGU9ImJvcmRlcjogMHB4IGJsYWNrOyBib3JkZXItaW1hZ2U6IG5vbmU7IG1h -cmdpbi10b3A6IDBweDsiPlRlc3QgcmVwbHkuPC9wPgoKPHAgc3R5bGU9ImJvcmRlcjogMHB4IGJs -YWNrOyBib3JkZXItaW1hZ2U6IG5vbmU7IG1hcmdpbi10b3A6IDBweDsiPkZpcnN0IHBhcmFncmFw -aC48L3A+Cgo8cCBzdHlsZT0iYm9yZGVyOiAwcHggYmxhY2s7IGJvcmRlci1pbWFnZTogbm9uZTsg -bWFyZ2luLXRvcDogMHB4OyI+U2Vjb25kIHBhcmFncmFwaC48L3A+CjwvdGQ+CiAgICA8L3RyPgog -IDwvdGJvZHk+CjwvdGFibGU+CgoKICA8ZGl2IHN0eWxlPSJjb2xvcjogcmdiKDEwMiwgMTAyLCAx -MDIpOyI+CiAgICA8cD5UbyByZXNwb25kLCByZXBseSB0byB0aGlzIGVtYWlsIG9yIHZpc2l0IDxh -IHN0eWxlPSJjb2xvcjogcmdiKDEwMiwgMTAyLCAxMDIpOyBmb250LXdlaWdodDogYm9sZDsgdGV4 -dC1kZWNvcmF0aW9uOiBub25lOyIgaHJlZj0iaHR0cHM6Ly9tZXRhLmRpc2NvdXJzZS5vcmcvdC90 -ZXN0aW5nLWRlZmF1bHQtZW1haWwtcmVwbGllcy8yMjYzOC8zIiB0YXJnZXQ9Il9wYXJlbnQiPmh0 -dHBzOi8vbWV0YS5kaXNjb3Vyc2Uub3JnL3QvdGVzdGluZy1kZWZhdWx0LWVtYWlsLXJlcGxpZXMv -MjI2MzgvMzwvYT4gaW4geW91ciBicm93c2VyLjwvcD4KICA8L2Rpdj4KICA8aHIgc3R5bGU9ImJv -cmRlcjogMXB4IGJsYWNrOyBib3JkZXItaW1hZ2U6IG5vbmU7IGhlaWdodDogMXB4OyBiYWNrZ3Jv -dW5kLWNvbG9yOiByZ2IoMjIxLCAyMjEsIDIyMSk7Ij4KICA8aDQ+UHJldmlvdXMgUmVwbGllczwv -aDQ+CgogIDx0YWJsZSB0YWJpbmRleD0iLTEiIHN0eWxlPSJtYXJnaW4tYm90dG9tOiAyNXB4OyIg -Ym9yZGVyPSIwIiBjZWxsc3BhY2luZz0iMCIgY2VsbHBhZGRpbmc9IjAiPgogIDx0Ym9keT4KICAg -IDx0cj4KICAgICAgPHRkIHN0eWxlPSJ3aWR0aDogNTVweDsgdmVydGljYWwtYWxpZ246IHRvcDsi -PgogICAgICAgIDxpbWcgd2lkdGg9IjQ1IiBoZWlnaHQ9IjQ1IiB0YWJpbmRleD0iLTEiIHN0eWxl -PSJtYXgtd2lkdGg6IDEwMCU7IiBzcmM9Imh0dHBzOi8vbWV0YS1kaXNjb3Vyc2UuZ2xvYmFsLnNz -bC5mYXN0bHkubmV0L3VzZXJfYXZhdGFyL21ldGEuZGlzY291cnNlLm9yZy9jb2Rpbmdob3Jyb3Iv -NDUvNTI5Ny5wbmciIGRhdGEtbXMtaW1nc3JjPSJodHRwczovL21ldGEtZGlzY291cnNlLmdsb2Jh -bC5zc2wuZmFzdGx5Lm5ldC91c2VyX2F2YXRhci9tZXRhLmRpc2NvdXJzZS5vcmcvY29kaW5naG9y -cm9yLzQ1LzUyOTcucG5nIj4KICAgICAgPC90ZD4KICAgICAgPHRkPgogICAgICAgIDxhIHN0eWxl -PSdjb2xvcjogcmdiKDU5LCA4OSwgMTUyKTsgZm9udC1mYW1pbHk6ICJsdWNpZGEgZ3JhbmRlIix0 -YWhvbWEsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmOyBmb250LXNpemU6IDEzcHg7IGZvbnQtd2Vp -Z2h0OiBib2xkOyB0ZXh0LWRlY29yYXRpb246IG5vbmU7JyBocmVmPSJodHRwczovL21ldGEuZGlz -Y291cnNlLm9yZy91c2Vycy9jb2Rpbmdob3Jyb3IiIHRhcmdldD0iX3BhcmVudCI+Y29kaW5naG9y -cm9yPC9hPjxicj4KICAgICAgICA8c3BhbiBzdHlsZT0ndGV4dC1hbGlnbjogcmlnaHQ7IGNvbG9y -OiByZ2IoMTUzLCAxNTMsIDE1Myk7IHBhZGRpbmctcmlnaHQ6IDVweDsgZm9udC1mYW1pbHk6ICJs -dWNpZGEgZ3JhbmRlIix0YWhvbWEsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmOyBmb250LXNpemU6 -IDExcHg7Jz5Ob3ZlbWJlciAyODwvc3Bhbj4KICAgICAgPC90ZD4KICAgIDwvdHI+CiAgICA8dHI+ -CiAgICAgIDx0ZCBzdHlsZT0icGFkZGluZy10b3A6IDVweDsiIGNvbHNwYW49IjIiPgo8cCBzdHls -ZT0iYm9yZGVyOiAwcHggYmxhY2s7IGJvcmRlci1pbWFnZTogbm9uZTsgbWFyZ2luLXRvcDogMHB4 -OyI+V2UncmUgdGVzdGluZyB0aGUgbGF0ZXN0IEdpdEh1YiBlbWFpbCBwcm9jZXNzaW5nIGxpYnJh -cnkgd2hpY2ggd2UgYXJlIGludGVncmF0aW5nIG5vdy48L3A+Cgo8cCBzdHlsZT0iYm9yZGVyOiAw -cHggYmxhY2s7IGJvcmRlci1pbWFnZTogbm9uZTsgbWFyZ2luLXRvcDogMHB4OyI+PGEgc3R5bGU9 -ImNvbG9yOiByZ2IoMCwgMTAyLCAxNTMpOyBmb250LXdlaWdodDogYm9sZDsgdGV4dC1kZWNvcmF0 -aW9uOiBub25lOyIgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL2dpdGh1Yi9lbWFpbF9yZXBseV9w -YXJzZXIiIHRhcmdldD0iX3BhcmVudCI+aHR0cHM6Ly9naXRodWIuY29tL2dpdGh1Yi9lbWFpbF9y -ZXBseV9wYXJzZXI8L2E+PC9wPgoKPHAgc3R5bGU9ImJvcmRlcjogMHB4IGJsYWNrOyBib3JkZXIt -aW1hZ2U6IG5vbmU7IG1hcmdpbi10b3A6IDBweDsiPkdvIGFoZWFkIGFuZCByZXBseSB0byB0aGlz -IHRvcGljIGFuZCBJJ2xsIHJlcGx5IGZyb20gdmFyaW91cyBlbWFpbCBjbGllbnRzIGZvciB0ZXN0 -aW5nLjwvcD4KPC90ZD4KICAgIDwvdHI+CiAgPC90Ym9keT4KPC90YWJsZT4KCgo8aHIgc3R5bGU9 -ImJvcmRlcjogMXB4IGJsYWNrOyBib3JkZXItaW1hZ2U6IG5vbmU7IGhlaWdodDogMXB4OyBiYWNr -Z3JvdW5kLWNvbG9yOiByZ2IoMjIxLCAyMjEsIDIyMSk7Ij4KCjxkaXYgc3R5bGU9ImNvbG9yOiBy -Z2IoMTAyLCAxMDIsIDEwMik7Ij4KPHA+VG8gcmVzcG9uZCwgcmVwbHkgdG8gdGhpcyBlbWFpbCBv -ciB2aXNpdCA8YSBzdHlsZT0iY29sb3I6IHJnYigxMDIsIDEwMiwgMTAyKTsgZm9udC13ZWlnaHQ6 -IGJvbGQ7IHRleHQtZGVjb3JhdGlvbjogbm9uZTsiIGhyZWY9Imh0dHBzOi8vbWV0YS5kaXNjb3Vy -c2Uub3JnL3QvdGVzdGluZy1kZWZhdWx0LWVtYWlsLXJlcGxpZXMvMjI2MzgvMyIgdGFyZ2V0PSJf -cGFyZW50Ij5odHRwczovL21ldGEuZGlzY291cnNlLm9yZy90L3Rlc3RpbmctZGVmYXVsdC1lbWFp -bC1yZXBsaWVzLzIyNjM4LzM8L2E+IGluIHlvdXIgYnJvd3Nlci48L3A+CjwvZGl2Pgo8ZGl2IHN0 -eWxlPSJjb2xvcjogcmdiKDEwMiwgMTAyLCAxMDIpOyI+CjxwPlRvIHVuc3Vic2NyaWJlIGZyb20g -dGhlc2UgZW1haWxzLCB2aXNpdCB5b3VyIDxhIHN0eWxlPSJjb2xvcjogcmdiKDEwMiwgMTAyLCAx -MDIpOyBmb250LXdlaWdodDogYm9sZDsgdGV4dC1kZWNvcmF0aW9uOiBub25lOyIgaHJlZj0iaHR0 -cHM6Ly9tZXRhLmRpc2NvdXJzZS5vcmcvbXkvcHJlZmVyZW5jZXMiIHRhcmdldD0iX3BhcmVudCI+ -dXNlciBwcmVmZXJlbmNlczwvYT4uPC9wPgo8L2Rpdj4KPC9kaXY+CjwvZGl2PjxkaXYgc3R5bGU9 -ImZvbnQtc2l6ZTogMTRwdDsiPjxicj48L2Rpdj48L2Rpdj4KPC9ib2R5Pgo8L2h0bWw+Cg== - ---_866E2678-BB4F-4DD8-BE18-81B04AD8D1BC_-- diff --git a/spec/fixtures/emails/wrong_reply_key.eml b/spec/fixtures/emails/wrong_reply_key.eml deleted file mode 100644 index 1c30cfc51..000000000 --- a/spec/fixtures/emails/wrong_reply_key.eml +++ /dev/null @@ -1,40 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: reply+03d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo -Message-ID: -Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - -I could not disagree more. I am obviously biased but adventure time is the -greatest show ever created. Everyone should watch it. - -- Jake out - - -On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta - wrote: -> -> -> -> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: -> -> --- -> hey guys everyone knows adventure time sucks! -> -> --- -> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 -> -> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). -> diff --git a/spec/jobs/jobs_spec.rb b/spec/jobs/jobs_spec.rb index fa31b7ea1..57d2717b2 100644 --- a/spec/jobs/jobs_spec.rb +++ b/spec/jobs/jobs_spec.rb @@ -1,3 +1,4 @@ +require "sidekiq/testing" require 'rails_helper' require_dependency 'jobs/base' @@ -76,23 +77,27 @@ describe Jobs do end describe 'cancel_scheduled_job' do + it 'deletes the matching job' do - job_to_delete = stub_everything(klass: 'Sidekiq::Extensions::DelayedClass', args: [YAML.dump(['Jobs::DrinkBeer', :delayed_perform, [{beer_id: 42}]])]) - job_to_delete.expects(:delete) - job_to_keep1 = stub_everything(klass: 'Sidekiq::Extensions::DelayedClass', args: [YAML.dump(['Jobs::DrinkBeer', :delayed_perform, [{beer_id: 43}]])]) - job_to_keep1.expects(:delete).never - job_to_keep2 = stub_everything(klass: 'Sidekiq::Extensions::DelayedClass', args: [YAML.dump(['Jobs::DrinkBeer', :delayed_perform, [{beer_id: 44}]])]) - job_to_keep2.expects(:delete).never - Sidekiq::ScheduledSet.stubs(:new).returns( [job_to_keep1, job_to_delete, job_to_keep2] ) - expect(Jobs.cancel_scheduled_job(:drink_beer, {beer_id: 42})).to eq(true) + SiteSetting.queue_jobs = true + + Sidekiq::Testing.disable! do + scheduled_jobs = Sidekiq::ScheduledSet.new + scheduled_jobs.clear + + expect(scheduled_jobs.size).to eq(0) + + Jobs.enqueue_in(1.year, :run_heartbeat, topic_id: 1234) + Jobs.enqueue_in(2.years, :run_heartbeat, topic_id: 5678) + + expect(scheduled_jobs.size).to eq(2) + + Jobs.cancel_scheduled_job(:run_heartbeat, topic_id: 1234) + + expect(scheduled_jobs.size).to eq(1) + end end - it 'returns false when no matching job is scheduled' do - job_to_keep = stub_everything(klass: 'Sidekiq::Extensions::DelayedClass', args: [YAML.dump(['Jobs::DrinkBeer', :delayed_perform, [{beer_id: 43}]])]) - job_to_keep.expects(:delete).never - Sidekiq::ScheduledSet.stubs(:new).returns( [job_to_keep] ) - expect(Jobs.cancel_scheduled_job(:drink_beer, {beer_id: 42})).to eq(false) - end end describe 'enqueue_at' do diff --git a/spec/jobs/poll_mailbox_spec.rb b/spec/jobs/poll_mailbox_spec.rb index 563bfab82..451cf07e3 100644 --- a/spec/jobs/poll_mailbox_spec.rb +++ b/spec/jobs/poll_mailbox_spec.rb @@ -3,25 +3,20 @@ require_dependency 'jobs/regular/process_post' describe Jobs::PollMailbox do - let!(:poller) { Jobs::PollMailbox.new } + let(:poller) { Jobs::PollMailbox.new } describe ".execute" do it "does no polling if pop3_polling_enabled is false" do - SiteSetting.expects(:pop3_polling_enabled?).returns(false) + SiteSetting.expects(:pop3_polling_enabled).returns(false) poller.expects(:poll_pop3).never - poller.execute({}) end - describe "with pop3_polling_enabled" do - - it "calls poll_pop3" do - SiteSetting.expects(:pop3_polling_enabled?).returns(true) - poller.expects(:poll_pop3).once - - poller.execute({}) - end + it "polls when pop3_polling_enabled is true" do + SiteSetting.expects(:pop3_polling_enabled).returns(true) + poller.expects(:poll_pop3).once + poller.execute({}) end end @@ -29,13 +24,8 @@ describe Jobs::PollMailbox do describe ".poll_pop3" do it "logs an error on pop authentication error" do - error = Net::POPAuthenticationError.new - data = { limit_once_per: 1.hour, message_params: { error: error }} - - Net::POP3.any_instance.expects(:start).raises(error) - + Net::POP3.any_instance.expects(:start).raises(Net::POPAuthenticationError.new) Discourse.expects(:handle_job_exception) - poller.poll_pop3 end @@ -43,275 +33,15 @@ describe Jobs::PollMailbox do SiteSetting.pop3_polling_ssl = true Net::POP3.any_instance.stubs(:start) Net::POP3.any_instance.expects(:enable_ssl) - poller.poll_pop3 end - it "does not call enable_ssl when the setting is off" do + it "does not call enable_ssl when the setting is disabled" do SiteSetting.pop3_polling_ssl = false Net::POP3.any_instance.stubs(:start) Net::POP3.any_instance.expects(:enable_ssl).never - poller.poll_pop3 end end - # Testing mock for the email objects that you get - # from Net::POP3.start { |pop| pop.mails } - class MockPop3EmailObject - def initialize(mail_string) - @message = mail_string - @delete_called = 0 - end - - def pop - @message - end - - def delete - @delete_called += 1 - end - - # call 'assert email.deleted?' at the end of the test - def deleted? - @delete_called == 1 - end - end - - def expect_success - poller.expects(:handle_failure).never - end - - def expect_exception(clazz) - poller.expects(:handle_failure).with(anything, instance_of(clazz)) - end - - describe "processing emails" do - let(:category) { Fabricate(:category) } - let(:user) { Fabricate(:user) } - - before do - SiteSetting.email_in = true - SiteSetting.reply_by_email_address = "reply+%{reply_key}@appmail.adventuretime.ooo" - category.email_in = 'incoming+amazing@appmail.adventuretime.ooo' - category.save - user.change_trust_level! 2 - user.username = 'Jake' - user.email = 'jake@adventuretime.ooo' - user.save - end - - describe "a valid incoming email" do - let(:email) { - # this string replacing is kinda dumb - str = fixture_file('emails/valid_incoming.eml') - str = str.gsub("FROM", 'jake@adventuretime.ooo').gsub("TO", 'incoming+amazing@appmail.adventuretime.ooo') - MockPop3EmailObject.new str - } - let(:expected_post) { fixture_file('emails/valid_incoming.cooked') } - - it "posts a new topic with the correct content" do - expect_success - - poller.handle_mail(email) - - topic = Topic.where(category: category).where.not(id: category.topic_id).last - expect(topic).to be_present - expect(topic.title).to eq("We should have a post-by-email-feature") - - post = topic.posts.first - expect(post.cooked.strip).to eq(expected_post.strip) - - expect(email).to be_deleted - end - - describe "with insufficient trust" do - before do - user.change_trust_level! 0 - end - - it "raises a UserNotSufficientTrustLevelError" do - expect_exception Email::Receiver::UserNotSufficientTrustLevelError - - poller.handle_mail(email) - end - - it "posts the topic if allow_strangers is true" do - begin - category.email_in_allow_strangers = true - category.save - - expect_success - poller.handle_mail(email) - topic = Topic.where(category: category).where.not(id: category.topic_id).last - expect(topic).to be_present - expect(topic.title).to eq("We should have a post-by-email-feature") - ensure - category.email_in_allow_strangers = false - category.save - end - end - end - - describe "user in restricted group" do - - it "raises InvalidAccess error" do - restricted_group = Fabricate(:group) - restricted_group.add(user) - restricted_group.save - - category.set_permissions(restricted_group => :readonly) - category.save - - expect_exception Discourse::InvalidAccess - - poller.handle_mail(email) - expect(email).to be_deleted - end - end - end - - describe "a valid reply" do - let(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' } - let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) } - let(:raw_email) { fill_email(fixture_file("emails/valid_reply.eml"), user.email, to) } - let(:email) { MockPop3EmailObject.new(raw_email) } - let(:expected_post) { fixture_file('emails/valid_reply.cooked') } - let(:topic) { Fabricate(:topic) } - let(:first_post) { Fabricate(:post, user: user, topic: topic, post_number: 1) } - - before do - first_post.save - EmailLog.create(to_address: user.email, - email_type: 'user_posted', - reply_key: reply_key, - user: user, - post: first_post, - topic: topic) - end - - it "creates a new post" do - expect_success - - poller.handle_mail(email) - - new_post = Post.find_by(topic: topic, post_number: 2) - assert new_post.present? - assert_equal expected_post.strip, new_post.cooked.strip - - expect(email).to be_deleted - end - - it "works with multiple To addresses" do - email = MockPop3EmailObject.new fixture_file('emails/multiple_destinations.eml') - expect_success - - poller.handle_mail(email) - - new_post = Post.find_by(topic: topic, post_number: 2) - assert new_post.present? - assert_equal expected_post.strip, new_post.cooked.strip - - expect(email).to be_deleted - end - - describe "with the wrong reply key" do - let(:email) { MockPop3EmailObject.new fixture_file('emails/wrong_reply_key.eml') } - - it "raises an EmailLogNotFound error" do - expect_exception Email::Receiver::EmailLogNotFound - - poller.handle_mail(email) - expect(email).to be_deleted - end - end - end - - describe "when topic is closed" do - let(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' } - let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) } - let(:raw_email) { fill_email(fixture_file("emails/valid_reply.eml"), user.email, to) } - let(:email) { MockPop3EmailObject.new(raw_email) } - let(:topic) { Fabricate(:topic, closed: true) } - let(:first_post) { Fabricate(:post, user: user, topic: topic, post_number: 1) } - - before do - first_post.save - EmailLog.create(to_address: user.email, - email_type: 'user_posted', - reply_key: reply_key, - user: user, - post: first_post, - topic: topic) - end - - describe "should not create post" do - it "raises a TopicClosedError" do - expect_exception Email::Receiver::TopicClosedError - - poller.handle_mail(email) - expect(email).to be_deleted - end - end - end - - describe "when topic is deleted" do - let(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' } - let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) } - let(:raw_email) { fill_email(fixture_file("emails/valid_reply.eml"), user.email, to) } - let(:email) { MockPop3EmailObject.new(raw_email) } - let(:deleted_topic) { Fabricate(:deleted_topic) } - let(:first_post) { Fabricate(:post, user: user, topic: deleted_topic, post_number: 1)} - - before do - first_post.save - EmailLog.create(to_address: user.email, - email_type: 'user_posted', - reply_key: reply_key, - user: user, - post: first_post, - topic: deleted_topic) - end - - describe "should not create post" do - it "raises a TopicNotFoundError" do - expect_exception Email::Receiver::TopicNotFoundError - - poller.handle_mail(email) - expect(email).to be_deleted - end - end - end - - describe "in failure conditions" do - - it "a valid reply without an email log raises an EmailLogNotFound error" do - to = SiteSetting.reply_by_email_address.gsub("%{reply_key}", '59d8df8370b7e95c5a49fbf86aeb2c93') - raw_email = fill_email(fixture_file("emails/valid_reply.eml"), user.email, to) - email = MockPop3EmailObject.new(raw_email) - expect_exception Email::Receiver::EmailLogNotFound - - poller.handle_mail(email) - expect(email).to be_deleted - end - - it "a no content reply raises an EmptyEmailError" do - email = MockPop3EmailObject.new fixture_file('emails/no_content_reply.eml') - expect_exception Email::Receiver::EmptyEmailError - - poller.handle_mail(email) - expect(email).to be_deleted - end - - it "a fully empty email raises an EmptyEmailError" do - email = MockPop3EmailObject.new fixture_file('emails/empty.eml') - expect_exception Email::Receiver::EmptyEmailError - - poller.handle_mail(email) - expect(email).to be_deleted - end - - end - end - end diff --git a/spec/mailers/user_notifications_spec.rb b/spec/mailers/user_notifications_spec.rb index 3f0745163..fafe7b8ee 100644 --- a/spec/mailers/user_notifications_spec.rb +++ b/spec/mailers/user_notifications_spec.rb @@ -108,7 +108,7 @@ describe UserNotifications do expect(mail.subject).to match(/India/) # 2 respond to links cause we have 1 context post - expect(mail.html_part.to_s.scan(/To respond/).count).to eq(2) + expect(mail.html_part.to_s.scan(/to respond/).count).to eq(2) # 1 unsubscribe expect(mail.html_part.to_s.scan(/To unsubscribe/).count).to eq(1) @@ -159,7 +159,7 @@ describe UserNotifications do expect(mail.subject).not_to match(/Uncategorized/) # 2 respond to links cause we have 1 context post - expect(mail.html_part.to_s.scan(/To respond/).count).to eq(2) + expect(mail.html_part.to_s.scan(/to respond/).count).to eq(2) # 1 unsubscribe link expect(mail.html_part.to_s.scan(/To unsubscribe/).count).to eq(1) @@ -188,7 +188,7 @@ describe UserNotifications do expect(mail.subject).to match("[PM]") # 1 respond to link - expect(mail.html_part.to_s.scan(/To respond/).count).to eq(1) + expect(mail.html_part.to_s.scan(/to respond/).count).to eq(1) # 1 unsubscribe link expect(mail.html_part.to_s.scan(/To unsubscribe/).count).to eq(1) diff --git a/spec/models/category_group_spec.rb b/spec/models/category_group_spec.rb new file mode 100644 index 000000000..d884f7f91 --- /dev/null +++ b/spec/models/category_group_spec.rb @@ -0,0 +1,20 @@ +require 'rails_helper' + +describe CategoryGroup do + + describe '#permission_types' do + context "verify enum sequence" do + before do + @permission_types = CategoryGroup.permission_types + end + + it "'full' should be at 1st position" do + expect(@permission_types[:full]).to eq(1) + end + + it "'readonly' should be at 3rd position" do + expect(@permission_types[:readonly]).to eq(3) + end + end + end +end diff --git a/spec/models/category_spec.rb b/spec/models/category_spec.rb index f466be73c..ff253badb 100644 --- a/spec/models/category_spec.rb +++ b/spec/models/category_spec.rb @@ -503,6 +503,22 @@ describe Category do end end + describe "#url_with_id" do + let(:category) { Fabricate(:category, name: 'cats') } + + it "includes the id in the URL" do + expect(category.url_with_id).to eq("/c/#{category.id}-cats") + end + + context "child category" do + let(:child_category) { Fabricate(:category, parent_category_id: category.id, name: 'dogs') } + + it "includes the id in the URL" do + expect(child_category.url_with_id).to eq("/c/cats/dogs/#{child_category.id}") + end + end + end + describe "uncategorized" do let(:cat) { Category.where(id: SiteSetting.uncategorized_category_id).first } diff --git a/spec/models/directory_item_spec.rb b/spec/models/directory_item_spec.rb index ad3b23b0f..d19be0af8 100644 --- a/spec/models/directory_item_spec.rb +++ b/spec/models/directory_item_spec.rb @@ -1,6 +1,23 @@ require 'rails_helper' describe DirectoryItem do + + describe '#period_types' do + context "verify enum sequence" do + before do + @period_types = DirectoryItem.period_types + end + + it "'all' should be at 1st position" do + expect(@period_types[:all]).to eq(1) + end + + it "'quarterly' should be at 6th position" do + expect(@period_types[:quarterly]).to eq(6) + end + end + end + context 'refresh' do let!(:post) { Fabricate(:post) } diff --git a/spec/models/embeddable_host_spec.rb b/spec/models/embeddable_host_spec.rb index 2a4c306a0..4ee44c560 100644 --- a/spec/models/embeddable_host_spec.rb +++ b/spec/models/embeddable_host_spec.rb @@ -20,6 +20,12 @@ describe EmbeddableHost do expect(eh.host).to eq('example.com') end + it "supports ip addresses" do + eh = EmbeddableHost.new(host: '192.168.0.1') + expect(eh).to be_valid + expect(eh.host).to eq('192.168.0.1') + end + describe "allows_embeddable_host" do let!(:host) { Fabricate(:embeddable_host) } diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 87bd3898b..36ad2b32c 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -2,6 +2,22 @@ require 'rails_helper' describe Group do + describe '#builtin' do + context "verify enum sequence" do + before do + @builtin = Group.builtin + end + + it "'moderators' should be at 1st position" do + expect(@builtin[:moderators]).to eq(1) + end + + it "'trust_level_2' should be at 4th position" do + expect(@builtin[:trust_level_2]).to eq(4) + end + end + end + # UGLY but perf is horrible with this callback before do User.set_callback(:create, :after, :ensure_in_trust_level_group) diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb index eef253c2c..1fa789f65 100644 --- a/spec/models/notification_spec.rb +++ b/spec/models/notification_spec.rb @@ -11,6 +11,22 @@ describe Notification do it { is_expected.to belong_to :user } it { is_expected.to belong_to :topic } + describe '#types' do + context "verify enum sequence" do + before do + @types = Notification.types + end + + it "'mentioned' should be at 1st position" do + expect(@types[:mentioned]).to eq(1) + end + + it "'group_mentioned' should be at 15th position" do + expect(@types[:group_mentioned]).to eq(15) + end + end + end + describe 'post' do let(:topic) { Fabricate(:topic) } let(:post_args) do diff --git a/spec/models/post_action_type_spec.rb b/spec/models/post_action_type_spec.rb new file mode 100644 index 000000000..7408ed669 --- /dev/null +++ b/spec/models/post_action_type_spec.rb @@ -0,0 +1,20 @@ +require 'rails_helper' + +describe PostActionType do + + describe '#types' do + context "verify enum sequence" do + before do + @types = PostActionType.types + end + + it "'bookmark' should be at 1st position" do + expect(@types[:bookmark]).to eq(1) + end + + it "'spam' should be at 8th position" do + expect(@types[:spam]).to eq(8) + end + end + end +end diff --git a/spec/models/post_analyzer_spec.rb b/spec/models/post_analyzer_spec.rb index 096e90014..57e68ee88 100644 --- a/spec/models/post_analyzer_spec.rb +++ b/spec/models/post_analyzer_spec.rb @@ -205,5 +205,10 @@ describe PostAnalyzer do post_analyzer = PostAnalyzer.new("@Jake @Finn @Jake_Old", default_topic_id) expect(post_analyzer.raw_mentions).to eq(['jake', 'finn', 'jake_old']) end + + it "ignores emails" do + post_analyzer = PostAnalyzer.new("1@test.com 1@best.com @best @not", default_topic_id) + expect(post_analyzer.raw_mentions).to eq(['best', 'not']) + end end end diff --git a/spec/models/post_mover_spec.rb b/spec/models/post_mover_spec.rb index 7cf0f900e..c90dcae0c 100644 --- a/spec/models/post_mover_spec.rb +++ b/spec/models/post_mover_spec.rb @@ -2,6 +2,22 @@ require 'rails_helper' describe PostMover do + describe '#move_types' do + context "verify enum sequence" do + before do + @move_types = PostMover.move_types + end + + it "'new_topic' should be at 1st position" do + expect(@move_types[:new_topic]).to eq(1) + end + + it "'existing_topic' should be at 2nd position" do + expect(@move_types[:existing_topic]).to eq(2) + end + end + end + context 'move_posts' do let(:user) { Fabricate(:user) } let(:another_user) { Fabricate(:evil_trout) } diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb index 1baf1caf5..c7a6ead93 100644 --- a/spec/models/post_spec.rb +++ b/spec/models/post_spec.rb @@ -4,6 +4,54 @@ require_dependency 'post_destroyer' describe Post do before { Oneboxer.stubs :onebox } + describe '#hidden_reasons' do + context "verify enum sequence" do + before do + @hidden_reasons = Post.hidden_reasons + end + + it "'flag_threshold_reached' should be at 1st position" do + expect(@hidden_reasons[:flag_threshold_reached]).to eq(1) + end + + it "'flagged_by_tl3_user' should be at 4th position" do + expect(@hidden_reasons[:flagged_by_tl3_user]).to eq(4) + end + end + end + + describe '#types' do + context "verify enum sequence" do + before do + @types = Post.types + end + + it "'regular' should be at 1st position" do + expect(@types[:regular]).to eq(1) + end + + it "'whisper' should be at 4th position" do + expect(@types[:whisper]).to eq(4) + end + end + end + + describe '#cook_methods' do + context "verify enum sequence" do + before do + @cook_methods = Post.cook_methods + end + + it "'regular' should be at 1st position" do + expect(@cook_methods[:regular]).to eq(1) + end + + it "'email' should be at 3rd position" do + expect(@cook_methods[:email]).to eq(3) + end + end + end + # Help us build a post with a raw body def post_with_body(body, user=nil) args = post_args.merge(raw: body) diff --git a/spec/models/queued_post_spec.rb b/spec/models/queued_post_spec.rb index 5a8b974be..8a16ca3a9 100644 --- a/spec/models/queued_post_spec.rb +++ b/spec/models/queued_post_spec.rb @@ -3,6 +3,22 @@ require_dependency 'queued_post' describe QueuedPost do + describe '#states' do + context "verify enum sequence" do + before do + @states = QueuedPost.states + end + + it "'new' should be at 1st position" do + expect(@states[:new]).to eq(1) + end + + it "'rejected' should be at 3rd position" do + expect(@states[:rejected]).to eq(3) + end + end + end + context "creating a post" do let(:topic) { Fabricate(:topic) } let(:user) { Fabricate(:user) } diff --git a/spec/models/s3_region_site_setting_spec.rb b/spec/models/s3_region_site_setting_spec.rb index 2249fbad5..ffba50b7f 100644 --- a/spec/models/s3_region_site_setting_spec.rb +++ b/spec/models/s3_region_site_setting_spec.rb @@ -14,7 +14,7 @@ describe S3RegionSiteSetting do describe 'values' do it 'returns all the S3 regions' do - expect(S3RegionSiteSetting.values.map {|x| x[:value]}.sort).to eq(['us-east-1', 'us-west-1', 'us-west-2', 'us-gov-west-1', 'eu-west-1', 'eu-central-1', 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'sa-east-1'].sort) + expect(S3RegionSiteSetting.values.map {|x| x[:value]}.sort).to eq(['us-east-1', 'us-west-1', 'us-west-2', 'us-gov-west-1', 'eu-west-1', 'eu-central-1', 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'ap-northeast-2', 'sa-east-1'].sort) end end diff --git a/spec/models/top_topic_spec.rb b/spec/models/top_topic_spec.rb index 0e3a473b5..7c73a98b3 100644 --- a/spec/models/top_topic_spec.rb +++ b/spec/models/top_topic_spec.rb @@ -2,6 +2,22 @@ require 'rails_helper' describe TopTopic do + describe '#sorted_periods' do + context "verify enum sequence" do + before do + @sorted_periods = TopTopic.sorted_periods + end + + it "'daily' should be at 1st position" do + expect(@sorted_periods[:daily]).to eq(1) + end + + it "'all' should be at 6th position" do + expect(@sorted_periods[:all]).to eq(6) + end + end + end + it { is_expected.to belong_to :topic } context "refresh!" do @@ -22,9 +38,108 @@ describe TopTopic do it "should have top topics" do expect(TopTopic.pluck(:topic_id)).to match_array([t1.id, t2.id]) end - end - end + describe "#compute_top_score_for" do + + let(:user) { Fabricate(:user) } + let(:coding_horror) { Fabricate(:coding_horror) } + + let!(:topic_1) { Fabricate(:topic, posts_count: 10, like_count: 28) } + let!(:t1_post_1) { Fabricate(:post, topic: topic_1, like_count: 28, post_number: 1)} + + let!(:topic_2) { Fabricate(:topic, posts_count: 10, like_count: 20) } + let!(:t2_post_1) { Fabricate(:post, topic: topic_2, like_count: 10, post_number: 1) } + let!(:t2_post_2) { Fabricate(:post, topic: topic_2, like_count: 10) } + + let!(:topic_3) { Fabricate(:topic, posts_count: 10) } + let!(:t3_post_1) { Fabricate(:post, topic_id: topic_3.id) } + let!(:t3_view_1) { TopicViewItem.add(topic_3.id, '127.0.0.1', user) } + let!(:t3_view_2) { TopicViewItem.add(topic_3.id, '127.0.0.2', coding_horror) } + + # Note: all topics has 10 posts so we can skip "0 - ((10 - topics.posts_count) / 20) * #{period}_op_likes_count" calculation + + it "should compute top score" do + # Default Formula: log(views_count) * {2} + op_likes_count * {0.5} + LEAST(likes_count / posts_count, {3}) + 10 + log(posts_count) + # + # topic_1 => 0 + 14 + 3 + 10 + 0 => 27 + # topic_2 => 0 + 5 + 3 + 10 + 0.301029995664 => 18.301029995664 + # topic_3 => 0.602059991328 + 0 + 0 + 10 + 0 => 10.602059991328 + + TopTopic.refresh! + top_topics = TopTopic.all + + expect(top_topics.where(topic_id: topic_1.id).pluck(:yearly_score).first).to eq(27) + expect(top_topics.where(topic_id: topic_2.id).pluck(:yearly_score).first).to eq(18.301029995664) + expect(top_topics.where(topic_id: topic_3.id).pluck(:yearly_score).first).to eq(10.602059991328) + + # when 'top_topics_formula_log_views_multiplier' setting is changed + SiteSetting.top_topics_formula_log_views_multiplier = 4 + SiteSetting.top_topics_formula_first_post_likes_multiplier = 0.5 # unchanged + SiteSetting.top_topics_formula_least_likes_per_post_multiplier = 3 # unchanged + + # New Formula: log(views_count) * {4} + op_likes_count * {0.5} + LEAST(likes_count / posts_count, {3}) + 10 + log(posts_count) + # + # topic_1 => 0 + 14 + 3 + 10 + 0 => 27 + # topic_2 => 0 + 5 + 3 + 10 + 0.301029995664 => 18.301029995664 + # topic_3 => 1.2041199826559 + 0 + 0 + 10 + 0 => 11.2041199826559 + + TopTopic.refresh! + top_topics = TopTopic.all + + expect(top_topics.where(topic_id: topic_1.id).pluck(:yearly_score).first).to eq(27) + expect(top_topics.where(topic_id: topic_2.id).pluck(:yearly_score).first).to eq(18.301029995664) + expect(top_topics.where(topic_id: topic_3.id).pluck(:yearly_score).first).to eq(11.2041199826559) + + # when 'top_topics_formula_first_post_likes_multiplier' setting is changed + SiteSetting.top_topics_formula_log_views_multiplier = 2 # unchanged + SiteSetting.top_topics_formula_first_post_likes_multiplier = 2 + SiteSetting.top_topics_formula_least_likes_per_post_multiplier = 3 # unchanged + + # New Formula: log(views_count) * {2} + op_likes_count * {2} + LEAST(likes_count / posts_count, {3}) + 10 + log(posts_count) + # + # topic_1 => 0 + 56 + 3 + 10 + 0 => 69 + # topic_2 => 0 + 20 + 3 + 10 + 0.301029995664 => 33.301029995664 + # topic_3 => 0.602059991328 + 0 + 0 + 10 + 0 => 10.602059991328 + + TopTopic.refresh! + top_topics = TopTopic.all + + expect(top_topics.where(topic_id: topic_1.id).pluck(:yearly_score).first).to eq(69) + expect(top_topics.where(topic_id: topic_2.id).pluck(:yearly_score).first).to eq(33.301029995664) + expect(top_topics.where(topic_id: topic_3.id).pluck(:yearly_score).first).to eq(10.602059991328) + + # when 'top_topics_formula_least_likes_per_post_multiplier' setting is changed + SiteSetting.top_topics_formula_log_views_multiplier = 2 # unchanged + SiteSetting.top_topics_formula_first_post_likes_multiplier = 0.5 # unchanged + SiteSetting.top_topics_formula_least_likes_per_post_multiplier = 6 + + # New Formula: log(views_count) * {2} + op_likes_count * {0.5} + LEAST(likes_count / posts_count, {6}) + 10 + log(posts_count) + # + # topic_1 => 0 + 14 + 6 + 10 + 0 => 30 + # topic_2 => 0 + 5 + 6 + 10 + 0.301029995664 => 21.301029995664 + # topic_3 => 0.602059991328 + 0 + 0 + 10 + 0 => 10.602059991328 + + TopTopic.refresh! + top_topics = TopTopic.all + + expect(top_topics.where(topic_id: topic_1.id).pluck(:yearly_score).first).to eq(30) + expect(top_topics.where(topic_id: topic_2.id).pluck(:yearly_score).first).to eq(21.301029995664) + expect(top_topics.where(topic_id: topic_3.id).pluck(:yearly_score).first).to eq(10.602059991328) + + # handles invalid string value + SiteSetting.top_topics_formula_log_views_multiplier = "not good" + SiteSetting.top_topics_formula_first_post_likes_multiplier = "not good" + SiteSetting.top_topics_formula_least_likes_per_post_multiplier = "not good" + + TopTopic.refresh! + top_topics = TopTopic.all + + expect(top_topics.where(topic_id: topic_1.id).pluck(:yearly_score).first).to eq(27) + expect(top_topics.where(topic_id: topic_2.id).pluck(:yearly_score).first).to eq(18.301029995664) + expect(top_topics.where(topic_id: topic_3.id).pluck(:yearly_score).first).to eq(10.602059991328) + + end + end end diff --git a/spec/models/topic_spec.rb b/spec/models/topic_spec.rb index 6a8eb9d76..3799738ad 100644 --- a/spec/models/topic_spec.rb +++ b/spec/models/topic_spec.rb @@ -378,7 +378,7 @@ describe Topic do expect(topic.invite(topic.user, walter.username)).to eq(true) expect(topic.allowed_users.include?(walter)).to eq(true) - expect(topic.remove_allowed_user(walter.username)).to eq(true) + expect(topic.remove_allowed_user(topic.user, walter.username)).to eq(true) topic.reload expect(topic.allowed_users.include?(walter)).to eq(false) end @@ -386,6 +386,11 @@ describe Topic do it 'creates a notification' do expect { topic.invite(topic.user, walter.username) }.to change(Notification, :count) end + + it 'creates a small action post' do + expect { topic.invite(topic.user, walter.username) }.to change(Post, :count) + expect { topic.remove_allowed_user(topic.user, walter.username) }.to change(Post, :count) + end end context 'by email' do @@ -1563,6 +1568,20 @@ describe Topic do expect(Guardian.new(walter).can_see?(group_private_topic)).to be_truthy end end + end + it "Correctly sets #message_archived?" do + topic = Fabricate(:private_message_topic) + user = topic.user + + expect(topic.message_archived?(user)).to eq(false) + + group = Fabricate(:group) + group.add(user) + + TopicAllowedGroup.create!(topic_id: topic.id, group_id: group.id) + GroupArchivedMessage.create!(topic_id: topic.id, group_id: group.id) + + expect(topic.message_archived?(user)).to eq(true) end end diff --git a/spec/models/topic_user_spec.rb b/spec/models/topic_user_spec.rb index c5b4d331a..9f9f5651a 100644 --- a/spec/models/topic_user_spec.rb +++ b/spec/models/topic_user_spec.rb @@ -2,6 +2,38 @@ require 'rails_helper' describe TopicUser do + describe '#notification_levels' do + context "verify enum sequence" do + before do + @notification_levels = TopicUser.notification_levels + end + + it "'muted' should be at 0 position" do + expect(@notification_levels[:muted]).to eq(0) + end + + it "'watching' should be at 3rd position" do + expect(@notification_levels[:watching]).to eq(3) + end + end + end + + describe '#notification_reasons' do + context "verify enum sequence" do + before do + @notification_reasons = TopicUser.notification_reasons + end + + it "'created_topic' should be at 1st position" do + expect(@notification_reasons[:created_topic]).to eq(1) + end + + it "'plugin_changed' should be at 9th position" do + expect(@notification_reasons[:plugin_changed]).to eq(9) + end + end + end + it { is_expected.to belong_to :user } it { is_expected.to belong_to :topic } diff --git a/spec/models/user_email_observer_spec.rb b/spec/models/user_email_observer_spec.rb index 1fb5be42b..9dfe1cdae 100644 --- a/spec/models/user_email_observer_spec.rb +++ b/spec/models/user_email_observer_spec.rb @@ -2,10 +2,13 @@ require 'rails_helper' describe UserEmailObserver do + let(:topic) { Fabricate(:topic) } + let(:post) { Fabricate(:post, topic: topic) } + # something is off with fabricator def create_notification(type, user=nil) user ||= Fabricate(:user) - Notification.create(data: '', user: user, notification_type: type) + Notification.create(data: '', user: user, notification_type: type, topic: topic, post_number: post.post_number) end shared_examples "enqueue" do @@ -32,6 +35,16 @@ describe UserEmailObserver do end + context "small action" do + + it "doesn't enqueue a job" do + Post.any_instance.expects(:post_type).returns(Post.types[:small_action]) + Jobs.expects(:enqueue_in).with(delay, :user_email, has_entry(type: type)).never + UserEmailObserver.send(:new).after_commit(notification) + end + + end + end shared_examples "enqueue_public" do diff --git a/spec/models/user_history_spec.rb b/spec/models/user_history_spec.rb index f19db854d..659432d95 100644 --- a/spec/models/user_history_spec.rb +++ b/spec/models/user_history_spec.rb @@ -2,6 +2,22 @@ require 'rails_helper' describe UserHistory do + describe '#actions' do + context "verify enum sequence" do + before do + @actions = UserHistory.actions + end + + it "'delete_user' should be at 1st position" do + expect(@actions[:delete_user]).to eq(1) + end + + it "'change_site_text' should be at 29th position" do + expect(@actions[:change_site_text]).to eq(29) + end + end + end + describe '#staff_action_records' do context "with some records" do before do diff --git a/spec/models/user_stat_spec.rb b/spec/models/user_stat_spec.rb index dcd5f8242..e977d3b08 100644 --- a/spec/models/user_stat_spec.rb +++ b/spec/models/user_stat_spec.rb @@ -102,6 +102,4 @@ describe UserStat do end end - - end diff --git a/spec/services/staff_action_logger_spec.rb b/spec/services/staff_action_logger_spec.rb index d1c7422c7..676b899dc 100644 --- a/spec/services/staff_action_logger_spec.rb +++ b/spec/services/staff_action_logger_spec.rb @@ -176,8 +176,6 @@ describe StaffActionLogger do describe "log_site_text_change" do it "raises an error when params are invalid" do expect { logger.log_site_text_change(nil, 'new text', 'old text') }.to raise_error(Discourse::InvalidParameters) - expect { logger.log_site_text_change('created', nil, 'old text') }.to raise_error(Discourse::InvalidParameters) - expect { logger.log_site_text_change('created', 'new text', nil) }.to raise_error(Discourse::InvalidParameters) end it "creates a new UserHistory record" do diff --git a/test/javascripts/components/d-editor-test.js.es6 b/test/javascripts/components/d-editor-test.js.es6 index e43fdf686..ecb486f3a 100644 --- a/test/javascripts/components/d-editor-test.js.es6 +++ b/test/javascripts/components/d-editor-test.js.es6 @@ -130,7 +130,7 @@ testCase(`italic button with no selection`, function(assert, textarea) { click(`button.italic`); andThen(() => { const example = I18n.t(`composer.italic_text`); - assert.equal(this.get('value'), `hello world.*${example}*`); + assert.equal(this.get('value'), `hello world._${example}_`); assert.equal(textarea.selectionStart, 13); assert.equal(textarea.selectionEnd, 13 + example.length); @@ -143,7 +143,7 @@ testCase(`italic button with a selection`, function(assert, textarea) { click(`button.italic`); andThen(() => { - assert.equal(this.get('value'), `hello *world*.`); + assert.equal(this.get('value'), `hello _world_.`); assert.equal(textarea.selectionStart, 7); assert.equal(textarea.selectionEnd, 12); }); @@ -166,7 +166,7 @@ testCase(`italic with a multiline selection`, function (assert, textarea) { click(`button.italic`); andThen(() => { - assert.equal(this.get('value'), `*hello*\n\n*world*\n\ntest.`); + assert.equal(this.get('value'), `_hello_\n\n_world_\n\ntest.`); assert.equal(textarea.selectionStart, 0); assert.equal(textarea.selectionEnd, 16); }); diff --git a/test/javascripts/lib/click-track-test.js.es6 b/test/javascripts/lib/click-track-test.js.es6 index 3b228d59a..403a2c15f 100644 --- a/test/javascripts/lib/click-track-test.js.es6 +++ b/test/javascripts/lib/click-track-test.js.es6 @@ -16,21 +16,22 @@ module("lib:click-track", { windowOpen = sandbox.stub(window, "open").returns(win); sandbox.stub(win, "focus"); - fixture().html([ - '
    ', - ' ', - '
    '].join("\n")); + fixture().html( + ``); } }); @@ -64,6 +65,10 @@ test("does not track clicks on quote buttons", function() { ok(track(generateClickEventOn('.quote-other-topic'))); }); +test("does not track clicks on category badges", () => { + ok(!track(generateClickEventOn('.hashtag'))); +}); + test("removes the href and put it as a data attribute", function() { track(generateClickEventOn('a')); diff --git a/test/javascripts/lib/markdown-test.js.es6 b/test/javascripts/lib/markdown-test.js.es6 index 818b75514..fc1a9261b 100644 --- a/test/javascripts/lib/markdown-test.js.es6 +++ b/test/javascripts/lib/markdown-test.js.es6 @@ -289,6 +289,46 @@ test("Mentions", function() { "it allows mentions within HTML tags"); }); +test("Category hashtags", () => { + var alwaysTrue = { categoryHashtagLookup: (function() { return ["category", "http://test.discourse.org/category-hashtag"]; }) }; + + cookedOptions("Check out #category-hashtag", alwaysTrue, + "

    Check out #category-hashtag

    ", + "it translates category hashtag into links"); + + cooked("Check out #category-hashtag", + "

    Check out #category-hashtag

    ", + "it does not translate category hashtag into links if it is not a valid category hashtag"); + + cookedOptions("[#category-hashtag](http://www.test.com)", alwaysTrue, + "

    #category-hashtag

    ", + "it does not translate category hashtag within links"); + + cooked("```\n# #category-hashtag\n```", + "

    # #category-hashtag

    ", + "it does not translate category hashtags to links in code blocks"); + + cooked("># #category-hashtag\n", + "

    #category-hashtag

    ", + "it handles category hashtags in simple quotes"); + + cooked("# #category-hashtag", + "

    #category-hashtag

    ", + "it works within ATX-style headers"); + + cooked("don't `#category-hashtag`", + "

    don't #category-hashtag

    ", + "it does not mention in an inline code block"); + + cooked("test #hashtag1/#hashtag2", + "

    test #hashtag1/#hashtag2

    ", + "it does not convert category hashtag not bounded by spaces"); + + cooked("#category-hashtag", + "

    #category-hashtag

    ", + "it works between HTML tags"); +}); + test("Heading", function() { cooked("**Bold**\n----------", "

    Bold

    ", "It will bold the heading"); diff --git a/test/javascripts/lib/utilities-test.js.es6 b/test/javascripts/lib/utilities-test.js.es6 index 0a8ebd636..003c40bb1 100644 --- a/test/javascripts/lib/utilities-test.js.es6 +++ b/test/javascripts/lib/utilities-test.js.es6 @@ -158,3 +158,26 @@ test("defaultHomepage", function() { Discourse.SiteSettings.top_menu = "latest|top|hot"; equal(utils.defaultHomepage(), "latest", "default homepage is the first item in the top_menu site setting"); }); + +test("caretRowCol", () => { + var textarea = document.createElement('textarea'); + const content = document.createTextNode("01234\n56789\n012345"); + textarea.appendChild(content); + document.body.appendChild(textarea); + + const assertResult = (setCaretPos, expectedRowNum, expectedColNum) => { + Discourse.Utilities.setCaretPosition(textarea, setCaretPos); + + const result = Discourse.Utilities.caretRowCol(textarea); + equal(result.rowNum, expectedRowNum, "returns the right row of the caret"); + equal(result.colNum, expectedColNum, "returns the right col of the caret"); + }; + + assertResult(0, 1, 0); + assertResult(5, 1, 5); + assertResult(6, 2, 0); + assertResult(11, 2, 5); + assertResult(14, 3, 2); + + document.body.removeChild(textarea); +}); diff --git a/test/javascripts/models/category-test.js.es6 b/test/javascripts/models/category-test.js.es6 index 48735f88e..cb6c9858e 100644 --- a/test/javascripts/models/category-test.js.es6 +++ b/test/javascripts/models/category-test.js.es6 @@ -1,4 +1,5 @@ import createStore from 'helpers/create-store'; +import Category from 'discourse/models/category'; module("model:category"); @@ -50,6 +51,8 @@ test('findBySlug', function() { deepEqual(Discourse.Category.findBySlug('뉴스피드', '熱帶風暴畫眉'), newsFeed, 'we can find a category with CJK slug whose parent slug is also CJK'); deepEqual(Discourse.Category.findBySlug('时间', 'darth'), time, 'we can find a category with CJK slug whose parent slug is english'); deepEqual(Discourse.Category.findBySlug('bah', '熱帶風暴畫眉'), bah, 'we can find a category with english slug whose parent slug is CJK'); + + sandbox.restore(); }); test('findSingleBySlug', function() { @@ -122,3 +125,60 @@ test('postCountStats', function() { result = category5.get('postCountStats'); equal(result.length, 0, "should show nothing"); }); + +test('search with category name', () => { + const store = createStore(), + category1 = store.createRecord('category', { id: 1, name: 'middle term', slug: 'different-slug' }), + category2 = store.createRecord('category', { id: 2, name: 'middle term', slug: 'another-different-slug' }); + + sandbox.stub(Category, "listByActivity").returns([category1, category2]); + + deepEqual(Category.search('term', { limit: 0 }), [], "returns an empty array when limit is 0"); + deepEqual(Category.search(''), [category1, category2], "orders by activity if no term is matched"); + deepEqual(Category.search('term'), [category1, category2], "orders by activity"); + + category2.set('name', 'TeRm start'); + deepEqual(Category.search('tErM'), [category2, category1], "ignores case of category name and search term"); + + category2.set('name', 'term start'); + deepEqual(Category.search('term'), [category2, category1], "orders matching begin with and then contains"); + + sandbox.restore(); + + const child_category1 = store.createRecord('category', { id: 3, name: 'term start', parent_category_id: category1.get('id') }), + read_restricted_category = store.createRecord('category', { id: 4, name: 'some term', read_restricted: true }); + + sandbox.stub(Category, "listByActivity").returns([read_restricted_category, category1, child_category1, category2]); + + deepEqual(Category.search(''), + [category1, category2, read_restricted_category], + "prioritize non read_restricted and does not include child categories when term is blank"); + + deepEqual(Category.search('', { limit: 3 }), + [category1, category2, read_restricted_category], + "prioritize non read_restricted and does not include child categories categories when term is blank with limit"); + + deepEqual(Category.search('term'), + [child_category1, category2, category1, read_restricted_category], + "prioritize non read_restricted"); + + deepEqual(Category.search('term', { limit: 3 }), + [child_category1, category2, read_restricted_category], + "prioritize non read_restricted with limit"); + + sandbox.restore(); +}); + +test('search with category slug', () => { + const store = createStore(), + category1 = store.createRecord('category', { id: 1, name: 'middle term', slug: 'different-slug' }), + category2 = store.createRecord('category', { id: 2, name: 'middle term', slug: 'another-different-slug' }); + + sandbox.stub(Category, "listByActivity").returns([category1, category2]); + + deepEqual(Category.search('different-slug'), [category1, category2], "returns the right categories"); + deepEqual(Category.search('another-different'), [category2], "returns the right categories"); + + category2.set('slug', 'ANOTher-DIFfereNT'); + deepEqual(Category.search('anOtHer-dIfFeREnt'), [category2], "ignores case of category slug and search term"); +}); diff --git a/vendor/assets/javascripts/handlebars.js b/vendor/assets/javascripts/handlebars.js index 04bb23566..289ae458a 100644 --- a/vendor/assets/javascripts/handlebars.js +++ b/vendor/assets/javascripts/handlebars.js @@ -1,8 +1,8 @@ /*! - handlebars v2.0.0 + handlebars v4.0.5 -Copyright (C) 2011-2014 by Yehuda Katz +Copyright (C) 2011-2015 by Yehuda Katz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -24,3061 +24,4585 @@ THE SOFTWARE. @license */ -/* exported Handlebars */ -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - define([], factory); - } else if (typeof exports === 'object') { - module.exports = factory(); - } else { - root.Handlebars = root.Handlebars || factory(); - } -}(this, function () { -// handlebars/safe-string.js -var __module4__ = (function() { - "use strict"; - var __exports__; - // Build out our basic SafeString type - function SafeString(string) { - this.string = string; - } - - SafeString.prototype.toString = function() { - return "" + this.string; - }; - - __exports__ = SafeString; - return __exports__; -})(); - -// handlebars/utils.js -var __module3__ = (function(__dependency1__) { - "use strict"; - var __exports__ = {}; - /*jshint -W004 */ - var SafeString = __dependency1__; - - var escape = { - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'", - '`': '`', - '\n' : '\\n', // NewLine - '\r' : '\\n', // Return - '\b' : '\\b', // Backspace - '\f' : '\\f', // Form fee - '\t' : '\\t', // Tab - '\v' : '\\v' // Vertical Tab - }; - var badChars = /[&<>"'`\b\f\n\r\t\v]/g; - var possible = /[&<>"'`\b\f\n\r\t\v]/; - - function escapeChar(chr) { - return escape[chr]; - } - - function extend(obj /* , ...source */) { - for (var i = 1; i < arguments.length; i++) { - for (var key in arguments[i]) { - if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { - obj[key] = arguments[i][key]; - } - } - } - - return obj; - } - - __exports__.extend = extend;var toString = Object.prototype.toString; - __exports__.toString = toString; - // Sourced from lodash - // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt - var isFunction = function(value) { - return typeof value === 'function'; - }; - // fallback for older versions of Chrome and Safari - /* istanbul ignore next */ - if (isFunction(/x/)) { - isFunction = function(value) { - return typeof value === 'function' && toString.call(value) === '[object Function]'; - }; - } - var isFunction; - __exports__.isFunction = isFunction; - /* istanbul ignore next */ - var isArray = Array.isArray || function(value) { - return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false; - }; - __exports__.isArray = isArray; - - function escapeExpression(string) { - // don't escape SafeStrings, since they're already safe - if (string instanceof SafeString) { - return string.toString(); - } else if (string == null) { - return ""; - } else if (!string) { - return string + ''; - } - - // Force a string conversion as this will be done by the append regardless and - // the regex test will do this transparently behind the scenes, causing issues if - // an object's to string has escaped characters in it. - string = "" + string; - - if(!possible.test(string)) { return string; } - return string.replace(badChars, escapeChar); - } - - __exports__.escapeExpression = escapeExpression;function isEmpty(value) { - if (!value && value !== 0) { - return true; - } else if (isArray(value) && value.length === 0) { - return true; - } else { - return false; - } - } - - __exports__.isEmpty = isEmpty;function appendContextPath(contextPath, id) { - return (contextPath ? contextPath + '.' : '') + id; - } - - __exports__.appendContextPath = appendContextPath; - return __exports__; -})(__module4__); - -// handlebars/exception.js -var __module5__ = (function() { - "use strict"; - var __exports__; - - var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; - - function Exception(message, node) { - var line; - if (node && node.firstLine) { - line = node.firstLine; - - message += ' - ' + line + ':' + node.firstColumn; - } - - var tmp = Error.prototype.constructor.call(this, message); - - // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. - for (var idx = 0; idx < errorProps.length; idx++) { - this[errorProps[idx]] = tmp[errorProps[idx]]; - } - - if (line) { - this.lineNumber = line; - this.column = node.firstColumn; - } - } - - Exception.prototype = new Error(); - - __exports__ = Exception; - return __exports__; -})(); - -// handlebars/base.js -var __module2__ = (function(__dependency1__, __dependency2__) { - "use strict"; - var __exports__ = {}; - var Utils = __dependency1__; - var Exception = __dependency2__; - - var VERSION = "2.0.0"; - __exports__.VERSION = VERSION;var COMPILER_REVISION = 6; - __exports__.COMPILER_REVISION = COMPILER_REVISION; - var REVISION_CHANGES = { - 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it - 2: '== 1.0.0-rc.3', - 3: '== 1.0.0-rc.4', - 4: '== 1.x.x', - 5: '== 2.0.0-alpha.x', - 6: '>= 2.0.0-beta.1' - }; - __exports__.REVISION_CHANGES = REVISION_CHANGES; - var isArray = Utils.isArray, - isFunction = Utils.isFunction, - toString = Utils.toString, - objectType = '[object Object]'; - - function HandlebarsEnvironment(helpers, partials) { - this.helpers = helpers || {}; - this.partials = partials || {}; - - registerDefaultHelpers(this); - } - - __exports__.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = { - constructor: HandlebarsEnvironment, - - logger: logger, - log: log, - - registerHelper: function(name, fn) { - if (toString.call(name) === objectType) { - if (fn) { throw new Exception('Arg not supported with multiple helpers'); } - Utils.extend(this.helpers, name); - } else { - this.helpers[name] = fn; - } - }, - unregisterHelper: function(name) { - delete this.helpers[name]; - }, - - registerPartial: function(name, partial) { - if (toString.call(name) === objectType) { - Utils.extend(this.partials, name); - } else { - this.partials[name] = partial; - } - }, - unregisterPartial: function(name) { - delete this.partials[name]; - } - }; - - function registerDefaultHelpers(instance) { - instance.registerHelper('helperMissing', function(/* [args, ]options */) { - if(arguments.length === 1) { - // A missing field in a {{foo}} constuct. - return undefined; - } else { - // Someone is actually trying to call something, blow up. - throw new Exception("Missing helper: '" + arguments[arguments.length-1].name + "'"); - } - }); - - instance.registerHelper('blockHelperMissing', function(context, options) { - var inverse = options.inverse, - fn = options.fn; - - if(context === true) { - return fn(this); - } else if(context === false || context == null) { - return inverse(this); - } else if (isArray(context)) { - if(context.length > 0) { - if (options.ids) { - options.ids = [options.name]; - } - - return instance.helpers.each(context, options); - } else { - return inverse(this); - } - } else { - if (options.data && options.ids) { - var data = createFrame(options.data); - data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name); - options = {data: data}; - } - - return fn(context, options); - } - }); - - instance.registerHelper('each', function(context, options) { - if (!options) { - throw new Exception('Must pass iterator to #each'); - } - - var fn = options.fn, inverse = options.inverse; - var i = 0, ret = "", data; - - var contextPath; - if (options.data && options.ids) { - contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.'; - } - - if (isFunction(context)) { context = context.call(this); } - - if (options.data) { - data = createFrame(options.data); - } - - if(context && typeof context === 'object') { - if (isArray(context)) { - for(var j = context.length; i 0) { - throw new Exception("Invalid path: " + original, this); - } else if (part === "..") { - depth++; - depthString += '../'; - } else { - this.isScoped = true; - } - } else { - dig.push(part); - } - } - - this.original = original; - this.parts = dig; - this.string = dig.join('.'); - this.depth = depth; - this.idName = depthString + this.string; - - // an ID is simple if it only has one part, and that part is not - // `..` or `this`. - this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; - - this.stringModeValue = this.string; - }, - - PartialNameNode: function(name, locInfo) { - LocationInfo.call(this, locInfo); - this.type = "PARTIAL_NAME"; - this.name = name.original; - }, - - DataNode: function(id, locInfo) { - LocationInfo.call(this, locInfo); - this.type = "DATA"; - this.id = id; - this.stringModeValue = id.stringModeValue; - this.idName = '@' + id.stringModeValue; - }, - - StringNode: function(string, locInfo) { - LocationInfo.call(this, locInfo); - this.type = "STRING"; - this.original = - this.string = - this.stringModeValue = string; - }, - - NumberNode: function(number, locInfo) { - LocationInfo.call(this, locInfo); - this.type = "NUMBER"; - this.original = - this.number = number; - this.stringModeValue = Number(number); - }, - - BooleanNode: function(bool, locInfo) { - LocationInfo.call(this, locInfo); - this.type = "BOOLEAN"; - this.bool = bool; - this.stringModeValue = bool === "true"; - }, - - CommentNode: function(comment, locInfo) { - LocationInfo.call(this, locInfo); - this.type = "comment"; - this.comment = comment; - - this.strip = { - inlineStandalone: true - }; - } - }; - - - // Must be exported as an object rather than the root of the module as the jison lexer - // most modify the object to operate properly. - __exports__ = AST; - return __exports__; -})(__module5__); - -// handlebars/compiler/parser.js -var __module9__ = (function() { - "use strict"; - var __exports__; - /* jshint ignore:start */ - /* istanbul ignore next */ - /* Jison generated parser */ - var handlebars = (function(){ - var parser = {trace: function trace() { }, - yy: {}, - symbols_: {"error":2,"root":3,"program":4,"EOF":5,"program_repetition0":6,"statement":7,"mustache":8,"block":9,"rawBlock":10,"partial":11,"CONTENT":12,"COMMENT":13,"openRawBlock":14,"END_RAW_BLOCK":15,"OPEN_RAW_BLOCK":16,"sexpr":17,"CLOSE_RAW_BLOCK":18,"openBlock":19,"block_option0":20,"closeBlock":21,"openInverse":22,"block_option1":23,"OPEN_BLOCK":24,"CLOSE":25,"OPEN_INVERSE":26,"inverseAndProgram":27,"INVERSE":28,"OPEN_ENDBLOCK":29,"path":30,"OPEN":31,"OPEN_UNESCAPED":32,"CLOSE_UNESCAPED":33,"OPEN_PARTIAL":34,"partialName":35,"param":36,"partial_option0":37,"partial_option1":38,"sexpr_repetition0":39,"sexpr_option0":40,"dataName":41,"STRING":42,"NUMBER":43,"BOOLEAN":44,"OPEN_SEXPR":45,"CLOSE_SEXPR":46,"hash":47,"hash_repetition_plus0":48,"hashSegment":49,"ID":50,"EQUALS":51,"DATA":52,"pathSegments":53,"SEP":54,"$accept":0,"$end":1}, - terminals_: {2:"error",5:"EOF",12:"CONTENT",13:"COMMENT",15:"END_RAW_BLOCK",16:"OPEN_RAW_BLOCK",18:"CLOSE_RAW_BLOCK",24:"OPEN_BLOCK",25:"CLOSE",26:"OPEN_INVERSE",28:"INVERSE",29:"OPEN_ENDBLOCK",31:"OPEN",32:"OPEN_UNESCAPED",33:"CLOSE_UNESCAPED",34:"OPEN_PARTIAL",42:"STRING",43:"NUMBER",44:"BOOLEAN",45:"OPEN_SEXPR",46:"CLOSE_SEXPR",50:"ID",51:"EQUALS",52:"DATA",54:"SEP"}, - productions_: [0,[3,2],[4,1],[7,1],[7,1],[7,1],[7,1],[7,1],[7,1],[10,3],[14,3],[9,4],[9,4],[19,3],[22,3],[27,2],[21,3],[8,3],[8,3],[11,5],[11,4],[17,3],[17,1],[36,1],[36,1],[36,1],[36,1],[36,1],[36,3],[47,1],[49,3],[35,1],[35,1],[35,1],[41,2],[30,1],[53,3],[53,1],[6,0],[6,2],[20,0],[20,1],[23,0],[23,1],[37,0],[37,1],[38,0],[38,1],[39,0],[39,2],[40,0],[40,1],[48,1],[48,2]], - performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { - - var $0 = $$.length - 1; - switch (yystate) { - case 1: yy.prepareProgram($$[$0-1].statements, true); return $$[$0-1]; - break; - case 2:this.$ = new yy.ProgramNode(yy.prepareProgram($$[$0]), {}, this._$); - break; - case 3:this.$ = $$[$0]; - break; - case 4:this.$ = $$[$0]; - break; - case 5:this.$ = $$[$0]; - break; - case 6:this.$ = $$[$0]; - break; - case 7:this.$ = new yy.ContentNode($$[$0], this._$); - break; - case 8:this.$ = new yy.CommentNode($$[$0], this._$); - break; - case 9:this.$ = new yy.RawBlockNode($$[$0-2], $$[$0-1], $$[$0], this._$); - break; - case 10:this.$ = new yy.MustacheNode($$[$0-1], null, '', '', this._$); - break; - case 11:this.$ = yy.prepareBlock($$[$0-3], $$[$0-2], $$[$0-1], $$[$0], false, this._$); - break; - case 12:this.$ = yy.prepareBlock($$[$0-3], $$[$0-2], $$[$0-1], $$[$0], true, this._$); - break; - case 13:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], yy.stripFlags($$[$0-2], $$[$0]), this._$); - break; - case 14:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], yy.stripFlags($$[$0-2], $$[$0]), this._$); - break; - case 15:this.$ = { strip: yy.stripFlags($$[$0-1], $$[$0-1]), program: $$[$0] }; - break; - case 16:this.$ = {path: $$[$0-1], strip: yy.stripFlags($$[$0-2], $$[$0])}; - break; - case 17:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], yy.stripFlags($$[$0-2], $$[$0]), this._$); - break; - case 18:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], yy.stripFlags($$[$0-2], $$[$0]), this._$); - break; - case 19:this.$ = new yy.PartialNode($$[$0-3], $$[$0-2], $$[$0-1], yy.stripFlags($$[$0-4], $$[$0]), this._$); - break; - case 20:this.$ = new yy.PartialNode($$[$0-2], undefined, $$[$0-1], yy.stripFlags($$[$0-3], $$[$0]), this._$); - break; - case 21:this.$ = new yy.SexprNode([$$[$0-2]].concat($$[$0-1]), $$[$0], this._$); - break; - case 22:this.$ = new yy.SexprNode([$$[$0]], null, this._$); - break; - case 23:this.$ = $$[$0]; - break; - case 24:this.$ = new yy.StringNode($$[$0], this._$); - break; - case 25:this.$ = new yy.NumberNode($$[$0], this._$); - break; - case 26:this.$ = new yy.BooleanNode($$[$0], this._$); - break; - case 27:this.$ = $$[$0]; - break; - case 28:$$[$0-1].isHelper = true; this.$ = $$[$0-1]; - break; - case 29:this.$ = new yy.HashNode($$[$0], this._$); - break; - case 30:this.$ = [$$[$0-2], $$[$0]]; - break; - case 31:this.$ = new yy.PartialNameNode($$[$0], this._$); - break; - case 32:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0], this._$), this._$); - break; - case 33:this.$ = new yy.PartialNameNode(new yy.NumberNode($$[$0], this._$)); - break; - case 34:this.$ = new yy.DataNode($$[$0], this._$); - break; - case 35:this.$ = new yy.IdNode($$[$0], this._$); - break; - case 36: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2]; - break; - case 37:this.$ = [{part: $$[$0]}]; - break; - case 38:this.$ = []; - break; - case 39:$$[$0-1].push($$[$0]); - break; - case 48:this.$ = []; - break; - case 49:$$[$0-1].push($$[$0]); - break; - case 52:this.$ = [$$[$0]]; - break; - case 53:$$[$0-1].push($$[$0]); - break; - } - }, - table: [{3:1,4:2,5:[2,38],6:3,12:[2,38],13:[2,38],16:[2,38],24:[2,38],26:[2,38],31:[2,38],32:[2,38],34:[2,38]},{1:[3]},{5:[1,4]},{5:[2,2],7:5,8:6,9:7,10:8,11:9,12:[1,10],13:[1,11],14:16,16:[1,20],19:14,22:15,24:[1,18],26:[1,19],28:[2,2],29:[2,2],31:[1,12],32:[1,13],34:[1,17]},{1:[2,1]},{5:[2,39],12:[2,39],13:[2,39],16:[2,39],24:[2,39],26:[2,39],28:[2,39],29:[2,39],31:[2,39],32:[2,39],34:[2,39]},{5:[2,3],12:[2,3],13:[2,3],16:[2,3],24:[2,3],26:[2,3],28:[2,3],29:[2,3],31:[2,3],32:[2,3],34:[2,3]},{5:[2,4],12:[2,4],13:[2,4],16:[2,4],24:[2,4],26:[2,4],28:[2,4],29:[2,4],31:[2,4],32:[2,4],34:[2,4]},{5:[2,5],12:[2,5],13:[2,5],16:[2,5],24:[2,5],26:[2,5],28:[2,5],29:[2,5],31:[2,5],32:[2,5],34:[2,5]},{5:[2,6],12:[2,6],13:[2,6],16:[2,6],24:[2,6],26:[2,6],28:[2,6],29:[2,6],31:[2,6],32:[2,6],34:[2,6]},{5:[2,7],12:[2,7],13:[2,7],16:[2,7],24:[2,7],26:[2,7],28:[2,7],29:[2,7],31:[2,7],32:[2,7],34:[2,7]},{5:[2,8],12:[2,8],13:[2,8],16:[2,8],24:[2,8],26:[2,8],28:[2,8],29:[2,8],31:[2,8],32:[2,8],34:[2,8]},{17:21,30:22,41:23,50:[1,26],52:[1,25],53:24},{17:27,30:22,41:23,50:[1,26],52:[1,25],53:24},{4:28,6:3,12:[2,38],13:[2,38],16:[2,38],24:[2,38],26:[2,38],28:[2,38],29:[2,38],31:[2,38],32:[2,38],34:[2,38]},{4:29,6:3,12:[2,38],13:[2,38],16:[2,38],24:[2,38],26:[2,38],28:[2,38],29:[2,38],31:[2,38],32:[2,38],34:[2,38]},{12:[1,30]},{30:32,35:31,42:[1,33],43:[1,34],50:[1,26],53:24},{17:35,30:22,41:23,50:[1,26],52:[1,25],53:24},{17:36,30:22,41:23,50:[1,26],52:[1,25],53:24},{17:37,30:22,41:23,50:[1,26],52:[1,25],53:24},{25:[1,38]},{18:[2,48],25:[2,48],33:[2,48],39:39,42:[2,48],43:[2,48],44:[2,48],45:[2,48],46:[2,48],50:[2,48],52:[2,48]},{18:[2,22],25:[2,22],33:[2,22],46:[2,22]},{18:[2,35],25:[2,35],33:[2,35],42:[2,35],43:[2,35],44:[2,35],45:[2,35],46:[2,35],50:[2,35],52:[2,35],54:[1,40]},{30:41,50:[1,26],53:24},{18:[2,37],25:[2,37],33:[2,37],42:[2,37],43:[2,37],44:[2,37],45:[2,37],46:[2,37],50:[2,37],52:[2,37],54:[2,37]},{33:[1,42]},{20:43,27:44,28:[1,45],29:[2,40]},{23:46,27:47,28:[1,45],29:[2,42]},{15:[1,48]},{25:[2,46],30:51,36:49,38:50,41:55,42:[1,52],43:[1,53],44:[1,54],45:[1,56],47:57,48:58,49:60,50:[1,59],52:[1,25],53:24},{25:[2,31],42:[2,31],43:[2,31],44:[2,31],45:[2,31],50:[2,31],52:[2,31]},{25:[2,32],42:[2,32],43:[2,32],44:[2,32],45:[2,32],50:[2,32],52:[2,32]},{25:[2,33],42:[2,33],43:[2,33],44:[2,33],45:[2,33],50:[2,33],52:[2,33]},{25:[1,61]},{25:[1,62]},{18:[1,63]},{5:[2,17],12:[2,17],13:[2,17],16:[2,17],24:[2,17],26:[2,17],28:[2,17],29:[2,17],31:[2,17],32:[2,17],34:[2,17]},{18:[2,50],25:[2,50],30:51,33:[2,50],36:65,40:64,41:55,42:[1,52],43:[1,53],44:[1,54],45:[1,56],46:[2,50],47:66,48:58,49:60,50:[1,59],52:[1,25],53:24},{50:[1,67]},{18:[2,34],25:[2,34],33:[2,34],42:[2,34],43:[2,34],44:[2,34],45:[2,34],46:[2,34],50:[2,34],52:[2,34]},{5:[2,18],12:[2,18],13:[2,18],16:[2,18],24:[2,18],26:[2,18],28:[2,18],29:[2,18],31:[2,18],32:[2,18],34:[2,18]},{21:68,29:[1,69]},{29:[2,41]},{4:70,6:3,12:[2,38],13:[2,38],16:[2,38],24:[2,38],26:[2,38],29:[2,38],31:[2,38],32:[2,38],34:[2,38]},{21:71,29:[1,69]},{29:[2,43]},{5:[2,9],12:[2,9],13:[2,9],16:[2,9],24:[2,9],26:[2,9],28:[2,9],29:[2,9],31:[2,9],32:[2,9],34:[2,9]},{25:[2,44],37:72,47:73,48:58,49:60,50:[1,74]},{25:[1,75]},{18:[2,23],25:[2,23],33:[2,23],42:[2,23],43:[2,23],44:[2,23],45:[2,23],46:[2,23],50:[2,23],52:[2,23]},{18:[2,24],25:[2,24],33:[2,24],42:[2,24],43:[2,24],44:[2,24],45:[2,24],46:[2,24],50:[2,24],52:[2,24]},{18:[2,25],25:[2,25],33:[2,25],42:[2,25],43:[2,25],44:[2,25],45:[2,25],46:[2,25],50:[2,25],52:[2,25]},{18:[2,26],25:[2,26],33:[2,26],42:[2,26],43:[2,26],44:[2,26],45:[2,26],46:[2,26],50:[2,26],52:[2,26]},{18:[2,27],25:[2,27],33:[2,27],42:[2,27],43:[2,27],44:[2,27],45:[2,27],46:[2,27],50:[2,27],52:[2,27]},{17:76,30:22,41:23,50:[1,26],52:[1,25],53:24},{25:[2,47]},{18:[2,29],25:[2,29],33:[2,29],46:[2,29],49:77,50:[1,74]},{18:[2,37],25:[2,37],33:[2,37],42:[2,37],43:[2,37],44:[2,37],45:[2,37],46:[2,37],50:[2,37],51:[1,78],52:[2,37],54:[2,37]},{18:[2,52],25:[2,52],33:[2,52],46:[2,52],50:[2,52]},{12:[2,13],13:[2,13],16:[2,13],24:[2,13],26:[2,13],28:[2,13],29:[2,13],31:[2,13],32:[2,13],34:[2,13]},{12:[2,14],13:[2,14],16:[2,14],24:[2,14],26:[2,14],28:[2,14],29:[2,14],31:[2,14],32:[2,14],34:[2,14]},{12:[2,10]},{18:[2,21],25:[2,21],33:[2,21],46:[2,21]},{18:[2,49],25:[2,49],33:[2,49],42:[2,49],43:[2,49],44:[2,49],45:[2,49],46:[2,49],50:[2,49],52:[2,49]},{18:[2,51],25:[2,51],33:[2,51],46:[2,51]},{18:[2,36],25:[2,36],33:[2,36],42:[2,36],43:[2,36],44:[2,36],45:[2,36],46:[2,36],50:[2,36],52:[2,36],54:[2,36]},{5:[2,11],12:[2,11],13:[2,11],16:[2,11],24:[2,11],26:[2,11],28:[2,11],29:[2,11],31:[2,11],32:[2,11],34:[2,11]},{30:79,50:[1,26],53:24},{29:[2,15]},{5:[2,12],12:[2,12],13:[2,12],16:[2,12],24:[2,12],26:[2,12],28:[2,12],29:[2,12],31:[2,12],32:[2,12],34:[2,12]},{25:[1,80]},{25:[2,45]},{51:[1,78]},{5:[2,20],12:[2,20],13:[2,20],16:[2,20],24:[2,20],26:[2,20],28:[2,20],29:[2,20],31:[2,20],32:[2,20],34:[2,20]},{46:[1,81]},{18:[2,53],25:[2,53],33:[2,53],46:[2,53],50:[2,53]},{30:51,36:82,41:55,42:[1,52],43:[1,53],44:[1,54],45:[1,56],50:[1,26],52:[1,25],53:24},{25:[1,83]},{5:[2,19],12:[2,19],13:[2,19],16:[2,19],24:[2,19],26:[2,19],28:[2,19],29:[2,19],31:[2,19],32:[2,19],34:[2,19]},{18:[2,28],25:[2,28],33:[2,28],42:[2,28],43:[2,28],44:[2,28],45:[2,28],46:[2,28],50:[2,28],52:[2,28]},{18:[2,30],25:[2,30],33:[2,30],46:[2,30],50:[2,30]},{5:[2,16],12:[2,16],13:[2,16],16:[2,16],24:[2,16],26:[2,16],28:[2,16],29:[2,16],31:[2,16],32:[2,16],34:[2,16]}], - defaultActions: {4:[2,1],44:[2,41],47:[2,43],57:[2,47],63:[2,10],70:[2,15],73:[2,45]}, - parseError: function parseError(str, hash) { - throw new Error(str); - }, - parse: function parse(input) { - var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1; - this.lexer.setInput(input); - this.lexer.yy = this.yy; - this.yy.lexer = this.lexer; - this.yy.parser = this; - if (typeof this.lexer.yylloc == "undefined") - this.lexer.yylloc = {}; - var yyloc = this.lexer.yylloc; - lstack.push(yyloc); - var ranges = this.lexer.options && this.lexer.options.ranges; - if (typeof this.yy.parseError === "function") - this.parseError = this.yy.parseError; - function popStack(n) { - stack.length = stack.length - 2 * n; - vstack.length = vstack.length - n; - lstack.length = lstack.length - n; - } - function lex() { - var token; - token = self.lexer.lex() || 1; - if (typeof token !== "number") { - token = self.symbols_[token] || token; - } - return token; - } - var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected; - while (true) { - state = stack[stack.length - 1]; - if (this.defaultActions[state]) { - action = this.defaultActions[state]; - } else { - if (symbol === null || typeof symbol == "undefined") { - symbol = lex(); - } - action = table[state] && table[state][symbol]; - } - if (typeof action === "undefined" || !action.length || !action[0]) { - var errStr = ""; - if (!recovering) { - expected = []; - for (p in table[state]) - if (this.terminals_[p] && p > 2) { - expected.push("'" + this.terminals_[p] + "'"); - } - if (this.lexer.showPosition) { - errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; - } else { - errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); - } - this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); - } - } - if (action[0] instanceof Array && action.length > 1) { - throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); - } - switch (action[0]) { - case 1: - stack.push(symbol); - vstack.push(this.lexer.yytext); - lstack.push(this.lexer.yylloc); - stack.push(action[1]); - symbol = null; - if (!preErrorSymbol) { - yyleng = this.lexer.yyleng; - yytext = this.lexer.yytext; - yylineno = this.lexer.yylineno; - yyloc = this.lexer.yylloc; - if (recovering > 0) - recovering--; - } else { - symbol = preErrorSymbol; - preErrorSymbol = null; - } - break; - case 2: - len = this.productions_[action[1]][1]; - yyval.$ = vstack[vstack.length - len]; - yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; - if (ranges) { - yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; - } - r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); - if (typeof r !== "undefined") { - return r; - } - if (len) { - stack = stack.slice(0, -1 * len * 2); - vstack = vstack.slice(0, -1 * len); - lstack = lstack.slice(0, -1 * len); - } - stack.push(this.productions_[action[1]][0]); - vstack.push(yyval.$); - lstack.push(yyval._$); - newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; - stack.push(newState); - break; - case 3: - return true; - } - } - return true; - } - }; - /* Jison generated lexer */ - var lexer = (function(){ - var lexer = ({EOF:1, - parseError:function parseError(str, hash) { - if (this.yy.parser) { - this.yy.parser.parseError(str, hash); - } else { - throw new Error(str); - } - }, - setInput:function (input) { - this._input = input; - this._more = this._less = this.done = false; - this.yylineno = this.yyleng = 0; - this.yytext = this.matched = this.match = ''; - this.conditionStack = ['INITIAL']; - this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; - if (this.options.ranges) this.yylloc.range = [0,0]; - this.offset = 0; - return this; - }, - input:function () { - var ch = this._input[0]; - this.yytext += ch; - this.yyleng++; - this.offset++; - this.match += ch; - this.matched += ch; - var lines = ch.match(/(?:\r\n?|\n).*/g); - if (lines) { - this.yylineno++; - this.yylloc.last_line++; - } else { - this.yylloc.last_column++; - } - if (this.options.ranges) this.yylloc.range[1]++; - - this._input = this._input.slice(1); - return ch; - }, - unput:function (ch) { - var len = ch.length; - var lines = ch.split(/(?:\r\n?|\n)/g); - - this._input = ch + this._input; - this.yytext = this.yytext.substr(0, this.yytext.length-len-1); - //this.yyleng -= len; - this.offset -= len; - var oldLines = this.match.split(/(?:\r\n?|\n)/g); - this.match = this.match.substr(0, this.match.length-1); - this.matched = this.matched.substr(0, this.matched.length-1); - - if (lines.length-1) this.yylineno -= lines.length-1; - var r = this.yylloc.range; - - this.yylloc = {first_line: this.yylloc.first_line, - last_line: this.yylineno+1, - first_column: this.yylloc.first_column, - last_column: lines ? - (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: - this.yylloc.first_column - len - }; - - if (this.options.ranges) { - this.yylloc.range = [r[0], r[0] + this.yyleng - len]; - } - return this; - }, - more:function () { - this._more = true; - return this; - }, - less:function (n) { - this.unput(this.match.slice(n)); - }, - pastInput:function () { - var past = this.matched.substr(0, this.matched.length - this.match.length); - return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); - }, - upcomingInput:function () { - var next = this.match; - if (next.length < 20) { - next += this._input.substr(0, 20-next.length); - } - return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); - }, - showPosition:function () { - var pre = this.pastInput(); - var c = new Array(pre.length + 1).join("-"); - return pre + this.upcomingInput() + "\n" + c+"^"; - }, - next:function () { - if (this.done) { - return this.EOF; - } - if (!this._input) this.done = true; - - var token, - match, - tempMatch, - index, - col, - lines; - if (!this._more) { - this.yytext = ''; - this.match = ''; - } - var rules = this._currentRules(); - for (var i=0;i < rules.length; i++) { - tempMatch = this._input.match(this.rules[rules[i]]); - if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { - match = tempMatch; - index = i; - if (!this.options.flex) break; - } - } - if (match) { - lines = match[0].match(/(?:\r\n?|\n).*/g); - if (lines) this.yylineno += lines.length; - this.yylloc = {first_line: this.yylloc.last_line, - last_line: this.yylineno+1, - first_column: this.yylloc.last_column, - last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; - this.yytext += match[0]; - this.match += match[0]; - this.matches = match; - this.yyleng = this.yytext.length; - if (this.options.ranges) { - this.yylloc.range = [this.offset, this.offset += this.yyleng]; - } - this._more = false; - this._input = this._input.slice(match[0].length); - this.matched += match[0]; - token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); - if (this.done && this._input) this.done = false; - if (token) return token; - else return; - } - if (this._input === "") { - return this.EOF; - } else { - return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), - {text: "", token: null, line: this.yylineno}); - } - }, - lex:function lex() { - var r = this.next(); - if (typeof r !== 'undefined') { - return r; - } else { - return this.lex(); - } - }, - begin:function begin(condition) { - this.conditionStack.push(condition); - }, - popState:function popState() { - return this.conditionStack.pop(); - }, - _currentRules:function _currentRules() { - return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; - }, - topState:function () { - return this.conditionStack[this.conditionStack.length-2]; - }, - pushState:function begin(condition) { - this.begin(condition); - }}); - lexer.options = {}; - lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { - - - function strip(start, end) { - return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng-end); - } - - - var YYSTATE=YY_START - switch($avoiding_name_collisions) { - case 0: - if(yy_.yytext.slice(-2) === "\\\\") { - strip(0,1); - this.begin("mu"); - } else if(yy_.yytext.slice(-1) === "\\") { - strip(0,1); - this.begin("emu"); - } else { - this.begin("mu"); - } - if(yy_.yytext) return 12; - - break; - case 1:return 12; - break; - case 2: - this.popState(); - return 12; - - break; - case 3: - yy_.yytext = yy_.yytext.substr(5, yy_.yyleng-9); - this.popState(); - return 15; - - break; - case 4: return 12; - break; - case 5:strip(0,4); this.popState(); return 13; - break; - case 6:return 45; - break; - case 7:return 46; - break; - case 8: return 16; - break; - case 9: - this.popState(); - this.begin('raw'); - return 18; - - break; - case 10:return 34; - break; - case 11:return 24; - break; - case 12:return 29; - break; - case 13:this.popState(); return 28; - break; - case 14:this.popState(); return 28; - break; - case 15:return 26; - break; - case 16:return 26; - break; - case 17:return 32; - break; - case 18:return 31; - break; - case 19:this.popState(); this.begin('com'); - break; - case 20:strip(3,5); this.popState(); return 13; - break; - case 21:return 31; - break; - case 22:return 51; - break; - case 23:return 50; - break; - case 24:return 50; - break; - case 25:return 54; - break; - case 26:// ignore whitespace - break; - case 27:this.popState(); return 33; - break; - case 28:this.popState(); return 25; - break; - case 29:yy_.yytext = strip(1,2).replace(/\\"/g,'"'); return 42; - break; - case 30:yy_.yytext = strip(1,2).replace(/\\'/g,"'"); return 42; - break; - case 31:return 52; - break; - case 32:return 44; - break; - case 33:return 44; - break; - case 34:return 43; - break; - case 35:return 50; - break; - case 36:yy_.yytext = strip(1,2); return 50; - break; - case 37:return 'INVALID'; - break; - case 38:return 5; - break; - } - }; - lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/,/^(?:\{\{\{\{\/[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.])\}\}\}\})/,/^(?:[^\x00]*?(?=(\{\{\{\{\/)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\()/,/^(?:\))/,/^(?:\{\{\{\{)/,/^(?:\}\}\}\})/,/^(?:\{\{(~)?>)/,/^(?:\{\{(~)?#)/,/^(?:\{\{(~)?\/)/,/^(?:\{\{(~)?\^\s*(~)?\}\})/,/^(?:\{\{(~)?\s*else\s*(~)?\}\})/,/^(?:\{\{(~)?\^)/,/^(?:\{\{(~)?\s*else\b)/,/^(?:\{\{(~)?\{)/,/^(?:\{\{(~)?&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{(~)?)/,/^(?:=)/,/^(?:\.\.)/,/^(?:\.(?=([=~}\s\/.)])))/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}(~)?\}\})/,/^(?:(~)?\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=([~}\s)])))/,/^(?:false(?=([~}\s)])))/,/^(?:-?[0-9]+(?:\.[0-9]+)?(?=([~}\s)])))/,/^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)]))))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/]; - lexer.conditions = {"mu":{"rules":[6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[5],"inclusive":false},"raw":{"rules":[3,4],"inclusive":false},"INITIAL":{"rules":[0,1,38],"inclusive":true}}; - return lexer;})() - parser.lexer = lexer; - function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; - return new Parser; - })();__exports__ = handlebars; - /* jshint ignore:end */ - return __exports__; -})(); - -// handlebars/compiler/helpers.js -var __module10__ = (function(__dependency1__) { - "use strict"; - var __exports__ = {}; - var Exception = __dependency1__; - - function stripFlags(open, close) { - return { - left: open.charAt(2) === '~', - right: close.charAt(close.length-3) === '~' - }; - } - - __exports__.stripFlags = stripFlags; - function prepareBlock(mustache, program, inverseAndProgram, close, inverted, locInfo) { - /*jshint -W040 */ - if (mustache.sexpr.id.original !== close.path.original) { - throw new Exception(mustache.sexpr.id.original + ' doesn\'t match ' + close.path.original, mustache); - } - - var inverse = inverseAndProgram && inverseAndProgram.program; - - var strip = { - left: mustache.strip.left, - right: close.strip.right, - - // Determine the standalone candiacy. Basically flag our content as being possibly standalone - // so our parent can determine if we actually are standalone - openStandalone: isNextWhitespace(program.statements), - closeStandalone: isPrevWhitespace((inverse || program).statements) - }; - - if (mustache.strip.right) { - omitRight(program.statements, null, true); - } - - if (inverse) { - var inverseStrip = inverseAndProgram.strip; - - if (inverseStrip.left) { - omitLeft(program.statements, null, true); - } - if (inverseStrip.right) { - omitRight(inverse.statements, null, true); - } - if (close.strip.left) { - omitLeft(inverse.statements, null, true); - } - - // Find standalone else statments - if (isPrevWhitespace(program.statements) - && isNextWhitespace(inverse.statements)) { - - omitLeft(program.statements); - omitRight(inverse.statements); - } - } else { - if (close.strip.left) { - omitLeft(program.statements, null, true); - } - } - - if (inverted) { - return new this.BlockNode(mustache, inverse, program, strip, locInfo); - } else { - return new this.BlockNode(mustache, program, inverse, strip, locInfo); - } - } - - __exports__.prepareBlock = prepareBlock; - function prepareProgram(statements, isRoot) { - for (var i = 0, l = statements.length; i < l; i++) { - var current = statements[i], - strip = current.strip; - - if (!strip) { - continue; - } - - var _isPrevWhitespace = isPrevWhitespace(statements, i, isRoot, current.type === 'partial'), - _isNextWhitespace = isNextWhitespace(statements, i, isRoot), - - openStandalone = strip.openStandalone && _isPrevWhitespace, - closeStandalone = strip.closeStandalone && _isNextWhitespace, - inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace; - - if (strip.right) { - omitRight(statements, i, true); - } - if (strip.left) { - omitLeft(statements, i, true); - } - - if (inlineStandalone) { - omitRight(statements, i); - - if (omitLeft(statements, i)) { - // If we are on a standalone node, save the indent info for partials - if (current.type === 'partial') { - current.indent = (/([ \t]+$)/).exec(statements[i-1].original) ? RegExp.$1 : ''; - } - } - } - if (openStandalone) { - omitRight((current.program || current.inverse).statements); - - // Strip out the previous content node if it's whitespace only - omitLeft(statements, i); - } - if (closeStandalone) { - // Always strip the next node - omitRight(statements, i); - - omitLeft((current.inverse || current.program).statements); - } - } - - return statements; - } - - __exports__.prepareProgram = prepareProgram;function isPrevWhitespace(statements, i, isRoot) { - if (i === undefined) { - i = statements.length; - } - - // Nodes that end with newlines are considered whitespace (but are special - // cased for strip operations) - var prev = statements[i-1], - sibling = statements[i-2]; - if (!prev) { - return isRoot; - } - - if (prev.type === 'content') { - return (sibling || !isRoot ? (/\r?\n\s*?$/) : (/(^|\r?\n)\s*?$/)).test(prev.original); - } - } - function isNextWhitespace(statements, i, isRoot) { - if (i === undefined) { - i = -1; - } - - var next = statements[i+1], - sibling = statements[i+2]; - if (!next) { - return isRoot; - } - - if (next.type === 'content') { - return (sibling || !isRoot ? (/^\s*?\r?\n/) : (/^\s*?(\r?\n|$)/)).test(next.original); - } - } - - // Marks the node to the right of the position as omitted. - // I.e. {{foo}}' ' will mark the ' ' node as omitted. - // - // If i is undefined, then the first child will be marked as such. - // - // If mulitple is truthy then all whitespace will be stripped out until non-whitespace - // content is met. - function omitRight(statements, i, multiple) { - var current = statements[i == null ? 0 : i + 1]; - if (!current || current.type !== 'content' || (!multiple && current.rightStripped)) { - return; - } - - var original = current.string; - current.string = current.string.replace(multiple ? (/^\s+/) : (/^[ \t]*\r?\n?/), ''); - current.rightStripped = current.string !== original; - } - - // Marks the node to the left of the position as omitted. - // I.e. ' '{{foo}} will mark the ' ' node as omitted. - // - // If i is undefined then the last child will be marked as such. - // - // If mulitple is truthy then all whitespace will be stripped out until non-whitespace - // content is met. - function omitLeft(statements, i, multiple) { - var current = statements[i == null ? statements.length - 1 : i - 1]; - if (!current || current.type !== 'content' || (!multiple && current.leftStripped)) { - return; - } - - // We omit the last node if it's whitespace only and not preceeded by a non-content node. - var original = current.string; - current.string = current.string.replace(multiple ? (/\s+$/) : (/[ \t]+$/), ''); - current.leftStripped = current.string !== original; - return current.leftStripped; - } - return __exports__; -})(__module5__); - -// handlebars/compiler/base.js -var __module8__ = (function(__dependency1__, __dependency2__, __dependency3__, __dependency4__) { - "use strict"; - var __exports__ = {}; - var parser = __dependency1__; - var AST = __dependency2__; - var Helpers = __dependency3__; - var extend = __dependency4__.extend; - - __exports__.parser = parser; - - var yy = {}; - extend(yy, Helpers, AST); - - function parse(input) { - // Just return if an already-compile AST was passed in. - if (input.constructor === AST.ProgramNode) { return input; } - - parser.yy = yy; - - return parser.parse(input); - } - - __exports__.parse = parse; - return __exports__; -})(__module9__, __module7__, __module10__, __module3__); - -// handlebars/compiler/compiler.js -var __module11__ = (function(__dependency1__, __dependency2__) { - "use strict"; - var __exports__ = {}; - var Exception = __dependency1__; - var isArray = __dependency2__.isArray; - - var slice = [].slice; - - function Compiler() {} - - __exports__.Compiler = Compiler;// the foundHelper register will disambiguate helper lookup from finding a - // function in a context. This is necessary for mustache compatibility, which - // requires that context functions in blocks are evaluated by blockHelperMissing, - // and then proceed as if the resulting value was provided to blockHelperMissing. - - Compiler.prototype = { - compiler: Compiler, - - equals: function(other) { - var len = this.opcodes.length; - if (other.opcodes.length !== len) { - return false; - } - - for (var i = 0; i < len; i++) { - var opcode = this.opcodes[i], - otherOpcode = other.opcodes[i]; - if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) { - return false; - } - } - - // We know that length is the same between the two arrays because they are directly tied - // to the opcode behavior above. - len = this.children.length; - for (i = 0; i < len; i++) { - if (!this.children[i].equals(other.children[i])) { - return false; - } - } - - return true; - }, - - guid: 0, - - compile: function(program, options) { - this.opcodes = []; - this.children = []; - this.depths = {list: []}; - this.options = options; - this.stringParams = options.stringParams; - this.trackIds = options.trackIds; - - // These changes will propagate to the other compiler components - var knownHelpers = this.options.knownHelpers; - this.options.knownHelpers = { - 'helperMissing': true, - 'blockHelperMissing': true, - 'each': true, - 'if': true, - 'unless': true, - 'with': true, - 'log': true, - 'lookup': true - }; - if (knownHelpers) { - for (var name in knownHelpers) { - this.options.knownHelpers[name] = knownHelpers[name]; - } - } - - return this.accept(program); - }, - - accept: function(node) { - return this[node.type](node); - }, - - program: function(program) { - var statements = program.statements; - - for(var i=0, l=statements.length; i 0) { - varDeclarations += ", " + locals.join(", "); - } - - // Generate minimizer alias mappings - for (var alias in this.aliases) { - if (this.aliases.hasOwnProperty(alias)) { - varDeclarations += ', ' + alias + '=' + this.aliases[alias]; - } - } - - var params = ["depth0", "helpers", "partials", "data"]; - - if (this.useDepths) { - params.push('depths'); - } - - // Perform a second pass over the output to merge content when possible - var source = this.mergeSource(varDeclarations); - - if (asObject) { - params.push(source); - - return Function.apply(this, params); - } else { - return 'function(' + params.join(',') + ') {\n ' + source + '}'; - } - }, - mergeSource: function(varDeclarations) { - var source = '', - buffer, - appendOnly = !this.forceBuffer, - appendFirst; - - for (var i = 0, len = this.source.length; i < len; i++) { - var line = this.source[i]; - if (line.appendToBuffer) { - if (buffer) { - buffer = buffer + '\n + ' + line.content; - } else { - buffer = line.content; - } - } else { - if (buffer) { - if (!source) { - appendFirst = true; - source = buffer + ';\n '; - } else { - source += 'buffer += ' + buffer + ';\n '; - } - buffer = undefined; - } - source += line + '\n '; - - if (!this.environment.isSimple) { - appendOnly = false; - } - } - } - - if (appendOnly) { - if (buffer || !source) { - source += 'return ' + (buffer || '""') + ';\n'; - } - } else { - varDeclarations += ", buffer = " + (appendFirst ? '' : this.initializeBuffer()); - if (buffer) { - source += 'return buffer + ' + buffer + ';\n'; - } else { - source += 'return buffer;\n'; - } - } - - if (varDeclarations) { - source = 'var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n ') + source; - } - - return source; - }, - - // [blockValue] - // - // On stack, before: hash, inverse, program, value - // On stack, after: return value of blockHelperMissing - // - // The purpose of this opcode is to take a block of the form - // `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and - // replace it on the stack with the result of properly - // invoking blockHelperMissing. - blockValue: function(name) { - this.aliases.blockHelperMissing = 'helpers.blockHelperMissing'; - - var params = [this.contextName(0)]; - this.setupParams(name, 0, params); - - var blockName = this.popStack(); - params.splice(1, 0, blockName); - - this.push('blockHelperMissing.call(' + params.join(', ') + ')'); - }, - - // [ambiguousBlockValue] - // - // On stack, before: hash, inverse, program, value - // Compiler value, before: lastHelper=value of last found helper, if any - // On stack, after, if no lastHelper: same as [blockValue] - // On stack, after, if lastHelper: value - ambiguousBlockValue: function() { - this.aliases.blockHelperMissing = 'helpers.blockHelperMissing'; - - // We're being a bit cheeky and reusing the options value from the prior exec - var params = [this.contextName(0)]; - this.setupParams('', 0, params, true); - - this.flushInline(); - - var current = this.topStack(); - params.splice(1, 0, current); - - this.pushSource("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }"); - }, - - // [appendContent] - // - // On stack, before: ... - // On stack, after: ... - // - // Appends the string value of `content` to the current buffer - appendContent: function(content) { - if (this.pendingContent) { - content = this.pendingContent + content; - } - - this.pendingContent = content; - }, - - // [append] - // - // On stack, before: value, ... - // On stack, after: ... - // - // Coerces `value` to a String and appends it to the current buffer. - // - // If `value` is truthy, or 0, it is coerced into a string and appended - // Otherwise, the empty string is appended - append: function() { - // Force anything that is inlined onto the stack so we don't have duplication - // when we examine local - this.flushInline(); - var local = this.popStack(); - this.pushSource('if (' + local + ' != null) { ' + this.appendToBuffer(local) + ' }'); - if (this.environment.isSimple) { - this.pushSource("else { " + this.appendToBuffer("''") + " }"); - } - }, - - // [appendEscaped] - // - // On stack, before: value, ... - // On stack, after: ... - // - // Escape `value` and append it to the buffer - appendEscaped: function() { - this.aliases.escapeExpression = 'this.escapeExpression'; - - this.pushSource(this.appendToBuffer("escapeExpression(" + this.popStack() + ")")); - }, - - // [getContext] - // - // On stack, before: ... - // On stack, after: ... - // Compiler value, after: lastContext=depth - // - // Set the value of the `lastContext` compiler value to the depth - getContext: function(depth) { - this.lastContext = depth; - }, - - // [pushContext] - // - // On stack, before: ... - // On stack, after: currentContext, ... - // - // Pushes the value of the current context onto the stack. - pushContext: function() { - this.pushStackLiteral(this.contextName(this.lastContext)); - }, - - // [lookupOnContext] - // - // On stack, before: ... - // On stack, after: currentContext[name], ... - // - // Looks up the value of `name` on the current context and pushes - // it onto the stack. - lookupOnContext: function(parts, falsy, scoped) { - /*jshint -W083 */ - var i = 0, - len = parts.length; - - if (!scoped && this.options.compat && !this.lastContext) { - // The depthed query is expected to handle the undefined logic for the root level that - // is implemented below, so we evaluate that directly in compat mode - this.push(this.depthedLookup(parts[i++])); - } else { - this.pushContext(); - } - - for (; i < len; i++) { - this.replaceStack(function(current) { - var lookup = this.nameLookup(current, parts[i], 'context'); - // We want to ensure that zero and false are handled properly if the context (falsy flag) - // needs to have the special handling for these values. - if (!falsy) { - return ' != null ? ' + lookup + ' : ' + current; - } else { - // Otherwise we can use generic falsy handling - return ' && ' + lookup; - } - }); - } - }, - - // [lookupData] - // - // On stack, before: ... - // On stack, after: data, ... - // - // Push the data lookup operator - lookupData: function(depth, parts) { - /*jshint -W083 */ - if (!depth) { - this.pushStackLiteral('data'); - } else { - this.pushStackLiteral('this.data(data, ' + depth + ')'); - } - - var len = parts.length; - for (var i = 0; i < len; i++) { - this.replaceStack(function(current) { - return ' && ' + this.nameLookup(current, parts[i], 'data'); - }); - } - }, - - // [resolvePossibleLambda] - // - // On stack, before: value, ... - // On stack, after: resolved value, ... - // - // If the `value` is a lambda, replace it on the stack by - // the return value of the lambda - resolvePossibleLambda: function() { - this.aliases.lambda = 'this.lambda'; - - this.push('lambda(' + this.popStack() + ', ' + this.contextName(0) + ')'); - }, - - // [pushStringParam] - // - // On stack, before: ... - // On stack, after: string, currentContext, ... - // - // This opcode is designed for use in string mode, which - // provides the string value of a parameter along with its - // depth rather than resolving it immediately. - pushStringParam: function(string, type) { - this.pushContext(); - this.pushString(type); - - // If it's a subexpression, the string result - // will be pushed after this opcode. - if (type !== 'sexpr') { - if (typeof string === 'string') { - this.pushString(string); - } else { - this.pushStackLiteral(string); - } - } - }, - - emptyHash: function() { - this.pushStackLiteral('{}'); - - if (this.trackIds) { - this.push('{}'); // hashIds - } - if (this.stringParams) { - this.push('{}'); // hashContexts - this.push('{}'); // hashTypes - } - }, - pushHash: function() { - if (this.hash) { - this.hashes.push(this.hash); - } - this.hash = {values: [], types: [], contexts: [], ids: []}; - }, - popHash: function() { - var hash = this.hash; - this.hash = this.hashes.pop(); - - if (this.trackIds) { - this.push('{' + hash.ids.join(',') + '}'); - } - if (this.stringParams) { - this.push('{' + hash.contexts.join(',') + '}'); - this.push('{' + hash.types.join(',') + '}'); - } - - this.push('{\n ' + hash.values.join(',\n ') + '\n }'); - }, - - // [pushString] - // - // On stack, before: ... - // On stack, after: quotedString(string), ... - // - // Push a quoted version of `string` onto the stack - pushString: function(string) { - this.pushStackLiteral(this.quotedString(string)); - }, - - // [push] - // - // On stack, before: ... - // On stack, after: expr, ... - // - // Push an expression onto the stack - push: function(expr) { - this.inlineStack.push(expr); - return expr; - }, - - // [pushLiteral] - // - // On stack, before: ... - // On stack, after: value, ... - // - // Pushes a value onto the stack. This operation prevents - // the compiler from creating a temporary variable to hold - // it. - pushLiteral: function(value) { - this.pushStackLiteral(value); - }, - - // [pushProgram] - // - // On stack, before: ... - // On stack, after: program(guid), ... - // - // Push a program expression onto the stack. This takes - // a compile-time guid and converts it into a runtime-accessible - // expression. - pushProgram: function(guid) { - if (guid != null) { - this.pushStackLiteral(this.programExpression(guid)); - } else { - this.pushStackLiteral(null); - } - }, - - // [invokeHelper] - // - // On stack, before: hash, inverse, program, params..., ... - // On stack, after: result of helper invocation - // - // Pops off the helper's parameters, invokes the helper, - // and pushes the helper's return value onto the stack. - // - // If the helper is not found, `helperMissing` is called. - invokeHelper: function(paramSize, name, isSimple) { - this.aliases.helperMissing = 'helpers.helperMissing'; - - var nonHelper = this.popStack(); - var helper = this.setupHelper(paramSize, name); - - var lookup = (isSimple ? helper.name + ' || ' : '') + nonHelper + ' || helperMissing'; - this.push('((' + lookup + ').call(' + helper.callParams + '))'); - }, - - // [invokeKnownHelper] - // - // On stack, before: hash, inverse, program, params..., ... - // On stack, after: result of helper invocation - // - // This operation is used when the helper is known to exist, - // so a `helperMissing` fallback is not required. - invokeKnownHelper: function(paramSize, name) { - var helper = this.setupHelper(paramSize, name); - this.push(helper.name + ".call(" + helper.callParams + ")"); - }, - - // [invokeAmbiguous] - // - // On stack, before: hash, inverse, program, params..., ... - // On stack, after: result of disambiguation - // - // This operation is used when an expression like `{{foo}}` - // is provided, but we don't know at compile-time whether it - // is a helper or a path. - // - // This operation emits more code than the other options, - // and can be avoided by passing the `knownHelpers` and - // `knownHelpersOnly` flags at compile-time. - invokeAmbiguous: function(name, helperCall) { - this.aliases.functionType = '"function"'; - this.aliases.helperMissing = 'helpers.helperMissing'; - this.useRegister('helper'); - - var nonHelper = this.popStack(); - - this.emptyHash(); - var helper = this.setupHelper(0, name, helperCall); - - var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper'); - - this.push( - '((helper = (helper = ' + helperName + ' || ' + nonHelper + ') != null ? helper : helperMissing' - + (helper.paramsInit ? '),(' + helper.paramsInit : '') + '),' - + '(typeof helper === functionType ? helper.call(' + helper.callParams + ') : helper))'); - }, - - // [invokePartial] - // - // On stack, before: context, ... - // On stack after: result of partial invocation - // - // This operation pops off a context, invokes a partial with that context, - // and pushes the result of the invocation back. - invokePartial: function(name, indent) { - var params = [this.nameLookup('partials', name, 'partial'), "'" + indent + "'", "'" + name + "'", this.popStack(), this.popStack(), "helpers", "partials"]; - - if (this.options.data) { - params.push("data"); - } else if (this.options.compat) { - params.push('undefined'); - } - if (this.options.compat) { - params.push('depths'); - } - - this.push("this.invokePartial(" + params.join(", ") + ")"); - }, - - // [assignToHash] - // - // On stack, before: value, ..., hash, ... - // On stack, after: ..., hash, ... - // - // Pops a value off the stack and assigns it to the current hash - assignToHash: function(key) { - var value = this.popStack(), - context, - type, - id; - - if (this.trackIds) { - id = this.popStack(); - } - if (this.stringParams) { - type = this.popStack(); - context = this.popStack(); - } - - var hash = this.hash; - if (context) { - hash.contexts.push("'" + key + "': " + context); - } - if (type) { - hash.types.push("'" + key + "': " + type); - } - if (id) { - hash.ids.push("'" + key + "': " + id); - } - hash.values.push("'" + key + "': (" + value + ")"); - }, - - pushId: function(type, name) { - if (type === 'ID' || type === 'DATA') { - this.pushString(name); - } else if (type === 'sexpr') { - this.pushStackLiteral('true'); - } else { - this.pushStackLiteral('null'); - } - }, - - // HELPERS - - compiler: JavaScriptCompiler, - - compileChildren: function(environment, options) { - var children = environment.children, child, compiler; - - for(var i=0, l=children.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } - return this.topStackName(); - }, - topStackName: function() { - return "stack" + this.stackSlot; - }, - flushInline: function() { - var inlineStack = this.inlineStack; - if (inlineStack.length) { - this.inlineStack = []; - for (var i = 0, len = inlineStack.length; i < len; i++) { - var entry = inlineStack[i]; - if (entry instanceof Literal) { - this.compileStack.push(entry); - } else { - this.pushStack(entry); - } - } - } - }, - isInline: function() { - return this.inlineStack.length; - }, - - popStack: function(wrapped) { - var inline = this.isInline(), - item = (inline ? this.inlineStack : this.compileStack).pop(); - - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - if (!inline) { - /* istanbul ignore next */ - if (!this.stackSlot) { - throw new Exception('Invalid stack pop'); - } - this.stackSlot--; - } - return item; - } - }, - - topStack: function() { - var stack = (this.isInline() ? this.inlineStack : this.compileStack), - item = stack[stack.length - 1]; - - if (item instanceof Literal) { - return item.value; - } else { - return item; - } - }, - - contextName: function(context) { - if (this.useDepths && context) { - return 'depths[' + context + ']'; - } else { - return 'depth' + context; - } - }, - - quotedString: function(str) { - return '"' + str - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 - .replace(/\u2029/g, '\\u2029') + '"'; - }, - - objectLiteral: function(obj) { - var pairs = []; - - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - pairs.push(this.quotedString(key) + ':' + obj[key]); - } - } - - return '{' + pairs.join(',') + '}'; - }, - - setupHelper: function(paramSize, name, blockHelper) { - var params = [], - paramsInit = this.setupParams(name, paramSize, params, blockHelper); - var foundHelper = this.nameLookup('helpers', name, 'helper'); - - return { - params: params, - paramsInit: paramsInit, - name: foundHelper, - callParams: [this.contextName(0)].concat(params).join(", ") - }; - }, - - setupOptions: function(helper, paramSize, params) { - var options = {}, contexts = [], types = [], ids = [], param, inverse, program; - - options.name = this.quotedString(helper); - options.hash = this.popStack(); - - if (this.trackIds) { - options.hashIds = this.popStack(); - } - if (this.stringParams) { - options.hashTypes = this.popStack(); - options.hashContexts = this.popStack(); - } - - inverse = this.popStack(); - program = this.popStack(); - - // Avoid setting fn and inverse if neither are set. This allows - // helpers to do a check for `if (options.fn)` - if (program || inverse) { - if (!program) { - program = 'this.noop'; - } - - if (!inverse) { - inverse = 'this.noop'; - } - - options.fn = program; - options.inverse = inverse; - } - - // The parameters go on to the stack in order (making sure that they are evaluated in order) - // so we need to pop them off the stack in reverse order - var i = paramSize; - while (i--) { - param = this.popStack(); - params[i] = param; - - if (this.trackIds) { - ids[i] = this.popStack(); - } - if (this.stringParams) { - types[i] = this.popStack(); - contexts[i] = this.popStack(); - } - } - - if (this.trackIds) { - options.ids = "[" + ids.join(",") + "]"; - } - if (this.stringParams) { - options.types = "[" + types.join(",") + "]"; - options.contexts = "[" + contexts.join(",") + "]"; - } - - if (this.options.data) { - options.data = "data"; - } - - return options; - }, - - // the params and contexts arguments are passed in arrays - // to fill in - setupParams: function(helperName, paramSize, params, useRegister) { - var options = this.objectLiteral(this.setupOptions(helperName, paramSize, params)); - - if (useRegister) { - this.useRegister('options'); - params.push('options'); - return 'options=' + options; - } else { - params.push(options); - return ''; - } - } - }; - - var reservedWords = ( - "break else new var" + - " case finally return void" + - " catch for switch while" + - " continue function this with" + - " default if throw" + - " delete in try" + - " do instanceof typeof" + - " abstract enum int short" + - " boolean export interface static" + - " byte extends long super" + - " char final native synchronized" + - " class float package throws" + - " const goto private transient" + - " debugger implements protected volatile" + - " double import public let yield" - ).split(" "); - - var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {}; - - for(var i=0, l=reservedWords.length; i= 2.0.0-beta.1', + 7: '>= 4.0.0' + }; + + exports.REVISION_CHANGES = REVISION_CHANGES; + var objectType = '[object Object]'; + + function HandlebarsEnvironment(helpers, partials, decorators) { + this.helpers = helpers || {}; + this.partials = partials || {}; + this.decorators = decorators || {}; + + _helpers.registerDefaultHelpers(this); + _decorators.registerDefaultDecorators(this); + } + + HandlebarsEnvironment.prototype = { + constructor: HandlebarsEnvironment, + + logger: _logger2['default'], + log: _logger2['default'].log, + + registerHelper: function registerHelper(name, fn) { + if (_utils.toString.call(name) === objectType) { + if (fn) { + throw new _exception2['default']('Arg not supported with multiple helpers'); + } + _utils.extend(this.helpers, name); + } else { + this.helpers[name] = fn; + } + }, + unregisterHelper: function unregisterHelper(name) { + delete this.helpers[name]; + }, + + registerPartial: function registerPartial(name, partial) { + if (_utils.toString.call(name) === objectType) { + _utils.extend(this.partials, name); + } else { + if (typeof partial === 'undefined') { + throw new _exception2['default']('Attempting to register a partial called "' + name + '" as undefined'); + } + this.partials[name] = partial; + } + }, + unregisterPartial: function unregisterPartial(name) { + delete this.partials[name]; + }, + + registerDecorator: function registerDecorator(name, fn) { + if (_utils.toString.call(name) === objectType) { + if (fn) { + throw new _exception2['default']('Arg not supported with multiple decorators'); + } + _utils.extend(this.decorators, name); + } else { + this.decorators[name] = fn; + } + }, + unregisterDecorator: function unregisterDecorator(name) { + delete this.decorators[name]; + } + }; + + var log = _logger2['default'].log; + + exports.log = log; + exports.createFrame = _utils.createFrame; + exports.logger = _logger2['default']; + +/***/ }, +/* 5 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + exports.extend = extend; + exports.indexOf = indexOf; + exports.escapeExpression = escapeExpression; + exports.isEmpty = isEmpty; + exports.createFrame = createFrame; + exports.blockParams = blockParams; + exports.appendContextPath = appendContextPath; + var escape = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`', + '=': '=' + }; + + var badChars = /[&<>"'`=]/g, + possible = /[&<>"'`=]/; + + function escapeChar(chr) { + return escape[chr]; + } + + function extend(obj /* , ...source */) { + for (var i = 1; i < arguments.length; i++) { + for (var key in arguments[i]) { + if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { + obj[key] = arguments[i][key]; + } + } + } + + return obj; + } + + var toString = Object.prototype.toString; + + exports.toString = toString; + // Sourced from lodash + // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt + /* eslint-disable func-style */ + var isFunction = function isFunction(value) { + return typeof value === 'function'; + }; + // fallback for older versions of Chrome and Safari + /* istanbul ignore next */ + if (isFunction(/x/)) { + exports.isFunction = isFunction = function (value) { + return typeof value === 'function' && toString.call(value) === '[object Function]'; + }; + } + exports.isFunction = isFunction; + + /* eslint-enable func-style */ + + /* istanbul ignore next */ + var isArray = Array.isArray || function (value) { + return value && typeof value === 'object' ? toString.call(value) === '[object Array]' : false; + }; + + exports.isArray = isArray; + // Older IE versions do not directly support indexOf so we must implement our own, sadly. + + function indexOf(array, value) { + for (var i = 0, len = array.length; i < len; i++) { + if (array[i] === value) { + return i; + } + } + return -1; + } + + function escapeExpression(string) { + if (typeof string !== 'string') { + // don't escape SafeStrings, since they're already safe + if (string && string.toHTML) { + return string.toHTML(); + } else if (string == null) { + return ''; + } else if (!string) { + return string + ''; + } + + // Force a string conversion as this will be done by the append regardless and + // the regex test will do this transparently behind the scenes, causing issues if + // an object's to string has escaped characters in it. + string = '' + string; + } + + if (!possible.test(string)) { + return string; + } + return string.replace(badChars, escapeChar); + } + + function isEmpty(value) { + if (!value && value !== 0) { + return true; + } else if (isArray(value) && value.length === 0) { + return true; + } else { + return false; + } + } + + function createFrame(object) { + var frame = extend({}, object); + frame._parent = object; + return frame; + } + + function blockParams(params, ids) { + params.path = ids; + return params; + } + + function appendContextPath(contextPath, id) { + return (contextPath ? contextPath + '.' : '') + id; + } + +/***/ }, +/* 6 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + + var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; + + function Exception(message, node) { + var loc = node && node.loc, + line = undefined, + column = undefined; + if (loc) { + line = loc.start.line; + column = loc.start.column; + + message += ' - ' + line + ':' + column; + } + + var tmp = Error.prototype.constructor.call(this, message); + + // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. + for (var idx = 0; idx < errorProps.length; idx++) { + this[errorProps[idx]] = tmp[errorProps[idx]]; + } + + /* istanbul ignore else */ + if (Error.captureStackTrace) { + Error.captureStackTrace(this, Exception); + } + + if (loc) { + this.lineNumber = line; + this.column = column; + } + } + + Exception.prototype = new Error(); + + exports['default'] = Exception; + module.exports = exports['default']; + +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.registerDefaultHelpers = registerDefaultHelpers; + + var _helpersBlockHelperMissing = __webpack_require__(8); + + var _helpersBlockHelperMissing2 = _interopRequireDefault(_helpersBlockHelperMissing); + + var _helpersEach = __webpack_require__(9); + + var _helpersEach2 = _interopRequireDefault(_helpersEach); + + var _helpersHelperMissing = __webpack_require__(10); + + var _helpersHelperMissing2 = _interopRequireDefault(_helpersHelperMissing); + + var _helpersIf = __webpack_require__(11); + + var _helpersIf2 = _interopRequireDefault(_helpersIf); + + var _helpersLog = __webpack_require__(12); + + var _helpersLog2 = _interopRequireDefault(_helpersLog); + + var _helpersLookup = __webpack_require__(13); + + var _helpersLookup2 = _interopRequireDefault(_helpersLookup); + + var _helpersWith = __webpack_require__(14); + + var _helpersWith2 = _interopRequireDefault(_helpersWith); + + function registerDefaultHelpers(instance) { + _helpersBlockHelperMissing2['default'](instance); + _helpersEach2['default'](instance); + _helpersHelperMissing2['default'](instance); + _helpersIf2['default'](instance); + _helpersLog2['default'](instance); + _helpersLookup2['default'](instance); + _helpersWith2['default'](instance); + } + +/***/ }, +/* 8 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + exports['default'] = function (instance) { + instance.registerHelper('blockHelperMissing', function (context, options) { + var inverse = options.inverse, + fn = options.fn; + + if (context === true) { + return fn(this); + } else if (context === false || context == null) { + return inverse(this); + } else if (_utils.isArray(context)) { + if (context.length > 0) { + if (options.ids) { + options.ids = [options.name]; + } + + return instance.helpers.each(context, options); + } else { + return inverse(this); + } + } else { + if (options.data && options.ids) { + var data = _utils.createFrame(options.data); + data.contextPath = _utils.appendContextPath(options.data.contextPath, options.name); + options = { data: data }; + } + + return fn(context, options); + } + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 9 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + exports['default'] = function (instance) { + instance.registerHelper('each', function (context, options) { + if (!options) { + throw new _exception2['default']('Must pass iterator to #each'); + } + + var fn = options.fn, + inverse = options.inverse, + i = 0, + ret = '', + data = undefined, + contextPath = undefined; + + if (options.data && options.ids) { + contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.'; + } + + if (_utils.isFunction(context)) { + context = context.call(this); + } + + if (options.data) { + data = _utils.createFrame(options.data); + } + + function execIteration(field, index, last) { + if (data) { + data.key = field; + data.index = index; + data.first = index === 0; + data.last = !!last; + + if (contextPath) { + data.contextPath = contextPath + field; + } + } + + ret = ret + fn(context[field], { + data: data, + blockParams: _utils.blockParams([context[field], field], [contextPath + field, null]) + }); + } + + if (context && typeof context === 'object') { + if (_utils.isArray(context)) { + for (var j = context.length; i < j; i++) { + if (i in context) { + execIteration(i, i, i === context.length - 1); + } + } + } else { + var priorKey = undefined; + + for (var key in context) { + if (context.hasOwnProperty(key)) { + // We're running the iterations one step out of sync so we can detect + // the last iteration without have to scan the object twice and create + // an itermediate keys array. + if (priorKey !== undefined) { + execIteration(priorKey, i - 1); + } + priorKey = key; + i++; + } + } + if (priorKey !== undefined) { + execIteration(priorKey, i - 1, true); + } + } + } + + if (i === 0) { + ret = inverse(this); + } + + return ret; + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 10 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + exports['default'] = function (instance) { + instance.registerHelper('helperMissing', function () /* [args, ]options */{ + if (arguments.length === 1) { + // A missing field in a {{foo}} construct. + return undefined; + } else { + // Someone is actually trying to call something, blow up. + throw new _exception2['default']('Missing helper: "' + arguments[arguments.length - 1].name + '"'); + } + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 11 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + exports['default'] = function (instance) { + instance.registerHelper('if', function (conditional, options) { + if (_utils.isFunction(conditional)) { + conditional = conditional.call(this); + } + + // Default behavior is to render the positive path if the value is truthy and not empty. + // The `includeZero` option may be set to treat the condtional as purely not empty based on the + // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative. + if (!options.hash.includeZero && !conditional || _utils.isEmpty(conditional)) { + return options.inverse(this); + } else { + return options.fn(this); + } + }); + + instance.registerHelper('unless', function (conditional, options) { + return instance.helpers['if'].call(this, conditional, { fn: options.inverse, inverse: options.fn, hash: options.hash }); + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 12 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + + exports['default'] = function (instance) { + instance.registerHelper('log', function () /* message, options */{ + var args = [undefined], + options = arguments[arguments.length - 1]; + for (var i = 0; i < arguments.length - 1; i++) { + args.push(arguments[i]); + } + + var level = 1; + if (options.hash.level != null) { + level = options.hash.level; + } else if (options.data && options.data.level != null) { + level = options.data.level; + } + args[0] = level; + + instance.log.apply(instance, args); + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 13 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + + exports['default'] = function (instance) { + instance.registerHelper('lookup', function (obj, field) { + return obj && obj[field]; + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 14 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + exports['default'] = function (instance) { + instance.registerHelper('with', function (context, options) { + if (_utils.isFunction(context)) { + context = context.call(this); + } + + var fn = options.fn; + + if (!_utils.isEmpty(context)) { + var data = options.data; + if (options.data && options.ids) { + data = _utils.createFrame(options.data); + data.contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]); + } + + return fn(context, { + data: data, + blockParams: _utils.blockParams([context], [data && data.contextPath]) + }); + } else { + return options.inverse(this); + } + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 15 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.registerDefaultDecorators = registerDefaultDecorators; + + var _decoratorsInline = __webpack_require__(16); + + var _decoratorsInline2 = _interopRequireDefault(_decoratorsInline); + + function registerDefaultDecorators(instance) { + _decoratorsInline2['default'](instance); + } + +/***/ }, +/* 16 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + exports['default'] = function (instance) { + instance.registerDecorator('inline', function (fn, props, container, options) { + var ret = fn; + if (!props.partials) { + props.partials = {}; + ret = function (context, options) { + // Create a new partials stack frame prior to exec. + var original = container.partials; + container.partials = _utils.extend({}, original, props.partials); + var ret = fn(context, options); + container.partials = original; + return ret; + }; + } + + props.partials[options.args[0]] = options.fn; + + return ret; + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 17 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + var logger = { + methodMap: ['debug', 'info', 'warn', 'error'], + level: 'info', + + // Maps a given level value to the `methodMap` indexes above. + lookupLevel: function lookupLevel(level) { + if (typeof level === 'string') { + var levelMap = _utils.indexOf(logger.methodMap, level.toLowerCase()); + if (levelMap >= 0) { + level = levelMap; + } else { + level = parseInt(level, 10); + } + } + + return level; + }, + + // Can be overridden in the host environment + log: function log(level) { + level = logger.lookupLevel(level); + + if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) { + var method = logger.methodMap[level]; + if (!console[method]) { + // eslint-disable-line no-console + method = 'log'; + } + + for (var _len = arguments.length, message = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + message[_key - 1] = arguments[_key]; + } + + console[method].apply(console, message); // eslint-disable-line no-console + } + } + }; + + exports['default'] = logger; + module.exports = exports['default']; + +/***/ }, +/* 18 */ +/***/ function(module, exports) { + + // Build out our basic SafeString type + 'use strict'; + + exports.__esModule = true; + function SafeString(string) { + this.string = string; + } + + SafeString.prototype.toString = SafeString.prototype.toHTML = function () { + return '' + this.string; + }; + + exports['default'] = SafeString; + module.exports = exports['default']; + +/***/ }, +/* 19 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireWildcard = __webpack_require__(3)['default']; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.checkRevision = checkRevision; + exports.template = template; + exports.wrapProgram = wrapProgram; + exports.resolvePartial = resolvePartial; + exports.invokePartial = invokePartial; + exports.noop = noop; + + var _utils = __webpack_require__(5); + + var Utils = _interopRequireWildcard(_utils); + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + var _base = __webpack_require__(4); + + function checkRevision(compilerInfo) { + var compilerRevision = compilerInfo && compilerInfo[0] || 1, + currentRevision = _base.COMPILER_REVISION; + + if (compilerRevision !== currentRevision) { + if (compilerRevision < currentRevision) { + var runtimeVersions = _base.REVISION_CHANGES[currentRevision], + compilerVersions = _base.REVISION_CHANGES[compilerRevision]; + throw new _exception2['default']('Template was precompiled with an older version of Handlebars than the current runtime. ' + 'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').'); + } else { + // Use the embedded version info since the runtime doesn't know about this revision yet + throw new _exception2['default']('Template was precompiled with a newer version of Handlebars than the current runtime. ' + 'Please update your runtime to a newer version (' + compilerInfo[1] + ').'); + } + } + } + + function template(templateSpec, env) { + /* istanbul ignore next */ + if (!env) { + throw new _exception2['default']('No environment passed to template'); + } + if (!templateSpec || !templateSpec.main) { + throw new _exception2['default']('Unknown template object: ' + typeof templateSpec); + } + + templateSpec.main.decorator = templateSpec.main_d; + + // Note: Using env.VM references rather than local var references throughout this section to allow + // for external users to override these as psuedo-supported APIs. + env.VM.checkRevision(templateSpec.compiler); + + function invokePartialWrapper(partial, context, options) { + if (options.hash) { + context = Utils.extend({}, context, options.hash); + if (options.ids) { + options.ids[0] = true; + } + } + + partial = env.VM.resolvePartial.call(this, partial, context, options); + var result = env.VM.invokePartial.call(this, partial, context, options); + + if (result == null && env.compile) { + options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env); + result = options.partials[options.name](context, options); + } + if (result != null) { + if (options.indent) { + var lines = result.split('\n'); + for (var i = 0, l = lines.length; i < l; i++) { + if (!lines[i] && i + 1 === l) { + break; + } + + lines[i] = options.indent + lines[i]; + } + result = lines.join('\n'); + } + return result; + } else { + throw new _exception2['default']('The partial ' + options.name + ' could not be compiled when running in runtime-only mode'); + } + } + + // Just add water + var container = { + strict: function strict(obj, name) { + if (!(name in obj)) { + throw new _exception2['default']('"' + name + '" not defined in ' + obj); + } + return obj[name]; + }, + lookup: function lookup(depths, name) { + var len = depths.length; + for (var i = 0; i < len; i++) { + if (depths[i] && depths[i][name] != null) { + return depths[i][name]; + } + } + }, + lambda: function lambda(current, context) { + return typeof current === 'function' ? current.call(context) : current; + }, + + escapeExpression: Utils.escapeExpression, + invokePartial: invokePartialWrapper, + + fn: function fn(i) { + var ret = templateSpec[i]; + ret.decorator = templateSpec[i + '_d']; + return ret; + }, + + programs: [], + program: function program(i, data, declaredBlockParams, blockParams, depths) { + var programWrapper = this.programs[i], + fn = this.fn(i); + if (data || depths || blockParams || declaredBlockParams) { + programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths); + } else if (!programWrapper) { + programWrapper = this.programs[i] = wrapProgram(this, i, fn); + } + return programWrapper; + }, + + data: function data(value, depth) { + while (value && depth--) { + value = value._parent; + } + return value; + }, + merge: function merge(param, common) { + var obj = param || common; + + if (param && common && param !== common) { + obj = Utils.extend({}, common, param); + } + + return obj; + }, + + noop: env.VM.noop, + compilerInfo: templateSpec.compiler + }; + + function ret(context) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var data = options.data; + + ret._setup(options); + if (!options.partial && templateSpec.useData) { + data = initData(context, data); + } + var depths = undefined, + blockParams = templateSpec.useBlockParams ? [] : undefined; + if (templateSpec.useDepths) { + if (options.depths) { + depths = context !== options.depths[0] ? [context].concat(options.depths) : options.depths; + } else { + depths = [context]; + } + } + + function main(context /*, options*/) { + return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths); + } + main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams); + return main(context, options); + } + ret.isTop = true; + + ret._setup = function (options) { + if (!options.partial) { + container.helpers = container.merge(options.helpers, env.helpers); + + if (templateSpec.usePartial) { + container.partials = container.merge(options.partials, env.partials); + } + if (templateSpec.usePartial || templateSpec.useDecorators) { + container.decorators = container.merge(options.decorators, env.decorators); + } + } else { + container.helpers = options.helpers; + container.partials = options.partials; + container.decorators = options.decorators; + } + }; + + ret._child = function (i, data, blockParams, depths) { + if (templateSpec.useBlockParams && !blockParams) { + throw new _exception2['default']('must pass block params'); + } + if (templateSpec.useDepths && !depths) { + throw new _exception2['default']('must pass parent depths'); + } + + return wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths); + }; + return ret; + } + + function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) { + function prog(context) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var currentDepths = depths; + if (depths && context !== depths[0]) { + currentDepths = [context].concat(depths); + } + + return fn(container, context, container.helpers, container.partials, options.data || data, blockParams && [options.blockParams].concat(blockParams), currentDepths); + } + + prog = executeDecorators(fn, prog, container, depths, data, blockParams); + + prog.program = i; + prog.depth = depths ? depths.length : 0; + prog.blockParams = declaredBlockParams || 0; + return prog; + } + + function resolvePartial(partial, context, options) { + if (!partial) { + if (options.name === '@partial-block') { + partial = options.data['partial-block']; + } else { + partial = options.partials[options.name]; + } + } else if (!partial.call && !options.name) { + // This is a dynamic partial that returned a string + options.name = partial; + partial = options.partials[partial]; + } + return partial; + } + + function invokePartial(partial, context, options) { + options.partial = true; + if (options.ids) { + options.data.contextPath = options.ids[0] || options.data.contextPath; + } + + var partialBlock = undefined; + if (options.fn && options.fn !== noop) { + options.data = _base.createFrame(options.data); + partialBlock = options.data['partial-block'] = options.fn; + + if (partialBlock.partials) { + options.partials = Utils.extend({}, options.partials, partialBlock.partials); + } + } + + if (partial === undefined && partialBlock) { + partial = partialBlock; + } + + if (partial === undefined) { + throw new _exception2['default']('The partial ' + options.name + ' could not be found'); + } else if (partial instanceof Function) { + return partial(context, options); + } + } + + function noop() { + return ''; + } + + function initData(context, data) { + if (!data || !('root' in data)) { + data = data ? _base.createFrame(data) : {}; + data.root = context; + } + return data; + } + + function executeDecorators(fn, prog, container, depths, data, blockParams) { + if (fn.decorator) { + var props = {}; + prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths); + Utils.extend(prog, props); + } + return prog; + } + +/***/ }, +/* 20 */ +/***/ function(module, exports) { + + /* WEBPACK VAR INJECTION */(function(global) {/* global window */ + 'use strict'; + + exports.__esModule = true; + + exports['default'] = function (Handlebars) { + /* istanbul ignore next */ + var root = typeof global !== 'undefined' ? global : window, + $Handlebars = root.Handlebars; + /* istanbul ignore next */ + Handlebars.noConflict = function () { + if (root.Handlebars === Handlebars) { + root.Handlebars = $Handlebars; + } + return Handlebars; + }; + }; + + module.exports = exports['default']; + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 21 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + var AST = { + // Public API used to evaluate derived attributes regarding AST nodes + helpers: { + // a mustache is definitely a helper if: + // * it is an eligible helper, and + // * it has at least one parameter or hash segment + helperExpression: function helperExpression(node) { + return node.type === 'SubExpression' || (node.type === 'MustacheStatement' || node.type === 'BlockStatement') && !!(node.params && node.params.length || node.hash); + }, + + scopedId: function scopedId(path) { + return (/^\.|this\b/.test(path.original) + ); + }, + + // an ID is simple if it only has one part, and that part is not + // `..` or `this`. + simpleId: function simpleId(path) { + return path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth; + } + } + }; + + // Must be exported as an object rather than the root of the module as the jison lexer + // must modify the object to operate properly. + exports['default'] = AST; + module.exports = exports['default']; + +/***/ }, +/* 22 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + var _interopRequireWildcard = __webpack_require__(3)['default']; + + exports.__esModule = true; + exports.parse = parse; + + var _parser = __webpack_require__(23); + + var _parser2 = _interopRequireDefault(_parser); + + var _whitespaceControl = __webpack_require__(24); + + var _whitespaceControl2 = _interopRequireDefault(_whitespaceControl); + + var _helpers = __webpack_require__(26); + + var Helpers = _interopRequireWildcard(_helpers); + + var _utils = __webpack_require__(5); + + exports.parser = _parser2['default']; + + var yy = {}; + _utils.extend(yy, Helpers); + + function parse(input, options) { + // Just return if an already-compiled AST was passed in. + if (input.type === 'Program') { + return input; + } + + _parser2['default'].yy = yy; + + // Altering the shared object here, but this is ok as parser is a sync operation + yy.locInfo = function (locInfo) { + return new yy.SourceLocation(options && options.srcName, locInfo); + }; + + var strip = new _whitespaceControl2['default'](options); + return strip.accept(_parser2['default'].parse(input)); + } + +/***/ }, +/* 23 */ +/***/ function(module, exports) { + + /* istanbul ignore next */ + /* Jison generated parser */ + "use strict"; + + var handlebars = (function () { + var parser = { trace: function trace() {}, + yy: {}, + symbols_: { "error": 2, "root": 3, "program": 4, "EOF": 5, "program_repetition0": 6, "statement": 7, "mustache": 8, "block": 9, "rawBlock": 10, "partial": 11, "partialBlock": 12, "content": 13, "COMMENT": 14, "CONTENT": 15, "openRawBlock": 16, "rawBlock_repetition_plus0": 17, "END_RAW_BLOCK": 18, "OPEN_RAW_BLOCK": 19, "helperName": 20, "openRawBlock_repetition0": 21, "openRawBlock_option0": 22, "CLOSE_RAW_BLOCK": 23, "openBlock": 24, "block_option0": 25, "closeBlock": 26, "openInverse": 27, "block_option1": 28, "OPEN_BLOCK": 29, "openBlock_repetition0": 30, "openBlock_option0": 31, "openBlock_option1": 32, "CLOSE": 33, "OPEN_INVERSE": 34, "openInverse_repetition0": 35, "openInverse_option0": 36, "openInverse_option1": 37, "openInverseChain": 38, "OPEN_INVERSE_CHAIN": 39, "openInverseChain_repetition0": 40, "openInverseChain_option0": 41, "openInverseChain_option1": 42, "inverseAndProgram": 43, "INVERSE": 44, "inverseChain": 45, "inverseChain_option0": 46, "OPEN_ENDBLOCK": 47, "OPEN": 48, "mustache_repetition0": 49, "mustache_option0": 50, "OPEN_UNESCAPED": 51, "mustache_repetition1": 52, "mustache_option1": 53, "CLOSE_UNESCAPED": 54, "OPEN_PARTIAL": 55, "partialName": 56, "partial_repetition0": 57, "partial_option0": 58, "openPartialBlock": 59, "OPEN_PARTIAL_BLOCK": 60, "openPartialBlock_repetition0": 61, "openPartialBlock_option0": 62, "param": 63, "sexpr": 64, "OPEN_SEXPR": 65, "sexpr_repetition0": 66, "sexpr_option0": 67, "CLOSE_SEXPR": 68, "hash": 69, "hash_repetition_plus0": 70, "hashSegment": 71, "ID": 72, "EQUALS": 73, "blockParams": 74, "OPEN_BLOCK_PARAMS": 75, "blockParams_repetition_plus0": 76, "CLOSE_BLOCK_PARAMS": 77, "path": 78, "dataName": 79, "STRING": 80, "NUMBER": 81, "BOOLEAN": 82, "UNDEFINED": 83, "NULL": 84, "DATA": 85, "pathSegments": 86, "SEP": 87, "$accept": 0, "$end": 1 }, + terminals_: { 2: "error", 5: "EOF", 14: "COMMENT", 15: "CONTENT", 18: "END_RAW_BLOCK", 19: "OPEN_RAW_BLOCK", 23: "CLOSE_RAW_BLOCK", 29: "OPEN_BLOCK", 33: "CLOSE", 34: "OPEN_INVERSE", 39: "OPEN_INVERSE_CHAIN", 44: "INVERSE", 47: "OPEN_ENDBLOCK", 48: "OPEN", 51: "OPEN_UNESCAPED", 54: "CLOSE_UNESCAPED", 55: "OPEN_PARTIAL", 60: "OPEN_PARTIAL_BLOCK", 65: "OPEN_SEXPR", 68: "CLOSE_SEXPR", 72: "ID", 73: "EQUALS", 75: "OPEN_BLOCK_PARAMS", 77: "CLOSE_BLOCK_PARAMS", 80: "STRING", 81: "NUMBER", 82: "BOOLEAN", 83: "UNDEFINED", 84: "NULL", 85: "DATA", 87: "SEP" }, + productions_: [0, [3, 2], [4, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [13, 1], [10, 3], [16, 5], [9, 4], [9, 4], [24, 6], [27, 6], [38, 6], [43, 2], [45, 3], [45, 1], [26, 3], [8, 5], [8, 5], [11, 5], [12, 3], [59, 5], [63, 1], [63, 1], [64, 5], [69, 1], [71, 3], [74, 3], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [56, 1], [56, 1], [79, 2], [78, 1], [86, 3], [86, 1], [6, 0], [6, 2], [17, 1], [17, 2], [21, 0], [21, 2], [22, 0], [22, 1], [25, 0], [25, 1], [28, 0], [28, 1], [30, 0], [30, 2], [31, 0], [31, 1], [32, 0], [32, 1], [35, 0], [35, 2], [36, 0], [36, 1], [37, 0], [37, 1], [40, 0], [40, 2], [41, 0], [41, 1], [42, 0], [42, 1], [46, 0], [46, 1], [49, 0], [49, 2], [50, 0], [50, 1], [52, 0], [52, 2], [53, 0], [53, 1], [57, 0], [57, 2], [58, 0], [58, 1], [61, 0], [61, 2], [62, 0], [62, 1], [66, 0], [66, 2], [67, 0], [67, 1], [70, 1], [70, 2], [76, 1], [76, 2]], + performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$ + /**/) { + + var $0 = $$.length - 1; + switch (yystate) { + case 1: + return $$[$0 - 1]; + break; + case 2: + this.$ = yy.prepareProgram($$[$0]); + break; + case 3: + this.$ = $$[$0]; + break; + case 4: + this.$ = $$[$0]; + break; + case 5: + this.$ = $$[$0]; + break; + case 6: + this.$ = $$[$0]; + break; + case 7: + this.$ = $$[$0]; + break; + case 8: + this.$ = $$[$0]; + break; + case 9: + this.$ = { + type: 'CommentStatement', + value: yy.stripComment($$[$0]), + strip: yy.stripFlags($$[$0], $$[$0]), + loc: yy.locInfo(this._$) + }; + + break; + case 10: + this.$ = { + type: 'ContentStatement', + original: $$[$0], + value: $$[$0], + loc: yy.locInfo(this._$) + }; + + break; + case 11: + this.$ = yy.prepareRawBlock($$[$0 - 2], $$[$0 - 1], $$[$0], this._$); + break; + case 12: + this.$ = { path: $$[$0 - 3], params: $$[$0 - 2], hash: $$[$0 - 1] }; + break; + case 13: + this.$ = yy.prepareBlock($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0], false, this._$); + break; + case 14: + this.$ = yy.prepareBlock($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0], true, this._$); + break; + case 15: + this.$ = { open: $$[$0 - 5], path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) }; + break; + case 16: + this.$ = { path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) }; + break; + case 17: + this.$ = { path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) }; + break; + case 18: + this.$ = { strip: yy.stripFlags($$[$0 - 1], $$[$0 - 1]), program: $$[$0] }; + break; + case 19: + var inverse = yy.prepareBlock($$[$0 - 2], $$[$0 - 1], $$[$0], $$[$0], false, this._$), + program = yy.prepareProgram([inverse], $$[$0 - 1].loc); + program.chained = true; + + this.$ = { strip: $$[$0 - 2].strip, program: program, chain: true }; + + break; + case 20: + this.$ = $$[$0]; + break; + case 21: + this.$ = { path: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 2], $$[$0]) }; + break; + case 22: + this.$ = yy.prepareMustache($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0 - 4], yy.stripFlags($$[$0 - 4], $$[$0]), this._$); + break; + case 23: + this.$ = yy.prepareMustache($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0 - 4], yy.stripFlags($$[$0 - 4], $$[$0]), this._$); + break; + case 24: + this.$ = { + type: 'PartialStatement', + name: $$[$0 - 3], + params: $$[$0 - 2], + hash: $$[$0 - 1], + indent: '', + strip: yy.stripFlags($$[$0 - 4], $$[$0]), + loc: yy.locInfo(this._$) + }; + + break; + case 25: + this.$ = yy.preparePartialBlock($$[$0 - 2], $$[$0 - 1], $$[$0], this._$); + break; + case 26: + this.$ = { path: $$[$0 - 3], params: $$[$0 - 2], hash: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 4], $$[$0]) }; + break; + case 27: + this.$ = $$[$0]; + break; + case 28: + this.$ = $$[$0]; + break; + case 29: + this.$ = { + type: 'SubExpression', + path: $$[$0 - 3], + params: $$[$0 - 2], + hash: $$[$0 - 1], + loc: yy.locInfo(this._$) + }; + + break; + case 30: + this.$ = { type: 'Hash', pairs: $$[$0], loc: yy.locInfo(this._$) }; + break; + case 31: + this.$ = { type: 'HashPair', key: yy.id($$[$0 - 2]), value: $$[$0], loc: yy.locInfo(this._$) }; + break; + case 32: + this.$ = yy.id($$[$0 - 1]); + break; + case 33: + this.$ = $$[$0]; + break; + case 34: + this.$ = $$[$0]; + break; + case 35: + this.$ = { type: 'StringLiteral', value: $$[$0], original: $$[$0], loc: yy.locInfo(this._$) }; + break; + case 36: + this.$ = { type: 'NumberLiteral', value: Number($$[$0]), original: Number($$[$0]), loc: yy.locInfo(this._$) }; + break; + case 37: + this.$ = { type: 'BooleanLiteral', value: $$[$0] === 'true', original: $$[$0] === 'true', loc: yy.locInfo(this._$) }; + break; + case 38: + this.$ = { type: 'UndefinedLiteral', original: undefined, value: undefined, loc: yy.locInfo(this._$) }; + break; + case 39: + this.$ = { type: 'NullLiteral', original: null, value: null, loc: yy.locInfo(this._$) }; + break; + case 40: + this.$ = $$[$0]; + break; + case 41: + this.$ = $$[$0]; + break; + case 42: + this.$ = yy.preparePath(true, $$[$0], this._$); + break; + case 43: + this.$ = yy.preparePath(false, $$[$0], this._$); + break; + case 44: + $$[$0 - 2].push({ part: yy.id($$[$0]), original: $$[$0], separator: $$[$0 - 1] });this.$ = $$[$0 - 2]; + break; + case 45: + this.$ = [{ part: yy.id($$[$0]), original: $$[$0] }]; + break; + case 46: + this.$ = []; + break; + case 47: + $$[$0 - 1].push($$[$0]); + break; + case 48: + this.$ = [$$[$0]]; + break; + case 49: + $$[$0 - 1].push($$[$0]); + break; + case 50: + this.$ = []; + break; + case 51: + $$[$0 - 1].push($$[$0]); + break; + case 58: + this.$ = []; + break; + case 59: + $$[$0 - 1].push($$[$0]); + break; + case 64: + this.$ = []; + break; + case 65: + $$[$0 - 1].push($$[$0]); + break; + case 70: + this.$ = []; + break; + case 71: + $$[$0 - 1].push($$[$0]); + break; + case 78: + this.$ = []; + break; + case 79: + $$[$0 - 1].push($$[$0]); + break; + case 82: + this.$ = []; + break; + case 83: + $$[$0 - 1].push($$[$0]); + break; + case 86: + this.$ = []; + break; + case 87: + $$[$0 - 1].push($$[$0]); + break; + case 90: + this.$ = []; + break; + case 91: + $$[$0 - 1].push($$[$0]); + break; + case 94: + this.$ = []; + break; + case 95: + $$[$0 - 1].push($$[$0]); + break; + case 98: + this.$ = [$$[$0]]; + break; + case 99: + $$[$0 - 1].push($$[$0]); + break; + case 100: + this.$ = [$$[$0]]; + break; + case 101: + $$[$0 - 1].push($$[$0]); + break; + } + }, + table: [{ 3: 1, 4: 2, 5: [2, 46], 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 1: [3] }, { 5: [1, 4] }, { 5: [2, 2], 7: 5, 8: 6, 9: 7, 10: 8, 11: 9, 12: 10, 13: 11, 14: [1, 12], 15: [1, 20], 16: 17, 19: [1, 23], 24: 15, 27: 16, 29: [1, 21], 34: [1, 22], 39: [2, 2], 44: [2, 2], 47: [2, 2], 48: [1, 13], 51: [1, 14], 55: [1, 18], 59: 19, 60: [1, 24] }, { 1: [2, 1] }, { 5: [2, 47], 14: [2, 47], 15: [2, 47], 19: [2, 47], 29: [2, 47], 34: [2, 47], 39: [2, 47], 44: [2, 47], 47: [2, 47], 48: [2, 47], 51: [2, 47], 55: [2, 47], 60: [2, 47] }, { 5: [2, 3], 14: [2, 3], 15: [2, 3], 19: [2, 3], 29: [2, 3], 34: [2, 3], 39: [2, 3], 44: [2, 3], 47: [2, 3], 48: [2, 3], 51: [2, 3], 55: [2, 3], 60: [2, 3] }, { 5: [2, 4], 14: [2, 4], 15: [2, 4], 19: [2, 4], 29: [2, 4], 34: [2, 4], 39: [2, 4], 44: [2, 4], 47: [2, 4], 48: [2, 4], 51: [2, 4], 55: [2, 4], 60: [2, 4] }, { 5: [2, 5], 14: [2, 5], 15: [2, 5], 19: [2, 5], 29: [2, 5], 34: [2, 5], 39: [2, 5], 44: [2, 5], 47: [2, 5], 48: [2, 5], 51: [2, 5], 55: [2, 5], 60: [2, 5] }, { 5: [2, 6], 14: [2, 6], 15: [2, 6], 19: [2, 6], 29: [2, 6], 34: [2, 6], 39: [2, 6], 44: [2, 6], 47: [2, 6], 48: [2, 6], 51: [2, 6], 55: [2, 6], 60: [2, 6] }, { 5: [2, 7], 14: [2, 7], 15: [2, 7], 19: [2, 7], 29: [2, 7], 34: [2, 7], 39: [2, 7], 44: [2, 7], 47: [2, 7], 48: [2, 7], 51: [2, 7], 55: [2, 7], 60: [2, 7] }, { 5: [2, 8], 14: [2, 8], 15: [2, 8], 19: [2, 8], 29: [2, 8], 34: [2, 8], 39: [2, 8], 44: [2, 8], 47: [2, 8], 48: [2, 8], 51: [2, 8], 55: [2, 8], 60: [2, 8] }, { 5: [2, 9], 14: [2, 9], 15: [2, 9], 19: [2, 9], 29: [2, 9], 34: [2, 9], 39: [2, 9], 44: [2, 9], 47: [2, 9], 48: [2, 9], 51: [2, 9], 55: [2, 9], 60: [2, 9] }, { 20: 25, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 36, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 37, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 39: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 4: 38, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 13: 40, 15: [1, 20], 17: 39 }, { 20: 42, 56: 41, 64: 43, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 45, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 5: [2, 10], 14: [2, 10], 15: [2, 10], 18: [2, 10], 19: [2, 10], 29: [2, 10], 34: [2, 10], 39: [2, 10], 44: [2, 10], 47: [2, 10], 48: [2, 10], 51: [2, 10], 55: [2, 10], 60: [2, 10] }, { 20: 46, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 47, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 48, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 42, 56: 49, 64: 43, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [2, 78], 49: 50, 65: [2, 78], 72: [2, 78], 80: [2, 78], 81: [2, 78], 82: [2, 78], 83: [2, 78], 84: [2, 78], 85: [2, 78] }, { 23: [2, 33], 33: [2, 33], 54: [2, 33], 65: [2, 33], 68: [2, 33], 72: [2, 33], 75: [2, 33], 80: [2, 33], 81: [2, 33], 82: [2, 33], 83: [2, 33], 84: [2, 33], 85: [2, 33] }, { 23: [2, 34], 33: [2, 34], 54: [2, 34], 65: [2, 34], 68: [2, 34], 72: [2, 34], 75: [2, 34], 80: [2, 34], 81: [2, 34], 82: [2, 34], 83: [2, 34], 84: [2, 34], 85: [2, 34] }, { 23: [2, 35], 33: [2, 35], 54: [2, 35], 65: [2, 35], 68: [2, 35], 72: [2, 35], 75: [2, 35], 80: [2, 35], 81: [2, 35], 82: [2, 35], 83: [2, 35], 84: [2, 35], 85: [2, 35] }, { 23: [2, 36], 33: [2, 36], 54: [2, 36], 65: [2, 36], 68: [2, 36], 72: [2, 36], 75: [2, 36], 80: [2, 36], 81: [2, 36], 82: [2, 36], 83: [2, 36], 84: [2, 36], 85: [2, 36] }, { 23: [2, 37], 33: [2, 37], 54: [2, 37], 65: [2, 37], 68: [2, 37], 72: [2, 37], 75: [2, 37], 80: [2, 37], 81: [2, 37], 82: [2, 37], 83: [2, 37], 84: [2, 37], 85: [2, 37] }, { 23: [2, 38], 33: [2, 38], 54: [2, 38], 65: [2, 38], 68: [2, 38], 72: [2, 38], 75: [2, 38], 80: [2, 38], 81: [2, 38], 82: [2, 38], 83: [2, 38], 84: [2, 38], 85: [2, 38] }, { 23: [2, 39], 33: [2, 39], 54: [2, 39], 65: [2, 39], 68: [2, 39], 72: [2, 39], 75: [2, 39], 80: [2, 39], 81: [2, 39], 82: [2, 39], 83: [2, 39], 84: [2, 39], 85: [2, 39] }, { 23: [2, 43], 33: [2, 43], 54: [2, 43], 65: [2, 43], 68: [2, 43], 72: [2, 43], 75: [2, 43], 80: [2, 43], 81: [2, 43], 82: [2, 43], 83: [2, 43], 84: [2, 43], 85: [2, 43], 87: [1, 51] }, { 72: [1, 35], 86: 52 }, { 23: [2, 45], 33: [2, 45], 54: [2, 45], 65: [2, 45], 68: [2, 45], 72: [2, 45], 75: [2, 45], 80: [2, 45], 81: [2, 45], 82: [2, 45], 83: [2, 45], 84: [2, 45], 85: [2, 45], 87: [2, 45] }, { 52: 53, 54: [2, 82], 65: [2, 82], 72: [2, 82], 80: [2, 82], 81: [2, 82], 82: [2, 82], 83: [2, 82], 84: [2, 82], 85: [2, 82] }, { 25: 54, 38: 56, 39: [1, 58], 43: 57, 44: [1, 59], 45: 55, 47: [2, 54] }, { 28: 60, 43: 61, 44: [1, 59], 47: [2, 56] }, { 13: 63, 15: [1, 20], 18: [1, 62] }, { 15: [2, 48], 18: [2, 48] }, { 33: [2, 86], 57: 64, 65: [2, 86], 72: [2, 86], 80: [2, 86], 81: [2, 86], 82: [2, 86], 83: [2, 86], 84: [2, 86], 85: [2, 86] }, { 33: [2, 40], 65: [2, 40], 72: [2, 40], 80: [2, 40], 81: [2, 40], 82: [2, 40], 83: [2, 40], 84: [2, 40], 85: [2, 40] }, { 33: [2, 41], 65: [2, 41], 72: [2, 41], 80: [2, 41], 81: [2, 41], 82: [2, 41], 83: [2, 41], 84: [2, 41], 85: [2, 41] }, { 20: 65, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 26: 66, 47: [1, 67] }, { 30: 68, 33: [2, 58], 65: [2, 58], 72: [2, 58], 75: [2, 58], 80: [2, 58], 81: [2, 58], 82: [2, 58], 83: [2, 58], 84: [2, 58], 85: [2, 58] }, { 33: [2, 64], 35: 69, 65: [2, 64], 72: [2, 64], 75: [2, 64], 80: [2, 64], 81: [2, 64], 82: [2, 64], 83: [2, 64], 84: [2, 64], 85: [2, 64] }, { 21: 70, 23: [2, 50], 65: [2, 50], 72: [2, 50], 80: [2, 50], 81: [2, 50], 82: [2, 50], 83: [2, 50], 84: [2, 50], 85: [2, 50] }, { 33: [2, 90], 61: 71, 65: [2, 90], 72: [2, 90], 80: [2, 90], 81: [2, 90], 82: [2, 90], 83: [2, 90], 84: [2, 90], 85: [2, 90] }, { 20: 75, 33: [2, 80], 50: 72, 63: 73, 64: 76, 65: [1, 44], 69: 74, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 72: [1, 80] }, { 23: [2, 42], 33: [2, 42], 54: [2, 42], 65: [2, 42], 68: [2, 42], 72: [2, 42], 75: [2, 42], 80: [2, 42], 81: [2, 42], 82: [2, 42], 83: [2, 42], 84: [2, 42], 85: [2, 42], 87: [1, 51] }, { 20: 75, 53: 81, 54: [2, 84], 63: 82, 64: 76, 65: [1, 44], 69: 83, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 26: 84, 47: [1, 67] }, { 47: [2, 55] }, { 4: 85, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 39: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 47: [2, 20] }, { 20: 86, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 87, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 26: 88, 47: [1, 67] }, { 47: [2, 57] }, { 5: [2, 11], 14: [2, 11], 15: [2, 11], 19: [2, 11], 29: [2, 11], 34: [2, 11], 39: [2, 11], 44: [2, 11], 47: [2, 11], 48: [2, 11], 51: [2, 11], 55: [2, 11], 60: [2, 11] }, { 15: [2, 49], 18: [2, 49] }, { 20: 75, 33: [2, 88], 58: 89, 63: 90, 64: 76, 65: [1, 44], 69: 91, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 65: [2, 94], 66: 92, 68: [2, 94], 72: [2, 94], 80: [2, 94], 81: [2, 94], 82: [2, 94], 83: [2, 94], 84: [2, 94], 85: [2, 94] }, { 5: [2, 25], 14: [2, 25], 15: [2, 25], 19: [2, 25], 29: [2, 25], 34: [2, 25], 39: [2, 25], 44: [2, 25], 47: [2, 25], 48: [2, 25], 51: [2, 25], 55: [2, 25], 60: [2, 25] }, { 20: 93, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 31: 94, 33: [2, 60], 63: 95, 64: 76, 65: [1, 44], 69: 96, 70: 77, 71: 78, 72: [1, 79], 75: [2, 60], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 33: [2, 66], 36: 97, 63: 98, 64: 76, 65: [1, 44], 69: 99, 70: 77, 71: 78, 72: [1, 79], 75: [2, 66], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 22: 100, 23: [2, 52], 63: 101, 64: 76, 65: [1, 44], 69: 102, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 33: [2, 92], 62: 103, 63: 104, 64: 76, 65: [1, 44], 69: 105, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [1, 106] }, { 33: [2, 79], 65: [2, 79], 72: [2, 79], 80: [2, 79], 81: [2, 79], 82: [2, 79], 83: [2, 79], 84: [2, 79], 85: [2, 79] }, { 33: [2, 81] }, { 23: [2, 27], 33: [2, 27], 54: [2, 27], 65: [2, 27], 68: [2, 27], 72: [2, 27], 75: [2, 27], 80: [2, 27], 81: [2, 27], 82: [2, 27], 83: [2, 27], 84: [2, 27], 85: [2, 27] }, { 23: [2, 28], 33: [2, 28], 54: [2, 28], 65: [2, 28], 68: [2, 28], 72: [2, 28], 75: [2, 28], 80: [2, 28], 81: [2, 28], 82: [2, 28], 83: [2, 28], 84: [2, 28], 85: [2, 28] }, { 23: [2, 30], 33: [2, 30], 54: [2, 30], 68: [2, 30], 71: 107, 72: [1, 108], 75: [2, 30] }, { 23: [2, 98], 33: [2, 98], 54: [2, 98], 68: [2, 98], 72: [2, 98], 75: [2, 98] }, { 23: [2, 45], 33: [2, 45], 54: [2, 45], 65: [2, 45], 68: [2, 45], 72: [2, 45], 73: [1, 109], 75: [2, 45], 80: [2, 45], 81: [2, 45], 82: [2, 45], 83: [2, 45], 84: [2, 45], 85: [2, 45], 87: [2, 45] }, { 23: [2, 44], 33: [2, 44], 54: [2, 44], 65: [2, 44], 68: [2, 44], 72: [2, 44], 75: [2, 44], 80: [2, 44], 81: [2, 44], 82: [2, 44], 83: [2, 44], 84: [2, 44], 85: [2, 44], 87: [2, 44] }, { 54: [1, 110] }, { 54: [2, 83], 65: [2, 83], 72: [2, 83], 80: [2, 83], 81: [2, 83], 82: [2, 83], 83: [2, 83], 84: [2, 83], 85: [2, 83] }, { 54: [2, 85] }, { 5: [2, 13], 14: [2, 13], 15: [2, 13], 19: [2, 13], 29: [2, 13], 34: [2, 13], 39: [2, 13], 44: [2, 13], 47: [2, 13], 48: [2, 13], 51: [2, 13], 55: [2, 13], 60: [2, 13] }, { 38: 56, 39: [1, 58], 43: 57, 44: [1, 59], 45: 112, 46: 111, 47: [2, 76] }, { 33: [2, 70], 40: 113, 65: [2, 70], 72: [2, 70], 75: [2, 70], 80: [2, 70], 81: [2, 70], 82: [2, 70], 83: [2, 70], 84: [2, 70], 85: [2, 70] }, { 47: [2, 18] }, { 5: [2, 14], 14: [2, 14], 15: [2, 14], 19: [2, 14], 29: [2, 14], 34: [2, 14], 39: [2, 14], 44: [2, 14], 47: [2, 14], 48: [2, 14], 51: [2, 14], 55: [2, 14], 60: [2, 14] }, { 33: [1, 114] }, { 33: [2, 87], 65: [2, 87], 72: [2, 87], 80: [2, 87], 81: [2, 87], 82: [2, 87], 83: [2, 87], 84: [2, 87], 85: [2, 87] }, { 33: [2, 89] }, { 20: 75, 63: 116, 64: 76, 65: [1, 44], 67: 115, 68: [2, 96], 69: 117, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [1, 118] }, { 32: 119, 33: [2, 62], 74: 120, 75: [1, 121] }, { 33: [2, 59], 65: [2, 59], 72: [2, 59], 75: [2, 59], 80: [2, 59], 81: [2, 59], 82: [2, 59], 83: [2, 59], 84: [2, 59], 85: [2, 59] }, { 33: [2, 61], 75: [2, 61] }, { 33: [2, 68], 37: 122, 74: 123, 75: [1, 121] }, { 33: [2, 65], 65: [2, 65], 72: [2, 65], 75: [2, 65], 80: [2, 65], 81: [2, 65], 82: [2, 65], 83: [2, 65], 84: [2, 65], 85: [2, 65] }, { 33: [2, 67], 75: [2, 67] }, { 23: [1, 124] }, { 23: [2, 51], 65: [2, 51], 72: [2, 51], 80: [2, 51], 81: [2, 51], 82: [2, 51], 83: [2, 51], 84: [2, 51], 85: [2, 51] }, { 23: [2, 53] }, { 33: [1, 125] }, { 33: [2, 91], 65: [2, 91], 72: [2, 91], 80: [2, 91], 81: [2, 91], 82: [2, 91], 83: [2, 91], 84: [2, 91], 85: [2, 91] }, { 33: [2, 93] }, { 5: [2, 22], 14: [2, 22], 15: [2, 22], 19: [2, 22], 29: [2, 22], 34: [2, 22], 39: [2, 22], 44: [2, 22], 47: [2, 22], 48: [2, 22], 51: [2, 22], 55: [2, 22], 60: [2, 22] }, { 23: [2, 99], 33: [2, 99], 54: [2, 99], 68: [2, 99], 72: [2, 99], 75: [2, 99] }, { 73: [1, 109] }, { 20: 75, 63: 126, 64: 76, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 5: [2, 23], 14: [2, 23], 15: [2, 23], 19: [2, 23], 29: [2, 23], 34: [2, 23], 39: [2, 23], 44: [2, 23], 47: [2, 23], 48: [2, 23], 51: [2, 23], 55: [2, 23], 60: [2, 23] }, { 47: [2, 19] }, { 47: [2, 77] }, { 20: 75, 33: [2, 72], 41: 127, 63: 128, 64: 76, 65: [1, 44], 69: 129, 70: 77, 71: 78, 72: [1, 79], 75: [2, 72], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 5: [2, 24], 14: [2, 24], 15: [2, 24], 19: [2, 24], 29: [2, 24], 34: [2, 24], 39: [2, 24], 44: [2, 24], 47: [2, 24], 48: [2, 24], 51: [2, 24], 55: [2, 24], 60: [2, 24] }, { 68: [1, 130] }, { 65: [2, 95], 68: [2, 95], 72: [2, 95], 80: [2, 95], 81: [2, 95], 82: [2, 95], 83: [2, 95], 84: [2, 95], 85: [2, 95] }, { 68: [2, 97] }, { 5: [2, 21], 14: [2, 21], 15: [2, 21], 19: [2, 21], 29: [2, 21], 34: [2, 21], 39: [2, 21], 44: [2, 21], 47: [2, 21], 48: [2, 21], 51: [2, 21], 55: [2, 21], 60: [2, 21] }, { 33: [1, 131] }, { 33: [2, 63] }, { 72: [1, 133], 76: 132 }, { 33: [1, 134] }, { 33: [2, 69] }, { 15: [2, 12] }, { 14: [2, 26], 15: [2, 26], 19: [2, 26], 29: [2, 26], 34: [2, 26], 47: [2, 26], 48: [2, 26], 51: [2, 26], 55: [2, 26], 60: [2, 26] }, { 23: [2, 31], 33: [2, 31], 54: [2, 31], 68: [2, 31], 72: [2, 31], 75: [2, 31] }, { 33: [2, 74], 42: 135, 74: 136, 75: [1, 121] }, { 33: [2, 71], 65: [2, 71], 72: [2, 71], 75: [2, 71], 80: [2, 71], 81: [2, 71], 82: [2, 71], 83: [2, 71], 84: [2, 71], 85: [2, 71] }, { 33: [2, 73], 75: [2, 73] }, { 23: [2, 29], 33: [2, 29], 54: [2, 29], 65: [2, 29], 68: [2, 29], 72: [2, 29], 75: [2, 29], 80: [2, 29], 81: [2, 29], 82: [2, 29], 83: [2, 29], 84: [2, 29], 85: [2, 29] }, { 14: [2, 15], 15: [2, 15], 19: [2, 15], 29: [2, 15], 34: [2, 15], 39: [2, 15], 44: [2, 15], 47: [2, 15], 48: [2, 15], 51: [2, 15], 55: [2, 15], 60: [2, 15] }, { 72: [1, 138], 77: [1, 137] }, { 72: [2, 100], 77: [2, 100] }, { 14: [2, 16], 15: [2, 16], 19: [2, 16], 29: [2, 16], 34: [2, 16], 44: [2, 16], 47: [2, 16], 48: [2, 16], 51: [2, 16], 55: [2, 16], 60: [2, 16] }, { 33: [1, 139] }, { 33: [2, 75] }, { 33: [2, 32] }, { 72: [2, 101], 77: [2, 101] }, { 14: [2, 17], 15: [2, 17], 19: [2, 17], 29: [2, 17], 34: [2, 17], 39: [2, 17], 44: [2, 17], 47: [2, 17], 48: [2, 17], 51: [2, 17], 55: [2, 17], 60: [2, 17] }], + defaultActions: { 4: [2, 1], 55: [2, 55], 57: [2, 20], 61: [2, 57], 74: [2, 81], 83: [2, 85], 87: [2, 18], 91: [2, 89], 102: [2, 53], 105: [2, 93], 111: [2, 19], 112: [2, 77], 117: [2, 97], 120: [2, 63], 123: [2, 69], 124: [2, 12], 136: [2, 75], 137: [2, 32] }, + parseError: function parseError(str, hash) { + throw new Error(str); + }, + parse: function parse(input) { + var self = this, + stack = [0], + vstack = [null], + lstack = [], + table = this.table, + yytext = "", + yylineno = 0, + yyleng = 0, + recovering = 0, + TERROR = 2, + EOF = 1; + this.lexer.setInput(input); + this.lexer.yy = this.yy; + this.yy.lexer = this.lexer; + this.yy.parser = this; + if (typeof this.lexer.yylloc == "undefined") this.lexer.yylloc = {}; + var yyloc = this.lexer.yylloc; + lstack.push(yyloc); + var ranges = this.lexer.options && this.lexer.options.ranges; + if (typeof this.yy.parseError === "function") this.parseError = this.yy.parseError; + function popStack(n) { + stack.length = stack.length - 2 * n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + function lex() { + var token; + token = self.lexer.lex() || 1; + if (typeof token !== "number") { + token = self.symbols_[token] || token; + } + return token; + } + var symbol, + preErrorSymbol, + state, + action, + a, + r, + yyval = {}, + p, + len, + newState, + expected; + while (true) { + state = stack[stack.length - 1]; + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol === null || typeof symbol == "undefined") { + symbol = lex(); + } + action = table[state] && table[state][symbol]; + } + if (typeof action === "undefined" || !action.length || !action[0]) { + var errStr = ""; + if (!recovering) { + expected = []; + for (p in table[state]) if (this.terminals_[p] && p > 2) { + expected.push("'" + this.terminals_[p] + "'"); + } + if (this.lexer.showPosition) { + errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; + } else { + errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1 ? "end of input" : "'" + (this.terminals_[symbol] || symbol) + "'"); + } + this.parseError(errStr, { text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected }); + } + } + if (action[0] instanceof Array && action.length > 1) { + throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); + } + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) recovering--; + } else { + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = { first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column }; + if (ranges) { + yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; + } + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); + if (typeof r !== "undefined") { + return r; + } + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); + } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; + } + } + return true; + } + }; + /* Jison generated lexer */ + var lexer = (function () { + var lexer = { EOF: 1, + parseError: function parseError(str, hash) { + if (this.yy.parser) { + this.yy.parser.parseError(str, hash); + } else { + throw new Error(str); + } + }, + setInput: function setInput(input) { + this._input = input; + this._more = this._less = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = { first_line: 1, first_column: 0, last_line: 1, last_column: 0 }; + if (this.options.ranges) this.yylloc.range = [0, 0]; + this.offset = 0; + return this; + }, + input: function input() { + var ch = this._input[0]; + this.yytext += ch; + this.yyleng++; + this.offset++; + this.match += ch; + this.matched += ch; + var lines = ch.match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno++; + this.yylloc.last_line++; + } else { + this.yylloc.last_column++; + } + if (this.options.ranges) this.yylloc.range[1]++; + + this._input = this._input.slice(1); + return ch; + }, + unput: function unput(ch) { + var len = ch.length; + var lines = ch.split(/(?:\r\n?|\n)/g); + + this._input = ch + this._input; + this.yytext = this.yytext.substr(0, this.yytext.length - len - 1); + //this.yyleng -= len; + this.offset -= len; + var oldLines = this.match.split(/(?:\r\n?|\n)/g); + this.match = this.match.substr(0, this.match.length - 1); + this.matched = this.matched.substr(0, this.matched.length - 1); + + if (lines.length - 1) this.yylineno -= lines.length - 1; + var r = this.yylloc.range; + + this.yylloc = { first_line: this.yylloc.first_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.first_column, + last_column: lines ? (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length : this.yylloc.first_column - len + }; + + if (this.options.ranges) { + this.yylloc.range = [r[0], r[0] + this.yyleng - len]; + } + return this; + }, + more: function more() { + this._more = true; + return this; + }, + less: function less(n) { + this.unput(this.match.slice(n)); + }, + pastInput: function pastInput() { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...' : '') + past.substr(-20).replace(/\n/g, ""); + }, + upcomingInput: function upcomingInput() { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20 - next.length); + } + return (next.substr(0, 20) + (next.length > 20 ? '...' : '')).replace(/\n/g, ""); + }, + showPosition: function showPosition() { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c + "^"; + }, + next: function next() { + if (this.done) { + return this.EOF; + } + if (!this._input) this.done = true; + + var token, match, tempMatch, index, col, lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i = 0; i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (!this.options.flex) break; + } + } + if (match) { + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) this.yylineno += lines.length; + this.yylloc = { first_line: this.yylloc.last_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.last_column, + last_column: lines ? lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length }; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[index], this.conditionStack[this.conditionStack.length - 1]); + if (this.done && this._input) this.done = false; + if (token) return token;else return; + } + if (this._input === "") { + return this.EOF; + } else { + return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), { text: "", token: null, line: this.yylineno }); + } + }, + lex: function lex() { + var r = this.next(); + if (typeof r !== 'undefined') { + return r; + } else { + return this.lex(); + } + }, + begin: function begin(condition) { + this.conditionStack.push(condition); + }, + popState: function popState() { + return this.conditionStack.pop(); + }, + _currentRules: function _currentRules() { + return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules; + }, + topState: function topState() { + return this.conditionStack[this.conditionStack.length - 2]; + }, + pushState: function begin(condition) { + this.begin(condition); + } }; + lexer.options = {}; + lexer.performAction = function anonymous(yy, yy_, $avoiding_name_collisions, YY_START + /**/) { + + function strip(start, end) { + return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng - end); + } + + var YYSTATE = YY_START; + switch ($avoiding_name_collisions) { + case 0: + if (yy_.yytext.slice(-2) === "\\\\") { + strip(0, 1); + this.begin("mu"); + } else if (yy_.yytext.slice(-1) === "\\") { + strip(0, 1); + this.begin("emu"); + } else { + this.begin("mu"); + } + if (yy_.yytext) return 15; + + break; + case 1: + return 15; + break; + case 2: + this.popState(); + return 15; + + break; + case 3: + this.begin('raw');return 15; + break; + case 4: + this.popState(); + // Should be using `this.topState()` below, but it currently + // returns the second top instead of the first top. Opened an + // issue about it at https://github.com/zaach/jison/issues/291 + if (this.conditionStack[this.conditionStack.length - 1] === 'raw') { + return 15; + } else { + yy_.yytext = yy_.yytext.substr(5, yy_.yyleng - 9); + return 'END_RAW_BLOCK'; + } + + break; + case 5: + return 15; + break; + case 6: + this.popState(); + return 14; + + break; + case 7: + return 65; + break; + case 8: + return 68; + break; + case 9: + return 19; + break; + case 10: + this.popState(); + this.begin('raw'); + return 23; + + break; + case 11: + return 55; + break; + case 12: + return 60; + break; + case 13: + return 29; + break; + case 14: + return 47; + break; + case 15: + this.popState();return 44; + break; + case 16: + this.popState();return 44; + break; + case 17: + return 34; + break; + case 18: + return 39; + break; + case 19: + return 51; + break; + case 20: + return 48; + break; + case 21: + this.unput(yy_.yytext); + this.popState(); + this.begin('com'); + + break; + case 22: + this.popState(); + return 14; + + break; + case 23: + return 48; + break; + case 24: + return 73; + break; + case 25: + return 72; + break; + case 26: + return 72; + break; + case 27: + return 87; + break; + case 28: + // ignore whitespace + break; + case 29: + this.popState();return 54; + break; + case 30: + this.popState();return 33; + break; + case 31: + yy_.yytext = strip(1, 2).replace(/\\"/g, '"');return 80; + break; + case 32: + yy_.yytext = strip(1, 2).replace(/\\'/g, "'");return 80; + break; + case 33: + return 85; + break; + case 34: + return 82; + break; + case 35: + return 82; + break; + case 36: + return 83; + break; + case 37: + return 84; + break; + case 38: + return 81; + break; + case 39: + return 75; + break; + case 40: + return 77; + break; + case 41: + return 72; + break; + case 42: + yy_.yytext = yy_.yytext.replace(/\\([\\\]])/g, '$1');return 72; + break; + case 43: + return 'INVALID'; + break; + case 44: + return 5; + break; + } + }; + lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/, /^(?:[^\x00]+)/, /^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/, /^(?:\{\{\{\{(?=[^/]))/, /^(?:\{\{\{\{\/[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.])\}\}\}\})/, /^(?:[^\x00]*?(?=(\{\{\{\{)))/, /^(?:[\s\S]*?--(~)?\}\})/, /^(?:\()/, /^(?:\))/, /^(?:\{\{\{\{)/, /^(?:\}\}\}\})/, /^(?:\{\{(~)?>)/, /^(?:\{\{(~)?#>)/, /^(?:\{\{(~)?#\*?)/, /^(?:\{\{(~)?\/)/, /^(?:\{\{(~)?\^\s*(~)?\}\})/, /^(?:\{\{(~)?\s*else\s*(~)?\}\})/, /^(?:\{\{(~)?\^)/, /^(?:\{\{(~)?\s*else\b)/, /^(?:\{\{(~)?\{)/, /^(?:\{\{(~)?&)/, /^(?:\{\{(~)?!--)/, /^(?:\{\{(~)?![\s\S]*?\}\})/, /^(?:\{\{(~)?\*?)/, /^(?:=)/, /^(?:\.\.)/, /^(?:\.(?=([=~}\s\/.)|])))/, /^(?:[\/.])/, /^(?:\s+)/, /^(?:\}(~)?\}\})/, /^(?:(~)?\}\})/, /^(?:"(\\["]|[^"])*")/, /^(?:'(\\[']|[^'])*')/, /^(?:@)/, /^(?:true(?=([~}\s)])))/, /^(?:false(?=([~}\s)])))/, /^(?:undefined(?=([~}\s)])))/, /^(?:null(?=([~}\s)])))/, /^(?:-?[0-9]+(?:\.[0-9]+)?(?=([~}\s)])))/, /^(?:as\s+\|)/, /^(?:\|)/, /^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)|]))))/, /^(?:\[(\\\]|[^\]])*\])/, /^(?:.)/, /^(?:$)/]; + lexer.conditions = { "mu": { "rules": [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44], "inclusive": false }, "emu": { "rules": [2], "inclusive": false }, "com": { "rules": [6], "inclusive": false }, "raw": { "rules": [3, 4, 5], "inclusive": false }, "INITIAL": { "rules": [0, 1, 44], "inclusive": true } }; + return lexer; + })(); + parser.lexer = lexer; + function Parser() { + this.yy = {}; + }Parser.prototype = parser;parser.Parser = Parser; + return new Parser(); + })();exports.__esModule = true; + exports['default'] = handlebars; + +/***/ }, +/* 24 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _visitor = __webpack_require__(25); + + var _visitor2 = _interopRequireDefault(_visitor); + + function WhitespaceControl() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + this.options = options; + } + WhitespaceControl.prototype = new _visitor2['default'](); + + WhitespaceControl.prototype.Program = function (program) { + var doStandalone = !this.options.ignoreStandalone; + + var isRoot = !this.isRootSeen; + this.isRootSeen = true; + + var body = program.body; + for (var i = 0, l = body.length; i < l; i++) { + var current = body[i], + strip = this.accept(current); + + if (!strip) { + continue; + } + + var _isPrevWhitespace = isPrevWhitespace(body, i, isRoot), + _isNextWhitespace = isNextWhitespace(body, i, isRoot), + openStandalone = strip.openStandalone && _isPrevWhitespace, + closeStandalone = strip.closeStandalone && _isNextWhitespace, + inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace; + + if (strip.close) { + omitRight(body, i, true); + } + if (strip.open) { + omitLeft(body, i, true); + } + + if (doStandalone && inlineStandalone) { + omitRight(body, i); + + if (omitLeft(body, i)) { + // If we are on a standalone node, save the indent info for partials + if (current.type === 'PartialStatement') { + // Pull out the whitespace from the final line + current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1]; + } + } + } + if (doStandalone && openStandalone) { + omitRight((current.program || current.inverse).body); + + // Strip out the previous content node if it's whitespace only + omitLeft(body, i); + } + if (doStandalone && closeStandalone) { + // Always strip the next node + omitRight(body, i); + + omitLeft((current.inverse || current.program).body); + } + } + + return program; + }; + + WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function (block) { + this.accept(block.program); + this.accept(block.inverse); + + // Find the inverse program that is involed with whitespace stripping. + var program = block.program || block.inverse, + inverse = block.program && block.inverse, + firstInverse = inverse, + lastInverse = inverse; + + if (inverse && inverse.chained) { + firstInverse = inverse.body[0].program; + + // Walk the inverse chain to find the last inverse that is actually in the chain. + while (lastInverse.chained) { + lastInverse = lastInverse.body[lastInverse.body.length - 1].program; + } + } + + var strip = { + open: block.openStrip.open, + close: block.closeStrip.close, + + // Determine the standalone candiacy. Basically flag our content as being possibly standalone + // so our parent can determine if we actually are standalone + openStandalone: isNextWhitespace(program.body), + closeStandalone: isPrevWhitespace((firstInverse || program).body) + }; + + if (block.openStrip.close) { + omitRight(program.body, null, true); + } + + if (inverse) { + var inverseStrip = block.inverseStrip; + + if (inverseStrip.open) { + omitLeft(program.body, null, true); + } + + if (inverseStrip.close) { + omitRight(firstInverse.body, null, true); + } + if (block.closeStrip.open) { + omitLeft(lastInverse.body, null, true); + } + + // Find standalone else statments + if (!this.options.ignoreStandalone && isPrevWhitespace(program.body) && isNextWhitespace(firstInverse.body)) { + omitLeft(program.body); + omitRight(firstInverse.body); + } + } else if (block.closeStrip.open) { + omitLeft(program.body, null, true); + } + + return strip; + }; + + WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function (mustache) { + return mustache.strip; + }; + + WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function (node) { + /* istanbul ignore next */ + var strip = node.strip || {}; + return { + inlineStandalone: true, + open: strip.open, + close: strip.close + }; + }; + + function isPrevWhitespace(body, i, isRoot) { + if (i === undefined) { + i = body.length; + } + + // Nodes that end with newlines are considered whitespace (but are special + // cased for strip operations) + var prev = body[i - 1], + sibling = body[i - 2]; + if (!prev) { + return isRoot; + } + + if (prev.type === 'ContentStatement') { + return (sibling || !isRoot ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test(prev.original); + } + } + function isNextWhitespace(body, i, isRoot) { + if (i === undefined) { + i = -1; + } + + var next = body[i + 1], + sibling = body[i + 2]; + if (!next) { + return isRoot; + } + + if (next.type === 'ContentStatement') { + return (sibling || !isRoot ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test(next.original); + } + } + + // Marks the node to the right of the position as omitted. + // I.e. {{foo}}' ' will mark the ' ' node as omitted. + // + // If i is undefined, then the first child will be marked as such. + // + // If mulitple is truthy then all whitespace will be stripped out until non-whitespace + // content is met. + function omitRight(body, i, multiple) { + var current = body[i == null ? 0 : i + 1]; + if (!current || current.type !== 'ContentStatement' || !multiple && current.rightStripped) { + return; + } + + var original = current.value; + current.value = current.value.replace(multiple ? /^\s+/ : /^[ \t]*\r?\n?/, ''); + current.rightStripped = current.value !== original; + } + + // Marks the node to the left of the position as omitted. + // I.e. ' '{{foo}} will mark the ' ' node as omitted. + // + // If i is undefined then the last child will be marked as such. + // + // If mulitple is truthy then all whitespace will be stripped out until non-whitespace + // content is met. + function omitLeft(body, i, multiple) { + var current = body[i == null ? body.length - 1 : i - 1]; + if (!current || current.type !== 'ContentStatement' || !multiple && current.leftStripped) { + return; + } + + // We omit the last node if it's whitespace only and not preceeded by a non-content node. + var original = current.value; + current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, ''); + current.leftStripped = current.value !== original; + return current.leftStripped; + } + + exports['default'] = WhitespaceControl; + module.exports = exports['default']; + +/***/ }, +/* 25 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + function Visitor() { + this.parents = []; + } + + Visitor.prototype = { + constructor: Visitor, + mutating: false, + + // Visits a given value. If mutating, will replace the value if necessary. + acceptKey: function acceptKey(node, name) { + var value = this.accept(node[name]); + if (this.mutating) { + // Hacky sanity check: This may have a few false positives for type for the helper + // methods but will generally do the right thing without a lot of overhead. + if (value && !Visitor.prototype[value.type]) { + throw new _exception2['default']('Unexpected node type "' + value.type + '" found when accepting ' + name + ' on ' + node.type); + } + node[name] = value; + } + }, + + // Performs an accept operation with added sanity check to ensure + // required keys are not removed. + acceptRequired: function acceptRequired(node, name) { + this.acceptKey(node, name); + + if (!node[name]) { + throw new _exception2['default'](node.type + ' requires ' + name); + } + }, + + // Traverses a given array. If mutating, empty respnses will be removed + // for child elements. + acceptArray: function acceptArray(array) { + for (var i = 0, l = array.length; i < l; i++) { + this.acceptKey(array, i); + + if (!array[i]) { + array.splice(i, 1); + i--; + l--; + } + } + }, + + accept: function accept(object) { + if (!object) { + return; + } + + /* istanbul ignore next: Sanity code */ + if (!this[object.type]) { + throw new _exception2['default']('Unknown type: ' + object.type, object); + } + + if (this.current) { + this.parents.unshift(this.current); + } + this.current = object; + + var ret = this[object.type](object); + + this.current = this.parents.shift(); + + if (!this.mutating || ret) { + return ret; + } else if (ret !== false) { + return object; + } + }, + + Program: function Program(program) { + this.acceptArray(program.body); + }, + + MustacheStatement: visitSubExpression, + Decorator: visitSubExpression, + + BlockStatement: visitBlock, + DecoratorBlock: visitBlock, + + PartialStatement: visitPartial, + PartialBlockStatement: function PartialBlockStatement(partial) { + visitPartial.call(this, partial); + + this.acceptKey(partial, 'program'); + }, + + ContentStatement: function ContentStatement() /* content */{}, + CommentStatement: function CommentStatement() /* comment */{}, + + SubExpression: visitSubExpression, + + PathExpression: function PathExpression() /* path */{}, + + StringLiteral: function StringLiteral() /* string */{}, + NumberLiteral: function NumberLiteral() /* number */{}, + BooleanLiteral: function BooleanLiteral() /* bool */{}, + UndefinedLiteral: function UndefinedLiteral() /* literal */{}, + NullLiteral: function NullLiteral() /* literal */{}, + + Hash: function Hash(hash) { + this.acceptArray(hash.pairs); + }, + HashPair: function HashPair(pair) { + this.acceptRequired(pair, 'value'); + } + }; + + function visitSubExpression(mustache) { + this.acceptRequired(mustache, 'path'); + this.acceptArray(mustache.params); + this.acceptKey(mustache, 'hash'); + } + function visitBlock(block) { + visitSubExpression.call(this, block); + + this.acceptKey(block, 'program'); + this.acceptKey(block, 'inverse'); + } + function visitPartial(partial) { + this.acceptRequired(partial, 'name'); + this.acceptArray(partial.params); + this.acceptKey(partial, 'hash'); + } + + exports['default'] = Visitor; + module.exports = exports['default']; + +/***/ }, +/* 26 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.SourceLocation = SourceLocation; + exports.id = id; + exports.stripFlags = stripFlags; + exports.stripComment = stripComment; + exports.preparePath = preparePath; + exports.prepareMustache = prepareMustache; + exports.prepareRawBlock = prepareRawBlock; + exports.prepareBlock = prepareBlock; + exports.prepareProgram = prepareProgram; + exports.preparePartialBlock = preparePartialBlock; + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + function validateClose(open, close) { + close = close.path ? close.path.original : close; + + if (open.path.original !== close) { + var errorNode = { loc: open.path.loc }; + + throw new _exception2['default'](open.path.original + " doesn't match " + close, errorNode); + } + } + + function SourceLocation(source, locInfo) { + this.source = source; + this.start = { + line: locInfo.first_line, + column: locInfo.first_column + }; + this.end = { + line: locInfo.last_line, + column: locInfo.last_column + }; + } + + function id(token) { + if (/^\[.*\]$/.test(token)) { + return token.substr(1, token.length - 2); + } else { + return token; + } + } + + function stripFlags(open, close) { + return { + open: open.charAt(2) === '~', + close: close.charAt(close.length - 3) === '~' + }; + } + + function stripComment(comment) { + return comment.replace(/^\{\{~?\!-?-?/, '').replace(/-?-?~?\}\}$/, ''); + } + + function preparePath(data, parts, loc) { + loc = this.locInfo(loc); + + var original = data ? '@' : '', + dig = [], + depth = 0, + depthString = ''; + + for (var i = 0, l = parts.length; i < l; i++) { + var part = parts[i].part, + + // If we have [] syntax then we do not treat path references as operators, + // i.e. foo.[this] resolves to approximately context.foo['this'] + isLiteral = parts[i].original !== part; + original += (parts[i].separator || '') + part; + + if (!isLiteral && (part === '..' || part === '.' || part === 'this')) { + if (dig.length > 0) { + throw new _exception2['default']('Invalid path: ' + original, { loc: loc }); + } else if (part === '..') { + depth++; + depthString += '../'; + } + } else { + dig.push(part); + } + } + + return { + type: 'PathExpression', + data: data, + depth: depth, + parts: dig, + original: original, + loc: loc + }; + } + + function prepareMustache(path, params, hash, open, strip, locInfo) { + // Must use charAt to support IE pre-10 + var escapeFlag = open.charAt(3) || open.charAt(2), + escaped = escapeFlag !== '{' && escapeFlag !== '&'; + + var decorator = /\*/.test(open); + return { + type: decorator ? 'Decorator' : 'MustacheStatement', + path: path, + params: params, + hash: hash, + escaped: escaped, + strip: strip, + loc: this.locInfo(locInfo) + }; + } + + function prepareRawBlock(openRawBlock, contents, close, locInfo) { + validateClose(openRawBlock, close); + + locInfo = this.locInfo(locInfo); + var program = { + type: 'Program', + body: contents, + strip: {}, + loc: locInfo + }; + + return { + type: 'BlockStatement', + path: openRawBlock.path, + params: openRawBlock.params, + hash: openRawBlock.hash, + program: program, + openStrip: {}, + inverseStrip: {}, + closeStrip: {}, + loc: locInfo + }; + } + + function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) { + if (close && close.path) { + validateClose(openBlock, close); + } + + var decorator = /\*/.test(openBlock.open); + + program.blockParams = openBlock.blockParams; + + var inverse = undefined, + inverseStrip = undefined; + + if (inverseAndProgram) { + if (decorator) { + throw new _exception2['default']('Unexpected inverse block on decorator', inverseAndProgram); + } + + if (inverseAndProgram.chain) { + inverseAndProgram.program.body[0].closeStrip = close.strip; + } + + inverseStrip = inverseAndProgram.strip; + inverse = inverseAndProgram.program; + } + + if (inverted) { + inverted = inverse; + inverse = program; + program = inverted; + } + + return { + type: decorator ? 'DecoratorBlock' : 'BlockStatement', + path: openBlock.path, + params: openBlock.params, + hash: openBlock.hash, + program: program, + inverse: inverse, + openStrip: openBlock.strip, + inverseStrip: inverseStrip, + closeStrip: close && close.strip, + loc: this.locInfo(locInfo) + }; + } + + function prepareProgram(statements, loc) { + if (!loc && statements.length) { + var firstLoc = statements[0].loc, + lastLoc = statements[statements.length - 1].loc; + + /* istanbul ignore else */ + if (firstLoc && lastLoc) { + loc = { + source: firstLoc.source, + start: { + line: firstLoc.start.line, + column: firstLoc.start.column + }, + end: { + line: lastLoc.end.line, + column: lastLoc.end.column + } + }; + } + } + + return { + type: 'Program', + body: statements, + strip: {}, + loc: loc + }; + } + + function preparePartialBlock(open, program, close, locInfo) { + validateClose(open, close); + + return { + type: 'PartialBlockStatement', + name: open.path, + params: open.params, + hash: open.hash, + program: program, + openStrip: open.strip, + closeStrip: close && close.strip, + loc: this.locInfo(locInfo) + }; + } + +/***/ }, +/* 27 */ +/***/ function(module, exports, __webpack_require__) { + + /* eslint-disable new-cap */ + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.Compiler = Compiler; + exports.precompile = precompile; + exports.compile = compile; + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + var _utils = __webpack_require__(5); + + var _ast = __webpack_require__(21); + + var _ast2 = _interopRequireDefault(_ast); + + var slice = [].slice; + + function Compiler() {} + + // the foundHelper register will disambiguate helper lookup from finding a + // function in a context. This is necessary for mustache compatibility, which + // requires that context functions in blocks are evaluated by blockHelperMissing, + // and then proceed as if the resulting value was provided to blockHelperMissing. + + Compiler.prototype = { + compiler: Compiler, + + equals: function equals(other) { + var len = this.opcodes.length; + if (other.opcodes.length !== len) { + return false; + } + + for (var i = 0; i < len; i++) { + var opcode = this.opcodes[i], + otherOpcode = other.opcodes[i]; + if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) { + return false; + } + } + + // We know that length is the same between the two arrays because they are directly tied + // to the opcode behavior above. + len = this.children.length; + for (var i = 0; i < len; i++) { + if (!this.children[i].equals(other.children[i])) { + return false; + } + } + + return true; + }, + + guid: 0, + + compile: function compile(program, options) { + this.sourceNode = []; + this.opcodes = []; + this.children = []; + this.options = options; + this.stringParams = options.stringParams; + this.trackIds = options.trackIds; + + options.blockParams = options.blockParams || []; + + // These changes will propagate to the other compiler components + var knownHelpers = options.knownHelpers; + options.knownHelpers = { + 'helperMissing': true, + 'blockHelperMissing': true, + 'each': true, + 'if': true, + 'unless': true, + 'with': true, + 'log': true, + 'lookup': true + }; + if (knownHelpers) { + for (var _name in knownHelpers) { + /* istanbul ignore else */ + if (_name in knownHelpers) { + options.knownHelpers[_name] = knownHelpers[_name]; + } + } + } + + return this.accept(program); + }, + + compileProgram: function compileProgram(program) { + var childCompiler = new this.compiler(), + // eslint-disable-line new-cap + result = childCompiler.compile(program, this.options), + guid = this.guid++; + + this.usePartial = this.usePartial || result.usePartial; + + this.children[guid] = result; + this.useDepths = this.useDepths || result.useDepths; + + return guid; + }, + + accept: function accept(node) { + /* istanbul ignore next: Sanity code */ + if (!this[node.type]) { + throw new _exception2['default']('Unknown type: ' + node.type, node); + } + + this.sourceNode.unshift(node); + var ret = this[node.type](node); + this.sourceNode.shift(); + return ret; + }, + + Program: function Program(program) { + this.options.blockParams.unshift(program.blockParams); + + var body = program.body, + bodyLength = body.length; + for (var i = 0; i < bodyLength; i++) { + this.accept(body[i]); + } + + this.options.blockParams.shift(); + + this.isSimple = bodyLength === 1; + this.blockParams = program.blockParams ? program.blockParams.length : 0; + + return this; + }, + + BlockStatement: function BlockStatement(block) { + transformLiteralToPath(block); + + var program = block.program, + inverse = block.inverse; + + program = program && this.compileProgram(program); + inverse = inverse && this.compileProgram(inverse); + + var type = this.classifySexpr(block); + + if (type === 'helper') { + this.helperSexpr(block, program, inverse); + } else if (type === 'simple') { + this.simpleSexpr(block); + + // now that the simple mustache is resolved, we need to + // evaluate it by executing `blockHelperMissing` + this.opcode('pushProgram', program); + this.opcode('pushProgram', inverse); + this.opcode('emptyHash'); + this.opcode('blockValue', block.path.original); + } else { + this.ambiguousSexpr(block, program, inverse); + + // now that the simple mustache is resolved, we need to + // evaluate it by executing `blockHelperMissing` + this.opcode('pushProgram', program); + this.opcode('pushProgram', inverse); + this.opcode('emptyHash'); + this.opcode('ambiguousBlockValue'); + } + + this.opcode('append'); + }, + + DecoratorBlock: function DecoratorBlock(decorator) { + var program = decorator.program && this.compileProgram(decorator.program); + var params = this.setupFullMustacheParams(decorator, program, undefined), + path = decorator.path; + + this.useDecorators = true; + this.opcode('registerDecorator', params.length, path.original); + }, + + PartialStatement: function PartialStatement(partial) { + this.usePartial = true; + + var program = partial.program; + if (program) { + program = this.compileProgram(partial.program); + } + + var params = partial.params; + if (params.length > 1) { + throw new _exception2['default']('Unsupported number of partial arguments: ' + params.length, partial); + } else if (!params.length) { + if (this.options.explicitPartialContext) { + this.opcode('pushLiteral', 'undefined'); + } else { + params.push({ type: 'PathExpression', parts: [], depth: 0 }); + } + } + + var partialName = partial.name.original, + isDynamic = partial.name.type === 'SubExpression'; + if (isDynamic) { + this.accept(partial.name); + } + + this.setupFullMustacheParams(partial, program, undefined, true); + + var indent = partial.indent || ''; + if (this.options.preventIndent && indent) { + this.opcode('appendContent', indent); + indent = ''; + } + + this.opcode('invokePartial', isDynamic, partialName, indent); + this.opcode('append'); + }, + PartialBlockStatement: function PartialBlockStatement(partialBlock) { + this.PartialStatement(partialBlock); + }, + + MustacheStatement: function MustacheStatement(mustache) { + this.SubExpression(mustache); + + if (mustache.escaped && !this.options.noEscape) { + this.opcode('appendEscaped'); + } else { + this.opcode('append'); + } + }, + Decorator: function Decorator(decorator) { + this.DecoratorBlock(decorator); + }, + + ContentStatement: function ContentStatement(content) { + if (content.value) { + this.opcode('appendContent', content.value); + } + }, + + CommentStatement: function CommentStatement() {}, + + SubExpression: function SubExpression(sexpr) { + transformLiteralToPath(sexpr); + var type = this.classifySexpr(sexpr); + + if (type === 'simple') { + this.simpleSexpr(sexpr); + } else if (type === 'helper') { + this.helperSexpr(sexpr); + } else { + this.ambiguousSexpr(sexpr); + } + }, + ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) { + var path = sexpr.path, + name = path.parts[0], + isBlock = program != null || inverse != null; + + this.opcode('getContext', path.depth); + + this.opcode('pushProgram', program); + this.opcode('pushProgram', inverse); + + path.strict = true; + this.accept(path); + + this.opcode('invokeAmbiguous', name, isBlock); + }, + + simpleSexpr: function simpleSexpr(sexpr) { + var path = sexpr.path; + path.strict = true; + this.accept(path); + this.opcode('resolvePossibleLambda'); + }, + + helperSexpr: function helperSexpr(sexpr, program, inverse) { + var params = this.setupFullMustacheParams(sexpr, program, inverse), + path = sexpr.path, + name = path.parts[0]; + + if (this.options.knownHelpers[name]) { + this.opcode('invokeKnownHelper', params.length, name); + } else if (this.options.knownHelpersOnly) { + throw new _exception2['default']('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr); + } else { + path.strict = true; + path.falsy = true; + + this.accept(path); + this.opcode('invokeHelper', params.length, path.original, _ast2['default'].helpers.simpleId(path)); + } + }, + + PathExpression: function PathExpression(path) { + this.addDepth(path.depth); + this.opcode('getContext', path.depth); + + var name = path.parts[0], + scoped = _ast2['default'].helpers.scopedId(path), + blockParamId = !path.depth && !scoped && this.blockParamIndex(name); + + if (blockParamId) { + this.opcode('lookupBlockParam', blockParamId, path.parts); + } else if (!name) { + // Context reference, i.e. `{{foo .}}` or `{{foo ..}}` + this.opcode('pushContext'); + } else if (path.data) { + this.options.data = true; + this.opcode('lookupData', path.depth, path.parts, path.strict); + } else { + this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped); + } + }, + + StringLiteral: function StringLiteral(string) { + this.opcode('pushString', string.value); + }, + + NumberLiteral: function NumberLiteral(number) { + this.opcode('pushLiteral', number.value); + }, + + BooleanLiteral: function BooleanLiteral(bool) { + this.opcode('pushLiteral', bool.value); + }, + + UndefinedLiteral: function UndefinedLiteral() { + this.opcode('pushLiteral', 'undefined'); + }, + + NullLiteral: function NullLiteral() { + this.opcode('pushLiteral', 'null'); + }, + + Hash: function Hash(hash) { + var pairs = hash.pairs, + i = 0, + l = pairs.length; + + this.opcode('pushHash'); + + for (; i < l; i++) { + this.pushParam(pairs[i].value); + } + while (i--) { + this.opcode('assignToHash', pairs[i].key); + } + this.opcode('popHash'); + }, + + // HELPERS + opcode: function opcode(name) { + this.opcodes.push({ opcode: name, args: slice.call(arguments, 1), loc: this.sourceNode[0].loc }); + }, + + addDepth: function addDepth(depth) { + if (!depth) { + return; + } + + this.useDepths = true; + }, + + classifySexpr: function classifySexpr(sexpr) { + var isSimple = _ast2['default'].helpers.simpleId(sexpr.path); + + var isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]); + + // a mustache is an eligible helper if: + // * its id is simple (a single part, not `this` or `..`) + var isHelper = !isBlockParam && _ast2['default'].helpers.helperExpression(sexpr); + + // if a mustache is an eligible helper but not a definite + // helper, it is ambiguous, and will be resolved in a later + // pass or at runtime. + var isEligible = !isBlockParam && (isHelper || isSimple); + + // if ambiguous, we can possibly resolve the ambiguity now + // An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc. + if (isEligible && !isHelper) { + var _name2 = sexpr.path.parts[0], + options = this.options; + + if (options.knownHelpers[_name2]) { + isHelper = true; + } else if (options.knownHelpersOnly) { + isEligible = false; + } + } + + if (isHelper) { + return 'helper'; + } else if (isEligible) { + return 'ambiguous'; + } else { + return 'simple'; + } + }, + + pushParams: function pushParams(params) { + for (var i = 0, l = params.length; i < l; i++) { + this.pushParam(params[i]); + } + }, + + pushParam: function pushParam(val) { + var value = val.value != null ? val.value : val.original || ''; + + if (this.stringParams) { + if (value.replace) { + value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.'); + } + + if (val.depth) { + this.addDepth(val.depth); + } + this.opcode('getContext', val.depth || 0); + this.opcode('pushStringParam', value, val.type); + + if (val.type === 'SubExpression') { + // SubExpressions get evaluated and passed in + // in string params mode. + this.accept(val); + } + } else { + if (this.trackIds) { + var blockParamIndex = undefined; + if (val.parts && !_ast2['default'].helpers.scopedId(val) && !val.depth) { + blockParamIndex = this.blockParamIndex(val.parts[0]); + } + if (blockParamIndex) { + var blockParamChild = val.parts.slice(1).join('.'); + this.opcode('pushId', 'BlockParam', blockParamIndex, blockParamChild); + } else { + value = val.original || value; + if (value.replace) { + value = value.replace(/^this(?:\.|$)/, '').replace(/^\.\//, '').replace(/^\.$/, ''); + } + + this.opcode('pushId', val.type, value); + } + } + this.accept(val); + } + }, + + setupFullMustacheParams: function setupFullMustacheParams(sexpr, program, inverse, omitEmpty) { + var params = sexpr.params; + this.pushParams(params); + + this.opcode('pushProgram', program); + this.opcode('pushProgram', inverse); + + if (sexpr.hash) { + this.accept(sexpr.hash); + } else { + this.opcode('emptyHash', omitEmpty); + } + + return params; + }, + + blockParamIndex: function blockParamIndex(name) { + for (var depth = 0, len = this.options.blockParams.length; depth < len; depth++) { + var blockParams = this.options.blockParams[depth], + param = blockParams && _utils.indexOf(blockParams, name); + if (blockParams && param >= 0) { + return [depth, param]; + } + } + } + }; + + function precompile(input, options, env) { + if (input == null || typeof input !== 'string' && input.type !== 'Program') { + throw new _exception2['default']('You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' + input); + } + + options = options || {}; + if (!('data' in options)) { + options.data = true; + } + if (options.compat) { + options.useDepths = true; + } + + var ast = env.parse(input, options), + environment = new env.Compiler().compile(ast, options); + return new env.JavaScriptCompiler().compile(environment, options); + } + + function compile(input, options, env) { + if (options === undefined) options = {}; + + if (input == null || typeof input !== 'string' && input.type !== 'Program') { + throw new _exception2['default']('You must pass a string or Handlebars AST to Handlebars.compile. You passed ' + input); + } + + if (!('data' in options)) { + options.data = true; + } + if (options.compat) { + options.useDepths = true; + } + + var compiled = undefined; + + function compileInput() { + var ast = env.parse(input, options), + environment = new env.Compiler().compile(ast, options), + templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true); + return env.template(templateSpec); + } + + // Template is only compiled on first use and cached after that point. + function ret(context, execOptions) { + if (!compiled) { + compiled = compileInput(); + } + return compiled.call(this, context, execOptions); + } + ret._setup = function (setupOptions) { + if (!compiled) { + compiled = compileInput(); + } + return compiled._setup(setupOptions); + }; + ret._child = function (i, data, blockParams, depths) { + if (!compiled) { + compiled = compileInput(); + } + return compiled._child(i, data, blockParams, depths); + }; + return ret; + } + + function argEquals(a, b) { + if (a === b) { + return true; + } + + if (_utils.isArray(a) && _utils.isArray(b) && a.length === b.length) { + for (var i = 0; i < a.length; i++) { + if (!argEquals(a[i], b[i])) { + return false; + } + } + return true; + } + } + + function transformLiteralToPath(sexpr) { + if (!sexpr.path.parts) { + var literal = sexpr.path; + // Casting to string here to make false and 0 literal values play nicely with the rest + // of the system. + sexpr.path = { + type: 'PathExpression', + data: false, + depth: 0, + parts: [literal.original + ''], + original: literal.original + '', + loc: literal.loc + }; + } + } + +/***/ }, +/* 28 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _base = __webpack_require__(4); + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + var _utils = __webpack_require__(5); + + var _codeGen = __webpack_require__(29); + + var _codeGen2 = _interopRequireDefault(_codeGen); + + function Literal(value) { + this.value = value; + } + + function JavaScriptCompiler() {} + + JavaScriptCompiler.prototype = { + // PUBLIC API: You can override these methods in a subclass to provide + // alternative compiled forms for name lookup and buffering semantics + nameLookup: function nameLookup(parent, name /* , type*/) { + if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { + return [parent, '.', name]; + } else { + return [parent, '[', JSON.stringify(name), ']']; + } + }, + depthedLookup: function depthedLookup(name) { + return [this.aliasable('container.lookup'), '(depths, "', name, '")']; + }, + + compilerInfo: function compilerInfo() { + var revision = _base.COMPILER_REVISION, + versions = _base.REVISION_CHANGES[revision]; + return [revision, versions]; + }, + + appendToBuffer: function appendToBuffer(source, location, explicit) { + // Force a source as this simplifies the merge logic. + if (!_utils.isArray(source)) { + source = [source]; + } + source = this.source.wrap(source, location); + + if (this.environment.isSimple) { + return ['return ', source, ';']; + } else if (explicit) { + // This is a case where the buffer operation occurs as a child of another + // construct, generally braces. We have to explicitly output these buffer + // operations to ensure that the emitted code goes in the correct location. + return ['buffer += ', source, ';']; + } else { + source.appendToBuffer = true; + return source; + } + }, + + initializeBuffer: function initializeBuffer() { + return this.quotedString(''); + }, + // END PUBLIC API + + compile: function compile(environment, options, context, asObject) { + this.environment = environment; + this.options = options; + this.stringParams = this.options.stringParams; + this.trackIds = this.options.trackIds; + this.precompile = !asObject; + + this.name = this.environment.name; + this.isChild = !!context; + this.context = context || { + decorators: [], + programs: [], + environments: [] + }; + + this.preamble(); + + this.stackSlot = 0; + this.stackVars = []; + this.aliases = {}; + this.registers = { list: [] }; + this.hashes = []; + this.compileStack = []; + this.inlineStack = []; + this.blockParams = []; + + this.compileChildren(environment, options); + + this.useDepths = this.useDepths || environment.useDepths || environment.useDecorators || this.options.compat; + this.useBlockParams = this.useBlockParams || environment.useBlockParams; + + var opcodes = environment.opcodes, + opcode = undefined, + firstLoc = undefined, + i = undefined, + l = undefined; + + for (i = 0, l = opcodes.length; i < l; i++) { + opcode = opcodes[i]; + + this.source.currentLocation = opcode.loc; + firstLoc = firstLoc || opcode.loc; + this[opcode.opcode].apply(this, opcode.args); + } + + // Flush any trailing content that might be pending. + this.source.currentLocation = firstLoc; + this.pushSource(''); + + /* istanbul ignore next */ + if (this.stackSlot || this.inlineStack.length || this.compileStack.length) { + throw new _exception2['default']('Compile completed with content left on stack'); + } + + if (!this.decorators.isEmpty()) { + this.useDecorators = true; + + this.decorators.prepend('var decorators = container.decorators;\n'); + this.decorators.push('return fn;'); + + if (asObject) { + this.decorators = Function.apply(this, ['fn', 'props', 'container', 'depth0', 'data', 'blockParams', 'depths', this.decorators.merge()]); + } else { + this.decorators.prepend('function(fn, props, container, depth0, data, blockParams, depths) {\n'); + this.decorators.push('}\n'); + this.decorators = this.decorators.merge(); + } + } else { + this.decorators = undefined; + } + + var fn = this.createFunctionContext(asObject); + if (!this.isChild) { + var ret = { + compiler: this.compilerInfo(), + main: fn + }; + + if (this.decorators) { + ret.main_d = this.decorators; // eslint-disable-line camelcase + ret.useDecorators = true; + } + + var _context = this.context; + var programs = _context.programs; + var decorators = _context.decorators; + + for (i = 0, l = programs.length; i < l; i++) { + if (programs[i]) { + ret[i] = programs[i]; + if (decorators[i]) { + ret[i + '_d'] = decorators[i]; + ret.useDecorators = true; + } + } + } + + if (this.environment.usePartial) { + ret.usePartial = true; + } + if (this.options.data) { + ret.useData = true; + } + if (this.useDepths) { + ret.useDepths = true; + } + if (this.useBlockParams) { + ret.useBlockParams = true; + } + if (this.options.compat) { + ret.compat = true; + } + + if (!asObject) { + ret.compiler = JSON.stringify(ret.compiler); + + this.source.currentLocation = { start: { line: 1, column: 0 } }; + ret = this.objectLiteral(ret); + + if (options.srcName) { + ret = ret.toStringWithSourceMap({ file: options.destName }); + ret.map = ret.map && ret.map.toString(); + } else { + ret = ret.toString(); + } + } else { + ret.compilerOptions = this.options; + } + + return ret; + } else { + return fn; + } + }, + + preamble: function preamble() { + // track the last context pushed into place to allow skipping the + // getContext opcode when it would be a noop + this.lastContext = 0; + this.source = new _codeGen2['default'](this.options.srcName); + this.decorators = new _codeGen2['default'](this.options.srcName); + }, + + createFunctionContext: function createFunctionContext(asObject) { + var varDeclarations = ''; + + var locals = this.stackVars.concat(this.registers.list); + if (locals.length > 0) { + varDeclarations += ', ' + locals.join(', '); + } + + // Generate minimizer alias mappings + // + // When using true SourceNodes, this will update all references to the given alias + // as the source nodes are reused in situ. For the non-source node compilation mode, + // aliases will not be used, but this case is already being run on the client and + // we aren't concern about minimizing the template size. + var aliasCount = 0; + for (var alias in this.aliases) { + // eslint-disable-line guard-for-in + var node = this.aliases[alias]; + + if (this.aliases.hasOwnProperty(alias) && node.children && node.referenceCount > 1) { + varDeclarations += ', alias' + ++aliasCount + '=' + alias; + node.children[0] = 'alias' + aliasCount; + } + } + + var params = ['container', 'depth0', 'helpers', 'partials', 'data']; + + if (this.useBlockParams || this.useDepths) { + params.push('blockParams'); + } + if (this.useDepths) { + params.push('depths'); + } + + // Perform a second pass over the output to merge content when possible + var source = this.mergeSource(varDeclarations); + + if (asObject) { + params.push(source); + + return Function.apply(this, params); + } else { + return this.source.wrap(['function(', params.join(','), ') {\n ', source, '}']); + } + }, + mergeSource: function mergeSource(varDeclarations) { + var isSimple = this.environment.isSimple, + appendOnly = !this.forceBuffer, + appendFirst = undefined, + sourceSeen = undefined, + bufferStart = undefined, + bufferEnd = undefined; + this.source.each(function (line) { + if (line.appendToBuffer) { + if (bufferStart) { + line.prepend(' + '); + } else { + bufferStart = line; + } + bufferEnd = line; + } else { + if (bufferStart) { + if (!sourceSeen) { + appendFirst = true; + } else { + bufferStart.prepend('buffer += '); + } + bufferEnd.add(';'); + bufferStart = bufferEnd = undefined; + } + + sourceSeen = true; + if (!isSimple) { + appendOnly = false; + } + } + }); + + if (appendOnly) { + if (bufferStart) { + bufferStart.prepend('return '); + bufferEnd.add(';'); + } else if (!sourceSeen) { + this.source.push('return "";'); + } + } else { + varDeclarations += ', buffer = ' + (appendFirst ? '' : this.initializeBuffer()); + + if (bufferStart) { + bufferStart.prepend('return buffer + '); + bufferEnd.add(';'); + } else { + this.source.push('return buffer;'); + } + } + + if (varDeclarations) { + this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n')); + } + + return this.source.merge(); + }, + + // [blockValue] + // + // On stack, before: hash, inverse, program, value + // On stack, after: return value of blockHelperMissing + // + // The purpose of this opcode is to take a block of the form + // `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and + // replace it on the stack with the result of properly + // invoking blockHelperMissing. + blockValue: function blockValue(name) { + var blockHelperMissing = this.aliasable('helpers.blockHelperMissing'), + params = [this.contextName(0)]; + this.setupHelperArgs(name, 0, params); + + var blockName = this.popStack(); + params.splice(1, 0, blockName); + + this.push(this.source.functionCall(blockHelperMissing, 'call', params)); + }, + + // [ambiguousBlockValue] + // + // On stack, before: hash, inverse, program, value + // Compiler value, before: lastHelper=value of last found helper, if any + // On stack, after, if no lastHelper: same as [blockValue] + // On stack, after, if lastHelper: value + ambiguousBlockValue: function ambiguousBlockValue() { + // We're being a bit cheeky and reusing the options value from the prior exec + var blockHelperMissing = this.aliasable('helpers.blockHelperMissing'), + params = [this.contextName(0)]; + this.setupHelperArgs('', 0, params, true); + + this.flushInline(); + + var current = this.topStack(); + params.splice(1, 0, current); + + this.pushSource(['if (!', this.lastHelper, ') { ', current, ' = ', this.source.functionCall(blockHelperMissing, 'call', params), '}']); + }, + + // [appendContent] + // + // On stack, before: ... + // On stack, after: ... + // + // Appends the string value of `content` to the current buffer + appendContent: function appendContent(content) { + if (this.pendingContent) { + content = this.pendingContent + content; + } else { + this.pendingLocation = this.source.currentLocation; + } + + this.pendingContent = content; + }, + + // [append] + // + // On stack, before: value, ... + // On stack, after: ... + // + // Coerces `value` to a String and appends it to the current buffer. + // + // If `value` is truthy, or 0, it is coerced into a string and appended + // Otherwise, the empty string is appended + append: function append() { + if (this.isInline()) { + this.replaceStack(function (current) { + return [' != null ? ', current, ' : ""']; + }); + + this.pushSource(this.appendToBuffer(this.popStack())); + } else { + var local = this.popStack(); + this.pushSource(['if (', local, ' != null) { ', this.appendToBuffer(local, undefined, true), ' }']); + if (this.environment.isSimple) { + this.pushSource(['else { ', this.appendToBuffer("''", undefined, true), ' }']); + } + } + }, + + // [appendEscaped] + // + // On stack, before: value, ... + // On stack, after: ... + // + // Escape `value` and append it to the buffer + appendEscaped: function appendEscaped() { + this.pushSource(this.appendToBuffer([this.aliasable('container.escapeExpression'), '(', this.popStack(), ')'])); + }, + + // [getContext] + // + // On stack, before: ... + // On stack, after: ... + // Compiler value, after: lastContext=depth + // + // Set the value of the `lastContext` compiler value to the depth + getContext: function getContext(depth) { + this.lastContext = depth; + }, + + // [pushContext] + // + // On stack, before: ... + // On stack, after: currentContext, ... + // + // Pushes the value of the current context onto the stack. + pushContext: function pushContext() { + this.pushStackLiteral(this.contextName(this.lastContext)); + }, + + // [lookupOnContext] + // + // On stack, before: ... + // On stack, after: currentContext[name], ... + // + // Looks up the value of `name` on the current context and pushes + // it onto the stack. + lookupOnContext: function lookupOnContext(parts, falsy, strict, scoped) { + var i = 0; + + if (!scoped && this.options.compat && !this.lastContext) { + // The depthed query is expected to handle the undefined logic for the root level that + // is implemented below, so we evaluate that directly in compat mode + this.push(this.depthedLookup(parts[i++])); + } else { + this.pushContext(); + } + + this.resolvePath('context', parts, i, falsy, strict); + }, + + // [lookupBlockParam] + // + // On stack, before: ... + // On stack, after: blockParam[name], ... + // + // Looks up the value of `parts` on the given block param and pushes + // it onto the stack. + lookupBlockParam: function lookupBlockParam(blockParamId, parts) { + this.useBlockParams = true; + + this.push(['blockParams[', blockParamId[0], '][', blockParamId[1], ']']); + this.resolvePath('context', parts, 1); + }, + + // [lookupData] + // + // On stack, before: ... + // On stack, after: data, ... + // + // Push the data lookup operator + lookupData: function lookupData(depth, parts, strict) { + if (!depth) { + this.pushStackLiteral('data'); + } else { + this.pushStackLiteral('container.data(data, ' + depth + ')'); + } + + this.resolvePath('data', parts, 0, true, strict); + }, + + resolvePath: function resolvePath(type, parts, i, falsy, strict) { + // istanbul ignore next + + var _this = this; + + if (this.options.strict || this.options.assumeObjects) { + this.push(strictLookup(this.options.strict && strict, this, parts, type)); + return; + } + + var len = parts.length; + for (; i < len; i++) { + /* eslint-disable no-loop-func */ + this.replaceStack(function (current) { + var lookup = _this.nameLookup(current, parts[i], type); + // We want to ensure that zero and false are handled properly if the context (falsy flag) + // needs to have the special handling for these values. + if (!falsy) { + return [' != null ? ', lookup, ' : ', current]; + } else { + // Otherwise we can use generic falsy handling + return [' && ', lookup]; + } + }); + /* eslint-enable no-loop-func */ + } + }, + + // [resolvePossibleLambda] + // + // On stack, before: value, ... + // On stack, after: resolved value, ... + // + // If the `value` is a lambda, replace it on the stack by + // the return value of the lambda + resolvePossibleLambda: function resolvePossibleLambda() { + this.push([this.aliasable('container.lambda'), '(', this.popStack(), ', ', this.contextName(0), ')']); + }, + + // [pushStringParam] + // + // On stack, before: ... + // On stack, after: string, currentContext, ... + // + // This opcode is designed for use in string mode, which + // provides the string value of a parameter along with its + // depth rather than resolving it immediately. + pushStringParam: function pushStringParam(string, type) { + this.pushContext(); + this.pushString(type); + + // If it's a subexpression, the string result + // will be pushed after this opcode. + if (type !== 'SubExpression') { + if (typeof string === 'string') { + this.pushString(string); + } else { + this.pushStackLiteral(string); + } + } + }, + + emptyHash: function emptyHash(omitEmpty) { + if (this.trackIds) { + this.push('{}'); // hashIds + } + if (this.stringParams) { + this.push('{}'); // hashContexts + this.push('{}'); // hashTypes + } + this.pushStackLiteral(omitEmpty ? 'undefined' : '{}'); + }, + pushHash: function pushHash() { + if (this.hash) { + this.hashes.push(this.hash); + } + this.hash = { values: [], types: [], contexts: [], ids: [] }; + }, + popHash: function popHash() { + var hash = this.hash; + this.hash = this.hashes.pop(); + + if (this.trackIds) { + this.push(this.objectLiteral(hash.ids)); + } + if (this.stringParams) { + this.push(this.objectLiteral(hash.contexts)); + this.push(this.objectLiteral(hash.types)); + } + + this.push(this.objectLiteral(hash.values)); + }, + + // [pushString] + // + // On stack, before: ... + // On stack, after: quotedString(string), ... + // + // Push a quoted version of `string` onto the stack + pushString: function pushString(string) { + this.pushStackLiteral(this.quotedString(string)); + }, + + // [pushLiteral] + // + // On stack, before: ... + // On stack, after: value, ... + // + // Pushes a value onto the stack. This operation prevents + // the compiler from creating a temporary variable to hold + // it. + pushLiteral: function pushLiteral(value) { + this.pushStackLiteral(value); + }, + + // [pushProgram] + // + // On stack, before: ... + // On stack, after: program(guid), ... + // + // Push a program expression onto the stack. This takes + // a compile-time guid and converts it into a runtime-accessible + // expression. + pushProgram: function pushProgram(guid) { + if (guid != null) { + this.pushStackLiteral(this.programExpression(guid)); + } else { + this.pushStackLiteral(null); + } + }, + + // [registerDecorator] + // + // On stack, before: hash, program, params..., ... + // On stack, after: ... + // + // Pops off the decorator's parameters, invokes the decorator, + // and inserts the decorator into the decorators list. + registerDecorator: function registerDecorator(paramSize, name) { + var foundDecorator = this.nameLookup('decorators', name, 'decorator'), + options = this.setupHelperArgs(name, paramSize); + + this.decorators.push(['fn = ', this.decorators.functionCall(foundDecorator, '', ['fn', 'props', 'container', options]), ' || fn;']); + }, + + // [invokeHelper] + // + // On stack, before: hash, inverse, program, params..., ... + // On stack, after: result of helper invocation + // + // Pops off the helper's parameters, invokes the helper, + // and pushes the helper's return value onto the stack. + // + // If the helper is not found, `helperMissing` is called. + invokeHelper: function invokeHelper(paramSize, name, isSimple) { + var nonHelper = this.popStack(), + helper = this.setupHelper(paramSize, name), + simple = isSimple ? [helper.name, ' || '] : ''; + + var lookup = ['('].concat(simple, nonHelper); + if (!this.options.strict) { + lookup.push(' || ', this.aliasable('helpers.helperMissing')); + } + lookup.push(')'); + + this.push(this.source.functionCall(lookup, 'call', helper.callParams)); + }, + + // [invokeKnownHelper] + // + // On stack, before: hash, inverse, program, params..., ... + // On stack, after: result of helper invocation + // + // This operation is used when the helper is known to exist, + // so a `helperMissing` fallback is not required. + invokeKnownHelper: function invokeKnownHelper(paramSize, name) { + var helper = this.setupHelper(paramSize, name); + this.push(this.source.functionCall(helper.name, 'call', helper.callParams)); + }, + + // [invokeAmbiguous] + // + // On stack, before: hash, inverse, program, params..., ... + // On stack, after: result of disambiguation + // + // This operation is used when an expression like `{{foo}}` + // is provided, but we don't know at compile-time whether it + // is a helper or a path. + // + // This operation emits more code than the other options, + // and can be avoided by passing the `knownHelpers` and + // `knownHelpersOnly` flags at compile-time. + invokeAmbiguous: function invokeAmbiguous(name, helperCall) { + this.useRegister('helper'); + + var nonHelper = this.popStack(); + + this.emptyHash(); + var helper = this.setupHelper(0, name, helperCall); + + var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper'); + + var lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')']; + if (!this.options.strict) { + lookup[0] = '(helper = '; + lookup.push(' != null ? helper : ', this.aliasable('helpers.helperMissing')); + } + + this.push(['(', lookup, helper.paramsInit ? ['),(', helper.paramsInit] : [], '),', '(typeof helper === ', this.aliasable('"function"'), ' ? ', this.source.functionCall('helper', 'call', helper.callParams), ' : helper))']); + }, + + // [invokePartial] + // + // On stack, before: context, ... + // On stack after: result of partial invocation + // + // This operation pops off a context, invokes a partial with that context, + // and pushes the result of the invocation back. + invokePartial: function invokePartial(isDynamic, name, indent) { + var params = [], + options = this.setupParams(name, 1, params); + + if (isDynamic) { + name = this.popStack(); + delete options.name; + } + + if (indent) { + options.indent = JSON.stringify(indent); + } + options.helpers = 'helpers'; + options.partials = 'partials'; + options.decorators = 'container.decorators'; + + if (!isDynamic) { + params.unshift(this.nameLookup('partials', name, 'partial')); + } else { + params.unshift(name); + } + + if (this.options.compat) { + options.depths = 'depths'; + } + options = this.objectLiteral(options); + params.push(options); + + this.push(this.source.functionCall('container.invokePartial', '', params)); + }, + + // [assignToHash] + // + // On stack, before: value, ..., hash, ... + // On stack, after: ..., hash, ... + // + // Pops a value off the stack and assigns it to the current hash + assignToHash: function assignToHash(key) { + var value = this.popStack(), + context = undefined, + type = undefined, + id = undefined; + + if (this.trackIds) { + id = this.popStack(); + } + if (this.stringParams) { + type = this.popStack(); + context = this.popStack(); + } + + var hash = this.hash; + if (context) { + hash.contexts[key] = context; + } + if (type) { + hash.types[key] = type; + } + if (id) { + hash.ids[key] = id; + } + hash.values[key] = value; + }, + + pushId: function pushId(type, name, child) { + if (type === 'BlockParam') { + this.pushStackLiteral('blockParams[' + name[0] + '].path[' + name[1] + ']' + (child ? ' + ' + JSON.stringify('.' + child) : '')); + } else if (type === 'PathExpression') { + this.pushString(name); + } else if (type === 'SubExpression') { + this.pushStackLiteral('true'); + } else { + this.pushStackLiteral('null'); + } + }, + + // HELPERS + + compiler: JavaScriptCompiler, + + compileChildren: function compileChildren(environment, options) { + var children = environment.children, + child = undefined, + compiler = undefined; + + for (var i = 0, l = children.length; i < l; i++) { + child = children[i]; + compiler = new this.compiler(); // eslint-disable-line new-cap + + var index = this.matchExistingProgram(child); + + if (index == null) { + this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children + index = this.context.programs.length; + child.index = index; + child.name = 'program' + index; + this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile); + this.context.decorators[index] = compiler.decorators; + this.context.environments[index] = child; + + this.useDepths = this.useDepths || compiler.useDepths; + this.useBlockParams = this.useBlockParams || compiler.useBlockParams; + } else { + child.index = index; + child.name = 'program' + index; + + this.useDepths = this.useDepths || child.useDepths; + this.useBlockParams = this.useBlockParams || child.useBlockParams; + } + } + }, + matchExistingProgram: function matchExistingProgram(child) { + for (var i = 0, len = this.context.environments.length; i < len; i++) { + var environment = this.context.environments[i]; + if (environment && environment.equals(child)) { + return i; + } + } + }, + + programExpression: function programExpression(guid) { + var child = this.environment.children[guid], + programParams = [child.index, 'data', child.blockParams]; + + if (this.useBlockParams || this.useDepths) { + programParams.push('blockParams'); + } + if (this.useDepths) { + programParams.push('depths'); + } + + return 'container.program(' + programParams.join(', ') + ')'; + }, + + useRegister: function useRegister(name) { + if (!this.registers[name]) { + this.registers[name] = true; + this.registers.list.push(name); + } + }, + + push: function push(expr) { + if (!(expr instanceof Literal)) { + expr = this.source.wrap(expr); + } + + this.inlineStack.push(expr); + return expr; + }, + + pushStackLiteral: function pushStackLiteral(item) { + this.push(new Literal(item)); + }, + + pushSource: function pushSource(source) { + if (this.pendingContent) { + this.source.push(this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation)); + this.pendingContent = undefined; + } + + if (source) { + this.source.push(source); + } + }, + + replaceStack: function replaceStack(callback) { + var prefix = ['('], + stack = undefined, + createdStack = undefined, + usedLiteral = undefined; + + /* istanbul ignore next */ + if (!this.isInline()) { + throw new _exception2['default']('replaceStack on non-inline'); + } + + // We want to merge the inline statement into the replacement statement via ',' + var top = this.popStack(true); + + if (top instanceof Literal) { + // Literals do not need to be inlined + stack = [top.value]; + prefix = ['(', stack]; + usedLiteral = true; + } else { + // Get or create the current stack name for use by the inline + createdStack = true; + var _name = this.incrStack(); + + prefix = ['((', this.push(_name), ' = ', top, ')']; + stack = this.topStack(); + } + + var item = callback.call(this, stack); + + if (!usedLiteral) { + this.popStack(); + } + if (createdStack) { + this.stackSlot--; + } + this.push(prefix.concat(item, ')')); + }, + + incrStack: function incrStack() { + this.stackSlot++; + if (this.stackSlot > this.stackVars.length) { + this.stackVars.push('stack' + this.stackSlot); + } + return this.topStackName(); + }, + topStackName: function topStackName() { + return 'stack' + this.stackSlot; + }, + flushInline: function flushInline() { + var inlineStack = this.inlineStack; + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + /* istanbul ignore if */ + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + var stack = this.incrStack(); + this.pushSource([stack, ' = ', entry, ';']); + this.compileStack.push(stack); + } + } + }, + isInline: function isInline() { + return this.inlineStack.length; + }, + + popStack: function popStack(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); + + if (!wrapped && item instanceof Literal) { + return item.value; + } else { + if (!inline) { + /* istanbul ignore next */ + if (!this.stackSlot) { + throw new _exception2['default']('Invalid stack pop'); + } + this.stackSlot--; + } + return item; + } + }, + + topStack: function topStack() { + var stack = this.isInline() ? this.inlineStack : this.compileStack, + item = stack[stack.length - 1]; + + /* istanbul ignore if */ + if (item instanceof Literal) { + return item.value; + } else { + return item; + } + }, + + contextName: function contextName(context) { + if (this.useDepths && context) { + return 'depths[' + context + ']'; + } else { + return 'depth' + context; + } + }, + + quotedString: function quotedString(str) { + return this.source.quotedString(str); + }, + + objectLiteral: function objectLiteral(obj) { + return this.source.objectLiteral(obj); + }, + + aliasable: function aliasable(name) { + var ret = this.aliases[name]; + if (ret) { + ret.referenceCount++; + return ret; + } + + ret = this.aliases[name] = this.source.wrap(name); + ret.aliasable = true; + ret.referenceCount = 1; + + return ret; + }, + + setupHelper: function setupHelper(paramSize, name, blockHelper) { + var params = [], + paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper); + var foundHelper = this.nameLookup('helpers', name, 'helper'), + callContext = this.aliasable(this.contextName(0) + ' != null ? ' + this.contextName(0) + ' : {}'); + + return { + params: params, + paramsInit: paramsInit, + name: foundHelper, + callParams: [callContext].concat(params) + }; + }, + + setupParams: function setupParams(helper, paramSize, params) { + var options = {}, + contexts = [], + types = [], + ids = [], + objectArgs = !params, + param = undefined; + + if (objectArgs) { + params = []; + } + + options.name = this.quotedString(helper); + options.hash = this.popStack(); + + if (this.trackIds) { + options.hashIds = this.popStack(); + } + if (this.stringParams) { + options.hashTypes = this.popStack(); + options.hashContexts = this.popStack(); + } + + var inverse = this.popStack(), + program = this.popStack(); + + // Avoid setting fn and inverse if neither are set. This allows + // helpers to do a check for `if (options.fn)` + if (program || inverse) { + options.fn = program || 'container.noop'; + options.inverse = inverse || 'container.noop'; + } + + // The parameters go on to the stack in order (making sure that they are evaluated in order) + // so we need to pop them off the stack in reverse order + var i = paramSize; + while (i--) { + param = this.popStack(); + params[i] = param; + + if (this.trackIds) { + ids[i] = this.popStack(); + } + if (this.stringParams) { + types[i] = this.popStack(); + contexts[i] = this.popStack(); + } + } + + if (objectArgs) { + options.args = this.source.generateArray(params); + } + + if (this.trackIds) { + options.ids = this.source.generateArray(ids); + } + if (this.stringParams) { + options.types = this.source.generateArray(types); + options.contexts = this.source.generateArray(contexts); + } + + if (this.options.data) { + options.data = 'data'; + } + if (this.useBlockParams) { + options.blockParams = 'blockParams'; + } + return options; + }, + + setupHelperArgs: function setupHelperArgs(helper, paramSize, params, useRegister) { + var options = this.setupParams(helper, paramSize, params); + options = this.objectLiteral(options); + if (useRegister) { + this.useRegister('options'); + params.push('options'); + return ['options=', options]; + } else if (params) { + params.push(options); + return ''; + } else { + return options; + } + } + }; + + (function () { + var reservedWords = ('break else new var' + ' case finally return void' + ' catch for switch while' + ' continue function this with' + ' default if throw' + ' delete in try' + ' do instanceof typeof' + ' abstract enum int short' + ' boolean export interface static' + ' byte extends long super' + ' char final native synchronized' + ' class float package throws' + ' const goto private transient' + ' debugger implements protected volatile' + ' double import public let yield await' + ' null true false').split(' '); + + var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {}; + + for (var i = 0, l = reservedWords.length; i < l; i++) { + compilerWords[reservedWords[i]] = true; + } + })(); + + JavaScriptCompiler.isValidJavaScriptVariableName = function (name) { + return !JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name); + }; + + function strictLookup(requireTerminal, compiler, parts, type) { + var stack = compiler.popStack(), + i = 0, + len = parts.length; + if (requireTerminal) { + len--; + } + + for (; i < len; i++) { + stack = compiler.nameLookup(stack, parts[i], type); + } + + if (requireTerminal) { + return [compiler.aliasable('container.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ')']; + } else { + return stack; + } + } + + exports['default'] = JavaScriptCompiler; + module.exports = exports['default']; + +/***/ }, +/* 29 */ +/***/ function(module, exports, __webpack_require__) { + + /* global define */ + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + var SourceNode = undefined; + + try { + /* istanbul ignore next */ + if (false) { + // We don't support this in AMD environments. For these environments, we asusme that + // they are running on the browser and thus have no need for the source-map library. + var SourceMap = require('source-map'); + SourceNode = SourceMap.SourceNode; + } + } catch (err) {} + /* NOP */ + + /* istanbul ignore if: tested but not covered in istanbul due to dist build */ + if (!SourceNode) { + SourceNode = function (line, column, srcFile, chunks) { + this.src = ''; + if (chunks) { + this.add(chunks); + } + }; + /* istanbul ignore next */ + SourceNode.prototype = { + add: function add(chunks) { + if (_utils.isArray(chunks)) { + chunks = chunks.join(''); + } + this.src += chunks; + }, + prepend: function prepend(chunks) { + if (_utils.isArray(chunks)) { + chunks = chunks.join(''); + } + this.src = chunks + this.src; + }, + toStringWithSourceMap: function toStringWithSourceMap() { + return { code: this.toString() }; + }, + toString: function toString() { + return this.src; + } + }; + } + + function castChunk(chunk, codeGen, loc) { + if (_utils.isArray(chunk)) { + var ret = []; + + for (var i = 0, len = chunk.length; i < len; i++) { + ret.push(codeGen.wrap(chunk[i], loc)); + } + return ret; + } else if (typeof chunk === 'boolean' || typeof chunk === 'number') { + // Handle primitives that the SourceNode will throw up on + return chunk + ''; + } + return chunk; + } + + function CodeGen(srcFile) { + this.srcFile = srcFile; + this.source = []; + } + + CodeGen.prototype = { + isEmpty: function isEmpty() { + return !this.source.length; + }, + prepend: function prepend(source, loc) { + this.source.unshift(this.wrap(source, loc)); + }, + push: function push(source, loc) { + this.source.push(this.wrap(source, loc)); + }, + + merge: function merge() { + var source = this.empty(); + this.each(function (line) { + source.add([' ', line, '\n']); + }); + return source; + }, + + each: function each(iter) { + for (var i = 0, len = this.source.length; i < len; i++) { + iter(this.source[i]); + } + }, + + empty: function empty() { + var loc = this.currentLocation || { start: {} }; + return new SourceNode(loc.start.line, loc.start.column, this.srcFile); + }, + wrap: function wrap(chunk) { + var loc = arguments.length <= 1 || arguments[1] === undefined ? this.currentLocation || { start: {} } : arguments[1]; + + if (chunk instanceof SourceNode) { + return chunk; + } + + chunk = castChunk(chunk, this, loc); + + return new SourceNode(loc.start.line, loc.start.column, this.srcFile, chunk); + }, + + functionCall: function functionCall(fn, type, params) { + params = this.generateList(params); + return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']); + }, + + quotedString: function quotedString(str) { + return '"' + (str + '').replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 + .replace(/\u2029/g, '\\u2029') + '"'; + }, + + objectLiteral: function objectLiteral(obj) { + var pairs = []; + + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + var value = castChunk(obj[key], this); + if (value !== 'undefined') { + pairs.push([this.quotedString(key), ':', value]); + } + } + } + + var ret = this.generateList(pairs); + ret.prepend('{'); + ret.add('}'); + return ret; + }, + + generateList: function generateList(entries) { + var ret = this.empty(); + + for (var i = 0, len = entries.length; i < len; i++) { + if (i) { + ret.add(','); + } + + ret.add(castChunk(entries[i], this)); + } + + return ret; + }, + + generateArray: function generateArray(entries) { + var ret = this.generateList(entries); + ret.prepend('['); + ret.add(']'); + + return ret; + } + }; + + exports['default'] = CodeGen; + module.exports = exports['default']; + +/***/ } +/******/ ]) +}); +; \ No newline at end of file diff --git a/vendor/assets/javascripts/handlebars.runtime.js b/vendor/assets/javascripts/handlebars.runtime.js index acd734440..95049f3b8 100644 --- a/vendor/assets/javascripts/handlebars.runtime.js +++ b/vendor/assets/javascripts/handlebars.runtime.js @@ -1,8 +1,8 @@ /*! - handlebars v2.0.0 + handlebars v4.0.5 -Copyright (C) 2011-2014 by Yehuda Katz +Copyright (C) 2011-2015 by Yehuda Katz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -24,643 +24,1217 @@ THE SOFTWARE. @license */ -/* exported Handlebars */ -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - define([], factory); - } else if (typeof exports === 'object') { - module.exports = factory(); - } else { - root.Handlebars = root.Handlebars || factory(); - } -}(this, function () { -// handlebars/safe-string.js -var __module3__ = (function() { - "use strict"; - var __exports__; - // Build out our basic SafeString type - function SafeString(string) { - this.string = string; - } - - SafeString.prototype.toString = function() { - return "" + this.string; - }; - - __exports__ = SafeString; - return __exports__; -})(); - -// handlebars/utils.js -var __module2__ = (function(__dependency1__) { - "use strict"; - var __exports__ = {}; - /*jshint -W004 */ - var SafeString = __dependency1__; - - var escape = { - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`", - '\n' : '\\n', // NewLine - '\r' : '\\n', // Return - '\b' : '\\b', // Backspace - '\f' : '\\f', // Form fee - '\t' : '\\t', // Tab - '\v' : '\\v' // Vertical Tab - }; - var badChars = /[&<>"'`\b\f\n\r\t\v]/g; - var possible = /[&<>"'`\b\f\n\r\t\v]/; - - - function escapeChar(chr) { - return escape[chr]; - } - - function extend(obj /* , ...source */) { - for (var i = 1; i < arguments.length; i++) { - for (var key in arguments[i]) { - if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { - obj[key] = arguments[i][key]; - } - } - } - - return obj; - } - - __exports__.extend = extend;var toString = Object.prototype.toString; - __exports__.toString = toString; - // Sourced from lodash - // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt - var isFunction = function(value) { - return typeof value === 'function'; - }; - // fallback for older versions of Chrome and Safari - /* istanbul ignore next */ - if (isFunction(/x/)) { - isFunction = function(value) { - return typeof value === 'function' && toString.call(value) === '[object Function]'; - }; - } - var isFunction; - __exports__.isFunction = isFunction; - /* istanbul ignore next */ - var isArray = Array.isArray || function(value) { - return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false; - }; - __exports__.isArray = isArray; - - function escapeExpression(string) { - // don't escape SafeStrings, since they're already safe - if (string instanceof SafeString) { - return string.toString(); - } else if (string == null) { - return ""; - } else if (!string) { - return string + ''; - } - - // Force a string conversion as this will be done by the append regardless and - // the regex test will do this transparently behind the scenes, causing issues if - // an object's to string has escaped characters in it. - string = "" + string; - - if(!possible.test(string)) { return string; } - return string.replace(badChars, escapeChar); - } - - __exports__.escapeExpression = escapeExpression;function isEmpty(value) { - if (!value && value !== 0) { - return true; - } else if (isArray(value) && value.length === 0) { - return true; - } else { - return false; - } - } - - __exports__.isEmpty = isEmpty;function appendContextPath(contextPath, id) { - return (contextPath ? contextPath + '.' : '') + id; - } - - __exports__.appendContextPath = appendContextPath; - return __exports__; -})(__module3__); - -// handlebars/exception.js -var __module4__ = (function() { - "use strict"; - var __exports__; - - var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; - - function Exception(message, node) { - var line; - if (node && node.firstLine) { - line = node.firstLine; - - message += ' - ' + line + ':' + node.firstColumn; - } - - var tmp = Error.prototype.constructor.call(this, message); - - // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. - for (var idx = 0; idx < errorProps.length; idx++) { - this[errorProps[idx]] = tmp[errorProps[idx]]; - } - - if (line) { - this.lineNumber = line; - this.column = node.firstColumn; - } - } - - Exception.prototype = new Error(); - - __exports__ = Exception; - return __exports__; -})(); - -// handlebars/base.js -var __module1__ = (function(__dependency1__, __dependency2__) { - "use strict"; - var __exports__ = {}; - var Utils = __dependency1__; - var Exception = __dependency2__; - - var VERSION = "2.0.0"; - __exports__.VERSION = VERSION;var COMPILER_REVISION = 6; - __exports__.COMPILER_REVISION = COMPILER_REVISION; - var REVISION_CHANGES = { - 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it - 2: '== 1.0.0-rc.3', - 3: '== 1.0.0-rc.4', - 4: '== 1.x.x', - 5: '== 2.0.0-alpha.x', - 6: '>= 2.0.0-beta.1' - }; - __exports__.REVISION_CHANGES = REVISION_CHANGES; - var isArray = Utils.isArray, - isFunction = Utils.isFunction, - toString = Utils.toString, - objectType = '[object Object]'; - - function HandlebarsEnvironment(helpers, partials) { - this.helpers = helpers || {}; - this.partials = partials || {}; - - registerDefaultHelpers(this); - } - - __exports__.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = { - constructor: HandlebarsEnvironment, - - logger: logger, - log: log, - - registerHelper: function(name, fn) { - if (toString.call(name) === objectType) { - if (fn) { throw new Exception('Arg not supported with multiple helpers'); } - Utils.extend(this.helpers, name); - } else { - this.helpers[name] = fn; - } - }, - unregisterHelper: function(name) { - delete this.helpers[name]; - }, - - registerPartial: function(name, partial) { - if (toString.call(name) === objectType) { - Utils.extend(this.partials, name); - } else { - this.partials[name] = partial; - } - }, - unregisterPartial: function(name) { - delete this.partials[name]; - } - }; - - function registerDefaultHelpers(instance) { - instance.registerHelper('helperMissing', function(/* [args, ]options */) { - if(arguments.length === 1) { - // A missing field in a {{foo}} constuct. - return undefined; - } else { - // Someone is actually trying to call something, blow up. - throw new Exception("Missing helper: '" + arguments[arguments.length-1].name + "'"); - } - }); - - instance.registerHelper('blockHelperMissing', function(context, options) { - var inverse = options.inverse, - fn = options.fn; - - if(context === true) { - return fn(this); - } else if(context === false || context == null) { - return inverse(this); - } else if (isArray(context)) { - if(context.length > 0) { - if (options.ids) { - options.ids = [options.name]; - } - - return instance.helpers.each(context, options); - } else { - return inverse(this); - } - } else { - if (options.data && options.ids) { - var data = createFrame(options.data); - data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name); - options = {data: data}; - } - - return fn(context, options); - } - }); - - instance.registerHelper('each', function(context, options) { - if (!options) { - throw new Exception('Must pass iterator to #each'); - } - - var fn = options.fn, inverse = options.inverse; - var i = 0, ret = "", data; - - var contextPath; - if (options.data && options.ids) { - contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.'; - } - - if (isFunction(context)) { context = context.call(this); } - - if (options.data) { - data = createFrame(options.data); - } - - if(context && typeof context === 'object') { - if (isArray(context)) { - for(var j = context.length; i= 2.0.0-beta.1', + 7: '>= 4.0.0' + }; + + exports.REVISION_CHANGES = REVISION_CHANGES; + var objectType = '[object Object]'; + + function HandlebarsEnvironment(helpers, partials, decorators) { + this.helpers = helpers || {}; + this.partials = partials || {}; + this.decorators = decorators || {}; + + _helpers.registerDefaultHelpers(this); + _decorators.registerDefaultDecorators(this); + } + + HandlebarsEnvironment.prototype = { + constructor: HandlebarsEnvironment, + + logger: _logger2['default'], + log: _logger2['default'].log, + + registerHelper: function registerHelper(name, fn) { + if (_utils.toString.call(name) === objectType) { + if (fn) { + throw new _exception2['default']('Arg not supported with multiple helpers'); + } + _utils.extend(this.helpers, name); + } else { + this.helpers[name] = fn; + } + }, + unregisterHelper: function unregisterHelper(name) { + delete this.helpers[name]; + }, + + registerPartial: function registerPartial(name, partial) { + if (_utils.toString.call(name) === objectType) { + _utils.extend(this.partials, name); + } else { + if (typeof partial === 'undefined') { + throw new _exception2['default']('Attempting to register a partial called "' + name + '" as undefined'); + } + this.partials[name] = partial; + } + }, + unregisterPartial: function unregisterPartial(name) { + delete this.partials[name]; + }, + + registerDecorator: function registerDecorator(name, fn) { + if (_utils.toString.call(name) === objectType) { + if (fn) { + throw new _exception2['default']('Arg not supported with multiple decorators'); + } + _utils.extend(this.decorators, name); + } else { + this.decorators[name] = fn; + } + }, + unregisterDecorator: function unregisterDecorator(name) { + delete this.decorators[name]; + } + }; + + var log = _logger2['default'].log; + + exports.log = log; + exports.createFrame = _utils.createFrame; + exports.logger = _logger2['default']; + +/***/ }, +/* 4 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + exports.extend = extend; + exports.indexOf = indexOf; + exports.escapeExpression = escapeExpression; + exports.isEmpty = isEmpty; + exports.createFrame = createFrame; + exports.blockParams = blockParams; + exports.appendContextPath = appendContextPath; + var escape = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`', + '=': '=' + }; + + var badChars = /[&<>"'`=]/g, + possible = /[&<>"'`=]/; + + function escapeChar(chr) { + return escape[chr]; + } + + function extend(obj /* , ...source */) { + for (var i = 1; i < arguments.length; i++) { + for (var key in arguments[i]) { + if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { + obj[key] = arguments[i][key]; + } + } + } + + return obj; + } + + var toString = Object.prototype.toString; + + exports.toString = toString; + // Sourced from lodash + // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt + /* eslint-disable func-style */ + var isFunction = function isFunction(value) { + return typeof value === 'function'; + }; + // fallback for older versions of Chrome and Safari + /* istanbul ignore next */ + if (isFunction(/x/)) { + exports.isFunction = isFunction = function (value) { + return typeof value === 'function' && toString.call(value) === '[object Function]'; + }; + } + exports.isFunction = isFunction; + + /* eslint-enable func-style */ + + /* istanbul ignore next */ + var isArray = Array.isArray || function (value) { + return value && typeof value === 'object' ? toString.call(value) === '[object Array]' : false; + }; + + exports.isArray = isArray; + // Older IE versions do not directly support indexOf so we must implement our own, sadly. + + function indexOf(array, value) { + for (var i = 0, len = array.length; i < len; i++) { + if (array[i] === value) { + return i; + } + } + return -1; + } + + function escapeExpression(string) { + if (typeof string !== 'string') { + // don't escape SafeStrings, since they're already safe + if (string && string.toHTML) { + return string.toHTML(); + } else if (string == null) { + return ''; + } else if (!string) { + return string + ''; + } + + // Force a string conversion as this will be done by the append regardless and + // the regex test will do this transparently behind the scenes, causing issues if + // an object's to string has escaped characters in it. + string = '' + string; + } + + if (!possible.test(string)) { + return string; + } + return string.replace(badChars, escapeChar); + } + + function isEmpty(value) { + if (!value && value !== 0) { + return true; + } else if (isArray(value) && value.length === 0) { + return true; + } else { + return false; + } + } + + function createFrame(object) { + var frame = extend({}, object); + frame._parent = object; + return frame; + } + + function blockParams(params, ids) { + params.path = ids; + return params; + } + + function appendContextPath(contextPath, id) { + return (contextPath ? contextPath + '.' : '') + id; + } + +/***/ }, +/* 5 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + + var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; + + function Exception(message, node) { + var loc = node && node.loc, + line = undefined, + column = undefined; + if (loc) { + line = loc.start.line; + column = loc.start.column; + + message += ' - ' + line + ':' + column; + } + + var tmp = Error.prototype.constructor.call(this, message); + + // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. + for (var idx = 0; idx < errorProps.length; idx++) { + this[errorProps[idx]] = tmp[errorProps[idx]]; + } + + /* istanbul ignore else */ + if (Error.captureStackTrace) { + Error.captureStackTrace(this, Exception); + } + + if (loc) { + this.lineNumber = line; + this.column = column; + } + } + + Exception.prototype = new Error(); + + exports['default'] = Exception; + module.exports = exports['default']; + +/***/ }, +/* 6 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(2)['default']; + + exports.__esModule = true; + exports.registerDefaultHelpers = registerDefaultHelpers; + + var _helpersBlockHelperMissing = __webpack_require__(7); + + var _helpersBlockHelperMissing2 = _interopRequireDefault(_helpersBlockHelperMissing); + + var _helpersEach = __webpack_require__(8); + + var _helpersEach2 = _interopRequireDefault(_helpersEach); + + var _helpersHelperMissing = __webpack_require__(9); + + var _helpersHelperMissing2 = _interopRequireDefault(_helpersHelperMissing); + + var _helpersIf = __webpack_require__(10); + + var _helpersIf2 = _interopRequireDefault(_helpersIf); + + var _helpersLog = __webpack_require__(11); + + var _helpersLog2 = _interopRequireDefault(_helpersLog); + + var _helpersLookup = __webpack_require__(12); + + var _helpersLookup2 = _interopRequireDefault(_helpersLookup); + + var _helpersWith = __webpack_require__(13); + + var _helpersWith2 = _interopRequireDefault(_helpersWith); + + function registerDefaultHelpers(instance) { + _helpersBlockHelperMissing2['default'](instance); + _helpersEach2['default'](instance); + _helpersHelperMissing2['default'](instance); + _helpersIf2['default'](instance); + _helpersLog2['default'](instance); + _helpersLookup2['default'](instance); + _helpersWith2['default'](instance); + } + +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(4); + + exports['default'] = function (instance) { + instance.registerHelper('blockHelperMissing', function (context, options) { + var inverse = options.inverse, + fn = options.fn; + + if (context === true) { + return fn(this); + } else if (context === false || context == null) { + return inverse(this); + } else if (_utils.isArray(context)) { + if (context.length > 0) { + if (options.ids) { + options.ids = [options.name]; + } + + return instance.helpers.each(context, options); + } else { + return inverse(this); + } + } else { + if (options.data && options.ids) { + var data = _utils.createFrame(options.data); + data.contextPath = _utils.appendContextPath(options.data.contextPath, options.name); + options = { data: data }; + } + + return fn(context, options); + } + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 8 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(2)['default']; + + exports.__esModule = true; + + var _utils = __webpack_require__(4); + + var _exception = __webpack_require__(5); + + var _exception2 = _interopRequireDefault(_exception); + + exports['default'] = function (instance) { + instance.registerHelper('each', function (context, options) { + if (!options) { + throw new _exception2['default']('Must pass iterator to #each'); + } + + var fn = options.fn, + inverse = options.inverse, + i = 0, + ret = '', + data = undefined, + contextPath = undefined; + + if (options.data && options.ids) { + contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.'; + } + + if (_utils.isFunction(context)) { + context = context.call(this); + } + + if (options.data) { + data = _utils.createFrame(options.data); + } + + function execIteration(field, index, last) { + if (data) { + data.key = field; + data.index = index; + data.first = index === 0; + data.last = !!last; + + if (contextPath) { + data.contextPath = contextPath + field; + } + } + + ret = ret + fn(context[field], { + data: data, + blockParams: _utils.blockParams([context[field], field], [contextPath + field, null]) + }); + } + + if (context && typeof context === 'object') { + if (_utils.isArray(context)) { + for (var j = context.length; i < j; i++) { + if (i in context) { + execIteration(i, i, i === context.length - 1); + } + } + } else { + var priorKey = undefined; + + for (var key in context) { + if (context.hasOwnProperty(key)) { + // We're running the iterations one step out of sync so we can detect + // the last iteration without have to scan the object twice and create + // an itermediate keys array. + if (priorKey !== undefined) { + execIteration(priorKey, i - 1); + } + priorKey = key; + i++; + } + } + if (priorKey !== undefined) { + execIteration(priorKey, i - 1, true); + } + } + } + + if (i === 0) { + ret = inverse(this); + } + + return ret; + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 9 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(2)['default']; + + exports.__esModule = true; + + var _exception = __webpack_require__(5); + + var _exception2 = _interopRequireDefault(_exception); + + exports['default'] = function (instance) { + instance.registerHelper('helperMissing', function () /* [args, ]options */{ + if (arguments.length === 1) { + // A missing field in a {{foo}} construct. + return undefined; + } else { + // Someone is actually trying to call something, blow up. + throw new _exception2['default']('Missing helper: "' + arguments[arguments.length - 1].name + '"'); + } + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 10 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(4); + + exports['default'] = function (instance) { + instance.registerHelper('if', function (conditional, options) { + if (_utils.isFunction(conditional)) { + conditional = conditional.call(this); + } + + // Default behavior is to render the positive path if the value is truthy and not empty. + // The `includeZero` option may be set to treat the condtional as purely not empty based on the + // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative. + if (!options.hash.includeZero && !conditional || _utils.isEmpty(conditional)) { + return options.inverse(this); + } else { + return options.fn(this); + } + }); + + instance.registerHelper('unless', function (conditional, options) { + return instance.helpers['if'].call(this, conditional, { fn: options.inverse, inverse: options.fn, hash: options.hash }); + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 11 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + + exports['default'] = function (instance) { + instance.registerHelper('log', function () /* message, options */{ + var args = [undefined], + options = arguments[arguments.length - 1]; + for (var i = 0; i < arguments.length - 1; i++) { + args.push(arguments[i]); + } + + var level = 1; + if (options.hash.level != null) { + level = options.hash.level; + } else if (options.data && options.data.level != null) { + level = options.data.level; + } + args[0] = level; + + instance.log.apply(instance, args); + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 12 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + + exports['default'] = function (instance) { + instance.registerHelper('lookup', function (obj, field) { + return obj && obj[field]; + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 13 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(4); + + exports['default'] = function (instance) { + instance.registerHelper('with', function (context, options) { + if (_utils.isFunction(context)) { + context = context.call(this); + } + + var fn = options.fn; + + if (!_utils.isEmpty(context)) { + var data = options.data; + if (options.data && options.ids) { + data = _utils.createFrame(options.data); + data.contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]); + } + + return fn(context, { + data: data, + blockParams: _utils.blockParams([context], [data && data.contextPath]) + }); + } else { + return options.inverse(this); + } + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 14 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(2)['default']; + + exports.__esModule = true; + exports.registerDefaultDecorators = registerDefaultDecorators; + + var _decoratorsInline = __webpack_require__(15); + + var _decoratorsInline2 = _interopRequireDefault(_decoratorsInline); + + function registerDefaultDecorators(instance) { + _decoratorsInline2['default'](instance); + } + +/***/ }, +/* 15 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(4); + + exports['default'] = function (instance) { + instance.registerDecorator('inline', function (fn, props, container, options) { + var ret = fn; + if (!props.partials) { + props.partials = {}; + ret = function (context, options) { + // Create a new partials stack frame prior to exec. + var original = container.partials; + container.partials = _utils.extend({}, original, props.partials); + var ret = fn(context, options); + container.partials = original; + return ret; + }; + } + + props.partials[options.args[0]] = options.fn; + + return ret; + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 16 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(4); + + var logger = { + methodMap: ['debug', 'info', 'warn', 'error'], + level: 'info', + + // Maps a given level value to the `methodMap` indexes above. + lookupLevel: function lookupLevel(level) { + if (typeof level === 'string') { + var levelMap = _utils.indexOf(logger.methodMap, level.toLowerCase()); + if (levelMap >= 0) { + level = levelMap; + } else { + level = parseInt(level, 10); + } + } + + return level; + }, + + // Can be overridden in the host environment + log: function log(level) { + level = logger.lookupLevel(level); + + if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) { + var method = logger.methodMap[level]; + if (!console[method]) { + // eslint-disable-line no-console + method = 'log'; + } + + for (var _len = arguments.length, message = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + message[_key - 1] = arguments[_key]; + } + + console[method].apply(console, message); // eslint-disable-line no-console + } + } + }; + + exports['default'] = logger; + module.exports = exports['default']; + +/***/ }, +/* 17 */ +/***/ function(module, exports) { + + // Build out our basic SafeString type + 'use strict'; + + exports.__esModule = true; + function SafeString(string) { + this.string = string; + } + + SafeString.prototype.toString = SafeString.prototype.toHTML = function () { + return '' + this.string; + }; + + exports['default'] = SafeString; + module.exports = exports['default']; + +/***/ }, +/* 18 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireWildcard = __webpack_require__(1)['default']; + + var _interopRequireDefault = __webpack_require__(2)['default']; + + exports.__esModule = true; + exports.checkRevision = checkRevision; + exports.template = template; + exports.wrapProgram = wrapProgram; + exports.resolvePartial = resolvePartial; + exports.invokePartial = invokePartial; + exports.noop = noop; + + var _utils = __webpack_require__(4); + + var Utils = _interopRequireWildcard(_utils); + + var _exception = __webpack_require__(5); + + var _exception2 = _interopRequireDefault(_exception); + + var _base = __webpack_require__(3); + + function checkRevision(compilerInfo) { + var compilerRevision = compilerInfo && compilerInfo[0] || 1, + currentRevision = _base.COMPILER_REVISION; + + if (compilerRevision !== currentRevision) { + if (compilerRevision < currentRevision) { + var runtimeVersions = _base.REVISION_CHANGES[currentRevision], + compilerVersions = _base.REVISION_CHANGES[compilerRevision]; + throw new _exception2['default']('Template was precompiled with an older version of Handlebars than the current runtime. ' + 'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').'); + } else { + // Use the embedded version info since the runtime doesn't know about this revision yet + throw new _exception2['default']('Template was precompiled with a newer version of Handlebars than the current runtime. ' + 'Please update your runtime to a newer version (' + compilerInfo[1] + ').'); + } + } + } + + function template(templateSpec, env) { + /* istanbul ignore next */ + if (!env) { + throw new _exception2['default']('No environment passed to template'); + } + if (!templateSpec || !templateSpec.main) { + throw new _exception2['default']('Unknown template object: ' + typeof templateSpec); + } + + templateSpec.main.decorator = templateSpec.main_d; + + // Note: Using env.VM references rather than local var references throughout this section to allow + // for external users to override these as psuedo-supported APIs. + env.VM.checkRevision(templateSpec.compiler); + + function invokePartialWrapper(partial, context, options) { + if (options.hash) { + context = Utils.extend({}, context, options.hash); + if (options.ids) { + options.ids[0] = true; + } + } + + partial = env.VM.resolvePartial.call(this, partial, context, options); + var result = env.VM.invokePartial.call(this, partial, context, options); + + if (result == null && env.compile) { + options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env); + result = options.partials[options.name](context, options); + } + if (result != null) { + if (options.indent) { + var lines = result.split('\n'); + for (var i = 0, l = lines.length; i < l; i++) { + if (!lines[i] && i + 1 === l) { + break; + } + + lines[i] = options.indent + lines[i]; + } + result = lines.join('\n'); + } + return result; + } else { + throw new _exception2['default']('The partial ' + options.name + ' could not be compiled when running in runtime-only mode'); + } + } + + // Just add water + var container = { + strict: function strict(obj, name) { + if (!(name in obj)) { + throw new _exception2['default']('"' + name + '" not defined in ' + obj); + } + return obj[name]; + }, + lookup: function lookup(depths, name) { + var len = depths.length; + for (var i = 0; i < len; i++) { + if (depths[i] && depths[i][name] != null) { + return depths[i][name]; + } + } + }, + lambda: function lambda(current, context) { + return typeof current === 'function' ? current.call(context) : current; + }, + + escapeExpression: Utils.escapeExpression, + invokePartial: invokePartialWrapper, + + fn: function fn(i) { + var ret = templateSpec[i]; + ret.decorator = templateSpec[i + '_d']; + return ret; + }, + + programs: [], + program: function program(i, data, declaredBlockParams, blockParams, depths) { + var programWrapper = this.programs[i], + fn = this.fn(i); + if (data || depths || blockParams || declaredBlockParams) { + programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths); + } else if (!programWrapper) { + programWrapper = this.programs[i] = wrapProgram(this, i, fn); + } + return programWrapper; + }, + + data: function data(value, depth) { + while (value && depth--) { + value = value._parent; + } + return value; + }, + merge: function merge(param, common) { + var obj = param || common; + + if (param && common && param !== common) { + obj = Utils.extend({}, common, param); + } + + return obj; + }, + + noop: env.VM.noop, + compilerInfo: templateSpec.compiler + }; + + function ret(context) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var data = options.data; + + ret._setup(options); + if (!options.partial && templateSpec.useData) { + data = initData(context, data); + } + var depths = undefined, + blockParams = templateSpec.useBlockParams ? [] : undefined; + if (templateSpec.useDepths) { + if (options.depths) { + depths = context !== options.depths[0] ? [context].concat(options.depths) : options.depths; + } else { + depths = [context]; + } + } + + function main(context /*, options*/) { + return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths); + } + main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams); + return main(context, options); + } + ret.isTop = true; + + ret._setup = function (options) { + if (!options.partial) { + container.helpers = container.merge(options.helpers, env.helpers); + + if (templateSpec.usePartial) { + container.partials = container.merge(options.partials, env.partials); + } + if (templateSpec.usePartial || templateSpec.useDecorators) { + container.decorators = container.merge(options.decorators, env.decorators); + } + } else { + container.helpers = options.helpers; + container.partials = options.partials; + container.decorators = options.decorators; + } + }; + + ret._child = function (i, data, blockParams, depths) { + if (templateSpec.useBlockParams && !blockParams) { + throw new _exception2['default']('must pass block params'); + } + if (templateSpec.useDepths && !depths) { + throw new _exception2['default']('must pass parent depths'); + } + + return wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths); + }; + return ret; + } + + function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) { + function prog(context) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var currentDepths = depths; + if (depths && context !== depths[0]) { + currentDepths = [context].concat(depths); + } + + return fn(container, context, container.helpers, container.partials, options.data || data, blockParams && [options.blockParams].concat(blockParams), currentDepths); + } + + prog = executeDecorators(fn, prog, container, depths, data, blockParams); + + prog.program = i; + prog.depth = depths ? depths.length : 0; + prog.blockParams = declaredBlockParams || 0; + return prog; + } + + function resolvePartial(partial, context, options) { + if (!partial) { + if (options.name === '@partial-block') { + partial = options.data['partial-block']; + } else { + partial = options.partials[options.name]; + } + } else if (!partial.call && !options.name) { + // This is a dynamic partial that returned a string + options.name = partial; + partial = options.partials[partial]; + } + return partial; + } + + function invokePartial(partial, context, options) { + options.partial = true; + if (options.ids) { + options.data.contextPath = options.ids[0] || options.data.contextPath; + } + + var partialBlock = undefined; + if (options.fn && options.fn !== noop) { + options.data = _base.createFrame(options.data); + partialBlock = options.data['partial-block'] = options.fn; + + if (partialBlock.partials) { + options.partials = Utils.extend({}, options.partials, partialBlock.partials); + } + } + + if (partial === undefined && partialBlock) { + partial = partialBlock; + } + + if (partial === undefined) { + throw new _exception2['default']('The partial ' + options.name + ' could not be found'); + } else if (partial instanceof Function) { + return partial(context, options); + } + } + + function noop() { + return ''; + } + + function initData(context, data) { + if (!data || !('root' in data)) { + data = data ? _base.createFrame(data) : {}; + data.root = context; + } + return data; + } + + function executeDecorators(fn, prog, container, depths, data, blockParams) { + if (fn.decorator) { + var props = {}; + prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths); + Utils.extend(prog, props); + } + return prog; + } + +/***/ }, +/* 19 */ +/***/ function(module, exports) { + + /* WEBPACK VAR INJECTION */(function(global) {/* global window */ + 'use strict'; + + exports.__esModule = true; + + exports['default'] = function (Handlebars) { + /* istanbul ignore next */ + var root = typeof global !== 'undefined' ? global : window, + $Handlebars = root.Handlebars; + /* istanbul ignore next */ + Handlebars.noConflict = function () { + if (root.Handlebars === Handlebars) { + root.Handlebars = $Handlebars; + } + return Handlebars; + }; + }; + + module.exports = exports['default']; + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ } +/******/ ]) +}); +; \ No newline at end of file diff --git a/vendor/gems/discourse_imgur/lib/discourse_imgur/locale/server.sk.yml b/vendor/gems/discourse_imgur/lib/discourse_imgur/locale/server.sk.yml new file mode 100644 index 000000000..60f6f6db4 --- /dev/null +++ b/vendor/gems/discourse_imgur/lib/discourse_imgur/locale/server.sk.yml @@ -0,0 +1,12 @@ +# encoding: utf-8 +# +# Never edit this file. It will be overwritten when translations are pulled from Transifex. +# +# To work with us on translations, join this project: +# https://www.transifex.com/projects/p/discourse-org/ + +sk: + site_settings: + enable_imgur: "Zapnúť imgur api pre upload, nehostujte súbory lokálne" + imgur_client_id: "Vaše imgur.com klientské ID potrebné pre funkčnosť uploadu obrázku" + imgur_client_secret: "Váš imgur.com klientský kľúč. Nie je aktuálne potrebný pre funkčnosť uploadu obrázkov, ale je možné že v budúcnosti bude."