From 6d357c9c233f7bea9be489078a9a8005aa7a8884 Mon Sep 17 00:00:00 2001 From: riking Date: Tue, 26 Aug 2014 16:52:35 -0700 Subject: [PATCH 01/43] Rename pop3s settings to pop3, remove 'insecure' --- app/jobs/scheduled/poll_mailbox.rb | 19 ++++++++----------- config/application.rb | 2 +- config/site_settings.yml | 13 ++++++------- ...826234625_rename_settings_pop3s_to_pop3.rb | 9 +++++++++ spec/jobs/poll_mailbox_spec.rb | 18 +++++++++--------- 5 files changed, 33 insertions(+), 28 deletions(-) create mode 100644 db/migrate/20140826234625_rename_settings_pop3s_to_pop3.rb diff --git a/app/jobs/scheduled/poll_mailbox.rb b/app/jobs/scheduled/poll_mailbox.rb index 7faf2cfb9..8d86043c5 100644 --- a/app/jobs/scheduled/poll_mailbox.rb +++ b/app/jobs/scheduled/poll_mailbox.rb @@ -8,14 +8,14 @@ require_dependency 'email/message_builder' module Jobs class PollMailbox < Jobs::Scheduled - every SiteSetting.pop3s_polling_period_mins.minutes + every SiteSetting.pop3_polling_period_mins.minutes sidekiq_options retry: false include Email::BuildEmailHelper def execute(args) @args = args - if SiteSetting.pop3s_polling_enabled? - poll_pop3s + if SiteSetting.pop3_polling_enabled? + poll_pop3 end end @@ -72,14 +72,11 @@ module Jobs end end - def poll_pop3s - if !SiteSetting.pop3s_polling_insecure - Net::POP3.enable_ssl(OpenSSL::SSL::VERIFY_NONE) - end - Net::POP3.start(SiteSetting.pop3s_polling_host, - SiteSetting.pop3s_polling_port, - SiteSetting.pop3s_polling_username, - SiteSetting.pop3s_polling_password) do |pop| + def poll_pop3 + Net::POP3.start(SiteSetting.pop3_polling_host, + SiteSetting.pop3_polling_port, + SiteSetting.pop3_polling_username, + SiteSetting.pop3_polling_password) do |pop| unless pop.mails.empty? pop.each do |mail| handle_mail(mail) diff --git a/config/application.rb b/config/application.rb index f290bd5c9..a73266698 100644 --- a/config/application.rb +++ b/config/application.rb @@ -91,7 +91,7 @@ module Discourse # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [ :password, - :pop3s_polling_password, + :pop3_polling_password, :s3_secret_access_key, :twitter_consumer_secret, :facebook_app_secret, diff --git a/config/site_settings.yml b/config/site_settings.yml index 72a13e9c6..623e48e60 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -385,13 +385,12 @@ email: email_custom_headers: 'Auto-Submitted: auto-generated' reply_by_email_enabled: false reply_by_email_address: '' - pop3s_polling_enabled: false - pop3s_polling_insecure: false - pop3s_polling_period_mins: 5 - pop3s_polling_host: '' - pop3s_polling_port: 995 - pop3s_polling_username: '' - pop3s_polling_password: '' + pop3_polling_enabled: false + pop3_polling_period_mins: 5 + pop3_polling_host: '' + pop3_polling_port: 995 + pop3_polling_username: '' + pop3_polling_password: '' email_in: default: false client: true diff --git a/db/migrate/20140826234625_rename_settings_pop3s_to_pop3.rb b/db/migrate/20140826234625_rename_settings_pop3s_to_pop3.rb new file mode 100644 index 000000000..0672e5168 --- /dev/null +++ b/db/migrate/20140826234625_rename_settings_pop3s_to_pop3.rb @@ -0,0 +1,9 @@ +class RenameSettingsPop3sToPop3 < ActiveRecord::Migration + def up + execute "UPDATE site_settings SET name = replace(name, 'pop3s', 'pop3') WHERE name ILIKE 'pop3%'" + end + + def down + execute "UPDATE site_settings SET name = replace(name, 'pop3', 'pop3s') WHERE name ILIKE 'pop3%'" + end +end diff --git a/spec/jobs/poll_mailbox_spec.rb b/spec/jobs/poll_mailbox_spec.rb index b10dcc09e..3ba94945f 100644 --- a/spec/jobs/poll_mailbox_spec.rb +++ b/spec/jobs/poll_mailbox_spec.rb @@ -7,18 +7,18 @@ describe Jobs::PollMailbox do describe ".execute" do - it "does no polling if pop3s_polling_enabled is false" do - SiteSetting.expects(:pop3s_polling_enabled?).returns(false) - poller.expects(:poll_pop3s).never + it "does no polling if pop3_polling_enabled is false" do + SiteSetting.expects(:pop3_polling_enabled?).returns(false) + poller.expects(:poll_pop3).never poller.execute({}) end - describe "with pop3s_polling_enabled" do + describe "with pop3_polling_enabled" do - it "calls poll_pop3s" do - SiteSetting.expects(:pop3s_polling_enabled?).returns(true) - poller.expects(:poll_pop3s).once + it "calls poll_pop3" do + SiteSetting.expects(:pop3_polling_enabled?).returns(true) + poller.expects(:poll_pop3).once poller.execute({}) end @@ -26,7 +26,7 @@ describe Jobs::PollMailbox do end - describe ".poll_pop3s" do + describe ".poll_pop3" do it "logs an error on pop authentication error" do error = Net::POPAuthenticationError.new @@ -36,7 +36,7 @@ describe Jobs::PollMailbox do Discourse.expects(:handle_exception) - poller.poll_pop3s + poller.poll_pop3 end end From e28ef099a4f9213a87e5fb99fccc74629958cad1 Mon Sep 17 00:00:00 2001 From: riking Date: Tue, 26 Aug 2014 17:00:27 -0700 Subject: [PATCH 02/43] Fix pop3 SSL state leaking over multisite --- app/jobs/scheduled/poll_mailbox.rb | 8 ++++---- config/site_settings.yml | 1 + spec/jobs/poll_mailbox_spec.rb | 17 ++++++++++++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/app/jobs/scheduled/poll_mailbox.rb b/app/jobs/scheduled/poll_mailbox.rb index 8d86043c5..6226461f1 100644 --- a/app/jobs/scheduled/poll_mailbox.rb +++ b/app/jobs/scheduled/poll_mailbox.rb @@ -73,10 +73,10 @@ module Jobs end def poll_pop3 - Net::POP3.start(SiteSetting.pop3_polling_host, - SiteSetting.pop3_polling_port, - SiteSetting.pop3_polling_username, - SiteSetting.pop3_polling_password) do |pop| + connection = Net::POP3.new(SiteSetting.pop3_polling_host, SiteSetting.pop3_polling_port) + connection.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 do |mail| handle_mail(mail) diff --git a/config/site_settings.yml b/config/site_settings.yml index 623e48e60..0ec749d6c 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -386,6 +386,7 @@ email: reply_by_email_enabled: false reply_by_email_address: '' pop3_polling_enabled: false + pop3_polling_ssl: true pop3_polling_period_mins: 5 pop3_polling_host: '' pop3_polling_port: 995 diff --git a/spec/jobs/poll_mailbox_spec.rb b/spec/jobs/poll_mailbox_spec.rb index 3ba94945f..9b365a404 100644 --- a/spec/jobs/poll_mailbox_spec.rb +++ b/spec/jobs/poll_mailbox_spec.rb @@ -32,13 +32,28 @@ describe Jobs::PollMailbox do error = Net::POPAuthenticationError.new data = { limit_once_per: 1.hour, message_params: { error: error }} - Net::POP3.expects(:start).raises(error) + Net::POP3.any_instance.expects(:start).raises(error) Discourse.expects(:handle_exception) poller.poll_pop3 end + it "calls enable_ssl when the setting is enabled" 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 + 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 e0c2f3df3af259c4e6be9059494773a4cd9d6681 Mon Sep 17 00:00:00 2001 From: Eyal Levin Date: Wed, 27 Aug 2014 14:38:03 +0300 Subject: [PATCH 03/43] Enable RTL direction in emails. --- app/helpers/application_helper.rb | 21 +++-------- app/mailers/user_notifications.rb | 6 ++- app/models/rtl.rb | 29 +++++++++++++++ app/views/email/notification.html.erb | 2 +- lib/email/styles.rb | 1 + spec/models/rtl_spec.rb | 53 +++++++++++++++++++++++++++ 6 files changed, 94 insertions(+), 18 deletions(-) create mode 100644 app/models/rtl.rb create mode 100644 spec/models/rtl_spec.rb diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 064963813..efa4d2cbf 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -32,7 +32,11 @@ module ApplicationHelper end def html_classes - "#{mobile_view? ? 'mobile-view' : 'desktop-view'} #{mobile_device? ? 'mobile-device' : 'not-mobile-device'} #{rtl_view? ? 'rtl' : ''}" + "#{mobile_view? ? 'mobile-view' : 'desktop-view'} #{mobile_device? ? 'mobile-device' : 'not-mobile-device'} #{rtl_class}" + end + + def rtl_class + RTL.new(current_user).css_class end def escape_unicode(javascript) @@ -131,21 +135,6 @@ module ApplicationHelper MobileDetection.mobile_device?(request.user_agent) end - def rtl_view? - site_default_rtl? || current_user_rtl? - end - - def current_user_rtl? - SiteSetting.allow_user_locale && current_user.try(:locale).in?(rtl_locales) - end - - def site_default_rtl? - !SiteSetting.allow_user_locale && SiteSetting.default_locale.in?(rtl_locales) - end - - def rtl_locales - %w(he ar) - end def customization_disabled? controller.class.name.split("::").first == "Admin" || session[:disable_customization] diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb index 6ada3e2e4..d1e5e5845 100644 --- a/app/mailers/user_notifications.rb +++ b/app/mailers/user_notifications.rb @@ -198,7 +198,11 @@ class UserNotifications < ActionMailer::Base html = UserNotificationRenderer.new(Rails.configuration.paths["app/views"]).render( template: 'email/notification', format: :html, - locals: { context_posts: context_posts, post: post, top: top ? PrettyText.cook(top).html_safe : nil } + locals: { context_posts: context_posts, + post: post, + top: top ? PrettyText.cook(top).html_safe : nil, + classes: RTL.new(user).css_class + } ) template = "user_notifications.user_#{notification_type}" diff --git a/app/models/rtl.rb b/app/models/rtl.rb new file mode 100644 index 000000000..d4857c1fb --- /dev/null +++ b/app/models/rtl.rb @@ -0,0 +1,29 @@ +class RTL + + attr_reader :user + + def initialize(user) + @user = user + end + + def enabled? + site_locale_rtl? || current_user_rtl? + end + + def current_user_rtl? + SiteSetting.allow_user_locale && user.try(:locale).in?(rtl_locales) + end + + def site_locale_rtl? + !SiteSetting.allow_user_locale && SiteSetting.default_locale.in?(rtl_locales) + end + + def rtl_locales + %w(he ar) + end + + def css_class + enabled? ? 'rtl' : '' + end + +end diff --git a/app/views/email/notification.html.erb b/app/views/email/notification.html.erb index 8108cc693..363f2ae46 100644 --- a/app/views/email/notification.html.erb +++ b/app/views/email/notification.html.erb @@ -1,4 +1,4 @@ -
+
> <% if top.present? %>
<%= top %>
diff --git a/lib/email/styles.rb b/lib/email/styles.rb index ea7744edd..ecb7afb0b 100644 --- a/lib/email/styles.rb +++ b/lib/email/styles.rb @@ -58,6 +58,7 @@ module Email style('.user-avatar', 'vertical-align:top;width:55px;') style('.user-avatar img', nil, width: '45', height: '45') style('hr', 'background-color: #ddd; height: 1px; border: 1px;') + style('.rtl', 'direction: rtl;') # we can do this but it does not look right # style('#main', 'font-family:"Helvetica Neue", Helvetica, Arial, sans-serif') style('td.body', 'padding-top:5px;', colspan: "2") diff --git a/spec/models/rtl_spec.rb b/spec/models/rtl_spec.rb new file mode 100644 index 000000000..0d69a8588 --- /dev/null +++ b/spec/models/rtl_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe RTL do + + let(:user) { Fabricate.build(:user) } + + describe '.css_class' do + + context 'user locale is allowed' do + before { SiteSetting.stubs(:allow_user_locale).returns(true) } + + context 'user locale is RTL' do + before { user.stubs(:locale).returns('he') } + + it 'returns rtl class' do + RTL.new(user).css_class.should == 'rtl' + end + end + + context 'user locale is not RTL' do + it 'returns empty class' do + RTL.new(user).css_class.should == '' + end + end + + end + + context 'user locale is not allowed' do + before { SiteSetting.stubs(:allow_user_locale).returns(false) } + + context 'site default locale is RTL' do + before { SiteSetting.stubs(:default_locale).returns('he') } + + it 'returns rtl class' do + RTL.new(user).css_class.should == 'rtl' + end + end + + context 'site default locale is LTR' do + before { SiteSetting.stubs(:default_locale).returns('en') } + + context 'user locale is RTL' do + before { user.stubs(:locale).returns('he') } + + it 'returns empty class' do + RTL.new(user).css_class.should == '' + end + end + end + end + + end +end From bba3f7c0aceb6109661362a6aa834c672b4f9882 Mon Sep 17 00:00:00 2001 From: Wilhansen Li Date: Tue, 26 Aug 2014 23:52:07 +0800 Subject: [PATCH 04/43] Fix SSO avatar downloading issues. * Follow redirects when downloading SSO avatars. * Add proper image ext. to downloaded SSO avatars. --- app/models/discourse_single_sign_on.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/models/discourse_single_sign_on.rb b/app/models/discourse_single_sign_on.rb index 6e192717f..0476c384f 100644 --- a/app/models/discourse_single_sign_on.rb +++ b/app/models/discourse_single_sign_on.rb @@ -126,10 +126,12 @@ class DiscourseSingleSignOn < SingleSignOn avatar_force_update.to_i != 0 || sso_record.external_avatar_url != avatar_url) begin - tempfile = FileHelper.download(avatar_url, 1.megabyte, "sso-avatar") + tempfile = FileHelper.download(avatar_url, 1.megabyte, "sso-avatar", true) - upload = Upload.create_for(user.id, tempfile, "external-avatar", File.size(tempfile.path), { origin: avatar_url }) + ext = FastImage.type(tempfile).to_s + tempfile.rewind + upload = Upload.create_for(user.id, tempfile, "external-avatar." + ext, File.size(tempfile.path), { origin: avatar_url }) user.uploaded_avatar_id = upload.id if !user.user_avatar.contains_upload?(upload.id) From 3a9f0445bbd603887fbef76f840a78bdd51a55c0 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 28 Aug 2014 11:57:24 +1000 Subject: [PATCH 05/43] HACK: remove jarring scroll when hitting back from topic --- .../discourse/routes/discourse_location.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/routes/discourse_location.js b/app/assets/javascripts/discourse/routes/discourse_location.js index b7125ae1d..9dda10165 100644 --- a/app/assets/javascripts/discourse/routes/discourse_location.js +++ b/app/assets/javascripts/discourse/routes/discourse_location.js @@ -186,7 +186,9 @@ Ember.DiscourseLocation = Ember.Object.extend({ if (self.getURL() === self._previousURL) { return; } } var url = self.getURL(); - popstateCallbacks.forEach(function(cb) { cb(url); }); + popstateCallbacks.forEach(function(cb) { + cb(url); + }); callback(url); }); }, @@ -226,6 +228,16 @@ Ember.CloakedCollectionView.reopen({ _watchForPopState: function() { var self = this, cb = function() { + // Sam: This is a hack, but a very important one + // Due to the way we use replace state the back button works strangely + // + // If you visit a topic from the front page, scroll a bit around and then hit back + // you notice that first the page scrolls a bit (within the topic) and then it goes back + // this transition is jarring and adds uneeded rendering costs. + // + // To repro comment the hack out and wack a debugger statement here and in + // topic_route deactivate + $('.posts,#topic-title').hide(); self.cleanUp(); self.set('controller.postStream.loaded', false); }; From be8249f1135d4fdf195e2f1838f4769092a771ab Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Thu, 28 Aug 2014 08:52:11 +0530 Subject: [PATCH 06/43] Update Translations --- config/locales/client.da.yml | 1 - config/locales/client.de.yml | 189 ++++++++--------- config/locales/client.es.yml | 1 - config/locales/client.fi.yml | 1 - config/locales/client.fr.yml | 9 +- config/locales/client.he.yml | 1 - config/locales/client.it.yml | 5 +- config/locales/client.ja.yml | 1 - config/locales/client.ko.yml | 1 - config/locales/client.nl.yml | 54 ++++- config/locales/client.pl_PL.yml | 355 ++++++++++++++++++-------------- config/locales/client.pt.yml | 1 - config/locales/client.pt_BR.yml | 1 - config/locales/client.ru.yml | 10 +- config/locales/client.zh_CN.yml | 44 +++- config/locales/client.zh_TW.yml | 13 +- config/locales/server.de.yml | 1 - config/locales/server.es.yml | 1 - config/locales/server.fi.yml | 1 - config/locales/server.fr.yml | 1 - config/locales/server.he.yml | 1 - config/locales/server.it.yml | 1 - config/locales/server.nl.yml | 14 +- config/locales/server.pl_PL.yml | 56 ++--- config/locales/server.pt_BR.yml | 1 - config/locales/server.ru.yml | 1 - config/locales/server.uk.yml | 1 - config/locales/server.zh_CN.yml | 25 ++- config/locales/server.zh_TW.yml | 1 - 29 files changed, 496 insertions(+), 296 deletions(-) diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index 06924b6f8..1365b1765 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -626,7 +626,6 @@ da: invitee_accepted: "

{{username}} accepted your invitation

" moved_post: "

{{username}} moved {{description}}

" linked: "

{{username}} {{description}}

" - granted_badge: "

You were granted {{description}}

" upload_selector: title: "Indsæt billede" title_with_attachments: "Tilføj et billede eller en fil" diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index 19142bb20..2b0c887fc 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -287,7 +287,7 @@ de: muted_categories: "Stummgeschaltet" muted_categories_instructions: "Du erhältst keine Benachrichtigungen über neue Themen in diesen Kategorien und sie werden nicht in deiner Liste ungelesener Themen aufscheinen." delete_account: "Lösche mein Benutzerkonto" - delete_account_confirm: "Bist du sicher, dass du dein Benutzerkonto permanent löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden!" + delete_account_confirm: "Möchtest du wirklich dein Benutzerkonto permanent löschen? Diese Aktion kann nicht rückgängig gemacht werden!" deleted_yourself: "Dein Benutzerkonto wurde erfolgreich gelöscht." delete_yourself_not_allowed: "Du kannst im Moment dein Benutzerkonto nicht löschen. Kontaktiere einen Administrator, um dein Benutzerkonto löschen zu lassen." unread_message_count: "Nachrichten" @@ -310,7 +310,7 @@ de: title: "„Über mich“ ändern" change_username: title: "Benutzernamen ändern" - confirm: "Wenn du deinen Benutzernamen änderst, werden alle derzeit vorhandenen Zitate deiner Beiträge und alle Erwähnungen per @Name nicht mehr funktionieren. Bist Du sicher, dass du fortfahren willst?" + confirm: "Wenn du deinen Benutzernamen änderst, werden alle derzeit vorhandenen Zitate deiner Beiträge und alle Erwähnungen per @Name nicht mehr funktionieren. Bist du dir absolut sicher, dass du fortfahren willst?" taken: "Der Benutzername ist bereits vergeben." error: "Bei der Änderung deines Benutzernamens ist ein Fehler aufgetreten." invalid: "Der Benutzernamen ist nicht zulässig. Er darf nur Zahlen und Buchstaben enthalten." @@ -333,16 +333,19 @@ de: title: "Profilhintergrund" email: title: "E-Mail" + instructions: "Wird niemals öffentlich angezeigt." ok: "Schaut gut aus. Wir schicken dir eine E-Mail zur Bestätigung." invalid: "Bitte gib eine gültige E-Mail-Adresse ein." authenticated: "Deine E-Mail-Adresse wurde von {{provider}} bestätigt." frequency: "Wir werden dir nur E-Mails senden, wenn wir dich kürzlich nicht gesehen haben und wenn du die betroffenen Inhalte noch nicht gesehen hast." name: title: "Name" + instructions: "Längere Version deines Namens." too_short: "Dein Name ist zu kurz." ok: "Dein Name schaut gut aus." username: title: "Benutzername" + instructions: "Muss eindeutig, ohne Leerzeichen und kurz sein." short_instructions: "Andere Benutzer können dich mit @{{username}} erwähnen." available: "Dein Benutzername ist verfügbar." global_match: "Die E-Mail-Adresse stimmt mit dem registrierten Benutzernamen überein." @@ -368,7 +371,7 @@ de: website: "Website" email_settings: "E-Mail" email_digests: - title: "Schicke eine E-Mail mit Neuigkeiten, wenn ich die Seite länger nicht besuche:" + title: "Schicke eine E-Mail mit Neuigkeiten, wenn ich die Website länger nicht besuche:" daily: "täglich" weekly: "wöchentlich" bi_weekly: "alle zwei Wochen" @@ -447,10 +450,13 @@ de: prev_page: "während des Ladens" reasons: network: "Netzwerkfehler" + server: "Server-Fehler" + forbidden: "Zugriff verweigert" unknown: "Fehler" desc: network: "Bitte überprüfe deine Netzwerkverbindung." network_fixed: "Sieht aus, als wäre es wieder da." + server: "Fehlercode: {{status}}" unknown: "Etwas ist schief gelaufen." buttons: back: "Zurück" @@ -459,8 +465,8 @@ de: close: "Schließen" assets_changed_confirm: "Diese Website wurde gerade aktualisiert. Neu laden für die neuste Version?" read_only_mode: - enabled: "Ein Administrator hat den Nur-Lesen-Modus aktiviert. Du kannst die Seite weiter durchsuchen und lesen. Einige Funktionen werden jedoch wahrscheinlich nicht funktionieren." - login_disabled: "Login ist deaktiviert während sich die Seite im Nur-Lesen-Modus befindet." + enabled: "Ein Administrator hat den Nur-Lesen-Modus aktiviert. Du kannst die Website weiter durchsuchen und lesen. Einige Funktionen werden jedoch wahrscheinlich nicht funktionieren." + login_disabled: "Die Anmeldung ist deaktiviert während sich die Website im Nur-Lesen-Modus befindet." too_few_topics_notice: "Erstelle mindestens 5 öffentlich sichtbare Themen und %{posts} öffentlich sichtbare Beiträge, um die Unterhaltung ins Rollen zu bringen. Neue Benutzer können ihre Vertrauensstufe nur erhöhen, wenn es für sie etwas zu Lesen gibt. Diese Meldung wird nur Mitarbeitern angezeigt." learn_more: "mehr erfahren..." year: 'Jahr' @@ -605,7 +611,7 @@ de: redo_title: "Wiederholen" help: "Hilfe zur Markdown-Formatierung" toggler: "Eingabebereich aus- oder einblenden" - admin_options_title: "Optionale Moderator-Einstellungen für dieses Thema" + admin_options_title: "Optionale Mitarbeiter-Einstellungen für dieses Thema" auto_close_label: "Schließe das Thema automatisch nach:" auto_close_units: "(Stunden, Uhrzeit oder Datum)" auto_close_examples: 'Gib eine Anzahl von Stunden, eine Uhrzeit oder ein Datum ein — 24, 17:00, 2013-11-22 14:00' @@ -626,7 +632,6 @@ de: invitee_accepted: "

{{username}} hat deine Einladung angenommen

" moved_post: "

{{username}} hat {{description}} verschoben

" linked: "

{{username}} {{description}}

" - granted_badge: "

Dir wurde das Abzeichen {{description}} verliehen

" upload_selector: title: "Ein Bild hinzufügen" title_with_attachments: "Ein Bild oder eine Datei hinzufügen" @@ -651,7 +656,7 @@ de: site_map: "zu einer anderen Themenliste oder Kategorie wechseln" go_back: 'zurückgehen' not_logged_in_user: 'Benutzerseite mit einer Zusammenfassung der Benutzeraktivitäten und Einstellungen' - current_user: 'zu deiner Nutzerseite gehen' + current_user: 'zu deiner Benutzerseite gehen' starred: title: 'Favoriten' help: @@ -662,9 +667,9 @@ de: reset_read: "Gelesene zurücksetzen" delete: "Themen löschen" dismiss_posts: "Beiträge ignorieren" - dismiss_posts_tooltip: "Neue Beiträge in diesen Themen ignorieren, die Themen aber später wieder als ungelesen anzeigen, sobald weitere neue Beiträge verfasst wurden." + dismiss_posts_tooltip: "Neue Beiträge in diesen Themen ignorieren. Die Themen aber später wieder als ungelesen anzeigen, sobald neue Beiträge verfasst werden." dismiss_topics: "Themen ignorieren" - dismiss_topics_tooltip: "Diese Themen ignorieren und nicht mehr als ungelesen anzeigen, selbst wenn weitere neue Beiträge verfasst wurden." + dismiss_topics_tooltip: "Diese Themen ignorieren und nicht mehr als ungelesen anzeigen, selbst wenn neue Beiträge verfasst werden." dismiss_new: "Verwerfe neue Themen" toggle: "zu Massenoperationen auf Themen umschalten" actions: "Massenoperationen" @@ -789,13 +794,13 @@ de: description: "Die Anzahl der ungelesenen und neuen Nachrichten wird neben dem Eintrag in der Themenliste angezeigt. Du wirst nur dann benachrichtigt, wenn jemand deinen @Namen erwähnt oder deine Nachricht beantwortet." tracking: title: "Verfolgen" - description: "Die Anzahl der ungelesenen und neuen Beiträge wird neben dem Eintrag in der Themenliste angezeigt. Du wirst nur dann benachrichtigt, wenn jemand deinen @Namen erwähnt oder deinen Beitrag beantwortet." + description: "Die Anzahl der ungelesenen und neuen Beiträge wird neben dem Eintrag in der Themenliste angezeigt. Du wirst nur dann benachrichtigt, wenn jemand deinen @Namen erwähnt oder auf deinen Beitrag antwortet." regular: title: "Normal" description: "Du wirst nur dann benachrichtigt, wenn jemand deinen @Namen erwähnt oder auf deinen Beitrag antwortet." regular_pm: title: "Normal" - description: "Du wirst nur dann benachrichtigt, wenn jemand deinen @Namen erwähnt oder auf deinen Beitrag in der privaten Unterhaltung antwortet." + description: "Du wirst nur dann benachrichtigt, wenn jemand deinen @Namen erwähnt oder auf deine Nachricht in der privaten Unterhaltung antwortet." muted_pm: title: "Stummgeschaltet" description: "Du erhältst keine Benachrichtigungen über diese private Unterhaltung." @@ -847,10 +852,10 @@ de: title: 'Einladung' action: 'Einladung senden' help: 'Sendet Freunden eine Einladung, so dass mit einem Klick auf dieses Thema antworten können.' - to_topic: "Wir senden deinem Freund eine kurze Email, die ihm ermöglicht direkt auf dieses Thema zu antworten. Hierfür ist kein Login erforderlich." - to_forum: "Wir senden deinem Freund eine kurze Email, die ihm ermöglicht dem Forum beizutreten. Hierfür ist kein Login erforderlich." + to_topic: "Wir senden deinem Freund eine kurze E-Mail, die es ihm ermöglicht, direkt auf dieses Thema zu antworten. Es ist keine Anmeldung erforderlich." + to_forum: "Wir senden deinem Freund eine kurze E-Mail, die es ihm ermöglicht, dem Forum sofort beizutreten. Es ist keine Anmeldung erforderlich." email_placeholder: 'name@example.com' - success: "Wir haben deine Einladung an {{email}} verschickt. Wir lassen Dich wissen, sobald die Einladung eingelöst wird. In deinem Nutzerprofil kannst Du alle deine Einladungen überwachen." + success: "Wir haben an {{email}} eine Einladung verschickt. Wir werden dich benachrichtigen, sobald die Einladung angenommen wurde. Auf deiner Benutzerseite kannst du den Status deiner Einladungen verfolgen." error: "Entschuldige, wir konnten diese Person nicht einladen. Vielleicht ist sie schon ein Nutzer?" login_reply: 'Anmelden, um zu antworten' filters: @@ -931,7 +936,7 @@ de: image_upload_not_allowed_for_new_user: "Entschuldige, neue Benutzer dürfen keine Bilder hochladen." attachment_upload_not_allowed_for_new_user: "Entschuldige, neue Benutzer dürfen keine Dateien hochladen." abandon: - confirm: "Willst du deinen Beitrag wirklich verwerfen?" + confirm: "Möchtest du deinen Beitrag wirklich verwerfen?" no_value: "Nein, beibehalten" yes_value: "Ja, verwerfen" wiki: @@ -1055,8 +1060,8 @@ de: zero: keine Bearbeitungen delete: confirm: - one: "Bist du sicher, dass du diesen Beitrag löschen willst?" - other: "Bist du sicher, dass du all diese Beiträge löschen willst?" + one: "Möchtest du wirklich diesen Beitrag löschen?" + other: "Möchtest du wirklich all diese Beiträge löschen?" revisions: controls: first: "Erste Überarbeitung" @@ -1098,21 +1103,21 @@ de: badge_colors: "Plakettenfarben" background_color: "Hintergrundfarbe" foreground_color: "Vordergrundfarbe" - name_placeholder: "Sollte kurz und knapp sein." - color_placeholder: "Irgendeine Webfarbe" - delete_confirm: "Bist Du sicher, dass Du diese Kategorie löschen willst?" + name_placeholder: "Ein oder maximal zwei Wörter" + color_placeholder: "Irgendeine Web-Farbe" + delete_confirm: "Möchtest du wirklich diese Kategorie löschen?" delete_error: "Beim Löschen der Kategorie ist ein Fehler aufgetreten." list: "Kategorien auflisten" no_description: "Bitte füge eine Beschreibung für diese Kategorie hinzu." - change_in_category_topic: "Besuche die Themen dieser Kategorie um einen Eindruck für eine gute Beschreibung zu gewinnen." + change_in_category_topic: "Beschreibung bearbeiten" already_used: 'Diese Farbe wird bereits für eine andere Kategorie verwendet' security: "Sicherheit" images: "Bilder" auto_close_label: "Themen automatisch schließen nach:" auto_close_units: "Stunden" - email_in: "Benutzerdefinierte Email Adresse:" + email_in: "Benutzerdefinierte Adresse für eingehende E-Mails:" 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 Einstellungen deaktiviert. Um das Erstellen von neuen Themen per E-Mail zu erlauben," + 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“.' allow_badges_label: "Erlaube das Verleihen von Abzeichen in dieser Kategorie" edit_permissions: "Berechtigungen bearbeiten" @@ -1122,7 +1127,7 @@ de: default_position: "Standardposition" position_disabled: "Kategorien werden in der Reihenfolge der Aktivität angezeigt. Um die Reihenfolge von Kategorien in Listen zu steuern," position_disabled_click: 'aktiviere die Einstellung „fixed category positions“.' - parent: "Elternkategorie" + parent: "Übergeordnete Kategorie" notifications: watching: title: "Beobachten" @@ -1221,7 +1226,7 @@ de: categories: title: "Kategorien" title_in: "Kategorie - {{categoryName}}" - help: "Alle Themen, gruppiert nach Kategorie" + help: "alle Themen, gruppiert nach Kategorie" unread: title: zero: "Ungelesen" @@ -1284,8 +1289,8 @@ de: critical_available: "Ein kritisches Update ist verfügbar." updates_available: "Updates sind verfügbar." please_upgrade: "Bitte Upgrade durchführen!" - no_check_performed: "Es wurde nicht nach Updates gesucht. Bitte sicherstellen, dass sidekiq lauft." - stale_data: "Es wurde schon länger nicht nach Updates gesucht. Bitte sicherstellen, dass sidekiq lauft." + no_check_performed: "Es wurde nicht nach Updates gesucht. Bitte stelle sicher, dass sidekiq läuft." + stale_data: "Es wurde schon länger nicht nach Updates gesucht. Bitte stelle sicher, dass sidekiq läuft." version_check_pending: "Sieht so aus, als hättest du vor Kurzem upgedated. Großartig!" installed_version: "Installiert" latest_version: "Neueste" @@ -1395,8 +1400,8 @@ de: generate: "API-Schlüssel generieren" regenerate: "API-Schlüssel neu generieren" revoke: "Widerrufen" - confirm_regen: "Bist du sicher, dass du den API Key mit einem neuen ersetzen willst?" - confirm_revoke: "Bist du dir sicher, dass du den API Key widerrufen willst?" + confirm_regen: "Möchtest du wirklich den API Key mit einem neuen ersetzen?" + confirm_revoke: "Möchtest du wirklich den API Key widerrufen?" info_html: "Dein API-Schlüssel erlaubt Dir das Erstellen und Bearbeiten von Themen via JSON aufrufen." all_users: "Alle Benutzer" note_html: "Behalte deinen Schlüssel geheim, jeder Benutzer mit diesem Schlüssel kann beliebige Beiträge unter jedem Benutzer im Forum erstellen." @@ -1408,14 +1413,14 @@ de: none: "Kein Backup verfügbar." read_only: enable: - title: "Aktiviere den Nur-Lesen Modus" - text: "Aktiviere Nur-Lesen Modus" - confirm: "Bist du sicher, dass du den Nur-Lesen Modus aktivieren möchtest?" + title: "Nur-Lesen-Modus aktivieren" + text: "Nur-Lesen-Modus aktivieren" + confirm: "Möchtest du wirklich den Nur-Lesen Modus aktivieren?" disable: - title: "Deaktiviere den Nur-Lesen Modus" - text: "Deaktiviere Nur-Lesen Modus" + title: "Nur-Lesen-Modus deaktivieren" + text: "Nur-Lesen-Modus deaktivieren" logs: - none: "Noch keine Logs verfügbar..." + none: "Noch keine Protokolleinträge verfügbar..." columns: filename: "Dateiname" size: "Größe" @@ -1429,11 +1434,11 @@ de: failed: "Der Vorgang '{{operation}}' ist fehlgeschlagen. Bitte überprüfe die Logs." cancel: text: "Abbrechen" - title: "Bricht den aktuellen Vorgang ab" - confirm: "Bist du sicher, dass du den aktuellen Vorgang abbrechen möchtest?" + title: "Den aktuellen Vorgang abbrechen" + confirm: "Möchtest du wirklich den aktuellen Vorgang abbrechen?" backup: text: "Backup" - title: "Erstellt ein Backup" + title: "Ein Backup erstellen" confirm: "Willst du ein neues Backup starten?" without_uploads: "Ja (ohne Upload)" download: @@ -1441,17 +1446,17 @@ de: title: "Backup herunterladen" destroy: text: "Löschen" - title: "Löscht das Backup" - confirm: "Bist du sicher, dass du das Backup löschen möchtest?" + title: "Das Backup löschen" + confirm: "Möchtest du wirklich das Backup löschen?" restore: - is_disabled: "Wiederherstellung ist in den Einstellungen deaktiviert." + is_disabled: "Wiederherstellung ist in den Website-Einstellungen deaktiviert." text: "Wiederherstellen" - title: "Wiederherstellung des Backups" - confirm: "Bist du sicher, dass du dieses Backup wiederherstellen möchtest?" + title: "Das Backup wiederherstellen" + confirm: "Möchtest du wirklich dieses Backup wiederherstellen?" rollback: text: "Zurücksetzen" - title: "Setzt die Datenbank auf den letzten funktionierenden Zustand zurück" - confirm: "Bist du sicher, dass du die Datenbank auf den letzten funktionierenden Zustand zurücksetzen möchtest?" + title: "Die Datenbank auf den letzten funktionierenden Zustand zurücksetzen" + confirm: "Möchtest du wirklich die Datenbank auf den letzten funktionierenden Stand zurücksetzen?" export_csv: users: text: "Exportieren" @@ -1459,61 +1464,61 @@ de: success: "Der Export wurde gestartet. Du wirst in Kürze über den Fortschritt benachrichtigt." failed: "Der Export ist fehlgeschlagen. Bitte überprüfe die Logs." customize: - title: "Personalisieren" - long_title: "Seite personalisieren" - header: "Header" + title: "Anpassen" + long_title: "Website-Anpassungen" + header: "Kopfbereich" css: "Stylesheet" - mobile_header: "Mobiler Header" - mobile_css: "Mobiles Stylesheet" - override_default: "Standard überschreiben?" + mobile_header: "Kopfbereich für Mobilgeräte" + mobile_css: "Stylesheet Mobilgeräte" + override_default: "Das Standard-Stylesheet nicht verwenden" enabled: "Aktiviert?" preview: "Vorschau" undo_preview: "Vorschau entfernen" - rescue_preview: "Standardstil" - explain_preview: "Zeige die Seite mit benutzerdefiniertem Stylesheet" + rescue_preview: "Standard-Style" + explain_preview: "Zeige die Website mit benutzerdefiniertem Stylesheet an" explain_undo_preview: "Gehe zurück zum aktuell aktivierten benutzerdefinierten Stylesheet" - explain_rescue_preview: "Zeige die Seite mit dem Standard-Stylesheet" + explain_rescue_preview: "Zeige die Website mit dem Standard-Stylesheet an" save: "Speichern" new: "Neu" - new_style: "Neuer Stil" + new_style: "Neuer Style" delete: "Löschen" delete_confirm: "Diese Anpassung löschen?" - about: "Ändere CSS stylesheets und HTML header auf der Seite. Füge eine Anpassung hinzu um zu starten." + about: "Ändere die Stylesheets (CSS) und den HTML-Header auf der Website. Füge eine Anpassung hinzu, um zu starten." color: "Farbe" opacity: "Transparenz" copy: "Kopieren" css_html: - title: "CSS, HTML" + title: "CSS/HTML" long_title: "CSS und HTML Anpassungen" colors: title: "Farben" - long_title: "Farb-Schemata" - about: "Farbschemen erlauben dir die auf der Seite benutzen Farben zu ändern ohne CSS schreiben zu müssen. Wähle oder füge eine hinzu, um zu beginnen." + long_title: "Farbschemata" + about: "Farbschemen erlauben dir die auf der Seite benutzen Farben zu ändern ohne CSS schreiben zu müssen. Füge ein Schema hinzu, um zu beginnen." new_name: "Neues Farbschema" copy_name_prefix: "Kopie von" delete_confirm: "Dieses Farbschema löschen?" undo: "rückgängig" undo_title: "Änderungen an dieser Farbe bis zum lezten Speichern rückgängig machen" revert: "verwerfen" - revert_title: "Diese Farbe auf Discourse Standard Farbschema zurücksetzen." + revert_title: "Diese Farbe auf das Discourse-Standard-Farbschema zurücksetzen." primary: name: 'erste' description: 'Die meisten Texte, Bilder und Ränder.' secondary: name: 'zweite' - description: 'Die Haupt Hintergrundfarbe, und Textfarbe einiger Schaltflächen.' + description: 'Die Haupthintergrundfarbe und Textfarbe einiger Schaltflächen.' tertiary: - name: 'tertiär' - description: 'Links, einige Schaltflächen, Benachrichtigungen und Akzentfarben' + name: 'dritte' + description: 'Links, einige Schaltflächen, Benachrichtigungen und Akzentfarben.' quaternary: - name: "quaternären" + name: "vierte" description: "Navigations-Links" header_background: name: "Hintergrund Kopfbereich" - description: "Hintergrundfarbe des Seitenkopfs." + description: "Hintergrundfarbe des Kopfbereichs der Website." header_primary: name: "primärer Kopfbereich" - description: "Texte und Bilder im Kopfbereich der Seite." + description: "Text und Symbole im Kopfbereich der Website." highlight: name: 'hervorheben' description: 'Die Hintergrundfarbe der hervorgehobenen Elemente auf der Seite, so wie Beiträge und Themen.' @@ -1527,33 +1532,33 @@ de: name: 'Liebe' description: "Die Farbe des Like-Buttons." email: - title: "Mailprotokoll" + title: "E-Mail" settings: "Einstellungen" all: "Alle" - sending_test: "Versende Testemail..." + sending_test: "Versende Test-E-Mail..." test_error: "Es gab ein Problem beim Senden der Test-E-Mail. Bitte überprüfe nochmals deine E-Mail-Einstellungen, stelle sicher dass dein Anbieter keine E-Mail-Verbindungen blockiert und probiere es erneut." sent: "Gesendet" skipped: "Übersprungen" sent_at: "Gesendet am" time: "Zeit" user: "Benutzer" - email_type: "Mailtyp" + email_type: "E-Mail-Typ" to_address: "Empfänger" - test_email_address: "Mailadresse zum Testen" - send_test: "Testmail senden" + test_email_address: "E-Mail-Adresse für Test" + send_test: "Test-E-Mail senden" sent_test: "Gesendet!" delivery_method: "Versandmethode" preview_digest: "Vorschau auf Neuigkeiten anzeigen" preview_digest_desc: "Hiermit kannst du dir anschauen, wie die regelmäßig versendeten Neuigkeits-Mails aussehen." refresh: "Aktualisieren" format: "Format" - html: "html" - text: "text" + html: "HTML" + text: "Text" last_seen_user: "Letzter Benutzer:" reply_key: "Antwort-Schlüssel" skipped_reason: "Grund des Überspringens" logs: - none: "Keine Protokolle gefunden." + none: "Keine Protokolleinträge gefunden." filters: title: "Filter" user_placeholder: "Benutzername" @@ -1568,14 +1573,14 @@ de: match_count: "Treffer" ip_address: "IP" delete: 'Löschen' - edit: 'Editieren' + edit: 'Bearbeiten' save: 'Speichern' screened_actions: block: "blockieren" - do_nothing: "nichts machen" + do_nothing: "nichts tun" staff_actions: - title: "Mitarbeiter Aktion" - instructions: "Klicke auf die Benutzernamen und Aktionen um die Liste zu filtern. Klicke den Avatar um die Benutzerseite zu sehen." + title: "Mitarbeiter-Aktionen" + instructions: "Klicke auf die Benutzernamen und Aktionen, um die Liste zu filtern. Klicke auf Avatare, um die Benutzerseiten zu sehen." clear_filters: "Alles anzeigen" staff_user: "Mitarbeiter" target_user: "Zielnutzer" @@ -1585,36 +1590,36 @@ de: details: "Details" previous_value: "Alt" new_value: "Neu" - diff: "Diff" + diff: "Vergleich" show: "Anzeigen" modal_title: "Details" - no_previous: "Es gibt keinen vorgängigen Wert." + no_previous: "Es gibt keinen vorherigen Wert." deleted: "Kein neuer Wert. Der Eintrag wurde gelöscht." actions: delete_user: "Benutzer löschen" change_trust_level: "Vertrauensstufe ändern" - change_site_setting: "Seiteneinstellungen ändern" - change_site_customization: "Seitenanpassungen ändern" - delete_site_customization: "Seitenanpassungen löschen" + change_site_setting: "Website-Einstellungen ändern" + change_site_customization: "Website-Anpassungen ändern" + delete_site_customization: "Website-Anpassungen löschen" suspend_user: "gesperrter Benutzer" unsuspend_user: "ungesperrter Benutzer" grant_badge: "Abzeichen verleihen" revoke_badge: "Abzeichen entziehen" screened_emails: - title: "Geschützte E-Mails" + title: "Überprüfte E-Mails" description: "Wenn jemand ein Konto erstellt, werden die folgenden E-Mail-Adressen überprüft und es wird die Anmeldung blockiert oder eine andere Aktion ausgeführt." email: "E-Mail-Adresse" actions: allow: "Erlauben" screened_urls: - title: "Geschützte URLs" + title: "Überprüfte URLs" description: "Die aufgelisteten URLs wurden in Beiträgen von identifizierten Spammen verwendet." url: "URL" domain: "Domain" screened_ips: - title: "Geschützte IPs" + title: "Überprüfte IPs" description: 'IP-Adressen die beobachtet werden. Benutze „Erlauben“ um IP-Adressen auf die Whitelist zu setzen.' - delete_confirm: "Bist du dir sicher, dass du die Rege für %{ip_address} entfernen willst?" + delete_confirm: "Möchtest du wirklich die Regel für %{ip_address} entfernen?" actions: block: "Blockieren" do_nothing: "Erlauben" @@ -1675,11 +1680,11 @@ de: unsuspend_failed: "Beim Entsperren dieses Benutzers ist etwas schief gegangen {{error}}" suspend_duration: "Wie lange soll dieser Benutzer gesperrt werden?" suspend_duration_units: "(Tage)" - suspend_reason_label: "Warum wird gesperrt? Dieser Text ist auf der Profilseite des Benutzers für jeden sichtbar und wird dem Benutzer angezeigt, wenn sich dieser anmelden will. Bitte kurz halten." + suspend_reason_label: "Warum sperrst du? Dieser Text ist auf der Profilseite des Benutzers für jeden sichtbar und wird dem Benutzer angezeigt, wenn sich dieser anmelden will. Bitte kurz halten." suspend_reason: "Grund" suspended_by: "Gesperrt von" delete_all_posts: "Lösche alle Beiträge" - delete_all_posts_confirm: "Du wirst %{posts} Beiträge und %{topics} Themen löschen. Bist du sicher?" + delete_all_posts_confirm: "Du wirst %{posts} Beiträge und %{topics} Themen löschen. Bist du dir sicher?" suspend: "Sperren" unsuspend: "Entsperren" suspended: "Gesperrt?" @@ -1824,9 +1829,9 @@ de: granted_at: Verliehen am save: Speichern delete: Löschen - delete_confirm: Bist du dir sicher, dass du dieses Abzeichen löschen willst? + delete_confirm: Möchtest du wirklich dieses Abzeichen löschen? revoke: Entziehen - revoke_confirm: Bist du dir sicher, dass du dieses Abzeichen entziehen willst? + revoke_confirm: Möchtest du wirklich dieses Abzeichen entziehen? edit_badges: Abzeichen bearbeiten grant_badge: Abzeichen verleihen granted_badges: Verliehene Abzeichen diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index 33e935f80..7f5f43412 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -626,7 +626,6 @@ es: invitee_accepted: "

{{username}} ha aceptado tu invitación

" moved_post: "

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

" linked: "

{{username}} {{description}}

" - granted_badge: "

Se te ha concedido {{description}}

" upload_selector: title: "Añadir imagen" title_with_attachments: "Añadir una imagen o archivo" diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index fc521e9c1..c160c6293 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -625,7 +625,6 @@ fi: invitee_accepted: "

{{username}} hyväksyi kutsusi

" moved_post: "

{{username}} siirsi {{description}}

" linked: "

{{username}} {{description}}

" - granted_badge: "

Sinulle myönnettiin {{description}}

" upload_selector: title: "Lisää kuva" title_with_attachments: "Lisää kuva tai tidosto" diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index cca1bd910..86ec968c4 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -333,16 +333,19 @@ fr: title: "Arrière plan du profil" email: title: "Courriel" + instructions: "Ne sera jamais visible publiquement." ok: "Cela semble correct. Un courriel de confirmation sera envoyé." invalid: "Merci de saisir une adresse de courriel valide." authenticated: "Votre adresse de courriel a été authentifiée par {{provider}}." frequency: "Nous vous envoyons des courriels contenant uniquement des informations que vous n'avez pas déjà vues lors d'une précédente connexion." name: title: "Nom" + instructions: "Nom complet." too_short: "Votre nom est trop court." ok: "Votre nom semble correct." username: title: "Pseudo" + instructions: "Doit être unique, court et sans espace." short_instructions: "Les utilisateurs peuvent vous mentionner avec @{{username}}." available: "Votre pseudo est disponible." global_match: "L'adresse de courriel correspond au pseudo enregistré." @@ -447,10 +450,13 @@ fr: prev_page: "lors d'une tentative de chargement" reasons: network: "Erreur réseau" + server: "Erreur serveur" + forbidden: "Accès refusé" unknown: "Erreur" desc: network: "Veuillez vérifier votre connexion." network_fixed: "On dirait que c'est revenu." + server: "Code d'erreur: {{status}}" unknown: "Une erreur est survenue." buttons: back: "Retour" @@ -576,7 +582,7 @@ fr: view_new_post: "Voir votre nouveau message." saving: "Sauvegarde…" saved: "Sauvegardé !" - saved_draft: "Vous un message brouillon en cours. Sélectionner cette barre pour reprendre son édition." + saved_draft: "Vous avez un message brouillon en cours. Sélectionner cette barre pour reprendre son édition." uploading: "Envoi en cours…" show_preview: 'afficher la prévisualisation »' hide_preview: '« cacher la prévisualisation' @@ -626,7 +632,6 @@ fr: invitee_accepted: "

{{username}} a accepté votre invitation

" moved_post: "

{{username}} a déplacé {{description}}

" linked: "

{{username}} {{description}}

" - granted_badge: "

On vous a décerné {{description}}

" upload_selector: title: "Ajouter une image" title_with_attachments: "Ajouter une image ou un fichier" diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index 032adbe9f..d288a9861 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -615,7 +615,6 @@ he: invitee_accepted: "

{{username}} אישר/ה את הזמנתך

" moved_post: "

{{username}} הזיז/ה {{description}}

" linked: "

{{username}} {{description}}

" - granted_badge: "

הוענק/ו לך {{description}}

" upload_selector: title: "הוספת תמונה" title_with_attachments: "הוספת תמונה או קובץ" diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index bc52dfaf6..2b45e1d91 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -447,6 +447,8 @@ it: prev_page: "durante il caricamento" reasons: network: "Errore di Rete" + server: "Errore del Server" + forbidden: "Accesso Negato" unknown: "Errore" desc: network: "Per favore controlla la tua connessione." @@ -626,7 +628,6 @@ it: invitee_accepted: "

{{username}}ha accettato il tuo invito

" moved_post: "

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

" linked: "

{{username}} {{description}}

" - granted_badge: "

Ti è stato assegnato il grado {{description}}

" upload_selector: title: "Aggiungi un'immagine" title_with_attachments: "Aggiungi un'immagine o un file" @@ -1763,6 +1764,8 @@ it: qualifies: "Requisiti per il livello 3" will_be_promoted: "Verrà promosso entro 24 ore." does_not_qualify: "Non si qualifica per il livello 3." + sso: + external_name: "Nome" site_content: none: "Scegli un tipo di contenuto per iniziare" title: 'Contenuto' diff --git a/config/locales/client.ja.yml b/config/locales/client.ja.yml index 38f4d0687..3c0901e39 100644 --- a/config/locales/client.ja.yml +++ b/config/locales/client.ja.yml @@ -584,7 +584,6 @@ ja: invitee_accepted: "

{{username}} accepted your invitation

" moved_post: "

{{username}} moved {{description}}

" linked: "

{{username}} {{description}}

" - granted_badge: "

{{description}}バッジを付けられました

" upload_selector: title: "画像のアップロード" title_with_attachments: "画像/ファイルをアップロード" diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml index c4efa3c16..158c39668 100644 --- a/config/locales/client.ko.yml +++ b/config/locales/client.ko.yml @@ -595,7 +595,6 @@ ko: invitee_accepted: "

{{username}} accepted your invitation

" moved_post: "

{{username}} moved {{description}}

" linked: "

{{username}} {{description}}

" - granted_badge: "

{{description}} 배지를 받았습니다.

" upload_selector: title: "이미지 추가하기" title_with_attachments: "이미지 또는 파일 추가하기" diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index f99209da9..92903367d 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -626,7 +626,6 @@ nl: invitee_accepted: "

{{username}} accepted your invitation

" moved_post: "

{{username}} moved {{description}}

" linked: "

{{username}} {{description}}

" - granted_badge: "

Je hebt {{description}} gekregen

" upload_selector: title: "Voeg een afbeelding toe" title_with_attachments: "Voeg een afbeelding of bestand toe" @@ -1292,15 +1291,45 @@ nl: title: "Meldingen" old: "Oud" active: "Actief" + agree: "Akkoord" + agree_title: "Bevestig dat deze melding geldig en correct is" + agree_flag_modal_title: "Akkoord en ... " + agree_flag_hide_post: "Akkoord (verberg bericht en stuur privébericht)" + agree_flag_hide_post_title: "Verberg dit bericht en stuur de gebruiker automatisch een privébericht met het verzoek om het bericht aan te passen. " + agree_flag: "Akkoord met melding" + agree_flag_title: "Akkoord met melding en het bericht ongewijzigd laten" + defer_flag: "Negeer" + defer_flag_title: "Verwijder deze melding; nu geen actie nodig" + delete: "Verwijder" + delete_title: "Verwijder het bericht waar deze melding naar verwijst" + delete_post_defer_flag: "Verwijder bericht en negeer melding" + delete_post_defer_flag_title: "Verwijder bericht; de hele topic als dit het eerste bericht is" + delete_post_agree_flag: "Verwijder bericht en akkoord met melding" + delete_post_agree_flag_title: "Verwijder bericht; de hele topic als dit het eerste bericht is" + delete_flag_modal_title: "Verwijder en ... " + delete_spammer: "Verwijder spammer" + delete_spammer_title: "Verwijder de gebruiker en al hun berichten en topics." + disagree_flag_unhide_post: "Niet akkoord (toon bericht)" + disagree_flag_unhide_post_title: "Verwijder elke melding van dit bericht en maak het weer zichtbaar" + disagree_flag: "Niet akkoord" + disagree_flag_title: "Deze melding is ongeldig of niet correct" clear_topic_flags: "Gedaan" clear_topic_flags_title: "Het topic is onderzocht en problemen zijn opgelost. Klik op Gedaan om de meldingen te verwijderen." + more: "(meer antwoorden...)" + dispositions: + agreed: "akkoord" + disagreed: "niet akkoord" + deferred: "genegeerd" flagged_by: "Gemarkeerd door" resolved_by: "Opgelost door" + took_action: "Heeft actie ondernomen" system: "Systeem" error: "Er ging iets mis" + reply_message: "Reageer" no_results: "Er zijn geen markeringen" topic_flagged: "Deze topic is gemarkeerd." visit_topic: "Ga naar de topic om te zien wat er aan de hand is en om actie te ondernemen" + was_edited: "Bericht is gewijzigd na de eerste melding" summary: action_type_3: one: "off-topic" @@ -1379,6 +1408,8 @@ nl: backup: text: "Backup" title: "Maak een backup" + confirm: "Wil je een nieuwe backup starten? " + without_uploads: "Ja (zonder upload)" download: text: "Download" title: "Download de backup" @@ -1395,6 +1426,12 @@ nl: text: "Rollback" title: "Herstel de database naar de laatst werkende versie" confirm: "Weet je zeker dat je de database wil herstellen naar de laatste versie?" + export_csv: + users: + text: "Exporteer gebruikers" + title: "Exporteer gebruikerslijst in *.CSV-formaat" + success: "Exporteren is gestart; over enkele momenten krijg je meer informatie over de voortgang" + failed: "Exporteren is mislukt. Controleer de logbestanden." customize: title: "Aanpassingen" long_title: "Aanpassingen aan de site" @@ -1625,9 +1662,11 @@ nl: edit_title: "Wijzig titel" save_title: "Bewaar titel" refresh_browsers: "Forceer browser refresh" + refresh_browsers_message: "Bericht verstuurd aan alle gebruikers!" show_public_profile: "Bekijk openbaar profiel" impersonate: 'Log in als gebruiker' ip_lookup: "Zoek IP-adres op" + log_out: "Uitloggen" logged_out: "Gebruiker is uitgelogd op alle apparaten" revoke_admin: 'Ontneem beheerdersrechten' grant_admin: 'Geef Beheerdersrechten' @@ -1638,12 +1677,15 @@ nl: reputation: Reputatie permissions: Toestemmingen activity: Activiteit + like_count: '''Vind ik leuks'' gegeven / ontvangen' + last_100_days: 'in de laatste 100 dagen' private_topics_count: Privétopics posts_read_count: Berichten gelezen post_count: Berichten gemaakt topics_entered: Topics bekeken flags_given_count: Meldingen gedaan flags_received_count: Meldigen ontvangen + flags_given_received_count: 'Meldingen gedaan / ontvangen' approve: 'Accepteer' approved_by: "Geaccepteerd door" approve_success: "Gebruiker geaccepteerd en e-mail verzonden met instructies voor activering." @@ -1660,6 +1702,9 @@ nl: cant_delete_all_too_many_posts: one: "Kan niet alle berichten verwijderen omdat de gebruiker meer dan 1 bericht heeft (delete_all_posts_max)." other: "Kan niet alle berichten verwijderen omdat de gebruiker meer dan %{count} berichten heeft (delete_all_posts_max)." + delete_confirm: "Weet je zeker dat je deze gebruiker definitief wil verwijderen? Deze handeling kan niet ongedaan worden gemaakt! " + delete_and_block: "Verwijder en blokkeer dit e-mail- en IP-adres" + delete_dont_block: "Alleen verwijderen" deleted: "De gebruiker is verwijderd." delete_failed: "Er ging iets mis bij het verwijderen van deze gebruiker. Zorg er voor dat alle berichten van deze gebruiker eerst verwijderd zijn." send_activation_email: "Verstuur activatiemail" @@ -1692,9 +1737,16 @@ nl: posts_read_all_time: "Berichten gelezen (ooit)" flagged_posts: "Gemarkeerde berichten" flagged_by_users: "Gebruikers die gemarkeerd hebben" + likes_given: "'Vind ik leuks' gegeven" + likes_received: "'Vind ik leuks' ontvangen" qualifies: "Komt in aanmerking voor Trust Level 3" will_be_promoted: "Zal over 24 uur gepromoveerd worden." does_not_qualify: "Komt niet in aanmerking voor Trust Level 3" + sso: + external_username: "Gebruikersnaam" + external_name: "Naam" + external_email: "E-mail" + external_avatar_url: "URL voor profielfoto" site_content: none: "Selecteer een tekst om deze te bewerken" title: 'Teksten' diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml index 374a65cd3..5de8668b3 100644 --- a/config/locales/client.pl_PL.yml +++ b/config/locales/client.pl_PL.yml @@ -126,7 +126,7 @@ pl_PL: never: "nigdy" daily: "dziennie" weekly: "tygodniowo" - every_two_weeks: "na każde dwa tygodnie" + every_two_weeks: "co dwa tygodnie" max: "maks" character_count: one: "1 znak" @@ -185,10 +185,10 @@ pl_PL: preview: "podgląd" cancel: "anuluj" save: "Zapisz zmiany" - saving: "Zapisywanie..." + saving: "Zapisuję…" saved: "Zapisano!" upload: "Wgraj" - uploading: "Wgrywanie..." + uploading: "Wysyłam…" uploaded: "Wgrano!" enable: "Włącz" disable: "Wyłącz" @@ -203,16 +203,16 @@ pl_PL: placeholder: "tutaj wpisz tytuł tematu" user_action: user_posted_topic: "{{user}} utworzył(-a) temat" - you_posted_topic: "Dodałeś(-aś) temat" - user_replied_to_post: "{{user}} odpowiedział(-a) na {{post_number}}" - you_replied_to_post: "Odpisałeś(-aś) na {{post_number}}" - user_replied_to_topic: "{{user}} odpisał(-a) na temat" - you_replied_to_topic: "Dodałeś(-aś) odpowiedź w temacie" - user_mentioned_user: "{{user}} wspomniał(-a) o {{another_user}}" + you_posted_topic: "Dodajesz temat" + user_replied_to_post: "{{user}} odpowiada na {{post_number}}" + you_replied_to_post: "Twoja odpowiedź na {{post_number}}" + user_replied_to_topic: "{{user}} odpisuje na temat" + you_replied_to_topic: "Twoja odpowiedź w temacie" + user_mentioned_user: "{{user}} wspomina o {{another_user}}" user_mentioned_you: "{{user}} wspomniał o tobie" you_mentioned_user: "Wspomniałeś/aś o użytkowniku {{another_user}}" - posted_by_user: "Dodano przez {{user}}" - posted_by_you: "Dodano przez Ciebie" + posted_by_user: "Dodany przez {{user}}" + posted_by_you: "Dodany przez Ciebie" sent_by_user: "Wysłano przez {{user}}" sent_by_you: "Wysłano przez Ciebie" groups: @@ -231,8 +231,8 @@ pl_PL: members_mods_and_admins: "Tylko członkowie grupy, moderatorzy i administratorzy" everyone: "Wszyscy" user_action_groups: - '1': "Polubień" - '2': "Otrzymanych polubień" + '1': "Przyznane polubienia " + '2': "Otrzymane polubienia" '3': "Zakładki" '4': "Tematy" '5': "Wpisy" @@ -248,21 +248,22 @@ pl_PL: all_subcategories: "wszystkie" no_subcategory: "żadne" category: "Kategoria" + posts: "Wpisy" topics: "Tematy" - latest: "Najnowsze" + latest: "Ostatnie" latest_by: "najnowsze przez" toggle_ordering: "przełącz kolejność kontroli" subcategories: "Podkategorie" topic_stats: "Liczba nowych tematów." topic_stat_sentence: - one: "%{count} nowy temat w ciągu ostatnich %{unit}." - few: "%{count} nowych tematów w ciągu ostatnich %{unit}." - other: "%{count} nowych tematów w ciągu ostatnich %{unit}." + one: "%{count} nowy temat – ostatni %{unit}." + few: "%{count} nowe tematy – ostatni %{unit}." + other: "%{count} nowych tematów – ostatni %{unit}." post_stats: "Liczba nowych wpisów." post_stat_sentence: - one: "%{count} nowy wpis w ciągu ostatnich %{unit}." - few: "%{count} nowych wpisów w ciągu ostatnich %{unit}." - other: "%{count} nowych wpisów w ciągu ostatnich %{unit}." + one: "%{count} nowy wpis – ostatni %{unit}." + few: "%{count} nowe wpisy – ostatni %{unit}." + other: "%{count} nowych wpisów – ostatni %{unit}." ip_lookup: title: Wyszukiwanie adresu IP hostname: Nazwa hosta @@ -301,9 +302,9 @@ pl_PL: suspended_reason: "Powód: " mailing_list_mode: "Otrzymuj email dla każdego wpisu na forum (o ile nie wyciszysz kategorii lub tematu)" watched_categories: "Obserwowane" - watched_categories_instructions: "Będziesz automatycznie śledzić wszystkie nowe tematy w tych kategoriach. Otrzymasz powiadomienia o wszystkich nowych wpisach i tematach, a liczba nieprzeczytanych i nowych wpisów będzie wyświetlana obok tytułów na liście tematów." + watched_categories_instructions: "Będziesz automatycznie śledzić wszystkie nowe tematy w tych kategoriach: liczba nieprzeczytanych i nowych wpisów będzie wyświetlana obok tytułów na liście tematów. Dodatkowo będziesz otrzymywać powiadomienie o każdym nowym wpisie i temacie." tracked_categories: "Śledzone" - tracked_categories_instructions: "Będziesz automatycznie śledzić wszystkie nowe tematy w tych kategoriach. Licznik nowych i nieprzeczytanych wpisów pojawi się obok tytułu na liście tematów." + tracked_categories_instructions: "Będziesz automatycznie śledzić wszystkie nowe tematy w tych kategoriach: licznik nowych i nieprzeczytanych wpisów pojawi się obok tytułu na liście tematów." muted_categories: "Wyciszone" muted_categories_instructions: "Nie będziesz powiadamiany o niczym dotyczącym nowych tematów w tych kategoriach, i nie będą się one pojawiać na karcie nieprzeczytanych." delete_account: "Usuń moje konto" @@ -328,7 +329,7 @@ pl_PL: title: "Zmień O mnie" change_username: title: "Zmień nazwę użytkownika" - confirm: "Jeżeli zmienisz swoją nazwę użytkownika, wszystkie wcześniejsze cytaty z Twoich wpisów i wzmianki przez @nazwa będą uszkodzone. Czy na pewno tego chcesz?" + confirm: "Jeżeli zmienisz swoją nazwę użytkownika, wszystkie stare cytaty twoich wpisów oraz wzmianki przez @nazwę przestaną działać. Czy na pewno tego chcesz?" taken: "Przykro nam, ale ta nazwa użytkownika jest zajęta." error: "Podczas zmiany twojej nazwy użytkownika wystąpił błąd." invalid: "Ta nazwa użytkownika jest niepoprawna. Powinna ona zawierać jedynie liczby i litery." @@ -351,24 +352,27 @@ pl_PL: title: "Tło profilu" email: title: "Email" + instructions: "Nigdy nie będzie wyświetlony publicznie." ok: "Wygląda dobrze. Wyślemy Ci wiadomość do potwierdzenia." invalid: "Podaj poprawny adres email." authenticated: "Twój adres email został potwierdzony przez {{provider}}." frequency: "Wyślemy do Ciebie email tylko jeżeli dawno Cię nie widzieliśmy i wyłącznie na temat rzeczy których jeszcze nie widziałeś." name: - title: "Imię" + title: "Pełna nazwa" + instructions: "Pełna wersja twojej nazwy." too_short: "Twoja nazwa użytkownika jest za krótka." ok: "Twoja nazwa użytkownika jest poprawna." username: title: "Nazwa użytkownika" - short_instructions: "Aby o Tobie wspomnieć, wystarczy napisać @{{username}}." + instructions: "Powinien być unikalny, krótki i bez spacji." + short_instructions: "Aby o tobie wspomnieć, wystarczy napisać @{{username}}." available: "Ta nazwa użytkownika jest dostępna." global_match: "Email zgadza się z zarejestrowaną nazwą użytkownika." global_mismatch: "Zajęta. Może spróbuj {{suggestion}}?" not_available: "Niedostępna. Może spróbuj {{suggestion}}?" too_short: "Nazwa użytkownika jest za krótka." too_long: "Nazwa użytkownika jest za długa." - checking: "Sprawdzanie, czy nazwa użytkownika jest dostępna..." + checking: "Sprawdzanie, czy nazwa użytkownika jest dostępna…" enter_email: 'Nazwa użytkownika znaleziona. Wpisz przypisany adres email.' prefilled: "Email zgadza się z zarejestrowaną nazwą użytkownika." locale: @@ -390,7 +394,7 @@ pl_PL: daily: "codziennie" weekly: "co tydzień" bi_weekly: "co 2 tygodnie" - email_direct: "Wyślij powiadomienie, kiedy ktoś mnie cytuje, odpowiada na mój wpis lub wspomina moją @nazwę_użytkownika" + email_direct: "Wyślij powiadomienie, kiedy ktoś mnie cytuje, odpowiada na mój wpis lub wspomina moją @nazwę" email_private_messages: "Wyślij powiadomienie, kiedy ktoś wyśle mi prywatną wiadomość" email_always: "Otrzymuj powiadomienia email i wyciągi email nawet gdy jestem aktywny na forum" other_settings: "Inne" @@ -420,7 +424,7 @@ pl_PL: few: "po {{count}} minutach" other: "po {{count}} minutach" invited: - search: "wpisz aby szukać zaproszeń..." + search: "wpisz aby szukać zaproszeń…" title: "Zaproszenia" user: "Zaproszony(-a) użytkownik(-czka)" none: "Jeszcze nikt nie został przez ciebie zaproszony." @@ -464,14 +468,18 @@ pl_PL: sent_by: "Wysłane przez" private_message: "prywatna wiadomość" the_topic: "temat" - loading: "Wczytywanie..." + loading: "Wczytuję…" errors: + prev_page: "podczas próby wczytania" reasons: network: "Błąd sieci" + server: "Błąd serwera" + forbidden: "Brak dostępu" unknown: "Błąd" desc: network: "Sprawdź swoje połączenie." network_fixed: "Chyba już w porządku." + server: "Kod błędu: {{status}}" unknown: "Coś poszło nie tak." buttons: back: "Cofnij" @@ -483,7 +491,7 @@ pl_PL: enabled: "Administrator włączył tryb tylko do odczytu. Możesz przeglądać serwis, jednak zmiany nie będą możliwe." login_disabled: "Logowanie jest zablokowane, gdy strona jest w trybie tylko do odczytu." too_few_topics_notice: "Utwórz co najmniej 5 publicznych tematów i %{posts} publicznych wpisów by rozpocząć dyskusję. Nowi użytkownicy nie będą mogli podnieść poziomu zaufania jeśli nie będą mieć do czytania wystarczającej ilości treści." - learn_more: "dowiedz się więcej..." + learn_more: "dowiedz się więcej…" year: 'rok' year_desc: 'tematy dodane w ciągu ostatnich 365 dni' month: 'miesiąc' @@ -539,7 +547,7 @@ pl_PL: reset_password: 'Resetuj hasło' logging_in: "Uwierzytelnianie…" or: "Lub" - authenticating: "Uwierzytelnianie..." + authenticating: "Uwierzytelnianie…" awaiting_confirmation: "Twoje konto czeka na aktywację. Użyj odnośnika przypomnienia hasła, aby otrzymać kolejny email aktywujący konta." awaiting_approval: "Twoje konto jeszcze nie zostało zatwierdzone przez osoby z obsługi. Otrzymasz email gdy zostanie zatwierdzone." requires_invite: "Przepraszamy, dostęp do tego forum jest tylko za zaproszeniem." @@ -569,7 +577,7 @@ pl_PL: saving_draft_tip: "zapisywanie" saved_draft_tip: "zapisano" saved_local_draft_tip: "zapisano lokalnie" - similar_topics: "Twój temat jest podobny do..." + similar_topics: "Twój temat jest podobny do…" drafts_offline: "szkice offline" min_length: need_more_for_title: "jeszcze co najmniej {{n}} znaków w tytule" @@ -589,16 +597,16 @@ pl_PL: create_topic: "Utwórz temat" create_pm: "Utwórz prywatną wiadomość" title: "Lub naciśnij Ctrl+Enter" - users_placeholder: "Dodaj użytkownika(-czkę)" + users_placeholder: "Dodaj osobę" title_placeholder: "O czym jest ta dyskusja w jednym zwartym zdaniu. " edit_reason_placeholder: "z jakiego powodu edytujesz?" show_edit_reason: "(dodaj powód edycji)" reply_placeholder: "Tu wprowadź treść. Użyj składni Markdown lub BBCode do formatowania tekstu. Przeciągnij lub wklej obraz, aby go wgrać." view_new_post: "Zobacz Twój nowy wpis." - saving: "Zapisywanie..." + saving: "Zapisuję…" saved: "Zapisano!" saved_draft: "Posiadasz zachowany szkic wpisu. Kliknij tu aby wznowić jego edycję." - uploading: "Ładowanie..." + uploading: "Wczytuję…" show_preview: 'pokaż podgląd »' hide_preview: '« schowaj podgląd' quote_post_title: "Cytuj cały wpis" @@ -631,13 +639,21 @@ pl_PL: auto_close_examples: 'podaj bezwzględny czas lub liczbę godzin — 24, 17:00, 2013-11-22 14:00' auto_close_error: "Proszę podaj poprawną wartość." notifications: - title: "powiadomienia dla wspomnień @name, odpowiedzi do twoich wpisów i tematów, prywatne wiadomości, itp" + title: "powiadomienia dla wspomnień przy użyciu @nazwy, odpowiedzi do twoich wpisów i tematów, prywatne wiadomości, itp" none: "Aktualnie nie masz żadnych powiadomień." more: "pokaż starsze powiadomienia" total_flagged: "wszystkie oflagowane wpisy" + mentioned: "@

{{username}} {{description}}

" + quoted: "

{{username}} {{description}}

" + replied: "

{{username}} {{description}}

" + posted: "

{{username}} {{description}}

" edited: "

{{username}} {{description}}

" liked: "

{{username}} {{description}}

" - granted_badge: "

Przyznano Ci {{description}}

" + private_message: "

{{username}} {{description}}

" + invited_to_private_message: "

{{username}} {{description}}

" + invitee_accepted: "

{{username}} przyjmuje twoje zaproszenie

" + moved_post: "

{{username}} przenosi {{description}}

" + linked: "

{{username}} {{description}}

" upload_selector: title: "Dodaj obraz" title_with_attachments: "Dodaj obraz lub plik" @@ -654,7 +670,7 @@ pl_PL: search: title: "szukaj tematów, wpisów, użytkowników lub kategorii" no_results: "Brak wyników wyszukiwania" - searching: "Wyszukiwanie ..." + searching: "Szukam…" context: user: "Szukaj wpisów @{{username}}" category: "Szukaj w kategorii \"{{category}}\"" @@ -672,7 +688,9 @@ pl_PL: bulk: reset_read: "Wyzeruj przeczytane" delete: "Usuń tematy" + dismiss_posts: "Wyczyść liczniki wpisów" dismiss_posts_tooltip: "Wyczyść liczniki nieprzeczytanych wpisów w tych tematach, ale informuj mnie jeśli pojawią się w nich nowe wpisy w przyszłości." + dismiss_topics: "Wyczyść status termatów" dismiss_topics_tooltip: "Nie pokazuj tych tematów na mojej liście nieprzeczytanych gdy pojawią się w nich nowe wpisy." dismiss_new: "Zignoruj nowe" toggle: "włącz grupowe zaznaczanie tematów" @@ -695,8 +713,8 @@ pl_PL: category: "Nie ma tematów w kategorii {{category}}." top: "Brak najlepszych tematów." educate: - new: '

Domyślnie tematy są traktowane jako nowe jeśli zostały utworzone w ciągu ostatnich 2 dni.

Możesz to zmienić w swoich ustawieniach.

' - unread: '

Domyślnie wskażnik Nieprzeczytane pojawia się tylko dla tematów które:

  • Utworzyłeś
  • Odpowiedziałeś w nich
  • Czytałeś dłużej niż 4 minuty

Lub jeżeli ręcznie przyznałeś status Śledzony lub Obserwowany w ustawieniach powiadomień poniżej każdego tematu.

Możesz to zmienić w swoich ustawieniach.

' + new: '

Domyślnie, tematy są traktowane jako nowe jeśli zostały utworzone w ciągu ostatnich 2 dni.

Możesz to zmienić w swoich ustawieniach.

' + unread: '

Domyślnie, pojawiają się tu tematy:

  • twojego autorstwa
  • te w których są twoje odpowiedzi
  • czytane przez ciebie dłużej niż 4 minuty

Znajdą się tu też te, którym ręcznie przyznano status Śledzony lub Obserwowany przyciskiem znajdującym się na końcu każdego tematu.

Możesz zmienić te zachowania w swoich preferencjach.

' bottom: latest: "Nie ma więcej najnowszych tematów." hot: "Nie ma więcej gorących tematów." @@ -724,8 +742,8 @@ pl_PL: few: '{{count}} nieprzeczytane tematy' other: '{{count}} nieprzeczytanych tematów' title: 'Temat' - loading_more: "Ładowanie następnych tematów..." - loading: 'Wczytywanie tematu...' + loading_more: "Wczytuję więcej tematów…" + loading: 'Wczytuję temat…' invalid_access: title: "Temat jest prywatny" description: "Przepraszamy, nie masz dostępu do tego tematu!" @@ -780,38 +798,38 @@ pl_PL: position: "wpis %{current} z %{total}" notifications: reasons: - '3_6': 'Powiadomienia będą wysyłane, ponieważ obserwujesz tę kategorię.' - '3_5': 'Będziesz otrzymywać powiadomienia, ponieważ włączono automatyczne obserwowanie tematu.' - '3_2': 'Będziesz otrzymywał(-a) powiadomienia, ponieważ obserwujesz ten temat.' - '3_1': 'Będziesz otrzymywał(-a) powiadomienia, ponieważ stworzyłeś(-aś) ten temat.' - '3': 'Będziesz otrzymywał(-a) powiadomienia, ponieważ obserwujesz ten temat.' - '2_8': 'Powiadomienia będą wysyłane, ponieważ śledzisz tę kategorię.' - '2_4': 'Będziesz otrzymywać powiadomienia, ponieważ odpowiedziałeś(-aś) w tym temacie.' + '3_6': 'Będziesz otrzymywać powiadomienia o każdym nowym wpisie i temacie, ponieważ obserwujesz tę kategorię.' + '3_5': 'Będziesz otrzymywać powiadomienia o każdym nowym wpisie, ponieważ włączono automatyczne obserwowanie tego tematu.' + '3_2': 'Będziesz otrzymywać powiadomienia o każdym nowym wpisie, ponieważ obserwujesz ten temat.' + '3_1': 'Będziesz otrzymywać powiadomienia, ponieważ jesteś autorem tego tematu.' + '3': 'Będziesz otrzymywać powiadomienia o każdym nowym wpisie, ponieważ obserwujesz ten temat.' + '2_8': 'Będziesz otrzymywać powiadomienia, ponieważ śledzisz tę kategorię.' + '2_4': 'Będziesz otrzymywać powiadomienia, ponieważ jesteś autorem odpowiedzi w tym temacie.' '2_2': 'Będziesz otrzymywać powiadomienia, ponieważ śledzisz ten temat.' - '2': 'Będziesz otrzymywał(-a) powiadomienia, ponieważ przeczytałeś(-aś) ten temat.' - '1_2': 'Dostaniesz powiadomienie tylko gdy ktoś wspomni twój @login lub odpowie na twój wpis.' - '1': 'Dostaniesz powiadomienie tylko gdy ktoś wspomni twój @login lub odpowie na twój wpis.' - '0_7': 'Ignorujesz wszystkie powiadomienia z tej kategorii' + '2': 'Będziesz otrzymywać powiadomienia, ponieważ ten temat został uznany za przeczytany..' + '1_2': 'Dostaniesz powiadomienie jedynie gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis.' + '1': 'Dostaniesz powiadomienie jedynie gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis.' + '0_7': 'Ignorujesz wszystkie powiadomienia z tej kategorii.' '0_2': 'Ignorujesz wszystkie powiadomienia w tym temacie.' '0': 'Ignorujesz wszystkie powiadomienia w tym temacie.' watching_pm: - title: "Obserwowanie" + title: "Obserwuj wszystko" description: "Dostaniesz powiadomienie o każdym nowym wpisie w tej prywatnej dyskusji. Liczba nowych i nieprzeczytanych wpisów pojawi się obok jej tytułu na liście tematów." watching: - title: "Obserwowanie" + title: "Obserwuj wszystko" description: "Dostaniesz powiadomienie o każdym nowym wpisie w tym temacie. Liczba nowych i nieprzeczytanych wpisów pojawi się obok jego tytułu na liście tematów." tracking_pm: title: "Śledzenie" - description: "Licznik nowych i nieprzeczytanych wpisów pojawi się obok prywatnej wiadomości. Dostaniesz powiadomienie tylko gdy ktoś wspomni twój @login lub odpisze na twój wpis." + description: "Licznik nowych i nieprzeczytanych wpisów pojawi się obok prywatnej wiadomości. Dostaniesz powiadomienie jedynie gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis." tracking: title: "Śledzenie" - description: "Licznik nowych i nieprzeczytanych wpisów pojawi się obok tytułu tego tematu. Dostaniesz powiadomienie jedynie gdy ktoś wspomni twój @login lub odpisze na twój wpis." + description: "Licznik nowych i nieprzeczytanych wpisów pojawi się obok tytułu tego tematu. Dostaniesz powiadomienie jedynie gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis." regular: title: "Normalny" - description: "Będziesz powiadamiany(-a) tylko jeśli ktoś wspomni o @Tobie lub odpowie na Twój wpis." + description: "Dostaniesz powiadomienie jedynie gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis." regular_pm: title: "Normalny" - description: "Dostaniesz powiadomienie tylko gdy ktoś wspomni twój @login lub odpisze na twój wpis w prywatnej wiadomości." + description: "Dostaniesz powiadomienie jedynie gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis w prywatnej wiadomości." muted_pm: title: "Wyciszono" description: "Nie będziesz dostawać jakichkolwiek powiadomień dotyczących tej prywatnej wiadomości." @@ -819,19 +837,21 @@ pl_PL: title: "Wyciszenie" description: "Nie będzie jakichkolwiek powiadomień dotyczących tego tematu i nie będzie się on pojawiać na karcie nieprzeczytanych." actions: - recover: "Przywróc Temat" + recover: "Przywróć temat" delete: "Usuń temat" open: "Otwórz temat" close: "Zamknij temat" auto_close: "Zamknij automatycznie" + make_banner: "Ustaw jako baner" + remove_banner: "Wyłącz ten baner" unpin: "Odepnij temat" pin: "Przypnij temat" pin_globally: "Przypnij temat globalnie" unarchive: "Przywróć z archiwum" archive: "Archiwizuj temat" - invisible: "Ukryj" - visible: "Pokaż" - reset_read: "Zresetuj Przeczytane Dane" + invisible: "Uczyń niewidocznym" + visible: "Uczyń widocznym" + reset_read: "Zresetuj przeczytane dane" multi_select: "Wybierz wpisy" reply: title: 'Odpowiedz' @@ -846,7 +866,7 @@ pl_PL: title: 'Zgłoś' help: 'zgłoś ten temat, aby zwrócić uwagę moderacji lub wyślij powiadomienie o nim' success_message: 'Ten temat został pomyślnie zgłoszony.' - inviting: "Zapraszanie..." + inviting: "Zapraszam…" invite_private: title: 'Zaproś do pisanie Prywatnej Wiadomości' email_or_username: "Adres email lub nazwa użytkownika zapraszanej osoby" @@ -860,7 +880,7 @@ pl_PL: action: 'Zaproś przez e-mail' help: 'wyślij zaproszenia do znajomych by mogli odpowiedzieć na ten temat jednym kliknięciem' email_placeholder: 'nazwa@example.com' - success: "Wysłaliśmy zaproszenie do {{email}}. Powiadomimy Ciebie jeśli zaproszenie zostanie przyjęte. Sprawdzaj zakładkę zaproszenia na Twoim profilu użytkownika by śledzić swoje zaproszenia." + success: "Wysłaliśmy zaproszenie do {{email}}. Powiadomimy cię gdy zaproszenie zostanie przyjęte. Status swoich zaproszeń możesz śledzić na dedykowanej zakładce w swoim profilu." error: "Przepraszamy, nie mogliśmy zaprosić tej osoby. Być może jest już na forum?" login_reply: 'Zaloguj się, aby odpowiedzieć' filters: @@ -912,7 +932,7 @@ pl_PL: post: reply: "Odpowiedz na {{link}} napisany przez {{replyAvatar}} {{username}}" reply_topic: "Odpowiedz na {{link}}" - quote_reply: "cytuj odpowiedź" + quote_reply: "odpowiedz na ten cytat" edit: "Edytuj {{link}} napisany przez {{replyAvatar}} {{username}}" edit_reason: "Powód" post_number: "wpis {{number}}" @@ -932,7 +952,7 @@ pl_PL: one: "1 ukryty wpis" few: "{{count}} ukryte wpisy" other: "{{count}} ukrytych wpisów" - more_links: "{{count}} więcej..." + more_links: "{{count}} więcej…" unread: "Nieprzeczytany wpis" has_replies: one: "Odpowiedź" @@ -960,7 +980,7 @@ pl_PL: reply: "zacznij tworzyć odpowiedź na ten wpis" like: "polub ten wpis" has_liked: "polubiono ten wpis" - undo_like: "cofnij polubienie" + undo_like: "anuluj polubienie" edit: "edytuj ten wpis" edit_anonymous: "Przykro nam, ale musisz być zalogowany aby edytować ten wpis." flag: "oflaguj ten wpis lub wyślij powiadomienie o nim do moderatorów" @@ -996,7 +1016,7 @@ pl_PL: spam: "Cofnij flagę" inappropriate: "Cofnij flagę" bookmark: "Cofnij zakładkę" - like: "Cofnij polubienie" + like: "Anuluj polubienie" vote: "Cofnij głos" people: off_topic: "{{icons}} oznaczyli jako nie-na-temat" @@ -1010,14 +1030,14 @@ pl_PL: like: "{{icons}} lubi to" vote: "{{icons}} zagłosowało za tym" by_you: - off_topic: "Oznaczyłeś(-aś) to jako nie-na-temat" - spam: "Oflagowałeś(-aś) to jako spam" - inappropriate: "Oflagowałeś(-aś) to jako niewłaściwe" - notify_moderators: "Oflagowałeś(-aś) to do moderacji" + off_topic: "Oznaczono jako nie-na-temat" + spam: "Oflagowano jako spam" + inappropriate: "Oznaczono jako niewłaściwe" + notify_moderators: "Oflagowano do moderacji" notify_user: "Wysłałeś(-aś) prywatną wiadomość do użytkownika(-czki)" - bookmark: "Dodałeś(-aś) ten wpis do zakładek" - like: "Polubiłeś(-aś) to" - vote: "Zagłosowałeś(-aś) za tym wpisem" + bookmark: "Dodano zakładkę w tym wpisie" + like: "Lubisz to" + vote: "Zagłosowano na ten wpis" by_you_and_others: off_topic: one: "Ty i 1 inna osoba oznaczyliście to jako nie-na-temat." @@ -1119,7 +1139,7 @@ pl_PL: edit: 'edytuj' edit_long: "Edytuj" view: 'Pokaż Tematy w Kategorii' - general: 'Ogólna' + general: 'Ogólne' settings: 'Ustawienia' delete: 'Usuń kategorię' create: 'Utwórz kategorię' @@ -1148,6 +1168,8 @@ pl_PL: auto_close_units: "godzin" email_in: "Niestandardowy adres poczty przychodządej" email_in_allow_strangers: "Akceptuj wiadomości email od anonimowych, nieposiadających kont użytkowników " + email_in_disabled: "Tworzenie nowych tematów emailem jest wyłączone w ustawieniach serwisu. " + email_in_disabled_click: 'Kliknij tu, aby włączyć.' allow_badges_label: "Włącz przyznawanie odznak na podstawie aktywności w tej kategorii" edit_permissions: "Edytuj uprawnienia" add_permission: "Dodaj uprawnienie" @@ -1158,16 +1180,17 @@ pl_PL: parent: "Kategoria rodzica" notifications: watching: - title: "Obserwowana" - description: "Będziesz automatycznie obserwować tematy w tych kategoriach. Dostaniesz powiadomienie o każdym nowym wpisie i temacie. Liczba nowych i nieprzeczytanych wpisów pojawi się obok tytułu każdego tematu." + title: "Obserwuj wszystko" + description: "Będziesz automatycznie śledzić wszystkie nowe tematy w tych kategoriach: liczba nieprzeczytanych i nowych wpisów będzie wyświetlana obok tytułów na liście tematów. Dodatkowo będziesz otrzymywać powiadomienie o każdym nowym wpisie i temacie." tracking: title: "Śledzona" - description: "Będziesz automatycznie śledzić wszystkie tematy w tych kategoriach. Licznik nowych i nieprzeczytanych wpisów pojawi się obok ich tytułów na liście tematów." + description: "Będziesz automatycznie śledzić wszystkie tematy w tych kategoriach: licznik nowych i nieprzeczytanych wpisów pojawi się obok ich tytułów na liście tematów." regular: title: "Normalny" - description: "Zostaniesz powiadomiony tylko jeśli ktoś wspomni twoją @nazwę w odpowiedzi na twój wpis" + description: "Dostaniesz powiadomienie jedynie gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis." muted: title: "Wyciszone" + description: "Nie będziesz powiadamiany o nowych tematach w tych kategoriach i nie będą się one pojawiać w karcie Nieprzeczytane." flagging: title: 'Dlaczego chcesz oflagować ten wpis?' action: 'Oflaguj wpis' @@ -1179,11 +1202,11 @@ pl_PL: submit_tooltip: "Zapisz prywatną flagę." take_action_tooltip: "Nie czekaj, aż wpis zostanie zgłoszony przez innych, natychmiast oflaguj do działania . " cant: "Przepraszamy, nie możesz oflagować teraz tego wpisu." - custom_placeholder_notify_user: "Dlaczego ten wpis wymaga pomówienia z użytkownikiem prywatnie i bezpośrednio? Bądz konkretny, konstuktywny i zawsze miły." + custom_placeholder_notify_user: "Dlaczego ten wpis wymaga bezpośredniej, prywatnej rozmowy z tym użytkownikiem? Napisz konkretnie, konstuktywnie i kulturalnie." custom_placeholder_notify_moderators: "Dlaczego ten wpis wymaga uwagi moderatora? Daj nam znać co konkretnie Cię zaniepokoiło i dostarcz nam odpowiednie odnośniki jeśli to możliwe." custom_message: at_least: "wprowadź co najmniej {{n}} znaków" - more: "{{n}} aby wysłać..." + more: "{{n}} aby wysłać…" left: "{{n}} pozostało" flagging_topic: title: "Dlaczego chcesz zgłosić ten temat?" @@ -1191,7 +1214,7 @@ pl_PL: notify_action: "Wyślij prywatną wiadomość" topic_map: title: "Podsumowanie tematu" - links_shown: "pokaż wszystkie {{totalLinks}} odnośników..." + links_shown: "pokaż wszystkie {{totalLinks}} odnośników…" clicks: one: "1 kliknięcie" few: "%{count} kliknięć" @@ -1213,25 +1236,28 @@ pl_PL: invisible: help: "Ten temat jest niewidzialny - nie będzie wyświetlany na listach tematów i można uzyskać do niego dostęp tylko poprzez bezpośredni odnośnik" posts: "Wpisy" - posts_lowercase: "wpisów" + posts_lowercase: "wpisy" posts_long: "jest {{number}} wpisów w tym temacie" original_post: "Oryginalny wpis" views: "Wyświetlenia" - views_lowercase: "wyświetleń" + views_lowercase: "wyświetlenia" replies: "Odpowiedzi" views_long: "ten temat był oglądany {number}} razy" activity: "Aktywność" likes: "Polubienia" + likes_lowercase: "polubienia" likes_long: "jest {{number}} polubień w tym temacie" users: "Użytkownicy" - users_lowercase: "użytkowników" + users_lowercase: "użytkownicy" category_title: "Kategoria" history: "Historia" changed_by: "przez {{author}}" categories_list: "Lista Kategorii" filters: + with_topics: "%{filter} tematy" + with_category: "%{filter} tematy w %{category} " latest: - title: "Najnowsze" + title: "Ostatnie" help: "tematy z ostatnimi wpisami" hot: title: "Gorące" @@ -1275,16 +1301,16 @@ pl_PL: other: "{{categoryName}} ({{count}})" help: "najnowsze tematy w kategorii {{categoryName}}" top: - title: "Najpopularniejsze" - help: "najpopularniejsze tematy w ubiegłym roku, miesiącu, tygodniu lub dniu" + title: "Popularne" + help: "popularne tematy w ubiegłym roku, miesiącu, tygodniu lub dniu" yearly: - title: "Najpopularniejsze w tym roku" + title: "Popularne w tym roku" monthly: - title: "Najpopularniejsze w tym miesiącu" + title: "Popularne w tym miesiącu" weekly: - title: "Najpopularniejsze w tym tygodniu" + title: "Popularne w tym tygodniu" daily: - title: "Najpopularniejsze dzisiaj" + title: "Popularne dzisiaj" this_year: "W tym roku" this_month: "W tym miesiącu" this_week: "W tym tygodniu" @@ -1292,13 +1318,13 @@ pl_PL: other_periods: "zobacz więcej popularnych tematów" browser_update: 'Niestety Twoja przeglądarka jest za stara, aby obsługiwała forum Discourse. Proszę zaktualizuj swoją przeglądarkę.' permission_types: - full: "Tworzenie / Odpowiadanie / Oglądanie" - create_post: "Odpowiadanie / Oglądanie" - readonly: "Oglądanie" + full: "tworzyć / odpowiadać / przeglądać" + create_post: "odpowiadać / przeglądać" + readonly: "przeglądać" admin_js: - type_to_filter: "wpiasz aby filtrować..." + type_to_filter: "pisz, aby filtrować…" admin: - title: 'Admin Discourse' + title: 'Administrator Discourse' moderator: 'Moderator' dashboard: title: "Kokpit" @@ -1341,16 +1367,45 @@ pl_PL: title: "Flagi" old: "Stare" active: "Aktywność" + agree: "Potwierdź" agree_title: "Potwierdź to zgłoszenie jako uzasadnione i poprawne" + agree_flag_modal_title: "Potwierdź i..." + agree_flag_hide_post: "Potwierdź (ukryj post i wyślij PW)" agree_flag_hide_post_title: "Ukryj ten wpis i automatycznie wyślij użytkownikowi prywatną wiadomość informującą, że wpis wymaga przeredagowania" - clear_topic_flags: "Wykonane" - clear_topic_flags_title: "Ten temat został zbadany i problemy zostały rozwiązane. Kliknij Wykone by usunąć oflagowanie." + agree_flag: "Potwierdź flagę" + agree_flag_title: "Potwierdź flagę i zostaw wpis bez zmian" + defer_flag: "Zignoruj" + defer_flag_title: "Usunięcie flagi, nie wymaga dalszych działań." + delete: "Usuń" + delete_title: "Usuń wpis do którego odnosi się flaga." + delete_post_defer_flag: "Usuń wpis i zignoruj flagę" + delete_post_defer_flag_title: "Usuń wpis. Jeśli jest pierwszym w temacie, usuń temat." + delete_post_agree_flag: "Usuń post i potwierdź flagę" + delete_post_agree_flag_title: "Usuń wpis. Jeśli jest pierwszym w temacie, usuń temat." + delete_flag_modal_title: "Usuń i..." + delete_spammer: "Usuń spamera" + delete_spammer_title: "Usuwa konto tego użytkownika oraz wszystkie tematy i wpisy jakie nim utworzono." + disagree_flag_unhide_post: "Wycofaj (pokaż wpis)" + disagree_flag_unhide_post_title: "Usuń wszystkie flagi z tego wpisu i uczyń go widocznym ponownie." + disagree_flag: "Wycofaj" + disagree_flag_title: "Wycofaj nieuzasadnioną flagę." + clear_topic_flags: "Zrobione" + clear_topic_flags_title: "Ten temat został sprawdzony i związane z nim problemy zostały rozwiązane. Kliknij Zrobione, aby usunąć flagi." more: "(więcej odpowiedzi…)" - flagged_by: "Oflagowany przez" + dispositions: + agreed: "potwierdzono" + disagreed: "wycofano" + deferred: "zignorowano" + flagged_by: "Oflagowano przez" + resolved_by: "Rozwiązano przez" + took_action: "Podjęto działanie" system: "System" error: "Coś poszło nie tak" + reply_message: "Odpowiedz" no_results: "Nie ma flag." topic_flagged: "Ten temat został oflagowany." + visit_topic: "Odwiedź temat by podjąć działania." + was_edited: "Wpis został zmieniony po pierwszej fladze" summary: action_type_3: one: "nie-na-temat" @@ -1362,12 +1417,12 @@ pl_PL: other: "nieodpowiednie x{{count}}" action_type_6: one: "niestandardowy" - few: "x{{count}} niestandardowych" - other: "x{{count}} niestandardowych" + few: "niestandardowe x{{count}}" + other: "niestandardowych x{{count}}" action_type_7: one: "niestandardowy" - few: "x{{count}} niestandardowych " - other: "x{{count}} niestandardowych " + few: "niestandardowe x{{count}}" + other: "niestandardowych x{{count}} " action_type_8: one: "spam" few: "spam x{{count}}" @@ -1376,7 +1431,7 @@ pl_PL: primary: "Główna grupa" no_primary: "(brak podstawowej grupy)" title: "Grupy" - edit: "Edytuj Grupy" + edit: "Edytuj grupy" refresh: "Odśwież" new: "Nowa" selector_placeholder: "dodaj użytkowników" @@ -1415,7 +1470,7 @@ pl_PL: title: "Wyłącz tryb tylko do odczytu" text: "Wyłącz tryb tylko do odczytu" logs: - none: "Póki co brak logów..." + none: "Póki co brak logów…" columns: filename: "Nazwa pliku" size: "Rozmiar" @@ -1434,6 +1489,8 @@ pl_PL: backup: text: "Kopia zapasowa" title: "Wykonaj kopię zapasową" + confirm: "Czy chcesz wykonać kopię zapasową?" + without_uploads: "Tak (bez wysyłania)" download: text: "Pobierz" title: "Pobierz kopię zapasową" @@ -1546,7 +1603,7 @@ pl_PL: format: "Format" html: "html" text: "text" - last_seen_user: "Użytkownik Ostatnio Widziany:" + last_seen_user: "Ostatnia " reply_key: "Klucz odpowiedzi" skipped_reason: "Powód pominięcia" logs: @@ -1651,13 +1708,13 @@ pl_PL: few: "odrzuć użytkowników ({{count}})" other: "odrzuć użytkowników ({{count}})" titles: - active: 'Aktywni Użytkownicy' + active: 'Aktywni użytkownicy' new: 'Nowi Użytkownicy' pending: 'Użytkownicy Oczekujący na Przegląd' newuser: 'Użytkownicy na Poziomie Zaufania 0 (Nowi Użytkownicy)' - basic: 'Użytkownicy na Poziomie Zaufania 1 (Podstawowi Użytkownicy)' - regular: 'Użytkownicy na Poziomie Zaufania 2 (zwykli użytkownicy)' - leader: 'Użytkownicy na Poziomie Zaufania 3 (Liderzy)' + basic: 'Użytkownicy na Poziomie Zaufania 1 (Podstawowi)' + regular: 'Użytkownicy na Poziomie Zaufania 2 (Zwyczajni)' + leader: 'Użytkownicy na Poziomie Zaufania 3 (Przodownicy)' elder: 'Użytkownicy na Poziomie Zaufania 4 (Starsi)' admins: 'Administratorzy' moderators: 'Moderatoratorzy' @@ -1791,7 +1848,7 @@ pl_PL: categories: all_results: 'Wszystkie' required: 'Wymagane' - basic: 'Podstawowe ustawienia' + basic: 'Podstawowe' users: 'Użytkownicy' posting: 'Pisanie' email: 'Email' @@ -1801,8 +1858,8 @@ pl_PL: onebox: "Onebox" seo: 'SEO' spam: 'Spam' - rate_limits: 'Ograniczenia tempa' - developer: 'Developer' + rate_limits: 'Limity' + developer: 'Deweloperskie' embedding: "Osadzanie" legal: "Prawne" uncategorized: 'Inne' @@ -1841,45 +1898,45 @@ pl_PL: lightbox: download: "pobierz" keyboard_shortcuts_help: - title: 'Skróty Klawiszowe' + title: 'Skróty klawiszowe' jump_to: - title: 'Przeskocz do' - home: 'g, h Początek (Najnowsze)' - latest: 'g, l Najnowsze' + title: 'Skocz do' + home: 'g, h strona główna (Ostatnie)' + latest: 'g, l Ostatnie' new: 'g, n Nowe' unread: 'g, u Nieprzeczytane' - starred: 'g, f Oznaczone' + starred: 'g, f Ulubione' categories: 'g, c Kategorie' navigation: title: 'Nawigacja' - jump: '# Idź to wpisu o numerze' - back: 'u Wstecz' - up_down: 'k/j Przesuń wybrane w górę/dół' - open: 'o or Enter Pokaż wybrany temat' - next_prev: '`/~ Następny/poprzedni fragment' + jump: '# idź do wpisu o numerze' + back: 'u wstecz' + up_down: 'k/j przesuń zaznaczenie w górę/dół' + open: 'o lub Enter otwórz wybrany temat' + next_prev: '`/~ następna/poprzednia sekcja' application: title: 'Aplikacja' - create: 'c Utwórz nowy temat' - notifications: 'n Pokaż powiadomienia' - search: '/ Szukaj' - help: '? Pokaż skróty klawiszowe' + create: 'c utwórz nowy temat' + notifications: 'n pokaż powiadomienia' + search: '/ wyszukaj' + help: '? pokaż skróty klawiszowe' actions: title: 'Operacje' - star: 'f Gwiazdka dla tematu' - share_topic: 'shift s Udostępnij temat' - share_post: 's Udostępnij wpis' - reply_topic: 'shift r Odpowiedz w temacie' - reply_post: 'r Odpowiedz na wpis' - quote_post: 'q Cytuj wpis' - like: 'l Polub wpis' - flag: '! Oflaguj wpis' - bookmark: 'b Zakładka na wpisie' - edit: 'e Edytuj wpis' - delete: 'd Usuń wpis' - mark_muted: 'm, m Ucisz temat' - mark_regular: 'm, r Zwykły (domyślny) temat' - mark_tracking: 'm, t Śledź temat' - mark_watching: 'm, w Obserwuj temat' + star: 'f oznacz temat gwiazdką' + share_topic: 'shift s udostępnij temat' + share_post: 's udostępnij wpis' + reply_topic: 'shift r odpowiedz w temacie' + reply_post: 'r odpowiedz na wpis' + quote_post: 'q cytuj wpis' + like: 'l polub wpis' + flag: '! oflaguj wpis' + bookmark: 'b ustaw zakładkę na wpisie' + edit: 'e edytuj wpis' + delete: 'd usuń wpis' + mark_muted: 'm, m ucisz temat' + mark_regular: 'm, r zwykły (domyślny) temat' + mark_tracking: 'm, t śledź temat' + mark_watching: 'm, w śledź wszystko w temacie' badges: title: Odznaki allow_title: "użycie odznaki jako tytułu?" @@ -1917,10 +1974,10 @@ pl_PL: name: Podstawowi description: Przyznano wszystkie podstawowe funkcje regular_user: - name: Regularność + name: Zwyczajni description: Przyznano zaproszenia leader: - name: Liderzy + name: Przodownik description: Przyznano możliwość zmiany kategorii, nazwy, linków oraz salon elder: name: Starszyzna @@ -1932,10 +1989,10 @@ pl_PL: name: Autobiograf description: Wypełnienie profilu użytkownika nice_post: - name: Miły Wpis + name: Niezły wpis description: Otrzymano 10 polubień za wpis. Ta odznaka może być przyznawana wielokrotnie good_post: - name: Dobry Wpis + name: Dobry wpis description: Otrzymano 25 polubień za wpis. Ta odznaka może być przyznawana wielokrotnie great_post: name: Wspaniały wpis diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index ca1fda3ae..7c6efb2c9 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -619,7 +619,6 @@ pt: invitee_accepted: "

{{username}} accepted your invitation

" moved_post: "

{{username}} moved {{description}}

" linked: "

{{username}} {{description}}

" - granted_badge: "

You were granted {{description}}

" upload_selector: title: "Adicionar uma imagem" title_with_attachments: "Adicionar uma imagem ou um ficheiro" diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml index 748dd07a2..d14f20903 100644 --- a/config/locales/client.pt_BR.yml +++ b/config/locales/client.pt_BR.yml @@ -626,7 +626,6 @@ pt_BR: invitee_accepted: "

{{username}} accepted your invitation

" moved_post: "

{{username}} moved {{description}}

" linked: "

{{username}} {{description}}

" - granted_badge: "

You were granted {{description}}

" upload_selector: title: "Adicionar uma imagem" title_with_attachments: "Adicionar uma imagem ou arquivo" diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index 751334fe9..36f9f3c93 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -360,16 +360,19 @@ ru: title: "Фон профиля" email: title: "E-mail" + instructions: "Будет всегда скрыт." ok: "Отлично, мы послали вам письмо с инструкциями." invalid: "Введите корректный адрес электронной почты." authenticated: "Ваш адрес электронной почты подтвержден через {{provider}}." frequency: "В случае вашего отсутствия на форуме вы будете получать уведомления, но только о новых сообщениях." name: title: "Имя" + instructions: "Ваше полное имя." too_short: "Ваше имя слишком короткое." ok: "Допустимое имя." username: title: "Псевдоним" + instructions: "Должен быть уникальным, без пробелов и не очень длинным." short_instructions: "Пользователи могут упоминать вас по @{{username}}." available: "Псевдоним доступен." global_match: "Адрес электронной почты совпадает с зарегистрированным." @@ -478,10 +481,13 @@ ru: prev_page: "при попытке загрузки" reasons: network: "Ошибка сети" + server: "Ошибка сервера" + forbidden: "Доступ закрыт" unknown: "Ошибка" desc: network: "Пожалуйста, проверьте ваше соединение." network_fixed: "Похоже, сеть появилась." + server: "Ошибка: {{status}}" unknown: "Что-то пошло не так." buttons: back: "Вернуться" @@ -657,7 +663,6 @@ ru: invitee_accepted: "

{{username}} принял(а) ваше приглашение

" moved_post: "

{{username}} переместил(а) {{description}}

" linked: "

{{username}} {{description}}

" - granted_badge: "

Вы награждены: {{description}}

" upload_selector: title: "Add an image" title_with_attachments: "Add an image or a file" @@ -1848,9 +1853,12 @@ ru: will_be_promoted: "Будет повышен в течение 24 часов." does_not_qualify: "Не заслуживает уровень доверия 3." sso: + title: "Технология единого входа SSO" + external_id: "Внешний идентификатор" external_username: "Псевдоним" external_name: "Имя" external_email: "E-mail" + external_avatar_url: "URL-адрес аватарки" site_content: none: "Выберите тип контента, чтобы начать редактирование." title: 'Контент сайта' diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index a6032efec..579343759 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -20,6 +20,10 @@ zh_CN: tb: TB dates: time: "h:mm a" + long_no_year: "MMM DD h:mm a" + long_no_year_no_time: "MMM D" + long_with_year: "YYYY MMM D h:mm a" + long_with_year_no_time: "YYYY MMM D" tiny: half_a_minute: "< 1 分钟" less_than_x_seconds: @@ -302,16 +306,19 @@ zh_CN: title: "个人资料背景" email: title: "电子邮箱" + instructions: "绝不会被公开显示。" ok: "看起来不错哦,我们会发送电子邮件让您确认。" invalid: "请填写正确的电子邮箱地址。" authenticated: "您的电子邮箱已经被 {{provider}} 确认有效。" frequency: "只有当您最近一段时间没有访问时,我们才会把您未读过的内容发送到您的电子邮箱。" name: title: "名字" + instructions: "名字全称。" too_short: "您设置的名字太短了。" ok: "您的名字符合要求。" username: title: "用户名" + instructions: "必须唯一,没有空格,简短。" short_instructions: "其他人可以用 @{{username}} 来提及您。" available: "您的用户名可用。" global_match: "电子邮箱与注册用户名相匹配。" @@ -412,10 +419,13 @@ zh_CN: prev_page: "当尝试载入时" reasons: network: "网络错误" + server: "服务器错误" + forbidden: "访问被阻止" unknown: "错误" desc: network: "请检查您的网络连接。" network_fixed: "似乎恢复正常了。" + server: "错误代码:{{status}}" unknown: "出错了。" buttons: back: "返回" @@ -541,6 +551,7 @@ zh_CN: view_new_post: "浏览您的新帖子。" saving: "保存中..." saved: "已保存!" + saved_draft: "您有一个帖子草稿尚未发表。点击该栏任意位置即可继续编辑。" uploading: "上传中..." show_preview: '显示预览 »' hide_preview: '« 隐藏预览' @@ -590,7 +601,6 @@ zh_CN: invitee_accepted: "

{{username}} 已接受您的邀请

" moved_post: "

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

" linked: "

{{username}} {{description}}

" - granted_badge: "

您被授予了 {{description}}

" upload_selector: title: "插入图片" title_with_attachments: "上传图片或文件" @@ -647,6 +657,9 @@ zh_CN: hot: "没有热门主题。" category: "没有 {{category}} 分类的主题。" top: "没有最佳主题。" + educate: + new: '

默认情况下,最近两天创建的主题是新主题。

您可以在您的设置里改变这一行为。

' + unread: '

默认情况下,未读提醒将仅对以下主题显示:

  • 创建的
  • 回复的
  • 阅读超过4分钟的

或者,如果您在每个主题底部的通知控制中选择了追踪或监视的。

您可以改变您的用户设置

' bottom: latest: "没有更多主题可看了。" hot: "没有更多热门主题可看了。" @@ -880,6 +893,8 @@ zh_CN: confirm: "您确定要放弃编辑您的帖子吗?" no_value: "否" yes_value: "是" + wiki: + about: "这个帖子是维基;基础用户能编辑它" archetypes: save: '保存选项' controls: @@ -888,6 +903,7 @@ zh_CN: has_liked: "您已经赞了本帖" undo_like: "撤销赞" edit: "编辑本帖" + edit_anonymous: "抱歉,但是您需要登陆后才能编辑该贴。" flag: "私下报告本帖以提醒管理人员关注或发送私信通知" delete: "删除本帖" undelete: "恢复本帖" @@ -1105,6 +1121,12 @@ zh_CN: posts: "帖子" posts_lowercase: "帖子" posts_long: "本主题有 {{number}} 个帖子" + posts_likes_MF: | + 这个主题有 {count, plural, one {1 个帖子}其他{# 帖子数}} {ratio, select, + low {with a high like to like ratio} + med {with a very high post to like ratio} + high {with an extremely high post to like ratio} + other {}} original_post: "原始帖" views: "浏览" views_lowercase: "浏览" @@ -1169,6 +1191,7 @@ zh_CN: help: "在 {{categoryName}} 分类中热门的主题" top: title: "热门" + help: "最近一年、一月、一周或一天的最活跃主题" yearly: title: "年度热门" monthly: @@ -1257,6 +1280,7 @@ zh_CN: disagree_flag_title: "拒绝这个报告,无效或不正确" clear_topic_flags: "完成" clear_topic_flags_title: "这个主题已被调查且提案已被解决。单击完成以删除报告。" + more: "(更多回复...)" dispositions: agreed: "已批准" disagreed: "未批准" @@ -1270,6 +1294,7 @@ zh_CN: no_results: "没有报告" topic_flagged: "这个主题已被报告。" visit_topic: "浏览主题才能操作" + was_edited: "帖子在第一次标记后被编辑" summary: action_type_3: other: "偏离主题 x{{count}}" @@ -1343,6 +1368,8 @@ zh_CN: backup: text: "备份" title: "建立一个备份" + confirm: "您确定要开始建立一个备份吗?" + without_uploads: "是(但不上传)" download: text: "下载" title: "下载该备份" @@ -1529,6 +1556,8 @@ zh_CN: label: "新:" ip_address: "IP地址" add: "添加" + logster: + title: "错误日志" impersonate: title: "以用户角度检视" username_or_email: "用户名或用户电子邮件" @@ -1591,6 +1620,7 @@ zh_CN: edit_title: "编辑头衔" save_title: "保存头衔" refresh_browsers: "强制浏览器刷新" + refresh_browsers_message: "消息发送至所有用户!" show_public_profile: "显示公开介绍" impersonate: '检视角度' ip_lookup: "IP 查询" @@ -1605,12 +1635,15 @@ zh_CN: reputation: 声誉 permissions: 权限 activity: 活动 + like_count: 给出的赞 / 收到的赞 + last_100_days: '在最近 100 天' private_topics_count: 私有主题数量 posts_read_count: 已阅帖子数量 post_count: 创建的帖子数量 topics_entered: 已查看的主题数量 flags_given_count: 所做报告数量 flags_received_count: 收到报告数量 + flags_given_received_count: '给出的标记 / 收到的标记' approve: '批准' approved_by: "批准人" approve_success: "用户已被批准, 激活邮件已发送。" @@ -1659,9 +1692,18 @@ zh_CN: posts_read_all_time: "已读的帖子 (全部)" flagged_posts: "旗标帖子" flagged_by_users: "标记的用户" + likes_given: "给出的赞" + likes_received: "收到的赞" qualifies: "符合等级3的信用度" will_be_promoted: "在24小时后升级" does_not_qualify: "未符合等级3的信用度" + sso: + title: "单点登录" + external_id: "外部 ID" + external_username: "用户名" + external_name: "名字" + external_email: "电子邮件" + external_avatar_url: "头像 URL" site_content: none: "选择内容类型以开始编辑。" title: '内容' diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml index b044e016e..9d0e5ce79 100644 --- a/config/locales/client.zh_TW.yml +++ b/config/locales/client.zh_TW.yml @@ -589,7 +589,6 @@ zh_TW: invitee_accepted: "

{{username}}已接受你的邀請

" moved_post: "

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

" linked: "

{{username}} {{description}}

" - granted_badge: "

你已升級{{description}}

" upload_selector: title: "加入一張圖片" title_with_attachments: "加入一張圖片或一個檔案" @@ -1516,6 +1515,8 @@ zh_TW: label: "新增:" ip_address: "IP 位址" add: "加入" + logster: + title: "錯誤紀錄" impersonate: title: "以用戶的角度檢視" username_or_email: "用戶名稱或電子郵件地址" @@ -1593,6 +1594,7 @@ zh_TW: reputation: 聲望 permissions: 權限 activity: 活動 + last_100_days: '在過去 100 天內' private_topics_count: 私人討論話題 posts_read_count: 讀過的文章 post_count: 已發表的文章 @@ -1647,9 +1649,16 @@ zh_TW: posts_read_all_time: "已讀的文章 (任何時間)" flagged_posts: "被投訴的文章" flagged_by_users: "投訴之用戶" + likes_given: "給出的讚" + likes_received: "收到的讚" qualifies: "符合信任等級 3 的條件。" will_be_promoted: "將在 24 小時內升級。" does_not_qualify: "不符合信任等級 3 的條件。" + sso: + external_username: "用戶名稱" + external_name: "名稱" + external_email: "電子郵件" + external_avatar_url: "頭像 URL" site_content: none: "選擇要編輯的內容類型。" title: '內容' @@ -1691,6 +1700,8 @@ zh_TW: description: 簡述 badge_type: 徽章類型 badge_grouping: 群組 + badge_groupings: + modal_title: 徽章群組 granted_by: 升級者為 granted_at: 升級在 save: 儲存 diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml index 06c6d185c..3a448ab7e 100644 --- a/config/locales/server.de.yml +++ b/config/locales/server.de.yml @@ -872,7 +872,6 @@ de: invited_to_private_message: "%{display_username} hat Dich zu einem privaten Gespräch eingeladen: %{link}" invitee_accepted: "%{display_username} hat Deine Einladung angenommen." linked: "%{display_username} hat dich auf %{link} verlinkt" - granted_badge: "Dir wurde %{link} verliehen" search: within_post: "#%{post_number} von %{username}: %{excerpt}" types: diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml index 82158b772..8f10a8999 100644 --- a/config/locales/server.es.yml +++ b/config/locales/server.es.yml @@ -869,7 +869,6 @@ es: invited_to_private_message: "%{display_username} te ha invitado a un mensaje privado: %{link}" invitee_accepted: "%{display_username} ha aceptado tu invitación" linked: "%{display_username} te enlazó en %{link}" - granted_badge: "Te ha sido concedido %{link}" search: within_post: "#%{post_number} por %{username}: %{excerpt}" types: diff --git a/config/locales/server.fi.yml b/config/locales/server.fi.yml index 27b4f2cd5..c8bdd0b76 100644 --- a/config/locales/server.fi.yml +++ b/config/locales/server.fi.yml @@ -878,7 +878,6 @@ fi: invited_to_private_message: "%{display_username} kutsui sinut yksityisviestiin: %{link}" invitee_accepted: "%{display_username} hyväksyi kutsusi" linked: "%{display_username} linkitti sinuun täällä %{link}" - granted_badge: "Sinulle myönnettiin %{link}" search: within_post: "#%{post_number} käyttäjältä %{username}: %{excerpt}" types: diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml index eeba0b81d..d30aa6cb1 100644 --- a/config/locales/server.fr.yml +++ b/config/locales/server.fr.yml @@ -883,7 +883,6 @@ fr: invited_to_private_message: "%{display_username} vous a invité dans une conversation privée: %{link}" invitee_accepted: "%{display_username} a accepté votre invitation" linked: "%{display_username} a mentionné l'un de vos messages : %{link}" - granted_badge: "On vous a décerné %{link}" search: within_post: "#%{post_number} par %{username}: %{excerpt}" types: diff --git a/config/locales/server.he.yml b/config/locales/server.he.yml index 94e73e97f..074176619 100644 --- a/config/locales/server.he.yml +++ b/config/locales/server.he.yml @@ -831,7 +831,6 @@ he: invited_to_private_message: "%{display_username} הזמין אותך להודעה פרטית: %{link}" invitee_accepted: "%{display_username} קיבל את הזמנתך" linked: "%{display_username} קישר אליך ב %{link}" - granted_badge: "הוענק לך %{link}" search: within_post: "#%{post_number} מאת %{username}: %{excerpt}" types: diff --git a/config/locales/server.it.yml b/config/locales/server.it.yml index 7e11bd8f1..07a9c6f02 100644 --- a/config/locales/server.it.yml +++ b/config/locales/server.it.yml @@ -618,7 +618,6 @@ it: invited_to_private_message: "%{display_username} ti ha invitato in un messaggio privato: %{link}" invitee_accepted: "%{display_username} ha accettato il tuo invito" linked: "%{display_username} ti ha linkato in %{link}" - granted_badge: "Ti è stato assegnato %{link}" search: within_post: "#%{post_number} di %{username}: %{excerpt}" types: diff --git a/config/locales/server.nl.yml b/config/locales/server.nl.yml index 035116134..2fd189fb0 100644 --- a/config/locales/server.nl.yml +++ b/config/locales/server.nl.yml @@ -20,6 +20,7 @@ nl: posts: "berichten" 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" errors: @@ -201,6 +202,7 @@ nl: title: "ervaren lid" change_failed_explanation: "Je probeerde %{user_name} te degraderen naar '%{new_trust_level}'. Echter, het trust level is al '%{current_trust_level}'. %{user_name} blijft op trust level '%{current_trust_level}'" rate_limiter: + slow_down: "Je hebt deze actie te vaak uitgevoerd. Probeer het later nog een keer." too_many_requests: "Er is een dagelijks limiet voor hoe vaak je dat kan doen. Wacht %{time_left} voordat je dit opnieuw probeert." hours: one: "1 uur" @@ -602,7 +604,7 @@ nl: suppress_uncategorized_badge: "Laat de badge niet zien voor topics zonder categorie in de topiclijsten." global_notice: "Laat een LET OP, BELANGRIJK-banner zien aan alle gebruikers. Laat leeg om niet te tonen (HTML is toegestaan)." enable_names: "Gebruikers mogen hun volledige naam laten zien. Zet uit om volledige namen te verbergen." - display_name_on_posts: "Laat de volledige naam van een gebruiker bij zijn berichten zijn, naar de @gebruikersnaam" + display_name_on_posts: "Laat de volledige naam van een gebruiker bij zijn berichten zien, na de @gebruikersnaam" short_progress_text_threshold: "Als het aantal berichten in een topic meer is dan dit aantal zal de voortgangsbalk alleen het nummer van het huidige bericht tonen. Als je de breedte van de voortgangsbalk verandert, moet je dit getal misschien ook aanpassen." default_code_lang: "Standaard programmeertaal die gebruikt wordt voor syntax highlighting van GitHub codeblokken (lang-auto, ruby, python etc.)" tos_accept_required: "Gebruikers moeten een vinkje zetten bij het inschrijven om aan te geven dat ze akkoord gaan met de algemene voorwaarden als deze optie actief is. Ga naar 'Inschijfformulier: Algemene Voorwaarden' in de Inhoud tab om het bericht te wijzigen." @@ -625,11 +627,10 @@ nl: invited_to_private_message: "%{display_username} heeft je uitgenodigd voor een privé-bericht: %{link}" invitee_accepted: "%{display_username} heeft je uitnodiging geaccepteerd" linked: "%{display_username} heeft je gelinkt in %{link}" - granted_badge: "%{link} is aan jou toegekend" search: within_post: "#%{post_number} door %{username}: %{excerpt}" types: - category: 'Categorien' + category: 'Categorieën' topic: 'Resultaten' user: 'Leden' original_poster: "Oorspronkelijk geplaatst door" @@ -880,6 +881,10 @@ nl: see_more: "Meer" search_title: "Zoek op deze site" search_google: "Google" + login_required: + welcome_message: | + #[Welkom op %{title}](#welcome) + Voor dit forum is een account nodig. Maak een account aan of log in om verder te gaan. terms_of_service: title: "Algemene Voorwaarden" user_content_license: | @@ -903,6 +908,7 @@ nl: sockpuppet: "Een nieuwe gebruiker maakte een nieuwe topic en een andere gebruiker reageerde - vanaf hetzelfde IP-adres. Zie de flag_sockpuppets instelling." email_log: no_user: "Kan geen gebruiker met id %{user_id} vinden" + suspended_not_pm: "De gebruiker is geschorst, geen privébericht" seen_recently: "Gebruiker was recentelijk nog online" post_not_found: "Kan geen bericht met id %{post_id} vinden" notification_already_read: "De notificatie waar deze mail voor is is al gezien" @@ -919,6 +925,8 @@ nl: guidelines: "Regels" privacy: "Privacy" edit_this_page: "Wijzig deze pagina" + static_topic_first_reply: | + Verander het eerste bericht in dit Topic om de inhoud van de %{page_name} pagina te wijzigen. guidelines_topic: title: "FAQ/Regels" tos_topic: diff --git a/config/locales/server.pl_PL.yml b/config/locales/server.pl_PL.yml index 58016880d..351661aac 100644 --- a/config/locales/server.pl_PL.yml +++ b/config/locales/server.pl_PL.yml @@ -47,7 +47,7 @@ pl_PL: one: "1 odpowiedź więcej" few: "%{count} odpowiedzi więcej" other: "%{count} odpowiedzi więcej" - loading: "Wczytywanie dyskusji..." + loading: "Wczytuję dyskusję…" permalink: "Link bezpośredni" in_reply_to: "▶ %{username}" replies: @@ -79,14 +79,14 @@ pl_PL: just_posted_that: "jest zbyt podobne do tego co umieściłeś niedawno" has_already_been_used: "został już użyty" invalid_characters: "zawiera niepoprawne znaki" - is_invalid: "jest niepoprawny; spróbuj opisać to dokładniej" + is_invalid: "jest niewystarczająca – spróbuj ją rozwinąć" next_page: "następna strona →" prev_page: "← poprzednia strona" page_num: "Strona %{num}" topics_in_category: "Tematy w kategorii '%{category}'" rss_posts_in_topic: "Kanał RSS tematu '%{topic}'" rss_topics_in_category: "Kanał RSS tematów z kategorii '%{category}'" - author_wrote: "%{author} napisał(-a):" + author_wrote: "%{author} pisze:" num_posts: "Wpisy:" num_participants: "Uczestnicy:" read_full_topic: "Przeczytaj cały temat" @@ -123,11 +123,11 @@ pl_PL: sequential_replies: | ### Rozważ odpowiadanie na kilka postów jednocześnie - Zamiast umieszczać tyle kolejnych odpowiedzi w jednym temacie, rozważ zastosowanie jednej odpowiedzi, która zawiera cytaty lub odwołania przez @nazwa do poprzednich wpisów. + Zamiast umieszczać odpowiedzi jedna po drugiej w jednym temacie, rozważ zastosowanie jednej odpowiedzi, która zawiera cytaty lub odwołania przez @nazwy do poprzednich wpisów. - Możesz edytować swoją poprzednią odpowiedź by dodać cytat przez zaznaczenie tekstu i kliknięcie pojawiającego się przycisku quote reply. + Możesz edytować swoją poprzednią odpowiedź aby dodać cytat przez zaznaczenie tekstu i kliknięcie odpowiedz na ten cytat. - Łatwiej jest czytać tematy mające mniej głębszych odpowiedzi niż wiele małych, indywidualnych odpowiedzi. + O wiele łatwiej jest czytać tematy mające kilka wyczerpujących odpowiedzi niż wiele krótkich i zdawkowych. dominating_topic: | ### Pozwól innym dołączyć do rozmowy @@ -154,7 +154,7 @@ pl_PL: raw: "Treść" errors: messages: - is_invalid: "jest niepoprawny; spróbuj opisać go dokładniej" + is_invalid: "jest niewystarczająca – spróbuj ją rozwinąć" has_already_been_used: "jest już użyty" models: topic: @@ -317,7 +317,7 @@ pl_PL: update: 'Zmień hasło' save: 'Ustaw hasło' title: 'Zresetuj hasło' - success: "Zmieniłeś(-aś) swoje hasło i zostałeś(-aś) zalogowany(-a)." + success: "Twoje hasło zostało pomyślnie zmienione i nastąpiło zalogowanie.." success_unapproved: "Zmieniłeś(-a) swoje hasło." continue: "Przejdź do %{site_name}" change_email: @@ -486,7 +486,6 @@ pl_PL: title_nag: "Ustawienie serwisu title nadal ma domyślną wartość. Zmień ją na tytuł Twojego serwisu w ustawieniach serwisu." site_description_missing: "The site_description setting is blank. Write a brief description of this forum in the Site Settings." 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." - access_password_removal: "Your site was using the access_password setting, which has been removed. The login_required and must_approve_users settings have been enabled, which should be used instead. You can change them in the Site Settings. Be sure to approve users in the Pending Users list. (This message will go away after 2 days.)" 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: @@ -516,6 +515,7 @@ pl_PL: default_locale: "Domyślny język tej instancji Discourse (kod ISO 639-1)" min_private_message_title_length: "Minimalna ilość znaków w tytule prywatnej wiadomości" allow_uncategorized_topics: "Pozwól na tworzenie tematów bez kategorii" + uncategorized_description: "Znajdują się tu wątki którym jeszcze nie przypisano odpowiedniej kategorii." unique_posts_mins: "Ile minut musi upłynąć zanim użytkownik będzie mógł ponownie zrobić wpis z tą samą treścią" queue_jobs: "DEVELOPER ONLY! WARNING! By default, queue jobs in sidekiq. If disabled, your site will be broken." category_featured_topics: "Number of topics displayed per category on the /categories page. After changing this value, it takes up to 15 minutes for the categories page to update." @@ -528,18 +528,18 @@ pl_PL: long_polling_interval: "Interval before a new long poll is issued in milliseconds " polling_interval: "How often should logged in user clients poll in milliseconds" anon_polling_interval: "How often should anonymous clients poll in milliseconds" - auto_track_topics_after: "Global default milliseconds before a topic is automatically tracked, users can override (0 for always, -1 for never)" + auto_track_topics_after: "Liczba milisekund zanim temat jest automatycznie dodany do śledzonych. użytkownik może nadpisać tę wartość w swoich preferencjach (0 zawsze, -1 nigdy)" new_topic_duration_minutes: "Global default number of minutes a topic is considered new, users can override (-1 for always, -2 for last visit)" flags_required_to_hide_post: "Number of flags that cause a post to be automatically hidden and PM sent to the user (0 for never)" notify_mods_when_user_blocked: "If a user is automatically blocked, send a message to all moderators." - ga_tracking_code: "Google analytics (ga.js) tracking code code, eg: UA-12345678-9; see http://google.com/analytics" + ga_tracking_code: "Identyfikator Google analytics (ga.js), np: UA-12345678-9; zobacz http://google.com/analytics" ga_domain_name: "Google analytics (ga.js) domain name, eg: mysite.com; see http://google.com/analytics" + ga_universal_tracking_code: "Identyfikator Google Universal Analytics (analytics.js), np: UA-12345678-9; zobacz http://google.com/analytics" enable_noscript_support: "Enable standard webcrawler search engine support via the noscript tag" top_menu: "Determine which items appear in the homepage navigation, and in what order. Example latest|new|unread|starred|categories|top|read|posted" - track_external_right_clicks: "Track external links that are right clicked (eg: open in new tab) disabled by default because it rewrites URLs" + track_external_right_clicks: "Śledź zewnętrzne linki kliknięte prawym klawiszem (np. otwierane w nowej zakładce). Domyślnie wyłączone, gdyż wymaga nadpisywania URLi." port: "DEVELOPER ONLY! WARNING! Use this HTTP port rather than the default of port 80. Leave blank for default of 80." force_hostname: "DEVELOPER ONLY! WARNING! Specify a hostname in the URL. Leave blank for default." - invite_expiry_days: "How long user invitation keys are valid, in days" min_password_length: "Minimalna długość hasła." enable_yahoo_logins: "Enable Yahoo authentication" enable_twitter_logins: "Enable Twitter authentication, requires twitter_consumer_key and twitter_consumer_secret" @@ -555,6 +555,8 @@ pl_PL: previous_visit_timeout_hours: "How long a visit lasts before we consider it the 'previous' visit, in hours" s3_upload_bucket: "The Amazon S3 bucket name that files will be uploaded into. WARNING: must be lowercase, no periods." min_trust_to_create_topic: "The minimum trust level required to create a new topic." + newuser_max_mentions_per_post: "Maksymalna liczba powiadomień poprzez @nazwę w jednym wpisie (dla nowych użytkowników)." + max_mentions_per_post: "Maksymalna liczba powiadomień poprzez @nazwę w jednym wpisie (dla wszystkich)." title_fancy_entities: "Convert common ASCII characters to fancy HTML entities in topic titles, ala SmartyPants http://daringfireball.net/projects/smartypants/" title_prettify: "Prevent common title typos and errors, including all caps, lowercase first character, multiple ! and ?, extra . at end, etc." faq_url: "If you have a FAQ hosted elsewhere that you want to use, provide the full URL here." @@ -563,22 +565,20 @@ pl_PL: newuser_spam_host_threshold: "How many times a new user can post a link to the same host within their `newuser_spam_host_posts` posts before being considered spam." reply_by_email_address: "Template for reply by email incoming email address, for example: %{reply_key}@reply.example.com or replies+%{reply_key}@example.com" delete_all_posts_max: "The maximum number of posts that can be deleted at once with the Delete All Posts button. If a user has more than this many posts, the posts cannot all be deleted at once and the user can't be deleted." - username_change_period: "The number of days after registration that accounts can change their username (0 to disallow username change)." email_editable: "Allow users to change their e-mail address after registration." default_digest_email_frequency: "How often users receive digest emails by default. They can change this setting in their preferences." 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." notification_types: - mentioned: "%{display_username} wspomniał(-a) o Tobie w %{link}" + mentioned: "%{display_username} wspomina o Tobie w %{link}" liked: "%{display_username} polubił(-a) Twój wpis w %{link}" - replied: "%{display_username} odpowiedział(-a) na Twój wpis w %{link}" - quoted: "%{display_username} zacytował(-a) Twój wpis w %{link}" - edited: "%{display_username} edytował(-a) Twój wpis w %{link}" - posted: "%{display_username} napisał(-a) w %{link}" + replied: "%{display_username} odpowiada na Twój wpis w %{link}" + quoted: "%{display_username} cytuje Twój wpis w %{link}" + edited: "%{display_username} edytuje Twój wpis w %{link}" + posted: "%{display_username} pisze w %{link}" moved_post: "%{display_username} przeniósł(-a) Twój wpis do %{link}" - private_message: "%{display_username} wysłał(-a) Ci prywatną wiadomość: %{link}" + private_message: "%{display_username} wysyła Ci prywatną wiadomość: %{link}" invited_to_private_message: "%{display_username} zaprosił(-a) Cię do prywatnej wiadomości: %{link}" invitee_accepted: "%{display_username} przyjął(-ęła) Twoje zaproszenie" - granted_badge: "Otrzymałeś %{link}" search: types: category: 'Kategorie' @@ -602,10 +602,10 @@ pl_PL: closed_enabled: "This topic is now closed. New replies are no longer allowed." closed_disabled: "This topic is now opened. New replies are allowed." autoclosed_disabled: "This topic is now opened. New replies are allowed." - pinned_enabled: "This topic is now pinned. It will appear at the top of its category until it is unpinned by staff for everyone, or by individual users for themselves." + pinned_enabled: "Ten temat został przypięty. Pojawi się na górze swojej kategorii dopóki nie zostanie odpięty dla wszystkich przez opiekuna, lub tylko dla siebie przez użytkownika." pinned_disabled: "This topic is now unpinned. It will no longer appear at the top of its category." visible_enabled: "This topic is now visible. It will be displayed in topic lists." - visible_disabled: "This topic is now invisible. It will no longer be displayed in any topic lists. The only way to access this topic is via direct link." + visible_disabled: "Ten temat został ustawiony jako niewidoczny. Nie pojawi się na żadnej liście tematów. Aby go otworzyć wymagana będzie znajomość linku bezpośredniego. " login: not_approved: "Twoje konto nie zostało jeszcze aktywowane. Zostaniesz powiadomiony emailem gdy będziesz mógł się zalogować." incorrect_username_email_or_password: "Niepoprawna nazwa użytkownika, email lub hasło" @@ -660,6 +660,10 @@ pl_PL: system_messages: post_hidden: subject_template: "Wpis został ukryty z powodu oflagowania przez społeczność" + usage_tips: + text_body_template: "Ta prywatna wiadomość zawiera kilka szybkich porad na początek:\n\n## Przewijaj!\n\nNie ma przycisków do przejścia na następną stronę ani numerów stron.\nChcesz przeczytać więcej? **Po prostu dalej przewijaj w dół!**\n\nGdy ktoś napisze nową wiadomość, pojawi się ona automatycznie na końcu tematu.\n\n## Gdzie ja jestem? \n\n- Wyszukiwanie, twój profil oraz menu znajdują się **pod ikonami w prawym, górnym rogu**.\n\n- Podczas czytania tematu, możesz przejść na początek ↑ klikając jego tytuł na górze strony. \nKliknij na pasku postępu na dole strony, aby wyświetlić szczegółową nawigację lub użyj klawiszy home i end.\n\n \n\n- Kliknięcie na tytule innego tematu zawsze przenosi do miejsca w którym pojawiają się nieprzeczytane wpisy. \n\n## Jak odpowiadać?\n\n - By odpowiedzieć na ogólny wątek tematu użyj przycisku *Odpowiedz* na samym końcu tematu.\n\n - By odpowiedzieć na konkretny wpis, użyj przycisku *Odpowiedz* przy danym wpisie.\n\n - By poprowadzić rozmowę w innym kierunku, lecz utrzymać powiązanie, użyj przycisku *Odpowiedz jako nowy temat* wyświetlanego po prawej stronie wpisu.\n\n - By zacytować kogoś w swojej odpowiedzi zaznacz fragment którego chcesz użyć:\n\n (możesz też użyć przycisku *Cytuj cały wpis* na pasku edytora, aby uzyskać źródło całej cytowanej wiadomości)\n\n - By odnieść się do kogoś w swojej odpowiedzi użyj nazwy tej osoby poprzedzonej znakiem `@`.\n Jeśli kilka osób ma podobne nazwy, powinno pojawić się małe okno wyboru:\n\n\n\ + ## Co jeszcze mogę zrobić?\n\n\n\n- Wpis który uważasz za wartościowy możesz **polubić** jednym z przycisków widocznych na jego końcu.\n- Jeśli chcesz **udostępnić** dany wpis, możesz skopiować jego *link bezpośredni*.\n- Jeśli z wpisem jest coś nie tak, możesz go **oflagować**, co zasygnalizuje autorowi lub moderatorom, że należy go poprawić.\n- Możesz też stworzyć **zakładkę** zaznaczyć miejsce do którego chcesz wrócić \n\nObsługiwany jest [standard Emoji](http://www.emoji-cheat-sheet.com/): zacznij od `:` lub standardowych emotikon `:)` :smile:\n\n## Kto do mnie pisze?\n\nGdy ktoś odpowie na twój wpis, zacytuje, lub wspomni o tobie używając twojej `@nazwy` w prawym górnym rogu strony, nad dymkiem pojawi się liczba. Klikając na niej dowiesz się kto i gdzie do ciebie pisze.\n\n\n\nPowiadomienie o odpowiedziach oraz prywatnych wiadomościach otrzymasz emailem, jeśli w chwili ich otrzymania nie będziesz na stronie.\n\n## Nowe dyskusje?\n\nDomyślnie wszystkie dyskusje młodsze niż dwa dni są uważane za nowe.\nKażda w której bierzesz udział jest automatycznie śledzona.\n\nObok tych tematów zobaczysz niebieskie wskaźniki nowości oraz ilości nieprzeczytanych wpisów:\n\n\n\nMożesz zmienić stan powiadomienia każdego tematu przy użyciu przycisku znajdującego się na jego końcu (kategorie posiadają podobny przycisk).\n\nAby zmienić domyślne ustawienia śledzenia i zdefiniować nowe wejdź w [preferencje](/my/preferences).\n\n## Dlaczego nie mogę wykonać niektórych operacji?\n\nDla ochrony innych, nowi użytkownicy mają nałożone ograniczenia. Udzielając się w dyskusjach zdobędziesz zaufanie społeczności, staniesz się pełnoprawnym jej obywatelem i wspomniane limity zostaną automatycznie zdjęte. Zdobywając odpowiednio wysoki [poziom zaufania](https://meta.discourse.org/t/what-do-user-trust-levels-do/4924)\ + \ otrzymasz dostęp do zaawansowanych funkcji dzięki którym będziesz mógł pomóc w zarządzaniu społecznością.\n" welcome_user: subject_template: "Witaj na forum %{site_name}!" welcome_invite: @@ -695,9 +699,9 @@ pl_PL: unsubscribe: title: "Wypisz" description: "Nie chcesz otrzymywać tych emaili? Nie ma problemu! Kliknij poniżej by wypisać się natychmiast:" - reply_by_email: "To respond, reply to this email or visit %{base_url}%{url} in your browser." + 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: "Dodane przez %{username} dnia %{post_date}" + posted_by: "Dodany przez %{username} w dniu %{post_date}" user_invited_to_private_message_pm: subject_template: "[%{site_name}] %{username} dołączył ciebie do prywatnego tematu '%{topic_title}'" text_body_template: | @@ -743,7 +747,7 @@ pl_PL: digest: subject_template: "[%{site_name}] Streszczenie dla %{date}" new_activity: "Nowa aktywność w Twoich tematach i wpisach:" - top_topics: "Najpopularniejsze wpisy" + top_topics: "Popularne wpisy" other_new_topics: "Popularne wątki" click_here: "kliknij tutaj" from: "%{site_name} streszczenie" diff --git a/config/locales/server.pt_BR.yml b/config/locales/server.pt_BR.yml index 1091f5070..6e39e3cf9 100644 --- a/config/locales/server.pt_BR.yml +++ b/config/locales/server.pt_BR.yml @@ -579,7 +579,6 @@ pt_BR: invited_to_private_message: "%{display_username} convidou-te para uma conversa privada: %{link}" invitee_accepted: "%{display_username} aceitou o seu convite" linked: "%{display_username} linkou você em %{link}" - granted_badge: "Foi concedido a você %{link}" search: within_post: "#%{post_number} por %{username}: %{excerpt}" types: diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml index cf09d8174..ddaabbf69 100644 --- a/config/locales/server.ru.yml +++ b/config/locales/server.ru.yml @@ -772,7 +772,6 @@ ru: invited_to_private_message: "%{display_username} пригласил вас в частную беседу: %{link}" invitee_accepted: "%{display_username} принял ваше приглашение" linked: "пользователь %{display_username} упомянул вас в %{link}" - granted_badge: "Вы награждены: %{link}" search: within_post: "#%{post_number} от %{username}: %{excerpt}" types: diff --git a/config/locales/server.uk.yml b/config/locales/server.uk.yml index 6312ea078..35a6b9340 100644 --- a/config/locales/server.uk.yml +++ b/config/locales/server.uk.yml @@ -480,7 +480,6 @@ uk: private_message: "%{display_username} sent you a private message: %{link}" invited_to_private_message: "%{display_username} invited you to a private message: %{link}" invitee_accepted: "%{display_username} accepted your invitation" - granted_badge: "Ви отримали %{link}" search: within_post: "#%{post_number} by %{username}: %{excerpt}" types: diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml index a2de77d71..47904ec83 100644 --- a/config/locales/server.zh_CN.yml +++ b/config/locales/server.zh_CN.yml @@ -190,8 +190,10 @@ zh_CN: vip_category_name: "贵宾室" vip_category_description: "高于信任等级3的用户参与讨论的分类。" meta_category_name: "站务" + meta_category_description: "讨论该站点的站务、组织、如何运作和如何改善它。" staff_category_name: "职员" staff_category_description: "职员私有的分类。只有管理员和版主才能阅览主题。" + assets_topic_body: "这是一个永久主题,仅对职员可见,用于存储论坛设计使用的图像和文件。不要删除它!\n\n\n详细教程:\n\n\n1. 回复到这个主题。\n2. 上传您想用作站点标志、图标和其他所有图片到这儿。(使用帖子编辑器工具栏中的上传图标,或者拖拽或者粘贴图像。)\n3. 提交您的回复添加帖子。\n4. 在您的新帖子里右键点击图片获得这些已上传图片的链接,或者点击编辑按钮来编辑帖子,获得到图片的相对地址。复制这些相对地址。\n5. 粘贴这些图像的路径到[基础设置](/admin/site_settings/category/required)。\n\n\n如果您需要运行额外的上传文件类型,在[文件设置](/admin/site_settings/category/files)中编辑 `authorized_extensions`。" lounge_welcome: title: "欢迎来到贵宾室" body: |2 @@ -243,6 +245,7 @@ zh_CN: title: "资深用户" change_failed_explanation: "您尝试将 %{user_name} 降至 '%{new_trust_level}'。然而他们的信任等级已经是 '%{current_trust_level}'。%{user_name} 将仍是 '%{current_trust_level}'" rate_limiter: + slow_down: "您已经重复这一操作太多次了,请一会再重试" too_many_requests: "您的请求过于频繁,请等待 %{time_left} 之后再试。" hours: other: "%{count} 小时" @@ -374,6 +377,7 @@ zh_CN: email_body: "%{link}\n\n%{message}" flagging: you_must_edit: '

您的帖子被社群成员标记了。请查看您的私信

' + user_must_edit: '

您的帖子已经被社群标记并被临时隐藏。

' archetypes: regular: title: "常规主题" @@ -503,6 +507,9 @@ zh_CN: education_new_topic: title: "新用户培训:第一个主题" description: "在新用户发表前两个主题时,自动在编辑器上方弹出的帮助信息。" + usage_tips: + title: "新用户指引" + description: "新用户指引和重要信息" welcome_user: title: "欢迎:新用户" description: "当新用户注册后自动发送给他们的欢迎私信。" @@ -653,6 +660,7 @@ zh_CN: sso_overrides_email: "用 SSO 信息中的外部邮件地址覆盖本地邮件地址(警告:因为对本地邮件的统一处理,这个邮件地址可能不同)" sso_overrides_username: "用 SSO 信息中的外部用户名覆盖用户名(警告:因为对本地用户名的统一处理,用户名的长度或者要求可能不同)" sso_overrides_name: "用 SSO 信息中的外部名字覆盖本地名字(警告:因为对本地名字的统一处理,这个名字可能不同)" + sso_overrides_avatar: "用单点登录信息中的外部站点头像覆盖用户头像。如果启用,强烈建议禁用 allow_uploaded_avatars" enable_local_logins: "启用本地用户名和密码验证。(注意:邀请特性需要该选项)" allow_new_registrations: "允许新用户注册。取消选择将阻止任何人创建一个新账户。" enable_google_logins: "(废弃)启用 Google 验证。这是 OpenID 的验证方案,已被 Google 废弃。新论坛将无法使用它。使用 Google Oauth2 替代它。现存论坛必须在2015年4月20日前切换至 Google Oauth。" @@ -672,6 +680,7 @@ zh_CN: allow_restore: "允许导入数据,这将能替换所有全站数据!除非您计划导入数据,否则请保持设置为 false" maximum_backups: "磁盘保存的最大备份数量。老的备份将自动删除" backup_daily: "每天自动创建站点备份。" + enable_s3_backups: "当完成备份后上传备份到 S3。重要:确定在文件设置中的 S3 验证信息有效。" s3_backup_bucket: "远端备份 bucket。警告:确认它使私有的 bucket。" active_user_rate_limit_secs: "更新'最后一次见到'数据的间隔,单位为秒" previous_visit_timeout_hours: "系统判断一次访问之后多少小时后为'上一次'访问" @@ -691,7 +700,9 @@ zh_CN: clean_up_uploads: "移除孤立的已上传资料。警告:您可能想要在启用这个设定前备份一下 /uploads 目录。" clean_orphan_uploads_grace_period_hours: "删除孤立上传资料的宽限期(单位:小时)" purge_deleted_uploads_grace_period_days: "移除已删除的孤立上传资料的宽限期(单位:小时)" + purge_inactive_users_grace_period_days: "移除不活跃用户的宽限期(单位:天)。" enable_s3_uploads: "上传文件到 Amazon S3。" + s3_use_iam_profile: '使用 AWS EC2 IAM 角色来获得 key。注意:启用这个会覆盖“S3 access key id”和“S3 secret access key” 设置。' s3_upload_bucket: "上传文件保存于 Amazon S3 的 bucket 名字。警告:必须为小写,无句点(.)。" s3_access_key_id: "将用于上传图片的 Amazon S3 access key。" s3_secret_access_key: "将用于上传图片的 Amazon S3 secret key。" @@ -717,6 +728,8 @@ zh_CN: leader_requires_posts_read_all_time: "用户升至领导信任等级(3)所需查看的最小帖子数量。" leader_requires_max_flagged: "用户在最近 100 天内升至领导(3)信任等级(0或更高)所需的必须没有超过 x 个帖子被 x 个不同的用户标记数量,x为数量。" leader_promotion_min_duration: "领导可被降级前最小持续天数。" + leader_requires_likes_given: "在最近 100 天内升至领导(3)信任等级所需给出的赞。" + leader_requires_likes_received: "在最近 100 天内升至领导(3)信任等级所需收到的赞。" leader_links_no_follow: "领导的链接是否使用 nofollow 属性。" min_trust_to_create_topic: "创建主题所需的最低信任等级。" min_trust_to_edit_wiki_post: "能编辑维基模式帖子的最小信任等级" @@ -742,6 +755,12 @@ zh_CN: authorized_extensions: "允许上传文件的扩展名列表('*' 表示允许所有文件类型)" max_similar_results: "当用户撰写新主题时,显示多少类似主题给用户。比较是根据标题和内容进行的。" title_prettify: "防止常见标题里的错别字和错误,包括全部大写,第一个字符小写,多个'!'和'?',结尾多余的'.'等等。" + topic_views_heat_low: "多少次浏览后,该视图将稍稍高亮。" + topic_views_heat_medium: "多少次浏览后,该视图将明显高亮。" + topic_views_heat_high: "多少次浏览后,该视图将强烈高亮。" + cold_age_days_low: "在讨论开始多少天后,最后活跃日期将轻微高亮。" + cold_age_days_medium: "在讨论开始多少天后,最后活跃日期将明显高亮。" + cold_age_days_high: "在讨论开始多少天后,最后活跃日期将强烈高亮。" faq_url: "如果您的 FAQ 文档在外部,那么请在此填写其完整 URL 地址。" tos_url: "如果您的服务条款文档在外部,那么请在此填写其完整 URL 地址。" privacy_policy_url: "如果您的隐私政策文档在外部,那么请在此填写其完整 URL 地址。" @@ -831,7 +850,6 @@ zh_CN: invited_to_private_message: "%{display_username} 邀请您进行私信:%{link}" invitee_accepted: "%{display_username} 接受了您的邀请" linked: "%{display_username} 在 %{link} 里链接了您" - granted_badge: "您被授予了 %{link}" search: within_post: "#%{post_number} %{username}: %{excerpt}" types: @@ -1286,6 +1304,10 @@ zh_CN: see_more: "更多" search_title: "搜索该网页" search_google: "Google" + login_required: + welcome_message: | + #[欢迎来到 %{title}](#welcome) + 您需要一个账号。请创建一个账号或者登录以继续。 terms_of_service: title: "服务条款" user_content_license: | @@ -1310,6 +1332,7 @@ zh_CN: spam_hosts: "新用户试图创建链接到同一个域名的多个帖子。查看站点设置的 newuser_spam_host_threshold。" email_log: no_user: "不能找到用户 ID 为 %{user_id} 的用户" + suspended_not_pm: "用户被封禁,这不是私信" seen_recently: "用户最近活跃" post_not_found: "不能找到帖子 ID 为 %{post_id} 的帖子" notification_already_read: "这封邮件中的通知已经被阅读了" diff --git a/config/locales/server.zh_TW.yml b/config/locales/server.zh_TW.yml index d3650ed4e..4623aa3a6 100644 --- a/config/locales/server.zh_TW.yml +++ b/config/locales/server.zh_TW.yml @@ -641,7 +641,6 @@ zh_TW: invited_to_private_message: "%{display_username} 邀請你進行私人通訊:%{link}" invitee_accepted: "%{display_username} 接受了你的邀請" linked: "%{display_username} 在 %{link} 關連了你" - granted_badge: "你被授與了 %{link}" search: within_post: "#%{post_number} by %{username}: %{excerpt}" types: From a1244043d37e05669473441d5360aac1acbec7e0 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 28 Aug 2014 17:15:53 +1000 Subject: [PATCH 07/43] FIX: when search finds a deep link in a topic it takes you to it --- app/controllers/search_controller.rb | 2 +- lib/search.rb | 13 ++++++++----- spec/components/search_spec.rb | 12 +----------- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index f3e00c16b..bcf0e7fe9 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -8,7 +8,7 @@ class SearchController < ApplicationController def query params.require(:term) - + search_args = {guardian: guardian} search_args[:type_filter] = params[:type_filter] if params[:type_filter].present? if params[:include_blurbs].present? diff --git a/lib/search.rb b/lib/search.rb index 7d3c167c0..0be363c47 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -279,13 +279,16 @@ class Search def aggregate_search cols = ['topics.id', 'topics.title', 'topics.slug'] - topics = posts_query(@limit, aggregate_search: true).group(*cols).pluck(*cols) + topics = posts_query(@limit, aggregate_search: true) + .group(*cols) + .pluck('min(posts.post_number)',*cols) + topics.each do |t| @results.add_result(SearchResult.new(type: :topic, - topic_id: t[0], - id: t[0], - title: t[1], - url: "/t/#{t[2]}/#{t[0]}")) + topic_id: t[1], + id: t[1], + title: t[2], + url: "/t/#{t[3]}/#{t[1]}/#{t[0]}")) end end diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb index 29b4ce20b..3fb68152e 100644 --- a/spec/components/search_spec.rb +++ b/spec/components/search_spec.rb @@ -110,17 +110,8 @@ describe Search do it 'returns a result' do result.should be_present - end - - it 'has the display name as the title' do result[:title].should == user.username - end - - it 'has the avatar_template is there so it can hand it to the client' do result[:avatar_template].should_not be_nil - end - - it 'has a url for the record' do result[:url].should == "/users/#{user.username_lower}" end @@ -196,8 +187,7 @@ describe Search do result.should be_present result[:title].should == topic.title - # The link is to the topic url because it's aggregated - result[:url].should == topic.relative_url + result[:url].should == topic.relative_url + "/2" end end From 38f52f0f8e9f590c31c76e644b6da8507ed98d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Thu, 28 Aug 2014 16:15:48 +0200 Subject: [PATCH 08/43] FIX: banner topic's max-width & max-height on both mobile & desktop --- .../stylesheets/common/components/banner.css.scss | 1 + app/assets/stylesheets/desktop/banner.scss | 12 ++---------- app/assets/stylesheets/mobile/banner.scss | 9 +++++++++ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/assets/stylesheets/common/components/banner.css.scss b/app/assets/stylesheets/common/components/banner.css.scss index ee93bbd4a..7ab2422d1 100644 --- a/app/assets/stylesheets/common/components/banner.css.scss +++ b/app/assets/stylesheets/common/components/banner.css.scss @@ -8,6 +8,7 @@ background: scale-color($tertiary, $lightness: 90%); @include box-shadow(0 1px 2px scale-color($tertiary, $lightness: 70%)); z-index: 501; + overflow: auto; &.overlay { position: fixed; diff --git a/app/assets/stylesheets/desktop/banner.scss b/app/assets/stylesheets/desktop/banner.scss index a67c674d3..3bfe77780 100644 --- a/app/assets/stylesheets/desktop/banner.scss +++ b/app/assets/stylesheets/desktop/banner.scss @@ -4,14 +4,6 @@ #banner { margin-bottom: 10px; -} - -@media all -and (min-width: 1090px) { - width: 1090px; -} - -@media all -and (max-width: 1090px) { - width: 100%; + max-width: 1090px; + max-height: 250px; } diff --git a/app/assets/stylesheets/mobile/banner.scss b/app/assets/stylesheets/mobile/banner.scss index 90001fedd..bc82f0520 100644 --- a/app/assets/stylesheets/mobile/banner.scss +++ b/app/assets/stylesheets/mobile/banner.scss @@ -4,4 +4,13 @@ #banner { margin: 10px; + + @media all and (max-height: 499px) { + max-height: 100px; + } + + @media all and (min-height: 500px) { + max-height: 180px; + } + } From aed0285bc60eeb7b7e812de925266143246d532e Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 28 Aug 2014 11:28:34 -0400 Subject: [PATCH 09/43] Update onebox gem --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index c5f887fbb..65c4c9c6e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -226,7 +226,7 @@ GEM omniauth-twitter (1.0.1) multi_json (~> 1.3) omniauth-oauth (~> 1.0) - onebox (1.4.5) + onebox (1.4.7) moneta (~> 0.7) multi_json (~> 1.7) mustache (~> 0.99) From c9262a839025e1436ac0e7b813134d8dccd939c5 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 28 Aug 2014 12:07:13 -0400 Subject: [PATCH 10/43] FIX: Resend activation email was busted --- app/controllers/application_controller.rb | 7 +++++-- app/controllers/users_controller.rb | 2 +- spec/controllers/users_controller_spec.rb | 6 +----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 30cea7940..27179e4ab 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -212,11 +212,14 @@ class ApplicationController < ActionController::Base Middleware::AnonymousCache.anon_cache(request.env, time_length) end - def fetch_user_from_params + def fetch_user_from_params(opts=nil) + opts ||= {} user = if params[:username] username_lower = params[:username].downcase username_lower.gsub!(/\.json$/, '') - User.find_by(username_lower: username_lower, active: true) + find_opts = {username_lower: username_lower} + find_opts[:active] = true unless opts[:include_inactive] + User.find_by(find_opts) elsif params[:external_id] SingleSignOnRecord.find_by(external_id: params[:external_id]).try(:user) end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 2cd93c9aa..e08f4df69 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -302,7 +302,7 @@ class UsersController < ApplicationController end def send_activation_email - @user = fetch_user_from_params + @user = fetch_user_from_params(include_inactive: true) @email_token = @user.email_tokens.unconfirmed.active.first enqueue_activation_email if @user render nothing: true diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 053eb2391..ed1c6e33f 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -942,11 +942,7 @@ describe UsersController do describe 'send_activation_email' do context 'for an existing user' do - let(:user) { Fabricate(:user) } - - before do - UsersController.any_instance.stubs(:fetch_user_from_params).returns(user) - end + let(:user) { Fabricate(:user, active: false) } context 'with a valid email_token' do it 'should send the activation email' do From 30b102aa980f70a104b4bb9b439df18847d42416 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 28 Aug 2014 12:42:31 -0400 Subject: [PATCH 11/43] FIX: Show uncategorized with badge/description in the category chooser --- .../javascripts/discourse/models/category.js | 7 +++++++ .../discourse/views/category-chooser.js.es6 | 18 ++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/discourse/models/category.js b/app/assets/javascripts/discourse/models/category.js index 37e5b95e4..0b40ed3e0 100644 --- a/app/assets/javascripts/discourse/models/category.js +++ b/app/assets/javascripts/discourse/models/category.js @@ -188,6 +188,8 @@ Discourse.Category = Discourse.Model.extend({ }.property('id') }); +var _uncategorized; + Discourse.Category.reopenClass({ NotificationLevel: { @@ -197,6 +199,11 @@ Discourse.Category.reopenClass({ MUTED: 0 }, + findUncategorized: function() { + _uncategorized = _uncategorized || Discourse.Category.list().findBy('id', Discourse.Site.currentProp('uncategorized_category_id')); + return _uncategorized; + }, + slugFor: function(category) { if (!category) return ""; diff --git a/app/assets/javascripts/discourse/views/category-chooser.js.es6 b/app/assets/javascripts/discourse/views/category-chooser.js.es6 index bd5258d6e..84bb3424d 100644 --- a/app/assets/javascripts/discourse/views/category-chooser.js.es6 +++ b/app/assets/javascripts/discourse/views/category-chooser.js.es6 @@ -34,7 +34,7 @@ export default ComboboxView.extend({ if (this.get('rootNone')) { return "category.none"; } else { - return Discourse.Category.list().findBy('id', Discourse.Site.currentProp('uncategorized_category_id')); + return Discourse.Category.findUncategorized(); } } else { return 'category.choose'; @@ -42,9 +42,20 @@ export default ComboboxView.extend({ }.property(), template: function(item) { - var category = Discourse.Category.findById(parseInt(item.id,10)); - if (!category) return item.text; + var category; + + // If we have no id, but text with the uncategorized name, we can use that badge. + if (Em.empty(item.id)) { + var uncat = Discourse.Category.findUncategorized(); + if (uncat && uncat.get('name') === item.text) { + category = uncat; + } + } else { + category = Discourse.Category.findById(parseInt(item.id,10)); + } + + if (!category) return item.text; var result = badgeHtml(category, {showParent: false, link: false, allowUncategorized: true}), parentCategoryId = category.get('parent_category_id'); if (parentCategoryId) { @@ -56,7 +67,6 @@ export default ComboboxView.extend({ var description = category.get('description'); // TODO wtf how can this be null?; if (description && description !== 'null') { - result += '
' + description.substr(0,200) + (description.length > 200 ? '…' : '') + From 9090df63baf0897085b6a6a008e132a07d287863 Mon Sep 17 00:00:00 2001 From: riking Date: Thu, 28 Aug 2014 10:45:40 -0700 Subject: [PATCH 12/43] Fix pop3 settings in locale file --- config/locales/server.en.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 838f07849..be823fc7c 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -923,13 +923,13 @@ en: disable_emails: "Prevent Discourse from sending any kind of emails" - pop3s_polling_enabled: "Poll via POP3S for email replies." - pop3s_polling_insecure: "Poll using plain text POP3 without SSL." - pop3s_polling_period_mins: "The period in minutes between checking the POP3S account for email. NOTE: requires restart." - pop3s_polling_port: "The port to poll a POP3S account on." - pop3s_polling_host: "The host to poll for email via POP3S." - pop3s_polling_username: "The username for the POP3S account to poll for email." - pop3s_polling_password: "The password for the POP3S account to poll for email." + 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." + pop3_polling_port: "The port to poll a POP3 account on." + pop3_polling_host: "The host to poll for email via POP3." + pop3_polling_username: "The username for the POP3 account to poll for email." + pop3_polling_password: "The password for the POP3 account to poll for email." email_in: "Allow users to post new topics via email (requires pop3 polling). Configure the addresses in the \"Settings\" tab of each category." email_in_min_trust: "The minimum trust level a user needs to have to be allowed to post new topics via email." email_prefix: "The [label] used in the subject of emails. It will default to 'title' if not set." From 30e034aa5301eaf7c7deebb7b2f7f3addeecc33f Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Thu, 28 Aug 2014 23:50:46 +0530 Subject: [PATCH 13/43] Update onebox gem --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 65c4c9c6e..234fe6757 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -226,7 +226,7 @@ GEM omniauth-twitter (1.0.1) multi_json (~> 1.3) omniauth-oauth (~> 1.0) - onebox (1.4.7) + onebox (1.4.8) moneta (~> 0.7) multi_json (~> 1.7) mustache (~> 0.99) From 69cb5bc425b7967f8b0a180d7ab6fa1bba01bc1a Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 28 Aug 2014 14:34:31 -0400 Subject: [PATCH 14/43] FIX: Centralize Top rendering, remove old code paths. Fix some bugs. --- .../controllers/discovery/top.js.es6 | 29 ----- .../controllers/discovery/topics.js.es6 | 2 + .../dynamic-route-builders.js.es6 | 11 ++ .../javascripts/discourse/models/top_list.js | 37 ------ .../discourse/models/topic_list.js | 1 + .../routes/build-category-route.js.es6 | 2 +- .../discourse/routes/build-topic-route.js.es6 | 7 +- .../discourse/routes/discovery_top_routes.js | 123 ------------------ .../templates/discovery/top.js.handlebars | 24 ---- .../templates/discovery/topics.js.handlebars | 4 + app/controllers/list_controller.rb | 38 ++---- app/models/topic_list.rb | 3 +- app/serializers/topic_list_serializer.rb | 7 +- test/javascripts/fixtures/top_fixture.js.es6 | 2 +- .../integration/topic-discovery-test.js.es6 | 2 +- 15 files changed, 42 insertions(+), 250 deletions(-) delete mode 100644 app/assets/javascripts/discourse/controllers/discovery/top.js.es6 delete mode 100644 app/assets/javascripts/discourse/models/top_list.js delete mode 100644 app/assets/javascripts/discourse/routes/discovery_top_routes.js delete mode 100644 app/assets/javascripts/discourse/templates/discovery/top.js.handlebars diff --git a/app/assets/javascripts/discourse/controllers/discovery/top.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/top.js.es6 deleted file mode 100644 index 1bce2033a..000000000 --- a/app/assets/javascripts/discourse/controllers/discovery/top.js.es6 +++ /dev/null @@ -1,29 +0,0 @@ -import DiscoveryController from 'discourse/controllers/discovery'; - -export default DiscoveryController.extend({ - needs: ['discovery'], - - period: function() { - return this.get('controllers.discovery.periods').findBy('id', this.get('periodId')); - }.property('periodId'), - - topicList: function() { - return this.get('model.' + this.get('periodId')); - }.property('periodId'), - - actions: { - refresh: function() { - var self = this; - - // Don't refresh if we're still loading - if (this.get('controllers.discovery.loading')) { return; } - - this.send('loading'); - Discourse.TopList.find().then(function(top_lists) { - self.set('model', top_lists); - self.send('loadingComplete'); - }); - } - } - -}); diff --git a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 index 8bde80be3..c9cef05c5 100644 --- a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 @@ -6,6 +6,8 @@ var controllerOpts = { bulkSelectEnabled: false, selected: [], + redirectedReason: Em.computed.alias('currentUser.redirected_to_top_reason'), + order: 'default', ascending: false, diff --git a/app/assets/javascripts/discourse/initializers/dynamic-route-builders.js.es6 b/app/assets/javascripts/discourse/initializers/dynamic-route-builders.js.es6 index b7b1f0433..f27267480 100644 --- a/app/assets/javascripts/discourse/initializers/dynamic-route-builders.js.es6 +++ b/app/assets/javascripts/discourse/initializers/dynamic-route-builders.js.es6 @@ -18,6 +18,17 @@ export default { app["Discovery" + filter.capitalize() + "CategoryNoneRoute"] = buildCategoryRoute(filter, {no_subcategories: true}); }); + Discourse.DiscoveryTopRoute = buildTopicRoute('top', { + actions: { + willTransition: function() { + Discourse.User.currentProp("should_be_redirected_to_top", false); + Discourse.User.currentProp("redirected_to_top_reason", null); + } + } + }); + + Discourse.DiscoveryTopCategoryRoute = buildCategoryRoute('top'); + Discourse.DiscoveryTopCategoryNoneRoute = buildCategoryRoute('top', {no_subcategories: true}); Discourse.Site.currentProp('periods').forEach(function(period) { app["DiscoveryTop" + period.capitalize() + "Controller"] = DiscoverySortableController.extend(); app["DiscoveryTop" + period.capitalize() + "Route"] = buildTopicRoute('top/' + period); diff --git a/app/assets/javascripts/discourse/models/top_list.js b/app/assets/javascripts/discourse/models/top_list.js deleted file mode 100644 index 7ea8bf5ea..000000000 --- a/app/assets/javascripts/discourse/models/top_list.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - A data model representing a list of top topic lists - - @class TopList - @extends Discourse.Model - @namespace Discourse - @module Discourse -**/ - -Discourse.TopList = Discourse.Model.extend({}); - -Discourse.TopList.reopenClass({ - find: function(filter) { - return PreloadStore.getAndRemove("top_lists", function() { - var url = Discourse.getURL("/") + (filter || "top") + ".json"; - return Discourse.ajax(url); - }).then(function (result) { - var topList = Discourse.TopList.create({ - can_create_topic: result.can_create_topic, - draft: result.draft, - draft_key: result.draft_key, - draft_sequence: result.draft_sequence - }); - - Discourse.Site.currentProp('periods').forEach(function(period) { - // if there is a list for that period - if (result[period]) { - // instanciate a new topic list with no sorting - topList.set(period, Discourse.TopicList.from(result[period])); - topList.set('periodId', period); - } - }); - - return topList; - }); - } -}); diff --git a/app/assets/javascripts/discourse/models/topic_list.js b/app/assets/javascripts/discourse/models/topic_list.js index 740089b24..4e6f381f5 100644 --- a/app/assets/javascripts/discourse/models/topic_list.js +++ b/app/assets/javascripts/discourse/models/topic_list.js @@ -176,6 +176,7 @@ Discourse.TopicList.reopenClass({ draft_key: result.topic_list.draft_key, draft_sequence: result.topic_list.draft_sequence, draft: result.topic_list.draft, + for_period: result.topic_list.for_period, loaded: true }); diff --git a/app/assets/javascripts/discourse/routes/build-category-route.js.es6 b/app/assets/javascripts/discourse/routes/build-category-route.js.es6 index 31ec0f56a..f060bb42d 100644 --- a/app/assets/javascripts/discourse/routes/build-category-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-category-route.js.es6 @@ -63,7 +63,7 @@ export default function(filter, params) { setupController: function(controller, model) { var topics = this.get('topics'), periods = this.controllerFor('discovery').get('periods'), - periodId = filter.indexOf('/') > 0 ? filter.split('/')[1] : '', + periodId = topics.get('for_period') || (filter.indexOf('/') > 0 ? filter.split('/')[1] : ''), filterText = I18n.t('filters.' + filter.replace('/', '.') + '.title', {count: 0}); Discourse.set('title', I18n.t('filters.with_category', { filter: filterText, category: model.get('name') })); diff --git a/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 b/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 index d1b9cd924..e92464e4f 100644 --- a/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 @@ -12,7 +12,8 @@ export function filterQueryParams(params, defaultParams) { return findOpts; } -export default function(filter) { +export default function(filter, extras) { + extras = extras || {}; return Discourse.Route.extend({ queryParams: queryParams, @@ -36,7 +37,7 @@ export default function(filter) { }))); var periods = this.controllerFor('discovery').get('periods'), - periodId = filter.indexOf('/') > 0 ? filter.split('/')[1] : '', + periodId = model.get('for_period') || (filter.indexOf('/') > 0 ? filter.split('/')[1] : ''), filterText = I18n.t('filters.' + filter.replace('/', '.') + '.title', {count: 0}); if (filter === Discourse.Utilities.defaultHomepage()) { @@ -61,6 +62,6 @@ export default function(filter) { this.render('navigation/default', { outlet: 'navigation-bar' }); this.render('discovery/topics', { controller: 'discovery/topics', outlet: 'list-container' }); } - }); + }, extras); } diff --git a/app/assets/javascripts/discourse/routes/discovery_top_routes.js b/app/assets/javascripts/discourse/routes/discovery_top_routes.js deleted file mode 100644 index f8c960881..000000000 --- a/app/assets/javascripts/discourse/routes/discovery_top_routes.js +++ /dev/null @@ -1,123 +0,0 @@ -/** - Handles the routes related to 'Top' - - @class DiscoveryTopRoute - @extends Discourse.Route - @namespace Discourse - @module Discourse -**/ -Discourse.DiscoveryTopRoute = Discourse.Route.extend(Discourse.OpenComposer, { - beforeModel: function() { - this.controllerFor('navigation/default').set('filterMode', 'top'); - }, - - model: function() { - return Discourse.TopList.find(); - }, - - setupController: function(controller, model) { - var filterText = I18n.t('filters.top.title'); - Discourse.set('title', I18n.t('filters.with_topics', {filter: filterText})); - this.controllerFor('discovery/top').setProperties({ model: model, category: null }); - this.controllerFor('navigation/default').set('canCreateTopic', model.get('can_create_topic')); - - this.openTopicDraft(model); - }, - - renderTemplate: function() { - this.render('navigation/default', { outlet: 'navigation-bar' }); - this.render('discovery/top', { outlet: 'list-container' }); - }, - - actions: { - - willTransition: function () { - Discourse.User.currentProp("should_be_redirected_to_top", false); - Discourse.User.currentProp("redirected_to_top_reason", null); - }, - - createTopic: function() { - this.openComposer(this.controllerFor('discovery/top')); - } - - } - -}); - -/** - Handles the routes related to 'Top' within a category - - @class DiscoveryTopCategoryRoute - @extends Discourse.Route - @namespace Discourse - @module Discourse -**/ -Discourse.DiscoveryTopCategoryRoute = Discourse.Route.extend(Discourse.OpenComposer, { - model: function(params) { - return Discourse.Category.findBySlug(params.slug, params.parentSlug); - }, - - afterModel: function(model) { - var self = this, - noSubcategories = this.get('no_subcategories'), - filterMode = 'category/' + Discourse.Category.slugFor(model) + (noSubcategories ? '/none' : '') + '/l/top'; - - this.controllerFor('search').set('searchContext', model); - - var opts = { category: model, filterMode: filterMode }; - opts.noSubcategories = noSubcategories; - opts.canEditCategory = model.get('can_edit'); - this.controllerFor('navigation/category').setProperties(opts); - - return Discourse.TopList.find(filterMode).then(function(list) { - // If all the categories are the same, we can hide them - var hideCategory = !_.any(Discourse.Site.currentProp('periods'), function(period){ - if (list[period]) { - return list[period].get('topics').find(function(t) { return t.get('category') !== model; }); - } - return false; - }); - list.set('hideCategory', hideCategory); - self.set('topList', list); - }); - }, - - setupController: function(controller, model) { - var topList = this.get('topList'); - var filterText = I18n.t('filters.top.title'); - Discourse.set('title', I18n.t('filters.with_category', {filter: filterText, category: model.get('name').capitalize()})); - this.controllerFor('navigation/category').set('canCreateTopic', topList.get('can_create_topic')); - this.controllerFor('discovery/top').setProperties({ - model: topList, - category: model, - noSubcategories: this.get('no_subcategories') - }); - this.set('topList', null); - }, - - renderTemplate: function() { - this.render('navigation/category', { outlet: 'navigation-bar' }); - this.render('discovery/top', { controller: 'discovery/top', outlet: 'list-container' }); - }, - - deactivate: function() { - this._super(); - this.controllerFor('search').set('searchContext', null); - }, - - actions: { - - willTransition: function () { - Discourse.User.currentProp("should_be_redirected_to_top", false); - Discourse.User.currentProp("redirected_to_top_reason", null); - }, - - createTopic: function() { - this.openComposer(this.controllerFor('discovery/top')); - } - - } - -}); - -Discourse.DiscoveryTopCategoryNoneRoute = Discourse.DiscoveryTopCategoryRoute.extend({no_subcategories: true}); diff --git a/app/assets/javascripts/discourse/templates/discovery/top.js.handlebars b/app/assets/javascripts/discourse/templates/discovery/top.js.handlebars deleted file mode 100644 index 270702b9b..000000000 --- a/app/assets/javascripts/discourse/templates/discovery/top.js.handlebars +++ /dev/null @@ -1,24 +0,0 @@ -
- {{#if currentUser.redirected_to_top_reason}} -
{{currentUser.redirected_to_top_reason}}
- {{/if}} - - {{#if topicList}} -
- {{top-period-chooser period=period}} - {{basic-topic-list topicList=topicList hideCategory=hideCategory postsAction="showTopicEntrance"}} - {{#if topicList.topics.length}}{{i18n show_more}}{{/if}} -
- {{/if}} - -
-

- {{#if hasDisplayedAllTopLists}} - {{#link-to "discovery.categories"}}{{i18n topic.browse_all_categories}}{{/link-to}} {{i18n or}} {{#link-to 'discovery.latest'}}{{i18n topic.view_latest_topics}}{{/link-to}}. - {{else}} - {{#link-to "discovery.categories"}}{{i18n topic.browse_all_categories}}{{/link-to}}, {{#link-to 'discovery.latest'}}{{i18n topic.view_latest_topics}}{{/link-to}} {{i18n or}} {{i18n filters.top.other_periods}} - {{top-period-buttons period=period}} - {{/if}} -

-
-
diff --git a/app/assets/javascripts/discourse/templates/discovery/topics.js.handlebars b/app/assets/javascripts/discourse/templates/discovery/topics.js.handlebars index 92e101e15..34708a1eb 100644 --- a/app/assets/javascripts/discourse/templates/discovery/topics.js.handlebars +++ b/app/assets/javascripts/discourse/templates/discovery/topics.js.handlebars @@ -1,3 +1,7 @@ +{{#if redirectedReason}} +
{{redirectedReason}}
+{{/if}} + {{#if showDismissAtTop}}
{{#if showDismissRead}} diff --git a/app/controllers/list_controller.rb b/app/controllers/list_controller.rb index 0bb05b16c..9e3ce049f 100644 --- a/app/controllers/list_controller.rb +++ b/app/controllers/list_controller.rb @@ -130,43 +130,22 @@ class ListController < ApplicationController redirect_to latest_path, :status => 301 end - def top(options = nil) - discourse_expires_in 1.minute - - top_options = build_topic_list_options - top_options.merge!(options) if options - - top = generate_top_lists(top_options) - - top.draft_key = Draft::NEW_TOPIC - top.draft_sequence = DraftSequence.current(current_user, Draft::NEW_TOPIC) - top.draft = Draft.get(current_user, top.draft_key, top.draft_sequence) if current_user - - respond_to do |format| - format.html do - @top = top - store_preloaded('top_lists', MultiJson.dump(TopListSerializer.new(top, scope: guardian, root: false))) - render 'top' - end - format.json do - render json: MultiJson.dump(TopListSerializer.new(top, scope: guardian, root: false)) - end - end + def top(options=nil) + options ||= {} + period = ListController.best_period_for(current_user.try(:previous_visit_at), options[:category]) + send("top_#{period}", options) end def category_top - options = { category: @category.id } - top(options) + top({ category: @category.id }) end def category_none_top - options = { category: @category.id, no_subcategories: true } - top(options) + top({ category: @category.id, no_subcategories: true }) end def parent_category_category_top - options = { category: @category.id } - top(options) + top({ category: @category.id }) end TopTopic.periods.each do |period| @@ -176,6 +155,7 @@ class ListController < ApplicationController top_options[:per_page] = SiteSetting.topics_per_period_in_top_page user = list_target_user list = TopicQuery.new(user, top_options).list_top_for(period) + list.for_period = period list.more_topics_url = construct_next_url_with(top_options) list.prev_topics_url = construct_prev_url_with(top_options) respond(list) @@ -189,7 +169,7 @@ class ListController < ApplicationController self.send("top_#{period}", { category: @category.id, no_subcategories: true }) end - define_method("parent_category_category_#{period}") do + define_method("parent_category_category_top_#{period}") do self.send("top_#{period}", { category: @category.id }) end end diff --git a/app/models/topic_list.rb b/app/models/topic_list.rb index a6b0b4a4f..b8ffab9f1 100644 --- a/app/models/topic_list.rb +++ b/app/models/topic_list.rb @@ -8,7 +8,8 @@ class TopicList :draft, :draft_key, :draft_sequence, - :filter + :filter, + :for_period def initialize(filter, current_user, topics) @filter = filter diff --git a/app/serializers/topic_list_serializer.rb b/app/serializers/topic_list_serializer.rb index 833ef2ca3..f459261ae 100644 --- a/app/serializers/topic_list_serializer.rb +++ b/app/serializers/topic_list_serializer.rb @@ -4,7 +4,8 @@ class TopicListSerializer < ApplicationSerializer :more_topics_url, :draft, :draft_key, - :draft_sequence + :draft_sequence, + :for_period has_many :topics, serializer: TopicListItemSerializer, embed: :objects @@ -12,6 +13,10 @@ class TopicListSerializer < ApplicationSerializer scope.can_create?(Topic) end + def include_for_period? + for_period.present? + end + def include_more_topics_url? object.more_topics_url.present? && (object.topics.size == SiteSetting.topics_per_page) end diff --git a/test/javascripts/fixtures/top_fixture.js.es6 b/test/javascripts/fixtures/top_fixture.js.es6 index 2374e16b1..9dc5c7cd5 100644 --- a/test/javascripts/fixtures/top_fixture.js.es6 +++ b/test/javascripts/fixtures/top_fixture.js.es6 @@ -1,2 +1,2 @@ /*jshint maxlen:10000000 */ -export default {"/top.json": {"can_create_topic":false,"draft":null,"draft_key":"new_topic","draft_sequence":null,"yearly":{"users":[{"id":8222,"username":"techAPJ","uploaded_avatar_id":3281,"avatar_template":"/user_avatar/meta.discourse.org/techapj/{size}/3281.png"},{"id":6626,"username":"riking","uploaded_avatar_id":9779,"avatar_template":"/user_avatar/meta.discourse.org/riking/{size}/9779.png"},{"id":8869,"username":"Puzle","uploaded_avatar_id":3546,"avatar_template":"/user_avatar/meta.discourse.org/puzle/{size}/3546.png"},{"id":1,"username":"sam","uploaded_avatar_id":5243,"avatar_template":"/user_avatar/meta.discourse.org/sam/{size}/5243.png"},{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png"},{"id":2316,"username":"pakl","uploaded_avatar_id":9157,"avatar_template":"/user_avatar/meta.discourse.org/pakl/{size}/9157.png"},{"id":2770,"username":"awesomerobot","uploaded_avatar_id":32393,"avatar_template":"/user_avatar/meta.discourse.org/awesomerobot/{size}/32393.png"},{"id":8307,"username":"HAWK","uploaded_avatar_id":5159,"avatar_template":"/user_avatar/meta.discourse.org/hawk/{size}/5159.png"},{"id":10886,"username":"Onyx","uploaded_avatar_id":33015,"avatar_template":"/user_avatar/meta.discourse.org/onyx/{size}/33015.png"},{"id":10855,"username":"abarker","uploaded_avatar_id":33014,"avatar_template":"/user_avatar/meta.discourse.org/abarker/{size}/33014.png"},{"id":8300,"username":"cpradio","uploaded_avatar_id":4970,"avatar_template":"/user_avatar/meta.discourse.org/cpradio/{size}/4970.png"},{"id":761,"username":"marcoceppi","uploaded_avatar_id":6552,"avatar_template":"/user_avatar/meta.discourse.org/marcoceppi/{size}/6552.png"},{"id":5586,"username":"mkirk","uploaded_avatar_id":14579,"avatar_template":"/user_avatar/meta.discourse.org/mkirk/{size}/14579.png"},{"id":6586,"username":"adopilot","uploaded_avatar_id":12933,"avatar_template":"/user_avatar/meta.discourse.org/adopilot/{size}/12933.png"},{"id":4263,"username":"mcwumbly","uploaded_avatar_id":9796,"avatar_template":"/user_avatar/meta.discourse.org/mcwumbly/{size}/9796.png"},{"id":471,"username":"BhaelOchon","uploaded_avatar_id":6069,"avatar_template":"/user_avatar/meta.discourse.org/bhaelochon/{size}/6069.png"},{"id":7948,"username":"probus","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/probus/{size}/2.png"},{"id":5559,"username":"downey","uploaded_avatar_id":14532,"avatar_template":"/user_avatar/meta.discourse.org/downey/{size}/14532.png"},{"id":3415,"username":"radq","uploaded_avatar_id":11026,"avatar_template":"/user_avatar/meta.discourse.org/radq/{size}/11026.png"},{"id":9759,"username":"alttext","uploaded_avatar_id":31815,"avatar_template":"/user_avatar/meta.discourse.org/alttext/{size}/31815.png"},{"id":1995,"username":"zogstrip","uploaded_avatar_id":8630,"avatar_template":"/user_avatar/meta.discourse.org/zogstrip/{size}/8630.png"},{"id":2989,"username":"meglio","uploaded_avatar_id":33005,"avatar_template":"/user_avatar/meta.discourse.org/meglio/{size}/33005.png"},{"id":5707,"username":"trident","uploaded_avatar_id":31178,"avatar_template":"/user_avatar/meta.discourse.org/trident/{size}/31178.png"},{"id":8492,"username":"Onaldan","uploaded_avatar_id":18651,"avatar_template":"/user_avatar/meta.discourse.org/onaldan/{size}/18651.png"},{"id":2,"username":"neil","uploaded_avatar_id":5245,"avatar_template":"/user_avatar/meta.discourse.org/neil/{size}/5245.png"},{"id":6060,"username":"lightyear","uploaded_avatar_id":15335,"avatar_template":"/user_avatar/meta.discourse.org/lightyear/{size}/15335.png"},{"id":5351,"username":"erlend_sh","uploaded_avatar_id":9794,"avatar_template":"/user_avatar/meta.discourse.org/erlend_sh/{size}/9794.png"},{"id":8085,"username":"watchmanmonitor","uploaded_avatar_id":18154,"avatar_template":"/user_avatar/meta.discourse.org/watchmanmonitor/{size}/18154.png"},{"id":7717,"username":"lake54","uploaded_avatar_id":17722,"avatar_template":"/user_avatar/meta.discourse.org/lake54/{size}/17722.png"},{"id":8325,"username":"StevieD","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/stevied/{size}/2.png"},{"id":4217,"username":"mshappe","uploaded_avatar_id":12337,"avatar_template":"/user_avatar/meta.discourse.org/mshappe/{size}/12337.png"},{"id":19,"username":"eviltrout","uploaded_avatar_id":5275,"avatar_template":"/user_avatar/meta.discourse.org/eviltrout/{size}/5275.png"},{"id":6329,"username":"voltagex","uploaded_avatar_id":15769,"avatar_template":"/user_avatar/meta.discourse.org/voltagex/{size}/15769.png"},{"id":6621,"username":"larsiusprime","uploaded_avatar_id":9783,"avatar_template":"/user_avatar/meta.discourse.org/larsiusprime/{size}/9783.png"},{"id":8873,"username":"birarda","uploaded_avatar_id":19156,"avatar_template":"/user_avatar/meta.discourse.org/birarda/{size}/19156.png"},{"id":8437,"username":"paully21","uploaded_avatar_id":31176,"avatar_template":"/user_avatar/meta.discourse.org/paully21/{size}/31176.png"},{"id":8434,"username":"ArmedGuy","uploaded_avatar_id":18581,"avatar_template":"/user_avatar/meta.discourse.org/armedguy/{size}/18581.png"},{"id":6754,"username":"yeonhoyoon","uploaded_avatar_id":16420,"avatar_template":"/user_avatar/meta.discourse.org/yeonhoyoon/{size}/16420.png"},{"id":6819,"username":"gmanjapan","uploaded_avatar_id":16528,"avatar_template":"/user_avatar/meta.discourse.org/gmanjapan/{size}/16528.png"},{"id":6548,"username":"michaeld","uploaded_avatar_id":1594,"avatar_template":"/user_avatar/meta.discourse.org/michaeld/{size}/1594.png"},{"id":6268,"username":"ChaoticLoki","uploaded_avatar_id":15676,"avatar_template":"/user_avatar/meta.discourse.org/chaoticloki/{size}/15676.png"},{"id":8343,"username":"Piioo","uploaded_avatar_id":3577,"avatar_template":"/user_avatar/meta.discourse.org/piioo/{size}/3577.png"},{"id":9536,"username":"nahtnam","uploaded_avatar_id":20077,"avatar_template":"/user_avatar/meta.discourse.org/nahtnam/{size}/20077.png"},{"id":9093,"username":"RRManzke","uploaded_avatar_id":3868,"avatar_template":"/user_avatar/meta.discourse.org/rrmanzke/{size}/3868.png"},{"id":10981,"username":"daybreaker","uploaded_avatar_id":32709,"avatar_template":"/user_avatar/meta.discourse.org/daybreaker/{size}/32709.png"},{"id":8810,"username":"fantasticfears","uploaded_avatar_id":19068,"avatar_template":"/user_avatar/meta.discourse.org/fantasticfears/{size}/19068.png"},{"id":8,"username":"geek","uploaded_avatar_id":5255,"avatar_template":"/user_avatar/meta.discourse.org/geek/{size}/5255.png"},{"id":10778,"username":"Lid","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/lid/{size}/2.png"},{"id":6664,"username":"judy","uploaded_avatar_id":16281,"avatar_template":"/user_avatar/meta.discourse.org/judy/{size}/16281.png"},{"id":10098,"username":"jwatte","uploaded_avatar_id":20848,"avatar_template":"/user_avatar/meta.discourse.org/jwatte/{size}/20848.png"},{"id":9775,"username":"elberet","uploaded_avatar_id":9789,"avatar_template":"/user_avatar/meta.discourse.org/elberet/{size}/9789.png"},{"id":8820,"username":"aaroleung","uploaded_avatar_id":3539,"avatar_template":"/user_avatar/meta.discourse.org/aaroleung/{size}/3539.png"},{"id":6746,"username":"shiningdracon","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/shiningdracon/{size}/2.png"},{"id":9909,"username":"unikevin","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/unikevin/{size}/2.png"},{"id":8571,"username":"tobiaseigen","uploaded_avatar_id":9785,"avatar_template":"/user_avatar/meta.discourse.org/tobiaseigen/{size}/9785.png"},{"id":8344,"username":"pyro240","uploaded_avatar_id":18464,"avatar_template":"/user_avatar/meta.discourse.org/pyro240/{size}/18464.png"}],"topic_list":{"can_create_topic":false,"draft":null,"draft_key":null,"draft_sequence":null,"topics":[{"id":12156,"title":"Beginners Guide to Deploy Discourse on Digital Ocean using Docker","fancy_title":"Beginners Guide to Deploy Discourse on Digital Ocean using Docker","slug":"beginners-guide-to-deploy-discourse-on-digital-ocean-using-docker","posts_count":203,"reply_count":153,"highest_post_number":213,"image_url":"http://www.discourse.org/images/install/droplet-step-1.png","created_at":"2014-01-23T14:58:17.918-05:00","last_posted_at":"2014-06-13T16:30:30.190-04:00","bumped":true,"bumped_at":"2014-06-13T16:30:30.190-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":9339,"like_count":179,"has_summary":true,"archetype":"regular","last_poster_username":"codinghorror","category_id":10,"posters":[{"extras":null,"description":"Original Poster","user_id":8222},{"extras":null,"description":"Most Posts","user_id":6626},{"extras":null,"description":"Frequent Poster","user_id":8869},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":"latest","description":"Most Recent Poster","user_id":32}]},{"id":13088,"title":"Initial Discourse badge design spec","fancy_title":"Initial Discourse badge design spec","slug":"initial-discourse-badge-design-spec","posts_count":129,"reply_count":87,"highest_post_number":132,"image_url":"/uploads/default/3429/a20bcab33be2b6e2.png","created_at":"2014-02-26T04:55:39.741-05:00","last_posted_at":"2014-07-15T17:15:47.236-04:00","bumped":true,"bumped_at":"2014-07-15T17:15:47.236-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":3136,"like_count":299,"has_summary":true,"archetype":"regular","last_poster_username":"HAWK","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":32},{"extras":null,"description":"Most Posts","user_id":2316},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":2770},{"extras":"latest","description":"Most Recent Poster","user_id":8307}]},{"id":18063,"title":"10k+ posts causes progress bar to show single number","fancy_title":"10k+ posts causes progress bar to show single number","slug":"10k-posts-causes-progress-bar-to-show-single-number","posts_count":67,"reply_count":57,"highest_post_number":70,"image_url":"/uploads/default/_optimized/fdc/03e/3d48765fc4_690x45.png","created_at":"2014-07-25T13:31:34.474-04:00","last_posted_at":"2014-07-26T04:14:18.323-04:00","bumped":true,"bumped_at":"2014-07-26T04:20:54.730-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":259,"like_count":337,"has_summary":true,"archetype":"regular","last_poster_username":"sam","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":10886},{"extras":null,"description":"Most Posts","user_id":10855},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":8300},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":8793,"title":"Brand new plugin interface","fancy_title":"Brand new plugin interface","slug":"brand-new-plugin-interface","posts_count":97,"reply_count":67,"highest_post_number":107,"image_url":"/uploads/meta_discourse/1514/3cca40e605bfe9a3.png","created_at":"2013-08-01T02:18:43.035-04:00","last_posted_at":"2014-07-15T12:14:11.278-04:00","bumped":true,"bumped_at":"2014-07-15T12:14:11.278-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":8515,"like_count":167,"has_summary":true,"archetype":"regular","last_poster_username":"adopilot","category_id":5,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Most Posts","user_id":761},{"extras":null,"description":"Frequent Poster","user_id":5586},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":"latest","description":"Most Recent Poster","user_id":6586}]},{"id":13479,"title":"Topic List design experiments","fancy_title":"Topic List design experiments","slug":"topic-list-design-experiments","posts_count":90,"reply_count":70,"highest_post_number":93,"image_url":"/uploads/default/_optimized/8f2/41d/0436a3b666_689x392.png","created_at":"2014-03-06T23:41:26.312-05:00","last_posted_at":"2014-07-30T16:03:05.846-04:00","bumped":true,"bumped_at":"2014-07-30T16:03:05.846-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1464,"like_count":109,"has_summary":true,"archetype":"regular","last_poster_username":"probus","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":2770},{"extras":null,"description":"Most Posts","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":4263},{"extras":null,"description":"Frequent Poster","user_id":471},{"extras":"latest","description":"Most Recent Poster","user_id":7948}]},{"id":13789,"title":"Badges feedback","fancy_title":"Badges feedback","slug":"badges-feedback","posts_count":89,"reply_count":63,"highest_post_number":92,"image_url":null,"created_at":"2014-03-16T20:16:29.885-04:00","last_posted_at":"2014-07-27T10:10:50.852-04:00","bumped":true,"bumped_at":"2014-07-27T10:10:50.852-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":2089,"like_count":3,"has_summary":true,"archetype":"regular","last_poster_username":"zogstrip","category_id":7,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Most Posts","user_id":5559},{"extras":null,"description":"Frequent Poster","user_id":3415},{"extras":null,"description":"Frequent Poster","user_id":9759},{"extras":"latest","description":"Most Recent Poster","user_id":1995}]},{"id":11911,"title":"How should we implement polls?","fancy_title":"How should we implement polls?","slug":"how-should-we-implement-polls","posts_count":70,"reply_count":51,"highest_post_number":73,"image_url":null,"created_at":"2014-01-12T21:48:03.160-05:00","last_posted_at":"2014-07-27T18:11:30.077-04:00","bumped":true,"bumped_at":"2014-07-27T18:11:30.077-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":2584,"like_count":123,"has_summary":true,"archetype":"regular","last_poster_username":"meglio","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Most Posts","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":6626},{"extras":null,"description":"Frequent Poster","user_id":3415},{"extras":"latest","description":"Most Recent Poster","user_id":2989}]},{"id":10515,"title":"Flatter styling now deployed","fancy_title":"Flatter styling now deployed","slug":"flatter-styling-now-deployed","posts_count":80,"reply_count":41,"highest_post_number":80,"image_url":null,"created_at":"2013-10-20T19:36:00.465-04:00","last_posted_at":"2014-03-18T14:04:00.515-04:00","bumped":true,"bumped_at":"2014-03-18T14:04:00.515-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1753,"like_count":78,"has_summary":true,"archetype":"regular","last_poster_username":"mcwumbly","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Most Posts","user_id":2770},{"extras":null,"description":"Frequent Poster","user_id":5707},{"extras":null,"description":"Frequent Poster","user_id":471},{"extras":"latest","description":"Most Recent Poster","user_id":4263}]},{"id":12346,"title":"What about an easier styling/theming system?","fancy_title":"What about an easier styling/theming system?","slug":"what-about-an-easier-styling-theming-system","posts_count":54,"reply_count":26,"highest_post_number":54,"image_url":null,"created_at":"2014-01-31T19:11:51.887-05:00","last_posted_at":"2014-07-01T17:42:38.425-04:00","bumped":true,"bumped_at":"2014-07-01T17:42:38.425-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1688,"like_count":129,"has_summary":true,"archetype":"regular","last_poster_username":"neil","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":32},{"extras":null,"description":"Most Posts","user_id":2770},{"extras":null,"description":"Frequent Poster","user_id":7948},{"extras":null,"description":"Frequent Poster","user_id":8492},{"extras":"latest","description":"Most Recent Poster","user_id":2}]},{"id":13099,"title":"Replacing Mailing lists: Email-In","fancy_title":"Replacing Mailing lists: Email-In","slug":"replacing-mailing-lists-email-in","posts_count":66,"reply_count":46,"highest_post_number":68,"image_url":null,"created_at":"2014-02-26T13:24:44.965-05:00","last_posted_at":"2014-07-09T18:01:21.166-04:00","bumped":true,"bumped_at":"2014-07-09T19:10:30.547-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1470,"like_count":75,"has_summary":true,"archetype":"regular","last_poster_username":"lake54","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":6060},{"extras":null,"description":"Most Posts","user_id":5351},{"extras":null,"description":"Frequent Poster","user_id":5559},{"extras":null,"description":"Frequent Poster","user_id":8085},{"extras":"latest","description":"Most Recent Poster","user_id":7717}]},{"id":12257,"title":"Is \"Activity\" too ambiguous?","fancy_title":"Is “Activity” too ambiguous?","slug":"is-activity-too-ambiguous","posts_count":53,"reply_count":40,"highest_post_number":53,"image_url":"/uploads/default/_optimized/542/c04/82250e51e5_690x248.png","created_at":"2014-01-28T14:01:08.745-05:00","last_posted_at":"2014-04-13T18:25:45.492-04:00","bumped":true,"bumped_at":"2014-04-13T18:25:45.492-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":632,"like_count":99,"has_summary":true,"archetype":"regular","last_poster_username":"StevieD","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":2770},{"extras":null,"description":"Most Posts","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":4263},{"extras":null,"description":"Frequent Poster","user_id":7948},{"extras":"latest","description":"Most Recent Poster","user_id":8325}]},{"id":8823,"title":"Why is Discourse so slow on Android?","fancy_title":"Why is Discourse so slow on Android?","slug":"why-is-discourse-so-slow-on-android","posts_count":62,"reply_count":42,"highest_post_number":70,"image_url":"/plugins/emoji/images/confused.png","created_at":"2013-08-02T17:25:07.109-04:00","last_posted_at":"2014-05-27T16:05:02.072-04:00","bumped":true,"bumped_at":"2014-05-27T18:37:07.732-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":9299,"like_count":83,"has_summary":true,"archetype":"regular","last_poster_username":"larsiusprime","category_id":7,"posters":[{"extras":null,"description":"Original Poster","user_id":32},{"extras":null,"description":"Most Posts","user_id":4217},{"extras":null,"description":"Frequent Poster","user_id":19},{"extras":null,"description":"Frequent Poster","user_id":6329},{"extras":"latest","description":"Most Recent Poster","user_id":6621}]},{"id":13045,"title":"Official Single-Sign-On for Discourse","fancy_title":"Official Single-Sign-On for Discourse","slug":"official-single-sign-on-for-discourse","posts_count":59,"reply_count":36,"highest_post_number":61,"image_url":"/uploads/default/_optimized/07c/3bf/3fa1d69ceb_690x207.png","created_at":"2014-02-25T03:30:34.321-05:00","last_posted_at":"2014-07-16T21:32:13.904-04:00","bumped":true,"bumped_at":"2014-07-16T21:32:13.904-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":11118,"like_count":70,"has_summary":true,"archetype":"regular","last_poster_username":"yeonhoyoon","category_id":10,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Most Posts","user_id":8873},{"extras":null,"description":"Frequent Poster","user_id":8437},{"extras":null,"description":"Frequent Poster","user_id":8434},{"extras":"latest","description":"Most Recent Poster","user_id":6754}]},{"id":9621,"title":"Free Hosted Option?","fancy_title":"Free Hosted Option?","slug":"free-hosted-option","posts_count":43,"reply_count":33,"highest_post_number":43,"image_url":null,"created_at":"2013-09-05T16:22:20.790-04:00","last_posted_at":"2014-04-08T00:24:46.320-04:00","bumped":true,"bumped_at":"2014-04-08T00:24:46.320-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1756,"like_count":93,"has_summary":false,"archetype":"regular","last_poster_username":"ChaoticLoki","category_id":8,"posters":[{"extras":null,"description":"Original Poster","user_id":6819},{"extras":null,"description":"Most Posts","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":6548},{"extras":null,"description":"Frequent Poster","user_id":19},{"extras":"latest","description":"Most Recent Poster","user_id":6268}]},{"id":11763,"title":"Google AdSense plugin is now available","fancy_title":"Google AdSense plugin is now available","slug":"google-adsense-plugin-is-now-available","posts_count":56,"reply_count":36,"highest_post_number":57,"image_url":"/uploads/default/_optimized/66d/cf0/d69e6709fe_496x500.PNG","created_at":"2014-01-05T14:28:58.037-05:00","last_posted_at":"2014-07-29T19:36:47.514-04:00","bumped":true,"bumped_at":"2014-07-29T19:36:47.514-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1951,"like_count":61,"has_summary":true,"archetype":"regular","last_poster_username":"daybreaker","category_id":22,"posters":[{"extras":null,"description":"Original Poster","user_id":6548},{"extras":null,"description":"Most Posts","user_id":8343},{"extras":null,"description":"Frequent Poster","user_id":9536},{"extras":null,"description":"Frequent Poster","user_id":9093},{"extras":"latest","description":"Most Recent Poster","user_id":10981}]},{"id":13184,"title":"Discourse General Polish prior to V1","fancy_title":"Discourse General Polish prior to V1","slug":"discourse-general-polish-prior-to-v1","posts_count":44,"reply_count":30,"highest_post_number":48,"image_url":"/plugins/emoji/images/arrow_left.png","created_at":"2014-02-27T19:10:41.496-05:00","last_posted_at":"2014-06-08T03:32:02.009-04:00","bumped":true,"bumped_at":"2014-06-06T03:30:23.984-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":1816,"like_count":77,"has_summary":false,"archetype":"regular","last_poster_username":"codinghorror","category_id":7,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Most Posts","user_id":8810},{"extras":null,"description":"Frequent Poster","user_id":2770},{"extras":null,"description":"Frequent Poster","user_id":8222},{"extras":"latest","description":"Most Recent Poster, Frequent Poster","user_id":32}]},{"id":17454,"title":"Spambots from Tor exit points keep taking over my forum","fancy_title":"Spambots from Tor exit points keep taking over my forum","slug":"spambots-from-tor-exit-points-keep-taking-over-my-forum","posts_count":42,"reply_count":26,"highest_post_number":42,"image_url":"/uploads/default/_optimized/b0d/ab3/20401b97ce_690x454.png","created_at":"2014-07-11T03:20:49.433-04:00","last_posted_at":"2014-07-24T10:56:21.112-04:00","bumped":true,"bumped_at":"2014-07-24T10:56:21.112-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":736,"like_count":71,"has_summary":false,"archetype":"regular","last_poster_username":"judy","category_id":6,"posters":[{"extras":null,"description":"Original Poster","user_id":8},{"extras":null,"description":"Most Posts","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":10778},{"extras":"latest","description":"Most Recent Poster","user_id":6664}]},{"id":17694,"title":"Release schedule post version 1.0","fancy_title":"Release schedule post version 1.0","slug":"release-schedule-post-version-1-0","posts_count":44,"reply_count":35,"highest_post_number":44,"image_url":null,"created_at":"2014-07-17T19:45:21.459-04:00","last_posted_at":"2014-07-23T03:51:03.564-04:00","bumped":true,"bumped_at":"2014-07-29T17:20:06.942-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":466,"like_count":62,"has_summary":false,"archetype":"regular","last_poster_username":"probus","category_id":17,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Most Posts","user_id":10098},{"extras":null,"description":"Frequent Poster","user_id":9775},{"extras":null,"description":"Frequent Poster","user_id":4263},{"extras":"latest","description":"Most Recent Poster","user_id":7948}]},{"id":13287,"title":"Chinese search issues","fancy_title":"Chinese search issues","slug":"chinese-search-issues","posts_count":60,"reply_count":41,"highest_post_number":60,"image_url":"https://f.cloud.github.com/assets/6783175/2296397/3dcabcf8-a09e-11e3-9f5a-2a94d981fced.png","created_at":"2014-03-01T10:12:14.845-05:00","last_posted_at":"2014-07-10T17:03:25.796-04:00","bumped":true,"bumped_at":"2014-07-10T17:03:25.796-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":892,"like_count":25,"has_summary":true,"archetype":"regular","last_poster_username":"sam","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":8820},{"extras":null,"description":"Most Posts","user_id":6746},{"extras":null,"description":"Frequent Poster","user_id":9909},{"extras":null,"description":"Frequent Poster","user_id":8810},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":15336,"title":"Switch from Gravatar to HTML/CSS letters for no-avatar users","fancy_title":"Switch from Gravatar to HTML/CSS letters for no-avatar users","slug":"switch-from-gravatar-to-html-css-letters-for-no-avatar-users","posts_count":39,"reply_count":25,"highest_post_number":39,"image_url":"/uploads/default/_optimized/d29/bc1/25fa89ae0a_415x500.png","created_at":"2014-05-05T18:46:02.221-04:00","last_posted_at":"2014-05-28T18:07:12.448-04:00","bumped":true,"bumped_at":"2014-05-28T18:07:09.701-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":907,"like_count":63,"has_summary":false,"archetype":"regular","last_poster_username":"codinghorror","category_id":26,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":32},{"extras":null,"description":"Most Posts","user_id":9775},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":8571},{"extras":null,"description":"Frequent Poster","user_id":8344}]}]}},"monthly":{"users":[{"id":10886,"username":"Onyx","uploaded_avatar_id":33015,"avatar_template":"/user_avatar/meta.discourse.org/onyx/{size}/33015.png"},{"id":10855,"username":"abarker","uploaded_avatar_id":33014,"avatar_template":"/user_avatar/meta.discourse.org/abarker/{size}/33014.png"},{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png"},{"id":8300,"username":"cpradio","uploaded_avatar_id":4970,"avatar_template":"/user_avatar/meta.discourse.org/cpradio/{size}/4970.png"},{"id":1,"username":"sam","uploaded_avatar_id":5243,"avatar_template":"/user_avatar/meta.discourse.org/sam/{size}/5243.png"},{"id":8,"username":"geek","uploaded_avatar_id":5255,"avatar_template":"/user_avatar/meta.discourse.org/geek/{size}/5255.png"},{"id":10778,"username":"Lid","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/lid/{size}/2.png"},{"id":6664,"username":"judy","uploaded_avatar_id":16281,"avatar_template":"/user_avatar/meta.discourse.org/judy/{size}/16281.png"},{"id":10098,"username":"jwatte","uploaded_avatar_id":20848,"avatar_template":"/user_avatar/meta.discourse.org/jwatte/{size}/20848.png"},{"id":9775,"username":"elberet","uploaded_avatar_id":9789,"avatar_template":"/user_avatar/meta.discourse.org/elberet/{size}/9789.png"},{"id":4263,"username":"mcwumbly","uploaded_avatar_id":9796,"avatar_template":"/user_avatar/meta.discourse.org/mcwumbly/{size}/9796.png"},{"id":7948,"username":"probus","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/probus/{size}/2.png"},{"id":11003,"username":"node","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/node/{size}/2.png"},{"id":11017,"username":"Matches","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/matches/{size}/2.png"},{"id":9536,"username":"nahtnam","uploaded_avatar_id":20077,"avatar_template":"/user_avatar/meta.discourse.org/nahtnam/{size}/20077.png"},{"id":5017,"username":"tuananh","uploaded_avatar_id":32901,"avatar_template":"/user_avatar/meta.discourse.org/tuananh/{size}/32901.png"},{"id":2770,"username":"awesomerobot","uploaded_avatar_id":32393,"avatar_template":"/user_avatar/meta.discourse.org/awesomerobot/{size}/32393.png"},{"id":8617,"username":"Mittineague","uploaded_avatar_id":4462,"avatar_template":"/user_avatar/meta.discourse.org/mittineague/{size}/4462.png"},{"id":7794,"username":"ben_lubar","uploaded_avatar_id":17813,"avatar_template":"/user_avatar/meta.discourse.org/ben_lubar/{size}/17813.png"},{"id":8493,"username":"PJH","uploaded_avatar_id":33082,"avatar_template":"/user_avatar/meta.discourse.org/pjh/{size}/33082.png"},{"id":10860,"username":"fatbull","uploaded_avatar_id":33021,"avatar_template":"/user_avatar/meta.discourse.org/fatbull/{size}/33021.png"},{"id":1995,"username":"zogstrip","uploaded_avatar_id":8630,"avatar_template":"/user_avatar/meta.discourse.org/zogstrip/{size}/8630.png"},{"id":11163,"username":"faoileag","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/faoileag/{size}/2.png"},{"id":11160,"username":"boomzilla","uploaded_avatar_id":33029,"avatar_template":"/user_avatar/meta.discourse.org/boomzilla/{size}/33029.png"},{"id":5351,"username":"erlend_sh","uploaded_avatar_id":9794,"avatar_template":"/user_avatar/meta.discourse.org/erlend_sh/{size}/9794.png"},{"id":3987,"username":"Sander78","uploaded_avatar_id":9787,"avatar_template":"/user_avatar/meta.discourse.org/sander78/{size}/9787.png"},{"id":2,"username":"neil","uploaded_avatar_id":5245,"avatar_template":"/user_avatar/meta.discourse.org/neil/{size}/5245.png"},{"id":5249,"username":"cawas","uploaded_avatar_id":14043,"avatar_template":"/user_avatar/meta.discourse.org/cawas/{size}/14043.png"},{"id":813,"username":"kohenkatz","uploaded_avatar_id":6642,"avatar_template":"/user_avatar/meta.discourse.org/kohenkatz/{size}/6642.png"},{"id":8944,"username":"hunterboerner","uploaded_avatar_id":33072,"avatar_template":"/user_avatar/meta.discourse.org/hunterboerner/{size}/33072.png"},{"id":10990,"username":"Nagesh","uploaded_avatar_id":32736,"avatar_template":"/user_avatar/meta.discourse.org/nagesh/{size}/32736.png"},{"id":471,"username":"BhaelOchon","uploaded_avatar_id":6069,"avatar_template":"/user_avatar/meta.discourse.org/bhaelochon/{size}/6069.png"},{"id":10816,"username":"Alankrit_Choudh","uploaded_avatar_id":32379,"avatar_template":"/user_avatar/meta.discourse.org/alankrit_choudh/{size}/32379.png"},{"id":5707,"username":"trident","uploaded_avatar_id":31178,"avatar_template":"/user_avatar/meta.discourse.org/trident/{size}/31178.png"},{"id":11066,"username":"WolfShade","uploaded_avatar_id":32834,"avatar_template":"/user_avatar/meta.discourse.org/wolfshade/{size}/32834.png"},{"id":8810,"username":"fantasticfears","uploaded_avatar_id":19068,"avatar_template":"/user_avatar/meta.discourse.org/fantasticfears/{size}/19068.png"},{"id":5559,"username":"downey","uploaded_avatar_id":14532,"avatar_template":"/user_avatar/meta.discourse.org/downey/{size}/14532.png"},{"id":8307,"username":"HAWK","uploaded_avatar_id":5159,"avatar_template":"/user_avatar/meta.discourse.org/hawk/{size}/5159.png"},{"id":1323,"username":"InsaneMosquito","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/insanemosquito/{size}/2.png"},{"id":6695,"username":"illspirit","uploaded_avatar_id":32566,"avatar_template":"/user_avatar/meta.discourse.org/illspirit/{size}/32566.png"},{"id":2989,"username":"meglio","uploaded_avatar_id":33005,"avatar_template":"/user_avatar/meta.discourse.org/meglio/{size}/33005.png"}],"topic_list":{"can_create_topic":false,"draft":null,"draft_key":null,"draft_sequence":null,"topics":[{"id":18063,"title":"10k+ posts causes progress bar to show single number","fancy_title":"10k+ posts causes progress bar to show single number","slug":"10k-posts-causes-progress-bar-to-show-single-number","posts_count":67,"reply_count":57,"highest_post_number":70,"image_url":"/uploads/default/_optimized/fdc/03e/3d48765fc4_690x45.png","created_at":"2014-07-25T13:31:34.474-04:00","last_posted_at":"2014-07-26T04:14:18.323-04:00","bumped":true,"bumped_at":"2014-07-26T04:20:54.730-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":259,"like_count":337,"has_summary":true,"archetype":"regular","last_poster_username":"sam","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":10886},{"extras":null,"description":"Most Posts","user_id":10855},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":8300},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":17454,"title":"Spambots from Tor exit points keep taking over my forum","fancy_title":"Spambots from Tor exit points keep taking over my forum","slug":"spambots-from-tor-exit-points-keep-taking-over-my-forum","posts_count":42,"reply_count":26,"highest_post_number":42,"image_url":"/uploads/default/_optimized/b0d/ab3/20401b97ce_690x454.png","created_at":"2014-07-11T03:20:49.433-04:00","last_posted_at":"2014-07-24T10:56:21.112-04:00","bumped":true,"bumped_at":"2014-07-24T10:56:21.112-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":736,"like_count":71,"has_summary":false,"archetype":"regular","last_poster_username":"judy","category_id":6,"posters":[{"extras":null,"description":"Original Poster","user_id":8},{"extras":null,"description":"Most Posts","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":10778},{"extras":"latest","description":"Most Recent Poster","user_id":6664}]},{"id":17694,"title":"Release schedule post version 1.0","fancy_title":"Release schedule post version 1.0","slug":"release-schedule-post-version-1-0","posts_count":44,"reply_count":35,"highest_post_number":44,"image_url":null,"created_at":"2014-07-17T19:45:21.459-04:00","last_posted_at":"2014-07-23T03:51:03.564-04:00","bumped":true,"bumped_at":"2014-07-29T17:20:06.942-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":466,"like_count":62,"has_summary":false,"archetype":"regular","last_poster_username":"probus","category_id":17,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Most Posts","user_id":10098},{"extras":null,"description":"Frequent Poster","user_id":9775},{"extras":null,"description":"Frequent Poster","user_id":4263},{"extras":"latest","description":"Most Recent Poster","user_id":7948}]},{"id":17727,"title":"Compliance with EU Cookie Law","fancy_title":"Compliance with EU Cookie Law","slug":"compliance-with-eu-cookie-law","posts_count":46,"reply_count":32,"highest_post_number":46,"image_url":null,"created_at":"2014-07-18T17:39:38.499-04:00","last_posted_at":"2014-07-26T18:01:33.751-04:00","bumped":true,"bumped_at":"2014-07-26T18:01:33.751-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":739,"like_count":45,"has_summary":false,"archetype":"regular","last_poster_username":"node","category_id":6,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":11003},{"extras":null,"description":"Most Posts","user_id":11017},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":9536}]},{"id":17443,"title":"Can I Keep Nofollow for All User Links, Including from Trust Level 3?","fancy_title":"Can I Keep Nofollow for All User Links, Including from Trust Level 3?","slug":"can-i-keep-nofollow-for-all-user-links-including-from-trust-level-3","posts_count":40,"reply_count":30,"highest_post_number":41,"image_url":null,"created_at":"2014-07-10T22:06:49.357-04:00","last_posted_at":"2014-07-14T19:20:37.014-04:00","bumped":true,"bumped_at":"2014-07-14T19:20:37.014-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":209,"like_count":35,"has_summary":false,"archetype":"regular","last_poster_username":"cpradio","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":8},{"extras":null,"description":"Most Posts","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":5017},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":"latest","description":"Most Recent Poster","user_id":8300}]},{"id":17945,"title":"Unread/new badge style?","fancy_title":"Unread/new badge style?","slug":"unread-new-badge-style","posts_count":35,"reply_count":23,"highest_post_number":35,"image_url":"/uploads/default/_optimized/b61/a61/3508713cc1_690x202.png","created_at":"2014-07-23T10:49:18.864-04:00","last_posted_at":"2014-07-28T13:52:16.773-04:00","bumped":true,"bumped_at":"2014-07-28T13:52:16.773-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":241,"like_count":40,"has_summary":false,"archetype":"regular","last_poster_username":"Mittineague","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":2770},{"extras":null,"description":"Most Posts","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":4263},{"extras":"latest","description":"Most Recent Poster","user_id":8617}]},{"id":17367,"title":"Classes are not being sanitized in cooked markdown","fancy_title":"Classes are not being sanitized in cooked markdown","slug":"classes-are-not-being-sanitized-in-cooked-markdown","posts_count":33,"reply_count":24,"highest_post_number":33,"image_url":null,"created_at":"2014-07-09T04:07:07.021-04:00","last_posted_at":"2014-07-10T19:25:19.670-04:00","bumped":true,"bumped_at":"2014-07-10T16:51:59.746-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":397,"like_count":38,"has_summary":false,"archetype":"regular","last_poster_username":"sam","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":7794},{"extras":null,"description":"Most Posts","user_id":8493},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":10860},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":18175,"title":"Likes Count Inconsistently Low for Topics","fancy_title":"Likes Count Inconsistently Low for Topics","slug":"likes-count-inconsistently-low-for-topics","posts_count":16,"reply_count":9,"highest_post_number":16,"image_url":"/uploads/default/_optimized/b14/e6d/3f83822bfe_690x173.png","created_at":"2014-07-28T14:58:45.757-04:00","last_posted_at":"2014-07-29T00:38:42.954-04:00","bumped":true,"bumped_at":"2014-07-28T16:35:07.490-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":91,"like_count":59,"has_summary":false,"archetype":"regular","last_poster_username":"sam","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":10855},{"extras":null,"description":"Most Posts","user_id":1995},{"extras":null,"description":"Frequent Poster","user_id":11163},{"extras":null,"description":"Frequent Poster","user_id":11160},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":17192,"title":"Proposal: unify category tag with corresponding new tag","fancy_title":"Proposal: unify category tag with corresponding new tag","slug":"proposal-unify-category-tag-with-corresponding-new-tag","posts_count":28,"reply_count":22,"highest_post_number":28,"image_url":"/uploads/default/32383/2071f93b7f1104d2.png","created_at":"2014-07-03T21:52:06.914-04:00","last_posted_at":"2014-07-24T14:06:43.357-04:00","bumped":true,"bumped_at":"2014-07-24T14:06:43.357-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":231,"like_count":32,"has_summary":false,"archetype":"regular","last_poster_username":"awesomerobot","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":10778},{"extras":null,"description":"Most Posts","user_id":8300},{"extras":null,"description":"Frequent Poster","user_id":5351},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":"latest","description":"Most Recent Poster","user_id":2770}]},{"id":17759,"title":"User rank should be a little more forgiving and give more insight","fancy_title":"User rank should be a little more forgiving and give more insight","slug":"user-rank-should-be-a-little-more-forgiving-and-give-more-insight","posts_count":24,"reply_count":18,"highest_post_number":25,"image_url":"/plugins/emoji/images/smile.png","created_at":"2014-07-19T17:12:44.070-04:00","last_posted_at":"2014-07-28T14:00:51.950-04:00","bumped":true,"bumped_at":"2014-07-28T14:00:51.950-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":240,"like_count":38,"has_summary":false,"archetype":"regular","last_poster_username":"neil","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":3987},{"extras":null,"description":"Most Posts","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":9536},{"extras":"latest","description":"Most Recent Poster","user_id":2}]},{"id":17322,"title":"Slow scrolling in Chrome due to replaceState bug","fancy_title":"Slow scrolling in Chrome due to replaceState bug","slug":"slow-scrolling-in-chrome-due-to-replacestate-bug","posts_count":28,"reply_count":19,"highest_post_number":28,"image_url":null,"created_at":"2014-07-07T20:20:42.830-04:00","last_posted_at":"2014-07-20T03:45:13.749-04:00","bumped":true,"bumped_at":"2014-07-17T21:51:23.168-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":539,"like_count":30,"has_summary":false,"archetype":"regular","last_poster_username":"codinghorror","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":10778},{"extras":null,"description":"Most Posts","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":5249},{"extras":null,"description":"Frequent Poster","user_id":813},{"extras":"latest","description":"Most Recent Poster, Frequent Poster","user_id":32}]},{"id":17732,"title":"I dislike pink like","fancy_title":"I dislike pink like","slug":"i-dislike-pink-like","posts_count":33,"reply_count":23,"highest_post_number":34,"image_url":"/uploads/default/32788/b4c2a875e59271a0.png","created_at":"2014-07-18T19:53:28.711-04:00","last_posted_at":"2014-07-22T10:21:39.334-04:00","bumped":true,"bumped_at":"2014-07-22T10:21:39.334-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":291,"like_count":17,"has_summary":false,"archetype":"regular","last_poster_username":"BhaelOchon","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Most Posts","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":8944},{"extras":null,"description":"Frequent Poster","user_id":10990},{"extras":"latest","description":"Most Recent Poster","user_id":471}]},{"id":17347,"title":"Clarifying the flag requirement for trust level 3","fancy_title":"Clarifying the flag requirement for trust level 3","slug":"clarifying-the-flag-requirement-for-trust-level-3","posts_count":28,"reply_count":20,"highest_post_number":28,"image_url":null,"created_at":"2014-07-08T15:41:59.377-04:00","last_posted_at":"2014-07-11T04:54:58.827-04:00","bumped":true,"bumped_at":"2014-07-11T04:54:58.827-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":217,"like_count":11,"has_summary":false,"archetype":"regular","last_poster_username":"sam","category_id":6,"posters":[{"extras":null,"description":"Original Poster","user_id":32},{"extras":null,"description":"Most Posts","user_id":8493},{"extras":null,"description":"Frequent Poster","user_id":8300},{"extras":null,"description":"Frequent Poster","user_id":2},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":17781,"title":"Problem when logging out of discourse and more","fancy_title":"Problem when logging out of discourse and more","slug":"problem-when-logging-out-of-discourse-and-more","posts_count":27,"reply_count":19,"highest_post_number":28,"image_url":"/plugins/emoji/images/frowning.png","created_at":"2014-07-20T15:18:56.716-04:00","last_posted_at":"2014-07-29T02:28:34.534-04:00","bumped":true,"bumped_at":"2014-07-22T18:39:55.996-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":126,"like_count":5,"has_summary":false,"archetype":"regular","last_poster_username":"codinghorror","category_id":6,"posters":[{"extras":null,"description":"Original Poster","user_id":10816},{"extras":null,"description":"Most Posts","user_id":9775},{"extras":null,"description":"Frequent Poster","user_id":5707},{"extras":null,"description":"Frequent Poster","user_id":8300},{"extras":"latest","description":"Most Recent Poster, Frequent Poster","user_id":32}]},{"id":17324,"title":"Mobile viewport not being set correctly so things are \"too small\"","fancy_title":"Mobile viewport not being set correctly so things are “too small”","slug":"mobile-viewport-not-being-set-correctly-so-things-are-too-small","posts_count":18,"reply_count":7,"highest_post_number":18,"image_url":"/uploads/default/_optimized/180/6e3/935b69520e_538x500.png","created_at":"2014-07-08T00:37:05.441-04:00","last_posted_at":"2014-07-11T00:46:38.114-04:00","bumped":true,"bumped_at":"2014-07-11T00:01:34.404-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":128,"like_count":22,"has_summary":false,"archetype":"regular","last_poster_username":"codinghorror","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":4263},{"extras":null,"description":"Most Posts","user_id":2770},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":8},{"extras":"latest","description":"Most Recent Poster, Frequent Poster","user_id":32}]},{"id":17812,"title":"Notifications: disable?","fancy_title":"Notifications: disable?","slug":"notifications-disable","posts_count":25,"reply_count":18,"highest_post_number":25,"image_url":null,"created_at":"2014-07-21T11:50:00.950-04:00","last_posted_at":"2014-07-21T15:40:24.405-04:00","bumped":true,"bumped_at":"2014-07-21T15:40:24.405-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":78,"like_count":6,"has_summary":false,"archetype":"regular","last_poster_username":"WolfShade","category_id":6,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":11066},{"extras":null,"description":"Most Posts","user_id":8300},{"extras":null,"description":"Frequent Poster","user_id":8810},{"extras":null,"description":"Frequent Poster","user_id":9775}]},{"id":17819,"title":"Middle click mouse scrolling behavior is 'wrong'","fancy_title":"Middle click mouse scrolling behavior is ‘wrong’","slug":"middle-click-mouse-scrolling-behavior-is-wrong","posts_count":24,"reply_count":22,"highest_post_number":26,"image_url":null,"created_at":"2014-07-21T14:36:54.930-04:00","last_posted_at":"2014-07-21T23:57:01.271-04:00","bumped":true,"bumped_at":"2014-07-21T23:57:01.271-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":138,"like_count":7,"has_summary":false,"archetype":"regular","last_poster_username":"Matches","category_id":1,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":11017},{"extras":null,"description":"Most Posts","user_id":10778},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":8}]},{"id":17248,"title":"Send Promotion PMs","fancy_title":"Send Promotion PMs","slug":"send-promotion-pms","posts_count":18,"reply_count":11,"highest_post_number":18,"image_url":null,"created_at":"2014-07-05T16:41:01.997-04:00","last_posted_at":"2014-07-07T21:44:58.449-04:00","bumped":true,"bumped_at":"2014-07-07T21:44:58.449-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":136,"like_count":19,"has_summary":false,"archetype":"regular","last_poster_username":"HAWK","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":8300},{"extras":null,"description":"Most Posts","user_id":8617},{"extras":null,"description":"Frequent Poster","user_id":5559},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":"latest","description":"Most Recent Poster","user_id":8307}]},{"id":18282,"title":"Topic Templates","fancy_title":"Topic Templates","slug":"topic-templates","posts_count":17,"reply_count":9,"highest_post_number":17,"image_url":"/plugins/emoji/images/smile.png","created_at":"2014-07-30T14:55:51.778-04:00","last_posted_at":"2014-07-31T17:52:44.101-04:00","bumped":true,"bumped_at":"2014-07-31T17:52:44.101-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":96,"like_count":19,"has_summary":false,"archetype":"regular","last_poster_username":"meglio","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":1323},{"extras":null,"description":"Most Posts","user_id":8300},{"extras":null,"description":"Frequent Poster","user_id":10778},{"extras":null,"description":"Frequent Poster","user_id":6695},{"extras":"latest","description":"Most Recent Poster","user_id":2989}]},{"id":17991,"title":"DoS: Deeply nested kbd's hangs firefox","fancy_title":"DoS: Deeply nested kbd’s hangs firefox","slug":"dos-deeply-nested-kbds-hangs-firefox","posts_count":19,"reply_count":12,"highest_post_number":19,"image_url":null,"created_at":"2014-07-24T06:43:21.486-04:00","last_posted_at":"2014-07-26T12:42:36.483-04:00","bumped":true,"bumped_at":"2014-07-25T11:17:01.089-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":117,"like_count":13,"has_summary":false,"archetype":"regular","last_poster_username":"codinghorror","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":8493},{"extras":null,"description":"Most Posts","user_id":2770},{"extras":null,"description":"Frequent Poster","user_id":9775},{"extras":null,"description":"Frequent Poster","user_id":5017},{"extras":"latest","description":"Most Recent Poster","user_id":32}]}]}},"weekly":{"users":[{"id":10886,"username":"Onyx","uploaded_avatar_id":33015,"avatar_template":"/user_avatar/meta.discourse.org/onyx/{size}/33015.png"},{"id":10855,"username":"abarker","uploaded_avatar_id":33014,"avatar_template":"/user_avatar/meta.discourse.org/abarker/{size}/33014.png"},{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png"},{"id":8300,"username":"cpradio","uploaded_avatar_id":4970,"avatar_template":"/user_avatar/meta.discourse.org/cpradio/{size}/4970.png"},{"id":1,"username":"sam","uploaded_avatar_id":5243,"avatar_template":"/user_avatar/meta.discourse.org/sam/{size}/5243.png"},{"id":1995,"username":"zogstrip","uploaded_avatar_id":8630,"avatar_template":"/user_avatar/meta.discourse.org/zogstrip/{size}/8630.png"},{"id":11163,"username":"faoileag","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/faoileag/{size}/2.png"},{"id":11160,"username":"boomzilla","uploaded_avatar_id":33029,"avatar_template":"/user_avatar/meta.discourse.org/boomzilla/{size}/33029.png"},{"id":1323,"username":"InsaneMosquito","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/insanemosquito/{size}/2.png"},{"id":10778,"username":"Lid","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/lid/{size}/2.png"},{"id":6695,"username":"illspirit","uploaded_avatar_id":32566,"avatar_template":"/user_avatar/meta.discourse.org/illspirit/{size}/32566.png"},{"id":2989,"username":"meglio","uploaded_avatar_id":33005,"avatar_template":"/user_avatar/meta.discourse.org/meglio/{size}/33005.png"},{"id":4263,"username":"mcwumbly","uploaded_avatar_id":9796,"avatar_template":"/user_avatar/meta.discourse.org/mcwumbly/{size}/9796.png"},{"id":11162,"username":"darkmatter","uploaded_avatar_id":33045,"avatar_template":"/user_avatar/meta.discourse.org/darkmatter/{size}/33045.png"},{"id":2770,"username":"awesomerobot","uploaded_avatar_id":32393,"avatar_template":"/user_avatar/meta.discourse.org/awesomerobot/{size}/32393.png"},{"id":5351,"username":"erlend_sh","uploaded_avatar_id":9794,"avatar_template":"/user_avatar/meta.discourse.org/erlend_sh/{size}/9794.png"},{"id":7948,"username":"probus","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/probus/{size}/2.png"},{"id":9775,"username":"elberet","uploaded_avatar_id":9789,"avatar_template":"/user_avatar/meta.discourse.org/elberet/{size}/9789.png"},{"id":10968,"username":"Maciejasjmj","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/maciejasjmj/{size}/2.png"},{"id":11084,"username":"jgehrcke","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/jgehrcke/{size}/2.png"},{"id":8493,"username":"PJH","uploaded_avatar_id":33082,"avatar_template":"/user_avatar/meta.discourse.org/pjh/{size}/33082.png"},{"id":19,"username":"eviltrout","uploaded_avatar_id":5275,"avatar_template":"/user_avatar/meta.discourse.org/eviltrout/{size}/5275.png"},{"id":471,"username":"BhaelOchon","uploaded_avatar_id":6069,"avatar_template":"/user_avatar/meta.discourse.org/bhaelochon/{size}/6069.png"},{"id":2,"username":"neil","uploaded_avatar_id":5245,"avatar_template":"/user_avatar/meta.discourse.org/neil/{size}/5245.png"},{"id":8492,"username":"Onaldan","uploaded_avatar_id":18651,"avatar_template":"/user_avatar/meta.discourse.org/onaldan/{size}/18651.png"},{"id":8810,"username":"fantasticfears","uploaded_avatar_id":19068,"avatar_template":"/user_avatar/meta.discourse.org/fantasticfears/{size}/19068.png"},{"id":8222,"username":"techAPJ","uploaded_avatar_id":3281,"avatar_template":"/user_avatar/meta.discourse.org/techapj/{size}/3281.png"},{"id":4939,"username":"stevebaer","uploaded_avatar_id":13523,"avatar_template":"/user_avatar/meta.discourse.org/stevebaer/{size}/13523.png"},{"id":8617,"username":"Mittineague","uploaded_avatar_id":4462,"avatar_template":"/user_avatar/meta.discourse.org/mittineague/{size}/4462.png"},{"id":11039,"username":"heinrich5991","uploaded_avatar_id":32815,"avatar_template":"/user_avatar/meta.discourse.org/heinrich5991/{size}/32815.png"},{"id":11265,"username":"cipher1","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/cipher1/{size}/2.png"},{"id":8944,"username":"hunterboerner","uploaded_avatar_id":33072,"avatar_template":"/user_avatar/meta.discourse.org/hunterboerner/{size}/33072.png"},{"id":3,"username":"supermathie","uploaded_avatar_id":5247,"avatar_template":"/user_avatar/meta.discourse.org/supermathie/{size}/5247.png"},{"id":11209,"username":"icaroperseo","uploaded_avatar_id":33076,"avatar_template":"/user_avatar/meta.discourse.org/icaroperseo/{size}/33076.png"},{"id":11017,"username":"Matches","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/matches/{size}/2.png"},{"id":9215,"username":"Naatan","uploaded_avatar_id":19636,"avatar_template":"/user_avatar/meta.discourse.org/naatan/{size}/19636.png"},{"id":11269,"username":"Defman","uploaded_avatar_id":33156,"avatar_template":"/user_avatar/meta.discourse.org/defman/{size}/33156.png"},{"id":10185,"username":"davestephens","uploaded_avatar_id":20954,"avatar_template":"/user_avatar/meta.discourse.org/davestephens/{size}/20954.png"},{"id":4457,"username":"Lee_Ars","uploaded_avatar_id":1597,"avatar_template":"/user_avatar/meta.discourse.org/lee_ars/{size}/1597.png"},{"id":6626,"username":"riking","uploaded_avatar_id":9779,"avatar_template":"/user_avatar/meta.discourse.org/riking/{size}/9779.png"},{"id":11254,"username":"benito_m","uploaded_avatar_id":33132,"avatar_template":"/user_avatar/meta.discourse.org/benito_m/{size}/33132.png"},{"id":10707,"username":"spocksplanet","uploaded_avatar_id":32228,"avatar_template":"/user_avatar/meta.discourse.org/spocksplanet/{size}/32228.png"},{"id":9931,"username":"Frank","uploaded_avatar_id":32861,"avatar_template":"/user_avatar/meta.discourse.org/frank/{size}/32861.png"},{"id":4881,"username":"gerhard","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/gerhard/{size}/2.png"},{"id":7803,"username":"caffeine","uploaded_avatar_id":17828,"avatar_template":"/user_avatar/meta.discourse.org/caffeine/{size}/17828.png"},{"id":10892,"username":"jeffreycwitt","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/jeffreycwitt/{size}/2.png"},{"id":4775,"username":"ediblemonkey","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/ediblemonkey/{size}/2.png"},{"id":11179,"username":"nichtsistwahr","uploaded_avatar_id":33030,"avatar_template":"/user_avatar/meta.discourse.org/nichtsistwahr/{size}/33030.png"}],"topic_list":{"can_create_topic":false,"draft":null,"draft_key":null,"draft_sequence":null,"topics":[{"id":18063,"title":"10k+ posts causes progress bar to show single number","fancy_title":"10k+ posts causes progress bar to show single number","slug":"10k-posts-causes-progress-bar-to-show-single-number","posts_count":67,"reply_count":57,"highest_post_number":70,"image_url":"/uploads/default/_optimized/fdc/03e/3d48765fc4_690x45.png","created_at":"2014-07-25T13:31:34.474-04:00","last_posted_at":"2014-07-26T04:14:18.323-04:00","bumped":true,"bumped_at":"2014-07-26T04:20:54.730-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":259,"like_count":337,"has_summary":true,"archetype":"regular","last_poster_username":"sam","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":10886},{"extras":null,"description":"Most Posts","user_id":10855},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":8300},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":18175,"title":"Likes Count Inconsistently Low for Topics","fancy_title":"Likes Count Inconsistently Low for Topics","slug":"likes-count-inconsistently-low-for-topics","posts_count":16,"reply_count":9,"highest_post_number":16,"image_url":"/uploads/default/_optimized/b14/e6d/3f83822bfe_690x173.png","created_at":"2014-07-28T14:58:45.757-04:00","last_posted_at":"2014-07-29T00:38:42.954-04:00","bumped":true,"bumped_at":"2014-07-28T16:35:07.490-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":91,"like_count":59,"has_summary":false,"archetype":"regular","last_poster_username":"sam","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":10855},{"extras":null,"description":"Most Posts","user_id":1995},{"extras":null,"description":"Frequent Poster","user_id":11163},{"extras":null,"description":"Frequent Poster","user_id":11160},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":18282,"title":"Topic Templates","fancy_title":"Topic Templates","slug":"topic-templates","posts_count":17,"reply_count":9,"highest_post_number":17,"image_url":"/plugins/emoji/images/smile.png","created_at":"2014-07-30T14:55:51.778-04:00","last_posted_at":"2014-07-31T17:52:44.101-04:00","bumped":true,"bumped_at":"2014-07-31T17:52:44.101-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":96,"like_count":19,"has_summary":false,"archetype":"regular","last_poster_username":"meglio","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":1323},{"extras":null,"description":"Most Posts","user_id":8300},{"extras":null,"description":"Frequent Poster","user_id":10778},{"extras":null,"description":"Frequent Poster","user_id":6695},{"extras":"latest","description":"Most Recent Poster","user_id":2989}]},{"id":18209,"title":"Gray and blue unread counters on topics","fancy_title":"Gray and blue unread counters on topics","slug":"gray-and-blue-unread-counters-on-topics","posts_count":13,"reply_count":7,"highest_post_number":13,"image_url":"/uploads/default/_optimized/b2c/4e4/27af2f0eaf_690x48.png","created_at":"2014-07-29T10:01:33.740-04:00","last_posted_at":"2014-07-30T10:32:20.395-04:00","bumped":true,"bumped_at":"2014-07-30T11:54:44.716-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":87,"like_count":22,"has_summary":false,"archetype":"regular","last_poster_username":"mcwumbly","category_id":9,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":4263},{"extras":null,"description":"Most Posts","user_id":11162},{"extras":null,"description":"Frequent Poster","user_id":2770},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":5351}]},{"id":18257,"title":"Move the new/unread counters to the first column in topic list","fancy_title":"Move the new/unread counters to the first column in topic list","slug":"move-the-new-unread-counters-to-the-first-column-in-topic-list","posts_count":12,"reply_count":9,"highest_post_number":12,"image_url":null,"created_at":"2014-07-30T02:33:42.679-04:00","last_posted_at":"2014-07-31T02:20:22.866-04:00","bumped":true,"bumped_at":"2014-07-31T02:20:22.866-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":71,"like_count":21,"has_summary":false,"archetype":"regular","last_poster_username":"Maciejasjmj","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":7948},{"extras":null,"description":"Most Posts","user_id":9775},{"extras":null,"description":"Frequent Poster","user_id":4263},{"extras":null,"description":"Frequent Poster","user_id":2770},{"extras":"latest","description":"Most Recent Poster","user_id":10968}]},{"id":18211,"title":"Latest revision: \":( Error while trying to load Something went wrong.\"","fancy_title":"Latest revision: “:( Error while trying to load Something went wrong.”","slug":"latest-revision-error-while-trying-to-load-something-went-wrong","posts_count":18,"reply_count":13,"highest_post_number":18,"image_url":null,"created_at":"2014-07-29T10:14:13.804-04:00","last_posted_at":"2014-07-29T21:15:40.366-04:00","bumped":true,"bumped_at":"2014-07-29T20:09:58.973-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":70,"like_count":10,"has_summary":false,"archetype":"regular","last_poster_username":"sam","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":11084},{"extras":null,"description":"Most Posts","user_id":8493},{"extras":null,"description":"Frequent Poster","user_id":19},{"extras":null,"description":"Frequent Poster","user_id":10886},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":18166,"title":"Ability to unroll context when in-reply-to indicator is suppressed?","fancy_title":"Ability to unroll context when in-reply-to indicator is suppressed?","slug":"ability-to-unroll-context-when-in-reply-to-indicator-is-suppressed","posts_count":17,"reply_count":14,"highest_post_number":17,"image_url":null,"created_at":"2014-07-28T09:54:27.014-04:00","last_posted_at":"2014-07-30T01:22:14.958-04:00","bumped":true,"bumped_at":"2014-07-30T01:22:14.958-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":78,"like_count":9,"has_summary":false,"archetype":"regular","last_poster_username":"Maciejasjmj","category_id":2,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":10968},{"extras":null,"description":"Most Posts","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":471},{"extras":null,"description":"Frequent Poster","user_id":7948}]},{"id":18075,"title":"Translations for v1.0","fancy_title":"Translations for v1.0","slug":"translations-for-v1-0","posts_count":13,"reply_count":6,"highest_post_number":13,"image_url":null,"created_at":"2014-07-25T17:11:36.676-04:00","last_posted_at":"2014-07-28T13:01:35.559-04:00","bumped":true,"bumped_at":"2014-07-28T13:01:35.559-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":374,"like_count":15,"has_summary":false,"archetype":"regular","last_poster_username":"fantasticfears","category_id":27,"posters":[{"extras":null,"description":"Original Poster","user_id":2},{"extras":null,"description":"Most Posts","user_id":2989},{"extras":null,"description":"Frequent Poster","user_id":8492},{"extras":null,"description":"Frequent Poster","user_id":7948},{"extras":"latest","description":"Most Recent Poster","user_id":8810}]},{"id":18106,"title":"Remove Invite Spam Protection?","fancy_title":"Remove Invite Spam Protection?","slug":"remove-invite-spam-protection","posts_count":14,"reply_count":11,"highest_post_number":15,"image_url":null,"created_at":"2014-07-26T12:50:02.896-04:00","last_posted_at":"2014-07-29T19:32:20.224-04:00","bumped":true,"bumped_at":"2014-07-29T19:32:20.224-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":89,"like_count":13,"has_summary":false,"archetype":"regular","last_poster_username":"mcwumbly","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":8222},{"extras":null,"description":"Most Posts","user_id":4939},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":8617},{"extras":"latest","description":"Most Recent Poster","user_id":4263}]},{"id":18269,"title":"JS-less user interface","fancy_title":"JS-less user interface","slug":"js-less-user-interface","posts_count":17,"reply_count":12,"highest_post_number":17,"image_url":null,"created_at":"2014-07-30T07:39:20.275-04:00","last_posted_at":"2014-07-30T20:14:28.469-04:00","bumped":true,"bumped_at":"2014-07-30T20:10:07.347-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":137,"like_count":12,"has_summary":false,"archetype":"regular","last_poster_username":"eviltrout","category_id":7,"posters":[{"extras":null,"description":"Original Poster","user_id":11039},{"extras":null,"description":"Frequent Poster","user_id":11265},{"extras":null,"description":"Frequent Poster","user_id":8944},{"extras":null,"description":"Frequent Poster","user_id":3},{"extras":"latest","description":"Most Recent Poster, Most Posts","user_id":19}]},{"id":18183,"title":"Docker/Discourse upgrade issues","fancy_title":"Docker/Discourse upgrade issues","slug":"docker-discourse-upgrade-issues","posts_count":16,"reply_count":8,"highest_post_number":17,"image_url":null,"created_at":"2014-07-28T17:59:25.107-04:00","last_posted_at":"2014-07-30T10:59:16.971-04:00","bumped":true,"bumped_at":"2014-07-30T10:59:16.971-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":124,"like_count":2,"has_summary":false,"archetype":"regular","last_poster_username":"icaroperseo","category_id":6,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":11209},{"extras":null,"description":"Most Posts","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":2}]},{"id":18226,"title":"What does \"Defer\" mean?","fancy_title":"What does “Defer” mean?","slug":"what-does-defer-mean","posts_count":14,"reply_count":9,"highest_post_number":14,"image_url":null,"created_at":"2014-07-29T13:17:19.574-04:00","last_posted_at":"2014-07-30T04:02:41.860-04:00","bumped":true,"bumped_at":"2014-07-30T04:02:41.860-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":82,"like_count":5,"has_summary":false,"archetype":"regular","last_poster_username":"meglio","category_id":27,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":2989},{"extras":null,"description":"Most Posts","user_id":11017},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":9775}]},{"id":18321,"title":"User with no mod is able to move topics","fancy_title":"User with no mod is able to move topics","slug":"user-with-no-mod-is-able-to-move-topics","posts_count":12,"reply_count":9,"highest_post_number":12,"image_url":null,"created_at":"2014-07-31T10:34:32.436-04:00","last_posted_at":"2014-07-31T15:56:17.419-04:00","bumped":true,"bumped_at":"2014-07-31T15:56:17.419-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":43,"like_count":7,"has_summary":false,"archetype":"regular","last_poster_username":"Naatan","category_id":6,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":9215},{"extras":null,"description":"Most Posts","user_id":8300},{"extras":null,"description":"Frequent Poster","user_id":19},{"extras":null,"description":"Frequent Poster","user_id":8617},{"extras":null,"description":"Frequent Poster","user_id":11269}]},{"id":18074,"title":"How to change FAQ, Privacy Policy, and Terms of Service","fancy_title":"How to change FAQ, Privacy Policy, and Terms of Service","slug":"how-to-change-faq-privacy-policy-and-terms-of-service","posts_count":8,"reply_count":3,"highest_post_number":8,"image_url":"/uploads/default/33006/853e857ac75d52a8.png","created_at":"2014-07-25T17:05:24.651-04:00","last_posted_at":"2014-07-29T11:29:52.195-04:00","bumped":true,"bumped_at":"2014-07-29T11:29:52.195-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":134,"like_count":12,"has_summary":false,"archetype":"regular","last_poster_username":"neil","category_id":10,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":2},{"extras":null,"description":"Most Posts","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":10185},{"extras":null,"description":"Frequent Poster","user_id":7948},{"extras":null,"description":"Frequent Poster","user_id":4457}]},{"id":18071,"title":"Setting minimum username length to 4 with a 3 char username breaks the site","fancy_title":"Setting minimum username length to 4 with a 3 char username breaks the site","slug":"setting-minimum-username-length-to-4-with-a-3-char-username-breaks-the-site","posts_count":11,"reply_count":7,"highest_post_number":11,"image_url":"http://imagizer.imageshack.com/img539/9936/6gCxhW.png","created_at":"2014-07-25T16:11:20.125-04:00","last_posted_at":"2014-07-25T17:52:54.554-04:00","bumped":true,"bumped_at":"2014-07-25T17:52:54.554-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":79,"like_count":5,"has_summary":false,"archetype":"regular","last_poster_username":"meglio","category_id":1,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":2989},{"extras":null,"description":"Most Posts","user_id":6626},{"extras":null,"description":"Frequent Poster","user_id":19},{"extras":null,"description":"Frequent Poster","user_id":32}]},{"id":18275,"title":"I can't get the emoji images to display in my Discourse site","fancy_title":"I can’t get the emoji images to display in my Discourse site","slug":"i-cant-get-the-emoji-images-to-display-in-my-discourse-site","posts_count":13,"reply_count":8,"highest_post_number":13,"image_url":null,"created_at":"2014-07-30T10:26:51.629-04:00","last_posted_at":"2014-07-31T17:23:15.900-04:00","bumped":true,"bumped_at":"2014-07-31T17:33:08.360-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":66,"like_count":1,"has_summary":false,"archetype":"regular","last_poster_username":"elberet","category_id":6,"posters":[{"extras":null,"description":"Original Poster","user_id":11254},{"extras":null,"description":"Most Posts","user_id":19},{"extras":null,"description":"Frequent Poster","user_id":8617},{"extras":"latest","description":"Most Recent Poster","user_id":9775}]},{"id":18158,"title":"How to create 1000 subcategories in a scalable way?","fancy_title":"How to create 1000 subcategories in a scalable way?","slug":"how-to-create-1000-subcategories-in-a-scalable-way","posts_count":11,"reply_count":8,"highest_post_number":11,"image_url":null,"created_at":"2014-07-27T23:38:06.845-04:00","last_posted_at":"2014-07-28T12:49:36.989-04:00","bumped":true,"bumped_at":"2014-07-28T12:49:36.989-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":117,"like_count":4,"has_summary":false,"archetype":"regular","last_poster_username":"spocksplanet","category_id":2,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":10707},{"extras":null,"description":"Most Posts","user_id":8300},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":9931}]},{"id":18151,"title":"German translation","fancy_title":"German translation","slug":"german-translation","posts_count":9,"reply_count":3,"highest_post_number":9,"image_url":"/plugins/emoji/images/wink.png","created_at":"2014-07-27T18:49:11.192-04:00","last_posted_at":"2014-07-30T04:29:32.398-04:00","bumped":true,"bumped_at":"2014-07-30T04:29:32.398-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":79,"like_count":5,"has_summary":false,"archetype":"regular","last_poster_username":"caffeine","category_id":27,"posters":[{"extras":null,"description":"Original Poster","user_id":4881},{"extras":null,"description":"Most Posts","user_id":9775},{"extras":"latest","description":"Most Recent Poster","user_id":7803}]},{"id":18108,"title":"Login broken in latest revision","fancy_title":"Login broken in latest revision","slug":"login-broken-in-latest-revision","posts_count":8,"reply_count":3,"highest_post_number":8,"image_url":null,"created_at":"2014-07-26T13:18:29.571-04:00","last_posted_at":"2014-07-27T05:18:55.566-04:00","bumped":true,"bumped_at":"2014-07-26T17:32:38.186-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":73,"like_count":8,"has_summary":false,"archetype":"regular","last_poster_username":"zogstrip","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":11084},{"extras":null,"description":"Frequent Poster","user_id":10892},{"extras":"latest","description":"Most Recent Poster, Most Posts","user_id":1995}]},{"id":18096,"title":"0.9.9.13 - Moderators cannot see Users Admin page","fancy_title":"0.9.9.13 - Moderators cannot see Users Admin page","slug":"0-9-9-13-moderators-cannot-see-users-admin-page","posts_count":9,"reply_count":3,"highest_post_number":9,"image_url":null,"created_at":"2014-07-26T05:25:59.853-04:00","last_posted_at":"2014-07-27T22:07:02.465-04:00","bumped":true,"bumped_at":"2014-07-27T22:06:59.846-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":98,"like_count":5,"has_summary":false,"archetype":"regular","last_poster_username":"sam","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":4775},{"extras":null,"description":"Most Posts","user_id":9931},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":11179},{"extras":"latest","description":"Most Recent Poster","user_id":1}]}]}},"daily":{"users":[{"id":9215,"username":"Naatan","uploaded_avatar_id":19636,"avatar_template":"/user_avatar/meta.discourse.org/naatan/{size}/19636.png"},{"id":8300,"username":"cpradio","uploaded_avatar_id":4970,"avatar_template":"/user_avatar/meta.discourse.org/cpradio/{size}/4970.png"},{"id":19,"username":"eviltrout","uploaded_avatar_id":5275,"avatar_template":"/user_avatar/meta.discourse.org/eviltrout/{size}/5275.png"},{"id":8617,"username":"Mittineague","uploaded_avatar_id":4462,"avatar_template":"/user_avatar/meta.discourse.org/mittineague/{size}/4462.png"},{"id":11269,"username":"Defman","uploaded_avatar_id":33156,"avatar_template":"/user_avatar/meta.discourse.org/defman/{size}/33156.png"},{"id":10995,"username":"resurrectedstar","uploaded_avatar_id":32742,"avatar_template":"/user_avatar/meta.discourse.org/resurrectedstar/{size}/32742.png"},{"id":1995,"username":"zogstrip","uploaded_avatar_id":8630,"avatar_template":"/user_avatar/meta.discourse.org/zogstrip/{size}/8630.png"},{"id":9775,"username":"elberet","uploaded_avatar_id":9789,"avatar_template":"/user_avatar/meta.discourse.org/elberet/{size}/9789.png"},{"id":471,"username":"BhaelOchon","uploaded_avatar_id":6069,"avatar_template":"/user_avatar/meta.discourse.org/bhaelochon/{size}/6069.png"},{"id":10395,"username":"michael28","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/michael28/{size}/2.png"},{"id":1,"username":"sam","uploaded_avatar_id":5243,"avatar_template":"/user_avatar/meta.discourse.org/sam/{size}/5243.png"},{"id":7717,"username":"lake54","uploaded_avatar_id":17722,"avatar_template":"/user_avatar/meta.discourse.org/lake54/{size}/17722.png"},{"id":4881,"username":"gerhard","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/gerhard/{size}/2.png"},{"id":2,"username":"neil","uploaded_avatar_id":5245,"avatar_template":"/user_avatar/meta.discourse.org/neil/{size}/5245.png"},{"id":2989,"username":"meglio","uploaded_avatar_id":33005,"avatar_template":"/user_avatar/meta.discourse.org/meglio/{size}/33005.png"},{"id":5017,"username":"tuananh","uploaded_avatar_id":32901,"avatar_template":"/user_avatar/meta.discourse.org/tuananh/{size}/32901.png"},{"id":11059,"username":"trilson","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/trilson/{size}/2.png"},{"id":2770,"username":"awesomerobot","uploaded_avatar_id":32393,"avatar_template":"/user_avatar/meta.discourse.org/awesomerobot/{size}/32393.png"},{"id":6828,"username":"CamilleRoux","uploaded_avatar_id":16538,"avatar_template":"/user_avatar/meta.discourse.org/camilleroux/{size}/16538.png"},{"id":11267,"username":"tvvocold","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/tvvocold/{size}/2.png"}],"topic_list":{"can_create_topic":false,"draft":null,"draft_key":null,"draft_sequence":null,"topics":[{"id":18321,"title":"User with no mod is able to move topics","fancy_title":"User with no mod is able to move topics","slug":"user-with-no-mod-is-able-to-move-topics","posts_count":12,"reply_count":9,"highest_post_number":12,"image_url":null,"created_at":"2014-07-31T10:34:32.436-04:00","last_posted_at":"2014-07-31T15:56:17.419-04:00","bumped":true,"bumped_at":"2014-07-31T15:56:17.419-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":43,"like_count":7,"has_summary":false,"archetype":"regular","last_poster_username":"Naatan","category_id":6,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":9215},{"extras":null,"description":"Most Posts","user_id":8300},{"extras":null,"description":"Frequent Poster","user_id":19},{"extras":null,"description":"Frequent Poster","user_id":8617},{"extras":null,"description":"Frequent Poster","user_id":11269}]},{"id":18320,"title":"Rebuild Failing","fancy_title":"Rebuild Failing","slug":"rebuild-failing","posts_count":6,"reply_count":4,"highest_post_number":6,"image_url":null,"created_at":"2014-07-31T10:13:09.561-04:00","last_posted_at":"2014-07-31T15:52:31.831-04:00","bumped":true,"bumped_at":"2014-07-31T15:52:31.831-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":26,"like_count":1,"has_summary":false,"archetype":"regular","last_poster_username":"elberet","category_id":6,"posters":[{"extras":null,"description":"Original Poster","user_id":10995},{"extras":null,"description":"Most Posts","user_id":1995},{"extras":"latest","description":"Most Recent Poster","user_id":9775}]},{"id":18322,"title":"Odd white space appearing on the side of the forum","fancy_title":"Odd white space appearing on the side of the forum","slug":"odd-white-space-appearing-on-the-side-of-the-forum","posts_count":5,"reply_count":1,"highest_post_number":5,"image_url":null,"created_at":"2014-07-31T12:55:09.188-04:00","last_posted_at":"2014-07-31T16:36:10.134-04:00","bumped":true,"bumped_at":"2014-07-31T16:36:10.134-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":34,"like_count":2,"has_summary":false,"archetype":"regular","last_poster_username":"resurrectedstar","category_id":6,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":10995},{"extras":null,"description":"Most Posts","user_id":471},{"extras":null,"description":"Frequent Poster","user_id":8300}]},{"id":18306,"title":"Unable to edit type or delete badges created programatically","fancy_title":"Unable to edit type or delete badges created programatically","slug":"unable-to-edit-type-or-delete-badges-created-programatically","posts_count":5,"reply_count":3,"highest_post_number":5,"image_url":null,"created_at":"2014-07-31T03:06:51.929-04:00","last_posted_at":"2014-07-31T04:34:50.642-04:00","bumped":true,"bumped_at":"2014-07-31T04:34:50.642-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":27,"like_count":2,"has_summary":false,"archetype":"regular","last_poster_username":"michael28","category_id":7,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":10395},{"extras":null,"description":"Most Posts","user_id":1}]},{"id":18316,"title":"Prevent Badge IDs of less than 100?","fancy_title":"Prevent Badge IDs of less than 100?","slug":"prevent-badge-ids-of-less-than-100","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":null,"created_at":"2014-07-31T07:25:19.231-04:00","last_posted_at":"2014-07-31T16:37:45.456-04:00","bumped":true,"bumped_at":"2014-07-31T16:37:45.456-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":33,"like_count":2,"has_summary":false,"archetype":"regular","last_poster_username":"lake54","category_id":6,"posters":[{"extras":null,"description":"Original Poster","user_id":8300},{"extras":null,"description":"Most Posts","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":10395},{"extras":"latest","description":"Most Recent Poster","user_id":7717}]},{"id":18302,"title":"Why does Discourse not use Web Sockets","fancy_title":"Why does Discourse not use Web Sockets","slug":"why-does-discourse-not-use-web-sockets","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2014-07-30T20:06:00.863-04:00","last_posted_at":"2014-07-30T20:06:00.950-04:00","bumped":true,"bumped_at":"2014-07-30T22:30:26.737-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":70,"like_count":7,"has_summary":false,"archetype":"regular","last_poster_username":"sam","category_id":7,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":1}]},{"id":18304,"title":"min_post_length is not enforced on the server for admins","fancy_title":"min_post_length is not enforced on the server for admins","slug":"min-post-length-is-not-enforced-on-the-server-for-admins","posts_count":4,"reply_count":0,"highest_post_number":4,"image_url":null,"created_at":"2014-07-30T23:36:46.421-04:00","last_posted_at":"2014-07-31T11:24:42.930-04:00","bumped":true,"bumped_at":"2014-07-31T11:24:42.930-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":34,"like_count":1,"has_summary":false,"archetype":"regular","last_poster_username":"eviltrout","category_id":6,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":"latest","description":"Most Recent Poster","user_id":19}]},{"id":18328,"title":"How to translate strings with key \"js.notifications...\"","fancy_title":"How to translate strings with key “js.notifications…”","slug":"how-to-translate-strings-with-key-js-notifications","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":null,"created_at":"2014-07-31T15:32:17.941-04:00","last_posted_at":"2014-07-31T15:54:17.749-04:00","bumped":true,"bumped_at":"2014-07-31T15:54:17.749-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":16,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"gerhard","category_id":27,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":4881},{"extras":null,"description":"Most Posts","user_id":2},{"extras":null,"description":"Frequent Poster","user_id":9775}]},{"id":18310,"title":"An exception in JS file: Uncaught exception: TypeError: Cannot convert 'e' to object [v.0.9.9.14]","fancy_title":"An exception in JS file: Uncaught exception: TypeError: Cannot convert ‘e’ to object [v.0.9.9.14]","slug":"an-exception-in-js-file-uncaught-exception-typeerror-cannot-convert-e-to-object-v-0-9-9-14","posts_count":3,"reply_count":1,"highest_post_number":3,"image_url":null,"created_at":"2014-07-31T05:06:39.739-04:00","last_posted_at":"2014-07-31T05:38:21.559-04:00","bumped":true,"bumped_at":"2014-07-31T05:38:21.559-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":21,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"meglio","category_id":6,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":2989},{"extras":null,"description":"Most Posts","user_id":1}]},{"id":18303,"title":"Is it possible running discourse on a domain like example.com/forum/?","fancy_title":"Is it possible running discourse on a domain like example.com/forum/?","slug":"is-it-possible-running-discourse-on-a-domain-like-example-com-forum","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2014-07-30T21:25:45.386-04:00","last_posted_at":"2014-07-30T21:39:18.052-04:00","bumped":true,"bumped_at":"2014-07-30T21:39:18.052-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":51,"like_count":2,"has_summary":false,"archetype":"regular","last_poster_username":"sam","category_id":6,"posters":[{"extras":null,"description":"Original Poster","user_id":5017},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":18307,"title":"Discourse schema - is there an ER diagram available anywhere?","fancy_title":"Discourse schema - is there an ER diagram available anywhere?","slug":"discourse-schema-is-there-an-er-diagram-available-anywhere","posts_count":3,"reply_count":0,"highest_post_number":3,"image_url":null,"created_at":"2014-07-31T04:32:13.202-04:00","last_posted_at":"2014-07-31T11:28:47.789-04:00","bumped":true,"bumped_at":"2014-07-31T11:28:39.795-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":42,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"eviltrout","category_id":7,"posters":[{"extras":null,"description":"Original Poster","user_id":11059},{"extras":"latest","description":"Most Recent Poster","user_id":19}]},{"id":18312,"title":"Full-height categories dropdown could simplify category selection - even if there are less than 10 ones","fancy_title":"Full-height categories dropdown could simplify category selection - even if there are less than 10 ones","slug":"full-height-categories-dropdown-could-simplify-category-selection-even-if-there-are-less-than-10-ones","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":"http://imagizer.imageshack.com/img903/7131/DK5IBc.png","created_at":"2014-07-31T05:19:54.618-04:00","last_posted_at":"2014-07-31T09:32:22.308-04:00","bumped":true,"bumped_at":"2014-07-31T09:54:54.768-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":27,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"BhaelOchon","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":2989},{"extras":"latest","description":"Most Recent Poster","user_id":471}]},{"id":18332,"title":"Programmatically adjusting color variables with SASS","fancy_title":"Programmatically adjusting color variables with SASS","slug":"programmatically-adjusting-color-variables-with-sass","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":"/plugins/emoji/images/thumbsup.png","created_at":"2014-07-31T17:13:38.844-04:00","last_posted_at":"2014-07-31T17:13:39.108-04:00","bumped":true,"bumped_at":"2014-07-31T17:40:52.980-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":26,"like_count":3,"has_summary":false,"archetype":"regular","last_poster_username":"awesomerobot","category_id":9,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":2770}]},{"id":18308,"title":"Fixed category positions option does not affect the dropdown categories list [v.0.9.9.14]","fancy_title":"Fixed category positions option does not affect the dropdown categories list [v.0.9.9.14]","slug":"fixed-category-positions-option-does-not-affect-the-dropdown-categories-list-v-0-9-9-14","posts_count":3,"reply_count":0,"highest_post_number":3,"image_url":null,"created_at":"2014-07-31T04:56:15.781-04:00","last_posted_at":"2014-07-31T07:36:16.252-04:00","bumped":true,"bumped_at":"2014-07-31T07:27:02.458-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":25,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"sam","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":2989},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":18301,"title":"Wrong localisation in Twitter one box","fancy_title":"Wrong localisation in Twitter one box","slug":"wrong-localisation-in-twitter-one-box","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":"/uploads/default/_optimized/1be/6ca/1606b1d3cf_690x332.png","created_at":"2014-07-30T19:26:50.665-04:00","last_posted_at":"2014-07-30T19:26:50.752-04:00","bumped":true,"bumped_at":"2014-07-30T19:26:50.752-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":25,"like_count":3,"has_summary":false,"archetype":"regular","last_poster_username":"CamilleRoux","category_id":27,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":6828}]},{"id":18327,"title":"Missing key in English locale","fancy_title":"Missing key in English locale","slug":"missing-key-in-english-locale","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2014-07-31T15:23:47.980-04:00","last_posted_at":"2014-07-31T16:42:07.009-04:00","bumped":true,"bumped_at":"2014-07-31T16:42:07.009-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":20,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"neil","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":4881},{"extras":"latest","description":"Most Recent Poster","user_id":2}]},{"id":18330,"title":"Find user by external ID (SSO)","fancy_title":"Find user by external ID (SSO)","slug":"find-user-by-external-id-sso","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2014-07-31T16:06:26.014-04:00","last_posted_at":"2014-07-31T16:10:49.605-04:00","bumped":true,"bumped_at":"2014-07-31T16:10:49.605-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":15,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"michael28","category_id":2,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":10395}]},{"id":18313,"title":"Cannot install discource in windows azure","fancy_title":"Cannot install discource in windows azure","slug":"cannot-install-discource-in-windows-azure","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2014-07-31T05:22:59.776-04:00","last_posted_at":"2014-07-31T05:22:59.863-04:00","bumped":true,"bumped_at":"2014-07-31T05:22:59.863-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":27,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"tvvocold","category_id":6,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":11267}]},{"id":18299,"title":"Dropdown in Profile is a little bit too small","fancy_title":"Dropdown in Profile is a little bit too small","slug":"dropdown-in-profile-is-a-little-bit-too-small","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":"/uploads/default/33145/68375c400509c222.png","created_at":"2014-07-30T18:07:13.720-04:00","last_posted_at":"2014-07-30T18:07:13.791-04:00","bumped":true,"bumped_at":"2014-07-30T18:07:13.791-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":26,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"gerhard","category_id":9,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":4881}]},{"id":18319,"title":"Settings -> Content -> Textarea is not expandable","fancy_title":"Settings -> Content -> Textarea is not expandable","slug":"settings-content-textarea-is-not-expandable","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":"http://imagizer.imageshack.com/img631/4171/HNSz6R.png","created_at":"2014-07-31T10:00:54.528-04:00","last_posted_at":"2014-07-31T10:00:54.612-04:00","bumped":true,"bumped_at":"2014-07-31T10:00:54.612-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":18,"like_count":1,"has_summary":false,"archetype":"regular","last_poster_username":"meglio","category_id":9,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":2989}]}]}}} }; +export default {"/top.json":{"users":[{"id":32,"username":"codinghorror","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/codinghorror/{size}/2.png"},{"id":2316,"username":"pakl","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/pakl/{size}/2.png"},{"id":1,"username":"sam","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/sam/{size}/2.png"},{"id":2770,"username":"awesomerobot","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/awesomerobot/{size}/2.png"},{"id":8307,"username":"HAWK","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/hawk/{size}/2.png"},{"id":10886,"username":"Onyx","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/onyx/{size}/2.png"},{"id":10855,"username":"abarker","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/abarker/{size}/2.png"},{"id":8300,"username":"cpradio","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/cpradio/{size}/2.png"},{"id":5559,"username":"downey","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/downey/{size}/2.png"},{"id":11160,"username":"boomzilla","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/boomzilla/{size}/2.png"},{"id":4263,"username":"mcwumbly","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/mcwumbly/{size}/2.png"},{"id":8909,"username":"AdamCapriola","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/adamcapriola/{size}/2.png"},{"id":4500,"username":"bbendick","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/bbendick/{size}/2.png"},{"id":3415,"username":"radq","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/radq/{size}/2.png"},{"id":471,"username":"BhaelOchon","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/bhaelochon/{size}/2.png"},{"id":7948,"username":"probus","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/probus/{size}/2.png"},{"id":6626,"username":"riking","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/riking/{size}/2.png"},{"id":2989,"username":"meglio","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/meglio/{size}/2.png"},{"id":8493,"username":"PJH","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/pjh/{size}/2.png"},{"id":11455,"username":"Dan_G","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/dan_g/{size}/2.png"},{"id":5707,"username":"trident","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/trident/{size}/2.png"},{"id":5351,"username":"erlend_sh","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/erlend_sh/{size}/2.png"},{"id":2,"username":"neil","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/neil/{size}/2.png"},{"id":11017,"username":"Matches","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/matches/{size}/2.png"},{"id":19,"username":"eviltrout","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/eviltrout/{size}/2.png"},{"id":8325,"username":"StevieD","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/stevied/{size}/2.png"},{"id":6060,"username":"lightyear","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/lightyear/{size}/2.png"},{"id":8085,"username":"watchmanmonitor","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/watchmanmonitor/{size}/2.png"},{"id":7717,"username":"lake54","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/lake54/{size}/2.png"},{"id":8873,"username":"birarda","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/birarda/{size}/2.png"},{"id":8434,"username":"ArmedGuy","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/armedguy/{size}/2.png"},{"id":8437,"username":"paully21","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/paully21/{size}/2.png"},{"id":9147,"username":"davemaxwell","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/davemaxwell/{size}/2.png"},{"id":9653,"username":"TechnoBear","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/technobear/{size}/2.png"},{"id":11589,"username":"mott555","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/mott555/{size}/2.png"},{"id":6607,"username":"aahank","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/aahank/{size}/2.png"},{"id":10816,"username":"Alankrit_Choudh","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/alankrit_choudh/{size}/2.png"},{"id":8222,"username":"techAPJ","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/techapj/{size}/2.png"},{"id":11780,"username":"cosban","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/cosban/{size}/2.png"},{"id":6819,"username":"gmanjapan","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/gmanjapan/{size}/2.png"},{"id":6548,"username":"michaeld","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/michaeld/{size}/2.png"},{"id":6268,"username":"ChaoticLoki","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/chaoticloki/{size}/2.png"},{"id":8,"username":"geek","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/geek/{size}/2.png"},{"id":8343,"username":"Piioo","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/piioo/{size}/2.png"},{"id":9536,"username":"nahtnam","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/nahtnam/{size}/2.png"},{"id":9093,"username":"RRManzke","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/rrmanzke/{size}/2.png"},{"id":8364,"username":"codetricity","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/codetricity/{size}/2.png"},{"id":5013,"username":"zenkamal","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/zenkamal/{size}/2.png"},{"id":10778,"username":"Lid","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/lid/{size}/2.png"},{"id":5399,"username":"jeffwidman","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/jeffwidman/{size}/2.png"},{"id":11747,"username":"fysics","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/fysics/{size}/2.png"},{"id":11762,"username":"bruceoberg","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/bruceoberg/{size}/2.png"},{"id":10856,"username":"youderian","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/youderian/{size}/2.png"},{"id":8810,"username":"fantasticfears","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/fantasticfears/{size}/2.png"},{"id":10098,"username":"jwatte","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/jwatte/{size}/2.png"},{"id":9775,"username":"elberet","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/elberet/{size}/2.png"},{"id":704,"username":"AstonJ","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/astonj/{size}/2.png"},{"id":10920,"username":"Webinsane","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/webinsane/{size}/2.png"},{"id":6613,"username":"haiku","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/haiku/{size}/2.png"},{"id":8820,"username":"aaroleung","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/aaroleung/{size}/2.png"},{"id":6746,"username":"shiningdracon","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/shiningdracon/{size}/2.png"},{"id":9909,"username":"unikevin","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/unikevin/{size}/2.png"},{"id":11003,"username":"node","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/node/{size}/2.png"},{"id":8571,"username":"tobiaseigen","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/tobiaseigen/{size}/2.png"},{"id":8344,"username":"pyro240","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/pyro240/{size}/2.png"},{"id":8399,"username":"edwardlafoy","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/edwardlafoy/{size}/2.png"},{"id":10949,"username":"stu1","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/stu1/{size}/2.png"},{"id":9664,"username":"cameronmartin","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/cameronmartin/{size}/2.png"},{"id":9931,"username":"Frank","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/frank/{size}/2.png"},{"id":10470,"username":"brpc","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/brpc/{size}/2.png"},{"id":10548,"username":"RabidFX","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/rabidfx/{size}/2.png"},{"id":4983,"username":"hey_julien","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/hey_julien/{size}/2.png"},{"id":7074,"username":"Maomao","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/maomao/{size}/2.png"},{"id":7502,"username":"Pablo_Macaluso","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/pablo_macaluso/{size}/2.png"},{"id":5609,"username":"camilohollanda","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/camilohollanda/{size}/2.png"},{"id":8059,"username":"Torrelles","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/torrelles/{size}/2.png"},{"id":8105,"username":"trevor_ratliff","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/trevor_ratliff/{size}/2.png"},{"id":8072,"username":"apere006","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/apere006/{size}/2.png"},{"id":9497,"username":"arumdev","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/arumdev/{size}/2.png"},{"id":5017,"username":"tuananh","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/tuananh/{size}/2.png"},{"id":11163,"username":"faoileag","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/faoileag/{size}/2.png"},{"id":11265,"username":"cipher1","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/cipher1/{size}/2.png"},{"id":5105,"username":"Ricky_Mason","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/ricky_mason/{size}/2.png"},{"id":1353,"username":"sparr","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/sparr/{size}/2.png"},{"id":5851,"username":"TheChadMiller","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/thechadmiller/{size}/2.png"},{"id":2520,"username":"anotherchris","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/anotherchris/{size}/2.png"},{"id":5249,"username":"cawas","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/cawas/{size}/2.png"},{"id":4457,"username":"Lee_Ars","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/lee_ars/{size}/2.png"},{"id":5160,"username":"eriko","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/eriko/{size}/2.png"},{"id":4220,"username":"kirantpatil","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/kirantpatil/{size}/2.png"},{"id":3704,"username":"mojzis","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/mojzis/{size}/2.png"},{"id":8944,"username":"hunterboerner","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/hunterboerner/{size}/2.png"},{"id":6808,"username":"velesin","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/velesin/{size}/2.png"},{"id":8933,"username":"JohnONolan","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/johnonolan/{size}/2.png"},{"id":7604,"username":"citkane","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/citkane/{size}/2.png"},{"id":1783,"username":"iainb","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/iainb/{size}/2.png"},{"id":9371,"username":"Vocino","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/vocino/{size}/2.png"},{"id":8617,"username":"Mittineague","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/mittineague/{size}/2.png"},{"id":10632,"username":"justinmayer","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/justinmayer/{size}/2.png"},{"id":438,"username":"TuringTest","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/turingtest/{size}/2.png"},{"id":9726,"username":"brybell","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/brybell/{size}/2.png"},{"id":3675,"username":"jk779","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/jk779/{size}/2.png"}],"topic_list":{"can_create_topic":false,"draft":null,"draft_key":"new_topic","draft_sequence":null,"for_period":"yearly","topics":[{"id":13088,"title":"Initial Discourse badge design spec","fancy_title":"Initial Discourse badge design spec","slug":"initial-discourse-badge-design-spec","posts_count":129,"reply_count":87,"highest_post_number":132,"image_url":"/uploads/default/3429/a20bcab33be2b6e2.png","created_at":"2014-02-26T04:55:39.741-05:00","last_posted_at":"2014-07-15T17:15:47.236-04:00","bumped":true,"bumped_at":"2014-07-15T17:15:47.236-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":3278,"like_count":305,"has_summary":true,"archetype":"regular","last_poster_username":"HAWK","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":2316},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":2770},{"extras":"latest","description":"Most Recent Poster","user_id":8307}]},{"id":18063,"title":"10k+ posts causes progress bar to show single number","fancy_title":"10k+ posts causes progress bar to show single number","slug":"10k-posts-causes-progress-bar-to-show-single-number","posts_count":67,"reply_count":57,"highest_post_number":70,"image_url":"/uploads/default/_optimized/fdc/03e/3d48765fc4_690x45.png","created_at":"2014-07-25T13:31:34.474-04:00","last_posted_at":"2014-07-26T04:14:18.323-04:00","bumped":true,"bumped_at":"2014-07-26T04:20:54.730-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":335,"like_count":337,"has_summary":true,"archetype":"regular","last_poster_username":"sam","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":10886},{"extras":null,"description":"Frequent Poster","user_id":10855},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":8300},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":18827,"title":"Consolidating Activity field","fancy_title":"Consolidating Activity field","slug":"consolidating-activity-field","posts_count":89,"reply_count":81,"highest_post_number":94,"image_url":"/uploads/default/33551/6483991bda61d4e5.png","created_at":"2014-08-13T18:46:09.613-04:00","last_posted_at":"2014-08-18T16:31:12.479-04:00","bumped":true,"bumped_at":"2014-08-18T16:30:13.362-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":226,"like_count":181,"has_summary":true,"archetype":"regular","last_poster_username":"codinghorror","category_id":9,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":8300},{"extras":null,"description":"Frequent Poster","user_id":5559},{"extras":null,"description":"Frequent Poster","user_id":11160},{"extras":null,"description":"Frequent Poster","user_id":4263}]},{"id":18397,"title":"Does anyone actually like the \"Likes\" column?","fancy_title":"Does anyone actually like the “Likes” column?","slug":"does-anyone-actually-like-the-likes-column","posts_count":81,"reply_count":94,"highest_post_number":111,"image_url":null,"created_at":"2014-08-02T22:15:54.016-04:00","last_posted_at":"2014-08-25T19:37:00.313-04:00","bumped":true,"bumped_at":"2014-08-25T19:37:00.313-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":460,"like_count":191,"has_summary":true,"archetype":"regular","last_poster_username":"bbendick","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":8909},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":11160},{"extras":null,"description":"Frequent Poster","user_id":5559},{"extras":"latest","description":"Most Recent Poster","user_id":4500}]},{"id":13789,"title":"Badges feedback","fancy_title":"Badges feedback","slug":"badges-feedback","posts_count":101,"reply_count":74,"highest_post_number":104,"image_url":null,"created_at":"2014-03-16T20:16:29.885-04:00","last_posted_at":"2014-08-25T13:38:58.464-04:00","bumped":true,"bumped_at":"2014-08-25T13:38:58.464-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":2227,"like_count":97,"has_summary":true,"archetype":"regular","last_poster_username":"cpradio","category_id":7,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":3415},{"extras":null,"description":"Frequent Poster","user_id":5559},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":"latest","description":"Most Recent Poster","user_id":8300}]},{"id":13479,"title":"Topic List design experiments","fancy_title":"Topic List design experiments","slug":"topic-list-design-experiments","posts_count":90,"reply_count":70,"highest_post_number":93,"image_url":"/uploads/default/_optimized/8f2/41d/0436a3b666_689x392.png","created_at":"2014-03-06T23:41:26.312-05:00","last_posted_at":"2014-07-30T16:03:05.846-04:00","bumped":true,"bumped_at":"2014-07-30T16:03:05.846-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1532,"like_count":109,"has_summary":true,"archetype":"regular","last_poster_username":"probus","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":2770},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":4263},{"extras":null,"description":"Frequent Poster","user_id":471},{"extras":"latest","description":"Most Recent Poster","user_id":7948}]},{"id":11911,"title":"How should we implement polls?","fancy_title":"How should we implement polls?","slug":"how-should-we-implement-polls","posts_count":70,"reply_count":51,"highest_post_number":73,"image_url":null,"created_at":"2014-01-12T21:48:03.160-05:00","last_posted_at":"2014-07-27T18:11:30.077-04:00","bumped":true,"bumped_at":"2014-07-27T18:11:30.077-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":2724,"like_count":123,"has_summary":true,"archetype":"regular","last_poster_username":"meglio","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":3415},{"extras":null,"description":"Frequent Poster","user_id":6626},{"extras":"latest","description":"Most Recent Poster","user_id":2989}]},{"id":18524,"title":"Rename \"Dismiss Unread\" to \"Stop Tracking Topics\"","fancy_title":"Rename “Dismiss Unread” to “Stop Tracking Topics”","slug":"rename-dismiss-unread-to-stop-tracking-topics","posts_count":74,"reply_count":53,"highest_post_number":74,"image_url":null,"created_at":"2014-08-06T01:12:01.086-04:00","last_posted_at":"2014-08-12T05:47:20.750-04:00","bumped":true,"bumped_at":"2014-08-12T05:47:20.750-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":149,"like_count":103,"has_summary":true,"archetype":"regular","last_poster_username":"Dan_G","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":4263},{"extras":null,"description":"Frequent Poster","user_id":8493},{"extras":"latest","description":"Most Recent Poster","user_id":11455}]},{"id":10515,"title":"Flatter styling now deployed","fancy_title":"Flatter styling now deployed","slug":"flatter-styling-now-deployed","posts_count":80,"reply_count":41,"highest_post_number":80,"image_url":null,"created_at":"2013-10-20T19:36:00.465-04:00","last_posted_at":"2014-03-18T14:04:00.515-04:00","bumped":true,"bumped_at":"2014-03-18T14:04:00.515-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1790,"like_count":78,"has_summary":true,"archetype":"regular","last_poster_username":"mcwumbly","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":2770},{"extras":null,"description":"Frequent Poster","user_id":5707},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":"latest","description":"Most Recent Poster","user_id":4263}]},{"id":12346,"title":"What about an easier styling/theming system?","fancy_title":"What about an easier styling/theming system?","slug":"what-about-an-easier-styling-theming-system","posts_count":54,"reply_count":26,"highest_post_number":54,"image_url":null,"created_at":"2014-01-31T19:11:51.887-05:00","last_posted_at":"2014-07-01T17:42:38.425-04:00","bumped":true,"bumped_at":"2014-07-01T17:42:38.425-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1740,"like_count":130,"has_summary":true,"archetype":"regular","last_poster_username":"neil","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":2770},{"extras":null,"description":"Frequent Poster","user_id":7948},{"extras":null,"description":"Frequent Poster","user_id":5351},{"extras":"latest","description":"Most Recent Poster","user_id":2}]},{"id":18875,"title":"Notification when a moderator or admin deletes your message","fancy_title":"Notification when a moderator or admin deletes your message","slug":"notification-when-a-moderator-or-admin-deletes-your-message","posts_count":58,"reply_count":44,"highest_post_number":66,"image_url":null,"created_at":"2014-08-14T18:57:28.722-04:00","last_posted_at":"2014-08-20T12:34:26.180-04:00","bumped":true,"bumped_at":"2014-08-20T12:34:20.630-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":181,"like_count":122,"has_summary":true,"archetype":"regular","last_poster_username":"eviltrout","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":11017},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":11160},{"extras":null,"description":"Frequent Poster","user_id":11455},{"extras":"latest","description":"Most Recent Poster","user_id":19}]},{"id":12257,"title":"Is \"Activity\" too ambiguous?","fancy_title":"Is “Activity” too ambiguous?","slug":"is-activity-too-ambiguous","posts_count":53,"reply_count":40,"highest_post_number":53,"image_url":"/uploads/default/_optimized/542/c04/82250e51e5_690x248.png","created_at":"2014-01-28T14:01:08.745-05:00","last_posted_at":"2014-04-13T18:25:45.492-04:00","bumped":true,"bumped_at":"2014-04-13T18:25:45.492-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":686,"like_count":103,"has_summary":true,"archetype":"regular","last_poster_username":"StevieD","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":2770},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":4263},{"extras":null,"description":"Frequent Poster","user_id":7948},{"extras":"latest","description":"Most Recent Poster","user_id":8325}]},{"id":13099,"title":"Replacing Mailing lists: Email-In","fancy_title":"Replacing Mailing lists: Email-In","slug":"replacing-mailing-lists-email-in","posts_count":66,"reply_count":46,"highest_post_number":68,"image_url":null,"created_at":"2014-02-26T13:24:44.965-05:00","last_posted_at":"2014-07-09T18:01:21.166-04:00","bumped":true,"bumped_at":"2014-07-09T19:10:30.547-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1567,"like_count":76,"has_summary":true,"archetype":"regular","last_poster_username":"lake54","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":6060},{"extras":null,"description":"Frequent Poster","user_id":5351},{"extras":null,"description":"Frequent Poster","user_id":5559},{"extras":null,"description":"Frequent Poster","user_id":8085},{"extras":"latest","description":"Most Recent Poster","user_id":7717}]},{"id":13045,"title":"Official Single-Sign-On for Discourse","fancy_title":"Official Single-Sign-On for Discourse","slug":"official-single-sign-on-for-discourse","posts_count":61,"reply_count":37,"highest_post_number":64,"image_url":"/uploads/default/_optimized/07c/3bf/3fa1d69ceb_690x207.png","created_at":"2014-02-25T03:30:34.321-05:00","last_posted_at":"2014-08-01T17:44:56.523-04:00","bumped":true,"bumped_at":"2014-08-07T13:27:14.684-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":13052,"like_count":74,"has_summary":true,"archetype":"regular","last_poster_username":"riking","category_id":10,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":8873},{"extras":null,"description":"Frequent Poster","user_id":8434},{"extras":null,"description":"Frequent Poster","user_id":8437},{"extras":"latest","description":"Most Recent Poster","user_id":6626}]},{"id":19099,"title":"Should search prioritize recent topics over older topics?","fancy_title":"Should search prioritize recent topics over older topics?","slug":"should-search-prioritize-recent-topics-over-older-topics","posts_count":55,"reply_count":48,"highest_post_number":58,"image_url":"/uploads/default/33840/49e57c5a286a2131.png","created_at":"2014-08-20T12:00:12.737-04:00","last_posted_at":"2014-08-22T17:46:34.073-04:00","bumped":true,"bumped_at":"2014-08-22T17:46:20.038-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":149,"like_count":83,"has_summary":true,"archetype":"regular","last_poster_username":"codinghorror","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":9147},{"extras":null,"description":"Frequent Poster","user_id":8300},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":9653},{"extras":"latest","description":"Most Recent Poster","user_id":32}]},{"id":18879,"title":"Further simplifying the columns: quality score > view count","fancy_title":"Further simplifying the columns: quality score > view count","slug":"further-simplifying-the-columns-quality-score-view-count","posts_count":44,"reply_count":32,"highest_post_number":44,"image_url":"/uploads/default/33627/b40ad535eba2b7a3.png","created_at":"2014-08-14T21:19:24.118-04:00","last_posted_at":"2014-08-22T14:25:12.092-04:00","bumped":true,"bumped_at":"2014-08-22T15:21:09.995-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":560,"like_count":95,"has_summary":false,"archetype":"regular","last_poster_username":"codinghorror","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":8909},{"extras":null,"description":"Frequent Poster","user_id":5559},{"extras":null,"description":"Frequent Poster","user_id":11160},{"extras":null,"description":"Frequent Poster","user_id":11589},{"extras":"latest","description":"Most Recent Poster","user_id":32}]},{"id":13847,"title":"Allowing SSL for your Discourse Docker setup","fancy_title":"Allowing SSL for your Discourse Docker setup","slug":"allowing-ssl-for-your-discourse-docker-setup","posts_count":47,"reply_count":59,"highest_post_number":58,"image_url":null,"created_at":"2014-03-18T19:45:27.517-04:00","last_posted_at":"2014-08-28T04:03:20.851-04:00","bumped":true,"bumped_at":"2014-08-28T04:17:17.852-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":6927,"like_count":87,"has_summary":false,"archetype":"regular","last_poster_username":"cosban","category_id":10,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":6607},{"extras":null,"description":"Frequent Poster","user_id":10816},{"extras":null,"description":"Frequent Poster","user_id":8222},{"extras":"latest","description":"Most Recent Poster","user_id":11780}]},{"id":9621,"title":"Free Hosted Option?","fancy_title":"Free Hosted Option?","slug":"free-hosted-option","posts_count":43,"reply_count":33,"highest_post_number":43,"image_url":null,"created_at":"2013-09-05T16:22:20.790-04:00","last_posted_at":"2014-04-08T00:24:46.320-04:00","bumped":true,"bumped_at":"2014-04-08T00:24:46.320-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1844,"like_count":93,"has_summary":false,"archetype":"regular","last_poster_username":"ChaoticLoki","category_id":8,"posters":[{"extras":null,"description":"Original Poster","user_id":6819},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":6548},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":"latest","description":"Most Recent Poster","user_id":6268}]},{"id":18873,"title":"Alternative to blue colors for coldmapping","fancy_title":"Alternative to blue colors for coldmapping","slug":"alternative-to-blue-colors-for-coldmapping","posts_count":47,"reply_count":23,"highest_post_number":47,"image_url":null,"created_at":"2014-08-14T18:33:21.844-04:00","last_posted_at":"2014-08-15T10:46:08.175-04:00","bumped":true,"bumped_at":"2014-08-15T10:46:08.175-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":122,"like_count":84,"has_summary":false,"archetype":"regular","last_poster_username":"boomzilla","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":10855},{"extras":null,"description":"Frequent Poster","user_id":5559},{"extras":null,"description":"Frequent Poster","user_id":8},{"extras":"latest","description":"Most Recent Poster","user_id":11160}]},{"id":11763,"title":"Google AdSense plugin is now available","fancy_title":"Google AdSense plugin is now available","slug":"google-adsense-plugin-is-now-available","posts_count":57,"reply_count":36,"highest_post_number":58,"image_url":"/uploads/default/_optimized/66d/cf0/d69e6709fe_496x500.PNG","created_at":"2014-01-05T14:28:58.037-05:00","last_posted_at":"2014-08-08T07:55:23.454-04:00","bumped":true,"bumped_at":"2014-08-08T07:55:23.454-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":2085,"like_count":62,"has_summary":true,"archetype":"regular","last_poster_username":"michaeld","category_id":22,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":6548},{"extras":null,"description":"Frequent Poster","user_id":8343},{"extras":null,"description":"Frequent Poster","user_id":9536},{"extras":null,"description":"Frequent Poster","user_id":3415},{"extras":null,"description":"Frequent Poster","user_id":9093}]},{"id":13485,"title":"What do you like/dislike about the NodeBB design?","fancy_title":"What do you like/dislike about the NodeBB design?","slug":"what-do-you-like-dislike-about-the-nodebb-design","posts_count":52,"reply_count":28,"highest_post_number":53,"image_url":null,"created_at":"2014-03-07T03:38:14.227-05:00","last_posted_at":"2014-08-20T15:19:00.969-04:00","bumped":true,"bumped_at":"2014-08-19T20:22:04.123-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1495,"like_count":68,"has_summary":true,"archetype":"regular","last_poster_username":"codinghorror","category_id":9,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":8364},{"extras":null,"description":"Frequent Poster","user_id":5013},{"extras":null,"description":"Frequent Poster","user_id":4263},{"extras":null,"description":"Frequent Poster","user_id":2770}]},{"id":17454,"title":"Spambots from Tor exit points keep taking over my forum","fancy_title":"Spambots from Tor exit points keep taking over my forum","slug":"spambots-from-tor-exit-points-keep-taking-over-my-forum","posts_count":46,"reply_count":32,"highest_post_number":46,"image_url":"/uploads/default/_optimized/b0d/ab3/20401b97ce_690x454.png","created_at":"2014-07-11T03:20:49.433-04:00","last_posted_at":"2014-08-19T18:09:10.799-04:00","bumped":true,"bumped_at":"2014-08-19T18:02:57.107-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1243,"like_count":78,"has_summary":false,"archetype":"regular","last_poster_username":"codinghorror","category_id":6,"posters":[{"extras":null,"description":"Original Poster","user_id":8},{"extras":null,"description":"Frequent Poster","user_id":10778},{"extras":null,"description":"Frequent Poster","user_id":8300},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":"latest","description":"Most Recent Poster","user_id":32}]},{"id":19317,"title":"Introducing Discourse 1.0","fancy_title":"Introducing Discourse 1.0","slug":"introducing-discourse-1-0","posts_count":36,"reply_count":3,"highest_post_number":36,"image_url":null,"created_at":"2014-08-26T15:43:01.370-04:00","last_posted_at":"2014-08-28T13:16:42.484-04:00","bumped":true,"bumped_at":"2014-08-28T13:16:42.484-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":709,"like_count":103,"has_summary":false,"archetype":"regular","last_poster_username":"youderian","category_id":13,"posters":[{"extras":null,"description":"Original Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":5399},{"extras":null,"description":"Frequent Poster","user_id":11747},{"extras":null,"description":"Frequent Poster","user_id":11762},{"extras":"latest","description":"Most Recent Poster","user_id":10856}]},{"id":13184,"title":"Discourse General Polish prior to V1","fancy_title":"Discourse General Polish prior to V1","slug":"discourse-general-polish-prior-to-v1","posts_count":44,"reply_count":30,"highest_post_number":48,"image_url":"/plugins/emoji/images/arrow_left.png","created_at":"2014-02-27T19:10:41.496-05:00","last_posted_at":"2014-06-08T03:32:02.009-04:00","bumped":true,"bumped_at":"2014-06-06T03:30:23.984-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":1864,"like_count":77,"has_summary":false,"archetype":"regular","last_poster_username":"codinghorror","category_id":7,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":8810},{"extras":null,"description":"Frequent Poster","user_id":2770},{"extras":null,"description":"Frequent Poster","user_id":8222},{"extras":"latest","description":"Most Recent Poster","user_id":32}]},{"id":17694,"title":"Release schedule post version 1.0","fancy_title":"Release schedule post version 1.0","slug":"release-schedule-post-version-1-0","posts_count":44,"reply_count":35,"highest_post_number":44,"image_url":null,"created_at":"2014-07-17T19:45:21.459-04:00","last_posted_at":"2014-07-23T03:51:03.564-04:00","bumped":true,"bumped_at":"2014-07-29T17:20:06.942-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":539,"like_count":70,"has_summary":false,"archetype":"regular","last_poster_username":"probus","category_id":17,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":10098},{"extras":null,"description":"Frequent Poster","user_id":9775},{"extras":null,"description":"Frequent Poster","user_id":4263},{"extras":"latest","description":"Most Recent Poster","user_id":7948}]},{"id":18533,"title":"My latest forum... but it's not running Discourse - here's why","fancy_title":"My latest forum… but it’s not running Discourse - here’s why","slug":"my-latest-forum-but-its-not-running-discourse-heres-why","posts_count":37,"reply_count":27,"highest_post_number":38,"image_url":null,"created_at":"2014-08-06T06:01:35.608-04:00","last_posted_at":"2014-08-15T13:27:13.386-04:00","bumped":true,"bumped_at":"2014-08-15T13:27:13.386-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1185,"like_count":73,"has_summary":false,"archetype":"regular","last_poster_username":"codinghorror","category_id":17,"posters":[{"extras":null,"description":"Original Poster","user_id":704},{"extras":null,"description":"Frequent Poster","user_id":10920},{"extras":null,"description":"Frequent Poster","user_id":6613},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":"latest","description":"Most Recent Poster","user_id":32}]},{"id":13287,"title":"Chinese search issues","fancy_title":"Chinese search issues","slug":"chinese-search-issues","posts_count":60,"reply_count":41,"highest_post_number":60,"image_url":"https://f.cloud.github.com/assets/6783175/2296397/3dcabcf8-a09e-11e3-9f5a-2a94d981fced.png","created_at":"2014-03-01T10:12:14.845-05:00","last_posted_at":"2014-07-10T17:03:25.796-04:00","bumped":true,"bumped_at":"2014-07-10T17:03:25.796-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":947,"like_count":25,"has_summary":true,"archetype":"regular","last_poster_username":"sam","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":8820},{"extras":null,"description":"Frequent Poster","user_id":6746},{"extras":null,"description":"Frequent Poster","user_id":9909},{"extras":null,"description":"Frequent Poster","user_id":8810},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":17727,"title":"Compliance with EU Cookie Law","fancy_title":"Compliance with EU Cookie Law","slug":"compliance-with-eu-cookie-law","posts_count":46,"reply_count":32,"highest_post_number":46,"image_url":null,"created_at":"2014-07-18T17:39:38.499-04:00","last_posted_at":"2014-07-26T18:01:33.751-04:00","bumped":true,"bumped_at":"2014-07-26T18:01:33.751-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":836,"like_count":48,"has_summary":false,"archetype":"regular","last_poster_username":"node","category_id":6,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":11003},{"extras":null,"description":"Frequent Poster","user_id":11017},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":9536}]},{"id":15336,"title":"Switch from Gravatar to HTML/CSS letters for no-avatar users","fancy_title":"Switch from Gravatar to HTML/CSS letters for no-avatar users","slug":"switch-from-gravatar-to-html-css-letters-for-no-avatar-users","posts_count":39,"reply_count":25,"highest_post_number":39,"image_url":"/uploads/default/_optimized/d29/bc1/25fa89ae0a_415x500.png","created_at":"2014-05-05T18:46:02.221-04:00","last_posted_at":"2014-05-28T18:07:12.448-04:00","bumped":true,"bumped_at":"2014-05-28T18:07:09.701-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":1011,"like_count":63,"has_summary":false,"archetype":"regular","last_poster_username":"codinghorror","category_id":26,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":9775},{"extras":null,"description":"Frequent Poster","user_id":8571},{"extras":null,"description":"Frequent Poster","user_id":8344}]},{"id":12957,"title":"Discourse for iOS","fancy_title":"Discourse for iOS","slug":"discourse-for-ios","posts_count":43,"reply_count":24,"highest_post_number":43,"image_url":"http://a4.mzstatic.com/us/r30/Purple/v4/8d/85/93/8d859353-625c-8abc-5c00-36be5f293709/mzl.luwjaamb.png","created_at":"2014-02-21T20:37:44.606-05:00","last_posted_at":"2014-08-20T15:09:19.767-04:00","bumped":true,"bumped_at":"2014-08-20T15:09:19.767-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1082,"like_count":48,"has_summary":false,"archetype":"regular","last_poster_username":"erlend_sh","category_id":5,"posters":[{"extras":null,"description":"Original Poster","user_id":8399},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":10949},{"extras":null,"description":"Frequent Poster","user_id":8085},{"extras":"latest","description":"Most Recent Poster","user_id":5351}]},{"id":14973,"title":"Symbol for like - why is it a heart?","fancy_title":"Symbol for like - why is it a heart?","slug":"symbol-for-like-why-is-it-a-heart","posts_count":29,"reply_count":14,"highest_post_number":29,"image_url":null,"created_at":"2014-04-22T12:24:22.822-04:00","last_posted_at":"2014-05-08T17:41:27.803-04:00","bumped":true,"bumped_at":"2014-05-08T17:41:27.803-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1421,"like_count":73,"has_summary":false,"archetype":"regular","last_poster_username":"Frank","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":9664},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":2770},{"extras":null,"description":"Frequent Poster","user_id":8085},{"extras":"latest","description":"Most Recent Poster","user_id":9931}]},{"id":16875,"title":"Options to disable hijack of CMD+F / CTRL+F and \"/\" keys for search?","fancy_title":"Options to disable hijack of CMD+F / CTRL+F and “/” keys for search?","slug":"options-to-disable-hijack-of-cmd-f-ctrl-f-and-keys-for-search","posts_count":44,"reply_count":36,"highest_post_number":44,"image_url":null,"created_at":"2014-06-25T17:04:48.413-04:00","last_posted_at":"2014-08-25T04:01:38.132-04:00","bumped":true,"bumped_at":"2014-08-25T04:01:38.132-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":541,"like_count":41,"has_summary":false,"archetype":"regular","last_poster_username":"RabidFX","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":10470},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":8300},{"extras":"latest","description":"Most Recent Poster","user_id":10548}]},{"id":9975,"title":"Translators We Want You!","fancy_title":"Translators We Want You!","slug":"translators-we-want-you","posts_count":50,"reply_count":28,"highest_post_number":50,"image_url":null,"created_at":"2013-09-23T13:47:39.521-04:00","last_posted_at":"2014-03-16T16:21:13.891-04:00","bumped":true,"bumped_at":"2014-03-16T16:21:13.891-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1262,"like_count":26,"has_summary":true,"archetype":"regular","last_poster_username":"Torrelles","category_id":27,"posters":[{"extras":null,"description":"Original Poster","user_id":4983},{"extras":null,"description":"Frequent Poster","user_id":7074},{"extras":null,"description":"Frequent Poster","user_id":7502},{"extras":null,"description":"Frequent Poster","user_id":5609},{"extras":"latest","description":"Most Recent Poster","user_id":8059}]},{"id":12112,"title":"The system user needs a cool avatar","fancy_title":"The system user needs a cool avatar","slug":"the-system-user-needs-a-cool-avatar","posts_count":35,"reply_count":24,"highest_post_number":35,"image_url":"/uploads/default/31460/c596ef65a9d0533c.png","created_at":"2014-01-21T22:26:01.574-05:00","last_posted_at":"2014-01-31T16:54:22.261-05:00","bumped":true,"bumped_at":"2014-01-31T16:54:22.261-05:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":848,"like_count":55,"has_summary":false,"archetype":"regular","last_poster_username":"sam","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":6626},{"extras":null,"description":"Frequent Poster","user_id":8105},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":2770},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":11718,"title":"Reply button while logged out","fancy_title":"Reply button while logged out","slug":"reply-button-while-logged-out","posts_count":46,"reply_count":42,"highest_post_number":46,"image_url":null,"created_at":"2014-01-02T17:11:14.130-05:00","last_posted_at":"2014-04-05T10:20:11.921-04:00","bumped":true,"bumped_at":"2014-04-05T10:20:11.921-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":830,"like_count":32,"has_summary":false,"archetype":"regular","last_poster_username":"apere006","category_id":9,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":8072},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":9497},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":471}]},{"id":18698,"title":"Site setting for sending analytics data to Discourse.org","fancy_title":"Site setting for sending analytics data to Discourse.org","slug":"site-setting-for-sending-analytics-data-to-discourse-org","posts_count":27,"reply_count":18,"highest_post_number":27,"image_url":null,"created_at":"2014-08-10T11:47:04.016-04:00","last_posted_at":"2014-08-11T15:36:21.664-04:00","bumped":true,"bumped_at":"2014-08-11T15:36:04.020-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":true,"archived":false,"views":135,"like_count":72,"has_summary":false,"archetype":"regular","last_poster_username":"codinghorror","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":7948},{"extras":null,"description":"Frequent Poster","user_id":5559},{"extras":null,"description":"Frequent Poster","user_id":6548},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":"latest","description":"Most Recent Poster","user_id":32}]},{"id":17443,"title":"Can I Keep Nofollow for All User Links, Including from Trust Level 3?","fancy_title":"Can I Keep Nofollow for All User Links, Including from Trust Level 3?","slug":"can-i-keep-nofollow-for-all-user-links-including-from-trust-level-3","posts_count":40,"reply_count":30,"highest_post_number":41,"image_url":null,"created_at":"2014-07-10T22:06:49.357-04:00","last_posted_at":"2014-07-14T19:20:37.014-04:00","bumped":true,"bumped_at":"2014-07-14T19:20:37.014-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":237,"like_count":42,"has_summary":false,"archetype":"regular","last_poster_username":"cpradio","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":8},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":5017},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":"latest","description":"Most Recent Poster","user_id":8300}]},{"id":18821,"title":"Suggestion: # of likes in a topic in the tool tip","fancy_title":"Suggestion: # of likes in a topic in the tool tip","slug":"suggestion-of-likes-in-a-topic-in-the-tool-tip","posts_count":27,"reply_count":22,"highest_post_number":27,"image_url":null,"created_at":"2014-08-13T15:23:46.745-04:00","last_posted_at":"2014-08-15T07:40:57.684-04:00","bumped":true,"bumped_at":"2014-08-15T07:40:57.684-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":133,"like_count":68,"has_summary":false,"archetype":"regular","last_poster_username":"boomzilla","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":11163},{"extras":null,"description":"Frequent Poster","user_id":11265},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":8493},{"extras":"latest","description":"Most Recent Poster","user_id":11160}]},{"id":9741,"title":"Difference between Reddit and Discourse","fancy_title":"Difference between Reddit and Discourse","slug":"difference-between-reddit-and-discourse","posts_count":42,"reply_count":32,"highest_post_number":42,"image_url":null,"created_at":"2013-09-11T22:17:39.971-04:00","last_posted_at":"2013-09-17T19:01:36.139-04:00","bumped":true,"bumped_at":"2013-09-17T19:01:36.139-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":3303,"like_count":35,"has_summary":false,"archetype":"regular","last_poster_username":"anotherchris","category_id":3,"posters":[{"extras":null,"description":"Original Poster","user_id":5105},{"extras":null,"description":"Frequent Poster","user_id":1353},{"extras":null,"description":"Frequent Poster","user_id":5851},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":"latest","description":"Most Recent Poster","user_id":2520}]},{"id":12156,"title":"Beginners Guide to Deploy Discourse on Digital Ocean using Docker","fancy_title":"Beginners Guide to Deploy Discourse on Digital Ocean using Docker","slug":"beginners-guide-to-deploy-discourse-on-digital-ocean-using-docker","posts_count":28,"reply_count":157,"highest_post_number":219,"image_url":"http://www.discourse.org/images/install/droplet-step-1.png","created_at":"2014-01-23T14:58:17.918-05:00","last_posted_at":"2014-08-26T10:06:25.833-04:00","bumped":true,"bumped_at":"2014-08-26T10:06:25.833-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":10112,"like_count":63,"has_summary":false,"archetype":"regular","last_poster_username":"cawas","category_id":10,"posters":[{"extras":null,"description":"Original Poster","user_id":8222},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":6626},{"extras":null,"description":"Frequent Poster","user_id":8364},{"extras":"latest","description":"Most Recent Poster","user_id":5249}]},{"id":12522,"title":"Permission Changes (moderators have less)","fancy_title":"Permission Changes (moderators have less)","slug":"permission-changes-moderators-have-less","posts_count":42,"reply_count":30,"highest_post_number":43,"image_url":null,"created_at":"2014-02-06T22:34:05.332-05:00","last_posted_at":"2014-08-01T12:26:40.440-04:00","bumped":true,"bumped_at":"2014-08-01T12:26:40.440-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1806,"like_count":37,"has_summary":false,"archetype":"regular","last_poster_username":"codinghorror","category_id":17,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":5559},{"extras":null,"description":"Frequent Poster","user_id":6626},{"extras":null,"description":"Frequent Poster","user_id":4457},{"extras":"latest","description":"Most Recent Poster","user_id":32}]},{"id":10291,"title":"CAS sso auth plugin","fancy_title":"CAS sso auth plugin","slug":"cas-sso-auth-plugin","posts_count":48,"reply_count":32,"highest_post_number":51,"image_url":null,"created_at":"2013-10-09T17:01:21.524-04:00","last_posted_at":"2014-08-27T16:08:25.417-04:00","bumped":true,"bumped_at":"2014-08-27T16:08:25.417-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1433,"like_count":20,"has_summary":false,"archetype":"regular","last_poster_username":"eriko","category_id":22,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5160},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":4220},{"extras":null,"description":"Frequent Poster","user_id":3704},{"extras":null,"description":"Frequent Poster","user_id":32}]},{"id":16877,"title":"Discourse V1.0 Next Month","fancy_title":"Discourse V1.0 Next Month","slug":"discourse-v1-0-next-month","posts_count":25,"reply_count":11,"highest_post_number":26,"image_url":null,"created_at":"2014-06-25T18:54:32.020-04:00","last_posted_at":"2014-08-14T13:07:09.405-04:00","bumped":true,"bumped_at":"2014-08-14T13:07:09.405-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1113,"like_count":65,"has_summary":false,"archetype":"regular","last_poster_username":"Dan_G","category_id":13,"posters":[{"extras":null,"description":"Original Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":8810},{"extras":null,"description":"Frequent Poster","user_id":8944},{"extras":null,"description":"Frequent Poster","user_id":10920},{"extras":"latest","description":"Most Recent Poster","user_id":11455}]},{"id":18257,"title":"Move the new/unread counters to the first column in topic list","fancy_title":"Move the new/unread counters to the first column in topic list","slug":"move-the-new-unread-counters-to-the-first-column-in-topic-list","posts_count":32,"reply_count":25,"highest_post_number":32,"image_url":null,"created_at":"2014-07-30T02:33:42.679-04:00","last_posted_at":"2014-08-01T12:33:11.694-04:00","bumped":true,"bumped_at":"2014-08-01T12:33:11.694-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":217,"like_count":51,"has_summary":false,"archetype":"regular","last_poster_username":"codinghorror","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":7948},{"extras":null,"description":"Frequent Poster","user_id":4263},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":2770},{"extras":"latest","description":"Most Recent Poster","user_id":32}]},{"id":13249,"title":"Syncing the editor viewport scroll","fancy_title":"Syncing the editor viewport scroll","slug":"syncing-the-editor-viewport-scroll","posts_count":35,"reply_count":15,"highest_post_number":35,"image_url":null,"created_at":"2014-02-28T19:03:57.708-05:00","last_posted_at":"2014-04-06T21:04:59.528-04:00","bumped":true,"bumped_at":"2014-04-06T21:04:59.528-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":980,"like_count":44,"has_summary":false,"archetype":"regular","last_poster_username":"sam","category_id":7,"posters":[{"extras":null,"description":"Original Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":6808},{"extras":null,"description":"Frequent Poster","user_id":8933},{"extras":null,"description":"Frequent Poster","user_id":19},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":9711,"title":"Now testing: mobile (small screen) layouts on key pages","fancy_title":"Now testing: mobile (small screen) layouts on key pages","slug":"now-testing-mobile-small-screen-layouts-on-key-pages","posts_count":43,"reply_count":31,"highest_post_number":51,"image_url":"/uploads/meta_discourse/1787/beb2b60fba4c46c3.png","created_at":"2013-09-10T19:45:51.532-04:00","last_posted_at":"2014-02-05T02:03:24.974-05:00","bumped":true,"bumped_at":"2014-02-05T13:45:55.088-05:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":1635,"like_count":27,"has_summary":false,"archetype":"regular","last_poster_username":"iainb","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":2},{"extras":null,"description":"Frequent Poster","user_id":8},{"extras":null,"description":"Frequent Poster","user_id":7604},{"extras":"latest","description":"Most Recent Poster","user_id":1783}]},{"id":15048,"title":"Linking a Discourse User db with a Mumble server (Murmur)","fancy_title":"Linking a Discourse User db with a Mumble server (Murmur)","slug":"linking-a-discourse-user-db-with-a-mumble-server-murmur","posts_count":48,"reply_count":40,"highest_post_number":48,"image_url":null,"created_at":"2014-04-24T18:30:17.568-04:00","last_posted_at":"2014-05-30T20:43:13.387-04:00","bumped":true,"bumped_at":"2014-05-30T20:43:13.387-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":625,"like_count":17,"has_summary":false,"archetype":"regular","last_poster_username":"Vocino","category_id":7,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":9371},{"extras":null,"description":"Frequent Poster","user_id":9775},{"extras":null,"description":"Frequent Poster","user_id":4457},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":6613}]},{"id":17945,"title":"Unread/new badge style?","fancy_title":"Unread/new badge style?","slug":"unread-new-badge-style","posts_count":35,"reply_count":23,"highest_post_number":35,"image_url":"/uploads/default/_optimized/b61/a61/3508713cc1_690x202.png","created_at":"2014-07-23T10:49:18.864-04:00","last_posted_at":"2014-07-28T13:52:16.773-04:00","bumped":true,"bumped_at":"2014-07-28T13:52:16.773-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":279,"like_count":43,"has_summary":false,"archetype":"regular","last_poster_username":"Mittineague","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":2770},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":4263},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":"latest","description":"Most Recent Poster","user_id":8617}]},{"id":16803,"title":"Auto-hide persistent fixed header on scroll","fancy_title":"Auto-hide persistent fixed header on scroll","slug":"auto-hide-persistent-fixed-header-on-scroll","posts_count":39,"reply_count":27,"highest_post_number":39,"image_url":null,"created_at":"2014-06-23T13:25:32.523-04:00","last_posted_at":"2014-07-07T10:45:40.399-04:00","bumped":true,"bumped_at":"2014-07-07T10:45:40.399-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":792,"like_count":34,"has_summary":false,"archetype":"regular","last_poster_username":"mcwumbly","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":10632},{"extras":null,"description":"Frequent Poster","user_id":438},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":2770},{"extras":"latest","description":"Most Recent Poster","user_id":4263}]},{"id":15858,"title":"Configuring Google OAuth2 login for Discourse","fancy_title":"Configuring Google OAuth2 login for Discourse","slug":"configuring-google-oauth2-login-for-discourse","posts_count":36,"reply_count":24,"highest_post_number":40,"image_url":"/uploads/default/_optimized/9ae/174/5a30a33f56_690x399.png","created_at":"2014-05-21T18:46:55.403-04:00","last_posted_at":"2014-08-17T15:30:40.593-04:00","bumped":true,"bumped_at":"2014-08-17T15:29:45.558-04:00","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"views":5257,"like_count":41,"has_summary":false,"archetype":"regular","last_poster_username":"codinghorror","category_id":10,"posters":[{"extras":null,"description":"Original Poster","user_id":2},{"extras":null,"description":"Frequent Poster","user_id":9726},{"extras":null,"description":"Frequent Poster","user_id":3675},{"extras":null,"description":"Frequent Poster","user_id":6626},{"extras":"latest","description":"Most Recent Poster","user_id":32}]}]}}}; diff --git a/test/javascripts/integration/topic-discovery-test.js.es6 b/test/javascripts/integration/topic-discovery-test.js.es6 index a70ed7a7e..296c49739 100644 --- a/test/javascripts/integration/topic-discovery-test.js.es6 +++ b/test/javascripts/integration/topic-discovery-test.js.es6 @@ -20,6 +20,6 @@ test("Visit Discovery Pages", function() { visit("/top"); andThen(function() { - ok(exists('.topic-list tr td.main-link'), "has topics"); + ok(exists('.topic-list .topic-list-item'), "has topics"); }); }); From 8bf27642ad728020108bd9dbf76ca3a84b71342e Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 28 Aug 2014 14:44:38 -0400 Subject: [PATCH 15/43] FIX: Refresh was losing top controls after bulk operation. Removed console.log --- .../javascripts/discourse/controllers/discovery/topics.js.es6 | 1 + app/views/users/activate_account.html.erb | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 index c9cef05c5..f992ec9b9 100644 --- a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 @@ -5,6 +5,7 @@ var controllerOpts = { needs: ['discovery'], bulkSelectEnabled: false, selected: [], + period: null, redirectedReason: Em.computed.alias('currentUser.redirected_to_top_reason'), diff --git a/app/views/users/activate_account.html.erb b/app/views/users/activate_account.html.erb index 2f9cca159..f6847f937 100644 --- a/app/views/users/activate_account.html.erb +++ b/app/views/users/activate_account.html.erb @@ -23,7 +23,6 @@ $('#activate-account-form').submit(); }).fail(function() { $('#activate-account-button').prop('disabled', false); - console.log('test'); }); } From b5b13b8c5ffca9b50f03aa2b95662d39878c720f Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 28 Aug 2014 15:08:06 -0400 Subject: [PATCH 16/43] Revert "Merge pull request #2713 from cpradio/master" This reverts commit a9de189682b0e0ec7c6b4209b4352d28c1a1e428, reversing changes made to b7ab43e454683db208a177ecf48af294189a2321. --- app/assets/javascripts/discourse/views/header.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/views/header.js.es6 b/app/assets/javascripts/discourse/views/header.js.es6 index 0986709a5..c6e7e3013 100644 --- a/app/assets/javascripts/discourse/views/header.js.es6 +++ b/app/assets/javascripts/discourse/views/header.js.es6 @@ -58,7 +58,7 @@ export default Discourse.View.extend({ // fade it fast $dropdown.fadeIn('fast'); // autofocus any text input field - $dropdown.find('input[type=text],a:first').focus().select(); + $dropdown.find('input[type=text]').focus().select(); $html.on('click.d-dropdown', function(e) { return $(e.target).closest('.d-dropdown').length > 0 ? true : hideDropdown.apply(self); From 8a6c4234fc3dcb091053934b8159f6c5bce14bf6 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 28 Aug 2014 15:42:29 -0400 Subject: [PATCH 17/43] FIX: Re-enable searching for topic by id when using the split topic interface. --- .../javascripts/discourse/lib/search.js | 1 + .../discourse/views/choose-topic.js.es6 | 2 +- app/controllers/search_controller.rb | 1 + lib/search.rb | 3 +- spec/components/search_spec.rb | 12 +++- spec/controllers/search_controller_spec.rb | 61 ++++++++++--------- 6 files changed, 49 insertions(+), 31 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/search.js b/app/assets/javascripts/discourse/lib/search.js index a64f90d07..512213d02 100644 --- a/app/assets/javascripts/discourse/lib/search.js +++ b/app/assets/javascripts/discourse/lib/search.js @@ -23,6 +23,7 @@ Discourse.Search = { // Only include the data we have var data = { term: term }; if (opts.typeFilter) data.type_filter = opts.typeFilter; + if (opts.searchForId) data.search_for_id = true; if (opts.searchContext) { data.search_context = { diff --git a/app/assets/javascripts/discourse/views/choose-topic.js.es6 b/app/assets/javascripts/discourse/views/choose-topic.js.es6 index c5f3cc9e5..70cb7a810 100644 --- a/app/assets/javascripts/discourse/views/choose-topic.js.es6 +++ b/app/assets/javascripts/discourse/views/choose-topic.js.es6 @@ -30,7 +30,7 @@ export default Discourse.View.extend({ self.setProperties({ topics: null, loading: false }); return; } - Discourse.Search.forTerm(title, {typeFilter: 'topic'}).then(function (facets) { + Discourse.Search.forTerm(title, {typeFilter: 'topic', searchForId: true}).then(function (facets) { if (facets && facets[0] && facets[0].results) { self.set('topics', facets[0].results); } else { diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index bcf0e7fe9..57aa7a1fd 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -14,6 +14,7 @@ class SearchController < ApplicationController if params[:include_blurbs].present? search_args[:include_blurbs] = params[:include_blurbs] == "true" end + search_args[:search_for_id] = true if params[:search_for_id].present? search_context = params[:search_context] if search_context.present? diff --git a/lib/search.rb b/lib/search.rb index 0be363c47..0dc1535a3 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -111,7 +111,8 @@ class Search return nil if @term.blank? || @term.length < (@opts[:min_search_term_length] || SiteSetting.min_search_term_length) # If the term is a number or url to a topic, just include that topic - if @results.type_filter == 'topic' + if @opts[:search_for_id] && @results.type_filter == 'topic' + return single_topic(@term.to_i).as_json if @term =~ /^\d+$/ begin route = Rails.application.routes.recognize_path(@term) return single_topic(route[:topic_id]).as_json if route[:topic_id].present? diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb index 3fb68152e..b2f9a2d66 100644 --- a/spec/components/search_spec.rb +++ b/spec/components/search_spec.rb @@ -191,8 +191,18 @@ describe Search do end end + context "search for a topic by id" do + let(:result) { first_of_type(Search.new(topic.id, type_filter: 'topic', search_for_id: true, min_search_term_length: 1).execute, 'topic') } + + it 'returns the topic' do + result.should be_present + result[:title].should == topic.title + result[:url].should == topic.relative_url + end + end + context "search for a topic by url" do - let(:result) { first_of_type(Search.new(topic.relative_url, type_filter: 'topic').execute, 'topic') } + let(:result) { first_of_type(Search.new(topic.relative_url, search_for_id: true, type_filter: 'topic').execute, 'topic') } it 'returns the topic' do result.should be_present diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index e51297860..b93cf7de9 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -4,38 +4,43 @@ describe SearchController do let(:search_context) { {type: 'user', id: 'eviltrout'} } - it 'performs the query' do - guardian = Guardian.new - Guardian.stubs(:new).returns(guardian) + context "basics" do + let(:guardian) { Guardian.new } + let(:search) { mock() } - search = mock() - Search.expects(:new).with('test', guardian: guardian).returns(search) - search.expects(:execute) + before do + Guardian.stubs(:new).returns(guardian) + end - xhr :get, :query, term: 'test' + it 'performs the query' do + Search.expects(:new).with('test', guardian: guardian).returns(search) + search.expects(:execute) + + xhr :get, :query, term: 'test' + end + + it 'performs the query with a filter' do + Search.expects(:new).with('test', guardian: guardian, type_filter: 'topic').returns(search) + search.expects(:execute) + + xhr :get, :query, term: 'test', type_filter: 'topic' + end + + it "performs the query and returns results including blurbs" do + Search.expects(:new).with('test', guardian: guardian, include_blurbs: true).returns(search) + search.expects(:execute) + + xhr :get, :query, term: 'test', include_blurbs: 'true' + end + + it 'performs the query with a filter and passes through search_for_id' do + Search.expects(:new).with('test', guardian: guardian, search_for_id: true, type_filter: 'topic').returns(search) + search.expects(:execute) + + xhr :get, :query, term: 'test', type_filter: 'topic', search_for_id: true + end end - it 'performs the query with a filter' do - guardian = Guardian.new - Guardian.stubs(:new).returns(guardian) - - search = mock() - Search.expects(:new).with('test', guardian: guardian, type_filter: 'topic').returns(search) - search.expects(:execute) - - xhr :get, :query, term: 'test', type_filter: 'topic' - end - - it "performs the query and returns results including blurbs" do - guardian = Guardian.new - Guardian.stubs(:new).returns(guardian) - - search = mock() - Search.expects(:new).with('test', guardian: guardian, include_blurbs: true).returns(search) - search.expects(:execute) - - xhr :get, :query, term: 'test', include_blurbs: 'true' - end context "search context" do From 14890a6002c7602568f24ae6b80bad00c90c6051 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Thu, 28 Aug 2014 15:09:36 -0400 Subject: [PATCH 18/43] FEATURE: add a way to map arbitrary urls to a topic, post, or category. Useful for sites that have migrated to Discourse and want to redirect from their old site to Discourse with 301 redirects. --- app/controllers/permalinks_controller.rb | 12 ++++ app/models/permalink.rb | 21 ++++++ config/application.rb | 4 ++ config/routes.rb | 1 + config/routes_last.rb | 10 +++ .../20140828172407_create_permalinks.rb | 14 ++++ .../controllers/permalinks_controller_spec.rb | 19 +++++ spec/fabricators/permalink_fabricator.rb | 3 + spec/models/permalink_spec.rb | 72 +++++++++++++++++++ 9 files changed, 156 insertions(+) create mode 100644 app/controllers/permalinks_controller.rb create mode 100644 app/models/permalink.rb create mode 100644 config/routes_last.rb create mode 100644 db/migrate/20140828172407_create_permalinks.rb create mode 100644 spec/controllers/permalinks_controller_spec.rb create mode 100644 spec/fabricators/permalink_fabricator.rb create mode 100644 spec/models/permalink_spec.rb diff --git a/app/controllers/permalinks_controller.rb b/app/controllers/permalinks_controller.rb new file mode 100644 index 000000000..6a01035ac --- /dev/null +++ b/app/controllers/permalinks_controller.rb @@ -0,0 +1,12 @@ +class PermalinksController < ApplicationController + skip_before_filter :check_xhr, :preload_json + + def show + permalink = Permalink.find_by_url(params[:url]) + if permalink && permalink.target_url + redirect_to permalink.target_url, status: :moved_permanently + else + raise Discourse::NotFound + end + end +end diff --git a/app/models/permalink.rb b/app/models/permalink.rb new file mode 100644 index 000000000..d0d06e1f0 --- /dev/null +++ b/app/models/permalink.rb @@ -0,0 +1,21 @@ +class Permalink < ActiveRecord::Base + belongs_to :topic + belongs_to :post + belongs_to :category + + before_validation :normalize_url + + def normalize_url + if self.url + self.url = self.url.strip + self.url = self.url[1..-1] if url[0,1] == '/' + end + end + + def target_url + return post.url if post + return topic.relative_url if topic + return category.url if category + nil + end +end diff --git a/config/application.rb b/config/application.rb index f290bd5c9..357621b23 100644 --- a/config/application.rb +++ b/config/application.rb @@ -148,6 +148,10 @@ module Discourse require 'auth' Discourse.activate_plugins! unless Rails.env.test? and ENV['LOAD_PLUGINS'] != "1" + initializer :add_last_routes, :after => :add_routing_paths do |app| + app.routes_reloader.paths << File.join(Rails.root, 'config', 'routes_last.rb') + end + config.after_initialize do # So open id logs somewhere sane OpenID::Util.logger = Rails.logger diff --git a/config/routes.rb b/config/routes.rb index e375a258b..3cb72a1fa 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -425,4 +425,5 @@ Discourse::Application.routes.draw do # special case for top root to: "list#top", constraints: HomePageConstraint.new("top"), :as => "top_lists" + # See config/routes_last.rb for more end diff --git a/config/routes_last.rb b/config/routes_last.rb new file mode 100644 index 000000000..5382e5f9e --- /dev/null +++ b/config/routes_last.rb @@ -0,0 +1,10 @@ +# These routes must be loaded after all others. +# Routes are loaded in this order: +# +# 1. config/routes.rb +# 2. routes in engines +# 3. config/routes_last.rb + +Discourse::Application.routes.draw do + get "*url", to: 'permalinks#show' +end diff --git a/db/migrate/20140828172407_create_permalinks.rb b/db/migrate/20140828172407_create_permalinks.rb new file mode 100644 index 000000000..5e4d9ce9b --- /dev/null +++ b/db/migrate/20140828172407_create_permalinks.rb @@ -0,0 +1,14 @@ +class CreatePermalinks < ActiveRecord::Migration + def change + create_table :permalinks do |t| + t.string :url, null: false + t.integer :topic_id + t.integer :post_id + t.integer :category_id + + t.timestamps + end + + add_index :permalinks, :url + end +end diff --git a/spec/controllers/permalinks_controller_spec.rb b/spec/controllers/permalinks_controller_spec.rb new file mode 100644 index 000000000..afc50e364 --- /dev/null +++ b/spec/controllers/permalinks_controller_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe PermalinksController do + describe 'show' do + it "should redirect to a permalink's target_url with status 301" do + permalink = Fabricate(:permalink) + Permalink.any_instance.stubs(:target_url).returns('/t/the-topic-slug/42') + get :show, url: permalink.url + response.should redirect_to('/t/the-topic-slug/42') + response.status.should == 301 + end + + it 'return 404 if permalink record does not exist' do + get :show, url: '/not/a/valid/url' + response.status.should == 404 + end + end + +end diff --git a/spec/fabricators/permalink_fabricator.rb b/spec/fabricators/permalink_fabricator.rb new file mode 100644 index 000000000..22a3c59de --- /dev/null +++ b/spec/fabricators/permalink_fabricator.rb @@ -0,0 +1,3 @@ +Fabricator(:permalink) do + url { sequence(:url) {|i| "my/#{i}/url" } } +end \ No newline at end of file diff --git a/spec/models/permalink_spec.rb b/spec/models/permalink_spec.rb new file mode 100644 index 000000000..de5118fa3 --- /dev/null +++ b/spec/models/permalink_spec.rb @@ -0,0 +1,72 @@ +require "spec_helper" + +describe Permalink do + + describe "new record" do + it "strips blanks" do + permalink = described_class.create(url: " my/old/url ") + permalink.url.should == "my/old/url" + end + + it "removes leading slash" do + permalink = described_class.create(url: "/my/old/url") + permalink.url.should == "my/old/url" + end + end + + describe "target_url" do + + let(:permalink) { Fabricate.build(:permalink) } + let(:topic) { Fabricate(:topic) } + let(:post) { Fabricate(:post, topic: topic) } + let(:category) { Fabricate(:category) } + subject(:target_url) { permalink.target_url } + + it "returns a topic url when topic_id is set" do + permalink.topic_id = topic.id + target_url.should == topic.relative_url + end + + it "returns nil when topic_id is set but topic is not found" do + permalink.topic_id = 99999 + target_url.should be_nil + end + + it "returns a post url when post_id is set" do + permalink.post_id = post.id + target_url.should == post.url + end + + it "returns nil when post_id is set but post is not found" do + permalink.post_id = 99999 + target_url.should be_nil + end + + it "returns a post url when post_id and topic_id are both set" do + permalink.post_id = post.id + permalink.topic_id = topic.id + target_url.should == post.url + end + + it "returns a category url when category_id is set" do + permalink.category_id = category.id + target_url.should == category.url + end + + it "returns nil when category_id is set but category is not found" do + permalink.category_id = 99999 + target_url.should be_nil + end + + it "returns a post url when topic_id, post_id, and category_id are all set for some reason" do + permalink.post_id = post.id + permalink.topic_id = topic.id + permalink.category_id = category.id + target_url.should == post.url + end + + it "returns nil when nothing is set" do + target_url.should be_nil + end + end +end From 464a922910ca4c5f9a482c3afa267f50ef3ad269 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Thu, 28 Aug 2014 16:16:39 -0400 Subject: [PATCH 19/43] FIX: permalinks table: increase limit of url column, make url index unique --- ...20140828200231_make_url_col_bigger_in_permalinks.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 db/migrate/20140828200231_make_url_col_bigger_in_permalinks.rb diff --git a/db/migrate/20140828200231_make_url_col_bigger_in_permalinks.rb b/db/migrate/20140828200231_make_url_col_bigger_in_permalinks.rb new file mode 100644 index 000000000..f2767d1a2 --- /dev/null +++ b/db/migrate/20140828200231_make_url_col_bigger_in_permalinks.rb @@ -0,0 +1,10 @@ +class MakeUrlColBiggerInPermalinks < ActiveRecord::Migration + def up + remove_index :permalinks, :url + change_column :permalinks, :url, :string, limit: 1000, null: false + add_index :permalinks, :url, unique: true + end + + def down + end +end From 9e2c72e2777d614b9f919d98bdccdb7e41958b12 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 28 Aug 2014 17:02:26 -0400 Subject: [PATCH 20/43] Allow restoring from backups in development mode even if the setting is off. --- .../admin/controllers/admin-backups-index.js.es6 | 2 +- .../javascripts/admin/models/backup_status.js | 13 +++---------- .../javascripts/admin/routes/admin_backups_route.js | 3 ++- config/site_settings.yml | 2 +- lib/backup_restore.rb | 1 + 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 index 15fe49339..a7ecd0c30 100644 --- a/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 @@ -9,7 +9,7 @@ export default Ember.ArrayController.extend({ restoreDisabled: Em.computed.alias("status.restoreDisabled"), restoreTitle: function() { - if (!Discourse.SiteSettings.allow_restore) { + if (!this.get('status.allowRestore')) { return I18n.t("admin.backups.operations.restore.is_disabled"); } else if (this.get("status.isOperationRunning")) { return I18n.t("admin.backups.operation_already_running"); diff --git a/app/assets/javascripts/admin/models/backup_status.js b/app/assets/javascripts/admin/models/backup_status.js index c11c87581..e67a34bc3 100644 --- a/app/assets/javascripts/admin/models/backup_status.js +++ b/app/assets/javascripts/admin/models/backup_status.js @@ -1,16 +1,9 @@ -/** - Data model for representing the status of backup/restore - - @class BackupStatus - @extends Discourse.Model - @namespace Discourse - @module Discourse -**/ Discourse.BackupStatus = Discourse.Model.extend({ restoreDisabled: Em.computed.not("restoreEnabled"), restoreEnabled: function() { - return Discourse.SiteSettings.allow_restore && !this.get("isOperationRunning"); - }.property("isOperationRunning") + return this.get('allowRestore') && !this.get("isOperationRunning"); + }.property("isOperationRunning", "allowRestore") + }); diff --git a/app/assets/javascripts/admin/routes/admin_backups_route.js b/app/assets/javascripts/admin/routes/admin_backups_route.js index 6580ed075..da5dd9a8a 100644 --- a/app/assets/javascripts/admin/routes/admin_backups_route.js +++ b/app/assets/javascripts/admin/routes/admin_backups_route.js @@ -31,7 +31,8 @@ Discourse.AdminBackupsRoute = Discourse.Route.extend({ }).then(function (status) { return Discourse.BackupStatus.create({ isOperationRunning: status.is_operation_running, - canRollback: status.can_rollback + canRollback: status.can_rollback, + allowRestore: status.allow_restore }); }); }, diff --git a/config/site_settings.yml b/config/site_settings.yml index 4d5425a4e..9e0d0a4e3 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -628,7 +628,7 @@ legal: backups: allow_restore: - client: true + client: false default: false maximum_backups: client: true diff --git a/lib/backup_restore.rb b/lib/backup_restore.rb index 9872cf911..b59943761 100644 --- a/lib/backup_restore.rb +++ b/lib/backup_restore.rb @@ -56,6 +56,7 @@ module BackupRestore { is_operation_running: is_operation_running?, can_rollback: can_rollback?, + allow_restore: Rails.env.development? || SiteSetting.allow_restore } end From 0d0225133cf01a1bb676901a40a351c43cf9f117 Mon Sep 17 00:00:00 2001 From: riking Date: Tue, 26 Aug 2014 17:30:12 -0700 Subject: [PATCH 21/43] FIX: Failed incoming emails could create empty topics A failure condition is eliminated where a topic would be created, but post creation would fail, leaving the forum with a topic without any posts. By asking PostCreator to create the topic instead, inside of its transaction, this failure condition is eliminated. Additionally, attachments are restored to working status. Previously, the attachment code would build up the post raw, but then drop it and not do anything with the result (creating orphaned uploads). By actually placing the raw value back in the options hash, it is included in the created post. --- lib/email/receiver.rb | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb index 3845a9798..ff3db75f9 100644 --- a/lib/email/receiver.rb +++ b/lib/email/receiver.rb @@ -94,7 +94,6 @@ module Email {type: :invalid, obj: nil} end - private def parse_body html = nil @@ -168,37 +167,37 @@ module Email [/quote]" end + private + def create_reply - create_post_with_attachments(email_log.user, @body, @email_log.topic_id, @email_log.post.post_number) + create_post_with_attachments(@email_log.user, + raw: @body, + topic_id: @email_log.topic_id, + reply_to_post_number: @email_log.post.post_number) end def create_new_topic - topic = TopicCreator.new( - @user, - Guardian.new(@user), - category: @category_id, - title: @message.subject, - ).create - - post = create_post_with_attachments(@user, @body, topic.id) + post = create_post_with_attachments(@user, + raw: @body, + title: @message.subject, + category: @category_id) EmailLog.create( email_type: "topic_via_incoming_email", - to_address: @message.to.first, - topic_id: topic.id, + to_address: @message.from.first, # pick from address because we want the user's email + topic_id: post.topic.id, user_id: @user.id, ) post end - def create_post_with_attachments(user, raw, topic_id, reply_to_post_number=nil) + def create_post_with_attachments(user, post_opts={}) options = { - raw: raw, - topic_id: topic_id, cooking_options: { traditional_markdown_linebreaks: true }, - } - options[:reply_to_post_number] = reply_to_post_number if reply_to_post_number + }.merge(post_opts) + + raw = options[:raw] # deal with attachments @message.attachments.each do |attachment| @@ -215,9 +214,10 @@ module Email ensure tmp.close! end - end + options[:raw] = raw + create_post(user, options) end @@ -232,9 +232,11 @@ module Email def create_post(user, options) creator = PostCreator.new(user, options) post = creator.create + if creator.errors.present? raise InvalidPost, creator.errors.full_messages.join("\n") end + post end From cb55ef47027e8994cbd093b6938989057f03cc9d Mon Sep 17 00:00:00 2001 From: riking Date: Tue, 26 Aug 2014 12:31:47 -0700 Subject: [PATCH 22/43] Add Email::HtmlCleaner for email processing This class is in charge of stripping out most of the crap from the HTML portion of emails that email clients generate, so that it can be sanely post-processed for signatures and quoting boundaries. --- lib/email/html_cleaner.rb | 120 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 lib/email/html_cleaner.rb diff --git a/lib/email/html_cleaner.rb b/lib/email/html_cleaner.rb new file mode 100644 index 000000000..19e4b4173 --- /dev/null +++ b/lib/email/html_cleaner.rb @@ -0,0 +1,120 @@ +module Email + # HtmlCleaner cleans up the extremely dirty HTML that many email clients + # generate by stripping out any excess divs or spans, removing styling in + # the process (which also makes the html more suitable to be parsed as + # Markdown). + class HtmlCleaner + # Elements to hoist all children out of + HTML_HOIST_ELEMENTS = %w(div span font table tbody th tr td) + # Node types to always delete + HTML_DELETE_ELEMENT_TYPES = [Nokogiri::XML::Node::DTD_NODE, + Nokogiri::XML::Node::COMMENT_NODE, + ] + + # Private variables: + # @doc - nokogiri document + # @out - same as @doc, but only if trimming has occured + def initialize(html) + if String === html + @doc = Nokogiri::HTML(html) + else + @doc = html + end + end + + class << self + # Email::HtmlCleaner.trim(inp, opts={}) + # + # Arguments: + # inp - Either a HTML string or a Nokogiri document. + # Options: + # :return => :doc, :string + # Specify the desired return type. + # Defaults to the type of the input. + # A value of :string is equivalent to calling get_document_text() + # on the returned document. + def trim(inp, opts={}) + cleaner = HtmlCleaner.new(inp) + + opts[:return] ||= ((String === inp) ? :string : :doc) + + if opts[:return] == :string + cleaner.output_html + else + cleaner.output_document + end + end + + # Email::HtmlCleaner.get_document_text(doc) + # + # Get the body portion of the document, including html, as a string. + def get_document_text(doc) + body = doc.xpath('//body') + if body + body.inner_html + else + doc.inner_html + end + end + end + + def output_document + @out ||= begin + doc = @doc + trim_process_node doc + add_newlines doc + doc + end + end + + def output_html + HtmlCleaner.get_document_text(output_document) + end + + private + + def add_newlines(doc) + doc.xpath('//br').each do |br| + br.replace(Nokogiri::XML::Text.new("\n", doc)) + end + end + + def trim_process_node(node) + if should_hoist?(node) + hoisted = trim_hoist_element node + hoisted.each { |child| trim_process_node child } + elsif should_delete?(node) + node.remove + else + if children = node.children + children.each { |child| trim_process_node child } + end + end + + node + end + + def trim_hoist_element(element) + hoisted = [] + element.children.each do |child| + element.before(child) + hoisted << child + end + element.remove + hoisted + end + + def should_hoist?(node) + return false unless node.element? + HTML_HOIST_ELEMENTS.include? node.name + end + + def should_delete?(node) + return true if HTML_DELETE_ELEMENT_TYPES.include? node.type + return true if node.element? && node.name == 'head' + return true if node.text? && node.text.strip.blank? + + false + end + end +end From 0a09593f3b6193010c0d19a20df8dd7d0d56cb52 Mon Sep 17 00:00:00 2001 From: riking Date: Tue, 26 Aug 2014 17:31:51 -0700 Subject: [PATCH 23/43] FIX: Prefer HTML in incoming emails, heavily refactor email receiver This commit heavily refactors Email::Receiver to both better handle different emails and improve testability. A primary focus of the refactor is reducing the usage of class variables, in favor of actually passing parameters - making it possible for multiple tests to use the same Receiver instance. The EmailLog reported when a topic is created is reflected to put the user's email in the to_address field, instead of the system address. The discourse_email_parser function is renamed to discourse_email_trimmer, and additional stopping conditions are added to make up for EmailReplyParser's inability to deal with html at the start of a line. The force_encoding calls are refactored out to a 'fix_charset' method. parse_body is renamed to select_body, and the scrub_html method is dropped in favor of the new HtmlCleaner class. A new parse_body method is added, which performs the job of the removed lines of code in the 'process' method. EmailUnparsableError is redefined again, to be encoding errors (when the declared encoding is not what was delivered). --- lib/email/receiver.rb | 108 +++++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 49 deletions(-) diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb index ff3db75f9..c644950ce 100644 --- a/lib/email/receiver.rb +++ b/lib/email/receiver.rb @@ -1,3 +1,4 @@ +require 'email/html_cleaner' # # Handles an incoming message # @@ -26,20 +27,12 @@ module Email def process raise EmptyEmailError if @raw.blank? - @message = Mail.new(@raw) + message = Mail.new(@raw) - # First remove the known discourse stuff. - parse_body - raise EmptyEmailError if @body.blank? - - # Then run the github EmailReplyParser on it in case we didn't catch it - @body = EmailReplyParser.read(@body).visible_text.force_encoding('UTF-8') - - discourse_email_parser - raise EmailUnparsableError if @body.blank? + body = parse_body message dest_info = {type: :invalid, obj: nil} - @message.to.each do |to_address| + message.to.each do |to_address| if dest_info[:type] == :invalid dest_info = check_address to_address end @@ -47,6 +40,10 @@ module Email raise BadDestinationAddress if dest_info[:type] == :invalid + # TODO get to a state where we can remove this + @message = message + @body = body + if dest_info[:type] == :category raise BadDestinationAddress unless SiteSetting.email_in category = dest_info[:obj] @@ -74,6 +71,8 @@ module Email create_reply end + rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e + raise EmailUnparsableError.new(e) end def check_address(address) @@ -94,56 +93,63 @@ module Email {type: :invalid, obj: nil} end + def parse_body(message) + body = select_body message + raise EmptyEmailError if body.strip.blank? - def parse_body + body = discourse_email_trimmer body + raise EmptyEmailError if body.strip.blank? + + body = EmailReplyParser.parse_reply body + raise EmptyEmailError if body.strip.blank? + + body + end + + def select_body(message) html = nil - - # If the message is multipart, find the best type for our purposes - if @message.multipart? - if p = @message.text_part - @body = p.charset ? p.body.decoded.force_encoding(p.charset).encode("UTF-8").to_s : p.body.to_s - return @body - elsif p = @message.html_part - html = p.charset ? p.body.decoded.force_encoding(p.charset).encode("UTF-8").to_s : p.body.to_s + # If the message is multipart, return that part (favor html) + if message.multipart? + html = fix_charset message.html_part + text = fix_charset message.text_part + # TODO picking text if available may be better + if text && !html + return text end + elsif message.content_type =~ /text\/html/ + html = fix_charset message end - if @message.content_type =~ /text\/html/ - if defined? @message.charset - html = @message.body.decoded.force_encoding(@message.charset).encode("UTF-8").to_s - else - html = @message.body.to_s - end + if html + body = HtmlCleaner.new(html).output_html + else + body = fix_charset message end - if html.present? - @body = scrub_html(html) - return @body - end - - @body = @message.charset ? @message.body.decoded.force_encoding(@message.charset).encode("UTF-8").to_s.strip : @message.body.to_s - # Certain trigger phrases that means we didn't parse correctly - @body = nil if @body =~ /Content\-Type\:/ || - @body =~ /multipart\/alternative/ || - @body =~ /text\/plain/ + if body =~ /Content\-Type\:/ || body =~ /multipart\/alternative/ || body =~ /text\/plain/ + raise EmptyEmailError + end - @body + body end - def scrub_html(html) - # If we have an HTML message, strip the markup - doc = Nokogiri::HTML(html) + # Force encoding to UTF-8 on a Mail::Message or Mail::Part + def fix_charset(object) + return nil if object.nil? - # Blackberry is annoying in that it only provides HTML. We can easily extract it though - content = doc.at("#BB10_response_div") - return content.text if content.present? - - doc.xpath("//text()").text + if object.charset + object.body.decoded.force_encoding(object.charset).encode("UTF-8").to_s + else + object.body.to_s + end end - def discourse_email_parser - lines = @body.scrub.lines.to_a + REPLYING_HEADER_LABELS = ['From', 'Sent', 'To', 'Subject', 'Reply To'] + REPLYING_HEADER_REGEX = Regexp.union(REPLYING_HEADER_LABELS.map { |lbl| "#{lbl}:" }) + + def discourse_email_trimmer(body) + lines = body.scrub.lines.to_a range_end = 0 lines.each_with_index do |l, idx| @@ -154,11 +160,15 @@ module Email # Let's try it and see how well it works. (l =~ /\d{4}/ && l =~ /\d:\d\d/ && 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 = idx end - @body = lines[0..range_end].join - @body.strip! + lines[0..range_end].join.strip end def wrap_body_in_quote(user_email) From 1c9f6159cd6a03a4dc74b0912f475fc73c39d613 Mon Sep 17 00:00:00 2001 From: riking Date: Tue, 26 Aug 2014 17:08:53 -0700 Subject: [PATCH 24/43] Update the Receiver and PollMailbox specs for the changes Tests are both added, moved, and deleted. Add test for topic not being created Move html_only.eml to parse_body testing section --- spec/components/email/receiver_spec.rb | 271 ++++++++++++++------- spec/fixtures/emails/boundary.eml | 4 +- spec/fixtures/emails/multiline_wrote.eml | 23 -- spec/fixtures/emails/multipart.eml | 67 ----- spec/fixtures/emails/paragraphs.cooked | 7 + spec/fixtures/emails/too_many_mentions.eml | 31 +++ spec/fixtures/emails/too_short.eml | 21 ++ spec/jobs/poll_mailbox_spec.rb | 4 +- spec/spec_helper.rb | 1 + 9 files changed, 247 insertions(+), 182 deletions(-) delete mode 100644 spec/fixtures/emails/multiline_wrote.eml delete mode 100644 spec/fixtures/emails/multipart.eml create mode 100644 spec/fixtures/emails/paragraphs.cooked create mode 100644 spec/fixtures/emails/too_many_mentions.eml create mode 100644 spec/fixtures/emails/too_short.eml diff --git a/spec/components/email/receiver_spec.rb b/spec/components/email/receiver_spec.rb index 71bc6a4d5..c5ef25543 100644 --- a/spec/components/email/receiver_spec.rb +++ b/spec/components/email/receiver_spec.rb @@ -8,124 +8,225 @@ describe Email::Receiver do before do SiteSetting.reply_by_email_address = "reply+%{reply_key}@appmail.adventuretime.ooo" SiteSetting.email_in = false + SiteSetting.title = "Discourse" end - describe 'invalid emails' do + describe 'parse_body' do + def test_parse_body(mail_string) + Email::Receiver.new(nil).parse_body(Mail::Message.new mail_string) + end + it "raises EmptyEmailError if the message is blank" do - expect { Email::Receiver.new("").process }.to raise_error(Email::Receiver::EmptyEmailError) + expect { test_parse_body("") }.to raise_error(Email::Receiver::EmptyEmailError) end it "raises EmptyEmailError if the message is not an email" do - expect { Email::Receiver.new("asdf" * 30).process}.to raise_error(Email::Receiver::EmptyEmailError) + expect { test_parse_body("asdf" * 30) }.to raise_error(Email::Receiver::EmptyEmailError) end - it "raises EmailUnparsableError if there is no reply content" do - expect { Email::Receiver.new(fixture_file("emails/no_content_reply.eml")).process}.to raise_error(Email::Receiver::EmailUnparsableError) + 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) end - end - describe "with multipart" do - let(:reply_below) { fixture_file("emails/multipart.eml") } - let(:receiver) { Email::Receiver.new(reply_below) } - - it "processes correctly" do - expect { receiver.process}.to raise_error(Email::Receiver::EmailLogNotFound) - expect(receiver.body).to eq( -"So presumably all the quoted garbage and my (proper) signature will get -stripped from my reply?") + pending "raises EmailUnparsableError if the headers are corrupted" do + expect { ; }.to raise_error(Email::Receiver::EmailUnparsableError) end - end - describe "html only" do - let(:reply_below) { fixture_file("emails/html_only.eml") } - let(:receiver) { Email::Receiver.new(reply_below) } - - it "processes correctly" do - expect { receiver.process}.to raise_error(Email::Receiver::EmailLogNotFound) - expect(receiver.body).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 "can parse the html section" do + test_parse_body(fixture_file("emails/html_only.eml")).should == "The EC2 instance - I've seen that there tends to be odd and " + + "unrecommended settings on the Bitnami installs that I've checked out." end - end - describe "it supports a dutch reply" do - let(:dutch) { fixture_file("emails/dutch.eml") } - let(:receiver) { Email::Receiver.new(dutch) } - - it "processes correctly" do - expect { receiver.process}.to raise_error(Email::Receiver::EmailLogNotFound) - expect(receiver.body).to eq("Dit is een antwoord in het Nederlands.") + it "supports a Dutch reply" do + test_parse_body(fixture_file("emails/dutch.eml")).should == "Dit is een antwoord in het Nederlands." end - end - describe "It supports a non english reply" do - let(:hebrew) { fixture_file("emails/hebrew.eml") } - let(:receiver) { Email::Receiver.new(hebrew) } - - it "processes correctly" do + it "supports a Hebrew reply" do I18n.expects(:t).with('user_notifications.previous_discussion').returns('כלטוב') - expect { receiver.process}.to raise_error(Email::Receiver::EmailLogNotFound) - expect(receiver.body).to eq("שלום") + + # The force_encoding call is only needed for the test - it is passed on fine to the cooked post + test_parse_body(fixture_file("emails/hebrew.eml")).force_encoding("UTF-8").should == "שלום" end - end - describe "It supports a non UTF-8 reply" do - let(:big5) { fixture_file("emails/big5.eml") } - let(:receiver) { Email::Receiver.new(big5) } - - it "processes correctly" do + it "supports a BIG5-encoded reply" do I18n.expects(:t).with('user_notifications.previous_discussion').returns('媽!我上電視了!') - expect { receiver.process}.to raise_error(Email::Receiver::EmailLogNotFound) - expect(receiver.body).to eq("媽!我上電視了!") + + # The force_encoding call is only needed for the test - it is passed on fine to the cooked post + test_parse_body(fixture_file("emails/big5.eml")).force_encoding("UTF-8").should == "媽!我上電視了!" end - end - describe "via" do - let(:wrote) { fixture_file("emails/via_line.eml") } - let(:receiver) { Email::Receiver.new(wrote) } + it "removes 'via' lines if they match the site title" do + SiteSetting.title = "Discourse" - it "removes via lines if we know them" do - expect { receiver.process}.to raise_error(Email::Receiver::EmailLogNotFound) - expect(receiver.body).to eq("Hello this email has content!") + test_parse_body(fixture_file("emails/via_line.eml")).should == "Hello this email has content!" end - end - describe "if wrote is on a second line" do - let(:wrote) { fixture_file("emails/multiline_wrote.eml") } - let(:receiver) { Email::Receiver.new(wrote) } - - it "processes correctly" do - expect { receiver.process}.to raise_error(Email::Receiver::EmailLogNotFound) - expect(receiver.body).to eq("Thanks!") + it "removes the 'Previous Discussion' marker" do + test_parse_body(fixture_file("emails/previous.eml")).should == "This will not include the previous discussion that is present in this email." end - end - describe "remove previous discussion" do - let(:previous) { fixture_file("emails/previous.eml") } - let(:receiver) { Email::Receiver.new(previous) } - - it "processes correctly" do - expect { receiver.process}.to raise_error(Email::Receiver::EmailLogNotFound) - expect(receiver.body).to eq("This will not include the previous discussion that is present in this email.") - end - end - - describe "multiple paragraphs" do - let(:paragraphs) { fixture_file("emails/paragraphs.eml") } - let(:receiver) { Email::Receiver.new(paragraphs) } - - it "processes correctly" do - expect { receiver.process}.to raise_error(Email::Receiver::EmailLogNotFound) - expect(receiver.body).to eq( + it "handles multiple paragraphs" do + test_parse_body(fixture_file("emails/paragraphs.eml")). + should == ( "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.") +Thanks for listening." + ) end end + describe "posting replies" do + let(:reply_key) { raise "Override this in a lower describe block" } + let(:email_raw) { raise "Override this in a lower describe block" } + # ---- + let(:receiver) { Email::Receiver.new(email_raw) } + let(:post) { create_post } + let(:topic) { post.topic } + let(:posting_user) { post.user } + 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: post.topic_id, + email_type: 'user_posted', + user: replying_user, + user_id: replying_user.id, + to_address: replying_user_email + ) } + + before do + email_log.save + end + + # === Success Posting === + + describe "valid_reply.eml" do + let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' } + let!(:email_raw) { fixture_file("emails/valid_reply.eml") } + + it "creates a post with the correct content" do + start_count = topic.posts.count + + receiver.process + + topic.posts.count.should == (start_count + 1) + topic.posts.last.cooked.strip.should == fixture_file("emails/valid_reply.cooked").strip + end + end + + describe "paragraphs.eml" do + let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' } + let!(:email_raw) { fixture_file("emails/paragraphs.eml") } + + it "cooks multiple paragraphs with traditional Markdown linebreaks" do + start_count = topic.posts.count + + receiver.process + + topic.posts.count.should == (start_count + 1) + topic.posts.last.cooked.strip.should == fixture_file("emails/paragraphs.cooked").strip + topic.posts.last.cooked.should_not match /
/ + Upload.find_by(sha1: upload_sha).should_not be_nil + 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 + + end + + describe "posting a new topic" 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) + + Topic.count.should == before_topic_count + end + + end + + end + def fill_email(mail, from, to, body = nil, subject = nil) result = mail.gsub("FROM", from).gsub("TO", to) if body @@ -181,12 +282,6 @@ greatest show ever created. Everyone should watch it. expect(receiver.body).to eq(reply_body) expect(receiver.email_log).to eq(email_log) - - attachment_email = fixture_file("emails/attachment.eml") - attachment_email = fill_email(attachment_email, "test@test.com", to) - r = Email::Receiver.new(attachment_email) - expect { r.process }.to_not raise_error - expect(r.body).to match(/here is an image attachment\n\n/) end end diff --git a/spec/fixtures/emails/boundary.eml b/spec/fixtures/emails/boundary.eml index 92eb4347f..1250fe498 100644 --- a/spec/fixtures/emails/boundary.eml +++ b/spec/fixtures/emails/boundary.eml @@ -18,7 +18,7 @@ Content-Type: text/plain; charset=ISO-8859-1 I'll look into it, thanks! -On Wednesday, June 19, 2013, jake via Adventure Time wrote: +On Wednesday, June 19, 2013, jake via Discourse wrote: > jake mentioned you in 'peppermint butler is missing' on Adventure > Time: @@ -58,4 +58,4 @@ p> ime.ooo/user_preferences" target=3D"_blank">user preferences.

---001a11c206a073876a04df81d2a9-- \ No newline at end of file +--001a11c206a073876a04df81d2a9-- diff --git a/spec/fixtures/emails/multiline_wrote.eml b/spec/fixtures/emails/multiline_wrote.eml deleted file mode 100644 index 0829990dc..000000000 --- a/spec/fixtures/emails/multiline_wrote.eml +++ /dev/null @@ -1,23 +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 - -Thanks! - -On 24 Jul 2013, at 15:58, walter via Discourse -wrote: - - walter July 24 - -You look great today Walter. diff --git a/spec/fixtures/emails/multipart.eml b/spec/fixtures/emails/multipart.eml deleted file mode 100644 index b61f9fa48..000000000 --- a/spec/fixtures/emails/multipart.eml +++ /dev/null @@ -1,67 +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/alternative; - boundary="------------070503080300090900010604" - -This is a multi-part message in MIME format. ---------------070503080300090900010604 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 7bit - -So presumably all the quoted garbage and my (proper) signature will get -stripped from my reply? - --- -Anakin Skywalker | `One of the main causes of the fall of -evildad@darthvader.ca | the Roman Empire was that, lacking zero, - | they had no way to indicate successful - | termination of their C programs.' - Firth - - ---------------070503080300090900010604 -Content-Type: text/html; charset=UTF-8 -Content-Transfer-Encoding: 7bit - - - - - - -
On 13-06-19 06:14 PM, Han Solo via - Death Star wrote:
-
-
-

Han Solo just sent you a private message

-
-

I got it here! Yay it worked!

-
-

Please visit this link to respond: http://darthvader.ca/t/regarding-your-post-in-site-customization-not-working/7641/2

-

To unsubscribe from these emails, visit your user - preferences.

-
- So presumably all the quoted garbage and my (proper) signature will - get stripped from my reply?
-
-
--
-Anakin Skywalker               | `One of the main causes of the fall of
-evildad@darthvader.ca       | the Roman Empire was that, lacking zero,
-                            | they had no way to indicate successful
-                            | termination of their C programs.' - Firth
-
- - - ---------------070503080300090900010604-- diff --git a/spec/fixtures/emails/paragraphs.cooked b/spec/fixtures/emails/paragraphs.cooked new file mode 100644 index 000000000..da83260e0 --- /dev/null +++ b/spec/fixtures/emails/paragraphs.cooked @@ -0,0 +1,7 @@ +

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.

\ No newline at end of file diff --git a/spec/fixtures/emails/too_many_mentions.eml b/spec/fixtures/emails/too_many_mentions.eml new file mode 100644 index 000000000..9cc7b75c9 --- /dev/null +++ b/spec/fixtures/emails/too_many_mentions.eml @@ -0,0 +1,31 @@ +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 +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 diff --git a/spec/fixtures/emails/too_short.eml b/spec/fixtures/emails/too_short.eml new file mode 100644 index 000000000..54fed0f98 --- /dev/null +++ b/spec/fixtures/emails/too_short.eml @@ -0,0 +1,21 @@ +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 + + ++1 \ No newline at end of file diff --git a/spec/jobs/poll_mailbox_spec.rb b/spec/jobs/poll_mailbox_spec.rb index b10dcc09e..3084b2b2f 100644 --- a/spec/jobs/poll_mailbox_spec.rb +++ b/spec/jobs/poll_mailbox_spec.rb @@ -202,9 +202,9 @@ describe Jobs::PollMailbox do email.should be_deleted end - it "a no content reply raises an EmailUnparsableError" do + it "a no content reply raises an EmptyEmailError" do email = MockPop3EmailObject.new fixture_file('emails/no_content_reply.eml') - expect_exception Email::Receiver::EmailUnparsableError + expect_exception Email::Receiver::EmptyEmailError poller.handle_mail(email) email.should be_deleted diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 23c827e7d..f90de100e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -30,6 +30,7 @@ Spork.prefork do # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} + Dir[Rails.root.join("spec/fabricators/*.rb")].each {|f| require f} # let's not run seed_fu every test SeedFu.quiet = true if SeedFu.respond_to? :quiet From 8ddd90daa42f7487b81b1d6bbd2d68b15b7fa42b Mon Sep 17 00:00:00 2001 From: riking Date: Thu, 28 Aug 2014 12:09:42 -0700 Subject: [PATCH 25/43] Have parse_body() recover from ASCII-8BIT encoding Added a test to make sure that the result can be passed into TextCleaner (which expects UTF-8) --- lib/email/receiver.rb | 3 ++- spec/components/email/receiver_spec.rb | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb index c644950ce..c15f8c427 100644 --- a/lib/email/receiver.rb +++ b/lib/email/receiver.rb @@ -95,6 +95,7 @@ module Email def parse_body(message) body = select_body message + encoding = body.encoding raise EmptyEmailError if body.strip.blank? body = discourse_email_trimmer body @@ -103,7 +104,7 @@ module Email body = EmailReplyParser.parse_reply body raise EmptyEmailError if body.strip.blank? - body + body.force_encoding(encoding).encode("UTF-8") end def select_body(message) diff --git a/spec/components/email/receiver_spec.rb b/spec/components/email/receiver_spec.rb index c5ef25543..a04316067 100644 --- a/spec/components/email/receiver_spec.rb +++ b/spec/components/email/receiver_spec.rb @@ -45,14 +45,14 @@ describe Email::Receiver do I18n.expects(: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 - test_parse_body(fixture_file("emails/hebrew.eml")).force_encoding("UTF-8").should == "שלום" + test_parse_body(fixture_file("emails/hebrew.eml")).should == "שלום" end it "supports a BIG5-encoded reply" do I18n.expects(: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 - test_parse_body(fixture_file("emails/big5.eml")).force_encoding("UTF-8").should == "媽!我上電視了!" + test_parse_body(fixture_file("emails/big5.eml")).should == "媽!我上電視了!" end it "removes 'via' lines if they match the site title" do @@ -77,6 +77,16 @@ it there without worrying about it too much, imo. Thanks for listening." ) end + + it "converts back to UTF-8 at the end" do + result = test_parse_body(fixture_file("emails/big5.eml")) + result.encoding.should == Encoding::UTF_8 + + # should not throw + TextCleaner.normalize_whitespaces( + test_parse_body(fixture_file("emails/big5.eml")) + ) + end end describe "posting replies" do From 85c6eb9b085ac583f0122d3186a964c3509c4360 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 28 Aug 2014 17:45:13 -0400 Subject: [PATCH 26/43] SECURITY: Only redirect to our host by path on the login action --- app/controllers/static_controller.rb | 20 ++++++++++++++------ spec/controllers/static_controller_spec.rb | 21 +++++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/app/controllers/static_controller.rb b/app/controllers/static_controller.rb index f29c24b63..3a452362c 100644 --- a/app/controllers/static_controller.rb +++ b/app/controllers/static_controller.rb @@ -54,13 +54,21 @@ class StaticController < ApplicationController params.delete(:username) params.delete(:password) - redirect_to( - if params[:redirect].blank? || params[:redirect].match(login_path) - "/" - else - params[:redirect] + destination = "/" + + if params[:redirect].present? && !params[:redirect].match(login_path) + begin + forum_uri = URI(Discourse.base_url) + uri = URI(params[:redirect]) + if uri.path.present? && (uri.host.blank? || uri.host == forum_uri.host) + destination = uri.path + end + rescue URI::InvalidURIError + # Do nothing if the URI is invalid end - ) + end + + redirect_to destination end skip_before_filter :verify_authenticity_token, only: [:cdn_asset] diff --git a/spec/controllers/static_controller_spec.rb b/spec/controllers/static_controller_spec.rb index 4b1a63805..86c185503 100644 --- a/spec/controllers/static_controller_spec.rb +++ b/spec/controllers/static_controller_spec.rb @@ -82,6 +82,27 @@ describe StaticController do end end + context 'with a full url' do + it 'redirects to the correct path' do + xhr :post, :enter, redirect: "#{Discourse.base_url}/foo" + expect(response).to redirect_to '/foo' + end + end + + context 'with a full url to someone else' do + it 'redirects to the root path' do + xhr :post, :enter, redirect: "http://eviltrout.com/foo" + expect(response).to redirect_to '/' + end + end + + context 'with an invalid URL' do + it "redirects to the root" do + xhr :post, :enter, redirect: "javascript:alert('trout')" + expect(response).to redirect_to '/' + end + end + context 'when the redirect path is the login page' do it 'redirects to the root url' do xhr :post, :enter, redirect: login_path From bfb9fc4e99fbca00820fc6cad04426a45d7792f7 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 28 Aug 2014 15:47:38 -0700 Subject: [PATCH 27/43] FEATURE: reduce user edit window from 1y to 6m --- config/site_settings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/site_settings.yml b/config/site_settings.yml index 9e0d0a4e3..2d7546429 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -326,7 +326,7 @@ posting: default: 15 enable_private_messages: true ninja_edit_window: 300 - post_edit_time_limit: 525600 + post_edit_time_limit: 262800 edit_history_visible_to_public: client: true default: true From fe6e4d7da10b2e0f27640f91c4b7112ef0ca55be Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 29 Aug 2014 12:01:13 +1000 Subject: [PATCH 28/43] FIX: fails on initial migration if default_locale is nil --- lib/tasks/db.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index bc5cc8206..4c07eb45d 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -1,6 +1,6 @@ # we need to run seed_fu every time we run rake db:migrate task 'db:migrate' => 'environment' do - I18n.locale = SiteSetting.default_locale rescue :en + I18n.locale = (SiteSetting.default_locale || :en) rescue :en SeedFu.seed if SiteSetting.vacuum_db_days > 0 && From b7e8bcda073bd84a7c35f5ac9a7afa508556366f Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 29 Aug 2014 12:21:33 +1000 Subject: [PATCH 29/43] FIX: strip leading/trailing spaces from developer emails --- config/environments/development.rb | 2 +- config/environments/production.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/environments/development.rb b/config/environments/development.rb index 143e5992e..8ed39bf6d 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -47,7 +47,7 @@ Discourse::Application.configure do require 'rbtrace' if emails = GlobalSetting.developer_emails - config.developer_emails = emails.split(",") + config.developer_emails = emails.split(",").map(&:strip!) end end diff --git a/config/environments/production.rb b/config/environments/production.rb index e92ffa87a..3a8b992a8 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -61,7 +61,7 @@ Discourse::Application.configure do # developers have god like rights and may impersonate anyone in the system # normal admins may only impersonate other moderators (not admins) if emails = GlobalSetting.developer_emails - config.developer_emails = emails.split(",") + config.developer_emails = emails.split(",").map(&:strip!) end end From 0a7a4eae99a76b988f63f708f694e936dd4e0e91 Mon Sep 17 00:00:00 2001 From: riking Date: Tue, 26 Aug 2014 17:58:43 -0700 Subject: [PATCH 30/43] Publish lightboxing on the message bus With this change, images appear to lightbox instantly on my development machine. --- app/jobs/regular/process_post.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/jobs/regular/process_post.rb b/app/jobs/regular/process_post.rb index e8753e7e1..49d9684a0 100644 --- a/app/jobs/regular/process_post.rb +++ b/app/jobs/regular/process_post.rb @@ -16,7 +16,16 @@ module Jobs cp.post_process(args[:bypass_bump]) # If we changed the document, save it - post.update_column(:cooked, cp.html) if cp.dirty? + if cp.dirty? + post.update_column(:cooked, cp.html) + + MessageBus.publish("/topic/#{post.topic_id}", { + type: "revised", + id: post.id, + updated_at: Time.now, + post_number: post.post_number + }, group_ids: post.topic.secure_group_ids ) + end end end From 3396e6fea3b410e850873bcf65442c1f239d78b1 Mon Sep 17 00:00:00 2001 From: riking Date: Thu, 28 Aug 2014 20:34:32 -0700 Subject: [PATCH 31/43] Centralize MessageBus post updates After this change, only two files directly publish to MessageBus with a topic interpolated in the channel: Post and TopicUser. --- .../discourse/controllers/topic.js.es6 | 15 ++++++++---- app/jobs/regular/process_post.rb | 8 +------ app/models/post.rb | 9 ++++++++ app/models/post_action.rb | 8 +------ lib/post_creator.rb | 9 +------- lib/post_destroyer.rb | 23 +++++++------------ lib/post_revisor.rb | 14 ++--------- 7 files changed, 32 insertions(+), 54 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index 089374e71..aedd09970 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -496,25 +496,30 @@ export default ObjectController.extend(Discourse.SelectedPostsCount, { } var postStream = topicController.get('postStream'); - if (data.type === "revised" || data.type === "acted"){ + if (data.type === "revised" || data.type === "acted") { // TODO we could update less data for "acted" // (only post actions) postStream.triggerChangedPost(data.id, data.updated_at); return; } - if (data.type === "deleted"){ + if (data.type === "deleted") { postStream.triggerDeletedPost(data.id, data.post_number); return; } - if (data.type === "recovered"){ + if (data.type === "recovered") { postStream.triggerRecoveredPost(data.id, data.post_number); return; } - // Add the new post into the stream - postStream.triggerNewPostInStream(data.id); + if (data.type === "created") { + postStream.triggerNewPostInStream(data.id); + return; + } + + // log a warning + Em.Logger.warn("unknown topic bus message type", data); }); }, diff --git a/app/jobs/regular/process_post.rb b/app/jobs/regular/process_post.rb index 49d9684a0..394b2ae19 100644 --- a/app/jobs/regular/process_post.rb +++ b/app/jobs/regular/process_post.rb @@ -18,13 +18,7 @@ module Jobs # If we changed the document, save it if cp.dirty? post.update_column(:cooked, cp.html) - - MessageBus.publish("/topic/#{post.topic_id}", { - type: "revised", - id: post.id, - updated_at: Time.now, - post_number: post.post_number - }, group_ids: post.topic.secure_group_ids ) + post.publish_change_to_clients! :revised end end diff --git a/app/models/post.rb b/app/models/post.rb index 7dd093ca1..b1b0e9672 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -87,6 +87,15 @@ class Post < ActiveRecord::Base end end + def publish_change_to_clients!(type) + MessageBus.publish("/topic/#{topic_id}", { + id: id, + post_number: post_number, + updated_at: Time.now, + type: type + }, group_ids: topic.secure_group_ids) + end + def trash!(trashed_by=nil) self.topic_links.each(&:destroy) super(trashed_by) diff --git a/app/models/post_action.rb b/app/models/post_action.rb index 108b130fb..f250acdbc 100644 --- a/app/models/post_action.rb +++ b/app/models/post_action.rb @@ -375,13 +375,7 @@ class PostAction < ActiveRecord::Base def notify_subscribers if (is_like? || is_flag?) && post - MessageBus.publish("/topic/#{post.topic_id}",{ - id: post.id, - post_number: post.post_number, - type: "acted" - }, - group_ids: post.topic.secure_group_ids - ) + post.publish_change_to_clients! :acted end end diff --git a/lib/post_creator.rb b/lib/post_creator.rb index a0145bbc7..dfe34c392 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -270,14 +270,7 @@ class PostCreator return if @opts[:import_mode] return unless @post.post_number > 1 - MessageBus.publish("/topic/#{@post.topic_id}",{ - id: @post.id, - created_at: @post.created_at, - user: BasicUserSerializer.new(@post.user).as_json(root: false), - post_number: @post.post_number - }, - group_ids: @topic.secure_group_ids - ) + @post.publish_change_to_clients! :created end def extract_links diff --git a/lib/post_destroyer.rb b/lib/post_destroyer.rb index 990bb114f..1719f9267 100644 --- a/lib/post_destroyer.rb +++ b/lib/post_destroyer.rb @@ -51,7 +51,7 @@ class PostDestroyer def staff_recovered @post.recover! - publish("recovered") + @post.publish_change_to_clients! :recovered end # When a post is properly deleted. Well, it's still soft deleted, but it will no longer @@ -75,21 +75,8 @@ class PostDestroyer update_associated_category_latest_topic update_user_counts end - publish("deleted") - end - def publish(message) - # edge case, topic is already destroyed - return unless @post.topic - - MessageBus.publish("/topic/#{@post.topic_id}",{ - id: @post.id, - post_number: @post.post_number, - updated_at: @post.updated_at, - type: message - }, - group_ids: @post.topic.secure_group_ids - ) + @post.publish_change_to_clients! :deleted if @post.topic end # When a user 'deletes' their own post. We just change the text. @@ -100,6 +87,9 @@ class PostDestroyer @post.update_flagged_posts_count @post.topic_links.each(&:destroy) end + + # covered by PostRevisor + # @post.publish_change_to_clients! :revised end def user_recovered @@ -109,6 +99,9 @@ class PostDestroyer @post.revise(@user, @post.revisions.last.modifications["raw"][0], force_new_version: true) @post.update_flagged_posts_count end + + # covered by PostRevisor + # @post.publish_change_to_clients! :revised end diff --git a/lib/post_revisor.rb b/lib/post_revisor.rb index dea3b55a4..b065b5a4f 100644 --- a/lib/post_revisor.rb +++ b/lib/post_revisor.rb @@ -21,6 +21,7 @@ class PostRevisor @opts = opts @new_raw = TextCleaner.normalize_whitespaces(new_raw).strip + # TODO this is not in a transaction - dangerous! return false unless should_revise? @post.acting_user = @editor revise_post @@ -30,7 +31,7 @@ class PostRevisor update_topic_word_counts @post.advance_draft_sequence PostAlerter.new.after_save_post(@post) - publish_revision + @post.publish_change_to_clients! :revised BadgeGranter.queue_badge_grant(Badge::Trigger::PostRevision, post: @post) true @@ -38,17 +39,6 @@ class PostRevisor private - def publish_revision - MessageBus.publish("/topic/#{@post.topic_id}",{ - id: @post.id, - post_number: @post.post_number, - updated_at: @post.updated_at, - type: "revised" - }, - group_ids: @post.topic.secure_group_ids - ) - end - def should_revise? @post.raw != @new_raw || @opts[:changed_owner] end From 085b18577c1d2ce4453b472d248d10cab434da15 Mon Sep 17 00:00:00 2001 From: riking Date: Thu, 28 Aug 2014 22:07:40 -0700 Subject: [PATCH 32/43] Remove unnecessary user/topic load in PostCreator --- lib/post_creator.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/post_creator.rb b/lib/post_creator.rb index dfe34c392..6ae33ebf9 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -281,8 +281,8 @@ class PostCreator def track_topic return if @opts[:auto_track] == false - TopicUser.change(@post.user.id, - @post.topic.id, + TopicUser.change(@post.user_id, + @topic.id, posted: true, last_read_post_number: @post.post_number, seen_post_count: @post.post_number) From 2c7d694c3425be414d17ca118df14a591b2230ac Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 28 Aug 2014 23:09:37 -0700 Subject: [PATCH 33/43] UX: extremely confusing copy on setting --- config/locales/server.en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 34b5019e9..eadf66784 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -858,7 +858,7 @@ en: leader_promotion_min_duration: "The minimum number of days that a promotion to leader lasts before a user can be demoted." leader_requires_likes_given: "The minimum number of likes that must be given in the last 100 days to qualify for promotion to leader (3) trust level." leader_requires_likes_received: "The minimum number of likes that must be received in the last 100 days to qualify for promotion to leader (3) trust level." - leader_links_no_follow: "Whether links from leaders have the nofollow attribute." + leader_links_no_follow: "Do not remove rel=nofollow from leader links." min_trust_to_create_topic: "The minimum trust level required to create a new topic." From 911b8647cfd70bd19a59cdded5ac68ba3d0f9255 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 29 Aug 2014 17:06:14 +1000 Subject: [PATCH 34/43] FIX: Revert permalink support until implemented correctly --- config/application.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/config/application.rb b/config/application.rb index 6ec3c0bc5..b4c62b137 100644 --- a/config/application.rb +++ b/config/application.rb @@ -148,9 +148,13 @@ module Discourse require 'auth' Discourse.activate_plugins! unless Rails.env.test? and ENV['LOAD_PLUGINS'] != "1" - initializer :add_last_routes, :after => :add_routing_paths do |app| - app.routes_reloader.paths << File.join(Rails.root, 'config', 'routes_last.rb') - end + # FIXME: needs to work with engines such as docker manager + # this mounts routes_last before the engine, one option here if a hook is to hard + # is to add a constraint on the route that ensures its only captured if its a permalink + # + # initializer :add_last_routes, :after => :add_routing_paths do |app| + # app.routes_reloader.paths << File.join(Rails.root, 'config', 'routes_last.rb') + # end config.after_initialize do # So open id logs somewhere sane From 163bbb1d3a3aef66ed721f9f972dbfaee9fe2f1c Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 29 Aug 2014 17:27:28 +1000 Subject: [PATCH 35/43] comment out test till fixed --- spec/controllers/permalinks_controller_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/controllers/permalinks_controller_spec.rb b/spec/controllers/permalinks_controller_spec.rb index afc50e364..6846bfb12 100644 --- a/spec/controllers/permalinks_controller_spec.rb +++ b/spec/controllers/permalinks_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe PermalinksController do describe 'show' do - it "should redirect to a permalink's target_url with status 301" do + pending "should redirect to a permalink's target_url with status 301" do permalink = Fabricate(:permalink) Permalink.any_instance.stubs(:target_url).returns('/t/the-topic-slug/42') get :show, url: permalink.url @@ -10,7 +10,7 @@ describe PermalinksController do response.status.should == 301 end - it 'return 404 if permalink record does not exist' do + pending 'return 404 if permalink record does not exist' do get :show, url: '/not/a/valid/url' response.status.should == 404 end From dd972856d68e77ddf1510819527d6114f6710316 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 29 Aug 2014 00:42:32 -0700 Subject: [PATCH 36/43] Update INSTALL-digital-ocean.md --- docs/INSTALL-digital-ocean.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/INSTALL-digital-ocean.md b/docs/INSTALL-digital-ocean.md index df69a3c08..5d96e203f 100644 --- a/docs/INSTALL-digital-ocean.md +++ b/docs/INSTALL-digital-ocean.md @@ -161,7 +161,7 @@ Do you want... - Multiple Discourse sites on the same server? [Configure multisite](https://meta.discourse.org/t/multisite-configuration-with-docker/14084). -- A Content Delivery Network to speed up worldwide access? [Configure a CDN](https://meta.discourse.org/t/enable-a-cdn-for-your-discourse/14857). +- A Content Delivery Network to speed up worldwide access? [Configure a CDN](https://meta.discourse.org/t/enable-a-cdn-for-your-discourse/14857). We recommend [Fastly](http://www.fastly.com/). - Import old content from vBulletin, PHPbb, Vanilla, Drupal, BBPress, etc? [See our open source importers](https://github.com/discourse/discourse/tree/master/script/import_scripts) From 36405287b477e6b4ba72de0b9bd57f8ab12043a1 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 29 Aug 2014 03:34:23 -0700 Subject: [PATCH 37/43] UX: stop displaying "user deleted, OK" dialog --- app/assets/javascripts/admin/models/admin_user.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/assets/javascripts/admin/models/admin_user.js b/app/assets/javascripts/admin/models/admin_user.js index a84d3adbc..e83dca18a 100644 --- a/app/assets/javascripts/admin/models/admin_user.js +++ b/app/assets/javascripts/admin/models/admin_user.js @@ -307,9 +307,6 @@ Discourse.AdminUser = Discourse.User.extend({ data: formData }).then(function(data) { if (data.deleted) { - bootbox.alert(I18n.t("admin.user.deleted"), function() { - document.location = "/admin/users/list/active"; - }); } else { bootbox.alert(I18n.t("admin.user.delete_failed")); if (data.user) { @@ -361,9 +358,6 @@ Discourse.AdminUser = Discourse.User.extend({ data: {delete_posts: true, block_email: true, block_urls: true, block_ip: true, context: window.location.pathname} }).then(function(data) { if (data.deleted) { - bootbox.alert(I18n.t("admin.user.deleted"), function() { - if (successCallback) successCallback(); - }); } else { bootbox.alert(I18n.t("admin.user.delete_failed")); } From 8bda8a2235efb0dee145d133afc9ef1f48818160 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 29 Aug 2014 03:49:00 -0700 Subject: [PATCH 38/43] Revert "UX: stop displaying "user deleted, OK" dialog" This reverts commit 36405287b477e6b4ba72de0b9bd57f8ab12043a1. --- app/assets/javascripts/admin/models/admin_user.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/assets/javascripts/admin/models/admin_user.js b/app/assets/javascripts/admin/models/admin_user.js index e83dca18a..a84d3adbc 100644 --- a/app/assets/javascripts/admin/models/admin_user.js +++ b/app/assets/javascripts/admin/models/admin_user.js @@ -307,6 +307,9 @@ Discourse.AdminUser = Discourse.User.extend({ data: formData }).then(function(data) { if (data.deleted) { + bootbox.alert(I18n.t("admin.user.deleted"), function() { + document.location = "/admin/users/list/active"; + }); } else { bootbox.alert(I18n.t("admin.user.delete_failed")); if (data.user) { @@ -358,6 +361,9 @@ Discourse.AdminUser = Discourse.User.extend({ data: {delete_posts: true, block_email: true, block_urls: true, block_ip: true, context: window.location.pathname} }).then(function(data) { if (data.deleted) { + bootbox.alert(I18n.t("admin.user.deleted"), function() { + if (successCallback) successCallback(); + }); } else { bootbox.alert(I18n.t("admin.user.delete_failed")); } From 521cb6bd0d36368bee7e7793ec9eef408fe4443b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 29 Aug 2014 12:54:24 +0200 Subject: [PATCH 39/43] FIX: dual modal when selecting 'delete spammer' in flags section --- app/assets/javascripts/admin/routes/admin_flags_route.js | 4 +--- app/assets/javascripts/discourse/controllers/flag.js.es6 | 7 +------ 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/admin/routes/admin_flags_route.js b/app/assets/javascripts/admin/routes/admin_flags_route.js index e1d30fe68..dafd8b221 100644 --- a/app/assets/javascripts/admin/routes/admin_flags_route.js +++ b/app/assets/javascripts/admin/routes/admin_flags_route.js @@ -22,6 +22,7 @@ Discourse.AdminFlagsRouteType = Discourse.Route.extend({ @method deleteSpammer **/ deleteSpammer: function (user) { + this.send('closeModal'); user.deleteAsSpammer(function() { window.location.reload(); }); } } @@ -50,6 +51,3 @@ Discourse.AdminFlagsActiveRoute = Discourse.AdminFlagsRouteType.extend({ Discourse.AdminFlagsOldRoute = Discourse.AdminFlagsRouteType.extend({ filter: 'old' }); - - - diff --git a/app/assets/javascripts/discourse/controllers/flag.js.es6 b/app/assets/javascripts/discourse/controllers/flag.js.es6 index b4ef30e3f..38bacc91a 100644 --- a/app/assets/javascripts/discourse/controllers/flag.js.es6 +++ b/app/assets/javascripts/discourse/controllers/flag.js.es6 @@ -105,7 +105,7 @@ export default ObjectController.extend(ModalFunctionality, { changePostActionType: function(action) { this.set('selected', action); - } + }, }, canDeleteSpammer: function() { @@ -118,11 +118,6 @@ export default ObjectController.extend(ModalFunctionality, { } }.property('selected.name_key', 'userDetails.can_be_deleted', 'userDetails.can_delete_all_posts'), - deleteSpammer: function() { - this.send('closeModal'); - this.get('userDetails').deleteAsSpammer(function() { window.location.reload(); }); - }, - usernameChanged: function() { this.set('userDetails', null); this.fetchUserDetails(); From fd6dd88afbe77e541921cf8e5bfe56a0eb170134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 29 Aug 2014 13:05:51 +0200 Subject: [PATCH 40/43] FIX: dual modal when deleting spammer in the flag modal --- .../javascripts/admin/routes/admin_flags_route.js | 12 ------------ .../javascripts/discourse/routes/application.js.es6 | 9 +++++++++ .../discourse/templates/modal/flag.js.handlebars | 3 +-- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/admin/routes/admin_flags_route.js b/app/assets/javascripts/admin/routes/admin_flags_route.js index dafd8b221..a8c7cc1ec 100644 --- a/app/assets/javascripts/admin/routes/admin_flags_route.js +++ b/app/assets/javascripts/admin/routes/admin_flags_route.js @@ -15,18 +15,6 @@ Discourse.AdminFlagsRouteType = Discourse.Route.extend({ adminFlagsController.set('query', this.get('filter')); }, - actions: { - /** - Deletes a user and all posts and topics created by that user. - - @method deleteSpammer - **/ - deleteSpammer: function (user) { - this.send('closeModal'); - user.deleteAsSpammer(function() { window.location.reload(); }); - } - } - }); Discourse.AdminFlagsActiveRoute = Discourse.AdminFlagsRouteType.extend({ diff --git a/app/assets/javascripts/discourse/routes/application.js.es6 b/app/assets/javascripts/discourse/routes/application.js.es6 index 71da530d0..2d15c1d45 100644 --- a/app/assets/javascripts/discourse/routes/application.js.es6 +++ b/app/assets/javascripts/discourse/routes/application.js.es6 @@ -136,7 +136,16 @@ var ApplicationRoute = Em.Route.extend({ router.controllerFor('editCategory').set('selectedTab', 'general'); }); } + }, + /** + Deletes a user and all posts and topics created by that user. + + @method deleteSpammer + **/ + deleteSpammer: function (user) { + this.send('closeModal'); + user.deleteAsSpammer(function() { window.location.reload(); }); } }, diff --git a/app/assets/javascripts/discourse/templates/modal/flag.js.handlebars b/app/assets/javascripts/discourse/templates/modal/flag.js.handlebars index 4a06a87f3..41d95f330 100644 --- a/app/assets/javascripts/discourse/templates/modal/flag.js.handlebars +++ b/app/assets/javascripts/discourse/templates/modal/flag.js.handlebars @@ -28,7 +28,6 @@ {{/if}} {{#if canDeleteSpammer}} - + {{/if}}
- From e823f568a7f0812e058c87b573292c30aefc046e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 29 Aug 2014 13:11:56 +0200 Subject: [PATCH 41/43] FIX: preprocessing regexes in vBulletin importer --- script/import_scripts/vbulletin.rb | 58 +++++++++++++++++------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/script/import_scripts/vbulletin.rb b/script/import_scripts/vbulletin.rb index 09c5a4513..7c34a1cb7 100644 --- a/script/import_scripts/vbulletin.rb +++ b/script/import_scripts/vbulletin.rb @@ -350,8 +350,26 @@ class ImportScripts::VBulletin < ImportScripts::Base raw = raw.gsub(/(\\r)?\\n/, "\n") .gsub("\\t", "\t") + # remove attachments + raw = raw.gsub(/\[attach\]\d+\[\/attach\]/i, "") + + # replace all chevrons with HTML entities + # NOTE: must be before any of the "quote" processing + raw = raw.gsub(/`([^`]+)`/im) { "`" + $1.gsub("<", "\u2603") + "`" } + .gsub("<", "<") + .gsub("\u2603", "<") + + raw = raw.gsub(/`([^`]+)`/im) { "`" + $1.gsub(">", "\u2603") + "`" } + .gsub(">", ">") + .gsub("\u2603", ">") + + # [URL=...]...[/URL] + raw = raw.gsub(/\[url="?(.+?)"?\](.+)\[\/url\]/i) { "[#{$2}](#{$1})" } + # [URL]...[/URL] - raw = raw.gsub(/\[url\](.+?)\[\/url\]/i) { $1.to_s } + # [MP3]...[/MP3] + raw = raw.gsub(/\[\/?url\]/i, "") + .gsub(/\[\/?mp3\]/i, "") # [MENTION][/MENTION] raw = raw.gsub(/\[mention\](.+?)\[\/mention\]/i) do @@ -363,7 +381,7 @@ class ImportScripts::VBulletin < ImportScripts::Base end # [MENTION=][/MENTION] - raw = raw.gsub(/\[mention=(\d+)\](.+?)\[\/mention\]/i) do + raw = raw.gsub(/\[mention="?(\d+)"?\](.+?)\[\/mention\]/i) do user_id, old_username = $1, $2 if user = @users.select { |u| u[:userid] == user_id }.first old_username = @old_username_to_new_usernames[user[:username]] || user[:username] @@ -384,37 +402,29 @@ class ImportScripts::VBulletin < ImportScripts::Base end # [HTML]...[/HTML] + raw = raw.gsub(/\[html\]/i, "\n```html\n") + .gsub(/\[\/html\]/i, "\n```\n") + # [PHP]...[/PHP] - ["html", "php"].each do |language| - raw = raw.gsub(/\[#{language}\](.+?)\[\/#{language}\]/im) { "\n```#{language}\n#{$1}\n```\n" } - end + raw = raw.gsub(/\[php\]/i, "\n```php\n") + .gsub(/\[\/php\]/i, "\n```\n") + + # [HIGHLIGHT="..."] + raw = raw.gsub(/\[highlight="?(\w+)"?\]/i) { "\n```#{$1.downcase}\n" } # [CODE]...[/CODE] - raw = raw.gsub(/\[code\](.+?)\[\/code\]/im) { "\n```\n#{$1}\n```\n" } + # [HIGHLIGHT]...[/HIGHLIGHT] + raw = raw.gsub(/\[\/?code\]/i, "\n```\n") + .gsub(/\[\/?highlight\]/i, "\n```\n") - # [HIGHLIGHT="..."]...[/HIGHLIGHT] - raw = raw.gsub(/\[highlight(?:[^\]]*)\](.+)\[\/highlight\]/im) { "\n```\n#{$1}\n```\n" } - - # [SAMP]...[SAMP] - raw = raw.gsub(/\[samp\](.+?)\[\/samp\]/i) { "`#{$1}`" } + # [SAMP]...[/SAMP] + raw = raw.gsub(/\[\/?samp\]/i, "`") # [YOUTUBE][/YOUTUBE] raw = raw.gsub(/\[youtube\](.+?)\[\/youtube\]/i) { "\n//youtu.be/#{$1}\n" } # [VIDEO=youtube;]...[/VIDEO] - raw = raw.gsub(/\[video=youtube;([^\]]+)\].*\[\/video\]/i) { "\n//youtu.be/#{$1}\n" } - - # [MP3][/MP3] - raw = raw.gsub(/\[MP3\](.+?)\[\/MP3\]/i) { "\n#{$1}\n" } - - # replace all chevrons with HTML entities - raw = raw.gsub(/`([^`]+)`/im) { "`" + $1.gsub("<", "\u2603") + "`" } - .gsub("<", "<") - .gsub("\u2603", "<") - - raw = raw.gsub(/`([^`]+)`/im) { "`" + $1.gsub(">", "\u2603") + "`" } - .gsub(">", ">") - .gsub("\u2603", ">") + raw = raw.gsub(/\[video=youtube;([^\]]+)\].*?\[\/video\]/i) { "\n//youtu.be/#{$1}\n" } raw end From 3f085c3fd3242b8d7b2ee48111d290f1bf64960f Mon Sep 17 00:00:00 2001 From: cpradio Date: Fri, 29 Aug 2014 09:22:47 -0400 Subject: [PATCH 42/43] Change the Dismiss keybindings to use 'x' instead of 'd' --- app/assets/javascripts/discourse/lib/keyboard_shortcuts.js | 1 + .../templates/modal/keyboard_shortcuts_help.js.handlebars | 1 + config/locales/client.en.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js b/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js index 473f9d14e..a4321fe55 100644 --- a/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js +++ b/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js @@ -35,6 +35,7 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({ 'm r': 'div.notification-options li[data-id="1"] a', // mark topic as regular 'm t': 'div.notification-options li[data-id="2"] a', // mark topic as tracking 'm w': 'div.notification-options li[data-id="3"] a', // mark topic as watching + '.': '.alert .alert-info .clickable', // show incoming topics 'n': '#user-notifications', // open notifications menu '=': '#site-map', // open site map menu 'p': '#current-user', // open current user menu diff --git a/app/assets/javascripts/discourse/templates/modal/keyboard_shortcuts_help.js.handlebars b/app/assets/javascripts/discourse/templates/modal/keyboard_shortcuts_help.js.handlebars index 76617712d..2ac870f2c 100644 --- a/app/assets/javascripts/discourse/templates/modal/keyboard_shortcuts_help.js.handlebars +++ b/app/assets/javascripts/discourse/templates/modal/keyboard_shortcuts_help.js.handlebars @@ -27,6 +27,7 @@
  • {{{i18n keyboard_shortcuts_help.application.site_map_menu}}}
  • {{{i18n keyboard_shortcuts_help.application.user_profile_menu}}}
  • {{{i18n keyboard_shortcuts_help.application.search}}}
  • +
  • {{{i18n keyboard_shortcuts_help.application.show_incoming_updated}}}
  • {{{i18n keyboard_shortcuts_help.application.help}}}
  • diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 7fe3c5299..1edf950b2 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2056,6 +2056,7 @@ en: notifications: 'n Open notifications' site_map_menu: '= Open site map menu' user_profile_menu: 'p Open user profile menu' + show_incoming_updated: '. Show incoming/updated topics' search: '/ Search' help: '? Open keyboard shortcuts help' actions: From cde64580b14ea2e69f782004cede231d2530906c Mon Sep 17 00:00:00 2001 From: cpradio Date: Fri, 29 Aug 2014 09:35:38 -0400 Subject: [PATCH 43/43] Change the Dismiss keybindings to use 'x' instead of 'd' - moved it to a function binding --- app/assets/javascripts/discourse/lib/keyboard_shortcuts.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js b/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js index a4321fe55..79e5d71b6 100644 --- a/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js +++ b/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js @@ -35,7 +35,6 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({ 'm r': 'div.notification-options li[data-id="1"] a', // mark topic as regular 'm t': 'div.notification-options li[data-id="2"] a', // mark topic as tracking 'm w': 'div.notification-options li[data-id="3"] a', // mark topic as watching - '.': '.alert .alert-info .clickable', // show incoming topics 'n': '#user-notifications', // open notifications menu '=': '#site-map', // open site map menu 'p': '#current-user', // open current user menu @@ -55,6 +54,7 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({ '`': 'nextSection', '~': 'prevSection', '/': 'showSearch', + '.': 'showIncomingUpdatedTopics', // show incoming topics 'ctrl+f': 'showBuiltinSearch', 'command+f': 'showBuiltinSearch', '?': 'showHelpModal', // open keyboard shortcut help @@ -134,6 +134,11 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({ } }, + showIncomingUpdatedTopics: function() { + $('.alert .alert-info .clickable').click(); + return false; + }, + toggleProgress: function() { Discourse.__container__.lookup('controller:topic-progress').send('toggleExpansion', {highlight: true}); },