From 49dc470a280ba8c506a77b5ae4d807345ec470e7 Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Fri, 18 Sep 2015 21:12:37 +0200 Subject: [PATCH 001/114] Add spec to check locale files for duplicate keys --- lib/locale_file_walker.rb | 48 +++++++++++++++++++++++++++++++++++++ spec/integrity/i18n_spec.rb | 37 ++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 lib/locale_file_walker.rb diff --git a/lib/locale_file_walker.rb b/lib/locale_file_walker.rb new file mode 100644 index 0000000000..0ac3146e84 --- /dev/null +++ b/lib/locale_file_walker.rb @@ -0,0 +1,48 @@ +require 'psych' + +class LocaleFileWalker + protected + + def handle_document(document) + # we want to ignore the language (first key), so let's start at -1 + handle_nodes(document.root.children, -1, []) + end + + def handle_nodes(nodes, depth, parents) + if nodes + consecutive_scalars = 0 + nodes.each do |node| + consecutive_scalars = handle_node(node, depth, parents, consecutive_scalars) + end + end + end + + def handle_node(node, depth, parents, consecutive_scalars) + node_is_scalar = node.is_a?(Psych::Nodes::Scalar) + + if node_is_scalar + handle_scalar(node, depth, parents) if valid_scalar?(depth, consecutive_scalars) + elsif node.is_a?(Psych::Nodes::Alias) + handle_alias(node, depth, parents) + elsif node.is_a?(Psych::Nodes::Mapping) + handle_mapping(node, depth, parents) + handle_nodes(node.children, depth + 1, parents.dup) + end + + node_is_scalar ? consecutive_scalars + 1 : 0 + end + + def valid_scalar?(depth, consecutive_scalars) + depth >= 0 && consecutive_scalars.even? + end + + def handle_scalar(node, depth, parents) + parents[depth] = node.value + end + + def handle_alias(node, depth, parents) + end + + def handle_mapping(node, depth, parents) + end +end diff --git a/spec/integrity/i18n_spec.rb b/spec/integrity/i18n_spec.rb index 2525668f54..e7b9c08fec 100644 --- a/spec/integrity/i18n_spec.rb +++ b/spec/integrity/i18n_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'locale_file_walker' describe "i18n integrity checks" do @@ -57,4 +58,40 @@ describe "i18n integrity checks" do end end + describe 'keys in English locale files' do + locale_files = ['config/locales', 'plugins/**/locales'] + .product(['server.en.yml', 'client.en.yml']) + .collect { |dir, filename| Dir["#{Rails.root}/#{dir}/#{filename}"] } + .flatten + .map { |path| Pathname.new(path).relative_path_from(Rails.root) } + + class DuplicateKeyFinder < LocaleFileWalker + def find_duplicates(filename) + @keys_with_count = {} + + document = Psych.parse_file(filename) + handle_document(document) + + @keys_with_count.delete_if { |key, count| count <= 1 }.keys + end + + protected + + def handle_scalar(node, depth, parents) + super(node, depth, parents) + + key = parents.join('.') + @keys_with_count[key] = @keys_with_count.fetch(key, 0) + 1 + end + end + + locale_files.each do |path| + context path do + it 'has no duplicate keys' do + duplicates = DuplicateKeyFinder.new.find_duplicates("#{Rails.root}/#{path}") + expect(duplicates).to be_empty + end + end + end + end end From ade31c4468206240b3fba1e6cb4dd00d7c865a19 Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Fri, 18 Sep 2015 22:29:01 +0200 Subject: [PATCH 002/114] FIX: Remove duplicate keys from locale files --- config/locales/client.en.yml | 1 - config/locales/server.en.yml | 23 ++++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 71a635f9af..186c462884 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -153,7 +153,6 @@ en: sign_up: "Sign Up" log_in: "Log In" age: "Age" - last_post: "Last Post" joined: "Joined" admin_title: "Admin" flags_title: "Flags" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 1a91ce928d..2cab867e20 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -20,17 +20,21 @@ en: short_date: "D MMM, YYYY" long_date: "MMMM D, YYYY h:mma" - datetime: &datetime - month_names: - [~, January, February, March, April, May, June, July, August, September, October, November, December] + datetime_formats: &datetime_formats formats: + # Format directives: http://ruby-doc.org/core-2.2.0/Time.html#method-i-strftime short: "%m-%d-%Y" + # Format directives: http://ruby-doc.org/core-2.2.0/Time.html#method-i-strftime short_no_year: "%B %-d" + # Format directives: http://ruby-doc.org/core-2.2.0/Time.html#method-i-strftime date_only: "%B %-d, %Y" date: - <<: *datetime + # Do not remove the brackets and commas and do not translate the first month name. It should be "null". + month_names: + [~, January, February, March, April, May, June, July, August, September, October, November, December] + <<: *datetime_formats time: - <<: *datetime + <<: *datetime_formats title: "Discourse" topics: "Topics" @@ -62,8 +66,10 @@ en: exclusion: is reserved greater_than: must be greater than %{count} greater_than_or_equal_to: must be greater than or equal to %{count} + has_already_been_used: "has already been used" inclusion: is not included in the list invalid: is invalid + is_invalid: "is invalid; try to be a little more descriptive" less_than: must be less than %{count} less_than_or_equal_to: must be less than or equal to %{count} not_a_number: is not a number @@ -101,9 +107,6 @@ en: activemodel: errors: <<: *errors - activerecord: - errors: - <<: *errors bulk_invite: file_should_be_csv: "The uploaded file should be of csv or txt format." @@ -280,9 +283,7 @@ en: user: ip_address: "" errors: - messages: - is_invalid: "is invalid; try to be a little more descriptive" - has_already_been_used: "has already been used" + <<: *errors models: topic: attributes: From d2d823186cf76461f649227474e6406d8fff2783 Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Fri, 18 Sep 2015 22:30:28 +0200 Subject: [PATCH 003/114] Add support for YAML aliases to pull_translations script The English locale files use aliases and anchors in order to prevent duplicate translations. Transifex doesn't support them. The script finds those aliases and anchors in the original locale files and adds them to the locale files pulled from Transifex. --- script/pull_translations.rb | 187 +++++++++++++++++++++++++++++++++--- 1 file changed, 175 insertions(+), 12 deletions(-) diff --git a/script/pull_translations.rb b/script/pull_translations.rb index 98da5ad419..231482fcb2 100644 --- a/script/pull_translations.rb +++ b/script/pull_translations.rb @@ -6,6 +6,7 @@ # team will pull them in. require 'open3' +require_relative '../lib/locale_file_walker' if `which tx`.strip.empty? puts '', 'The Transifex client needs to be installed to use this script.' @@ -17,10 +18,11 @@ if `which tx`.strip.empty? exit 1 end -locales = Dir.glob(File.expand_path('../../config/locales/client.*.yml', __FILE__)).map {|x| x.split('.')[-2]}.select {|x| x != 'en'}.sort.join(',') +languages = Dir.glob(File.expand_path('../../config/locales/client.*.yml', __FILE__)) + .map { |x| x.split('.')[-2] }.select { |x| x != 'en' }.sort puts 'Pulling new translations...', '' -command = "tx pull --mode=developer --language=#{locales} #{ARGV.include?('force') ? '-f' : ''}" +command = "tx pull --mode=developer --language=#{languages.join(',')} #{ARGV.include?('force') ? '-f' : ''}" Open3.popen2e(command) do |stdin, stdout_err, wait_thr| while (line = stdout_err.gets) @@ -46,19 +48,180 @@ END YML_DIRS = ['config/locales', 'plugins/poll/config/locales', 'vendor/gems/discourse_imgur/lib/discourse_imgur/locale'] +YML_FILE_PREFIXES = ['server', 'client'] -# Add comments to the top of files -['client', 'server'].each do |base| - YML_DIRS.each do |dir| - Dir.glob(File.expand_path("../../#{dir}/#{base}.*.yml", __FILE__)).each do |file_name| - language = File.basename(file_name).match(Regexp.new("#{base}\\.([^\\.]*)\\.yml"))[1] +def yml_path(dir, prefix, language) + path = "../../#{dir}/#{prefix}.#{language}.yml" + path = File.expand_path(path, __FILE__) + File.exists?(path) ? path : nil +end - lines = File.readlines(file_name) - lines.collect! {|line| line =~ /^[a-z_]+:$/i ? "#{language}:" : line} +# Add comments to the top of files and replace the language (first key in YAML file) +def update_file_header(filename, language) + lines = File.readlines(filename) + lines.collect! {|line| line =~ /^[a-z_]+:$/i ? "#{language}:" : line} - File.open(file_name, 'w+') do |f| - f.puts(YML_FILE_COMMENTS, '') unless lines[0][0] == '#' - f.puts(lines) + File.open(filename, 'w+') do |f| + f.puts(YML_FILE_COMMENTS, '') unless lines[0][0] == '#' + f.puts(lines) + end +end + +class YamlAliasFinder < LocaleFileWalker + def initialize + @anchors = {} + @aliases = Hash.new { |hash, key| hash[key] = [] } + end + + def parse_file(filename) + document = Psych.parse_file(filename) + handle_document(document) + {anchors: @anchors, aliases: @aliases} + end + + private + + def handle_alias(node, depth, parents) + @aliases[node.anchor] << parents.dup + end + + def handle_mapping(node, depth, parents) + if node.anchor + @anchors[parents.dup] = node.anchor + end + end +end + +class YamlAliasSynchronizer < LocaleFileWalker + def initialize(original_alias_data) + @anchors = original_alias_data[:anchors] + @aliases = original_alias_data[:aliases] + @used_anchors = Set.new + + calculate_required_keys + end + + def add_to(filename) + stream = Psych.parse_stream(File.read(filename)) + stream.children.each { |document| handle_document(document) } + + add_aliases + write_yaml(stream, filename) + end + + private + + def calculate_required_keys + @required_keys = {} + + @aliases.each_value do |key_sets| + key_sets.each do |keys| + until keys.empty? + add_needed_node(keys) + keys = keys.dup + keys.pop + end + end + end + + add_needed_node([]) unless @required_keys.empty? + end + + def add_needed_node(keys) + @required_keys[keys] = {mapping: nil, scalar: nil, alias: nil} + end + + def write_yaml(stream, filename) + yaml = stream.to_yaml(nil, {:line_width => -1}) + + File.open(filename, 'w') do |file| + file.write(yaml) + end + end + + def handle_scalar(node, depth, parents) + super(node, depth, parents) + + if @required_keys.has_key?(parents) + @required_keys[parents][:scalar] = node + end + end + + def handle_alias(node, depth, parents) + if @required_keys.has_key?(parents) + @required_keys[parents][:alias] = node + end + end + + def handle_mapping(node, depth, parents) + if @anchors.has_key?(parents) + node.anchor = @anchors[parents] + @used_anchors.add(node.anchor) + end + + if @required_keys.has_key?(parents) + @required_keys[parents][:mapping] = node + end + end + + def add_aliases + @used_anchors.each do |anchor| + @aliases[anchor].each do |keys| + parents = [] + parent_node = @required_keys[[]] + + keys.each_with_index do |key, index| + parents << key + current_node = @required_keys[parents] + is_last = index == keys.size - 1 + add_node(current_node, parent_node, key, is_last ? anchor : nil) + parent_node = current_node + end + end + end + end + + def add_node(node, parent_node, scalar_name, anchor) + parent_mapping = parent_node[:mapping] + parent_mapping.children ||= [] + + if node[:scalar].nil? + node[:scalar] = Psych::Nodes::Scalar.new(scalar_name) + parent_mapping.children << node[:scalar] + end + + if anchor.nil? + if node[:mapping].nil? + node[:mapping] = Psych::Nodes::Mapping.new + parent_mapping.children << node[:mapping] + end + elsif node[:alias].nil? + parent_mapping.children << Psych::Nodes::Alias.new(anchor) + end + end +end + +def get_english_alias_data(dir, prefix) + filename = yml_path(dir, prefix, 'en') + filename ? YamlAliasFinder.new.parse_file(filename) : nil +end + +def add_anchors_and_aliases(english_alias_data, filename) + if english_alias_data + YamlAliasSynchronizer.new(english_alias_data).add_to(filename) + end +end + +YML_DIRS.each do |dir| + YML_FILE_PREFIXES.each do |prefix| + english_alias_data = get_english_alias_data(dir, prefix) + + languages.each do |language| + filename = yml_path(dir, prefix, language) + + if filename + add_anchors_and_aliases(english_alias_data, filename) + update_file_header(filename, language) end end end From 11a6b61cbdd48acc2d97349095258b8894e7150a Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Sun, 11 Oct 2015 14:48:37 +0200 Subject: [PATCH 004/114] Remove example CSS selectors from locale file --- .../javascripts/admin/components/embedding-setting.js.es6 | 7 +------ .../admin/templates/components/embedding-setting.hbs | 2 +- app/assets/javascripts/admin/templates/embedding.hbs | 4 ++-- config/locales/client.en.yml | 2 -- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/admin/components/embedding-setting.js.es6 b/app/assets/javascripts/admin/components/embedding-setting.js.es6 index 039e5d5dc3..f1d31fcf68 100644 --- a/app/assets/javascripts/admin/components/embedding-setting.js.es6 +++ b/app/assets/javascripts/admin/components/embedding-setting.js.es6 @@ -6,11 +6,6 @@ export default Ember.Component.extend({ @computed('field') inputId(field) { return field.dasherize(); }, - @computed('placeholder') - placeholderValue(placeholder) { - return placeholder ? I18n.t(placeholder) : null; - }, - @computed('field') translationKey(field) { return `admin.embedding.${field}`; }, @@ -21,7 +16,7 @@ export default Ember.Component.extend({ checked: { get(value) { return !!value; }, set(value) { - this.set('value', value); + this.set('value', value); return value; } } diff --git a/app/assets/javascripts/admin/templates/components/embedding-setting.hbs b/app/assets/javascripts/admin/templates/components/embedding-setting.hbs index e3a99c3202..eb63c40af9 100644 --- a/app/assets/javascripts/admin/templates/components/embedding-setting.hbs +++ b/app/assets/javascripts/admin/templates/components/embedding-setting.hbs @@ -5,7 +5,7 @@ {{else}} - {{input value=value id=inputId placeholder=placeholderValue}} + {{input value=value id=inputId placeholder=placeholder}} {{/if}}
diff --git a/app/assets/javascripts/admin/templates/embedding.hbs b/app/assets/javascripts/admin/templates/embedding.hbs index d41970fb84..30f279105d 100644 --- a/app/assets/javascripts/admin/templates/embedding.hbs +++ b/app/assets/javascripts/admin/templates/embedding.hbs @@ -48,11 +48,11 @@ {{embedding-setting field="embed_whitelist_selector" value=embedding.embed_whitelist_selector - placeholder="admin.embedding.whitelist_example"}} + placeholder="article, #story, .post"}} {{embedding-setting field="embed_blacklist_selector" value=embedding.embed_blacklist_selector - placeholder="admin.embedding.blacklist_example"}} + placeholder=".ad-unit, header"}}
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 1b1daeed99..1cf4db7098 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2595,9 +2595,7 @@ en: embed_username_key_from_feed: "Key to pull discourse username from feed" embed_truncate: "Truncate the embedded posts" embed_whitelist_selector: "CSS selector for elements that are allowed in embeds" - whitelist_example: "article, #story, .post" embed_blacklist_selector: "CSS selector for elements that are removed from embeds" - blacklist_example: ".ad-unit, header" feed_polling_enabled: "Import posts via RSS/ATOM" feed_polling_url: "URL of RSS/ATOM feed to crawl" save: "Save Embedding Settings" From 97d11b66562096fb2e148261c8548973e167f52b Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Tue, 20 Oct 2015 09:19:45 +0800 Subject: [PATCH 005/114] FIX: Emoji toolbar too wide on mobile. --- .../javascripts/discourse/lib/emoji/emoji-toolbar.js.es6 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/lib/emoji/emoji-toolbar.js.es6 b/app/assets/javascripts/discourse/lib/emoji/emoji-toolbar.js.es6 index ffd54875ce..5219e39faa 100644 --- a/app/assets/javascripts/discourse/lib/emoji/emoji-toolbar.js.es6 +++ b/app/assets/javascripts/discourse/lib/emoji/emoji-toolbar.js.es6 @@ -4,7 +4,9 @@ import KeyValueStore from "discourse/lib/key-value-store"; const keyValueStore = new KeyValueStore("discourse_emojis_"); const EMOJI_USAGE = "emojiUsage"; -const PER_ROW = 12, PER_PAGE = 60; +let PER_ROW = 12; +const PER_PAGE = 60; + let ungroupedIcons, recentlyUsedIcons; if (!keyValueStore.getObject(EMOJI_USAGE)) { @@ -159,6 +161,7 @@ function showSelector(options) { options.appendTo.append('
'); $('.emoji-modal-wrapper').click(() => closeSelector()); + if (Discourse.Mobile.mobileView) PER_ROW = 9; const page = keyValueStore.getInt("emojiPage", 0); const offset = keyValueStore.getInt("emojiOffset", 0); From d9fe78da209af2aa6f6d46875f9c11ba392a1013 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Tue, 20 Oct 2015 10:12:52 +0800 Subject: [PATCH 006/114] FIX: Topic#last_posted_at was not being set when changing topic timestamp. --- app/services/post_timestamp_changer.rb | 13 +++++++++---- spec/services/post_timestamp_changer_spec.rb | 2 ++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/services/post_timestamp_changer.rb b/app/services/post_timestamp_changer.rb index 65b056128f..2364c12472 100644 --- a/app/services/post_timestamp_changer.rb +++ b/app/services/post_timestamp_changer.rb @@ -8,15 +8,19 @@ class PostTimestampChanger def change! ActiveRecord::Base.transaction do - update_topic + last_posted_at = @timestamp @posts.each do |post| if post.is_first_post? update_post(post, @timestamp) else - update_post(post, Time.at(post.created_at.to_f + @time_difference)) + new_created_at = Time.at(post.created_at.to_f + @time_difference) + last_posted_at = new_created_at if new_created_at > last_posted_at + update_post(post, new_created_at) end end + + update_topic(last_posted_at) end # Burst the cache for stats @@ -29,11 +33,12 @@ class PostTimestampChanger @timestamp - @topic.created_at end - def update_topic + def update_topic(last_posted_at) @topic.update_attributes( created_at: @timestamp, updated_at: @timestamp, - bumped_at: @timestamp + bumped_at: @timestamp, + last_posted_at: last_posted_at ) end diff --git a/spec/services/post_timestamp_changer_spec.rb b/spec/services/post_timestamp_changer_spec.rb index 03ec9a6644..245eaa3df9 100644 --- a/spec/services/post_timestamp_changer_spec.rb +++ b/spec/services/post_timestamp_changer_spec.rb @@ -21,6 +21,8 @@ describe PostTimestampChanger do [:created_at, :updated_at].each do |column| expect(p1.public_send(column)).to be_within_one_second_of(new_timestamp) end + + expect(topic.last_posted_at).to be_within_one_second_of(p2.reload.created_at) end describe 'predated timestamp' do From 63fbab5b291c251d4677636d3aa8dee0b3ed49eb Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Tue, 20 Oct 2015 11:10:53 +0800 Subject: [PATCH 007/114] UX: Misaligned buttons in image uploader. --- app/assets/stylesheets/desktop/upload.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/desktop/upload.scss b/app/assets/stylesheets/desktop/upload.scss index 5dc9fe66e4..0d37a0919a 100644 --- a/app/assets/stylesheets/desktop/upload.scss +++ b/app/assets/stylesheets/desktop/upload.scss @@ -37,6 +37,6 @@ .image-upload-controls { padding: 10px; label.btn { - padding: 7px 10px 5px 10px; + padding: 6px 10px; } } From 722cce7400edc77c7bd00d2df6b890c9ae52c15d Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Mon, 19 Oct 2015 23:02:32 -0700 Subject: [PATCH 008/114] FIX: mobile logo was too wide --- app/assets/stylesheets/mobile/header.scss | 4 ++-- app/assets/stylesheets/mobile/topic-list.scss | 20 +++++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/mobile/header.scss b/app/assets/stylesheets/mobile/header.scss index 25fd40d141..3b8e8e15f4 100644 --- a/app/assets/stylesheets/mobile/header.scss +++ b/app/assets/stylesheets/mobile/header.scss @@ -5,12 +5,12 @@ .d-header { #site-logo { - max-width: 155px; + max-width: 130px; } // some protection for text-only site titles .title { - max-width: 160px; + max-width: 130px; height: 39px; overflow: hidden; padding: 0; diff --git a/app/assets/stylesheets/mobile/topic-list.scss b/app/assets/stylesheets/mobile/topic-list.scss index 043fe85f6d..a391f4c67f 100644 --- a/app/assets/stylesheets/mobile/topic-list.scss +++ b/app/assets/stylesheets/mobile/topic-list.scss @@ -140,6 +140,7 @@ .posts { width: 10%; + vertical-align: top; } .age { @@ -236,10 +237,15 @@ tr.category-topic-link { font-size: 110%; } - .category-topic-link .num { - white-space: nowrap; - .number { - font-size: 1.071em; + .category-topic-link { + .num { + white-space: nowrap; + .number { + font-size: 1.071em; + } + } + .topic-excerpt { + width: 110%; } } @@ -404,4 +410,10 @@ td .main-link { font-size: 1.071em; padding-top: 2px; } + // so the topic excerpt is full width + // as the containing div is 80% + .topic-excerpt { + padding-right: 0; + width: 120%; + } } From 5ca93cd79c74699b813cfe13b93a3eb8ebd14652 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Mon, 19 Oct 2015 23:58:42 -0700 Subject: [PATCH 009/114] add opacity to mobile user page details --- app/assets/stylesheets/mobile/user.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/mobile/user.scss b/app/assets/stylesheets/mobile/user.scss index b4acd17022..5bb4095a66 100644 --- a/app/assets/stylesheets/mobile/user.scss +++ b/app/assets/stylesheets/mobile/user.scss @@ -258,6 +258,7 @@ .details { padding: 15px 15px 4px 15px; background-color: dark-light-choose(rgba($primary, .9), rgba($secondary, .9)); + opacity: 0.8; h1 { font-size: 2.143em; From 5d341bef576ae0b187e2726229ec06b32cf77d85 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Tue, 20 Oct 2015 00:46:20 -0700 Subject: [PATCH 010/114] remove valign-helper hack, let's go flexbox --- .../discourse/templates/components/home-logo.hbs | 3 --- app/assets/stylesheets/common/base/header.scss | 6 ------ 2 files changed, 9 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/components/home-logo.hbs b/app/assets/javascripts/discourse/templates/components/home-logo.hbs index 976aefd88f..b2b8f9e6db 100644 --- a/app/assets/javascripts/discourse/templates/components/home-logo.hbs +++ b/app/assets/javascripts/discourse/templates/components/home-logo.hbs @@ -1,18 +1,15 @@ {{#if showSmallLogo}} {{#if smallLogoUrl}} - {{else}} {{/if}} {{else}} {{#if showMobileLogo}} - {{else}} {{#if bigLogoUrl}} - {{else}} diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss index bc20cefeaa..51d5835c2b 100644 --- a/app/assets/stylesheets/common/base/header.scss +++ b/app/assets/stylesheets/common/base/header.scss @@ -19,12 +19,6 @@ float: left; } - .valign-helper { - display: inline-block; - height: 100%; - vertical-align: middle; - } - #site-logo { max-height: 40px; } From 4a880a758f33c32da0e59d005de7543d470adafa Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Tue, 20 Oct 2015 14:39:25 +0530 Subject: [PATCH 011/114] FEATURE: new Dismiss posts/topics modal --- .../discourse/controllers/discovery/topics.js.es6 | 6 +++++- .../discourse/mixins/bulk-topic-selection.js.es6 | 1 + .../javascripts/discourse/routes/discovery.js.es6 | 4 ++++ .../discourse/templates/discovery/topics.hbs | 6 ++---- .../discourse/templates/mobile/discovery/topics.hbs | 3 +-- .../discourse/templates/modal/dismiss-read.hbs | 10 ++++++++++ config/locales/client.en.yml | 6 +++--- 7 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 app/assets/javascripts/discourse/templates/modal/dismiss-read.hbs diff --git a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 index 0a2b046237..775aa1690b 100644 --- a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 @@ -2,6 +2,7 @@ import DiscoveryController from 'discourse/controllers/discovery'; import { queryParams } from 'discourse/controllers/discovery-sortable'; import BulkTopicSelection from 'discourse/mixins/bulk-topic-selection'; import { endWith } from 'discourse/lib/computed'; +import showModal from 'discourse/lib/show-modal'; const controllerOpts = { needs: ['discovery'], @@ -66,10 +67,13 @@ const controllerOpts = { }); }, - resetNew() { this.topicTrackingState.resetNew(); Discourse.Topic.resetNew().then(() => this.send('refresh')); + }, + + dismissReadPosts() { + showModal('dismiss-read', { title: 'topics.bulk.dismiss' }); } }, diff --git a/app/assets/javascripts/discourse/mixins/bulk-topic-selection.js.es6 b/app/assets/javascripts/discourse/mixins/bulk-topic-selection.js.es6 index c0e7650d8b..705f281a35 100644 --- a/app/assets/javascripts/discourse/mixins/bulk-topic-selection.js.es6 +++ b/app/assets/javascripts/discourse/mixins/bulk-topic-selection.js.es6 @@ -42,6 +42,7 @@ export default Ember.Mixin.create({ }); tracker.incrementMessageCount(); } + self.send('closeModal'); self.send('refresh'); }); } diff --git a/app/assets/javascripts/discourse/routes/discovery.js.es6 b/app/assets/javascripts/discourse/routes/discovery.js.es6 index 83b5b4186b..12b3e1f180 100644 --- a/app/assets/javascripts/discourse/routes/discovery.js.es6 +++ b/app/assets/javascripts/discourse/routes/discovery.js.es6 @@ -46,6 +46,10 @@ const DiscoveryRoute = Discourse.Route.extend(OpenComposer, { createTopic() { this.openComposer(this.controllerFor("discovery/topics")); + }, + + dismissRead(operationType) { + this.controllerFor("discovery/topics").send('dismissRead', operationType); } } diff --git a/app/assets/javascripts/discourse/templates/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/discovery/topics.hbs index b57679afc7..8af6278937 100644 --- a/app/assets/javascripts/discourse/templates/discovery/topics.hbs +++ b/app/assets/javascripts/discourse/templates/discovery/topics.hbs @@ -5,8 +5,7 @@ {{#if showDismissAtTop}}
{{#if showDismissRead}} - - + {{/if}} {{#if showResetNew}} @@ -55,8 +54,7 @@ {{conditional-loading-spinner condition=model.loadingMore}} {{#if allLoaded}} {{#if showDismissRead}} - - + {{/if}} {{#if showResetNew}} diff --git a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs index ae964b46e3..a58ce372e7 100644 --- a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs @@ -25,8 +25,7 @@ {{conditional-loading-spinner condition=model.loadingMore}} {{#if allLoaded}} {{#if showDismissRead}} - - + {{/if}} {{#if showResetNew}} diff --git a/app/assets/javascripts/discourse/templates/modal/dismiss-read.hbs b/app/assets/javascripts/discourse/templates/modal/dismiss-read.hbs new file mode 100644 index 0000000000..9136970027 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/modal/dismiss-read.hbs @@ -0,0 +1,10 @@ + + + diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 00c9a6b4a4..49da8b6a5b 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1010,10 +1010,10 @@ en: bulk: reset_read: "Reset Read" delete: "Delete Topics" - dismiss_posts: "Dismiss Posts" - dismiss_posts_tooltip: "Clear unread counts on these topics but continue to show them on my unread list when new posts are made" + dismiss: "Dismiss" + dismiss_body: "Would you like to dismiss just the new posts in these topics, or dismiss the topics entirely?" + dismiss_posts: "Dismiss Just New Posts" dismiss_topics: "Dismiss Topics" - dismiss_topics_tooltip: "Stop showing these topics in my unread list when new posts are made" dismiss_new: "Dismiss New" toggle: "toggle bulk selection of topics" actions: "Bulk Actions" From 35496cae4fae86bef2a8f76406e61718a3d036a3 Mon Sep 17 00:00:00 2001 From: cpradio Date: Tue, 20 Oct 2015 09:17:40 -0400 Subject: [PATCH 012/114] FIX: Fix the shortcuts shift+j and shift+k --- .../javascripts/discourse/lib/keyboard-shortcuts.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 index a17e4f50c0..f15711283c 100644 --- a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 +++ b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 @@ -353,8 +353,8 @@ export default { }, _changeSection(direction) { - const $sections = $('#navigation-bar li'), - active = $('#navigation-bar li.active'), + const $sections = $('.nav.nav-pills li'), + active = $('.nav.nav-pills li.active'), index = $sections.index(active) + direction; if (index >= 0 && index < $sections.length) { From d2502bd1272762272682e14ed085e65a65f1b6dc Mon Sep 17 00:00:00 2001 From: cpradio Date: Tue, 20 Oct 2015 09:40:43 -0400 Subject: [PATCH 013/114] FIX: Fix the shortcut shift+p for pinning/unpinning a topic --- app/assets/javascripts/discourse/controllers/topic.js.es6 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index 8a6c7b2287..8d54f9918a 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -374,10 +374,11 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { togglePinnedForUser() { if (this.get('model.pinned_at')) { - if (this.get('pinned')) { - this.get('content').clearPin(); + const topic = this.get('content'); + if (topic.get('pinned')) { + topic.clearPin(); } else { - this.get('content').rePin(); + topic.rePin(); } } }, From 0428bacfa943fa06861b383b9ab1d19b203bbcee Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 20 Oct 2015 12:09:59 -0400 Subject: [PATCH 014/114] SECURITY: A user could XSS themselves on their preference page --- .../javascripts/discourse/components/d-editor.js.es6 | 2 +- test/javascripts/components/d-editor-test.js.es6 | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6 index 4d8061c6e6..879cb9c643 100644 --- a/app/assets/javascripts/discourse/components/d-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/d-editor.js.es6 @@ -29,7 +29,7 @@ export default Ember.Component.extend({ preview(ready, value) { if (!ready) { return; } - const text = Discourse.Dialect.cook(value || "", {}); + const text = Discourse.Dialect.cook(value || "", {sanitize: true}); return text ? text : ""; }, diff --git a/test/javascripts/components/d-editor-test.js.es6 b/test/javascripts/components/d-editor-test.js.es6 index 7ae1c78239..f27607499c 100644 --- a/test/javascripts/components/d-editor-test.js.es6 +++ b/test/javascripts/components/d-editor-test.js.es6 @@ -19,6 +19,17 @@ componentTest('preview updates with markdown', { } }); +componentTest('preview sanitizes HTML', { + template: '{{d-editor value=value}}', + + test(assert) { + this.set('value', `">`); + andThen(() => { + assert.equal(this.$('.d-editor-preview').html().trim(), '

\">

'); + }); + } +}); + componentTest('updating the value refreshes the preview', { template: '{{d-editor value=value}}', From f8ff6fc0b56e9a415d4f0de703a18b5d752b9409 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 20 Oct 2015 12:42:14 -0400 Subject: [PATCH 015/114] FIX: Typo in teardown of `d-editor` --- .../javascripts/discourse/components/d-editor-modal.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/components/d-editor-modal.js.es6 b/app/assets/javascripts/discourse/components/d-editor-modal.js.es6 index 0b6f6920a6..049914fe2c 100644 --- a/app/assets/javascripts/discourse/components/d-editor-modal.js.es6 +++ b/app/assets/javascripts/discourse/components/d-editor-modal.js.es6 @@ -34,7 +34,7 @@ export default Ember.Component.extend({ }); }, - @on('willDestoryElement') + @on('willDestroyElement') _stopListening() { this.$().off('keydown.d-modal'); }, From 717be06f17e232f5bf26ff30efa2a2d0346b9104 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Tue, 20 Oct 2015 23:11:42 +0530 Subject: [PATCH 016/114] Update Translations --- config/locales/client.ar.yml | 14 +++++++++----- config/locales/client.bs_BA.yml | 4 ---- config/locales/client.cs.yml | 4 ---- config/locales/client.da.yml | 5 ----- config/locales/client.de.yml | 8 ++------ config/locales/client.es.yml | 18 +++++++++++++----- config/locales/client.fa_IR.yml | 4 ---- config/locales/client.fi.yml | 5 ----- config/locales/client.fr.yml | 20 ++++++++------------ config/locales/client.he.yml | 5 ----- config/locales/client.it.yml | 4 ---- config/locales/client.ja.yml | 4 ---- config/locales/client.ko.yml | 4 ---- config/locales/client.nb_NO.yml | 4 ---- config/locales/client.nl.yml | 5 ----- config/locales/client.pl_PL.yml | 19 ++++++++++++++----- config/locales/client.pt.yml | 14 +++++++++----- config/locales/client.pt_BR.yml | 5 ----- config/locales/client.ro.yml | 4 ---- config/locales/client.ru.yml | 6 +----- config/locales/client.sq.yml | 4 ---- config/locales/client.sv.yml | 4 ---- config/locales/client.te.yml | 4 ---- config/locales/client.tr_TR.yml | 4 ---- config/locales/client.uk.yml | 3 --- config/locales/client.zh_CN.yml | 5 ----- config/locales/client.zh_TW.yml | 4 ---- config/locales/server.ar.yml | 1 - config/locales/server.bs_BA.yml | 1 - config/locales/server.de.yml | 5 ++--- config/locales/server.es.yml | 7 ++++++- config/locales/server.fa_IR.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.ja.yml | 1 - config/locales/server.ko.yml | 1 - config/locales/server.nl.yml | 1 - config/locales/server.pl_PL.yml | 4 +++- config/locales/server.pt.yml | 7 +++++-- config/locales/server.pt_BR.yml | 1 - config/locales/server.ru.yml | 1 - config/locales/server.sq.yml | 1 - config/locales/server.tr_TR.yml | 1 - config/locales/server.zh_CN.yml | 1 - plugins/poll/config/locales/client.nl.yml | 6 ++++++ plugins/poll/config/locales/client.pl_PL.yml | 12 ++++++++++++ plugins/poll/config/locales/server.nl.yml | 6 ++++++ plugins/poll/config/locales/server.pl_PL.yml | 8 ++++++++ 50 files changed, 104 insertions(+), 150 deletions(-) diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index 5205a07c28..07528c903f 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -538,7 +538,6 @@ ar: tracked_categories: "Tracked" tracked_categories_instructions: "ستتبع بشكل تلقائي جميع المواضيع الجديدة في هذه التصنيفات. عدد المشاركات الجديدة سيظهر بجانب الموضوع." muted_categories: "كتم" - muted_categories_instructions: "تلقائيا ستقوم بمتابعة جميع المواضيع في هذا التصنيف , وسيتم اشعارك لجميع المشاركات والمواضيع وكذلك عداد الغيرمقروء والمشاركات الجديدة" delete_account: "حذف الحساب" delete_account_confirm: "هل انت متاكد من انك تريد حذف هذا المستخدم؟ هذا الإجراء دائماً!" deleted_yourself: "حسابك تم حذفه بنجاح" @@ -677,6 +676,13 @@ ar: user: "المستخدمين المدعويين" sent: "تم الإرسال" none: "لا توجد دعوات معلقة لعرضها." + truncated: + zero: "لا يوجد دعوات لعرضها." + one: "عرض الدعوة الأولى." + two: "عرض الدعوتان الأولتان." + few: "عرض الدعوات الأولية." + many: "عرض الدعوات {{count}} الأولى." + other: "عرض الدعوات {{count}} الأولى." redeemed: "دعوات مستخدمة" redeemed_tab: "محررة" redeemed_tab_with_count: "({{count}}) محررة" @@ -850,6 +856,7 @@ ar: admin_not_allowed_from_ip_address: "لا يمكنك تسجيل الدخول كمدير من خلال هذا العنوان الرقمي - IP." resend_activation_email: "اضغط هنا لإرسال رسالة إلكترونية أخرى لتفعيل الحساب." sent_activation_email_again: "لقد سبق وأن تم إرسال رسالة إلكترونية إلى {{currentEmail}} لتفعيل حسابك. تأكد من مجلد السبام في بريدك." + to_continue: "الرجاء تسجيل الدخول..." google: title: "مع جوجل" message: "التحقق من خلال حساب جوجل ( الرجاء التأكد من عدم تشغيل مانع الاعلانات المنبثقة في المتصفح)" @@ -874,6 +881,7 @@ ar: emoji_one: "تعبيرات" composer: emoji: "تعبيرات: ابتسامة" + more_emoji: "أكثر..." options: "خيارات" whisper: "همس" add_warning: "هذا تحذير رسمي" @@ -906,7 +914,6 @@ ar: show_edit_reason: "(اضف سبب التعديل)" reply_placeholder: "أكتب هنا. استخدم Markdown, BBCode, أو HTML للتشكيل. اسحب أو الصق الصور." view_new_post: "الاطلاع على أحدث مشاركاتك" - saving: "يتم الحفظ..." saved: "تم الحفظ" saved_draft: "جاري إضافة المسودة. اضغط للاستئناف" uploading: "يتم الرفع..." @@ -1210,7 +1217,6 @@ ar: description: "لن يتم إشعارك بأي جديد يخص هذه الرسالة الخاصة." muted: title: "مكتوم" - description: "لن يتم إشعارك بأي جديد يخص هذا الموضوع ولن يظهرهذا الموضوع في تبويب المواضيع الغير مقروءة." actions: recover: "استرجاع الموضوع" delete: "حذف الموضوع" @@ -1727,7 +1733,6 @@ ar: description: "سوف تُنبه اذا قام أحد بالاشارة لاسمك \"@name\" أو الرد عليك." muted: title: "كتم" - description: "لن يتم اعلامك باي مواضيع جديدة في هذه الفئة، ولن تظهر في صفحتك الخاصة بالمواضيع الغير مقروءه" flagging: title: 'شكرا لمساعدتك في إبقاء مجتمعنا نظيفاً.' private_reminder: 'التبليغات ذات خصوصية، تظهر فقط للمشرفين' @@ -1784,7 +1789,6 @@ ar: help: "هذا الموضوع غير مثبت بالنسبة لك, سيتم عرضه بالترتيب العادي" pinned_globally: title: "تثبيت عام" - help: "هذا الموضوع مثبت بشكل عام, سوف يظهر في المقدمة في جميع القوائم" pinned: title: "مثبت" help: "هذا الموضوع مثبت لك, سوف يتم عرضه في اول القسم" diff --git a/config/locales/client.bs_BA.yml b/config/locales/client.bs_BA.yml index 4946ca9e4f..242a214b74 100644 --- a/config/locales/client.bs_BA.yml +++ b/config/locales/client.bs_BA.yml @@ -338,7 +338,6 @@ bs_BA: watched_categories: "Watched" tracked_categories: "Tracked" muted_categories: "Muted" - muted_categories_instructions: "You will not be notified of anything about new topics in these categories, and they will not appear on your unread tab." delete_account: "Delete My Account" delete_account_confirm: "Are you sure you want to permanently delete your account? This action cannot be undone!" deleted_yourself: "Your account has been deleted successfully." @@ -620,7 +619,6 @@ bs_BA: edit_reason_placeholder: "zašto pravite izmjenu?" show_edit_reason: "(dodaj razlog izmjene)" view_new_post: "Pogledaj svoj novi post." - saving: "Čuvam..." saved: "Sačuvano!" saved_draft: "Imate sačuvan post. Kliknite ovdje da nastavite sa izmjenama" uploading: "Uplodujem..." @@ -817,7 +815,6 @@ bs_BA: description: "You will never be notified of anything about this private message." muted: title: "Mutirano" - description: "Obaviještenja za ovu temu su isključena i sistem vam neće prikazivati nove postove za ovu temu." actions: recover: "Un-Delete Topic" delete: "Delete Topic" @@ -1105,7 +1102,6 @@ bs_BA: help: "This topic is unpinned; it will display in default order" pinned_globally: title: "Zakačena Globalno" - help: "Ova tema je zakačena globalno; biće na vrhu svih listi" pinned: title: "Zakačena" help: "Ova tema je zakačena; biće na vrhu svoje kategorije" diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index 3d43f32535..d37152776b 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -399,7 +399,6 @@ cs: tracked_categories: "Sledované" tracked_categories_instructions: "Všechna nová témata v této kategorii budou automaticky hlídaná. Počet nových příspěvků se zobrazí vedle tématu." muted_categories: "Ztišené" - muted_categories_instructions: "Nebudete upozorněni na žádná nová témata v těchto kategoriích a ani se nebudou zobrazovat jako nepřečtené." delete_account: "Smazat můj účet" delete_account_confirm: "Jste si jisti, že chcete trvale odstranit svůj účet? Tuto akci nelze vrátit zpět!" deleted_yourself: "Váš účet byl úspěšně odstraněn." @@ -721,7 +720,6 @@ cs: edit_reason_placeholder: "proč byla nutná úprava?" show_edit_reason: "(přidat důvod úpravy)" view_new_post: "Zobrazit váš nový příspěvek." - saving: "Ukládám..." saved: "Uloženo!" saved_draft: "Máte rozepsaný příspěvek. Klikněte pro obnovení." uploading: "Nahrávám..." @@ -963,7 +961,6 @@ cs: description: "Nikdy nedostanete oznámení týkající se čehokoliv v této zprávě." muted: title: "Ztišené" - description: "nebudete dostávat žádná oznámení k tomuto tématu a ani se nebude zobrazovat v seznamu nepřečtených témat." actions: recover: "Vrátit téma" delete: "Odstranit téma" @@ -1423,7 +1420,6 @@ cs: help: "Pro vás toto téma není připnuté; bude se zobrazovat v běžném pořadí" pinned_globally: title: "Připnuté globálně" - help: "Toto téma je připnuto globálně, zobrazí se na vršku všech seznamů" pinned: title: "Připnuto" help: "Pro vás je toto téma připnuté; bude se zobrazovat na vrcholu seznamu ve své kategorii" diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index ae7e90996a..9b794b43f5 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -418,7 +418,6 @@ da: tracked_categories: "Fulgt" tracked_categories_instructions: "Du vil automatisk følge alle nye emner i disse kategorier. En optælling af nye indlæg vises ved emnet." muted_categories: "Ignoreret" - muted_categories_instructions: "Du ignorerer automatisk alle emner i disse kategorier" delete_account: "Slet min konto" delete_account_confirm: "Er du sikker på du vil slette din konto permanent? Dette kan ikke fortrydes!" deleted_yourself: "Din konto er nu slettet." @@ -782,7 +781,6 @@ da: show_edit_reason: "(tilføj en begrundelse for ændringen)" reply_placeholder: "Skriv her. Brug Markdown, BBCode eller HTML til at formattere. Træk eller indsæt billeder." view_new_post: "Se dit nye indlæg." - saving: "Gemmer…" saved: "Gemt!" saved_draft: "Kladde i gang. Vælg for at fortsætte med den." uploading: "Uploader…" @@ -1054,7 +1052,6 @@ da: description: "Du vil aldrig få notifikationer om denne besked." muted: title: "Stille!" - description: "du får ikke besked om nogen hændelser i dette emne, og det vil ikke fremgå af din liste over ulæste emner." actions: recover: "Gendan emne" delete: "Slet emne" @@ -1455,7 +1452,6 @@ da: description: "Du vil modtage en notifikation, hvis nogen nævner dit @name eller svarer dig." muted: title: "Ignoreret" - description: "Du vil aldrig blive underrettet om noget som helst angående nye emner i disse categorier, og de vil ikke blive vist under \"ulæste\"." flagging: title: 'Tak fordi du hjælper med at holde vores forum civiliseret!' private_reminder: 'flag er private, de er kun

synlige for personalet' @@ -1508,7 +1504,6 @@ da: help: "Dette emne er ikke fastgjort for dig; det vil blive vist i den normale rækkefølge" pinned_globally: title: "Fastgjort globalt" - help: "Dette emne er fastgjort globalt; det vil blive vist øverst på alle lister" pinned: title: "Fastgjort" help: "Dette emne er fastgjort for dig; det vil blive vist i toppen af dets kategori" diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index 87294580d9..06a8571b4e 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -416,7 +416,6 @@ de: tracked_categories: "Verfolgt" tracked_categories_instructions: "Du wirst automatisch allen neuen Themen in diesen Kategorien folgen. Die Anzahl der neuen Antworten wird bei den betroffenen Themen angezeigt." muted_categories: "Stummgeschaltet" - muted_categories_instructions: "Du erhältst keine Benachrichtigungen über neue Themen in diesen Kategorien und sie werden nicht in deiner Liste ungelesener Themen aufscheinen." delete_account: "Lösche mein Benutzerkonto" 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." @@ -724,6 +723,7 @@ de: admin_not_allowed_from_ip_address: "Von dieser IP-Adresse darfst du dich nicht als Administrator anmelden." resend_activation_email: "Klicke hier, um eine neue Aktivierungsmail zu schicken." sent_activation_email_again: "Wir haben dir eine weitere E-Mail zur Aktivierung an {{currentEmail}} geschickt. Es könnte ein paar Minuten dauern, bis diese ankommt; sieh auch im Spam-Ordner nach." + preferences: "Du musst eingeloggt sein, um deine Benutzereinstellungen bearbeiten zu können." google: title: "mit Google" message: "Authentifiziere mit Google (stelle sicher, dass keine Pop-up-Blocker aktiviert sind)" @@ -781,7 +781,6 @@ de: show_edit_reason: "(Bearbeitungsgrund hinzufügen)" reply_placeholder: "Schreib hier. Verwende Markdown, BBCode oder HTML zur Formatierung. Füge Bilder ein oder ziehe sie herein." view_new_post: "Sieh deinen neuen Beitrag an." - saving: "Speichere..." saved: "Gespeichert!" saved_draft: "Ein Beitrag ist in Arbeit. Zum Fortsetzen hier klicken." uploading: "Wird hochgeladen..." @@ -1055,7 +1054,6 @@ de: description: "Du erhältst keine Benachrichtigungen im Zusammenhang mit dieser Unterhaltung." muted: title: "Stummgeschaltet" - description: "Du erhältst keine Benachrichtigungen über dieses Thema und es wird nicht in deiner Liste ungelesener Themen aufscheinen." actions: recover: "Löschen rückgängig machen" delete: "Thema löschen" @@ -1455,7 +1453,6 @@ de: description: "Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder dir antwortet." muted: title: "Stummgeschaltet" - description: "Du erhältst keine Benachrichtigungen über neue Themen in diesen Kategorien und sie werden nicht in deinem \"ungelesen\"-Tab erscheinen." flagging: title: 'Danke für deine Mithilfe!' private_reminder: 'Meldungen sind vertraulich und nur für Mitarbeiter sichtbar' @@ -1508,7 +1505,6 @@ de: help: "Dieses Thema ist für dich losgelöst; es wird in der normalen Reihenfolge angezeigt" pinned_globally: title: "Global angeheftet" - help: "Dieses Thema ist global angeheftet; es wird immer am Anfang aller Listen auftauchen" pinned: title: "Angeheftet" help: "Dieses Thema ist für dich angeheftet; es wird immer am Anfang seiner Kategorie auftauchen" @@ -2572,7 +2568,7 @@ de: description: Hat in einem Thema mit mehr als 100 Beiträgen jeden Beitrag gelesen popular_link: name: Beliebter Link - description: Hat einen externen Link veröffentlicht, der mindestens 50 Klicks erhalten hat. + description: Hat einen externen Link veröffentlicht, welcher mindestens 50 Klicks erhalten hat. hot_link: name: Angesagter Link description: Hat einen externen Link veröffentlicht, welcher mindestens 300 Klicks erhalten hat. diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index bb349741bb..bedde32bdf 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -418,7 +418,7 @@ es: tracked_categories: "Siguiendo" tracked_categories_instructions: "Seguirás automáticamente todos los nuevos temas en estas categorías. Se añadirá un contador de posts nuevos y sin leer al lado del tema." muted_categories: "Silenciado" - muted_categories_instructions: "No será notificado de nuevos temas en estas categorías, y no aparecerán en la sección de \"no leídos\"." + muted_categories_instructions: "No serás notificado de ningún tema en estas categorías, y no aparecerán en la página de mensajes recientes." delete_account: "Borrar Mi Cuenta" delete_account_confirm: "¿Estás seguro que quieres borrar permanentemente tu cuenta? ¡Esta acción no puede ser revertida!" deleted_yourself: "Tu cuenta ha sido borrada exitosamente." @@ -557,6 +557,9 @@ es: user: "Invitar Usuario" sent: "Enviadas" none: "No hay ninguna invitación pendiente que mostrar." + truncated: + one: "Mostrando la primera invitación." + other: "Mostrando las primeras {{count}} invitaciones." redeemed: "Invitaciones aceptadas" redeemed_tab: "Usado" redeemed_tab_with_count: "Aceptadas ({{count}})" @@ -726,6 +729,9 @@ es: admin_not_allowed_from_ip_address: "No puedes iniciar sesión como admin desde esta dirección IP." resend_activation_email: "Has clic aquí para enviar el email de activación nuevamente." sent_activation_email_again: "Te hemos enviado otro e-mail de activación a {{currentemail}}. Podría tardar algunos minutos en llegar; asegúrate de revisar tu carpeta de spam." + to_continue: "Por favor, inicia sesión" + preferences: "Debes tener una sesión iniciada para cambiar tus preferencias de usuario." + forgot: "No me acuerdo de los detalles de mi cuenta." google: title: "con Google" message: "Autenticando con Google (asegúrate de desactivar cualquier bloqueador de pop ups)" @@ -750,6 +756,7 @@ es: emoji_one: "Emoji One" composer: emoji: "Emoji :smile:" + more_emoji: "más..." options: "Opciones" whisper: "susurrar" add_warning: "Ésta es una advertencia oficial." @@ -782,7 +789,7 @@ es: show_edit_reason: "(añadir motivo de edición)" reply_placeholder: "Escribe aquí. Usa Markdown, BBCode o HTML para darle formato. Arrastra o pega imágenes." view_new_post: "Ver tu nuevo post." - saving: "Guardando..." + saving: "Guardando" saved: "¡Guardado!" saved_draft: "Borrador en progreso. Selecciona para continuar." uploading: "Subiendo..." @@ -1054,7 +1061,7 @@ es: description: "Nunca se te notificará nada sobre este hilo de mensajes." muted: title: "Silenciar" - description: "Nunca recibirás notificaciones sobre este tema y no aparecerá en tu pestaña de no leídos." + description: "No serás notificado de algo relacionado con este tema, y no aparecerá en la página de mensajes recientes." actions: recover: "Deshacer borrar tema" delete: "Eliminar tema" @@ -1271,6 +1278,7 @@ es: revert_to_regular: "Eliminar el formato de post de staff" rebake: "Reconstruir HTML" unhide: "Deshacer ocultar" + change_owner: "Cambiar dueño" actions: flag: 'Reportar' defer_flags: @@ -1455,7 +1463,7 @@ es: description: "Se te notificará solo si alguien menciona tu @nombre o te responde a un post." muted: title: "Silenciadas" - description: "Nunca serás notificado sobre nuevos temas en estas categorías, y no aparecerán en tu pestaña de no leído." + description: "No serás notificado de ningún tema en estas categorías, y no aparecerán en la página de mensajes recientes." flagging: title: '¡Gracias por ayudar a mantener una comunidad civilizada!' private_reminder: 'los reportes son privados, son visibles únicamente por los administradores' @@ -1508,7 +1516,7 @@ es: help: "Este tema se ha dejado de destacar para ti; en tu listado de temas se mostrará en orden normal" pinned_globally: title: "Destacado globalmente" - help: "Este tema ha sido destacado globalmente, se mostrará en la parte superior de todas las listas" + help: "Este tema ha sido destacado globalmente, se mostrará en la parte superior de la página de mensajes recientes y de su categoría." pinned: title: "Destacado" help: "Este tema ha sido destacado para ti; se mostrará en la parte superior de su categoría" diff --git a/config/locales/client.fa_IR.yml b/config/locales/client.fa_IR.yml index 06d2bd7984..49d6c91455 100644 --- a/config/locales/client.fa_IR.yml +++ b/config/locales/client.fa_IR.yml @@ -339,7 +339,6 @@ fa_IR: tracked_categories: "پی‌گیری شده" tracked_categories_instructions: "شما به صورت خودکار تمام عناوین جدید در این دسته را پیگیری خواهید کرد. تعداد نوشته های جدید در کنار عنواین نمایش داده می‌شود." muted_categories: "بی صدا شد" - muted_categories_instructions: "از هر آن‌چه درباره موضوعات تازه در این دسته‌ بندی ها روی دهد، شما آگاه نخواهید شد و در تب نخواندهٔ‌ شما نمایش داده نمی‌شوند." delete_account: "حساب من را پاک کن" delete_account_confirm: "آیا مطمئنید که می‌خواهید شناسه‌تان را برای همیشه پاک کنید؟ برگشتی در کار نیست!" deleted_yourself: "حساب‌ کاربری شما با موفقیت حذف شد." @@ -660,7 +659,6 @@ fa_IR: edit_reason_placeholder: "چرا ویرایش می‌کنید؟" show_edit_reason: "(افزودن دلیل ویرایش)" view_new_post: "نوشته تازه‌تان را ببینید." - saving: "در حال ذخیره سازی..." saved: "اندوخته شد!" saved_draft: "در حال حاضر پیشنویس وجود دارد . برای از سر گیری انتخاب نمایید." uploading: "بارگذاری..." @@ -893,7 +891,6 @@ fa_IR: description: " در باره این پیام هرگز به شما اطلاع رسانی نخواهید شد" muted: title: "بی صدا شد" - description: "درباره این موضوع به شما هرگز اطلاع رسانی نخواهد شد٬ و در کنار جستار شمار خوانده نشده ها پدیدار نمی شود" actions: recover: "بازیابی موضوع" delete: "پاک کردن موضوع" @@ -1294,7 +1291,6 @@ fa_IR: help: "این موضوع برای شما شنجاق نشده است، آن طور منظم نمایش داده خواهد شد" pinned_globally: title: "به صورت سراسری سنجاق شد" - help: "این موضوع بصورت سراسری سنجاق شده است، آن بالای تمامی موضوعات نمایش داده خواهد شد" pinned: title: "سنجاق شد" help: "این موضوع برای شما سنجاق شده است، آن طور منظم در بالای دسته بندی نمایش داده خواهد شد." diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index 0b98358657..872159da16 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -418,7 +418,6 @@ fi: tracked_categories: "Seuratut" tracked_categories_instructions: "Näiden alueiden kaikki uudet ketjut asetetaan automaattisesti seurantaan. Uusien viestien lukumäärä näytetään ketjun otsikon vieressä." muted_categories: "Vaimennetut" - muted_categories_instructions: "Et saa imoituksia uusista viesteistä näillä alueilla, eivätkä ne näy Lukemattomat-välilehdellä." delete_account: "Poista tilini" delete_account_confirm: "Oletko varma, että haluat lopullisesti poistaa käyttäjätilisi? Tätä toimintoa ei voi perua!" deleted_yourself: "Käyttäjätilisi on poistettu." @@ -782,7 +781,6 @@ fi: show_edit_reason: "(lisää syy muokkaukselle)" reply_placeholder: "Kirjoita tähän. Käytä Markdownia, BBCodea tai HTML:ää muotoiluun. Raahaa tai liitä kuvia." view_new_post: "Katsele uutta viestiäsi." - saving: "Tallennetaan..." saved: "Tallennettu!" saved_draft: "Viestiluonnos kesken. Klikkaa tähän jatkaaksesi." uploading: "Lähettää..." @@ -1054,7 +1052,6 @@ fi: description: "Et saa mitään ilmoituksia tästä keskustelusta." muted: title: "Vaimenna" - description: "Et saa ilmoituksia mistään tässä ketjussa eikä se näy lukemattomat-välilehdessä." actions: recover: "Peru ketjun poisto" delete: "Poista ketju" @@ -1455,7 +1452,6 @@ fi: description: "Saat ilmoituksen jos joku mainitsee @nimesi tai vastaa sinulle." muted: title: "Vaimennettu" - description: "Et saa imoituksia uusista viesteistä näillä alueilla, eivätkä ne näy Lukemattomat-välilehdellä." flagging: title: 'Kiitos avustasi yhteisön hyväksi!' private_reminder: 'liput ovat yksityisiä, ne näkyvät ainoastaan henkilökunnalle' @@ -1508,7 +1504,6 @@ fi: help: "Ketjun kiinnitys on poistettu sinulta; se näytetään tavallisessa järjestyksessä." pinned_globally: title: "Kiinnitetty koko palstalle" - help: "Tämä ketju on nyt kiinnitetty koko palstalle; se näytetään kaikkien listausten ylimpänä" pinned: title: "Kiinnitetty" help: "Tämä ketju on kiinnitetty sinulle; se näytetään alueensa ensimmäisenä" diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index 1cc54b4c3a..ccf523d0ec 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -418,7 +418,6 @@ fr: tracked_categories: "Suivies" tracked_categories_instructions: "Vous allez suivre automatiquement tous les nouveaux sujets dans ces catégories. Le nombre de nouveaux messages apparaîtra à côté du sujet." muted_categories: "Désactivés" - muted_categories_instructions: "Vous ne recevrez aucune notification concernant les nouveaux sujets de ces catégories, et ils n'apparaitront pas dans votre onglet non lu." delete_account: "Supprimer mon compte" delete_account_confirm: "Êtes-vous sûr de vouloir supprimer définitivement votre compte ? Cette action ne peut être annulée !" deleted_yourself: "Votre compte a été supprimé avec succès." @@ -726,6 +725,7 @@ fr: admin_not_allowed_from_ip_address: "Vous ne pouvez pas vous connecter comme administrateur depuis cette adresse IP." resend_activation_email: "Cliquez ici pour envoyer à nouveau le courriel d'activation." sent_activation_email_again: "Nous venons d'envoyer un nouveau courriel d'activation à {{currentEmail}}. Il peut prendre quelques minutes à arriver; n'oubliez pas de vérifier votre répertoire spam." + to_continue: "Veuillez vous connecter" google: title: "via Google" message: "Authentification via Google (assurez-vous que les popups ne soient pas bloquées)" @@ -781,7 +781,6 @@ fr: show_edit_reason: "(ajouter la raison de l'édition)" reply_placeholder: "Écrivez ici. Utilisez Markdown, BBCode, ou HTML pour formatter. Glissez ou collez des images." view_new_post: "Voir votre nouveau message." - saving: "Sauvegarde…" saved: "Sauvegardé !" saved_draft: "Vous avez un message brouillon en cours. Sélectionner cette barre pour reprendre son édition." uploading: "Envoi en cours…" @@ -876,13 +875,13 @@ fr: select_file: "Sélectionner Fichier" image_link: "lien vers lequel pointe l'image" search: - sort_by: "Tri par" + sort_by: "Trier par" relevance: "Pertinence" latest_post: "Dernier Message" most_viewed: "Plus Vu" most_liked: "Plus Aimé" - select_all: "Tout sélectionner" - clear_all: "Tout supprimer" + select_all: "Sélectionner tout" + clear_all: "Supprimer tout" result_count: one: "1 résultat pour \"{{term}}\"" other: "{{count}} résultats pour \"{{term}}\"" @@ -1057,7 +1056,6 @@ fr: description: "Vous ne serez jamais averti de quoi que ce soit à propos de ce message." muted: title: "Silencieux" - description: "Vous ne serez jamais informé de quoi que ce soit à propos de ce sujet, et il n'apparaîtra pas dans votre onglet non lu." actions: recover: "Annuler Suppression Sujet" delete: "Supprimer Sujet" @@ -1436,7 +1434,7 @@ fr: email_in_allow_strangers: "Accepter les courriels d'utilisateurs anonymes sans compte" email_in_disabled: "La possibilité de créer des nouveaux sujets via courriel est désactivé dans les Paramètres. Pour l'activer," email_in_disabled_click: 'activer le paramètre "email in".' - suppress_from_homepage: "Supprimer cette catégorie de la page d'accueil" + suppress_from_homepage: "Retirer cette catégorie de la page d'accueil" allow_badges_label: "Autoriser les badges à être accordé dans cette catégorie" edit_permissions: "Éditer les permissions" add_permission: "Ajouter une Permission" @@ -1458,7 +1456,6 @@ fr: description: "Vous serez notifié si quelqu'un mentionne votre @pseudo ou vous répond." muted: title: "Silencieux" - description: "Vous ne serez jamais notifié concernant les nouveaux sujets dans ces catégories, et ils n'apparaîtront pas dans votre onglet non-lus." flagging: title: 'Merci de nous aider à garder notre communauté aimable !' private_reminder: 'les signalements sont privés, seulement visible aux modérateurs' @@ -1511,7 +1508,6 @@ fr: help: "Ce sujet est désépinglé pour vous; il sera affiché dans l'ordre par défaut" pinned_globally: title: "Épingler globalement" - help: "Ce sujet est épinglé globalement; il s'affichera en haut de toutes les listes de sujets" pinned: title: "Épingler" help: "Ce sujet est épinglé pour vous; il s'affichera en haut de sa catégorie" @@ -2031,9 +2027,9 @@ fr: impersonate: "incarner" anonymize_user: "rendre l'utilisateur anonyme" roll_up: "consolider des blocs d'IP" - change_category_settings: "modifier les paramètres de la catégorie" - delete_category: "supprimer catégorie" - create_category: "créer catégorie" + change_category_settings: "Modifier les paramètres de la catégorie" + delete_category: "Supprimer la catégorie" + create_category: "Créer une catégorie" screened_emails: title: "Courriels affichés" description: "Lorsque quelqu'un essaye de créé un nouveau compte, les adresses de courriel suivantes seront vérifiées et l'inscription sera bloquée, ou une autre action sera réalisée." diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index dc7098909b..8d0ea9014a 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -418,7 +418,6 @@ he: tracked_categories: "רגיל+" tracked_categories_instructions: "בקטגוריות אלה סך הפרסומים החדשים שלא נקראו יופיע לצד שם הפוסט." muted_categories: "מושתק" - muted_categories_instructions: "לא תקבלו התראות על נושאים חדשים בקטגוריות אלו, והם לא יופיעו בעמוד הלא נקראו שלך." delete_account: "מחק את החשבון שלי" delete_account_confirm: "אתה בטוח שברצונך למחוק את החשבון? לא ניתן לבטל פעולה זו!" deleted_yourself: "חשבונך נמחק בהצלחה." @@ -781,7 +780,6 @@ he: show_edit_reason: "(הוספת סיבת עריכה)" reply_placeholder: "הקלד כאן. השתמש ב Markdown, BBCode או HTML לערוך. גרור או הדבק תמונות." view_new_post: "הצגת את ההודעה החדשה שלך." - saving: "שומר..." saved: "נשמר!" saved_draft: "טיוטאת פרסום בתהליך, לחצו כדי להמשיך." uploading: "מעלה..." @@ -1053,7 +1051,6 @@ he: description: "לעולם לא תקבל/י התראה בנוגע להודעה זו." muted: title: "מושתק" - description: "לעולם לא תקבל/י התראות על הפוסט הזה, והוא לא יופיע בעמוד ה\"לא נקראו\" שלך." actions: recover: "שחזר פוסט" delete: "מחק פוסט" @@ -1454,7 +1451,6 @@ he: description: "תקבלו התראה אם מישהו יזכיר את @שם_המשתמש/ת שלך או ישיב לפרסום שלך." muted: title: "מושתק" - description: " אתה לעולם לא תקבל התראות על פוסטים חדשים מקטגוריות אלו והם לא יופיעו בטאב ״לא נקראו״." flagging: title: 'תודה על עזרתך לשמירה על תרבות הקהילה שלנו!' private_reminder: 'דגלים הם פרטיים וניתנים לצפייה ע"י הצוות בלבד' @@ -1507,7 +1503,6 @@ he: help: "פוסט זה אינו מקובע עבורך; הוא יופיע בסדר הרגיל" pinned_globally: title: "נעוץ גלובאלית" - help: "הפוסט הזה נעוץ גלובאלית; הוא יוצג בראש כל הרשימות" pinned: title: "נעוץ" help: "פוסט זה מקובע עבורך, הוא יופיע בראש הקטגוריה" diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index 9b82a49e3b..2e54405af8 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -418,7 +418,6 @@ it: tracked_categories: "Seguite" tracked_categories_instructions: "Seguirai automaticamente tutti i nuovi argomenti appartenenti a queste categorie. Di fianco all'argomento comparirà il conteggio dei nuovi messaggi." muted_categories: "Silenziate" - muted_categories_instructions: "Non ti verrà notificato nulla sui nuovi argomenti in queste categorie, e non compariranno nel tab dei non letti." delete_account: "Cancella il mio account" delete_account_confirm: "Sei sicuro di voler cancellare il tuo account in modo permanente? Questa azione non può essere annullata!" deleted_yourself: "Il tuo account è stato eliminato con successo." @@ -778,7 +777,6 @@ it: show_edit_reason: "(aggiungi motivo della modifica)" reply_placeholder: "Scrivi qui. Per formattare il testo usa Markdown, BBCode o HTML. Trascina o incolla le immagini." view_new_post: "Visualizza il tuo nuovo messaggio." - saving: "Salvataggio..." saved: "Salvato!" saved_draft: "Hai un messaggio in bozza in sospeso. Seleziona per riprendere la modifica." uploading: "In caricamento..." @@ -1042,7 +1040,6 @@ it: description: "Non ti verrà notificato nulla per questo messaggio." muted: title: "Silenziato" - description: "Non ti verrà notificato nulla di questo argomento e non comparirà nella tab dei non letti." actions: recover: "Ripristina Argomento" delete: "Cancella Argomento" @@ -1492,7 +1489,6 @@ it: help: "Questo argomento è per te spuntato; verrà mostrato con l'ordinamento di default" pinned_globally: title: "Appuntato Globalmente" - help: "Questo argomento è appuntato globalmente; verrà mostrato in cima ad ogni lista" pinned: title: "Appuntato" help: "Questo argomento è per te appuntato; verrà mostrato con l'ordinamento di default" diff --git a/config/locales/client.ja.yml b/config/locales/client.ja.yml index 8fac15c7b0..c2b8fbee2d 100644 --- a/config/locales/client.ja.yml +++ b/config/locales/client.ja.yml @@ -353,7 +353,6 @@ ja: tracked_categories: "トラック中" tracked_categories_instructions: "これらのカテゴリの新規トピックを自動的にトラックします。トピックに対して新しい投稿があった場合、トピック一覧に新しい投稿数がつきます。" muted_categories: "ミュート中" - muted_categories_instructions: "このカテゴリに投稿されたトピックについての通知を受け取りません。また、未読タブにも通知されません。" delete_account: "アカウントを削除する" delete_account_confirm: "本当にアカウントを削除しますか?削除されたアカウントを復元できません。" deleted_yourself: "あなたのアカウントは削除されました。" @@ -675,7 +674,6 @@ ja: edit_reason_placeholder: "編集する理由は何ですか?" show_edit_reason: "(編集理由を追加)" view_new_post: "新規ポストを見る。" - saving: "保存中..." saved: "保存完了!" saved_draft: "編集中の投稿があります。選択すると再開します" uploading: "アップロード中..." @@ -908,7 +906,6 @@ ja: description: "このメッセージについての通知を受け取りません。" muted: title: "ミュート" - description: "このトピックについての通知を受け取りません。また、未読タブにも通知されません。" actions: recover: "トピック削除の取り消し" delete: "トピック削除" @@ -1315,7 +1312,6 @@ ja: help: "このトピックはピン留めされていません。 既定の順番に表示されます。" pinned_globally: title: "全サイト的にピン留めされました" - help: "このトピックは全サイト的にピン留めされました。全てのリストのトップに表示されます。" pinned: title: "ピン留め" help: "このトピックはピン留めされています。常にカテゴリのトップに表示されます" diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml index af910a06b7..18016dee63 100644 --- a/config/locales/client.ko.yml +++ b/config/locales/client.ko.yml @@ -385,7 +385,6 @@ ko: tracked_categories: "추적하기" tracked_categories_instructions: "이 카테고리 내의 새로운 토픽들을 추적하도록 자동으로 설정됩니다. 토픽 옆에 읽지 않은 게시글의 수가 표시됩니다." muted_categories: "알림 끄기" - muted_categories_instructions: "이 카테고리들에 새로 작성되는 새로운 토픽에 대한 알림이 오지 않도록 합니다. '읽지 않은'탭에서도 보이지 않게 됩니다." delete_account: "내 계정 삭제" delete_account_confirm: "정말로 계정을 삭제할까요? 이 작업은 되돌릴 수 없습니다." deleted_yourself: "계정이 삭제 되었습니다." @@ -728,7 +727,6 @@ ko: edit_reason_placeholder: "why are you editing?" show_edit_reason: "(add edit reason)" view_new_post: "새로운 글을 볼 수 있습니다." - saving: "저장 중..." saved: "저장 완료!" saved_draft: "작성중인 글이 있습니다. 계속 작성하려면 여기를 클릭하세요." uploading: "업로딩 중..." @@ -970,7 +968,6 @@ ko: description: "이 메시지에 대해 어떠한 알림도 받지 않지 않습니다." muted: title: "알림 없음" - description: "아무 알림도 없습니다. '읽지 않은 글' 탭에 나타나지 않습니다." actions: recover: "토픽 다시 복구" delete: "토픽 삭제" @@ -1380,7 +1377,6 @@ ko: help: "이 토픽은 핀 제거 되었습니다. 목록에서 일반적인 순서대로 표시됩니다." pinned_globally: title: "핀 지정됨 (전역적)" - help: "이 토픽은 전역적으로 핀 지정 되었습니다. 모든 목록의 최상위에 표시됩니다." pinned: title: "핀 지정됨" help: "이 토픽은 고정되었습니다. 카테고리의 상단에 표시됩니다." diff --git a/config/locales/client.nb_NO.yml b/config/locales/client.nb_NO.yml index 1c96eda677..757c14190d 100644 --- a/config/locales/client.nb_NO.yml +++ b/config/locales/client.nb_NO.yml @@ -395,7 +395,6 @@ nb_NO: tracked_categories: "Sporet" tracked_categories_instructions: "Du vil automatisk spore alle nye emner i disse kategoriene. Antallet uleste og nye innlegg vil vises ved emnets oppføring." muted_categories: "Dempet" - muted_categories_instructions: "Du vil ikke bli varslet om nye emner i disse kategoriene og de vil ikke vises i listen over ulest innhold." delete_account: "Slett kontoen min" delete_account_confirm: "Er du sikker på at du vil slette kontoen din permanent? Denne handlingen kan ikke angres!" deleted_yourself: "Slettingen av din konto har vært vellykket." @@ -724,7 +723,6 @@ nb_NO: edit_reason_placeholder: "hvorfor endrer du?" show_edit_reason: "(legg till endringsbegrunnelse)" view_new_post: "Se ditt nye innlegg." - saving: "Lagrer..." saved: "Lagret!" saved_draft: "Innleggsutkast. Velg for å fortsette." uploading: "Laster opp..." @@ -966,7 +964,6 @@ nb_NO: description: "Du vil ikke få varslinger om noe i denne meldingnen. " muted: title: "Dempet" - description: "du vil ikke bli varslet om noen ting i dette emnet, og det vil ikke visest som ulest." actions: recover: "Gjenopprett emne" delete: "slett emne" @@ -1397,7 +1394,6 @@ nb_NO: help: "Dette emnet er ikke lenger fastsatt, det vil vises i vanlig rekkefølge" pinned_globally: title: "Globalt fastsatt" - help: "Dette emnet er globalt fastsatt; det vil vises på toppen av alle lister" pinned: title: "Fastsatt" help: "Dette emnet er fastsatt for deg; det vil vises i toppen av sin kategori" diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index a11b68a3e7..d95016bcb3 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -418,7 +418,6 @@ nl: tracked_categories: "Gevolgd" tracked_categories_instructions: "Je volgt automatisch alle nieuwe topics in deze categorie. Naast het topic wordt het aantal nieuwe berichten weergegeven." muted_categories: "Genegeerd" - muted_categories_instructions: "Je zal geen notificaties krijgen over nieuwe topics en berichten in deze categoriën en ze verschijnen niet op je ongelezen overzicht." delete_account: "Verwijder mijn account" delete_account_confirm: "Weet je zeker dat je je account definitief wil verwijderen? Dit kan niet meer ongedaan gemaakt worden!" deleted_yourself: "Je account is verwijderd." @@ -782,7 +781,6 @@ nl: show_edit_reason: "(geef een reden)" reply_placeholder: "Typ hier. Gebruik Markdown, BBCode, of HTML om op te maken. Sleep of plak afbeeldingen." view_new_post: "Bekijk je nieuwe bericht." - saving: "Opslaan..." saved: "Opgeslagen!" saved_draft: "Bezig met conceptbericht. Selecteer om door te gaan." uploading: "Uploaden..." @@ -1054,7 +1052,6 @@ nl: description: "Je zal geen enkele notificatie ontvangen over dit bericht." muted: title: "Negeren" - description: "Je zal geen notificaties krijgen van dit topic en het zal ook niet verschijnen in je lijst ongelezen topics." actions: recover: "Herstel topic" delete: "Verwijder topic" @@ -1455,7 +1452,6 @@ nl: description: "Je krijgt een notificatie als iemand je @naam noemt of reageert op een bericht van jou." muted: title: "Genegeerd" - description: "Je zal geen notificaties krijgen over nieuwe topics en berichten in deze categoriën en ze verschijnen niet op je ongelezen overzicht." flagging: title: 'Bedankt voor het helpen beleefd houden van onze gemeenschap!' private_reminder: 'vlaggen zijn privé, alleen zichtbaar voor de staf' @@ -1508,7 +1504,6 @@ nl: help: "Dit topic is niet langer vastgepind voor je en zal weer in de normale volgorde getoond worden" pinned_globally: title: "Globaal vastgepind" - help: "Deze topic is globaal vastgepind en zal bovenaan alle topiclijsten getoond worden" pinned: title: "Vastgepind" help: "Dit topic is vastgepind voor je en zal bovenaan de categorie getoond worden" diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml index e6618d7f66..b4dd85df58 100644 --- a/config/locales/client.pl_PL.yml +++ b/config/locales/client.pl_PL.yml @@ -448,7 +448,7 @@ pl_PL: tracked_categories: "Śledzone" tracked_categories_instructions: "Będziesz automatycznie śledzić wszystkie nowe tematy w tych kategoriach. Licznik nowych wpisów pojawi się obok tytułu na liście tematów." muted_categories: "Wyciszone" - muted_categories_instructions: "Nie będziesz powiadamiany o niczym dotyczącym nowych tematów w tych kategoriach, i nie będą się one pojawiać na karcie nieprzeczytanych." + muted_categories_instructions: "Nie będziesz powiadamiany o nowych tematach w tych kategoriach. Nie pojawią się na liście nieprzeczytanych." delete_account: "Usuń moje konto" delete_account_confirm: "Czy na pewno chcesz usunąć swoje konto? To nieodwracalne!" deleted_yourself: "Twoje konto zostało usunięte." @@ -587,6 +587,10 @@ pl_PL: user: "Zaproszony(-a) użytkownik(-czka)" sent: "Wysłane" none: "Nie ma żadnych zaproszeń do wyświetlenia." + truncated: + one: "Wyświetlanie pierwszego zaproszenia." + few: "Wyświetlanie {{count}} pierwszych zaproszeń." + other: "Wyświetlanie {{count}} pierwszych zaproszeń." redeemed: "Cofnięte zaproszenia" redeemed_tab: "Przyjęte" redeemed_tab_with_count: "Zrealizowane ({{count}})" @@ -757,6 +761,9 @@ pl_PL: admin_not_allowed_from_ip_address: "Nie możesz się zalogować jako admin z tego adresu IP." resend_activation_email: "Kliknij tutaj, aby ponownie wysłać email z aktywacją konta." sent_activation_email_again: "Wysłaliśmy do ciebie kolejny email z aktywacją konta na {{currentEmail}}. Zanim dotrze, może minąć kilka minut; pamiętaj, żeby sprawdzić folder ze spamem." + to_continue: "Zaloguj się" + preferences: "Musisz się zalogować, aby zmieniać swoje ustawienia." + forgot: "Nie pamiętam konta" google: title: "przez Google" message: "Uwierzytelnianie przy pomocy konta Google (upewnij się, że blokada wyskakujących okienek nie jest włączona)" @@ -781,6 +788,7 @@ pl_PL: emoji_one: "Emoji One" composer: emoji: "Emoji :smile:" + more_emoji: "więcej…" options: "Opcje" whisper: "szept" add_warning: "To jest oficjalne ostrzeżenie." @@ -813,7 +821,7 @@ pl_PL: show_edit_reason: "(dodaj powód edycji)" reply_placeholder: "Pisz w tym miejscu. Wspierane formatowanie to Markdown, BBCode lub HTML. Możesz też przeciągnąć tu obrazek." view_new_post: "Zobacz Twój nowy wpis." - saving: "Zapisuję…" + saving: "Zapisywanie" saved: "Zapisano!" saved_draft: "Posiadasz zachowany szkic wpisu. Kliknij tu aby wznowić jego edycję." uploading: "Wczytuję…" @@ -1094,7 +1102,7 @@ pl_PL: description: "Nie będziesz otrzymywać powiadomień dotyczących tej dyskusji." muted: title: "Wyciszenie" - description: "Nie będzie jakichkolwiek powiadomień dotyczących tego tematu i nie będzie się on pojawiać na karcie nieprzeczytanych." + description: "Nie otrzymasz powiadomień o nowych wpisach w tym temacie. Nie pojawią się na liście nieprzeczytanych" actions: recover: "Przywróć temat" delete: "Usuń temat" @@ -1322,6 +1330,7 @@ pl_PL: revert_to_regular: "Wyłącz kolor moderatora" rebake: "Odśwież HTML" unhide: "Wycofaj ukrycie" + change_owner: "Zmiana właściciela" actions: flag: 'Oflaguj' defer_flags: @@ -1524,7 +1533,7 @@ pl_PL: description: "Dostaniesz powiadomienie jedynie, gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis." muted: title: "Wyciszone" - description: "Nie otrzymasz powiadomień o tematach w tych kategoriach i nie będą się one pojawiać na karcie Nieprzeczytane." + description: "Nie otrzymasz powiadomień o nowych tematach w tych kategoriach. Nie pojawią się na liście nieprzeczytanych." flagging: title: 'Dziękujemy za pomoc w utrzymaniu porządku w naszej społeczności!' private_reminder: 'oflagowania są poufne i widoczne jedynie dla obsługi serwisu' @@ -1578,7 +1587,7 @@ pl_PL: help: "Temat nie jest przypięty w ramach twojego konta. Będzie wyświetlany w normalnej kolejności." pinned_globally: title: "Przypięty globalnie" - help: "Temat przypięty globalnie. Będzie wyświetlany na początku wszystkich list." + help: "Ten temat jest przypięty globalnie. Będzie wyświetlany na początku głównej listy oraz swojej kategorii." pinned: title: "Przypięty" help: "Temat przypięty dla twojego konta. Będzie wyświetlany na początku swojej kategorii." diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index 33e22e21a8..0e6bd6054a 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -418,7 +418,7 @@ pt: tracked_categories: "Acompanhado" tracked_categories_instructions: "Irá acompanhar automaticamente todos os novos tópicos nestas categorias. Uma contagem de novas mensagens irá aparecer junto ao tópico." muted_categories: "Silenciado" - muted_categories_instructions: "Não será notificado relativamente a novos tópicos nestas categorias, e não aparecerão no seu separador de tópicos não lidos." + muted_categories_instructions: "Não será notificado de nada acerca de novos tópicos nestas categorias, e estes não irão aparecer nos recentes." delete_account: "Eliminar A Minha Conta" delete_account_confirm: "Tem a certeza que pretende eliminar a sua conta de forma permanente? Esta ação não pode ser desfeita!" deleted_yourself: "A sua conta foi eliminada com sucesso." @@ -729,6 +729,9 @@ pt: admin_not_allowed_from_ip_address: "Não pode iniciar sessão como administrador a partir desse endereço IP." resend_activation_email: "Clique aqui para enviar o email de ativação novamente." sent_activation_email_again: "Enviámos mais um email de ativação para o endereço {{currentEmail}}. Pode ser que demore alguns minutos; certifique-se que verifica a sua pasta de spam ou lixo." + to_continue: "Por favor Inicie Sessão" + preferences: "Necessita de ter sessão iniciada para alterar as suas preferências de utilizador." + forgot: "Não me recordo dos detalhes da minha conta" google: title: "com Google" message: "A autenticar com Google (certifique-se de que os bloqueadores de popup estão desativados)" @@ -786,7 +789,7 @@ pt: show_edit_reason: "(adicione a razão para a edição)" reply_placeholder: "Digite aqui. Utilize Markdown, BBCode, ou HTML para formatar. Arraste ou cole imagens." view_new_post: "Ver a sua nova mensagem." - saving: "A Guardar..." + saving: "A Guardar" saved: "Guardado!" saved_draft: "Rascunho da mensagem em progresso. Selecione para continuar." uploading: "A carregar…" @@ -1058,7 +1061,7 @@ pt: description: "Não será notificado de nada relacionado com esta mensagem." muted: title: "Silenciado" - description: "Não será notificado de nada que esteja relacionado com este tópico, e este não será apresentado no seu separador de 'não lido'." + description: "Nunca será notificado de nada acerca deste tópico, e este não irá aparecer nos recentes." actions: recover: "Recuperar Tópico" delete: "Eliminar Tópico" @@ -1275,6 +1278,7 @@ pt: revert_to_regular: "Remover Cor do Pessoal" rebake: "Reconstruir HTML" unhide: "Mostrar" + change_owner: "Mudar Titularidade" actions: flag: 'Sinalizar' defer_flags: @@ -1459,7 +1463,7 @@ pt: description: "Será notificado se alguém mencionar o seu @nome ou responder-lhe." muted: title: "Silenciado" - description: "Nunca será notificado de nada acerca de novos tópicos nestas categorias, e estes não irão aparecer no seu separador de não lidos." + description: "Nunca será notificado de nada acerca de novos tópicos nestas categorias, e estes não irão aparecer nos recentes." flagging: title: 'Obrigado por ajudar a manter a nossa comunidade cívica!' private_reminder: 'as sinalizações são privadas, visíveis apenas para o pessoal' @@ -1512,7 +1516,7 @@ pt: help: "Este tópico foi desafixado por si; será mostrado na ordem habitual" pinned_globally: title: "Fixado Globalmente" - help: "Este tópico está fixado globalmente; será mostrado no topo de todas as listas" + help: "Este tópico está fixado globalmente; será exibido no topo dos recentes e da sua categoria" pinned: title: "Fixado" help: "Este tópico foi fixado por si; será mostrado no topo da sua categoria" diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml index 9803ba5b08..3f3b4261c3 100644 --- a/config/locales/client.pt_BR.yml +++ b/config/locales/client.pt_BR.yml @@ -418,7 +418,6 @@ pt_BR: tracked_categories: "Monitorado" tracked_categories_instructions: "Automaticamente monitora todos novos tópicos nestas categorias. Uma contagem de posts não lidos e novos aparecerá próximo ao tópico." muted_categories: "Silenciado" - muted_categories_instructions: "Você não será notificado sobre novos tópicos dessas categorias e eles não vão aparecer na guia de mensagens não lidas." delete_account: "Excluir Minha Conta" delete_account_confirm: "Tem certeza de que deseja excluir permanentemente a sua conta? Essa ação não pode ser desfeita!" deleted_yourself: "Sua conta foi excluída com sucesso." @@ -786,7 +785,6 @@ pt_BR: show_edit_reason: "(adicione motivo da edição)" reply_placeholder: "Escreva aqui. Use Markdown, BBCode ou HTML para formatar. Arraste ou cole uma imagens." view_new_post: "Ver sua nova resposta." - saving: "Salvando..." saved: "Salvo!" saved_draft: "Rascunho salvo, clique em selecionar para continuar editando." uploading: "Enviando..." @@ -1058,7 +1056,6 @@ pt_BR: description: "Você nunca será notificado de qualquer coisa sobre essa mensagem privada." muted: title: "Silenciar" - description: "Você nunca será notificado em nada sobre esse tópico e não aparecerá na guia de não lidas." actions: recover: "Recuperar Tópico" delete: "Apagar tópico" @@ -1459,7 +1456,6 @@ pt_BR: description: "Você será notificado se alguém mencionar o seu @nome ou responder à sua mensagem." muted: title: "Silenciar" - description: "Você não será notificado sobre novos tópicos dessas categorias e eles não vão aparecer na guia de mensagens não lidas." flagging: title: 'Obrigado por ajudar a manter a civilidade da nossa comunidade!' private_reminder: 'sinalizações são privadas, apenas ficam visíveis a moderação' @@ -1512,7 +1508,6 @@ pt_BR: help: "Este tópico está desfixado para você; ele será mostrado em ordem normal" pinned_globally: title: "Fixo Globalmente" - help: "Esse tópico está fixo; ele será mostrado no topo de todas as listas" pinned: title: "Fixo" help: "Este tópico está fixado para você; ele será mostrado no topo de sua categoria" diff --git a/config/locales/client.ro.yml b/config/locales/client.ro.yml index 265ca0fd51..9841082a3b 100644 --- a/config/locales/client.ro.yml +++ b/config/locales/client.ro.yml @@ -370,7 +370,6 @@ ro: watched_categories: "Văzut" tracked_categories: "Tracked" muted_categories: "Muted" - muted_categories_instructions: "Nu vei fii notificat de dicuțiile apărute în aceste categorii și ele nu vor apărea în tabul necitite." delete_account: "Șterge-mi contul" delete_account_confirm: "Ești sigur că vrei sa ștergi contul? Această acțiune poate fi anulată!" deleted_yourself: "Contul tău a fost șters cu succes." @@ -689,7 +688,6 @@ ro: edit_reason_placeholder: "de ce editați?" show_edit_reason: "(adaugă motivul editării)" view_new_post: "Vizualizează noua postare." - saving: "Salvează..." saved: "Salvat!" saved_draft: "Ai o postare în stadiul neterminat. Fă click oriunde pentru a continua editarea." uploading: "Încarcă..." @@ -919,7 +917,6 @@ ro: description: "Nu veţi fi niciodată notificat despre acest mesaj." muted: title: "Silențios" - description: "Nu veți fi notificat de nimic în legătură cu această dicuție, nu va apărea în tabul necitite." actions: recover: "Rescrie discuție" delete: "Șterge Discuție" @@ -1357,7 +1354,6 @@ ro: help: "Această discuţie va fi afişată în ordinea iniţială, nici un mesaj nu este promovat la inceputul listei." pinned_globally: title: "Fixată Global" - help: "Această discuție a fost fixată global; va fi afișată în capătul tuturor listelor" pinned: title: "Fixată" help: "Aceast mesaj va fi promovat. Va fi afişat la începutul discuţiei." diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index 24f0829381..b71d9ed55a 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -26,7 +26,7 @@ ru: tb: ТБ short: thousands: "{{number}} тыс." - millions: "{{number}} млн" + millions: "{{number}} млн." dates: time: "HH:mm" long_no_year: "D MMM HH:mm" @@ -471,7 +471,6 @@ ru: tracked_categories: "Отслеживаемые разделы" tracked_categories_instructions: "Автоматически следить за всеми новыми темами из следующих разделов. Счетчик новых и непрочитанных сообщений будет появляться рядом с названием темы." muted_categories: "Выключенные разделы" - muted_categories_instructions: "Вы не будете получать уведомления о новых темах в этих разделах. Также, они не будут показываться во вкладке Непрочитанное." delete_account: "Удалить мою учётную запись" delete_account_confirm: "Вы уверены, что хотите удалить свою учётную запись? Отменить удаление будет невозможно!" deleted_yourself: "Ваша учётная запись была успешно удалена." @@ -819,7 +818,6 @@ ru: edit_reason_placeholder: "почему вы хотите изменить?" show_edit_reason: "(добавить причину редактирования)" view_new_post: "Посмотреть созданное вами сообщение." - saving: "Сохранение..." saved: "Сохранено!" saved_draft: "Черновик сообщения. Нажмите, чтобы продолжить редактирование." uploading: "Загрузка..." @@ -1083,7 +1081,6 @@ ru: description: "Никогда не получать уведомлений, связанных с этой беседой." muted: title: "Без уведомлений" - description: "Никогда не получать уведомлений, связанных с этой темой, и не показывать ее во вкладке «Непрочитанные»." actions: recover: "Отменить удаление темы" delete: "Удалить тему" @@ -1573,7 +1570,6 @@ ru: help: "Эта тема не закреплена; она будет отображаться в обычном порядке" pinned_globally: title: "Закреплена глобально" - help: "Эта тема закреплена глобально и будет отображаться в начале всех списков" pinned: title: "Закреплена" help: "Тема закреплена; она будет показана вверху соответствующего раздела" diff --git a/config/locales/client.sq.yml b/config/locales/client.sq.yml index 937919b681..01ce1e1a11 100644 --- a/config/locales/client.sq.yml +++ b/config/locales/client.sq.yml @@ -383,7 +383,6 @@ sq: tracked_categories: "Gjurmuar" tracked_categories_instructions: "You will automatically track all new topics in these categories. A count of new posts will appear next to the topic." muted_categories: "Heshtur" - muted_categories_instructions: "You will not be notified of anything about new topics in these categories, and they will not appear on your unread tab." delete_account: "Fshi Llogarin Time" delete_account_confirm: "Are you sure you want to permanently delete your account? This action cannot be undone!" deleted_yourself: "Llogaria juaj u fshi me sukses." @@ -706,7 +705,6 @@ sq: edit_reason_placeholder: "pse jeni duke e redaktuar?" show_edit_reason: "(arsye redaktimit)" view_new_post: "Shikoni postimin tuaj te ri." - saving: "Duke ruajtur..." saved: "U Ruajt!" saved_draft: "Post draft in progress. Select to resume." uploading: "Duke nga ngarkuar..." @@ -946,7 +944,6 @@ sq: description: "You will never be notified of anything about this message." muted: title: "Muted" - description: "You will never be notified of anything about this topic, and it will not appear on your unread tab." actions: recover: "Un-Delete Topic" delete: "Fshi Diskutimin" @@ -1383,7 +1380,6 @@ sq: help: "This topic is unpinned for you; it will display in regular order" pinned_globally: title: "Pinned Globally" - help: "This topic is pinned globally; it will display at the top of all lists" pinned: title: "Pinned" help: "This topic is pinned for you; it will display at the top of its category" diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index 762ab29f8e..a5c21f6699 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -381,7 +381,6 @@ sv: tracked_categories: "Bevakade" tracked_categories_instructions: "Du kommer automatiskt att följa alla nya ämnen i dessa kategorier. Antalet nya inlägg visas bredvid ämnet." muted_categories: "Tystad" - muted_categories_instructions: "Du kommer inte att få notifieringar om nya ämnen inom dessa kategorier, och de kommer inte att visas under din \"oläst\"-flik." delete_account: "Radera mitt konto" delete_account_confirm: "Är du säker på att du vill ta bort ditt konto permanent? Denna åtgärd kan inte ångras!" deleted_yourself: "Ditt konto har tagits bort." @@ -702,7 +701,6 @@ sv: edit_reason_placeholder: "varför redigerar du?" show_edit_reason: "(lägg till anledningar för redigering)" view_new_post: "Visa ditt nya inlägg." - saving: "Sparar..." saved: "Sparat!" saved_draft: "Utkast för inlägg. Välj för att fortsätta." uploading: "Laddar upp..." @@ -938,7 +936,6 @@ sv: description: "Du kommer aldrig bli notifierad om något gällande detta meddelande." muted: title: "Dämpad" - description: "Du kommer aldrig meddelas om detta ämne alls, och den kommer inte visas i din \"oläst\"-flik." actions: recover: "Återställ ämne" delete: "Radera ämne" @@ -1366,7 +1363,6 @@ sv: help: "Detta ämne är oklistrat för dig. Det visas i vanlig ordning" pinned_globally: title: "Klistrat Globalt" - help: "Det här ämnet är klistrat globalt; det kommer att visas högst upp i alla listor" pinned: title: "Klistrat" help: "Detta ämne är klistrat för dig. Det visas i toppen av dess kategori" diff --git a/config/locales/client.te.yml b/config/locales/client.te.yml index 331aa68870..1190b07614 100644 --- a/config/locales/client.te.yml +++ b/config/locales/client.te.yml @@ -303,7 +303,6 @@ te: watched_categories: "ఒకకన్నేసారు" tracked_categories: "గమనించారు" muted_categories: "నిశ్శబ్దం" - muted_categories_instructions: "ఈ వర్గాలలోని ఏ కొత్త విషయాల గురించీ మీకు ప్రకటనలు రావు. ఇంకా అవి చదవని ట్యాబులో కనిపించవు." delete_account: "నా ఖాతా తొలగించు" delete_account_confirm: "నిజ్జంగా మీరు మీ ఖాతాను శాస్వతంగా తొలగించాలనుకుంటున్నారా? ఈ చర్య రద్దుచేయలేరు సుమా! " deleted_yourself: "మీ ఖాతా విజయవంతంగా తొలగించబడింది. " @@ -597,7 +596,6 @@ te: edit_reason_placeholder: "మీరెందుకు సవరిస్తున్నారు?" show_edit_reason: "(సవరణ కారణం రాయండి)" view_new_post: "మీ కొత్త టపా చూడండి" - saving: "భద్రమవుతోంది..." saved: "భద్రం!" saved_draft: "టపా చిత్తుప్రతి నడుస్తోంది. కొనసాగించుటకు ఎంచుకోండి." uploading: "ఎగుమతవుతోంది..." @@ -805,7 +803,6 @@ te: title: "నిశ్శబ్దం" muted: title: "నిశ్శబ్దం" - description: "ఈ ప్రైవేటు సందేశం నుండి మీకు అస్సలు ప్రకటనలు రావు. ఇంకా చదవని సంఖ్య కనిపించదు." actions: recover: "విషయం తొలగింపు రద్దుచేయి" delete: "విషయం తొలగించు" @@ -1157,7 +1154,6 @@ te: help: "ఈ విషయం మీకు అగ్గుచ్చబడింది. ఇది ఇహ క్రమ వరుసలోనే కనిపిస్తుంది" pinned_globally: title: "సార్వత్రికంగా గుచ్చారు" - help: "ఈ విషయం సార్వత్రికంగా గుచ్చారు; అన్ని వరుసల్లోనూ ఇది అగ్రభాగాన కనిపిస్తుంది." pinned: title: "గుచ్చారు" help: "ఈ విషయం మీకు గుచ్చబడింది. దాని వర్గంలో అది అగ్రభాగాన కనిపిస్తుంది." diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml index 6ab81095b3..3502a74a05 100644 --- a/config/locales/client.tr_TR.yml +++ b/config/locales/client.tr_TR.yml @@ -386,7 +386,6 @@ tr_TR: tracked_categories: "Takip edildi" tracked_categories_instructions: "Bu kategorilerdeki tüm yeni konuları otomatik olarak takip edeceksiniz. Okunmamış ve yeni gönderilerin sayısı ilgili konunun yanında belirecek." muted_categories: "Susturuldu" - muted_categories_instructions: "Bu kategorideki yeni konular okunmamışlar sekmenizde belirmeyecek, ve haklarında hiçbir bildirim almayacaksınız." delete_account: "Hesabımı Sil" delete_account_confirm: "Hesabınızı kalıcı olarak silmek istediğinize emin misiniz? Bu işlemi geri alamazsınız!" deleted_yourself: "Hesabınız başarıyla silindi." @@ -726,7 +725,6 @@ tr_TR: show_edit_reason: "(düzenleme sebebi ekle)" reply_placeholder: "Buraya yazın. Biçimlendirmek için Markdown, BBCode ya da HTML kullanabilirsin. Resimleri sürükleyebilir ya da yapıştırabilirsin." view_new_post: "Yeni gönderinizi görüntüleyin." - saving: "Kaydediliyor..." saved: "Kaydedildi!" saved_draft: "Gönderi taslağı işleniyor. Geri almak için seçin. " uploading: "Yükleniyor..." @@ -983,7 +981,6 @@ tr_TR: description: "Bu mesajlaşmayla ilgili hiç bir bildirim almayacaksınız." muted: title: "Susturuldu" - description: "Bu konu okunmamışlar sekmenizde belirmeyecek, ve hakkında hiç bir bildirim almayacaksınız." actions: recover: "Konuyu Geri Getir" delete: "Konuyu Sil" @@ -1403,7 +1400,6 @@ tr_TR: help: "Bu konu sizin için başa tutturulmuyor; normal sıralama içerisinde görünecek" pinned_globally: title: "Her Yerde Başa Tutturuldu" - help: "Bu konu her yerde başa tutturuldu; tüm listelerin başında görünecek" pinned: title: "Başa Tutturuldu" help: "Bu konu sizin için başa tutturuldu; kendi kategorisinin en üstünde görünecek" diff --git a/config/locales/client.uk.yml b/config/locales/client.uk.yml index 3e3b842183..afb008b9dd 100644 --- a/config/locales/client.uk.yml +++ b/config/locales/client.uk.yml @@ -299,7 +299,6 @@ uk: watched_categories: "Відслідковувані" tracked_categories: "Відстежувані" muted_categories: "Ігноровані" - muted_categories_instructions: "Ви не будете отримувати жодних сповіщень про нові теми у цих категоріях, і вони не з'являтимуться у вкладці Непрочитані." delete_account: "Delete My Account" delete_account_confirm: "Are you sure you want to permanently delete your account? This action cannot be undone!" deleted_yourself: "Your account has been deleted successfully." @@ -547,7 +546,6 @@ uk: edit_reason_placeholder: "чому Ви редагуєте допис?" show_edit_reason: "(додати причину редагування)" view_new_post: "Перегляньте свій новий допис." - saving: "Збереження..." saved: "Збережено!" uploading: "Завантаження..." show_preview: 'попередній перегляд »' @@ -689,7 +687,6 @@ uk: title: "Ігнорувати" muted: title: "Ігнорувати" - description: "Ви ніколи не будете отримувати жодних сповіщень з цієї теми, і вона не буде відображатися у Вашій вкладці \"Непрочитані\"." actions: recover: "Відкликати видалення теми" delete: "Видалити тему" diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index 9f64ce6cd0..91f5aedc91 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -388,7 +388,6 @@ zh_CN: tracked_categories: "已追踪" tracked_categories_instructions: "你将会自动追踪这些分类中的所有新主题。新帖数量将在每个主题后显示。" muted_categories: "已屏蔽" - muted_categories_instructions: "你不会收到这些分类的新主题的任何通知,他们也不会出现在你的未读标签中。" delete_account: "删除我的帐号" delete_account_confirm: "你真的要永久删除自己的账号吗?删除之后无法恢复!" deleted_yourself: "你的帐号已被成功删除。" @@ -751,7 +750,6 @@ zh_CN: show_edit_reason: "(添加编辑理由)" reply_placeholder: "正文。使用 Markdown、BBCode 或 HTML 格式化内容。拖拽或粘贴图片。" view_new_post: "浏览你的新帖子。" - saving: "保存中..." saved: "已保存!" saved_draft: "帖子还没写完,点击继续" uploading: "上传中..." @@ -1015,7 +1013,6 @@ zh_CN: description: "你不会收到关于此消息的任何通知。" muted: title: "防打扰" - description: "你不会收到关于此主题的任何通知,也不会在你的未阅选项卡中显示。" actions: recover: "撤销删除主题" delete: "删除主题" @@ -1387,7 +1384,6 @@ zh_CN: description: "如果某人@你或者回复你,你将收到通知。" muted: title: "免打扰" - description: "你不会收到这些分类的新主题的任何通知,他们也不会出现在你的未读标签中。" flagging: title: '感谢帮助社群远离邪恶!' private_reminder: '标记是不公开的,只有职员才可以见到' @@ -1439,7 +1435,6 @@ zh_CN: help: "主题已经解除置顶;它将以默认顺序显示" pinned_globally: title: "全局置顶" - help: "本主题已置顶;它将始终显示在它所属分类的顶部" pinned: title: "置顶" help: "本主题已置顶;它将始终显示在它所属分类的顶部" diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml index 299ef0bd11..a377ca822c 100644 --- a/config/locales/client.zh_TW.yml +++ b/config/locales/client.zh_TW.yml @@ -339,7 +339,6 @@ zh_TW: watched_categories: "關注" tracked_categories: "追蹤" muted_categories: "靜音" - muted_categories_instructions: "你將不會收到此分類的新討論話題通知,以及不會出現在未讀欄內。" delete_account: "刪除我的帳號" delete_account_confirm: "你真的要刪除帳號嗎?此動作不能被還原!" deleted_yourself: "你的帳號已成功刪除" @@ -667,7 +666,6 @@ zh_TW: edit_reason_placeholder: "你為什麼做編輯?" show_edit_reason: "(請加入編輯原因)" view_new_post: "檢視你的新文章。" - saving: "正在儲存..." saved: "儲存完畢!" saved_draft: "草稿待完成,點擊繼續。" uploading: "正在上傳..." @@ -880,7 +878,6 @@ zh_TW: description: "你將不會再收到關於此訊息的通知。" muted: title: "靜音" - description: "你將不會收到任何關於此討論話題的通知,此討論話題也不會出現在你的未讀分頁裡。" actions: recover: "復原已刪除的討論話題" delete: "刪除討論話題" @@ -1258,7 +1255,6 @@ zh_TW: help: "此討論話題已取消置頂,將會以預設順序顯示。" pinned_globally: title: "全區置頂" - help: "此討論話題已全區置頂,將顯示在所有討論話題列表的最上方" pinned: title: "已釘選" help: "此討論話題已置頂,將顯示在它所屬分類話題列表的最上方" diff --git a/config/locales/server.ar.yml b/config/locales/server.ar.yml index 47d1c6266c..e2a15dbf82 100644 --- a/config/locales/server.ar.yml +++ b/config/locales/server.ar.yml @@ -885,7 +885,6 @@ ar: summary_likes_required: "الحد الأدنى للإعجابات في الموضوع قبل \"تلخيص هذا الموضوع\" مفعل." summary_percent_filter: "عندما يضغط المستخدم على \"تلخيص هذا الموضوع\"، يظهر أعلى % o المشاركات." summary_max_results: "الحد الأقصى للمشاركات العائدة بـ 'ملخص هذا الموضوع'" - enable_private_messages: "يسمح مستوى الثقة 1 للمستخدمين بإنشاء والرد على الرسائل" enable_long_polling: "ناقل الرسالة المستخدم للاشعارات يمكن أن يستخدم التصويت الطويل." long_polling_base_url: "قاعدة URL تستخدم للتصويت الطويل (عندما CDN يخدم المحتوى الديناميكي, تأكد لتعيين هذا لسحب الأصل) eg: http://origin.site.com" long_polling_interval: "المدة الزمنية التي سينتظرها الخادم قبل الرد على العملاء في حالة عدم وجود أية بيانات لارسالها (للأعضاء المسجلين فقط)" diff --git a/config/locales/server.bs_BA.yml b/config/locales/server.bs_BA.yml index af53526909..f2f172e5cb 100644 --- a/config/locales/server.bs_BA.yml +++ b/config/locales/server.bs_BA.yml @@ -500,7 +500,6 @@ bs_BA: summary_posts_required: "Minimum posts in a topic before 'Summarize This Topic' is enabled" summary_likes_required: "Minimum likes in a topic before 'Summarize This Topic' is enabled" summary_percent_filter: "When a user clicks 'Summarize This Topic', show the top % of posts" - enable_private_messages: "Allow trust level 1 users to create private messages and reply to private messages" enable_long_polling: "Message bus used for notification can use long polling" 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" diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml index 5d08d72792..d7c006626e 100644 --- a/config/locales/server.de.yml +++ b/config/locales/server.de.yml @@ -734,7 +734,6 @@ de: summary_likes_required: "Mindestanzahl an \"Gefällt mir\" Wertungen in einem Thema, bevor die \"Thema zusammenfassen\" Funktion aktiviert wird." summary_percent_filter: "Zeige die besten (n)% der Beiträge eines Themas in der \"Thema zusammenfassen\"-Ansicht." summary_max_results: "Maximale Anzahl der sichtbaren Beiträge beim Zusammenfassen von Themen" - enable_private_messages: "Erlaube Benutzern auf Vertrauensstufe 1, Direktnachrichten zu erstellen und zu beantworten." enable_long_polling: "Nachrichtenbus für Benachrichtigungen kann Long-Polling nutzen." long_polling_base_url: "Basis-URL für Long Polling (wenn zum Ausliefern von dynamischen Inhalten ein CDN verwendet wird, setze es auf Origin Pull), z. B. http://origin.site.com" long_polling_interval: "Wartezeit, bevor der Server auf Clients reagiert, wenn keine Daten gesendet werden müssen (nur für angemeldete Benutzer)" @@ -1767,11 +1766,11 @@ de: nice_post: | Dieses Abzeichen hast du erhalten, weil du eine Antwort erstellt hast, welche 10 Likes erhalten hat. Gut gemacht! nice_topic: | - Dieses Abzeichen hast du erhalten, weil du ein Thema erstellt hast, welche 10 Likes erhalten hat. Gut gemacht! + Dieses Abzeichen hast du erhalten, weil du ein Thema erstellt hast, welches 10 Likes erhalten hat. Gut gemacht! good_post: | Dieses Abzeichen hast du erhalten, weil du eine Antwort erstellt hast, welche 25 Likes erhalten hat. Gute Arbeit! good_topic: | - Dieses Abzeichen hast du erhalten, weil du ein Thema erstellt hast, welche 25 Likes erhalten hat. Gute Arbeit! + Dieses Abzeichen hast du erhalten, weil du ein Thema erstellt hast, welches 25 Likes erhalten hat. Gute Arbeit! great_post: | Dieses Abzeichen hast du erhalten, weil du einen Beitrag erstellt hast, welcher 50 Likes erhalten hat. Wow! great_topic: | diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml index 879f5c1bd3..b6b395d70d 100644 --- a/config/locales/server.es.yml +++ b/config/locales/server.es.yml @@ -745,7 +745,7 @@ es: summary_likes_required: "Mínimo de \"me gusta\" en un tema para habilitar 'Resumen de este tema'" summary_percent_filter: "Cuando un usuario hace clic en 'Resumen de este tema', se muestra el n % mejores posts" summary_max_results: "Máximo de posts devueltos en \"Resumen de este tema\"" - enable_private_messages: "Permitir a los usuarios de nivel de confianza 1 crear y responder mensajes" + enable_private_messages: "Permitir a los usuarios de nivel 1 (configurable vía nivel de confianza para enviar mensajes) crear y responder mensajes" enable_long_polling: "Los mensajes usados para notificaciones pueden usar el long polling" long_polling_base_url: "URL base usada para el 'long polling' (cuando un CDN esta sirviendo contenido dinámico, asegúrate de ajustar esto al 'pull' de origen) ejemplo: http://origin.site.com" long_polling_interval: "Cantidad de tiempo que el servidor debe de esperar antes de responder a los clientes que no hay datos enviados (solamente usuarios con sesión iniciada)." @@ -764,6 +764,7 @@ es: notify_mods_when_user_blocked: "Si un usuario es bloqueado automáticamente, enviar un mensaje a todos los moderadores." flag_sockpuppets: "Si un nuevo usuario responde a un tema desde la misma dirección de IP que el nuevo usuario que inició el tema, reportar los posts de los dos como spam en potencia." traditional_markdown_linebreaks: "Utiliza saltos de línea tradicionales en Markdown, que requieren dos espacios al final para un salto de línea." + allow_html_tables: "Permitir la inserción de tablas en Markdown usando etiquetas HTML. Se permitirá usar TABLE, THEAD, TD, TR o TH (requiere un rebake completo para los posts antiguos que contengan tablas)" post_undo_action_window_mins: "Número de minutos durante los cuales los usuarios pueden deshacer sus acciones recientes en un post (me gusta, reportes, etc)." must_approve_users: "Los miembros administración deben aprobar todas las nuevas cuentas antes de que se les permita el acceso al sitio. AVISO: ¡habilitar esta opción en un sitio activo revocará el acceso a los usuarios que no sean moderadores o admin!" ga_tracking_code: "Código de Google Analytics, ej: UA-12345678-9; visita http://google.com/analytics" @@ -790,6 +791,7 @@ es: topics_per_period_in_top_summary: "Número de mejores temas mostrados en el resumen de mejores temas." topics_per_period_in_top_page: "Número de mejores temas mostrados en la vista expandida al clicar en 'ver más'." redirect_users_to_top_page: "Redirigir automáticamente a los nuevos usuarios y a los ausentes de larga duración a la página de mejores temas." + top_page_default_timeframe: "Período de tiempo por defecto para la página de temas top" show_email_on_profile: "Mostrar el e-mail los usuarios en su perfil (solamente visibles para ellos mismos y el staff)" email_token_valid_hours: "Los tokens para restablecer contraseña olvidada / activar cuenta son válidos durante (n) horas." email_token_grace_period_hours: "Los tokens para restablecer contraseña olvidada / activar cuenta son válidos durante (n) horas de periodo de gracia, después de ser redimidos." @@ -875,6 +877,7 @@ es: avatar_sizes: "Lista de tamaños de avatar generados automáticamente." external_system_avatars_enabled: "Usar un servicio externo para los avatares." external_system_avatars_url: "Dirección URL del servicio externo para los avatares. Sustituciones permitidas: {username} {first_letter} {color} {size}" + default_opengraph_image_url: "URL de la imagen opengraph por defecto." enable_flash_video_onebox: "Habilitar el embebido de enlaces swf y flv (Adobe Flash) en formato Onebox. AVISO: podría introducir riesgos de seguridad." default_invitee_trust_level: "Nivel de confianza por defecto (0-4) para usuarios invitados." default_trust_level: "Nivel de confianza por defecto (0-4) para los nuevos usuarios. ¡AVISO! Cambiar esto puede resultar en riesgo por spam." @@ -901,6 +904,7 @@ es: tl3_links_no_follow: "No remover rel=nofollow de los enlaces publicados por usuarios con nivel de confianza 3." min_trust_to_create_topic: "El mínimo nivel de confianza requerido para crear un nuevo tema." min_trust_to_edit_wiki_post: "El mínimo nivel de confianza requerido para editar un post marcado como wiki." + min_trust_to_send_messages: "Mínimo nivel de confianza requerido para crear nuevos mensajes directos." newuser_max_links: "Cuántos enlaces puede un nuevo usuario añadir a un post." newuser_max_images: "Cuántas imágenes puede un nuevo usuario añadir a un post." newuser_max_attachments: "Cuántos adjuntos puede un nuevo usuario añadir a un post." @@ -1021,6 +1025,7 @@ es: embed_username_key_from_feed: "Clave para extraer el nombre de usuario en Discourse desde el feed." embed_truncate: "Truncar los posts embebidos." embed_post_limit: "Número máximo de posts a embeber." + embed_username_required: "Se requiere el nombre de usuario para la creación de temas." embed_whitelist_selector: "Selector CSS para los elementos que están permitidos en los embebidos." embed_blacklist_selector: "Selector CSS para los elementos que están eliminados desde los embebidos." notify_about_flags_after: "Si hay reportes que no han sido revisados después de este número de horas, enviar un correo electrónico al contact_email. Deshabilita esta opción introduciendo el valor 0." diff --git a/config/locales/server.fa_IR.yml b/config/locales/server.fa_IR.yml index d96de19f21..63f60e8c16 100644 --- a/config/locales/server.fa_IR.yml +++ b/config/locales/server.fa_IR.yml @@ -670,7 +670,6 @@ fa_IR: summary_likes_required: "حداقل پسندها در این جستار قبل از اینکه \" خلاصه این جستار\" فعال شود" summary_percent_filter: "وقتی کاربر بر روی ' خلاصه این جستار ' کلیک کرد٬‌ % بهترین نوشته ها را نشان بده" summary_max_results: "حداکثر نوشته های برگردانده شد با \" خلاصه این جستار\"" - enable_private_messages: "به کاربران سطح اعتماد 1 برای ساختن پیام ها و ارسال پاسخ با پیام ها اجازه بده" enable_long_polling: "پیام اتوبوس استفاده شده برای آگاه سازی می تواند برای رای گیری طولانی استفاده شود. " long_polling_base_url: " URL پایه استفاده شده برای رای گیری طولانی ( وقتی CDN خدمت محتوای پویا می دهد٬ مطمئن شوید از تنظیم بودن منشا این کشش) برای نمونه : http://origin.site.com" long_polling_interval: "مدت زمانی که سرور باید صبر کند قبل پاسخ دادن به مشتری ها وقتی در آنجا داده ای برای ارسال نیست ( فقط کاربران وارد شده )" diff --git a/config/locales/server.fi.yml b/config/locales/server.fi.yml index 76bdb7bfaa..4763891356 100644 --- a/config/locales/server.fi.yml +++ b/config/locales/server.fi.yml @@ -754,7 +754,6 @@ fi: summary_likes_required: "Montako tykkäystä ketjussa pitää olla, jotta ketjun tiivistelmä otetaan käyttöön" summary_percent_filter: "Kun käyttäjä klikkaa 'Näytä ketjun tiivistelmä', näytä paras % viesteistä" summary_max_results: "Maksimimäärä viestejä, jotka näytetään ketjun tiivistelmässä" - enable_private_messages: "Salli luottamustason 1 käyttäjien luoda ja vastata viesteihin" enable_long_polling: "Ilmoitusten käyttämä viestiväylä voi käyttää long pollingia" long_polling_base_url: "Base URL, jota käytetään long pollingissa (kun CDN on käytössä, varmista että tähän on asetettu origin pull) esim: http://origin.site.com" long_polling_interval: "Kuinka kauan palvelimen pitäisi odottaa ennen vastaamista asiakkaalle, kun ei ole mitään dataa jota lähettää (vain kirjautuneille käyttäjille)" diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml index 1eddf94c85..a3aa8d836c 100644 --- a/config/locales/server.fr.yml +++ b/config/locales/server.fr.yml @@ -732,7 +732,6 @@ fr: summary_likes_required: "Nombre de J'aime minimum dans un sujet avant que le 'Résumé du sujet' soit activé" summary_percent_filter: "Quand un utilisateur clique sur 'Résumé du sujet', montrer le top % des messages" summary_max_results: "Nombre maximum de messages retournés par 'Résumé de ce sujet'" - enable_private_messages: "Autoriser les utilisateurs de niveau de confiance 1 à créer des messages et à y répondre" enable_long_polling: "Utiliser les requêtes longues pour le flux de notifications." long_polling_base_url: "Racine de l'url utilisée pour les requêtes longues (dans le cas de l'utilisation d'un CDN pour fournir du contenu dynamique, pensez à le configurer en mode \"origin pull\") par exemple: http://origin.site.com" long_polling_interval: "Délai d'attente du serveur avant de répondre aux clients lorsqu'il n'y a pas de données à envoyer\n(réservé aux utilisateurs connectés)" diff --git a/config/locales/server.he.yml b/config/locales/server.he.yml index 42bb1f077c..8fa6c28cd4 100644 --- a/config/locales/server.he.yml +++ b/config/locales/server.he.yml @@ -732,7 +732,6 @@ he: summary_likes_required: "מינימום הלייקים לנושא לפני שהאפשרות \"סיכום נושא זה\" תתאפשר" summary_percent_filter: "כאשר משתמש/ת מקליקים על \"סיכום נושא זה\", הציגו את % o הפרסומים הראשונים" summary_max_results: "מספר הפרסומים שיוחזרו באמצעות \"סיכום נושא זה\"" - enable_private_messages: "אפשרו למשתמשים בעלי רמת_אמון 1 ליצור הודעות ולהגיב להודעות" enable_long_polling: "Message bus used for notification can use long polling" long_polling_base_url: "בסיס ה-URL שנמצא בשימוש עבור long polling (כאשר CDN מחזיר תוכן דינמי, זכרו להגדיר את ערך זה ל-Origin pull, דוגמת http://origin.site.com)" long_polling_interval: "כמות הזמן שהשרת צריך לחכות לפני שעונה ללקוחות, כאשר אין מידע לשליחה (משתמשים רשומים מחוברים למערכת בלבד)" diff --git a/config/locales/server.it.yml b/config/locales/server.it.yml index ab6349b801..a447956326 100644 --- a/config/locales/server.it.yml +++ b/config/locales/server.it.yml @@ -693,7 +693,6 @@ it: summary_likes_required: "Minimo numero di \"Mi piace\" in un argomento affinché venga abilitato 'Riassumi Questo Argomento'" summary_percent_filter: "Quando un utente clicca su 'Riassumi Questo Argomento', mostra i primi % messaggi" summary_max_results: "Massimo numero di messaggi mostrati in 'Riassumi Argomento'" - enable_private_messages: "Autorizza gli utenti con livello di esperienza 1 a creare e rispondere ai messaggi" enable_long_polling: "Il message bus per le notifiche può usare il long polling" long_polling_base_url: "URL di base usato per il long polling (quando una CDN serve contenuto dinamico, bisogna impostarlo come origin pull) es. http://origin.site.com" long_polling_interval: "Tempo di attesa prima che il server risponda ai client che non ci sono dati da trasmettere (solo per utenti autenticati)" diff --git a/config/locales/server.ja.yml b/config/locales/server.ja.yml index 2c2a0e80c9..cd804206a3 100644 --- a/config/locales/server.ja.yml +++ b/config/locales/server.ja.yml @@ -655,7 +655,6 @@ ja: summary_likes_required: "'トピックサマリー'が有効になるために必要な最小「いいね!」数" summary_percent_filter: "ユーザが'トピックサマリー'をクリックしたとき, 上位何パーセントのポストを表示するか" summary_max_results: "'トピックサマリー'として返却される最大ポスト数" - enable_private_messages: "トラストレベル1のユーザーにプライベートメッセージの作成と返信を許可する" enable_long_polling: "通知用のメッセージバスによるロングポーリングの利用を許可する" long_polling_base_url: "ロングポーリングのベースURL(CDNが動的コンテンツを配信している場合、これをoriginに指定してください) eg: http://origin.site.com" long_polling_interval: "ユーザに送信するデータが存在しないとき、サーバが待機する時間(ログインユーザーのみ)" diff --git a/config/locales/server.ko.yml b/config/locales/server.ko.yml index 050cabe74c..88b59e5857 100644 --- a/config/locales/server.ko.yml +++ b/config/locales/server.ko.yml @@ -652,7 +652,6 @@ ko: summary_likes_required: "하나의 글타래에 대하여 요약본 보기 모드가 활성화되기 전까지 요구되는 최소 좋아요 수" summary_percent_filter: "요약본 보기를 클릭시, 글 중에 몇 %의 상위 글을 보여줄 것인가?" summary_max_results: "이 주제에 대한 요약 글 최대 갯수" - enable_private_messages: "회원등급 1인 유저들에게 메세지 작성과 메세지 답변을 허용하기" enable_long_polling: "Message bus used for notification can use long polling" long_polling_base_url: "long polling에 사용 될 Base URL (CDN이 동적 콘텐트를 제공할 시에는 origin pull로 설정) eg: http://origin.site.com" long_polling_interval: "보낼 데이터가 없을 때 응답 전에 서버가 기다려야하는 시간 (로그인된 유저 전용)" diff --git a/config/locales/server.nl.yml b/config/locales/server.nl.yml index 9e177d56b3..826bdd84b6 100644 --- a/config/locales/server.nl.yml +++ b/config/locales/server.nl.yml @@ -735,7 +735,6 @@ nl: summary_likes_required: "Minimum aantal Likes in een topic voor weergave ´Samenvatting Topic´" summary_percent_filter: "Als iemand op 'Samenvatting Topic' klikt, laat dan de top % van de berichten zien" summary_max_results: "Maximaal aantal berichten in weergave ´Samenvatting Topic´" - enable_private_messages: "Sta toe dat trust level 1 gebruikers berichten kunnen maken en kunnen antwoorden op berichten" enable_long_polling: "De 'message bus' die gebruikt wordt voor notificaties kan 'long polling' gebruiken." long_polling_base_url: "Basis URL voor ´long polling´ (indien via een CDN dynamische content wordt toegepast, kies hier dan 'origin pull´) b.v. http://originele.website.com" long_polling_interval: "De hoeveelheid tijd die de server zou moeten wachten voordat het clients beantwoord als er geen data te verzenden is (alleen voor ingelogde gebruikers)" diff --git a/config/locales/server.pl_PL.yml b/config/locales/server.pl_PL.yml index 7bcde12cf4..4703b50241 100644 --- a/config/locales/server.pl_PL.yml +++ b/config/locales/server.pl_PL.yml @@ -75,6 +75,7 @@ pl_PL: max_username_length_exists: "Nie możesz ustawić maksymalnej długości nazwy użytkownika poniżej najdłuższej nazwy użytkownika." max_username_length_range: "Nie możesz ustawić maksimum poniżej minimum." default_categories_already_selected: "Nie możesz wybrać kategorii użytej w innej liście." + s3_upload_bucket_is_required: "Nie możesz wysyłać na S3 jeżeli nie podasz 's3_upload_bucket'." bulk_invite: file_should_be_csv: "Wysłany plik powinien być w formacie CSV lub TXT." backup: @@ -618,7 +619,9 @@ pl_PL: xaxis: "Dzień" yaxis: "Urządzenie zalogowane w zapytaniach API" page_view_anon_mobile_reqs: + title: "Anonimowe zapytania do API" xaxis: "Dzień" + yaxis: "Mobilne anonimowe zapytania do API" http_background_reqs: title: "Tło" xaxis: "Dzień" @@ -758,7 +761,6 @@ pl_PL: summary_likes_required: "Minimalna liczba polubień w temacie zanim 'Podsumowanie tematu' jest dostępne" summary_percent_filter: "Gdy użytkownik kliknie na 'Podsumowaniu tematu', pokaż % najlepszych wpisów" summary_max_results: "Maksymalna liczba wpisów w 'Podsumowaniu tematu'" - enable_private_messages: "Zezwalaj użytkownikom o 1 poziomie zaufania na tworzenie wiadomości i odpowiadanie na nie." enable_long_polling: "Message bus used for notification can use long polling" anon_polling_interval: "How often should anonymous clients poll in milliseconds" 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)" diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml index 1732af7fe2..90e0704ca4 100644 --- a/config/locales/server.pt.yml +++ b/config/locales/server.pt.yml @@ -757,7 +757,7 @@ pt: summary_likes_required: "Número mínimo de gostos num tópico antes que 'Resumir Este Tópico' seja ativo." summary_percent_filter: "Quando um utilizador clica em 'Resumir Este Tópico', mostrar as melhores % de mensagens" summary_max_results: "Número máximo de mensagens devolvidas por 'Resumo deste Tópico'" - enable_private_messages: "Permitir que utilizadores de nível de confiança 1 possam criar e responder a mensagens" + enable_private_messages: "Permitir que utilizadores de nível de confiança 1 (configurável através do nível de confiança mínimo para enviar mensagens) criem mensagens e respostas a mensagens." enable_long_polling: "O sistema de mensagens usado para notificações pode fazer solicitações longas" long_polling_base_url: "URL base usada para solicitação ao servidor (quando um CDN serve conteúdo dinâmico, certifique-se de configurá-lo para a 'pull' original) ex: http://origem.sítio.com" long_polling_interval: "Quantidade de tempo que um servidor deve esperar antes de notificar os clientes quando não há dados para serem enviados (apenas utilizadores ligados)" @@ -889,6 +889,7 @@ pt: avatar_sizes: "Lista de tamanhos de avatar gerados automaticamente." external_system_avatars_enabled: "Utilize o serviço do sistema externo de avatars." external_system_avatars_url: "URL do serviço do sistema externo de avatars. Substituições permitidas são {nome_de_utilizador} {primeira_letra} {cor} {tamanho}" + default_opengraph_image_url: "URL da imagem opengraph por defeito." enable_flash_video_onebox: "Ativar a incorporação de hiperligações swf e flv (Adobe Flash) em caixas únicas. AVISO: pode introduzir riscos de segurança." default_invitee_trust_level: "Nível de Confiança padrão (0-4) para utilizadores convidados." default_trust_level: "Nível de Confiança padrão (0-4) para todos os novos utilizadores. AVISO! Alterar isto irá colocá-lo em sério risco de spam." @@ -915,6 +916,7 @@ pt: tl3_links_no_follow: "Não remover rel=nofollow das hiperligações publicadas por utilizadores com Nível de Confiança 3." min_trust_to_create_topic: "O Nível de Confiança mínimo necessário para criar um novo tópico." min_trust_to_edit_wiki_post: "O nível mínimo de confiança necessário para editar mensagens marcadas como wiki." + min_trust_to_send_messages: "O nível de confiança mínimo necessário para criar mensagens privadas." newuser_max_links: "Quantas hiperligações um novo utilizador pode adicionar a uma mensagem." newuser_max_images: "Quantas imagens um novo utilizador pode adicionar a uma mensagem." newuser_max_attachments: "Quantos anexos um novo utilizador pode adicionar a uma mensagem." @@ -1036,6 +1038,7 @@ pt: embed_username_key_from_feed: "Chave para recuperar o nome de utilizador discourse do feed." embed_truncate: "Truncar mensagens incorporadas." embed_post_limit: "Número máximo de mensagens a serem incorporadas." + embed_username_required: "O nome de utilizador para a criação de tópico é necessário." embed_whitelist_selector: "Seletor CSS para elementos permitidos em incorporações." embed_blacklist_selector: "Seletor CSS para elementos que foram removidos de incorporações." notify_about_flags_after: "Se houver sinalizações que não tenham sido tratadas após tantas horas, envie um email para 'contact_email'. Configurar a 0 para desativar." @@ -1301,7 +1304,7 @@ pt: welcome_invite: subject_template: "Bem-vindo a %{site_name}!" text_body_template: | - Obrigado por ter aceite o convite para %{site_name} -- bem-vindo! + Obrigado por ter aceitado o convite para %{site_name} -- bem-vindo! Criámos uma nova conta **%{username}** para si, e neste momento encontra-se com sessão iniciada. Pode mudar o seu nome ao visitar [o seu perfil de utilizador][prefs]. diff --git a/config/locales/server.pt_BR.yml b/config/locales/server.pt_BR.yml index 69cc53f99a..9b94b77579 100644 --- a/config/locales/server.pt_BR.yml +++ b/config/locales/server.pt_BR.yml @@ -717,7 +717,6 @@ pt_BR: summary_likes_required: "Curtidas mínimas em um tópico antes de 'Resumir este tópico, ficar habilitado" summary_percent_filter: "Quando um usuário clicar em 'Resumor este tópico', mostrar os melhores % mensagens" summary_max_results: "Máximo número de posts quando resumidos por Categoria " - enable_private_messages: "Permitir a usuários de nível 1 criar e responder mensagens" enable_long_polling: "O sistema de mensagens das notificações pode fazer solicitações longas." long_polling_base_url: "URL Utilizada para \"long polling\" ( Quando um CDN for configurado, tenha certeza que essa configuração seja a padrão) ex: http://origin.site.com" long_polling_interval: "Tempo que o servidor deve aguardar antes de responder quando não existe nenhum dado para ser enviado. (apenas usuários logados)" diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml index bbf01e3039..627a8db3ee 100644 --- a/config/locales/server.ru.yml +++ b/config/locales/server.ru.yml @@ -780,7 +780,6 @@ ru: summary_likes_required: "Минимальное количество симпатий в теме для активации кнопки \"Сводка по теме\"" summary_percent_filter: "При нажатии на кнопку \"Сводка по теме\", показывать лучшие % сообщений" summary_max_results: "Максимальное количество сообщений, выводимых в 'Обзоре темы'" - enable_private_messages: "Разрешить пользователям 1-го уровеня доверия писать и отвечать на личные сообщения" enable_long_polling: "Использовать механизм long polling для уведомлений о событиях" long_polling_base_url: "Базовый URL, используемый для long polling (при использовании CDN для раздачи динамического контента установите в этом параметре адрес origin pull), например: http://origin.site.com" long_polling_interval: "Время, которое ожидает сервер до ответа клиентам, когда нет данных для отправки (только для пользователей, вошедших на сайт)" diff --git a/config/locales/server.sq.yml b/config/locales/server.sq.yml index 0ade7295f1..1fba2bbd68 100644 --- a/config/locales/server.sq.yml +++ b/config/locales/server.sq.yml @@ -729,7 +729,6 @@ sq: summary_likes_required: "Minimum likes in a topic before 'Summarize This Topic' is enabled" summary_percent_filter: "When a user clicks 'Summarize This Topic', show the top % of posts" summary_max_results: "Maximum posts returned by 'Summary This Topic'" - enable_private_messages: "Allow trust level 1 users to create messages and reply to messages" enable_long_polling: "Message bus used for notification can use long polling" long_polling_base_url: "Base URL used for long polling (when a CDN is serving dynamic content, be sure to set this to origin pull) eg: http://origin.site.com" long_polling_interval: "Amount of time the server should wait before responding to clients when there is no data to send (logged on users only)" diff --git a/config/locales/server.tr_TR.yml b/config/locales/server.tr_TR.yml index 3ecd3d87ae..e93a22854e 100644 --- a/config/locales/server.tr_TR.yml +++ b/config/locales/server.tr_TR.yml @@ -677,7 +677,6 @@ tr_TR: summary_likes_required: "'Bu Konuyu Özetle'nin etkinleştirilmesi için konuda olması gereken en az beğeni sayısı" summary_percent_filter: "Kullanıcı 'Bu Konuyu Özetle'ye tıkladığında, gönderinin ilk % kısmını göster" summary_max_results: "'Bu Konuyu Özetle'den dönen en fazla gönderi sayısı" - enable_private_messages: "Güven seviyesi 1 olan kullanıcıların mesaj oluşturmasına ve mesajlara cevap vermesine izin ver" enable_long_polling: "Bildiri için kullanılan message bus uzun sorgular yapabilir" long_polling_base_url: "Uzun sorgular için kullanılan baz URL (CDN dinamik içerik sunuyorsa, bunu origin olarak ayarladığına emin ol) ör: http://origin.site.com" long_polling_interval: "Gönderilecek bilgi olmadığı zaman sunucunun kullanıcılara geri dönmeden önce beklemesi gereken zaman (sadece giriş yapmış kullanıcın için)" diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml index 84e37cf5b6..46a0b84016 100644 --- a/config/locales/server.zh_CN.yml +++ b/config/locales/server.zh_CN.yml @@ -720,7 +720,6 @@ zh_CN: summary_likes_required: "在一个主题启用'摘要模式'的最小赞的数量" summary_percent_filter: "当用户点击摘要,显示前 % 几的帖子" summary_max_results: "“概括主题”返回的最大帖子数量" - enable_private_messages: "允许信任等级1的用户创建消息或者以消息回复" enable_long_polling: "启用 Message bus 使通知功能可以使用长轮询(long polling)" long_polling_base_url: "长轮询的基本 URL(当用 CDN 分发动态能让时,请设置此至原始拉取地址)例如:http://origin.site.com" long_polling_interval: "当没有数据向客户端发送时服务器端应等待的时间(仅对已登录用户有效)" diff --git a/plugins/poll/config/locales/client.nl.yml b/plugins/poll/config/locales/client.nl.yml index d4f833833f..2f7c23c816 100644 --- a/plugins/poll/config/locales/client.nl.yml +++ b/plugins/poll/config/locales/client.nl.yml @@ -17,6 +17,12 @@ nl: average_rating: "Gemiddeld cijfer: %{average}." multiple: help: + at_least_min_options: + one: "U dient tenminste 1 optie te kiezen." + other: "U dient tenminste %{count} opties te kiezen." + up_to_max_options: + one: "U kunt maximaal 1 optie kiezen." + other: "U kunt maximaal %{count} opties kiezen." between_min_and_max_options: "Je kan tussen %{min} en %{max} opties kiezen." cast-votes: title: "Geef je stem" diff --git a/plugins/poll/config/locales/client.pl_PL.yml b/plugins/poll/config/locales/client.pl_PL.yml index 262a6bfa90..cd2cc46375 100644 --- a/plugins/poll/config/locales/client.pl_PL.yml +++ b/plugins/poll/config/locales/client.pl_PL.yml @@ -19,6 +19,18 @@ pl_PL: average_rating: "Średnia ocena: %{average}." multiple: help: + at_least_min_options: + one: "Musisz wybrać przynajmniej jedną pozycje." + few: "Musisz wybrać co najmniej %{count} pozycje." + other: "Musisz wybrać co najmniej %{count} pozycje." + up_to_max_options: + one: "Możesz wybrać maksymalnie 1 opcję." + few: "Możesz wybrać maksymalnie %{count} opcje." + other: "Możesz wybrać maksymalnie %{count} opcji." + x_options: + one: "Musisz wybrać jedną opcję." + few: "Musisz wybrać %{count} opcje." + other: "Musisz wybrać %{count} opcji." between_min_and_max_options: "Możesz wybrać pomiędzy %{min} a %{max} pozycjami." cast-votes: title: "Oddaj głos" diff --git a/plugins/poll/config/locales/server.nl.yml b/plugins/poll/config/locales/server.nl.yml index 13865efdac..7218cfbd7d 100644 --- a/plugins/poll/config/locales/server.nl.yml +++ b/plugins/poll/config/locales/server.nl.yml @@ -14,6 +14,12 @@ nl: multiple_polls_with_same_name: "Er zijn meerdere polls met dezelfde naam : %{name}. Gebruik het 'naam' attribuut om je polls te identificeren." default_poll_must_have_at_least_2_options: "De poll moet minimaal 2 opties hebben." named_poll_must_have_at_least_2_options: "De poll genaamd %{name} moet minimaal 2 opties hebben." + default_poll_must_have_less_options: + one: "Poll dient minder dan 1 optie te hebben." + other: "Poll dient minder dan %{count} opties te hebben." + named_poll_must_have_less_options: + one: "De poll genaamd %{name} moet minder dan 1 optie hebben." + other: "De poll genaamd %{name} moet minder dan %{count} opties hebben." default_poll_must_have_different_options: "Polls moeten verschillende opties hebben." named_poll_must_have_different_options: "De poll genaamd %{name} moet verschillende opties hebben." default_poll_with_multiple_choices_has_invalid_parameters: "De poll met meerdere keuzes heeft ongeldige parameters." diff --git a/plugins/poll/config/locales/server.pl_PL.yml b/plugins/poll/config/locales/server.pl_PL.yml index 0247edf163..a347697da9 100644 --- a/plugins/poll/config/locales/server.pl_PL.yml +++ b/plugins/poll/config/locales/server.pl_PL.yml @@ -14,6 +14,14 @@ pl_PL: multiple_polls_with_same_name: "Istnieje kilka ankiet o tej samej nazwie: %{name}. Użyj atrybutu 'name', aby umożliwić ich jednoznaczną identyfikację." default_poll_must_have_at_least_2_options: "Ankieta musi posiadać co najmniej 2 opcje." named_poll_must_have_at_least_2_options: "Ankieta %{name} musi posiadać co najmniej 2 opcje do wyboru." + default_poll_must_have_less_options: + one: "Ankieta musi posiadać mniej niż 1 opcję do wyboru." + few: "Ankieta musi posiadać co najmniej %{count} pozycje." + other: "Ankieta musi posiadać co najmniej %{count} pozycji." + named_poll_must_have_less_options: + one: "Ankieta %{name} musi posiadać mniej niż 1 opcję." + few: "Ankieta %{name} musi posiadać mniej niż %{count} opcje." + other: "Ankieta %{name} musi posiadać mniej niż %{count} opcji." default_poll_must_have_different_options: "Ankieta musi posiadać kilka różnych opcji do wyboru." named_poll_must_have_different_options: "Ankieta %{name} musi posiadać kilka różnych opcji do wyboru." default_poll_with_multiple_choices_has_invalid_parameters: "Sonda wielokrotnego wyboru posiada nieprawidłowe parametry." From 897563a309f0c823ccf4d980a6ceddde090c394b Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Wed, 21 Oct 2015 00:00:06 +0530 Subject: [PATCH 017/114] FIX: List-ID should not contain space --- lib/email/sender.rb | 4 ++-- spec/components/email/sender_spec.rb | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/email/sender.rb b/lib/email/sender.rb index d1720aedd9..5f1be20d19 100644 --- a/lib/email/sender.rb +++ b/lib/email/sender.rb @@ -87,12 +87,12 @@ module Email # http://www.ietf.org/rfc/rfc2919.txt if topic && topic.category && !topic.category.uncategorized? - list_id = "<#{topic.category.name.downcase}.#{host}>" + list_id = "<#{topic.category.name.downcase.split(' ').join('-')}.#{host}>" # subcategory case if !topic.category.parent_category_id.nil? parent_category_name = Category.find_by(id: topic.category.parent_category_id).name - list_id = "<#{topic.category.name.downcase}.#{parent_category_name.downcase}.#{host}>" + list_id = "<#{topic.category.name.downcase.split(' ').join('-')}.#{parent_category_name.downcase.split(' ').join('-')}.#{host}>" end else list_id = "<#{host}>" diff --git a/spec/components/email/sender_spec.rb b/spec/components/email/sender_spec.rb index 55f56e4c35..06ed0bb65b 100644 --- a/spec/components/email/sender_spec.rb +++ b/spec/components/email/sender_spec.rb @@ -66,11 +66,14 @@ describe Email::Sender do context "adds a List-ID header to identify the forum" do before do - message.header['X-Discourse-Topic-Id'] = 5577 + category = Fabricate(:category, name: 'Name With Space') + topic = Fabricate(:topic, category_id: category.id) + message.header['X-Discourse-Topic-Id'] = topic.id end When { email_sender.send } Then { expect(message.header['List-ID']).to be_present } + Then { expect(message.header['List-ID'].to_s).to match('name-with-space') } end context "adds a Message-ID header even when topic id is not present" do From 3ee1dee3eb9410a4a08ce0fb4e47222fa4ee8592 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 20 Oct 2015 14:54:49 -0400 Subject: [PATCH 018/114] UX: If you only had a checkbox user field, it was hoisting Location --- app/assets/javascripts/discourse/templates/user/preferences.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/discourse/templates/user/preferences.hbs b/app/assets/javascripts/discourse/templates/user/preferences.hbs index c195c7839b..f06288a50e 100644 --- a/app/assets/javascripts/discourse/templates/user/preferences.hbs +++ b/app/assets/javascripts/discourse/templates/user/preferences.hbs @@ -144,6 +144,7 @@ {{#each uf in userFields}} {{user-field field=uf.field value=uf.value}} {{/each}} +
From 976692b38751c01bd3e4e51e0a96141ce04d5cb4 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Tue, 20 Oct 2015 15:55:17 -0400 Subject: [PATCH 019/114] FIX: moderators need to choose a category when uncategorized topics are no allowed --- app/assets/javascripts/discourse/models/composer.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6 index 366a55e73e..f566ef943a 100644 --- a/app/assets/javascripts/discourse/models/composer.js.es6 +++ b/app/assets/javascripts/discourse/models/composer.js.es6 @@ -201,7 +201,7 @@ const Composer = RestModel.extend({ return this.get('canCategorize') && !this.siteSettings.allow_uncategorized_topics && !this.get('categoryId') && - !this.user.get('staff'); + !this.user.get('admin'); } }.property('loading', 'canEditTitle', 'titleLength', 'targetUsernames', 'replyLength', 'categoryId', 'missingReplyCharacters'), From 1821ba8d93136e4cd7dfd05788109232bad89f3b Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Wed, 21 Oct 2015 13:43:20 +0530 Subject: [PATCH 020/114] optimize string replacment --- lib/email/sender.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/email/sender.rb b/lib/email/sender.rb index 5f1be20d19..8e0080c9a2 100644 --- a/lib/email/sender.rb +++ b/lib/email/sender.rb @@ -87,12 +87,12 @@ module Email # http://www.ietf.org/rfc/rfc2919.txt if topic && topic.category && !topic.category.uncategorized? - list_id = "<#{topic.category.name.downcase.split(' ').join('-')}.#{host}>" + list_id = "<#{topic.category.name.downcase.gsub(' ', '-')}.#{host}>" # subcategory case if !topic.category.parent_category_id.nil? parent_category_name = Category.find_by(id: topic.category.parent_category_id).name - list_id = "<#{topic.category.name.downcase.split(' ').join('-')}.#{parent_category_name.downcase.split(' ').join('-')}.#{host}>" + list_id = "<#{topic.category.name.downcase.gsub(' ', '-')}.#{parent_category_name.downcase.gsub(' ', '-')}.#{host}>" end else list_id = "<#{host}>" From 6238a43f9302097b3522f8e7c11b4f2a135fa093 Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Wed, 21 Oct 2015 19:07:31 +0200 Subject: [PATCH 021/114] Fix base and vBulletin importer --- script/import_scripts/base.rb | 2 +- script/import_scripts/vbulletin.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/script/import_scripts/base.rb b/script/import_scripts/base.rb index 49b86caccb..18da1fd7aa 100644 --- a/script/import_scripts/base.rb +++ b/script/import_scripts/base.rb @@ -199,7 +199,7 @@ class ImportScripts::Base Post.exec_sql('create temp table import_ids(val varchar(200) primary key)') - import_id_clause = import_ids.map{|id| "('#{PG::Connection.escape_string(id)}')"}.join(",") + import_id_clause = import_ids.map{|id| "('#{PG::Connection.escape_string(id.to_s)}')"}.join(",") Post.exec_sql("insert into import_ids values #{import_id_clause}") existing = "#{type.to_s.classify}CustomField".constantize.where(name: 'import_id') diff --git a/script/import_scripts/vbulletin.rb b/script/import_scripts/vbulletin.rb index 22e5883db3..588e1ddff1 100644 --- a/script/import_scripts/vbulletin.rb +++ b/script/import_scripts/vbulletin.rb @@ -210,7 +210,7 @@ class ImportScripts::VBulletin < ImportScripts::Base SQL break if topics.size < 1 - next if all_records_exist? :posts, topics.map {|t| "thread-#{topic["threadid"]}" } + next if all_records_exist? :posts, topics.map {|t| "thread-#{t["threadid"]}" } create_posts(topics, total: topic_count, offset: offset) do |topic| raw = preprocess_post_raw(topic["raw"]) rescue nil @@ -555,7 +555,7 @@ class ImportScripts::VBulletin < ImportScripts::Base end def mysql_query(sql) - @client.query(sql, cache_rows: false) + @client.query(sql, cache_rows: true) end end From ca98f66f7ecc1492d2c260618a3904669132fa47 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Wed, 21 Oct 2015 13:13:19 -0400 Subject: [PATCH 022/114] FIX: wrong root path on subfolder installs --- app/assets/javascripts/discourse.js | 2 +- test/javascripts/lib/discourse-test.js.es6 | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 test/javascripts/lib/discourse-test.js.es6 diff --git a/app/assets/javascripts/discourse.js b/app/assets/javascripts/discourse.js index 9c790e3fe4..96f4c90c6d 100644 --- a/app/assets/javascripts/discourse.js +++ b/app/assets/javascripts/discourse.js @@ -14,7 +14,7 @@ window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, { if (!url) return url; // if it's a non relative URL, return it. - if (!/^\/[^\/]/.test(url)) return url; + if (url !== '/' && !/^\/[^\/]/.test(url)) return url; var u = Discourse.BaseUri === undefined ? "/" : Discourse.BaseUri; diff --git a/test/javascripts/lib/discourse-test.js.es6 b/test/javascripts/lib/discourse-test.js.es6 new file mode 100644 index 0000000000..8f3725fa44 --- /dev/null +++ b/test/javascripts/lib/discourse-test.js.es6 @@ -0,0 +1,7 @@ +module("lib:discourse"); + +test("getURL on subfolder install", function() { + Discourse.BaseUri = "/forum"; + equal(Discourse.getURL("/"), "/forum/", "root url has subfolder"); + equal(Discourse.getURL("/users/neil"), "/forum/users/neil", "relative url has subfolder"); +}); \ No newline at end of file From 9793ea2c8aca37c2538e1528c47f4f7e9cf22aac Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Thu, 22 Oct 2015 07:52:47 +0530 Subject: [PATCH 023/114] UX: change Dismiss button title and add tooltip --- .../javascripts/discourse/templates/discovery/topics.hbs | 4 ++-- .../discourse/templates/mobile/discovery/topics.hbs | 2 +- config/locales/client.en.yml | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/discovery/topics.hbs index 8af6278937..9b55d626ae 100644 --- a/app/assets/javascripts/discourse/templates/discovery/topics.hbs +++ b/app/assets/javascripts/discourse/templates/discovery/topics.hbs @@ -5,7 +5,7 @@ {{#if showDismissAtTop}}
{{#if showDismissRead}} - + {{/if}} {{#if showResetNew}} @@ -54,7 +54,7 @@ {{conditional-loading-spinner condition=model.loadingMore}} {{#if allLoaded}} {{#if showDismissRead}} - + {{/if}} {{#if showResetNew}} diff --git a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs index a58ce372e7..3355aef462 100644 --- a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs @@ -25,7 +25,7 @@ {{conditional-loading-spinner condition=model.loadingMore}} {{#if allLoaded}} {{#if showDismissRead}} - + {{/if}} {{#if showResetNew}} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 49da8b6a5b..f51f35b04c 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1011,6 +1011,8 @@ en: reset_read: "Reset Read" delete: "Delete Topics" dismiss: "Dismiss" + dismiss_button: "Dismiss…" + dismiss_tooltip: "Dismiss just new posts or stop tracking topics" dismiss_body: "Would you like to dismiss just the new posts in these topics, or dismiss the topics entirely?" dismiss_posts: "Dismiss Just New Posts" dismiss_topics: "Dismiss Topics" From ed0ac4eed390c3393042f16854f7ec9a4babf0ef Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Wed, 21 Oct 2015 22:53:40 -0700 Subject: [PATCH 024/114] FIX: incorrect profile background styles --- app/assets/stylesheets/common/base/upload.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/common/base/upload.scss b/app/assets/stylesheets/common/base/upload.scss index 9777fd3204..84e29bb14a 100644 --- a/app/assets/stylesheets/common/base/upload.scss +++ b/app/assets/stylesheets/common/base/upload.scss @@ -1,6 +1,6 @@ .uploaded-image-preview { + background: $primary center; background-size: cover; - background: $primary center center; } .image-uploader.no-repeat { From b46663fab09ad59eafda71769eabdae81770e646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Thu, 22 Oct 2015 18:10:01 +0200 Subject: [PATCH 025/114] FIX: hide full name field on the signup dialog when 'enable_names' is disabled --- .../templates/modal/create-account.hbs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/modal/create-account.hbs b/app/assets/javascripts/discourse/templates/modal/create-account.hbs index 8b4ead4a85..d3b0052674 100644 --- a/app/assets/javascripts/discourse/templates/modal/create-account.hbs +++ b/app/assets/javascripts/discourse/templates/modal/create-account.hbs @@ -35,17 +35,20 @@ {{/if}} - - - - {{text-field value=accountName id="new-account-name"}} -  {{input-tip validation=nameValidation}} + {{#if siteSettings.enable_names}} + + + - - + + {{text-field value=accountName id="new-account-name"}} {{input-tip validation=nameValidation}} + + + - + + {{/if}} {{#if passwordRequired}} From 57fc1e5e0c90112c76cec0ab531baff7378c8aba Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 22 Oct 2015 12:32:25 -0400 Subject: [PATCH 026/114] FIX: Don't run a `FULL ANALYZE` on first migration. This seems to block databases unecessarily. We should only really be vacuuming when there is a lot of deleted data to recover. --- lib/tasks/db.rake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index ab40a807c2..8d23222512 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -7,6 +7,8 @@ end task 'db:migrate' => ['environment', 'set_locale'] do SeedFu.seed + SiteSetting.last_vacuum = Time.now.to_i if SiteSetting.last_vacuum == 0 + if SiteSetting.vacuum_db_days > 0 && SiteSetting.last_vacuum < (Time.now.to_i - SiteSetting.vacuum_db_days.days.to_i) puts "Running VACUUM FULL ANALYZE to reclaim DB space, this may take a while" From 515fc49727bbd8b7988c177df29f935fd6237d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Thu, 22 Oct 2015 19:10:07 +0200 Subject: [PATCH 027/114] FIX: replace polls with a link in emails --- lib/email/message_builder.rb | 2 +- lib/email/renderer.rb | 4 ++-- lib/email/styles.rb | 5 +++-- plugins/poll/config/locales/server.en.yml | 3 +++ plugins/poll/plugin.rb | 14 +++++++++++--- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/email/message_builder.rb b/lib/email/message_builder.rb index bacc5f1cf1..f805328795 100644 --- a/lib/email/message_builder.rb +++ b/lib/email/message_builder.rb @@ -68,7 +68,7 @@ module Email html_override.gsub!("%{unsubscribe_link}", unsubscribe_link) end - styled = Email::Styles.new(html_override) + styled = Email::Styles.new(html_override, @opts) styled.format_basic if style = @opts[:style] diff --git a/lib/email/renderer.rb b/lib/email/renderer.rb index 4f21cb8bf7..9b209781ff 100644 --- a/lib/email/renderer.rb +++ b/lib/email/renderer.rb @@ -16,11 +16,11 @@ module Email def html if @message.html_part - style = Email::Styles.new(@message.html_part.body.to_s) + style = Email::Styles.new(@message.html_part.body.to_s, @opts) style.format_basic style.format_html else - style = Email::Styles.new(PrettyText.cook(text)) + style = Email::Styles.new(PrettyText.cook(text), @opts) style.format_basic end diff --git a/lib/email/styles.rb b/lib/email/styles.rb index 22f9ffe1e8..c8001e8da7 100644 --- a/lib/email/styles.rb +++ b/lib/email/styles.rb @@ -6,8 +6,9 @@ module Email class Styles @@plugin_callbacks = [] - def initialize(html) + def initialize(html, opts=nil) @html = html + @opts = opts || {} @fragment = Nokogiri::HTML.fragment(@html) end @@ -146,7 +147,7 @@ module Email # this method is reserved for styles specific to plugin def plugin_styles - @@plugin_callbacks.each { |block| block.call(@fragment) } + @@plugin_callbacks.each { |block| block.call(@fragment, @opts) } end def to_html diff --git a/plugins/poll/config/locales/server.en.yml b/plugins/poll/config/locales/server.en.yml index 9417c9ba08..1ef088e0e4 100644 --- a/plugins/poll/config/locales/server.en.yml +++ b/plugins/poll/config/locales/server.en.yml @@ -55,3 +55,6 @@ en: topic_must_be_open_to_toggle_status: "The topic must be open to toggle status." only_staff_or_op_can_toggle_status: "Only a staff member or the original poster can toggle a poll status." + + email: + link_to_poll: "Click to view the poll." diff --git a/plugins/poll/plugin.rb b/plugins/poll/plugin.rb index 3f789922da..3d79475912 100644 --- a/plugins/poll/plugin.rb +++ b/plugins/poll/plugin.rb @@ -22,9 +22,17 @@ DEFAULT_POLL_NAME ||= "poll".freeze after_initialize do - # remove "Vote Now!" & "Show Results" links in emails - Email::Styles.register_plugin_style do |fragment| - fragment.css(".poll a.cast-votes, .poll a.toggle-results").each(&:remove) + # turn polls into a link in emails + Email::Styles.register_plugin_style do |fragment, opts| + post = Post.find_by(id: opts[:post_id]) rescue nil + if post.nil? || post.trashed? + fragment.css(".poll").each(&:remove) + else + post_url = "#{Discourse.base_url}#{post.url}" + fragment.css(".poll").each do |poll| + poll.replace "

#{I18n.t("poll.email.link_to_poll")}

" + end + end end module ::DiscoursePoll From 6c2978168729df6c654665cb527811b220a03079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Thu, 22 Oct 2015 19:46:48 +0200 Subject: [PATCH 028/114] FIX: this is making oneboxed images way too big (reverts 121426287) --- lib/email/styles.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/email/styles.rb b/lib/email/styles.rb index c8001e8da7..fae9da6215 100644 --- a/lib/email/styles.rb +++ b/lib/email/styles.rb @@ -43,7 +43,6 @@ module Email img['width'] = 'auto' img['height'] = 'auto' end - add_styles(img, 'max-width:100%;') if img['style'] !~ /max-width/ end # ensure all urls are absolute From 0ea54e9255d7111ffd18bc9fe052831f641ee614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Thu, 22 Oct 2015 20:11:17 +0200 Subject: [PATCH 029/114] UX: don't show an empty list when all extensions are authorized --- .../javascripts/discourse/views/upload-selector.js.es6 | 6 +++--- config/locales/client.en.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/discourse/views/upload-selector.js.es6 b/app/assets/javascripts/discourse/views/upload-selector.js.es6 index e8967b8b2b..4bf80f2aad 100644 --- a/app/assets/javascripts/discourse/views/upload-selector.js.es6 +++ b/app/assets/javascripts/discourse/views/upload-selector.js.es6 @@ -27,9 +27,9 @@ export default ModalBodyView.extend({ }, tip: function() { - const source = this.get("controller.local") ? "local" : "remote", - opts = { authorized_extensions: Discourse.Utilities.authorizedExtensions() }; - return uploadTranslate(source + "_tip", opts); + const source = this.get("controller.local") ? "local" : "remote"; + const authorized_extensions = Discourse.Utilities.authorizesAllExtensions() ? "" : `(${Discourse.Utilities.authorizedExtensions()})`; + return uploadTranslate(source + "_tip", { authorized_extensions }); }.property("controller.local"), hint: function() { diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index f51f35b04c..af5d2e6e09 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -967,9 +967,9 @@ en: from_my_computer: "From my device" from_the_web: "From the web" remote_tip: "link to image" - remote_tip_with_attachments: "link to image or file ({{authorized_extensions}})" + remote_tip_with_attachments: "link to image or file {{authorized_extensions}}" local_tip: "select images from your device" - local_tip_with_attachments: "select images or files from your device ({{authorized_extensions}})" + local_tip_with_attachments: "select images or files from your device {{authorized_extensions}}" hint: "(you can also drag & drop into the editor to upload them)" hint_for_supported_browsers: "you can also drag and drop or paste images into the editor" uploading: "Uploading" From 3b35972d254876971c39e2b53c9fff73f3221a41 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 22 Oct 2015 15:02:53 -0400 Subject: [PATCH 030/114] Improvements to importing a mailing list --- script/import_scripts/mbox.rb | 52 ++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/script/import_scripts/mbox.rb b/script/import_scripts/mbox.rb index d47b5553fc..3ff41e9454 100755 --- a/script/import_scripts/mbox.rb +++ b/script/import_scripts/mbox.rb @@ -37,6 +37,7 @@ class ImportScripts::Mbox < ImportScripts::Base topics = [] topic_lookup = {} + topic_titles = {} replies = [] all_messages do |mail, filename| @@ -44,21 +45,55 @@ class ImportScripts::Mbox < ImportScripts::Base msg_id = mail['Message-ID'].to_s reply_to = mail['In-Reply-To'].to_s + title = clean_title(mail['Subject'].to_s) if reply_to.present? topic = topic_lookup[reply_to] || reply_to topic_lookup[msg_id] = topic - replies << {id: msg_id, topic: topic, file: filename} + replies << {id: msg_id, topic: topic, file: filename, title: title} else - topics << {id: msg_id, file: filename} + topics << {id: msg_id, file: filename, title: title} + topic_titles[title] ||= msg_id end end + # Replies without parents should be hoisted to topics + to_hoist = [] + replies.each do |r| + to_hoist << r if !topic_lookup[r[:topic]] + end + + to_hoist.each do |h| + replies.delete(h) + topics << {id: h[:id], file: h[:file], title: h[:title]} + topic_titles[h[:title]] ||= h[:id] + end + + # Topics with duplicate replies should be replies + to_group = [] + topics.each do |t| + first = topic_titles[t[:title]] + to_group << t if first && first != t[:id] + end + + to_group.each do |t| + topics.delete(t) + replies << {id: t[:id], topic: topic_titles[t[:title]], file: t[:file], title: t[:title]} + end + File.write(USER_INDEX_PATH, {users: users}.to_json) File.write(TOPIC_INDEX_PATH, {topics: topics}.to_json) File.write(REPLY_INDEX_PATH, {replies: replies}.to_json) end + def clean_title(title) + title.gsub(/^Re: */i, '') + end + + def clean_raw(raw) + raw.gsub(/-- \nYou received this message because you are subscribed to the Google Groups "[^"]*" group.\nTo unsubscribe from this group and stop receiving emails from it, send an email to [^+@]+\+unsubscribe@googlegroups.com\.\nFor more options, visit https:\/\/groups\.google\.com\/groups\/opt_out\./, '') + end + def import_users puts "", "importing users" @@ -100,7 +135,7 @@ class ImportScripts::Mbox < ImportScripts::Base topics = all_topics[offset..offset+BATCH_SIZE-1] break if topics.nil? - next if all_records_exist? :posts, topics.map {|t| t['id'].to_i} + next if all_records_exist? :posts, topics.map {|t| t['id']} create_posts(topics, total: topic_count, offset: offset) do |t| raw_email = File.read(t['file']) @@ -116,11 +151,11 @@ class ImportScripts::Mbox < ImportScripts::Base title = mail.subject.gsub(/\[[^\]]+\]+/, '').strip { id: t['id'], - title: title, + title: clean_title(title), user_id: user_id_from_imported_user_id(mail.from.first) || Discourse::SYSTEM_USER_ID, created_at: mail.date, category: CATEGORY_ID, - raw: raw, + raw: clean_raw(raw), cook_method: Post.cook_methods[:email] } end end @@ -129,9 +164,6 @@ class ImportScripts::Mbox < ImportScripts::Base def import_replies puts "", "creating topic replies" - all_topics = ::JSON.parse(File.read(TOPIC_INDEX_PATH))['topics'] - topic_count = all_topics.size - replies = ::JSON.parse(File.read(REPLY_INDEX_PATH))['replies'] post_count = replies.size @@ -139,7 +171,7 @@ class ImportScripts::Mbox < ImportScripts::Base posts = replies[offset..offset+BATCH_SIZE-1] break if posts.nil? - next if all_records_exist? :posts, posts.map {|p| p['id'].to_i} + next if all_records_exist? :posts, posts.map {|p| p['id']} create_posts(posts, total: post_count, offset: offset) do |p| parent_id = p['topic'] @@ -161,7 +193,7 @@ class ImportScripts::Mbox < ImportScripts::Base topic_id: topic_id, user_id: user_id_from_imported_user_id(mail.from.first) || Discourse::SYSTEM_USER_ID, created_at: mail.date, - raw: raw, + raw: clean_raw(raw), cook_method: Post.cook_methods[:email] } end end From 2fdf8cba3da726ee2d8feab4fa49d9672de952b4 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 22 Oct 2015 16:08:52 -0400 Subject: [PATCH 031/114] FIX: Support formatted `>` quotes in emails --- lib/email/styles.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/email/styles.rb b/lib/email/styles.rb index fae9da6215..f518a6f999 100644 --- a/lib/email/styles.rb +++ b/lib/email/styles.rb @@ -92,11 +92,15 @@ module Email def onebox_styles # Links to other topics - style('aside.quote', 'border-left: 5px solid #bebebe; background-color: #f1f1f1; padding: 12px 25px 2px 12px; margin-bottom: 10px;') - style('aside.quote blockquote', 'border: 0px; padding: 0; margin: 7px 0') + style('aside.quote', 'border-left: 5px solid #e9e9e9; background-color: #f8f8f8; padding: 12px 25px 2px 12px; margin-bottom: 10px;') + style('aside.quote blockquote', 'border: 0px; padding: 0; margin: 7px 0; background-color: clear;') + style('aside.quote blockquote > p', 'padding: 0;') style('aside.quote div.info-line', 'color: #666; margin: 10px 0') style('aside.quote .avatar', 'margin-right: 5px; width:20px; height:20px') + style('blockquote', 'border-left: 5px solid #e9e9e9; background-color: #f8f8f8; margin: 0;') + style('blockquote > p', 'padding: 1em;') + # Oneboxes style('aside.onebox', "padding: 12px 25px 2px 12px; border-left: 5px solid #bebebe; background: #eee; margin-bottom: 10px;") style('aside.onebox img', "max-height: 80%; max-width: 25%; height: auto; float: left; margin-right: 10px; margin-bottom: 10px") From 1e50883d06c7d44923edfe94dc59b4d374b080ee Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 22 Oct 2015 16:37:44 -0400 Subject: [PATCH 032/114] FIX: Incorrect search link --- app/assets/javascripts/discourse/templates/header.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/templates/header.hbs b/app/assets/javascripts/discourse/templates/header.hbs index 9624352a34..87a1d19648 100644 --- a/app/assets/javascripts/discourse/templates/header.hbs +++ b/app/assets/javascripts/discourse/templates/header.hbs @@ -22,7 +22,7 @@ mobileAction="fullPageSearch" loginAction="showLogin" title="search.title" - href="search"}} + href="/search"}} {{/header-dropdown}} {{#header-dropdown iconId="toggle-hamburger-menu" From 567bc70391b9077b893ab44eacd41c8e0502749f Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 22 Oct 2015 14:37:00 -0700 Subject: [PATCH 033/114] make #banner full width on mobile --- app/assets/stylesheets/mobile/banner.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/mobile/banner.scss b/app/assets/stylesheets/mobile/banner.scss index bc82f05204..8740699c6b 100644 --- a/app/assets/stylesheets/mobile/banner.scss +++ b/app/assets/stylesheets/mobile/banner.scss @@ -3,7 +3,9 @@ // -------------------------------------------------- #banner { - margin: 10px; + // go full width on mobile, by extending into the 10px wrap + // borders on left and right + margin: 0 -10px; @media all and (max-height: 499px) { max-height: 100px; From 8ea1ad1b2daf2be54fd5a2c94855b90fc2ae9d1c Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 22 Oct 2015 14:40:19 -0700 Subject: [PATCH 034/114] minor css tweak to mobile alert-info --- app/assets/stylesheets/mobile/alert.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/mobile/alert.scss b/app/assets/stylesheets/mobile/alert.scss index dbd1770bec..40878bf1e7 100644 --- a/app/assets/stylesheets/mobile/alert.scss +++ b/app/assets/stylesheets/mobile/alert.scss @@ -5,6 +5,7 @@ // there are (n) new or updated topics, click to show .alert.alert-info { margin: 0; + margin-bottom: -3px; padding: 15px; font-size: 1.1em; } From 7b95f8b6331ac7e7309b456263aef6cc46973fa5 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 22 Oct 2015 14:42:37 -0700 Subject: [PATCH 035/114] another minor mobile tweak to alert-info --- app/assets/stylesheets/mobile/alert.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/mobile/alert.scss b/app/assets/stylesheets/mobile/alert.scss index 40878bf1e7..b7ff758af0 100644 --- a/app/assets/stylesheets/mobile/alert.scss +++ b/app/assets/stylesheets/mobile/alert.scss @@ -6,6 +6,7 @@ .alert.alert-info { margin: 0; margin-bottom: -3px; + margin-top: -5px; padding: 15px; font-size: 1.1em; } From 352824a3a42ce9fa7dba82095707f9aaed653cc6 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 22 Oct 2015 16:43:01 -0700 Subject: [PATCH 036/114] fix mobile layout issues on user page --- app/assets/stylesheets/desktop/discourse.scss | 2 +- app/assets/stylesheets/mobile/discourse.scss | 3 +-- app/assets/stylesheets/mobile/user.scss | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/desktop/discourse.scss b/app/assets/stylesheets/desktop/discourse.scss index 17baebbc1c..543521bccf 100644 --- a/app/assets/stylesheets/desktop/discourse.scss +++ b/app/assets/stylesheets/desktop/discourse.scss @@ -84,7 +84,7 @@ body { margin: 0; i { font-size: 1.071em; - } + } } i.fa-envelope { diff --git a/app/assets/stylesheets/mobile/discourse.scss b/app/assets/stylesheets/mobile/discourse.scss index d88232804a..fce51428a5 100644 --- a/app/assets/stylesheets/mobile/discourse.scss +++ b/app/assets/stylesheets/mobile/discourse.scss @@ -64,8 +64,7 @@ blockquote { } .topic-statuses { - display: inline-block; - vertical-align: middle; + float: left; .topic-status { i { color: dark-light-diff($secondary, $primary, 40%, -20%); diff --git a/app/assets/stylesheets/mobile/user.scss b/app/assets/stylesheets/mobile/user.scss index 5bb4095a66..60057ef5ff 100644 --- a/app/assets/stylesheets/mobile/user.scss +++ b/app/assets/stylesheets/mobile/user.scss @@ -292,7 +292,6 @@ img.avatar { float: left; - margin-right: 15px; } .suspended { From 8a5f8d62b2e07575db3581a80212e024610cfbba Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 22 Oct 2015 16:54:46 -0700 Subject: [PATCH 037/114] remove "right" class from profile buttons --- .../discourse/components/private-message-map.js.es6 | 2 +- .../javascripts/discourse/controllers/topic.js.es6 | 2 +- .../javascripts/discourse/templates/user/user.hbs | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/discourse/components/private-message-map.js.es6 b/app/assets/javascripts/discourse/components/private-message-map.js.es6 index f8d402ba76..0d01910c72 100644 --- a/app/assets/javascripts/discourse/components/private-message-map.js.es6 +++ b/app/assets/javascripts/discourse/components/private-message-map.js.es6 @@ -9,7 +9,7 @@ export default Ember.Component.extend({ var self = this; bootbox.dialog(I18n.t("private_message_info.remove_allowed_user", {name: user.get('username')}), [ {label: I18n.t("no_value"), - 'class': 'btn-danger rightg'}, + 'class': 'btn-danger right'}, {label: I18n.t("yes_value"), 'class': 'btn-primary', callback: function() { diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index d2e81ba4a5..e0e480953f 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -153,7 +153,7 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { if (user.get('staff') && replyCount > 0) { bootbox.dialog(I18n.t("post.controls.delete_replies.confirm", {count: replyCount}), [ {label: I18n.t("cancel"), - 'class': 'btn-danger rightg'}, + 'class': 'btn-danger right'}, {label: I18n.t("post.controls.delete_replies.no_value"), callback() { post.destroy(user); diff --git a/app/assets/javascripts/discourse/templates/user/user.hbs b/app/assets/javascripts/discourse/templates/user/user.hbs index a1f74c5dda..8644c13683 100644 --- a/app/assets/javascripts/discourse/templates/user/user.hbs +++ b/app/assets/javascripts/discourse/templates/user/user.hbs @@ -41,20 +41,20 @@ {{/if}} {{#if viewingSelf}} -
  • {{fa-icon "sign-out"}}{{i18n 'user.log_out'}}
  • +
  • {{fa-icon "sign-out"}}{{i18n 'user.log_out'}}
  • {{/if}} {{#if currentUser.staff}} -
  • {{fa-icon "wrench"}}{{i18n 'admin.user.show_admin_profile'}}
  • +
  • {{fa-icon "wrench"}}{{i18n 'admin.user.show_admin_profile'}}
  • {{/if}} {{#if model.can_edit}} -
  • {{#link-to 'preferences' class="btn right"}}{{fa-icon "cog"}}{{i18n 'user.preferences'}}{{/link-to}}
  • +
  • {{#link-to 'preferences' class="btn"}}{{fa-icon "cog"}}{{i18n 'user.preferences'}}{{/link-to}}
  • {{/if}} {{#if canInviteToForum}} -
  • {{#link-to 'userInvited' class="btn right"}}{{fa-icon "user-plus"}}{{i18n 'user.invited.title'}}{{/link-to}}
  • +
  • {{#link-to 'userInvited' class="btn"}}{{fa-icon "user-plus"}}{{i18n 'user.invited.title'}}{{/link-to}}
  • {{/if}} {{#if collapsedInfo}} {{#if viewingSelf}} -
  • {{fa-icon "angle-double-down"}}{{i18n 'user.expand_profile'}}
  • +
  • {{fa-icon "angle-double-down"}}{{i18n 'user.expand_profile'}}
  • {{/if}} {{/if}} From fac25763ba05ca0815d9adde5e9964e0f459b60c Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 22 Oct 2015 23:23:13 -0700 Subject: [PATCH 038/114] mobile topic list and user page CSS tweaks --- app/assets/javascripts/discourse/templates/user/user.hbs | 2 +- app/assets/stylesheets/mobile/topic-list.scss | 4 +++- app/assets/stylesheets/mobile/user.scss | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/user/user.hbs b/app/assets/javascripts/discourse/templates/user/user.hbs index 8644c13683..c52d241ef0 100644 --- a/app/assets/javascripts/discourse/templates/user/user.hbs +++ b/app/assets/javascripts/discourse/templates/user/user.hbs @@ -34,7 +34,7 @@
      {{#if model.can_send_private_message_to_user}}
    • - + {{fa-icon "envelope"}} {{i18n 'user.private_message'}} diff --git a/app/assets/stylesheets/mobile/topic-list.scss b/app/assets/stylesheets/mobile/topic-list.scss index a391f4c67f..50966703f7 100644 --- a/app/assets/stylesheets/mobile/topic-list.scss +++ b/app/assets/stylesheets/mobile/topic-list.scss @@ -120,7 +120,9 @@ .age { white-space: nowrap; a { - padding: 15px 10px 15px 5px; + // let's make all ages dim on mobile so we're not + // overwhelming people with info about each topic + color: dark-light-choose(scale-color($primary, $lightness: 70%), scale-color($secondary, $lightness: 30%)) !important; } } } diff --git a/app/assets/stylesheets/mobile/user.scss b/app/assets/stylesheets/mobile/user.scss index 60057ef5ff..58e9ed8f0b 100644 --- a/app/assets/stylesheets/mobile/user.scss +++ b/app/assets/stylesheets/mobile/user.scss @@ -256,7 +256,7 @@ } .details { - padding: 15px 15px 4px 15px; + padding: 15px 10px 4px 10px; background-color: dark-light-choose(rgba($primary, .9), rgba($secondary, .9)); opacity: 0.8; From 9483940244690ecc06763622bd9317b2a35d589a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 23 Oct 2015 10:19:28 +0200 Subject: [PATCH 039/114] UX: new topics list for mobile --- .../javascripts/discourse/models/topic.js.es6 | 2 + .../mobile/list/topic_list_item.raw.hbs | 49 +++++++++++-------- app/assets/stylesheets/mobile/topic-list.scss | 8 +++ 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index 3b63069ed7..d8560778bb 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -8,6 +8,8 @@ const Topic = RestModel.extend({ message: null, errorLoading: false, + creator: Ember.computed.alias("posters.firstObject.user"), + @computed('fancy_title') fancyTitle(title) { title = title || ""; diff --git a/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs b/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs index aa4370c966..5de5683412 100644 --- a/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs @@ -1,28 +1,35 @@ - -
      - {{raw "list/post-count-or-badges" topic=content postBadgesEnabled=controller.showTopicPostBadges}} -
      -
      + -
      - {{#unless controller.hideCategory}} -
      - {{category-link content.category}} +
      + - {{/unless}} - {{plugin-outlet "topic-list-tags"}} -
      -
      - {{raw "list/activity-column" topic=content tagName="span" class="age"}} - {{content.last_poster_username}} +
      + {{raw "list/post-count-or-badges" topic=content postBadgesEnabled=controller.showTopicPostBadges}} +
      + +
      + {{#unless controller.hideCategory}} +
      + {{category-link content.category}} +
      + {{/unless}} + + {{plugin-outlet "topic-list-tags"}} + + + +
      -
      -
      diff --git a/app/assets/stylesheets/mobile/topic-list.scss b/app/assets/stylesheets/mobile/topic-list.scss index 50966703f7..f9b6990e4e 100644 --- a/app/assets/stylesheets/mobile/topic-list.scss +++ b/app/assets/stylesheets/mobile/topic-list.scss @@ -63,6 +63,14 @@ .topic-list { + .creator { + margin-left: 5px; + } + + .right { + margin-left: 60px; + } + > tbody > tr { &.highlighted { background-color: dark-light-choose(scale-color($tertiary, $lightness: 85%), scale-color($tertiary, $lightness: -55%)); From f3d9d1295ab0b72fdf1ef2427b368b9f698a9351 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 23 Oct 2015 12:01:25 -0400 Subject: [PATCH 040/114] FIX: Support subfolder URLs for middle clicking search --- .../javascripts/discourse/components/header-dropdown.js.es6 | 6 +++--- app/assets/javascripts/discourse/templates/header.hbs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/discourse/components/header-dropdown.js.es6 b/app/assets/javascripts/discourse/components/header-dropdown.js.es6 index 447184410c..387f7ae8f1 100644 --- a/app/assets/javascripts/discourse/components/header-dropdown.js.es6 +++ b/app/assets/javascripts/discourse/components/header-dropdown.js.es6 @@ -4,9 +4,9 @@ export default Ember.Component.extend({ tagName: 'li', classNameBindings: [':header-dropdown-toggle', 'active'], - @computed('showUser') - href(showUser) { - return showUser ? this.currentUser.get('path') : ''; + @computed('showUser', 'path') + href(showUser, path) { + return showUser ? this.currentUser.get('path') : Discourse.getURL(path); }, active: Ember.computed.alias('toggleVisible'), diff --git a/app/assets/javascripts/discourse/templates/header.hbs b/app/assets/javascripts/discourse/templates/header.hbs index 87a1d19648..28da2d800c 100644 --- a/app/assets/javascripts/discourse/templates/header.hbs +++ b/app/assets/javascripts/discourse/templates/header.hbs @@ -22,7 +22,7 @@ mobileAction="fullPageSearch" loginAction="showLogin" title="search.title" - href="/search"}} + path="/search"}} {{/header-dropdown}} {{#header-dropdown iconId="toggle-hamburger-menu" From 09195768be4f05d49e8c9b3b24ee2afd23e777df Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 23 Oct 2015 12:48:15 -0400 Subject: [PATCH 041/114] FIX: Quote button was broken when the quoted post was unloaded --- .../javascripts/discourse/controllers/quote-button.js.es6 | 6 ++++-- app/assets/javascripts/discourse/models/post-stream.js.es6 | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/quote-button.js.es6 b/app/assets/javascripts/discourse/controllers/quote-button.js.es6 index bd50004977..620decb81d 100644 --- a/app/assets/javascripts/discourse/controllers/quote-button.js.es6 +++ b/app/assets/javascripts/discourse/controllers/quote-button.js.es6 @@ -101,8 +101,10 @@ export default Ember.Controller.extend({ // defer load if needed, if in an expanded replies section if (!post) { const postStream = this.get('controllers.topic.model.postStream'); - postStream.loadPost(postId).then(() => this.quoteText()); - return; + return postStream.loadPost(postId).then(p => { + this.set('post', p); + return this.quoteText(); + }); } // If we can't create a post, delegate to reply as new topic diff --git a/app/assets/javascripts/discourse/models/post-stream.js.es6 b/app/assets/javascripts/discourse/models/post-stream.js.es6 index 010330451b..4ea6646f03 100644 --- a/app/assets/javascripts/discourse/models/post-stream.js.es6 +++ b/app/assets/javascripts/discourse/models/post-stream.js.es6 @@ -445,8 +445,7 @@ const PostStream = RestModel.extend({ const url = "/posts/" + postId; const store = this.store; - return Discourse.ajax(url).then((p) => - this.storePost(store.createRecord('post', p))); + return Discourse.ajax(url).then(p => this.storePost(store.createRecord('post', p))); }, /** From 6ad42d4cd22f245557760dcc2ef7c6642bc5694e Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 23 Oct 2015 16:39:03 -0400 Subject: [PATCH 042/114] FIX: Category topics weren't refreshing when changing sort order --- .../routes/build-category-route.js.es6 | 42 ++++++++++--------- .../discourse/routes/discovery.js.es6 | 4 +- 2 files changed, 23 insertions(+), 23 deletions(-) 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 b1d75980d1..8456eaf4fa 100644 --- a/app/assets/javascripts/discourse/routes/build-category-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-category-route.js.es6 @@ -7,7 +7,7 @@ export default (filter, params) => { queryParams, model(modelParams) { - return Discourse.Category.findBySlug(modelParams.slug, modelParams.parentSlug); + return { category: Discourse.Category.findBySlug(modelParams.slug, modelParams.parentSlug) }; }, afterModel(model, transition) { @@ -16,27 +16,27 @@ export default (filter, params) => { return; } - this._setupNavigation(model); - return Em.RSVP.all([this._createSubcategoryList(model), - this._retrieveTopicList(model, transition)]); + this._setupNavigation(model.category); + return Em.RSVP.all([this._createSubcategoryList(model.category), + this._retrieveTopicList(model.category, transition)]); }, - _setupNavigation(model) { + _setupNavigation(category) { const noSubcategories = params && !!params.no_subcategories, - filterMode = `c/${Discourse.Category.slugFor(model)}${noSubcategories ? "/none" : ""}/l/${filter}`; + filterMode = `c/${Discourse.Category.slugFor(category)}${noSubcategories ? "/none" : ""}/l/${filter}`; this.controllerFor('navigation/category').setProperties({ - category: model, + category, filterMode: filterMode, noSubcategories: params && params.no_subcategories, - canEditCategory: model.get('can_edit') + canEditCategory: category.get('can_edit') }); }, - _createSubcategoryList(model) { + _createSubcategoryList(category) { this._categoryList = null; - if (Em.isNone(model.get('parentCategory')) && Discourse.SiteSettings.show_subcategory_list) { - return Discourse.CategoryList.listForParent(this.store, model) + if (Em.isNone(category.get('parentCategory')) && Discourse.SiteSettings.show_subcategory_list) { + return Discourse.CategoryList.listForParent(this.store, category) .then(list => this._categoryList = list); } @@ -44,28 +44,30 @@ export default (filter, params) => { return Em.RSVP.resolve(); }, - _retrieveTopicList(model, transition) { - const listFilter = `c/${Discourse.Category.slugFor(model)}/l/${filter}`, + _retrieveTopicList(category, transition) { + const listFilter = `c/${Discourse.Category.slugFor(category)}/l/${filter}`, findOpts = filterQueryParams(transition.queryParams, params), extras = { cached: this.isPoppedState(transition) }; return findTopicList(this.store, this.topicTrackingState, listFilter, findOpts, extras).then(list => { - Discourse.TopicList.hideUniformCategory(list, model); + Discourse.TopicList.hideUniformCategory(list, category); this.set('topics', list); + return list; }); }, titleToken() { const filterText = I18n.t('filters.' + filter.replace('/', '.') + '.title', { count: 0 }), - model = this.currentModel; + category = this.currentModel.category; - return I18n.t('filters.with_category', { filter: filterText, category: model.get('name') }); + return I18n.t('filters.with_category', { filter: filterText, category: category.get('name') }); }, setupController(controller, model) { const topics = this.get('topics'), + category = model.category, canCreateTopic = topics.get('can_create_topic'), - canCreateTopicOnCategory = model.get('permission') === Discourse.PermissionType.FULL; + canCreateTopicOnCategory = category.get('permission') === Discourse.PermissionType.FULL; this.controllerFor('navigation/category').setProperties({ canCreateTopicOnCategory: canCreateTopicOnCategory, @@ -75,7 +77,7 @@ export default (filter, params) => { var topicOpts = { model: topics, - category: model, + category, period: topics.get('for_period') || (filter.indexOf('/') > 0 ? filter.split('/')[1] : ''), selected: [], noSubcategories: params && !!params.no_subcategories, @@ -84,7 +86,7 @@ export default (filter, params) => { canCreateTopicOnCategory: canCreateTopicOnCategory }; - const p = model.get('params'); + const p = category.get('params'); if (p && Object.keys(p).length) { if (p.order !== undefined) { topicOpts.order = p.order; @@ -95,7 +97,7 @@ export default (filter, params) => { } this.controllerFor('discovery/topics').setProperties(topicOpts); - this.searchService.set('searchContext', model.get('searchContext')); + this.searchService.set('searchContext', category.get('searchContext')); this.set('topics', null); this.openTopicDraft(topics); diff --git a/app/assets/javascripts/discourse/routes/discovery.js.es6 b/app/assets/javascripts/discourse/routes/discovery.js.es6 index 12b3e1f180..c0f40e7a6a 100644 --- a/app/assets/javascripts/discourse/routes/discovery.js.es6 +++ b/app/assets/javascripts/discourse/routes/discovery.js.es6 @@ -5,7 +5,7 @@ import OpenComposer from "discourse/mixins/open-composer"; import { scrollTop } from "discourse/mixins/scroll-top"; -const DiscoveryRoute = Discourse.Route.extend(OpenComposer, { +export default Discourse.Route.extend(OpenComposer, { redirect() { return this.redirectIfLoginRequired(); }, @@ -54,5 +54,3 @@ const DiscoveryRoute = Discourse.Route.extend(OpenComposer, { } }); - -export default DiscoveryRoute; From 56c9b4b6e28e44f3c98b18b7f5a15216bedb6891 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 23 Oct 2015 16:35:22 -0700 Subject: [PATCH 043/114] UX: hide topic list views column on <= 850px w --- app/assets/stylesheets/desktop/topic-list.scss | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/desktop/topic-list.scss b/app/assets/stylesheets/desktop/topic-list.scss index b0432b2fc2..210fcaa703 100644 --- a/app/assets/stylesheets/desktop/topic-list.scss +++ b/app/assets/stylesheets/desktop/topic-list.scss @@ -337,18 +337,10 @@ and (max-width : 850px) { padding: 12px 2px; font-size: 0.929em; } - .star { - padding: 12px 5px; - width: auto; - } .main-link { font-size: 1.071em; padding: 12px 8px 12px 0px; } - - .likes { - width: auto; - } .category { min-width: 0; padding: 0; @@ -356,6 +348,12 @@ and (max-width : 850px) { .topic-excerpt { padding-right: 20px; } + th.views { + display: none; + } + td.views { + display: none; + } th.posters { text-align: center; } From 2b64ccb98c340e7f9b2ede320924630af7e9c97b Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 23 Oct 2015 17:15:13 -0700 Subject: [PATCH 044/114] clean up portrait .topic-list CSS media query stuff --- .../stylesheets/desktop/topic-list.scss | 39 ++++--------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/app/assets/stylesheets/desktop/topic-list.scss b/app/assets/stylesheets/desktop/topic-list.scss index 210fcaa703..c62ce07318 100644 --- a/app/assets/stylesheets/desktop/topic-list.scss +++ b/app/assets/stylesheets/desktop/topic-list.scss @@ -298,6 +298,7 @@ button.dismiss-read { @media all and (max-width : 850px) { + // slightly smaller font, tighten spacing on nav pills .nav-pills { > li > a { font-size: 1em; @@ -305,58 +306,32 @@ and (max-width : 850px) { } } - .list-controls { - - .btn { - font-size: 1em - } - - .category-dropdown-menu { - min-width: 139px; - } - - a.badge-category { - font-size: 1em; - } - - } - .topic-list { - .categories td.category { - padding-left: 10px; - } + + // tighter table header spacing th:first-of-type { padding: 12px 5px; } - th { - .btn .fa { - margin-right: 2px; - } - } + // smaller table cell font and cell spacing th, td { padding: 12px 2px; font-size: 0.929em; } .main-link { font-size: 1.071em; - padding: 12px 8px 12px 0px; } .category { min-width: 0; padding: 0; } - .topic-excerpt { - padding-right: 20px; - } + // suppress views column th.views { display: none; } td.views { display: none; } - th.posters { - text-align: center; - } + // show only the first poster .posters { min-width: 0; width: 50px; @@ -366,6 +341,6 @@ and (max-width : 850px) { a.latest { padding: 0 8px; } - } + } } } From 25161eef39c276889069f7631456295ecb1377d5 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Sat, 24 Oct 2015 23:57:55 +0800 Subject: [PATCH 045/114] FIX: 'q' keyboard shortcut not working. --- app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 index b146d93336..6a507050f0 100644 --- a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 +++ b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 @@ -99,7 +99,7 @@ export default { $('.topic-post.selected button.create').click(); // lazy but should work for now setTimeout(function() { - $('#wmd-quote-post').click(); + $('.wmd-quote-post').click(); }, 500); }, From c28843e87bb8d4fc94124a4d530acc988d60e579 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Sun, 25 Oct 2015 10:30:19 +0530 Subject: [PATCH 046/114] FIX: redirect to return_url when working as SSO provider --- .../discourse/controllers/login.js.es6 | 8 ++- app/controllers/session_controller.rb | 9 ++- spec/controllers/session_controller_spec.rb | 69 ++++++++++++------- 3 files changed, 58 insertions(+), 28 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/login.js.es6 b/app/assets/javascripts/discourse/controllers/login.js.es6 index 50abf282d7..157bbaf841 100644 --- a/app/assets/javascripts/discourse/controllers/login.js.es6 +++ b/app/assets/javascripts/discourse/controllers/login.js.es6 @@ -78,9 +78,15 @@ export default Ember.Controller.extend(ModalFunctionality, { const $hidden_login_form = $('#hidden-login-form'); const destinationUrl = $.cookie('destination_url'); const shouldRedirectToUrl = self.session.get("shouldRedirectToUrl"); + const ssoDestinationUrl = $.cookie('sso_destination_url'); $hidden_login_form.find('input[name=username]').val(self.get('loginName')); $hidden_login_form.find('input[name=password]').val(self.get('loginPassword')); - if (destinationUrl) { + + if (ssoDestinationUrl) { + $.cookie('sso_destination_url', null); + window.location.assign(ssoDestinationUrl); + return; + } else if (destinationUrl) { // redirect client to the original URL $.cookie('destination_url', null); $hidden_login_form.find('input[name=redirect]').val(destinationUrl); diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb index a2a771a354..60765e0cd8 100644 --- a/app/controllers/session_controller.rb +++ b/app/controllers/session_controller.rb @@ -37,7 +37,11 @@ class SessionController < ApplicationController sso.external_id = current_user.id.to_s sso.admin = current_user.admin? sso.moderator = current_user.moderator? - redirect_to sso.to_url(sso.return_sso_url) + if request.xhr? + cookies[:sso_destination_url] = sso.to_url(sso.return_sso_url) + else + redirect_to sso.to_url(sso.return_sso_url) + end else session[:sso_payload] = request.query_string redirect_to path('/login') @@ -266,9 +270,8 @@ class SessionController < ApplicationController if payload = session.delete(:sso_payload) sso_provider(payload) - else - render_serialized(user, UserSerializer) end + render_serialized(user, UserSerializer) end end diff --git a/spec/controllers/session_controller_spec.rb b/spec/controllers/session_controller_spec.rb index 26d9b7f579..3f1af05533 100644 --- a/spec/controllers/session_controller_spec.rb +++ b/spec/controllers/session_controller_spec.rb @@ -264,40 +264,61 @@ describe SessionController do expect(response.code).to eq('419') end - it 'can act as an SSO provider' do - SiteSetting.enable_sso_provider = true - SiteSetting.enable_sso = false - SiteSetting.enable_local_logins = true - SiteSetting.sso_secret = "topsecret" + describe 'can act as an SSO provider' do + before do + SiteSetting.enable_sso_provider = true + SiteSetting.enable_sso = false + SiteSetting.enable_local_logins = true + SiteSetting.sso_secret = "topsecret" - sso = SingleSignOn.new - sso.nonce = "mynonce" - sso.sso_secret = SiteSetting.sso_secret - sso.return_sso_url = "http://somewhere.over.rainbow/sso" + @sso = SingleSignOn.new + @sso.nonce = "mynonce" + @sso.sso_secret = SiteSetting.sso_secret + @sso.return_sso_url = "http://somewhere.over.rainbow/sso" - get :sso_provider, Rack::Utils.parse_query(sso.payload) + @user = Fabricate(:user, password: "frogs", active: true, admin: true) + EmailToken.update_all(confirmed: true) + end - expect(response).to redirect_to("/login") + it "successfully logs in and redirects user to return_sso_url when the user is not logged in" do + get :sso_provider, Rack::Utils.parse_query(@sso.payload) + expect(response).to redirect_to("/login") - user = Fabricate(:user, password: "frogs", active: true, admin: true) - EmailToken.update_all(confirmed: true) + xhr :post, :create, login: @user.username, password: "frogs", format: :json - xhr :post, :create, login: user.username, password: "frogs", format: :json + location = cookies[:sso_destination_url] + # javascript code will handle redirection of user to return_sso_url + expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/) - location = response.header["Location"] - expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/) + payload = location.split("?")[1] + sso2 = SingleSignOn.parse(payload, "topsecret") - payload = location.split("?")[1] + expect(sso2.email).to eq(@user.email) + expect(sso2.name).to eq(@user.name) + expect(sso2.username).to eq(@user.username) + expect(sso2.external_id).to eq(@user.id.to_s) + expect(sso2.admin).to eq(true) + expect(sso2.moderator).to eq(false) + end - sso2 = SingleSignOn.parse(payload, "topsecret") + it "successfully redirects user to return_sso_url when the user is logged in" do + log_in_user(@user) - expect(sso2.email).to eq(user.email) - expect(sso2.name).to eq(user.name) - expect(sso2.username).to eq(user.username) - expect(sso2.external_id).to eq(user.id.to_s) - expect(sso2.admin).to eq(true) - expect(sso2.moderator).to eq(false) + get :sso_provider, Rack::Utils.parse_query(@sso.payload) + location = response.header["Location"] + expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/) + + payload = location.split("?")[1] + sso2 = SingleSignOn.parse(payload, "topsecret") + + expect(sso2.email).to eq(@user.email) + expect(sso2.name).to eq(@user.name) + expect(sso2.username).to eq(@user.username) + expect(sso2.external_id).to eq(@user.id.to_s) + expect(sso2.admin).to eq(true) + expect(sso2.moderator).to eq(false) + end end describe 'local attribute override from SSO payload' do From c3cadbb4d8d12e6df3ede910b5d2b13de367ed83 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Mon, 26 Oct 2015 22:38:34 +0530 Subject: [PATCH 047/114] Update Translations --- config/locales/client.ar.yml | 18 ++++++++++---- config/locales/client.bs_BA.yml | 5 ---- config/locales/client.cs.yml | 5 ---- config/locales/client.da.yml | 24 ++++++++++++++---- config/locales/client.de.yml | 9 +++---- config/locales/client.es.yml | 11 +++++---- config/locales/client.fa_IR.yml | 5 ---- config/locales/client.fi.yml | 5 ---- config/locales/client.fr.yml | 26 ++++++++++++++++---- config/locales/client.he.yml | 5 ---- config/locales/client.it.yml | 5 ---- config/locales/client.ja.yml | 5 ---- config/locales/client.ko.yml | 5 ---- config/locales/client.nb_NO.yml | 5 ---- config/locales/client.nl.yml | 5 ---- config/locales/client.pl_PL.yml | 5 ---- config/locales/client.pt.yml | 11 +++++---- config/locales/client.pt_BR.yml | 5 ---- config/locales/client.ro.yml | 5 ---- config/locales/client.ru.yml | 5 ---- config/locales/client.sq.yml | 5 ---- config/locales/client.sv.yml | 5 ---- config/locales/client.te.yml | 5 ---- config/locales/client.tr_TR.yml | 5 ---- config/locales/client.uk.yml | 2 -- config/locales/client.zh_CN.yml | 5 ---- config/locales/client.zh_TW.yml | 5 ---- config/locales/server.ar.yml | 2 -- config/locales/server.bs_BA.yml | 2 -- config/locales/server.cs.yml | 2 -- config/locales/server.da.yml | 2 -- config/locales/server.de.yml | 3 +-- config/locales/server.es.yml | 2 -- config/locales/server.fa_IR.yml | 2 -- config/locales/server.fi.yml | 2 -- config/locales/server.fr.yml | 19 ++++++++++++-- config/locales/server.he.yml | 2 -- config/locales/server.id.yml | 1 - config/locales/server.it.yml | 6 ++--- config/locales/server.ja.yml | 2 -- config/locales/server.ko.yml | 2 -- config/locales/server.nb_NO.yml | 1 - config/locales/server.nl.yml | 2 -- config/locales/server.pl_PL.yml | 2 -- config/locales/server.pt.yml | 4 +-- config/locales/server.pt_BR.yml | 2 -- config/locales/server.ro.yml | 2 -- config/locales/server.ru.yml | 2 -- config/locales/server.sq.yml | 2 -- config/locales/server.sv.yml | 1 - config/locales/server.tr_TR.yml | 4 +-- config/locales/server.uk.yml | 2 -- config/locales/server.zh_CN.yml | 2 -- config/locales/server.zh_TW.yml | 2 -- plugins/poll/config/locales/client.tr_TR.yml | 6 +++++ 55 files changed, 100 insertions(+), 184 deletions(-) diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index 07528c903f..ecd1820bdf 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -538,6 +538,7 @@ ar: tracked_categories: "Tracked" tracked_categories_instructions: "ستتبع بشكل تلقائي جميع المواضيع الجديدة في هذه التصنيفات. عدد المشاركات الجديدة سيظهر بجانب الموضوع." muted_categories: "كتم" + muted_categories_instructions: "لن يتم إشعارك بأي جديد عن المواضيع الجديدة في هذه التصنيفات، ولن تظهر مواضيع هذه التصنيفات في قائمة المواضيع المنشورة مؤخراً." delete_account: "حذف الحساب" delete_account_confirm: "هل انت متاكد من انك تريد حذف هذا المستخدم؟ هذا الإجراء دائماً!" deleted_yourself: "حسابك تم حذفه بنجاح" @@ -857,6 +858,8 @@ ar: resend_activation_email: "اضغط هنا لإرسال رسالة إلكترونية أخرى لتفعيل الحساب." sent_activation_email_again: "لقد سبق وأن تم إرسال رسالة إلكترونية إلى {{currentEmail}} لتفعيل حسابك. تأكد من مجلد السبام في بريدك." to_continue: "الرجاء تسجيل الدخول..." + preferences: "يتوجب عليك تسجيل الدخول لتغيير إعداداتك الشخصية." + forgot: "لا أذكر معلومات حسابي" google: title: "مع جوجل" message: "التحقق من خلال حساب جوجل ( الرجاء التأكد من عدم تشغيل مانع الاعلانات المنبثقة في المتصفح)" @@ -914,6 +917,7 @@ ar: show_edit_reason: "(اضف سبب التعديل)" reply_placeholder: "أكتب هنا. استخدم Markdown, BBCode, أو HTML للتشكيل. اسحب أو الصق الصور." view_new_post: "الاطلاع على أحدث مشاركاتك" + saving: "جارِ الحفظ" saved: "تم الحفظ" saved_draft: "جاري إضافة المسودة. اضغط للاستئناف" uploading: "يتم الرفع..." @@ -928,6 +932,7 @@ ar: link_description: "ادخل وصف الرابط هنا " link_dialog_title: "اضف الرابط" link_optional_text: "عنوان اختياري" + link_placeholder: "http://example.com \"نص إختياري\"" quote_title: "حظر الاقتباس" quote_text: "حظر الاقتباس" code_title: "الاطلاع على التنسيق" @@ -940,10 +945,10 @@ ar: heading_title: "اخفاء" heading_text: "اخفاء" hr_title: "Horizontal Rule" - undo_title: "تراجع" - redo_title: "تقدم" help: "مساعدة في الـ Maekdown" toggler: "اخف او اظهر معلومات الكاتب" + modal_ok: "موافق" + modal_cancel: "إلغاء" admin_options_title: "اختياري اضافة اعدادات الموضوع" auto_close: label: "اغلاق تلقائي لوقت الموضع" @@ -1042,10 +1047,10 @@ ar: bulk: reset_read: "تصفير القراءات" delete: "المواضيع المحذوفة" - dismiss_posts: "إخفاء المشاركات" - dismiss_posts_tooltip: "تصفير عدادا المواضيع الغير مقروءة مع الاستمرار في إظهارها في قائمة المواضيع الغير مقروءة عند إضافة مشاركات جديدة" + dismiss: "إخفاء" + dismiss_body: "هل تريد إخفاء المشاركات الجديدة فقط في هذه المواضيع؟ أم تريد إخفاء المواضيع بشكل كامل؟" + dismiss_posts: "إخفاء المشاركات الجديدة فقط" dismiss_topics: "إخفاء المواضيع" - dismiss_topics_tooltip: "توقف عن إظهار هذه المواضيع ضمن قائمة الغير مقروءة عند إضافة مشاركات جديدة لها" dismiss_new: "إخفاء الجديد" toggle: "إيقاف/تشغيل الاختيار المتعدد للمواضيع" actions: "عمليات تنفذ دفعة واحدة" @@ -1217,6 +1222,7 @@ ar: description: "لن يتم إشعارك بأي جديد يخص هذه الرسالة الخاصة." muted: title: "مكتوم" + description: "لن يتم إشعارك بأي جديد يخص هذا الموضوع ولن يظهرهذا الموضوع في قائمة المواضيع المنشورة مؤخراً." actions: recover: "استرجاع الموضوع" delete: "حذف الموضوع" @@ -1477,6 +1483,7 @@ ar: revert_to_regular: "حذف اللون الوظيفي" rebake: "إعادة بناء HTML" unhide: "إظهار" + change_owner: "تغيير الملكية" actions: flag: 'التبليغات' defer_flags: @@ -1733,6 +1740,7 @@ ar: description: "سوف تُنبه اذا قام أحد بالاشارة لاسمك \"@name\" أو الرد عليك." muted: title: "كتم" + description: "لن يتم إشعارك بأي مشاركات جديدة في هذه التصنيفات ولن يتم عرضها في قائمة المواضيع المنشورة مؤخراً." flagging: title: 'شكرا لمساعدتك في إبقاء مجتمعنا نظيفاً.' private_reminder: 'التبليغات ذات خصوصية، تظهر فقط للمشرفين' diff --git a/config/locales/client.bs_BA.yml b/config/locales/client.bs_BA.yml index 242a214b74..fe08f74644 100644 --- a/config/locales/client.bs_BA.yml +++ b/config/locales/client.bs_BA.yml @@ -645,8 +645,6 @@ bs_BA: heading_title: "Naslov" heading_text: "Naslov" hr_title: "Horizontalna Crta" - undo_title: "Poništi" - redo_title: "Povrati" help: "Markdown Editing Help" toggler: "sakrij ili pokaži komposer" admin_options_title: "Optional staff settings for this topic" @@ -702,10 +700,7 @@ bs_BA: bulk: reset_read: "Reset Read" delete: "Delete Topics" - dismiss_posts: "Dismiss Posts" - dismiss_posts_tooltip: "Clear unread counts on these topics but continue to show them on my unread list when new posts are made" dismiss_topics: "Dismiss Topics" - dismiss_topics_tooltip: "Stop showing these topics in my unread list when new posts are made" dismiss_new: "Dismiss New" toggle: "toggle bulk selection of topics" actions: "Bulk Actions" diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index d37152776b..aaba93cf0d 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -746,8 +746,6 @@ cs: heading_title: "Nadpis" heading_text: "Nadpis" hr_title: "Horizontální oddělovač" - undo_title: "Zpět" - redo_title: "Opakovat" help: "Nápověda pro Markdown" toggler: "zobrazit nebo skrýt editor příspěvku" admin_options_title: "Volitelné administrační nastavení tématu" @@ -813,10 +811,7 @@ cs: bulk: reset_read: "reset přečteného" delete: "Smazat témata" - dismiss_posts: "Odbýt příspěvky" - dismiss_posts_tooltip: "Smazat počet nepřečtených příspěvků v tématu ale na seznamu je dál zobrazovat jako nepřečtené" dismiss_topics: "Odbýt témata" - dismiss_topics_tooltip: "Nezobrazovat tato témata jako nepřečtená při novém příspěvku" dismiss_new: "Odbýt nová" toggle: "hromadný výběr témat" actions: "Hromadné akce" diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index 9b794b43f5..aca4e827f1 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -418,6 +418,7 @@ da: tracked_categories: "Fulgt" tracked_categories_instructions: "Du vil automatisk følge alle nye emner i disse kategorier. En optælling af nye indlæg vises ved emnet." muted_categories: "Ignoreret" + muted_categories_instructions: "Du får ikke beskeder om nye emner i disse kategorier og de fremstår ikke i seneste." delete_account: "Slet min konto" delete_account_confirm: "Er du sikker på du vil slette din konto permanent? Dette kan ikke fortrydes!" deleted_yourself: "Din konto er nu slettet." @@ -556,6 +557,9 @@ da: user: "Inviteret bruger" sent: "Sendt" none: "Der er ingen afventende invitationer." + truncated: + one: "Viser den første invitation." + other: "Viser de første {{count}} invitationer." redeemed: "Brugte invitationer" redeemed_tab: "Indløst" redeemed_tab_with_count: "Indløst ({{count}})" @@ -725,6 +729,9 @@ da: admin_not_allowed_from_ip_address: "Du kan ikke logge på som administrator fra denne IP adresse." resend_activation_email: "Klik her for at sende aktiverings-e-mail’en igen." sent_activation_email_again: "Vi har sendt endnu en aktiverings-e-mail til dig på {{currentEmail}}. Det kan tage nogen få minutter før den når frem; kontrollér også din spam-mappe." + to_continue: "Log venligst ind" + preferences: "Du skal være logget ind for at ændre præferencer." + forgot: "Jeg kan ikke huske min kontos detaljer" google: title: "med Google" message: "Logger ind med Google (kontrollér at pop-op-blokering ikke er aktiv)" @@ -749,6 +756,7 @@ da: emoji_one: "Emoji One" composer: emoji: "Emoji :smile:" + more_emoji: "mere..." options: "Indstillinger" whisper: "hvisken" add_warning: "Dette er en officiel advarsel." @@ -781,6 +789,7 @@ da: show_edit_reason: "(tilføj en begrundelse for ændringen)" reply_placeholder: "Skriv her. Brug Markdown, BBCode eller HTML til at formattere. Træk eller indsæt billeder." view_new_post: "Se dit nye indlæg." + saving: "Gemmer." saved: "Gemt!" saved_draft: "Kladde i gang. Vælg for at fortsætte med den." uploading: "Uploader…" @@ -795,6 +804,7 @@ da: link_description: "skriv linkets beskrivelse her" link_dialog_title: "Indsæt link" link_optional_text: "evt. titel" + link_placeholder: "http://example.com \"valgfri tekst\"" quote_title: "Citatblok" quote_text: "Citatblok" code_title: "Præformateret tekst" @@ -807,10 +817,10 @@ da: heading_title: "Overskrift" heading_text: "Overskrift" hr_title: "Vandret streg" - undo_title: "Fortryd" - redo_title: "Gentag" help: "Hjælp til Markdown-redigering" toggler: "skjul eller vis editor-panelet" + modal_ok: "OK" + modal_cancel: "Annuller" admin_options_title: "Valgfrie staff-indstillinger for dette emne" auto_close: label: "Tidspunkt for automatisk lukning af emne:" @@ -905,10 +915,10 @@ da: bulk: reset_read: "Nulstil \"læst\"" delete: "Slet emner" - dismiss_posts: "Afvis indlæg" - dismiss_posts_tooltip: "Nulstil \"ulæste\" på disse emner, men fortsæt med at vise dem på min \"ulæste\" liste når der kommer nye indlæg" + dismiss: "Afvis" + dismiss_body: "Vil du kun afvise nye indlæg i emnerne eller afvise emnerne helt?" + dismiss_posts: "Afvis kun nye indlæg" dismiss_topics: "Afvist Emner" - dismiss_topics_tooltip: "Stop med at vide disse emner i min \"ulæste\" liste når der kommer nye indlæg" dismiss_new: "Afvis nye" toggle: "vælg flere emner af gangen" actions: "Handlinger på flere indlæg" @@ -1052,6 +1062,7 @@ da: description: "Du vil aldrig få notifikationer om denne besked." muted: title: "Stille!" + description: "Du vil aldrig få beskeder om noget i indlæggene og de vil ikke vises i seneste." actions: recover: "Gendan emne" delete: "Slet emne" @@ -1268,6 +1279,7 @@ da: revert_to_regular: "Fjern Personale farve" rebake: "Gendan HTML" unhide: "Vis" + change_owner: "Skift ejerskab" actions: flag: 'Flag' defer_flags: @@ -1452,6 +1464,7 @@ da: description: "Du vil modtage en notifikation, hvis nogen nævner dit @name eller svarer dig." muted: title: "Ignoreret" + description: "Du vil aldrig få besked om noget om nye emner i kategorierne og de vises heller ikke i seneste." flagging: title: 'Tak fordi du hjælper med at holde vores forum civiliseret!' private_reminder: 'flag er private, de er kun

      synlige for personalet' @@ -1504,6 +1517,7 @@ da: help: "Dette emne er ikke fastgjort for dig; det vil blive vist i den normale rækkefølge" pinned_globally: title: "Fastgjort globalt" + help: "Dette emne er globalt fastgjort; det vises i toppen af seneste og i dets kategori" pinned: title: "Fastgjort" help: "Dette emne er fastgjort for dig; det vil blive vist i toppen af dets kategori" diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index 06a8571b4e..a8b6ad7319 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -795,6 +795,7 @@ de: link_description: "gib hier eine Link-Beschreibung ein" link_dialog_title: "Hyperlink einfügen" link_optional_text: "Optionaler Titel" + link_placeholder: "http://example.com \"Optionaler Text\"" quote_title: "Zitat" quote_text: "Zitat" code_title: "Vorformatierter Text" @@ -807,8 +808,6 @@ de: heading_title: "Überschrift" heading_text: "Überschrift" hr_title: "Horizontale Linie" - undo_title: "Rückgängig machen" - redo_title: "Wiederholen" help: "Hilfe zur Markdown-Formatierung" toggler: "Eingabebereich aus- oder einblenden" admin_options_title: "Optionale Mitarbeiter-Einstellungen für dieses Thema" @@ -905,10 +904,7 @@ de: bulk: 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 neue Beiträge verfasst werden." dismiss_topics: "Themen ignorieren" - dismiss_topics_tooltip: "Diese Themen ignorieren und nicht mehr als ungelesen anzeigen, selbst wenn neue Beiträge verfasst werden." dismiss_new: "Neue Themen ignorieren" toggle: "zu Massenoperationen auf Themen umschalten" actions: "Massenoperationen" @@ -1424,6 +1420,7 @@ de: change_in_category_topic: "Beschreibung bearbeiten" already_used: 'Diese Farbe wird bereits für eine andere Kategorie verwendet' security: "Sicherheit" + special_warning: "Warnung: Diese Kategorie is eine pre-seeded Kategorie und die Sicherheitseinstellungen können nicht bearbeitet werden. Wenn du wünschst nicht diese Kategorie zu benutzen dann lösche sie anstatt sie zu wiederverwenden" images: "Bilder" auto_close_label: "Themen automatisch schließen nach:" auto_close_units: "Stunden" @@ -2368,7 +2365,9 @@ de: image: "Bild" delete_confirm: "Möchtest du wirklich das :%{name}: Emoji löschen?" embedding: + get_started: "Wenn du Discourse in einer anderen Website einbetten möchtest, beginne mit dem hinzufügen des host. " confirm_delete: "Möchtest du wirklich diesen Host löschen?" + sample: "Benutze den folgenden HTML code für deine Seite um discourse Beiträge zu erstellen und einzubetten. Ersetze ERSETZE_MICH mit der URL der Seite in die du sie einbetten möchtest." title: "Einbettung" host: "Erlaubte Hosts" edit: "bearbeiten" diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index bedde32bdf..ed6e470b23 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -804,6 +804,7 @@ es: link_description: "introduzca descripción del enlace aquí" link_dialog_title: "Insertar Enlace" link_optional_text: "título opcional" + link_placeholder: "http://ejemplo.com \"texto opcional\"" quote_title: "Cita" quote_text: "Cita" code_title: "Texto preformateado" @@ -816,10 +817,10 @@ es: heading_title: "Encabezado" heading_text: "Encabezado" hr_title: "Linea Horizontal" - undo_title: "Deshacer" - redo_title: "Rehacer" help: "Ayuda de Edición con Markdown" toggler: "ocultar o mostrar el panel de edición" + modal_ok: "OK" + modal_cancel: "Cancelar" admin_options_title: "Opciones de moderación para este tema" auto_close: label: "Tiempo para cierre automático del tema" @@ -914,10 +915,10 @@ es: bulk: reset_read: "Restablecer leídos" delete: "Eliminar temas" - dismiss_posts: "Ignorar publicaciones" - dismiss_posts_tooltip: "Limpiar contadores de 'no leídos' en estos temas pero continuar mostrándolos en mi lista de 'no leídos' cuando se hacen nuevas publicaciones" + dismiss: "Descartar" + dismiss_body: "¿Quieres descartar solo los nuevos posts en estos temas o los temas por completo?" + dismiss_posts: "Descartar nuevos posts" dismiss_topics: "Descartar Temas" - dismiss_topics_tooltip: "Dejar de mostrar estos temas en mi lista de 'no leídos' cuando se hacen nuevas publicaciones" dismiss_new: "Ignorar nuevos" toggle: "activar selección de temas en bloque" actions: "Acciones en bloque" diff --git a/config/locales/client.fa_IR.yml b/config/locales/client.fa_IR.yml index 49d6c91455..0e187fca11 100644 --- a/config/locales/client.fa_IR.yml +++ b/config/locales/client.fa_IR.yml @@ -685,8 +685,6 @@ fa_IR: heading_title: "عنوان" heading_text: "عنوان" hr_title: "خط کش افقی" - undo_title: "برگردانی" - redo_title: "دوباره‌گردانی" help: "راهنمای ویرایش با Markdown" toggler: "مخفی یا نشان دادن پنل نوشتن" admin_options_title: "تنظیمات اختیاری مدیران برای این موضوع" @@ -756,10 +754,7 @@ fa_IR: bulk: reset_read: "تنظیم مجدد خوانده شد" delete: "حذف موضوعات" - dismiss_posts: "بستن نوشته ها" - dismiss_posts_tooltip: "پاک کردن شمارش خوانده نشده های این موضوع اما همچنان در لیست خوانده نشده های من آنها را نمایش بده زمانی که پست جدید ساخته شد" dismiss_topics: "بستن موضوعات" - dismiss_topics_tooltip: "نشان دادن این تاپیک را از لیست خوانده نشده متوقف کن وقتی پست های جدید بوجود آمد" dismiss_new: "بستن جدید" toggle: "ضامن انتخاب یکباره موضوعات" actions: "عملیات یکجا" diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index 872159da16..28713872ff 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -807,8 +807,6 @@ fi: heading_title: "Otsikko" heading_text: "Otsikko" hr_title: "Vaakaviiva" - undo_title: "Peru" - redo_title: "Tee uudestaan" help: "Markdown apu" toggler: "näytä tai piilota kirjoitusalue" admin_options_title: "Tämän ketjun vain henkilökunnalle näytettävät asetukset" @@ -905,10 +903,7 @@ fi: bulk: reset_read: "Palauta lukutila" delete: "Poista ketjut" - dismiss_posts: "Unohda viestit" - dismiss_posts_tooltip: "Tyhjennä nyt ilmoitukset lukemattomista viesteistä näiden ketjujen osalta, mutta näytä ilmoitukset uudestaan, kun ketjuihin kirjoitetaan uusia viestejä." dismiss_topics: "Unohda ketjut" - dismiss_topics_tooltip: "Lakkaa pysyvästi näyttämästä ilmoituksia uusista viesteistä näissä ketjuissa." dismiss_new: "Unohda uudet" toggle: "Vaihda useamman ketjun valintaa" actions: "Massatoiminnot" diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index ccf523d0ec..789ecbc7ed 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -418,6 +418,7 @@ fr: tracked_categories: "Suivies" tracked_categories_instructions: "Vous allez suivre automatiquement tous les nouveaux sujets dans ces catégories. Le nombre de nouveaux messages apparaîtra à côté du sujet." muted_categories: "Désactivés" + muted_categories_instructions: "Vous ne serez notifié de rien concernant les nouveaux sujets dans ces catégories, et elles n'apparaîtront pas dans les dernières catégories." delete_account: "Supprimer mon compte" delete_account_confirm: "Êtes-vous sûr de vouloir supprimer définitivement votre compte ? Cette action ne peut être annulée !" deleted_yourself: "Votre compte a été supprimé avec succès." @@ -556,6 +557,9 @@ fr: user: "Utilisateurs" sent: "Envoyé" none: "Il n'y a plus d'invitation en attente à afficher." + truncated: + one: "Afficher la première invitation." + other: "Afficher les {{count}} premières invitations." redeemed: "Invitations acceptées" redeemed_tab: "Utilisés" redeemed_tab_with_count: "Invitations acceptées ({{count}})" @@ -726,6 +730,8 @@ fr: resend_activation_email: "Cliquez ici pour envoyer à nouveau le courriel d'activation." sent_activation_email_again: "Nous venons d'envoyer un nouveau courriel d'activation à {{currentEmail}}. Il peut prendre quelques minutes à arriver; n'oubliez pas de vérifier votre répertoire spam." to_continue: "Veuillez vous connecter" + preferences: "Vous devez être connecté pour modifier vos préférences utilisateur." + forgot: "J'ai oublié les détails de mon compte" google: title: "via Google" message: "Authentification via Google (assurez-vous que les popups ne soient pas bloquées)" @@ -750,9 +756,11 @@ fr: emoji_one: "Emoji One" composer: emoji: "Emoji :smile:" + more_emoji: "plus..." options: "Options" whisper: "murmure" add_warning: "Ceci est un avertissement officiel." + toggle_whisper: "Activer/Désactiver Whisper" posting_not_on_topic: "À quel sujet voulez-vous répondre ?" saving_draft_tip: "sauvegarde en cours..." saved_draft_tip: "sauvegardé" @@ -781,6 +789,7 @@ fr: show_edit_reason: "(ajouter la raison de l'édition)" reply_placeholder: "Écrivez ici. Utilisez Markdown, BBCode, ou HTML pour formatter. Glissez ou collez des images." view_new_post: "Voir votre nouveau message." + saving: "Sauvegarde" saved: "Sauvegardé !" saved_draft: "Vous avez un message brouillon en cours. Sélectionner cette barre pour reprendre son édition." uploading: "Envoi en cours…" @@ -795,6 +804,7 @@ fr: link_description: "saisir ici la description du lien" link_dialog_title: "Insérez le lien" link_optional_text: "titre optionnel" + link_placeholder: "http://exemple.fr \"texte facultatif\"" quote_title: "Citation" quote_text: "Citation" code_title: "Texte préformaté" @@ -807,10 +817,10 @@ fr: heading_title: "Titre" heading_text: "Titre" hr_title: "Barre horizontale" - undo_title: "Annuler" - redo_title: "Refaire" help: "Aide Markdown" toggler: "Afficher ou cacher le composer" + modal_ok: "OK" + modal_cancel: "Annuler" admin_options_title: "Paramètres optionnels pour ce sujet" auto_close: label: "Heure de fermeture automatique de ce sujet :" @@ -905,10 +915,10 @@ fr: bulk: reset_read: "Réinitialiser la lecture" delete: "Supprimer les sujets" - dismiss_posts: "Ignorer les messages" - dismiss_posts_tooltip: "Marquer comme lus ces sujets mais continuer de les afficher dans ma liste de sujets non-lus si de nouveaux messages apparaissent." + dismiss: "Ignorer" + dismiss_body: "Voulez-vous ignorer uniquement les nouveaux messages dans ces sujets, ou bien ignorer les sujets dans leur intégralité ?" + dismiss_posts: "Ignorer uniquement les nouveaux messages" dismiss_topics: "Ignorer les sujets" - dismiss_topics_tooltip: "Marquer tous les sujets comme lus et ne plus les afficher dans ma liste de sujet non-lus si de nouveaux messages apparaissent." dismiss_new: "Ignorer Nouveaux" toggle: "activer la sélection multiple des sujets" actions: "Actions sur sélection multiple" @@ -1056,6 +1066,7 @@ fr: description: "Vous ne serez jamais averti de quoi que ce soit à propos de ce message." muted: title: "Silencieux" + description: "Vous ne serez jamais notifié de rien concernant ce sujet, et il n'apparaîtra pas des les derniers sujets." actions: recover: "Annuler Suppression Sujet" delete: "Supprimer Sujet" @@ -1272,6 +1283,7 @@ fr: revert_to_regular: "Retirer la couleur modérateur" rebake: "Reconstruire l'HTML" unhide: "Ré-afficher" + change_owner: "Modifier la propriété" actions: flag: 'Signaler' defer_flags: @@ -1456,6 +1468,7 @@ fr: description: "Vous serez notifié si quelqu'un mentionne votre @pseudo ou vous répond." muted: title: "Silencieux" + description: "Vous ne serez jamais notifié de rien concernant les nouveaux sujets dans ces catégories, et elles n'apparaîtront pas dans les dernières catégories." flagging: title: 'Merci de nous aider à garder notre communauté aimable !' private_reminder: 'les signalements sont privés, seulement visible aux modérateurs' @@ -1508,6 +1521,7 @@ fr: help: "Ce sujet est désépinglé pour vous; il sera affiché dans l'ordre par défaut" pinned_globally: title: "Épingler globalement" + help: "Ce sujet est épinglé globalement; il apparaîtra en premier dans la liste des derniers sujets et dans sa catégorie" pinned: title: "Épingler" help: "Ce sujet est épinglé pour vous; il s'affichera en haut de sa catégorie" @@ -2394,7 +2408,9 @@ fr: embed_username_key_from_feed: "Clé pour extraire le pseudo du flux." embed_truncate: "Tronquer les messages intégrés" embed_whitelist_selector: "Sélecteur CSS pour les éléments qui seront autorisés dans les contenus intégrés" + whitelist_example: "article, #story, .post" embed_blacklist_selector: "Sélecteur CSS pour les éléments qui seront interdits dans les contenus intégrés" + blacklist_example: ".ad-unit, header" feed_polling_enabled: "Importer les messages via flux RSS/ATOM" feed_polling_url: "URL du flux RSS/ATOM à importer" save: "Sauvegarder paramètres d'intégration" diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index 8d0ea9014a..dcb3b77ed6 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -806,8 +806,6 @@ he: heading_title: "כותרת" heading_text: "כותרת" hr_title: "קו אופקי" - undo_title: "בטל" - redo_title: "חזור" help: "עזרה על כתיבה ב-Markdown" toggler: "הסתר או הצג את פאנל העריכה" admin_options_title: "אפשרויות צוות אופציונליות לפוסט זה" @@ -904,10 +902,7 @@ he: bulk: reset_read: "איפוס נקראו" delete: "מחיקת פוסטים" - dismiss_posts: "ביטול פרסומים" - dismiss_posts_tooltip: "ניקוי ספירת הלא-נקראים בפוסטים אלו, תווך המשך הצגתם ברשימת הלא נקראים כאשר נוספים פרסומים חדשים" dismiss_topics: "דחיית פוסטים" - dismiss_topics_tooltip: "הפסקת הצגת פוסטים אלו ברשימת הלא-נקראו האישית כאשר נוספים פרסומים חדשים" dismiss_new: "שחרור חדשים" toggle: "החלף קבוצה מסומנת של פוסטים" actions: "מקבץ פעולות" diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index 2e54405af8..be2a0d2791 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -803,8 +803,6 @@ it: heading_title: "Intestazione" heading_text: "Intestazione" hr_title: "Linea Orizzontale" - undo_title: "Annulla" - redo_title: "Ripeti" help: "Aiuto Inserimento Markdown" toggler: "nascondi o mostra il pannello di editing" admin_options_title: "Impostazioni dello staff opzionali per l'argomento" @@ -893,10 +891,7 @@ it: bulk: reset_read: "Reimposta Lettura" delete: "Elimina Argomenti" - dismiss_posts: "Chiudi Messaggi" - dismiss_posts_tooltip: "Azzera il contatore dei messaggi non-letti di questi argomenti, ma quando vengono creati nuovi messaggi continua a mostrarli nella mia lista dei non-letti " dismiss_topics: "Chiudi Argomenti" - dismiss_topics_tooltip: "Quando viene creato un nuovo messaggio, non mostrare più questi argomenti nella mia lista dei non-letti " dismiss_new: "Chiudi Nuovo" toggle: "commuta la selezione multipla degli argomenti" actions: "Azioni Multiple" diff --git a/config/locales/client.ja.yml b/config/locales/client.ja.yml index c2b8fbee2d..1665cfecd1 100644 --- a/config/locales/client.ja.yml +++ b/config/locales/client.ja.yml @@ -700,8 +700,6 @@ ja: heading_title: "見出し" heading_text: "見出し" hr_title: "水平線" - undo_title: "取り消し" - redo_title: "やり直し" help: "Markdown 編集のヘルプ" toggler: "編集パネルの表示/非表示" admin_options_title: "このトピックの詳細設定" @@ -771,10 +769,7 @@ ja: bulk: reset_read: "未読に設定" delete: "トピックを削除" - dismiss_posts: "既読に設定" - dismiss_posts_tooltip: "未読トピック数をクリアする。" dismiss_topics: "既読に設定" - dismiss_topics_tooltip: "新しい投稿を未読リストに非表示する" dismiss_new: "既読に設定" toggle: "選択したトピックを切り替え" actions: "操作" diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml index 18016dee63..700691f066 100644 --- a/config/locales/client.ko.yml +++ b/config/locales/client.ko.yml @@ -753,8 +753,6 @@ ko: heading_title: "표제" heading_text: "표제" hr_title: "수평선" - undo_title: "취소" - redo_title: "다시" help: "마크다운 편집 도움말" toggler: "작성 패널을 숨기거나 표시" admin_options_title: "이 토픽에 대한 옵션 설정" @@ -829,10 +827,7 @@ ko: bulk: reset_read: "읽기 초기화" delete: "토픽 삭제" - dismiss_posts: "글 닫기" - dismiss_posts_tooltip: "이 토픽들의 '읽지 않은' 표시를 없애고 새 토픽이 발생하면 내 '읽지 않은' 목록에 표시하기" dismiss_topics: "토픽 닫기" - dismiss_topics_tooltip: "새글이 발생할 때 내 '읽지 않은' 목록에 표시하지 않기" dismiss_new: "새글 제거" toggle: "토픽 복수 선택" actions: "일괄 적용" diff --git a/config/locales/client.nb_NO.yml b/config/locales/client.nb_NO.yml index 757c14190d..e793a49a25 100644 --- a/config/locales/client.nb_NO.yml +++ b/config/locales/client.nb_NO.yml @@ -749,8 +749,6 @@ nb_NO: heading_title: "Overskrift" heading_text: "Overskrift" hr_title: "Horisontalt Skille" - undo_title: "Angre" - redo_title: "Gjenta" help: "Hjelp for redigering i Markdown" toggler: "gjem eller vis redigeringspanelet" admin_options_title: "Valgfrie emne-instillinger for ansatte" @@ -820,10 +818,7 @@ nb_NO: bulk: reset_read: "Nullstill lest" delete: "Slett Emne" - dismiss_posts: "Avvis innlegg" - dismiss_posts_tooltip: "Tøm antallet uleste innlegg i disse emnene men fortsett å vis dem i min liste over uleste innlegg når nye innlegg blir lagt inn" dismiss_topics: "Avvis innlegg" - dismiss_topics_tooltip: "Ikke vis disse emnene i min ulest-liste når nye innlegg i disse forekommer" dismiss_new: "Lest" toggle: "Veksle mellom massevelging av emner" actions: "Massehandlinger" diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index d95016bcb3..5751724e5c 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -807,8 +807,6 @@ nl: heading_title: "Kop" heading_text: "Kop" hr_title: "Horizontale lijn" - undo_title: "Herstel" - redo_title: "Opnieuw" help: "Uitleg over Markdown" toggler: "verberg of toon de editor" admin_options_title: "Optionele stafinstellingen voor deze topic" @@ -905,10 +903,7 @@ nl: bulk: reset_read: "markeer als ongelezen" delete: "Verwijder topics" - dismiss_posts: "Verwijder berichten" - dismiss_posts_tooltip: "Reset de teller voor ongelezen berichten voor deze topics, maar houd ze wel in mijn lijst van ongelezen topics als er nieuwe berichten worden toegevoegd" dismiss_topics: "Verwijder topics" - dismiss_topics_tooltip: "Laat deze topics niet meer in mijn lijst van ongelezen topics zien wanneer er nieuwe berichten worden geplaatst." dismiss_new: "markeer nieuwe berichten als gelezen" toggle: "toggle bulkselectie van topics" actions: "Bulk Acties" diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml index b4dd85df58..dccfaaf6fd 100644 --- a/config/locales/client.pl_PL.yml +++ b/config/locales/client.pl_PL.yml @@ -848,8 +848,6 @@ pl_PL: heading_title: "Nagłówek" heading_text: "Nagłówek" hr_title: "Pozioma linia" - undo_title: "Cofnij" - redo_title: "Ponów" help: "Pomoc formatowania Markdown" toggler: "ukryj lub pokaż panel kompozytora tekstu" admin_options_title: "Opcjonalne ustawienia obsługi dla tego tematu" @@ -947,10 +945,7 @@ 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: "Wyczyść nieprzeczytane" toggle: "włącz grupowe zaznaczanie tematów" actions: "Operacje grupowe" diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index 0e6bd6054a..ebc8d0ef4c 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -804,6 +804,7 @@ pt: link_description: "digite a descrição da hiperligação aqui" link_dialog_title: "Inserir Hiperligação" link_optional_text: "título opcional" + link_placeholder: "http://example.com \"texto opcional\"" quote_title: "Bloco de Citação" quote_text: "Bloco de Citação" code_title: "Texto pré-formatado" @@ -816,10 +817,10 @@ pt: heading_title: "Título" heading_text: "Título" hr_title: "Barra horizontal" - undo_title: "Desfazer" - redo_title: "Refazer" help: "Ajuda de Edição Markdown" toggler: "esconder ou exibir o painel de composição" + modal_ok: "OK" + modal_cancel: "Cancelar" admin_options_title: "Configurações opcionais do pessoal para este tópico" auto_close: label: "Tempo de fecho automático do tópico:" @@ -914,10 +915,10 @@ pt: bulk: reset_read: "Repor Leitura" delete: "Eliminar Tópicos" - dismiss_posts: "Destituir mensagens" - dismiss_posts_tooltip: "Remover contagem de não lidos nestes tópicos, mas manter a exibição dos mesmos na minha lista de não lidos quando novas mensagens são criadas." + dismiss: "Destituir" + dismiss_body: "Gostaria de destituir apenas as novas mensagens nestes tópicos, ou destituir os tópicos por inteiro?" + dismiss_posts: "Destituir Apenas Novas Mensagens" dismiss_topics: "Destituir Tópicos" - dismiss_topics_tooltip: "Parar a exibição destes tópicos na minha lista de não lidos quando são criadas novas mensagens" dismiss_new: "Destituir Novo" toggle: "ativar seleção em massa de tópicos" actions: "Ações em Massa" diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml index 3f3b4261c3..8b5341ca41 100644 --- a/config/locales/client.pt_BR.yml +++ b/config/locales/client.pt_BR.yml @@ -811,8 +811,6 @@ pt_BR: heading_title: "Título" heading_text: "Título" hr_title: "Barra horizontal" - undo_title: "Desfazer" - redo_title: "Refazer" help: "Ajuda da edição Markdown" toggler: "esconder ou exibir o painel de composição" admin_options_title: "Configurações opcionais da equipe para este tópico" @@ -909,10 +907,7 @@ pt_BR: bulk: reset_read: "Redefinir Lido" delete: "Apagar Tópicos" - dismiss_posts: "Ignorar Posts" - dismiss_posts_tooltip: "Zerar contagem de não lidos nestes tópicos, mas continuar a mostrar eles em minha lista de não lidos quando novos posts forem feitos." dismiss_topics: "Ignorar Tópicos" - dismiss_topics_tooltip: "Para de mostrar estes tópicos em minha lista de não lidos quando novos posts forem feitos." dismiss_new: "Dispensar Nova" toggle: "alternar a seleção em massa de tópicos" actions: "Ações em Massa" diff --git a/config/locales/client.ro.yml b/config/locales/client.ro.yml index 9841082a3b..be2b87fdc1 100644 --- a/config/locales/client.ro.yml +++ b/config/locales/client.ro.yml @@ -714,8 +714,6 @@ ro: heading_title: "Titlu" heading_text: "Titlu" hr_title: "Regulă de ordonare orizontală" - undo_title: "Anulează schimbările" - redo_title: "Refă schimbările" help: "Ajutor de editare" toggler: "ascunde sau arată panelul de compus" admin_options_title: "Setări opționale ale discuției pentru moderatori" @@ -780,10 +778,7 @@ ro: bulk: reset_read: "resetează citirea" delete: "Șterge subiectele" - dismiss_posts: "Șterge postarea" - dismiss_posts_tooltip: "Șterge părțile necitite din această discuție dar continuă afișarea lor în lista de necitite când au loc postări noi" dismiss_topics: "Sterge discuția" - dismiss_topics_tooltip: "Nu arăta aceste discuții în lista de necitite când au loc postări noi" dismiss_new: "Anulează cele noi" toggle: "activează selecția în masă pentru discuții" actions: "Acțiuni în masă" diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index b71d9ed55a..078246c42a 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -844,8 +844,6 @@ ru: heading_title: "Заголовок" heading_text: "Заголовок" hr_title: "Горизонтальный разделитель" - undo_title: "Отменить" - redo_title: "Повторить" help: "Справка по Markdown" toggler: "скрыть / показать панель редактирования" admin_options_title: "Дополнительные настройки темы" @@ -925,10 +923,7 @@ ru: bulk: reset_read: "Сбросить прочтённые" delete: "Удалить темы" - dismiss_posts: "Отложить сообщения" - dismiss_posts_tooltip: "Сбросить текущие непрочитанные сообщения в этих темах сейчас, но снова показывать в непрочитанных, когда появятся новые ответы." dismiss_topics: "Отложить темы" - dismiss_topics_tooltip: "Больше не показывать эти темы в непрочитанных, когда в них появятся новые ответы." dismiss_new: "Отложить новые" toggle: "Вкл./выкл. выбор нескольких тем" actions: "Массовое действие" diff --git a/config/locales/client.sq.yml b/config/locales/client.sq.yml index 01ce1e1a11..a78dc12a61 100644 --- a/config/locales/client.sq.yml +++ b/config/locales/client.sq.yml @@ -731,8 +731,6 @@ sq: heading_title: "Heading" heading_text: "Heading" hr_title: "Horizontal Rule" - undo_title: "Rikthe" - redo_title: "Ribëj" help: "Markdown Editing Help" toggler: "hide or show the composer panel" admin_options_title: "Optional staff settings for this topic" @@ -802,10 +800,7 @@ sq: bulk: reset_read: "Reseto Leximet" delete: "Delete Topics" - dismiss_posts: "Dismiss Posts" - dismiss_posts_tooltip: "Clear unread counts on these topics but continue to show them on my unread list when new posts are made" dismiss_topics: "Dismiss Topics" - dismiss_topics_tooltip: "Stop showing these topics in my unread list when new posts are made" dismiss_new: "Dismiss New" toggle: "toggle bulk selection of topics" actions: "Bulk Actions" diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index a5c21f6699..2fd9406992 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -727,8 +727,6 @@ sv: heading_title: "Rubrik" heading_text: "Rubrik" hr_title: "Horisontell linje" - undo_title: "Ångra" - redo_title: "Återställ" help: "Markdown Redigeringshjälp" toggler: "Dölj eller visa composer-panelen" admin_options_title: "Valfria personalinställningar för detta ämne" @@ -794,10 +792,7 @@ sv: bulk: reset_read: "Återställ Lästa" delete: "Ta bort diskussioner" - dismiss_posts: "Avfärda inlägg" - dismiss_posts_tooltip: "Nollställ räknaren för olästa i dessa diskussioner men fortsätt visa mig dem på min olästalista när nya inlägg har gjorts. " dismiss_topics: "Avfärda Diskussioner" - dismiss_topics_tooltip: "Sluta visa mig dessa diskussioner i min olästalista när nya inlägg har gjorts" dismiss_new: "Avfärda Nya" toggle: "toggla val av multipla ämnen" actions: "Massändringar" diff --git a/config/locales/client.te.yml b/config/locales/client.te.yml index 1190b07614..6cb6a53b4f 100644 --- a/config/locales/client.te.yml +++ b/config/locales/client.te.yml @@ -622,8 +622,6 @@ te: heading_title: "తలకట్టు" heading_text: "తలకట్టు" hr_title: "అడ్డు గీత" - undo_title: "రద్దు" - redo_title: "తిద్దు" help: "మార్క్ డైన్ సవరణ సహాయం" toggler: "దాచు లేదా చూపు కంపోజరు ఫలకం" admin_options_title: "ఈ విషయానికి ఐచ్చిక సిబ్బంది అమరికలు" @@ -677,10 +675,7 @@ te: bulk: reset_read: "రీలోడ్ రీసెట్" delete: "విషయాలు తొలగించు" - dismiss_posts: "టపాలు తుడువు" - dismiss_posts_tooltip: "ఈ విషయాలపే చదవని సంఖ్యను తుడువు, కానీ కొత్తగా వచ్చే టపాలను నా చదవని సంఖ్యలో చూపుటు కొనసాగించు" dismiss_topics: "విషయాలు తుడువు" - dismiss_topics_tooltip: "కొత్త టపాలు వచ్చినా, నా చదవని జాబితాలో ఈ విషయాలు నుండి చూపుటు ఆపు. " dismiss_new: "కొత్తవి తుడువు" toggle: "విషయాల బహుళ ఎంపికలు అటుఇటుచేయి" actions: "బహుళ చర్యలు" diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml index 3502a74a05..86b09c9a6a 100644 --- a/config/locales/client.tr_TR.yml +++ b/config/locales/client.tr_TR.yml @@ -751,8 +751,6 @@ tr_TR: heading_title: "Başlık" heading_text: "Başlık" hr_title: "Yatay Çizgi" - undo_title: "Geri Al" - redo_title: "Tekrarla" help: "Markdown Düzenleme Yardımı" toggler: "yazım alanını gizle veya göster" admin_options_title: "Bu konu için opsiyonel görevli ayarları" @@ -841,10 +839,7 @@ tr_TR: bulk: reset_read: "Okunmuşları Sıfırla" delete: "Konuları Sil" - dismiss_posts: "Gönderileri Yoksay" - dismiss_posts_tooltip: "Bu konulardaki okunmamışların sayısını sıfırla ama yeni gönderiler oluşturulduğunda okunmamışlar listemde göster" dismiss_topics: "Konuları Yoksay" - dismiss_topics_tooltip: "Bu konularda yeni gönderiler oluşturulunca okunmamış listemde gösterme" dismiss_new: "Yenileri Yoksay" toggle: "konuların toplu seçimini aç/kapa" actions: "Toplu İşlemler" diff --git a/config/locales/client.uk.yml b/config/locales/client.uk.yml index afb008b9dd..5fe50f3bb4 100644 --- a/config/locales/client.uk.yml +++ b/config/locales/client.uk.yml @@ -570,8 +570,6 @@ uk: heading_title: "Заголовок" heading_text: "Заголовок" hr_title: "Горизонтальна лінія" - undo_title: "Скасувати" - redo_title: "Повернути" help: "Markdown Editing Help" toggler: "показати або сховати панель редагування" admin_options_title: "Необов'язкові налаштування персоналу для цієї теми" diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index 91f5aedc91..c323239fbb 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -776,8 +776,6 @@ zh_CN: heading_title: "标题" heading_text: "标题头" hr_title: "分割线" - undo_title: "撤销" - redo_title: "重做" help: "Markdown 编辑帮助" toggler: "隐藏或显示编辑面板" admin_options_title: "本主题可选设置" @@ -873,10 +871,7 @@ zh_CN: bulk: reset_read: "设为未读" delete: "删除主题" - dismiss_posts: "忽略帖子" - dismiss_posts_tooltip: "清除这些帖子的未读计数但当有新帖子时在我的未读列表中继续显示他们" dismiss_topics: "忽略主题" - dismiss_topics_tooltip: "停止当有新帖子时在我的未读列表中显示这些主题" dismiss_new: "设为已读" toggle: "切换批量选择" actions: "批量操作" diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml index a377ca822c..586da7e973 100644 --- a/config/locales/client.zh_TW.yml +++ b/config/locales/client.zh_TW.yml @@ -692,8 +692,6 @@ zh_TW: heading_title: "標頭" heading_text: "標頭" hr_title: "分隔線" - undo_title: "復原" - redo_title: "重做" help: "Markdown 編輯說明" toggler: "隱藏或顯示編輯面板" admin_options_title: "此討論話題可選用之工作人員設定選項" @@ -753,10 +751,7 @@ zh_TW: bulk: reset_read: "重設閱讀" delete: "刪除討論話題" - dismiss_posts: "已讀文章" - dismiss_posts_tooltip: "標記此討論話題內未讀文章為已讀" dismiss_topics: "已讀討論話題" - dismiss_topics_tooltip: "停止顯示這個討論話題在我的未讀清單" dismiss_new: "設定新文章為已讀" toggle: "批量切換選擇討論話題" actions: "批量操作" diff --git a/config/locales/server.ar.yml b/config/locales/server.ar.yml index e2a15dbf82..38c2eaacb1 100644 --- a/config/locales/server.ar.yml +++ b/config/locales/server.ar.yml @@ -311,8 +311,6 @@ ar: [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "عن الفئة %{category}." - replace_paragraph: "[استبدل هذا الفقرة الأولى بوصف قصير لفئتك الجديدة. سيظهر هذا التوجيه في منطقة اختيار الفئة, لذا حاول أن تبقيها أقل من 200 حرف. حتى عند تحريرك لهذا النص أو إنشائك لموضوع, هذه الفئة لن تظهر على صفحة الفئات.]" - post_template: "%{replace_paragraph}\n\nاستخدم الفقرات التالي لوصف طويل, إضافة لإنشاء أي فئة قواعد أو توجيهات.\n\nبعض الأشياء تؤخذ بعين الإعتبار في أسفل أي ردود لنقاش:\n\n- لما هذا الفئة؟لماذا يختار الناس هذه الفئة لمواضيعهم؟\n\n- كيف تختلف الفئات الأخرى عن التي لدينا؟\n\n- هل نحتاج هذه الفئة؟\n\n- هل يجب أن ندمج هذه مع فئة أخرى, أو تقسم إلى أكثر من قئة ؟\n" errors: uncategorized_parent: "غير مصنف لا يمكن أن يكون فئة الأم" self_parent: "الفئة الفرعية لا يمكن ان تكون كالفئة الرئيسية." diff --git a/config/locales/server.bs_BA.yml b/config/locales/server.bs_BA.yml index f2f172e5cb..d8a9f98c7d 100644 --- a/config/locales/server.bs_BA.yml +++ b/config/locales/server.bs_BA.yml @@ -203,8 +203,6 @@ bs_BA: [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "About the %{category} category" - replace_paragraph: "[Replace this first paragraph with a short description of your new category. This guidance will appear in the category selection area, so try to keep it below 200 characters. Until you edit this text or create topics, this category won't appear on the categories page.]" - post_template: "%{replace_paragraph}\n\nUse the following paragraphs for a longer description, as well as to establish any category guidelines or rules.\n\nSome things to consider in any discussion replies below:\n\n- What is this category for? Why should people select this category for their topic?\n\n- How is this different than the other categories we already have?\n\n- Do we need this category?\n\n- Should we merge this with another category, or split it into more categories?\n" errors: uncategorized_parent: "Uncategorized can't have a parent category" self_parent: "A subcategory's parent cannot be itself" diff --git a/config/locales/server.cs.yml b/config/locales/server.cs.yml index 450f54f9cb..a92ac335a9 100644 --- a/config/locales/server.cs.yml +++ b/config/locales/server.cs.yml @@ -225,8 +225,6 @@ cs: title: "Vítejte v Redakci" category: topic_prefix: "Definice kategorie pro %{category}" - replace_paragraph: "[Nahraďte tento první odstavec krátkých popisem nové kategorie. Zkuste se vejít do 200 znaků.]" - post_template: "%{replace_paragraph}\\\n\\\nPoužijte toto místo níže pro delší popis a stanovení pravidel diskuze!\n" errors: uncategorized_parent: "„Bez kategorie“ nemůže mít nadřazenou kategorii" self_parent: "Nadřazená kategorie nemůže zároveň být podkategorie" diff --git a/config/locales/server.da.yml b/config/locales/server.da.yml index 60cb7ee897..5fab30b84c 100644 --- a/config/locales/server.da.yml +++ b/config/locales/server.da.yml @@ -246,8 +246,6 @@ da: title: "Velkommen i loungen" category: topic_prefix: "Kategoridefinition for %{category}" - replace_paragraph: "[Erstat det første afsnit med en kort beskrivelse af din nye kategori. Denne tekst vises i området til valg af kategori, så prøv at holde den på under 200 tegn. Indtil du redigerer denne tekst eller opretter emner vil denne kategori ikke optræde på kategorisiden.]" - post_template: "%{replace_paragraph}\n\nBrug de følgende afsnit til en længere beskrivelse samt til at fastlægge retningslinjer eller regler for kategorien.\n\nNogle ting som du kan overveje i svarene nedenfor:\n\n- Hvad er kategorien beregnet til? Hvorfor skal folk vælge denne kategori til deres emne?\n\n- Hvordan er den anderledes end de andre kategorier vi allerede har?\n\n- Har vi behov for denne kategori?\n\n- Skal vi kombinere denne kategori med en anden kategori eller dele den op i flere kategorier?\n" errors: uncategorized_parent: "Ukategoriserede kan ikke have en overordnede kategori" self_parent: "En underkategori kan ikke være sin egen overordnede kategori." diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml index d7c006626e..37d8c25600 100644 --- a/config/locales/server.de.yml +++ b/config/locales/server.de.yml @@ -264,8 +264,6 @@ de: body: "\nGratuliere! :confetti_ball:\n\nWenn du dieses Thema sehen kannst, wurdest du vor Kurzem zum **Stammgast** (Vertrauensstufe 3) befördert.\n \nDu kannst nun …\n\n* den Titel eines jeden Themas ändern\n* Themen in andere Kategorien verschieben\n* Links veröffentlichen, die von Suchmaschinen weiterverfolgt werden (das automatische [nofollow](http://de.wikipedia.org/wiki/Nofollow) wird entfernt)\n* auf die private Lounge-Kategorie zugreifen, die für Benutzer mit Vertrauensstufe 3 oder höher sichtbar ist\n\nHier ist die [aktuelle Liste aller Stammgäste](/badges/3/regular). Vergiss nicht, hallo zu sagen!\n\nVielen Dank dafür, dass du ein wichtiger Teil dieser Community bist!\n\n(Wenn du mehr über Vertrauensstufen wissen möchtest, kannst du [dieses Thema lesen][trust]. Beachte bitte, dass nur jene Mitglieder Stammgäste bleiben, die auch im Laufe der Zeit die Anforderungen erfüllen.)\n\n[trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924\n" category: topic_prefix: "Über die Kategorie %{category}" - replace_paragraph: "[Ersetze den ersten Absatz mit einer kurzen Beschreibung deiner neuen Kategorie. Bitte benutze weniger als 200 Zeichen.]" - post_template: "%{replace_paragraph}\n\nBenutze den folgenden Platz für eine ausführliche Beschreibung sowie für Regeln und zur Diskussion!" errors: uncategorized_parent: "Die \"unkategorisiert\" Kategorie kann keine Elternkategorie haben." self_parent: "Eine Kategorie kann nicht sich selbst untergeordnet werden." @@ -1009,6 +1007,7 @@ de: embed_username_key_from_feed: "Schlüsse, um Discourse-Benutzernamen aus Feed zu ziehen." embed_truncate: "Kürze die eingebetteten Beiträge" embed_post_limit: "Maximale Anzahl der Beiträge die eingebettet werden." + embed_username_required: "Der Benutzername ist für die Betragserstellung notwendig" embed_whitelist_selector: "CSS Selektor für Elemente, die in Einbettungen erlaubt sind." embed_blacklist_selector: "CSS Selektor für Elemente, die in Einbettungen entfernt werden." notify_about_flags_after: "Wenn es Markierungen gibt, die nicht nach dieser Anzahl von Stunden behandelt wurden, sende eine E-Mail an contact_email. Setze dies auf 0 um es zu deaktivieren." diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml index b6b395d70d..d5938254c5 100644 --- a/config/locales/server.es.yml +++ b/config/locales/server.es.yml @@ -275,8 +275,6 @@ es: [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Definición de la categoría %{category}" - replace_paragraph: "[Reemplaza el primer párrafo con una descripción corta de tu nueva categoría. Esta guía aparecerá en el área de selección de categoría, por lo que trata de mantenerla por debajo de los 200 caracteres.]" - post_template: "%{replace_paragraph}\n\nUtiliza los siguientes párrafos para dar una descripción más larga y también para establecer cualquier norma o recomendación para esta categoría.\n\nAlgunos aspectos a tener en consideración en cualquier respuesta:\n\n- ¿Para que es esta categoría? ¿Por qué deben que seleccionar esta categoría como tema?\n\n- ¿Qué diferencia esta categoría de las demas?\n\n- ¿Es necesaria esta categoría?\n\n- ¿Deberíamos unir esta categoría con otra o dividirla en varias?\n" errors: uncategorized_parent: "Sin categoría no puede tener categoría primaria" self_parent: "La categoría primaria de una subcategoría no puede ser ella misma." diff --git a/config/locales/server.fa_IR.yml b/config/locales/server.fa_IR.yml index 63f60e8c16..81ffdca4d6 100644 --- a/config/locales/server.fa_IR.yml +++ b/config/locales/server.fa_IR.yml @@ -252,8 +252,6 @@ fa_IR: [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "در رابطه با دسته بندی {category}% " - replace_paragraph: "[به جای پاراگراف اول توضیحات کوتاهی درباره دسته جدیدتون قرار بدید. این راهنما در قسمت دسته ها نمایش داده می شود٬ پس سعی کن زیر 200 کلمه اون را نگاه دارید. قبل از اینکه این متن را ویرایش کنید یا جستار جدیدی بوجود بیاورید٬ این دسته در صفحه دسته ها نمایش داده نمی شود.]" - post_template: "%{replace_paragraph}\n\nUse the following paragraphs for a longer description, as well as to establish any category guidelines or rules.\n\nSome things to consider in any discussion replies below:\n\n- What is this category for? Why should people select this category for their topic?\n\n- How is this different than the other categories we already have?\n\n- Do we need this category?\n\n- Should we merge this with another category, or split it into more categories?\n" errors: uncategorized_parent: "بدون دسته بندی نمی تواند طبقه خانواده داشته باشد" self_parent: "والد زیر شاخه نمی تواند خودش باشد." diff --git a/config/locales/server.fi.yml b/config/locales/server.fi.yml index 4763891356..2e5d504f26 100644 --- a/config/locales/server.fi.yml +++ b/config/locales/server.fi.yml @@ -284,8 +284,6 @@ fi: [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Alueesta %{category}" - replace_paragraph: "[Korvaa tämä kappale lyhyellä kuvauksella uudesta alueesta. Tämä kuvaus näytetään alueen valinnan yhteydessä, joten yritä pitää se alle 200 merkin pituisenä. Tätä aluetta ei näytetä listauksissa ennen kuin olet muokannut tätä tekstiä tai luonut viestiketjuja.]" - post_template: "%{replace_paragraph}\n\nKäytä seuraavat kappaleet pidempään kuvaukseen ja selvittääksesi alueen säännöt ja ohjeet.\n\nJotain huomioitavia asioita alle muodostuvaa keskustelua ajatellen:\n\n- Mitä varten tämä alue on? Miksi ihmiset valitsisivat tämän alueen viestiketjulleen?\n\n- Kuinka se eroaa muista alueista?\n\n- Tarvitaanko tätä aluetta?\n\n- Pitäisikö alue yhdistää toisen alueen kanssa, tai jakaa useammaksi alueeksi?\n" errors: uncategorized_parent: "Alueettomia ei voi asettaa toisen alueen alle" self_parent: "Alue ei voi olla itsensä ylempi alue." diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml index a3aa8d836c..31153a05cc 100644 --- a/config/locales/server.fr.yml +++ b/config/locales/server.fr.yml @@ -71,6 +71,7 @@ fr: max_username_length_exists: "Il n'est pas possible de définir une longeur maximale de pseudo qui soit plus court qu'un pseudo qui existe déjà." max_username_length_range: "Vous ne pouvez pas mettre le maximum sous le minimum" default_categories_already_selected: "Vous ne pouvez pas séléctionner une catégorie qui est utilisée dans une autre liste." + s3_upload_bucket_is_required: "Vous ne pouvez pas activer l'upload sur S3 avant d'avoir renseigné le 's3_upload_bucket'." bulk_invite: file_should_be_csv: "Le fichier envoyé doit être au format csv ou txt." backup: @@ -278,8 +279,7 @@ fr: [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "A propos de la catégorie %{category}" - replace_paragraph: "[Remplacez ce premier paragraphe par une courte description de votre nouvelle catégorie. Ce texte apparaît dans la zone de sélection de catégorie, alors essayez de le maintenir en dessous de 200 caractères. Cette catégorie n'apparaîtra pas sur la page des catégories tant que vous modifiez ce texte ou jusqu'à ce qu'un sujet soit crée.]" - post_template: "%{replace_paragraph}\n\nUtilisez les paragraphes suivants pour une plus longue description, voire pour établir des règles. \n\nChoses à considérer avant toute réponse ou discussion :⏎\n\n- A quoi sert cette catégorie ? Pourquoi les utilisateurs choisiraient-ils cette catégorie pour leur discussion ?\n\n- En quoi elle est différente des autres catégories que nous avons déjà ?\n\n- Avons-nous besoin de cette catégorie ?\n\n- Devrions-nous fusionner celle-ci avec une autre catégorie, ou la diviser en plusieurs catégories ?\n" + replace_paragraph: "(Remplacez ce premier paragraphe par une brève description de votre nouvelle catégorie. Ce guide apparaîtra dans la zone de sélection de la catégorie, alors essayez de rester en dessous de 200 caractères. **Cette catégorie n'apparaîtra pas sur la page des catégories jusqu'à ce que vous ayez modifié cette description ou créé des sujets.**" errors: uncategorized_parent: "Sans catégorie ne peut pas avoir de parent" self_parent: "Le parent d'une sous-catégorie ne peut pas être elle-même" @@ -305,7 +305,18 @@ fr: title: "meneur" change_failed_explanation: "Vous avez essayé de rétrograder %{user_name} au niveau '%{new_trust_level}'. Cependant son niveau de confiance est déjà '%{current_trust_level}'. %{user_name} restera au niveau '%{current_trust_level}' - Si vous souhaitez rétrograder un utilisateur vous devez verrouiller le niveau de confiance au préalable" rate_limiter: + slow_down: "Vous avez réalisé cette action un trop grand nombre de fois, essayez plus tard." too_many_requests: "Nous avons une limite journalière du nombre d'actions qui peuvent être effectuées. Veuillez patienter %{time_left} avant de recommencer." + by_type: + first_day_replies_per_day: "Vous avez atteint le nombre maximum de réponses qu'un nouvel utilisateur peut créer pour son premier jour. Patientez s'il vous plaît %{time_left} avant d'essayer à nouveau." + first_day_topics_per_day: "Vous avez atteint le nombre maximum de sujets qu'un nouvel utilisateur peut créer pour son premier jour. Patientez s'il vous plaît %{time_left} avant d'essayer à nouveau." + create_topic: "Vous créez des sujets trop rapidement. Patientez s'il vous plaît %{time_left} avant d'essayer à nouveau." + create_post: "Vous répondez trop rapidement. Patientez s'il vous plaît %{time_left} avant d'essayer à nouveau." + topics_per_day: "Vous avez atteint le nombre maximum de nouveaux sujets pour aujourd'hui. Patientez s'il vous plaît %{time_left} avant d'essayer à nouveau." + pms_per_day: "Vous avez atteint le nombre maximum de nouveaux messages pour aujourd'hui. Patientez s'il vous plaît %{time_left} avant d'essayer à nouveau." + create_like: "Vous avez atteint le nombre maximum de J'aime pour aujourd'hui. Patientez s'il vous plaît %{time_left} avant d'essayer à nouveau." + create_bookmark: "Vous avez atteint le nombre maximum de favoris pour aujourd'hui. Patientez s'il vous plaît %{time_left} avant d'essayer à nouveau." + edit_post: "Vous avez atteint le nombre maximum de modifications pour aujourd'hui. Patientez s'il vous plaît %{time_left} avant d'essayer à nouveau." hours: one: "1 heure" other: "%{count} heures" @@ -494,6 +505,10 @@ fr: title: "Nouveaux utilisateurs" xaxis: "Jour" yaxis: "Nombre de nouveaux utilisateurs" + profile_views: + title: "Vues du Profil Utilisateur" + xaxis: "Jour" + yaxis: "Nombre de profils utilisateurs consultés" topics: title: "Sujets" xaxis: "Jour" diff --git a/config/locales/server.he.yml b/config/locales/server.he.yml index 8fa6c28cd4..cf7178f0a3 100644 --- a/config/locales/server.he.yml +++ b/config/locales/server.he.yml @@ -263,8 +263,6 @@ he: [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "אודות הקטגוריה %{category}" - replace_paragraph: "[החלף את הפסקה הראשונה הזו עם תיאור קצר של הקטגוריה החדש שלך. התיאור הזה יופיע באיזור בחירת הקטגוריה, כך שיש לנסות לשמור עליו באורך פחות מ-200 תויים. עד שתעריך את המלל הזה או תיצור נושאים, הקטגוריה הזו לא תופיע בעמוד הקטגוריות.]" - post_template: "%{replace_paragraph}\n\nהשתמש בפסקאות הבאו עבור תיאור אורך יותר, כמו גם כדי להחיל כללים או חוקים על הקטגוריה.\n\nחלק מהנושאים שיש לשקול בכל דיון:\n\n- בשביל מה הקטגוריה הזו? למה שאנשים יבחרו בה בשביל הנושא שלהם?\n\n- איך היא שונה מקטגוריות אחרות שכבר יש לנו?\n\n- אנחנו צריכים את הקטגוריה הזו?\n\n- אולי כדאי לאחד אותה עם קטגוריה אחרת, או לפצל אותה לכמה קטגוריות?\n" errors: uncategorized_parent: "נטול קטגוריה לא יכול להיות עם קטגוריית אם" self_parent: "קטגוריית אם לא יכולה להיות הקטגוריה עצמה" diff --git a/config/locales/server.id.yml b/config/locales/server.id.yml index 19c8c01db5..9988698026 100644 --- a/config/locales/server.id.yml +++ b/config/locales/server.id.yml @@ -74,7 +74,6 @@ id: no_info_other: "
      %{name} belum mengisi kolom Tentang Saya untuk profilnya
      " category: topic_prefix: "Tentang kategori %{category}" - replace_paragraph: "[Ganti paragraf pertama ini dengan deskripsi singkat mengenai kategori baru Anda. Panduan ini akan muncul dalam area pemilihan kategori, cobalah untuk membuatnya kurang dari 200 karakter. Kategori ini tidak akan muncul dalam halaman kategori hingga Anda mengubah teks ini atau membuat topik.]" trust_levels: newuser: title: "pengguna baru" diff --git a/config/locales/server.it.yml b/config/locales/server.it.yml index a447956326..049cdb37a1 100644 --- a/config/locales/server.it.yml +++ b/config/locales/server.it.yml @@ -241,8 +241,8 @@ it: body: "\nCongratulazioni! :confetti_ball: \n\nSe vedi questo argomento, sei stato recentemente promosso a **esperto** (livello di esperienza 3). \n\nAdesso puoi … \n\n* Modificare il titolo di qualunque argomento \n* Cambiare la categoria di qualunque argomento \n* Seguire tutti i tuoi collegamenti (è rimosso il [nofollow automatico](http://it.wikipedia.org/wiki/Nofollow) ) \n* Accedere alla categoria privata Lounge visibile solo agli utenti con livello di esperienza 3 o superiore \n* Cancellare i messaggi spam con un singolo clic\n\nEcco l'elenco degli altri [utenti esperti](/badges/3/regular). Vai a salutarli. \n\nGrazie per essere una parte importante di questa comunità! \n\n(Per maggiori informazioni sui livelli di esperienza, [leggi questa discussione][trust]. Nota che possono rimanere utenti esperti solo quegli utenti che continuano a soddisfare i criteri di selezione)\n\n[trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924\n" category: topic_prefix: "Definizione della categoria %{category}" - replace_paragraph: "[Sostituisci questo primo paragrafo con una breve descrizione della nuova categoria. Questa guida apparirà nell'area di selezione della categoria, perciò cerca di stare sotto i 200 caratteri. Finché non modifichi questo testo o non crei argomenti, questa categoria non apparirà sulla pagina delle categorie.]" - post_template: "%{replace_paragraph}\n\nUsa i paragrafi seguenti per una descrizione più estesa, ed anche per stabilire delle regole per la categoria o delle linee guida.\n\nAlcune cose da considerare in qualunque discussione sono:\n\n- A che serve questa categoria? Perché la gente dovrebbe scegliere questa categoria per i loro argomenti?\n\n- In cosa questa categoria è diversa dalle altre categorie già esistenti?\n\n- C'è bisogno di questa categoria?\n\n- Dovremmo fondere questa categoria con un'altra, oppure dividerla in più categorie?\n" + replace_paragraph: "(Sostituisci questo primo paragrafo con una breve descrizione della nuova categoria. Questa guida apparirà nell'area di selezione della categoria, perciò cerca di stare sotto i 200 caratteri. **Finché non modifichi questo testo o non crei argomenti, questa categoria non apparirà sulla pagina delle categorie.**)" + post_template: "%{replace_paragraph}\n\nUsa i seguenti paragrafi per una descrizione più estesa, ed anche per stabilire delle regole o linee guida per la categoria:\n\n- Perché la gente dovrebbe usare questa categoria? A che serve? \n\n- In cosa esattamente questa categoria è diversa dalle altre categorie già esistenti? \n\n- Cosa dovrebbero contenere gli argomenti in questa categoria? \n\n- C'è bisogno di questa categoria? Possiamo fonderla con un'altra categoria o sottocategoria?\n" errors: uncategorized_parent: "La categoria \"Non classificato\" non può avere una categoria superiore." self_parent: "La categoria-genitore di una sottocategoria non può essere se stessa." @@ -1254,7 +1254,7 @@ it: %{base_url}/users/authorize-email/%{email_token} signup_after_approval: subject_template: "Sei stato ammesso su %{site_name}!" - text_body_template: "Benvenuto su %{site_name}! \n\nUn membro dello staff ha approvato il tuo account su %{site_name}. \n\nClicca sul seguente collegamento per confermare e attivare il tuo nuovo account:\n%{base_url}/users/activate-account/%{email_token} \n\nSe il collegamento qui sopra non è cliccabile, prova a copiarlo e incollarlo nella barra degli indirizzi del tuo browser. \n\n%{new_user_tips}\n\nNoi crediamo fermamente in un [comportamento comunitario civile](%{base_url}/guidelines) sempre.\n\nBuona permanenza!\n\n(Se hai bisogno di comunicare privatamente con i [membri dello staff](%{base_url}/about) come nuovo utente, rispondi semplicemente a questo messaggio privato.)\n" + text_body_template: "Benvenuto su %{site_name}! \n\nUn membro dello staff ha approvato il tuo account su %{site_name}. \n\nClicca sul seguente collegamento per confermare e attivare il tuo nuovo account:\n%{base_url}/users/activate-account/%{email_token} \n\nSe il collegamento qui sopra non è cliccabile, prova a copiarlo e incollarlo nella barra degli indirizzi del tuo browser. \n\n%{new_user_tips}\n\nNoi crediamo in un [comportamento comunitario civile](%{base_url}/guidelines), sempre.\n\nBuona permanenza!\n\nps: se vuoi comunicare privatamente con i [membri dello staff](%{base_url}/about) come nuovo utente, rispondi semplicemente a questo messaggio.\n" signup: text_body_template: | Benvenuto su %{site_name}! diff --git a/config/locales/server.ja.yml b/config/locales/server.ja.yml index cd804206a3..b0edc53441 100644 --- a/config/locales/server.ja.yml +++ b/config/locales/server.ja.yml @@ -229,8 +229,6 @@ ja: title: "ラウンジへようこそ" category: topic_prefix: "%{category} カテゴリの定義" - replace_paragraph: "[この最初のパラグラフを、本カテゴリの簡単な紹介文に置き換えてください。このガイダンスはカテゴリ選択エリアに表示されるため、パラグラフは200文字以内にしてください。]" - post_template: "%{replace_paragraph}\n\nこれ以降のパラグラフに、カテゴリのガイドライン等を書いてください。\n\nいくつかのヒント:\n\n- このカテゴリは何か? どのようなトピックをこのカテゴリに入れるべきか?\n\n- 既に存在するカテゴリとの違いは何か?\n\n- このカテゴリはなぜ必要か?\n\n- 他のカテゴリとの統合や、複数カテゴリへの分割をするべきか?\n" errors: uncategorized_parent: "未分類カテゴリは親カテゴリに設定出来ません。" self_parent: "自分自身がサブカテゴリの親になることはできません" diff --git a/config/locales/server.ko.yml b/config/locales/server.ko.yml index 88b59e5857..17e01ef910 100644 --- a/config/locales/server.ko.yml +++ b/config/locales/server.ko.yml @@ -242,8 +242,6 @@ ko: 축하합니다! :confetti_ball: 당신의 회원등급이 **지도자** (회원등급 3등급)로 올라갔기 때문에 이 글타래를 볼 수 있게 되었습니다. 이제부터 아래의 행동들을 할 수 있습니다 … * 글타래를 제목을 수정할 수 있습니다 * 글타래의 카테고리를 수정할 수 있습니다 * 링크가 follow로 처리됩니다 ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) 가 제거됩니다) * 회원등급 3 이상인 사용자가 접근할 수 있는 비공개 카테고리인 라운지에 접근할 수 있습니다 * 좋아요와 신고의 가중치가 높아집니다 [정규 멤버 목록](/badges/3/regular)을 확인할 수 있습니다. 인사하는 것을 잊지마세요. 이 커뮤니티의 중요한 역할을 해주신 것에 감사드립니다! (회원등급에 관한 더 자세한 정보를 보시려면, [이 글타래를 확인하세요][trust]. 기준에 맞는 사람만 정규 회원으로 자격이 유지됩니다.) [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "'%{category}' 카테고리의 설명" - replace_paragraph: "[이 첫번째 문단을 당신의 새로운 카테고리의 짧은 설명으로 바꿔주세요. 이 지침은 카테고리 선택 화면에 나타날 것입니다. 200자 이하로 적어주세요.]" - post_template: "%{replace_paragraph}\n\n이 공간은 긴 설명을 사용하여 주세요. 또한 카테고리 가이드라인이나 룰도 이 공간을 이용해주세요.\n\n몇 가지 답글에 관련하여 고려해봐야 할 것들:\n\n- 무엇을 위한 카테고리인가? 왜 사용자들이 이 카테고리를 필요로 하겠는가?\n\n- 이미 가지고있는 다른 카테고리와 어떻게 다른가?\n\n- 정말 이 카테고리가 필요한가?\n\n- 다른 카테고리와 합병이 필요한가 아니면 더욱 자세한 카테고리로 나눌 필요가 있는가?\n" errors: uncategorized_parent: "Uncategorized 카테고리는 부모 카테고리를 가질 수 없습니다." self_parent: "하위 카테고리의 부모는 자신이 될 수 없습니다." diff --git a/config/locales/server.nb_NO.yml b/config/locales/server.nb_NO.yml index a135e49860..cdf8fe3518 100644 --- a/config/locales/server.nb_NO.yml +++ b/config/locales/server.nb_NO.yml @@ -259,7 +259,6 @@ nb_NO: [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Om kategorien %{category} " - replace_paragraph: "[Erstatt dette første avsnittet med en kort beskrivelse av din nye kategori. Denne veiledningen vil vises i kategorivelgeren så prøv å hold den kortere enn 200 tegn. Frem til du redigerer denne teksten eller oppretter emner vil ikke denne kategorien vises på kategorisiden.]" errors: uncategorized_parent: "Ukategorisert kan ikke ha en foreldrekategori" self_parent: "En underkategori kan ikke være underlagt seg selv" diff --git a/config/locales/server.nl.yml b/config/locales/server.nl.yml index 826bdd84b6..38f7b7ee1e 100644 --- a/config/locales/server.nl.yml +++ b/config/locales/server.nl.yml @@ -277,8 +277,6 @@ nl: [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Over de categorie %{category}" - replace_paragraph: "[Vervang deze eerste regel met een korte omschrijving van je nieuwe categorie. Deze regel verschijnt in het selectiemenu als iemand een categorie kiest, dus hou het kort (max. 200 tekens). Deze categorie zal niet in de lijst verschijnen totdat je deze tekst wijzigt of zelf een topic in de categorie plaatst.]" - post_template: "%{replace_paragraph}\n\nGebruik de volgende alinea's voor een lange omschrijving en om wat verwachtingen en regels van deze categorie uit te leggen.\n\nZaken waar je het over kan hebben in de reacties hieronder:\n\n- Waar is deze categorie voor? Waarom zouden mensen deze categorie moeten kiezen voor hun topic?\n\n- Waarin verschilt deze categorie van de andere categorien die we al hebben?\n\n- Hebben we deze categorie echt nodig?\n\n- Moeten we deze categorie samenvoegen met een andere categorie, of juist opsplitsen?\n" errors: uncategorized_parent: "Ongecategoriseerd kan geen bovenliggende categorie hebben" self_parent: "Een categorie kan niet zijn eigen bovenliggende categorie zijn" diff --git a/config/locales/server.pl_PL.yml b/config/locales/server.pl_PL.yml index 4703b50241..0a736ac601 100644 --- a/config/locales/server.pl_PL.yml +++ b/config/locales/server.pl_PL.yml @@ -274,8 +274,6 @@ pl_PL: [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "O kategorii %{category}" - replace_paragraph: "[Zamień ten pierwszy paragraf na krórki opis nowej kategorii. Opis ten będzie wyświetlany na karcie wyboru kategorii, więc postaraj się utrzymać go poniżej 200 znaków. Póki nie zmienisz tego tekstu, ani nie utworzysz tematu, kategoria nie będzie wyświetlana na liście kategorii.]" - post_template: "%{replace_paragraph}\n\nW kolejnych paragrafach możesz umieścić dłuższy opis, oraz opisać zasady i zwyczaje jakie będą obowiązywać w tej kategorii. \n\nKilka rzeczy do rozważenia:\n\n- Czemu ma służyć ta kategoria? Dlaczego inni powinni wybierać tę kategorię dla swoich tematów?\n\n- Czym się różni od innych kategorii, które już mamy?\n\n- Czy potrzebujemy tej kategorii?\n\n- Czy powinniśmy ją połączyć z jakąś inną kategorią, lub podzielić na więcej kategorii?\n" errors: uncategorized_parent: "Inne nie mogą być podkategorią innej kategorii." self_parent: "Podkategoria nie może być swoim rodzicem" diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml index 90e0704ca4..3f3468ded9 100644 --- a/config/locales/server.pt.yml +++ b/config/locales/server.pt.yml @@ -287,8 +287,8 @@ pt: category: topic_prefix: "Acerca da categoria %{category}" - replace_paragraph: "[Substituir este primeiro parágrafo, com uma breve descrição de sua nova categoria. Esta orientação será exibida na área de seleção de categoria, por isso tente mantê-la abaixo de 200 caracteres. Até editar este texto ou criar tópicos, esta categoria não aparece na página de categorias.]" - post_template: "%{replace_paragraph}\n\nUse os seguintes parágrados para uma descrição longa, assim como para estabelecer diretrizes ou regras da categoria.\n\nAlguns pontos a ter em consideração em qualquer resposta a uma discussão:\n\n- Para que serve esta categoria? Porque alguém usaria esta categoria no seu tópico?\n\n- Como é que esta categoria se difere das demais categorias já existentes?\n\n- Precisamos desta categoria?\n\n- Deveríamos juntar esta categoria com outra, ou dividir esta categoria em mais categorias?\n" + replace_paragraph: "(Substitua este primeiro parágrafo com uma breve descrição da sua nova categoria. Este guia irá aparecer na área de seleção da categoria, por isso tente mantê-lo abaixo dos 200 caracteres. **Até editar esta descrição ou criar tópicos, esta categoria não irá aparecer na página das categorias.**)" + post_template: "%{replace_paragraph}\n\nUtilize os seguintes parágrafos para uma descrição mais longa, ou para estabelecer diretrizes ou regras da categoria:\n\n- Porque devem as pessoas utilizar esta categoria? Para que serve?\n\n- No que difere ao certo em relação às outras categorias que já temos?\n\n- O que devem conter, de maneira geral, os tópicos desta categoria?\n\n- Precisamos desta categoria? Podemos juntá-la com outra categoria, ou subcategoria?\n" errors: uncategorized_parent: "Sem categoria não podem ter categorias de nível superior" self_parent: "Uma subcategoria não pode ser superior a ela própria" diff --git a/config/locales/server.pt_BR.yml b/config/locales/server.pt_BR.yml index 9b94b77579..41efb66029 100644 --- a/config/locales/server.pt_BR.yml +++ b/config/locales/server.pt_BR.yml @@ -266,8 +266,6 @@ pt_BR: [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Sobre a categoria %{category} " - replace_paragraph: "[Substitua este primeiro parágrafo com uma breve descrição de sua nova categoria. Esta orientação será exibida na área de seleção de categoria, assim, tente mantê-lo abaixo de 200 caracteres. Até que você edite o texto ou crie tópicos, esta categoria não aparecerá na página de categorias.]" - post_template: "%{replace_paragraph}\n\nUse os parágrafos a seguir para uma descrição longa, assim como para estabelecer diretrizes ou regras da categoria.\n\nAlgumas coisas para se considerar em qualquer resposta de discussão:\n\n- Para que serve esta categoria? Porque algum usaria esta categoria para seu tópico?\n\n- Como esta categoria se difere das demais categorias já existentes?\n\n- Nós precisamos desta categoria?\n\n- Deveríamos mesclar esta categoria com outra, ou dividir esta categoria em mais categorias?\n" errors: uncategorized_parent: "Sem categoria não pode ter uma categoria pai" self_parent: "A subcategoria mãe não pode ser ela mesma" diff --git a/config/locales/server.ro.yml b/config/locales/server.ro.yml index e132b92755..52587248a2 100644 --- a/config/locales/server.ro.yml +++ b/config/locales/server.ro.yml @@ -247,8 +247,6 @@ ro: [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Despre categoria %{category}" - replace_paragraph: "[Înlocuiește acest paragraf cu o scurtă descriere despre categorie. Acest îndemn va apărea în aria de selectare a categoriei, deci încercați să aibă sub 200 caractere. Pană nu editați acest text sau creați o nouă discuție, Această categorie nu v-a apărea în pagina de categorii.]" - post_template: "%{replace_paragraph}\n\n Folosește următoarele paragrafe pentru o descriere mai lungă, cât și pentru a stabili orice regulă de bază sau de ajutor a categoriei.\n\n Câteva lucruri de avut în vedere în orice discuție mai jos: \n\n- Pentru ce este această categorie? De ce ar selecta alte persoane această categorie pentru discuțiile lor?\n\n- În ce fel este diferită față de celălalte existente?\n\n- Avem nevoie de această categorie?\n\n- Ar trebuii unită cu o altă categorie, sau divizată în alte 2?\n" errors: uncategorized_parent: "ce nu este categorisit nu poate avea categorie părinte" self_parent: "Părintele unei subcategorii nu poate fi ea însăși" diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml index 627a8db3ee..e413f26753 100644 --- a/config/locales/server.ru.yml +++ b/config/locales/server.ru.yml @@ -279,8 +279,6 @@ ru: \ до тех пор пока вы придерживаетесь этих правил.)\n\n[trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924\n" category: topic_prefix: "Описание раздела %{category}" - replace_paragraph: "[Замените данный текст кратким описанием нового раздела. Это описание будет отображаться в списке разделов, поэтому постарайтесь сделать его коротким (не более 200 символов).]" - post_template: "%{replace_paragraph}\n\nВ следующих абзацах введите более подробное описание раздела, а также возможные правила опубликования тем в нем.\n\nНесколько аспектов, которые следует учитывать:\n\n- Для чего нужен этот раздел? Почему люди выберут этот раздел для размещения своей темы?\n\n- Чем данные раздел отличается от тех, которые у нас уже есть?\n\n- Нужен ли нам этот раздел?\n\n- Стоит ли нам объединить его с другим разделом или разбить на несколько?\n" errors: uncategorized_parent: "Разделу для тем вне разделов нельзя назначать родительский раздел." self_parent: "Подраздел не может быть родительским для самого себя." diff --git a/config/locales/server.sq.yml b/config/locales/server.sq.yml index 1fba2bbd68..6a7c993a2a 100644 --- a/config/locales/server.sq.yml +++ b/config/locales/server.sq.yml @@ -277,8 +277,6 @@ sq: [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Rreth kategorisë %{category}" - replace_paragraph: "[Replace this first paragraph with a short description of your new category. This guidance will appear in the category selection area, so try to keep it below 200 characters. Until you edit this text or create topics, this category won't appear on the categories page.]" - post_template: "%{replace_paragraph}\n\nUse the following paragraphs for a longer description, as well as to establish any category guidelines or rules.\n\nSome things to consider in any discussion replies below:\n\n- What is this category for? Why should people select this category for their topic?\n\n- How is this different than the other categories we already have?\n\n- Do we need this category?\n\n- Should we merge this with another category, or split it into more categories?\n" errors: uncategorized_parent: "Uncategorized can't have a parent category" self_parent: "A subcategory's parent cannot be itself" diff --git a/config/locales/server.sv.yml b/config/locales/server.sv.yml index f7b517db82..ecaeb5d747 100644 --- a/config/locales/server.sv.yml +++ b/config/locales/server.sv.yml @@ -244,7 +244,6 @@ sv: title: "Välkommen till loungen" category: topic_prefix: "Om kategorin %{category}" - post_template: "%{replace_paragraph}\n\nUse the following paragraphs for a longer description, as well as to establish any category guidelines or rules.\n\nSome things to consider in any discussion replies below:\n\n- What is this category for? Why should people select this category for their topic?\n\n- How is this different than the other categories we already have?\n\n- Do we need this category?\n\n- Should we merge this with another category, or split it into more categories?\n" errors: uncategorized_parent: "Kategorin \"Okategoriserat\" kan inte ha en föräldrar kategori." self_parent: "En underkategori kan inte ha sig själv som förälder." diff --git a/config/locales/server.tr_TR.yml b/config/locales/server.tr_TR.yml index e93a22854e..3f059523ab 100644 --- a/config/locales/server.tr_TR.yml +++ b/config/locales/server.tr_TR.yml @@ -249,8 +249,6 @@ tr_TR: [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "%{category} kategorisi hakkında " - replace_paragraph: "[Bu paragrafın yerine kategorinizin kısa bir açıklamasını girin. Buraya yazılanlar kategori seçim alanında görüneceği için, açıklamanızı 200 karakterin altında tutmaya çalışın. Siz bu metni düzenleyene veya herhangi bir konu yaratana kadar bu kategori, kategoriler sayfasında gözükmeyecek.]" - post_template: "%{replace_paragraph}\n\nDaha uzun bir açıklama, kategori yönergeleri veya kuralları oluşturmak için aşağıdaki paragrafları kullanın.\n\nHerhangi bir tartışma için düşünülebilecek bazı maddeler:\n\n- Bu kategorinin amacı ne? İnsanlar konu oluştururken neden bu kategoriyi seçmeliler?\n\n- Bu kategorinin sahip olduğumuz diğer mevcut kategorilerden farkı ne?\n\n- Bu kategoriye ihtiyacımız var mı?\n\n- Bunu başka bir kategoriyle birleştirmeli miyiz, yoksa daha fazla kategoriye mi bölmeli miyiz?\n" errors: uncategorized_parent: "Kategorisizin üst kategorisi olamaz" self_parent: "Alt kategorinin üst kategorisi kendisi olamaz" @@ -439,6 +437,8 @@ tr_TR: title: "Yeni Kullanıcılar" xaxis: "Gün" yaxis: "Yeni kullanıcı sayısı" + profile_views: + xaxis: "Gün" topics: title: "Konular" xaxis: "Gün" diff --git a/config/locales/server.uk.yml b/config/locales/server.uk.yml index b187edd567..63111f3a9e 100644 --- a/config/locales/server.uk.yml +++ b/config/locales/server.uk.yml @@ -115,8 +115,6 @@ uk: staff_category_description: "Закрита категорія для обговорень між персоналом. Теми бачать тільки адміністратори та модератори." category: topic_prefix: "Про категорію %{category}" - replace_paragraph: "[Замініть цей перший абзац коротким описом Вашої нової категорії. Він з'являтиметься в області вибору категорій, тому намагайтеся, щоб він був коротшим за 200 символів. Поки Ви не відредагуєте цей текст або не створите тем, ця категорія не з'явиться на сторінці категорій.]" - post_template: "%{replace_paragraph}\n\nВикористовуйте наступні абзаци для довшого опису, а також для того, щоб встановити якісь правила категорії.\n\nДеякі речі, які можна обговорити у відповідях на цей допис:\n\n- Для чого потрібна ця категорія? Чому людям слід обирати цю категорію для своїх тем?\n\n- Чим вона відрізняється від інших категорій, що в нас є?\n\n- Чи потрібна нам ця категорія?\n\n- Чи потрібно об'єднати цю категорію з якоюсь іншою, або розбити її на декілька?\n" errors: depth: "Ви не можете вкладати підкатегорію під іншу" trust_levels: diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml index 46a0b84016..a20318d4b8 100644 --- a/config/locales/server.zh_CN.yml +++ b/config/locales/server.zh_CN.yml @@ -276,8 +276,6 @@ zh_CN: [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "关于分类:%{category}" - replace_paragraph: "[用一段简短的话描述分类,并替换这第一段的内容。这段文字将出现在分类选择区域,所以尽量不要超过200个字符。当你编辑了这段文字或者再次分类创建了一个主题后,分类才会出现在分类列表中。]" - post_template: "%{replace_paragraph}\n\n在接下来的一段文字中输入分类的详细描述信息,可以在这里包含在此分类下讨论的规则、内容导向等等。\n\n考虑这些事情:\n\n- 这个分类用来做什么? 为什么人们选择这个分类寻找主题?\n\n- 这个分类和其他已有分类有什么不同?\n\n- 我们需要这个分类么?\n\n- 我们应该和已有分类合并吗?还是分离成更多的分类?\n" errors: uncategorized_parent: "未分类不能有一个父分类" self_parent: "一个子分类不能属于它自己。" diff --git a/config/locales/server.zh_TW.yml b/config/locales/server.zh_TW.yml index 36b8da5627..988d96ff40 100644 --- a/config/locales/server.zh_TW.yml +++ b/config/locales/server.zh_TW.yml @@ -205,8 +205,6 @@ zh_TW: title: "歡迎來到貴賓室" category: topic_prefix: "對於分類:%{category} 的定義" - replace_paragraph: "[ 將此首段文字替換為描述新分類的簡短說明。說明將會出現在分類選擇區裡,故請盡量少於 200 個字元。在你編輯此處文字或者新增討論話題之前,此分類將不會顯示在分類頁面上。]" - post_template: "%{replace_paragraph}\n\n在下面的空格輸入分類的詳細描述, 可以包括在此分類下討論的規則、內容主題等等。" errors: uncategorized_parent: "未分類不能有一個父分類" self_parent: "一個子分類的上層不能是自己" diff --git a/plugins/poll/config/locales/client.tr_TR.yml b/plugins/poll/config/locales/client.tr_TR.yml index 5074053397..4b9e75273e 100644 --- a/plugins/poll/config/locales/client.tr_TR.yml +++ b/plugins/poll/config/locales/client.tr_TR.yml @@ -15,6 +15,12 @@ tr_TR: average_rating: "Ortalama oran: %{average}." multiple: help: + at_least_min_options: + other: "En az %{count} seçim yapmalısınız." + up_to_max_options: + other: "En fazla %{count} seçim yapabilirsiniz." + x_options: + other: "%{count} seçim yapmalısınız." between_min_and_max_options: "%{min} ve %{max} seçenekleri arasında seçim yapabilirsiniz." cast-votes: title: "Oyunuzu kullanın" From e1d550305316d5120738a9238e0c4c389c169041 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Mon, 26 Oct 2015 23:14:14 +0530 Subject: [PATCH 048/114] FIX: flag button was broken for posts --- app/assets/javascripts/discourse/routes/topic.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/routes/topic.js.es6 b/app/assets/javascripts/discourse/routes/topic.js.es6 index 8bfdf13d5b..976de12166 100644 --- a/app/assets/javascripts/discourse/routes/topic.js.es6 +++ b/app/assets/javascripts/discourse/routes/topic.js.es6 @@ -43,7 +43,7 @@ const TopicRoute = Discourse.Route.extend({ showFlags(model) { showModal('flag', { model }); - this.controllerFor('flag').setProperties({ selected: null }); + this.controllerFor('flag').setProperties({ selected: null, flagTopic: false }); }, showFlagTopic(model) { From 47e25648dfa200ac266a6311ba585827638d2bcc Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Mon, 26 Oct 2015 15:56:59 -0400 Subject: [PATCH 049/114] FEATURE: Change user groups in bulk via admin --- .../controllers/admin-groups-bulk.js.es6 | 34 ++++++++++++++++++ .../admin/routes/admin-groups-bulk.js.es6 | 13 +++++++ .../admin/routes/admin-route-map.js.es6 | 2 ++ .../admin/templates/groups-bulk-complete.hbs | 1 + .../admin/templates/groups-bulk.hbs | 19 ++++++++++ .../javascripts/admin/templates/groups.hbs | 1 + .../stylesheets/common/admin/admin_base.scss | 10 ++++++ app/controllers/admin/groups_controller.rb | 36 +++++++++++++++++++ config/locales/client.en.yml | 4 +++ config/routes.rb | 3 ++ .../admin/groups_controller_spec.rb | 20 +++++++++++ 11 files changed, 143 insertions(+) create mode 100644 app/assets/javascripts/admin/controllers/admin-groups-bulk.js.es6 create mode 100644 app/assets/javascripts/admin/routes/admin-groups-bulk.js.es6 create mode 100644 app/assets/javascripts/admin/templates/groups-bulk-complete.hbs create mode 100644 app/assets/javascripts/admin/templates/groups-bulk.hbs diff --git a/app/assets/javascripts/admin/controllers/admin-groups-bulk.js.es6 b/app/assets/javascripts/admin/controllers/admin-groups-bulk.js.es6 new file mode 100644 index 0000000000..6628a6fa72 --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-groups-bulk.js.es6 @@ -0,0 +1,34 @@ +import computed from 'ember-addons/ember-computed-decorators'; +import { popupAjaxError } from 'discourse/lib/ajax-error'; + +export default Ember.Controller.extend({ + users: null, + groupId: null, + saving: false, + + @computed('saving', 'users', 'groupId') + buttonDisabled(saving, users, groupId) { + return saving || !groupId || !users || !users.length; + }, + + actions: { + addToGroup() { + if (this.get('saving')) { return; } + + const users = this.get('users').split("\n") + .uniq() + .reject(x => x.length === 0); + + this.set('saving', true); + Discourse.ajax('/admin/groups/bulk', { + data: { users, group_id: this.get('groupId') }, + method: 'PUT' + }).then(() => { + this.transitionToRoute('adminGroups.bulkComplete'); + }).catch(popupAjaxError).finally(() => { + this.set('saving', false); + }); + + } + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-groups-bulk.js.es6 b/app/assets/javascripts/admin/routes/admin-groups-bulk.js.es6 new file mode 100644 index 0000000000..8d9554556f --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-groups-bulk.js.es6 @@ -0,0 +1,13 @@ +import Group from 'discourse/models/group'; + +export default Ember.Route.extend({ + model() { + return Group.findAll().then(groups => { + return groups.filter(g => !g.get('automatic')); + }); + }, + + setupController(controller, groups) { + controller.setProperties({ groups, groupId: null, users: null }); + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 index e01d0f8f0d..dd758556c6 100644 --- a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 @@ -49,6 +49,8 @@ export default { }); this.resource('adminGroups', { path: '/groups' }, function() { + this.route('bulk'); + this.route('bulkComplete', { path: 'bulk-complete' }); this.resource('adminGroupsType', { path: '/:type' }, function() { this.resource('adminGroup', { path: '/:name' }); }); diff --git a/app/assets/javascripts/admin/templates/groups-bulk-complete.hbs b/app/assets/javascripts/admin/templates/groups-bulk-complete.hbs new file mode 100644 index 0000000000..51eb3e4394 --- /dev/null +++ b/app/assets/javascripts/admin/templates/groups-bulk-complete.hbs @@ -0,0 +1 @@ +

      {{i18n "admin.groups.bulk_complete"}}

      diff --git a/app/assets/javascripts/admin/templates/groups-bulk.hbs b/app/assets/javascripts/admin/templates/groups-bulk.hbs new file mode 100644 index 0000000000..baf3a63cda --- /dev/null +++ b/app/assets/javascripts/admin/templates/groups-bulk.hbs @@ -0,0 +1,19 @@ +
      +

      {{i18n "admin.groups.bulk_paste"}}

      + +
      + {{textarea value=users class="paste-users"}} +
      + +
      + {{combo-box content=groups valueAttribute="id" value=groupId none="admin.groups.bulk_select"}} +
      + +
      + {{d-button disabled=buttonDisabled + class="btn-primary" + action="addToGroup" + icon="plus" + label="admin.groups.bulk"}} +
      +
      diff --git a/app/assets/javascripts/admin/templates/groups.hbs b/app/assets/javascripts/admin/templates/groups.hbs index 2d767c3844..aa7d9213ca 100644 --- a/app/assets/javascripts/admin/templates/groups.hbs +++ b/app/assets/javascripts/admin/templates/groups.hbs @@ -1,6 +1,7 @@ {{#admin-nav}} {{nav-item route='adminGroupsType' routeParam='custom' label='admin.groups.custom'}} {{nav-item route='adminGroupsType' routeParam='automatic' label='admin.groups.automatic'}} + {{nav-item route='adminGroups.bulk' label='admin.groups.bulk'}} {{/admin-nav}}
      diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index 60396122e8..63c01ffed8 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -215,6 +215,11 @@ td.flaggers td { } } +.paste-users { + width: 400px; + height: 150px; +} + .groups, .badges { .form-horizontal { label { @@ -1015,6 +1020,11 @@ table.api-keys { } } +.groups-bulk { + .control { + margin-bottom: 1em; + } +} .commits-widget { border: solid 1px dark-light-diff($primary, $secondary, 90%, -60%); diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 767657f1e1..67e8d86966 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -19,6 +19,42 @@ class Admin::GroupsController < Admin::AdminController render nothing: true end + def bulk + render nothing: true + end + + def bulk_perform + group = Group.find(params[:group_id].to_i) + if group.present? + users = (params[:users] || []).map {|u| u.downcase} + user_ids = User.where("username_lower in (:users) OR email IN (:users)", users: users).pluck(:id) + + if user_ids.present? + Group.exec_sql("INSERT INTO group_users + (group_id, user_id, created_at, updated_at) + SELECT #{group.id}, + u.id, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + FROM users AS u + WHERE u.id IN (#{user_ids.join(', ')}) + AND NOT EXISTS(SELECT 1 FROM group_users AS gu + WHERE gu.user_id = u.id AND + gu.group_id = #{group.id})") + + if group.primary_group? + User.where(id: user_ids).update_all(primary_group_id: group.id) + end + + if group.title.present? + User.where(id: user_ids).update_all(title: group.title) + end + end + end + + render json: success_json + end + def create group = Group.new diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index af5d2e6e09..6eff38ce54 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1949,6 +1949,10 @@ en: add: "Add" add_members: "Add members" custom: "Custom" + bulk_complete: "The users have been added to the group." + bulk: "Bulk Add to Group" + bulk_paste: "Paste a list of usernames or emails, one per line:" + bulk_select: "(select a group)" automatic: "Automatic" automatic_membership_email_domains: "Users who register with an email domain that exactly matches one in this list will be automatically added to this group:" automatic_membership_retroactive: "Apply the same email domain rule to add existing registered users" diff --git a/config/routes.rb b/config/routes.rb index cc55476b0a..0af5799a22 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -60,6 +60,9 @@ Discourse::Application.routes.draw do resources :groups, constraints: AdminConstraint.new do collection do post "refresh_automatic_groups" => "groups#refresh_automatic_groups" + get 'bulk' + get 'bulk-complete' => 'groups#bulk' + put 'bulk' => 'groups#bulk_perform' end member do put "members" => "groups#add_members" diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb index 75bc9ddac1..e7c2233ec1 100644 --- a/spec/controllers/admin/groups_controller_spec.rb +++ b/spec/controllers/admin/groups_controller_spec.rb @@ -36,6 +36,26 @@ describe Admin::GroupsController do end + context ".bulk" do + it "can assign users to a group by email or username" do + group = Fabricate(:group, name: "test", primary_group: true, title: 'WAT') + user = Fabricate(:user) + user2 = Fabricate(:user) + + xhr :put, :bulk_perform, group_id: group.id, users: [user.username.upcase, user2.email, 'doesnt_exist'] + + expect(response).to be_success + + user.reload + expect(user.primary_group).to eq(group) + expect(user.title).to eq("WAT") + + user2.reload + expect(user2.primary_group).to eq(group) + + end + end + context ".create" do it "strip spaces on the group name" do From 5dab5ada1cc86549dd779ee108e903c3d528ff66 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Tue, 27 Oct 2015 09:20:49 +0530 Subject: [PATCH 050/114] FIX: loading members on group page was broken --- .../javascripts/discourse/controllers/group/members.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/controllers/group/members.js.es6 b/app/assets/javascripts/discourse/controllers/group/members.js.es6 index 794b5856a7..a22c82705a 100644 --- a/app/assets/javascripts/discourse/controllers/group/members.js.es6 +++ b/app/assets/javascripts/discourse/controllers/group/members.js.es6 @@ -11,7 +11,7 @@ export default Ember.Controller.extend({ this.set("loading", true); - Discourse.Group.loadMembers(this.get("name"), this.get("model.members.length"), this.get("limit")).then(result => { + Discourse.Group.loadMembers(this.get("model.name"), this.get("model.members.length"), this.get("limit")).then(result => { this.get("model.members").addObjects(result.members.map(member => Discourse.User.create(member))); this.setProperties({ loading: false, From 9ea2d3010504c0246c3af2767ce1f2bb5c756322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Tue, 27 Oct 2015 10:52:05 +0100 Subject: [PATCH 051/114] UX: always show the avatar modal now that we have the letter avatars --- .../discourse/controllers/avatar-selector.js.es6 | 4 ++-- .../discourse/controllers/preferences.js.es6 | 8 +------- .../discourse/templates/modal/avatar_selector.hbs | 2 +- .../discourse/templates/user/preferences.hbs | 14 +++++--------- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/avatar-selector.js.es6 b/app/assets/javascripts/discourse/controllers/avatar-selector.js.es6 index 32aa7b514d..6a34f559b0 100644 --- a/app/assets/javascripts/discourse/controllers/avatar-selector.js.es6 +++ b/app/assets/javascripts/discourse/controllers/avatar-selector.js.es6 @@ -21,8 +21,8 @@ export default Ember.Controller.extend(ModalFunctionality, { }, @computed() - allowImageUpload() { - return Discourse.Utilities.allowsImages(); + allowAvatarUpload() { + return this.siteSettings.allow_uploaded_avatars && Discourse.Utilities.allowsImages(); }, actions: { diff --git a/app/assets/javascripts/discourse/controllers/preferences.js.es6 b/app/assets/javascripts/discourse/controllers/preferences.js.es6 index a7aa333fbc..f23f342e08 100644 --- a/app/assets/javascripts/discourse/controllers/preferences.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences.js.es6 @@ -5,12 +5,6 @@ import computed from "ember-addons/ember-computed-decorators"; export default Ember.Controller.extend(CanCheckEmails, { - allowAvatarUpload: setting('allow_uploaded_avatars'), - allowUserLocale: setting('allow_user_locale'), - ssoOverridesAvatar: setting('sso_overrides_avatar'), - allowBackgrounds: setting('allow_profile_backgrounds'), - editHistoryVisible: setting('edit_history_visible_to_public'), - @computed("model.watchedCategories", "model.trackedCategories", "model.mutedCategories") selectedCategories(watched, tracked, muted) { return [].concat(watched, tracked, muted); @@ -45,7 +39,7 @@ export default Ember.Controller.extend(CanCheckEmails, { @computed() nameInstructions() { - return I18n.t(Discourse.SiteSettings.full_name_required ? 'user.name.instructions_required' : 'user.name.instructions'); + return I18n.t(this.siteSettings.full_name_required ? 'user.name.instructions_required' : 'user.name.instructions'); }, @computed("model.has_title_badges") diff --git a/app/assets/javascripts/discourse/templates/modal/avatar_selector.hbs b/app/assets/javascripts/discourse/templates/modal/avatar_selector.hbs index e7d070d814..760ab1a580 100644 --- a/app/assets/javascripts/discourse/templates/modal/avatar_selector.hbs +++ b/app/assets/javascripts/discourse/templates/modal/avatar_selector.hbs @@ -9,7 +9,7 @@ {{d-button action="refreshGravatar" title="user.change_avatar.refresh_gravatar_title" disabled=gravatarRefreshDisabled icon="refresh"}}
      - {{#if allowImageUpload}} + {{#if allowAvatarUpload}}
      - {{#if allowBackgrounds}} + {{#if siteSettings.allow_profile_backgrounds}}
      @@ -122,7 +118,7 @@
      {{/if}} - {{#if allowUserLocale}} + {{#if siteSettings.allow_user_locale}}
      @@ -213,7 +209,7 @@ {{preference-checkbox labelKey="user.enable_quoting" checked=model.enable_quoting}} {{preference-checkbox labelKey="user.dynamic_favicon" checked=model.dynamic_favicon}} {{preference-checkbox labelKey="user.disable_jump_reply" checked=model.disable_jump_reply}} - {{#unless editHistoryVisible}} + {{#unless siteSettings.edit_history_visible_to_public}} {{preference-checkbox labelKey="user.edit_history_public" checked=model.edit_history_public}} {{/unless}} From 68660cb9ac00a07fe1b13507792048537e2a9b99 Mon Sep 17 00:00:00 2001 From: cpradio Date: Tue, 27 Oct 2015 09:57:01 -0400 Subject: [PATCH 052/114] UX: Add custom classes to the profile page to permit hiding the invited_by and trust_level fields --- app/assets/javascripts/discourse/templates/user/user.hbs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/user/user.hbs b/app/assets/javascripts/discourse/templates/user/user.hbs index c52d241ef0..1f61a722de 100644 --- a/app/assets/javascripts/discourse/templates/user/user.hbs +++ b/app/assets/javascripts/discourse/templates/user/user.hbs @@ -122,9 +122,9 @@ {{/if}}
      {{i18n 'views'}}
      {{model.profile_view_count}}
      {{#if model.invited_by}} -
      {{i18n 'user.invited_by'}}
      {{#link-to 'user' model.invited_by}}{{model.invited_by.username}}{{/link-to}}
      +
      {{i18n 'user.invited_by'}}
      {{#link-to 'user' model.invited_by}}{{model.invited_by.username}}{{/link-to}}
      {{/if}} -
      {{i18n 'user.trust_level'}}
      {{model.trustLevel.name}}
      +
      {{i18n 'user.trust_level'}}
      {{model.trustLevel.name}}
      {{#if canCheckEmails}}
      {{i18n 'user.email.title'}}
      From 6c172a30d3165eeaf6e13006c06441c29a41f3ab Mon Sep 17 00:00:00 2001 From: Carson Reinke Date: Tue, 27 Oct 2015 12:14:18 -0400 Subject: [PATCH 053/114] #compact! can return nil if no changes were made --- lib/memory_diagnostics.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/memory_diagnostics.rb b/lib/memory_diagnostics.rb index d79d84407b..e484bf5129 100644 --- a/lib/memory_diagnostics.rb +++ b/lib/memory_diagnostics.rb @@ -22,6 +22,7 @@ module MemoryDiagnostics diff = diff.map do |id| ObjectSpace._id2ref(id) rescue nil end.compact! + diff ||= [] report = "#{diff.length} objects have leaked\n" From df916e86b79b731ce33d201ca4009b03001c30c3 Mon Sep 17 00:00:00 2001 From: Carson Reinke Date: Tue, 27 Oct 2015 12:27:37 -0400 Subject: [PATCH 054/114] compact! only returns array if changes were made --- lib/memory_diagnostics.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/memory_diagnostics.rb b/lib/memory_diagnostics.rb index e484bf5129..a49480677a 100644 --- a/lib/memory_diagnostics.rb +++ b/lib/memory_diagnostics.rb @@ -21,8 +21,8 @@ module MemoryDiagnostics require 'objspace' diff = diff.map do |id| ObjectSpace._id2ref(id) rescue nil - end.compact! - diff ||= [] + end + diff.compact! report = "#{diff.length} objects have leaked\n" From ebb16bfafc3841db8849e176bc7608c71cee5013 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 27 Oct 2015 14:21:29 -0400 Subject: [PATCH 055/114] FIX: mbox import should respect date order after grouping --- script/import_scripts/mbox.rb | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/script/import_scripts/mbox.rb b/script/import_scripts/mbox.rb index 3ff41e9454..a7773f0a56 100755 --- a/script/import_scripts/mbox.rb +++ b/script/import_scripts/mbox.rb @@ -46,17 +46,21 @@ class ImportScripts::Mbox < ImportScripts::Base msg_id = mail['Message-ID'].to_s reply_to = mail['In-Reply-To'].to_s title = clean_title(mail['Subject'].to_s) + date = Time.parse(mail['date'].to_s).to_i if reply_to.present? topic = topic_lookup[reply_to] || reply_to topic_lookup[msg_id] = topic - replies << {id: msg_id, topic: topic, file: filename, title: title} + replies << {id: msg_id, topic: topic, file: filename, title: title, date: date} else - topics << {id: msg_id, file: filename, title: title} - topic_titles[title] ||= msg_id + topics << {id: msg_id, file: filename, title: title, date: date} + topic_titles[title] ||= msg_id end end + replies.sort! {|a, b| a[:date] <=> b[:date]} + topics.sort! {|a, b| a[:date] <=> b[:date]} + # Replies without parents should be hoisted to topics to_hoist = [] replies.each do |r| @@ -65,7 +69,7 @@ class ImportScripts::Mbox < ImportScripts::Base to_hoist.each do |h| replies.delete(h) - topics << {id: h[:id], file: h[:file], title: h[:title]} + topics << {id: h[:id], file: h[:file], title: h[:title], date: h[:date]} topic_titles[h[:title]] ||= h[:id] end @@ -78,9 +82,13 @@ class ImportScripts::Mbox < ImportScripts::Base to_group.each do |t| topics.delete(t) - replies << {id: t[:id], topic: topic_titles[t[:title]], file: t[:file], title: t[:title]} + replies << {id: t[:id], topic: topic_titles[t[:title]], file: t[:file], title: t[:title], date: t[:date]} end + replies.sort! {|a, b| a[:date] <=> b[:date]} + topics.sort! {|a, b| a[:date] <=> b[:date]} + + File.write(USER_INDEX_PATH, {users: users}.to_json) File.write(TOPIC_INDEX_PATH, {topics: topics}.to_json) File.write(REPLY_INDEX_PATH, {replies: replies}.to_json) From 010d847c2d4fbd8844c18ec6dd9fb6e5ca2dea06 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 27 Oct 2015 15:05:37 -0400 Subject: [PATCH 056/114] Extensibility point for adding a new main button below topics --- .../javascripts/discourse/views/topic-footer-main-buttons.js.es6 | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/discourse/views/topic-footer-main-buttons.js.es6 b/app/assets/javascripts/discourse/views/topic-footer-main-buttons.js.es6 index 8ee4409353..8ba5288553 100644 --- a/app/assets/javascripts/discourse/views/topic-footer-main-buttons.js.es6 +++ b/app/assets/javascripts/discourse/views/topic-footer-main-buttons.js.es6 @@ -26,5 +26,6 @@ export default ContainerView.extend({ if (this.get('topic.details.can_create_post')) { this.attachViewClass('reply-button'); } + this.trigger('additionalButtons', this); } }); From 46ca66771be750305a3437f09773465c6f6a649b Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 27 Oct 2015 16:25:30 -0400 Subject: [PATCH 057/114] FIX: Better error message for resending activation. Don't limit staff. --- app/assets/javascripts/admin/models/admin-user.js.es6 | 5 +---- app/controllers/users_controller.rb | 6 ++++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/admin/models/admin-user.js.es6 b/app/assets/javascripts/admin/models/admin-user.js.es6 index 1e83de6206..20a9579bd6 100644 --- a/app/assets/javascripts/admin/models/admin-user.js.es6 +++ b/app/assets/javascripts/admin/models/admin-user.js.es6 @@ -288,10 +288,7 @@ const AdminUser = Discourse.User.extend({ data: { username: this.get('username') } }).then(function() { bootbox.alert( I18n.t('admin.user.activation_email_sent') ); - }).catch(function(e) { - var error = I18n.t('admin.user.send_activation_email_failed', { error: "http: " + e.status + " - " + e.body }); - bootbox.alert(error); - }); + }).catch(popupAjaxError); }, anonymizeForbidden: Em.computed.not("can_be_anonymized"), diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index eb28b8cb18..12abf12a97 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -498,8 +498,10 @@ class UsersController < ApplicationController end def send_activation_email - RateLimiter.new(nil, "activate-hr-#{request.remote_ip}", 30, 1.hour).performed! - RateLimiter.new(nil, "activate-min-#{request.remote_ip}", 6, 1.minute).performed! + if current_user.blank? || !current_user.staff? + RateLimiter.new(nil, "activate-hr-#{request.remote_ip}", 30, 1.hour).performed! + RateLimiter.new(nil, "activate-min-#{request.remote_ip}", 6, 1.minute).performed! + end @user = User.find_by_username_or_email(params[:username].to_s) From 6b236d3c836f91d3c57227072320ed24f7a0a483 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 27 Oct 2015 16:57:40 -0400 Subject: [PATCH 058/114] FEATURE: Bulk Unlisting of topics --- .../controllers/topic-bulk-actions.js.es6 | 80 +++++++++---------- .../templates/modal/bulk_actions_buttons.hbs | 4 +- config/locales/client.en.yml | 1 + lib/topics_bulk_action.rb | 11 ++- spec/components/topics_bulk_action_spec.rb | 27 +++++++ 5 files changed, 77 insertions(+), 46 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6 b/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6 index b5e8bcbb4b..2dd79219e5 100644 --- a/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6 @@ -13,12 +13,13 @@ addBulkButton('closeTopics', 'close_topics'); addBulkButton('archiveTopics', 'archive_topics'); addBulkButton('showNotificationLevel', 'notification_level'); addBulkButton('resetRead', 'reset_read'); +addBulkButton('unlistTopics', 'unlist_topics'); // Modal for performing bulk actions on topics export default Ember.ArrayController.extend(ModalFunctionality, { buttonRows: null, - onShow: function() { + onShow() { this.set('controllers.modal.modalClass', 'topic-bulk-actions-modal small'); const buttonRows = []; @@ -36,87 +37,80 @@ export default Ember.ArrayController.extend(ModalFunctionality, { this.send('changeBulkTemplate', 'modal/bulk_actions_buttons'); }, - perform: function(operation) { + perform(operation) { this.set('loading', true); - var self = this, - topics = this.get('model'); - return Discourse.Topic.bulkOperation(this.get('model'), operation).then(function(result) { - self.set('loading', false); + const topics = this.get('model'); + return Discourse.Topic.bulkOperation(this.get('model'), operation).then(result => { + this.set('loading', false); if (result && result.topic_ids) { - return result.topic_ids.map(function (t) { - return topics.findBy('id', t); - }); + return result.topic_ids.map(t => topics.findBy('id', t)); } return result; - }).catch(function() { + }).catch(() => { bootbox.alert(I18n.t('generic_error')); - self.set('loading', false); + this.set('loading', false); }); }, - forEachPerformed: function(operation, cb) { - var self = this; - this.perform(operation).then(function (topics) { + forEachPerformed(operation, cb) { + this.perform(operation).then(topics => { if (topics) { topics.forEach(cb); - const refreshTarget = self.get('refreshTarget'); + const refreshTarget = this.get('refreshTarget'); if (refreshTarget) { refreshTarget.send('refresh'); } - self.send('closeModal'); + this.send('closeModal'); } }); }, - performAndRefresh: function(operation) { - const self = this; - return this.perform(operation).then(function() { - const refreshTarget = self.get('refreshTarget'); + performAndRefresh(operation) { + return this.perform(operation).then(() => { + const refreshTarget = this.get('refreshTarget'); if (refreshTarget) { refreshTarget.send('refresh'); } - self.send('closeModal'); + this.send('closeModal'); }); }, actions: { - showChangeCategory: function() { + showChangeCategory() { this.send('changeBulkTemplate', 'modal/bulk_change_category'); this.set('controllers.modal.modalClass', 'topic-bulk-actions-modal full'); }, - showNotificationLevel: function() { + showNotificationLevel() { this.send('changeBulkTemplate', 'modal/bulk_notification_level'); }, - deleteTopics: function() { + deleteTopics() { this.performAndRefresh({type: 'delete'}); }, - closeTopics: function() { - this.forEachPerformed({type: 'close'}, function(t) { - t.set('closed', true); - }); + closeTopics() { + this.forEachPerformed({type: 'close'}, t => t.set('closed', true)); }, - archiveTopics: function() { - this.forEachPerformed({type: 'archive'}, function(t) { - t.set('archived', true); - }); + archiveTopics() { + this.forEachPerformed({type: 'archive'}, t => t.set('archived', true)); }, - changeCategory: function() { - var categoryId = parseInt(this.get('newCategoryId'), 10) || 0, - category = Discourse.Category.findById(categoryId), - self = this; - this.perform({type: 'change_category', category_id: categoryId}).then(function(topics) { - topics.forEach(function(t) { - t.set('category', category); - }); - const refreshTarget = self.get('refreshTarget'); + unlistTopics() { + this.forEachPerformed({type: 'unlist'}, t => t.set('visible', false)); + }, + + changeCategory() { + const categoryId = parseInt(this.get('newCategoryId'), 10) || 0; + const category = Discourse.Category.findById(categoryId); + + this.perform({type: 'change_category', category_id: categoryId}).then(topics => { + topics.forEach(t => t.set('category', category)); + const refreshTarget = this.get('refreshTarget'); if (refreshTarget) { refreshTarget.send('refresh'); } - self.send('closeModal'); + this.send('closeModal'); }); }, - resetRead: function() { + resetRead() { this.performAndRefresh({ type: 'reset_read' }); } } diff --git a/app/assets/javascripts/discourse/templates/modal/bulk_actions_buttons.hbs b/app/assets/javascripts/discourse/templates/modal/bulk_actions_buttons.hbs index 6cd4580a53..94f87b8074 100644 --- a/app/assets/javascripts/discourse/templates/modal/bulk_actions_buttons.hbs +++ b/app/assets/javascripts/discourse/templates/modal/bulk_actions_buttons.hbs @@ -1,6 +1,6 @@ -{{#each row in buttonRows}} +{{#each buttonRows as |row|}}

      - {{#each button in row}} + {{#each row as |button|}} {{d-button action=button.action label=button.label}} {{/each}}

      diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 6eff38ce54..64cec34cb3 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1008,6 +1008,7 @@ en: topics: bulk: + unlist_topics: "Unlist Topics" reset_read: "Reset Read" delete: "Delete Topics" dismiss: "Dismiss" diff --git a/lib/topics_bulk_action.rb b/lib/topics_bulk_action.rb index 337c32eac1..1a54ae9e3c 100644 --- a/lib/topics_bulk_action.rb +++ b/lib/topics_bulk_action.rb @@ -8,7 +8,7 @@ class TopicsBulkAction end def self.operations - @operations ||= %w(change_category close archive change_notification_level reset_read dismiss_posts delete) + @operations ||= %w(change_category close archive change_notification_level reset_read dismiss_posts delete unlist) end def self.register_operation(name, &block) @@ -66,6 +66,15 @@ class TopicsBulkAction end end + def unlist + topics.each do |t| + if guardian.can_moderate?(t) + t.update_status('visible', false, @user) + @changed_ids << t.id + end + end + end + def archive topics.each do |t| if guardian.can_moderate?(t) diff --git a/spec/components/topics_bulk_action_spec.rb b/spec/components/topics_bulk_action_spec.rb index 2a3396d2dc..24dc4310ba 100644 --- a/spec/components/topics_bulk_action_spec.rb +++ b/spec/components/topics_bulk_action_spec.rb @@ -153,4 +153,31 @@ describe TopicsBulkAction do end end end + + describe "unlist" do + let(:topic) { Fabricate(:topic) } + + context "when the user can moderate the topic" do + it "unlists the topic and returns the topic_id" do + Guardian.any_instance.expects(:can_moderate?).returns(true) + Guardian.any_instance.expects(:can_create?).returns(true) + tba = TopicsBulkAction.new(topic.user, [topic.id], type: 'unlist') + topic_ids = tba.perform! + expect(topic_ids).to eq([topic.id]) + topic.reload + expect(topic).not_to be_visible + end + end + + context "when the user can't edit the topic" do + it "doesn't unlist the topic" do + Guardian.any_instance.expects(:can_moderate?).returns(false) + tba = TopicsBulkAction.new(topic.user, [topic.id], type: 'unlist') + topic_ids = tba.perform! + expect(topic_ids).to be_blank + topic.reload + expect(topic).to be_visible + end + end + end end From 23371b026de7cce8ea2d05aed36a27bf4eefb8bd Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 28 Oct 2015 12:21:54 -0400 Subject: [PATCH 059/114] FIX: Don't raise an error if you try to assign a group that exists --- app/controllers/admin/users_controller.rb | 5 ++++- spec/controllers/admin/users_controller_spec.rb | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index c096cf078f..ee03ac7058 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -121,7 +121,10 @@ class Admin::UsersController < Admin::AdminController def add_group group = Group.find(params[:group_id].to_i) return render_json_error group unless group && !group.automatic - group.users << @user + + # We don't care about duplicate group assignment + group.users << @user rescue ActiveRecord::RecordNotUnique + render nothing: true end diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index 4f93fe55a5..20d5d198df 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -37,7 +37,6 @@ describe Admin::UsersController do expect(UserHistory.where(action: UserHistory.actions[:check_email], acting_user_id: @user.id).count).to eq(0) xhr :get, :index, show_emails: "true" - data = ::JSON.parse(response.body) expect(UserHistory.where(action: UserHistory.actions[:check_email], acting_user_id: @user.id).count).to eq(1) end @@ -173,6 +172,22 @@ describe Admin::UsersController do end end + context '.add_group' do + let(:user) { Fabricate(:user) } + let(:group) { Fabricate(:group) } + + it 'adds the user to the group' do + xhr :post, :add_group, group_id: group.id, user_id: user.id + expect(response).to be_success + + expect(GroupUser.where(user_id: user.id, group_id: group.id).exists?).to eq(true) + + # Doing it again doesn't raise an error + xhr :post, :add_group, group_id: group.id, user_id: user.id + expect(response).to be_success + end + end + context '.primary_group' do before do @another_user = Fabricate(:coding_horror) From 734c272de81baac8a70801e257a994a8a6e9f616 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Wed, 28 Oct 2015 22:15:07 +0530 Subject: [PATCH 060/114] UX: universal date format in digest email --- app/mailers/user_notifications.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb index a5b73f5db7..030c5caa7a 100644 --- a/app/mailers/user_notifications.rb +++ b/app/mailers/user_notifications.rb @@ -42,7 +42,12 @@ class UserNotifications < ActionMailer::Base end def short_date(dt) - I18n.l(dt, format: :short) + current = Time.now + if dt.year == current.year + dt.strftime("%B #{dt.day.ordinalize}") + else + dt.strftime("%B #{dt.day.ordinalize}, %Y") + end end def digest(user, opts={}) From 971af6a762d64afc24cc6a0c451322672a9d68fc Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 28 Oct 2015 13:03:34 -0400 Subject: [PATCH 061/114] FIX: PostAlerter should ignore deleted posts --- app/jobs/regular/post_alert.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/jobs/regular/post_alert.rb b/app/jobs/regular/post_alert.rb index 36785df4e4..30cef2534b 100644 --- a/app/jobs/regular/post_alert.rb +++ b/app/jobs/regular/post_alert.rb @@ -3,10 +3,8 @@ module Jobs def execute(args) # maybe it was removed by the time we are making the post - if post = Post.find(args[:post_id]) - # maybe the topic was deleted, so skip in that case as well - PostAlerter.post_created(post) if post.topic - end + post = Post.where(id: args[:post_id]).first + PostAlerter.post_created(post) if post && post.topic end end From d9997a6b9e2b118c4785b912dc47d467d1de583a Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 28 Oct 2015 13:31:28 -0400 Subject: [PATCH 062/114] Add auto link support to the email formatter --- lib/email_cook.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/email_cook.rb b/lib/email_cook.rb index 33693190cb..d30f4b9f66 100644 --- a/lib/email_cook.rb +++ b/lib/email_cook.rb @@ -1,4 +1,7 @@ # A very simple formatter for imported emails + +require 'uri' + class EmailCook def initialize(raw) @@ -30,6 +33,10 @@ class EmailCook result.gsub!(/(
      ){3,10}/, '

      ') + URI.extract(result).each do |m| + result.gsub!(m, "#{m}") + end + result end From bb79e6aff7acd95d1dac8fcf367978af2ad81162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 28 Oct 2015 19:56:08 +0100 Subject: [PATCH 063/114] FEATURE: new hide_user_profiles_from_public site setting --- .../components/hamburger-menu.js.es6 | 7 + .../discourse/controllers/user-card.js.es6 | 5 + .../javascripts/discourse/routes/user.js.es6 | 6 + .../javascripts/discourse/routes/users.js.es6 | 6 + .../templates/components/hamburger-menu.hbs | 2 +- app/controllers/users_controller.rb | 3 +- config/locales/server.en.yml | 2 + config/site_settings.yml | 3 + lib/search.rb | 2 + spec/components/search_spec.rb | 16 ++- spec/controllers/users_controller_spec.rb | 134 ++++++++++-------- 11 files changed, 127 insertions(+), 59 deletions(-) diff --git a/app/assets/javascripts/discourse/components/hamburger-menu.js.es6 b/app/assets/javascripts/discourse/components/hamburger-menu.js.es6 index 910b8d661a..e10c01c84d 100644 --- a/app/assets/javascripts/discourse/components/hamburger-menu.js.es6 +++ b/app/assets/javascripts/discourse/components/hamburger-menu.js.es6 @@ -56,6 +56,13 @@ export default Ember.Component.extend({ }); }, + @computed() + showUserDirectoryLink() { + if (!this.siteSettings.enable_user_directory) return false; + if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) return false; + return true; + }, + actions: { keyboardShortcuts() { this.sendAction('showKeyboardAction'); diff --git a/app/assets/javascripts/discourse/controllers/user-card.js.es6 b/app/assets/javascripts/discourse/controllers/user-card.js.es6 index ce2e065a89..c67125cdb2 100644 --- a/app/assets/javascripts/discourse/controllers/user-card.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-card.js.es6 @@ -39,6 +39,11 @@ export default Ember.Controller.extend({ // XSS protection (should be encapsulated) username = username.toString().replace(/[^A-Za-z0-9_\.\-]/g, ""); + // No user card for anon + if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) { + return; + } + // Don't show on mobile if (Discourse.Mobile.mobileView) { const url = "/users/" + username; diff --git a/app/assets/javascripts/discourse/routes/user.js.es6 b/app/assets/javascripts/discourse/routes/user.js.es6 index 7fb8fffe6f..040f949b5a 100644 --- a/app/assets/javascripts/discourse/routes/user.js.es6 +++ b/app/assets/javascripts/discourse/routes/user.js.es6 @@ -33,6 +33,12 @@ export default Discourse.Route.extend({ } }, + beforeModel() { + if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) { + this.replaceWith("discovery"); + } + }, + model(params) { // If we're viewing the currently logged in user, return that object instead const currentUser = this.currentUser; diff --git a/app/assets/javascripts/discourse/routes/users.js.es6 b/app/assets/javascripts/discourse/routes/users.js.es6 index beb25276fc..ab3cbe3206 100644 --- a/app/assets/javascripts/discourse/routes/users.js.es6 +++ b/app/assets/javascripts/discourse/routes/users.js.es6 @@ -23,6 +23,12 @@ export default Discourse.Route.extend({ } }, + beforeModel() { + if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) { + this.replaceWith("discovery"); + } + }, + model(params) { // If we refresh via `refreshModel` set the old model to loading this._params = params; diff --git a/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs b/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs index 0deea8e1a7..3d875eb606 100644 --- a/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs +++ b/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs @@ -57,7 +57,7 @@
    • {{d-link route="badges" class="badge-link" label="badges.title"}}
    • {{/if}} - {{#if siteSettings.enable_user_directory}} + {{#if showUserDirectoryLink}}
    • {{d-link route="users" class="user-directory-link" label="directory.title"}}
    • {{/if}} diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 12abf12a97..1c2be6d1ba 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -29,6 +29,8 @@ class UsersController < ApplicationController end def show + raise Discourse::InvalidAccess if SiteSetting.hide_user_profiles_from_public && !current_user + @user = fetch_user_from_params user_serializer = UserSerializer.new(@user, scope: guardian, root: 'user') if params[:stats].to_s == "false" @@ -162,7 +164,6 @@ class UsersController < ApplicationController end def my_redirect - raise Discourse::NotFound if params[:path] !~ /^[a-z\-\/]+$/ if current_user.blank? diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index dd4240c338..c5784bd249 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1162,6 +1162,8 @@ en: anonymous_posting_min_trust_level: "Minimum trust level required to enable anonymous posting" anonymous_account_duration_minutes: "To protect anonymity create a new anonymous account every N minutes for each user. Example: if set to 600, as soon as 600 minutes elapse from last post AND user switches to anon, a new anonymous account is created." + hide_user_profiles_from_public: "Disable user cards, user profiles and user directory for anonymous users." + allow_profile_backgrounds: "Allow users to upload profile backgrounds." sequential_replies_threshold: "Number posts a user has to make in a row in a topic before being reminded about too many sequential replies. " diff --git a/config/site_settings.yml b/config/site_settings.yml index 73c801b2c9..1497138727 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -342,6 +342,9 @@ users: client: true anonymous_account_duration_minutes: default: 10080 + hide_user_profiles_from_public: + default: false + client: true posting: min_post_length: diff --git a/lib/search.rb b/lib/search.rb index e99e603612..4c60c318fb 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -379,6 +379,8 @@ class Search end def user_search + return if SiteSetting.hide_user_profiles_from_public && !@guardian.user + users = User.includes(:user_search_data) .where("active = true AND user_search_data.search_data @@ #{ts_query("simple")}") .order("CASE WHEN username_lower = '#{@original_term.downcase}' THEN 0 ELSE 1 END") diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb index 462b119112..093aeb766f 100644 --- a/spec/components/search_spec.rb +++ b/spec/components/search_spec.rb @@ -85,6 +85,21 @@ describe Search do expect(result.users.length).to eq(1) expect(result.users[0].id).to eq(user.id) end + + context 'hiding user profiles' do + before { SiteSetting.stubs(:hide_user_profiles_from_public).returns(true) } + + it 'returns no result for anon' do + expect(result.users.length).to eq(0) + end + + it 'returns a result for logged in users' do + result = Search.execute('bruce', type_filter: 'user', guardian: Guardian.new(user)) + expect(result.users.length).to eq(1) + end + + end + end context 'inactive users' do @@ -119,7 +134,6 @@ describe Search do TopicAllowedUser.create!(user_id: reply.user_id, topic_id: topic.id) TopicAllowedUser.create!(user_id: post.user_id, topic_id: topic.id) - results = Search.execute('mars', type_filter: 'private_messages', guardian: Guardian.new(reply.user)) diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index e693da0225..fb49b404c7 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -3,71 +3,93 @@ require 'spec_helper' describe UsersController do describe '.show' do - let(:user) { log_in } - it 'returns success' do - xhr :get, :show, username: user.username, format: :json - expect(response).to be_success - json = JSON.parse(response.body) + context "anon" do - expect(json["user"]["has_title_badges"]).to eq(false) + let(:user) { Discourse.system_user } - end - - it "returns not found when the username doesn't exist" do - xhr :get, :show, username: 'madeuppity' - expect(response).not_to be_success - end - - it 'returns not found when the user is inactive' do - inactive = Fabricate(:user, active: false) - xhr :get, :show, username: inactive.username - expect(response).not_to be_success - end - - it "raises an error on invalid access" do - Guardian.any_instance.expects(:can_see?).with(user).returns(false) - xhr :get, :show, username: user.username - expect(response).to be_forbidden - end - - describe "user profile views" do - let(:other_user) { Fabricate(:user) } - - it "should track a user profile view for a signed in user" do - UserProfileView.expects(:add).with(other_user.user_profile.id, request.remote_ip, user.id) - xhr :get, :show, username: other_user.username - end - - it "should not track a user profile view for a user viewing his own profile" do - UserProfileView.expects(:add).never - xhr :get, :show, username: user.username - end - - it "should track a user profile view for an anon user" do - UserProfileView.expects(:add).with(other_user.user_profile.id, request.remote_ip, nil) - xhr :get, :show, username: other_user.username - end - - it "skips tracking" do - UserProfileView.expects(:add).never - xhr :get, :show, { username: user.username, skip_track_visit: true } - end - end - - context "fetching a user by external_id" do - before { user.create_single_sign_on_record(external_id: '997', last_payload: '') } - - it "returns fetch for a matching external_id" do - xhr :get, :show, external_id: '997' + it "returns success" do + xhr :get, :show, username: user.username, format: :json expect(response).to be_success end - it "returns not found when external_id doesn't match" do - xhr :get, :show, external_id: '99' + it "raises an error for anon when profiles are hidden" do + SiteSetting.stubs(:hide_user_profiles_from_public).returns(true) + xhr :get, :show, username: user.username, format: :json expect(response).not_to be_success end + end + + context "logged in" do + + let(:user) { log_in } + + it 'returns success' do + xhr :get, :show, username: user.username, format: :json + expect(response).to be_success + json = JSON.parse(response.body) + + expect(json["user"]["has_title_badges"]).to eq(false) + end + + it "returns not found when the username doesn't exist" do + xhr :get, :show, username: 'madeuppity' + expect(response).not_to be_success + end + + it 'returns not found when the user is inactive' do + inactive = Fabricate(:user, active: false) + xhr :get, :show, username: inactive.username + expect(response).not_to be_success + end + + it "raises an error on invalid access" do + Guardian.any_instance.expects(:can_see?).with(user).returns(false) + xhr :get, :show, username: user.username + expect(response).to be_forbidden + end + + describe "user profile views" do + let(:other_user) { Fabricate(:user) } + + it "should track a user profile view for a signed in user" do + UserProfileView.expects(:add).with(other_user.user_profile.id, request.remote_ip, user.id) + xhr :get, :show, username: other_user.username + end + + it "should not track a user profile view for a user viewing his own profile" do + UserProfileView.expects(:add).never + xhr :get, :show, username: user.username + end + + it "should track a user profile view for an anon user" do + UserProfileView.expects(:add).with(other_user.user_profile.id, request.remote_ip, nil) + xhr :get, :show, username: other_user.username + end + + it "skips tracking" do + UserProfileView.expects(:add).never + xhr :get, :show, { username: user.username, skip_track_visit: true } + end + end + + context "fetching a user by external_id" do + before { user.create_single_sign_on_record(external_id: '997', last_payload: '') } + + it "returns fetch for a matching external_id" do + xhr :get, :show, external_id: '997' + expect(response).to be_success + end + + it "returns not found when external_id doesn't match" do + xhr :get, :show, external_id: '99' + expect(response).not_to be_success + end + end + + end + end describe '.user_preferences_redirect' do From db5379508e76c62c68fb46935af468cbf6bd3de8 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 28 Oct 2015 15:11:36 -0400 Subject: [PATCH 064/114] FIX: Don't show an anonymous cache if there is a flash --- app/controllers/application_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e8376b6a28..efea5b2f65 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -243,7 +243,7 @@ class ApplicationController < ActionController::Base end def can_cache_content? - !current_user.present? + current_user.blank? && flash[:authentication_data].blank? end # Our custom cache method From a9823ab59aefffd0c746c05b7d87075f11c60cbc Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 28 Oct 2015 17:16:56 -0400 Subject: [PATCH 065/114] FIX: Use a cookie to bypass the anon cache --- app/controllers/users/omniauth_callbacks_controller.rb | 1 + lib/middleware/anonymous_cache.rb | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index 39209b417f..a7fc7aaf49 100644 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -57,6 +57,7 @@ class Users::OmniauthCallbacksController < ApplicationController complete_response_data if provider && provider.full_screen_login + cookies['_bypass_cache'] = true flash[:authentication_data] = @auth_result.to_client_hash.to_json redirect_to @origin else diff --git a/lib/middleware/anonymous_cache.rb b/lib/middleware/anonymous_cache.rb index a65eb2df1a..a79b422566 100644 --- a/lib/middleware/anonymous_cache.rb +++ b/lib/middleware/anonymous_cache.rb @@ -64,8 +64,13 @@ module Middleware CurrentUser.has_auth_cookie?(@env) end + def no_cache_bypass + request = Rack::Request.new(@env) + request.cookies['_bypass_cache'].nil? + end + def cacheable? - !!(!has_auth_cookie? && get?) + !!(!has_auth_cookie? && get? && no_cache_bypass) end def cached From 0b4c9005f95fe58363045302b6806e5e37488ed0 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 29 Oct 2015 12:19:09 -0400 Subject: [PATCH 066/114] FIX: Don't include `name` in hash when names are disabled. This could break some SSO implementations due to honeypot not being triggered. --- lib/auth/result.rb | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/auth/result.rb b/lib/auth/result.rb index 461c8f7c9f..3c78b2cf5f 100644 --- a/lib/auth/result.rb +++ b/lib/auth/result.rb @@ -46,15 +46,18 @@ class Auth::Result } end else - { - email: email, - name: User.suggest_name(name || username || email), - username: UserNameSuggester.suggest(username || name || email), - # this feels a tad wrong - auth_provider: authenticator_name.capitalize, - email_valid: !!email_valid, - omit_username: !!omit_username - } + result = { email: email, + username: UserNameSuggester.suggest(username || name || email), + # this feels a tad wrong + auth_provider: authenticator_name.capitalize, + email_valid: !!email_valid, + omit_username: !!omit_username } + + if SiteSetting.enable_names? + result[:name] = User.suggest_name(name || username || email) + end + + result end end end From 2fc52e26a665f326e3193cfabd35169cdc564070 Mon Sep 17 00:00:00 2001 From: James Kiesel Date: Thu, 29 Oct 2015 19:39:30 +0200 Subject: [PATCH 067/114] Optimize all_allowed_users query --- app/models/topic.rb | 7 ++----- spec/models/topic_spec.rb | 41 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/app/models/topic.rb b/app/models/topic.rb index 37f9ebb331..e66888b0d3 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -253,11 +253,8 @@ class Topic < ActiveRecord::Base # all users (in groups or directly targetted) that are going to get the pm def all_allowed_users - # TODO we should probably change this to 1 query - allowed_user_ids = allowed_users.select('users.id').to_a - allowed_group_user_ids = allowed_group_users.select('users.id').to_a - allowed_staff_ids = private_message? && has_flags? ? User.where(moderator: true).pluck(:id).to_a : [] - User.where('id IN (?)', allowed_user_ids + allowed_group_user_ids + allowed_staff_ids) + moderators_sql = " UNION #{User.moderators.to_sql}" if private_message? && has_flags? + User.from("(#{allowed_users.to_sql} UNION #{allowed_group_users.to_sql}#{moderators_sql}) as users") end # Additional rate limits on topics: per day and private messages per day diff --git a/spec/models/topic_spec.rb b/spec/models/topic_spec.rb index 3ed9e895d6..9ad310e031 100644 --- a/spec/models/topic_spec.rb +++ b/spec/models/topic_spec.rb @@ -1315,6 +1315,47 @@ describe Topic do end end + describe 'all_allowed_users' do + let(:group) { Fabricate(:group) } + let(:topic) { Fabricate(:topic, allowed_groups: [group]) } + let!(:allowed_user) { Fabricate(:user) } + let!(:allowed_group_user) { Fabricate(:user) } + let!(:moderator) { Fabricate(:user, moderator: true) } + let!(:rando) { Fabricate(:user) } + + before do + topic.allowed_users << allowed_user + group.users << allowed_group_user + end + + it 'includes allowed_users' do + expect(topic.all_allowed_users).to include allowed_user + end + + it 'includes allowed_group_users' do + expect(topic.all_allowed_users).to include allowed_group_user + end + + it 'includes moderators if flagged and a pm' do + topic.stubs(:has_flags?).returns(true) + topic.stubs(:private_message?).returns(true) + expect(topic.all_allowed_users).to include moderator + end + + it 'does not include moderators if pm without flags' do + topic.stubs(:private_message?).returns(true) + expect(topic.all_allowed_users).not_to include moderator + end + + it 'does not include moderators for regular topic' do + expect(topic.all_allowed_users).not_to include moderator + end + + it 'does not include randos' do + expect(topic.all_allowed_users).not_to include rando + end + end + describe '#listable_count_per_day' do before(:each) do Timecop.freeze From fe901163cc450aab8c652ffd976399bbf3c3a683 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 29 Oct 2015 13:44:08 -0400 Subject: [PATCH 068/114] FIX: Fetch honeypot on `init` not name change which was error prone --- .../discourse/controllers/create-account.js.es6 | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/create-account.js.es6 b/app/assets/javascripts/discourse/controllers/create-account.js.es6 index b93363cb3b..b98a3285ac 100644 --- a/app/assets/javascripts/discourse/controllers/create-account.js.es6 +++ b/app/assets/javascripts/discourse/controllers/create-account.js.es6 @@ -1,6 +1,7 @@ import debounce from 'discourse/lib/debounce'; import ModalFunctionality from 'discourse/mixins/modal-functionality'; import { setting } from 'discourse/lib/computed'; +import { on } from 'ember-addons/ember-computed-decorators'; export default Ember.Controller.extend(ModalFunctionality, { needs: ['login'], @@ -78,10 +79,6 @@ export default Ember.Controller.extend(ModalFunctionality, { // Validate the name. nameValidation: function() { - if (this.get('accountPasswordConfirm') === 0) { - this.fetchConfirmationValue(); - } - if (Discourse.SiteSettings.full_name_required && Ember.isEmpty(this.get('accountName'))) { return Discourse.InputValidation.create({ failed: true }); } @@ -335,11 +332,11 @@ export default Ember.Controller.extend(ModalFunctionality, { }); }.property('accountPassword', 'rejectedPasswords.@each', 'accountUsername', 'accountEmail'), + @on('init') fetchConfirmationValue() { - const createAccountController = this; - return Discourse.ajax('/users/hp.json').then(function (json) { - createAccountController.set('accountPasswordConfirm', json.value); - createAccountController.set('accountChallenge', json.challenge.split("").reverse().join("")); + return Discourse.ajax('/users/hp.json').then(json => { + this.set('accountPasswordConfirm', json.value); + this.set('accountChallenge', json.challenge.split("").reverse().join("")); }); }, From c68cbe700ba50cc85be8fcd6c8ec5f9cbe2caee3 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 29 Oct 2015 16:01:50 -0400 Subject: [PATCH 069/114] FIX: Better link extraction than `URL.regexp` --- lib/email_cook.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/email_cook.rb b/lib/email_cook.rb index d30f4b9f66..494c91fb06 100644 --- a/lib/email_cook.rb +++ b/lib/email_cook.rb @@ -1,9 +1,11 @@ # A very simple formatter for imported emails -require 'uri' - class EmailCook + def self.url_regexp + /[^\>]*((?:https?:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.])(?:[^\s()<>]+|\([^\s()<>]+\))+(?:\([^\s()<>]+\)|[^`!()\[\]{};:'".,<>?«»\s]))/ + end + def initialize(raw) @raw = raw end @@ -23,6 +25,11 @@ class EmailCook quote_buffer = "" in_quote = false else + + l.scan(regexp).each do |m| + url = m[0] + l.gsub!(url, "#{url}") + end result << l << "
      " end end @@ -32,11 +39,6 @@ class EmailCook end result.gsub!(/(
      ){3,10}/, '

      ') - - URI.extract(result).each do |m| - result.gsub!(m, "#{m}") - end - result end From 1f6e40eebe32cb35a378eae997f16e22f32815d2 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 29 Oct 2015 16:07:45 -0400 Subject: [PATCH 070/114] FIX: typo --- lib/email_cook.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/email_cook.rb b/lib/email_cook.rb index 494c91fb06..48aea34454 100644 --- a/lib/email_cook.rb +++ b/lib/email_cook.rb @@ -26,7 +26,7 @@ class EmailCook in_quote = false else - l.scan(regexp).each do |m| + l.scan(EmailCook.url_regexp).each do |m| url = m[0] l.gsub!(url, "#{url}") end From 6c6d406e6390dd1c7ea0921fae419379ce03758f Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 29 Oct 2015 16:47:27 -0400 Subject: [PATCH 071/114] Small fixes to import base. --- script/import_scripts/base.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/script/import_scripts/base.rb b/script/import_scripts/base.rb index 18da1fd7aa..ea98e73cb0 100644 --- a/script/import_scripts/base.rb +++ b/script/import_scripts/base.rb @@ -246,7 +246,7 @@ class ImportScripts::Base failed += 1 puts "Failed to create user id: #{import_id}, username: #{new_user.username}, email: #{new_user.email}" puts "user errors: #{new_user.errors.full_messages}" - puts "user_profile errors: #{new_user.user_profiler.errors.full_messages}" + puts "user_profile errors: #{new_user.user_profile.errors.full_messages}" if new_user.user_profile.present? && new_user.user_profile.errors.present? end else failed += 1 @@ -273,6 +273,10 @@ class ImportScripts::Base location = opts.delete(:location) avatar_url = opts.delete(:avatar_url) + # Allow the || operations to work with empty strings '' + opts[:name] = nil if opts[:name].blank? + opts[:username] = nil if opts[:username].blank? + opts[:name] = User.suggest_name(opts[:email]) unless opts[:name] if opts[:username].blank? || opts[:username].length < User.username_length.begin || @@ -317,7 +321,7 @@ class ImportScripts::Base u = existing end else - puts "Error on record: #{opts}" + puts "Error on record: #{opts.inspect}" raise e end end From 9f8d6b6088912295421ebd04cf865677868a45ab Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Fri, 30 Oct 2015 11:28:05 +0530 Subject: [PATCH 072/114] FIX: allow exisiting users to be invited to topic/message when enable_local_logins is disabled --- app/assets/javascripts/discourse/controllers/invite.js.es6 | 6 +++--- app/models/topic.rb | 2 +- lib/guardian.rb | 2 +- spec/components/guardian_spec.rb | 6 ------ 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/invite.js.es6 b/app/assets/javascripts/discourse/controllers/invite.js.es6 index 7befba51c2..ffaaab94d1 100644 --- a/app/assets/javascripts/discourse/controllers/invite.js.es6 +++ b/app/assets/javascripts/discourse/controllers/invite.js.es6 @@ -68,12 +68,12 @@ export default Ember.Controller.extend(ModalFunctionality, { // Show Groups? (add invited user to private group) showGroups: function() { - return this.get('isAdmin') && (Discourse.Utilities.emailValid(this.get('emailOrUsername')) || this.get('isPrivateTopic') || !this.get('invitingToTopic')) && !Discourse.SiteSettings.enable_sso && !this.get('isMessage'); + return this.get('isAdmin') && (Discourse.Utilities.emailValid(this.get('emailOrUsername')) || this.get('isPrivateTopic') || !this.get('invitingToTopic')) && !Discourse.SiteSettings.enable_sso && Discourse.SiteSettings.enable_local_logins && !this.get('isMessage'); }.property('isAdmin', 'emailOrUsername', 'isPrivateTopic', 'isMessage', 'invitingToTopic'), // Instructional text for the modal. inviteInstructions: function() { - if (Discourse.SiteSettings.enable_sso) { + if (Discourse.SiteSettings.enable_sso || !Discourse.SiteSettings.enable_local_logins) { // inviting existing user when SSO enabled return I18n.t('topic.invite_reply.sso_enabled'); } else if (this.get('isMessage')) { @@ -128,7 +128,7 @@ export default Ember.Controller.extend(ModalFunctionality, { }.property('isMessage'), placeholderKey: function() { - return Discourse.SiteSettings.enable_sso ? + return (Discourse.SiteSettings.enable_sso || !Discourse.SiteSettings.enable_local_logins) ? 'topic.invite_reply.username_placeholder' : 'topic.invite_private.email_or_username_placeholder'; }.property(), diff --git a/app/models/topic.rb b/app/models/topic.rb index 37f9ebb331..ea884ec19c 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -585,7 +585,7 @@ class Topic < ActiveRecord::Base end end - if username_or_email =~ /^.+@.+$/ && !SiteSetting.enable_sso + if username_or_email =~ /^.+@.+$/ && !SiteSetting.enable_sso && SiteSetting.enable_local_logins # rate limit topic invite RateLimiter.new(invited_by, "topic-invitations-per-day", SiteSetting.max_topic_invitations_per_day, 1.day.to_i).performed! diff --git a/lib/guardian.rb b/lib/guardian.rb index 3896a02c12..78dcee9cd5 100644 --- a/lib/guardian.rb +++ b/lib/guardian.rb @@ -212,7 +212,7 @@ class Guardian def can_invite_to?(object, group_ids=nil) return false if ! authenticated? - return false unless ( SiteSetting.enable_local_logins && (!SiteSetting.must_approve_users? || is_staff?) ) + return false unless (!SiteSetting.must_approve_users? || is_staff?) return true if is_admin? return false if (SiteSetting.max_invites_per_day.to_i == 0 && !is_staff?) return false if ! can_see?(object) diff --git a/spec/components/guardian_spec.rb b/spec/components/guardian_spec.rb index 92318aada5..5d12ef0f19 100644 --- a/spec/components/guardian_spec.rb +++ b/spec/components/guardian_spec.rb @@ -316,12 +316,6 @@ describe Guardian do expect(Guardian.new(coding_horror).can_invite_to?(topic)).to be_falsey end - it 'returns false when local logins are disabled' do - SiteSetting.stubs(:enable_local_logins).returns(false) - expect(Guardian.new(moderator).can_invite_to?(topic)).to be_falsey - expect(Guardian.new(user).can_invite_to?(topic)).to be_falsey - end - it 'returns false for normal user on private topic' do expect(Guardian.new(user).can_invite_to?(private_topic)).to be_falsey end From 106cb9874ae7e5d7b961e2d8a4b70081659175be Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Fri, 30 Oct 2015 17:21:16 +0530 Subject: [PATCH 073/114] FIX: show 404 page when user is logged out and navigates to private message --- app/controllers/application_controller.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index efea5b2f65..db2812b528 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -86,13 +86,11 @@ class ApplicationController < ActionController::Base rescue_from Discourse::NotLoggedIn do |e| raise e if Rails.env.test? - if (request.format && request.format.json?) || request.xhr? || !request.get? rescue_discourse_actions(:not_logged_in, 403, true) else - redirect_to path("/") + rescue_discourse_actions(:not_found, 404) end - end rescue_from Discourse::NotFound do From 574805b68209f9fc29dad5a60df48996638db10f Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Fri, 30 Oct 2015 18:16:52 +0530 Subject: [PATCH 074/114] Update Translations --- config/locales/client.da.yml | 18 ++++++------ config/locales/client.de.yml | 7 +++-- config/locales/client.fi.yml | 29 +++++++++++++++++--- config/locales/client.it.yml | 11 ++++++-- config/locales/client.pl_PL.yml | 16 ++++++++--- config/locales/client.pt.yml | 6 ++-- config/locales/server.fi.yml | 8 ++++++ plugins/poll/config/locales/server.da.yml | 2 ++ plugins/poll/config/locales/server.de.yml | 2 ++ plugins/poll/config/locales/server.fi.yml | 2 ++ plugins/poll/config/locales/server.pl_PL.yml | 6 ++-- plugins/poll/config/locales/server.pt.yml | 2 ++ 12 files changed, 85 insertions(+), 24 deletions(-) diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index aca4e827f1..be8dd2f0e0 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -189,7 +189,7 @@ da: user_count: "Nye brugere" active_user_count: "Aktive brugere" contact: "Kontakt os" - contact_info: "I tilfælde af kritiske situationer eller vigtige spørgsmål angående denne side, kontakt os venligst på %{contact_emal}." + contact_info: "I tilfælde af kritiske situationer eller vigtige spørgsmål angående denne side, kontakt os venligst på %{contact_info}." bookmarked: title: "Bogmærke" clear_bookmarks: "Ryd Bogmærker" @@ -278,8 +278,8 @@ da: title: "Brugere" likes_given: "Givet" likes_received: "Modtaget" - topics_entered: "Indtastet" - topics_entered_long: "Emner indtastet" + topics_entered: "Besøgte" + topics_entered_long: "Emner besøgt" time_read: "Læsetid" topic_count: "Emner" topic_count_long: "Emner oprettet" @@ -363,7 +363,7 @@ da: username: "brugernavn" trust_level: "TL" read_time: "læse tid" - topics_entered: "emner indtastet" + topics_entered: "emner besøgt" post_count: "# indlæg" confirm_delete_other_accounts: "Er du sikker på, at du vil slette disse kontoer?" user_fields: @@ -406,7 +406,7 @@ da: change: "skift" moderator: "{{user}} er moderator" admin: "{{user}} er admin" - moderator_tooltip: "Dette bruger er moderator" + moderator_tooltip: "Denne bruger er moderator" admin_tooltip: "Denne bruger er administrator" blocked_tooltip: "Brugeren er blokeret" suspended_notice: "Denne bruger er suspenderet indtil {{date}}." @@ -876,9 +876,9 @@ da: from_my_computer: "Fra min computer" from_the_web: "Fra nettet" remote_tip: "link til billede" - remote_tip_with_attachments: "link til billede eller fil ({{authorized_extensions}})" + remote_tip_with_attachments: "link til billede eller fil {{authorized_extensions}}" local_tip: "vælg billeder fra din enhed" - local_tip_with_attachments: "vælg billeder eller filer fra din enhed ({{authorized_extensions}})" + local_tip_with_attachments: "vælg billeder eller filer fra din enhed {{authorized_extensions}}" hint: "(du kan også trække og slippe ind i editoren for at uploade dem)" hint_for_supported_browsers: "du kan også bruge træk-og-slip eller indsætte billeder i editoren" uploading: "Uploader billede" @@ -916,6 +916,8 @@ da: reset_read: "Nulstil \"læst\"" delete: "Slet emner" dismiss: "Afvis" + dismiss_button: "Afvis..." + dismiss_tooltip: "Afvis kun nye indlæg eller stop med at følge emner" dismiss_body: "Vil du kun afvise nye indlæg i emnerne eller afvise emnerne helt?" dismiss_posts: "Afvis kun nye indlæg" dismiss_topics: "Afvist Emner" @@ -2395,7 +2397,7 @@ da: category: "Opret i kategorien" add_host: "Tilføj server" settings: "Indlejrings-indstillinger" - feed_settings: "Feed-instillinger" + feed_settings: "Feed-indstillinger" feed_description: "Hvis du angiver et RSS/ATOM-feed for dit site, kan det forbedre Discourses mulighed for at importere dit indhold." crawling_settings: "Robot-indstillinger" crawling_description: "Når Discourse opretter emner for dine indlæg, og der ikke er noget RSS/ATOM-feed, vil den forsøge at parse dit indhold ud fra din HTML. Det kan nogengange være en udfordring at udtrække dit indhold, så vi giver mulighed for at specificere CSS-regler for at gøre udtræk lettere." diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index a8b6ad7319..9e3387083a 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -865,9 +865,7 @@ de: from_my_computer: "Von meinem Gerät" from_the_web: "Aus dem Web" remote_tip: "Link zu Bild" - remote_tip_with_attachments: "Link zu Bild oder Datei ({{authorized_extensions}})" local_tip: "wähle auf deinem Gerät gespeicherte Bilder aus" - local_tip_with_attachments: "wähle auf deinem Gerät gespeicherte Bilder oder Dateien aus ({{authorized_extensions}})" hint: "(du kannst Dateien auch in den Editor ziehen, um diese hochzuladen)" hint_for_supported_browsers: "du kannst Bilder auch in den Editor ziehen oder diese aus der Zwischenablage einfügen" uploading: "Wird hochgeladen" @@ -904,6 +902,11 @@ de: bulk: reset_read: "Gelesene zurücksetzen" delete: "Themen löschen" + dismiss: "Ignorieren" + dismiss_button: "Ignorieren..." + dismiss_tooltip: "Nur die neuen Beiträge ignorieren oder Themen nicht mehr verfolgen" + dismiss_body: "Willst du nur die neuen Beiträge ignorieren oder sollen die Themen vollständig ignoriert werden?" + dismiss_posts: "Nur neue Beiträge ignorieren" dismiss_topics: "Themen ignorieren" dismiss_new: "Neue Themen ignorieren" toggle: "zu Massenoperationen auf Themen umschalten" diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index 28713872ff..607c95cd55 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -418,6 +418,7 @@ fi: tracked_categories: "Seuratut" tracked_categories_instructions: "Näiden alueiden kaikki uudet ketjut asetetaan automaattisesti seurantaan. Uusien viestien lukumäärä näytetään ketjun otsikon vieressä." muted_categories: "Vaimennetut" + muted_categories_instructions: "Et saa imoituksia uusista viesteistä näillä alueilla, eivätkä ne näy tuoreimmissa." delete_account: "Poista tilini" delete_account_confirm: "Oletko varma, että haluat lopullisesti poistaa käyttäjätilisi? Tätä toimintoa ei voi perua!" deleted_yourself: "Käyttäjätilisi on poistettu." @@ -556,6 +557,9 @@ fi: user: "Kutsuttu käyttäjä" sent: "Lähetetty" none: "Avoimia kutsuja ei ole." + truncated: + one: "Näytetään ensimmäinen kutsu." + other: "Näytetään ensimmäiset {{count}} kutsua." redeemed: "Hyväksytyt kutsut" redeemed_tab: "Hyväksytyt" redeemed_tab_with_count: "Hyväksytyt ({{count}})" @@ -725,6 +729,9 @@ fi: admin_not_allowed_from_ip_address: "Et voi kirjautua ylläpitäjänä tästä IP-osoitteesta." resend_activation_email: "Klikkaa tästä lähettääksesi vahvistusviestin uudelleen." sent_activation_email_again: "Lähetimme uuden vahvistusviestin sinulle osoitteeseen {{sentTo}}. Viestin saapumisessa voi kestää muutama minuutti, muista tarkastaa myös roskapostikansio." + to_continue: "Ole hyvä ja kirjaudu sisään" + preferences: "Sinun täytyy olla kirjautuneena sisään muokataksesi tilisi asetuksia" + forgot: "En muista käyttäjätilini tietoja" google: title: "Googlella" message: "Todennetaan Googlen kautta (varmista, että ponnahdusikkunoiden esto ei ole päällä)" @@ -749,6 +756,7 @@ fi: emoji_one: "Emoji One" composer: emoji: "Emoji :smile:" + more_emoji: "lisää..." options: "Asetukset" whisper: "kuiskaus" add_warning: "Tämä on virallinen varoitus." @@ -781,6 +789,7 @@ fi: show_edit_reason: "(lisää syy muokkaukselle)" reply_placeholder: "Kirjoita tähän. Käytä Markdownia, BBCodea tai HTML:ää muotoiluun. Raahaa tai liitä kuvia." view_new_post: "Katsele uutta viestiäsi." + saving: "Tallennetaan" saved: "Tallennettu!" saved_draft: "Viestiluonnos kesken. Klikkaa tähän jatkaaksesi." uploading: "Lähettää..." @@ -795,6 +804,7 @@ fi: link_description: "kirjoita linkin kuvaus tähän" link_dialog_title: "Lisää linkki" link_optional_text: "vaihtoehtoinen kuvaus" + link_placeholder: "http://esimerkki.fi \"valinnainen teksti\"" quote_title: "Lainaus" quote_text: "Lainaus" code_title: "Teksti ilman muotoiluja" @@ -809,6 +819,8 @@ fi: hr_title: "Vaakaviiva" help: "Markdown apu" toggler: "näytä tai piilota kirjoitusalue" + modal_ok: "OK" + modal_cancel: "Peruuta" admin_options_title: "Tämän ketjun vain henkilökunnalle näytettävät asetukset" auto_close: label: "Sulje ketju automaattisesti tämän ajan jälkeen:" @@ -864,9 +876,9 @@ fi: from_my_computer: "Tästä laitteesta" from_the_web: "Netistä" remote_tip: "linkki kuvaan" - remote_tip_with_attachments: "linkki kuvaan tai tiedostoon ({{authorized_extensions}})" + remote_tip_with_attachments: "linkki kuvaan tai tiedostoon {{authorized_extensions}}" local_tip: "valitse kuvia laitteeltasi" - local_tip_with_attachments: "valitse kuvia tai tiedostoja laitteeltasi ({{authorized_extensions}})" + local_tip_with_attachments: "valitse kuvia tai tiedostoja laitteeltasi {{authorized_extensions}}" hint: "(voit myös raahata ne editoriin ladataksesi ne sivustolle)" hint_for_supported_browsers: "voit myös raahata tai liittää kuvia editoriin" uploading: "Lähettää" @@ -903,7 +915,12 @@ fi: bulk: reset_read: "Palauta lukutila" delete: "Poista ketjut" - dismiss_topics: "Unohda ketjut" + dismiss: "Unohda" + dismiss_button: "Unohda..." + dismiss_tooltip: "Unohda uudet viestit tai lopeta ketjujen seuraaminen" + dismiss_body: "Haluaisitko unohtaa vain uudet viestit, vai lopettaa ilmoitukset näistä ketjuista myös jatkossa?" + dismiss_posts: "Unohda vain nämä viestit" + dismiss_topics: "Lopeta ilmoitukset ketjuista" dismiss_new: "Unohda uudet" toggle: "Vaihda useamman ketjun valintaa" actions: "Massatoiminnot" @@ -1016,7 +1033,7 @@ fi: '3_1': 'Saat ilmoituksia, koska loit tämän ketjun.' '3': 'Saat ilmoituksia, koska olet asettanut ketjun tarkkailuun.' '2_8': 'Saat ilmoituksia, koska olet asettanut tämän alueen seurantaan.' - '2_4': 'Saat ilmoituksia, koska olet kirjoittanut ketjuun viestin.' + '2_4': 'Saat ilmoituksia, koska olet kirjoittanut ketjuun.' '2_2': 'Saat ilmoituksia, koska olet asettanut ketjun seurantaan.' '2': 'Saat ilmoituksia, koska luet tätä ketjua.' '1_2': 'Saat ilmoituksen jos joku mainitsee @nimesi tai vastaa sinulle.' @@ -1047,6 +1064,7 @@ fi: description: "Et saa mitään ilmoituksia tästä keskustelusta." muted: title: "Vaimenna" + description: "Et saa ilmoituksia mistään tässä ketjussa, eikä se näy tuoreimmissa." actions: recover: "Peru ketjun poisto" delete: "Poista ketju" @@ -1263,6 +1281,7 @@ fi: revert_to_regular: "Poista henkilökunnan taustaväri" rebake: "Tee HTML uudelleen" unhide: "Poista piilotus" + change_owner: "Vaihda omistajuutta" actions: flag: 'Liputa' defer_flags: @@ -1447,6 +1466,7 @@ fi: description: "Saat ilmoituksen jos joku mainitsee @nimesi tai vastaa sinulle." muted: title: "Vaimennettu" + description: "Et saa ilmoituksia uusista ketjuista näillä alueilla, eivätkä ne näy tuoreimmissa." flagging: title: 'Kiitos avustasi yhteisön hyväksi!' private_reminder: 'liput ovat yksityisiä, ne näkyvät ainoastaan henkilökunnalle' @@ -1499,6 +1519,7 @@ fi: help: "Ketjun kiinnitys on poistettu sinulta; se näytetään tavallisessa järjestyksessä." pinned_globally: title: "Kiinnitetty koko palstalle" + help: "Tämä ketju on kiinnitetty koko palstalle; se näytetään tuoreimpien ja oman alueensa ylimpänä" pinned: title: "Kiinnitetty" help: "Tämä ketju on kiinnitetty sinulle; se näytetään alueensa ensimmäisenä" diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index be2a0d2791..dde4eca5ca 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -805,6 +805,8 @@ it: hr_title: "Linea Orizzontale" help: "Aiuto Inserimento Markdown" toggler: "nascondi o mostra il pannello di editing" + modal_ok: "Ok" + modal_cancel: "Annulla" admin_options_title: "Impostazioni dello staff opzionali per l'argomento" auto_close: label: "Tempo per auto-chiusura argomento:" @@ -860,15 +862,15 @@ it: from_my_computer: "Dal mio dispositivo" from_the_web: "Dal web" remote_tip: "collegamento all'immagine" - remote_tip_with_attachments: "collegamento ad un'immagine o a un file ({{authorized_extensions}})" local_tip: "seleziona immagini dal tuo dispositivo" - local_tip_with_attachments: "seleziona immagini o file dal tuo dispositivo ({{authorized_extensions}})" hint: "(puoi anche trascinarle nell'editor per caricarle)" uploading: "In caricamento" select_file: "Seleziona File" image_link: "collegamento a cui la tua immagine punterà" search: latest_post: "Ultimo Messaggio" + most_viewed: "I più visti" + most_liked: "I più apprezzati" select_all: "Seleziona Tutto" clear_all: "Cancella Tutto" title: "cerca argomenti, messaggi, utenti o categorie" @@ -891,6 +893,10 @@ it: bulk: reset_read: "Reimposta Lettura" delete: "Elimina Argomenti" + dismiss: "Ignora" + dismiss_button: "Ignora..." + dismiss_tooltip: "Ignora solo gli ultimi messaggi o smetti di seguire gli argomenti" + dismiss_posts: "Ignora solo gli ultimi messaggi" dismiss_topics: "Chiudi Argomenti" dismiss_new: "Chiudi Nuovo" toggle: "commuta la selezione multipla degli argomenti" @@ -1251,6 +1257,7 @@ it: revert_to_regular: "Rimuovi Colore Staff" rebake: "Ricrea HTML" unhide: "Mostra nuovamente" + change_owner: "Cambia la proprietà" actions: flag: 'Segnala' defer_flags: diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml index dccfaaf6fd..0046503174 100644 --- a/config/locales/client.pl_PL.yml +++ b/config/locales/client.pl_PL.yml @@ -836,6 +836,7 @@ pl_PL: link_description: "wprowadź tutaj opis odnośnika" link_dialog_title: "Wstaw odnośnik" link_optional_text: "opcjonalny tytuł" + link_placeholder: "http://example.com \"opcjonalny tekst\"" quote_title: "Cytat" quote_text: "Cytat" code_title: "Tekst sformatowany" @@ -850,6 +851,8 @@ pl_PL: hr_title: "Pozioma linia" help: "Pomoc formatowania Markdown" toggler: "ukryj lub pokaż panel kompozytora tekstu" + modal_ok: "OK" + modal_cancel: "Anuluj" admin_options_title: "Opcjonalne ustawienia obsługi dla tego tematu" auto_close: label: "Automatycznie zamykaj tematy po:" @@ -905,9 +908,9 @@ pl_PL: from_my_computer: "Z mojego urządzenia" from_the_web: "Z Internetu" remote_tip: "link do obrazu" - remote_tip_with_attachments: "odnośnik do obrazu lub pliku ({{authorized_extensions}})" + remote_tip_with_attachments: "link do obrazu lub pliku {{authorized_extensions}}" local_tip: "wybierz obrazy ze swojego urządzenia" - local_tip_with_attachments: "wybierz obrazy lub pliki ze swojego urządzenia ({{authorized_extensions}})" + local_tip_with_attachments: "wybierz obrazy lub pliki ze swojego urządzenia {{authorized_extensions}}" hint: "(możesz także upuścić plik z katalogu komputera w okno edytora)" hint_for_supported_browsers: "możesz też przeciągać lub wklejać grafiki do edytora" uploading: "Wgrywanie" @@ -945,8 +948,13 @@ pl_PL: bulk: reset_read: "Wyzeruj przeczytane" delete: "Usuń tematy" - dismiss_topics: "Wyczyść status termatów" - dismiss_new: "Wyczyść nieprzeczytane" + dismiss: "Wyczyść" + dismiss_button: "Wyczyść…" + dismiss_tooltip: "Wyczyść nowe wpisy lub przestań śledzić tematy" + dismiss_body: "Chcesz tylko wyczyścić nieprzeczytane wpisy, czy też zupełnie przestać śledzić wskazane tematy?" + dismiss_posts: "Wyczyść nowe wpisy" + dismiss_topics: "Wyczyść tematy" + dismiss_new: "Wyczyść nowe" toggle: "włącz grupowe zaznaczanie tematów" actions: "Operacje grupowe" change_category: "Zmień kategorię" diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index ebc8d0ef4c..c5e6a73b65 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -876,9 +876,9 @@ pt: from_my_computer: "Do meu dispositivo " from_the_web: "Da internet" remote_tip: "hiperligação para imagem" - remote_tip_with_attachments: "hiperligação para imagem ou ficheiro ({{authorized_extensions}})" + remote_tip_with_attachments: "hiperligação para imagem ou ficheiro {{authorized_extensions}}" local_tip: "selecionar imagens do seu dispositivo" - local_tip_with_attachments: "selecionar imagens ou ficheiros do seu dispositivo ({{authorized_extensions}})" + local_tip_with_attachments: "selecionar imagens ou ficheiros a partir do seu dispositivo {{authorized_extensions}}" hint: "(pode também arrastar o ficheiro para o editor para fazer o carregamento)" hint_for_supported_browsers: "pode também arrastar e largar ou colar imagens no editor" uploading: "A carregar" @@ -916,6 +916,8 @@ pt: reset_read: "Repor Leitura" delete: "Eliminar Tópicos" dismiss: "Destituir" + dismiss_button: "Destituir..." + dismiss_tooltip: "Destituir apenas novas mensagens ou parar o acompanhamento de tópicos" dismiss_body: "Gostaria de destituir apenas as novas mensagens nestes tópicos, ou destituir os tópicos por inteiro?" dismiss_posts: "Destituir Apenas Novas Mensagens" dismiss_topics: "Destituir Tópicos" diff --git a/config/locales/server.fi.yml b/config/locales/server.fi.yml index 2e5d504f26..09c5cd73ff 100644 --- a/config/locales/server.fi.yml +++ b/config/locales/server.fi.yml @@ -284,6 +284,8 @@ fi: [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Alueesta %{category}" + replace_paragraph: "(Korvaa tämä kappale lyhyellä kuvauksella uudesta alueesta. Tämä kuvaus näytetään alueen valinnan yhteydessä, joten yritä pitää se alle 200 merkin pituisenä. **Aluetta ei näytetä Keskustelualueet-sivulla ennen kuin olet muokannut tätä tekstiä tai luonut viestiketjuja.**)" + post_template: "%{replace_paragraph}\n\nKäytä seuraavat kappaleet pidempään kuvaukseen tai selvittääksesi alueen säännöt ja ohjeet:\n\n- Miksi käyttäjät valitsisivat tämän alueen? Mitä varten se on?\n\n- Kuinka se tarkkaan ottaen eroaa muista olemassa olevista alueista?\n\n- Minkälaista sisältöä alueen ketjuissa tulisi yleensä olla?\n\n- Tarvitaanko tätä aluetta? Voisiko sen yhdistää toiseen alueeseen tai siirtää toisen alueen alle?\n" errors: uncategorized_parent: "Alueettomia ei voi asettaa toisen alueen alle" self_parent: "Alue ei voi olla itsensä ylempi alue." @@ -752,6 +754,7 @@ fi: summary_likes_required: "Montako tykkäystä ketjussa pitää olla, jotta ketjun tiivistelmä otetaan käyttöön" summary_percent_filter: "Kun käyttäjä klikkaa 'Näytä ketjun tiivistelmä', näytä paras % viesteistä" summary_max_results: "Maksimimäärä viestejä, jotka näytetään ketjun tiivistelmässä" + enable_private_messages: "Salli luottamustason 1 (muokattavissa asetuksista) käyttäjien lähettää yksityisviestejä ja vastata viesteihin" enable_long_polling: "Ilmoitusten käyttämä viestiväylä voi käyttää long pollingia" long_polling_base_url: "Base URL, jota käytetään long pollingissa (kun CDN on käytössä, varmista että tähän on asetettu origin pull) esim: http://origin.site.com" long_polling_interval: "Kuinka kauan palvelimen pitäisi odottaa ennen vastaamista asiakkaalle, kun ei ole mitään dataa jota lähettää (vain kirjautuneille käyttäjille)" @@ -770,6 +773,7 @@ fi: notify_mods_when_user_blocked: "Jos käyttäjä estetään automaattisesti, lähetä viesti kaikille valvojille." flag_sockpuppets: "Jos uusi käyttäjä vastaa toisen uuden käyttäjän luomaan ketjun samasta IP osoitteesta, liputa molemmat viestit mahdolliseksi roskapostiksi." traditional_markdown_linebreaks: "Käytä perinteisiä rivinvaihtoja Markdownissa, joka vaatii kaksi perättäistä välilyöntiä rivin vaihtoon." + allow_html_tables: "Salli taulukoiden syöttäminen Markdowniin käyttäen HTML tageja. TABLE, THEAD, TD, TR, TH valkolistataan (edellyttää kaikkien taulukoita sisältävien vanhojen viestien uudelleen rakentamisen)" post_undo_action_window_mins: "Kuinka monta minuuttia käyttäjällä on aikaa perua viestiin kohdistuva toimi (tykkäys, liputus, etc)." must_approve_users: "Henkilökunnan täytyy hyväksyä kaikki uudet tilit, ennen uusien käyttäjien päästämistä sivustolle. VAROITUS: tämän asetuksen valitseminen poistaa pääsyn kaikilta jo olemassa olevilta henkilökuntaan kuulumattomilta käyttäjiltä." ga_tracking_code: "Google analytics (ga.js) seurantakoodi, esim.: UA-12345678-9; katso http://google.com/analytics" @@ -796,6 +800,7 @@ fi: topics_per_period_in_top_summary: "Kejujen lukumäärä, joka näytetään oletuksena Huiput-listauksissa." topics_per_period_in_top_page: "Kejujen lukumäärä, joka näytetään laajennetussa Huiput-listauksessa." redirect_users_to_top_page: "Ohjaa uudet ja kauan poissa olleet käyttäjät automaattisesti huiput-sivulle." + top_page_default_timeframe: "Huiput-sivun oletusaikajakso." show_email_on_profile: "Näytä käyttäjän sähköpostiosoite profiilissa (näkyy vain käyttäjälle itselleen ja henkilökunnalle)" email_token_valid_hours: "Unohtuneen salasanan / tilin vahvistamisen tokenit ovat voimassa (n) tuntia." email_token_grace_period_hours: "Unohtuneen salasanan / tilin vahvistamisen tokenit ovat vielä voimassa (n) tuntia käyttämisen jälkeen." @@ -881,6 +886,7 @@ fi: avatar_sizes: "Profiilikuvista automaattisesti luotavat koot." external_system_avatars_enabled: "Käytä ulkopuolista avatarpalvelua." external_system_avatars_url: "Ulkoisen avatarpalvelun URL. Sallitut vaihdokset ovat {username} {first_letter} {color} {size}" + default_opengraph_image_url: "Oletuksena käytettävän opengraph-kuvan URL." enable_flash_video_onebox: "Ota käyttöön swf- ja flv-linkkien (Adobe Flash) onebox-tuki. VAROITUS: saattaa lisätä tietoturvariskejä." default_invitee_trust_level: "Oletus luottamustaso (0-4) kutsutuille käyttäjille." default_trust_level: "Uusien käyttäjien oletusarvoinen luottamustaso (0-4). VAROITUS! Tämän muuttaminen altistaa roskapostille." @@ -907,6 +913,7 @@ fi: tl3_links_no_follow: "Älä poista rel=nofollow -attribuuttia linkeistä luottamustason 3 käyttäjiltä." min_trust_to_create_topic: "Ketjun luomiseksi vaadittava luottamustaso." min_trust_to_edit_wiki_post: "Wikiviestin muokkaamiseen vaadittava luottamustaso." + min_trust_to_send_messages: "Yksityisviestien luomiseen vaadittava luottamustaso" newuser_max_links: "Kuinka monta linkkiä uusi käyttäjä voi lisätä viestiin." newuser_max_images: "Kuinka monta kuvaa uusi käyttäjä voi lisätä viestiin." newuser_max_attachments: "Kuinka monta liitettä uusi käyttäjä voi lisätä viestiin." @@ -1027,6 +1034,7 @@ fi: embed_username_key_from_feed: "Avain, jolla erotetaan Discourse-käyttäjänimi syötteestä." embed_truncate: "Typistä upotetut viestit." embed_post_limit: "Upotettavien viestien maksimimäärä." + embed_username_required: "Käyttäjänimi vaaditaan ketjun luomiseksi." embed_whitelist_selector: "CSS valitsin elementeille, jotka sallitaan upotetuissa viesteissä." embed_blacklist_selector: "CSS valitstin elementeille, jotka poistetaan upotetuista viesteistä." notify_about_flags_after: "Jos liputuksia ei ole käsitelty näin moneen tuntiin, lähetä sähköposti contact_email osoitteeseen. Aseta 0 ottaaksesi pois käytöstä." diff --git a/plugins/poll/config/locales/server.da.yml b/plugins/poll/config/locales/server.da.yml index 1e32f4ef13..0ad4aa946a 100644 --- a/plugins/poll/config/locales/server.da.yml +++ b/plugins/poll/config/locales/server.da.yml @@ -35,3 +35,5 @@ da: poll_must_be_open_to_vote: "Afstemning skal være åben for at kunne stemme." topic_must_be_open_to_toggle_status: "Emnet skal være åbent for at ændre status." only_staff_or_op_can_toggle_status: "Kun personalet eller emnets opretter kan ændre status for en afstemning" + email: + link_to_poll: "Klik for at se afstemningen." diff --git a/plugins/poll/config/locales/server.de.yml b/plugins/poll/config/locales/server.de.yml index ade4a865a7..e1672ac3d2 100644 --- a/plugins/poll/config/locales/server.de.yml +++ b/plugins/poll/config/locales/server.de.yml @@ -35,3 +35,5 @@ de: poll_must_be_open_to_vote: "Die Umfrage muss zum Abstimmen gestartet sein." topic_must_be_open_to_toggle_status: "Damit du den Status ändern kannst, muss das Thema geöffnet sein." only_staff_or_op_can_toggle_status: "Nur Mitarbeiter und der Autor des Beitrags können den Status der Umfrage ändern." + email: + link_to_poll: "Klicke hier, um die Umfrage zu sehen." diff --git a/plugins/poll/config/locales/server.fi.yml b/plugins/poll/config/locales/server.fi.yml index 7abf82d557..775e250db2 100644 --- a/plugins/poll/config/locales/server.fi.yml +++ b/plugins/poll/config/locales/server.fi.yml @@ -35,3 +35,5 @@ fi: poll_must_be_open_to_vote: "Vain avoimessa kyselyssä voi äänestää." topic_must_be_open_to_toggle_status: "Vain avoimessa ketjussa voi muuttaa äänestyksen tilaa." only_staff_or_op_can_toggle_status: "Vain henkilökunta tai kyselyn laatija voi muuttaa äänestyksen tilaa." + email: + link_to_poll: "Siirry äänestykseen klikkaamalla tästä" diff --git a/plugins/poll/config/locales/server.pl_PL.yml b/plugins/poll/config/locales/server.pl_PL.yml index a347697da9..0f2b9a368b 100644 --- a/plugins/poll/config/locales/server.pl_PL.yml +++ b/plugins/poll/config/locales/server.pl_PL.yml @@ -24,8 +24,8 @@ pl_PL: other: "Ankieta %{name} musi posiadać mniej niż %{count} opcji." default_poll_must_have_different_options: "Ankieta musi posiadać kilka różnych opcji do wyboru." named_poll_must_have_different_options: "Ankieta %{name} musi posiadać kilka różnych opcji do wyboru." - default_poll_with_multiple_choices_has_invalid_parameters: "Sonda wielokrotnego wyboru posiada nieprawidłowe parametry." - named_poll_with_multiple_choices_has_invalid_parameters: "Sonda wielokrotnego wyboru o nazwie %{name} posiada nieprawidłowe parametry." + default_poll_with_multiple_choices_has_invalid_parameters: "Ankieta wielokrotnego wyboru posiada nieprawidłowe parametry." + named_poll_with_multiple_choices_has_invalid_parameters: "Ankieta wielokrotnego wyboru o nazwie %{name} posiada nieprawidłowe parametry." requires_at_least_1_valid_option: "Musisz wybrać co najmniej 1 poprawną opcje." cannot_change_polls_after_5_minutes: "Po upływie 5 minut ankiety nie mogą być zmieniane." op_cannot_edit_options_after_5_minutes: "Po upływie 5 minut nie można dodawać lub usuwać opcji wyboru w ankietach. Skontaktuj się z moderatorem jeśli naprawdę musisz zmienić opcję w tej ankiecie." @@ -37,3 +37,5 @@ pl_PL: poll_must_be_open_to_vote: "Głosowanie jest możliwe tylko w otwartych ankietach." topic_must_be_open_to_toggle_status: "Zmiana statusu jest możliwa jedynie w otwartych tematach." only_staff_or_op_can_toggle_status: "Status może być zmieniony przez autora wpisu lub członka załogi serwisu." + email: + link_to_poll: "Kliknij, aby zobaczyć ankietę." diff --git a/plugins/poll/config/locales/server.pt.yml b/plugins/poll/config/locales/server.pt.yml index 24704a455b..6a8c13a7e5 100644 --- a/plugins/poll/config/locales/server.pt.yml +++ b/plugins/poll/config/locales/server.pt.yml @@ -35,3 +35,5 @@ pt: poll_must_be_open_to_vote: "Votação tem que estar aberta para votar." topic_must_be_open_to_toggle_status: "O tópico tem que estar aberto para alternar o estado." only_staff_or_op_can_toggle_status: "Apenas um membro do pessoal ou o autor original pode alternar o estado da votação." + email: + link_to_poll: "Clique para ver a votação." From bde4bc52d75e54e05138ade52e371cd12fdd3d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 30 Oct 2015 16:50:46 +0100 Subject: [PATCH 075/114] UX: use 'last poster' avatar for mobile topics list --- app/assets/javascripts/discourse/models/topic.js.es6 | 2 +- .../discourse/templates/mobile/list/topic_list_item.raw.hbs | 4 ++-- app/assets/stylesheets/mobile/topic-list.scss | 6 +----- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index d8560778bb..554546acba 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -8,7 +8,7 @@ const Topic = RestModel.extend({ message: null, errorLoading: false, - creator: Ember.computed.alias("posters.firstObject.user"), + lastPoster: Ember.computed.alias("posters.lastObject.user"), @computed('fancy_title') fancyTitle(title) { diff --git a/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs b/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs index 5de5683412..7a671458ff 100644 --- a/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs @@ -1,6 +1,6 @@ -
      - {{avatar topic.creator imageSize="large"}} +
      diff --git a/app/assets/stylesheets/mobile/topic-list.scss b/app/assets/stylesheets/mobile/topic-list.scss index f9b6990e4e..7104e59541 100644 --- a/app/assets/stylesheets/mobile/topic-list.scss +++ b/app/assets/stylesheets/mobile/topic-list.scss @@ -63,12 +63,8 @@ .topic-list { - .creator { - margin-left: 5px; - } - .right { - margin-left: 60px; + margin-left: 55px; } > tbody > tr { From 85da9c6ff87e5c4d083d66a0384b7d33f645837f Mon Sep 17 00:00:00 2001 From: Seth Reeser Date: Fri, 30 Oct 2015 13:31:48 -0400 Subject: [PATCH 076/114] Digital Ocean should be DigitalOcean https://www.digitalocean.com/legal/terms/ --- docs/INSTALL-cloud.md | 6 +++--- spec/fixtures/emails/html_only.eml | 2 +- test/javascripts/fixtures/search-fixtures.js.es6 | 4 ++-- test/javascripts/fixtures/top_fixture.js.es6 | 2 +- test/javascripts/fixtures/user_fixtures.js.es6 | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/INSTALL-cloud.md b/docs/INSTALL-cloud.md index 96a4a3fdea..d3387f1df5 100644 --- a/docs/INSTALL-cloud.md +++ b/docs/INSTALL-cloud.md @@ -1,8 +1,8 @@ -**Set up Discourse in the cloud in under 30 minutes** with zero knowledge of Rails or Linux shell using our [Discourse Docker image][dd]. We recommend [Digital Ocean][do], but these steps will work on any Docker-compatible cloud provider or local server. +**Set up Discourse in the cloud in under 30 minutes** with zero knowledge of Rails or Linux shell using our [Discourse Docker image][dd]. We recommend [DigitalOcean][do], but these steps will work on any Docker-compatible cloud provider or local server. # Create New Cloud Server -[Sign up for Digital Ocean][do], update billing info, then create your new cloud server. +[Sign up for DigitalOcean][do], update billing info, then create your new cloud server. - Enter your domain `discourse.example.com` as the name. @@ -22,7 +22,7 @@ Connect to your server via SSH, or use [Putty][put] on Windows: Replace `192.168.1.1` with the IP address of your server. -You will be asked for permission to connect, type `yes`, then enter the root password from the email Digital Ocean sent you when the server was set up. You may be prompted to change the root password, too. +You will be asked for permission to connect, type `yes`, then enter the root password from the email DigitalOcean sent you when the server was set up. You may be prompted to change the root password, too. diff --git a/spec/fixtures/emails/html_only.eml b/spec/fixtures/emails/html_only.eml index 561b8db2c7..db88f2c388 100644 --- a/spec/fixtures/emails/html_only.eml +++ b/spec/fixtures/emails/html_only.eml @@ -80,7 +80,7 @@ king"

      Grizzly B just sent you a private message

      -

      Log in to our EC2 instance -or- log into a new Digital Ocean instanc= +


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


      Please visit this link to respond: 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}]}]}}}; +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 DigitalOcean using Docker","fancy_title":"Beginners Guide to Deploy Discourse on DigitalOcean 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/fixtures/user_fixtures.js.es6 b/test/javascripts/fixtures/user_fixtures.js.es6 index a84974b695..7a24b9b546 100644 --- a/test/javascripts/fixtures/user_fixtures.js.es6 +++ b/test/javascripts/fixtures/user_fixtures.js.es6 @@ -1,6 +1,6 @@ /*jshint maxlen:10000000 */ export default { "/users/eviltrout.json": {"user_badges":[{"id":5870,"granted_at":"2014-05-16T02:39:38.388Z","badge_id":4,"user_id":19,"granted_by_id":-1},{"id":40673,"granted_at":"2014-03-31T14:23:18.060Z","post_id":7241,"post_number":19,"badge_id":23,"user_id":19,"granted_by_id":-1,"topic_id":3153},{"id":5868,"granted_at":"2014-05-16T02:39:38.380Z","badge_id":3,"user_id":19,"granted_by_id":-1}],"badges":[{"id":4,"name":"Leader","description":null,"grant_count":7,"allow_title":true,"multiple_grant":false,"icon":"fa-user","image":null,"listable":true,"enabled":true,"badge_grouping_id":4,"system":true,"badge_type_id":1},{"id":23,"name":"Great Share","description":null,"grant_count":14,"allow_title":false,"multiple_grant":true,"icon":"fa-certificate","image":null,"listable":true,"enabled":true,"badge_grouping_id":2,"system":true,"badge_type_id":1},{"id":3,"name":"Regular","description":null,"grant_count":30,"allow_title":true,"multiple_grant":false,"icon":"fa-user","image":null,"listable":true,"enabled":true,"badge_grouping_id":4,"system":true,"badge_type_id":2}],"badge_types":[{"id":1,"name":"Gold","sort_order":9},{"id":2,"name":"Silver","sort_order":8},{"id":3,"name":"Bronze","sort_order":7}],"users":[{"id":19,"username":"eviltrout","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/eviltrout/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png"},{"id":-1,"username":"system","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/system/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png"}],"topics":[{"id":3153,"title":"Is it better for Discourse to use JavaScript or CoffeeScript?","fancy_title":"Is it better for Discourse to use JavaScript or CoffeeScript?","slug":"is-it-better-for-discourse-to-use-javascript-or-coffeescript","posts_count":56}],"user":{"id":19,"username":"eviltrout","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/eviltrout/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png","name":"Robin Ward","email":"robin.ward@gmail.com","last_posted_at":"2015-05-07T15:23:35.074Z","last_seen_at":"2015-05-13T14:34:23.188Z","bio_raw":"Co-founder of Discourse. Previously, I created Forumwarz. Follow me on Twitter.","bio_cooked":"

      Co-founder of Discourse. Previously, I created Forumwarz. Follow me on Twitter.

      ","created_at":"2013-02-03T15:19:22.704Z","website":"http://eviltrout.com","location":"Toronto","can_edit":false,"can_edit_username":true,"can_edit_email":true,"can_edit_name":true,"stats":[{"action_type":13,"count":342,"id":null},{"action_type":12,"count":109,"id":null},{"action_type":4,"count":27,"id":null},{"action_type":5,"count":1607,"id":null},{"action_type":6,"count":771,"id":null},{"action_type":1,"count":333,"id":null},{"action_type":2,"count":2671,"id":null},{"action_type":7,"count":949,"id":null},{"action_type":9,"count":42,"id":null},{"action_type":3,"count":8,"id":null},{"action_type":11,"count":20,"id":null}],"can_send_private_messages":true,"can_send_private_message_to_user":false,"bio_excerpt":"Co-founder of Discourse. Previously, I created Forumwarz. Follow me on Twitter.","trust_level":4,"moderator":true,"admin":true,"title":"co-founder","badge_count":23,"notification_count":3244,"has_title_badges":true,"custom_fields":{},"user_fields":{"1":"33"},"pending_count":0,"post_count":1987,"can_be_deleted":false,"can_delete_all_posts":false,"locale":"","email_digests":true,"email_private_messages":true,"email_direct":true,"email_always":true,"digest_after_days":7,"mailing_list_mode":false,"auto_track_topics_after_msecs":60000,"new_topic_duration_minutes":1440,"external_links_in_new_tab":false,"dynamic_favicon":true,"enable_quoting":true,"muted_category_ids":[],"tracked_category_ids":[],"watched_category_ids":[3],"private_messages_stats":{"all":101,"mine":13,"unread":3},"disable_jump_reply":false,"gravatar_avatar_upload_id":5275,"custom_avatar_upload_id":1573,"card_image_badge":"https://meta-discourse.global.ssl.fastly.net/uploads/default/36220/15b19c80dd99d5a5.png","card_image_badge_id":120,"muted_usernames":[],"invited_by":{"id":1,"username":"sam","uploaded_avatar_id":null,"avatar_template":"/letter_avatar/sam/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png"},"custom_groups":[{"id":44,"automatic":false,"name":"ubuntu","user_count":11,"alias_level":0,"visible":true,"automatic_membership_email_domains":null,"automatic_membership_retroactive":false,"primary_group":false,"title":null},{"id":47,"automatic":false,"name":"discourse","user_count":7,"alias_level":0,"visible":true,"automatic_membership_email_domains":null,"automatic_membership_retroactive":false,"primary_group":false,"title":null}],"featured_user_badge_ids":[5870,40673,5868],"card_badge":{"id":120,"name":"Garbage Man","description":"This Discourse developer successfully called something \"garbage!\"","grant_count":3,"allow_title":false,"multiple_grant":false,"icon":"https://meta-discourse.global.ssl.fastly.net/uploads/default/36220/15b19c80dd99d5a5.png","image":"https://meta-discourse.global.ssl.fastly.net/uploads/default/36220/15b19c80dd99d5a5.png","listable":false,"enabled":false,"badge_grouping_id":8,"system":false,"badge_type_id":3}}}, -"/user_actions.json": {"user_actions":[{"action_type":7,"created_at":"2014-01-16T14:13:05Z","excerpt":"So again, \n\nWhat is the problem?\n\nI need to check user_trust_level , i get the 'username' from a form via ajax, i need to check what level he is on discourse \n\nAlso, if possible, i would like to get other details as well, like email address etc. \n\nI took a look at : https://github.com/discourse/dis…","avatar_template":"//www.gravatar.com/avatar/bdab7e61b3191e483492fd680f563fed.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/bdab7e61b3191e483492fd680f563fed.png?s={size}&r=pg&d=identicon","slug":"how-to-check-the-user-level-via-ajax","topic_id":11993,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":1,"reply_to_post_number":null,"username":"Abhishek_Gupta","name":"Abhishek Gupta","user_id":8021,"acting_username":"Abhishek_Gupta","acting_name":"Abhishek Gupta","acting_user_id":8021,"title":"How to check the user level via ajax?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-15T16:53:49Z","excerpt":"A good fix would be to have the ERB template do an if statement. We'd happily accept a PR that did this if you feel up to it: \n\n <% if SiteSetting.logo_url.present? %>\n display logo html\n<% else %>\n display title html\n<% end %>","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"users-activate-account-pulling-blank-logo-instead-of-defaulting-to-h2","topic_id":10911,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"/users/activate-account pulling blank logo instead of defaulting to h2","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-15T15:21:37Z","excerpt":"A good fix would be to have the ERB template do an if statement. We'd happily accept a PR that did this if you feel up to it: \n\n <% if SiteSetting.logo_url.present? %>\n display logo html\n<% else %>\n display title html\n<% end %>","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"users-activate-account-pulling-blank-logo-instead-of-defaulting-to-h2","topic_id":10911,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"/users/activate-account pulling blank logo instead of defaulting to h2","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-15T12:22:12Z","excerpt":"OK - i see what you mean. From the piwik code I should add: \n\n_paq.push(["setDocumentTitle", document.domain + "/" + document.title]);\n\n? \n\nUnfortunately I have had to give up on Piwik for now because I have switched the forum to SSL on a free cert and have used up the free subdomain for the forum. …","avatar_template":"//localhost:3000/uploads/default/avatars/2a8/a3c/8fddcac642/{size}.jpg","acting_avatar_template":"//localhost:3000/uploads/default/avatars/2a8/a3c/8fddcac642/{size}.jpg","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":26,"reply_to_post_number":25,"username":"citkane","name":"Michael Jonker","user_id":7604,"acting_username":"citkane","acting_name":"Michael Jonker","acting_user_id":7604,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-15T11:16:36Z","excerpt":"@eviltrout recently added support for multiple API keys [wink] \n\n[]","avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"allow-for-multiple-api-keys","topic_id":7444,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":null,"username":"zogstrip","name":"Régis Hanol","user_id":1995,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Allow for multiple API Keys","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-15T10:58:46Z","excerpt":"@eviltrout added a tooltip when you click on the user's avatar which allows you to show the posts made by that user \n\n[image]","avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"to-group-posts-by-a-user","topic_id":7412,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":4,"reply_to_post_number":3,"username":"zogstrip","name":"Régis Hanol","user_id":1995,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"To group posts by a user","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-15T10:36:15Z","excerpt":"@eviltrout implemented per-user API key a while ago [wink] \n\n [image]\nTopics_-_Discourse_Meta-5.png884x339 29.6 KB\n","avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"auth-using-rest-api","topic_id":5937,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"zogstrip","name":"Régis Hanol","user_id":1995,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Auth using REST API?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-15T09:55:17Z","excerpt":"@eviltrout has recently introduced this feature and has even blogged about it: \n\n \n \n \n \n eviltrout.com\n \n \n \n \n \n Hiding Offscreen Content in Ember.js - Evil Trout's Blog","avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"infinite-scrolling-reusing-dom-nodes","topic_id":5186,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":null,"username":"zogstrip","name":"Régis Hanol","user_id":1995,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Infinite scrolling: Reusing DOM nodes","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-15T00:54:32Z","excerpt":"You can retrieve a user's JSON by making a call to /users/username.json but that assumes you know the user's username. If that's impossible, I would be happy to accept a PR that would return the current user JSON from /session/current-user or something like that. \n\nAdditionally, if you're looking to…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/035d12bad251759d8fbc9fb10574d1f6.png?s={size}&r=pg&d=identicon","slug":"get-current-user-information-via-json","topic_id":11959,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"watchmanmonitor","acting_name":"Watchman Monitoring","acting_user_id":8085,"title":"Get current user information via JSON","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-14T21:59:51Z","excerpt":"You can retrieve a user's JSON by making a call to /users/username.json but that assumes you know the user's username. If that's impossible, I would be happy to accept a PR that would return the current user JSON from /session/current-user or something like that. \n\nAdditionally, if you're looking to…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/9cfd2536afac32d209335b092094c12c.png?s={size}&r=pg&d=identicon","slug":"get-current-user-information-via-json","topic_id":11959,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"znation","acting_name":"znation","acting_user_id":8163,"title":"Get current user information via JSON","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-14T21:46:50Z","excerpt":"Okay I've fixed the https [point_right] http links on the server side and in the Javascript click tracking as @BhaelOchon pointed out. \n\nLet me know if you find anything else broken.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"broken-links-possibly-related-to-https","topic_id":11831,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":18,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Broken links, possibly related to HTTPS","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-14T21:43:28Z","excerpt":"Thanks for your help @eviltrout! I will consider making that change and sending a pull request. I may not get to it for a while. \n\nI am embedding Discourse on another site and it is mostly going well. I have indeed been using your blog for inspiration.","avatar_template":"//www.gravatar.com/avatar/9cfd2536afac32d209335b092094c12c.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/9cfd2536afac32d209335b092094c12c.png?s={size}&r=pg&d=identicon","slug":"get-current-user-information-via-json","topic_id":11959,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"znation","name":"znation","user_id":8163,"acting_username":"znation","acting_name":"znation","acting_user_id":8163,"title":"Get current user information via JSON","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-14T21:21:52Z","excerpt":"Okay I've fixed the https [point_right] http links on the server side and in the Javascript click tracking as @BhaelOchon pointed out. \n\nLet me know if you find anything else broken.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"broken-links-possibly-related-to-https","topic_id":11831,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":18,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Broken links, possibly related to HTTPS","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T21:03:07Z","excerpt":"Okay I've fixed the https [point_right] http links on the server side and in the Javascript click tracking as @BhaelOchon pointed out. \n\nLet me know if you find anything else broken.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"broken-links-possibly-related-to-https","topic_id":11831,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":18,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Broken links, possibly related to HTTPS","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-14T20:42:51Z","excerpt":"You can retrieve a user's JSON by making a call to /users/username.json but that assumes you know the user's username. If that's impossible, I would be happy to accept a PR that would return the current user JSON from /session/current-user or something like that. \n\nAdditionally, if you're looking to…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"get-current-user-information-via-json","topic_id":11959,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Get current user information via JSON","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T20:29:23Z","excerpt":"You can retrieve a user's JSON by making a call to /users/username.json but that assumes you know the user's username. If that's impossible, I would be happy to accept a PR that would return the current user JSON from /session/current-user or something like that. \n\nAdditionally, if you're looking to…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"get-current-user-information-via-json","topic_id":11959,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Get current user information via JSON","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T19:20:28Z","excerpt":"Perhaps the ['trackpageView'] is not the correct API call? We can probably send more information across such as the URL.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":25,"reply_to_post_number":24,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T19:19:46Z","excerpt":"Nope but I bet you can find one!","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"how-far-to-take-user-documentation","topic_id":11943,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":4,"reply_to_post_number":3,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"How far to take user documentation?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-14T18:37:05Z","excerpt":"I'd be glad to write a pull request to take use there. Is there a specific part of their documentation you have in mind?","avatar_template":"//www.gravatar.com/avatar/035d12bad251759d8fbc9fb10574d1f6.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/035d12bad251759d8fbc9fb10574d1f6.png?s={size}&r=pg&d=identicon","slug":"how-far-to-take-user-documentation","topic_id":11943,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"watchmanmonitor","name":"Watchman Monitoring","user_id":8085,"acting_username":"watchmanmonitor","acting_name":"Watchman Monitoring","acting_user_id":8085,"title":"How far to take user documentation?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-14T16:04:28Z","excerpt":"Thanks @eviltrout , the code in the 'bottom of pages' now reads: \n\n<script type="text/javascript">\nDiscourse.PageTracker.current().on('change', function() {\n console.log('tracked!')\n _paq.push(['trackPageView']);\n});\n</script>\n\nThe console is logging 'tracked!' and piwik is logging for each page c…","avatar_template":"//localhost:3000/uploads/default/avatars/2a8/a3c/8fddcac642/{size}.jpg","acting_avatar_template":"//localhost:3000/uploads/default/avatars/2a8/a3c/8fddcac642/{size}.jpg","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":23,"reply_to_post_number":22,"username":"citkane","name":"Michael Jonker","user_id":7604,"acting_username":"citkane","acting_name":"Michael Jonker","acting_user_id":7604,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T15:58:27Z","excerpt":"This topic is now archived. It is frozen and cannot be changed in any way.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"regression-cannot-sort-topic-list","topic_id":11944,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":4,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Regression: Cannot sort topic list","deleted":false,"hidden":false,"moderator_action":true,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T15:26:57Z","excerpt":"I do think that leading them into the official rails documentation at that point is not a bad idea. Like "congratulations, everything is ready but now you'll need to understand the platform we built it in to be productive."","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"how-far-to-take-user-documentation","topic_id":11943,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"How far to take user documentation?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-14T08:28:00Z","excerpt":"I've just added the ability to list reply counts on your blog index and archive pages as you can see here. \n\nIt works with a similar API to embedding comments: \n\n <script type="text/javascript">\n var discourseUrl = "http://fishtank.eviltrout.com/";\n\n (function() {\n var d = document.createEleme…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"discourse-plugin-for-static-site-generators-like-jekyll-or-octopress","topic_id":7965,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":98,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Discourse plugin for static site generators like Jekyll or Octopress","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-14T00:21:26Z","excerpt":"In pull request 1821, @eviltrout asked: \n\n "About rails s: I wouldn't be against adding it but at what point do we stop holding their hand and expect them to know how rails works? I'm sure rails documentation could do a better job than us. Actually maybe we should just link to that? \n\nWhat point to …","avatar_template":"//www.gravatar.com/avatar/035d12bad251759d8fbc9fb10574d1f6.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/035d12bad251759d8fbc9fb10574d1f6.png?s={size}&r=pg&d=identicon","slug":"how-far-to-take-user-documentation","topic_id":11943,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":1,"reply_to_post_number":null,"username":"watchmanmonitor","name":"Watchman Monitoring","user_id":8085,"acting_username":"watchmanmonitor","acting_name":"Watchman Monitoring","acting_user_id":8085,"title":"How far to take user documentation?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-13T21:58:28Z","excerpt":"It looks uneeded, but you need to review a fair amount of code to confirm it is not needed. \n\nI am going to keep it for now cause its safer under some weird edge conditions.","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"ruby-question-about-use-of-klass-self-in-the-site-customization-rb","topic_id":11889,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Ruby question about use of klass=self in the site_customization.rb","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T21:11:32Z","excerpt":"I had to fix an issue with Google analytics so I added a new API hook that can be used. \n\nIf you add the following it should work: \n\n Discourse.PageTracker.current().on('change', function() {\n _paq.push(['trackPageView']);\n});","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-13T21:10:57Z","excerpt":"Having a look, the fix is a bit scary imho, we should fix the root issue.","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"error-after-update-to-0-9-8-1","topic_id":11903,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":11,"reply_to_post_number":10,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Error after update to 0.9.8.1","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T20:50:34Z","excerpt":"I've just added the ability to list reply counts on your blog index and archive pages as you can see here. \n\nIt works with a similar API to embedding comments: \n\n <script type="text/javascript">\n var discourseUrl = "http://fishtank.eviltrout.com/";\n\n (function() {\n var d = document.createEleme…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//localhost:3000/uploads/default/avatars/527/614/d16e1504d9/{size}.jpg","slug":"discourse-plugin-for-static-site-generators-like-jekyll-or-octopress","topic_id":7965,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":98,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"trident","acting_name":"Ben T","acting_user_id":5707,"title":"Discourse plugin for static site generators like Jekyll or Octopress","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T20:44:56Z","excerpt":"I had to fix an issue with Google analytics so I added a new API hook that can be used. \n\nIf you add the following it should work: \n\n Discourse.PageTracker.current().on('change', function() {\n _paq.push(['trackPageView']);\n});","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T20:40:21Z","excerpt":"I had to fix an issue with Google analytics so I added a new API hook that can be used. \n\nIf you add the following it should work: \n\n Discourse.PageTracker.current().on('change', function() {\n _paq.push(['trackPageView']);\n});","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T19:52:04Z","excerpt":"@Sam do you have any idea why only some people are getting this issue? I dont' mind the proposed fix but I'd prefer to know why it happens in the first place.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"error-after-update-to-0-9-8-1","topic_id":11903,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":10,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Error after update to 0.9.8.1","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T19:01:19Z","excerpt":"I've just added the ability to list reply counts on your blog index and archive pages as you can see here. \n\nIt works with a similar API to embedding comments: \n\n <script type="text/javascript">\n var discourseUrl = "http://fishtank.eviltrout.com/";\n\n (function() {\n var d = document.createEleme…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"discourse-plugin-for-static-site-generators-like-jekyll-or-octopress","topic_id":7965,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":98,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Discourse plugin for static site generators like Jekyll or Octopress","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T18:50:14Z","excerpt":"I've just added the ability to list reply counts on your blog index and archive pages as you can see here. \n\nIt works with a similar API to embedding comments: \n\n <script type="text/javascript">\n var discourseUrl = "http://fishtank.eviltrout.com/";\n\n (function() {\n var d = document.createEleme…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"discourse-plugin-for-static-site-generators-like-jekyll-or-octopress","topic_id":7965,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":98,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Discourse plugin for static site generators like Jekyll or Octopress","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T18:47:33Z","excerpt":"I am pretty sure that the denizens of SO are correct and the variable is unneeded. @sam can confirm but it seems like it was once needed for something that has since been removed and the variable declaration was left intact.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"ruby-question-about-use-of-klass-self-in-the-site-customization-rb","topic_id":11889,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Ruby question about use of klass=self in the site_customization.rb","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T18:45:41Z","excerpt":"I've just added the ability to list reply counts on your blog index and archive pages as you can see here. \n\nIt works with a similar API to embedding comments: \n\n <script type="text/javascript">\n var discourseUrl = "http://fishtank.eviltrout.com/";\n\n (function() {\n var d = document.createEleme…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"discourse-plugin-for-static-site-generators-like-jekyll-or-octopress","topic_id":7965,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":98,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Discourse plugin for static site generators like Jekyll or Octopress","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T17:19:08Z","excerpt":"@Sam do you have any idea why only some people are getting this issue? I dont' mind the proposed fix but I'd prefer to know why it happens in the first place.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/5120fc4e345db0d1a964888272073819.png?s={size}&r=pg&d=identicon","slug":"error-after-update-to-0-9-8-1","topic_id":11903,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":10,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"riking","acting_name":"Kane York","acting_user_id":6626,"title":"Error after update to 0.9.8.1","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-13T16:41:31Z","excerpt":"I'd love to see API support. @sam and @eviltrout, I can facilitate an intro to the piwik guys if you want—I've written about them before and they're typically super-responsive. Because I know you guys are totally hunting for new stuff to do [wink]","avatar_template":"//localhost:3000/uploads/default/avatars/95a/06d/c337428568/{size}.png","acting_avatar_template":"//localhost:3000/uploads/default/avatars/95a/06d/c337428568/{size}.png","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":20,"reply_to_post_number":null,"username":"Lee_Ars","name":"Lee_Ars","user_id":4457,"acting_username":"Lee_Ars","acting_name":"Lee_Ars","acting_user_id":4457,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T16:15:51Z","excerpt":"The code looks okay but it's hard to debug this way. \n\nOne thing you could do is add a: console.log('tracked!') just before line 8. Then open a developer console and see if the javascript is running properly.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":18,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T15:10:41Z","excerpt":"This is really interesting. I'd like to hear your findings.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"focus-events-track-which-window-is-the-last-active-instance-of-a-forum-edit","topic_id":11872,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":9,"reply_to_post_number":8,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Focus events: Track which window is the last active instance of a forum Edit","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T15:02:45Z","excerpt":"The code looks okay but it's hard to debug this way. \n\nOne thing you could do is add a: console.log('tracked!') just before line 8. Then open a developer console and see if the javascript is running properly.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":18,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T14:53:13Z","excerpt":"@Sam do you have any idea why only some people are getting this issue? I dont' mind the proposed fix but I'd prefer to know why it happens in the first place.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"error-after-update-to-0-9-8-1","topic_id":11903,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":10,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Error after update to 0.9.8.1","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-13T06:27:26Z","excerpt":"Can this be archived @eviltrout?","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"search-not-working-for-staff-users","topic_id":11371,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":13,"reply_to_post_number":null,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Search not working for Staff users","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-13T05:32:46Z","excerpt":"When you navigate to another topic using the "suggested topics" area we are not registering a page view with Google. \n\n@eviltrout perhaps we should do this from discourse location instead of application controller?","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"google-analytics-is-not-registering-page-views","topic_id":11914,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":1,"reply_to_post_number":null,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Google analytics is not registering page views","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-13T02:50:25Z","excerpt":"@eviltrout any ideas here, the code seems correct","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":17,"reply_to_post_number":16,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-12T22:31:35Z","excerpt":"This is an interesting approach an an interesting feature. @eviltrout your thoughts. Essentially allows us to have notifications cross tabs.","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"focus-events-track-which-window-is-the-last-active-instance-of-a-forum-edit","topic_id":11872,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":4,"reply_to_post_number":1,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Focus events: Track which window is the last active instance of a forum Edit","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-12T18:01:04Z","excerpt":"This was the link \n\nmetric_fu \n\n[metric_fu](https://github.com/metricfu/metric_fu/blob/b1bf8feb921916fc265f041efa3157a6a6530a9b/lib/metric_fu/logging/mf_debugger.rb#L24)\n\nSeems to work fine now that @eviltrout worked so hard to get us MDTest 1.1 compliant.","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"underscores-in-linked-text-can-cause-markdown-bug","topic_id":10848,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":null,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Underscores in linked text can cause markdown bug","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-12T04:14:06Z","excerpt":"Awesome plugin, but doesn't seem to work out of the box with images \n\nhttps://github.com/discourse/discourse-spoiler-alert/issues/2","avatar_template":"//localhost:3000/uploads/default/avatars/276/f19/3826efe463/{size}.jpg","acting_avatar_template":"//localhost:3000/uploads/default/avatars/276/f19/3826efe463/{size}.jpg","slug":"brand-new-plugin-interface","topic_id":8793,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":64,"reply_to_post_number":44,"username":"xrvk","name":"Eero Heikkinen","user_id":8068,"acting_username":"xrvk","acting_name":"Eero Heikkinen","acting_user_id":8068,"title":"Brand new plugin interface","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-11T23:36:11Z","excerpt":"A few things, \n\n@eviltrout myself and many others have discourse_docker hosted on digital ocean, my user cpu is usually around 2% I have plenty of capacity. \n\nI know that stonehearth and other larger scale discourse work on digital ocean fine. Officially we strongly recommend a 2GB instance, thoug…","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"performance-issue-on-digital-ocean-with-discourse-docker","topic_id":11895,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Performance issue on Digital Ocean with discourse_docker","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-11T00:58:23Z","excerpt":"Confirmed on try.discourse.org, this is still an issue. \n\n@eviltrout can you add that to your list -- unless you are a staff member you should not be able to delete (your own) posts from an archived topic.","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"archived-discussions-still-allow-posts-to-be-deleted","topic_id":6479,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":null,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Archived discussions still allow posts to be deleted","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-11T00:35:38Z","excerpt":"Agree, @eviltrout can you make sure the usercard is using the same logic as the user page in displaying profile info?","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"usercard-does-not-resize-for-obnoxiously-large-images","topic_id":11007,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":5,"reply_to_post_number":4,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Usercard does not resize for obnoxiously large images","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-11T00:34:06Z","excerpt":"@eviltrout can you make sure the "import post" button is suppressed on the user page when editing "about me"? \n\n(I agree it is like a "lose all my work" button on that page if you happen to press it..) \n\nThen I can archive this.","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"quote-post-button-should-be-disabled-or-raise-an-error-when-creating-a-new-topic","topic_id":834,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":5,"reply_to_post_number":4,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"\"Quote Post\" button should be disabled or raise an error when creating a new topic","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-10T21:00:11Z","excerpt":">\n\nLooks good now. Thanks for these fixes @eviltrout, we (and markdown-js) are now MDTest 1.1 compliant!","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"text-editor-issue-with-the-code-block","topic_id":10050,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":5,"reply_to_post_number":null,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Text Editor issue with the code block","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":1,"created_at":"2014-01-10T20:07:46Z","excerpt":"We can't repro that one, also seems a bit obscure. But thank you very much for all the reports, whenever I see a bug entry from YOU I always know it is going to be a good one based on experience here and elsewhere. [trophy]","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"security-error-on-console-noticed-on-meta","topic_id":11825,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":12,"reply_to_post_number":11,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Security Error on console (noticed on meta)","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T19:48:08Z","excerpt":"Thanks for letting us know. It turns out that by using minutely(5) instead of minutely causes ice_cube to peg a core at 100% usage. I've pushed out a fix in master.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T19:47:17Z","excerpt":"Thanks for letting us know. It turns out that by using minutely(5) instead of minutely causes ice_cube to peg a core at 100% usage. I've pushed out a fix in master.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/42776c4982dff1fa45ee8248532f8ad0.png?s={size}&r=pg&d=identicon","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"neil","acting_name":"Neil","acting_user_id":2,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T17:39:24Z","excerpt":"We should consider doing what Google Drive does: they intercept cmd-f and pop up a box that allows you to dynamically search.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/5120fc4e345db0d1a964888272073819.png?s={size}&r=pg&d=identicon","slug":"ctrl-f-search-is-interrupted-by-quotation-popup","topic_id":7114,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":12,"reply_to_post_number":11,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"riking","acting_name":"Kane York","acting_user_id":6626,"title":"Ctrl+F search is interrupted by quotation popup","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T17:29:15Z","excerpt":"Thanks for letting us know. It turns out that by using minutely(5) instead of minutely causes ice_cube to peg a core at 100% usage. I've pushed out a fix in master.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/5120fc4e345db0d1a964888272073819.png?s={size}&r=pg&d=identicon","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"riking","acting_name":"Kane York","acting_user_id":6626,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T17:24:37Z","excerpt":"Thanks for letting us know. It turns out that by using minutely(5) instead of minutely causes ice_cube to peg a core at 100% usage. I've pushed out a fix in master.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-10T17:02:35Z","excerpt":"Fixed [smile] \n\ntop - 12:02:00 up 12 days, 2:16, 1 user, load average: 0.28, 0.92, 0.97\nTasks: 115 total, 1 running, 114 sleeping, 0 stopped, 0 zombie\nCpu0 : 0.7%us, 0.3%sy, 0.0%ni, 99.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st\nCpu1 : 0.7%us, 0.3%sy, 0.0%ni, 99.0%id, 0.0%wa, 0.0%hi,…","avatar_template":"//localhost:3000/uploads/default/avatars/886/ea8/e533d87fd9/{size}.png","acting_avatar_template":"//localhost:3000/uploads/default/avatars/886/ea8/e533d87fd9/{size}.png","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":23,"reply_to_post_number":22,"username":"michaeld","name":"Michael","user_id":6548,"acting_username":"michaeld","acting_name":"Michael","acting_user_id":6548,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T16:58:12Z","excerpt":"Thanks for letting us know. It turns out that by using minutely(5) instead of minutely causes ice_cube to peg a core at 100% usage. I've pushed out a fix in master.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//localhost:3000/uploads/default/avatars/527/614/d16e1504d9/{size}.jpg","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"trident","acting_name":"Ben T","acting_user_id":5707,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null}]}, +"/user_actions.json": {"user_actions":[{"action_type":7,"created_at":"2014-01-16T14:13:05Z","excerpt":"So again, \n\nWhat is the problem?\n\nI need to check user_trust_level , i get the 'username' from a form via ajax, i need to check what level he is on discourse \n\nAlso, if possible, i would like to get other details as well, like email address etc. \n\nI took a look at : https://github.com/discourse/dis…","avatar_template":"//www.gravatar.com/avatar/bdab7e61b3191e483492fd680f563fed.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/bdab7e61b3191e483492fd680f563fed.png?s={size}&r=pg&d=identicon","slug":"how-to-check-the-user-level-via-ajax","topic_id":11993,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":1,"reply_to_post_number":null,"username":"Abhishek_Gupta","name":"Abhishek Gupta","user_id":8021,"acting_username":"Abhishek_Gupta","acting_name":"Abhishek Gupta","acting_user_id":8021,"title":"How to check the user level via ajax?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-15T16:53:49Z","excerpt":"A good fix would be to have the ERB template do an if statement. We'd happily accept a PR that did this if you feel up to it: \n\n <% if SiteSetting.logo_url.present? %>\n display logo html\n<% else %>\n display title html\n<% end %>","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"users-activate-account-pulling-blank-logo-instead-of-defaulting-to-h2","topic_id":10911,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"/users/activate-account pulling blank logo instead of defaulting to h2","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-15T15:21:37Z","excerpt":"A good fix would be to have the ERB template do an if statement. We'd happily accept a PR that did this if you feel up to it: \n\n <% if SiteSetting.logo_url.present? %>\n display logo html\n<% else %>\n display title html\n<% end %>","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"users-activate-account-pulling-blank-logo-instead-of-defaulting-to-h2","topic_id":10911,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"/users/activate-account pulling blank logo instead of defaulting to h2","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-15T12:22:12Z","excerpt":"OK - i see what you mean. From the piwik code I should add: \n\n_paq.push(["setDocumentTitle", document.domain + "/" + document.title]);\n\n? \n\nUnfortunately I have had to give up on Piwik for now because I have switched the forum to SSL on a free cert and have used up the free subdomain for the forum. …","avatar_template":"//localhost:3000/uploads/default/avatars/2a8/a3c/8fddcac642/{size}.jpg","acting_avatar_template":"//localhost:3000/uploads/default/avatars/2a8/a3c/8fddcac642/{size}.jpg","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":26,"reply_to_post_number":25,"username":"citkane","name":"Michael Jonker","user_id":7604,"acting_username":"citkane","acting_name":"Michael Jonker","acting_user_id":7604,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-15T11:16:36Z","excerpt":"@eviltrout recently added support for multiple API keys [wink] \n\n[]","avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"allow-for-multiple-api-keys","topic_id":7444,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":null,"username":"zogstrip","name":"Régis Hanol","user_id":1995,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Allow for multiple API Keys","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-15T10:58:46Z","excerpt":"@eviltrout added a tooltip when you click on the user's avatar which allows you to show the posts made by that user \n\n[image]","avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"to-group-posts-by-a-user","topic_id":7412,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":4,"reply_to_post_number":3,"username":"zogstrip","name":"Régis Hanol","user_id":1995,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"To group posts by a user","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-15T10:36:15Z","excerpt":"@eviltrout implemented per-user API key a while ago [wink] \n\n [image]\nTopics_-_Discourse_Meta-5.png884x339 29.6 KB\n","avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"auth-using-rest-api","topic_id":5937,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"zogstrip","name":"Régis Hanol","user_id":1995,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Auth using REST API?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-15T09:55:17Z","excerpt":"@eviltrout has recently introduced this feature and has even blogged about it: \n\n \n \n \n \n eviltrout.com\n \n \n \n \n \n Hiding Offscreen Content in Ember.js - Evil Trout's Blog","avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"infinite-scrolling-reusing-dom-nodes","topic_id":5186,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":null,"username":"zogstrip","name":"Régis Hanol","user_id":1995,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Infinite scrolling: Reusing DOM nodes","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-15T00:54:32Z","excerpt":"You can retrieve a user's JSON by making a call to /users/username.json but that assumes you know the user's username. If that's impossible, I would be happy to accept a PR that would return the current user JSON from /session/current-user or something like that. \n\nAdditionally, if you're looking to…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/035d12bad251759d8fbc9fb10574d1f6.png?s={size}&r=pg&d=identicon","slug":"get-current-user-information-via-json","topic_id":11959,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"watchmanmonitor","acting_name":"Watchman Monitoring","acting_user_id":8085,"title":"Get current user information via JSON","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-14T21:59:51Z","excerpt":"You can retrieve a user's JSON by making a call to /users/username.json but that assumes you know the user's username. If that's impossible, I would be happy to accept a PR that would return the current user JSON from /session/current-user or something like that. \n\nAdditionally, if you're looking to…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/9cfd2536afac32d209335b092094c12c.png?s={size}&r=pg&d=identicon","slug":"get-current-user-information-via-json","topic_id":11959,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"znation","acting_name":"znation","acting_user_id":8163,"title":"Get current user information via JSON","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-14T21:46:50Z","excerpt":"Okay I've fixed the https [point_right] http links on the server side and in the Javascript click tracking as @BhaelOchon pointed out. \n\nLet me know if you find anything else broken.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"broken-links-possibly-related-to-https","topic_id":11831,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":18,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Broken links, possibly related to HTTPS","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-14T21:43:28Z","excerpt":"Thanks for your help @eviltrout! I will consider making that change and sending a pull request. I may not get to it for a while. \n\nI am embedding Discourse on another site and it is mostly going well. I have indeed been using your blog for inspiration.","avatar_template":"//www.gravatar.com/avatar/9cfd2536afac32d209335b092094c12c.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/9cfd2536afac32d209335b092094c12c.png?s={size}&r=pg&d=identicon","slug":"get-current-user-information-via-json","topic_id":11959,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"znation","name":"znation","user_id":8163,"acting_username":"znation","acting_name":"znation","acting_user_id":8163,"title":"Get current user information via JSON","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-14T21:21:52Z","excerpt":"Okay I've fixed the https [point_right] http links on the server side and in the Javascript click tracking as @BhaelOchon pointed out. \n\nLet me know if you find anything else broken.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"broken-links-possibly-related-to-https","topic_id":11831,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":18,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Broken links, possibly related to HTTPS","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T21:03:07Z","excerpt":"Okay I've fixed the https [point_right] http links on the server side and in the Javascript click tracking as @BhaelOchon pointed out. \n\nLet me know if you find anything else broken.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"broken-links-possibly-related-to-https","topic_id":11831,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":18,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Broken links, possibly related to HTTPS","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-14T20:42:51Z","excerpt":"You can retrieve a user's JSON by making a call to /users/username.json but that assumes you know the user's username. If that's impossible, I would be happy to accept a PR that would return the current user JSON from /session/current-user or something like that. \n\nAdditionally, if you're looking to…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"get-current-user-information-via-json","topic_id":11959,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Get current user information via JSON","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T20:29:23Z","excerpt":"You can retrieve a user's JSON by making a call to /users/username.json but that assumes you know the user's username. If that's impossible, I would be happy to accept a PR that would return the current user JSON from /session/current-user or something like that. \n\nAdditionally, if you're looking to…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"get-current-user-information-via-json","topic_id":11959,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Get current user information via JSON","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T19:20:28Z","excerpt":"Perhaps the ['trackpageView'] is not the correct API call? We can probably send more information across such as the URL.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":25,"reply_to_post_number":24,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T19:19:46Z","excerpt":"Nope but I bet you can find one!","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"how-far-to-take-user-documentation","topic_id":11943,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":4,"reply_to_post_number":3,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"How far to take user documentation?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-14T18:37:05Z","excerpt":"I'd be glad to write a pull request to take use there. Is there a specific part of their documentation you have in mind?","avatar_template":"//www.gravatar.com/avatar/035d12bad251759d8fbc9fb10574d1f6.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/035d12bad251759d8fbc9fb10574d1f6.png?s={size}&r=pg&d=identicon","slug":"how-far-to-take-user-documentation","topic_id":11943,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"watchmanmonitor","name":"Watchman Monitoring","user_id":8085,"acting_username":"watchmanmonitor","acting_name":"Watchman Monitoring","acting_user_id":8085,"title":"How far to take user documentation?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-14T16:04:28Z","excerpt":"Thanks @eviltrout , the code in the 'bottom of pages' now reads: \n\n<script type="text/javascript">\nDiscourse.PageTracker.current().on('change', function() {\n console.log('tracked!')\n _paq.push(['trackPageView']);\n});\n</script>\n\nThe console is logging 'tracked!' and piwik is logging for each page c…","avatar_template":"//localhost:3000/uploads/default/avatars/2a8/a3c/8fddcac642/{size}.jpg","acting_avatar_template":"//localhost:3000/uploads/default/avatars/2a8/a3c/8fddcac642/{size}.jpg","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":23,"reply_to_post_number":22,"username":"citkane","name":"Michael Jonker","user_id":7604,"acting_username":"citkane","acting_name":"Michael Jonker","acting_user_id":7604,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T15:58:27Z","excerpt":"This topic is now archived. It is frozen and cannot be changed in any way.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"regression-cannot-sort-topic-list","topic_id":11944,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":4,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Regression: Cannot sort topic list","deleted":false,"hidden":false,"moderator_action":true,"edit_reason":null},{"action_type":5,"created_at":"2014-01-14T15:26:57Z","excerpt":"I do think that leading them into the official rails documentation at that point is not a bad idea. Like "congratulations, everything is ready but now you'll need to understand the platform we built it in to be productive."","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"how-far-to-take-user-documentation","topic_id":11943,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"How far to take user documentation?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-14T08:28:00Z","excerpt":"I've just added the ability to list reply counts on your blog index and archive pages as you can see here. \n\nIt works with a similar API to embedding comments: \n\n <script type="text/javascript">\n var discourseUrl = "http://fishtank.eviltrout.com/";\n\n (function() {\n var d = document.createEleme…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"discourse-plugin-for-static-site-generators-like-jekyll-or-octopress","topic_id":7965,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":98,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Discourse plugin for static site generators like Jekyll or Octopress","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-14T00:21:26Z","excerpt":"In pull request 1821, @eviltrout asked: \n\n "About rails s: I wouldn't be against adding it but at what point do we stop holding their hand and expect them to know how rails works? I'm sure rails documentation could do a better job than us. Actually maybe we should just link to that? \n\nWhat point to …","avatar_template":"//www.gravatar.com/avatar/035d12bad251759d8fbc9fb10574d1f6.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/035d12bad251759d8fbc9fb10574d1f6.png?s={size}&r=pg&d=identicon","slug":"how-far-to-take-user-documentation","topic_id":11943,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":1,"reply_to_post_number":null,"username":"watchmanmonitor","name":"Watchman Monitoring","user_id":8085,"acting_username":"watchmanmonitor","acting_name":"Watchman Monitoring","acting_user_id":8085,"title":"How far to take user documentation?","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-13T21:58:28Z","excerpt":"It looks uneeded, but you need to review a fair amount of code to confirm it is not needed. \n\nI am going to keep it for now cause its safer under some weird edge conditions.","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"ruby-question-about-use-of-klass-self-in-the-site-customization-rb","topic_id":11889,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":2,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Ruby question about use of klass=self in the site_customization.rb","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T21:11:32Z","excerpt":"I had to fix an issue with Google analytics so I added a new API hook that can be used. \n\nIf you add the following it should work: \n\n Discourse.PageTracker.current().on('change', function() {\n _paq.push(['trackPageView']);\n});","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-13T21:10:57Z","excerpt":"Having a look, the fix is a bit scary imho, we should fix the root issue.","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"error-after-update-to-0-9-8-1","topic_id":11903,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":11,"reply_to_post_number":10,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Error after update to 0.9.8.1","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T20:50:34Z","excerpt":"I've just added the ability to list reply counts on your blog index and archive pages as you can see here. \n\nIt works with a similar API to embedding comments: \n\n <script type="text/javascript">\n var discourseUrl = "http://fishtank.eviltrout.com/";\n\n (function() {\n var d = document.createEleme…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//localhost:3000/uploads/default/avatars/527/614/d16e1504d9/{size}.jpg","slug":"discourse-plugin-for-static-site-generators-like-jekyll-or-octopress","topic_id":7965,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":98,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"trident","acting_name":"Ben T","acting_user_id":5707,"title":"Discourse plugin for static site generators like Jekyll or Octopress","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T20:44:56Z","excerpt":"I had to fix an issue with Google analytics so I added a new API hook that can be used. \n\nIf you add the following it should work: \n\n Discourse.PageTracker.current().on('change', function() {\n _paq.push(['trackPageView']);\n});","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T20:40:21Z","excerpt":"I had to fix an issue with Google analytics so I added a new API hook that can be used. \n\nIf you add the following it should work: \n\n Discourse.PageTracker.current().on('change', function() {\n _paq.push(['trackPageView']);\n});","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T19:52:04Z","excerpt":"@Sam do you have any idea why only some people are getting this issue? I dont' mind the proposed fix but I'd prefer to know why it happens in the first place.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"error-after-update-to-0-9-8-1","topic_id":11903,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":10,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Error after update to 0.9.8.1","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T19:01:19Z","excerpt":"I've just added the ability to list reply counts on your blog index and archive pages as you can see here. \n\nIt works with a similar API to embedding comments: \n\n <script type="text/javascript">\n var discourseUrl = "http://fishtank.eviltrout.com/";\n\n (function() {\n var d = document.createEleme…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"discourse-plugin-for-static-site-generators-like-jekyll-or-octopress","topic_id":7965,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":98,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Discourse plugin for static site generators like Jekyll or Octopress","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T18:50:14Z","excerpt":"I've just added the ability to list reply counts on your blog index and archive pages as you can see here. \n\nIt works with a similar API to embedding comments: \n\n <script type="text/javascript">\n var discourseUrl = "http://fishtank.eviltrout.com/";\n\n (function() {\n var d = document.createEleme…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"discourse-plugin-for-static-site-generators-like-jekyll-or-octopress","topic_id":7965,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":98,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Discourse plugin for static site generators like Jekyll or Octopress","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T18:47:33Z","excerpt":"I am pretty sure that the denizens of SO are correct and the variable is unneeded. @sam can confirm but it seems like it was once needed for something that has since been removed and the variable declaration was left intact.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"ruby-question-about-use-of-klass-self-in-the-site-customization-rb","topic_id":11889,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Ruby question about use of klass=self in the site_customization.rb","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T18:45:41Z","excerpt":"I've just added the ability to list reply counts on your blog index and archive pages as you can see here. \n\nIt works with a similar API to embedding comments: \n\n <script type="text/javascript">\n var discourseUrl = "http://fishtank.eviltrout.com/";\n\n (function() {\n var d = document.createEleme…","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"discourse-plugin-for-static-site-generators-like-jekyll-or-octopress","topic_id":7965,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":98,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Discourse plugin for static site generators like Jekyll or Octopress","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T17:19:08Z","excerpt":"@Sam do you have any idea why only some people are getting this issue? I dont' mind the proposed fix but I'd prefer to know why it happens in the first place.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/5120fc4e345db0d1a964888272073819.png?s={size}&r=pg&d=identicon","slug":"error-after-update-to-0-9-8-1","topic_id":11903,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":10,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"riking","acting_name":"Kane York","acting_user_id":6626,"title":"Error after update to 0.9.8.1","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-13T16:41:31Z","excerpt":"I'd love to see API support. @sam and @eviltrout, I can facilitate an intro to the piwik guys if you want—I've written about them before and they're typically super-responsive. Because I know you guys are totally hunting for new stuff to do [wink]","avatar_template":"//localhost:3000/uploads/default/avatars/95a/06d/c337428568/{size}.png","acting_avatar_template":"//localhost:3000/uploads/default/avatars/95a/06d/c337428568/{size}.png","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":20,"reply_to_post_number":null,"username":"Lee_Ars","name":"Lee_Ars","user_id":4457,"acting_username":"Lee_Ars","acting_name":"Lee_Ars","acting_user_id":4457,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-13T16:15:51Z","excerpt":"The code looks okay but it's hard to debug this way. \n\nOne thing you could do is add a: console.log('tracked!') just before line 8. Then open a developer console and see if the javascript is running properly.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":18,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T15:10:41Z","excerpt":"This is really interesting. I'd like to hear your findings.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"focus-events-track-which-window-is-the-last-active-instance-of-a-forum-edit","topic_id":11872,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":9,"reply_to_post_number":8,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Focus events: Track which window is the last active instance of a forum Edit","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T15:02:45Z","excerpt":"The code looks okay but it's hard to debug this way. \n\nOne thing you could do is add a: console.log('tracked!') just before line 8. Then open a developer console and see if the javascript is running properly.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":18,"reply_to_post_number":16,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":5,"created_at":"2014-01-13T14:53:13Z","excerpt":"@Sam do you have any idea why only some people are getting this issue? I dont' mind the proposed fix but I'd prefer to know why it happens in the first place.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"error-after-update-to-0-9-8-1","topic_id":11903,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":10,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Error after update to 0.9.8.1","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-13T06:27:26Z","excerpt":"Can this be archived @eviltrout?","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"search-not-working-for-staff-users","topic_id":11371,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":13,"reply_to_post_number":null,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Search not working for Staff users","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-13T05:32:46Z","excerpt":"When you navigate to another topic using the "suggested topics" area we are not registering a page view with Google. \n\n@eviltrout perhaps we should do this from discourse location instead of application controller?","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"google-analytics-is-not-registering-page-views","topic_id":11914,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":1,"reply_to_post_number":null,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Google analytics is not registering page views","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-13T02:50:25Z","excerpt":"@eviltrout any ideas here, the code seems correct","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"support-for-piwik-analytics-as-an-alternative-to-google-analytics","topic_id":7512,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":17,"reply_to_post_number":16,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Support for Piwik Analytics as an alternative to Google Analytics","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-12T22:31:35Z","excerpt":"This is an interesting approach an an interesting feature. @eviltrout your thoughts. Essentially allows us to have notifications cross tabs.","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"focus-events-track-which-window-is-the-last-active-instance-of-a-forum-edit","topic_id":11872,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":4,"reply_to_post_number":1,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Focus events: Track which window is the last active instance of a forum Edit","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-12T18:01:04Z","excerpt":"This was the link \n\nmetric_fu \n\n[metric_fu](https://github.com/metricfu/metric_fu/blob/b1bf8feb921916fc265f041efa3157a6a6530a9b/lib/metric_fu/logging/mf_debugger.rb#L24)\n\nSeems to work fine now that @eviltrout worked so hard to get us MDTest 1.1 compliant.","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"underscores-in-linked-text-can-cause-markdown-bug","topic_id":10848,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":null,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Underscores in linked text can cause markdown bug","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-12T04:14:06Z","excerpt":"Awesome plugin, but doesn't seem to work out of the box with images \n\nhttps://github.com/discourse/discourse-spoiler-alert/issues/2","avatar_template":"//localhost:3000/uploads/default/avatars/276/f19/3826efe463/{size}.jpg","acting_avatar_template":"//localhost:3000/uploads/default/avatars/276/f19/3826efe463/{size}.jpg","slug":"brand-new-plugin-interface","topic_id":8793,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":64,"reply_to_post_number":44,"username":"xrvk","name":"Eero Heikkinen","user_id":8068,"acting_username":"xrvk","acting_name":"Eero Heikkinen","acting_user_id":8068,"title":"Brand new plugin interface","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-11T23:36:11Z","excerpt":"A few things, \n\n@eviltrout myself and many others have discourse_docker hosted on DigitalOcean, my user cpu is usually around 2% I have plenty of capacity. \n\nI know that stonehearth and other larger scale discourse work on DigitalOcean fine. Officially we strongly recommend a 2GB instance, thoug…","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","slug":"performance-issue-on-digital-ocean-with-discourse-docker","topic_id":11895,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":2,"reply_to_post_number":null,"username":"sam","name":"Sam Saffron","user_id":1,"acting_username":"sam","acting_name":"Sam Saffron","acting_user_id":1,"title":"Performance issue on DigitalOcean with discourse_docker","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-11T00:58:23Z","excerpt":"Confirmed on try.discourse.org, this is still an issue. \n\n@eviltrout can you add that to your list -- unless you are a staff member you should not be able to delete (your own) posts from an archived topic.","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"archived-discussions-still-allow-posts-to-be-deleted","topic_id":6479,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":3,"reply_to_post_number":null,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Archived discussions still allow posts to be deleted","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-11T00:35:38Z","excerpt":"Agree, @eviltrout can you make sure the usercard is using the same logic as the user page in displaying profile info?","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"usercard-does-not-resize-for-obnoxiously-large-images","topic_id":11007,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":5,"reply_to_post_number":4,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Usercard does not resize for obnoxiously large images","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-11T00:34:06Z","excerpt":"@eviltrout can you make sure the "import post" button is suppressed on the user page when editing "about me"? \n\n(I agree it is like a "lose all my work" button on that page if you happen to press it..) \n\nThen I can archive this.","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"quote-post-button-should-be-disabled-or-raise-an-error-when-creating-a-new-topic","topic_id":834,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":5,"reply_to_post_number":4,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"\"Quote Post\" button should be disabled or raise an error when creating a new topic","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":7,"created_at":"2014-01-10T21:00:11Z","excerpt":">\n\nLooks good now. Thanks for these fixes @eviltrout, we (and markdown-js) are now MDTest 1.1 compliant!","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"text-editor-issue-with-the-code-block","topic_id":10050,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":5,"reply_to_post_number":null,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Text Editor issue with the code block","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":1,"created_at":"2014-01-10T20:07:46Z","excerpt":"We can't repro that one, also seems a bit obscure. But thank you very much for all the reports, whenever I see a bug entry from YOU I always know it is going to be a good one based on experience here and elsewhere. [trophy]","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","slug":"security-error-on-console-noticed-on-meta","topic_id":11825,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":12,"reply_to_post_number":11,"username":"codinghorror","name":"Jeff Atwood","user_id":32,"acting_username":"eviltrout","acting_name":"Robin Ward","acting_user_id":19,"title":"Security Error on console (noticed on meta)","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T19:48:08Z","excerpt":"Thanks for letting us know. It turns out that by using minutely(5) instead of minutely causes ice_cube to peg a core at 100% usage. I've pushed out a fix in master.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"codinghorror","acting_name":"Jeff Atwood","acting_user_id":32,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T19:47:17Z","excerpt":"Thanks for letting us know. It turns out that by using minutely(5) instead of minutely causes ice_cube to peg a core at 100% usage. I've pushed out a fix in master.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/42776c4982dff1fa45ee8248532f8ad0.png?s={size}&r=pg&d=identicon","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"neil","acting_name":"Neil","acting_user_id":2,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T17:39:24Z","excerpt":"We should consider doing what Google Drive does: they intercept cmd-f and pop up a box that allows you to dynamically search.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/5120fc4e345db0d1a964888272073819.png?s={size}&r=pg&d=identicon","slug":"ctrl-f-search-is-interrupted-by-quotation-popup","topic_id":7114,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":12,"reply_to_post_number":11,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"riking","acting_name":"Kane York","acting_user_id":6626,"title":"Ctrl+F search is interrupted by quotation popup","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T17:29:15Z","excerpt":"Thanks for letting us know. It turns out that by using minutely(5) instead of minutely causes ice_cube to peg a core at 100% usage. I've pushed out a fix in master.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/5120fc4e345db0d1a964888272073819.png?s={size}&r=pg&d=identicon","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"riking","acting_name":"Kane York","acting_user_id":6626,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T17:24:37Z","excerpt":"Thanks for letting us know. It turns out that by using minutely(5) instead of minutely causes ice_cube to peg a core at 100% usage. I've pushed out a fix in master.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"zogstrip","acting_name":"Régis Hanol","acting_user_id":1995,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":6,"created_at":"2014-01-10T17:02:35Z","excerpt":"Fixed [smile] \n\ntop - 12:02:00 up 12 days, 2:16, 1 user, load average: 0.28, 0.92, 0.97\nTasks: 115 total, 1 running, 114 sleeping, 0 stopped, 0 zombie\nCpu0 : 0.7%us, 0.3%sy, 0.0%ni, 99.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st\nCpu1 : 0.7%us, 0.3%sy, 0.0%ni, 99.0%id, 0.0%wa, 0.0%hi,…","avatar_template":"//localhost:3000/uploads/default/avatars/886/ea8/e533d87fd9/{size}.png","acting_avatar_template":"//localhost:3000/uploads/default/avatars/886/ea8/e533d87fd9/{size}.png","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":23,"reply_to_post_number":22,"username":"michaeld","name":"Michael","user_id":6548,"acting_username":"michaeld","acting_name":"Michael","acting_user_id":6548,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null},{"action_type":2,"created_at":"2014-01-10T16:58:12Z","excerpt":"Thanks for letting us know. It turns out that by using minutely(5) instead of minutely causes ice_cube to peg a core at 100% usage. I've pushed out a fix in master.","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","acting_avatar_template":"//localhost:3000/uploads/default/avatars/527/614/d16e1504d9/{size}.jpg","slug":"sidekiq-cpu-load-since-latest-release","topic_id":9515,"target_user_id":19,"target_name":"Robin Ward","target_username":"eviltrout","post_number":22,"reply_to_post_number":null,"username":"eviltrout","name":"Robin Ward","user_id":19,"acting_username":"trident","acting_name":"Ben T","acting_user_id":5707,"title":"Sidekiq CPU load since latest release","deleted":false,"hidden":false,"moderator_action":false,"edit_reason":null}]}, "/topics/created-by/eviltrout.json": {"users":[{"id":19,"username":"eviltrout","avatar_template":"//www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon"},{"id":5460,"username":"ned","avatar_template":"//localhost:3000/uploads/default/avatars/06b/90d/3b3ea7e56b/{size}.png"},{"id":402,"username":"thebrianbarlow","avatar_template":"//www.gravatar.com/avatar/5ddf2459e8edd6cf52dfff6cb41ca70d.png?s={size}&r=pg&d=identicon"},{"id":5707,"username":"trident","avatar_template":"//localhost:3000/uploads/default/avatars/527/614/d16e1504d9/{size}.jpg"},{"id":32,"username":"codinghorror","avatar_template":"//www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon"},{"id":1995,"username":"zogstrip","avatar_template":"//www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon"},{"id":2702,"username":"ryanflorence","avatar_template":"//www.gravatar.com/avatar/749001c9fe6927c4b069a45c2a3d68f7.png?s={size}&r=pg&d=identicon"},{"id":9,"username":"tms","avatar_template":"//www.gravatar.com/avatar/3981cd271c302f5cba628c6b6d2b32ee.png?s={size}&r=pg&d=identicon"},{"id":1,"username":"sam","avatar_template":"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon"},{"id":2636,"username":"lonnon","avatar_template":"//www.gravatar.com/avatar/9489ef302fbff6c19bba507d09f8cd1d.png?s={size}&r=pg&d=identicon"}],"topic_list":{"can_create_topic":false,"draft":null,"draft_key":"new_topic","draft_sequence":null,"topics":[{"id":7764,"title":"New: Reply via Email Support!","fancy_title":"New: Reply via Email Support!","slug":"new-reply-via-email-support","posts_count":32,"reply_count":24,"highest_post_number":35,"image_url":"/uploads/meta_discourse/1227/8f4e5818dfaa56c7.png","created_at":"2013-06-25T11:58:39.000-04:00","last_posted_at":"2014-01-09T18:53:06.000-05:00","bumped":true,"bumped_at":"2014-01-09T17:09:40.000-05:00","unseen":false,"pinned":false,"visible":true,"closed":false,"archived":false,"views":2201,"like_count":46,"has_summary":false,"archetype":"regular","last_poster_username":"codinghorror","category_id":2,"posters":[{"extras":null,"description":"Original Poster","user_id":19},{"extras":null,"description":"Most Posts","user_id":5460},{"extras":null,"description":"Frequent Poster","user_id":402},{"extras":null,"description":"Frequent Poster","user_id":5707},{"extras":"latest","description":"Most Recent Poster","user_id":32}]},{"id":9318,"title":"Discourse has a new Markdown Parser!","fancy_title":"Discourse has a new Markdown Parser!","slug":"discourse-has-a-new-markdown-parser","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-08-24T14:08:06.000-04:00","last_posted_at":"2013-08-24T14:08:06.000-04:00","bumped":true,"bumped_at":"2013-08-24T14:13:25.000-04:00","unseen":false,"pinned":false,"visible":true,"closed":false,"archived":false,"views":812,"like_count":13,"has_summary":false,"archetype":"regular","last_poster_username":"eviltrout","category_id":7,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":19}]},{"id":7019,"title":"Discourse Ember Refactorings","fancy_title":"Discourse Ember Refactorings","slug":"discourse-ember-refactorings","posts_count":5,"reply_count":3,"highest_post_number":5,"image_url":null,"created_at":"2013-05-30T11:16:36.000-04:00","last_posted_at":"2013-06-02T11:22:58.000-04:00","bumped":true,"bumped_at":"2013-06-02T11:22:58.000-04:00","unseen":false,"pinned":false,"visible":true,"closed":false,"archived":false,"views":1075,"like_count":15,"has_summary":false,"archetype":"regular","last_poster_username":"eviltrout","category_id":7,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":19},{"extras":null,"description":"Most Posts","user_id":1995},{"extras":null,"description":"Frequent Poster","user_id":2702}]},{"id":4650,"title":"Migrating off Active Record Observers","fancy_title":"Migrating off Active Record Observers","slug":"migrating-off-active-record-observers","posts_count":8,"reply_count":7,"highest_post_number":8,"image_url":null,"created_at":"2013-03-11T11:26:13.000-04:00","last_posted_at":"2013-05-14T18:40:16.000-04:00","bumped":true,"bumped_at":"2013-05-14T18:40:16.000-04:00","unseen":false,"pinned":false,"visible":true,"closed":false,"archived":false,"views":377,"like_count":3,"has_summary":false,"archetype":"regular","last_poster_username":"sam","category_id":7,"posters":[{"extras":null,"description":"Original Poster","user_id":19},{"extras":null,"description":"Most Posts","user_id":9},{"extras":null,"description":"Frequent Poster","user_id":1995},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":4960,"title":"Vagrant Updates!","fancy_title":"Vagrant Updates!","slug":"vagrant-updates","posts_count":5,"reply_count":3,"highest_post_number":5,"image_url":"/plugins/emoji/images/fish.png","created_at":"2013-03-20T22:29:22.000-04:00","last_posted_at":"2013-03-21T19:06:40.000-04:00","bumped":true,"bumped_at":"2013-03-21T19:06:40.000-04:00","unseen":false,"pinned":false,"visible":true,"closed":false,"archived":false,"views":500,"like_count":4,"has_summary":false,"archetype":"regular","last_poster_username":"zogstrip","category_id":7,"posters":[{"extras":null,"description":"Original Poster","user_id":19},{"extras":null,"description":"Most Posts","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":"latest","description":"Most Recent Poster","user_id":1995}]},{"id":2918,"title":"New: Updated Docs","fancy_title":"New: Updated Docs","slug":"new-updated-docs","posts_count":3,"reply_count":2,"highest_post_number":3,"image_url":null,"created_at":"2013-02-12T12:13:02.000-05:00","last_posted_at":"2013-02-15T17:57:19.000-05:00","bumped":true,"bumped_at":"2013-02-15T17:57:19.000-05:00","unseen":false,"pinned":false,"visible":true,"closed":false,"archived":false,"views":457,"like_count":10,"has_summary":false,"archetype":"regular","last_poster_username":"eviltrout","category_id":10,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":19},{"extras":null,"description":"Most Posts","user_id":2636}]}]}} }; From fe5264f9e9fe2f2c057489e89c6c0d375316c401 Mon Sep 17 00:00:00 2001 From: Leo McArdle Date: Fri, 30 Oct 2015 18:05:54 +0000 Subject: [PATCH 077/114] filter by username in email digest preview adds a user prompt on the email digest preview page to generate a preview for a particular user also fixes some broken styling on the page --- .../controllers/admin-email-preview-digest.js.es6 | 2 +- app/assets/javascripts/admin/models/email_preview.js | 10 ++++++---- .../admin/templates/email_preview_digest.hbs | 2 ++ app/assets/stylesheets/common/admin/admin_base.scss | 8 ++++++++ app/controllers/admin/email_controller.rb | 4 +++- spec/controllers/admin/email_controller_spec.rb | 2 +- 6 files changed, 21 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/admin/controllers/admin-email-preview-digest.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-preview-digest.js.es6 index f259acba84..8a3fd0b960 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-preview-digest.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-preview-digest.js.es6 @@ -5,7 +5,7 @@ export default Ember.Controller.extend({ const model = this.get('model'); this.set('loading', true); - Discourse.EmailPreview.findDigest(this.get('lastSeen')).then(email => { + Discourse.EmailPreview.findDigest(this.get('lastSeen'), this.get('username')).then(email => { model.setProperties(email.getProperties('html_content', 'text_content')); this.set('loading', false); }); diff --git a/app/assets/javascripts/admin/models/email_preview.js b/app/assets/javascripts/admin/models/email_preview.js index b754095812..8ae9bdfcd4 100644 --- a/app/assets/javascripts/admin/models/email_preview.js +++ b/app/assets/javascripts/admin/models/email_preview.js @@ -9,18 +9,20 @@ Discourse.EmailPreview = Discourse.Model.extend({}); Discourse.EmailPreview.reopenClass({ - findDigest: function(lastSeenAt) { + findDigest: function(lastSeenAt, username) { if (Em.isEmpty(lastSeenAt)) { lastSeenAt = moment().subtract(7, 'days').format('YYYY-MM-DD'); } + if (Em.isEmpty(username)) { + username = Discourse.User.current().username; + } + return Discourse.ajax("/admin/email/preview-digest.json", { - data: {last_seen_at: lastSeenAt} + data: { last_seen_at: lastSeenAt, username: username } }).then(function (result) { return Discourse.EmailPreview.create(result); }); } }); - - diff --git a/app/assets/javascripts/admin/templates/email_preview_digest.hbs b/app/assets/javascripts/admin/templates/email_preview_digest.hbs index 83d99db35f..630260f131 100644 --- a/app/assets/javascripts/admin/templates/email_preview_digest.hbs +++ b/app/assets/javascripts/admin/templates/email_preview_digest.hbs @@ -4,6 +4,8 @@
      {{input type="date" value=lastSeen id="last-seen"}} + + {{user-selector single="true" usernames=username}}
      diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index 63c01ffed8..9046e2505a 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -213,6 +213,14 @@ td.flaggers td { display: inline-block; margin-right: 5px; } + #last-seen { + float: none; + } + .ac-wrap { + display: inline-block; + vertical-align: middle; + padding: 0; + } } .paste-users { diff --git a/app/controllers/admin/email_controller.rb b/app/controllers/admin/email_controller.rb index 8a6615f721..5ae6a32056 100644 --- a/app/controllers/admin/email_controller.rb +++ b/app/controllers/admin/email_controller.rb @@ -34,7 +34,9 @@ class Admin::EmailController < Admin::AdminController def preview_digest params.require(:last_seen_at) - renderer = Email::Renderer.new(UserNotifications.digest(current_user, since: params[:last_seen_at])) + params.require(:username) + user = User.find_by_username(params[:username]) + renderer = Email::Renderer.new(UserNotifications.digest(user, since: params[:last_seen_at])) render json: MultiJson.dump(html_content: renderer.html, text_content: renderer.text) end diff --git a/spec/controllers/admin/email_controller_spec.rb b/spec/controllers/admin/email_controller_spec.rb index 2820cb1543..68b0ee5319 100644 --- a/spec/controllers/admin/email_controller_spec.rb +++ b/spec/controllers/admin/email_controller_spec.rb @@ -66,7 +66,7 @@ describe Admin::EmailController do end it "previews the digest" do - xhr :get, :preview_digest, last_seen_at: 1.week.ago + xhr :get, :preview_digest, last_seen_at: 1.week.ago, username: user.username expect(response).to be_success end end From 21b29269d0f35626b33a56fde9bd7f978095cc4f Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 30 Oct 2015 14:12:56 -0400 Subject: [PATCH 078/114] Remove excessive text on signup CTA --- .../discourse/components/signup-cta.js.es6 | 30 ------------------- .../templates/components/signup-cta.hbs | 1 - 2 files changed, 31 deletions(-) diff --git a/app/assets/javascripts/discourse/components/signup-cta.js.es6 b/app/assets/javascripts/discourse/components/signup-cta.js.es6 index b1224306e8..b3567ac15b 100644 --- a/app/assets/javascripts/discourse/components/signup-cta.js.es6 +++ b/app/assets/javascripts/discourse/components/signup-cta.js.es6 @@ -18,36 +18,6 @@ export default Ember.Component.extend({ } }, - signupMethodsTranslated: function() { - const methods = Ember.get('Discourse.LoginMethod.all'); - const loginWithEmail = this.siteSettings.enable_local_logins; - if (this.siteSettings.enable_sso) { - return I18n.t('signup_cta.methods.sso'); - } else if (methods.length === 0) { - if (loginWithEmail) { - return I18n.t('signup_cta.methods.only_email'); - } else { - return I18n.t('signup_cta.methods.unknown'); - } - } else if (methods.length === 1) { - let providerName = methods[0].name.capitalize(); - if (providerName === "Google_oauth2") { - providerName = "Google"; - } - if (loginWithEmail) { - return I18n.t('signup_cta.methods.one_and_email', {provider: providerName}); - } else { - return I18n.t('signup_cta.methods.only_other', {provider: providerName}); - } - } else { - if (loginWithEmail) { - return I18n.t('signup_cta.methods.multiple', {count: methods.length}); - } else { - return I18n.t('signup_cta.methods.multiple_no_email', {count: methods.length}); - } - } - }.property(), - _turnOffIfHidden: function() { if (this.session.get('hideSignupCta')) { this.session.set('showSignupCta', false); diff --git a/app/assets/javascripts/discourse/templates/components/signup-cta.hbs b/app/assets/javascripts/discourse/templates/components/signup-cta.hbs index 721c38158e..e4a4ff28d2 100644 --- a/app/assets/javascripts/discourse/templates/components/signup-cta.hbs +++ b/app/assets/javascripts/discourse/templates/components/signup-cta.hbs @@ -7,7 +7,6 @@ {{else}}

      {{replace-emoji (i18n "signup_cta.intro")}}

      {{replace-emoji (i18n "signup_cta.value_prop")}}

      -

      {{signupMethodsTranslated}}

      {{d-button action="showCreateAccount" label="signup_cta.sign_up" icon="check" class="btn-primary"}} From 7eafca8f9dfeaeaf46418152fecd2da6f3f27d83 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 30 Oct 2015 15:31:31 -0400 Subject: [PATCH 079/114] Remove unused translations --- config/locales/client.en.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index aa3fdaeb67..c5b977dd1a 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -735,14 +735,6 @@ en: hidden_for_session: "OK, I'll ask you tomorrow. You can always use 'Log In' to create an account, too." intro: "Hey there! :heart_eyes: Looks like you're enjoying the discussion, but you're not signed up for an account." value_prop: "When you create an account, we remember exactly what you've read, so you always come right back where you left off. You also get notifications, here and via email, whenever new posts are made. And you can like posts to share the love. :heartbeat:" - methods: - sso: "Signing up is easy: all you need is an account on the main site." - only_email: "Signing up is easy: all you need is an email and password." - only_other: "Use your %{provider} account to sign up." - one_and_email: "Use your %{provider} account, or an email and password, to sign up." - multiple_no_email: "Signing up is easy: use any of our %{count} social logins." - multiple: "Signing up is easy: use any of our %{count} social logins, or an email and password." - unknown: "error getting supported login methods" summary: enabled_description: "You're viewing a summary of this topic: the most interesting posts as determined by the community." From 5e3da94c4646dcf614714249f6cba37df2b19cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 30 Oct 2015 22:46:46 +0100 Subject: [PATCH 080/114] FIX: prevent infinite loop in PullHotlinkedImages job --- app/jobs/regular/pull_hotlinked_images.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app/jobs/regular/pull_hotlinked_images.rb b/app/jobs/regular/pull_hotlinked_images.rb index efe7457e5c..8c5ece783d 100644 --- a/app/jobs/regular/pull_hotlinked_images.rb +++ b/app/jobs/regular/pull_hotlinked_images.rb @@ -76,12 +76,7 @@ module Jobs end post.reload - if start_raw != post.raw - # post was edited - start over (after 10 minutes) - backoff = args.fetch(:backoff, 1) + 1 - delay = SiteSetting.ninja_edit_window * args[:backoff] - Jobs.enqueue_in(delay.seconds.to_i, :pull_hotlinked_images, args.merge!(backoff: backoff)) - elsif raw != post.raw + if start_raw == post.raw && raw != post.raw changes = { raw: raw, edit_reason: I18n.t("upload.edit_reason") } # we never want that job to bump the topic options = { bypass_bump: true } From 7fbf902d094caf15c54e6ca2e590828c5755ec56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 30 Oct 2015 23:26:34 +0100 Subject: [PATCH 081/114] FIX: prevent cross-contamination of emojis in multisites --- app/assets/javascripts/discourse/lib/emoji/emoji.js.erb | 5 +++++ lib/pretty_text.rb | 2 ++ 2 files changed, 7 insertions(+) diff --git a/app/assets/javascripts/discourse/lib/emoji/emoji.js.erb b/app/assets/javascripts/discourse/lib/emoji/emoji.js.erb index 8a8fceb252..eff6b85e8e 100644 --- a/app/assets/javascripts/discourse/lib/emoji/emoji.js.erb +++ b/app/assets/javascripts/discourse/lib/emoji/emoji.js.erb @@ -12,6 +12,11 @@ Discourse.Dialect.registerEmoji = function(code, url) { extendedEmoji[code] = url; }; +// This method is used by PrettyText to reset custom emojis in multisites +Discourse.Dialect.resetEmoji = function() { + extendedEmoji = {}; +}; + Discourse.Emoji.list = function(){ var list = emoji.slice(0); _.each(extendedEmoji, function(v,k){ list.push(k); }); diff --git a/lib/pretty_text.rb b/lib/pretty_text.rb index f6e2d523b5..590576567a 100644 --- a/lib/pretty_text.rb +++ b/lib/pretty_text.rb @@ -189,6 +189,8 @@ module PrettyText end end + # reset emojis (v8 context is shared amongst multisites) + context.eval("Discourse.Dialect.resetEmoji();") # custom emojis Emoji.custom.each do |emoji| context.eval("Discourse.Dialect.registerEmoji('#{emoji.name}', '#{emoji.url}');") From 5c0fb34eee6081c1ecc8a8217f50016c5d7ae7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 30 Oct 2015 23:31:30 +0100 Subject: [PATCH 082/114] FIX: resize emoji job was generate errors --- app/jobs/regular/resize_emoji.rb | 2 +- app/models/optimized_image.rb | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/jobs/regular/resize_emoji.rb b/app/jobs/regular/resize_emoji.rb index e46879f8e0..fa30e629e7 100644 --- a/app/jobs/regular/resize_emoji.rb +++ b/app/jobs/regular/resize_emoji.rb @@ -11,7 +11,7 @@ module Jobs force_aspect_ratio: SiteSetting.enforce_square_emoji } # make sure emoji aren't too big - OptimizedImage.downsize(path, path, 100, 100, opts) + OptimizedImage.downsize(path, path, "100x100", opts) end end diff --git a/app/models/optimized_image.rb b/app/models/optimized_image.rb index c7ba61b10b..482585e8ea 100644 --- a/app/models/optimized_image.rb +++ b/app/models/optimized_image.rb @@ -142,10 +142,6 @@ class OptimizedImage < ActiveRecord::Base optimize("resize", from, to, "#{width}x#{height}", opts) end - def self.downsize(from, to, max_width, max_height, opts={}) - optimize("downsize", from, to, "#{max_width}x#{max_height}", opts) - end - def self.downsize(from, to, dimensions, opts={}) optimize("downsize", from, to, dimensions, opts) end From 54da4610249b01fa7757b9242b7a0e58d00f9a32 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Sat, 31 Oct 2015 15:31:05 +1100 Subject: [PATCH 083/114] UX: simplify Dismiss menu --- .../discourse/controllers/discovery/topics.js.es6 | 2 +- app/assets/javascripts/discourse/routes/discovery.js.es6 | 5 +++++ .../javascripts/discourse/templates/modal/dismiss-read.hbs | 5 ++--- config/locales/client.en.yml | 5 ++--- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 index 775aa1690b..6426f75524 100644 --- a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 @@ -73,7 +73,7 @@ const controllerOpts = { }, dismissReadPosts() { - showModal('dismiss-read', { title: 'topics.bulk.dismiss' }); + showModal('dismiss-read', { title: 'topics.bulk.dismiss_read' }); } }, diff --git a/app/assets/javascripts/discourse/routes/discovery.js.es6 b/app/assets/javascripts/discourse/routes/discovery.js.es6 index c0f40e7a6a..400d1df1ee 100644 --- a/app/assets/javascripts/discourse/routes/discovery.js.es6 +++ b/app/assets/javascripts/discourse/routes/discovery.js.es6 @@ -48,6 +48,11 @@ export default Discourse.Route.extend(OpenComposer, { this.openComposer(this.controllerFor("discovery/topics")); }, + dismissReadTopics(dismissTopics) { + var operationType = dismissTopics ? "topics" : "posts"; + this.controllerFor("discovery/topics").send('dismissRead', operationType); + }, + dismissRead(operationType) { this.controllerFor("discovery/topics").send('dismissRead', operationType); } diff --git a/app/assets/javascripts/discourse/templates/modal/dismiss-read.hbs b/app/assets/javascripts/discourse/templates/modal/dismiss-read.hbs index 9136970027..4213cfec4f 100644 --- a/app/assets/javascripts/discourse/templates/modal/dismiss-read.hbs +++ b/app/assets/javascripts/discourse/templates/modal/dismiss-read.hbs @@ -1,10 +1,9 @@ diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index c5b977dd1a..f48dd255bb 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1004,11 +1004,10 @@ en: reset_read: "Reset Read" delete: "Delete Topics" dismiss: "Dismiss" + dismiss_read: "Dismiss all unread" dismiss_button: "Dismiss…" dismiss_tooltip: "Dismiss just new posts or stop tracking topics" - dismiss_body: "Would you like to dismiss just the new posts in these topics, or dismiss the topics entirely?" - dismiss_posts: "Dismiss Just New Posts" - dismiss_topics: "Dismiss Topics" + also_dismiss_topics: "Stop tracking these topics? (Topics will no longer appear in the unread tab)" dismiss_new: "Dismiss New" toggle: "toggle bulk selection of topics" actions: "Bulk Actions" From 33fb870e48ed81b9542cb65b47939c9fb2d12843 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Sat, 31 Oct 2015 09:58:48 +0530 Subject: [PATCH 084/114] add Jive import script --- script/import_scripts/jive.rb | 346 ++++++++++++++++++++++++++++++++++ 1 file changed, 346 insertions(+) create mode 100644 script/import_scripts/jive.rb diff --git a/script/import_scripts/jive.rb b/script/import_scripts/jive.rb new file mode 100644 index 0000000000..bb5cedd854 --- /dev/null +++ b/script/import_scripts/jive.rb @@ -0,0 +1,346 @@ +# Jive importer +require 'nokogiri' +require 'csv' +require File.expand_path(File.dirname(__FILE__) + "/base.rb") + +class ImportScripts::Jive < ImportScripts::Base + + BATCH_SIZE = 1000 + CATEGORY_IDS = [2023,2003,2004,2042,2036,2029] # categories that should be skipped + + def initialize(path) + @path = path + super() + @bbcode_to_md = true + + puts "loading post mappings..." + @post_number_map = {} + Post.pluck(:id, :post_number).each do |post_id, post_number| + @post_number_map[post_id] = post_number + end + end + + def created_post(post) + @post_number_map[post.id] = post.post_number + super + end + + def execute + import_users + import_groups + import_group_members + import_categories + import_posts + + # Topic.update_all(closed: true) + end + + class RowResolver + def load(row) + @row = row + end + + def self.create(cols) + Class.new(RowResolver).new(cols) + end + + def initialize(cols) + cols.each_with_index do |col,idx| + self.class.send(:define_method, col) do + @row[idx] + end + end + end + end + + def load_user_batch!(users, offset, total) + if users.length > 0 + create_users(users, offset: offset, total: total) do |user| + user + end + users.clear + end + end + + def csv_parse(name) + filename = "#{@path}/#{name}.csv" + first = true + row = nil + + current_row = ""; + double_quote_count = 0 + + File.open(filename).each_line do |line| + + line.gsub!(/\\(.{1})/){|m| m[-1] == '"'? '""': m[-1]} + line.strip! + + current_row << "\n" unless current_row.empty? + current_row << line + + double_quote_count += line.scan('"').count + + if double_quote_count % 2 == 1 + next + end + + raw = begin + CSV.parse(current_row) + rescue CSV::MalformedCSVError => e + puts e.message + puts "*" * 100 + puts "Bad row skipped, line is: #{line}" + puts + puts current_row + puts + puts "double quote count is : #{double_quote_count}" + puts "*" * 100 + + current_row = "" + double_quote_count = 0 + next + end[0] + + if first + row = RowResolver.create(raw) + + current_row = "" + double_quote_count = 0 + first = false + next + end + + row.load(raw) + + yield row + + current_row = "" + double_quote_count = 0 + end + end + + def total_rows(table) + File.foreach("#{@path}/#{table}.csv").inject(0) {|c, line| c+1} - 1 + end + + def import_groups + puts "", "importing groups..." + + rows = [] + csv_parse("group") do |row| + rows << {id: row.groupid, name: row.name} + end + + create_groups(rows) do |row| + row + end + end + + def import_users + puts "", "creating users" + + count = 0 + users = [] + + total = total_rows("user") + + csv_parse("user") do |row| + + id = row.userid + + # append "-x" at the end of each email + email = "#{row.email}-x" + + # fake it + if row.email.blank? || row.email !~ /@/ + email = SecureRandom.hex << "@domain.com" + end + + name = "#{row.firstname} #{row.lastname}" + username = row.username + created_at = DateTime.parse(row.creationdate) + last_seen_at = DateTime.parse(row.lastloggedin) + is_activated = row.userenabled + + username = name if username == "NULL" + username = email.split("@")[0] if username.blank? + name = email.split("@")[0] if name.blank? + + users << { + id: id, + email: email, + name: name, + username: username, + created_at: created_at, + last_seen_at: last_seen_at, + active: is_activated.to_i == 1, + approved: is_activated.to_i == 1 + } + + count += 1 + if count % BATCH_SIZE == 0 + load_user_batch! users, count - users.length, total + end + + end + + load_user_batch! users, count, total + end + + def import_group_members + puts "", "importing group members..." + + csv_parse("group_members") do |row| + user_id = user_id_from_imported_user_id(row.userid) + group_id = group_id_from_imported_group_id(row.groupid) + + if user_id && group_id + GroupUser.find_or_create_by(user_id: user_id, group_id: group_id) + end + end + end + + def import_categories + rows = [] + + csv_parse("community") do |row| + next unless CATEGORY_IDS.include?(row.communityid.to_i) + rows << {id: row.communityid, name: "#{row.name} (#{row.communityid})"} + end + + create_categories(rows) do |row| + row + end + end + + def normalize_raw!(raw) + raw = raw.dup + raw = raw[5..-6] + + doc = Nokogiri::HTML.fragment(raw) + doc.css('img').each do |img| + img.remove if img['class'] == "jive-image" + end + + raw = doc.to_html + raw = raw[4..-1] + + raw + end + + def import_post_batch!(posts, topics, offset, total) + create_posts(posts, total: total, offset: offset) do |post| + + mapped = {} + + mapped[:id] = post[:id] + mapped[:user_id] = user_id_from_imported_user_id(post[:user_id]) || -1 + mapped[:raw] = post[:body] + mapped[:created_at] = post[:created_at] + + topic = topics[post[:topic_id]] + + unless topic + p "MISSING TOPIC #{post[:topic_id]}" + p post + next + end + + unless topic[:post_id] + mapped[:category] = category_id_from_imported_category_id(topic[:category_id]) + mapped[:title] = post[:title] + topic[:post_id] = post[:id] + else + parent = topic_lookup_from_imported_post_id(topic[:post_id]) + next unless parent + + mapped[:topic_id] = parent[:topic_id] + + reply_to_post_id = post_id_from_imported_post_id(post[:reply_id]) + if reply_to_post_id + reply_to_post_number = @post_number_map[reply_to_post_id] + if reply_to_post_number && reply_to_post_number > 1 + mapped[:reply_to_post_number] = reply_to_post_number + end + end + end + + next if topic[:deleted] or post[:deleted] + + mapped + end + + posts.clear + end + + def import_posts + puts "", "creating topics and posts" + + topic_map = {} + thread_map = {} + + csv_parse("message") do |thread| + + next unless CATEGORY_IDS.include?(thread.containerid.to_i) + + if !thread.parentmessageid + # topic + + thread_map[thread.threadid] = thread.messageid + + topic_map[thread.messageid] = { + id: thread.messageid, + topic_id: thread.messageid, + category_id: thread.containerid, + user_id: thread.userid, + title: thread.subject, + body: normalize_raw!(thread.body || thread.subject || ""), + created_at: DateTime.parse(thread.creationdate), + } + + end + end + + total = total_rows("message") + posts = [] + count = 0 + + topic_map.each do |_, topic| + posts << topic if topic[:body] + count+=1 + end + + csv_parse("message") do |thread| + # post + + next unless CATEGORY_IDS.include?(thread.containerid.to_i) + + if thread.parentmessageid + row = { + id: thread.messageid, + topic_id: thread_map["#{thread.threadid}"], + user_id: thread.userid, + title: thread.subject, + body: normalize_raw!(thread.body), + created_at: DateTime.parse(thread.creationdate) + } + posts << row + count+=1 + + if posts.length > 0 && posts.length % BATCH_SIZE == 0 + import_post_batch!(posts, topic_map, count - posts.length, total) + end + end + end + + import_post_batch!(posts, topic_map, count - posts.length, total) if posts.length > 0 + end + +end + +unless ARGV[0] && Dir.exist?(ARGV[0]) + puts "", "Usage:", "", "bundle exec ruby script/import_scripts/jive.rb DIRNAME", "" + exit 1 +end + +ImportScripts::Jive.new(ARGV[0]).perform From 0d15dbd88698304d4fec17b1dc32fb3948a17221 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Sun, 1 Nov 2015 14:30:54 +1100 Subject: [PATCH 085/114] FIX: lastPoster not defined correctly in model --- .../javascripts/discourse/models/topic.js.es6 | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index 554546acba..6ed996b336 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -8,7 +8,21 @@ const Topic = RestModel.extend({ message: null, errorLoading: false, - lastPoster: Ember.computed.alias("posters.lastObject.user"), + @computed('posters.firstObject') + creator(poster){ + return poster && poster.user; + }, + + @computed('posters.lastObject') + lastPoster(poster) { + if (poster){ + if (this.last_poster_username === poster.user.username){ + return poster.user; + } else { + return this.get('creator'); + } + } + }, @computed('fancy_title') fancyTitle(title) { From 606b10445e5a9980a4d68219b4b33c3f845b2574 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Mon, 2 Nov 2015 09:20:22 +1100 Subject: [PATCH 086/114] FEATURE: remove muted topics from suggested and latest --- app/assets/javascripts/discourse/models/user.js.es6 | 2 ++ .../javascripts/discourse/templates/user/preferences.hbs | 7 +++++++ config/locales/client.en.yml | 1 + lib/topic_query.rb | 9 +++++++++ 4 files changed, 19 insertions(+) diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6 index 87278959ef..3fc570c3a4 100644 --- a/app/assets/javascripts/discourse/models/user.js.es6 +++ b/app/assets/javascripts/discourse/models/user.js.es6 @@ -68,6 +68,8 @@ const User = RestModel.extend({ adminPath: url('username_lower', "/admin/users/%@"), + mutedTopicsPath: url('/latest?state=muted'), + @computed("username") username_lower(username) { return username.toLowerCase(); diff --git a/app/assets/javascripts/discourse/templates/user/preferences.hbs b/app/assets/javascripts/discourse/templates/user/preferences.hbs index e2dea410a5..3ebc10e5a5 100644 --- a/app/assets/javascripts/discourse/templates/user/preferences.hbs +++ b/app/assets/javascripts/discourse/templates/user/preferences.hbs @@ -235,6 +235,13 @@
      {{i18n 'user.muted_categories_instructions'}}
      +
      + + +
      +
      diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index f48dd255bb..5da7dae033 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -470,6 +470,7 @@ en: users: "Users" muted_users: "Muted" muted_users_instructions: "Suppress all notifications from these users." + muted_topics_link: "Show muted topics" staff_counters: flags_given: "helpful flags" diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 7637bbaa7b..ed23df5567 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -201,6 +201,7 @@ class TopicQuery def latest_results(options={}) result = default_results(options) + result = remove_muted_topics(result, @user) unless options && options[:state] = "muted".freeze result = remove_muted_categories(result, @user, exclude: options[:category]) result end @@ -215,6 +216,7 @@ class TopicQuery # TODO does this make sense or should it be ordered on created_at # it is ordering on bumped_at now result = TopicQuery.new_filter(default_results(options.reverse_merge(:unordered => true)), @user.treat_as_new_topic_start_date) + result = remove_muted_topics(result, @user) result = remove_muted_categories(result, @user, exclude: options[:category]) suggested_ordering(result, options) end @@ -395,6 +397,13 @@ class TopicQuery @guardian.filter_allowed_categories(result) end + def remove_muted_topics(list, user) + if user + list = list.where('tu.notification_level <> :muted', muted: TopicUser.notification_levels[:muted]) + end + + list + end def remove_muted_categories(list, user, opts=nil) category_id = get_category_id(opts[:exclude]) if opts From eaae72c3e4b8531f863b514844dbc1f457d7276c Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 2 Nov 2015 13:24:24 +1100 Subject: [PATCH 087/114] improve watching logic --- lib/autospec/manager.rb | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/autospec/manager.rb b/lib/autospec/manager.rb index 45256015d6..cf06e80068 100644 --- a/lib/autospec/manager.rb +++ b/lib/autospec/manager.rb @@ -148,8 +148,7 @@ class Autospec::Manager puts "@@@@@@@@@@@@ listen_for_changes" if @debug options = { - ignore: /^public|^lib\/autospec/, - relative_paths: true, + ignore: /^lib\/autospec/, } if @opts[:force_polling] @@ -157,11 +156,20 @@ class Autospec::Manager options[:latency] = @opts[:latency] || 3 end - Thread.start do - Listen.to('.', options) do |modified, added, _| - process_change([modified, added].flatten.compact) + path = File.expand_path(File.dirname(__FILE__) + "../../..") + + # to speed up boot we use a thread + Thread.new do + ["spec", "lib", "app", "config", "test", "vendor", "plugins"].each do |watch| + Listen.to("#{path}/#{watch}", options) do |modified, added, _| + paths = [modified, added].flatten + paths.compact! + paths.map!{|long| long[(path.length+1)..-1]} + process_change(paths) + end end end + end def process_change(files) From 2c4f290786f7faa7fefda08d388b2d3eb392d1ec Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 2 Nov 2015 13:33:08 +1100 Subject: [PATCH 088/114] docker dev binaries --- bin/docker/boot_dev | 14 ++++++++++++++ bin/docker/bundle | 5 +++++ bin/docker/psql | 5 +++++ bin/docker/rails | 9 +++++++++ bin/docker/rake | 5 +++++ bin/docker/reset_db | 12 ++++++++++++ bin/docker/shutdown_dev | 3 +++ 7 files changed, 53 insertions(+) create mode 100755 bin/docker/boot_dev create mode 100755 bin/docker/bundle create mode 100755 bin/docker/psql create mode 100755 bin/docker/rails create mode 100755 bin/docker/rake create mode 100755 bin/docker/reset_db create mode 100755 bin/docker/shutdown_dev diff --git a/bin/docker/boot_dev b/bin/docker/boot_dev new file mode 100755 index 0000000000..0b3ed1eca2 --- /dev/null +++ b/bin/docker/boot_dev @@ -0,0 +1,14 @@ +#!/bin/bash + +pushd `dirname $0` > /dev/null +SCRIPTPATH=`pwd -P` +popd > /dev/null + + +SOURCE_DIR=`(cd $SCRIPTPATH && cd ../../ && pwd)` +DATA_DIR=$SOURCE_DIR/tmp/postgres + +echo $SOURCE_DIR +echo $DATA_DIR + +docker run -d -p 3000:3000 -v $DATA_DIR:/shared/postgres_data -v $SOURCE_DIR:/src --hostname=discourse_dev --name=discourse_dev --restart=always discourse/dev /sbin/boot diff --git a/bin/docker/bundle b/bin/docker/bundle new file mode 100755 index 0000000000..d304ab4712 --- /dev/null +++ b/bin/docker/bundle @@ -0,0 +1,5 @@ +#!/bin/bash + +PARAMS="$@" +CMD="cd /src && HOME=/home/discourse chpst -u discourse:discourse bundle $PARAMS" +docker exec -it discourse_dev /bin/bash -c "$CMD" diff --git a/bin/docker/psql b/bin/docker/psql new file mode 100755 index 0000000000..6336af07b1 --- /dev/null +++ b/bin/docker/psql @@ -0,0 +1,5 @@ +#!/bin/bash + +PARAMS="$@" +CMD="chpst -u postgres psql $PARAMS" +docker exec -it discourse_dev /bin/bash -c "$CMD" diff --git a/bin/docker/rails b/bin/docker/rails new file mode 100755 index 0000000000..afbda65802 --- /dev/null +++ b/bin/docker/rails @@ -0,0 +1,9 @@ +#!/bin/bash + +PARAMS="$@" +if [[ $# = 1 ]] && [[ "$1" =~ "s" ]]; +then + PARAMS="$PARAMS -b 0.0.0.0" +fi +CMD="cd /src && HOME=/home/discourse RAILS_ENV=${RAILS_ENV:=development} chpst -u discourse:discourse rails $PARAMS" +docker exec -it discourse_dev /bin/bash -c "$CMD" diff --git a/bin/docker/rake b/bin/docker/rake new file mode 100755 index 0000000000..2ecac1b25e --- /dev/null +++ b/bin/docker/rake @@ -0,0 +1,5 @@ +#!/bin/bash + +PARAMS="$@" +CMD="cd /src && HOME=/home/discourse RAILS_ENV=${RAILS_ENV:=development} chpst -u discourse:discourse rake $PARAMS" +docker exec -it discourse_dev /bin/bash -c "$CMD" diff --git a/bin/docker/reset_db b/bin/docker/reset_db new file mode 100755 index 0000000000..2d1ae28c06 --- /dev/null +++ b/bin/docker/reset_db @@ -0,0 +1,12 @@ +#!/bin/bash + +pushd `dirname $0` > /dev/null +SCRIPTPATH=`pwd -P` +popd > /dev/null + + +SOURCE_DIR=`(cd $SCRIPTPATH && cd ../../ && pwd)` +DATA_DIR=$SOURCE_DIR/tmp/postgres + + +docker run -it -v $DATA_DIR:/shared/postgres_data samsaffron/discourse_dev:1.0.13 /bin/bash -c "rm -fr /shared/postgres_data/*" diff --git a/bin/docker/shutdown_dev b/bin/docker/shutdown_dev new file mode 100755 index 0000000000..557ccfe9df --- /dev/null +++ b/bin/docker/shutdown_dev @@ -0,0 +1,3 @@ +#!/bin/bash + +docker rm -f discourse_dev From 3575012a47b451f301959573b079cb879ebc3342 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 2 Nov 2015 14:59:10 +1100 Subject: [PATCH 089/114] correct implementation --- lib/topic_query.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/topic_query.rb b/lib/topic_query.rb index ed23df5567..32a09aa706 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -399,7 +399,7 @@ class TopicQuery def remove_muted_topics(list, user) if user - list = list.where('tu.notification_level <> :muted', muted: TopicUser.notification_levels[:muted]) + list = list.where('COALESCE(tu.notification_level,1) > :muted', muted: TopicUser.notification_levels[:muted]) end list From 48ef6090035f655884cc83c253bf5ce2d16608e8 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 2 Nov 2015 15:05:08 +1100 Subject: [PATCH 090/114] correct implementation add tests --- lib/topic_query.rb | 2 +- spec/components/topic_query_spec.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 32a09aa706..b02c3148e0 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -201,7 +201,7 @@ class TopicQuery def latest_results(options={}) result = default_results(options) - result = remove_muted_topics(result, @user) unless options && options[:state] = "muted".freeze + result = remove_muted_topics(result, @user) unless options && options[:state] == "muted".freeze result = remove_muted_categories(result, @user, exclude: options[:category]) result end diff --git a/spec/components/topic_query_spec.rb b/spec/components/topic_query_spec.rb index c06c6623b5..bb99bc35b8 100644 --- a/spec/components/topic_query_spec.rb +++ b/spec/components/topic_query_spec.rb @@ -374,6 +374,7 @@ describe TopicQuery do it "returns an empty set" do expect(topics).to be_blank + expect(topic_query.list_latest.topics).to be_blank end context 'un-muted' do @@ -383,6 +384,7 @@ describe TopicQuery do it "returns the topic again" do expect(topics).to eq([new_topic]) + expect(topic_query.list_latest.topics).not_to be_blank end end end From 30bddd11121684a0d714d149fe6597a519b01eb5 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Mon, 2 Nov 2015 13:13:17 +0800 Subject: [PATCH 091/114] FIX: Duplicated custom badges in AdminBadgesController. --- .../javascripts/admin/controllers/admin-badges-show.js.es6 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 index 96547435d8..46f4548ebd 100644 --- a/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 @@ -68,7 +68,8 @@ export default Ember.Controller.extend(BufferedContent, { model = this.get('model'); this.get('model').save(data).then(function() { if (newBadge) { - self.get('controllers.admin-badges').pushObject(model); + var adminBadgesController = self.get('controllers.admin-badges'); + if (!adminBadgesController.contains(model)) adminBadgesController.pushObject(model); self.transitionToRoute('adminBadges.show', model.get('id')); } else { self.commitBuffer(); From 700c005c26473a6963ba9273b7ebd9622b2c591f Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Mon, 2 Nov 2015 11:01:08 +0530 Subject: [PATCH 092/114] PERF: optimize export user list CSV queries --- app/jobs/regular/export_csv_file.rb | 103 ++++++++++++++++------------ spec/jobs/export_csv_file_spec.rb | 2 +- 2 files changed, 60 insertions(+), 45 deletions(-) diff --git a/app/jobs/regular/export_csv_file.rb b/app/jobs/regular/export_csv_file.rb index d3d2d3da89..236c3a4bd9 100644 --- a/app/jobs/regular/export_csv_file.rb +++ b/app/jobs/regular/export_csv_file.rb @@ -51,14 +51,66 @@ module Jobs end def user_list_export - query = ::AdminUserIndexQuery.new - user_data = query.find_users_query.to_a - user_data.map do |user| - group_names = get_group_names(user).join(';') - user_array = get_user_list_fields(user) - user_array.push(group_names) if group_names != '' - user_array + user_array = [] + user_field_ids = UserField.pluck(:id) + + if SiteSetting.enable_sso + # SSO enabled + User.includes(:user_stat, :single_sign_on_record, :groups).find_each do |user| + user_info_string = "#{user.id},#{user.name},#{user.username},#{user.email},#{user.title},#{user.created_at},#{user.last_seen_at},#{user.last_posted_at},#{user.last_emailed_at},#{user.trust_level},#{user.approved},#{user.suspended_at},#{user.suspended_till},#{user.blocked},#{user.active},#{user.admin},#{user.moderator},#{user.ip_address},#{user.user_stat.topics_entered},#{user.user_stat.posts_read_count},#{user.user_stat.time_read},#{user.user_stat.topic_count},#{user.user_stat.post_count},#{user.user_stat.likes_given},#{user.user_stat.likes_received}" + + # sso + if user.single_sign_on_record + user_info_string << ",#{user.single_sign_on_record.external_id},#{user.single_sign_on_record.external_email},#{user.single_sign_on_record.external_username},#{user.single_sign_on_record.external_name},#{user.single_sign_on_record.external_avatar_url}" + else + user_info_string << ",nil,nil,nil,nil,nil" + end + + # custom fields + if user_field_ids.present? + user.user_fields.each do |custom_field| + user_info_string << ",#{custom_field[1]}" + end + end + + # group names + group_names = "" + user.groups.each do |group| + group_names << "#{group.name};" + end + user_info_string << ",#{group_names[0..-2]}" unless group_names.blank? + group_names = nil + + user_array.push(user_info_string.split(",")) + user_info_string = nil + end + else + # SSO disabled + User.includes(:user_stat, :groups).find_each do |user| + user_info_string = "#{user.id},#{user.name},#{user.username},#{user.email},#{user.title},#{user.created_at},#{user.last_seen_at},#{user.last_posted_at},#{user.last_emailed_at},#{user.trust_level},#{user.approved},#{user.suspended_at},#{user.suspended_till},#{user.blocked},#{user.active},#{user.admin},#{user.moderator},#{user.ip_address},#{user.user_stat.topics_entered},#{user.user_stat.posts_read_count},#{user.user_stat.time_read},#{user.user_stat.topic_count},#{user.user_stat.post_count},#{user.user_stat.likes_given},#{user.user_stat.likes_received}" + + # custom fields + if user_field_ids.present? + user.user_fields.each do |custom_field| + user_info_string << ",#{custom_field[1]}" + end + end + + # group names + group_names = "" + user.groups.each do |group| + group_names << "#{group.name};" + end + user_info_string << ",#{group_names[0..-2]}" unless group_names.blank? + group_names = nil + + user_array.push(user_info_string.split(",")) + user_info_string = nil + end end + + user_field_ids = nil + user_array end def staff_action_export @@ -129,15 +181,6 @@ module Jobs private - def get_group_names(user) - group_names = [] - groups = user.groups - groups.each do |group| - group_names.push(group.name) - end - return group_names - end - def get_user_archive_fields(user_archive) user_archive_array = [] topic_data = user_archive.topic @@ -170,34 +213,6 @@ module Jobs user_archive_array end - def get_user_list_fields(user) - user_array = [] - - HEADER_ATTRS_FOR['user_list'].each do |attr| - user_array.push(user.attributes[attr]) - end - - HEADER_ATTRS_FOR['user_stats'].each do |stat| - user_array.push(user.user_stat.attributes[stat]) - end - - if SiteSetting.enable_sso - sso = user.single_sign_on_record - HEADER_ATTRS_FOR['user_sso'].each do |stat| - field = sso.attributes[stat] if sso - user_array.push(field) - end - end - - if user.user_fields.present? - user.user_fields.each do |custom_field| - user_array.push(custom_field[1]) - end - end - - user_array - end - def get_staff_action_fields(staff_action) staff_action_array = [] diff --git a/spec/jobs/export_csv_file_spec.rb b/spec/jobs/export_csv_file_spec.rb index 80e211c9e4..ebef5e599f 100644 --- a/spec/jobs/export_csv_file_spec.rb +++ b/spec/jobs/export_csv_file_spec.rb @@ -25,7 +25,7 @@ describe Jobs::ExportCsvFile do user = Fabricate(:user) user.create_single_sign_on_record(external_id: "123", last_payload: "xxx", external_email: 'test@test.com') - user = to_hash(user_list_export.find{|u| u[0] == user.id}) + user = to_hash(user_list_export.find{|u| u[0].to_i == user.id}) expect(user["external_id"]).to eq("123") expect(user["external_email"]).to eq("test@test.com") From 7ced16acbfa25d05e8aa67095155672c75950dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 2 Nov 2015 11:19:37 +0100 Subject: [PATCH 093/114] FIX: topic.lastPoster should be based on the 'latest' extra --- .../javascripts/discourse/models/topic.js.es6 | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index 6ed996b336..3530a2929f 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -13,15 +13,10 @@ const Topic = RestModel.extend({ return poster && poster.user; }, - @computed('posters.lastObject') - lastPoster(poster) { - if (poster){ - if (this.last_poster_username === poster.user.username){ - return poster.user; - } else { - return this.get('creator'); - } - } + @computed('posters.@each') + lastPoster(posters) { + const latest = posters.filter(p => p.extras && p.extras.indexOf("latest") >= 0)[0]; + return latest.user; }, @computed('fancy_title') From d0fe4fc4b5d4f45f10c6fed133968bd50ad61cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 2 Nov 2015 11:39:23 +0100 Subject: [PATCH 094/114] FIX: topic.posters might be null or empty --- app/assets/javascripts/discourse/models/topic.js.es6 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index 3530a2929f..9fc50d38df 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -15,8 +15,12 @@ const Topic = RestModel.extend({ @computed('posters.@each') lastPoster(posters) { - const latest = posters.filter(p => p.extras && p.extras.indexOf("latest") >= 0)[0]; - return latest.user; + if (posters && posters.length > 0) { + const latest = posters.filter(p => p.extras && p.extras.indexOf("latest") >= 0)[0]; + return latest.user; + } else { + return this.get("creator"); + } }, @computed('fancy_title') From a44c2f6ea39fa2b5b192a8f76b83bc282b016c45 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Mon, 2 Nov 2015 17:49:57 +0530 Subject: [PATCH 095/114] Update Translations --- config/locales/client.ar.yml | 4 ---- config/locales/client.bs_BA.yml | 1 - config/locales/client.cs.yml | 1 - config/locales/client.da.yml | 8 +++++--- config/locales/client.de.yml | 2 -- config/locales/client.es.yml | 15 ++++++++++----- config/locales/client.fa_IR.yml | 2 -- config/locales/client.fi.yml | 2 -- config/locales/client.fr.yml | 4 ---- config/locales/client.he.yml | 2 -- config/locales/client.ja.yml | 2 -- config/locales/client.ko.yml | 2 -- config/locales/client.nb_NO.yml | 2 -- config/locales/client.nl.yml | 4 ---- config/locales/client.pl_PL.yml | 7 +++++-- config/locales/client.pt.yml | 7 +++++-- config/locales/client.pt_BR.yml | 4 ---- config/locales/client.ro.yml | 1 - config/locales/client.ru.yml | 19 +++++++++++++++++-- config/locales/client.sq.yml | 2 -- config/locales/client.sv.yml | 1 - config/locales/client.te.yml | 1 - config/locales/client.tr_TR.yml | 2 -- config/locales/client.zh_CN.yml | 4 ---- config/locales/client.zh_TW.yml | 1 - config/locales/server.es.yml | 3 +++ config/locales/server.pt.yml | 1 + plugins/poll/config/locales/server.es.yml | 2 ++ 28 files changed, 48 insertions(+), 58 deletions(-) diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index ecd1820bdf..b8399797e9 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -1004,9 +1004,7 @@ ar: from_my_computer: "عن طريق جهازي" from_the_web: "عن طريق الويب" remote_tip: "رابط لصورة" - remote_tip_with_attachments: "رابط لصورة أو ملف ({{authorized_extensions}})" local_tip: "إختر صور من جهازك ." - local_tip_with_attachments: "اضغط لاختيار صورة أو ملف من جهازك ({{authorized_extensions}})" hint: "(تستطيع أيضا أن تسحب و تفلت ملف أو صورة في المحرر لرفعه)" hint_for_supported_browsers: "يمكنك أيضا سحبوإفلات أو لصق الصور إلى المحرر" uploading: "يتم الرفع" @@ -2743,9 +2741,7 @@ ar: embed_username_key_from_feed: "مفتاح لسحب اسم عضو discourse من المغذي" embed_truncate: "بتر المشاركات المضمنة" embed_whitelist_selector: "منتقي CSS للعناصر التي تسمح في التضمينات." - whitelist_example: "مقالة، #قصة، .مشاركة" embed_blacklist_selector: "منتقي CSS للعناصر التي حذفت من التضمينات." - blacklist_example: ".وحدة إعلان، رأس الصفحة" feed_polling_enabled: "استورد المشاركات عبر RSS/ATOM" feed_polling_url: "URL مغذي RSS/ATOM يتقدم ببطء." save: "أحفظ الإعدادات المضمنة" diff --git a/config/locales/client.bs_BA.yml b/config/locales/client.bs_BA.yml index fe08f74644..f5bf93dddb 100644 --- a/config/locales/client.bs_BA.yml +++ b/config/locales/client.bs_BA.yml @@ -680,7 +680,6 @@ bs_BA: from_my_computer: "Sa mog uređaja" from_the_web: "Sa neta" remote_tip: "link do slike http://primjer.com/slika.jpg" - remote_tip_with_attachments: "link to image or file http://primjer.com/file.ext (allowed extensions: {{authorized_extensions}})." hint: "(možete i mišom prenijeti vaše slike direktno iz vašeg foldera ovdje)" uploading: "Uplodujem" image_link: "link do vaše slike će pokazivati" diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index aaba93cf0d..bb5066b5ee 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -789,7 +789,6 @@ cs: from_my_computer: "Z mého zařízení" from_the_web: "Z webu" remote_tip: "odkaz na obrázek" - remote_tip_with_attachments: "odkaz na obrázek nebo osubor ({{authorized_extensions}})" hint: "(můžete také rovnou soubor do editoru přetáhnout)" uploading: "Nahrávám" select_file: "Vyberte soubor" diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index be8dd2f0e0..7fca81f2a0 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -920,7 +920,7 @@ da: dismiss_tooltip: "Afvis kun nye indlæg eller stop med at følge emner" dismiss_body: "Vil du kun afvise nye indlæg i emnerne eller afvise emnerne helt?" dismiss_posts: "Afvis kun nye indlæg" - dismiss_topics: "Afvist Emner" + dismiss_topics: "Afvis emner" dismiss_new: "Afvis nye" toggle: "vælg flere emner af gangen" actions: "Handlinger på flere indlæg" @@ -1773,6 +1773,10 @@ da: add: "Tilføj" add_members: "Tilføj medlemmer" custom: "Brugerdefineret" + bulk_complete: "Brugerne er tilføjet gruppen." + bulk: "Tilføj mange brugere til gruppe" + bulk_paste: "Indsæt en liste brugerne eller emails, én per linje:" + bulk_select: "(vælg en gruppe)" automatic: "Automatisk" automatic_membership_email_domains: "Brugere der registrerer med et email domæne der præcist matcher et på denne liste, vil automatisk blive tilføjet til denne gruppe:" automatic_membership_retroactive: "Brug denne email domæne regel til at tilføje brugere der allerede er registrerede brugere" @@ -2406,9 +2410,7 @@ da: embed_username_key_from_feed: "Nøgle til at udtrække discourse-brugernavn fra feed" embed_truncate: "Beskær de indlejrede indlæg" embed_whitelist_selector: "CSS-selektorer for elementer der er tilladte i indlejringer" - whitelist_example: "article, #story, .post" embed_blacklist_selector: "CSS-selektorer for elementer der fjernes fra indlejringer" - blacklist_example: ".ad-unit, header" feed_polling_enabled: "Importer indlæg via RSS/ATOM" feed_polling_url: "URL på RSS/ATOM feed der skal kravles" save: "Gem indlejrings-indstillinger" diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index 9e3387083a..bd056db4f0 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -2384,9 +2384,7 @@ de: embed_username_key_from_feed: "Schlüssel, um Discourse-Benutzernamen aus Feed zu extrahieren." embed_truncate: "Kürze die eingebetteten Beiträge" embed_whitelist_selector: "CSS Selektor für Elemente, die in Einbettungen erlaubt sind." - whitelist_example: "article, #story, .post" embed_blacklist_selector: "CSS Selektor für Elemente, die in Einbettungen entfernt werden." - blacklist_example: ".ad-unit, header" feed_polling_enabled: "Beiträge über RSS/ATOM importieren" feed_polling_url: "URL des RSS/ATOM Feeds für den Import" save: "Einbettungseinstellungen speichern" diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index ed6e470b23..635ae4b0cb 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -127,7 +127,7 @@ es: disabled: 'sin destacar %{when}' visible: enabled: 'listado %{when}' - disabled: 'sin listar %{when}' + disabled: 'quitado de la lista, invisible %{when}' topic_admin_menu: "acciones de administrador para el tema" emails_are_disabled: "Todos los emails salientes han sido desactivados por un administrador. No se enviará ninguna notificación por email." edit: 'editar el título y la categoría de este tema' @@ -876,9 +876,9 @@ es: from_my_computer: "Desde mi dispositivo" from_the_web: "Desde la web" remote_tip: "enlace a la imagen" - remote_tip_with_attachments: "enlace a la imagen o al archivo ({{authorized_extensions}})" + remote_tip_with_attachments: "enlace a imagen o archivo {{authorized_extensions}}" local_tip: "selecciona las imágenes desde tu dispositivo" - local_tip_with_attachments: "selecciona imágenes o archivos desde tu dispositivo ({{authorized_extensions}})" + local_tip_with_attachments: "selecciona imágenes o archivos desde tu dispositivo {{authorized_extensions}}" hint: "(también puedes arrastrarlos al editor para subirlos)" hint_for_supported_browsers: "puedes también arrastrar o pegar imágenes en el editor" uploading: "Subiendo" @@ -913,9 +913,12 @@ es: current_user: 'ir a tu página de usuario' topics: bulk: + unlist_topics: "Hacer invisibles" reset_read: "Restablecer leídos" delete: "Eliminar temas" dismiss: "Descartar" + dismiss_button: "Descartar..." + dismiss_tooltip: "Descartar solo los nuevos posts o dejar de seguir los temas" dismiss_body: "¿Quieres descartar solo los nuevos posts en estos temas o los temas por completo?" dismiss_posts: "Descartar nuevos posts" dismiss_topics: "Descartar Temas" @@ -1771,6 +1774,10 @@ es: add: "Añadir" add_members: "Añadir miembros" custom: "Personalizado" + bulk_complete: "Los usuarios han sido añadidos al grupo." + bulk: "Añadir al grupo en masa" + bulk_paste: "Pega una lista de nombres de usuario o emails, uno por línea:" + bulk_select: "(selecciona un grupo)" automatic: "Automático" automatic_membership_email_domains: "Los usuarios que se registren con un dominio de e-mail que esté en esta lista serán automáticamente añadidos a este grupo:" automatic_membership_retroactive: "Aplicar la misma regla de dominio de email para usuarios registrados existentes " @@ -2404,9 +2411,7 @@ es: embed_username_key_from_feed: "Clave para extraer usuario de discourse del feed" embed_truncate: "Truncar los posts insertados" embed_whitelist_selector: "Selector CSS para permitir elementos a embeber" - whitelist_example: "article, #story, .post" embed_blacklist_selector: "Selector CSS para restringir elementos a embeber" - blacklist_example: ".ad-unit, header" feed_polling_enabled: "Importar posts usando RSS/ATOM" feed_polling_url: "URL del feed RSS/ATOM del que extraer datos" save: "Guardar ajustes de Insertado" diff --git a/config/locales/client.fa_IR.yml b/config/locales/client.fa_IR.yml index 0e187fca11..d01d585ea7 100644 --- a/config/locales/client.fa_IR.yml +++ b/config/locales/client.fa_IR.yml @@ -728,9 +728,7 @@ fa_IR: from_my_computer: "از دستگاه من" from_the_web: "از وب" remote_tip: "لینک به تصویر" - remote_tip_with_attachments: "لطفا لینک تصویر یا فایل ({{authorized_extensions}})" local_tip: "عکس ها را از روی سیستم خود انتخاب کنید" - local_tip_with_attachments: "عکس ها را از روی سیستم خود انتخاب کنید ({{authorized_extensions}})" hint: "(برای آپلود می توانید فایل را کیشده و در ویرایشگر رها کنید)" uploading: "در حال بروز رسانی " select_file: "انتخاب فایل" diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index 607c95cd55..ba9f984554 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -2406,9 +2406,7 @@ fi: embed_username_key_from_feed: "Avain, jolla erotetaan Discourse-käyttäjänimi syötteestä" embed_truncate: "Typistä upotetut viestit" embed_whitelist_selector: "CSS valitsin elementeille, jotka sallitaan upotetuissa viesteissä" - whitelist_example: "article, #story, .post" embed_blacklist_selector: "CSS valitstin elementeille, jotka poistetaan upotetuista viesteistä" - blacklist_example: ".ad-unit, header" feed_polling_enabled: "Tuo kirjoitukset RSS/ATOM syötteen avulla" feed_polling_url: "RSS/ATOM syötteen URL" save: "Tallenna upotusasetukset" diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index 789ecbc7ed..8378034a9a 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -876,9 +876,7 @@ fr: from_my_computer: "Depuis mon appareil" from_the_web: "Depuis le web" remote_tip: "lien vers l'image" - remote_tip_with_attachments: "lien vers l'image ou le fichier ({{authorized_extensions}})" local_tip: "sélectionnez des images depuis votre appareil" - local_tip_with_attachments: "sélectionnez des images depuis votre appareil ({{authorized_extensions}})" hint: "(vous pouvez également faire un glisser-déposer dans l'éditeur pour les télécharger)" hint_for_supported_browsers: "vous pouvez aussi glisser/déposer ou coller des images dans l'éditeur" uploading: "Fichier en cours d'envoi" @@ -2408,9 +2406,7 @@ fr: embed_username_key_from_feed: "Clé pour extraire le pseudo du flux." embed_truncate: "Tronquer les messages intégrés" embed_whitelist_selector: "Sélecteur CSS pour les éléments qui seront autorisés dans les contenus intégrés" - whitelist_example: "article, #story, .post" embed_blacklist_selector: "Sélecteur CSS pour les éléments qui seront interdits dans les contenus intégrés" - blacklist_example: ".ad-unit, header" feed_polling_enabled: "Importer les messages via flux RSS/ATOM" feed_polling_url: "URL du flux RSS/ATOM à importer" save: "Sauvegarder paramètres d'intégration" diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index dcb3b77ed6..43586bc7a1 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -863,9 +863,7 @@ he: from_my_computer: "מהמחשב שלי" from_the_web: "מהאינטרנט" remote_tip: "קישור לתמונה" - remote_tip_with_attachments: "קישור לתמונה או קובץ ({{authorized_extensions}})" local_tip: "בחר תמונות ממכשירך" - local_tip_with_attachments: "בחר תמונות או קבצים ממכשירך ({{authorized_extensions}})" hint: "(ניתן גם לגרור לעורך להעלאה)" hint_for_supported_browsers: "תוכלו גם לגרור או להדביק תמונות לעורך" uploading: "מעלה" diff --git a/config/locales/client.ja.yml b/config/locales/client.ja.yml index 1665cfecd1..769fa879ea 100644 --- a/config/locales/client.ja.yml +++ b/config/locales/client.ja.yml @@ -743,9 +743,7 @@ ja: from_my_computer: "このデバイスから" from_the_web: "Web から" remote_tip: "画像へのリンク" - remote_tip_with_attachments: "画像かファイルへのリンク ({{authorized_extensions}})" local_tip: "ローカルからアップロードする画像を選択" - local_tip_with_attachments: "ローカルからアップロードする画像またはファイルを選択 ({{authorized_extensions}})" hint: "(アップロードする画像をエディタにドラッグ&ドロップすることもできます)" uploading: "アップロード中" select_file: "ファイル選択" diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml index 700691f066..164371b870 100644 --- a/config/locales/client.ko.yml +++ b/config/locales/client.ko.yml @@ -800,9 +800,7 @@ ko: from_my_computer: "컴퓨터에서 가져오기" from_the_web: "인터넷에서 가져오기" remote_tip: "이미지 링크" - remote_tip_with_attachments: "이미지 또는 파일 링크 ({{authorized_extensions}})" local_tip: "기기에서 이미지 선택" - local_tip_with_attachments: "기기에서이미지 또는 파일을 선택 ({{authorized_extensions}})" hint: "(드래그&드랍으로 업로드 가능)" uploading: "업로드 중입니다..." select_file: "파일 선택" diff --git a/config/locales/client.nb_NO.yml b/config/locales/client.nb_NO.yml index e793a49a25..21e85055b7 100644 --- a/config/locales/client.nb_NO.yml +++ b/config/locales/client.nb_NO.yml @@ -792,9 +792,7 @@ nb_NO: from_my_computer: "Fra Min Enhet" from_the_web: "Fra nettet" remote_tip: "link til bilde" - remote_tip_with_attachments: "link til bilde eller fil ({{authorized_extensions}})" local_tip: "velg bilder fra din enhet" - local_tip_with_attachments: "velg bilder eller filer fra din enhet ({{authorized_extensions}})" hint: "(du kan også drag & drop inn i editoren for å laste dem opp)" uploading: "Laster opp bilde" select_file: "Velg Fil" diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index 5751724e5c..954e4c3599 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -864,9 +864,7 @@ nl: from_my_computer: "Vanaf mijn apparaat" from_the_web: "Vanaf het web" remote_tip: "link naar afbeelding" - remote_tip_with_attachments: "vul een url in van een afbeelding of bestand (toegestane extensies: {{authorized_extensions}})." local_tip: "selecteer afbeeldingen van uw apparaat" - local_tip_with_attachments: "Selecteer afbeeldingen of bestanden van je apparaat ({{authorized_extensions}})" hint: "(je kan afbeeldingen ook slepen in de editor om deze te uploaden)" hint_for_supported_browsers: "je kunt ook afbeeldingen slepen of plakken in de editor" uploading: "Uploaden" @@ -2385,9 +2383,7 @@ nl: embed_username_key_from_feed: "Key voor de Discourse gebruikersnaam in de feed." embed_truncate: "Embedde berichten inkorten" embed_whitelist_selector: "CSS selector voor elementen die worden toegestaan bij embedding" - whitelist_example: "artikel #verhaal, .post" embed_blacklist_selector: "CSS selector voor elementen die worden verwijderd bij embedding" - blacklist_example: ".ad-unit, header" feed_polling_enabled: "Importeer berichten via RSS/ATOM" feed_polling_url: "URL van RSS/ATOM feed voor crawling" save: "Embedding Instellingen Opslaan " diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml index 0046503174..287ed99cc8 100644 --- a/config/locales/client.pl_PL.yml +++ b/config/locales/client.pl_PL.yml @@ -946,6 +946,7 @@ pl_PL: current_user: 'idź do swojej strony użytkowanika' topics: bulk: + unlist_topics: "Ukryj tematy" reset_read: "Wyzeruj przeczytane" delete: "Usuń tematy" dismiss: "Wyczyść" @@ -1846,6 +1847,10 @@ pl_PL: add: "Dodaj" add_members: "Dodaj członków" custom: "Niestandardowe" + bulk_complete: "Użytkownicy zostali dodani do wskazanej grupy." + bulk: "Dodaj więcej do grupy" + bulk_paste: "Podaj listę nazw użytkowników lub adresów e-mail, każdy w oddzielnej linii:" + bulk_select: "(wybierz grupę)" automatic: "Automatyczne" automatic_membership_email_domains: "Użytkownicy rejestrujący się przy pomocy adresu z tej listy zostaną automatycznie przypisani do tej grupy." automatic_membership_retroactive: "Zastosuj tę regułę domenową do już istniejących użytkowników." @@ -2486,9 +2491,7 @@ pl_PL: embed_username_key_from_feed: "Klucz używany do pobrania nazwy użytkownika z kanału" embed_truncate: "Skracaj treść osadzanych wpisów" embed_whitelist_selector: "Selektor CSS elementów jakie mogą być osadzane" - whitelist_example: "article, #tresc, .wpis" embed_blacklist_selector: "Selektor CSS elementów jakie są usuwane podczas osadzania" - blacklist_example: ".reklama, header" feed_polling_enabled: "Importowanie wpisów via RSS/ATOM" feed_polling_url: "URL kanału RSS/ATOM" save: "Zapisz" diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index c5e6a73b65..a423a053d4 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -913,6 +913,7 @@ pt: current_user: 'ir para a sua página de utilizador' topics: bulk: + unlist_topics: "Remover Tópicos da Lista" reset_read: "Repor Leitura" delete: "Eliminar Tópicos" dismiss: "Destituir" @@ -1773,6 +1774,10 @@ pt: add: "Adicionar" add_members: "Adicionar membros" custom: "Personalizar" + bulk_complete: "Os utilizadores foram adicionados ao grupo." + bulk: "Adicionar ao Grupo em Massa" + bulk_paste: "Colar uma lista de nomes de utilizador ou emails, um por linha:" + bulk_select: "(selecionar um grupo)" automatic: "Automático" automatic_membership_email_domains: "Utilizadores que registem um domínio de email que corresponde exactamente a algum desta lista irão ser automaticamente adicionados a este grupo:" automatic_membership_retroactive: "Aplicar a mesma regra de domínio de email para adicionar utilizadores registados existentes" @@ -2406,9 +2411,7 @@ pt: embed_username_key_from_feed: "Chave para puxar o nome de utilizador discouse do feed" embed_truncate: "Truncar as mensagens incorporadas" embed_whitelist_selector: "Seletor CSS para elementos que são permitidos nas incorporações" - whitelist_example: "artigo, #história, .mensagem" embed_blacklist_selector: "Seletor CSS para elementos que são removidos das incorporações" - blacklist_example: ".unidade-anuncio, cabeçalho" feed_polling_enabled: "Importar mensagens através de RSS/ATOM" feed_polling_url: "URL do feed RSS/ATOM para rastreio" save: "Guardar Configurações de Incorporação" diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml index 8b5341ca41..4abd9e026f 100644 --- a/config/locales/client.pt_BR.yml +++ b/config/locales/client.pt_BR.yml @@ -868,9 +868,7 @@ pt_BR: from_my_computer: "Do meu dispositivo" from_the_web: "Da internet" remote_tip: "link da imagem" - remote_tip_with_attachments: "link da imagem ou arquivo ({{authorized_extensions}})" local_tip: "selecione imagens a partir do seu dispositivo" - local_tip_with_attachments: "Selecione imagens ou arquivos do seu dispositivo ({{authorized_extensions}})" hint: "(Você também pode arrastar e soltar para o editor para carregá-las)" hint_for_supported_browsers: "Você pode também arrastar e soltar ou copiar imagens no editor" uploading: "Enviando" @@ -2389,9 +2387,7 @@ pt_BR: embed_username_key_from_feed: "Chave para obter o nome de usuário no discourse do feed" embed_truncate: "Truncar as postagens incorporadas" embed_whitelist_selector: "Seletor de CSS para elementos que são permitidos na incorporação" - whitelist_example: "article, #historia, .post" embed_blacklist_selector: "Seletor de CSS para elementos que são removidos da incorporação" - blacklist_example: ".ad-unit, header, .propaganda" feed_polling_enabled: "Importar postagens via RSS/ATOM" feed_polling_url: "URL do feed RSS/ATOM para pesquisar" save: "Salvar Configurações de Incorporação" diff --git a/config/locales/client.ro.yml b/config/locales/client.ro.yml index be2b87fdc1..abf368bdbf 100644 --- a/config/locales/client.ro.yml +++ b/config/locales/client.ro.yml @@ -757,7 +757,6 @@ ro: from_my_computer: "din dispozitivul meu" from_the_web: "De pe web" remote_tip: "adresă către imagine http://example.com/image.jpg" - remote_tip_with_attachments: "adresă către imagine sau fișier http://example.com/file.ext (extensii permise: {{authorized_extensions}})." hint: "(puteți să trageți și să aruncați în editor pentru a le încărca)" uploading: "Încarcă" image_link: "Adresa din imagine va duce la" diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index 078246c42a..976aa335e1 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -433,6 +433,7 @@ ru: private_messages: "Личные сообщения" activity_stream: "Активность" preferences: "Настройки" + expand_profile: "Развернуть" bookmarks: "Закладки" bio: "Обо мне" invited_by: "Пригласил" @@ -580,16 +581,28 @@ ru: every_two_weeks: "каждые 2 недели" email_direct: "Присылать почтовое уведомление, когда кто-то цитирует меня, отвечает на мой пост, упоминает мой @псевдоним или приглашает меня в тему" email_private_messages: "Присылать почтовое уведомление, когда кто-то оставляет мне сообщение" + email_always: "Присылать почтовое уведомление, даже если я присутствую на сайте" other_settings: "Прочее" categories_settings: "Разделы" new_topic_duration: label: "Считать темы новыми, если" not_viewed: "Еще не просмотрены" - last_here: "создано после вашего последнего визита" + last_here: "созданы после вашего последнего визита" + after_1_day: "созданы за прошедший день" + after_2_days: "созданы за последние 2 дня" + after_1_week: "созданы за последнюю неделю" + after_2_weeks: "созданы за последние 2 недели" auto_track_topics: "Автоматически отслеживать темы, которые я просматриваю" auto_track_options: never: "никогда" immediately: "немедленно" + after_30_seconds: "более 30 секунд" + after_1_minute: "более 1ой минуты" + after_2_minutes: "более 2х минут" + after_3_minutes: "более 3х минут" + after_4_minutes: "более 4х минут" + after_5_minutes: "более 5 минут" + after_10_minutes: "более 10 минут" invited: search: "Введите текст для поиска по приглашениям..." title: "Приглашения" @@ -887,7 +900,6 @@ ru: from_my_computer: "From my device" from_the_web: "From the web" remote_tip: "ссылка на изображение" - remote_tip_with_attachments: "ссылка на изображение или файл ({{authorized_extensions}})" hint: "(вы так же можете перетащить объект в редактор для его загрузки)" uploading: "Загрузка" select_file: "Выбрать файл" @@ -923,6 +935,9 @@ ru: bulk: reset_read: "Сбросить прочтённые" delete: "Удалить темы" + dismiss: "OK" + dismiss_button: "Отложить..." + dismiss_tooltip: "Отложить новые сообщения или перестать следить за этими темами" dismiss_topics: "Отложить темы" dismiss_new: "Отложить новые" toggle: "Вкл./выкл. выбор нескольких тем" diff --git a/config/locales/client.sq.yml b/config/locales/client.sq.yml index a78dc12a61..a6a645dd96 100644 --- a/config/locales/client.sq.yml +++ b/config/locales/client.sq.yml @@ -774,9 +774,7 @@ sq: from_my_computer: "Nga çdo paisje" from_the_web: "Nga web" remote_tip: "lidhje tek imazhi" - remote_tip_with_attachments: "lidhje për tek imazhi ose skedari ({{authorized_extensions}})" local_tip: "select images from your device" - local_tip_with_attachments: "select images or files from your device ({{authorized_extensions}})" hint: "(you can also drag & drop into the editor to upload them)" uploading: "Duke ngarkaur" select_file: "Select File" diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index 2fd9406992..5d7939400a 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -770,7 +770,6 @@ sv: from_my_computer: "Från min enhet" from_the_web: "Från webben" remote_tip: "länk till bild" - remote_tip_with_attachments: "länk till bild eller fil ({{authorized_extensions}})" hint: "(du kan också dra & släppa in i redigeraren för att ladda upp dem)" uploading: "Laddar upp bild" select_file: "Välj fil" diff --git a/config/locales/client.te.yml b/config/locales/client.te.yml index 6cb6a53b4f..855d6a223c 100644 --- a/config/locales/client.te.yml +++ b/config/locales/client.te.yml @@ -655,7 +655,6 @@ te: from_my_computer: "నా పరికరం నుండి" from_the_web: "జాలం నుండి" remote_tip: "బొమ్మకు లంకె" - remote_tip_with_attachments: "బొమ్మకు లేదా దస్త్రానికి లంకె ({{authorized_extensions}})" hint: "(మీరు వాటిని ఎడిటరులోకి లాగి వదిలెయ్యటు ద్వారా కూడా ఎగుమతించవచ్చు)" uploading: "ఎగుమతవుతోంది" image_link: "మీ బొమ్మ చూపే లంకె" diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml index 86b09c9a6a..b036721bfe 100644 --- a/config/locales/client.tr_TR.yml +++ b/config/locales/client.tr_TR.yml @@ -808,9 +808,7 @@ tr_TR: from_my_computer: "Kendi cihazımdan" from_the_web: "Webden" remote_tip: "resme bağlantı ver" - remote_tip_with_attachments: "resme ya da dosyaya ({{authorized_extensions}}) bağlantı ver" local_tip: "cihazınızdan resimler seçin" - local_tip_with_attachments: "cihazınızdan resim veya dosyalar seçin ({{authorized_extensions}})" hint: "(editöre sürekle & bırak yaparak da yükleyebilirsiniz)" hint_for_supported_browsers: "ayrıca resimleri düzenleyiciye sürükleyip bırakabilir ya da yapıştırabilirsiniz" uploading: "Yükleniyor" diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index c323239fbb..7082a69d00 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -833,9 +833,7 @@ zh_CN: from_my_computer: "来自我的设备" from_the_web: "来自网络" remote_tip: "图片链接" - remote_tip_with_attachments: "图片或文件链接({{authorized_extensions}})" local_tip: "从你的设备中选择图片" - local_tip_with_attachments: "从你的设备中选择图片或文件({{authorized_extensions}})" hint: "(你也可以通过拖放至编辑器的方式来上传)" hint_for_supported_browsers: "你也可以通过拖放或复制粘帖至编辑器" uploading: "上传中" @@ -2301,9 +2299,7 @@ zh_CN: embed_username_key_from_feed: "从流中拉取 Discourse 用户名的 Key " embed_truncate: "截断嵌入的帖子" embed_whitelist_selector: "使用 CSS 选择器选择允许的嵌入元素" - whitelist_example: "article, #story, .post" embed_blacklist_selector: "使用 CSS 选择器移除嵌入元素" - blacklist_example: ".ad-unit, header" feed_polling_enabled: "通过RSS/ATOM导入帖子" feed_polling_url: "用于抓取的 RSS/ATOM 流的 URL" save: "保存嵌入设置" diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml index 586da7e973..637a759f2e 100644 --- a/config/locales/client.zh_TW.yml +++ b/config/locales/client.zh_TW.yml @@ -729,7 +729,6 @@ zh_TW: from_my_computer: "從我的電腦" from_the_web: "從網站" remote_tip: "圖片連結" - remote_tip_with_attachments: "圖片或文件連結 ({{authorized_extensions}})" hint: "(你也可以將檔案拖放至編輯器直接上傳)" uploading: "正在上傳" select_file: "選取檔案" diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml index d5938254c5..bcd055ae80 100644 --- a/config/locales/server.es.yml +++ b/config/locales/server.es.yml @@ -275,6 +275,8 @@ es: [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Definición de la categoría %{category}" + replace_paragraph: "(Sustituye este primer párrafo con una descripción breve de tu nueva categoría. Esta descripción aparecerá en el área de selección de categoría, por ello, intenta que sea inferior a 200 caracteres. **Hasta que edites esta descripción o se creen temas, esta categoría no aparecerá en la página de categorías.**)" + post_template: "%{replace_paragraph}\n\nUtiliza los siguientes párrafos para una descripción más detallada, o establece las directrices o reglas de la categoría::\n\n- ¿Para qué podrían usar los usuarios esta categoría? ¿De qué trata?\n\n- ¿Cómo se distingue de otras categorías existentes?\n\n- ¿Qué temas debería contener esta categoría normalmente?\n\n- ¿Necesitamos esta categoría? ¿Podría agruparse o converger con otra categoría o subcategoría?\n" errors: uncategorized_parent: "Sin categoría no puede tener categoría primaria" self_parent: "La categoría primaria de una subcategoría no puede ser ella misma." @@ -999,6 +1001,7 @@ es: allow_anonymous_posting: "Permitir a los usuarios cambiar a modo anónimo" anonymous_posting_min_trust_level: "Nivel de confianza mínimo requerido para activar el modo anónimo" anonymous_account_duration_minutes: "Para proteger el anonimato, crear una nueva cuenta anónima cada N minutos para cada usuario. Ejemplo: si se establece en 600, tan pronto como pasen 600 minutos desde el último post Y el usuario cambie a anónimo, se creará una nueva cuenta anónima." + hide_user_profiles_from_public: "Desactiva las tarjetas de usuario, los perfiles y el directorio de usuarios para usuarios anónimos." allow_profile_backgrounds: "Permitir a los usuarios subir sus propios fondos de perfil personalizados." sequential_replies_threshold: "Número de posts consecutivos que un usuario publicará en un mismo tema hasta mostrarle un recordatorio sobre demasiadas repuestas seguidas." enable_mobile_theme: "Los dispositivos móviles utilizan un tema adaptado, con la habilidad de cambiar al estilo de sitio completo. Deshabilita esta opción si quieres utilizar una plantilla personalizada que sea completamente adaptable." diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml index 3f3468ded9..cec709c5ed 100644 --- a/config/locales/server.pt.yml +++ b/config/locales/server.pt.yml @@ -1013,6 +1013,7 @@ pt: allow_anonymous_posting: "Permitir que os utilizadores alterem para o modo anónimo" anonymous_posting_min_trust_level: "Nível de confiança mínimo necessário para ativar publicações anónimas" anonymous_account_duration_minutes: "Para proteger o anonimato crie uma nova conta anónima a cada N minutos para cada utilizador. Exemplo: se configurado para 600, assim que passarem 600 minutos desde a última mensagem E o utilizador altere para anónimo, uma nova conta anónima é criada." + hide_user_profiles_from_public: "Desativar cartões de utilizador, perfis de utilizador e diretoria de utilizadores para utilizadores anónimos." allow_profile_backgrounds: "Permitir que os utilizadores carreguem fundos de perfil." sequential_replies_threshold: "Número de mensagens que um utilizador tem que fazer em linha num tópico antes de ser relembrado acerca de demasiadas respostas sequenciais." enable_mobile_theme: "Os dispositivos móveis usam um tema mobile-friendly, com a possibilidade de mudar para o sítio completo. Desative isto se quer usar um estilo personalizado que é totalmente responsivo." diff --git a/plugins/poll/config/locales/server.es.yml b/plugins/poll/config/locales/server.es.yml index 990dde322d..1792d5cf91 100644 --- a/plugins/poll/config/locales/server.es.yml +++ b/plugins/poll/config/locales/server.es.yml @@ -35,3 +35,5 @@ es: poll_must_be_open_to_vote: "La encuesta debe estar abierta para votar." topic_must_be_open_to_toggle_status: "Este tema debe estar abierto para cambiar entre estados." only_staff_or_op_can_toggle_status: "Solo un moderador, administrador o el autor original del post puede cambiar el estado de una encuesta." + email: + link_to_poll: "Haz clic para ver la encuesta." From 7436a3feb33cb357230f0781ae36b51e722c7151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 2 Nov 2015 18:26:26 +0100 Subject: [PATCH 096/114] FEATURE: automatically unpin topic when user reaches bottom --- app/assets/javascripts/discourse/views/topic.js.es6 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/assets/javascripts/discourse/views/topic.js.es6 b/app/assets/javascripts/discourse/views/topic.js.es6 index 8a18282362..993f756dff 100644 --- a/app/assets/javascripts/discourse/views/topic.js.es6 +++ b/app/assets/javascripts/discourse/views/topic.js.es6 @@ -4,6 +4,7 @@ import ClickTrack from 'discourse/lib/click-track'; import { listenForViewEvent } from 'discourse/lib/app-events'; import { categoryBadgeHTML } from 'discourse/helpers/category-link'; import Scrolling from 'discourse/mixins/scrolling'; +import isElementInViewport from "discourse/lib/is-element-in-viewport"; const TopicView = Ember.View.extend(AddCategoryClass, AddArchetypeClass, Scrolling, { templateName: 'topic', @@ -117,6 +118,14 @@ const TopicView = Ember.View.extend(AddCategoryClass, AddArchetypeClass, Scrolli headerController.set('showExtraInfo', topic.get('postStream.firstPostNotLoaded')); } + // automatically unpin topics when the user reaches the bottom + if (topic.get("pinned")) { + const $topicFooterButtons = $("#topic-footer-buttons"); + if ($topicFooterButtons.length > 0 && isElementInViewport($topicFooterButtons)) { + Em.run.next(() => topic.clearPin()); + } + } + // Trigger a scrolled event this.appEvents.trigger('topic:scrolled', offset); }, From 4d6dd3a9523eae4e2f1c1c701cac0b9ed2058f6e Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Mon, 2 Nov 2015 14:38:07 -0500 Subject: [PATCH 097/114] add plugin outlet to the top of the admin dashboard --- app/assets/javascripts/admin/templates/dashboard.hbs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/admin/templates/dashboard.hbs b/app/assets/javascripts/admin/templates/dashboard.hbs index 87d9cb699d..ed98e21fcb 100644 --- a/app/assets/javascripts/admin/templates/dashboard.hbs +++ b/app/assets/javascripts/admin/templates/dashboard.hbs @@ -1,3 +1,5 @@ +{{plugin-outlet "admin-dashboard-top"}} +
      {{#if showVersionChecks}} {{partial 'admin/templates/version-checks'}} From f85a311ae14514a847831c8f9e0324121abc4814 Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Mon, 2 Nov 2015 20:40:50 +0100 Subject: [PATCH 098/114] add missing dependency --- lib/locale_file_walker.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/locale_file_walker.rb b/lib/locale_file_walker.rb index 0ac3146e84..4f11ea9ff8 100644 --- a/lib/locale_file_walker.rb +++ b/lib/locale_file_walker.rb @@ -1,4 +1,5 @@ require 'psych' +require 'set' class LocaleFileWalker protected From 290708ca535b63970424dbc80f045435ee79380c Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Tue, 3 Nov 2015 01:23:51 +0530 Subject: [PATCH 099/114] FIX: use absolute URL for open graph image tags --- app/helpers/application_helper.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 5b1a863f22..022141a73a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -129,6 +129,8 @@ module ApplicationHelper if opts[:image].present? && opts[:image].start_with?("//") uri = URI(Discourse.base_url) opts[:image] = "#{uri.scheme}:#{opts[:image]}" + elsif opts[:image].present? && opts[:image].start_with?("/uploads/") + opts[:image] = "#{Discourse.base_url}#{opts[:image]}" end # Add opengraph tags From 5cd63088500f6dc76f39e060fd3a3ef3988a791e Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Mon, 2 Nov 2015 16:05:40 -0500 Subject: [PATCH 100/114] API for adding buttons to the new composer --- .../discourse/components/d-editor.js.es6 | 158 +++++++++++++----- .../initializers/enable-emoji.js.es6 | 11 ++ .../templates/components/d-editor.hbs | 22 +-- .../components/d-editor-test.js.es6 | 42 +++-- 4 files changed, 160 insertions(+), 73 deletions(-) diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6 index 879cb9c643..43b07533ac 100644 --- a/app/assets/javascripts/discourse/components/d-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/d-editor.js.es6 @@ -12,6 +12,96 @@ function getHead(head, prev) { } } +const _createCallbacks = []; + +function Toolbar() { + this.groups = [ + {group: 'fontStyles', buttons: []}, + {group: 'insertions', buttons: []}, + {group: 'extras', buttons: [], lastGroup: true} + ]; + + this.addButton({ + id: 'bold', + group: 'fontStyles', + perform: e => e.applySurround('**', '**', 'bold_text') + }); + + this.addButton({ + id: 'italic', + group: 'fontStyles', + perform: e => e.applySurround('*', '*', 'italic_text') + }); + + this.addButton({group: 'insertions', id: 'link', action: 'showLinkModal'}); + + this.addButton({ + id: 'quote', + group: 'insertions', + icon: 'quote-right', + perform: e => e.applySurround('> ', '', 'code_text') + }); + + this.addButton({ + id: 'code', + group: 'insertions', + perform(e) { + if (e.selected.value.indexOf("\n") !== -1) { + e.applySurround(' ', '', 'code_text'); + } else { + e.applySurround('`', '`', 'code_text'); + } + }, + }); + + this.addButton({ + id: 'bullet', + group: 'extras', + icon: 'list-ul', + perform: e => e.applyList('* ', 'list_item') + }); + + this.addButton({ + id: 'list', + group: 'extras', + icon: 'list-ol', + perform: e => e.applyList(i => !i ? "1. " : `${parseInt(i) + 1}. `, 'list_item') + }); + + this.addButton({ + id: 'heading', + group: 'extras', + icon: 'font', + perform: e => e.applyList('## ', 'heading_text') + }); + + this.addButton({ + id: 'rule', + group: 'extras', + icon: 'minus', + perform: e => e.addText("\n\n----------\n") + }); +}; + +Toolbar.prototype.addButton = function(button) { + const g = this.groups.findProperty('group', button.group); + if (!g) { + throw `Couldn't find toolbar group ${button.group}`; + } + + g.buttons.push({ + id: button.id, + className: button.className || button.id, + icon: button.icon || button.id, + action: button.action || 'toolbarButton', + perform: button.perform || Ember.k + }); +}; + +export function onToolbarCreate(func) { + _createCallbacks.push(func); +}; + export default Ember.Component.extend({ classNames: ['d-editor'], ready: false, @@ -25,6 +115,13 @@ export default Ember.Component.extend({ loadScript('defer/html-sanitizer-bundle').then(() => this.set('ready', true)); }, + @property + toolbar() { + const toolbar = new Toolbar(); + _createCallbacks.forEach(cb => cb(toolbar)); + return toolbar; + }, + @property('ready', 'value') preview(ready, value) { if (!ready) { return; } @@ -51,7 +148,7 @@ export default Ember.Component.extend({ showSelector({ appendTo: self.$(), container, - onSelect: title => self._addText(`${title}:`) + onSelect: title => self._addText(this._getSelected(), `${title}:`) }); return ""; } @@ -112,8 +209,7 @@ export default Ember.Component.extend({ }); }, - _applySurround(head, tail, exampleKey) { - const sel = this._getSelected(); + _applySurround(sel, head, tail, exampleKey) { const pre = sel.pre; const post = sel.post; @@ -162,10 +258,9 @@ export default Ember.Component.extend({ } }, - _applyList(head, exampleKey) { - const sel = this._getSelected(); + _applyList(sel, head, exampleKey) { if (sel.value.indexOf("\n") !== -1) { - this._applySurround(head, '', exampleKey); + this._applySurround(sel, head, '', exampleKey); } else { const [hval, hlen] = getHead(head); @@ -185,20 +280,22 @@ export default Ember.Component.extend({ } }, - _addText(text, sel) { - sel = sel || this._getSelected(); + _addText(sel, text) { const insert = `${sel.pre}${text}`; this.set('value', `${insert}${sel.post}`); this._selectText(insert.length, 0); }, actions: { - bold() { - this._applySurround('**', '**', 'bold_text'); - }, + toolbarButton(button) { - italic() { - this._applySurround('*', '*', 'italic_text'); + const selected = this._getSelected(); + button.perform({ + selected, + applySurround: (head, tail, exampleKey) => this._applySurround(selected, head, tail, exampleKey), + applyList: (head, exampleKey) => this._applyList(selected, head, exampleKey), + addText: text => this._addText(selected, text) + }); }, showLinkModal() { @@ -214,48 +311,19 @@ export default Ember.Component.extend({ if (m && m.length === 2) { const description = m[1]; const remaining = link.replace(m[0], ''); - this._addText(`[${description}](${remaining})`, this._lastSel); + this._addText(this._lastSel, `[${description}](${remaining})`); } else { - this._addText(`[${link}](${link})`, this._lastSel); + this._addText(this._lastSel, `[${link}](${link})`); } this.set('link', ''); }, - code() { - const sel = this._getSelected(); - if (sel.value.indexOf("\n") !== -1) { - this._applySurround(' ', '', 'code_text'); - } else { - this._applySurround('`', '`', 'code_text'); - } - }, - - quote() { - this._applySurround('> ', "", 'code_text'); - }, - - bullet() { - this._applyList('* ', 'list_item'); - }, - - list() { - this._applyList(i => !i ? "1. " : `${parseInt(i) + 1}. `, 'list_item'); - }, - - heading() { - this._applyList('## ', 'heading_text'); - }, - - rule() { - this._addText("\n\n----------\n"); - }, - emoji() { showSelector({ appendTo: this.$(), container: this.container, - onSelect: title => this._addText(`:${title}:`) + onSelect: title => this._addText(this._getSelected(), `:${title}:`) }); } } diff --git a/app/assets/javascripts/discourse/initializers/enable-emoji.js.es6 b/app/assets/javascripts/discourse/initializers/enable-emoji.js.es6 index 3d2bd23b5f..be1b47b121 100644 --- a/app/assets/javascripts/discourse/initializers/enable-emoji.js.es6 +++ b/app/assets/javascripts/discourse/initializers/enable-emoji.js.es6 @@ -1,4 +1,5 @@ import { showSelector } from "discourse/lib/emoji/emoji-toolbar"; +import { onToolbarCreate } from 'discourse/components/d-editor'; export default { name: 'enable-emoji', @@ -6,6 +7,16 @@ export default { initialize(container) { const siteSettings = container.lookup('site-settings:main'); if (siteSettings.enable_emoji) { + + onToolbarCreate(toolbar => { + toolbar.addButton({ + id: 'emoji', + group: 'extras', + icon: 'smile-o', + action: 'emoji' + }); + }); + window.PagedownCustom.appendButtons.push({ id: 'wmd-emoji-button', description: I18n.t("composer.emoji"), diff --git a/app/assets/javascripts/discourse/templates/components/d-editor.hbs b/app/assets/javascripts/discourse/templates/components/d-editor.hbs index 8921294cbe..61e68ae451 100644 --- a/app/assets/javascripts/discourse/templates/components/d-editor.hbs +++ b/app/assets/javascripts/discourse/templates/components/d-editor.hbs @@ -8,20 +8,14 @@
      - {{d-button action="bold" icon="bold" class="bold"}} - {{d-button action="italic" icon="italic" class="italic"}} -
      - {{d-button action="showLinkModal" icon="link" class="link"}} - {{d-button action="quote" icon="quote-right" class="quote"}} - {{d-button action="code" icon="code" class="code"}} -
      - {{d-button action="bullet" icon="list-ul" class="bullet"}} - {{d-button action="list" icon="list-ol" class="list"}} - {{d-button action="heading" icon="font" class="heading"}} - {{d-button action="rule" icon="minus" class="rule"}} - {{#if siteSettings.enable_emoji}} - {{d-button action="emoji" icon="smile-o" class="emoji"}} - {{/if}} + {{#each toolbar.groups as |group|}} + {{#each group.buttons as |b|}} + {{d-button action=b.action actionParam=b icon=b.icon class=b.className title=t.title}} + {{/each}} + {{#unless group.lastGroup}} +
      + {{/unless}} + {{/each}}
      {{textarea value=value class="d-editor-input"}} diff --git a/test/javascripts/components/d-editor-test.js.es6 b/test/javascripts/components/d-editor-test.js.es6 index f27607499c..572d734269 100644 --- a/test/javascripts/components/d-editor-test.js.es6 +++ b/test/javascripts/components/d-editor-test.js.es6 @@ -1,4 +1,5 @@ import componentTest from 'helpers/component-test'; +import { onToolbarCreate } from 'discourse/components/d-editor'; moduleForComponent('d-editor', {integration: true}); @@ -441,21 +442,34 @@ testCase(`rule with a selection`, function(assert, textarea) { }); }); -testCase(`emoji`, function(assert) { - assert.equal($('.emoji-modal').length, 0); +componentTest('emoji', { + template: '{{d-editor value=value}}', + setup() { + // Test adding a custom button + onToolbarCreate(toolbar => { + toolbar.addButton({ + id: 'emoji', + group: 'extras', + icon: 'smile-o', + action: 'emoji' + }); + }); + this.set('value', 'hello world.'); + }, + test(assert) { + assert.equal($('.emoji-modal').length, 0); - click('button.emoji'); - andThen(() => { - assert.equal($('.emoji-modal').length, 1); - }); + click('button.emoji'); + andThen(() => { + assert.equal($('.emoji-modal').length, 1); + }); - click('a[data-group-id=0]'); - click('a[title=grinning]'); + click('a[data-group-id=0]'); + click('a[title=grinning]'); - andThen(() => { - assert.ok($('.emoji-modal').length === 0); - assert.equal(this.get('value'), 'hello world.:grinning:'); - }); + andThen(() => { + assert.ok($('.emoji-modal').length === 0); + assert.equal(this.get('value'), 'hello world.:grinning:'); + }); + } }); - - From 688d44278e3ad297fd7234b47707245a1c7b3c26 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Tue, 3 Nov 2015 12:25:22 +0530 Subject: [PATCH 101/114] UX: move imgur album CSS to Discourse repo --- .../stylesheets/common/base/onebox.scss | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/app/assets/stylesheets/common/base/onebox.scss b/app/assets/stylesheets/common/base/onebox.scss index b8b2acde7e..7f82f9b94d 100644 --- a/app/assets/stylesheets/common/base/onebox.scss +++ b/app/assets/stylesheets/common/base/onebox.scss @@ -260,3 +260,34 @@ aside.onebox.twitterstatus .onebox-body { padding-top: 5px; } } + +// Onebox - Imgur - Album +.onebox.imgur-album { + .outer-box { + position: absolute; + z-index: 935; + width: 100%; + height: 30px; + overflow: hidden; + font-size: 12px; + color: #fff; + background-color: rgba(0, 0, 0, 0.6); + + .inner-box { + padding-left: 10px; + padding-right: 10px; + overflow: hidden; + text-overflow: ellipsis; + word-wrap: normal; + white-space: nowrap; + + .album-title { + width: 100%; + font-size: 13px; + line-height: 30px; + color: #ccc; + text-decoration: none; + } + } + } +} From da32be9294f7ef349bae475676d002a047ca0f4f Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Tue, 3 Nov 2015 19:11:15 +0530 Subject: [PATCH 102/114] UX: resize stackexchange onebox image --- app/assets/stylesheets/common/base/onebox.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/assets/stylesheets/common/base/onebox.scss b/app/assets/stylesheets/common/base/onebox.scss index 7f82f9b94d..6b00a166e0 100644 --- a/app/assets/stylesheets/common/base/onebox.scss +++ b/app/assets/stylesheets/common/base/onebox.scss @@ -291,3 +291,9 @@ aside.onebox.twitterstatus .onebox-body { } } } + +// resize stackexchange onebox image +aside.onebox.stackexchange .onebox-body img { + max-height: 60%; + max-width: 10%; +} From 2e00e91cdcb322fa7a236443f446fb84efc884b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Tue, 3 Nov 2015 15:29:39 +0100 Subject: [PATCH 103/114] update onebox --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 6557c6915a..8142f566d5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -248,7 +248,7 @@ GEM omniauth-twitter (1.2.1) json (~> 1.3) omniauth-oauth (~> 1.1) - onebox (1.5.26) + onebox (1.5.27) moneta (~> 0.8) multi_json (~> 1.11) mustache From 4aa601414d666a5fd04a1fbe10b09c922f70345e Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 3 Nov 2015 11:56:35 -0500 Subject: [PATCH 104/114] Support for titles on d-editor buttons --- .../javascripts/discourse/components/d-editor.js.es6 | 8 ++++++-- .../discourse/initializers/enable-emoji.js.es6 | 3 ++- .../discourse/templates/components/d-editor.hbs | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6 index 43b07533ac..54f07dcd92 100644 --- a/app/assets/javascripts/discourse/components/d-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/d-editor.js.es6 @@ -58,6 +58,7 @@ function Toolbar() { id: 'bullet', group: 'extras', icon: 'list-ul', + title: 'composer.ulist_title', perform: e => e.applyList('* ', 'list_item') }); @@ -65,6 +66,7 @@ function Toolbar() { id: 'list', group: 'extras', icon: 'list-ol', + title: 'composer.olist_title', perform: e => e.applyList(i => !i ? "1. " : `${parseInt(i) + 1}. `, 'list_item') }); @@ -79,6 +81,7 @@ function Toolbar() { id: 'rule', group: 'extras', icon: 'minus', + title: 'composer.hr_title', perform: e => e.addText("\n\n----------\n") }); }; @@ -89,12 +92,14 @@ Toolbar.prototype.addButton = function(button) { throw `Couldn't find toolbar group ${button.group}`; } + const title = button.title || `composer.${button.id}_title`; g.buttons.push({ id: button.id, className: button.className || button.id, icon: button.icon || button.id, action: button.action || 'toolbarButton', - perform: button.perform || Ember.k + perform: button.perform || Ember.k, + title }); }; @@ -288,7 +293,6 @@ export default Ember.Component.extend({ actions: { toolbarButton(button) { - const selected = this._getSelected(); button.perform({ selected, diff --git a/app/assets/javascripts/discourse/initializers/enable-emoji.js.es6 b/app/assets/javascripts/discourse/initializers/enable-emoji.js.es6 index be1b47b121..d5204d1729 100644 --- a/app/assets/javascripts/discourse/initializers/enable-emoji.js.es6 +++ b/app/assets/javascripts/discourse/initializers/enable-emoji.js.es6 @@ -13,7 +13,8 @@ export default { id: 'emoji', group: 'extras', icon: 'smile-o', - action: 'emoji' + action: 'emoji', + title: 'composer.emoji' }); }); diff --git a/app/assets/javascripts/discourse/templates/components/d-editor.hbs b/app/assets/javascripts/discourse/templates/components/d-editor.hbs index 61e68ae451..ca71460686 100644 --- a/app/assets/javascripts/discourse/templates/components/d-editor.hbs +++ b/app/assets/javascripts/discourse/templates/components/d-editor.hbs @@ -10,7 +10,7 @@
      {{#each toolbar.groups as |group|}} {{#each group.buttons as |b|}} - {{d-button action=b.action actionParam=b icon=b.icon class=b.className title=t.title}} + {{d-button action=b.action actionParam=b title=b.title icon=b.icon class=b.className}} {{/each}} {{#unless group.lastGroup}}
      From bb21902954511c66b622ce36ca3aa9afbe49db4b Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 3 Nov 2015 14:01:07 -0500 Subject: [PATCH 105/114] Add keyboard shortcuts back to d-editor --- .../admin/views/admin-customize.js.es6 | 17 +- .../discourse/components/d-editor.js.es6 | 59 +- .../initializers/enable-emoji.js.es6 | 1 + .../templates/components/d-editor.hbs | 2 +- vendor/assets/javascripts/mousetrap.js | 1327 +++++++++-------- 5 files changed, 727 insertions(+), 679 deletions(-) diff --git a/app/assets/javascripts/admin/views/admin-customize.js.es6 b/app/assets/javascripts/admin/views/admin-customize.js.es6 index faca47026f..240ed0ac73 100644 --- a/app/assets/javascripts/admin/views/admin-customize.js.es6 +++ b/app/assets/javascripts/admin/views/admin-customize.js.es6 @@ -1,18 +1,3 @@ -/*global Mousetrap:true */ - export default Ember.View.extend({ - classNames: ['customize'], - - _init: function() { - var controller = this.get('controller'); - Mousetrap.bindGlobal('mod+s', function() { - controller.send("save"); - return false; - }); - }.on("didInsertElement"), - - _cleanUp: function() { - Mousetrap.unbindGlobal('mod+s'); - }.on("willDestroyElement") - + classNames: ['customize'] }); diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6 index 54f07dcd92..736d21bb64 100644 --- a/app/assets/javascripts/discourse/components/d-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/d-editor.js.es6 @@ -1,3 +1,4 @@ +/*global Mousetrap:true */ import loadScript from 'discourse/lib/load-script'; import { default as property, on } from 'ember-addons/ember-computed-decorators'; import { showSelector } from "discourse/lib/emoji/emoji-toolbar"; @@ -15,6 +16,8 @@ function getHead(head, prev) { const _createCallbacks = []; function Toolbar() { + this.shortcuts = {}; + this.groups = [ {group: 'fontStyles', buttons: []}, {group: 'insertions', buttons: []}, @@ -24,27 +27,31 @@ function Toolbar() { this.addButton({ id: 'bold', group: 'fontStyles', + shortcut: 'B', perform: e => e.applySurround('**', '**', 'bold_text') }); this.addButton({ id: 'italic', group: 'fontStyles', + shortcut: 'I', perform: e => e.applySurround('*', '*', 'italic_text') }); - this.addButton({group: 'insertions', id: 'link', action: 'showLinkModal'}); + this.addButton({id: 'link', group: 'insertions', shortcut: 'K', action: 'showLinkModal'}); this.addButton({ id: 'quote', group: 'insertions', icon: 'quote-right', + shortcut: 'Shift+9', perform: e => e.applySurround('> ', '', 'code_text') }); this.addButton({ id: 'code', group: 'insertions', + shortcut: 'Shift+C', perform(e) { if (e.selected.value.indexOf("\n") !== -1) { e.applySurround(' ', '', 'code_text'); @@ -58,6 +65,7 @@ function Toolbar() { id: 'bullet', group: 'extras', icon: 'list-ul', + shortcut: 'Shift+8', title: 'composer.ulist_title', perform: e => e.applyList('* ', 'list_item') }); @@ -66,6 +74,7 @@ function Toolbar() { id: 'list', group: 'extras', icon: 'list-ol', + shortcut: 'Shift+7', title: 'composer.olist_title', perform: e => e.applyList(i => !i ? "1. " : `${parseInt(i) + 1}. `, 'list_item') }); @@ -74,6 +83,7 @@ function Toolbar() { id: 'heading', group: 'extras', icon: 'font', + shortcut: 'Alt+1', perform: e => e.applyList('## ', 'heading_text') }); @@ -81,6 +91,7 @@ function Toolbar() { id: 'rule', group: 'extras', icon: 'minus', + shortcut: 'Alt+R', title: 'composer.hr_title', perform: e => e.addText("\n\n----------\n") }); @@ -92,15 +103,34 @@ Toolbar.prototype.addButton = function(button) { throw `Couldn't find toolbar group ${button.group}`; } - const title = button.title || `composer.${button.id}_title`; - g.buttons.push({ + const createdButton = { id: button.id, className: button.className || button.id, icon: button.icon || button.id, action: button.action || 'toolbarButton', - perform: button.perform || Ember.k, - title - }); + perform: button.perform || Ember.K + }; + + const title = I18n.t(button.title || `composer.${button.id}_title`); + if (button.shortcut) { + const mac = /Mac|iPod|iPhone|iPad/.test(navigator.platform); + const mod = mac ? 'Meta' : 'Ctrl'; + createdButton.title = `${title} (${mod}+${button.shortcut})`; + + // Mac users are used to glyphs for shortcut keys + if (mac) { + createdButton.title = createdButton.title.replace('Shift', "\u{21E7}") + .replace('Meta', "\u{2318}") + .replace('Alt', "\u{2325}") + .replace(/\+/g, ''); + } + + this.shortcuts[`${mod}+${button.shortcut}`.toLowerCase()] = createdButton; + } else { + createdButton.title = title; + } + + g.buttons.push(createdButton); }; export function onToolbarCreate(func) { @@ -115,9 +145,24 @@ export default Ember.Component.extend({ lastSel: null, @on('didInsertElement') - _loadSanitizer() { + _startUp() { this._applyEmojiAutocomplete(); loadScript('defer/html-sanitizer-bundle').then(() => this.set('ready', true)); + + const shortcuts = this.get('toolbar.shortcuts'); + Ember.keys(shortcuts).forEach(sc => { + const button = shortcuts[sc]; + Mousetrap(this.$('.d-editor-input')[0]).bind(sc, () => { + this.send(button.action, button); + }); + }); + }, + + @on('willDestroyElement') + _shutDown() { + Ember.keys(this.get('toolbar.shortcuts')).forEach(sc => { + Mousetrap(this.$('.d-editor-input')[0]).unbind(sc); + }); }, @property diff --git a/app/assets/javascripts/discourse/initializers/enable-emoji.js.es6 b/app/assets/javascripts/discourse/initializers/enable-emoji.js.es6 index d5204d1729..5f23126b77 100644 --- a/app/assets/javascripts/discourse/initializers/enable-emoji.js.es6 +++ b/app/assets/javascripts/discourse/initializers/enable-emoji.js.es6 @@ -14,6 +14,7 @@ export default { group: 'extras', icon: 'smile-o', action: 'emoji', + shortcut: 'Alt+E', title: 'composer.emoji' }); }); diff --git a/app/assets/javascripts/discourse/templates/components/d-editor.hbs b/app/assets/javascripts/discourse/templates/components/d-editor.hbs index ca71460686..5375d94c18 100644 --- a/app/assets/javascripts/discourse/templates/components/d-editor.hbs +++ b/app/assets/javascripts/discourse/templates/components/d-editor.hbs @@ -10,7 +10,7 @@
      {{#each toolbar.groups as |group|}} {{#each group.buttons as |b|}} - {{d-button action=b.action actionParam=b title=b.title icon=b.icon class=b.className}} + {{d-button action=b.action actionParam=b translatedTitle=b.title icon=b.icon class=b.className}} {{/each}} {{#unless group.lastGroup}}
      diff --git a/vendor/assets/javascripts/mousetrap.js b/vendor/assets/javascripts/mousetrap.js index 108216bd71..48d2f625c2 100644 --- a/vendor/assets/javascripts/mousetrap.js +++ b/vendor/assets/javascripts/mousetrap.js @@ -1,6 +1,6 @@ /*global define:false */ /** - * Copyright 2013 Craig Campbell + * Copyright 2015 Craig Campbell * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ * Mousetrap is a simple keyboard shortcut library for Javascript with * no external dependencies * - * @version 1.4.6 + * @version 1.5.3 * @url craig.is/killing/mice */ (function(window, document, undefined) { @@ -32,162 +32,112 @@ * @type {Object} */ var _MAP = { - 8: 'backspace', - 9: 'tab', - 13: 'enter', - 16: 'shift', - 17: 'ctrl', - 18: 'alt', - 20: 'capslock', - 27: 'esc', - 32: 'space', - 33: 'pageup', - 34: 'pagedown', - 35: 'end', - 36: 'home', - 37: 'left', - 38: 'up', - 39: 'right', - 40: 'down', - 45: 'ins', - 46: 'del', - 91: 'meta', - 93: 'meta', - 224: 'meta' - }, + 8: 'backspace', + 9: 'tab', + 13: 'enter', + 16: 'shift', + 17: 'ctrl', + 18: 'alt', + 20: 'capslock', + 27: 'esc', + 32: 'space', + 33: 'pageup', + 34: 'pagedown', + 35: 'end', + 36: 'home', + 37: 'left', + 38: 'up', + 39: 'right', + 40: 'down', + 45: 'ins', + 46: 'del', + 91: 'meta', + 93: 'meta', + 224: 'meta' + }; - /** - * mapping for special characters so they can support - * - * this dictionary is only used incase you want to bind a - * keyup or keydown event to one of these keys - * - * @type {Object} - */ - _KEYCODE_MAP = { - 106: '*', - 107: '+', - 109: '-', - 110: '.', - 111 : '/', - 186: ';', - 187: '=', - 188: ',', - 189: '-', - 190: '.', - 191: '/', - 192: '`', - 219: '[', - 220: '\\', - 221: ']', - 222: '\'' - }, + /** + * mapping for special characters so they can support + * + * this dictionary is only used incase you want to bind a + * keyup or keydown event to one of these keys + * + * @type {Object} + */ + var _KEYCODE_MAP = { + 106: '*', + 107: '+', + 109: '-', + 110: '.', + 111 : '/', + 186: ';', + 187: '=', + 188: ',', + 189: '-', + 190: '.', + 191: '/', + 192: '`', + 219: '[', + 220: '\\', + 221: ']', + 222: '\'' + }; - /** - * this is a mapping of keys that require shift on a US keypad - * back to the non shift equivelents - * - * this is so you can use keyup events with these keys - * - * note that this will only work reliably on US keyboards - * - * @type {Object} - */ - _SHIFT_MAP = { - '~': '`', - '!': '1', - '@': '2', - '#': '3', - '$': '4', - '%': '5', - '^': '6', - '&': '7', - '*': '8', - '(': '9', - ')': '0', - '_': '-', - '+': '=', - ':': ';', - '\"': '\'', - '<': ',', - '>': '.', - '?': '/', - '|': '\\' - }, + /** + * this is a mapping of keys that require shift on a US keypad + * back to the non shift equivelents + * + * this is so you can use keyup events with these keys + * + * note that this will only work reliably on US keyboards + * + * @type {Object} + */ + var _SHIFT_MAP = { + '~': '`', + '!': '1', + '@': '2', + '#': '3', + '$': '4', + '%': '5', + '^': '6', + '&': '7', + '*': '8', + '(': '9', + ')': '0', + '_': '-', + '+': '=', + ':': ';', + '\"': '\'', + '<': ',', + '>': '.', + '?': '/', + '|': '\\' + }; - /** - * this is a list of special strings you can use to map - * to modifier keys when you specify your keyboard shortcuts - * - * @type {Object} - */ - _SPECIAL_ALIASES = { - 'option': 'alt', - 'command': 'meta', - 'return': 'enter', - 'escape': 'esc', - 'mod': /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl' - }, + /** + * this is a list of special strings you can use to map + * to modifier keys when you specify your keyboard shortcuts + * + * @type {Object} + */ + var _SPECIAL_ALIASES = { + 'option': 'alt', + 'command': 'meta', + 'return': 'enter', + 'escape': 'esc', + 'plus': '+', + 'mod': /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl' + }; - /** - * variable to store the flipped version of _MAP from above - * needed to check if we should use keypress or not when no action - * is specified - * - * @type {Object|undefined} - */ - _REVERSE_MAP, - - /** - * a list of all the callbacks setup via Mousetrap.bind() - * - * @type {Object} - */ - _callbacks = {}, - - /** - * direct map of string combinations to callbacks used for trigger() - * - * @type {Object} - */ - _directMap = {}, - - /** - * keeps track of what level each sequence is at since multiple - * sequences can start out with the same sequence - * - * @type {Object} - */ - _sequenceLevels = {}, - - /** - * variable to store the setTimeout call - * - * @type {null|number} - */ - _resetTimer, - - /** - * temporary state where we will ignore the next keyup - * - * @type {boolean|string} - */ - _ignoreNextKeyup = false, - - /** - * temporary state where we will ignore the next keypress - * - * @type {boolean} - */ - _ignoreNextKeypress = false, - - /** - * are we currently inside of a sequence? - * type of action ("keyup" or "keydown" or "keypress") or false - * - * @type {boolean|string} - */ - _nextExpectedAction = false; + /** + * variable to store the flipped version of _MAP from above + * needed to check if we should use keypress or not when no action + * is specified + * + * @type {Object|undefined} + */ + var _REVERSE_MAP; /** * loop through the f keys, f1 to f19 and add them to the map @@ -277,103 +227,6 @@ return modifiers1.sort().join(',') === modifiers2.sort().join(','); } - /** - * resets all sequence counters except for the ones passed in - * - * @param {Object} doNotReset - * @returns void - */ - function _resetSequences(doNotReset) { - doNotReset = doNotReset || {}; - - var activeSequences = false, - key; - - for (key in _sequenceLevels) { - if (doNotReset[key]) { - activeSequences = true; - continue; - } - _sequenceLevels[key] = 0; - } - - if (!activeSequences) { - _nextExpectedAction = false; - } - } - - /** - * finds all callbacks that match based on the keycode, modifiers, - * and action - * - * @param {string} character - * @param {Array} modifiers - * @param {Event|Object} e - * @param {string=} sequenceName - name of the sequence we are looking for - * @param {string=} combination - * @param {number=} level - * @returns {Array} - */ - function _getMatches(character, modifiers, e, sequenceName, combination, level) { - var i, - callback, - matches = [], - action = e.type; - - // if there are no events related to this keycode - if (!_callbacks[character]) { - return []; - } - - // if a modifier key is coming up on its own we should allow it - if (action == 'keyup' && _isModifier(character)) { - modifiers = [character]; - } - - // loop through all callbacks for the key that was pressed - // and see if any of them match - for (i = 0; i < _callbacks[character].length; ++i) { - callback = _callbacks[character][i]; - - // if a sequence name is not specified, but this is a sequence at - // the wrong level then move onto the next match - if (!sequenceName && callback.seq && _sequenceLevels[callback.seq] != callback.level) { - continue; - } - - // if the action we are looking for doesn't match the action we got - // then we should keep going - if (action != callback.action) { - continue; - } - - // if this is a keypress event and the meta key and control key - // are not pressed that means that we need to only look at the - // character, otherwise check the modifiers as well - // - // chrome will not fire a keypress if meta or control is down - // safari will fire a keypress if meta or meta+shift is down - // firefox will fire a keypress if meta or control is down - if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || _modifiersMatch(modifiers, callback.modifiers)) { - - // when you bind a combination or sequence a second time it - // should overwrite the first one. if a sequenceName or - // combination is specified in this call it does just that - // - // @todo make deleting its own method? - var deleteCombo = !sequenceName && callback.combo == combination; - var deleteSequence = sequenceName && callback.seq == sequenceName && callback.level == level; - if (deleteCombo || deleteSequence) { - _callbacks[character].splice(i, 1); - } - - matches.push(callback); - } - } - - return matches; - } - /** * takes a key event and figures out what the modifiers are * @@ -432,147 +285,6 @@ e.cancelBubble = true; } - /** - * actually calls the callback function - * - * if your callback function returns false this will use the jquery - * convention - prevent default and stop propogation on the event - * - * @param {Function} callback - * @param {Event} e - * @returns void - */ - function _fireCallback(callback, e, combo, sequence) { - - // if this event should not happen stop here - if (Mousetrap.stopCallback(e, e.target || e.srcElement, combo, sequence)) { - return; - } - - if (callback(e, combo) === false) { - _preventDefault(e); - _stopPropagation(e); - } - } - - /** - * handles a character key event - * - * @param {string} character - * @param {Array} modifiers - * @param {Event} e - * @returns void - */ - function _handleKey(character, modifiers, e) { - var callbacks = _getMatches(character, modifiers, e), - i, - doNotReset = {}, - maxLevel = 0, - processedSequenceCallback = false; - - // Calculate the maxLevel for sequences so we can only execute the longest callback sequence - for (i = 0; i < callbacks.length; ++i) { - if (callbacks[i].seq) { - maxLevel = Math.max(maxLevel, callbacks[i].level); - } - } - - // loop through matching callbacks for this key event - for (i = 0; i < callbacks.length; ++i) { - - // fire for all sequence callbacks - // this is because if for example you have multiple sequences - // bound such as "g i" and "g t" they both need to fire the - // callback for matching g cause otherwise you can only ever - // match the first one - if (callbacks[i].seq) { - - // only fire callbacks for the maxLevel to prevent - // subsequences from also firing - // - // for example 'a option b' should not cause 'option b' to fire - // even though 'option b' is part of the other sequence - // - // any sequences that do not match here will be discarded - // below by the _resetSequences call - if (callbacks[i].level != maxLevel) { - continue; - } - - processedSequenceCallback = true; - - // keep a list of which sequences were matches for later - doNotReset[callbacks[i].seq] = 1; - _fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq); - continue; - } - - // if there were no sequence matches but we are still here - // that means this is a regular match so we should fire that - if (!processedSequenceCallback) { - _fireCallback(callbacks[i].callback, e, callbacks[i].combo); - } - } - - // if the key you pressed matches the type of sequence without - // being a modifier (ie "keyup" or "keypress") then we should - // reset all sequences that were not matched by this event - // - // this is so, for example, if you have the sequence "h a t" and you - // type "h e a r t" it does not match. in this case the "e" will - // cause the sequence to reset - // - // modifier keys are ignored because you can have a sequence - // that contains modifiers such as "enter ctrl+space" and in most - // cases the modifier key will be pressed before the next key - // - // also if you have a sequence such as "ctrl+b a" then pressing the - // "b" key will trigger a "keypress" and a "keydown" - // - // the "keydown" is expected when there is a modifier, but the - // "keypress" ends up matching the _nextExpectedAction since it occurs - // after and that causes the sequence to reset - // - // we ignore keypresses in a sequence that directly follow a keydown - // for the same character - var ignoreThisKeypress = e.type == 'keypress' && _ignoreNextKeypress; - if (e.type == _nextExpectedAction && !_isModifier(character) && !ignoreThisKeypress) { - _resetSequences(doNotReset); - } - - _ignoreNextKeypress = processedSequenceCallback && e.type == 'keydown'; - } - - /** - * handles a keydown event - * - * @param {Event} e - * @returns void - */ - function _handleKeyEvent(e) { - - // normalize e.which for key events - // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion - if (typeof e.which !== 'number') { - e.which = e.keyCode; - } - - var character = _characterFromEvent(e); - - // no character found then stop - if (!character) { - return; - } - - // need to use === for the character check because the character can be 0 - if (e.type == 'keyup' && _ignoreNextKeyup === character) { - _ignoreNextKeyup = false; - return; - } - - Mousetrap.handleKey(character, _eventModifiers(e), e); - } - /** * determines if the keycode specified is a modifier key or not * @@ -583,19 +295,6 @@ return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta'; } - /** - * called to set a 1 second timeout on the specified sequence - * - * this is so after each key press in the sequence you have 1 second - * to press the next key before you have to start over - * - * @returns void - */ - function _resetSequenceTimer() { - clearTimeout(_resetTimer); - _resetTimer = setTimeout(_resetSequences, 1000); - } - /** * reverses the map lookup so that we can look for specific keys * to see what can and can't use keypress @@ -645,74 +344,6 @@ return action; } - /** - * binds a key sequence to an event - * - * @param {string} combo - combo specified in bind call - * @param {Array} keys - * @param {Function} callback - * @param {string=} action - * @returns void - */ - function _bindSequence(combo, keys, callback, action) { - - // start off by adding a sequence level record for this combination - // and setting the level to 0 - _sequenceLevels[combo] = 0; - - /** - * callback to increase the sequence level for this sequence and reset - * all other sequences that were active - * - * @param {string} nextAction - * @returns {Function} - */ - function _increaseSequence(nextAction) { - return function() { - _nextExpectedAction = nextAction; - ++_sequenceLevels[combo]; - _resetSequenceTimer(); - }; - } - - /** - * wraps the specified callback inside of another function in order - * to reset all sequence counters as soon as this sequence is done - * - * @param {Event} e - * @returns void - */ - function _callbackAndReset(e) { - _fireCallback(callback, e, combo); - - // we should ignore the next key up if the action is key down - // or keypress. this is so if you finish a sequence and - // release the key the final key will not trigger a keyup - if (action !== 'keyup') { - _ignoreNextKeyup = _characterFromEvent(e); - } - - // weird race condition if a sequence ends with the key - // another sequence begins with - setTimeout(_resetSequences, 10); - } - - // loop through keys one at a time and bind the appropriate callback - // function. for any key leading up to the final one it should - // increase the sequence. after the final, it should reset all sequences - // - // if an action is specified in the original bind call then that will - // be used throughout. otherwise we will pass the action that the - // next key in the sequence should match. this allows a sequence - // to mix and match keypress and keydown events depending on which - // ones are better suited to the key provided - for (var i = 0; i < keys.length; ++i) { - var isFinal = i + 1 === keys.length; - var wrappedCallback = isFinal ? _callbackAndReset : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action); - _bindSingle(keys[i], wrappedCallback, action, combo, i); - } - } - /** * Converts from a string key combination to an array * @@ -724,6 +355,7 @@ return ['+']; } + combination = combination.replace(/\+{2}/g, '+plus'); return combination.split('+'); } @@ -735,10 +367,10 @@ * @returns {Object} */ function _getKeyInfo(combination, action) { - var keys, - key, - i, - modifiers = []; + var keys; + var key; + var i; + var modifiers = []; // take the keys from this pattern and figure out what the actual // pattern is all about @@ -777,228 +409,613 @@ }; } - /** - * binds a single keyboard combination - * - * @param {string} combination - * @param {Function} callback - * @param {string=} action - * @param {string=} sequenceName - name of sequence if part of sequence - * @param {number=} level - what part of the sequence the command is - * @returns void - */ - function _bindSingle(combination, callback, action, sequenceName, level) { - - // store a direct mapped reference for use with Mousetrap.trigger - _directMap[combination + ':' + action] = callback; - - // make sure multiple spaces in a row become a single space - combination = combination.replace(/\s+/g, ' '); - - var sequence = combination.split(' '), - info; - - // if this pattern is a sequence of keys then run through this method - // to reprocess each pattern one key at a time - if (sequence.length > 1) { - _bindSequence(combination, sequence, callback, action); - return; + function _belongsTo(element, ancestor) { + if (element === null || element === document) { + return false; } - info = _getKeyInfo(combination, action); - - // make sure to initialize array if this is the first time - // a callback is added for this key - _callbacks[info.key] = _callbacks[info.key] || []; - - // remove an existing match if there is one - _getMatches(info.key, info.modifiers, {type: info.action}, sequenceName, combination, level); - - // add this call back to the array - // if it is a sequence put it at the beginning - // if not put it at the end - // - // this is important because the way these are processed expects - // the sequence ones to come first - _callbacks[info.key][sequenceName ? 'unshift' : 'push']({ - callback: callback, - modifiers: info.modifiers, - action: info.action, - seq: sequenceName, - level: level, - combo: combination - }); - } - - /** - * binds multiple combinations to the same callback - * - * @param {Array} combinations - * @param {Function} callback - * @param {string|undefined} action - * @returns void - */ - function _bindMultiple(combinations, callback, action) { - for (var i = 0; i < combinations.length; ++i) { - _bindSingle(combinations[i], callback, action); + if (element === ancestor) { + return true; } + + return _belongsTo(element.parentNode, ancestor); } - // start! - _addEvent(document, 'keypress', _handleKeyEvent); - _addEvent(document, 'keydown', _handleKeyEvent); - _addEvent(document, 'keyup', _handleKeyEvent); + function Mousetrap(targetElement) { + var self = this; - var Mousetrap = { + targetElement = targetElement || document; + + if (!(self instanceof Mousetrap)) { + return new Mousetrap(targetElement); + } /** - * binds an event to mousetrap + * element to attach key events to * - * can be a single key, a combination of keys separated with +, - * an array of keys, or a sequence of keys separated by spaces + * @type {Element} + */ + self.target = targetElement; + + /** + * a list of all the callbacks setup via Mousetrap.bind() * - * be sure to list the modifier keys first to make sure that the - * correct key ends up getting bound (the last key in the pattern) + * @type {Object} + */ + self._callbacks = {}; + + /** + * direct map of string combinations to callbacks used for trigger() + * + * @type {Object} + */ + self._directMap = {}; + + /** + * keeps track of what level each sequence is at since multiple + * sequences can start out with the same sequence + * + * @type {Object} + */ + var _sequenceLevels = {}; + + /** + * variable to store the setTimeout call + * + * @type {null|number} + */ + var _resetTimer; + + /** + * temporary state where we will ignore the next keyup + * + * @type {boolean|string} + */ + var _ignoreNextKeyup = false; + + /** + * temporary state where we will ignore the next keypress + * + * @type {boolean} + */ + var _ignoreNextKeypress = false; + + /** + * are we currently inside of a sequence? + * type of action ("keyup" or "keydown" or "keypress") or false + * + * @type {boolean|string} + */ + var _nextExpectedAction = false; + + /** + * resets all sequence counters except for the ones passed in + * + * @param {Object} doNotReset + * @returns void + */ + function _resetSequences(doNotReset) { + doNotReset = doNotReset || {}; + + var activeSequences = false, + key; + + for (key in _sequenceLevels) { + if (doNotReset[key]) { + activeSequences = true; + continue; + } + _sequenceLevels[key] = 0; + } + + if (!activeSequences) { + _nextExpectedAction = false; + } + } + + /** + * finds all callbacks that match based on the keycode, modifiers, + * and action + * + * @param {string} character + * @param {Array} modifiers + * @param {Event|Object} e + * @param {string=} sequenceName - name of the sequence we are looking for + * @param {string=} combination + * @param {number=} level + * @returns {Array} + */ + function _getMatches(character, modifiers, e, sequenceName, combination, level) { + var i; + var callback; + var matches = []; + var action = e.type; + + // if there are no events related to this keycode + if (!self._callbacks[character]) { + return []; + } + + // if a modifier key is coming up on its own we should allow it + if (action == 'keyup' && _isModifier(character)) { + modifiers = [character]; + } + + // loop through all callbacks for the key that was pressed + // and see if any of them match + for (i = 0; i < self._callbacks[character].length; ++i) { + callback = self._callbacks[character][i]; + + // if a sequence name is not specified, but this is a sequence at + // the wrong level then move onto the next match + if (!sequenceName && callback.seq && _sequenceLevels[callback.seq] != callback.level) { + continue; + } + + // if the action we are looking for doesn't match the action we got + // then we should keep going + if (action != callback.action) { + continue; + } + + // if this is a keypress event and the meta key and control key + // are not pressed that means that we need to only look at the + // character, otherwise check the modifiers as well + // + // chrome will not fire a keypress if meta or control is down + // safari will fire a keypress if meta or meta+shift is down + // firefox will fire a keypress if meta or control is down + if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || _modifiersMatch(modifiers, callback.modifiers)) { + + // when you bind a combination or sequence a second time it + // should overwrite the first one. if a sequenceName or + // combination is specified in this call it does just that + // + // @todo make deleting its own method? + var deleteCombo = !sequenceName && callback.combo == combination; + var deleteSequence = sequenceName && callback.seq == sequenceName && callback.level == level; + if (deleteCombo || deleteSequence) { + self._callbacks[character].splice(i, 1); + } + + matches.push(callback); + } + } + + return matches; + } + + /** + * actually calls the callback function + * + * if your callback function returns false this will use the jquery + * convention - prevent default and stop propogation on the event * - * @param {string|Array} keys * @param {Function} callback - * @param {string=} action - 'keypress', 'keydown', or 'keyup' + * @param {Event} e * @returns void */ - bind: function(keys, callback, action) { - keys = keys instanceof Array ? keys : [keys]; - _bindMultiple(keys, callback, action); - return this; - }, + function _fireCallback(callback, e, combo, sequence) { + + // if this event should not happen stop here + if (self.stopCallback(e, e.target || e.srcElement, combo, sequence)) { + return; + } + + if (callback(e, combo) === false) { + _preventDefault(e); + _stopPropagation(e); + } + } /** - * unbinds an event to mousetrap + * handles a character key event * - * the unbinding sets the callback function of the specified key combo - * to an empty function and deletes the corresponding key in the - * _directMap dict. - * - * TODO: actually remove this from the _callbacks dictionary instead - * of binding an empty function - * - * the keycombo+action has to be exactly the same as - * it was defined in the bind method - * - * @param {string|Array} keys - * @param {string} action + * @param {string} character + * @param {Array} modifiers + * @param {Event} e * @returns void */ - unbind: function(keys, action) { - return Mousetrap.bind(keys, function() {}, action); - }, + self._handleKey = function(character, modifiers, e) { + var callbacks = _getMatches(character, modifiers, e); + var i; + var doNotReset = {}; + var maxLevel = 0; + var processedSequenceCallback = false; + + // Calculate the maxLevel for sequences so we can only execute the longest callback sequence + for (i = 0; i < callbacks.length; ++i) { + if (callbacks[i].seq) { + maxLevel = Math.max(maxLevel, callbacks[i].level); + } + } + + // loop through matching callbacks for this key event + for (i = 0; i < callbacks.length; ++i) { + + // fire for all sequence callbacks + // this is because if for example you have multiple sequences + // bound such as "g i" and "g t" they both need to fire the + // callback for matching g cause otherwise you can only ever + // match the first one + if (callbacks[i].seq) { + + // only fire callbacks for the maxLevel to prevent + // subsequences from also firing + // + // for example 'a option b' should not cause 'option b' to fire + // even though 'option b' is part of the other sequence + // + // any sequences that do not match here will be discarded + // below by the _resetSequences call + if (callbacks[i].level != maxLevel) { + continue; + } + + processedSequenceCallback = true; + + // keep a list of which sequences were matches for later + doNotReset[callbacks[i].seq] = 1; + _fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq); + continue; + } + + // if there were no sequence matches but we are still here + // that means this is a regular match so we should fire that + if (!processedSequenceCallback) { + _fireCallback(callbacks[i].callback, e, callbacks[i].combo); + } + } + + // if the key you pressed matches the type of sequence without + // being a modifier (ie "keyup" or "keypress") then we should + // reset all sequences that were not matched by this event + // + // this is so, for example, if you have the sequence "h a t" and you + // type "h e a r t" it does not match. in this case the "e" will + // cause the sequence to reset + // + // modifier keys are ignored because you can have a sequence + // that contains modifiers such as "enter ctrl+space" and in most + // cases the modifier key will be pressed before the next key + // + // also if you have a sequence such as "ctrl+b a" then pressing the + // "b" key will trigger a "keypress" and a "keydown" + // + // the "keydown" is expected when there is a modifier, but the + // "keypress" ends up matching the _nextExpectedAction since it occurs + // after and that causes the sequence to reset + // + // we ignore keypresses in a sequence that directly follow a keydown + // for the same character + var ignoreThisKeypress = e.type == 'keypress' && _ignoreNextKeypress; + if (e.type == _nextExpectedAction && !_isModifier(character) && !ignoreThisKeypress) { + _resetSequences(doNotReset); + } + + _ignoreNextKeypress = processedSequenceCallback && e.type == 'keydown'; + }; /** - * triggers an event that has already been bound + * handles a keydown event * - * @param {string} keys + * @param {Event} e + * @returns void + */ + function _handleKeyEvent(e) { + + // normalize e.which for key events + // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion + if (typeof e.which !== 'number') { + e.which = e.keyCode; + } + + var character = _characterFromEvent(e); + + // no character found then stop + if (!character) { + return; + } + + // need to use === for the character check because the character can be 0 + if (e.type == 'keyup' && _ignoreNextKeyup === character) { + _ignoreNextKeyup = false; + return; + } + + self.handleKey(character, _eventModifiers(e), e); + } + + /** + * called to set a 1 second timeout on the specified sequence + * + * this is so after each key press in the sequence you have 1 second + * to press the next key before you have to start over + * + * @returns void + */ + function _resetSequenceTimer() { + clearTimeout(_resetTimer); + _resetTimer = setTimeout(_resetSequences, 1000); + } + + /** + * binds a key sequence to an event + * + * @param {string} combo - combo specified in bind call + * @param {Array} keys + * @param {Function} callback * @param {string=} action * @returns void */ - trigger: function(keys, action) { - if (_directMap[keys + ':' + action]) { - _directMap[keys + ':' + action]({}, keys); + function _bindSequence(combo, keys, callback, action) { + + // start off by adding a sequence level record for this combination + // and setting the level to 0 + _sequenceLevels[combo] = 0; + + /** + * callback to increase the sequence level for this sequence and reset + * all other sequences that were active + * + * @param {string} nextAction + * @returns {Function} + */ + function _increaseSequence(nextAction) { + return function() { + _nextExpectedAction = nextAction; + ++_sequenceLevels[combo]; + _resetSequenceTimer(); + }; } - return this; - }, + + /** + * wraps the specified callback inside of another function in order + * to reset all sequence counters as soon as this sequence is done + * + * @param {Event} e + * @returns void + */ + function _callbackAndReset(e) { + _fireCallback(callback, e, combo); + + // we should ignore the next key up if the action is key down + // or keypress. this is so if you finish a sequence and + // release the key the final key will not trigger a keyup + if (action !== 'keyup') { + _ignoreNextKeyup = _characterFromEvent(e); + } + + // weird race condition if a sequence ends with the key + // another sequence begins with + setTimeout(_resetSequences, 10); + } + + // loop through keys one at a time and bind the appropriate callback + // function. for any key leading up to the final one it should + // increase the sequence. after the final, it should reset all sequences + // + // if an action is specified in the original bind call then that will + // be used throughout. otherwise we will pass the action that the + // next key in the sequence should match. this allows a sequence + // to mix and match keypress and keydown events depending on which + // ones are better suited to the key provided + for (var i = 0; i < keys.length; ++i) { + var isFinal = i + 1 === keys.length; + var wrappedCallback = isFinal ? _callbackAndReset : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action); + _bindSingle(keys[i], wrappedCallback, action, combo, i); + } + } /** - * resets the library back to its initial state. this is useful - * if you want to clear out the current keyboard shortcuts and bind - * new ones - for example if you switch to another page + * binds a single keyboard combination * + * @param {string} combination + * @param {Function} callback + * @param {string=} action + * @param {string=} sequenceName - name of sequence if part of sequence + * @param {number=} level - what part of the sequence the command is * @returns void */ - reset: function() { - _callbacks = {}; - _directMap = {}; - return this; - }, + function _bindSingle(combination, callback, action, sequenceName, level) { - /** - * should we stop this event before firing off callbacks - * - * @param {Event} e - * @param {Element} element - * @return {boolean} - */ - stopCallback: function(e, element) { + // store a direct mapped reference for use with Mousetrap.trigger + self._directMap[combination + ':' + action] = callback; - // if the element has the class "mousetrap" then no need to stop - if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) { - return false; + // make sure multiple spaces in a row become a single space + combination = combination.replace(/\s+/g, ' '); + + var sequence = combination.split(' '); + var info; + + // if this pattern is a sequence of keys then run through this method + // to reprocess each pattern one key at a time + if (sequence.length > 1) { + _bindSequence(combination, sequence, callback, action); + return; } - // stop for input, select, and textarea - return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable; - }, + info = _getKeyInfo(combination, action); + + // make sure to initialize array if this is the first time + // a callback is added for this key + self._callbacks[info.key] = self._callbacks[info.key] || []; + + // remove an existing match if there is one + _getMatches(info.key, info.modifiers, {type: info.action}, sequenceName, combination, level); + + // add this call back to the array + // if it is a sequence put it at the beginning + // if not put it at the end + // + // this is important because the way these are processed expects + // the sequence ones to come first + self._callbacks[info.key][sequenceName ? 'unshift' : 'push']({ + callback: callback, + modifiers: info.modifiers, + action: info.action, + seq: sequenceName, + level: level, + combo: combination + }); + } /** - * exposes _handleKey publicly so it can be overwritten by extensions + * binds multiple combinations to the same callback + * + * @param {Array} combinations + * @param {Function} callback + * @param {string|undefined} action + * @returns void */ - handleKey: _handleKey + self._bindMultiple = function(combinations, callback, action) { + for (var i = 0; i < combinations.length; ++i) { + _bindSingle(combinations[i], callback, action); + } + }; + + // start! + _addEvent(targetElement, 'keypress', _handleKeyEvent); + _addEvent(targetElement, 'keydown', _handleKeyEvent); + _addEvent(targetElement, 'keyup', _handleKeyEvent); + } + + /** + * binds an event to mousetrap + * + * can be a single key, a combination of keys separated with +, + * an array of keys, or a sequence of keys separated by spaces + * + * be sure to list the modifier keys first to make sure that the + * correct key ends up getting bound (the last key in the pattern) + * + * @param {string|Array} keys + * @param {Function} callback + * @param {string=} action - 'keypress', 'keydown', or 'keyup' + * @returns void + */ + Mousetrap.prototype.bind = function(keys, callback, action) { + var self = this; + keys = keys instanceof Array ? keys : [keys]; + self._bindMultiple.call(self, keys, callback, action); + return self; }; + /** + * unbinds an event to mousetrap + * + * the unbinding sets the callback function of the specified key combo + * to an empty function and deletes the corresponding key in the + * _directMap dict. + * + * TODO: actually remove this from the _callbacks dictionary instead + * of binding an empty function + * + * the keycombo+action has to be exactly the same as + * it was defined in the bind method + * + * @param {string|Array} keys + * @param {string} action + * @returns void + */ + Mousetrap.prototype.unbind = function(keys, action) { + var self = this; + return self.bind.call(self, keys, function() {}, action); + }; + + /** + * triggers an event that has already been bound + * + * @param {string} keys + * @param {string=} action + * @returns void + */ + Mousetrap.prototype.trigger = function(keys, action) { + var self = this; + if (self._directMap[keys + ':' + action]) { + self._directMap[keys + ':' + action]({}, keys); + } + return self; + }; + + /** + * resets the library back to its initial state. this is useful + * if you want to clear out the current keyboard shortcuts and bind + * new ones - for example if you switch to another page + * + * @returns void + */ + Mousetrap.prototype.reset = function() { + var self = this; + self._callbacks = {}; + self._directMap = {}; + return self; + }; + + /** + * should we stop this event before firing off callbacks + * + * @param {Event} e + * @param {Element} element + * @return {boolean} + */ + Mousetrap.prototype.stopCallback = function(e, element) { + var self = this; + + // if the element has the class "mousetrap" then no need to stop + if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) { + return false; + } + + if (_belongsTo(element, self.target)) { + return false; + } + + // stop for input, select, and textarea + return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable; + }; + + /** + * exposes _handleKey publicly so it can be overwritten by extensions + */ + Mousetrap.prototype.handleKey = function() { + var self = this; + return self._handleKey.apply(self, arguments); + }; + + /** + * Init the global mousetrap functions + * + * This method is needed to allow the global mousetrap functions to work + * now that mousetrap is a constructor function. + */ + Mousetrap.init = function() { + var documentMousetrap = Mousetrap(document); + for (var method in documentMousetrap) { + if (method.charAt(0) !== '_') { + Mousetrap[method] = (function(method) { + return function() { + return documentMousetrap[method].apply(documentMousetrap, arguments); + }; + } (method)); + } + } + }; + + Mousetrap.init(); + // expose mousetrap to the global object window.Mousetrap = Mousetrap; + // expose as a common js module + if (typeof module !== 'undefined' && module.exports) { + module.exports = Mousetrap; + } + // expose mousetrap as an AMD module if (typeof define === 'function' && define.amd) { - define(Mousetrap); + define(function() { + return Mousetrap; + }); } }) (window, document); - -/** - * adds a bindGlobal method to Mousetrap that allows you to - * bind specific keyboard shortcuts that will still work - * inside a text input field - * - * usage: - * Mousetrap.bindGlobal('ctrl+s', _saveChanges); - * Mousetrap.unbindGlobal('ctrl+s'); - */ -/* global Mousetrap:true */ -Mousetrap = (function(Mousetrap) { - var _globalCallbacks = {}, - _originalStopCallback = Mousetrap.stopCallback; - - Mousetrap.stopCallback = function(e, element, combo, sequence) { - if (_globalCallbacks[combo] || _globalCallbacks[sequence]) { - return false; - } - - return _originalStopCallback(e, element, combo); - }; - - Mousetrap.bindGlobal = function(keys, callback, action) { - Mousetrap.bind(keys, callback, action); - - if (keys instanceof Array) { - for (var i = 0; i < keys.length; i++) { - _globalCallbacks[keys[i]] = true; - } - return; - } - - _globalCallbacks[keys] = true; - }; - - Mousetrap.unbindGlobal = function(keys, action) { - Mousetrap.unbind(keys, action); - - if (keys instanceof Array) { - for (var i = 0; i < keys.length; i++) { - _globalCallbacks[keys[i]] = false; - } - return; - } - - _globalCallbacks[keys] = false; - }; - - return Mousetrap; -}) (Mousetrap); From d7d88f816c6a2a4eb63e27c7319d769e0f7fbbc0 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 3 Nov 2015 14:22:24 -0500 Subject: [PATCH 106/114] FIX: Don't use transform for the emoji popup, it blurs it --- app/assets/stylesheets/common/base/emoji.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/common/base/emoji.scss b/app/assets/stylesheets/common/base/emoji.scss index 264416f1b6..107fbd11af 100644 --- a/app/assets/stylesheets/common/base/emoji.scss +++ b/app/assets/stylesheets/common/base/emoji.scss @@ -9,11 +9,14 @@ body img.emoji { } .emoji-modal { - @include transform(translate(-50%, -50%)); z-index: 10000; position: fixed; left: 50%; top: 50%; + width: 445px; + height: 264px; + margin-top: -132px; + margin-left: -222px; background-color: dark-light-choose(#dadada, blend-primary-secondary(5%)); } From c5ed8f456ee1af444e561c852e7950f8b84385b5 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 3 Nov 2015 15:10:39 -0500 Subject: [PATCH 107/114] FIX: Unicode wasn't transpiled properly --- app/assets/javascripts/discourse/components/d-editor.js.es6 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6 index 736d21bb64..4d26eb3e43 100644 --- a/app/assets/javascripts/discourse/components/d-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/d-editor.js.es6 @@ -119,9 +119,9 @@ Toolbar.prototype.addButton = function(button) { // Mac users are used to glyphs for shortcut keys if (mac) { - createdButton.title = createdButton.title.replace('Shift', "\u{21E7}") - .replace('Meta', "\u{2318}") - .replace('Alt', "\u{2325}") + createdButton.title = createdButton.title.replace('Shift', "\u21E7") + .replace('Meta', "\u2318") + .replace('Alt', "\u2325") .replace(/\+/g, ''); } From 9effd92f73a3f27f0625bad390776e3c136ee644 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Tue, 3 Nov 2015 14:59:42 -0800 Subject: [PATCH 108/114] mobile topic status was aligned incorrectly --- app/assets/stylesheets/mobile/discourse.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/mobile/discourse.scss b/app/assets/stylesheets/mobile/discourse.scss index fce51428a5..a023c2e7ef 100644 --- a/app/assets/stylesheets/mobile/discourse.scss +++ b/app/assets/stylesheets/mobile/discourse.scss @@ -64,7 +64,7 @@ blockquote { } .topic-statuses { - float: left; + display: inline-block; .topic-status { i { color: dark-light-diff($secondary, $primary, 40%, -20%); From 131ae8486b611228c6a38cf0b3becd393ef14291 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Tue, 3 Nov 2015 15:10:21 -0800 Subject: [PATCH 109/114] slightly larger reply count numbers on mobile --- app/assets/stylesheets/mobile/topic-list.scss | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/assets/stylesheets/mobile/topic-list.scss b/app/assets/stylesheets/mobile/topic-list.scss index 7104e59541..c7340dcdc7 100644 --- a/app/assets/stylesheets/mobile/topic-list.scss +++ b/app/assets/stylesheets/mobile/topic-list.scss @@ -116,10 +116,6 @@ } } - .num.posts { - font-weight: bold; - text-align: right; - } .age { white-space: nowrap; @@ -412,9 +408,7 @@ td .main-link { } .topic-list { .posts-map { - display: inline; - font-size: 1.071em; - padding-top: 2px; + font-size: 1.15em; } // so the topic excerpt is full width // as the containing div is 80% From d56e91f44fdebdf39c672f81d649e8037b42672b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 4 Nov 2015 00:42:19 +0100 Subject: [PATCH 110/114] PERF: add 'lower(title)' index on topics --- .../20151103233815_add_lower_title_index_on_topics.rb | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 db/migrate/20151103233815_add_lower_title_index_on_topics.rb diff --git a/db/migrate/20151103233815_add_lower_title_index_on_topics.rb b/db/migrate/20151103233815_add_lower_title_index_on_topics.rb new file mode 100644 index 0000000000..bdd93fd533 --- /dev/null +++ b/db/migrate/20151103233815_add_lower_title_index_on_topics.rb @@ -0,0 +1,9 @@ +class AddLowerTitleIndexOnTopics < ActiveRecord::Migration + def up + execute "CREATE INDEX index_topics_on_lower_title ON topics (LOWER(title))" + end + + def down + execute "DROP INDEX index_topics_on_lower_title" + end +end From e2121c2c2c8b941d08231078b2e9fb09612785ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 4 Nov 2015 12:38:39 +0100 Subject: [PATCH 111/114] FIX: add 'max-width' to images with auto width/height in emails --- lib/email/styles.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/email/styles.rb b/lib/email/styles.rb index f518a6f999..6f0dd6c2b2 100644 --- a/lib/email/styles.rb +++ b/lib/email/styles.rb @@ -31,7 +31,6 @@ module Email # images @fragment.css('img').each do |img| - next if img['class'] == 'site-logo' if img['class'] == "emoji" || img['src'] =~ /plugins\/emoji/ @@ -56,9 +55,16 @@ module Email end end + # add max-width to big images + big_images = @fragment.css('img[width="auto"][height="auto"]') - + @fragment.css('aside.onebox img') - + @fragment.css('img.site-logo, img.emoji') + big_images.each do |img| + add_styles(img, 'max-width: 100%;') if img['style'] !~ /max-width/ + end + # attachments @fragment.css('a.attachment').each do |a| - # ensure all urls are absolute if a['href'] =~ /^\/[^\/]/ a['href'] = "#{Discourse.base_url}#{a['href']}" From fddf0d9191aecf6a0c4900ef746996ff6d5fbb93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 4 Nov 2015 15:51:00 +0100 Subject: [PATCH 112/114] UX: no avatar for pinned topics on mobile topics list --- .../templates/mobile/list/topic_list_item.raw.hbs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs b/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs index 7a671458ff..7e61ac9561 100644 --- a/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs @@ -1,9 +1,12 @@ + {{~#unless content.hasExcerpt}} -
      + {{else}} +
      + {{/unless~}}