From b2eacae4fb614dee7e1318989f7ad1b6a5ad6a95 Mon Sep 17 00:00:00 2001 From: ojab Date: Sat, 10 Jan 2015 02:42:19 +0300 Subject: [PATCH 001/230] `bundle update rbtrace` for mri-2.2.0 compatibility --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f9a0ef20bd..84322f949b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -225,7 +225,7 @@ GEM metaclass (~> 0.0.1) mock_redis (0.13.2) moneta (0.8.0) - msgpack (0.5.8) + msgpack (0.5.10) multi_json (1.10.1) multi_xml (0.5.5) multipart-post (2.0.0) @@ -328,7 +328,7 @@ GEM rb-fsevent (0.9.4) rb-inotify (0.9.5) ffi (>= 0.5.0) - rbtrace (0.4.5) + rbtrace (0.4.6) ffi (>= 1.0.6) msgpack (>= 0.4.3) trollop (>= 1.16.2) @@ -436,7 +436,7 @@ GEM treetop (1.4.15) polyglot polyglot (>= 0.3.1) - trollop (2.0) + trollop (2.1.1) tzinfo (1.2.2) thread_safe (~> 0.1) uglifier (2.6.0) From e572ad35ad0cd2b8564e6cb516c38d52c21fb43f Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 23 Jan 2015 23:39:44 -0800 Subject: [PATCH 002/230] new topic/post alert div regressed a bit on mobile --- app/assets/stylesheets/mobile/alert.scss | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/mobile/alert.scss b/app/assets/stylesheets/mobile/alert.scss index ec17f33ab5..dbd1770bec 100644 --- a/app/assets/stylesheets/mobile/alert.scss +++ b/app/assets/stylesheets/mobile/alert.scss @@ -3,10 +3,8 @@ } // there are (n) new or updated topics, click to show -.topic-list { - .alert { +.alert.alert-info { margin: 0; padding: 15px; - font-size: 120%; - } + font-size: 1.1em; } From 7d07ad4aa463eb12c06d2ab22ef383bbe0016299 Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 11 Jan 2015 06:42:41 +1100 Subject: [PATCH 003/230] FIX: check free space in uploads directory, not global system --- lib/cooked_post_processor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cooked_post_processor.rb b/lib/cooked_post_processor.rb index 57af1276d5..77dcb8d1df 100644 --- a/lib/cooked_post_processor.rb +++ b/lib/cooked_post_processor.rb @@ -264,7 +264,7 @@ class CookedPostProcessor end def available_disk_space - 100 - `df -l . | tail -1 | tr -s ' ' | cut -d ' ' -f 5`.to_i + 100 - `df -l #{Rails.root}/public/uploads | tail -1 | tr -s ' ' | cut -d ' ' -f 5`.to_i end def dirty? From b3b34314b61eebe455e7607edd68abd89c40efde Mon Sep 17 00:00:00 2001 From: Ed Gibbs Date: Mon, 12 Jan 2015 06:36:48 -0800 Subject: [PATCH 004/230] FIX: Update vagrant box for ruby and phantomjs versions These updates are needed to successfully run tests on the vagrant box. --- Vagrantfile | 4 +- chef/cookbooks/discourse/recipes/default.rb | 52 ++++++++++----------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index acbd512684..388822a4d1 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -3,8 +3,8 @@ # See https://github.com/discourse/discourse/blob/master/docs/VAGRANT.md # Vagrant.configure("2") do |config| - config.vm.box = 'discourse-0.9.9.13' - config.vm.box_url = "https://d3fvb7b7auiut8.cloudfront.net/discourse-0.9.9.13.box" + config.vm.box= "edgibbs/discourse-0.9.9.15.box" + config.vm.box_url = "https://vagrantcloud.com/edgibbs/discourse-0.9.9.15.box" # Make this VM reachable on the host network as well, so that other # VM's running other browsers can access our dev server. diff --git a/chef/cookbooks/discourse/recipes/default.rb b/chef/cookbooks/discourse/recipes/default.rb index 3b9a8164a6..7313bce441 100644 --- a/chef/cookbooks/discourse/recipes/default.rb +++ b/chef/cookbooks/discourse/recipes/default.rb @@ -1,26 +1,26 @@ -execute "upgrade-rvm" do - command "gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3" - command "rvm get stable && rvm reload" - action :nothing -end - -execute "upgrade-ruby" do - command "yes | rvm install ruby-2.1.5 --verify-downloads 1" - action :nothing -end - -execute "set-ruby" do - command "rvm use ruby-2.1.5" - user "vagrant" - action :nothing -end - -ruby_block "ruby-upgrade-message" do - block do - Chef::Log.info "Upgrading ruby. This will take a while." - end - notifies :run, "execute[upgrade-rvm]", :immediately - notifies :run, "execute[upgrade-ruby]", :immediately - notifies :run, "execute[set-ruby]", :immediately - action :create -end +# execute "upgrade-rvm" do +# command "gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3" +# command "rvm get stable && rvm reload" +# action :nothing +# end +# +# execute "upgrade-ruby" do +# command "yes | rvm install ruby-2.1.5 --verify-downloads 1" +# action :nothing +# end +# +# execute "set-ruby" do +# command "rvm use ruby-2.1.5" +# user "vagrant" +# action :nothing +# end +# +# ruby_block "ruby-upgrade-message" do +# block do +# Chef::Log.info "Upgrading ruby. This will take a while." +# end +# notifies :run, "execute[upgrade-rvm]", :immediately +# notifies :run, "execute[upgrade-ruby]", :immediately +# notifies :run, "execute[set-ruby]", :immediately +# action :create +# end From 78d5d22776cd173bcbd9d5f62ebc1a87fd3a1e23 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 14 Jan 2015 12:09:40 -0500 Subject: [PATCH 005/230] FIX: Posts weren't highlighting when jumping using the progress widget --- app/assets/javascripts/discourse/lib/url.js | 7 +++-- .../javascripts/discourse/views/post_view.js | 29 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/url.js b/app/assets/javascripts/discourse/lib/url.js index e196943e52..8c6b1dcccb 100644 --- a/app/assets/javascripts/discourse/lib/url.js +++ b/app/assets/javascripts/discourse/lib/url.js @@ -203,7 +203,6 @@ Discourse.URL = Em.Object.createWithMixins({ var container = Discourse.__container__, topicController = container.lookup('controller:topic'), - topicProgressController = container.lookup('controller:topic-progress'), opts = {}, postStream = topicController.get('postStream'); @@ -218,8 +217,10 @@ Discourse.URL = Em.Object.createWithMixins({ enteredAt: new Date().getTime().toString() }); var closestPost = postStream.closestPostForPostNumber(closest), - progress = postStream.progressIndexOfPost(closestPost); - topicProgressController.set('progressPosition', progress); + progress = postStream.progressIndexOfPost(closestPost), + progressController = container.lookup('controller:topic-progress'); + + progressController.set('progressPosition', progress); Discourse.PostView.considerHighlighting(topicController, closest); }).then(function() { Discourse.URL.jumpToPost(closest); diff --git a/app/assets/javascripts/discourse/views/post_view.js b/app/assets/javascripts/discourse/views/post_view.js index f95f12e255..77806e1074 100644 --- a/app/assets/javascripts/discourse/views/post_view.js +++ b/app/assets/javascripts/discourse/views/post_view.js @@ -307,28 +307,27 @@ Discourse.PostView = Discourse.GroupedView.extend(Ember.Evented, { }.observes('controller.searchHighlight', 'cooked') }); +function highlight(postNumber) { + var $contents = $('#post_' + postNumber +' .topic-body'), + origColor = $contents.data('orig-color') || $contents.css('backgroundColor'); + + $contents.data("orig-color", origColor) + .addClass('highlighted') + .stop() + .animate({ backgroundColor: origColor }, 2500, 'swing', function(){ + $contents.removeClass('highlighted'); + $contents.css({'background-color': ''}); + }); +} + Discourse.PostView.reopenClass({ - highlight: function(postNumber){ - var $contents = $('#post_' + postNumber +' .topic-body'), - origColor = $contents.data('orig-color') || $contents.css('backgroundColor'); - - $contents.data("orig-color", origColor); - $contents - .addClass('highlighted') - .stop() - .animate({ backgroundColor: origColor }, 2500, 'swing', function(){ - $contents.removeClass('highlighted'); - $contents.css({'background-color': ''}); - }); - }, - considerHighlighting: function(controller, postNumber) { var highlightNumber = controller.get('highlightOnInsert'); // If we're meant to highlight a post if (highlightNumber === postNumber) { controller.set('highlightOnInsert', null); - this.highlight(postNumber); + Ember.run.scheduleOnce('afterRender', null, highlight, postNumber); } } }); From 28f702a5b60742661f00be45e120cad43a1f7441 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 14 Jan 2015 12:20:37 -0500 Subject: [PATCH 006/230] Refactor app events to more efficiently handle post highlighting --- .../initializers/inject-objects.js.es6 | 3 +- .../discourse/lib/app-events.js.es6 | 23 ++++++++++ app/assets/javascripts/discourse/lib/url.js | 6 +-- .../discourse/mixins/url-refresh.js.es6 | 13 ++---- .../discourse/routes/topic-from-params.js.es6 | 4 +- .../views/{post_view.js => post.js.es6} | 43 ++----------------- .../javascripts/discourse/views/topic.js.es6 | 22 +++++++++- app/assets/javascripts/main_include.js | 1 + .../javascripts/initializers/poll.js.es6 | 3 +- 9 files changed, 61 insertions(+), 57 deletions(-) create mode 100644 app/assets/javascripts/discourse/lib/app-events.js.es6 rename app/assets/javascripts/discourse/views/{post_view.js => post.js.es6} (89%) diff --git a/app/assets/javascripts/discourse/initializers/inject-objects.js.es6 b/app/assets/javascripts/discourse/initializers/inject-objects.js.es6 index 31933358d5..885a3edd14 100644 --- a/app/assets/javascripts/discourse/initializers/inject-objects.js.es6 +++ b/app/assets/javascripts/discourse/initializers/inject-objects.js.es6 @@ -1,11 +1,12 @@ import Session from 'discourse/models/session'; +import AppEvents from 'discourse/lib/app-events'; export default { name: "inject-objects", initialize: function(container, application) { // Inject appEvents everywhere - var appEvents = Ember.Object.createWithMixins(Ember.Evented); + var appEvents = AppEvents.create(); application.register('app-events:main', appEvents, { instantiate: false }); application.inject('controller', 'appEvents', 'app-events:main'); diff --git a/app/assets/javascripts/discourse/lib/app-events.js.es6 b/app/assets/javascripts/discourse/lib/app-events.js.es6 new file mode 100644 index 0000000000..26135d3f7c --- /dev/null +++ b/app/assets/javascripts/discourse/lib/app-events.js.es6 @@ -0,0 +1,23 @@ +export default Ember.Object.extend(Ember.Evented); + +var id = 1; +function newKey() { + return "_view_app_event_" + (id++); +} + +export function createViewListener(eventName, cb) { + var extension = {}; + extension[newKey()] = function() { + this.appEvents.on(eventName, this, cb); + }.on('didInsertElement'); + + extension[newKey()] = function() { + this.appEvents.off(eventName, this, cb); + }.on('willDestroyElement'); + + return extension; +} + +export function listenForViewEvent(viewClass, eventName, cb) { + viewClass.reopen(createViewListener(eventName, cb)); +} diff --git a/app/assets/javascripts/discourse/lib/url.js b/app/assets/javascripts/discourse/lib/url.js index 8c6b1dcccb..2c6c212a28 100644 --- a/app/assets/javascripts/discourse/lib/url.js +++ b/app/assets/javascripts/discourse/lib/url.js @@ -2,7 +2,7 @@ var jumpScheduled = false, rewrites = []; -Discourse.URL = Em.Object.createWithMixins({ +Discourse.URL = Ember.Object.createWithMixins({ // Used for matching a topic TOPIC_REGEXP: /\/t\/([^\/]+)\/(\d+)\/?(\d+)?/, @@ -210,10 +210,10 @@ Discourse.URL = Em.Object.createWithMixins({ if (path.match(/last$/)) { opts.nearPost = topicController.get('highest_post_number'); } var closest = opts.nearPost || 1; + var self = this; postStream.refresh(opts).then(function() { topicController.setProperties({ currentPost: closest, - highlightOnInsert: closest, enteredAt: new Date().getTime().toString() }); var closestPost = postStream.closestPostForPostNumber(closest), @@ -221,7 +221,7 @@ Discourse.URL = Em.Object.createWithMixins({ progressController = container.lookup('controller:topic-progress'); progressController.set('progressPosition', progress); - Discourse.PostView.considerHighlighting(topicController, closest); + self.appEvents.trigger('post:highlight', closest); }).then(function() { Discourse.URL.jumpToPost(closest); }); diff --git a/app/assets/javascripts/discourse/mixins/url-refresh.js.es6 b/app/assets/javascripts/discourse/mixins/url-refresh.js.es6 index 389f60c76d..e5ab51cb3d 100644 --- a/app/assets/javascripts/discourse/mixins/url-refresh.js.es6 +++ b/app/assets/javascripts/discourse/mixins/url-refresh.js.es6 @@ -4,16 +4,9 @@ // // This is useful if you want to get around Ember's default // behavior of not refreshing when navigating to the same place. -export default Em.Mixin.create({ - _initURLRefresh: function() { - this.appEvents.on('url:refresh', this, '_urlRefresh'); - }.on('didInsertElement'), - _tearDownURLRefresh: function() { - this.appEvents.off('url:refresh', this, '_urlRefresh'); - }.on('willDestroyElement'), +import { createViewListener } from 'discourse/lib/app-events'; - _urlRefresh: function() { - this.get('controller').send('refresh'); - } +export default createViewListener('url:refresh', function() { + this.get('controller').send('refresh'); }); diff --git a/app/assets/javascripts/discourse/routes/topic-from-params.js.es6 b/app/assets/javascripts/discourse/routes/topic-from-params.js.es6 index 0f648039ba..cd6acccc81 100644 --- a/app/assets/javascripts/discourse/routes/topic-from-params.js.es6 +++ b/app/assets/javascripts/discourse/routes/topic-from-params.js.es6 @@ -1,5 +1,4 @@ // This route is used for retrieving a topic based on params - export default Discourse.Route.extend({ setupController: function(controller, params) { @@ -15,6 +14,7 @@ export default Discourse.Route.extend({ // I sincerely hope no topic gets this many posts if (params.nearPost === "last") { params.nearPost = 999999999; } + var self = this; postStream.refresh(params).then(function () { // TODO we are seeing errors where closest post is null and this is exploding @@ -28,13 +28,13 @@ export default Discourse.Route.extend({ topicController.setProperties({ currentPost: closest, enteredAt: new Date().getTime().toString(), - highlightOnInsert: closest }); topicProgressController.setProperties({ progressPosition: progress, expanded: false }); + self.appEvents.trigger('post:highlight', closest); Discourse.URL.jumpToPost(closest); if (topic.present('draft')) { diff --git a/app/assets/javascripts/discourse/views/post_view.js b/app/assets/javascripts/discourse/views/post.js.es6 similarity index 89% rename from app/assets/javascripts/discourse/views/post_view.js rename to app/assets/javascripts/discourse/views/post.js.es6 index 77806e1074..879f51b676 100644 --- a/app/assets/javascripts/discourse/views/post_view.js +++ b/app/assets/javascripts/discourse/views/post.js.es6 @@ -1,6 +1,6 @@ var DAY = 60 * 50 * 1000; -Discourse.PostView = Discourse.GroupedView.extend(Ember.Evented, { +var PostView = Discourse.GroupedView.extend(Ember.Evented, { classNames: ['topic-post', 'clearfix'], templateName: 'post', classNameBindings: ['postTypeClass', @@ -175,11 +175,7 @@ Discourse.PostView = Discourse.GroupedView.extend(Ember.Evented, { }, actions: { - /** - Toggle the replies this post is a reply to - - @method showReplyHistory - **/ + // Toggle the replies this post is a reply to toggleReplyHistory: function(post) { var replyHistory = post.get('replyHistory'), @@ -203,7 +199,7 @@ Discourse.PostView = Discourse.GroupedView.extend(Ember.Evented, { } Em.run.next(function() { - Discourse.PostView.highlight(replyPostNumber); + PostView.highlight(replyPostNumber); $(window).scrollTop(self.$().position().top - offsetFromTop); }); return; @@ -267,15 +263,7 @@ Discourse.PostView = Discourse.GroupedView.extend(Ember.Evented, { this._showLinkCounts(); - // Track this post Discourse.ScreenTrack.current().track(this.$().prop('id'), postNumber); - - // Highlight the post if required - if (postNumber > 1) { - Discourse.PostView.considerHighlighting(this.get('controller'), postNumber); - } - - // Add syntax highlighting Discourse.SyntaxHighlighting.apply($post); Discourse.Lightbox.apply($post); @@ -307,27 +295,4 @@ Discourse.PostView = Discourse.GroupedView.extend(Ember.Evented, { }.observes('controller.searchHighlight', 'cooked') }); -function highlight(postNumber) { - var $contents = $('#post_' + postNumber +' .topic-body'), - origColor = $contents.data('orig-color') || $contents.css('backgroundColor'); - - $contents.data("orig-color", origColor) - .addClass('highlighted') - .stop() - .animate({ backgroundColor: origColor }, 2500, 'swing', function(){ - $contents.removeClass('highlighted'); - $contents.css({'background-color': ''}); - }); -} - -Discourse.PostView.reopenClass({ - considerHighlighting: function(controller, postNumber) { - var highlightNumber = controller.get('highlightOnInsert'); - - // If we're meant to highlight a post - if (highlightNumber === postNumber) { - controller.set('highlightOnInsert', null); - Ember.run.scheduleOnce('afterRender', null, highlight, postNumber); - } - } -}); +export default PostView; diff --git a/app/assets/javascripts/discourse/views/topic.js.es6 b/app/assets/javascripts/discourse/views/topic.js.es6 index 76733c7950..66e73adb49 100644 --- a/app/assets/javascripts/discourse/views/topic.js.es6 +++ b/app/assets/javascripts/discourse/views/topic.js.es6 @@ -1,6 +1,7 @@ import AddCategoryClass from 'discourse/mixins/add-category-class'; +import { listenForViewEvent } from 'discourse/lib/app-events'; -export default Discourse.View.extend(AddCategoryClass, Discourse.Scrolling, { +var TopicView = Discourse.View.extend(AddCategoryClass, Discourse.Scrolling, { templateName: 'topic', topicBinding: 'controller.model', userFiltersBinding: 'controller.userFilters', @@ -157,3 +158,22 @@ export default Discourse.View.extend(AddCategoryClass, Discourse.Scrolling, { } }.property('topicTrackingState.messageCount') }); + +function highlight(postNumber) { + var $contents = $('#post_' + postNumber +' .topic-body'), + origColor = $contents.data('orig-color') || $contents.css('backgroundColor'); + + $contents.data("orig-color", origColor) + .addClass('highlighted') + .stop() + .animate({ backgroundColor: origColor }, 2500, 'swing', function(){ + $contents.removeClass('highlighted'); + $contents.css({'background-color': ''}); + }); +} + +listenForViewEvent(TopicView, 'post:highlight', function(postNumber) { + Ember.run.scheduleOnce('afterRender', null, highlight, postNumber); +}); + +export default TopicView; diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js index 5759d7a962..23b70b106c 100644 --- a/app/assets/javascripts/main_include.js +++ b/app/assets/javascripts/main_include.js @@ -10,6 +10,7 @@ // // Stuff we need to load first +//= require ./discourse/lib/app-events //= require ./discourse/helpers/i18n //= require ./discourse/lib/ember_compat_handlebars //= require ./discourse/lib/computed diff --git a/plugins/poll/assets/javascripts/initializers/poll.js.es6 b/plugins/poll/assets/javascripts/initializers/poll.js.es6 index 61bdeb321f..701b6edad1 100644 --- a/plugins/poll/assets/javascripts/initializers/poll.js.es6 +++ b/plugins/poll/assets/javascripts/initializers/poll.js.es6 @@ -1,4 +1,5 @@ import PollController from "discourse/plugins/poll/controllers/poll"; +import PostView from "discourse/views/post"; var Poll = Discourse.Model.extend({ post: null, @@ -71,7 +72,7 @@ export default { name: 'poll', initialize: function() { - Discourse.PostView.reopen({ + PostView.reopen({ createPollUI: function($post) { var post = this.get('post'); From e7443247dd48907d3e2730ca454e17863a1ae852 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 14 Jan 2015 15:01:41 -0500 Subject: [PATCH 007/230] Adds plugin-outlet for topic menu buttons. Also a `d-button` component. --- .../discourse/components/d-button.js.es6 | 18 +++++++++++ .../discourse/helpers/fa-icon.js.es6 | 20 +++++++----- .../discourse/templates/topic-admin-menu.hbs | 31 ++++++++++--------- app/assets/javascripts/main_include.js | 1 + 4 files changed, 47 insertions(+), 23 deletions(-) create mode 100644 app/assets/javascripts/discourse/components/d-button.js.es6 diff --git a/app/assets/javascripts/discourse/components/d-button.js.es6 b/app/assets/javascripts/discourse/components/d-button.js.es6 new file mode 100644 index 0000000000..b5a306dab1 --- /dev/null +++ b/app/assets/javascripts/discourse/components/d-button.js.es6 @@ -0,0 +1,18 @@ +import { iconHTML } from 'discourse/helpers/fa-icon'; + +export default Ember.Component.extend({ + tagName: 'button', + classNameBindings: [':btn'], + + render: function(buffer) { + var icon = this.get('icon'); + if (icon) { + buffer.push(iconHTML(icon) + ' '); + } + buffer.push(I18n.t(this.get('label'))); + }, + + click: function() { + this.sendAction(); + } +}); diff --git a/app/assets/javascripts/discourse/helpers/fa-icon.js.es6 b/app/assets/javascripts/discourse/helpers/fa-icon.js.es6 index 25ac7aebce..73d8b0d068 100644 --- a/app/assets/javascripts/discourse/helpers/fa-icon.js.es6 +++ b/app/assets/javascripts/discourse/helpers/fa-icon.js.es6 @@ -1,12 +1,16 @@ -Handlebars.registerHelper('fa-icon', function(icon, options) { - var labelKey; - if (options.hash) { labelKey = options.hash.label; } - +export function iconHTML(icon, label) { var html = ""; + if (label) { + html += "" + I18n.t(label) + ""; } - return new Handlebars.SafeString(html); + return html; +} + +Handlebars.registerHelper('fa-icon', function(icon, options) { + var label; + if (options.hash) { label = options.hash.label; } + + return new Handlebars.SafeString(iconHTML(icon, label)); }); diff --git a/app/assets/javascripts/discourse/templates/topic-admin-menu.hbs b/app/assets/javascripts/discourse/templates/topic-admin-menu.hbs index 50d67754ca..ef204205eb 100644 --- a/app/assets/javascripts/discourse/templates/topic-admin-menu.hbs +++ b/app/assets/javascripts/discourse/templates/topic-admin-menu.hbs @@ -2,48 +2,48 @@
  • - + {{d-button action="toggleMultiSelect" icon="tasks" label="topic.actions.multi_select" class="btn-admin"}}
  • {{#if details.can_delete}}
  • - + {{d-button action="deleteTopic" icon="trash-o" label="topic.actions.delete" class="btn-admin btn-danger"}}
  • {{/if}} {{#if showRecover}}
  • - + {{d-button action="recoverTopic" icon="undo" label="topic.actions.recover" class="btn-admin"}}
  • {{/if}}
  • {{#if closed}} - + {{d-button action="toggleClosed" icon="unlock" label="topic.actions.open" class="btn-admin"}} {{else}} - - + {{d-button action="toggleClosed" icon="lock" label="topic.actions.close" class="btn-admin"}} + {{d-button action="showAutoClose" icon="clock-o" label="topic.actions.auto_close" class="btn-admin"}} {{/if}}
  • {{#unless isPrivateMessage}}
  • {{#if isBanner}} - + {{d-button action="removeBanner" icon="bullhorn" label="topic.actions.remove_banner" class="btn-admin"}} {{else}} {{#if visible}} - + {{d-button action="makeBanner" icon="bullhorn" label="topic.actions.make_banner" class="btn-admin"}} {{/if}} {{/if}}
  • {{#if pinned_at}} - + {{d-button action="togglePinned" icon="thumb-tack" label="topic.actions.unpin" class="btn-admin"}} {{else}} {{#if visible}} - - + {{d-button action="togglePinned" icon="thumb-tack" label="topic.actions.pin" class="btn-admin"}} + {{d-button action="togglePinnedGlobally" icon="thumb-tack" label="topic.actions.pin_globally" class="btn-admin"}} {{/if}} {{/if}}
  • @@ -51,18 +51,19 @@
  • {{#if archived}} - + {{d-button action="toggleArchived" icon="folder" label="topic.actions.unarchive" class="btn-admin"}} {{else}} - + {{d-button action="toggleArchived" icon="folder" label="topic.actions.archive" class="btn-admin"}} {{/if}}
  • {{#if visible}} - + {{d-button action="toggleVisibility" icon="eye-slash" label="topic.actions.invisible" class="btn-admin"}} {{else}} - + {{d-button action="toggleVisibility" icon="eye" label="topic.actions.visible" class="btn-admin"}} {{/if}}
  • + {{plugin-outlet "topic-admin-menu-buttons"}}
diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js index 23b70b106c..2e7e7f43ba 100644 --- a/app/assets/javascripts/main_include.js +++ b/app/assets/javascripts/main_include.js @@ -12,6 +12,7 @@ // Stuff we need to load first //= require ./discourse/lib/app-events //= require ./discourse/helpers/i18n +//= require ./discourse/helpers/fa-icon //= require ./discourse/lib/ember_compat_handlebars //= require ./discourse/lib/computed //= require ./discourse/helpers/register-unbound From f8d3764d5eb555e3b7ff2d21916e22988588f69c Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 14 Jan 2015 15:36:38 -0500 Subject: [PATCH 008/230] Convert many buttons to `d-button` --- .../javascripts/admin/templates/dashboard.hbs | 4 ++-- .../javascripts/admin/templates/reports.hbs | 2 +- .../discourse/components/d-button.js.es6 | 21 +++++++++++++++---- .../discourse/templates/header.hbs | 8 ++----- .../discourse/templates/topic-entrance.hbs | 13 ++++++------ .../discourse/templates/topic-progress.hbs | 21 +++++++++++-------- .../javascripts/discourse/templates/topic.hbs | 8 +++---- .../discourse/templates/user-dropdown.hbs | 2 +- .../discourse/templates/user/user.hbs | 9 +++----- app/assets/stylesheets/common/base/topic.scss | 10 +++++++-- 10 files changed, 57 insertions(+), 41 deletions(-) diff --git a/app/assets/javascripts/admin/templates/dashboard.hbs b/app/assets/javascripts/admin/templates/dashboard.hbs index b788bb08f0..a2e8479f92 100644 --- a/app/assets/javascripts/admin/templates/dashboard.hbs +++ b/app/assets/javascripts/admin/templates/dashboard.hbs @@ -118,7 +118,7 @@

{{i18n 'admin.dashboard.last_checked'}}: {{problemsTimestamp}} - + {{d-button action="refreshProblems" class="btn-small" icon="refresh" label="admin.dashboard.refresh_problems"}}

@@ -130,7 +130,7 @@

{{i18n 'admin.dashboard.no_problems'}} - + {{d-button action="refreshProblems" class="btn-small" icon="refresh" label="admin.dashboard.refresh_problems"}}

diff --git a/app/assets/javascripts/admin/templates/reports.hbs b/app/assets/javascripts/admin/templates/reports.hbs index 98e7097408..e4fdc87cbf 100644 --- a/app/assets/javascripts/admin/templates/reports.hbs +++ b/app/assets/javascripts/admin/templates/reports.hbs @@ -3,7 +3,7 @@
{{i18n 'admin.dashboard.reports.start_date'}} {{input type="date" value=startDate}} {{i18n 'admin.dashboard.reports.end_date'}} {{input type="date" value=endDate}} - + {{d-button action="refreshReport" class="btn-primary" label="admin.dashboard.reports.refresh_report" icon="refresh"}}
diff --git a/app/assets/javascripts/discourse/components/d-button.js.es6 b/app/assets/javascripts/discourse/components/d-button.js.es6 index b5a306dab1..fa923a2cae 100644 --- a/app/assets/javascripts/discourse/components/d-button.js.es6 +++ b/app/assets/javascripts/discourse/components/d-button.js.es6 @@ -3,13 +3,26 @@ import { iconHTML } from 'discourse/helpers/fa-icon'; export default Ember.Component.extend({ tagName: 'button', classNameBindings: [':btn'], + attributeBindings: ['disabled', 'translatedTitle:title'], + + translatedTitle: function() { + var label = this.get('label'); + if (label) { + return I18n.t(this.get('label')); + } + }.property('label'), render: function(buffer) { - var icon = this.get('icon'); - if (icon) { - buffer.push(iconHTML(icon) + ' '); + var title = this.get('translatedTitle'), + icon = this.get('icon'); + + if (title || icon) { + if (icon) { buffer.push(iconHTML(icon) + ' '); } + if (title) { buffer.push(title); } + } else { + // If no label or icon is present, yield + return this._super(); } - buffer.push(I18n.t(this.get('label'))); }, click: function() { diff --git a/app/assets/javascripts/discourse/templates/header.hbs b/app/assets/javascripts/discourse/templates/header.hbs index d7f3412e2f..0cffe50fef 100644 --- a/app/assets/javascripts/discourse/templates/header.hbs +++ b/app/assets/javascripts/discourse/templates/header.hbs @@ -6,13 +6,9 @@
{{#unless currentUser}} {{#if showSignUpButton}} - + {{d-button action="showCreateAccount" class="btn-primary btn-small sign-up-button" label="sign_up"}} {{/if}} - + {{d-button action="showLogin" class="btn-primary btn-small login-button" icon="user" label="log_in"}} {{/unless}}
{{loading-spinner condition=retrying}} diff --git a/app/assets/javascripts/discourse/templates/user-dropdown.hbs b/app/assets/javascripts/discourse/templates/user-dropdown.hbs index 9b3323d409..12d2fe78de 100644 --- a/app/assets/javascripts/discourse/templates/user-dropdown.hbs +++ b/app/assets/javascripts/discourse/templates/user-dropdown.hbs @@ -8,6 +8,6 @@
  • {{#link-to 'userActivity.bookmarks' currentUser}}{{i18n 'user.bookmarks'}}{{/link-to}}
  • {{#link-to 'preferences' currentUser}}{{i18n 'user.preferences'}}{{/link-to}}
  • -
  • +
  • {{d-button action="logout" class="btn-danger right logout" icon="sign-out" label="user.log_out"}}
  • diff --git a/app/assets/javascripts/discourse/templates/user/user.hbs b/app/assets/javascripts/discourse/templates/user/user.hbs index 7c64ec6e69..6f54dbfcd6 100644 --- a/app/assets/javascripts/discourse/templates/user/user.hbs +++ b/app/assets/javascripts/discourse/templates/user/user.hbs @@ -117,7 +117,7 @@ {{#if email}} {{email}} {{else}} - + {{d-button action="checkEmail" icon="envelope-o" label="admin.users.check_email.text" class="btn-primary"}} {{/if}} {{/if}} @@ -130,10 +130,7 @@ {{/if}} {{#if canDeleteUser}} - + {{d-button action="adminDelete" icon="exclamation-triangle" label="user.admin_delete" class="btn-danger"}} {{/if}} {{plugin-outlet "user-profile-secondary"}} @@ -192,7 +189,7 @@ {{#if viewingSelf}}
    - + {{d-button action="exportUserArchive" label="user.download_archive" icon="download"}}
    {{/if}} diff --git a/app/assets/stylesheets/common/base/topic.scss b/app/assets/stylesheets/common/base/topic.scss index 0bb00feee9..1afcedb7fe 100644 --- a/app/assets/stylesheets/common/base/topic.scss +++ b/app/assets/stylesheets/common/base/topic.scss @@ -1,10 +1,16 @@ #topic-title { - .title-wrapper {float: left; width: 90%;} + .title-wrapper { + float: left; + width: 90%; + .btn-small { + margin: 0 6px 0 0; + } + } a.badge-category { margin-top: 5px; - } + } a.edit-topic i { font-size: 0.8em; } } From a28784c8f7387844e17daa83ab1d56a977deb121 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 14 Jan 2015 16:05:29 -0500 Subject: [PATCH 009/230] Add a `{{plugin-outlet}}` for the `admin-menu` --- app/assets/javascripts/admin/templates/admin.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/admin/templates/admin.hbs b/app/assets/javascripts/admin/templates/admin.hbs index f59f25c4c8..209a6dc293 100644 --- a/app/assets/javascripts/admin/templates/admin.hbs +++ b/app/assets/javascripts/admin/templates/admin.hbs @@ -23,6 +23,7 @@
  • {{#link-to 'admin.api'}}{{i18n 'admin.api.title'}}{{/link-to}}
  • {{#link-to 'admin.backups'}}{{i18n 'admin.backups.title'}}{{/link-to}}
  • {{/if}} + {{plugin-outlet "admin-menu"}}
    From 0a4582fbc72aef021f1b70af97c8dc0395b76ffd Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Wed, 14 Jan 2015 16:56:01 -0800 Subject: [PATCH 010/230] add meta viewport tag to JS-off view just in case --- app/views/topics/plain.html.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/topics/plain.html.erb b/app/views/topics/plain.html.erb index 7c852a3b69..8facc6a956 100644 --- a/app/views/topics/plain.html.erb +++ b/app/views/topics/plain.html.erb @@ -6,6 +6,7 @@ <%= crawlable_meta_data(title: @topic_view.title, description: @topic_view.summary, image: @topic_view.image_url) %> + <% @topic_view.posts.each do |post| %> From 655dd6688458fcc0249a02f4beb797b462db54b3 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Wed, 14 Jan 2015 17:06:45 -0800 Subject: [PATCH 011/230] change JS-off "emergency" viewport to 720px --- app/views/topics/plain.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/topics/plain.html.erb b/app/views/topics/plain.html.erb index 8facc6a956..8da43ff247 100644 --- a/app/views/topics/plain.html.erb +++ b/app/views/topics/plain.html.erb @@ -6,7 +6,7 @@ <%= crawlable_meta_data(title: @topic_view.title, description: @topic_view.summary, image: @topic_view.image_url) %> - + <% @topic_view.posts.each do |post| %> From 50392138408ab4c879dedcdbb4149291b608d1c4 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Thu, 15 Jan 2015 09:07:53 +0530 Subject: [PATCH 012/230] set csv compression to optimal speed/size --- app/jobs/regular/export_csv_file.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/regular/export_csv_file.rb b/app/jobs/regular/export_csv_file.rb index 82cbc1b70d..b0349bbdd0 100644 --- a/app/jobs/regular/export_csv_file.rb +++ b/app/jobs/regular/export_csv_file.rb @@ -278,7 +278,7 @@ module Jobs end end # compress CSV file - `gzip --best #{File.expand_path("#{UserExport.base_directory}/#{@file_name}", __FILE__)}` + `gzip -5 #{File.expand_path("#{UserExport.base_directory}/#{@file_name}", __FILE__)}` end def notify_user From 6a6e254096074b0eda20aab70bfec90858d02bf7 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 15 Jan 2015 00:24:39 -0800 Subject: [PATCH 013/230] move viewport meta tag out of shared header --- app/views/layouts/_head.html.erb | 1 - app/views/layouts/application.html.erb | 1 + app/views/layouts/crawler.html.erb | 1 + app/views/layouts/no_js.html.erb | 1 + app/views/topics/plain.html.erb | 1 - 5 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/_head.html.erb b/app/views/layouts/_head.html.erb index 367f11086f..e586fe4cbf 100644 --- a/app/views/layouts/_head.html.erb +++ b/app/views/layouts/_head.html.erb @@ -1,4 +1,3 @@ - diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 02618bfcf0..a99675e835 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -5,6 +5,7 @@ <%= content_for?(:title) ? yield(:title) + ' - ' + SiteSetting.title : SiteSetting.title %> <%= render partial: "layouts/head" %> + <%- if SiteSetting.enable_escaped_fragments? %> diff --git a/app/views/layouts/crawler.html.erb b/app/views/layouts/crawler.html.erb index 8775cb8c79..89d7139995 100644 --- a/app/views/layouts/crawler.html.erb +++ b/app/views/layouts/crawler.html.erb @@ -9,6 +9,7 @@ <%= raw SiteCustomization.custom_head_tag(session[:preview_style]) %> <%- end %> <%= yield :head %> + <%- unless customization_disabled? %> diff --git a/app/views/layouts/no_js.html.erb b/app/views/layouts/no_js.html.erb index 64999a3920..14ae98869d 100644 --- a/app/views/layouts/no_js.html.erb +++ b/app/views/layouts/no_js.html.erb @@ -9,6 +9,7 @@ <%= raw SiteCustomization.custom_head_tag(session[:preview_style]) %> <%- end %> <%= yield(:no_js_head) %> + <%- unless customization_disabled? %> diff --git a/app/views/topics/plain.html.erb b/app/views/topics/plain.html.erb index 8da43ff247..7c852a3b69 100644 --- a/app/views/topics/plain.html.erb +++ b/app/views/topics/plain.html.erb @@ -6,7 +6,6 @@ <%= crawlable_meta_data(title: @topic_view.title, description: @topic_view.summary, image: @topic_view.image_url) %> - <% @topic_view.posts.each do |post| %> From 5c92c390037d2bf92dcefbae9971556949fcb9e1 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 15 Jan 2015 00:41:30 -0800 Subject: [PATCH 014/230] remove csrf, font-face, css from js-off + crawler --- app/views/layouts/_head.html.erb | 5 ----- app/views/layouts/application.html.erb | 3 +++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/views/layouts/_head.html.erb b/app/views/layouts/_head.html.erb index e586fe4cbf..83a0bb75b6 100644 --- a/app/views/layouts/_head.html.erb +++ b/app/views/layouts/_head.html.erb @@ -5,8 +5,3 @@ <%= canonical_link_tag %> - -<%= render partial: "common/special_font_face" %> -<%= render partial: "common/discourse_stylesheet" %> - -<%= discourse_csrf_tags %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index a99675e835..fccbfb2686 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -5,6 +5,9 @@ <%= content_for?(:title) ? yield(:title) + ' - ' + SiteSetting.title : SiteSetting.title %> <%= render partial: "layouts/head" %> + <%= render partial: "common/special_font_face" %> + <%= render partial: "common/discourse_stylesheet" %> + <%= discourse_csrf_tags %> <%- if SiteSetting.enable_escaped_fragments? %> From 6b30f8444e962054b0ca013e1d263496d1c6cf39 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 15 Jan 2015 01:31:30 -0800 Subject: [PATCH 015/230] normalize crawler and no-js views --- app/views/layouts/_head.html.erb | 2 ++ app/views/layouts/application.html.erb | 1 - app/views/layouts/crawler.html.erb | 18 ++++-------- app/views/layouts/no_js.html.erb | 38 ++++++++++---------------- 4 files changed, 21 insertions(+), 38 deletions(-) diff --git a/app/views/layouts/_head.html.erb b/app/views/layouts/_head.html.erb index 83a0bb75b6..5a8d2b5a2a 100644 --- a/app/views/layouts/_head.html.erb +++ b/app/views/layouts/_head.html.erb @@ -4,4 +4,6 @@ + + <%= canonical_link_tag %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index fccbfb2686..533587e354 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -8,7 +8,6 @@ <%= render partial: "common/special_font_face" %> <%= render partial: "common/discourse_stylesheet" %> <%= discourse_csrf_tags %> - <%- if SiteSetting.enable_escaped_fragments? %> diff --git a/app/views/layouts/crawler.html.erb b/app/views/layouts/crawler.html.erb index 89d7139995..96001338d8 100644 --- a/app/views/layouts/crawler.html.erb +++ b/app/views/layouts/crawler.html.erb @@ -9,27 +9,19 @@ <%= raw SiteCustomization.custom_head_tag(session[:preview_style]) %> <%- end %> <%= yield :head %> - + <%- unless customization_disabled? %> <%= SiteCustomization.custom_header(session[:preview_style]) %> <%- end %> -
    -
    -
    -
    -
    - -
    -
    -
    -
    +
    +
    - <%= yield %> -

    <%= t 'powered_by_html' %>

    diff --git a/app/views/layouts/no_js.html.erb b/app/views/layouts/no_js.html.erb index 14ae98869d..0e117e9859 100644 --- a/app/views/layouts/no_js.html.erb +++ b/app/views/layouts/no_js.html.erb @@ -2,39 +2,29 @@ - <%=SiteSetting.title%> - + <%= content_for?(:title) ? yield(:title) + ' - ' + SiteSetting.title : SiteSetting.title %> + <%= render partial: "layouts/head" %> <%- unless customization_disabled? %> <%= raw SiteCustomization.custom_head_tag(session[:preview_style]) %> <%- end %> <%= yield(:no_js_head) %> - + <%- unless customization_disabled? %> <%= SiteCustomization.custom_header(session[:preview_style]) %> <%- end %> -
    -
    -
    -
    -
    -
    - -
    - <% unless current_user %> - - <% end %> -
    -
    -
    -
    -
    - <%= yield %> -
    -
    +
    + +
    +
    + <%= yield %> +
    +
    +

    <%= t 'powered_by_html' %>

    +
    From 71bfde3b4d7fb32904b79946a59911c27c63b48c Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 15 Jan 2015 01:37:02 -0800 Subject: [PATCH 016/230] meant device-width there, oops --- app/views/layouts/_head.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/_head.html.erb b/app/views/layouts/_head.html.erb index 5a8d2b5a2a..c5f6677f22 100644 --- a/app/views/layouts/_head.html.erb +++ b/app/views/layouts/_head.html.erb @@ -4,6 +4,6 @@ - + <%= canonical_link_tag %> From ef8976d68aaf11b8d7237e7fba7545d9463b9243 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 15 Jan 2015 02:20:38 -0800 Subject: [PATCH 017/230] better no-js and crawler image sizing --- app/views/layouts/crawler.html.erb | 2 +- app/views/layouts/no_js.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/crawler.html.erb b/app/views/layouts/crawler.html.erb index 96001338d8..5e69ce9141 100644 --- a/app/views/layouts/crawler.html.erb +++ b/app/views/layouts/crawler.html.erb @@ -10,7 +10,7 @@ <%- end %> <%= yield :head %> diff --git a/app/views/layouts/no_js.html.erb b/app/views/layouts/no_js.html.erb index 0e117e9859..27a974dbe0 100644 --- a/app/views/layouts/no_js.html.erb +++ b/app/views/layouts/no_js.html.erb @@ -10,7 +10,7 @@ <%- end %> <%= yield(:no_js_head) %> From 14ea59b62392dad0872d5f392c963cae1e82c3df Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 15 Jan 2015 02:50:30 -0800 Subject: [PATCH 018/230] remove unused starred item --- config/site_settings.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/config/site_settings.yml b/config/site_settings.yml index f87dd6a3d9..343d77f5d6 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -88,7 +88,6 @@ basic: - latest - new - unread - - starred - top - categories - read From beea92a74b1f0aa03c3339bb94642297e78bb6ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Thu, 15 Jan 2015 17:58:50 +0100 Subject: [PATCH 019/230] FIX: :bug: show email button wasn't working on user's profile page --- app/assets/javascripts/discourse/components/d-button.js.es6 | 2 +- app/assets/javascripts/discourse/templates/user/user.hbs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/components/d-button.js.es6 b/app/assets/javascripts/discourse/components/d-button.js.es6 index fa923a2cae..29bd2a5f1f 100644 --- a/app/assets/javascripts/discourse/components/d-button.js.es6 +++ b/app/assets/javascripts/discourse/components/d-button.js.es6 @@ -26,6 +26,6 @@ export default Ember.Component.extend({ }, click: function() { - this.sendAction(); + this.sendAction("action", this.get("actionParam")); } }); diff --git a/app/assets/javascripts/discourse/templates/user/user.hbs b/app/assets/javascripts/discourse/templates/user/user.hbs index 6f54dbfcd6..cd1eecbd7a 100644 --- a/app/assets/javascripts/discourse/templates/user/user.hbs +++ b/app/assets/javascripts/discourse/templates/user/user.hbs @@ -117,7 +117,7 @@ {{#if email}} {{email}} {{else}} - {{d-button action="checkEmail" icon="envelope-o" label="admin.users.check_email.text" class="btn-primary"}} + {{d-button action="checkEmail" actionParam=model icon="envelope-o" label="admin.users.check_email.text" class="btn-primary"}} {{/if}} {{/if}} From b4e593785021a28ee63c7642d43857d872a8964b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Thu, 15 Jan 2015 19:00:55 +0100 Subject: [PATCH 020/230] FIX: :bug: ensure emoji are case insensitive --- .../javascripts/discourse/lib/emoji/emoji.js.erb | 13 ++++++++----- spec/components/pretty_text_spec.rb | 1 - 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/emoji/emoji.js.erb b/app/assets/javascripts/discourse/lib/emoji/emoji.js.erb index b15b444cec..786485e496 100644 --- a/app/assets/javascripts/discourse/lib/emoji/emoji.js.erb +++ b/app/assets/javascripts/discourse/lib/emoji/emoji.js.erb @@ -7,6 +7,7 @@ var emoji = <%= Emoji.standard.map(&:name).flatten.inspect %>; var extendedEmoji = {}; Discourse.Dialect.registerEmoji = function(code, url) { + code = code.toLowerCase(); extendedEmoji[code] = url; }; @@ -53,13 +54,13 @@ var search = function(term, options) { Discourse.Emoji.search = search; var emojiHash = {}; -emoji.forEach(function(code){ - emojiHash[code] = true; -}); +emoji.forEach(function(code){ emojiHash[code] = true; }); var urlFor = function(code) { var url, set = Discourse.SiteSettings.emoji_set; + code = code.toLowerCase(); + if(extendedEmoji.hasOwnProperty(code)) { url = extendedEmoji[code]; } @@ -82,10 +83,12 @@ var urlFor = function(code) { Discourse.Emoji.urlFor = urlFor; Discourse.Emoji.exists = function(code){ - return !!(extendedEmoji.hasOwnProperty(code) || emojiHash.hasOwnProperty(code)); + code = code.toLowerCase(); + return !!(extendedEmoji.hasOwnProperty(code) || emojiHash.hasOwnProperty(code)); } function imageFor(code) { + code = code.toLowerCase(); var url = urlFor(code); if (url) { return ['img', { href: url, title: ':' + code + ':', 'class': 'emoji', alt: code }]; @@ -145,7 +148,7 @@ Object.keys(translations).forEach(function (t) { }); function escapeRegExp(s) { - return s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); + return s.replace(/[-/\\^$*+?.()|[\]{}]/gi, '\\$&'); } var translationColonRegexp = new RegExp(Object.keys(translationsWithColon).map(function (t) { diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb index c6543aaf4b..7dda67ae99 100644 --- a/spec/components/pretty_text_spec.rb +++ b/spec/components/pretty_text_spec.rb @@ -299,7 +299,6 @@ describe PrettyText do end end - it 'can escape *' do expect(PrettyText.cook("***a***a")).to match_html("

    aa

    ") expect(PrettyText.cook("***\\****a")).to match_html("

    *a

    ") From 6c4c542ae37f2cd16049b11aa2e1dcf51e557174 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 15 Jan 2015 13:00:48 -0500 Subject: [PATCH 021/230] FIX: Triggering a refresh while subcategory lists was broken. It would then list all categories isntead of the subcategory you were viewing at that time. --- .../controllers/discovery/categories.js.es6 | 13 +- .../discourse/models/category_list.js | 11 +- .../templates/discovery/categories.hbs | 120 +++++++++--------- .../discourse/templates/discovery/topics.hbs | 2 +- 4 files changed, 74 insertions(+), 72 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/discovery/categories.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/categories.js.es6 index 0fff6219a6..1e23c9154a 100644 --- a/app/assets/javascripts/discourse/controllers/discovery/categories.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery/categories.js.es6 @@ -8,7 +8,6 @@ export default DiscoveryController.extend({ actions: { refresh: function() { - var self = this; // Don't refresh if we're still loading if (this.get('controllers.discovery.loading')) { return; } @@ -17,7 +16,17 @@ export default DiscoveryController.extend({ // router and ember throws an error due to missing `handlerInfos`. // Lesson learned: Don't call `loading` yourself. this.set('controllers.discovery.loading', true); - Discourse.CategoryList.list('categories').then(function(list) { + + var parentCategory = this.get('model.parentCategory'); + var promise; + if (parentCategory) { + promise = Discourse.CategoryList.listForParent(parentCategory); + } else { + promise = Discourse.CategoryList.list(); + } + + var self = this; + promise.then(function(list) { self.set('model', list); self.send('loadingComplete'); }); diff --git a/app/assets/javascripts/discourse/models/category_list.js b/app/assets/javascripts/discourse/models/category_list.js index ee8b08c158..bd1e1711fb 100644 --- a/app/assets/javascripts/discourse/models/category_list.js +++ b/app/assets/javascripts/discourse/models/category_list.js @@ -1,11 +1,3 @@ -/** - A data model for containing a list of categories - - @class CategoryList - @extends Discourse.Model - @namespace Discourse - @module Discourse -**/ Discourse.CategoryList = Ember.ArrayProxy.extend({ init: function() { this.set('content', []); @@ -50,7 +42,8 @@ Discourse.CategoryList.reopenClass({ var self = this; return Discourse.ajax('/categories.json?parent_category_id=' + category.get('id')).then(function(result) { return Discourse.CategoryList.create({ - categories: self.categoriesFrom(result) + categories: self.categoriesFrom(result), + parentCategory: category }); }); }, diff --git a/app/assets/javascripts/discourse/templates/discovery/categories.hbs b/app/assets/javascripts/discourse/templates/discovery/categories.hbs index 691b4d0473..c3055c93b6 100644 --- a/app/assets/javascripts/discourse/templates/discovery/categories.hbs +++ b/app/assets/javascripts/discourse/templates/discovery/categories.hbs @@ -10,71 +10,71 @@ {{#each c in categories}} - - -
    -
    - {{category-title-link category=c}} - {{#if c.unreadTopics}} - {{i18n 'filters.unread.lower_title_with_count' count=c.unreadTopics}} - {{/if}} - {{#if c.newTopics}} - {{i18n 'filters.new.lower_title_with_count' count=c.newTopics}} - {{/if}} -
    -
    -
    - {{#if c.description_excerpt}} -
    - {{{c.description_excerpt}}} -
    - {{/if}} - {{#if c.subcategories}} -
    - {{#each s in c.subcategories}} - {{category-link s showParent="true" onlyStripe="true"}} - {{#if s.unreadTopics}} - {{unbound s.unreadTopics}} + + +
    +
    + {{category-title-link category=c}} + {{#if c.unreadTopics}} + {{i18n 'filters.unread.lower_title_with_count' count=c.unreadTopics}} {{/if}} - {{#if s.newTopics}} - {{unbound s.newTopics}} + {{#if c.newTopics}} + {{i18n 'filters.new.lower_title_with_count' count=c.newTopics}} {{/if}} - {{/each}} +
    +
    - {{/if}} - - - {{#each f in c.featuredTopics}} -
    From 2b877e4fc41e632332e1be5f5411e5d4c92e4ccf Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 16 Jan 2015 02:24:49 -0800 Subject: [PATCH 034/230] post expansion arrow CSS cleanup --- app/assets/stylesheets/desktop/topic-post.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss index aef34a1431..c794921324 100644 --- a/app/assets/stylesheets/desktop/topic-post.scss +++ b/app/assets/stylesheets/desktop/topic-post.scss @@ -254,7 +254,6 @@ nav.post-controls { opacity: 0; } } - &.bottom .arrow { float: right; } &.bottom { margin-top: -11px; .row { @@ -273,7 +272,7 @@ nav.post-controls { } .post-date { color: scale-color($primary, $lightness: 50%); } - .fa-arrow-up { margin-left: 5px; } + .fa-arrow-up, .fa-arrow-down { margin-left: 5px; } .row { border-top: 1px solid darken(scale-color-diff(), 10%); } From 24b282e5e9b841667471cc900f042fcb4012f387 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 16 Jan 2015 03:06:14 -0800 Subject: [PATCH 035/230] clicking expanded post date also jumps to post --- app/assets/javascripts/discourse/templates/embedded_post.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/templates/embedded_post.hbs b/app/assets/javascripts/discourse/templates/embedded_post.hbs index 695afbf0cf..853e1f632d 100644 --- a/app/assets/javascripts/discourse/templates/embedded_post.hbs +++ b/app/assets/javascripts/discourse/templates/embedded_post.hbs @@ -11,7 +11,7 @@ {{poster-name post=this}} {{#if view.parentView.previousPost}}{{/if}} {{#unless view.parentView.previousPost}}{{/unless}} - +
    {{{unbound cooked}}} From 37d73ec488719ffc8ff6d49bd46dc03b726e1be0 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 16 Jan 2015 12:52:16 -0500 Subject: [PATCH 036/230] Support customizing the composer below the textarea --- app/assets/javascripts/discourse/templates/composer.hbs | 1 + app/assets/javascripts/discourse/views/composer.js.es6 | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/app/assets/javascripts/discourse/templates/composer.hbs b/app/assets/javascripts/discourse/templates/composer.hbs index d28d450e12..45a7094640 100644 --- a/app/assets/javascripts/discourse/templates/composer.hbs +++ b/app/assets/javascripts/discourse/templates/composer.hbs @@ -101,6 +101,7 @@ so I'm going to stop rendering it until we figure out what's up {{#if currentUser}}
    + {{plugin-outlet "composer-fields-below"}} {{i18n 'cancel'}}
    diff --git a/app/assets/javascripts/discourse/views/composer.js.es6 b/app/assets/javascripts/discourse/views/composer.js.es6 index c62e86f20c..be2a8c14cc 100644 --- a/app/assets/javascripts/discourse/views/composer.js.es6 +++ b/app/assets/javascripts/discourse/views/composer.js.es6 @@ -73,6 +73,13 @@ var ComposerView = Discourse.View.extend(Ember.Evented, { if (pos) { self.$('.wmd-controls').css('top', $fields.height() + pos.top + 5); } + + // get the submit panel height + pos = self.$('.submit-panel').position(); + if (pos) { + self.$('.wmd-controls').css('bottom', h - pos.top + 7); + } + }); }.observes('model.composeState', 'model.action'), From 77ae0b4f7f2d968ea550e171ae2333a3be93b286 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 16 Jan 2015 17:28:50 -0800 Subject: [PATCH 037/230] block empty JS errors from Logster --- config/initializers/logster.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config/initializers/logster.rb b/config/initializers/logster.rb index d280a18f61..d0dcaca0bb 100644 --- a/config/initializers/logster.rb +++ b/config/initializers/logster.rb @@ -7,6 +7,16 @@ if Rails.env.production? /^ActionController::UnknownFormat/, + # super annoying empty JS errors in the form of + # + # Script error. + # Url: + # Line: 0 + # Column: 0 + # Window Location: http://discourse.codinghorror.com/t/the-god-login/2924/19 + # + /^Script error.\nUrl: \nLine: 0?\n/, + # suppress trackback spam bots Logster::IgnorePattern.new("Can't verify CSRF token authenticity", { REQUEST_URI: /\/trackback\/$/ }), From 18215f90d09cd0fc3c0b168748242e67df882be8 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 16 Jan 2015 17:36:18 -0800 Subject: [PATCH 038/230] more flexible regex to block empty JS Logster errors --- config/initializers/logster.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/logster.rb b/config/initializers/logster.rb index d0dcaca0bb..26e92c9e4e 100644 --- a/config/initializers/logster.rb +++ b/config/initializers/logster.rb @@ -15,7 +15,7 @@ if Rails.env.production? # Column: 0 # Window Location: http://discourse.codinghorror.com/t/the-god-login/2924/19 # - /^Script error.\nUrl: \nLine: 0?\n/, + /(?m)\AScript error\..*?Line: 0.*?Column: 0/, # suppress trackback spam bots Logster::IgnorePattern.new("Can't verify CSRF token authenticity", { REQUEST_URI: /\/trackback\/$/ }), From a2e77d8bf49c41c3a56515afe7dc118d8f88725f Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 16 Jan 2015 23:30:06 -0800 Subject: [PATCH 039/230] better regex JS err suppression for Logster --- config/initializers/logster.rb | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/config/initializers/logster.rb b/config/initializers/logster.rb index 26e92c9e4e..a86d0474b7 100644 --- a/config/initializers/logster.rb +++ b/config/initializers/logster.rb @@ -7,15 +7,21 @@ if Rails.env.production? /^ActionController::UnknownFormat/, - # super annoying empty JS errors in the form of + # some old browsers don't define the JavaScript console # - # Script error. - # Url: - # Line: 0 - # Column: 0 - # Window Location: http://discourse.codinghorror.com/t/the-god-login/2924/19 + # 'console' is undefined + # Url: http://discourse-cdn.codinghorror.com/assets/vendor-0dbc6e71b0ee5428574e3562afe91ef1.js + # Line: 16 + # Window Location: http://discourse.codinghorror.com/t/the-non-programming-programmer/120/2 # - /(?m)\AScript error\..*?Line: 0.*?Column: 0/, + /^'console' is undefined/, + + # ignore any empty JS errors that contain blanks or zeros for line and column fields + # + # Line: + # Column: + # + /(?m).*?Line: (?:\D|0).*?Column: (?:\D|0)/, # suppress trackback spam bots Logster::IgnorePattern.new("Can't verify CSRF token authenticity", { REQUEST_URI: /\/trackback\/$/ }), From 159106822668b89522f6aa0d54d653e61f9b1181 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Sat, 17 Jan 2015 01:26:21 -0800 Subject: [PATCH 040/230] add commented out SSL section to nginx config --- config/nginx.sample.conf | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/config/nginx.sample.conf b/config/nginx.sample.conf index 112614197c..c1c718107c 100644 --- a/config/nginx.sample.conf +++ b/config/nginx.sample.conf @@ -34,6 +34,18 @@ server { gzip_comp_level 5; gzip_types application/json text/css application/x-javascript application/javascript; + # Uncomment and configure this section for HTTPS support + # NOTE: Put your ssl cert in your main nginx config directory (/etc/nginx) + # + # rewrite ^/(.*) https://enter.your.web.hostname.here/$1 permanent; + # + # listen 443 ssl; + # ssl_certificate your-hostname-cert.pem; + # ssl_certificate_key your-hostname-cert.key; + # ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + # ssl_ciphers HIGH:!aNULL:!MD5; + # + server_name enter.your.web.hostname.here; server_tokens off; @@ -87,7 +99,7 @@ server { expires 1y; add_header Cache-Control public; } - + # cache emojis location ~ /_?emoji/ { expires 1y; From db5f2ae88c14f259180d27425f87a46a29ff33d9 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Sat, 17 Jan 2015 02:07:58 -0800 Subject: [PATCH 041/230] Fix relative URLs in plain text emails --- lib/email/sender.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/email/sender.rb b/lib/email/sender.rb index 19319b1d77..5d07c297a6 100644 --- a/lib/email/sender.rb +++ b/lib/email/sender.rb @@ -49,6 +49,13 @@ module Email @message.parts[0].body = @message.parts[0].body.to_s.gsub(/\[\/?email-indent\]/, '') + # Fix relative (ie upload) HTML links in markdown which do not work well in plain text emails. + # These are the links we add when a user uploads a file or image. + # Ideally we would parse general markdown into plain text, but that is almost an intractable problem. + url_prefix = Discourse.base_url + @message.parts[0].body = @message.parts[0].body.to_s.gsub(/([^<]*)<\/a>/, '[\2]('+url_prefix+'\1)') + @message.parts[0].body = @message.parts[0].body.to_s.gsub(/]*)>/, '![]('+url_prefix+'\1)') + @message.text_part.content_type = 'text/plain; charset=UTF-8' # Set up the email log From 1ab0d6bd82a24de7b47bb592b3c0f285409ac7c4 Mon Sep 17 00:00:00 2001 From: riking Date: Fri, 16 Jan 2015 14:30:46 -0800 Subject: [PATCH 042/230] FEATURE: Log username changes by staff Also fix the tests for changing username --- app/controllers/users_controller.rb | 3 +- app/models/user.rb | 6 +++- app/models/user_history.rb | 6 ++-- app/services/staff_action_logger.rb | 10 ++++++ config/locales/client.en.yml | 1 + spec/controllers/users_controller_spec.rb | 43 +++++++++++++++++++---- 6 files changed, 59 insertions(+), 10 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 06d03a7146..b2ff46cecd 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -88,7 +88,8 @@ class UsersController < ApplicationController user = fetch_user_from_params guardian.ensure_can_edit_username!(user) - result = user.change_username(params[:new_username]) + # TODO proper error surfacing (result is a Model#save call) + result = user.change_username(params[:new_username], current_user) raise Discourse::InvalidParameters.new(:new_username) unless result render json: { diff --git a/app/models/user.rb b/app/models/user.rb index f661b825fc..8b597298f6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -185,7 +185,11 @@ class User < ActiveRecord::Base Jobs.enqueue(:send_system_message, user_id: id, message_type: message_type) end - def change_username(new_username) + def change_username(new_username, actor=nil) + if actor && actor != self + StaffActionLogger.new(actor).log_username_change(self, self.username, new_username) + end + self.username = new_username save end diff --git a/app/models/user_history.rb b/app/models/user_history.rb index b4bfcad6a8..37722e4e57 100644 --- a/app/models/user_history.rb +++ b/app/models/user_history.rb @@ -34,7 +34,8 @@ class UserHistory < ActiveRecord::Base :delete_post, :delete_topic, :impersonate, - :roll_up) + :roll_up, + :change_username) end # Staff actions is a subset of all actions, used to audit actions taken by staff users. @@ -52,7 +53,8 @@ class UserHistory < ActiveRecord::Base :delete_post, :delete_topic, :impersonate, - :roll_up] + :roll_up, + :change_username] end def self.staff_action_ids diff --git a/app/services/staff_action_logger.rb b/app/services/staff_action_logger.rb index fc6bfdf100..620c1138f5 100644 --- a/app/services/staff_action_logger.rb +++ b/app/services/staff_action_logger.rb @@ -100,6 +100,16 @@ class StaffActionLogger })) end + def log_username_change(user, old_username, new_username, opts={}) + raise Discourse::InvalidParameters.new('user is nil') unless user + UserHistory.create( params(opts).merge({ + action: UserHistory.actions[:change_username], + target_user_id: user.id, + previous_value: old_username, + new_value: new_username + })) + end + def log_user_suspend(user, reason, opts={}) raise Discourse::InvalidParameters.new('user is nil') unless user UserHistory.create( params(opts).merge({ diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 3159cf6efd..a9686319ee 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1863,6 +1863,7 @@ en: actions: delete_user: "delete user" change_trust_level: "change trust level" + change_username: "change username" change_site_setting: "change site setting" change_site_customization: "change site customization" delete_site_customization: "delete site customization" diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index abd8cb76c6..0b3cfef5f4 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -612,28 +612,59 @@ describe UsersController do end context 'while logged in' do - let!(:user) { log_in } - let(:new_username) { "#{user.username}1234" } + let(:old_username) { "OrigUsrname" } + let(:new_username) { "#{old_username}1234" } + let(:user) { Fabricate(:user, username: old_username) } + + before do + user.username = old_username + log_in_user(user) + end it 'raises an error without a new_username param' do expect { xhr :put, :username, username: user.username }.to raise_error(ActionController::ParameterMissing) + user.reload.username.should == old_username end it 'raises an error when you don\'t have permission to change the username' do Guardian.any_instance.expects(:can_edit_username?).with(user).returns(false) xhr :put, :username, username: user.username, new_username: new_username expect(response).to be_forbidden + user.reload.username.should == old_username end + # Bad behavior, this should give a real JSON error, not an InvalidParameters it 'raises an error when change_username fails' do - User.any_instance.expects(:change_username).with(new_username).returns(false) + User.any_instance.expects(:save).returns(false) expect { xhr :put, :username, username: user.username, new_username: new_username }.to raise_error(Discourse::InvalidParameters) + user.reload.username.should == old_username end - it 'should succeed when the change_username returns true' do - User.any_instance.expects(:change_username).with(new_username).returns(true) + it 'should succeed in normal circumstances' do xhr :put, :username, username: user.username, new_username: new_username - expect(response).to be_success + response.should be_success + user.reload.username.should == new_username + end + + pending 'should fail if the user is old', 'ensure_can_edit_username! is not throwing' do + # Older than the change period and >1 post + user.created_at = Time.now - (SiteSetting.username_change_period + 1).days + user.stubs(:post_count).returns(200) + Guardian.new(user).can_edit_username?(user).should == false + + xhr :put, :username, username: user.username, new_username: new_username + + response.should be_forbidden + user.reload.username.should == old_username + end + + it 'should create a staff action log when a staff member changes the username' do + acting_user = Fabricate(:admin) + log_in_user(acting_user) + xhr :put, :username, username: user.username, new_username: new_username + response.should be_success + UserHistory.where(action: UserHistory.actions[:change_username], target_user_id: user.id, acting_user_id: acting_user.id).should be_present + user.reload.username.should == new_username end it 'should return a JSON response with the updated username' do From eedb977c5833a6016ca055a4e27fca6a1ce4446d Mon Sep 17 00:00:00 2001 From: Erick Guan Date: Sat, 17 Jan 2015 21:45:36 +0800 Subject: [PATCH 043/230] Fix guidelines_topic anchor point --- config/locales/server.en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 0eab5657b6..4861a7d759 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1951,7 +1951,7 @@ en: You may not post anything digital that belongs to someone else without permission. You may not post descriptions of, links to, or methods for stealing someone’s intellectual property (software, video, audio, images), or for breaking any other law. - + ## [Powered by You](#power) From 03fd275b454f2bf822f87a9170962cff44d3bff1 Mon Sep 17 00:00:00 2001 From: riking Date: Sat, 17 Jan 2015 23:14:59 -0800 Subject: [PATCH 044/230] FIX: Respect user locale for reply notifications --- app/services/post_alerter.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/services/post_alerter.rb b/app/services/post_alerter.rb index 3361d0291c..4d5da91c00 100644 --- a/app/services/post_alerter.rb +++ b/app/services/post_alerter.rb @@ -118,7 +118,13 @@ class PostAlerter if collapsed post = first_unread_post(user,post.topic) || post count = unread_count(user, post.topic) - opts[:display_username] = I18n.t('embed.replies', count: count) if count > 1 + I18n.with_locale(if SiteSetting.allow_user_locale && user.locale.present? + user.locale + else + SiteSetting.default_locale + end) do + opts[:display_username] = I18n.t('embed.replies', count: count) if count > 1 + end end UserActionObserver.log_notification(original_post, user, type, opts[:acting_user_id]) From fad9ca7b3645f454bd48eb9a40c6a85f84ce1ca4 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Mon, 19 Jan 2015 09:29:47 +0530 Subject: [PATCH 045/230] FIX: convert UTF8 charset to UTF-8 --- lib/email/receiver.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb index 8c9e7bec89..44857065e3 100644 --- a/lib/email/receiver.rb +++ b/lib/email/receiver.rb @@ -146,6 +146,8 @@ module Email return nil if object.nil? if object.charset + # convert UTF8 charset to UTF-8 + object.charset = object.charset.gsub(/utf8/i, "UTF-8") if object.charset.downcase == "utf8" object.body.decoded.force_encoding(object.charset).encode("UTF-8").to_s else object.body.to_s From d818fa4d60bf66585809eb077cadeb132faf20a2 Mon Sep 17 00:00:00 2001 From: Ed Gibbs Date: Sun, 18 Jan 2015 21:59:36 -0800 Subject: [PATCH 046/230] Move vagrant box to discourse hosting This change just moves the Vagrant box to the discourse account at Atlas. --- Vagrantfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index 388822a4d1..54955eaa06 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -3,8 +3,8 @@ # See https://github.com/discourse/discourse/blob/master/docs/VAGRANT.md # Vagrant.configure("2") do |config| - config.vm.box= "edgibbs/discourse-0.9.9.15.box" - config.vm.box_url = "https://vagrantcloud.com/edgibbs/discourse-0.9.9.15.box" + config.vm.box= "discourse/discourse-0.9.9.15.box" + config.vm.box_url = "https://vagrantcloud.com/discourse/discourse-0.9.9.15.box" # Make this VM reachable on the host network as well, so that other # VM's running other browsers can access our dev server. From 8fe2dd9186ba53c4116568ad49ce17e5b49da784 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Mon, 19 Jan 2015 00:03:22 -0800 Subject: [PATCH 047/230] copy improvement on invites --- config/locales/server.en.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 4861a7d759..a90a02bee0 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1416,11 +1416,11 @@ en: text_body_template: | Thanks for accepting your invitation to %{site_name} -- welcome! - We've automatically generated a username for you: **%{username}**, but you can change it any time by visiting [your user profile][prefs]. + We've created a new account **%{username}** for you, and you are logged in. You can change your name by visiting [your user profile][prefs]. - To log in again, either: + To log in again later: - 1. Log in using any method you like -- but it must resolve to the **same email address** that you received your original invitation email at. Otherwise we won't be able to tell it is you! + 1. Always **use the same email address from your original invitation** when logging in. Otherwise we won't be able to tell it's you! 2. Create a unique password for [your user profile][prefs], and use it to log in. From d0a32b28b9cbd084b94ce9965d84d870af37d3bb Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Mon, 19 Jan 2015 00:07:26 -0800 Subject: [PATCH 048/230] remove max_stars_per_day site setting --- config/locales/server.en.yml | 1 - config/site_settings.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index a90a02bee0..c225240f29 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -829,7 +829,6 @@ en: max_flags_per_day: "Maximum number of flags per user per day." max_bookmarks_per_day: "Maximum number of bookmarks per user per day." max_edits_per_day: "Maximum number of edits per user per day." - max_stars_per_day: "Maximum number of topics that can be starred per user per day." max_topics_per_day: "Maximum number of topics a user can create per day." max_private_messages_per_day: "Maximum number of private messages users can create per day." diff --git a/config/site_settings.yml b/config/site_settings.yml index 8e4709a438..2f22e397b2 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -611,7 +611,6 @@ rate_limits: max_bookmarks_per_day: 20 max_flags_per_day: 20 max_edits_per_day: 30 - max_stars_per_day: 20 max_topics_in_first_day: 5 max_replies_in_first_day: 10 From 3d0e59942cfc786e362924d2e0d3d617cc18599a Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Mon, 19 Jan 2015 00:33:51 -0800 Subject: [PATCH 049/230] IE9 doesn't support console.log --- app/assets/javascripts/locales/i18n.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/locales/i18n.js b/app/assets/javascripts/locales/i18n.js index d63f95994f..4e2bbb7c55 100644 --- a/app/assets/javascripts/locales/i18n.js +++ b/app/assets/javascripts/locales/i18n.js @@ -522,7 +522,7 @@ I18n.enable_verbose_localization = function(){ if (!_.isEmpty(value)) { message += ", parameters: " + JSON.stringify(value); } - window.console.log(message); + //window.console.log(message); } return t.apply(I18n, [scope, value]) + " (t" + current + ")"; }; From 3f7fa46af79c6ee4c17db3573446d9c06a3a05e1 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Mon, 19 Jan 2015 00:44:23 -0800 Subject: [PATCH 050/230] remove unnecessary site header title height --- app/assets/stylesheets/common/base/header.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss index d746de67e9..cc44112250 100644 --- a/app/assets/stylesheets/common/base/header.scss +++ b/app/assets/stylesheets/common/base/header.scss @@ -17,7 +17,6 @@ .title { display: table; float: left; - height: 45px; > a { display: table-cell; vertical-align: middle; From 90ff92e008170d5a38786b89dc655917e7d7c57d Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Mon, 19 Jan 2015 01:08:39 -0800 Subject: [PATCH 051/230] don't show self-bookmark avatar in user stream --- app/assets/javascripts/discourse/templates/user/stream.hbs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/user/stream.hbs b/app/assets/javascripts/discourse/templates/user/stream.hbs index 0d1f07c2f7..ed66367d51 100644 --- a/app/assets/javascripts/discourse/templates/user/stream.hbs +++ b/app/assets/javascripts/discourse/templates/user/stream.hbs @@ -17,9 +17,10 @@ + {{else}} +
    {{avatar grandChild imageSize="tiny" extraClasses="actor" ignoreTitle="true"}}
    + {{#if grandChild.edit_reason}} — {{unbound grandChild.edit_reason}}{{/if}} {{/if}} -
    {{avatar grandChild imageSize="tiny" extraClasses="actor" ignoreTitle="true"}}
    - {{#if grandChild.edit_reason}} — {{unbound grandChild.edit_reason}}{{/if}} {{/each}}
    {{/each}} From 4cb6606e8ca2f617a454d49da5d3f07144ed6f9c Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Mon, 19 Jan 2015 01:19:34 -0800 Subject: [PATCH 052/230] block some more dumb trackback spam from logging --- config/initializers/logster.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/initializers/logster.rb b/config/initializers/logster.rb index a86d0474b7..3e89656075 100644 --- a/config/initializers/logster.rb +++ b/config/initializers/logster.rb @@ -25,6 +25,9 @@ if Rails.env.production? # suppress trackback spam bots Logster::IgnorePattern.new("Can't verify CSRF token authenticity", { REQUEST_URI: /\/trackback\/$/ }), + # suppress trackback spam bots submitting to random URLs + # test for the presence of these params: url, title, excerpt, blog_name + Logster::IgnorePattern.new("Can't verify CSRF token authenticity", { params: { url: /./, title: /./, excerpt: /./, blog_name: /./} }, # API calls, TODO fix this in rails Logster::IgnorePattern.new("Can't verify CSRF token authenticity", { REQUEST_URI: /api_key/ }) From dae39b5b71021a42c4b90612f5676b28209894eb Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Mon, 19 Jan 2015 01:29:02 -0800 Subject: [PATCH 053/230] missed closing paren --- config/initializers/logster.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/logster.rb b/config/initializers/logster.rb index 3e89656075..48abc05567 100644 --- a/config/initializers/logster.rb +++ b/config/initializers/logster.rb @@ -27,7 +27,7 @@ if Rails.env.production? Logster::IgnorePattern.new("Can't verify CSRF token authenticity", { REQUEST_URI: /\/trackback\/$/ }), # suppress trackback spam bots submitting to random URLs # test for the presence of these params: url, title, excerpt, blog_name - Logster::IgnorePattern.new("Can't verify CSRF token authenticity", { params: { url: /./, title: /./, excerpt: /./, blog_name: /./} }, + Logster::IgnorePattern.new("Can't verify CSRF token authenticity", { params: { url: /./, title: /./, excerpt: /./, blog_name: /./} }), # API calls, TODO fix this in rails Logster::IgnorePattern.new("Can't verify CSRF token authenticity", { REQUEST_URI: /api_key/ }) From 5287669116f1821bfd99874bf54e4125e5985e40 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Mon, 19 Jan 2015 15:21:39 +0530 Subject: [PATCH 054/230] :lipstick: simplify utf-8 conversion --- lib/email/receiver.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb index 44857065e3..900108c531 100644 --- a/lib/email/receiver.rb +++ b/lib/email/receiver.rb @@ -146,9 +146,7 @@ module Email return nil if object.nil? if object.charset - # convert UTF8 charset to UTF-8 - object.charset = object.charset.gsub(/utf8/i, "UTF-8") if object.charset.downcase == "utf8" - object.body.decoded.force_encoding(object.charset).encode("UTF-8").to_s + object.body.decoded.force_encoding(object.charset.gsub(/utf8/i, "UTF-8")).encode("UTF-8").to_s else object.body.to_s end From 7687c95e7b24fa27a29601a86784ff83185022f9 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Mon, 19 Jan 2015 19:21:53 +0530 Subject: [PATCH 055/230] UX: add file size in CSV export notification --- app/jobs/regular/export_csv_file.rb | 4 +++- config/locales/server.en.yml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/jobs/regular/export_csv_file.rb b/app/jobs/regular/export_csv_file.rb index 9b2d319261..913c7593d0 100644 --- a/app/jobs/regular/export_csv_file.rb +++ b/app/jobs/regular/export_csv_file.rb @@ -4,6 +4,8 @@ require_dependency 'system_message' module Jobs class ExportCsvFile < Jobs::Base + include ActionView::Helpers::NumberHelper + HEADER_ATTRS_FOR = {} HEADER_ATTRS_FOR['user_archive'] = ['topic_title','category','sub_category','is_pm','post','like_count','reply_count','url','created_at'] HEADER_ATTRS_FOR['user_list'] = ['id','name','username','email','title','created_at','trust_level','active','admin','moderator','ip_address'] @@ -288,7 +290,7 @@ module Jobs def notify_user if @current_user if @file_name != "" && File.exists?("#{UserExport.base_directory}/#{@file_name}.gz") - SystemMessage.create_from_system_user(@current_user, :csv_export_succeeded, download_link: "#{Discourse.base_url}/export_csv/#{@file_name}.gz", file_name: "#{@file_name}.gz") + SystemMessage.create_from_system_user(@current_user, :csv_export_succeeded, download_link: "#{Discourse.base_url}/export_csv/#{@file_name}.gz", file_name: "#{@file_name}.gz", file_size: number_to_human_size(File.size("#{UserExport.base_directory}/#{@file_name}.gz"))) else SystemMessage.create_from_system_user(@current_user, :csv_export_failed) end diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index c225240f29..eb81c14712 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1485,7 +1485,7 @@ en: text_body_template: | Your data export was successful! :dvd: - %{file_name} + %{file_name} (%{file_size}) The above download link will be valid for 48 hours. From 6c4d85201148b73f097f8ea41dc46d8afd6e2efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 19 Jan 2015 15:00:55 +0100 Subject: [PATCH 056/230] Improve vBulletin importer - FEATURE: TopicCreator now supports 'pinned_at' parameter - FIX: :bug: FIX TopicQuerySQL to support pinned topic older than 2010 - FIX: :bug: Properly remove all HTML Entities from Usernames/Titles/Category Names/Groups in vBulletin importer - FIX: :bug: Properly handle specific vBulletin BBCode (quotes/mentions) - FIX: :bug: Make sure we generate a username from the name of the user instead of a fake email - FEATURE: Allow for custom timezone in vBulletin importer - FEATURE: Support for profile pictures/background in vBulletin importer - FIX: :bug: merge the categories tree to only 2 levels in vBulletin importer --- lib/post_creator.rb | 2 + lib/topic_creator.rb | 2 + lib/topic_query_sql.rb | 2 +- script/import_scripts/base.rb | 2 +- script/import_scripts/vbulletin.rb | 241 ++++++++++++++++++++++++----- 5 files changed, 208 insertions(+), 41 deletions(-) diff --git a/lib/post_creator.rb b/lib/post_creator.rb index bd21db5e9e..d87aa172e9 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -41,6 +41,8 @@ class PostCreator # target_usernames - comma delimited list of usernames for membership (private message) # target_group_names - comma delimited list of groups for membership (private message) # meta_data - Topic meta data hash + # created_at - Topic creation time (optional) + # pinned_at - Topic pinned time (optional) # def initialize(user, opts) # TODO: we should reload user in case it is tainted, should take in a user_id as opposed to user diff --git a/lib/topic_creator.rb b/lib/topic_creator.rb index 035af60887..179e8d2333 100644 --- a/lib/topic_creator.rb +++ b/lib/topic_creator.rb @@ -86,6 +86,8 @@ class TopicCreator topic_params[:created_at] = Time.zone.parse(@opts[:created_at].to_s) if @opts[:created_at].present? + topic_params[:pinned_at] = Time.zone.parse(@opts[:pinned_at].to_s) if @opts[:pinned_at].present? + topic_params end diff --git a/lib/topic_query_sql.rb b/lib/topic_query_sql.rb index 97d4598209..9decf581a6 100644 --- a/lib/topic_query_sql.rb +++ b/lib/topic_query_sql.rb @@ -6,7 +6,7 @@ module TopicQuerySQL class << self def lowest_date - "2010-01-01" + "1900-01-01" end def order_by_category_sql(dir) diff --git a/script/import_scripts/base.rb b/script/import_scripts/base.rb index 09d0c805b6..664a3088f6 100644 --- a/script/import_scripts/base.rb +++ b/script/import_scripts/base.rb @@ -229,7 +229,7 @@ class ImportScripts::Base results.each do |result| u = yield(result) - # block returns nil to skip a post + # block returns nil to skip a user if u.nil? users_skipped += 1 else diff --git a/script/import_scripts/vbulletin.rb b/script/import_scripts/vbulletin.rb index e847fc083d..dda4f29671 100644 --- a/script/import_scripts/vbulletin.rb +++ b/script/import_scripts/vbulletin.rb @@ -1,14 +1,21 @@ require File.expand_path(File.dirname(__FILE__) + "/base.rb") require 'mysql2' +require 'htmlentities' class ImportScripts::VBulletin < ImportScripts::Base - - DATABASE = "iref" BATCH_SIZE = 1000 + # CHANGE THESE BEFORE RUNNING THE IMPORTER + DATABASE = "iref" + TIMEZONE = "Asia/Kolkata" + def initialize super + @tz = TZInfo::Timezone.get(TIMEZONE) + + @htmlentities = HTMLEntities.new + @client = Mysql2::Client.new( host: "localhost", username: "root", @@ -24,6 +31,7 @@ class ImportScripts::VBulletin < ImportScripts::Base import_posts close_topics + post_process_posts end def import_groups @@ -37,8 +45,8 @@ class ImportScripts::VBulletin < ImportScripts::Base create_groups(groups) do |group| { - id: group["usergroupid"].to_i, - name: group["title"] + id: group["usergroupid"], + name: @htmlentities.decode(group["title"]).strip } end end @@ -50,6 +58,8 @@ class ImportScripts::VBulletin < ImportScripts::Base user_count = mysql_query("SELECT COUNT(userid) count FROM user").first["count"] + # TODO: add email back in when using real data + batches(BATCH_SIZE) do |offset| users = mysql_query <<-SQL SELECT userid, username, homepage, usertitle, usergroupid, joindate @@ -62,59 +72,118 @@ class ImportScripts::VBulletin < ImportScripts::Base break if users.size < 1 create_users(users, total: user_count, offset: offset) do |user| + username = @htmlentities.decode(user["username"]).strip + { - id: user["userid"].to_i, - username: user["username"], + id: user["userid"], + name: username, + username: username, email: user["email"].presence || fake_email, - website: user["homepage"], - title: user["usertitle"], + website: user["homepage"].strip, + title: @htmlentities.decode(user["usertitle"]).strip, primary_group_id: group_id_from_imported_group_id(user["usergroupid"]), - created_at: Time.at(user["joindate"].to_i), + created_at: parse_timestamp(user["joindate"]), post_create_action: proc do |u| @old_username_to_new_usernames[user["username"]] = u.username + import_profile_picture(user, u) + import_profile_background(user, u) end } end end end + def import_profile_picture(old_user, imported_user) + query = mysql_query <<-SQL + SELECT filedata, filename + FROM customavatar + WHERE userid = #{old_user["userid"]} + ORDER BY dateline DESC + LIMIT 1 + SQL + + picture = query.first + + return if picture.nil? + + file = Tempfile.new("profile-picture") + file.write(picture["filedata"].encode("ASCII-8BIT").force_encoding("UTF-8")) + file.rewind + + upload = Upload.create_for(imported_user.id, file, picture["filename"], file.size) + + return if !upload.persisted? + + imported_user.create_user_avatar + imported_user.user_avatar.update(custom_upload_id: upload.id) + imported_user.update(uploaded_avatar_id: upload.id) + ensure + file.close rescue nil + file.unlind rescue nil + end + + def import_profile_background(old_user, imported_user) + query = mysql_query <<-SQL + SELECT filedata, filename + FROM customprofilepic + WHERE userid = #{old_user["userid"]} + ORDER BY dateline DESC + LIMIT 1 + SQL + + background = query.first + + return if background.nil? + + file = Tempfile.new("profile-background") + file.write(background["filedata"].encode("ASCII-8BIT").force_encoding("UTF-8")) + file.rewind + + upload = Upload.create_for(imported_user.id, file, background["filename"], file.size) + + return if !upload.persisted? + + imported_user.user_profile.update(profile_background: upload.url) + ensure + file.close rescue nil + file.unlink rescue nil + end + def import_categories puts "", "importing top level categories..." - # TODO: deal with permissions + categories = mysql_query("SELECT forumid, title, description, displayorder, parentid FROM forum ORDER BY forumid").to_a - top_level_categories = mysql_query <<-SQL - SELECT forumid, title, description, displayorder - FROM forum - WHERE parentid = -1 - ORDER BY forumid - SQL + top_level_categories = categories.select { |c| c["parentid"] == -1 } create_categories(top_level_categories) do |category| { - id: category["forumid"].to_i, - name: category["title"], - position: category["displayorder"].to_i, - description: category["description"] + id: category["forumid"], + name: @htmlentities.decode(category["title"]).strip, + position: category["displayorder"], + description: @htmlentities.decode(category["description"]).strip } end puts "", "importing children categories..." - childen_categories = mysql_query <<-SQL - SELECT forumid, title, description, displayorder, parentid - FROM forum - WHERE parentid <> -1 - ORDER BY forumid - SQL + children_categories = categories.select { |c| c["parentid"] != -1 } + top_level_category_ids = Set.new(top_level_categories.map { |c| c["forumid"] }) - create_categories(childen_categories) do |category| + # cut down the tree to only 2 levels of categories + children_categories.each do |cc| + while !top_level_category_ids.include?(cc["parentid"]) + cc["parentid"] = categories.detect { |c| c["forumid"] == cc["parentid"] }["parentid"] + end + end + + create_categories(children_categories) do |category| { - id: category["forumid"].to_i, - name: category["title"], - position: category["displayorder"].to_i, - description: category["description"].strip!, - parent_category_id: category_from_imported_category_id(category["parentid"].to_i).try(:[], "id") + id: category["forumid"], + name: @htmlentities.decode(category["title"]).strip, + position: category["displayorder"], + description: @htmlentities.decode(category["description"]).strip, + parent_category_id: category_from_imported_category_id(category["parentid"]).try(:[], "id") } end end @@ -145,13 +214,13 @@ class ImportScripts::VBulletin < ImportScripts::Base @closed_topic_ids << topic_id if topic["open"] == "0" t = { id: topic_id, - user_id: user_id_from_imported_user_id(topic["postuserid"].to_i) || Discourse::SYSTEM_USER_ID, - title: CGI.unescapeHTML(topic["title"]).strip[0...255], - category: category_from_imported_category_id(topic["forumid"].to_i).try(:name), + user_id: user_id_from_imported_user_id(topic["postuserid"]) || Discourse::SYSTEM_USER_ID, + title: @htmlentities.decode(topic["title"]).strip[0...255], + category: category_from_imported_category_id(topic["forumid"]).try(:name), raw: preprocess_post_raw(topic["raw"]), - created_at: Time.at(topic["dateline"].to_i), + created_at: parse_timestamp(topic["dateline"]), visible: topic["visible"].to_i == 1, - views: topic["views"].to_i, + views: topic["views"], } t[:pinned_at] = t[:created_at] if topic["sticky"].to_i == 1 t @@ -179,11 +248,11 @@ class ImportScripts::VBulletin < ImportScripts::Base create_posts(posts, total: post_count, offset: offset) do |post| next unless topic = topic_lookup_from_imported_post_id("thread-#{post["threadid"]}") p = { - id: post["postid"].to_i, + id: post["postid"], user_id: user_id_from_imported_user_id(post["userid"]) || Discourse::SYSTEM_USER_ID, topic_id: topic[:topic_id], raw: preprocess_post_raw(post["raw"]), - created_at: Time.at(post["dateline"].to_i), + created_at: parse_timestamp(post["dateline"]), hidden: post["visible"].to_i == 0, } if parent = topic_lookup_from_imported_post_id(post["parentid"]) @@ -214,9 +283,32 @@ class ImportScripts::VBulletin < ImportScripts::Base Topic.exec_sql(sql, @closed_topic_ids) end + def post_process_posts + puts "", "Postprocessing posts..." + + current = 0 + max = Post.count + + Post.find_each do |post| + begin + new_raw = postprocess_post_raw(post.raw) + if new_raw != post.raw + post.raw = new_raw + post.save + end + ensure + print_status(current += 1, max) + end + end + end + def preprocess_post_raw(raw) return "" if raw.blank? + # decode HTML entities + raw = @htmlentities.decode(raw) + + # fix whitespaces raw = raw.gsub(/(\\r)?\\n/, "\n") .gsub("\\t", "\t") @@ -301,6 +393,77 @@ class ImportScripts::VBulletin < ImportScripts::Base raw end + def postprocess_post_raw(raw) + # [QUOTE=;]...[/QUOTE] + raw = raw.gsub(/\[quote=([^;]+);(\d+)\](.+?)\[\/quote\]/im) do + old_username, post_id, quote = $1, $2, $3 + + if @old_username_to_new_usernames.has_key?(old_username) + old_username = @old_username_to_new_usernames[old_username] + end + + if topic_lookup = topic_lookup_from_imported_post_id(post_id) + post_number = topic_lookup[:post_number] + topic_id = topic_lookup[:topic_id] + "\n[quote=\"#{old_username},post:#{post_number},topic:#{topic_id}\"]\n#{quote}\n[/quote]\n" + else + "\n[quote=\"#{old_username}\"]\n#{quote}\n[/quote]\n" + end + end + + # [THREAD][/THREAD] + # ==> http://my.discourse.org/t/slug/ + raw = raw.gsub(/\[thread\](\d+)\[\/thread\]/i) do + thread_id = $1 + if topic_lookup = topic_lookup_from_imported_post_id("thread-#{thread_id}") + topic_lookup[:url] + else + $& + end + end + + # [THREAD=]...[/THREAD] + # ==> [...](http://my.discourse.org/t/slug/) + raw = raw.gsub(/\[thread=(\d+)\](.+?)\[\/thread\]/i) do + thread_id, link = $1, $2 + if topic_lookup = topic_lookup_from_imported_post_id("thread-#{thread_id}") + url = topic_lookup[:url] + "[#{link}](#{url})" + else + $& + end + end + + # [POST][/POST] + # ==> http://my.discourse.org/t/slug// + raw = raw.gsub(/\[post\](\d+)\[\/post\]/i) do + post_id = $1 + if topic_lookup = topic_lookup_from_imported_post_id(post_id) + topic_lookup[:url] + else + $& + end + end + + # [POST=]...[/POST] + # ==> [...](http://my.discourse.org/t///) + raw = raw.gsub(/\[post=(\d+)\](.+?)\[\/post\]/i) do + post_id, link = $1, $2 + if topic_lookup = topic_lookup_from_imported_post_id(post_id) + url = topic_lookup[:url] + "[#{link}](#{url})" + else + $& + end + end + + raw + end + + def parse_timestamp(timestamp) + Time.zone.at(@tz.utc_to_local(timestamp)) + end + def fake_email SecureRandom.hex << "@domain.com" end From 23fe0cfb4ed48234176b86379e67f9538bf57425 Mon Sep 17 00:00:00 2001 From: Alex Williams Date: Mon, 19 Jan 2015 10:52:02 -0500 Subject: [PATCH 057/230] Fix spelling in contact_email_missing message. --- config/locales/server.en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index eb81c14712..03ca237684 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -599,7 +599,7 @@ en: image_magick_warning: 'The server is configured to create thumbnails of large images, but ImageMagick is not installed. Install ImageMagick using your favorite package manager or download the latest release.' failing_emails_warning: 'There are %{num_failed_jobs} email jobs that failed. Check your config/discourse.conf file and ensure that the mail server settings are correct. See the failed jobs in Sidekiq.' default_logo_warning: "Set the graphic logos for your site. Update logo_url, logo_small_url, and favicon_url in Site Settings." - contact_email_missing: "Enter a site contact email address so you can be reached you urgent matters regarding your site. Update it in Site Settings." + contact_email_missing: "Enter a site contact email address so you can be reached for urgent matters regarding your site. Update it in Site Settings." contact_email_invalid: "The site contact email is invalid. Update it in Site Settings." title_nag: "Enter the name of your site. Update title in Site Settings." site_description_missing: "Enter a one sentence description of your site that will appear in search results. Update site_description in Site Settings." From 7412ff4da70e8d15d1cc8fc845389cdc72cf9d2b Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Mon, 19 Jan 2015 12:36:56 -0500 Subject: [PATCH 058/230] FIX: suspended users are logged out when they are suspended. Show a reason for suspension when they try to log in. --- .../discourse/controllers/login.js.es6 | 6 ++++++ app/controllers/admin/users_controller.rb | 1 + lib/auth/result.rb | 20 +++++++++++++------ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/login.js.es6 b/app/assets/javascripts/discourse/controllers/login.js.es6 index f405e6ee1b..8e6c722c6a 100644 --- a/app/assets/javascripts/discourse/controllers/login.js.es6 +++ b/app/assets/javascripts/discourse/controllers/login.js.es6 @@ -160,6 +160,12 @@ export default DiscourseController.extend(ModalFunctionality, { this.set('authenticate', null); return; } + if (options.suspended) { + this.send('showLogin'); + this.flash(options.suspended_message, 'error'); + this.set('authenticate', null); + return; + } // Reload the page if we're authenticated if (options.authenticated) { if (window.location.pathname === Discourse.getURL('/login')) { diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index e62999fe01..c0f2469524 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -53,6 +53,7 @@ class Admin::UsersController < Admin::AdminController @user.suspended_at = DateTime.now @user.save! StaffActionLogger.new(current_user).log_user_suspend(@user, params[:reason]) + MessageBus.publish "/logout", @user.id, user_ids: [@user.id] render nothing: true end diff --git a/lib/auth/result.rb b/lib/auth/result.rb index 3b0a36b5de..505541ea4c 100644 --- a/lib/auth/result.rb +++ b/lib/auth/result.rb @@ -19,12 +19,20 @@ class Auth::Result if requires_invite { requires_invite: true } elsif user - { - authenticated: !!authenticated, - awaiting_activation: !!awaiting_activation, - awaiting_approval: !!awaiting_approval, - not_allowed_from_ip_address: !!not_allowed_from_ip_address - } + if user.suspended? + { + suspended: true, + suspended_message: I18n.t( user.suspend_reason ? "login.suspended_with_reason" : "login.suspended", + {date: I18n.l(user.suspended_till, format: :date_only), reason: user.suspend_reason} ) + } + else + { + authenticated: !!authenticated, + awaiting_activation: !!awaiting_activation, + awaiting_approval: !!awaiting_approval, + not_allowed_from_ip_address: !!not_allowed_from_ip_address + } + end else { email: email, From 5e751ce90a2803ca34a93bb5f1d1306e29eb4da0 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Tue, 20 Jan 2015 00:20:01 +0530 Subject: [PATCH 059/230] FEATURE: :gift: rate limit invites for non-staff users --- app/models/invite.rb | 9 +++++++++ config/locales/client.en.yml | 2 +- config/locales/server.en.yml | 1 + config/site_settings.yml | 1 + spec/models/invite_spec.rb | 2 ++ 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/models/invite.rb b/app/models/invite.rb index 28fb95526d..36194915ed 100644 --- a/app/models/invite.rb +++ b/app/models/invite.rb @@ -1,6 +1,11 @@ +require_dependency 'rate_limiter' + class Invite < ActiveRecord::Base + include RateLimiter::OnCreateRecord include Trashable + rate_limit :limit_invites_per_day + belongs_to :user belongs_to :topic belongs_to :invited_by, class_name: 'User' @@ -184,6 +189,10 @@ class Invite < ActiveRecord::Base Jobs.enqueue(:invite_email, invite_id: self.id) end + def limit_invites_per_day + RateLimiter.new(invited_by, "invites-per-day:#{Date.today}", SiteSetting.max_invites_per_day, 1.day.to_i) + end + def self.base_directory File.join(Rails.root, "public", "uploads", "csv", RailsMultisite::ConnectionManagement.current_db) end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index a9686319ee..95c269f0b5 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1018,7 +1018,7 @@ en: email_placeholder: 'name@example.com' success: "We mailed out an invitation to {{email}}. We'll notify you when the invitation is redeemed. Check the invitations tab on your user page to keep track of your invites." - error: "Sorry, we couldn't invite that person. Perhaps they are already a user?" + error: "Sorry, we couldn't invite that person. Perhaps they are already a user? (Invites are rate limited)" login_reply: 'Log In to Reply' diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index eb81c14712..9c6f113686 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -831,6 +831,7 @@ en: max_edits_per_day: "Maximum number of edits per user per day." max_topics_per_day: "Maximum number of topics a user can create per day." max_private_messages_per_day: "Maximum number of private messages users can create per day." + max_invites_per_day: "Maximum number of invites a user can send per day." suggested_topics: "Number of suggested topics shown at the bottom of a topic." limit_suggested_to_category: "Only show topics from the current category in suggested topics." diff --git a/config/site_settings.yml b/config/site_settings.yml index 2f22e397b2..c9b02b6058 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -611,6 +611,7 @@ rate_limits: max_bookmarks_per_day: 20 max_flags_per_day: 20 max_edits_per_day: 30 + max_invites_per_day: 10 max_topics_in_first_day: 5 max_replies_in_first_day: 10 diff --git a/spec/models/invite_spec.rb b/spec/models/invite_spec.rb index c64beed0fd..a7a1b02a2b 100644 --- a/spec/models/invite_spec.rb +++ b/spec/models/invite_spec.rb @@ -4,6 +4,8 @@ describe Invite do it { is_expected.to validate_presence_of :invited_by_id } + it { is_expected.to rate_limit } + let(:iceking) { 'iceking@adventuretime.ooo' } context 'user validators' do From 4c0129ccddb9037467133d37e5a93520a5f86f77 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Mon, 19 Jan 2015 15:30:16 -0500 Subject: [PATCH 060/230] PERF: slow user pages in admin. add an index for trust level 3 calculations, and memoize query results --- app/models/trust_level3_requirements.rb | 20 +++++++++---------- ...92813_add_posts_index_including_deleted.rb | 5 +++++ 2 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 db/migrate/20150119192813_add_posts_index_including_deleted.rb diff --git a/app/models/trust_level3_requirements.rb b/app/models/trust_level3_requirements.rb index a42775e123..919db83174 100644 --- a/app/models/trust_level3_requirements.rb +++ b/app/models/trust_level3_requirements.rb @@ -134,12 +134,12 @@ class TrustLevel3Requirements end def num_flagged_by_users - PostAction.with_deleted - .where(post_id: flagged_post_ids) - .where.not(user_id: @user.id) - .where.not(agreed_at: nil) - .pluck(:user_id) - .uniq.count + @_num_flagged_by_users ||= PostAction.with_deleted + .where(post_id: flagged_post_ids) + .where.not(user_id: @user.id) + .where.not(agreed_at: nil) + .pluck(:user_id) + .uniq.count end def max_flagged_by_users @@ -212,9 +212,9 @@ class TrustLevel3Requirements end def flagged_post_ids - @user.posts - .with_deleted - .where('created_at > ? AND (spam_count > 0 OR inappropriate_count > 0)', TIME_PERIOD.days.ago) - .pluck(:id) + @_flagged_post_ids ||= @user.posts + .with_deleted + .where('created_at > ? AND (spam_count > 0 OR inappropriate_count > 0)', TIME_PERIOD.days.ago) + .pluck(:id) end end diff --git a/db/migrate/20150119192813_add_posts_index_including_deleted.rb b/db/migrate/20150119192813_add_posts_index_including_deleted.rb new file mode 100644 index 0000000000..20d6e588df --- /dev/null +++ b/db/migrate/20150119192813_add_posts_index_including_deleted.rb @@ -0,0 +1,5 @@ +class AddPostsIndexIncludingDeleted < ActiveRecord::Migration + def change + add_index :posts, [:user_id, :created_at] + end +end From 350554e198084ee241143cc55891e80fd1c8c00a Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 20 Jan 2015 11:36:28 -0500 Subject: [PATCH 061/230] UX: Change category badge style to use stripes --- .../discourse/components/category-drop.js.es6 | 23 ++-- .../components/category-group.js.es6 | 4 +- .../controllers/edit-category.js.es6 | 3 +- .../discourse/controllers/history.js.es6 | 13 ++- .../discourse/helpers/category-link.js.es6 | 64 ++++++++++- app/assets/javascripts/discourse/lib/html.js | 108 +----------------- .../javascripts/discourse/models/category.js | 1 + .../category-group-autocomplete.raw.hbs | 2 +- .../templates/components/category-drop.hbs | 14 +-- .../templates/discovery/categories.hbs | 2 +- .../discourse/templates/header.hbs | 4 +- .../templates/list/category-column.raw.hbs | 2 +- .../mobile/components/basic-topic-list.hbs | 2 +- .../templates/mobile/discovery/categories.hbs | 2 +- .../mobile/list/topic_list_item.raw.hbs | 2 +- .../templates/modal/edit-category-general.hbs | 2 +- .../discourse/templates/site-map.hbs | 2 +- .../javascripts/discourse/templates/topic.hbs | 2 +- .../discourse/views/category-chooser.js.es6 | 7 +- .../javascripts/discourse/views/topic.js.es6 | 3 +- .../stylesheets/common/base/_topic-list.scss | 23 ++-- .../common/components/badges.css.scss | 4 +- app/assets/stylesheets/desktop/user.scss | 3 + app/helpers/user_notifications_helper.rb | 11 +- app/views/user_notifications/digest.html.erb | 2 +- .../lib/category-badge-test.js.es6 | 41 +++++++ test/javascripts/lib/html-test.js.es6 | 40 ------- 27 files changed, 175 insertions(+), 211 deletions(-) create mode 100644 test/javascripts/lib/category-badge-test.js.es6 diff --git a/app/assets/javascripts/discourse/components/category-drop.js.es6 b/app/assets/javascripts/discourse/components/category-drop.js.es6 index b05b9dcd77..527a4fb479 100644 --- a/app/assets/javascripts/discourse/components/category-drop.js.es6 +++ b/app/assets/javascripts/discourse/components/category-drop.js.es6 @@ -1,11 +1,5 @@ -/** - Renders a drop down for selecting a category +var get = Ember.get; - @class CategoryDropComponent - @extends Ember.Component - @namespace Discourse - @module Discourse -**/ export default Ember.Component.extend({ classNameBindings: ['category::no-category', 'categories:has-drop'], tagName: 'li', @@ -44,11 +38,20 @@ export default Ember.Component.extend({ badgeStyle: function() { var category = this.get('category'); + if (category) { - return Discourse.HTML.categoryStyle(category); - } else { - return "background-color: #eee; color: #333"; + var color = get(category, 'color'), + textColor = get(category, 'text_color'); + + if (color || textColor) { + var style = ""; + if (color) { style += "background-color: #" + color + "; "; } + if (textColor) { style += "color: #" + textColor + "; "; } + return style; + } } + + return "background-color: #eee; color: #333"; }.property('category'), clickEventName: function() { diff --git a/app/assets/javascripts/discourse/components/category-group.js.es6 b/app/assets/javascripts/discourse/components/category-group.js.es6 index 32434e74a9..14caa32bfe 100644 --- a/app/assets/javascripts/discourse/components/category-group.js.es6 +++ b/app/assets/javascripts/discourse/components/category-group.js.es6 @@ -1,3 +1,5 @@ +import { categoryBadgeHTML } from 'discourse/helpers/category-link'; + export default Ember.Component.extend({ _initializeAutocomplete: function(){ @@ -25,7 +27,7 @@ export default Ember.Component.extend({ }, template: template, transformComplete: function(category) { - return Discourse.HTML.categoryBadge(category, {allowUncategorized: true}); + return categoryBadgeHTML(category, {allowUncategorized: true}); } }); }.on('didInsertElement') diff --git a/app/assets/javascripts/discourse/controllers/edit-category.js.es6 b/app/assets/javascripts/discourse/controllers/edit-category.js.es6 index 443d3bcb7c..5928e97ea0 100644 --- a/app/assets/javascripts/discourse/controllers/edit-category.js.es6 +++ b/app/assets/javascripts/discourse/controllers/edit-category.js.es6 @@ -1,5 +1,6 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality'; import ObjectController from 'discourse/controllers/object'; +import { categoryBadgeHTML } from 'discourse/helpers/category-link'; // Modal for editing / creating a category export default ObjectController.extend(ModalFunctionality, { @@ -69,7 +70,7 @@ export default ObjectController.extend(ModalFunctionality, { parent_category_id: parseInt(this.get('parent_category_id'),10), read_restricted: this.get('model.read_restricted') }); - return Discourse.HTML.categoryBadge(c, {showParent: true, link: false}); + return categoryBadgeHTML(c, {link: false}); }.property('parent_category_id', 'categoryName', 'color', 'text_color'), // background colors are available as a pipe-separated string diff --git a/app/assets/javascripts/discourse/controllers/history.js.es6 b/app/assets/javascripts/discourse/controllers/history.js.es6 index 2a00dc18d5..55302edc9c 100644 --- a/app/assets/javascripts/discourse/controllers/history.js.es6 +++ b/app/assets/javascripts/discourse/controllers/history.js.es6 @@ -1,5 +1,6 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality'; import ObjectController from 'discourse/controllers/object'; +import { categoryBadgeHTML } from 'discourse/helpers/category-link'; // This controller handles displaying of history export default ObjectController.extend(ModalFunctionality, { @@ -22,14 +23,14 @@ export default ObjectController.extend(ModalFunctionality, { hide: function(postId, postVersion) { var self = this; - Discourse.Post.hideRevision(postId, postVersion).then(function (result) { + Discourse.Post.hideRevision(postId, postVersion).then(function () { self.refresh(postId, postVersion); }); }, show: function(postId, postVersion) { var self = this; - Discourse.Post.showRevision(postId, postVersion).then(function (result) { + Discourse.Post.showRevision(postId, postVersion).then(function () { self.refresh(postId, postVersion); }); }, @@ -68,7 +69,7 @@ export default ObjectController.extend(ModalFunctionality, { var changes = this.get("category_changes"); if (changes) { var category = Discourse.Category.findById(changes["previous"]); - return Discourse.HTML.categoryBadge(category, { allowUncategorized: true }); + return categoryBadgeHTML(category, { allowUncategorized: true }); } }.property("category_changes"), @@ -76,12 +77,12 @@ export default ObjectController.extend(ModalFunctionality, { var changes = this.get("category_changes"); if (changes) { var category = Discourse.Category.findById(changes["current"]); - return Discourse.HTML.categoryBadge(category, { allowUncategorized: true }); + return categoryBadgeHTML(category, { allowUncategorized: true }); } }.property("category_changes"), wiki_diff: function() { - var changes = this.get("wiki_changes") + var changes = this.get("wiki_changes"); if (changes) { return changes["current"] ? '' : @@ -93,7 +94,7 @@ export default ObjectController.extend(ModalFunctionality, { var moderator = Discourse.Site.currentProp('post_types.moderator_action'); var changes = this.get("post_type_changes"); if (changes) { - return changes["current"] == moderator ? + return changes["current"] === moderator ? '' : ''; } diff --git a/app/assets/javascripts/discourse/helpers/category-link.js.es6 b/app/assets/javascripts/discourse/helpers/category-link.js.es6 index 412a64df99..ea71350dfa 100644 --- a/app/assets/javascripts/discourse/helpers/category-link.js.es6 +++ b/app/assets/javascripts/discourse/helpers/category-link.js.es6 @@ -1,4 +1,63 @@ import registerUnbound from 'discourse/helpers/register-unbound'; +import { iconHTML } from 'discourse/helpers/fa-icon'; + +var get = Em.get, + escapeExpression = Handlebars.Utils.escapeExpression; + +function categoryStripe(tagName, category, extraClasses, href) { + if (!category) { return ""; } + + var color = Em.get(category, 'color'), + style = color ? "style='background-color: #" + color + ";'" : ""; + + return "<" + tagName + " class='badge-category-parent" + extraClasses + "' " + style + " href=\"" + href + "\">"; +} + +export function categoryBadgeHTML(category, opts) { + opts = opts || {}; + + if ((!category) || + (!opts.allowUncategorized && + Em.get(category, 'id') === Discourse.Site.currentProp("uncategorized_category_id") && + Discourse.SiteSettings.suppress_uncategorized_badge + ) + ) return ""; + + var description = get(category, 'description_text'), + restricted = get(category, 'read_restricted'), + url = Discourse.getURL("/c/") + Discourse.Category.slugFor(category), + href = (opts.link === false ? '' : url), + tagName = (opts.link === false || opts.link === "false" ? 'span' : 'a'), + extraClasses = (opts.extraClasses ? (' ' + opts.extraClasses) : ''); + + var html = ""; + + var parentCat = Discourse.Category.findById(category.get('parent_category_id')); + if (opts.hideParent) { parentCat = null; } + html += categoryStripe(tagName, parentCat, extraClasses, href); + + if (parentCat !== category) { + html += categoryStripe(tagName, category, extraClasses, href); + } + + var classNames = "badge-category clear-badge" + extraClasses; + if (restricted) { classNames += " restricted"; } + + html += "<" + tagName + ' href="' + href + '" ' + + 'data-drop-close="true" class="' + classNames + '"' + + (description ? 'title="' + escapeExpression(description) + '" ' : '') + + ">"; + + var name = escapeExpression(get(category, 'name')); + if (restricted) { + html += "
    " + iconHTML('lock') + " " + name + "
    "; + } else { + html += name; + } + html += ""; + + return "" + html + ""; +} export function categoryLinkHTML(category, options) { var categoryOptions = {}; @@ -9,12 +68,11 @@ export function categoryLinkHTML(category, options) { if (options) { if (options.allowUncategorized) { categoryOptions.allowUncategorized = true; } - if (options.showParent) { categoryOptions.showParent = true; } - if (options.onlyStripe) { categoryOptions.onlyStripe = true; } if (options.link !== undefined) { categoryOptions.link = options.link; } if (options.extraClasses) { categoryOptions.extraClasses = options.extraClasses; } + if (options.hideParent) { categoryOptions.hideParent = true; } } - return new Handlebars.SafeString(Discourse.HTML.categoryBadge(category, categoryOptions)); + return new Handlebars.SafeString(categoryBadgeHTML(category, categoryOptions)); } registerUnbound('category-link', categoryLinkHTML); diff --git a/app/assets/javascripts/discourse/lib/html.js b/app/assets/javascripts/discourse/lib/html.js index 8eb7a3be96..ccb807d6a8 100644 --- a/app/assets/javascripts/discourse/lib/html.js +++ b/app/assets/javascripts/discourse/lib/html.js @@ -1,11 +1,3 @@ -/** - Helpers to build HTML strings as well as custom fragments. - - @class HTML - @namespace Discourse - @module Discourse -**/ - var customizations = {}; Discourse.HTML = { @@ -15,9 +7,6 @@ Discourse.HTML = { using `setCustomHTML(key, html)`. This is used by a handlebars helper to find the HTML content it wants. It will also check the `PreloadStore` for any server side preloaded HTML. - - @method getCustomHTML - @param {String} key to lookup **/ getCustomHTML: function(key) { var c = customizations[key]; @@ -31,104 +20,9 @@ Discourse.HTML = { } }, - /** - Set a fragment of HTML by key. It can then be looked up with `getCustomHTML(key)`. - - @method setCustomHTML - @param {String} key to store the html - @param {String} html fragment to store - **/ + // Set a fragment of HTML by key. It can then be looked up with `getCustomHTML(key)`. setCustomHTML: function(key, html) { customizations[key] = html; - }, - - /** - Returns the CSS styles for a category - - @method categoryStyle - @param {Discourse.Category} category the category whose link we want - **/ - categoryStyle: function(category) { - var color = Em.get(category, 'color'), - textColor = Em.get(category, 'text_color'); - - if (!color && !textColor) { return; } - - // Add the custom style if we need to - var style = ""; - if (color) { style += "background-color: #" + color + "; "; } - if (textColor) { style += "color: #" + textColor + "; "; } - return style; - }, - - /** - Create a category badge - - @method categoryBadge - @param {Discourse.Category} category the category whose link we want - @param {Object} opts The options for the category link - @param {Boolean} opts.allowUncategorized Whether we allow rendering of the uncategorized category (default false) - @param {Boolean} opts.showParent Whether to visually show whether category is a sub-category (default false) - @param {Boolean} opts.link Whether this category badge should link to the category (default true) - @param {String} opts.extraClasses add this string to the class attribute of the badge - @returns {String} the html category badge - **/ - categoryBadge: function(category, opts) { - opts = opts || {}; - - if ((!category) || - (!opts.allowUncategorized && - Em.get(category, 'id') === Discourse.Site.currentProp("uncategorized_category_id") && - Discourse.SiteSettings.suppress_uncategorized_badge - ) - ) return ""; - - var name = Em.get(category, 'name'), - description = Em.get(category, 'description_text'), - restricted = Em.get(category, 'read_restricted'), - url = Discourse.getURL("/c/") + Discourse.Category.slugFor(category), - elem = (opts.link === false ? 'span' : 'a'), - extraClasses = (opts.extraClasses ? (' ' + opts.extraClasses) : ''), - html = "<" + elem + " href=\"" + (opts.link === false ? '' : url) + "\" ", - categoryStyle; - - // Parent stripe implies onlyStripe - if (opts.onlyStripe) { opts.showParent = true; } - - html += "data-drop-close=\"true\" class=\"badge-category" + (restricted ? ' restricted' : '' ) + - (opts.onlyStripe ? ' clear-badge' : '') + - extraClasses + "\" "; - name = Handlebars.Utils.escapeExpression(name); - - // Add description if we have it, without tags. Server has sanitized the description value. - if (description) html += "title=\"" + Handlebars.Utils.escapeExpression(description) + "\" "; - - if (!opts.onlyStripe) { - categoryStyle = Discourse.HTML.categoryStyle(category); - if (categoryStyle) { - html += "style=\"" + categoryStyle + "\" "; - } - } - - if (restricted) { - html += ">
    " + name + "
    "; - } else { - html += ">" + name + ""; - } - - if (opts.onlyStripe || (opts.showParent && category.get('parent_category_id'))) { - var parent = Discourse.Category.findById(category.get('parent_category_id')); - if (!parent) { parent = category; } - - categoryStyle = Discourse.HTML.categoryStyle(opts.onlyStripe ? category : parent) || ''; - html = "<" + elem + " class='badge-category-parent" + extraClasses + "' style=\"" + categoryStyle + - "\" href=\"" + (opts.link === false ? '' : url) + "\">" + - (Em.get(parent, 'read_restricted') ? " " : "") + - Em.get(parent, 'name') + "" + - html + ""; - } - - return html; } }; diff --git a/app/assets/javascripts/discourse/models/category.js b/app/assets/javascripts/discourse/models/category.js index 3f8bb6edb6..e7bc7dd6fa 100644 --- a/app/assets/javascripts/discourse/models/category.js +++ b/app/assets/javascripts/discourse/models/category.js @@ -232,6 +232,7 @@ Discourse.Category.reopenClass({ }, findById: function(id) { + if (!id) { return; } return Discourse.Category.idMap()[id]; }, diff --git a/app/assets/javascripts/discourse/templates/category-group-autocomplete.raw.hbs b/app/assets/javascripts/discourse/templates/category-group-autocomplete.raw.hbs index 5a3ec802d7..45533d2d7a 100644 --- a/app/assets/javascripts/discourse/templates/category-group-autocomplete.raw.hbs +++ b/app/assets/javascripts/discourse/templates/category-group-autocomplete.raw.hbs @@ -1,7 +1,7 @@
      {{#each option in options}} -
    • {{category-link option allowUncategorized="true"}}
    • +
    • {{category-link option allowUncategorized="true" link="false"}}
    • {{/each}}
    diff --git a/app/assets/javascripts/discourse/templates/components/category-drop.hbs b/app/assets/javascripts/discourse/templates/components/category-drop.hbs index 2f6481e77f..02a184e34b 100644 --- a/app/assets/javascripts/discourse/templates/components/category-drop.hbs +++ b/app/assets/javascripts/discourse/templates/components/category-drop.hbs @@ -1,27 +1,27 @@ {{#if category}} - + {{#if category.read_restricted}} - + {{fa-icon "lock"}} {{/if}} {{category.name}} {{else}} {{#if noSubcategories}} - {{i18n 'categories.no_subcategory'}} + {{i18n 'categories.no_subcategory'}} {{else}} - {{allCategoriesLabel}} + {{allCategoriesLabel}} {{/if}} {{/if}} {{#if categories}} - +
    {{#if subCategory}} - + {{/if}} {{#if renderCategories}} - {{#each c in categories}}
    {{category-link c allowUncategorized=true}}
    {{/each}} + {{#each c in categories}}
    {{category-link c allowUncategorized=true hideParent=subCategory}}
    {{/each}} {{/if}}
    {{/if}} diff --git a/app/assets/javascripts/discourse/templates/discovery/categories.hbs b/app/assets/javascripts/discourse/templates/discovery/categories.hbs index c3055c93b6..192cc7a77d 100644 --- a/app/assets/javascripts/discourse/templates/discovery/categories.hbs +++ b/app/assets/javascripts/discourse/templates/discovery/categories.hbs @@ -32,7 +32,7 @@ {{#if c.subcategories}}
    {{#each s in c.subcategories}} - {{category-link s showParent="true" onlyStripe="true"}} + {{category-link s hideParent="true"}} {{#if s.unreadTopics}} {{unbound s.unreadTopics}} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/header.hbs b/app/assets/javascripts/discourse/templates/header.hbs index 0cffe50fef..24aa48c715 100644 --- a/app/assets/javascripts/discourse/templates/header.hbs +++ b/app/assets/javascripts/discourse/templates/header.hbs @@ -113,9 +113,9 @@ {{/if}} {{#if topic.category.parentCategory}} - {{bound-category-link topic.category.parentCategory onlyStripe="true"}} + {{bound-category-link topic.category.parentCategory}} {{/if}} - {{bound-category-link topic.category onlyStripe="true"}} + {{bound-category-link topic.category hideParent=true}}
    diff --git a/app/assets/javascripts/discourse/templates/list/category-column.raw.hbs b/app/assets/javascripts/discourse/templates/list/category-column.raw.hbs index 614f502ba2..10c175aac6 100644 --- a/app/assets/javascripts/discourse/templates/list/category-column.raw.hbs +++ b/app/assets/javascripts/discourse/templates/list/category-column.raw.hbs @@ -1 +1 @@ -{{category-link category showParent="true"}} +{{category-link category}} diff --git a/app/assets/javascripts/discourse/templates/mobile/components/basic-topic-list.hbs b/app/assets/javascripts/discourse/templates/mobile/components/basic-topic-list.hbs index 8f04d3a242..99aa9d66bf 100644 --- a/app/assets/javascripts/discourse/templates/mobile/components/basic-topic-list.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/components/basic-topic-list.hbs @@ -32,7 +32,7 @@ {{#unless controller.hideCategory}}
    - {{category-link t.category showParent="true"}} + {{category-link t.category}}
    {{/unless}} {{#if controller.showParticipants}} diff --git a/app/assets/javascripts/discourse/templates/mobile/discovery/categories.hbs b/app/assets/javascripts/discourse/templates/mobile/discovery/categories.hbs index 2256ea394f..cbee5af14d 100644 --- a/app/assets/javascripts/discourse/templates/mobile/discovery/categories.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/discovery/categories.hbs @@ -46,7 +46,7 @@
    {{#each subcategory in c.subcategories}} - {{category-link subcategory showParent="true"}} + {{category-link subcategory}} {{/each}}
    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 6f8fbbe5b8..f52e48ba43 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 @@ -11,7 +11,7 @@
    {{#unless controller.hideCategory}}
    - {{category-link content.category showParent="true"}} + {{category-link content.category}}
    {{/unless}} diff --git a/app/assets/javascripts/discourse/templates/modal/edit-category-general.hbs b/app/assets/javascripts/discourse/templates/modal/edit-category-general.hbs index 7931c5ef57..2722479ed3 100644 --- a/app/assets/javascripts/discourse/templates/modal/edit-category-general.hbs +++ b/app/assets/javascripts/discourse/templates/modal/edit-category-general.hbs @@ -15,7 +15,7 @@ {{#if subCategories}} {{#each s in subCategories}} - {{category-badge s}} + {{category-badge s hideParent="true"}} {{/each}} {{else}} diff --git a/app/assets/javascripts/discourse/templates/site-map.hbs b/app/assets/javascripts/discourse/templates/site-map.hbs index 99c1e2c433..a1efed37ab 100644 --- a/app/assets/javascripts/discourse/templates/site-map.hbs +++ b/app/assets/javascripts/discourse/templates/site-map.hbs @@ -46,7 +46,7 @@ {{#each c in categories itemController='site-map-category'}}
  • - {{category-link c allowUncategorized="true" showParent="true"}} + {{category-link c allowUncategorized="true"}} {{#if c.unreadTotal}} {{c.unreadTotal}} diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs index 8af8ac1953..878fa421e3 100644 --- a/app/assets/javascripts/discourse/templates/topic.hbs +++ b/app/assets/javascripts/discourse/templates/topic.hbs @@ -46,7 +46,7 @@ {{#if category.parentCategory}} {{bound-category-link category.parentCategory}} {{/if}} - {{bound-category-link category}} + {{bound-category-link category hideParent=true}} {{plugin-outlet "topic-category"}} {{/unless}} {{/if}} diff --git a/app/assets/javascripts/discourse/views/category-chooser.js.es6 b/app/assets/javascripts/discourse/views/category-chooser.js.es6 index 46a82d0128..f0e39a8a1d 100644 --- a/app/assets/javascripts/discourse/views/category-chooser.js.es6 +++ b/app/assets/javascripts/discourse/views/category-chooser.js.es6 @@ -1,6 +1,5 @@ import ComboboxView from 'discourse/views/combo-box'; - -var badgeHtml = Discourse.HTML.categoryBadge; +import { categoryBadgeHTML } from 'discourse/helpers/category-link'; export default ComboboxView.extend({ classNames: ['combobox category-combobox'], @@ -57,10 +56,10 @@ export default ComboboxView.extend({ } if (!category) return item.text; - var result = badgeHtml(category, {showParent: false, link: false, allowUncategorized: true}), + var result = categoryBadgeHTML(category, {link: false, allowUncategorized: true, hideParent: true}), parentCategoryId = category.get('parent_category_id'); if (parentCategoryId) { - result = badgeHtml(Discourse.Category.findById(parentCategoryId), {link: false}) + " " + result; + result = categoryBadgeHTML(Discourse.Category.findById(parentCategoryId), {link: false}) + " " + result; } result += " × " + category.get('topic_count') + ""; diff --git a/app/assets/javascripts/discourse/views/topic.js.es6 b/app/assets/javascripts/discourse/views/topic.js.es6 index 66e73adb49..f2213de9a1 100644 --- a/app/assets/javascripts/discourse/views/topic.js.es6 +++ b/app/assets/javascripts/discourse/views/topic.js.es6 @@ -1,5 +1,6 @@ import AddCategoryClass from 'discourse/mixins/add-category-class'; import { listenForViewEvent } from 'discourse/lib/app-events'; +import { categoryBadgeHTML } from 'discourse/helpers/category-link'; var TopicView = Discourse.View.extend(AddCategoryClass, Discourse.Scrolling, { templateName: 'topic', @@ -130,7 +131,7 @@ var TopicView = Discourse.View.extend(AddCategoryClass, Discourse.Scrolling, { } if (category) { - opts.catLink = Discourse.HTML.categoryBadge(category, {showParent: true}); + opts.catLink = categoryBadgeHTML(category); } else { opts.catLink = "" + I18n.t("topic.browse_all_categories") + ""; } diff --git a/app/assets/stylesheets/common/base/_topic-list.scss b/app/assets/stylesheets/common/base/_topic-list.scss index 751adbada0..54a6fd1b43 100644 --- a/app/assets/stylesheets/common/base/_topic-list.scss +++ b/app/assets/stylesheets/common/base/_topic-list.scss @@ -154,9 +154,9 @@ } .list-controls { - .home { - background-color: scale-color-diff(); + .category-dropdown-menu .home { color: $primary; + margin-left: 8px; } .badge-category { padding: 4px 10px; @@ -164,6 +164,10 @@ line-height: 24px; float: left; } + + .category-dropdown-menu .badge-category { + width: 100%; + } .category-dropdown-button { border-left: 1px solid rgba(0,0,0,0.15); font-size: 1.143em; @@ -223,18 +227,21 @@ ol.category-breadcrumb { background-color: $secondary; z-index: 100; + .cat { + margin-right: 20px; + } + + a.badge-category, a.badge-category-parent { + line-height: 19px; + overflow:hidden; + margin-bottom: 0; + } a.badge-category { font-size: 0.929em; font-weight: bold; float: none; - line-height: 19px; text-transform: none; - width: 100%; - min-width: 102px; - margin-right: 20px; - margin-bottom: 0; max-width:200px; - overflow:hidden; text-overflow:ellipsis; } } diff --git a/app/assets/stylesheets/common/components/badges.css.scss b/app/assets/stylesheets/common/components/badges.css.scss index 7549e5f563..0765de22a0 100644 --- a/app/assets/stylesheets/common/components/badges.css.scss +++ b/app/assets/stylesheets/common/components/badges.css.scss @@ -28,9 +28,9 @@ .badge-category { padding: 6px; - color: $secondary; + color: $primary; &[href] { - color: $secondary; + color: $primary; } } diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss index 41e6923ab2..577612b09c 100644 --- a/app/assets/stylesheets/desktop/user.scss +++ b/app/assets/stylesheets/desktop/user.scss @@ -402,6 +402,9 @@ .user-stream { + .category { + margin-left: 3px; + } .excerpt { margin: 5px 0; font-size: 0.929em; diff --git a/app/helpers/user_notifications_helper.rb b/app/helpers/user_notifications_helper.rb index cb662043d5..c96da2c370 100644 --- a/app/helpers/user_notifications_helper.rb +++ b/app/helpers/user_notifications_helper.rb @@ -71,15 +71,8 @@ module UserNotificationsHelper category_url = "#{Discourse.base_url}#{category.url}" - if opts[:only_stripe] - result << " " - result << "#{category.name}" - else - if category.parent_category.present? - result << " " - end - result << "#{category.name}" - end + result << " " + result << "#{category.name}" result.html_safe end diff --git a/app/views/user_notifications/digest.html.erb b/app/views/user_notifications/digest.html.erb index 8545f198e6..833874de11 100644 --- a/app/views/user_notifications/digest.html.erb +++ b/app/views/user_notifications/digest.html.erb @@ -45,7 +45,7 @@
  • <%= link_to t.title, "#{Discourse.base_url}#{t.relative_url}" %> <%= t('user_notifications.digest.posts', count: t.posts_count) %> - <%= email_category(t.category, only_stripe: true) %> + <%= email_category(t.category) %>
  • <%- end -%> diff --git a/test/javascripts/lib/category-badge-test.js.es6 b/test/javascripts/lib/category-badge-test.js.es6 new file mode 100644 index 0000000000..22d76769a4 --- /dev/null +++ b/test/javascripts/lib/category-badge-test.js.es6 @@ -0,0 +1,41 @@ +module("categoryBadgeHTML"); + +import { categoryBadgeHTML } from "discourse/helpers/category-link"; + +test("categoryBadge without a category", function() { + blank(categoryBadgeHTML(), "it returns no HTML"); +}); + +test("Regular categoryBadge", function() { + var category = Discourse.Category.create({ + name: 'hello', + id: 123, + description_text: 'cool description', + color: 'ff0', + text_color: 'f00' + }), + tag = parseHTML(categoryBadgeHTML(category))[0]; + + equal(tag.name, 'span', 'it creates a `span` wrapper tag'); + equal(tag.attributes['class'], 'badge-wrapper', 'it has the correct class'); + + var label = tag.children[1]; + equal(label.attributes.title, 'cool description', 'it has the correct title'); + + equal(label.children[0].data, 'hello', 'it has the category name'); +}); + +test("undefined color", function() { + var noColor = Discourse.Category.create({ name: 'hello', id: 123 }), + tag = parseHTML(categoryBadgeHTML(noColor))[0]; + + blank(tag.attributes.style, "it has no color style because there are no colors"); +}); + +test("allowUncategorized", function() { + var uncategorized = Discourse.Category.create({name: 'uncategorized', id: 345}); + sandbox.stub(Discourse.Site, 'currentProp').withArgs('uncategorized_category_id').returns(345); + + blank(categoryBadgeHTML(uncategorized), "it doesn't return HTML for uncategorized by default"); + present(categoryBadgeHTML(uncategorized, {allowUncategorized: true}), "it returns HTML"); +}); diff --git a/test/javascripts/lib/html-test.js.es6 b/test/javascripts/lib/html-test.js.es6 index 57a4328f37..07ef2906a1 100644 --- a/test/javascripts/lib/html-test.js.es6 +++ b/test/javascripts/lib/html-test.js.es6 @@ -2,46 +2,6 @@ module("Discourse.HTML"); var html = Discourse.HTML; -test("categoryBadge without a category", function() { - blank(html.categoryBadge(), "it returns no HTML"); -}); - -test("Regular categoryBadge", function() { - var category = Discourse.Category.create({ - name: 'hello', - id: 123, - description_text: 'cool description', - color: 'ff0', - text_color: 'f00' - }), - tag = parseHTML(html.categoryBadge(category))[0]; - - equal(tag.name, 'a', 'it creates an `a` tag'); - equal(tag.attributes['class'], 'badge-category', 'it has the correct class'); - equal(tag.attributes.title, 'cool description', 'it has the correct title'); - - ok(tag.attributes.style.indexOf('#ff0') !== -1, "it has the color style"); - ok(tag.attributes.style.indexOf('#f00') !== -1, "it has the textColor style"); - - equal(tag.children[0].data, 'hello', 'it has the category name'); -}); - -test("undefined color", function() { - var noColor = Discourse.Category.create({ name: 'hello', id: 123 }), - tag = parseHTML(html.categoryBadge(noColor))[0]; - - blank(tag.attributes.style, "it has no color style because there are no colors"); -}); - -test("allowUncategorized", function() { - var uncategorized = Discourse.Category.create({name: 'uncategorized', id: 345}); - sandbox.stub(Discourse.Site, 'currentProp').withArgs('uncategorized_category_id').returns(345); - - blank(html.categoryBadge(uncategorized), "it doesn't return HTML for uncategorized by default"); - present(html.categoryBadge(uncategorized, {allowUncategorized: true}), "it returns HTML"); -}); - - test("customHTML", function() { blank(html.getCustomHTML('evil'), "there is no custom HTML for a key by default"); From 60523d8e02de7b56113d4ef5090fe253ecadccd8 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 20 Jan 2015 16:07:13 -0500 Subject: [PATCH 062/230] Convert html.js to ES6 module format --- .../discourse/helpers/custom-html.js.es6 | 4 ++- app/assets/javascripts/discourse/lib/html.js | 28 ---------------- .../javascripts/discourse/lib/html.js.es6 | 32 +++++++++++++++++++ test/javascripts/lib/html-test.js.es6 | 11 +++---- 4 files changed, 40 insertions(+), 35 deletions(-) delete mode 100644 app/assets/javascripts/discourse/lib/html.js create mode 100644 app/assets/javascripts/discourse/lib/html.js.es6 diff --git a/app/assets/javascripts/discourse/helpers/custom-html.js.es6 b/app/assets/javascripts/discourse/helpers/custom-html.js.es6 index 42155096de..1728963bf3 100644 --- a/app/assets/javascripts/discourse/helpers/custom-html.js.es6 +++ b/app/assets/javascripts/discourse/helpers/custom-html.js.es6 @@ -1,5 +1,7 @@ +import { getCustomHTML } from 'discourse/lib/html'; + Handlebars.registerHelper('custom-html', function(name, contextString, options) { - var html = Discourse.HTML.getCustomHTML(name); + var html = getCustomHTML(name); if (html) { return html; } var container = (options || contextString).data.view.container; diff --git a/app/assets/javascripts/discourse/lib/html.js b/app/assets/javascripts/discourse/lib/html.js deleted file mode 100644 index ccb807d6a8..0000000000 --- a/app/assets/javascripts/discourse/lib/html.js +++ /dev/null @@ -1,28 +0,0 @@ -var customizations = {}; - -Discourse.HTML = { - - /** - Return a custom fragment of HTML by key. It can be registered via a plugin - using `setCustomHTML(key, html)`. This is used by a handlebars helper to find - the HTML content it wants. It will also check the `PreloadStore` for any server - side preloaded HTML. - **/ - getCustomHTML: function(key) { - var c = customizations[key]; - if (c) { - return new Handlebars.SafeString(c); - } - - var html = PreloadStore.get("customHTML"); - if (html && html[key] && html[key].length) { - return new Handlebars.SafeString(html[key]); - } - }, - - // Set a fragment of HTML by key. It can then be looked up with `getCustomHTML(key)`. - setCustomHTML: function(key, html) { - customizations[key] = html; - } - -}; diff --git a/app/assets/javascripts/discourse/lib/html.js.es6 b/app/assets/javascripts/discourse/lib/html.js.es6 new file mode 100644 index 0000000000..f839b5ea52 --- /dev/null +++ b/app/assets/javascripts/discourse/lib/html.js.es6 @@ -0,0 +1,32 @@ +var _customizations = {}; + +/** + Return a custom fragment of HTML by key. It can be registered via a plugin + using `setCustomHTML(key, html)`. This is used by a handlebars helper to find + the HTML content it wants. It will also check the `PreloadStore` for any server + side preloaded HTML. +**/ +export function getCustomHTML(key) { + var c = _customizations[key]; + if (c) { + return new Handlebars.SafeString(c); + } + + var html = PreloadStore.get("customHTML"); + if (html && html[key] && html[key].length) { + return new Handlebars.SafeString(html[key]); + } +} + +// Set a fragment of HTML by key. It can then be looked up with `getCustomHTML(key)`. +export function setCustomHTML(key, html) { + _customizations[key] = html; +} + +var HTML = { + getCustomHTML: getCustomHTML, + setCustomHTML: setCustomHTML +}; + +Discourse.HTML = HTML; +export default HTML; diff --git a/test/javascripts/lib/html-test.js.es6 b/test/javascripts/lib/html-test.js.es6 index 07ef2906a1..835b3f1e65 100644 --- a/test/javascripts/lib/html-test.js.es6 +++ b/test/javascripts/lib/html-test.js.es6 @@ -1,14 +1,13 @@ module("Discourse.HTML"); -var html = Discourse.HTML; +import { getCustomHTML, setCustomHTML } from 'discourse/lib/html'; test("customHTML", function() { - blank(html.getCustomHTML('evil'), "there is no custom HTML for a key by default"); + blank(getCustomHTML('evil'), "there is no custom HTML for a key by default"); - html.setCustomHTML('evil', 'trout'); - equal(html.getCustomHTML('evil'), 'trout', 'it retrieves the custom html'); + setCustomHTML('evil', 'trout'); + equal(getCustomHTML('evil'), 'trout', 'it retrieves the custom html'); PreloadStore.store('customHTML', {cookie: 'monster'}); - equal(html.getCustomHTML('cookie'), 'monster', 'it returns HTML fragments from the PreloadStore'); - + equal(getCustomHTML('cookie'), 'monster', 'it returns HTML fragments from the PreloadStore'); }); From 649dfd8d23fbed05634fe6901541df83a410ce8b Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 20 Jan 2015 16:13:42 -0500 Subject: [PATCH 063/230] Revert "Convert html.js to ES6 module format" This reverts commit 60523d8e02de7b56113d4ef5090fe253ecadccd8. --- .../discourse/helpers/custom-html.js.es6 | 4 +-- app/assets/javascripts/discourse/lib/html.js | 28 ++++++++++++++++ .../javascripts/discourse/lib/html.js.es6 | 32 ------------------- test/javascripts/lib/html-test.js.es6 | 11 ++++--- 4 files changed, 35 insertions(+), 40 deletions(-) create mode 100644 app/assets/javascripts/discourse/lib/html.js delete mode 100644 app/assets/javascripts/discourse/lib/html.js.es6 diff --git a/app/assets/javascripts/discourse/helpers/custom-html.js.es6 b/app/assets/javascripts/discourse/helpers/custom-html.js.es6 index 1728963bf3..42155096de 100644 --- a/app/assets/javascripts/discourse/helpers/custom-html.js.es6 +++ b/app/assets/javascripts/discourse/helpers/custom-html.js.es6 @@ -1,7 +1,5 @@ -import { getCustomHTML } from 'discourse/lib/html'; - Handlebars.registerHelper('custom-html', function(name, contextString, options) { - var html = getCustomHTML(name); + var html = Discourse.HTML.getCustomHTML(name); if (html) { return html; } var container = (options || contextString).data.view.container; diff --git a/app/assets/javascripts/discourse/lib/html.js b/app/assets/javascripts/discourse/lib/html.js new file mode 100644 index 0000000000..ccb807d6a8 --- /dev/null +++ b/app/assets/javascripts/discourse/lib/html.js @@ -0,0 +1,28 @@ +var customizations = {}; + +Discourse.HTML = { + + /** + Return a custom fragment of HTML by key. It can be registered via a plugin + using `setCustomHTML(key, html)`. This is used by a handlebars helper to find + the HTML content it wants. It will also check the `PreloadStore` for any server + side preloaded HTML. + **/ + getCustomHTML: function(key) { + var c = customizations[key]; + if (c) { + return new Handlebars.SafeString(c); + } + + var html = PreloadStore.get("customHTML"); + if (html && html[key] && html[key].length) { + return new Handlebars.SafeString(html[key]); + } + }, + + // Set a fragment of HTML by key. It can then be looked up with `getCustomHTML(key)`. + setCustomHTML: function(key, html) { + customizations[key] = html; + } + +}; diff --git a/app/assets/javascripts/discourse/lib/html.js.es6 b/app/assets/javascripts/discourse/lib/html.js.es6 deleted file mode 100644 index f839b5ea52..0000000000 --- a/app/assets/javascripts/discourse/lib/html.js.es6 +++ /dev/null @@ -1,32 +0,0 @@ -var _customizations = {}; - -/** - Return a custom fragment of HTML by key. It can be registered via a plugin - using `setCustomHTML(key, html)`. This is used by a handlebars helper to find - the HTML content it wants. It will also check the `PreloadStore` for any server - side preloaded HTML. -**/ -export function getCustomHTML(key) { - var c = _customizations[key]; - if (c) { - return new Handlebars.SafeString(c); - } - - var html = PreloadStore.get("customHTML"); - if (html && html[key] && html[key].length) { - return new Handlebars.SafeString(html[key]); - } -} - -// Set a fragment of HTML by key. It can then be looked up with `getCustomHTML(key)`. -export function setCustomHTML(key, html) { - _customizations[key] = html; -} - -var HTML = { - getCustomHTML: getCustomHTML, - setCustomHTML: setCustomHTML -}; - -Discourse.HTML = HTML; -export default HTML; diff --git a/test/javascripts/lib/html-test.js.es6 b/test/javascripts/lib/html-test.js.es6 index 835b3f1e65..07ef2906a1 100644 --- a/test/javascripts/lib/html-test.js.es6 +++ b/test/javascripts/lib/html-test.js.es6 @@ -1,13 +1,14 @@ module("Discourse.HTML"); -import { getCustomHTML, setCustomHTML } from 'discourse/lib/html'; +var html = Discourse.HTML; test("customHTML", function() { - blank(getCustomHTML('evil'), "there is no custom HTML for a key by default"); + blank(html.getCustomHTML('evil'), "there is no custom HTML for a key by default"); - setCustomHTML('evil', 'trout'); - equal(getCustomHTML('evil'), 'trout', 'it retrieves the custom html'); + html.setCustomHTML('evil', 'trout'); + equal(html.getCustomHTML('evil'), 'trout', 'it retrieves the custom html'); PreloadStore.store('customHTML', {cookie: 'monster'}); - equal(getCustomHTML('cookie'), 'monster', 'it returns HTML fragments from the PreloadStore'); + equal(html.getCustomHTML('cookie'), 'monster', 'it returns HTML fragments from the PreloadStore'); + }); From 7cc96bdac5f73ee9eb0b67d9278ad5f43c59b5bf Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 20 Jan 2015 17:29:59 -0500 Subject: [PATCH 064/230] FIX: v8 issue with regexp --- vendor/assets/javascripts/better_markdown.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vendor/assets/javascripts/better_markdown.js b/vendor/assets/javascripts/better_markdown.js index f01451ec28..7a9e74b8c2 100644 --- a/vendor/assets/javascripts/better_markdown.js +++ b/vendor/assets/javascripts/better_markdown.js @@ -825,7 +825,6 @@ // TODO: Cache this regexp for certain depths. // Create a regexp suitable for matching an li for a given stack depth function regex_for_depth( depth ) { - return new RegExp( // m[1] = indent, m[2] = list_type "(?:^(" + indent_re + "{0," + depth + "} {0,3})(" + any_list + ")\\s+)|" + @@ -871,8 +870,11 @@ replace = new RegExp("^" + indent_re + "{" + depth + "}", "gm"), ret = []; + while ( blocks.length > 0 ) { - if ( re.exec( blocks[0] ) ) { + // HACK: Fixes a v8 issue + test = blocks[0].replace(/^ {8,}/, ' '); + if ( re.exec( test ) ) { var b = blocks.shift(), // Now remove that indent x = b.replace( replace, ""); From 614ad4daa7afedbff55b99e9cf1b5704bebcea7b Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 20 Jan 2015 17:51:12 -0500 Subject: [PATCH 065/230] UX: Formatting on search categories looked weird --- .../stylesheets/common/base/header.scss | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss index cc44112250..e5392f2cd0 100644 --- a/app/assets/stylesheets/common/base/header.scss +++ b/app/assets/stylesheets/common/base/header.scss @@ -315,19 +315,23 @@ } } -.search-link .topic-statuses { - float: none; - display: inline-block; - color: scale-color($primary, $lightness: 50%); - margin: 0; - .fa { - margin: 0; +.search-link { + .badge-category-parent { + line-height: 0.8em; + } + .topic-title { + margin-right: 6px; } -} -.search-link .badge-category { - padding: 4px 6px; - margin-left: 6px; + .topic-statuses { + float: none; + display: inline-block; + color: scale-color($primary, $lightness: 50%); + margin: 0; + .fa { + margin: 0; + } + } } .highlight-strong { From aa423212a084d42632922238d35c8a73c032c743 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Tue, 20 Jan 2015 16:39:11 -0800 Subject: [PATCH 066/230] make topic list bookmark color consistent with lock --- app/assets/stylesheets/desktop/topic-list.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/desktop/topic-list.scss b/app/assets/stylesheets/desktop/topic-list.scss index fe40b00de1..1ee67007fd 100644 --- a/app/assets/stylesheets/desktop/topic-list.scss +++ b/app/assets/stylesheets/desktop/topic-list.scss @@ -45,7 +45,7 @@ .fa-thumb-tack {color: $primary;} .fa-thumb-tack.unpinned {color: $primary;} a.title {color: $primary;} - a.title:visited:not(.badge-notification), .fa-bookmark {color: scale-color($primary, $lightness: 35%);} + a.title:visited:not(.badge-notification), .fa-bookmark {color: scale-color($primary, $lightness: 50%);} th, td { padding: 12px 5px; From c4ddc5b983f5a10629a09ea3892333305178032a Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Tue, 20 Jan 2015 17:28:06 -0800 Subject: [PATCH 067/230] quick mobile css fix for post selection buttons --- app/assets/stylesheets/mobile/topic-post.scss | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/assets/stylesheets/mobile/topic-post.scss b/app/assets/stylesheets/mobile/topic-post.scss index 457aac87f8..d46476ce85 100644 --- a/app/assets/stylesheets/mobile/topic-post.scss +++ b/app/assets/stylesheets/mobile/topic-post.scss @@ -476,3 +476,16 @@ span.highlighted { .read-state { display: none; } + +// hide the full set of selection buttons on mobile +.select-posts button { display: none; } + +// unhide the simple "select just this post" button +button.select-post { + display: inline-block; + position: absolute; + z-index: 401; // 400 is the reply-to tab + left: 200px; + background-color: scale-color($tertiary, $lightness: 50%); + color: $secondary; +} From e5cad726fd66a57eb60377f1104a7c46d5ee216f Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Tue, 20 Jan 2015 22:51:15 -0800 Subject: [PATCH 068/230] further tweaks to get post selection OK on mobile --- app/assets/stylesheets/mobile/topic-post.scss | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/app/assets/stylesheets/mobile/topic-post.scss b/app/assets/stylesheets/mobile/topic-post.scss index d46476ce85..56111455bd 100644 --- a/app/assets/stylesheets/mobile/topic-post.scss +++ b/app/assets/stylesheets/mobile/topic-post.scss @@ -384,12 +384,36 @@ iframe { } #selected-posts { - padding-left: 20px; + float: left; + width: 97%; + padding-left: 3%; + background-color: dark-light-diff($tertiary, $secondary, 90%, -40%); .btn { margin-bottom: 10px; + color: $secondary; + background: scale-color($tertiary, $lightness: 50%); + clear: both; + } + p { + clear: both; } } +// hide the full set of selection buttons on mobile +.select-posts button { display: none; } + +// unhide the simple "select just this post" button +button.select-post { + display: inline-block; + position: absolute; + z-index: 401; // 400 is the reply-to tab + left: 200px; + background-color: scale-color($tertiary, $lightness: 50%); + color: $secondary; + padding: 5px; +} + + .post-select { float: right; margin-right: 20px; @@ -477,15 +501,3 @@ span.highlighted { display: none; } -// hide the full set of selection buttons on mobile -.select-posts button { display: none; } - -// unhide the simple "select just this post" button -button.select-post { - display: inline-block; - position: absolute; - z-index: 401; // 400 is the reply-to tab - left: 200px; - background-color: scale-color($tertiary, $lightness: 50%); - color: $secondary; -} From eecc573fbc45d472ca3c5d3168cf6b1c7764f40e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 21 Jan 2015 09:36:46 +0100 Subject: [PATCH 069/230] FIX: don't break import when raw can't be preprocessed (vBulletin importer) --- script/import_scripts/vbulletin.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/script/import_scripts/vbulletin.rb b/script/import_scripts/vbulletin.rb index dda4f29671..5b755156d7 100644 --- a/script/import_scripts/vbulletin.rb +++ b/script/import_scripts/vbulletin.rb @@ -210,6 +210,8 @@ class ImportScripts::VBulletin < ImportScripts::Base break if topics.size < 1 create_posts(topics, total: topic_count, offset: offset) do |topic| + raw = preprocess_post_raw(topic["raw"]) rescue nil + next if raw.blank? topic_id = "thread-#{topic["threadid"]}" @closed_topic_ids << topic_id if topic["open"] == "0" t = { @@ -217,7 +219,7 @@ class ImportScripts::VBulletin < ImportScripts::Base user_id: user_id_from_imported_user_id(topic["postuserid"]) || Discourse::SYSTEM_USER_ID, title: @htmlentities.decode(topic["title"]).strip[0...255], category: category_from_imported_category_id(topic["forumid"]).try(:name), - raw: preprocess_post_raw(topic["raw"]), + raw: raw, created_at: parse_timestamp(topic["dateline"]), visible: topic["visible"].to_i == 1, views: topic["views"], @@ -231,6 +233,9 @@ class ImportScripts::VBulletin < ImportScripts::Base def import_posts puts "", "importing posts..." + # make sure `firstpostid` is indexed + mysql_query("CREATE INDEX firstpostid_index ON thread (firstpostid)") + post_count = mysql_query("SELECT COUNT(postid) count FROM post WHERE postid NOT IN (SELECT firstpostid FROM thread)").first["count"] batches(BATCH_SIZE) do |offset| @@ -246,12 +251,14 @@ class ImportScripts::VBulletin < ImportScripts::Base break if posts.size < 1 create_posts(posts, total: post_count, offset: offset) do |post| + raw = preprocess_post_raw(post["raw"]) rescue nil + next if raw.blank? next unless topic = topic_lookup_from_imported_post_id("thread-#{post["threadid"]}") p = { id: post["postid"], user_id: user_id_from_imported_user_id(post["userid"]) || Discourse::SYSTEM_USER_ID, topic_id: topic[:topic_id], - raw: preprocess_post_raw(post["raw"]), + raw: raw, created_at: parse_timestamp(post["dateline"]), hidden: post["visible"].to_i == 0, } From 1ed34be1b98ca4982dc58f7c2054b457908f1412 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Wed, 21 Jan 2015 03:15:51 -0800 Subject: [PATCH 070/230] make topic list pin color consistent with lock --- app/assets/stylesheets/desktop/topic-list.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/desktop/topic-list.scss b/app/assets/stylesheets/desktop/topic-list.scss index 1ee67007fd..f51952b2e3 100644 --- a/app/assets/stylesheets/desktop/topic-list.scss +++ b/app/assets/stylesheets/desktop/topic-list.scss @@ -42,10 +42,10 @@ .topic-list { margin: 0 0 10px; - .fa-thumb-tack {color: $primary;} - .fa-thumb-tack.unpinned {color: $primary;} + .fa-thumb-tack { color: scale-color($primary, $lightness: 50%); } + .fa-thumb-tack.unpinned { color: scale-color($primary, $lightness: 50%); } a.title {color: $primary;} - a.title:visited:not(.badge-notification), .fa-bookmark {color: scale-color($primary, $lightness: 50%);} + a.title:visited:not(.badge-notification), .fa-bookmark { color: scale-color($primary, $lightness: 50%); } th, td { padding: 12px 5px; From df8880a71a20e2d845934b2ab6e8515b3808eeb2 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 21 Jan 2015 11:52:59 -0500 Subject: [PATCH 071/230] FIX: (for IE9) if `console.log` doesn't exist, make it an noop. --- .../javascripts/discourse/initializers/ie9-hax.js.es6 | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 app/assets/javascripts/discourse/initializers/ie9-hax.js.es6 diff --git a/app/assets/javascripts/discourse/initializers/ie9-hax.js.es6 b/app/assets/javascripts/discourse/initializers/ie9-hax.js.es6 new file mode 100644 index 0000000000..4c124aac05 --- /dev/null +++ b/app/assets/javascripts/discourse/initializers/ie9-hax.js.es6 @@ -0,0 +1,10 @@ +export default { + name: 'ie9-hacks', + initialize: function() { + if (!window) { return; } + + // IE9 does not support a console object unless the developer tools are open + if (!window.console) { window.console = {}; } + if (!window.console.log) { window.console.log = Ember.K; } + } +}; From 48c7e20d80d8b8f7fcdecb2301b3f1860f772fc9 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 21 Jan 2015 12:20:39 -0500 Subject: [PATCH 072/230] DRY up the displaying of topic categories. Should not be duplicated. --- .../discourse/templates/components/topic-category.hbs | 6 ++++++ app/assets/javascripts/discourse/templates/header.hbs | 5 +---- app/assets/javascripts/discourse/templates/topic.hbs | 6 +----- 3 files changed, 8 insertions(+), 9 deletions(-) create mode 100644 app/assets/javascripts/discourse/templates/components/topic-category.hbs diff --git a/app/assets/javascripts/discourse/templates/components/topic-category.hbs b/app/assets/javascripts/discourse/templates/components/topic-category.hbs new file mode 100644 index 0000000000..8a914440e0 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/topic-category.hbs @@ -0,0 +1,6 @@ +{{#if topic.category.parentCategory}} + {{bound-category-link topic.category.parentCategory}} +{{/if}} +{{bound-category-link topic.category hideParent=true}} + +{{plugin-outlet "topic-category"}} diff --git a/app/assets/javascripts/discourse/templates/header.hbs b/app/assets/javascripts/discourse/templates/header.hbs index 24aa48c715..85056267fb 100644 --- a/app/assets/javascripts/discourse/templates/header.hbs +++ b/app/assets/javascripts/discourse/templates/header.hbs @@ -112,10 +112,7 @@ {{/if}} {{/if}} - {{#if topic.category.parentCategory}} - {{bound-category-link topic.category.parentCategory}} - {{/if}} - {{bound-category-link topic.category hideParent=true}} + {{topic-category topic=topic}}
    diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs index 878fa421e3..f1c0e22c4e 100644 --- a/app/assets/javascripts/discourse/templates/topic.hbs +++ b/app/assets/javascripts/discourse/templates/topic.hbs @@ -43,11 +43,7 @@ {{#unless isPrivateMessage}} - {{#if category.parentCategory}} - {{bound-category-link category.parentCategory}} - {{/if}} - {{bound-category-link category hideParent=true}} - {{plugin-outlet "topic-category"}} + {{topic-category topic=model}} {{/unless}} {{/if}} From a144e614408614898c4c5fa645cc831144f206ed Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Wed, 21 Jan 2015 22:58:30 +0530 Subject: [PATCH 073/230] Update Translations --- config/locales/client.ar.yml | 32 ++++- config/locales/client.cs.yml | 45 +++++++ config/locales/client.da.yml | 64 ++++++++++ config/locales/client.fi.yml | 61 ++++++++++ config/locales/client.fr.yml | 157 +++++++++++++----------- config/locales/client.it.yml | 10 ++ config/locales/client.ko.yml | 48 ++++++++ config/locales/client.nb_NO.yml | 29 ++++- config/locales/client.nl.yml | 31 +++++ config/locales/client.ru.yml | 50 +++++++- config/locales/client.tr_TR.yml | 20 ++++ config/locales/client.zh_CN.yml | 27 ++++- config/locales/client.zh_TW.yml | 33 ++++++ config/locales/server.cs.yml | 2 +- config/locales/server.de.yml | 18 ++- config/locales/server.fi.yml | 117 ++++++++++++++++++ config/locales/server.fr.yml | 203 +++++++++++++++++++++++++------- config/locales/server.it.yml | 4 + config/locales/server.ru.yml | 3 + config/locales/server.tr_TR.yml | 21 ++++ config/locales/server.zh_CN.yml | 14 +++ 21 files changed, 861 insertions(+), 128 deletions(-) diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index 9b2de570c8..f084964740 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -24,11 +24,11 @@ ar: mb: ميجا tb: تيرا dates: - time: "سا:دد ف" - long_no_year: "ش ش ش ي سا:دد ف" - long_no_year_no_time: "ش ش ش ي" - long_with_year: "ش ش ش ي, س س س س سا:دد ف" - long_with_year_no_time: "ش ش ش ي, س س س س" + time: "h:mm a" + long_no_year: "MMM D h:mm a" + long_no_year_no_time: "MMM D" + long_with_year: "MMM D, YYYY h:mm a" + long_with_year_no_time: "MMM D, YYYY" long_date_with_year: "ش ش ش ي, س س س س" long_date_without_year: "ش ش ش ي" long_date_with_year_without_time: "ش ش ش ي, س س س س" @@ -43,6 +43,13 @@ ar: few: "> %{count}ث" many: "> %{count}ث" other: "> %{count}ث" + x_seconds: + zero: "%{count} ثانية" + one: "%{count} ثانية" + two: "%{count} ثانية" + few: "%{count} ثانية" + many: "%{count} ثانية" + other: "%{count} ثانية" date_month: "ش ش ش ي" date_year: "ش ش ش ي, س س س س" medium: @@ -112,6 +119,13 @@ ar: topic_count: "مواضيع" post_count: "مشاركات" user_count: "مستخدمين" + contact: "اتصل بنا" + contact_info: "في حالة حدوث أي مشكلة حرجة أو مسألة عاجلة تؤثر على هذا الموقع، يرجى الاتصال بنا على %{contact_email}." + bookmarked: + title: "المفضلة" + help: + bookmark: "انقر هنا لإضافة العنوان إلى المفضلة" + unbookmark: "أنقر هنا لحذف العنوان من المفضلة" bookmarks: not_logged_in: "نعتذر يجب ان تكون متصلا لكي تقوم بإضافة هدا الموضوع للمفضلة" created: "لقد نجحت في إضافة الموضوع للمفضلة" @@ -280,8 +294,16 @@ ar: uploaded_avatar_empty: "اضافة صورة " upload_title: "رفع صورتك " upload_picture: "رفع الصورة" + image_is_not_a_square: "تنبيه: تم اقتصاص جزء من الصورة ، لأنها ليست مربعة الشكل." + change_profile_background: + title: "لون خلفية الحساب" + instructions: "سيتم وضع خلفية الحساب في المنتصف بعرض 850px" + change_card_background: + instructions: "سيتم وضع الخلفية في المنتصف بعرض 590px" email: title: "بريد الكتروني" + invalid: "يرجى إدخال بريد الكتروني فعّال." + authenticated: "تم توثيق بريدك الإلكتروني بواسطة {{provider}}" name: title: "الاسم" username: diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index 9d4134cf89..590544a162 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -160,6 +160,12 @@ cs: topic_count: "Témata" post_count: "Příspěvky" user_count: "Uživatelé" + contact: "Kontaktujte nás" + bookmarked: + title: "Záložka" + help: + bookmark: "Kliknutím přidáte záložku" + unbookmark: "Kliknutím odstraníte záložku" bookmarks: not_logged_in: "Pro přidání záložky se musíte přihlásit." created: "Záložka byla přidána." @@ -351,8 +357,10 @@ cs: error: "Nastala chyba při změně emailové adresy. Není tato adresa již používaná?" success: "Na zadanou adresu jsme zaslali email. Následujte, prosím, instrukce v tomto emailu." change_avatar: + title: "Změňte si svůj profilový obrázek" gravatar: "Založeno na Gravataru" refresh_gravatar_title: "Obnovit Gravatar" + letter_based: "Systémem přidělený profilový obrázek" uploaded_avatar: "Vlastní obrázek" uploaded_avatar_empty: "Přidat vlastní obrázek" upload_title: "Nahrát obrázek" @@ -476,6 +484,8 @@ cs: title: "Poslední IP adresa" registration_ip_address: title: "Registrační IP adresa" + avatar: + title: "Profilový obrázek" title: title: "Titul" filters: @@ -1229,10 +1239,13 @@ cs: notifications: watching: title: "Hlídání" + description: "Budete automaticky sledovat všechna nová témata v těchto kategoriích. Na všechny nové příspěvky budete upozorněni. Počet nepřečtených a nových příspěvků se zobrazí vedle tématu." tracking: title: "Sledování" + description: "Všechna nová témata v této kategorii budou automaticky hlídaná. Počet nepřečtených a nových příspěvků se zobrazí vedle tématu." regular: title: "Normální" + description: "Budete informováni, pokud někdo zmíní vaše @jméno nebo odpoví na váš příspěvek." muted: title: "Ztišený" description: "Nebudete upozorněni na žádná nová témata v těchto kategoriích a ani se nebudou zobrazovat jako nepřečtené." @@ -1254,6 +1267,7 @@ cs: inappropriate: "Je to nevhodné" spam: "Je to spam" custom_placeholder_notify_user: "Buďte věcný, konstruktivní a vždy zdvořilý." + custom_placeholder_notify_moderators: "Sdělte nám, co vás přesně trápí a kde to bude možné, tak nám poskytněte související odkazy a příklady." custom_message: at_least: "zadejte alespoň {{n}} znaků" more: "ještě {{n}}..." @@ -1272,17 +1286,23 @@ cs: topic_statuses: warning: help: "Toto je oficiální varování." + bookmarked: + help: "V tématu je vložena záložka" locked: help: "toto téma je uzavřené; další odpovědi nejsou přijímány" unpinned: title: "Nepřipnuté" + 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" archived: help: "toto téma je archivováno; je zmraženo a nelze ho již měnit" + invisible: + help: "Toto téma je neviditelné; nebude se zobrazovat v seznamu témat a lze ho navštívit pouze přes přímý odkaz" posts: "Příspěvků" posts_lowercase: "příspěvky" posts_long: "v tomto tématu je {{number}} příspěvků" @@ -1500,6 +1520,7 @@ cs: delete: "Smazat" delete_confirm: "Smazat toto skupiny?" delete_failed: "Unable to delete group. If this is an automatic group, it cannot be destroyed." + delete_member_confirm: "Odstranit '%{username}' ze '%{group}' skupiny?" name: "Jméno" add: "Přidat" add_members: "Přidat členy" @@ -1568,16 +1589,25 @@ cs: title: "Rollback the database to previous working state" confirm: "Are your sure you want to rollback the database to the previous working state?" export_csv: + success: "Exportování bylo zahájeno. O dokončení budete informováni pomocí soukromé zprávy." failed: "Exportování selhalo. Prosím zkontrolujte logy." + rate_limit_error: "Příspěvky mohou být staženy jednou za den. Prosíme, zkuste to znovu zítra." button_text: "Export" button_title: user: "Exportovat kompletní seznam uživatelů v CSV formátu." + screened_email: "Exportovat kompletní seznam emailů v CSV formátu." + screened_ip: "Exportovat kompletní seznam IP adres v CSV formátu." + screened_url: "Exportovat kompletní seznam URL v CSV formátu." customize: title: "Přizpůsobení" long_title: "Přizpůsobení webu" css: "CSS" header: "header" footer: "Patička" + head_tag: + text: "" + body_tag: + text: "" override_default: "Přetížit výchozí?" enabled: "Zapnuto?" preview: "náhled" @@ -1869,6 +1899,7 @@ cs: external_username: "Uživatelské jméno" external_name: "Jméno" external_email: "Email" + external_avatar_url: "URL na profilový obrázek" user_fields: save: "Uložit" edit: "Upravit" @@ -1944,7 +1975,16 @@ cs: preview: bad_count_warning: header: "VAROVÁNÍ!" + grant_count: + zero: "Žádné odznaky k udělení." + one: "1 odznak k udělení." + other: "%{count} odznaků k udělení." sample: "Příklad:" + grant: + with: %{username} + emoji: + name: "Jméno" + image: "Obrázek" lightbox: download: "download" search_help: @@ -1965,6 +2005,7 @@ cs: back: 'u Back' up_down: 'k/j Move selection ↑ ↓' open: 'o or Enter Open selected topic' + next_prev: 'shift+j/shift+k Následující/předchozí výběr' application: title: 'Application' create: 'c Create a new topic' @@ -1978,8 +2019,12 @@ cs: dismiss_topics: 'x, t Dismiss Topics' actions: title: 'Actions' + bookmark_topic: 'f Bookmark topic' + pin_unpin_topic: 'shift+p Připnout/Odepnout téma' + share_topic: 'shift+s Sdílet téma' share_post: 's Share post' reply_as_new_topic: 't Odpovědět v propojeném tématu' + reply_topic: 'shift+r Odpovědět na téma' reply_post: 'r Odpovědět na příspěvek' quote_post: 'q Citovat příspěvek' like: 'l Like post' diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index d4b60f048f..f9ba66c803 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -84,6 +84,7 @@ da: other: "%{count} dage siden" share: topic: 'del et link til dette emne' + post: 'indlæg #%{postNumber}' close: 'luk' twitter: 'del dette link på Twitter' facebook: 'del dette link på Facebook' @@ -138,9 +139,17 @@ da: stat: all_time: "Alt" last_7_days: "De sidste 7 Dage" + like_count: "Syntes godt om" topic_count: "Emner" post_count: "Indlæg" user_count: "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}." + bookmarked: + title: "Bogmærke" + help: + bookmark: "Klik for at tilføje dette emne til bogmærker" + unbookmark: "Klik for at fjerne dette emne fra bognmærker" bookmarks: not_logged_in: "Beklager, du skal været logget ind for at bogmærke indlæg" created: "Du har bogmærket dette indlæg." @@ -245,11 +254,19 @@ da: organisation: Organisation phone: Telefon other_accounts: "Andre konti med denne IP adresse" + delete_other_accounts: "Slet %{count}" + username: "brugernavn" + read_time: "læse tid" + topics_entered: "emner indstastet" + post_count: "# indlæg" + confirm_delete_other_accounts: "Er du sikker på, at du vil slette disse kontoer?" user: said: "{{username}}:" profile: "Profil" mute: "Mute" edit: "Redigér indstillinger" + download_archive: "Download Mine Posts" + new_private_message: "Ny Private Besked" private_message: "Private beskeder" private_messages: "Beskeder" activity_stream: "Aktivitet" @@ -317,8 +334,10 @@ da: error: "Der opstod en fejl i forbindelse med skift af din e-mail-adresse. Måske er adressen allerede i brug?" success: "Vi har sendt en e-mail til din nye adresse. Klik på linket i mail’en for at aktivere din nye e-mail-adresse." change_avatar: + title: "Skift dit profilbillede" gravatar: "Gravatar, baseret på" refresh_gravatar_title: "Gendindlæs dit profil billede" + letter_based: "System tildelt profilbillede" uploaded_avatar: "Brugerdefineret profil billede" uploaded_avatar_empty: "Tilføj et brugerdefineret profil billede" upload_title: "Upload dit profil billede" @@ -327,15 +346,37 @@ da: change_profile_background: title: "Profil baggrundsbillede" instructions: "Profil baggrunde vil blive centrerede og have en standard bredde på 850 pixels" + change_card_background: + title: "Buger Kort Baggrund" + instructions: "Baggrunds billeder vil blive centreret og have en standard bredde på 590px." email: title: "E-mail" + instructions: "Aldrig vist offentligt" + ok: "Vi vil sende dig en bekræftelses email" + invalid: "Indtast venligst en gyldig email adresse" + authenticated: "Din email er blevet bekræftet af {{provider}}" + frequency: + zero: "Vi vil straks sende dig en e-mail, hvis du ikke har læst det vi sender dig en e-mail omkring." + one: "Vi vil kun sende dig en e-mail hvis vi ikke har set dig det sidste minut og su ikke har læst det vi sender dig en e-mail omkring." + other: "Vi vil kun sende dig en e-mail hvis vi ikke har set dig de sidste {{count}} minutter og su ikke har læst det vi sender dig en e-mail omkring." name: title: "Navn" + instructions: "Dit fulde navn (optimalt)" + too_short: "Dit navn er for kort" + ok: "Dit navn ser fint ud" username: title: "Brugernavn" + instructions: "Unikt, ingen mellemrum, kort" + short_instructions: "Folk kan benævne dig som @{{username}}" + available: "Dit brugernavn er utilgængeligt" + global_match: "E-mail tilsvarende det registrerede brugernavn" global_mismatch: "Allerede registreret. Prøv {{suggestion}}?" not_available: "Ikke ledigt. Prøv {{suggestion}}?" + too_short: "Dit brugernavn er for kort" + too_long: "Dit brugernavn er for langt" checking: "Kontrollerer om brugernavnet er ledigt…" + enter_email: 'Brugernavn fundet; indtast tilsvarende e-mail' + prefilled: "E-mail tilsvarende dette registrerede brugernavn" locale: title: "sprog" instructions: "Brugerinterface sprog. Det skifter når de reloader siden." @@ -348,6 +389,8 @@ da: created: "Oprettet" log_out: "Log ud" location: "Sted" + card_badge: + title: "Bruger Kort Badge" website: "Site" email_settings: "E-mail" email_digests: @@ -403,16 +446,22 @@ da: bulk_invite: none: "Du har ikke inviteret nogen her endnu. Du kan sende individuelle invitationer eller invitere en masse mennesker på én gang ved at uploade en samlet liste over invitationer." text: "Masse invitering fra en fil" + uploading: "Uploader..." + success: "Fil uploaded successfuldt, du vil blive meddelt via private beskeder når processen er fuldendt." error: "Der var en fejl ved upload af filen '{{filename}}': {{message}}" password: title: "Adgangskode" too_short: "Din adgangskode er for kort." common: "Den adgangskode er for udbredt." ok: "Din adgangskode ser fin ud." + instructions: "Mindst %{count} tegn" + associated_accounts: "Logins" ip_address: title: "Sidste IP-adresse" registration_ip_address: title: "Registrerings IP adresse" + avatar: + title: "Profil Billede" title: title: "Titel" filters: @@ -442,6 +491,8 @@ da: fixed: "Indlæs side" close: "Luk" assets_changed_confirm: "Dette site er lige blevet opdateret. Vil du opdatere nu til den seneste version?" + logout: "Du var logget ud." + refresh: "Opdater" read_only_mode: enabled: "En administrator har aktiver \"kun læsnings\" tilstranden. Du kan fortsætte med at læse forummet, men nogle handlinger vil ikke fungere." login_disabled: "Log in er deaktiveret midlertidigt, da forummet er i \"kun læsnings\" tilstand." @@ -491,6 +542,10 @@ da: reset: "Nulstil adgangskode" complete_username: "Hvis en konto matcher brugernavnet %{username}, vil du om lidt modtage en email med instruktioner om hvordan man nulstiller passwordet." complete_email: "Hvis en konto matcher %{email}, vil du om lidt modtage en email med instruktioner om hvordan man nulstiller passwordet." + complete_username_found: "Vi fandt ingen kontoer tilsvarende brugernavnet %{username}, du burde modtage en e-mail med instruktioner om hvordan du nulstiller din adgangskode, i løbet af kort tid." + complete_email_found: "Vi har fundet en konto tilsvarende %{email}, du burde modtage en e-mail med instruktioner om hvordan du nulstiller din adgangskode, i løbet af kort tid." + complete_username_not_found: "Ingen kontoer passer til brugernavnet %{username}" + complete_email_not_found: "Ingen kontoer tilsvarende %{email}" login: title: "Log ind" username: "Bruger" @@ -507,6 +562,7 @@ da: awaiting_approval: "Din konto er ikke blevet godkendt af en moderator endnu. Du får en e-mail når den bliver godkendt." requires_invite: "Beklager, det kræve en invitation at blive medlem af dette forum." not_activated: "Du kan ikke logge ind endnu. Vi har tidligere sendt en aktiverings-e-mail til dig på {{sentTo}}. Følg venligst instruktionerne i den e-mail for at aktivere din konto." + not_allowed_from_ip_address: "Du kan ikke logge ind fra den 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." google: @@ -621,7 +677,10 @@ da: title_with_attachments: "Tilføj et billede eller en fil" 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}})" local_tip: "klik for at vælge et billede fra din computer." + local_tip_with_attachments: "klik for at vælge et billede eller fil fra din enhed ({{authorized_extensions}})" hint: "(du kan også trække og slippe ind i editoren for at uploade dem)" hint_for_supported_browsers: "(du kan også trække og slippe eller indsætte billeder ind i editoren for at uploade dem)" uploading: "Uploader billede" @@ -635,6 +694,7 @@ da: user: "Søg i indlæg fra @{{username}}" category: "Søg i kategorien \"{{category}}\"" topic: "Søg i dette emne" + private_messages: "Søg i private beskeder" site_map: "gå til en anden emneoversigt eller kategori" go_back: 'gå tilbage' not_logged_in_user: 'bruger side, med oversigt over aktivitet og indstillinger' @@ -666,6 +726,9 @@ da: hot: "Der er ingen populære emner." category: "Der er ingen emner i kategorien {{category}}." top: "Der er ingen top emner" + educate: + new: '

    Dine nye emner vises her.

    Som standard, betragtes emner som nye og vil vise en ny indikator hvis de er lavet inden for de sidste 2 dage.

    Du kan skifte dette i dine instillinger.

    ' + unread: '

    Dine ulæste emner vises her.

    Som standard, betragtes emner som ulæste og vil vise en ulæst-tæller 1 hvis du:

    • Lavede emnet
    • Svarede på emnet
    • læste emnet i mere end 4 min.

    Eller hvis du udtrykkeligt har sat emnet til Sporet eller Overvåget via notifikations kontrollen i budnen af hvert emne.

    Du kan ændre dette i dine instillinger.

    ' bottom: latest: "Der er ikke flere populære emner." hot: "There are no more hot topics." @@ -727,6 +790,7 @@ da: jump_reply_down: hop til senere svar deleted: "Emnet er blevet slettet" auto_close_notice: "Dette emne lukker automatisk %{timeLeft}." + auto_close_notice_based_on_last_post: "Dette emne vil lukke %{duration} efter det sidste svar." auto_close_title: 'Indstillinger for automatisk lukning' auto_close_save: "Gem" auto_close_remove: "Luk ikke dette emne automatisk" diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index 2c7febd293..e2a785ec27 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -84,6 +84,7 @@ fi: other: "%{count} päivää sitten" share: topic: 'jaa linkki tähän ketjuun' + post: '%{postNumber}. viesti' close: 'sulje' twitter: 'jaa tämä linkki Twitterissä' facebook: 'jaa tämä linkki Facebookissa' @@ -142,6 +143,13 @@ fi: topic_count: "Ketjuja" post_count: "Viestejä" user_count: "Käyttäjiä" + contact: "Yhteystiedot" + contact_info: "Sivustoon liittyvissä kiireellisissä asioissa, ota yhteyttä osoitteeseen %{contact_email}." + bookmarked: + title: "Kirjanmerkki" + help: + bookmark: "Klikkaa lisätäksesi ketjun kirjanmerkkeihin" + unbookmark: "Klikkaa poistaaksesi ketjun kirjanmerkeistä" bookmarks: not_logged_in: "pahoittelut, sinun täytyy kirjautua sisään voidaksesi lisätä viestin kirjanmerkin" created: "olet lisännyt tämän viestin kirjainmerkkeihisi" @@ -258,6 +266,7 @@ fi: profile: "Profiili" mute: "Vaimenna" edit: "Muokkaa asetuksia" + download_archive: "Lataa viestini" new_private_message: "Uusi yksityisviesti" private_message: "Yksityisviesti" private_messages: "Viestit" @@ -326,8 +335,10 @@ fi: error: "Sähköpostiosoitteen vaihdossa tapahtui virhe. Ehkäpä sama sähköpostiosoite on jo käytössä?" success: "Annettuun osoitteeseen on lähetetty viesti. Seuraa sen ohjeita sähköpostiosoitteen varmentamiseksi." change_avatar: + title: "Vaihda profiilikuvasi" gravatar: "Gravatar, osoitteesta" refresh_gravatar_title: "Päivitä Gravatar" + letter_based: "Sivuston luoma profiilikuva" uploaded_avatar: "Oma kuva" uploaded_avatar_empty: "Lisää oma kuva" upload_title: "Lataa oma kuva" @@ -437,6 +448,7 @@ fi: none: "Et ole kutsunut vielä ketään. Voit lähettää yksittäisiä kutsuja tai kutsua useita ihmisiä kerralla lähettämällä massakutsun tiedostosta." text: "Lähetä massakutsu tiedostosta" uploading: "Lähettää..." + success: "Tiedoston lähettäminen onnistui. Saat yksityisviestillä tiedon, kun prosessi on valmis." error: "Tiedoston '{{filename}}' lähetyksen aikana tapahtui virhe: {{message}}" password: title: "Salasana" @@ -444,10 +456,13 @@ fi: common: "Annettu salasana on liian yleinen." ok: "Salasana vaikuttaa hyvältä." instructions: "Vähintään %{count} merkkiä." + associated_accounts: "Kirjautumiset" ip_address: title: "Viimeinen IP-osoite" registration_ip_address: title: "IP osoite rekisteröityessä" + avatar: + title: "Profiilikuva" title: title: "Otsikko" filters: @@ -569,7 +584,12 @@ fi: github: title: "GitHubilla" message: "Todennetaan Githubin kautta (varmista, että ponnahdusikkunoiden esto ei ole päällä)" + apple_international: "Apple/kansainvälinen" + google: "Google" + twitter: "Twitter" + emoji_one: "Emoji One" composer: + emoji: "Emoji :smile:" add_warning: "Tämä on virallinen varoitus." posting_not_on_topic: "Mihin ketjuun haluat vastata?" saving_draft_tip: "tallennetaan" @@ -1124,6 +1144,8 @@ fi: delete: 'Poista alue' create: 'Uusi alue' save: 'Tallenna alue' + slug: 'Alueen lyhenne' + slug_placeholder: '(Valinnainen) url-lyhenne' creation_error: Alueen luonnissa tapahtui virhe. save_error: Alueen tallennuksessa tapahtui virhe. name: "Alueen nimi" @@ -1209,15 +1231,19 @@ fi: topic_statuses: warning: help: "Tämä on virallinen varoitus." + bookmarked: + help: "Olet lisännyt ketjun kirjanmerkkeihisi" locked: help: "Tämä ketju on suljettu; siihen ei voi enää vastata." unpinned: title: "Kiinnitys poistettu" + 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ä" archived: help: "Tämä ketju on arkistoitu; se on jäädytetty eikä sitä voi muuttaa" invisible: @@ -1428,12 +1454,17 @@ fi: edit: "Muokkaa ryhmiä" refresh: "Lataa uudelleen" new: "Uusi" + selector_placeholder: "syötä käyttäjätunnus" name_placeholder: "Ryhmän nimi, ei välilyöntejä, samt säännöt kuin käyttäjänimillä" about: "Muokkaa ryhmien jäsenyyksiä ja nimiä täällä" group_members: "Ryhmään kuuluvat" delete: "Poista" delete_confirm: "Poista tämä ryhmä?" delete_failed: "Ryhmän poistaminen ei onnistu. Jos tämä on automaattinen ryhmä, sitä ei voi poistaa." + delete_member_confirm: "Poista '%{username}' ryhmästä '%{group}'?" + name: "Nimi" + add: "Lisää" + add_members: "Lisää jäseniä" api: generate_master: "Luo rajapinnan pääavain" none: "Aktiivisia API avaimia ei ole määritelty." @@ -1501,14 +1532,29 @@ fi: title: "Palauta tietokanta edelliseen toimivaan tilaan" confirm: "Oletko varma, että haluat palauttaa tietokannan edelliseen toimivaan tilaan?" export_csv: + success: "Vienti on käynnissä. Saat ilmoituksen yksityisviestillä, kun prosessi on valmis." failed: "Vienti epäonnistui. Tarkista loki-tiedostot." + rate_limit_error: "Viestit voidaan ladata kerran päivässä, yritä uudestaan huomenna." button_text: "Vie" + button_title: + user: "Vie lista käyttäjistä CSV-formaatissa." + staff_action: "Vie lista henkilökunnan toimista CSV-formaatissa." + screened_email: "Vie koko lista seulotuista sähköpostiosoitteista CSV-formaatissa." + screened_ip: "Vie koko lista seulotuista IP-osoitteista CSV-formaatissa." + screened_url: "Vie koko lista seulotuista URL-osoitteista CSV-formaatissa." customize: title: "Mukauta" long_title: "Sivuston mukautukset" css: "CSS" header: "Header" + top: "Alku" footer: "Footer" + head_tag: + text: "" + title: "HTML, joka lisätään ennen elementtiä" + body_tag: + text: "" + title: "HTML, joka lisätään ennen elementtiä" override_default: "Älä sisällytä oletus-tyylitiedostoa" enabled: "Otettu käyttöön?" preview: "esikatselu" @@ -1626,6 +1672,7 @@ fi: do_nothing: "älä tee mitään" staff_actions: title: "Henkilökunnan toimet" + instructions: "Klikkaa käyttäjänimiä tai toimintoja suodattaaksesi listaa. Klikkaa profiilikuvaa siirtyäksesi käyttäjäsivulle." clear_filters: "Näytä kaikki" staff_user: "Palstan edustaja" target_user: "Kohteena ollut käyttäjä" @@ -1669,6 +1716,7 @@ fi: title: "Seulottavat IP:t" description: 'IP-osoitteet joita tarkkaillaan. Valitse "Salli" lisätäksesi osoitteen ohitettavien listalle.' delete_confirm: "Oletko varma, että haluat poistaa tämän säännön osoitteelle %{ip_address}?" + roll_up_confirm: "Oletko varma, että haluat yhdistää seulottavat IP-osoitteet aliverkoiksi?" rolled_up_some_subnets: "Porttikieltojen IP osoitteet käärittiin onnistuneesti näiksi aliverkoiksi: %{subnets}." rolled_up_no_subnet: "Mitään käärittävää ei ollut." actions: @@ -1852,6 +1900,7 @@ fi: external_username: "Käyttäjätunnus" external_name: "Nimi" external_email: "Sähköposti" + external_avatar_url: "Profiilikuvan URL" user_fields: title: "Käyttäjäkentät" help: "Lisää kenttiä jotka käyttäjät voivat täyttää." @@ -1969,6 +2018,13 @@ fi: with_post: %{username} viestille ketjussa %{link} with_post_time: %{username} viestille ketjussa %{link} %{time} with_time: %{username} %{time} + emoji: + title: "Emoji" + help: "Lisää uusi emoji joka on kaikkien käytettävissä. (Voit raahata useita tiedostoja kerralla)" + add: "Lisää uusi emoji" + name: "Nimi" + image: "Kuva" + delete_confirm: "Oletko varma, että haluat poistaa emojin :%{name}:?" lightbox: download: "lataa" search_help: @@ -1989,6 +2045,7 @@ fi: back: 'u Takaisin' up_down: 'k/j Siirrä valintaa ↑ ↓' open: 'o tai Enter Avaa valittu ketju' + next_prev: 'shift+j/shift+k Seuraava/edellinen osio' application: title: 'Ohjelmisto' create: 'c Luo uusi ketju' @@ -2002,8 +2059,12 @@ fi: dismiss_topics: 'x, t Unohda ketjut' actions: title: 'Toiminnot' + bookmark_topic: 'f Lisää ketju kirjanmerkkeihin' + pin_unpin_topic: 'shift+p Kiinnitä ketju/poista kiinnitys' + share_topic: 'shift+s Jaa ketju' share_post: 's Jaa viesti' reply_as_new_topic: 't Aloita uusi yhdistetty ketju' + reply_topic: 'shift+r Vastaa ketjuun' reply_post: 'r Vastaa viestiin' quote_post: 'q Lainaa viesti' like: 'l Tykkää viestistä' diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index 4e5d339708..5c4209e452 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -90,7 +90,7 @@ fr: facebook: 'partager ce lien sur Facebook' google+: 'partager ce lien sur Google+' email: 'envoyer ce lien dans un courriel' - topic_admin_menu: "action sur le sujet d'administrateur" + topic_admin_menu: "actions administrateur pour ce sujet" edit: 'éditer le titre et la catégorie de ce sujet' not_implemented: "Cette fonctionnalité n'a pas encore été implémentée, désolé." no_value: "Non" @@ -145,12 +145,17 @@ fr: user_count: "Nombre d'utilisateurs" contact: "Nous contacter" contact_info: "En cas de problème critique ou urgent sur ce site, veuillez nous contacter: %{contact_email}." + bookmarked: + title: "Signet" + help: + bookmark: "Cliquer pour ajouter ce sujet à vos signets" + unbookmark: "Cliquer pour retirer ce sujet de vos signets" bookmarks: - not_logged_in: "désolé, vous devez être connecté pour placer des message dans vos signets" - created: "vous avez placé ce message dans vos signets" - not_bookmarked: "vous avez lu ce message; cliquez pour le placer dans vos signets" - last_read: "ceci est le dernier message que vous avez lu; cliquez pour le placer dans vos signets" - remove: "Supprimer des signets" + not_logged_in: "désolé, vous devez être connecté pour ajouter des message dans vos signets" + created: "vous avez ajouté ce message dans vos signets" + not_bookmarked: "vous avez lu ce message; cliquez pour l'ajouter dans vos signets" + last_read: "ceci est le dernier message que vous avez lu; cliquez pour l'ajouter dans vos signets" + remove: "Retirer de vos signets" topic_count_latest: one: "{{count}} sujet récent." other: "{{count}} sujets récents." @@ -163,7 +168,7 @@ fr: click_to_show: "Cliquez pour afficher." preview: "prévisualiser" cancel: "annuler" - save: "Sauvegarder les changements" + save: "Sauvegarder les modifications" saving: "Sauvegarde en cours..." saved: "Sauvegardé !" upload: "Envoyer" @@ -250,10 +255,10 @@ fr: phone: Téléphone other_accounts: "Autres comptes avec cette adresse IP :" delete_other_accounts: "Supprimer %{count}" - username: "nom d'utilisateur" + username: "pseudo" trust_level: "NC" read_time: "temps de lecture" - topics_entered: "sujets postées" + topics_entered: "sujets visités" post_count: "# messages" confirm_delete_other_accounts: "Êtes-vous sûr de vouloir supprimer tous ces comptes ?" user: @@ -287,7 +292,7 @@ fr: suspended_notice: "L'utilisateur est suspendu jusqu'au {{date}}." suspended_reason: "Raison :" github_profile: "Github" - mailing_list_mode: "Recevoir un e-mail pour tous les nouveaux messages (sauf si vous avez rendu silencieux le sujet ou la catégorie)" + mailing_list_mode: "Recevoir un courriel pour tous les nouveaux messages (sauf si vous avez rendu silencieux le sujet ou la catégorie)" watched_categories: "Surveillés" watched_categories_instructions: "Vous pourrez suivre attentivement automatiquement toutes les nouveaux sujets dans ces catégories. Vous serez notifié de tous les nouveaux messages et sujets, de plus les nombres de messages non lus et de nouveaux messages apparaîtront également à côté de la liste des sujets." tracked_categories: "Suivies" @@ -301,7 +306,7 @@ fr: unread_message_count: "Messages" admin_delete: "Supprimer" staff_counters: - flags_given: "signalements obligeants" + flags_given: "signalements utiles" flagged_posts: "messages signalés" deleted_posts: "messages supprimés" suspensions: "suspensions" @@ -320,18 +325,20 @@ fr: title: "Modifier à propos de moi" change_username: title: "Modifier le pseudo" - confirm: "Si vous changez de pseudo, toutes les citations de vos messages et les mentions @pseudo seront cassées. Êtes-vous absolument sûr vouloir le faire ?" + confirm: "Si vous modifiez votre pseudo, toutes les citations de vos messages et les mentions @pseudo seront cassées. Êtes-vous absolument sûr de vouloir le faire ?" taken: "Désolé, ce pseudo est déjà pris." error: "Il y a eu une erreur lors du changement de votre pseudo." invalid: "Ce pseudo est invalide. Il ne doit être composé que de lettres et de chiffres." change_email: - title: "Changer d'adresse de courriel" + title: "Modifier l'adresse de courriel" taken: "Désolé, cette adresse de courriel est indisponible." error: "Il y a eu une erreur lors du changement d'adresse de courriel. Cette adresse est peut-être déjà utilisée ?" success: "Nous avons envoyé un courriel à cette adresse. Merci de suivre les instructions." change_avatar: + title: "Modifier votre image de profil" gravatar: "Gravatar, basé sur" refresh_gravatar_title: "Actualiser votre Gravatar" + letter_based: "Image de profil attribuée par le système" uploaded_avatar: "Avatar personnalisé" uploaded_avatar_empty: "Ajouter une avatar personnalisé" upload_title: "Envoyer votre avatar" @@ -346,15 +353,15 @@ fr: email: title: "Courriel" instructions: "Ne sera jamais visible publiquement" - ok: "On vous envoie un mail pour confirmer" - invalid: "Merci d'entrer une adresse email valide" - authenticated: "Votre adresse email a été authentifiée par {{provider}}" + ok: "On vous enverra un courriel pour confirmer" + invalid: "Merci d'entrer une adresse de courriel valide" + authenticated: "Votre adresse de courriel a été authentifiée par {{provider}}" frequency: - zero: "Nous vous enverrons des courriels seulement si vous n'avez pas lu le contenu pour lequel nous envoyons un courriel." - one: "Nous vous enverrons des courriels seulement si nous ne vous avons pas vu sur le site dans la dernière minute et que vous n'avez pas lu le contenu pour lequel nous envoyons un courriel." - other: "Nous vous enverrons des courriels seulement si nous ne vous avons pas vu sur le site dans les dernières {{count}} minutes et que vous n'avez pas lu le contenu pour lequel nous envoyons un courriel." + zero: "Nous vous enverrons un courriel immédiatement si vous n'avez déjà lu le contenu en question." + one: "Nous vous enverrons des courriels seulement si nous ne vous avons pas vu sur le site dans la dernière minute et que vous n'avez pas lu le contenu en question." + other: "Nous vous enverrons des courriels seulement si nous ne vous avons pas vu sur le site dans les dernières {{count}} minutes et que vous n'avez pas lu le contenu en question." name: - title: "Nom" + title: "Nom d'utilisateur" instructions: "Votre nom complet (facultatif)" too_short: "Votre nom est trop court" ok: "Votre nom a l'air bon." @@ -363,17 +370,17 @@ fr: instructions: "Unique, sans espace, court" short_instructions: "Les gens peuvent vous mentionner avec @{{username}}" available: "Votre pseudo est disponible" - global_match: "L'adresse email correspond au pseudo enregistré" + global_match: "L'adresse de courriel correspond au pseudo enregistré" global_mismatch: "Déjà enregistré. Essayez {{suggestion}} ?" not_available: "Non disponible. Essayez {{suggestion}} ?" too_short: "Votre pseudo est trop court" too_long: "Votre pseudo est trop long" checking: "Vérification de la disponibilité de votre pseudo..." - enter_email: 'Pseudo trouvé; Entrez l''adresse email correspondante' - prefilled: "L'adresse email correspond à ce pseudo enregistré" + enter_email: 'Pseudo trouvé; Entrez l''adresse de courriel correspondante' + prefilled: "L'adresse de courriel correspond à ce pseudo enregistré" locale: title: "Langue de l'interface" - instructions: "Langage de votre interface. Cette dernière changera lorsque vous actualiserez la page." + instructions: "Langue de votre interface. Cette dernière changera lorsque vous actualiserez la page." default: "(par défaut)" password_confirmation: title: "Confirmation du mot de passe" @@ -449,11 +456,13 @@ fr: common: "Ce mot de passe est trop commun." ok: "Votre mot de passe semble correct." instructions: "Au moins %{count} caractères." - associated_accounts: "Pseudos" + associated_accounts: "Connexions" ip_address: title: "Dernières adresses IP" registration_ip_address: title: "Adresse IP d'enregistrement" + avatar: + title: "Image de profil" title: title: "Titre" filters: @@ -516,7 +525,7 @@ fr: private_message_info: title: "Message Privé" invite: "Inviter d'autres utilisateurs…" - remove_allowed_user: "Voulez-vous vraiment supprimer {{name}} de ce message privé ?" + remove_allowed_user: "Êtes-vous sûr de vouloir supprimer {{name}} de ce message privé ?" email: 'Courriel' username: 'Pseudo' last_seen: 'Vu' @@ -545,7 +554,7 @@ fr: email_placeholder: "courriel ou pseudo" caps_lock_warning: "Majuscules vérrouillées" error: "Erreur inconnue" - blank_username_or_password: "Merci de saisir votre courriel ou nom d'utilisateur, et mot de passe." + blank_username_or_password: "Merci de saisir votre courriel ou pseudo, et mot de passe." reset_password: 'Réinitialiser le mot de passe' logging_in: "Connexion en cours…" or: "ou" @@ -647,9 +656,9 @@ fr: auto_close: label: "Heure de fermeture automatique de ce sujet :" error: "Merci d'entrer une valeur valide." - based_on_last_post: "Ne pas fermer tant que le dernier message dans ce sujet n'est pas plus ancien que çà." + based_on_last_post: "Ne pas fermer tant que le dernier message dans ce sujet n'est pas plus ancien que ceci." all: - examples: 'Saisir un nombre d''heures (24), une heure absolue (17:30) ou une date(2013-11-22 14:00).' + examples: 'Saisir un nombre d''heures (24), une heure absolue (17:30) ou une date (2013-11-22 14:00).' limited: units: "(# d'heures)" examples: 'Saisir le nombre d''heures (24).' @@ -708,10 +717,10 @@ fr: dismiss_new: "Ignorer Nouveaux" toggle: "activer la sélection multiple des sujets" actions: "Actions sur sélection multiple" - change_category: "Changer la Catégorie" + change_category: "Modifier la Catégorie" close_topics: "Fermer les sujets" archive_topics: "Sujets archivés" - notification_level: "Changer le niveau de notification" + notification_level: "Modifier le niveau de notification" selected: one: "Vous avez sélectionné 1 sujet." other: "Vous avez sélectionné {{count}} sujets." @@ -813,7 +822,7 @@ fr: '3_1': 'Vous recevrez des notifications car vous avez créé ce sujet.' '3': 'Vous recevrez des notifications car vous surveillez ce sujet.' '2_8': 'Vous recevrez des notifications parce que vous suivez cette catégorie.' - '2_4': 'Vous recevrez des notifications car vous avez posté une réponse dans ce sujet.' + '2_4': 'Vous recevrez des notifications car vous avez écrit une réponse dans ce sujet.' '2_2': 'Vous recevrez des notifications car vous suivez ce sujet.' '2': 'Vous recevrez des notifications car vous avez lu ce sujet.' '1_2': 'Vous serez notifié si un utilisateur mentionne votre @pseudo ou réponds à votre message.' @@ -835,9 +844,9 @@ fr: description: "Le nombre de messages non lus et de nouveaux messages apparaîtra à côté du sujet. Vous serez averti si quelqu'un mentionne votre @pseudo ou répond à votre message." regular: title: "Normal" - description: "Vous serez notifié si un utilisateur mentionne votre @pseudo ou réponds à votre message." + description: "Vous serez averti si quelqu'un mentionne votre @pseudo ou répond à votre message." regular_pm: - title: "Régulier" + title: "Normal" description: "Vous serez averti si quelqu'un mentionne votre @pseudo ou répond à votre message privé." muted_pm: title: "Silencieux" @@ -975,7 +984,7 @@ fr: attachment_upload_not_allowed_for_new_user: "Désolé, les nouveaux utilisateurs ne peuvent pas envoyer de fichier." attachment_download_requires_login: "Désolé, vous devez être connecté pour télécharger une pièce jointe." abandon: - confirm: "Voulez-vous vraiment abandonner votre message ?" + confirm: "Êtes-vous sûr de vouloir abandonner votre message ?" no_value: "Non, le conserver" yes_value: "Oui, abandonner" via_email: "message depuis un courriel" @@ -1007,7 +1016,7 @@ fr: convert_to_moderator: "Ajouter la couleur modérateur" revert_to_regular: "Retirer la couleur modérateur" rebake: "Reconstruire l'HTML" - unhide: "Afficher" + unhide: "Ré-afficher" actions: flag: 'Signaler' defer_flags: @@ -1025,7 +1034,7 @@ fr: off_topic: "Annuler le signalement" spam: "Annuler le signalement" inappropriate: "Annuler le signalement" - bookmark: "L'enlever des signets" + bookmark: "Retirer de vos signets" like: "Annuler j'aime" vote: "Retirer votre vote" people: @@ -1153,7 +1162,7 @@ fr: foreground_color: "Couleur du texte" name_placeholder: "Un ou deux mots maximum" color_placeholder: "N'importe quelle couleur" - delete_confirm: "Voulez-vous vraiment supprimer cette catégorie ?" + delete_confirm: "Êtes-vous sûr de vouloir supprimer cette catégorie ?" delete_error: "Il y a eu une erreur lors de la suppression." list: "Liste des catégories" no_description: "Veuillez ajouter une description pour cette catégorie" @@ -1190,7 +1199,7 @@ fr: title: "Silencieux" description: "Vous ne recevrez aucune notification sur les sujets de ces catégories, et ils n'apparaîtront pas dans votre onglet \"non-lus\"." flagging: - title: 'Merci de nous aider à garder notre communauté civilisé !' + title: 'Merci de nous aider à garder notre communauté aimable !' private_reminder: 'les signalements sont privés, seulement visible aux modérateurs' action: 'Signaler ce message' take_action: "Signaler" @@ -1206,7 +1215,7 @@ fr: formatted_name: off_topic: "C'est hors-sujet" inappropriate: "C'est inapproprié" - spam: "C'est un pourriel" + spam: "C'est du spam" custom_placeholder_notify_user: "Soyez précis, constructif, et toujours respectueux." custom_placeholder_notify_moderators: "Dites-nous ce qui vous dérange spécifiquement, et fournissez des liens pertinents et exemples si possible." custom_message: @@ -1232,11 +1241,13 @@ fr: help: "Ce sujet est fermé; il n'accepte plus de nouvelles réponses" unpinned: title: "Désépinglé" + 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" archived: help: "Ce sujet est archivé; il est gelé et ne peut être modifié" invisible: @@ -1376,7 +1387,7 @@ fr: start_date: "Date de début" end_date: "Date de fin" commits: - latest_changes: "Derniers changements..." + latest_changes: "Dernières modifications: merci de mettre à jour régulièrement!" by: "par" flags: title: "Signalements" @@ -1402,7 +1413,7 @@ fr: delete_flag_modal_title: "Supprimer et..." delete_spammer: "Supprimer le spammer" delete_spammer_title: "Supprimer cet utilisateur et tous ses messages et sujets de ce dernier." - disagree_flag_unhide_post: "Refuser (ré-affiché le message)" + disagree_flag_unhide_post: "Refuser (ré-afficher le message)" disagree_flag_unhide_post_title: "Supprimer tous les signalements de ce message et ré-affiché ce dernier" disagree_flag: "Refuser" disagree_flag_title: "Refuser le signalement car il est invalide ou incorrect" @@ -1447,7 +1458,7 @@ fr: edit: "Éditer les groupes" refresh: "Actualiser" new: "Nouveau" - selector_placeholder: "entrer le nom d'utilisateur" + selector_placeholder: "entrer le pseudo" name_placeholder: "Nom du groupe, sans espace, mêmes règles que pour les pseudos" about: "Modifier votre adhésion et les noms ici" group_members: "Membres du groupe" @@ -1531,16 +1542,23 @@ fr: button_text: "Exporter" button_title: user: "Exporter la liste des utilisateurs dans un fichier CSV." - staff_action: "Exporter la liste des actions du staff dans un fichier CSV." - screened_email: "Exporter la liste des emails sous surveillance dans un fichier CSV." + staff_action: "Exporter la liste des actions des responsables dans un fichier CSV." + screened_email: "Exporter la liste des adresses de courriel sous surveillance dans un fichier CSV." screened_ip: "Exporter la liste complète des adresses IP sous surveillance dans un fichier CSV." - screened_url: "Exporter toutes les URL affichées vers un fichier CSV" + screened_url: "Exporter toutes les URL sous surveillance vers un fichier CSV" customize: title: "Personnaliser" long_title: "Personnalisation du site" css: "CSS" header: "En-tête" + top: "Top" footer: "Pied de page" + head_tag: + text: "" + title: "HTML qui sera inséré avant la balise " + body_tag: + text: "" + title: "HTML qui sera inséré avant la balise " override_default: "Ne pas inclure la feuille de style par défaut" enabled: "Activé ?" preview: "prévisualiser" @@ -1569,7 +1587,7 @@ fr: copy_name_prefix: "Copie de" delete_confirm: "Supprimer cette palette de couleurs ?" undo: "annuler" - undo_title: "Annuler vos changements sur cette couleur depuis la dernière fois qu'elle a été sauvegarder." + undo_title: "Annuler vos modifications sur cette couleur depuis la dernière fois qu'elle a été sauvegarder." revert: "rétablir" revert_title: "Rétablir la couleur de la palette par défaut de Discourse." primary: @@ -1658,6 +1676,7 @@ fr: do_nothing: "ne rien faire" staff_actions: title: "Actions des modérateurs" + instructions: "Cliquez sur les pseudos et les actions pour filtrer la liste. Cliquez sur les images de profil pour aller aux pages des utilisateurs." clear_filters: "Tout Afficher" staff_user: "Membre de l'équipe des modérateurs" target_user: "Utilisateur cible" @@ -1674,16 +1693,16 @@ fr: deleted: "Pas de nouvelle valeur. L'enregistrement a été supprimé." actions: delete_user: "Supprimer l'utilisateur" - change_trust_level: "Modifier le niveau de confiance" + change_trust_level: "modifier le niveau de confiance" change_site_setting: "modifier les paramètres du site" - change_site_customization: "Modifier la personnalisation du site" - delete_site_customization: "Supprimer la personnalisation du site" + change_site_customization: "modifier la personnalisation du site" + delete_site_customization: "supprimer la personnalisation du site" suspend_user: "suspendre l'utilisateur" unsuspend_user: "retirer la suspension de l'utilisateur" grant_badge: "décerné le badge" revoke_badge: "retirer le badge" check_email: "vérifier l'adresse courriel" - delete_topic: "Supprimer le sujet" + delete_topic: "supprimer le sujet" delete_post: "supprimer le message" impersonate: "incarner" screened_emails: @@ -1701,7 +1720,8 @@ fr: title: "IP Suivies" description: 'Adresses IP qui sont surveillés. Utiliser "Autoriser" pour ajouter les adresses IP à la liste blanche.' delete_confirm: "Êtes-vous sûr de vouloir supprimer la règle pour %{ip_address} ?" - rolled_up_some_subnets: "Consolidation réussie des adresses IP interdites sous cette forme de plages de sous réseaux: %{subnets}." + roll_up_confirm: "Êtes-vous certain de vouloir consolider les adresses IP interdites sous forme de plages de sous réseaux ?" + rolled_up_some_subnets: "Consolidation réussie des adresses IP interdites vers ces plages de sous réseau: %{subnets}." rolled_up_no_subnet: "Aucune consolidation possible." actions: block: "Bloquer" @@ -1731,7 +1751,7 @@ fr: new: "Nouveaux" active: "Actifs" pending: "En attente" - staff: 'Membres de l''équipe' + staff: 'Responsables' suspended: 'Suspendus' blocked: 'Bloqués' suspect: 'Suspect' @@ -1749,9 +1769,9 @@ fr: newuser: 'Utilisateurs au niveau de confiance 0 (Nouveaux utilisateurs)' basic: 'Utilisateurs au niveau de confiance 1 (Utilisateurs de base)' regular: 'Utilisateurs de niveau 2 (Membre)' - leader: 'Utilisateurs de niveau 3 (Régulier)' + leader: 'Utilisateurs de niveau 3 (Habitué)' elder: 'Utilisateurs de niveau 4 (Meneur)' - staff: "Membres de l'équipe" + staff: "Membres de l'équipe des responables" admins: 'Administrateurs' moderators: 'Modérateurs' blocked: 'Utilisateurs bloqués' @@ -1806,7 +1826,7 @@ fr: last_100_days: 'dans les 100 derniers jours' private_topics_count: Messages privés posts_read_count: Messages lus - post_count: Messages postés + post_count: Messages crées topics_entered: Sujets consultés flags_given_count: Signalements effectués flags_received_count: Signalements reçus @@ -1821,7 +1841,7 @@ fr: delete_forbidden_because_staff: "Administrateurs et modérateurs ne peuvent pas être supprimés." delete_forbidden: one: "Les utilisateurs ne peuvent pas être supprimés s'ils ont posté des messages Supprimer tous les messages avant d'essayer de supprimer un utilisateur. (Les messages plus vieux que %{count} jour ne peut pas être supprimé.)" - other: "Les utilisateurs ne peuvent pas être supprimés s'ils ont posté des messages Supprimer tous les messages avant d'essayer de supprimer un utilisateur. (Les messages plus vieux que %{count} jours ne peut pas être supprimé.)" + other: "Les utilisateurs ne peuvent pas être supprimés s'ils ont crée des messages. Supprimer tous les messages avant d'essayer de supprimer un utilisateur. (Les messages plus vieux que %{count} jours ne peuvent pas être supprimés.)" cant_delete_all_posts: one: "Impossible de supprimer tout les messages. Certains messages sont âgés de plus de %{count} jour. (voir l'option delete_user_max_post_age)" other: "Impossible de supprimer tout les messages. Certains messages sont âgés de plus de %{count} jours. (voir l'option delete_user_max_post_age)" @@ -1849,10 +1869,10 @@ fr: suspend_modal_title: "Suspendre l'utilisateur" trust_level_2_users: "Utilisateurs de niveau de confiance 2" trust_level_3_requirements: "Niveaux de confiance 3 Pré-requis" - trust_level_locked_tip: "les niveaux de confiance sont verrouillés. Le système n'accordera plus de promotion ou ne rétrogradera d'utilisateur." + trust_level_locked_tip: "Le niveau de confiance est verrouillé. Le système ne changera plus le niveau de confiance de cet utilisateur." trust_level_unlocked_tip: "les niveaux de confiance sont déverrouillés. Le système pourra promouvoir ou rétrograder des utilisateurs." - lock_trust_level: "Verrouiller les niveaux de confiance" - unlock_trust_level: "Deverrouiller les niveaux de confiance" + lock_trust_level: "Verrouiller le niveau de confiance" + unlock_trust_level: "Déverrouiller le niveau de confiance" tl3_requirements: title: "Pré-requis pour le niveau de confiance 3" table_title: "Les 100 derniers jours :" @@ -1869,8 +1889,8 @@ fr: flagged_by_users: "Utilisateurs signalés" likes_given: "J'aimes donnés" likes_received: "J'aimes reçus" - likes_received_days: "J'aime reçus: chaque jours" - likes_received_users: "J'aime reçus: chaque utilisateurs" + likes_received_days: "J'aime reçus : par jour" + likes_received_users: "J'aime reçus : par utilisateur" qualifies: "Admissible au niveau de confiance 3." does_not_qualify: "Non admissible au niveau de confiance 3." will_be_promoted: "Sera promu prochainement." @@ -1884,6 +1904,7 @@ fr: external_username: "Pseudo" external_name: "Nom" external_email: "Courriel" + external_avatar_url: "URL de l'image de profil" user_fields: title: "Champs utilisateurs" help: "Ajouter des champs que vos utilisateurs pourront remplir." @@ -1926,7 +1947,7 @@ fr: posting: 'Messages' email: 'Courriel' files: 'Fichiers' - trust: 'Niveaux Confiance' + trust: 'Niveaux de confiance' security: 'Sécurité' onebox: "Onebox" seo: 'SEO' @@ -2061,7 +2082,7 @@ fr: mark_watching: 'm, w Marquer le sujet comme surveillé' badges: title: Badges - allow_title: "autorisé le badge comme titre ?" + allow_title: "autoriser le badge comme titre ?" multiple_grant: "peut être décerné plusieurs fois ?" badge_count: one: "1 badge" @@ -2096,7 +2117,7 @@ fr: name: Membre description: Accorde les invitations. regular: - name: Regulier + name: Habitué description: La re-catégorisation, le renommage, le suivi de lien et le salon sont accessibles leader: name: Meneur @@ -2123,13 +2144,13 @@ fr: name: Bon sujet description: A reçu 25 J'aime sur un sujet. Ce badge peut être décerné plusieurs fois great_topic: - name: Eminent sujet + name: Super sujet description: A reçu 50 J'aime sur un sujet. Ce badge peut être décerné plusieurs fois nice_share: - name: Partage + name: Partage sympa description: Message partagé avec 25 visiteurs uniques good_share: - name: Grand partage + name: Bon partage description: Message partagé avec 300 visiteurs uniques great_share: name: Super Partage diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index 7621cefab4..dde483b447 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -84,6 +84,7 @@ it: other: "%{count} giorni fa" share: topic: 'Condividi un link a questa conversazione' + post: 'messaggio n°%{postNumber}' close: 'chiudi' twitter: 'Condividi questo link su Twitter' facebook: 'Condividi questo link su Facebook' @@ -142,6 +143,12 @@ it: topic_count: "Argomenti" post_count: "Messaggi" user_count: "Utenti" + contact: "Contattaci" + bookmarked: + title: "Segnalibro" + help: + bookmark: "Clicca per aggiungere un segnalibro a questo argomento" + unbookmark: "Clicca per rimuove il segnalibro a questo argomento" bookmarks: not_logged_in: "spiacenti, devi essere connesso per aggiungere segnalibri ai messaggi" created: "hai inserito questo messaggio nei segnalibri." @@ -258,6 +265,7 @@ it: profile: "Profilo" mute: "Ignora" edit: "Modifica opzioni" + download_archive: "Scarica i miei messaggi" new_private_message: "Nuovo Messaggio Privato" private_message: "Messaggio privato" private_messages: "Messaggi" @@ -326,8 +334,10 @@ it: error: "C'è stato un errore nel cambio dell'email; potrebbe essere già usata da un altro utente." success: "Abbiamo inviato una email a questo indirizzo. Segui le indicazioni di conferma." change_avatar: + title: "Cambia l'immagine del tuo profilo" gravatar: "Gravatar, basato su" refresh_gravatar_title: "Ricarica il tuo Gravatar" + letter_based: "Immagine del profilo assegnata dal sistema" uploaded_avatar: "Immagine personalizzata" uploaded_avatar_empty: "Aggiungi un'immagine personalizzata" upload_title: "Carica la tua foto" diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml index 71575c92c1..69f8e98af5 100644 --- a/config/locales/client.ko.yml +++ b/config/locales/client.ko.yml @@ -68,6 +68,7 @@ ko: other: "%{count}일 전" share: topic: '토픽을 공유합니다.' + post: '포스트 #%{postNumber}' close: '닫기' twitter: 'twitter로 공유' facebook: 'Facebook으로 공유' @@ -127,6 +128,11 @@ ko: user_count: "사용자" contact: "Contact Us" contact_info: "사이트 운영과 관련된 사항이나 요청이 있으시다면 이메일 %{contact_email}로 연락주시기 바랍니다." + bookmarked: + title: "북마크" + help: + bookmark: "이 토픽을 북마크하려면 클릭하세요" + unbookmark: "이 토픽의 북마크를 제거하려면 클릭하세요" bookmarks: not_logged_in: "죄송합니다. 게시물을 즐겨찾기에 추가하려면 로그인을 해야 합니다." created: "이 게시글을 북마크 하였습니다." @@ -229,6 +235,7 @@ ko: username: "아이디" trust_level: "TL" read_time: "읽은 시간" + topics_entered: "입력된 제목:" post_count: "포스트 개수" confirm_delete_other_accounts: "정말 이 계정들을 삭제하시겠습니까?" user: @@ -236,6 +243,8 @@ ko: profile: "프로필" mute: "알림 끄기" edit: "환경 설정 편집" + download_archive: "내 게시글 다운로드" + new_private_message: "새로운 비공개 메시지" private_message: "개인 메시지" private_messages: "메시지" activity_stream: "활동" @@ -303,8 +312,10 @@ ko: error: "이메일 변경 중 오류가 발생했습니다. 이미 사용 중인 이메일인지 확인해주세요." success: "이메일 발송이 완료되었습니다. 확인하신 후 절차에 따라주세요." change_avatar: + title: "프로필 사진 변경" gravatar: "Gravatar 기반" refresh_gravatar_title: "Gravatar 새로고침" + letter_based: "자동 생성된 아바타" uploaded_avatar: "커스텀 사진" uploaded_avatar_empty: "커스텀 사진 추가" upload_title: "프로필 사진 업로드" @@ -332,6 +343,7 @@ ko: instructions: "중복될 수 없으며 띄어쓰기 사용이 불가합니다. 짧을 수록 좋아요." short_instructions: "@{{username}}으로 언급이 가능합니다." available: "아이디\x1D로 사용가능합니다." + global_match: "이메일이 등록된 아이디와 연결되어 있습니다." global_mismatch: "이미 등록된 아이디입니다. 다시 시도해보세요. {{suggestion}}" not_available: "사용할 수 없는 아이디입니다. 다시 시도해보세요. {{suggestion}}" too_short: "아이디가 너무 짧습니다" @@ -405,6 +417,7 @@ ko: none: "아직 아무도 초대하지 않았습니다. 초대장을 각각 보내거나, uploading a bulk invite file을 이용하여 단체 초대를 보낼 수 있습니다." text: "파일로 대량 초대하기" uploading: "업로드 중..." + success: "파일이 성공적으로 업로드되었습니다. 완료되면 비공개 메시지로 알려드리겠습니다." error: "'{{filename}}': {{message}} 업로드중 에러가 있었습니다." password: title: "비밀번호" @@ -412,10 +425,13 @@ ko: common: "That password is too common." ok: "적절한 암호입니다." instructions: "글자 수가 %{count}자 이상이어야 합니다." + associated_accounts: "로그인" ip_address: title: "마지막 IP 주소" registration_ip_address: title: "IP Address 등록" + avatar: + title: "프로필 사진" title: title: "호칭" filters: @@ -537,7 +553,12 @@ ko: github: title: "GitHub" message: "GitHub 인증 중(팝업 차단을 해제 하세요)" + apple_international: "Apple/International" + google: "Google" + twitter: "Twitter" + emoji_one: "Emoji One" composer: + emoji: "Emoji :smile:" add_warning: "공식적인 경고입니다." posting_not_on_topic: "어떤 토픽에 답글을 작성하시겠습니까?" saving_draft_tip: "저장 중..." @@ -649,6 +670,7 @@ ko: user: "@{{username}}의 글 검색" category: "\"{{category}}\" 카테고리 검색" topic: "이 토픽을 검색" + private_messages: "비공개 메시지 검색" site_map: "다른 토픽이나 카테고리로 이동" go_back: '돌아가기' not_logged_in_user: 'user page with summary of current activity and preferences' @@ -1143,15 +1165,19 @@ ko: topic_statuses: warning: help: "공식적인 주의입니다." + bookmarked: + help: "북마크한 토픽" locked: help: "이 토픽은 폐쇄되었습니다. 더 이상 새 답글을 받을 수 없습니다." unpinned: title: "핀 제거" + help: "이 토픽은 핀 제거 되었습니다. 목록에서 일반적인 순서대로 표시됩니다." pinned_globally: title: "핀 지정됨 (전역적)" help: "이 토픽은 전역적으로 핀 지정 되었습니다. 모든 목록의 최상위에 표시됩니다." pinned: title: "핀 지정됨" + help: "이 토픽은 고정되었습니다. 카테고리의 상단에 표시됩니다." archived: help: "이 토픽은 보관중입니다. 고정되어 변경이 불가능합니다." invisible: @@ -1357,12 +1383,16 @@ ko: edit: "그룹 수정" refresh: "새로고침" new: "새로운" + selector_placeholder: "아이디를 입력하세요" name_placeholder: "그룹 이름, 사용자 이름처럼 빈칸 없이 작성" about: "회원과 이름을 변경" group_members: "그룹 멤버" delete: "삭제" delete_confirm: "이 그룹을 삭제 하시겠습니까?" delete_failed: "이것은 자동으로 생성된 그룹입니다. 삭제할 수 없습니다." + name: "이름" + add: "추가" + add_members: "사용자 추가하기" api: generate_master: "마스터 API 키 생성" none: "지금 활성화된 API 키가 없습니다." @@ -1438,6 +1468,12 @@ ko: css: "CSS" header: "헤더" footer: "푸터(하단영역)" + head_tag: + text: "" + title: " 태그 전에 들어갈 HTML" + body_tag: + text: "" + title: " 태그 전에 들어갈 HTML" override_default: "표준 스타일 시트를 포함하지 마십시오" enabled: "사용가능?" preview: "미리 보기" @@ -1766,6 +1802,7 @@ ko: external_username: "아이디" external_name: "Name" external_email: "Email" + external_avatar_url: "프로필 사진 URL" user_fields: title: "사용자 필드" help: "사용자가 입력할 수 있는 필드를 추가" @@ -1883,6 +1920,12 @@ ko: with_post: %{username} for post in %{link} with_post_time: %{username} for post in %{link} at %{time} with_time: %{username} at %{time} + emoji: + title: "Emoji" + add: "새로운 Emoji 추가" + name: "이름" + image: "이미지" + delete_confirm: "정말 :%{name}: emoji를 삭제하시겠습니까?" lightbox: download: "download" search_help: @@ -1903,6 +1946,7 @@ ko: back: 'u Back' up_down: 'k/j 선택된 게시글 이동 ↑ ↓' open: 'o or Enter 선택한 토픽에 들어갑니다.' + next_prev: 'shift+j/shift+k 이전/다음 섹션' application: title: 'Application' create: 'c 새 토픽을 만듭니다.' @@ -1916,8 +1960,12 @@ ko: dismiss_topics: 'x, t 토픽 무시하기' actions: title: 'Actions' + bookmark_topic: 'f 토픽 북마크' + pin_unpin_topic: 'shift+p 핀고정/핀해제' + share_topic: 'shift+s 토픽 공유' share_post: 's 게시글 공유' reply_as_new_topic: 't 링크된 토픽으로 답글 작성하기' + reply_topic: 'shift+r 토픽에 답글 달기' reply_post: 'r 게시글에 답글 달기' quote_post: 'q 인용 글' like: 'l Like post' diff --git a/config/locales/client.nb_NO.yml b/config/locales/client.nb_NO.yml index abe8117ca7..b4f60ebfce 100644 --- a/config/locales/client.nb_NO.yml +++ b/config/locales/client.nb_NO.yml @@ -84,6 +84,7 @@ nb_NO: other: "%{count} dager siden" share: topic: 'del en lenke til dette emnet' + post: 'innlegg #%{postNumber}' close: 'lukk' twitter: 'del denne lenken på Twitter' facebook: 'del denne lenken på Facebook' @@ -142,6 +143,13 @@ nb_NO: topic_count: "Emner" post_count: "Innlegg" user_count: "Brukere" + contact: "Kontakt Oss" + contact_info: "Hvis noe kritisk skulle oppstå eller det er en hastesak som påvirker siden kontakt oss på %{contact_email}." + bookmarked: + title: "Bokmerke" + help: + bookmark: "Klikk for å lagre dette emnet" + unbookmark: "Klikk for å fjerne dette lagrede emnet" bookmarks: not_logged_in: "beklager, du må være innlogget for å kunne bokmerke innlegg" created: "du har bokmerket dette innlegget" @@ -258,6 +266,7 @@ nb_NO: profile: "Profil" mute: "Demp" edit: "Rediger innstillinger" + download_archive: "Last ned mine innlegg" new_private_message: "Ny Privat Melding" private_message: "Privat melding" private_messages: "Meldinger" @@ -326,8 +335,10 @@ nb_NO: error: "Det oppsto en feil ved endring av din e-postadresse. Kanskje den adressen allerede er i bruk?" success: "Vi har sendt en e-post til den adressen. Vennligst følg meldingens instruksjoner for bekreftelse." change_avatar: + title: "Bytt profilbilde" gravatar: "Gravatar, basert på" refresh_gravatar_title: "Oppdater din Gravatar" + letter_based: "Systemtildelt profilbilde" uploaded_avatar: "Egendefinert bilde" uploaded_avatar_empty: "Legg til egendefinert bilde" upload_title: "Last opp bilde" @@ -437,6 +448,7 @@ nb_NO: none: "Du har ikke invitert noen hit enda. Du kan sende individuelle invitasjoner, eller invitere en gruppe folk på en gang ved å laste opp en fil med flere invitasjoner." text: "Masseinvitasjon fra fil" uploading: "Laster opp..." + success: "Fil lastet opp. Du vil bli varslet med en privat melding når prosessen er fullført." error: "En feil oppsto ved opplastingen av '{{filename}}': {{message}}" password: title: "Passord" @@ -444,10 +456,13 @@ nb_NO: common: "Det passordet er for vanlig." ok: "Passordet ditt ser bra ut" instructions: "Minst %{count} tegn." + associated_accounts: "Innloggingsforsøk" ip_address: title: "Siste IP-adresse" registration_ip_address: title: "Registreringens IP-adresse." + avatar: + title: "Profilbilde" title: title: "Tittel" filters: @@ -569,6 +584,9 @@ nb_NO: github: title: "med GitHub" message: "Autentiserer med GitHub (sørg for at du tillater pop-up vindu)" + apple_international: "Apple/International" + google: "Google" + twitter: "Twitter" composer: add_warning: "Dette er en offisiell advarsel." posting_not_on_topic: "Du svarer på emnet \"{{title}}\", men for øyeblikket ser du på et annet emne." @@ -694,7 +712,7 @@ nb_NO: 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: "Avvis nye" + dismiss_new: "Lest" toggle: "Veksle mellom massevelging av emner" actions: "Massehandlinger" change_category: "Endre Kategori" @@ -1209,10 +1227,13 @@ nb_NO: topic_statuses: warning: help: "Dette er en offisiell advarsel." + bookmarked: + help: "Du lagret dette emnet" locked: help: "dette emnet er låst; det aksepterer ikke lenger nye svar" unpinned: title: "Løsgjort" + 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" @@ -1428,12 +1449,16 @@ nb_NO: edit: "Rediger Grupper" refresh: "Last inn på nytt" new: "Ny" + selector_placeholder: "oppgi brukernavn" name_placeholder: "Gruppenavn, ingen mellomrom, samme regler som for brukernavn" about: "Rediger gruppemedlemskap og navn her." group_members: "Gruppemedlemmer" delete: "Slett" delete_confirm: "Slette denne grupper?" delete_failed: "Unable to delete group. If this is an automatic group, it cannot be destroyed." + delete_member_confirm: "Fjern '%{username}' fra '%{group}' gruppen?" + name: "Navn" + add: "Legg til" api: generate_master: "Generer Master API-nøkkel" none: "Det er ingen aktive API-nøkler akkurat nå." @@ -1975,6 +2000,8 @@ nb_NO: with_post: %{username} for innlegg i %{link} with_post_time: %{username} for innlegg i %{link} - %{time} with_time: %{username} - %{time} + emoji: + name: "Navn" lightbox: download: "last ned" search_help: diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index 3e151fd6c1..4b2e8fdc60 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -84,6 +84,7 @@ nl: other: "%{count} dagen geleden" share: topic: 'deel een link naar deze topic' + post: 'bericht #%{postNumber}' close: 'sluit' twitter: 'deel deze link op Twitter' facebook: 'deel deze link op Facebook' @@ -142,6 +143,12 @@ nl: topic_count: "Topics" post_count: "Berichten" user_count: "Gebruikers" + contact: "Neem contact op" + bookmarked: + title: "Bewaar" + help: + bookmark: "Klik om deze topic te bewaren" + unbookmark: "Klik om deze topic niet meer te bewaren" bookmarks: not_logged_in: "sorry, je moet ingelogd zijn om berichten aan je favorieten toe te kunnen voegen" created: "je hebt dit bericht aan je favorieten toegevoegd" @@ -246,14 +253,18 @@ nl: organisation: Organisatie phone: Telefoon other_accounts: "Andere accounts met dit IP-adres" + delete_other_accounts: "Verwijder %{count}" username: "gebruikersnaam" + read_time: "leestijd" topics_entered: "topics ingevoerd" + post_count: "# berichten" confirm_delete_other_accounts: "Weet je zeker dat je deze accounts wil verwijderen?" user: said: "{{username}}:" profile: "Profiel" mute: "Negeer" edit: "Wijzig voorkeuren" + download_archive: "Download mijn berichten" new_private_message: "Nieuw persoonlijk bericht" private_message: "Privébericht" private_messages: "Berichten" @@ -321,8 +332,10 @@ nl: error: "Het veranderen van je e-mailadres is mislukt. Misschien is deze al in gebruik?" success: "We hebben een mail gestuurd naar dat adres. Volg de bevestigingsinstructies in die mail." change_avatar: + title: "Wijzig je profiel afbeelding" gravatar: "Gravatar, gebaseerd op" refresh_gravatar_title: "Laad je Gravatar opnieuw" + letter_based: "Door systeem toegekende profiel afbeelding" uploaded_avatar: "Eigen afbeelding" uploaded_avatar_empty: "Voeg een eigen afbeelding toe" upload_title: "Upload je afbeelding" @@ -330,15 +343,33 @@ nl: image_is_not_a_square: "Let op: we hebben je afbeelding bijgesneden; het is geen vierkant." change_profile_background: title: "Profielachtergrond" + instructions: "Profiel achtergronden worden gecentreerd en hebben een standaard breedte van 850px." + change_card_background: + instructions: "Achtergrond afbeeldingen worden gecentreerd en hebben een standaard breedte van 590px." email: title: "E-mail" + instructions: "Nooit publiekelijk vertonen" + ok: "We sturen een email ter bevestiging" + invalid: "Vul een geldig email adres in " + authenticated: "Je email is geauthenticeerd door {{provider}}" name: title: "Naam" + instructions: "Je volledige naam (optioneel)" + too_short: "Je naam is te kort" + ok: "Je naam ziet er goed uit" username: title: "Gebruikersnaam" + instructions: "Uniek, geen spaties, kort" + short_instructions: "Mensen kunnen naar je verwijzen als @{{username}}." + available: "Je gebruikersnaam is beschikbaar." + global_match: "E-mail hoort bij deze gebruikersnaam" global_mismatch: "Is al geregistreerd. Gebruikersnaam {{suggestion}} proberen?" not_available: "Niet beschikbaar. Gebruikersnaam {{suggestion}} proberen?" + too_short: "Je gebruikersnaam is te kort." + too_long: "Je gebruikersnaam is te lang." checking: "Kijken of gebruikersnaam beschikbaar is..." + enter_email: 'Gebruikersnaam gevonden. Vul het bijbehorende emailadres in.' + prefilled: "Je e-mailadres komt overeen met je geregistreerde gebruikersnaam." locale: title: "Interfacetaal" instructions: "De taal waarin het forum wordt getoond. Deze verandert als je de pagina herlaadt." diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index 62296ea4cb..67e286c3d0 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -161,6 +161,12 @@ ru: post_count: "Сообщения" user_count: "Пользователи" contact: "Контакты" + contact_info: "В случае возникновения критической ошибки или срочного дела, касающегося этого сайта, свяжитесь с нами по адресу %{contact_email}." + bookmarked: + title: "Избранное" + help: + bookmark: "Добавить в избранное" + unbookmark: "Убрать из Избранного" bookmarks: not_logged_in: "пожалуйста, войдите, чтобы добавлять сообщения в закладки" created: "вы добавили это сообщение в закладки" @@ -352,8 +358,10 @@ ru: error: "Произошла ошибка. Возможно, этот e-mail уже используется?" success: "На указанныю почту отправлено письмо с инструкциями." change_avatar: + title: "Изменить фон профиля" gravatar: "На основе Gravatar" refresh_gravatar_title: "Обновить ваш Gravatar" + letter_based: "Фон профиля по умолчанию" uploaded_avatar: "Собственный аватар" uploaded_avatar_empty: "Добавить собственный аватар" upload_title: "Загрузка собственного аватара" @@ -371,6 +379,10 @@ ru: ok: "Мы вышлем вам письмо для подтверждения" invalid: "Введите действующий адрес электронной почты" authenticated: "Ваш адрес электронной почты подтвержден {{provider}}" + frequency: + zero: "Получать уведомления о новых непрочитанных сообщениях незамедлительно." + one: "Получать уведомления о новых сообщениях, если они не прочитаны в течении одной минуты" + other: "Получать уведомления о новых сообщениях, если они не прочитаны в течении {{count}} минут(ы)" name: title: "Имя" instructions: "Ваше полное имя (опционально)" @@ -463,6 +475,7 @@ ru: none: "Вы еще никого не приглашали сюда. Вы можете отправить индивидуальные приглашения или пригласить группу людей сразу загрузив групповой файл приглашений." text: "Групповое приглашение из файла" uploading: "Загрузка..." + success: "Файл успешно загружен, вы получите уведомление через личные сообщения, когда процесс будет завершен." error: "В процессе загрузки файла '{{filename}}' произошла ошибка: {{message}}" password: title: "Пароль" @@ -475,6 +488,8 @@ ru: title: "Последний IP адрес" registration_ip_address: title: "IP адрес регистрации" + avatar: + title: "Фон профиля" title: title: "Заголовок" filters: @@ -1284,11 +1299,13 @@ ru: help: "Тема закрыта; в ней больше нельзя отвечать" unpinned: title: "Откреплена" + help: "Эта тема не прилеплена; она будет отображаться в обычном порядке" pinned_globally: title: "Закреплена глобально" help: "Эта тема закреплена глобально и будет отображаться в начале всех списков" pinned: title: "Закреплена" + help: "Тема закреплена; она будет показана вверху соответствующего раздела" archived: help: "Тема заархивирована и не может быть изменена" invisible: @@ -1582,14 +1599,29 @@ ru: title: "Откатить базу данных к предыдущему рабочему состоянию" confirm: "Вы уверены, что хотите откатить базу данных к предыдущему рабочему состоянию?" export_csv: + success: "Процедура экспорта начата, вы получите уведомление через личные сообщения, когда процесс будет завершен." failed: "Экспорт не удался. Пожалуйста, проверьте логи." + rate_limit_error: "Записи могут быть загружены один раз в день, пожалуйста, попробуйте еще раз завтра." button_text: "Экспорт" + button_title: + user: "Экспортировать список пользователей в CSV файл." + staff_action: "Экспортировать полный журнал действий персонала в CSV-файл." + screened_email: "Экспортировать список email-адресов в CSV формате." + screened_ip: "Экспортировать список IP в CSV формате." + screened_url: "Экспортировать список URL-адресов в CSV формате." customize: title: "Оформление" long_title: "Стили и заголовки" css: "CSS" header: "Заголовок" + top: "Топ" footer: "нижний колонтитул" + head_tag: + text: "" + title: "HTML код, который будет добавлен перед тегом ." + body_tag: + text: "" + title: "HTML код, который будет добавлен перед тегом ." override_default: "Не использовать стандартную таблицу стилей" enabled: "Разрешить?" preview: "как будет" @@ -1707,6 +1739,7 @@ ru: do_nothing: "ничего не делать" staff_actions: title: "Действия персонала" + instructions: "Клиекните по имени пользователя и действиям для фильтрации списка. Кликните по аватару для перехода на страницу пользователя." clear_filters: "Показать все" staff_user: "Персонал" target_user: "Целевой пользователь" @@ -1763,6 +1796,7 @@ ru: add: "Добавить" roll_up: text: "Конвертация" + title: "Создает новую запись бана подсети если уже имеется хотя бы 'min_ban_entries_for_roll_up' записей." logster: title: "Журнаш ошибок" impersonate: @@ -1940,6 +1974,7 @@ ru: external_username: "Псевдоним" external_name: "Имя" external_email: "E-mail" + external_avatar_url: "URL фона профиля" user_fields: title: "Поля пользователя" help: "Добавить поля, которые пользователи смогут заполнять." @@ -2058,11 +2093,12 @@ ru: with_post_time: %{username} за сообщение в %{link} в %{time} with_time: %{username} в %{time} emoji: - title: "Emoji" - add: "Добавить новый" - name: "Имя" + title: "Иконки" + help: "Добавить новые смайлики-emoji, которые будут доступны всем. (Подсказка: можно перетаскивать несколько файлов за раз)" + add: "Добавить новую иконку" + name: "Название" image: "Изображение" - delete_confirm: "Вы уверены, что хотите удалить :%{name}:?" + delete_confirm: "Вы уверены, что хотите удалить иконку :%{name}:?" lightbox: download: "загрузить" search_help: @@ -2083,6 +2119,7 @@ ru: back: 'u Назад' up_down: 'k/j Переместить выделение ↑ ↓' open: 'o или Enter Открыть выбранную тему' + next_prev: 'shift+j/shift+k Следующая/предыдущая секция' application: title: 'Приложение' create: 'c Создать новую тему' @@ -2096,9 +2133,12 @@ ru: dismiss_topics: 'x, t Отложить темы' actions: title: 'Действия' - bookmark_topic: 'f Добавить в Избранное' + bookmark_topic: 'f Добавить тему в закладки' + pin_unpin_topic: 'shift+p Закрепить/Открепить тему' + share_topic: 'shift+s Поделиться темой' share_post: 's Поделиться сообщением' reply_as_new_topic: 't Ответить в новой связанной теме' + reply_topic: 'shiftr+r Ответить на тему' reply_post: 'r Ответить на сообщение' quote_post: 'q Цитировать сообщение' like: 'l Лайкнуть сообщение' diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml index 0697e95f03..765fb11171 100644 --- a/config/locales/client.tr_TR.yml +++ b/config/locales/client.tr_TR.yml @@ -128,6 +128,11 @@ tr_TR: user_count: "Kullanıcılar" contact: "İletişim" contact_info: "Bu siteyi etkileyen kritik bir problem ya da acil bir durum oluştuğunda, lütfen %{contact_email} adresi üzerinden bizimle iletişime geçin." + bookmarked: + title: "İşaretle" + help: + bookmark: "Bu konuyu işaretlemek için tıklayın" + unbookmark: "Bu konunun işaretini kaldırmak için tıklayın" bookmarks: not_logged_in: "üzgünüz, gönderileri işaretleyebilmek için giriş yapmanız gerekiyor" created: "bu gönderiyi işaretlediniz" @@ -307,8 +312,10 @@ tr_TR: error: "E-posta adresinizi değiştirirken bir hata oluştu. Belki bu adres zaten kullanımdadır?" success: "Adresinize bir e-posta gönderdik. Lütfen onaylama talimatlarını uygulayınız." change_avatar: + title: "Profil resminizi değiştirin" gravatar: "Gravatar, baz alındı" refresh_gravatar_title: "Avatarınızı Yenileyin" + letter_based: "Sistem tarafından verilen profil resmi" uploaded_avatar: "Özel resim" uploaded_avatar_empty: "Özel resim ekleyin" upload_title: "Resminizi yükleyin" @@ -427,6 +434,8 @@ tr_TR: title: "Son IP Adresi" registration_ip_address: title: "Kayıt Anındaki IP Adresi" + avatar: + title: "Profil Resmi" title: title: "Başlık" filters: @@ -1166,11 +1175,13 @@ tr_TR: help: "Bu konu kapatıldı; artık yeni cevaplar kabul edilmiyor" unpinned: title: "Başa tutturma kaldırıldı" + help: "Bu konu sizin için başa tutturulmuyor; normal sıralama içerisinde görünecek" 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" archived: help: "Bu başlık arşive kaldırıldı; donduruldu ve değiştirilemez" invisible: @@ -1469,7 +1480,14 @@ tr_TR: long_title: "Site Özelleştirmeleri" css: "CSS" header: "Başlık" + top: "En Popüler" footer: "Altbilgi" + head_tag: + text: "" + title: " etiketinden önce eklenecek HTML" + body_tag: + text: "" + title: " etiketinden önce eklenecek HTML" override_default: "Standart stil sayfasını eklemeyin" enabled: "Etkinleştirildi mi?" preview: "önizleme" @@ -1587,6 +1605,7 @@ tr_TR: do_nothing: "hiçbir şey yapma" staff_actions: title: "Görevli Aksiyonları" + instructions: "Kullanıcı adları ve aksiyonlara tıklayarak listeyi filtrele. Profil resimlerine tıklayarak kullanıcı sayfalarına git." clear_filters: "Hepsini Göster" staff_user: "Görevli Kullanıcı" target_user: "Hedef Kullanıcı" @@ -1807,6 +1826,7 @@ tr_TR: external_username: "Kullanıcı adı" external_name: "İsim" external_email: "E-posta" + external_avatar_url: "Profil Resmi URLsi" user_fields: title: "Kullanıcı Alanları" help: "Kullanıcıların doldurabileceği alanlar ekleyin." diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index fb0961d581..9fc3220715 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -86,7 +86,7 @@ zh_CN: age: "年龄" joined: "加入时间:" admin_title: "管理" - flags_title: "旗帜" + flags_title: "标记" show_more: "显示更多" show_help: "帮助" links: "链接" @@ -126,6 +126,13 @@ zh_CN: topic_count: "主题" post_count: "帖子" user_count: "用户" + contact: "联系我们" + contact_info: "在遇到影响站点的重大错误或者紧急事件时,请通过 %{contact_email} 联系我们。" + bookmarked: + title: "书签" + help: + bookmark: "点击收藏此主题" + unbookmark: "点击这里将主题移除书签" bookmarks: not_logged_in: "抱歉,你需要先登录才能给帖子加书签" created: "你已经为此贴添加书签" @@ -305,8 +312,10 @@ zh_CN: error: "抱歉在修改你的电子邮箱时发生了错误,可能此邮箱已经被使用了?" success: "我们已经发送了一封确认信到此邮箱地址,请按照邮箱内指示完成确认。" change_avatar: + title: "更改你的头像" gravatar: "Gravatar头像,基于:" refresh_gravatar_title: "刷新你的头像" + letter_based: "系统分配的头像" uploaded_avatar: "自定义图片" uploaded_avatar_empty: "添加自定义图片" upload_title: "上传图片" @@ -425,6 +434,8 @@ zh_CN: title: "最后使用的 IP 地址" registration_ip_address: title: "注册 IP 地址" + avatar: + title: "头像" title: title: "头衔" filters: @@ -1158,15 +1169,19 @@ zh_CN: topic_statuses: warning: help: "这是一个正式的警告。" + bookmarked: + help: "你已经收藏了此主题" locked: help: "本主题已关闭;不再接受新的回复" unpinned: title: "解除置顶" + help: "主题已经解除置顶;它将以默认顺序显示" pinned_globally: title: "全局置顶" help: "本主题已置顶;它将始终显示在它所属分类的顶部" pinned: title: "置顶" + help: "本主题已置顶;它将始终显示在它所属分类的顶部" archived: help: "本主题已归档;即已经冻结,无法修改" invisible: @@ -1465,7 +1480,14 @@ zh_CN: long_title: "站点定制" css: "CSS" header: "头部" + top: "顶部" footer: "底部" + head_tag: + text: "" + title: "将在 标签前插入的 HTML" + body_tag: + text: "" + title: "将在 标签前插入的 HTML" override_default: "覆盖缺省值?" enabled: "启用?" preview: "预览" @@ -1583,6 +1605,7 @@ zh_CN: do_nothing: "无操作" staff_actions: title: "管理人员操作" + instructions: "点击用户名和操作可以过滤列表。点击头像可以访问用户个人页面。" clear_filters: "显示全部" staff_user: "管理人员" target_user: "目标用户" @@ -1803,6 +1826,7 @@ zh_CN: external_username: "用户名" external_name: "名字" external_email: "电子邮件" + external_avatar_url: "头像URL" user_fields: title: "用户属性" help: "增加用户能填写的字段。" @@ -1961,6 +1985,7 @@ zh_CN: dismiss_topics: 'x, t 解除主题提示' actions: title: '动作' + bookmark_topic: 'f 收藏主题' pin_unpin_topic: 'shift+p 置顶/截至置顶主题' share_topic: 'shift+s 分享主题' share_post: 's 分享帖子' diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml index 02f87010f9..655e3aae1f 100644 --- a/config/locales/client.zh_TW.yml +++ b/config/locales/client.zh_TW.yml @@ -68,6 +68,7 @@ zh_TW: other: "%{count} 天前" share: topic: '在此話題內分享連結' + post: '文章 #%{postNumber}' close: '關閉' twitter: '在 Twitter 分享此連結' facebook: '在 Facebook 分享此連結' @@ -125,6 +126,13 @@ zh_TW: topic_count: "討論話題" post_count: "文章" user_count: "用戶" + contact: "聯絡我們" + contact_info: "當網站發生嚴重錯誤或緊急事件,請聯絡我們 %{contact_email}" + bookmarked: + title: "書籤" + help: + bookmark: "加入書籤" + unbookmark: "移除書籤" bookmarks: not_logged_in: "抱歉,你必須先登入才能將文章加上書籤" created: "你已將此文章加上書籤" @@ -304,8 +312,10 @@ zh_TW: error: "修改你的電子郵件地址時發生錯誤,可能此電子郵件地址已經有人使用?" success: "我們已經寄出一封郵件至此電子郵件地址,請遵照說明進行確認。" change_avatar: + title: "設定個人資料圖片" gravatar: "Gravatar, based on" refresh_gravatar_title: "重新整理你的 Gravatar 頭像" + letter_based: "系統分配的個人資料圖片" uploaded_avatar: "自訂圖片" uploaded_avatar_empty: "新增一張自訂圖片" upload_title: "上傳你的圖片" @@ -424,6 +434,8 @@ zh_TW: title: "最近的 IP 位址" registration_ip_address: title: "註冊之 IP 位址" + avatar: + title: "個人資料圖片" title: title: "標題" filters: @@ -550,6 +562,7 @@ zh_TW: twitter: "Twitter" emoji_one: "Emoji One" composer: + emoji: "Emoji :smile:" add_warning: "這是正式警告。" posting_not_on_topic: "你想要回覆哪個討論話題?" saving_draft_tip: "正在儲存" @@ -1070,6 +1083,8 @@ zh_TW: delete: '刪除分類' create: '新分類' save: '儲存分類' + slug: '分類目錄' + slug_placeholder: '(選填) 在 url 加上虛線' creation_error: 建立分類時發生錯誤。 save_error: 儲存分類時發生錯誤。 name: "分類名稱" @@ -1154,15 +1169,19 @@ zh_TW: topic_statuses: warning: help: "這是正式警告。" + bookmarked: + help: "已將此討論話題加入書籤" locked: help: "此討論話題已關閉,不再接受回覆" unpinned: title: "取消釘選" + help: "此討論話題已取消置頂,將會以預設順序顯示。" pinned_globally: title: "全區置頂" help: "此討論話題已全區置頂,將顯示在所有討論話題列表的最上方" pinned: title: "已釘選" + help: "此討論話題已置頂,將顯示在它所屬分類話題列表的最上方" archived: help: "此討論話題已封存,已被凍結無法再修改" invisible: @@ -1368,6 +1387,7 @@ zh_TW: edit: "編輯群組" refresh: "重新整理" new: "建立" + selector_placeholder: "輸入用戶名稱" name_placeholder: "群組名稱,不可含有空白字元,使用戶名稱的規則相同" about: "請在此編輯你的群組成員與名稱" group_members: "群組成員" @@ -1375,6 +1395,8 @@ zh_TW: delete_confirm: "刪除此群組?" delete_failed: "無法刪除群組,自動建立的群組無法刪除。" delete_member_confirm: "從 '%{group}' 群組刪除 '%{username}' ?" + name: "名稱" + add: "加入" add_members: "新增成員" api: generate_master: "產生主 API 金鑰" @@ -1458,7 +1480,14 @@ zh_TW: long_title: "網站客製化" css: "CSS" header: "標頭" + top: "精選" footer: "頁尾" + head_tag: + text: "" + title: "HTML 將會置於 之前" + body_tag: + text: "" + title: "HTML 將會置於 之前" override_default: "不要保含標準樣式" enabled: "已啟用?" preview: "預覽" @@ -1576,6 +1605,7 @@ zh_TW: do_nothing: "無動作" staff_actions: title: "工作人員動作" + instructions: "點擊使用者名稱會過濾列表,點擊使用者圖片則會連結至使用者頁面。" clear_filters: "全部顯示" staff_user: "工作人員用戶" target_user: "目標用戶" @@ -1796,6 +1826,7 @@ zh_TW: external_username: "用戶名稱" external_name: "名稱" external_email: "電子郵件" + external_avatar_url: "個人資料圖片 URL" user_fields: title: "使用者欄位" help: "增加欄位讓你的使用者可以填寫" @@ -1917,6 +1948,7 @@ zh_TW: title: "Emoji" help: "新增新的emoji供所有人使用。(提示:一次拖放多個檔案)" add: "新增emoji" + name: "名稱" image: "圖片" delete_confirm: "你確定要刪除 :%{name}: emoji ?" lightbox: @@ -1953,6 +1985,7 @@ zh_TW: dismiss_topics: 'x, t 解除主題的提示' actions: title: '行動' + bookmark_topic: 'f 收藏討論話題' pin_unpin_topic: 'shift p 置頂/取消置頂' share_topic: 'shift s 分享討論話題' share_post: 's 分享文章' diff --git a/config/locales/server.cs.yml b/config/locales/server.cs.yml index 93b6782f5a..b307649250 100644 --- a/config/locales/server.cs.yml +++ b/config/locales/server.cs.yml @@ -29,7 +29,7 @@ cs: topics: "Témata" posts: "příspěvky" loading: "Nahrávám" - powered_by_html: 'Systém běží na Discourse, nejlepší zážitek je se zapnutým JavaScriptem' + powered_by_html: 'Systém běží na Discourse, nejlépe funguje se zapnutým JavaScriptem' log_in: "Přihlásit se" via: "%{username} přes %{site_name}" is_reserved: "je rezervováno" diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml index 5081ca13b3..93c4674d10 100644 --- a/config/locales/server.de.yml +++ b/config/locales/server.de.yml @@ -358,7 +358,7 @@ de: description: 'Dieser Beitrag hat nichts mit dem Thema zu tun wie es im Titel und ersten Beitrag steht. Deshalb sollte er woanders hin verschoben werden.' long_form: 'dies als am Thema vorbei gemeldet' spam: - title: 'Werbung' + title: 'Reklame' description: 'Dieser Beitrag besteht effektiv nur aus Werbung, die nicht als solche ausgewiesen ist. Er trägt nichts zum aktuellen Gespräch bei.' long_form: 'dies als Spam gemeldet' inappropriate: @@ -908,6 +908,7 @@ de: incorrect_username_email_or_password: "Benutzername, Mailadresse oder Passwort falsch" wait_approval: "Danke fürs Registrieren. Wir werden dich benachrichtigen, sobald dein Benutzerkonto freigeschaltet wurde." active: "Dein Konto ist nun freigeschaltet und einsatzbereit." + activate_email: "

    Fast fertig! Eine E-Mail mit einem Aktivierungscode wurde an Deine Mailadresse %{email} gesendet.
    Dort findest Du die Anleitung, um Deinen Zugang zu aktivieren.

    Solltest Du die Mail nicht vorfinden, kontrolliere bitte auch den Spamverdachts-Ordner. Gegebenenfalls kannst Du Dir von hier eine weitere Aktivierungs-Mail zusenden lassen.

    " not_activated: "Du kannst Dich noch nicht anmelden. Wir haben dir eine Aktivierungs-E-Mail geschickt. Bitte folge zunächst den Anweisungen in dieser E-Mail, um dein Konto zu aktivieren." not_allowed_from_ip_address: "Von dieser IP-Adresse können Sie sich nicht als %{username} anmelden." suspended: "Du kannst Dich bis zum %{date} nicht anmelden." @@ -936,7 +937,7 @@ de: invite_mailer: subject_template: "%{invitee_name} hat dich zum Thema '%{topic_title}' auf %{site_domain_name} eingeladen" text_body_template: | - %{invitee_name} hat Dich dazu eingeladen an einer Diskussion teilzunehmen: + %{invitee_name} hat Dich dazu eingeladen, an einer Diskussion teilzunehmen: > **%{topic_title}** > @@ -948,7 +949,7 @@ de: %{invite_link} - Dies ist eine EInladung von einem vertrautem Benutzer. Du kannst deshalb sofort auf die Diskussion antworten. + Dies ist eine Einladung von einem vertrautem Benutzer. Du kannst deshalb sofort auf die Diskussion antworten. invite_forum_mailer: subject_template: "%{invitee_name} hat dich eingeladen %{site_domain_name} beizutreten" text_body_template: | @@ -1007,17 +1008,24 @@ de: %{notes} flags_reminder: + flags_were_submitted: + one: "Folgende Markierungen wurden währen der letzten Stunden vorgenommen." + other: "Folgende Markierungen wurden währen der letzten %{count} Stunden vorgenommen." please_review: "Bitte überprüfe sie." post_number: "Beitrag" + how_to_disable: 'Die Benachrichtungen für markierte Beiträge kann deaktiviert oder in ihrer Häufigkeit geändert werden mittels des Wertes unter "notify about flags after".' subject_template: one: "Eine Markierung wartet auf Bearbeitung" other: "%{count} Markierungen warten auf Bearbeitung" + flag_reasons: + spam: "Dein Beitrag wurde als **Spam** geflaggt: Die Community denkt, dass es sich um Werbung handelt und nicht nützlich oder für das Diskussionsthema relevant ist." flags_dispositions: agreed: "Danke, dass du uns Bescheid gegeben hast. Wir sind auch der Meinung, dass es ein Problem gibt und sehen uns das an." agreed_and_deleted: "Danke, dass du uns Bescheid gegeben hast. Wir waren auch der Meinung, dass es ein Problem gibt und haben den Beitrag gelöscht." disagreed: "Danke für deine Meldung. Wir sehen uns das an." deferred: "Danke für deine Meldung. Wir sehen uns das an." deferred_and_deleted: "Danke für deine Meldung. Wir haben den Beitrag entfernt." + temporarily_closed_due_to_flags: "Der Diskussionsstrang wurde wegen zahlreicher Meldungen aus der Community vorübergehend geschlossen." system_messages: post_hidden: subject_template: "Beitrag wegen Meldungen aus der Community versteckt" @@ -1075,6 +1083,10 @@ de: Du hast nicht die notwendige Vertrauensstufe, um neue Themen über diese E-Mail-Adresse zu erstellen. Wenn du glaubst, dass das ein Irrtum ist, dann kontaktiere einen Mitarbeiter. email_reject_no_account: subject_template: "E-Mail-Problem -- Unbekannter Account" + text_body_template: | + Es tut uns leid, aber deine E-Mail-Nachricht an %{destination} (titled %{former_title}) konnte nicht verarbeitet werden! + + Es ist kein Account mit dieser E-Mail-Adresse bekannt. Versuche die Nachricht von einer anderen, m Forum registrierten E-Mail-Adresse zu verschicken oder kontaktiere einen Mitarbeiter. email_reject_empty: subject_template: "E-Mail-Problem -- Kein Inhalt" email_reject_parsing: diff --git a/config/locales/server.fi.yml b/config/locales/server.fi.yml index c6bc44ce63..e67e6a0905 100644 --- a/config/locales/server.fi.yml +++ b/config/locales/server.fi.yml @@ -39,6 +39,7 @@ fi: messages: too_long_validation: "on rajoitettu %{max} merkkiin; sinä syötit %{length} merkkiä." invalid_boolean: "Totuusarvomuuttuja ei kelpaa" + taken: "on jo varattu. (ryhmien nimet eivät ole merkkikokoriippuvaisia)" embed: load_from_remote: "Viestin lataamisessa tapahtui virhe." bulk_invite: @@ -195,6 +196,8 @@ fi: base: warning_requires_pm: "Voit liittää varoituksia vain yksityisviesteihin." too_many_users: "Voit lähettää varoituksia vain yhdelle käyttäjälle kerrallaan." + cant_send_pm: "Pahoittelut, et voi lähettää tälle käyttäjälle yksityisviestiä." + no_user_selected: "Sinun täytyy valita kelpaava käyttäjä." user: attributes: password: @@ -537,7 +540,14 @@ fi: s3_backup_config_warning: 'Palvelin on konfiguroitu lataamaan varmuuskopiot s3:een, mutta vähintään yksi arvoista s3_access_key_id, s3_secret_access_key tai s3_backup_bucket ei ole asetettu. Päivitä arvot sivuston asetuksissa.Voit lukea lisätietoja oppaasta "How to set up image uploads to S3?".' image_magick_warning: 'Palvelin on konfiguroitu luomaan esikatselukuvia suurista kuvista, mutta ImageMagickia ei ole asennettu. Asenna ImageMagick paketinhallinnasta tai lataamalla uusin versio.' failing_emails_warning: 'Sähköpostitehtävä on epäonnistunut %{num_failed_jobs} kertaa. Tarkista config/discourse.conf tiedostosta, että sähköpostiasetukset ovat kunnossa. Tarkastele epäonnistuneita tehtäviä Sidekiqissa.' + default_logo_warning: "Aseta sivustolle logot. Päivitä logo_url, logo_small_url, ja favicon_url sivuston asetuksissa." + contact_email_missing: "Aseta yhteystietoihin sähköpostiosoite, josta sinut saa tavoitettua sivustoon liittyvissä kiireellisissä asioissa. Voit syöttää sen sivuston asetuksissa." + contact_email_invalid: "Sivuston sähköpostiosoite ei kelpaa. Muokkaa sitä sivuston asetuksissa." + title_nag: "Aseta keskustelupalstalle nimi. Voita muokata sitä sivuston asetuksissa." + site_description_missing: "Aseta yhden lauseen mittainen kuvaus palstasta, joka näkyy hakutuloksissa. Muokkaa asetusta site_description sivuston asetuksissa." consumer_email_warning: "Sivusto on konfiguroitu käyttämään Gmailia (tai muuta yksityishenkilöille suunnattua sähköpostipalvelua) sähköpostin lähettämiseen. Gmail rajoittaa lähetettävien sähköpostien määrää. Harkitse sähköpostipalveluntarjoajaa, kuten mandrill.com varmistaaksesi sähköpostin lähetyksen." + site_contact_username_warning: "Aseta käyttäjänimi, jonka nimissä palsta automaattiset yksityisviestit lähetetään. Muokkaa asetusta site_contact sivuston asetuksissa." + notification_email_warning: "Palstan sähköpostien lähettäjäosoite ei ole kelvollinen, josta johtuen sähköpostit eivät mene perille luotettavasti. Aseta toimiva sähköpostiosoite kohtaan notification_email sivuston asetuksissa." content_types: education_new_reply: title: "Uuden käyttäjän opastus: Ensimmäinen vastaus" @@ -586,6 +596,9 @@ fi: allow_duplicate_topic_titles: "Salli ketjun luominen identtisellä otsikolla." unique_posts_mins: "Kuinka monta minuuttia, kunnes käyttäjä voi luoda uudestaan saman sisältöisen viestin" educate_until_posts: "Näytä uuden käyttäjän ohje, kun käyttäjä alkaa kirjoittamaan ensimmäistä (n) viestiään viestikenttään." + title: "Palstan nimi, käytetään title tagissa." + site_description: "Kuvaile sivustoa yhdellä lauseella, jota käytetään meta description tagissa." + contact_email: "Sähköpostiosoite, josta sivuston henkilökunnan saa tavoitettua. Lähetetään sivuston kriittiset ilmoitukset ja näytetään /about sivun yhteystietona." queue_jobs: "VAIN KEHITTÄJILLE! VAROITUS! Oletusarvona tehtävät asetetaan sidekiq jonoon. Jos poistetaan käytöstä, sivusto ei enää toimi." crawl_images: "Lataa linkatut kuvat kuvan dimensioiden määrittamiseksi." download_remote_images_to_local: "Muunna linkatut kuvat liitetiedostoiksi lataamalla ne; tämä estää kuvien rikkoontumisen vanhentuneiden linkkien vuoksi." @@ -605,8 +618,11 @@ fi: post_excerpt_maxlength: "Viestin katkelman merkkien maksimimäärä." post_onebox_maxlength: "Discourse-viestin Onebox-esikatselun merkkien maksimimäärä." onebox_domains_whitelist: "Lista verkkotunnuksista, joilta sallitaan onebox-esikatselut; näiden tunnusten pitäisi tukea OpenGraphia tai oEmbedia. Testaa ne osoitteessa http://iframely.com/debug" + logo_url: "Logo sivun vasemmassa ylälaidassa. Jos jätetään tyhjäksi, näytetään sen tilalla palstan nimi." digest_logo_url: "Vaihtoehtoinen logo, jota käytetään tiivistelmäsähköpostin yläreunassa. Jos asetus jätetään tyhjäksi 'logo_url' käytetään tämän sijaan. esim: http://esimerkki.fi/logo.png" + logo_small_url: "Logo sivun vasemmassa ylälaidassa, kun sivua on vieritetty alas. Jos jätetään tyhjäksi, näytetään koti-merkki." favicon_url: "Palstan favicon, katso http://fi.wikipedia.org/wiki/Favicon" + mobile_logo_url: "Logo mobiilisivun vasemmassa ylälaidassa. Jos jätetään tyhjäksi, näytetään sen tilalla palstan nimi." apple_touch_icon_url: "Applen laitteiden käyttämä ikoni, Suositeltu koko on 144px kertaa 144px." notification_email: "Sähköpostiosoite, josta kaikki tärkeät järjestelmän lähettämät sähköpostiviestit lähetetään. Verkkotunnuksen SPF, DKIM ja reverse PTR tietueiden täytyy olla kunnossa, jotta sähköpostit menevät perille." email_custom_headers: "Pystyviivalla eroteltu lista mukautetuista sähköpostin tunnisteista" @@ -650,6 +666,7 @@ fi: post_menu_hidden_items: "Piilotettavat painikkeet viestin valikosta, kunnes '...' klikataan." share_links: "Mitkä painikkeet näytetään Jaa-valikossa ja missä järjestyksessä." track_external_right_clicks: "Seuraa pois sivustolta vieviä linkkejä, jotka avataan hiiren oikealla näppäimellä (esim. avaa uudessa välilehdessä) oletuksena poistettu käytöstä, koska tämä kirjoittaa URL:n uudelleen" + site_contact_username: "Henkilökuntaan kuuluvan käyttäjä, jonka nimissä kaikki automaattiset yksityisviestit lähetetään. Jos jätetään tyhjäksi, oletuksena on System-käyttäjä." send_welcome_message: "Lähetä kaikille uusille käyttäjille yksityinen tervetuliaisviesti, jossa on pikakäyttöopas." suppress_reply_directly_below: "Älä näytä vastausten lukumäärää viestissä, jos ainoa vastaus on seuraavassa viestissä." suppress_reply_directly_above: "Älä näytä vastauksena-painiketta viestin yläreunassa, jos viestissä on vastattu vain edelliseen viestiin." @@ -679,6 +696,8 @@ fi: max_username_length: "Käyttäjänimen enimmäispituus merkeissä. VAROITUS: KÄYTTÄJÄT JOIDEN KÄYTTÄJÄTUNNUS ON PIDEMPI KUIN TÄMÄ EIVÄT VOI ENÄÄ KIRJAUTUA SISÄÄN MUUTOKSEN JÄLKEEN." min_password_length: "Salasanan vähimmäispituus." block_common_passwords: "Älä salli salasanoja, jotka ovat 10 000 yleisimmän salasanan joukossa." + enable_sso: "Ota käyttöön single sign on ulkopuolisen sivuston kautta (HUOM: voi estää kaikkia kirjautumasta sisään, jos asetukset eivät ole kunnossa ja poistaa kutsut käytöstä)" + enable_sso_provider: "Ota käyttöön Discourse SSO protokolla /session/sso_provider päätepisteessä, vaatii asetuksen sso_secret asettamista." sso_url: "single sign on endpointin URL" sso_secret: "Salausavain, jolla salataan/puretaan SSO tiedot, varmista että siinä on vähintään 10 merkkiä" sso_overrides_email: "Ohittaa paikallisen sähköpostiosoitteen SSO:n kautta saatavalla ulkopuolisella sähköpostillosoitteella (VAROITUS: eroavuuksia saattaa syntyä johtuen paikallisten sähköpostien normalisoinnista)" @@ -803,6 +822,8 @@ fi: min_ban_entries_for_roll_up: "Kun Kääri-painiketta painetaan, luodaan IP-porttikielloista aliverkon kattavia kieltoja jos kieltoja on asettu vähintään (N) määrä." max_age_unmatched_emails: "Poista osumattomat seulotut sähköpostiosoitteet (N) päivän jälkeen." max_age_unmatched_ips: "Poista osumattomat seulotut IP-osoitteet (N) päivän jälkeen." + num_flaggers_to_close_topic: "Minimimäärä uniikkeja liputtajia, joka vaaditaan ketjun automaattiseksi keskeyttämiseksi." + num_flags_to_close_topic: "Minimimäärä aktiivisia lippuja, joka vaaditaan ketjun automaattiseksi keskeyttämiseksi." reply_by_email_enabled: "Ota käyttöön vastaukset sähköpostin avulla." reply_by_email_address: "Saapuvan sähköpostin sapluuna sähköpostivastauksiin, esimerkiksi: %{reply_key}@reply.example.com tai replies+%{reply_key}@example.com" disable_emails: "Estä Discoursea lähettämästä mitään sähköpostia" @@ -866,6 +887,8 @@ fi: show_create_topics_notice: "Jos palstalla on vähemmän kuin 5 julkista ketjua, huomauta ylläpitäjiä ketjujen luonnista." vacuum_db_days: "Aja VACUUM FULL ANALYZE vapauttaaksesi tilaa tietokantaan migraatioiden jälkeen (aseta 0 ottaaksesi pois käytöstä)" prevent_anons_from_downloading_files: "Estä kirjautumattomia käyttäjiä lataamasta liitetiedostoja. VAROITUS: tämä estää viestin liitettyjen muiden tiedostojen, kuin kuvien käyttämisen sivustolla." + enable_emoji: "Ota emoji käyttöön" + emoji_set: "Minkälaisa emojia haluaisit käyttää?" errors: invalid_email: "Sähköpostiosoite ei kelpaa." invalid_username: "Tällä nimellä ei löydy käyttäjää." @@ -918,6 +941,10 @@ fi: other: "Siirsin %{count} viestiä ketjuun: %{topic_link}" change_owner: post_revision_text: "Omistus vaihdettu käyttäjältä %{old_user} käyttäjälle %{new_user}" + emoji: + errors: + name_already_exists: "Pahoittelut, nimi '%{name}' on jo käytössä toisella emojilla." + error_while_storing_emoji: "Pahoittelut, tämän emojin tallentamisessa tapahtui virhe." topic_statuses: archived_enabled: "Tämä ketju on arkistoitu. Se on jäädytetty eikä sitä voi muuttaa millään tavalla." archived_disabled: "Ketjun arkistointi on poistettu, se ei ole enää jäädytettynä ja sitä voi taas muuttaa." @@ -962,6 +989,7 @@ fi: errors: "%{errors}" not_available: "Ei saatavissa. Kokeile %{suggestion}?" something_already_taken: "Jotain meni pieleen. Ehkäpä tämä käyttäjänimi tai sähköpostiosoite on jo rekisteröity. Kokeile salasana unohtui -linkkiä." + omniauth_error: "Pahoittelut, tilisi tunnistautumisessa tapahtui virhe. Ehkäpä et hyväksynyt valtuutusta?" omniauth_error_unknown: "Jotain meni pieleen kirjautumisesti käsittelyssä, ole hyvä ja yritä uudelleen." new_registrations_disabled: "Uusien tilien luonti ei ole tällä hetkellä sallittu." password_too_long: "Salasanan enimmäispituus on 200 merkkiä." @@ -1111,12 +1139,18 @@ fi: subject_template: one: "Yksi liputus odottaa käsittelyä" other: "%{count} liputusta odottaa käsittelyä" + flag_reasons: + off_topic: "Viestisi liputettiin **asiaan kuulumattomaksi**: se ei kuulu tähän ketjuun, jonka aiheen määrittelee aloitusviesti ja otsikko." + inappropriate: "Viestisi liputettiin **asiattomaksi**: se on loukkaava, herjaava tai rikkoo [palstan sääntöjä](/guidelines)." + spam: "Viestisi liputettiin **roskapostiksi**: siinä on ilmoitus, joka on luonteeltaan mainostamista sen sijaan, että olisi hyödyllinen tai relevantti tässä keskustelussa." + notify_moderators: "Viestisi liputettiin **valvojalle tiedoksi**: siinä on jotain, johon henkilökunnan pitäisi puuttua." flags_dispositions: agreed: "Kiitos kun toit asian tietoomme. Olemme samaa mieltä ongelmasta ja selvitämme sitä." agreed_and_deleted: "Kiitos kun toit asian tietoomme. Olemme samaa mieltä ongelmasta ja olemme poistaneet kyseisen viestin." disagreed: "Kiitos kun toit asian tietoomme. Selvitämme asiaa." deferred: "Kiitos kun toit asian tietoomme. Selvitämme asiaa." deferred_and_deleted: "Kiitos kun toit asian tietoomme. Olemme poistaneet kyseisen viestin." + temporarily_closed_due_to_flags: "Tämä ketju on väliaikaisesti suljettu suuren lippumäärän vuoksi" system_messages: post_hidden: subject_template: "Viesti on piilotettu liputuksen johdosta" @@ -1203,8 +1237,38 @@ fi: Uusien käyttäjien oikeuksia rajoitetaan turvallisussyistä. Osallistumalla ansaitset yhteisön luottamuksen ja rajoitukset poistuvat automaattisesti. Korkeammilla [luottamustasoilla](https://meta.discourse.org/t/what-do-user-trust-levels-do/4924) saat vielä enemmän valtuuksia joiden avulla voit auttaa yhteisön ylläpitämisessä. welcome_user: subject_template: "Tervetuloa sivustolle %{site_name}!" + text_body_template: | + Kiitos liittymisestä sivustolle %{site_name}, ja tervetuloa! + + %{new_user_tips} + + Me uskomme [sivistyneeseen kanssakäymiseen](%{base_url}/guidelines). + + (Jos haluat keskustella yksityisesti [henkilökunnan jäsenten](/about) kanssa, vastaa vain tähän viestiin) + + Toivottavasti viihdyt! welcome_invite: subject_template: "Tervetuloa sivustolle %{site_name}!" + text_body_template: | + Kiitos kutsun hyväksymisestä sivustolle %{site_name} -- tervetuloa! + + Olemme luoneet sinulle automaattisesti käyttäjänimen: **%{username}**, mutta voit vaihtaa sen koska hyvänsä [käyttäjäprofiilistasi][prefs]. + + Kirjautuaksesi sisään uudestaan, joko: + + 1. Kirjaudu sisään millä metodilla haluat -- kunhan se on yhdistetty **samaan sähköpostiosoitteeseen,** kuin jonne vastaanotit alkuperäisen kutsun. Muuten emme voi tietää, että se todella olet sinä! + + 2. Luo salasana [käyttäjäprofiilissasi][prefs], ja käytä sitä kirjautumiseen. + + %{new_user_tips} + + Me uskomme [sivistyneeseen kanssakäymiseen](%{base_url}/guidelines). + + (Jos haluat keskustella yksityisesti [henkilökunnan jäsenten](/about) kanssa, vastaa vain tähän viestiin) + + Toivottavasti viihdyt! + + [prefs]: %{user_preferences_url} backup_succeeded: subject_template: "Varmuuskopiointi suoritettu onnistuneesti" text_body_template: "Varmuuskopiointi onnistui.\nVoit ladata varmuuskopioin avaamalla valikon [ylläpito > varmuuskopiot](/admin/backups)." @@ -1244,6 +1308,17 @@ fi: ``` %{logs} ``` + csv_export_succeeded: + subject_template: "Datan vieminen on tehty" + text_body_template: | + Datan vienti onnistui. + + %{file_name} + + Linkki tiedostoon on voimassa 48 tuntia. + csv_export_failed: + subject_template: "Datan vienti epäonnistui" + text_body_template: "Pahoittelemme, mutta datan vientisi epäonnistui. Tarkasta lokit tai ota yhteyttä henkilökuntaan." email_reject_trust_level: subject_template: "Sähköpostiongelma -- Riittämätön luottamustaso" text_body_template: | @@ -1484,6 +1559,23 @@ fi: %{base_url}/users/authorize-email/%{email_token} signup_after_approval: subject_template: "Sinut on hyväksytty sivustolle %{site_name}!" + text_body_template: | + Tervetuloa sivustolle %{site_name}! + + Henkilökunnan edustaja on hyväksynyt tilisi sivustolla %{site_name}. + + Klikkaa alla olevaa linkkiä vahvistaaksesi ja aktivoidaksesi uuden tilisit: + %{base_url}/users/activate-account/%{email_token} + + Jos linkkiä ei voi klikata, yritä kopioida ja liittää se selaimen osoiteriville. + + %{new_user_tips} + + Me uskomme [sivistyneeseen kanssakäyntiin](%{base_url}/guidelines). + + (Jos haluat keskustella yksityisesti [henkilökunnan jäsenten](/about) kanssa, vastaa vain tähän viestiin) + + Toivottavasti viihdyt! signup: subject_template: "[%{site_name}] Vahvista uusi tilisi" text_body_template: | @@ -1542,6 +1634,9 @@ fi: guidelines: "Ohjeet" privacy: "Yksityisyys" edit_this_page: "Muokkaa tätä sivua" + csv_export: + boolean_yes: "Kyllä" + boolean_no: "Ei" static_topic_first_reply: | Muokkaa ketjun aloitusviestiä muokataksesi sivun %{page_name} sisältöä. guidelines_topic: @@ -1550,3 +1645,25 @@ fi: title: "Käyttöehdot" privacy_topic: title: "Rekisteriseloste" + static: + search_help: | +

    Vinkkejä

    +

    +

      +
    • Otsikon vastaavuutta korostetaan, joten jos olet epävarma, etsi otsikkoa
    • +
    • Uniikit, harvinaiset sanat tuovat aina parhaan lopputuloksen
    • +
    • Rajoita hakusi tietylle alueelle, tiettyyn käyttäjään tai ketjuun aina kun mahdollista.
    • +
    +

    +

    Komennot:

    +

    + + + + + +
    order:viewsorder:latest
    status:openstatus:closedstatus:archivedstatus:norepliesstatus:singleuser
    category:foouser:foo
    in:likesin:postedin:watchingin:trackingin:private
    +

    +

    + sateenkaari category:puistot status:open order:latest etsii ketjuja, joissa mainitaan sana "sateenkaari" alueella "puistot" joita ei ole suljettu tai arkistoitu, järjestettynä viimeisen viestin mukaan. +

    diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml index 04586e3359..78ebc84b93 100644 --- a/config/locales/server.fr.yml +++ b/config/locales/server.fr.yml @@ -259,7 +259,7 @@ fr: regular: title: "membre" leader: - title: "régulier" + title: "habitué" elder: 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" @@ -359,7 +359,7 @@ fr: change_email: confirmed: "Votre adresse de courriel a été mise à jour." please_continue: "Continuer vers %{site_name}" - error: "Il y a eu une erreur lors du changement de votre adresse de courriel. Elle est peut-être déjà utilisée ?" + error: "Il y a eu une erreur lors de la modification de votre adresse de courriel. Elle est peut-être déjà utilisée ?" activation: action: "Activer votre compte" already_done: "Désolé, ce lien de confirmation n'est plus valide. Votre compte est peut-être déjà activé ?" @@ -393,7 +393,7 @@ fr: email_title: 'Un message dans "%{title}" requière l''attention d''un modérateur' email_body: "%{link}\n\n%{message}" bookmark: - title: 'Signets' + title: 'Signet' description: 'Ajouter aux signets' long_form: 'ajouté aux signets' like: @@ -424,7 +424,7 @@ fr: user_must_edit: '

    Ce message a été signalé par la communauté et temporairement masqué.

    ' archetypes: regular: - title: "Sujet régulier" + title: "Sujet normal" banner: message: make: "Ce sujet est maintenant un gros titre. Il sera affiché en haut de chaque page jusqu’à qu'il soit ignoré par un utilisateur." @@ -535,10 +535,14 @@ fr: s3_backup_config_warning: 'Le serveur est configuré pour envoyer les sauvegardes sur S3, mais l''un des paramètres suivants n''est pas renseigné: s3_access_key_id, s3_secret_access_key or s3_backup_bucket. Allez dans les Paramètres et mettez les à jour. Voir "Comment mettre en place une sauvegarde sur S3?" pour en savoir plus.' image_magick_warning: 'Le serveur est configuré pour créer des aperçus des grandes images, mais ImageMagick n''est pas installé. Installez ImageMagick en utilisant votre gestionnaire de paquets favori ou bien allez ici pour télécharger la dernière version.' failing_emails_warning: 'Il y a %{num_failed_jobs} tâches d''envois de courriel en erreur. Vérifiez votre fichier config/discourse.conf et assurez-vous de la conformité des paramètres du serveur de courriel. Regarder les processus en échec dans Sidekiq.' - default_logo_warning: "Changez le logo de votre site. Mettez à jour les paramètres du site suivant : logo_url, logo_small_url et favicon_url." - contact_email_invalid: "L'email de contact du site est invalide. Mettez-le à jour dans les paramètre du site." + default_logo_warning: "Modifier le logo de votre site. Mettez à jour les paramètres du site suivant : logo_url, logo_small_url et favicon_url." + contact_email_missing: "Saisissez l'adresse courriel de contact pour votre site web afin qu'on puisse vous contacter pour des problèmes urgents sur votre site. Mettez-le à jour dans les Paramètres du site." + contact_email_invalid: "L'adresse courriel de contact du site est invalide. Mettez-le à jour dans les paramètre du site." title_nag: "Saisissez le nom de votre site. Modifier le titre sous Paramètres du site." + site_description_missing: "Saisissez une phrase courte qui décrira votre site et apparaîtra dans les moteurs de recherche. Mettez site_description à jour dans les Paramètres du site." consumer_email_warning: "Votre site est configuré pour envoyer les courriels en utilisant Gmail (ou un autre site de courriels pour utilisateur standard). Gmail limite le nombre d'emails que vous pouvez envoyer. Nous vous conseillons d'utiliser un autre service d'envoi de courriels afin d'assurer une meilleure délivrabilité." + site_contact_username_warning: "Saisissez le pseudo d'un responsable sympathique à partir de laquele seront envoyés les messages privés importants. Mettez à jour site_contact_username dans les Paramètres du site." + notification_email_warning: "Les courriels de notification ne serot pas envoyés depuis une adresse de courriel valide sur votre domaine ; l'envoie des courriels sera aléatoire et peu fiable. Veuillez saisir une adresse de courriel locale dans notification_email dans les Paramètres du site." content_types: education_new_reply: title: "Education du nouvel utlisateur : premières réponses" @@ -589,6 +593,7 @@ fr: educate_until_posts: "Lors de la rédaction des (n) premiers nouveaux sujets de l'utilisateur, afficher le panneau d'aide à la saisie." title: "Le nom du site, utilisé dans la balise title." site_description: "Décrivez ce site en une seule phrase, utilisée dans le tag meta description." + contact_email: "Adresse de courriel du contact clef pour ce site. Ne sera utilisée que pour des notifications critiques, et aussi sur la formulaire de contact sur la page 'a propos'." queue_jobs: "SEULEMENT POUR LES DÉVELOPPEURS ! ATTENTION ! Par défaut, empiler les travaux dans sidekiq. Si désactivé, votre site sera cassé." crawl_images: "Récupère les images provenant de sources tierces pour y insérer les dimensions corrects (hauteur et largeur)" download_remote_images_to_local: "Transformer les images distantes en images locales en les téléchargeant; cela permet d'éviter les liens morts." @@ -608,8 +613,11 @@ fr: post_excerpt_maxlength: "Longueur maximale d'un extrait / résumé de message." post_onebox_maxlength: "Longueur maximale d'un message emboîté en nombre de caractères." onebox_domains_whitelist: "Liste des noms de domaines autorisés pour les 'onebox'. Ces domaines doivent supporter OpenGraph or oEmbed. Testez les sur http://iframely.com/debug" + logo_url: "L'image logo en haut à gauche de votre site ; si vide, le titre du site sera affiché." digest_logo_url: "Le logo alternatif qui sera utilisé dans les courriels de votre site. Si non renseigné, alors `logo_url` sera utilisé, par exemple : http://example.com/logo.png" + logo_small_url: "La petite image logo affiché en haut à gauche du site quand la page est défilée vers le bas. Si vide, une glyphe 'maison' sera affichée." favicon_url: "Le favicon de votre site, voir http://fr.wikipedia.org/wiki/Favicon" + mobile_logo_url: "L'image logo avec position fixe en haut à gauche de votre site en mode mobile. Si vide, le titre du site sera affiché." apple_touch_icon_url: "Icône utilisée pour les appareils d'Apple. Taille recommandée 144 px par 144 px." notification_email: "L'adresse de courriel dans le champs De qui sera utilisée pour envoyer les courriels systèmes essentiels. Le nom de domaine spécifié doit avoir les informations SPF, DKIM et PTR inversé renseignés correctement pour que le courriel arrive à destination." email_custom_headers: "Une liste délimité par des (|) pipes d'entêtes de courriel" @@ -653,6 +661,7 @@ fr: post_menu_hidden_items: "Les éléments du menu qui seront cachés par défaut jusqu’à extension du menu." share_links: "Choix des éléments qui doivent apparaître dans la fenêtre de partage, et leur ordre." track_external_right_clicks: "Suivi des clics sur les liens externes (ex: ouverture dans un nouvel onglet) désactivé par défaut car nécessite une ré-écriture de toutes les urls" + site_contact_username: "Un pseudo de responsable à partir de laquelle seront envoyés les message privés. Si laissé vide, le compte système sera utilisé." send_welcome_message: "Envoyer à tous les nouveaux utilisateurs un message privé avec un guide de démarrage rapide." suppress_reply_directly_below: "Ne pas afficher le panneau extensible des réponses d'un message quand la seule réponse est juste en dessous ce dernier." suppress_reply_directly_above: "Ne pas afficher 'en réponse à' sur un message quand la seule réponse est juste en dessus de ce dernier." @@ -662,7 +671,7 @@ fr: topics_per_period_in_top_summary: "Nombre de meilleurs sujets affichés dans le résumé par défaut des meilleurs sujets." topics_per_period_in_top_page: "Nombre de meilleurs sujets affichés lorsqu'on sélectionne \"Voir plus\" des meilleurs sujets." redirect_users_to_top_page: "Rediriger automatiquement les nouveaux utilisateurs et les longues absences sur la page Top." - show_email_on_profile: "Afficher l'adresse du courriel de l'utilisateur sur leur page utilisateur (seulement visible pour l'utilisatuer et les équipes techniques)" + show_email_on_profile: "Afficher l'adresse du courriel de l'utilisateur sur leur page utilisateur (seulement visible pour l'utilisateur et les équipes techniques)" email_token_valid_hours: "Les jetons (tokens) de Mot de passe oublié / Activation de comptes sont valides (n) jours." email_token_grace_period_hours: "Les jetons (tokens) de Mot de passe oublié / Activation de comptes sont encore valides pour une période de grâce de (n) heures après leur expiration." enable_badges: "Activer le système de badges" @@ -682,6 +691,8 @@ fr: max_username_length: "Longueur maximum des pseudos en caractères. ATTENTION : TOUS LES UTILISATEURS QUI DÉPASSE CETTE LIMITE NE POURRONS PLUS ACCÉDER AU SITE. " min_password_length: "Longueur minimale du mot de passe." block_common_passwords: "Ne pas autoriser les mots de passe qui font parti des 10 000 les plus utilisés." + enable_sso: "Activer l'authentication unique via un site externe (AVERTISSEMENT : si activé, peut empêcher toute connexion en cas de mauvaise configuration ; désactive aussi les invitations)" + enable_sso_provider: "Implémenter le procotole Discourse SSO à /session/sso_provider, requiert sso_secret" sso_url: "URL de l'authentification unique" sso_secret: "Chaîne de caractères secrète utilisée pour crypter/décrypter les informations SSO, assurez-vous qu'elle est de 10 caractères ou plus" sso_overrides_email: "Surcharger les adresses de courriel locales avec les adresses de courriel externes d'un SSO (ATTENTION: Des écarts peuvent se produire due aux règles locales sur les adresses de courriel)" @@ -718,7 +729,7 @@ fr: rate_limit_new_user_create_post: "Après avoir posté un message, les nouveaux utilisateurs doivent attendre (n) secondes avant de pouvoir en poster un nouveau." max_likes_per_day: "Nombre maximum de J'aime par utilisateur chaque jour." max_flags_per_day: "Nombre maximum de signalement par utilisateur chaque jour." - max_bookmarks_per_day: "Nombre maximum de signets par utilisateur chaque jour." + max_bookmarks_per_day: "Nombre maximum de signets par utilisateur et par jour." max_edits_per_day: "Nombre maximum de modifications par utilisateur chaque jour." max_stars_per_day: "Nombre maximum de favoris par utilisateur chaque jour." max_topics_per_day: "Nombre maximum de sujet qu'utilisateur peut créer par jour." @@ -804,8 +815,8 @@ fr: levenshtein_distance_spammer_emails: "Lorsque des courriels correspondent à des spammer, la différence du nombre de caractère permettra toujours une correspondance floue." max_new_accounts_per_registration_ip: "S'il y a déjà (n) Niveau de confiance 0 comptes à partir de cette adresse IP ( et aucun n'est un membre du personnel ou au NC2 ou ultérieure), ne plus accepter de nouvelles inscriptions de cette IP." min_ban_entries_for_roll_up: "En cliquant sur le bouton Consolider, une liste d'au moins (N) adresses interdites sera remplacée par une plage de sous réseau." - max_age_unmatched_emails: "Effacer les emails listées qui ne correspondent pas après (N) jours" - max_age_unmatched_ips: "Effacer les adresses IP listées qui ne correspondent pas après (N) jours" + max_age_unmatched_emails: "Effacer les adresses de courriel sous surveillance sans correspondance après (N) jours" + max_age_unmatched_ips: "Effacer les adresses IP sous surveillenace sans correspondance après (N) jours" num_flaggers_to_close_topic: "Nombre minimum de signalements uniques requis pour automatiquement suspendre un sujet pour intervention" num_flags_to_close_topic: "Nombre minimum de signalements uniques requis pour automatiquement suspendre un sujet pour intervention" reply_by_email_enabled: "Activer les réponses aux sujets via courriel." @@ -828,16 +839,16 @@ fr: relative_date_duration: "Nombre de jours après la création d'un message à partir desquels les dates seront affichées en absolu (20 Fév) plutôt qu'en relatif (7j)" delete_user_max_post_age: "Interdire la suppression des utilisateurs dont le premier message est date de plus de (n) jours." delete_all_posts_max: "Le nombre maximum de messages qui peuvent être supprimés en une seule fois avec le bouton Supprimer tous les messages. Si un utilisateur a plus que ce nombre de messages, ses messages ne pourront pas être supprimés en une seule fois et l'utilisateur ne pourra pas être supprimé." - username_change_period: "Le nombre de jours après l'enregistrement pour pouvoir changer de pseudo (0 pour empêcher le changement de pseudo)." - email_editable: "Autoriser les utilisateurs à changer leur adresse de courriel après l'inscription." + username_change_period: "Le nombre de jours après l'enregistrement pour pouvoir modifier le pseudo (0 pour empêcher la modification de pseudo)." + email_editable: "Autoriser les utilisateurs à modifier leur adresse de courriel après l'inscription." logout_redirect: "Adresse de redirection après déconnexion, ex: http://monsite.com/logout" allow_uploaded_avatars: "Autoriser les utilisateurs à envoyer leurs propres avatars." allow_animated_avatars: "Autoriser les utilisateurs à utiliser des avatars ens gif animés. ATTENTION: il est hautement recommandé d'exécuter la tâche rake avatars:refresh après avoir changé ce paramètre." allow_animated_thumbnails: "Créer des aperçus animés pour les gifs animés." - automatically_download_gravatars: "Télécharger les gravatars pour les utilisateurs lors de la création de compte ou du changement de courriel." + automatically_download_gravatars: "Télécharger les gravatars pour les utilisateurs lors de la création de compte ou de la modification de courriel." digest_topics: "Nombre maximum de sujets à afficher dans le courriel de résumé." digest_min_excerpt_length: "Taille minimum du résumé des messages dans les courriels, en caractères." - default_digest_email_frequency: "A quelle fréquence les utilisateurs reçoivent-ils les courriels par défaut. Ils peuvent changer ce paramétrage dans leur préférences." + default_digest_email_frequency: "A quelle fréquence les utilisateurs reçoivent-ils les courriels par défaut. Ils peuvent modifier ce paramétrage dans leur préférences." default_external_links_in_new_tab: "Les liens externes s'ouvrent dans un nouvel onglet. Les utilisateurs peuvent modifier ceci dans leurs préférences." detect_custom_avatars: "Vérifier ou pas que les utilisateurs ont envoyés des avatars personnalisés." max_daily_gravatar_crawls: "Nombre maximum de fois que Discourse vérifiera Gravatar pour des avatars personnalisés en une journée." @@ -852,7 +863,7 @@ fr: enable_names: "Autoriser l'affichage des noms complets des utilisateurs. Désactiver pour cacher les noms complets." display_name_on_posts: "Afficher le nom complet de l'utilisateur dans ses messages en plus de son @pseudo." invites_per_page: "Afficher les invitations sur la page de l'utilisateur." - short_progress_text_threshold: "Si le nombre de messages dans un sujet dépasse ce nombre, la barre de progression affichera uniquement le numéro de message actuel. Si vous modifiez la largeur de la barre de progression, vous devez peut être changer cette valeur." + short_progress_text_threshold: "Si le nombre de messages dans un sujet dépasse ce nombre, la barre de progression affichera uniquement le numéro de message actuel. Si vous modifiez la largeur de la barre de progression, vous devrez peut-être modifier cette valeur." default_code_lang: "Coloration syntaxique par défaut appliquée à la syntaxe des langages de programmation des blocs de code GitHub (lang-auto, Ruby, Python, etc)" warn_reviving_old_topic_age: "Lorsque quelqu'un commence à répondre à un sujet dont la dernière réponse est vielle de plusieurs jours, un avertissement sera affiché. Désactiver la fonctionnalité en indiquant: 0." autohighlight_all_code: "Forcer la mise en évidence de tout les textes dans les balises code, même si ils ne correspondent à aucun langage de programmation." @@ -872,7 +883,7 @@ fr: vacuum_db_days: "Exécuter VACUUM FULL ANALYZE pour récupérer de l'espace dans la base de donnée après une migration (0 pour désactivé) " prevent_anons_from_downloading_files: "Refuser le téléchargement de pièces jointes aux utilisateurs anonymes. ATTENTION: cela empêchera de fonctionner les ressources envoyées en pièce jointe qui ne sont pas des images." enable_emoji: "Activer les emojis" - emoji_set: "Comment voulez-vous vos emoji ?" + emoji_set: "Comment aimeriez-vous vos emoji ?" errors: invalid_email: "Adresse de courriel invalide." invalid_username: "Il n'y a pas d'utilisateur ayant ce pseudo." @@ -936,30 +947,30 @@ fr: closed_disabled: "Ce sujet est maintenant ouvert. Les nouvelles réponses sont autorisées." autoclosed_enabled_days: one: "Cette discussion a été automatiquement fermée après un jour. Aucune réponse n'est permise dorénavant." - other: "Cette discussion a été automatiquement fermée après %{count} jours. Aucune réponse n'est permise dorénavant." + other: "Ce sujet a été automatiquement fermé après %{count} jours. Aucune réponse n'est permise dorénavant." autoclosed_enabled_hours: one: "Cette discussion a été automatiquement fermée après une heure. Aucune réponse n'est permise dorénavant." - other: "Cette discussion a été automatiquement fermée après %{count} heures. Aucune réponse n'est permise dorénavant." + other: "Ce sujet a été automatiquement fermé après %{count} heures. Aucune réponse n'est permise dorénavant." autoclosed_enabled_minutes: one: "Cette discussion a été automatiquement fermée après une minute. Aucune réponse n'est permise dorénavant." - other: "Cette discussion a été automatiquement fermée après %{count} minutes. Aucune réponse n'est permise dorénavant." + other: "Ce sujet a été automatiquement fermé après %{count} minutes. Aucune réponse n'est permise dorénavant." autoclosed_enabled_lastpost_days: one: "Cette discussion a été automatiquement fermée après un jour. Aucune réponse n'est permise dorénavant." - other: "Cette discussion a été automatiquement fermée après %{count} jours. Aucune réponse n'est permise dorénavant." + other: "Ce sujet a été automatiquement fermé après %{count} jours. Aucune réponse n'est permise dorénavant." autoclosed_enabled_lastpost_hours: one: "Cette discussion a été automatiquement fermée après 1 heure suivant le dernier commentaire. Aucune réponse n'est permise dorénavant." - other: "Cette discussion a été automatiquement fermée après %{count} heures suivant le dernier commentaire. Aucune réponse n'est permise dorénavant." + other: "Ce sujet a été automatiquement fermé après %{count} heures suivant le dernier commentaire. Aucune réponse n'est permise dorénavant." autoclosed_enabled_lastpost_minutes: one: "Cette discussion a été automatiquement fermée une minute après le dernier message. Aucune réponse n'est permise dorénavant." - other: "Cette discussion a été automatiquement fermée %{count} minutes après le dernier message. Aucune réponse n'est permise dorénavant." + other: "Ce sujet a été automatiquement fermé %{count} minutes après le dernier message. Aucune réponse n'est permise dorénavant." autoclosed_disabled: "Ce sujet est maintenant ouvert. Les nouvelles réponses sont autorisées." - autoclosed_disabled_lastpost: "Cette discussion est maintenant ouverte. Vous pouvez y participer." + autoclosed_disabled_lastpost: "Ce sujet est maintenant ouvert. Vous pouvez y participer." pinned_enabled: "Ce sujet est maintenant épinglé. Il apparaîtra en haut de sa catégorie jusqu'à ce qu'il soit désépinglé par un modérateur, ou individuellement par les utilisateurs eux-mêmes." pinned_disabled: "Ce sujet est maintenant dé-épinglé. Il n'apparaîtra plus en haut de sa categorie." pinned_globally_enabled: "Ce sujet est maintenant épinglé pour tous. Il apparaîtra en haut de sa catégorie et de chaque liste de sujets jusqu'à ce qu'il soit désépinglé par un modérateur, ou individuellement par les utilisateurs eux-mêmes." pinned_globally_disabled: "Ce sujet est maintenant désépinglé. Il n'apparaîtra plus en haut de sa catégorie." - visible_enabled: "Cette discussion est maintenant visible. Elle sera affichée dans la liste des discussions." - visible_disabled: "Cette discussion est maintenant invisible. Elle ne sera plus affichée dans la liste des discussions. Le seul moyen d'y accéder est par lien direct." + visible_enabled: "Ce sujet est maintenant visible. Il sera affiché dans la liste des sujets." + visible_disabled: "Ce sujet est maintenant invisible. Il ne sera plus affiché dans la liste des sujets. Le seul moyen d'y accéder est par lien direct." login: not_approved: "Votre compte n'a pas encore été approuvé. Vous serez notifié par courriel quand cela sera fait." incorrect_username_email_or_password: "pseudo, adresse de courriel ou mot de passe incorrect" @@ -973,6 +984,7 @@ fr: errors: "%{errors}" not_available: "Pas disponible. Essayez %{suggestion} ?" something_already_taken: "Quelque chose c'est mal passé. Peut-être que votre pseudo ou votre adresse de courriel est déjà enregistré ? Essayez le lien : j'ai oublié mon mot de passe." + omniauth_error: "Désolé, une erreur est survenue lors de l'autorisation de votre compte. Vous n'avez peut-être pas approuvé l'autorisation ?" omniauth_error_unknown: "Quelque chose s'est mal passé lors de votre connexion, merci de réessayer." new_registrations_disabled: "La création de nouveau compte n'est pas autorisé pour le moment." password_too_long: "Les mots de passe sont limités à 200 caractères." @@ -1029,7 +1041,7 @@ fr: text_body_template: | Merci d'avoir accepté notre invitation pour %{site_name} -- bienvenue ! - Pour vous reconnecter, cliquez sur le liens suivant et choisissez un mot de passe: + Pour vous reconnecter, cliquez sur le lien suivant et choisissez un mot de passe: %{base_url}/users/password-reset/%{email_token} test_mailer: subject_template: "[%{site_name}] Test de délivrabilité d'un courriel" @@ -1112,20 +1124,25 @@ fr: flags_reminder: flags_were_submitted: one: "Il y a un signalement qui a été soumis il y a plus de %{count} heures." - other: "Il y a des signalements qui ont été soumis il y a plus de %{count} heures." + other: "Ces signalements ont été soumis il y a plus de %{count} heures." please_review: "Veuillez examiner cela." post_number: "message" how_to_disable: 'Vous pouvez désactiver ou modifier la fréquence de ce courriel de rappel via le réglage "m''informer sur les signalements".' subject_template: one: "un signalement en attente de traitement" other: "%{count} signalements en attente de traitement." + flag_reasons: + off_topic: "Votre message a été signalé comme étant **hors sujet**: la communauté considère qu'il ne correspond au sujet en discussion, qui est fixé par son titre et son premier message." + inappropriate: "Votre message a été signalé comme étant **inapproprié**: la communauté considère que son contenu est offensif, abusif ou enfreint notre [règlement interne](/guidelines)." + spam: "Votre message a été signalé comme étant du **spam**: la communauté considère qu'il s'agit d'une publicité, ou du contenu trop promotionnel au lieu d'être utile et à propos du sujet en discussion." + notify_moderators: "Votre message a été **signalé aux modérateurs**: la communauté considère que quelque chose requiert une intervention d'un des responsables." flags_dispositions: agreed: "Merci de nous en informer. Nous sommes en accord avec votre signalement et nous travaillons à sa résolution." agreed_and_deleted: "Merci de nous en informer. Nous sommes en accord avec votre signalement et avons supprimé le message." disagreed: "Merci de nous en informer. Nous travaillons à sa résolution." deferred: "Merci de nous en informer. Nous travaillons à sa résolution." deferred_and_deleted: "Merci de nous en informer. Nous avons supprimé le message." - temporarily_closed_due_to_flags: "Ce sujet est temporairement clos dû à un trop grand nombres de signalements" + temporarily_closed_due_to_flags: "Ce sujet est temporairement clos dû à un trop grand nombre de signalements" system_messages: post_hidden: subject_template: "Message caché suite à un signalement par la communauté" @@ -1140,14 +1157,14 @@ fr: Plusieurs membres ont signalé ce message avant qu'il ne soit caché, donc veuillez prendre en compte leurs remarques pour revoir votre message. **Vous pouvez modifier le message après %{edit_delay} minutes, et il sera automatiquement ré-affiché.** Ceci augmentera votre niveau de confiance sur le forum. - Cependant, si le message est signalé par la communauté une seconde fois, il restera marqué jusqu’à l'intervention d'un modérateur -- et ils pourront prendre d'autres mesures y compris l'éventuelle suspension de votre compte. + Cependant, si le message est signalé par la communauté une seconde fois, il restera marqué jusqu’à l'intervention d'un modérateur – et ils pourront prendre d'autres mesures y compris l'éventuelle suspension de votre compte. Pour plus d'informations, merci de vous en référer aux [règles de la communauté](%{base_url}/guidelines). usage_tips: - text_body_template: "Ce message privé contient quelques astuces pour vous aider à démarrer rapidement.\n\n## Continuez de descendre\n\nIl n'y a pas de bouton Page Suivante ni de numéro de page – pour en lire plus, **il vous suffit de descendre !**\n\nLorsque de nouvelles réponses arrivent, elles apparaissent automatiquement. \n\n## Où suis-je ?\n\n- Pour la recherche, votre page d'utilisateur, ou le menu, utiliser les **boutons icônes en haut à droite**.\n\n- Dans la liste des sujets, le titre vous emmènera toujours vers le prochain message non lu. Utiliser les colonnes Activité ou Messages pour allez au premier ou au dernier message. \n\n- Lorsque vous lisez un sujet, retournez en haut de celui ↑ en cliquant sur le titre. Cliquez sur la barre de progression en bas à droite pour avoir une navigation complète, ou utilisez les touches Home et Fin. \n\n \n\n## Comment je réponds ?\n\n- Pour répondre au sujet dans sa globalité, utilisez le bouton \"Répondre\" tout en bas de la page.\n\n- Pour répondre à un message spécifique, utilisez le bouton \"Répondre\" sur le message.\n\n - Si vous voulez continuer le sujet dans une section différente, mais garder le lien entre votre sujet et le message qui vous l'a inspiré, utilisez la fonction Répondre en créant un nouveau sujet à droite de chaque message.\n\nPour citer quelqu'un dans votre message, sélectionnez le texte que vous voulez citer et appuyez sur un des boutons Répondre.\n\n\n\nPour mentionner le pseudo d'un utilisateur, commencez à taper `@` et une liste d'auto-complétion apparaîtra.\n\n\n\nConcernant les [icones Emoji](http://www.emoji.codes/), commencez par écrire `:` ou le traditionnel smiley `:)` :smile: \n\n## Que puis-je faire d'autre ?\n\nÀ la fin de chaque message il y a un ensemble de boutons pour les différentes actions possibles.\n\n\n\nPour faire savoir à quelqu'un que vous avez apprécié son message, cliquez sur le bouton *j'aime** en bas du message. Si vous voyez un problème avec un message, n'hésitez pas à cliquer sur le bouton **signaler**\ - \ pour avertir en privé l'auteur ou les modérateurs du contenu de ce message.\n\nVous pouvez aussi **partager** un liens vers un message ou l'ajouter à vos **signets** pour le retrouver sur votre page d'utilisateur. \n\n## Qui me parle ?\n\nQuand quelqu'un répond à votre message, vous cite, ou mentionne votre `@pseudo`, un nombre apparaitra immédiatement en haut à droite de la page. Utilisez-le pour consulter vos **notifications**.\n\n\n\nNe soyez pas inquiêt de manquer des notifications – \nLes réponses directes (et les messages privés) vous seront envoyés par courriel si vous n'êtes pas présent sur le site lorsqu'ils sont envoyés. \n\n## Quand est-ce qu'une conversation est nouvelle ?\n\nPar défaut, toutes les conversations de moins de deux jours sont considérées comme nouvelles, et chaque sujets auxquels vous avez participé (répondus, créés ou lu pendant un long moment) seront automatiquements suivis. \n\nVous verrez le badge bleu de nouveauté et le nombre de nouveaux messages au côté de ces discussions:\n\n\n\nVous pouvez modifier l'état du suivi des notifications d'une discussion en utilisant les boutons en bas de la discussion\ - \ (vous pouvez également le modiifer au niveau des catégories).\nPour changer la façon dont vous suivez les discussions, ou pour définir de nouveaux suivis, consultez [vos préférences utilisateurs](/my/preferences). \n\n## Quelles sont les choses que je ne peux pas faire ?\n\nLes nouveaux utilisateurs sont limités pour des raisons de sécurité. Plus vous participerez, plus vous gagnerez la confiance de la communauté et les restrictions disparaîtrons. A plus haut [niveau de confiance](https://meta.discourse.org/t/what-do-user-trust-levels-do/4924), vous gagnerez de nouvelles compétences pour nous aider à gérer la communauté. \n" + text_body_template: "Ce message privé contient quelques astuces pour vous aider à démarrer rapidement.\n\n## Descendez toujours\n\nIl n'y a pas de bouton Page Suivante ni de numéro de page – pour en lire plus, **il vous suffit de descendre !**\n\nLorsque de nouvelles réponses arrivent, elles apparaissent automatiquement. \n\n## Où suis-je ?\n\n- Pour la recherche, votre page d'utilisateur, ou le menu, utiliser les **boutons icônes en haut à droite**.\n\n- Dans la liste des sujets, le titre vous emmènera toujours vers le prochain message non lu. Utiliser les colonnes Activité ou Messages pour allez au premier ou au dernier message. \n\n- Lorsque vous lisez un sujet, retournez en haut de celui ↑ en cliquant sur le titre. Cliquez sur la barre de progression en bas à droite pour avoir une navigation complète, ou utilisez les touches Home et Fin. \n\n \n\n## Comment je réponds ?\n\n- Pour répondre au sujet dans sa globalité, utilisez le bouton \"Répondre\" tout en bas de la page.\n\n- Pour répondre à un message spécifique, utilisez le bouton \"Répondre\" sur le message.\n\n - Si vous voulez continuer le sujet dans une section différente, mais garder le lien entre votre sujet et le message qui vous l'a inspiré, utilisez la fonction Répondre en créant un nouveau sujet à droite de chaque message.\n\nPour citer quelqu'un dans votre message, sélectionnez le texte que vous voulez citer et appuyez sur un des boutons Répondre.\n\n\n\nPour mentionner le pseudo d'un utilisateur, commencez à taper `@` et une liste d'auto-complétion apparaîtra.\n\n\n\nConcernant les [icones Emoji](http://www.emoji.codes/), commencez par écrire `:` ou le traditionnel smiley `:)` :smile: \n\n## Que puis-je faire d'autre ?\n\nÀ la fin de chaque message il y a un ensemble de boutons pour les différentes actions possibles.\n\n\n\nPour faire savoir à quelqu'un que vous avez apprécié son message, cliquez sur le bouton **j'aime** en bas du message. Si vous voyez un problème avec un message, n'hésitez pas à cliquer sur le bouton **signaler**\ + \ pour avertir en privé l'auteur ou les modérateurs du contenu de ce message.\n\nVous pouvez aussi **partager** un lien vers un message ou l'ajouter à vos **signets** pour le retrouver sur votre page d'utilisateur. \n\n## Qui me parle ?\n\nQuand quelqu'un répond à votre message, vous cite, ou mentionne votre `@pseudo`, un nombre apparaitra immédiatement en haut à droite de la page. Utilisez-le pour consulter vos **notifications**.\n\n\n\nNe soyez pas inquiêt de manquer des notifications – \nLes réponses directes (et les messages privés) vous seront envoyés par courriel si vous n'êtes pas présent sur le site lorsqu'ils sont envoyés. \n\n## Quand est-ce qu'une conversation est nouvelle ?\n\nPar défaut, toutes les conversations de moins de deux jours sont considérées comme nouvelles, et chaque sujet auxquels vous avez participé (répondu, créé ou lu pendant un long moment) sera automatiquements suivi. \n\nVous verrez le badge bleu de nouveauté et le nombre de nouveaux messages au côté de ces sujets:\n\n\n\nVous pouvez modifier l'état du suivi des notifications d'une discussion en utilisant les boutons en bas de la discussion (vous\ + \ pouvez également le modiifer au niveau des catégories).\nPour modifier la façon dont vous suivez les sujets, ou pour définir de nouveaux suivis, consultez [vos préférences utilisateurs](/my/preferences). \n\n## Quelles sont les choses que je ne peux pas faire ?\n\nLes nouveaux utilisateurs sont limités pour des raisons de sécurité. Plus vous participerez, plus vous gagnerez la confiance de la communauté et les restrictions disparaîtront. A plus haut [niveau de confiance](https://meta.discourse.org/t/what-do-user-trust-levels-do/4924), vous gagnerez de nouvelles compétences pour nous aider à gérer la communauté. \n" welcome_user: subject_template: "Bienvenue sur %{site_name} !" text_body_template: | @@ -1157,10 +1174,12 @@ fr: Nous croyons au [comportement communautaire civilisé](%{base_url}/guidelines) en tous temps. + Si en tant que nouvel utilisateur vous avez besoin de communiquer de façon privée avec un [responsable](/about), répondez simplement à ce message privé. + Amusez-vous bien ! welcome_invite: subject_template: "Bienvenue sur %{site_name} !" - text_body_template: "Merci d'avoir accepté votre invitation sur %{site_name}, et bienvenue !\n\nNous vous avons automatiquement généré un pseudo : **%{username}**, mais vous pouvez le changer quand vous voulez en allant sur [votre profil][prefs].\n\nPour vous reconnecter, vous pouvez :\n\n1. Utiliser votre identifiant préféré -- mais votre **adresse de courriel** devra être la même que celle de l'invitation que vous avez reçue. Autrement, nous ne pourrons pas vous reconnaître.\n\n2. Créer un mot de passe unique sur votre [page de profil utilisateur][prefs], et vous connecter avec. \n\n%{new_user_tips}\n\nNous croyons au [comportement communautaire civilisé](%{base_url}/guidelines) en tous temps.\n\nAmusez-vous bien !\n\n[prefs] : %{user_preferences_url}\n" + text_body_template: "Merci d'avoir accepté votre invitation sur %{site_name}, et bienvenue !\n\nNous vous avons automatiquement généré un pseudo : **%{username}**, mais vous pouvez le changer quand vous voulez en allant sur [votre profil][prefs].\n\nPour vous reconnecter, vous pouvez :\n\n1. Utiliser n'importe quel des méthodes proposés sur l'écran d'accueil. **Utilisez toujours la même adresse de courriel que celle de l'invitation que vous avez reçue**. Sinon, nous ne pourrons pas vous reconnaître !\n\n2. Créer un mot de passe unique sur votre [page de profil utilisateur][prefs], et vous connecter avec. \n\n%{new_user_tips}\n\nNous croyons au [comportement communautaire civilisé](%{base_url}/guidelines) en tous temps.\n\nSi en tant que nouvel utilisateur vous avez besoin de communiquer de façon privée avec un [responsable](/about), répondez simplement à ce message privé.\n\nAmusez-vous bien !\n\n[prefs] : %{user_preferences_url}\n" backup_succeeded: subject_template: "Sauvegarde terminée avec succès" text_body_template: "La sauvegarde a été un succès.\nVisitez la section [Administration > sauvegardes](/admin/backups) pour télécharger votre dernière sauvegarde." @@ -1264,20 +1283,19 @@ fr: text_body_template: | Nous sommes désolé, mais l'envoi de votre courriel à destination de {destination} (intitulé %{former_title}) n'a pas fonctionné. - Aucune adresse de destination n'est reconnue. Veuillez vérifier que l'adresse du site est bien renseignée dans le champs To: (et non dans les champs C:: ou Bcc:), et que vous envoyez bien - à l'adresse de courriel fournie par l'équipe. + Aucune adresse de destination n'est reconnue. Veuillez vérifier que l'adresse du site est bien renseignée dans le champs To: (et non dans les champs C:: ou Bcc:), et que vous envoyez bien à l'adresse de courriel fournie par l'équipe. email_reject_topic_not_found: subject_template: "Erreur de courriel -- Le sujet est introuvable" text_body_template: | - Nous sommes désolé, mais votre email à destination de {destination} (intitulé %{former_title}) n'a pas fonctionné. + Nous sommes désolé, mais votre courriel à destination de {destination} (intitulé %{former_title}) n'a pas fonctionné. - Le sujet est introuvable. Si vous pouvez que c'est une erreur, contacter l’équipe du site. + Le sujet est introuvable et a peut-être été supprimé. Si vous considérez que c'est une erreur, contacter un responsable. email_reject_topic_closed: subject_template: "Erreur de courriel -- Le sujet est fermé" text_body_template: | - Nous sommes désolé, mais votre email à destination de {destination} (intitulé %{former_title}) n'a pas fonctionné. + Nous sommes désolé, mais votre courriel à destination de {destination} (intitulé %{former_title}) n'a pas fonctionné. - Le sujet est fermé. Si vous pouvez que c'est une erreur, contacter l’équipe du site. + Le sujet est fermé. Si vous considérez que c'est une erreur, contacter un responsable. email_error_notification: subject_template: "Problème de courriel - Erreur d'authentification POP3" text_body_template: | @@ -1414,8 +1432,8 @@ fr: click_here: "cliquez ici" from: "Résumé de %{site_name}" read_more: "Lire la suite" - more_topics: "Il y a eu %{new_topics_since_seen} nouvelles discussions." - more_topics_category: "Plus de nouvelles discussions:" + more_topics: "Il y a eu %{new_topics_since_seen} nouveaux sujets." + more_topics_category: "Plus de nouveaux sujets :" posts: one: "1 message" other: "%{count} messages" @@ -1449,7 +1467,7 @@ fr: text_body_template: | Bienvenue sur %{site_name} ! - Un membre de l'équipe a approuvé votre compte sur %{site_name}. + Un responsable a approuvé votre compte sur %{site_name}. Cliquez sur le lien suivant pour confirmer et activer votre nouveau compte : %{base_url}/users/activate-account/%{email_token} @@ -1459,7 +1477,7 @@ fr: Nous croyons au [comportement communautaire civilisé](%{base_url}/guidelines) en tous temps. - (Si vous voulez communiquer en privée avec [staff members](/about) en tant que nouveau membre, répondez à ce message privé.) + (Si en tant que nouveau membre vous voulez communiquer en privée avec [un responable](/about), répondez à ce message privé.) Amusez-vous bien ! signup: @@ -1527,6 +1545,103 @@ fr: Modifier le premier message de ce sujet pour changer le contenu de la page %{page_name}. guidelines_topic: title: "FAQ/Règlement" + body: |+ + + + ## [Ceci est un endroit civilisé, merci de le respecter](#civilized) + + Merci de traiter ce forum de discussion avec le même respect que vous pourriez avoir dans un parc public. Nous, aussi, sommes une ressource partagée par la communauté — un endroit pour partager des compétences, savoirs et intérêts au travers de conversations. + + Il ne s'agit pas de règles absolues, uniquement des aides au bon sens de notre communauté. Suivez ces instructions pour garder cet endroit propre et bien éclairé pour un débat public civilisé. + + + + ## [Améliorez la discussion](#improve) + + Aidez nous à faire de cette endroit un endroit idéal pour la discussion en améliorant constamment le débat d'une certaine façon, même minime. Si vous n'êtes pas sûr que votre message soit utile à la discussion ou pourrait nuire à son utilité, pensez à ce que vous voulez dire et essayer de nouveau plus tard. + + Les sujets abordés ici sont importants pour nous, et nous voulons que vous agissiez comme s'ils comptaient également pour vous. Soyez respectueux des sujets et des autres utilisateurs, même si vous êtes en désaccord avec une partie de ce qui est dit. + + Une façon d'améliorer la discussion est de découvrir celles qui sont déjà en cours. Essayez de passez un peu de temps à parcourir les sujets avant de répondre ou démarrer votre propre discussion. De fait, vous aurez de plus grandes chances de rencontrer d'autres personnes qui partagent vos intérêts. + + + + ## [Soyez agréable, même si vous n'êtes pas d'accord](#agreeable) + + La critique peut-être constructive. Vous pouvez répondre à une personne en étant en désaccord avec elle. C'est très bien. Mais n'oubliez pas de *critiquer les idées, pas les gens*. À évitez: + + * Injures. + * Attaques ad hominem. + * Répondre à la tonalité d'un poste plutôt qu'à son contenu réel. + * Contradiction réflexe. + + Au lieu de cela, fournir des contre-arguments motivés qui améliorent la conversation. + + + + ## [Votre participation compte](#participate) + + Les conversations que nous avons ici donnent le ton pour tout le monde. Aidez-nous à influencer l'avenir de cette communauté en choisissant de vous engager dans des discussions qui font de ce forum un lieu intéressant, et en évitant celles qui n'en ont pas. + + Discourse fournit des outils qui permettent à la communauté d'identifier collectivement le meilleur (et le pire) des contributions: favoris, signets, des *J'aime*, des signalements, des réponses, des vérifications, et ainsi de suite. Utilisez ces outils pour améliorer votre propre expérience, et celle de tous les autres aussi. + + Essayons de laisser notre parc mieux que nous l'avons trouvé. + + + + ## [Si vous voyez un problème, signaler le](#flag-problems) + + Les modérateurs ont une rôle spécial; ils sont responsables de ce forum. Mais vous aussi. Avec votre aide, les modérateurs peuvent être des facilitateurs de la vie de la communauté, et pas seulement des concierges ou des policiers. + + Quand vous voyez un mauvais comportement, ne répondez pas. Votre reconnaissance encourage le mauvais comportement, il consomme votre énergie et fait perdre du temps à chacun. *Signalez le*. Si suffisamment de signalements s'accumulent, des mesures seront prises, automatiquement ou par l'intervention d'un modérateur. + + Afin de maintenir notre communauté, les modérateurs se réservent le droit de supprimer tout contenu et n'importe quel compte d'utilisateur pour une raison quelconque, et ce à n'importe quel moment. Les modérateurs ne disposent pas d'un aperçu des nouveaux messages avant leur publication, les modérateurs et opérateurs du site n'assument aucune responsabilité pour tout contenu affiché par la communauté. + + + + ## [Soyez toujours civilisé](#be-civil) + + Rien ne sabote une conversation saine mieux que la grossièreté : + + * Soyez civilisé. Ne postez pas ce qu'une personne raisonnable jugerait offensif, ni de discours violent ou haineux. + * Gardez-le forum propre. Ne publiez rien d'obscène ou sexuellement explicite. + * Se respecter mutuellement est primordial. Ne pas harceler ou chagriner quelqu'un, ni usurper l'identité de quelqu'un ou exposer des informations privées le concernant. + * Respecter notre forum. Ne postez pas de spam ou tout autre forme de vandalisme. + + Il ne s'agit pas ici de choses concrètes avec des définitions précises - évitez *même d'avoir l'air* d'agir de la sorte. Si vous avec un doute, demandez-vous comment vous vous sentiriez si votre post était publié sur la première page du Monde. + + Il s'agit d'un forum public, et les moteurs de recherche indexent ces discussions. Utilisez un langage, des liens et des images sans danger pour votre famille et vos amis. + + + + ## [Gardez cet endroit propre](#keep-tidy) + + Faites l'effort de mettre les choses au bon endroit, afin que nous puissions passer plus de temps à discuter et moins à ranger. Ainsi : + + * Ne commencez pas un sujet dans la mauvaise catégorie. + * Ne postez pas le même contenu dans plusieurs discussions. + * Ne postez pas de message sans contenu. + * Ne pas détourner un sujet en le changeant en cours de route. + * Ne signez pas vos messages — chaque post dispose d'informations sur le profil qui s'y rattachent. + + Plutôt que de poster "+1", "lol", "mdr" ou "d'accord", utilisez le bouton 'J'aime'. Plutôt que de faire prendre à une discussion existante une direction radicalement différente, utilisez "Répondre en créant un sujet lié". + + + + ## [Postez seulement vos propres trucs](#stealing) + + Vous ne pouvez pas poster n'importe quel contenu numérique s'il appartient à quelqu'un d'autre sans son autorisation. Vous ne pouvez pas poster des descriptions, des liens ou des méthodes pour voler la propriété intellectuelle de quelqu'un (logiciels, vidéo, audio, images), ou pour transgresser toute autre loi. + + ## [Powered by You](#power) + + Ce site est géré par votre [équipe de responsables](/about) ainsi que *vous*, la communauté. Si vous avez d'autres questions sur comment les choses devraient fonctionner ici, créez un nouveau sujet dans [la catégorie meta](/c/meta) et discutons-en! S'il y un problème critique ou urgent qui ne peut pas être résolu par un sujet meta ou un signalement, contactez nous [ici](/about). + + + + ## [Lisez les conditions générales d'utilisation](#tos) + + Oui, le jargon juridique est ennuyeux, mais nous devons nous protéger – et par extension, vous et vos données – contre des gens hostiles. Nous avons des [Conditions générales d'utilisation](/tos) décrivant votre (et notre) comportement, et les droits liés au contenu, la confidentialité et les lois. Pour utiliser ce service, vous devez accepter de respecter nos [CGU](/tos). + tos_topic: title: "Conditions générales d'utilisation" body: | diff --git a/config/locales/server.it.yml b/config/locales/server.it.yml index a88b7f0925..c17a66d543 100644 --- a/config/locales/server.it.yml +++ b/config/locales/server.it.yml @@ -39,6 +39,7 @@ it: messages: too_long_validation: "è limitato a %{max} caratteri; tu ne hai inseriti %{length}." invalid_boolean: "Booleano non valido." + taken: "è già stato usato (i nomi dei gruppi non distinguono le maiuscole)." embed: load_from_remote: "Si è verificato un errore nel caricamento del messaggio." bulk_invite: @@ -181,6 +182,8 @@ it: base: warning_requires_pm: "Puoi allegare solo avvertimenti ai messaggi privati." too_many_users: "Puoi inviare avvertimenti ad un solo utente per volta." + cant_send_pm: "Spiacenti, non puoi inviare un messaggio privato a quell'utente." + no_user_selected: "Seleziona un utente valido." user: attributes: password: @@ -503,6 +506,7 @@ it: s3_backup_config_warning: 'Il server è configurato per caricare i backup su S3, ma almeno uno dei seguenti campi non è impostato: s3_access_key_id, s3_secret_access_key or s3_backup_bucket. Vai sulle Impostazioni e aggiorna i relativi campi. Guarda "Come impostare il caricamento di immagini su S3?" per informazioni.' image_magick_warning: 'Il server è configurato per creare thumbnail di immagini di ampie dimensioni, ma ImageMagick non è installato. Installa ImageMagick usando il tuo Package Manager preferito o scarica la versione più recente.' failing_emails_warning: 'Ci sono %{num_failed_jobs} job di email falliti. Controlla il tuo file config/environments/production.rb e accertati che le impostazioni config.action_mailer siano corrette. Guarda i job falliti in Sidekiq.' + contact_email_invalid: "La email di contatto non è valida. Aggiornala nelle Impostazioni Sito." consumer_email_warning: "Il tuo sito è configurato per usare Gmail (o un altro servizio email consumer) per inviare le email. Gmail limita il numero di email che puoi inviare. Considera l'utilizzo di un provider come mandrill.com per l'invio delle tue email." content_types: education_new_reply: diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml index ffd035d4d7..18c0a35d77 100644 --- a/config/locales/server.ru.yml +++ b/config/locales/server.ru.yml @@ -39,6 +39,7 @@ ru: messages: too_long_validation: "не может быть длиннее %{max} символов, а введено %{length}." invalid_boolean: "Неправильное булевое значение." + taken: "уже занято. (названия групп нечувствительны к регистру)" embed: load_from_remote: "Произошла ошибка при загрузке сообщения." bulk_invite: @@ -199,6 +200,8 @@ ru: base: warning_requires_pm: "Прикреплять предупреждения можно только к приватным сообщениям." too_many_users: "Можно отправлять предупреждения только одному пользователю за раз." + cant_send_pm: "Извините, вы не можете посылать личные сообщения данному пользователю." + no_user_selected: "Вы должны выбрать корректного пользователя." user: attributes: password: diff --git a/config/locales/server.tr_TR.yml b/config/locales/server.tr_TR.yml index b3b9f5f72d..40b15424d0 100644 --- a/config/locales/server.tr_TR.yml +++ b/config/locales/server.tr_TR.yml @@ -494,7 +494,14 @@ tr_TR: s3_backup_config_warning: 'Sunucu s3''e yedekleme yüklenebilmesi için yapılandırılmış, fakat şunlardan en az biri henüz ayarlanmamış: s3_access_key_id, s3_secret_access_key or s3_backup_bucket. Site Ayarları sayfasına gidin ve ayarları güncelleyin. Daha fazla bilgi için "How to set up image uploads to S3?" konulu gönderiye bakın.' image_magick_warning: 'Sunucu büyük resimlerin küçük boylarının oluşturulması için yapılandırılmış, fakat ImageMagick henüz kurulmamış. Favori paket yöneticinizi kullanarak ImageMagick kurun veya son sürümünü indirin.' failing_emails_warning: 'Başarısız sonuçlanmış %{num_failed_jobs} e-posta işlemi bulunuyor. config/discourse.conf dosyanızı kontrol edin ve e-posta sunucu ayarlarınızın doğru olduğundan emin olun. Sidekiq''deki başarısız işlemlere gözatın.' + default_logo_warning: "Siteniz için logo grafikleri yükleyin. Site Ayarları'nda logo_url, logo_small_url, ve favicon_url güncellemelerini yapın." + contact_email_missing: "Sitenizle alakalı acil durumlar için size ulaşabileceğimiz bir iletişim emaili girin. Lütfen Site Ayarları'nda iletişim emailinizi güncelleyin." + contact_email_invalid: "Site iletişim emaili geçersiz. İletişim emailini Site Ayarları sayfasından güncelleyin." + title_nag: "Sitenizin ismini girin. Site ismini Site Ayarları sayfasından güncelleyin." + site_description_missing: "Arama sonuçlarında gözükecek, sitenizi açıklayan tek bir cümle girin. Site Ayarları sayfasından site açıklamasını güncelleyin." consumer_email_warning: "Sitenizde e-posta gönderimleri için Gmail (ya da başka bir e-posta hizmeti) kurulumu yapılmış. Gmail ile gönderebileceğiniz e-posta sayısı limitlidir. Onun yerine, emaillarınızın ulaştırılabildiğinden emin olmak için mandrill.com benzeri bir hizmet sağlayıcısını kullanın." + site_contact_username_warning: "Otomatik yollanan önemli özel mesajları gönderen kişi olarak gözükecek yardımsever adminin kullanıcı adını girin. Site Ayarları</a> sayfasından güncelleyin. " + notification_email_warning: "Bildiri emaillari alan adınıza bağlı geçerli bir email adresinden yollanmıyor; emaillar düzenli ve güvenilir bir şekilde ulaşmayabilir. Lütfen Site Ayarları sayfasından notification_email değeri için geçerli bir yerel email adresi girin." content_types: education_new_reply: title: "Yeni Kullanıcı Eğitimi: İlk Cevaplar" @@ -543,6 +550,9 @@ tr_TR: allow_duplicate_topic_titles: "Aynı başlık ile birden çok konu açılmasına izin ver." unique_posts_mins: "Kullanıcının aynı içerikle yeni bir gönderi oluşturmadan önce geçmesi gereken dakika" educate_until_posts: "Kullanıcılar, ilk (n) gönderilerini yazmaya başladıklarında, pop-up yeni kullanıcı eğitim paneli metin düzenleyecisinin üstünde çıksın." + title: "Sayfa başlığı etiketinde kullanılacak, bu sitenin ismi." + site_description: "Meta açıklama etiketinde kullanılacak, bu sitenin bir cümlelik açıklaması." + contact_email: "Bu site için sorumlu kişinin email adresi. Sadece acil durumlar için iletişim formunda ve kritik bildiriler için kullanılacak." queue_jobs: "SADECE YAZILIMCILAR İÇİN! UYARI! Aksi gerekmediği takdirde sidekiq işlerini hep otomatik sıraya koydurtun. Bu özellik devre dışı bırakılırsa, siteniz düzgün çalışmayacaktır." crawl_images: "Doğru genişlik ve yükseklik boyutlarını girmek için uzak URL'lerdeki resimlerin birer kopyasını alınsın." download_remote_images_to_local: "Uzaktaki resimler yerel resimlere çevirmek için indirilsin; bu ayar resim bağlantılarının kırılmasını önleyecektir" @@ -562,8 +572,11 @@ tr_TR: post_excerpt_maxlength: "Gönderi alıntısının / özetinin en fazla uzunluğu." post_onebox_maxlength: "Kutulanmış bir Discourse gönderisinin en fazla karakter uzunluğu" onebox_domains_whitelist: "Kutulamaya izin verilen alan adları listesi; bu alan adları OpenGraph ya da oEmbed desteklemeliler. http://iframely.com/debug adresinden test edebilirsiniz." + logo_url: "Sitenizin sol üstünde yer alacak logo resmi; boş bırakılırsa, sitenin ismi gözükecek." digest_logo_url: "Sitenizin özet e-postalarının üst kısmında kullanılacak logo. Boş bırakılırsa `logo_url` kullanılacak. örn: http://example.com/logo.png" + logo_small_url: "Sitenizin sol üstünde yer alacak, sayfayı aşağı kaydırdığınızda gözükecek küçük logo. Boş bırakılırsa, ana sayfa glifi gözükecek." favicon_url: "Site favikonunuz. Bakın; http://en.wikipedia.org/wiki/Favicon" + mobile_logo_url: "Mobil sitenizin sol üstünde yer alacak sabit pozisyonlu logo resmi. Boş bırakılırsa, sitenin ismi kullanılacak." apple_touch_icon_url: "Apple dokunmatik cihazları için kullanılan ikon. Önerilen boyut; 144 x 144 pixel." notification_email: "Tüm önemli sistem e-postaları için kullanılacak olan gönderen e-posta adresi. E-postaların başarıyla ulaşması için buraya girilen alan adının SPF, DKIM ve reverse PTR kayıtlarının doğru yapılması lazım." email_custom_headers: "Sınırlandırılmış özel e-posta başlıkları listesi" @@ -607,6 +620,7 @@ tr_TR: post_menu_hidden_items: "Gönderi menüsündeki maddeler, genişletme üç noktasına tıklanmadığı takdirde otomatik olarak gizlenir. " share_links: "Paylaşım penceresinde hangi maddelerin ne sırada gözükeceğini belirleyin." track_external_right_clicks: "URL'leri tekrar yazdığı için, sağ tıklanan dış bağlantıların (ör: yeni bir sekmede aç) takibi varsayılan ayarlarda devre dışı bırakılmıştır." + site_contact_username: "Tüm otomatik özel mesajlar için gönderen kişi olarak gözükecek geçerli bir görevli kullanıcı adı. Boş bırakılırsa varsayılan Sistem hesabı kullanılacak." send_welcome_message: "Tüm yeni kullanıcılara bir hoşgeldiniz özel mesajı ile beraber hızlı başlangıç kılavuzu gönderin." suppress_reply_directly_below: "Bu gönderinin direk altında sadece tek bir cevap varsa, gönderideki açılabilir cevap sayısı bölümünü gösterme." suppress_reply_directly_above: "Bu gönderinin direk üstünde sadece tek bir cevap varsa, gönderideki açılabilir hangi-cevaba-istinaden-cevapla bölümünü gösterme." @@ -636,6 +650,7 @@ tr_TR: max_username_length: "Kullanıcı adında olabilecek en fazla karakter sayısı. UYARI: HALİHAZIRDA BUNDAN DAHA UZUN BİR KULLANICI ADINA SAHİP OLAN KULLANICILAR SİTEYE GİRİŞ YAPAMAYACAKLAR. " min_password_length: "En az parola uzunluğu." block_common_passwords: "En çok kullanılan 10,000 parola arasında yer alan parolalara izin verme." + enable_sso: "Dış bir site aracılığı ile tek oturum açma sistemini etkinleştir. (UYARI: etkinleştirildiği takdirde, doğru yapılandırılmamışsa bazı kişilerin giriş yapmasını engelleyebilir; ayrıca davetiye sistemini de devre dışı bırakır)" enable_sso_provider: " /session/sso_provider son noktasında Discourse SSO uygula, sso_secret değerinin seçilmiş olmasını gerektirir" sso_url: "Bitiş noktasındaki tek oturum açma URL'i" sso_secret: "SSO bilgisinin şifrelemesi/deşifre edilmesi için kullanılan gizli string, 10 karakter veya daha uzun olması gerekli" @@ -920,6 +935,7 @@ tr_TR: errors: "%{errors}" not_available: "Uygun değil. Bunu denemeye ne dersiniz? %{suggestion}" something_already_taken: "Bir şeyler ters gitti. Kullanıcı adı veya e-posta zaten kayıtlı olabilir. Parolamı Unuttum bağlantısını deneyin." + omniauth_error: "Üzgünüz, hesabınız doğrulanırken bir hata oluştu. Doğrulama işlemini onaylamamış olabilir misiniz?" omniauth_error_unknown: "Girişiniz yapılırken bir şeyler ters gitti, lütfen tekrar deneyin." new_registrations_disabled: "Şu an yeni hesap oluşturulamıyor. " password_too_long: "Parolalar maksimum 200 karakter olabilir." @@ -1010,6 +1026,11 @@ tr_TR: how_to_disable: '"Bayraklarla ilgili bilgilendirme süresi" ayarından bu hatırlatma e-postasının gönderimini devre dışı bırakabilir ya da sıklığını değiştirebilirsiniz. ' subject_template: other: "İlgilenilmesi gereken %{count} bayrak var" + flag_reasons: + off_topic: "Gönderiniz **konu-dışı** olarak bayraklandı: topluluk, gönderinizin konu başlığı ve ilk gönderi ile tanımlanan konu içeriğine uygun olmadığını düşünüyor. " + inappropriate: "Gönderiniz **uygunsuz** olarak bayraklandı: topluluk gönderinizin saldırgan, kaba ya da [topluluk yönergelerine](/guidelines) aykırı olduğunu düşünüyor." + spam: "Gönderiniz **spam** olarak bayraklandı: topluluk, gönderinizin mevcut konuya uygun veya yararlı olmaktansa fazlasıyla promosyonel ve reklam içerikli olduğunu düşünüyor." + notify_moderators: "Gönderiniz **moderatör kontrolü için** bayraklandı: topluluk, gönderinizle ilgili bir şeyin moderatör tarafından kontrol edilmesi gerektiğini düşünüyor." flags_dispositions: agreed: "Bizi bilgilendirdiğiniz için teşekkür ederiz. Bir sorun olduğu konusunda size katılıyoruz ve ilgileniyoruz." agreed_and_deleted: "Bizi bilgilendirdiğiniz için teşekkür ederiz. Bir sorun olduğu konusunda size katılıyoruz, gönderiyi kaldırdık. " diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml index 21bb5bb812..8f139dcd7c 100644 --- a/config/locales/server.zh_CN.yml +++ b/config/locales/server.zh_CN.yml @@ -511,10 +511,12 @@ zh_CN: image_magick_warning: '服务器被设置为给大图片创建缩略图,但是 ImageMagick 没有被安装。用你最喜爱的包管理器安装 ImageMagick 或下载最新版。' failing_emails_warning: '%{num_failed_jobs} 个 email 任务失败。请检查 config/discourse.conf 文件是否正确配置了邮件服务器。在 Sidekiq 中查看失败的任务。' default_logo_warning: "设置你站点的图形logo。请在站点设置中配置logo_url、logo_small_url和favicon_url。" + contact_email_missing: "输入一个站点联系人的邮件地址,这样在用户在网站出现紧急状况时能够找到你。在站点设置中更新它。" contact_email_invalid: "站点联系邮箱不正确。在站点设置中修改。" title_nag: "为站点指定一个名字。在站点设置中更新标题。" site_description_missing: "输入一句话作为你站点的简介,将出现在搜索结果中。在站点设置中更新 site_description。" consumer_email_warning: "你的站点被设置为使用 Gmail 或者其他客户邮箱服务发送邮件。Gmail 限制每日邮件发送数量。请考虑使用其他邮件服务商来保证邮件的成功发送,例如 mandrill.com。" + site_contact_username_warning: "输入一个友善的职员账户名,并以他的名义发送重要的自动私信。在站点设置中更新 site_contact_username。" notification_email_warning: "通知邮件不是从你域名的一个有效地址发出的;邮件分发将会变得危险和不可靠。请在站点设置中将 notification_email 设置为一个有效的本地邮件地址。" content_types: education_new_reply: @@ -586,8 +588,11 @@ zh_CN: post_excerpt_maxlength: "帖子摘要的最大字符长度" post_onebox_maxlength: "Onebox 处理后的 Discourse 帖子的最大字符长度" onebox_domains_whitelist: "对这些站点启用 Onebox 的域名列表;这些域名应该支持 OpenGraph 或 oEmbed。在此测试他们:http://iframely.com/debug" + logo_url: "出现在您站点左上角的标志图片;如果留空,将显示站点标题文字。" digest_logo_url: "站点邮件摘要使用的标志。如果留空,则使用 `logo_url`。例如:http://example.com/logo.png" + logo_small_url: "出现在您站点左上角的小号标志图片,当滚动后可见。如果留空,将显示主页图标。" favicon_url: "你的站点图标(favicon),参考 http://zh.wikipedia.org/wiki/Favicon" + mobile_logo_url: "移动浏览器左上角的固定标志图片。如果留空,将显示站点标题文字。" apple_touch_icon_url: "Apple 触摸设备使用的图标。推荐 144x144 大小。" notification_email: "这个表格:被用于发送所有重要系统邮件的邮箱地址。指定的域名必须正确设置 SPF、DKIM 和反向 PTR 记录以发送邮件。" email_custom_headers: "一个逗号分离的自定义邮件头部" @@ -661,6 +666,7 @@ zh_CN: max_username_length: "最大用户名长度。警告:所有现用户名大于此限制长度的用户都将无法访问站点。" min_password_length: "最小密码长度。" block_common_passwords: "不允许使用 10,000 个最常用的密码。" + enable_sso: "启用通过外部站点单点登录(注意:如果没有正确配置就启用可能会使得所有人都无法登陆;同时禁用邀请功能)" enable_sso_provider: "在 /session/sso_provider endpoint 实现 Discourse SSO 协议,要求设置 sso_secret" sso_url: "单点登录 URL 入口点" sso_secret: "秘密字符串,用于加密/解密 SSO 信息,请保证有 10 个字符或者更长" @@ -945,6 +951,7 @@ zh_CN: errors: "%{errors}" not_available: "不可用,试试 %{suggestion}?" something_already_taken: "出了一些问题,可能此用户名或电子邮箱已经被注册。试试 忘记密码链接吧。" + omniauth_error: "抱歉,在验证你的帐号时发生了错误。可能你没有批准授权申请?" omniauth_error_unknown: "在处理你的登录过程中发生了错误,请重试。" new_registrations_disabled: "现在不允许注册新账户。" password_too_long: "密码不能超过 200 个字符。" @@ -1090,6 +1097,11 @@ zh_CN: how_to_disable: '你可以通过修改“notify about flags after”设置项以禁用或改变邮件提醒设置。' subject_template: other: "%{count} 个标记需要被处理" + flag_reasons: + off_topic: "您的帖子被标记为 **偏离主题**:鉴于当前的主题标题和第一个帖子,社群成员们感觉它不适合处于这个主题中。" + inappropriate: "您的帖子被标记为 **不恰当**:社群成员感觉它有冒犯或者侮辱的意味,亦或是它违反了[社群准则](/guidelines)。" + spam: "您的帖子被标记为 **广告**:社群成员觉得它是广告,像是在过度地推广着什么,而不是预期中与主题有关的内容。" + notify_moderators: "您的帖子被标记为 **需要版主关注**:社群成员感觉帖子需要职员的人工干预。" flags_dispositions: agreed: "感谢通知我们。我们认为这是一个问题,并且我们正在了解情况。" agreed_and_deleted: "感谢通知我们。我们认为这是一个问题,并且我们已经删除了帖子。" @@ -1573,6 +1585,8 @@ zh_CN: 编辑本主题的第一帖以改变 %{page_name} 页面的内容。 guidelines_topic: title: "FAQ/指引" + body: "\n\n## [文明讨论的地方](#civilized)\n\n请您在论坛表现得像在公共公园。我们也同样分享着一个公共社群资源 — 一个通过不断进行的讨论分享我们技能、知识和兴趣的地方。\n\n这些都不是硬性规定,只是一些帮助社群的人们来判断的规定。试用这些指引来保持干净和充满灵感的公开论坛。\n\n\n\n## [改善讨论](#improve)\n\n帮助我们做一些帮助改善讨论的事,这样能帮助我们让这个地方变成一个参与讨论的好地方。\n\n这里被讨论的主题对我们很重要,并且如果他们对您也很重要,请和我们一样严肃对待他们。即使您不同意别人的话,但是也要尊重正在讨论这些主题的人们。\n\n改进讨论的一个方式是探索已经发生过的事。请在回复或者开始您自己的主题前花一些时间浏览,这样您更有机会遇见和您有共同爱好的人。\n\n\n\n## [尊重,当您不同意时](#agreeable)\n\n您可能想表达您的不同意。这没问题。但是,记住_批评观点,而不是人_。请避免:\n\n* 指名道姓。\n* 人生攻击。\n* 回应帖子的语气而不是它的真正内容。\n* 下意识的反驳。\n\n相反,提供合理的观点改善讨论。\n\n\n\n## [您的参与有价值](#participate)\n\n我们在这儿的讨论为大家树立了榜样。通过选择参与那些让论坛变成一个有意思的地方,来帮助我们改善社群的未来 — 并且避免那些没有帮助的行为。\n\nDiscourse 提供了让社群共同鉴别最棒(或最差)的贡献的工具:收藏、书签、赞、标记、回复、编辑或者其他。使用这些工具改善您自己的体验,和其他人的体验。\n\n让我们留下一个比发现它之前更好的社群。\n\n\n\n## [如果您看见了一个问题,标记它](#flag-problems)\n\n版主有特别的权力;他们对论坛负责。但是您也是。通过您的帮助,版主能促进社群的发展,而不是守卫或者警察。\n\n当您见到不合适的行为,不要回应它。这种承认变相鼓励了这种不合适的行为,浪费了您的经历,并且浪费了每一个人的时间。_是要标记它。_如果收到了足够的标记,会有相应的处理,无论是自动地或版主的介入。\n\n为了维护我们的社群,版主保留了任何情况下删除任何内容和其他用户的权力。版主不能预览任何新帖子;版主和站点维护人员对社群里发表的任何言论均不负责任。\n\n\n\n## [永远文明](#be-civil)\n\n粗鲁这样的行为会破坏健康的讨论:\n\n* 文明。不要发表任何理智的人会认为会冒犯、滥用或招致怨恨的言论。\n* 保持干净。不要发表任何淫秽或性暗示的东西。\n* 尊重其他人。不要骚扰或者让别人难过,检视别人,或暴露他们的个人信息。\n* 尊重我们的论坛。不要发表广告或者其他垃圾信息。\n\n这些条款没有准确的定义 — 避免任何_可能_的这些事。如果您不确定,问问自己这些是否能出现在纽约时报的头版头条上。\n\n这是一个公共论坛,并且搜索引擎会索引这些讨论。注意语言、链接和家庭和朋友的图片的安全。\n\n\n\n## [保持整洁](#keep-tidy)\n\n花一点时间让东西出现在正确的位置,这样我们能花更多的时间在讨论上而非清理格式。所以:\n\n* 不要在错误的分类开始一个新主题。\n* 不要在多个主题中回复同样的内容。\n* 不要发布没有内容的回复。\n* 不要在中途改变主题。\n* 不要在您的帖子中签名 — 每一贴都附有您的个人信息。\n\n比起发表“+1”或者“同意”,使用赞按钮。比起将帖子带向一个决然不同的方向,使用“回复为关联主题”。\n\n\n\n## [发表您自己的东西](#stealing)\n\n您不能在没有他人授权的情况下发表任何属于他人的数字信息。您可能不能发表关于窃据他人知识产权(软件、视频、音频和图像)的任何简介、链接或方法,或其他任何不符合其他法律的事。\n\n\n\n## [有你参与](#power) \n\n这个站点由[一群友善的职员](/about)、你和社群一起运营。如果你对这里的事情仍有疑问,在[站务](/c/meta)分类新建一个主题并且开始讨论!如果遇到了重要或紧急的事情,并且不能用站务分类的主题或标记解决,通过[职员页面](/about)联系我们。\n\n\n\n## [使用条款](#tos)\n\n是的,法律很无聊,但是我们必须保护我们自己 – 引申开来,您和您的数据 – 用于针对不友好的家伙们。我们有一个[使用条款](/tos)描述您的(或者我们)关于内容、隐私和法律的行为和权力。要使用我们的服务,您必须同意遵守[使用条款](/tos)。\n" tos_topic: title: "服务条款" body: "下面的条款及条件适用于 %{company_domain} 网站的所有内容、服务和产品,其方式通过网站,其中包括,但不限于 %{company_domain} 论坛软件、%{company_domain} 支持论坛和在 %{company_domain} 托管服务(“托管”)(一起称为,网站)。该网站属于 %{company_full_name}(“%{COMPANY_NAME}”)并由其运营。该网站要求你必须接受所有本文中所包含的条款和条件以及其他所有的规则、政策(包括修改,但不限于 %{company_domain} 的[隐私政策](/privacy)、[社群准则](/FAQ)和程序(统称“协议”),并可能不时由 %{COMPANY_NAME} 发布。\n\n访问或使用本网站之前,请仔细阅读本协议。访问或使用网站的任何部分,即代表你同意成为受本协议的条款和条件。如果你不同意所有的条款和本协议的条件,那么你可能无法访问网站或使用任何服务。若这些条款和条件被认为是与 %{COMPANY_NAME} 的合同,则你必须接受这些条款。本网站只提供给 13 岁以上个人。\n\n \n\n## [1. 你的 %{company_domain} 帐号](#1) \n\n如果你在网站上创建一个帐户,你有责任维护你的帐号安全,你对帐户下发生的所有活动完全负责。你必须立即通知 %{COMPANY_NAME} 关于你的帐户的任何未经授权或任何其他违反安全的使用。%{COMPANY_NAME} 不会对你的任何行或不作为,包括因该等作为或不作为而导致的任何种类的任何损失承担任何责任。 \n\n \n\n## [2. 提供者的责任](#2) \n\n如果你张贴的材料有关于别的网站的链接,或以其他方式(或允许任何第三方进行)张贴别的网站的材料(不论何等材料,“内容”),你需对此完全负责,包括使用因该内容所造成的任何损害。不管所讨论的内容是文字、图形、音频文件或计算机软件。你声明和保证: \n\n* 下载、复制和使用内容不会侵犯专有权利,包括但不限于著作权,专利,商标或商业秘密权利的任何第三方;\n* 如果你的雇主对你的创造拥有知识产权,你必须以(i)获准从你的雇主发布或提供的内容,包括但不限于任何软件,或(ii)由你的雇主放弃拥有内容的所有权利;\n* 你已完全遵守有关该内容的第三方条约,并同意了所有面向最终用户所需的任何条款;\n* 内容不包含或安装任何病毒、蠕虫、恶意软件、特洛伊木马或其他有害或破坏性的内容;\n* 内容不是垃圾,不是机器的或随机生成的,并且不包含设计来吸引流量到第三方网站或助推第三方网站的搜索引擎排名,或者进一步非法行为不道德或不想要的广告内容(诸如网络钓鱼)或误导接受者为对材料的来源(如欺骗);\n* 内容不色情,不包含威胁或煽动暴力,并没有侵犯任何第三方的隐私或公开权;\n* 你的内容不是不受欢迎的电子讯息,如新闻组垃圾链接、邮件列表、博客和网站,以及类似的不请自来的宣传方式宣传;\n* 你的内容不是在误导读者,以你是另外一个人或公司的方式;和 \n* 你应在内容中包含计算机代码,准确地分类和/或所描述的类型、性质、用途和材料的影响,是否请求 %{COMPANY_NAME} 或以其他方式这样做的情况下。 \n\n \n\n## [3.用户内容授权](#3) \n\n用户贡献使用 [知识共享 署名-非商业性使用-相同方式共享3.0 Unported之许可](http://creativecommons.org/licenses/by-nc-sa/3.0/deed.zh_CN)授权。在不限制任何该等陈述或保证的情况下,%{COMPANY_NAME} 有权利(但没有义务)在 %{COMPANY_NAME} 的全权酌情决定下(i)根据 %{COMPANY_NAME} 的合理意见,任何违反 %{COMPANY_NAME} 政策或以任何方式伤害或使他人反感的情况能拒绝或删除任何内容;或(ii)以任何理由终止或拒绝任何个人和组织访问和使用本网站。%{COMPANY_NAME} 有唯一裁量权。%{COMPANY_NAME} 没有义务退还先前支付的任何款项。 \n\n\n \n\n## [4. Payment and Renewal](#4)\n\n### General Terms\n\nOptional paid services or upgrades may be available on the Website. When utilizing an optional paid service or upgrade, you agree to pay %{company_name} the monthly or annual subscription fees indicated.\ From 514a45b0c1727f56744ff22c2084c921e2479434 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 21 Jan 2015 13:57:03 -0500 Subject: [PATCH 074/230] SECURITY: Don't whitelist codepen as it is a potential vector for abuse --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 84322f949b..d9a7d662df 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -271,7 +271,7 @@ GEM omniauth-twitter (1.0.1) multi_json (~> 1.3) omniauth-oauth (~> 1.0) - onebox (1.5.6) + onebox (1.5.7) moneta (~> 0.7) multi_json (~> 1.7) mustache (~> 0.99) From 03eb4752d14fbd8f759713a64fd8a54e6fb4a68a Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 21 Jan 2015 14:34:01 -0500 Subject: [PATCH 075/230] Add `categories-list` css class to body for customization purposes --- .../discourse/views/discovery-categories.js.es6 | 10 +++++++++- .../integration/topic-discovery-test.js.es6 | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/views/discovery-categories.js.es6 b/app/assets/javascripts/discourse/views/discovery-categories.js.es6 index 8571185e21..460a154237 100644 --- a/app/assets/javascripts/discourse/views/discovery-categories.js.es6 +++ b/app/assets/javascripts/discourse/views/discovery-categories.js.es6 @@ -1,3 +1,11 @@ import UrlRefresh from 'discourse/mixins/url-refresh'; -export default Discourse.View.extend(UrlRefresh, Discourse.ScrollTop); +export default Discourse.View.extend(UrlRefresh, Discourse.ScrollTop, { + _addBodyClass: function() { + $('body').addClass('categories-list'); + }.on('didInsertElement'), + + _removeBodyClass: function() { + $('body').removeClass('categories-list'); + }.on('willDestroyElement') +}); diff --git a/test/javascripts/integration/topic-discovery-test.js.es6 b/test/javascripts/integration/topic-discovery-test.js.es6 index c5931f6335..fcd4a090d2 100644 --- a/test/javascripts/integration/topic-discovery-test.js.es6 +++ b/test/javascripts/integration/topic-discovery-test.js.es6 @@ -11,15 +11,20 @@ test("Visit Discovery Pages", function() { andThen(function() { ok(exists(".topic-list"), "The list of topics was rendered"); ok(exists('.topic-list .topic-list-item'), "has topics"); + ok($('body.category-1').length, "has a custom css class for the category id on the body"); }); visit("/categories"); andThen(function() { + ok($('body.category-1').length === 0, "removes the custom category class"); + ok(exists('.category'), "has a list of categories"); + ok($('body.categories-list').length, "has a custom class to indicate categories"); }); visit("/top"); andThen(function() { + ok($('body.categories-list').length === 0, "removes the `categories-list` class"); ok(exists('.topic-list .topic-list-item'), "has topics"); }); }); From e300945879211816a86860d89d440a1220c33e75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 21 Jan 2015 20:52:48 +0100 Subject: [PATCH 076/230] FEATURE: split group admin in 2 tabs (custom & automatic) FIX: clear the user-selector when adding new members --- .../admin/controllers/admin-group.js.es6 | 25 ++++++++------- .../controllers/admin-groups-type.js.es6 | 16 ++++++++++ .../admin/controllers/admin-groups.js.es6 | 24 -------------- .../admin/routes/admin-group-new.js.es6 | 6 ++++ ...dmin_group_route.js => admin-group.js.es6} | 9 ++++-- .../admin/routes/admin-groups-index.js.es6 | 5 +++ .../admin/routes/admin-groups-type.js.es6 | 17 ++++++++++ .../admin/routes/admin-route-map.js.es6 | 8 +++-- .../admin/routes/admin_groups_route.js | 31 ------------------- .../javascripts/admin/templates/admin.hbs | 2 +- .../javascripts/admin/templates/groups.hbs | 25 +++++---------- .../admin/templates/groups_index.hbs | 1 - .../admin/templates/groups_type.hbs | 20 ++++++++++++ .../discourse/components/user-selector.js.es6 | 20 +++++++----- .../javascripts/discourse/models/group.js | 13 +++++--- .../stylesheets/common/admin/admin_base.scss | 3 ++ app/controllers/admin/groups_controller.rb | 8 ++--- config/locales/client.en.yml | 2 ++ config/routes.rb | 9 ++++-- .../admin/groups_controller_spec.rb | 8 ++--- 20 files changed, 139 insertions(+), 113 deletions(-) create mode 100644 app/assets/javascripts/admin/controllers/admin-groups-type.js.es6 delete mode 100644 app/assets/javascripts/admin/controllers/admin-groups.js.es6 create mode 100644 app/assets/javascripts/admin/routes/admin-group-new.js.es6 rename app/assets/javascripts/admin/routes/{admin_group_route.js => admin-group.js.es6} (60%) create mode 100644 app/assets/javascripts/admin/routes/admin-groups-index.js.es6 create mode 100644 app/assets/javascripts/admin/routes/admin-groups-type.js.es6 delete mode 100644 app/assets/javascripts/admin/routes/admin_groups_route.js delete mode 100644 app/assets/javascripts/admin/templates/groups_index.hbs create mode 100644 app/assets/javascripts/admin/templates/groups_type.hbs diff --git a/app/assets/javascripts/admin/controllers/admin-group.js.es6 b/app/assets/javascripts/admin/controllers/admin-group.js.es6 index 6bd8eda6bf..89b448b52a 100644 --- a/app/assets/javascripts/admin/controllers/admin-group.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-group.js.es6 @@ -1,7 +1,6 @@ export default Em.ObjectController.extend({ - needs: ['adminGroups'], + needs: ['adminGroupsType'], disableSave: false, - usernames: null, currentPage: function() { if (this.get("user_count") == 0) { return 0; } @@ -59,28 +58,29 @@ export default Em.ObjectController.extend({ }, addMembers: function() { - // TODO: should clear the input if (Em.isEmpty(this.get("usernames"))) { return; } this.get("model").addMembers(this.get("usernames")); + // clear the user selector + this.set("usernames", null); }, save: function() { var self = this, - group = this.get('model'); + group = this.get('model'), + groupsController = this.get("controllers.adminGroupsType"); - self.set('disableSave', true); + this.set('disableSave', true); var promise; - if (group.get('id')) { + if (group.get("id")) { promise = group.save(); } else { promise = group.create().then(function() { - var groupsController = self.get('controllers.adminGroups'); groupsController.addObject(group); }); } promise.then(function() { - self.send('showGroup', group); + self.transitionToRoute("adminGroup", group); }, function(e) { var message = $.parseJSON(e.responseText).errors; bootbox.alert(message); @@ -91,12 +91,13 @@ export default Em.ObjectController.extend({ destroy: function() { var group = this.get('model'), - groupsController = this.get('controllers.adminGroups'), + groupsController = this.get('controllers.adminGroupsType'), self = this; - bootbox.confirm(I18n.t("admin.groups.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) { - if (result) { - self.set('disableSave', true); + this.set('disableSave', true); + + bootbox.confirm(I18n.t("admin.groups.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(confirmed) { + if (confirmed) { group.destroy().then(function() { groupsController.get('model').removeObject(group); self.transitionToRoute('adminGroups.index'); diff --git a/app/assets/javascripts/admin/controllers/admin-groups-type.js.es6 b/app/assets/javascripts/admin/controllers/admin-groups-type.js.es6 new file mode 100644 index 0000000000..2d75d1911b --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-groups-type.js.es6 @@ -0,0 +1,16 @@ +export default Ember.ArrayController.extend({ + sortProperties: ['name'], + refreshingAutoGroups: false, + + actions: { + refreshAutoGroups: function(){ + var self = this; + this.set('refreshingAutoGroups', true); + Discourse.ajax('/admin/groups/refresh_automatic_groups', {type: 'POST'}).then(function() { + self.transitionToRoute("adminGroupsType", "automatic").then(function() { + self.set('refreshingAutoGroups', false); + }); + }); + } + } +}); diff --git a/app/assets/javascripts/admin/controllers/admin-groups.js.es6 b/app/assets/javascripts/admin/controllers/admin-groups.js.es6 deleted file mode 100644 index 03de7cfe98..0000000000 --- a/app/assets/javascripts/admin/controllers/admin-groups.js.es6 +++ /dev/null @@ -1,24 +0,0 @@ -export default Ember.ArrayController.extend({ - sortProperties: ['name'], - - refreshingAutoGroups: false, - - actions: { - refreshAutoGroups: function(){ - var self = this, - groups = this.get('model'); - - self.set('refreshingAutoGroups', true); - this.transitionToRoute('adminGroups.index').then(function() { - Discourse.ajax('/admin/groups/refresh_automatic_groups', {type: 'POST'}).then(function() { - return Discourse.Group.findAll().then(function(newGroups) { - groups.clear(); - groups.addObjects(newGroups); - }).finally(function() { - self.set('refreshingAutoGroups', false); - }); - }); - }); - } - } -}); diff --git a/app/assets/javascripts/admin/routes/admin-group-new.js.es6 b/app/assets/javascripts/admin/routes/admin-group-new.js.es6 new file mode 100644 index 0000000000..5e1cff95a4 --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-group-new.js.es6 @@ -0,0 +1,6 @@ +export default Discourse.Route.extend({ + renderTemplate: function() { + debugger; + this.render("admin/templates/group"); + } +}); diff --git a/app/assets/javascripts/admin/routes/admin_group_route.js b/app/assets/javascripts/admin/routes/admin-group.js.es6 similarity index 60% rename from app/assets/javascripts/admin/routes/admin_group_route.js rename to app/assets/javascripts/admin/routes/admin-group.js.es6 index 89388e1c32..f397297767 100644 --- a/app/assets/javascripts/admin/routes/admin_group_route.js +++ b/app/assets/javascripts/admin/routes/admin-group.js.es6 @@ -1,17 +1,20 @@ -Discourse.AdminGroupRoute = Discourse.Route.extend({ +export default Discourse.Route.extend({ model: function(params) { - var groups = this.modelFor('adminGroups'), + var groups = this.modelFor('adminGroupsType'), group = groups.findProperty('name', params.name); if (!group) { return this.transitionTo('adminGroups.index'); } + return group; }, setupController: function(controller, model) { controller.set("model", model); + // clear the user selector + controller.set("usernames", null); + // load the members of the group model.findMembers(); } }); - diff --git a/app/assets/javascripts/admin/routes/admin-groups-index.js.es6 b/app/assets/javascripts/admin/routes/admin-groups-index.js.es6 new file mode 100644 index 0000000000..33c42cbb0f --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-groups-index.js.es6 @@ -0,0 +1,5 @@ +export default Discourse.Route.extend({ + redirect: function() { + this.transitionTo("adminGroupsType", "custom"); + } +}) diff --git a/app/assets/javascripts/admin/routes/admin-groups-type.js.es6 b/app/assets/javascripts/admin/routes/admin-groups-type.js.es6 new file mode 100644 index 0000000000..5c04e25064 --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-groups-type.js.es6 @@ -0,0 +1,17 @@ +export default Discourse.Route.extend({ + model: function(params) { + return Discourse.Group.findAll().then(function(groups) { + return groups.filterBy("type", params.type); + }); + }, + + actions: { + newGroup: function() { + var self = this; + this.transitionTo("adminGroupsType", "custom").then(function() { + var group = Discourse.Group.create({ automatic: false, visible: true }); + self.transitionTo("adminGroup", group); + }) + } + } +}); 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 8d868d7115..c5ce804efa 100644 --- a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 @@ -40,8 +40,10 @@ export default function() { this.route('screenedUrls', { path: '/screened_urls' }); }); - this.resource('adminGroups', { path: '/groups'}, function() { - this.resource('adminGroup', { path: '/:name' }); + this.resource('adminGroups', { path: '/groups' }, function() { + this.resource('adminGroupsType', { path: '/:type' }, function() { + this.resource('adminGroup', { path: '/:name' }); + }); }); this.resource('adminUsers', { path: '/users' }, function() { @@ -51,7 +53,7 @@ export default function() { }); this.resource('adminUsersList', { path: '/list' }, function() { - this.route('show', {path: '/:filter'}); + this.route('show', { path: '/:filter' }); }); }); diff --git a/app/assets/javascripts/admin/routes/admin_groups_route.js b/app/assets/javascripts/admin/routes/admin_groups_route.js deleted file mode 100644 index e66299a50f..0000000000 --- a/app/assets/javascripts/admin/routes/admin_groups_route.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - Handles routes for admin groups - - @class AdminGroupsRoute - @extends Discourse.Route - @namespace Discourse - @module Discourse -**/ -Discourse.AdminGroupsRoute = Discourse.Route.extend({ - model: function() { - return Discourse.Group.findAll(); - }, - - actions: { - showGroup: function(g) { - // This hack is needed because the autocomplete plugin does not - // refresh properly when the underlying data changes. TODO should - // be to update the plugin so it works properly and remove this hack. - var self = this; - this.transitionTo('adminGroups.index').then(function() { - self.transitionTo('adminGroup', g); - }); - }, - - newGroup: function(){ - var group = Discourse.Group.create({ visible: true }); - this.send('showGroup', group); - } - } -}); - diff --git a/app/assets/javascripts/admin/templates/admin.hbs b/app/assets/javascripts/admin/templates/admin.hbs index 209a6dc293..0958fe0d42 100644 --- a/app/assets/javascripts/admin/templates/admin.hbs +++ b/app/assets/javascripts/admin/templates/admin.hbs @@ -13,7 +13,7 @@
  • {{#link-to 'adminBadges.index'}}{{i18n 'admin.badges.title'}}{{/link-to}}
  • {{/if}} {{#if currentUser.admin}} -
  • {{#link-to 'adminGroups.index'}}{{i18n 'admin.groups.title'}}{{/link-to}}
  • +
  • {{#link-to 'adminGroups'}}{{i18n 'admin.groups.title'}}{{/link-to}}
  • {{/if}}
  • {{#link-to 'adminEmail'}}{{i18n 'admin.email.title'}}{{/link-to}}
  • {{#link-to 'adminFlags'}}{{i18n 'admin.flags.title'}}{{/link-to}}
  • diff --git a/app/assets/javascripts/admin/templates/groups.hbs b/app/assets/javascripts/admin/templates/groups.hbs index 061bf715de..751e46323f 100644 --- a/app/assets/javascripts/admin/templates/groups.hbs +++ b/app/assets/javascripts/admin/templates/groups.hbs @@ -1,20 +1,11 @@ -
    -
    -

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

    -
      - {{#each group in arrangedContent}} -
    • - {{group.name}} {{group.userCountDisplay}} -
    • - {{/each}} +
      +
      + -
      - - -
      -
      - -
      - {{outlet}}
      +
      + {{outlet}} +
      diff --git a/app/assets/javascripts/admin/templates/groups_index.hbs b/app/assets/javascripts/admin/templates/groups_index.hbs deleted file mode 100644 index f8a3f440e6..0000000000 --- a/app/assets/javascripts/admin/templates/groups_index.hbs +++ /dev/null @@ -1 +0,0 @@ -{{i18n 'admin.groups.about'}} diff --git a/app/assets/javascripts/admin/templates/groups_type.hbs b/app/assets/javascripts/admin/templates/groups_type.hbs new file mode 100644 index 0000000000..0aec861a66 --- /dev/null +++ b/app/assets/javascripts/admin/templates/groups_type.hbs @@ -0,0 +1,20 @@ +
      +
      +

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

      +
        + {{#each group in controller}} +
      • + {{#link-to "adminGroup" group.type group.name}}{{group.name}} {{group.userCountDisplay}}{{/link-to}} +
      • + {{/each}} +
      +
      + {{d-button action="newGroup" icon="plus" label="admin.groups.new"}} + {{d-button action="refreshAutoGroups" icon="refresh" label="admin.groups.refresh" disabled=refreshingAutoGroups}} +
      +
      + +
      + {{outlet}} +
      +
      diff --git a/app/assets/javascripts/discourse/components/user-selector.js.es6 b/app/assets/javascripts/discourse/components/user-selector.js.es6 index 15d50faeda..f008c599b3 100644 --- a/app/assets/javascripts/discourse/components/user-selector.js.es6 +++ b/app/assets/javascripts/discourse/components/user-selector.js.es6 @@ -16,18 +16,15 @@ export default TextField.extend({ return selected; } - var template = this.container.lookup('template:user-selector-autocomplete.raw'); - $(this.get('element')).val(this.get('usernames')).autocomplete({ - template: template, - + this.$().val(this.get('usernames')).autocomplete({ + template: this.container.lookup('template:user-selector-autocomplete.raw'), disabled: this.get('disabled'), single: this.get('single'), allowAny: this.get('allowAny'), dataSource: function(term) { - term = term.replace(/[^a-zA-Z0-9_]/, ''); return userSearch({ - term: term, + term: term.replace(/[^a-zA-Z0-9_]/, ''), topicId: self.get('topicId'), exclude: excludedUsernames(), includeGroups: includeGroups @@ -58,6 +55,15 @@ export default TextField.extend({ } }); - }.on('didInsertElement') + }.on('didInsertElement'), + + // THIS IS A HUGE HACK TO SUPPORT CLEARING THE INPUT + _clearInput: function() { + if (arguments.length > 1) { + if (Em.isEmpty(this.get("usernames"))) { + this.$().parent().find("a").click(); + } + } + }.observes("usernames") }); diff --git a/app/assets/javascripts/discourse/models/group.js b/app/assets/javascripts/discourse/models/group.js index b3fc5798b9..c492cce545 100644 --- a/app/assets/javascripts/discourse/models/group.js +++ b/app/assets/javascripts/discourse/models/group.js @@ -11,6 +11,10 @@ Discourse.Group = Discourse.Model.extend({ offset: 0, user_count: 0, + type: function() { + return this.get("automatic") ? "automatic" : "custom"; + }.property("automatic"), + userCountDisplay: function(){ var c = this.get('user_count'); // don't display zero its ugly @@ -20,7 +24,8 @@ Discourse.Group = Discourse.Model.extend({ findMembers: function() { if (Em.isEmpty(this.get('name'))) { return ; } - var self = this, offset = Math.min(this.get("user_count"), Math.max(this.get("offset"), 0)); + var self = this, + offset = Math.min(this.get("user_count"), Math.max(this.get("offset"), 0)); return Discourse.ajax('/groups/' + this.get('name') + '/members.json', { data: { @@ -100,7 +105,7 @@ Discourse.Group = Discourse.Model.extend({ Discourse.Group.reopenClass({ findAll: function(opts){ - return Discourse.ajax("/admin/groups.json", { data: opts }).then(function(groups){ + return Discourse.ajax("/admin/groups.json", { data: opts }).then(function (groups){ return groups.map(function(g) { return Discourse.Group.create(g); }); }); }, @@ -112,8 +117,8 @@ Discourse.Group.reopenClass({ }, find: function(name) { - return Discourse.ajax("/groups/" + name + ".json").then(function(g) { - return Discourse.Group.create(g.basic_group); + return Discourse.ajax("/groups/" + name + ".json").then(function (result) { + return Discourse.Group.create(result.basic_group); }); } }); diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index d77de3dc56..dca9a5403b 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -477,6 +477,9 @@ section.details { .btn.add { margin-top: 7px; } + .controls { + margin-top: 10px; + } } // Customise area diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index dd9e5f720d..0cdf041369 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -32,7 +32,7 @@ class Admin::GroupsController < Admin::AdminController end def update - group = Group.find(params[:id].to_i) + group = Group.find(params[:id]) group.alias_level = params[:alias_level].to_i if params[:alias_level].present? group.visible = params[:visible] == "true" @@ -47,7 +47,7 @@ class Admin::GroupsController < Admin::AdminController end def destroy - group = Group.find(params[:id].to_i) + group = Group.find(params[:id]) if group.automatic can_not_modify_automatic @@ -63,7 +63,7 @@ class Admin::GroupsController < Admin::AdminController end def add_members - group = Group.find(params.require(:group_id).to_i) + group = Group.find(params.require(:id)) usernames = params.require(:usernames) return can_not_modify_automatic if group.automatic @@ -82,7 +82,7 @@ class Admin::GroupsController < Admin::AdminController end def remove_member - group = Group.find(params.require(:group_id).to_i) + group = Group.find(params.require(:id)) user_id = params.require(:user_id).to_i return can_not_modify_automatic if group.automatic diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 95c269f0b5..d24229b9cb 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1631,6 +1631,8 @@ en: name: "Name" add: "Add" add_members: "Add members" + custom: "Custom" + automatic: "Automatic" api: generate_master: "Generate Master API Key" diff --git a/config/routes.rb b/config/routes.rb index 70d26b1896..3c288b57d0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -46,10 +46,15 @@ Discourse::Application.routes.draw do collection do post "refresh_automatic_groups" => "groups#refresh_automatic_groups" end - delete "members" => "groups#remove_member" - put "members" => "groups#add_members" + member do + put "members" => "groups#add_members" + delete "members" => "groups#remove_member" + end end + get "groups/:type" => "groups#show", constraints: AdminConstraint.new + get "groups/:type/:id" => "groups#show", constraints: AdminConstraint.new + resources :users, id: USERNAME_ROUTE_FORMAT do collection do get "list/:query" => "users#index" diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb index 45be241b06..8c83768e0d 100644 --- a/spec/controllers/admin/groups_controller_spec.rb +++ b/spec/controllers/admin/groups_controller_spec.rb @@ -91,7 +91,7 @@ describe Admin::GroupsController do context ".add_members" do it "cannot add members to automatic groups" do - xhr :put, :add_members, group_id: 1, usernames: "l77t" + xhr :put, :add_members, id: 1, usernames: "l77t" expect(response.status).to eq(422) end @@ -100,7 +100,7 @@ describe Admin::GroupsController do user2 = Fabricate(:user) group = Fabricate(:group) - xhr :put, :add_members, group_id: group.id, usernames: [user1.username, user2.username].join(",") + xhr :put, :add_members, id: group.id, usernames: [user1.username, user2.username].join(",") expect(response).to be_success group.reload @@ -112,7 +112,7 @@ describe Admin::GroupsController do context ".remove_member" do it "cannot remove members from automatic groups" do - xhr :put, :remove_member, group_id: 1, user_id: 42 + xhr :put, :remove_member, id: 1, user_id: 42 expect(response.status).to eq(422) end @@ -122,7 +122,7 @@ describe Admin::GroupsController do group.add(user) group.save - xhr :delete, :remove_member, group_id: group.id, user_id: user.id + xhr :delete, :remove_member, id: group.id, user_id: user.id expect(response).to be_success group.reload From 141f69748263c97478b99406e9a9b92d73103e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 21 Jan 2015 20:54:07 +0100 Subject: [PATCH 077/230] remove unused route --- app/assets/javascripts/admin/routes/admin-group-new.js.es6 | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 app/assets/javascripts/admin/routes/admin-group-new.js.es6 diff --git a/app/assets/javascripts/admin/routes/admin-group-new.js.es6 b/app/assets/javascripts/admin/routes/admin-group-new.js.es6 deleted file mode 100644 index 5e1cff95a4..0000000000 --- a/app/assets/javascripts/admin/routes/admin-group-new.js.es6 +++ /dev/null @@ -1,6 +0,0 @@ -export default Discourse.Route.extend({ - renderTemplate: function() { - debugger; - this.render("admin/templates/group"); - } -}); From 62c12915485e6f37dcf625807107e0efcd59460d Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Thu, 22 Jan 2015 02:28:04 +0530 Subject: [PATCH 078/230] FIX: user name and title were not showing on post creation --- app/assets/javascripts/discourse/models/composer.js | 2 ++ app/serializers/current_user_serializer.rb | 1 + 2 files changed, 3 insertions(+) diff --git a/app/assets/javascripts/discourse/models/composer.js b/app/assets/javascripts/discourse/models/composer.js index 1e25f9b67b..2379584aa5 100644 --- a/app/assets/javascripts/discourse/models/composer.js +++ b/app/assets/javascripts/discourse/models/composer.js @@ -530,9 +530,11 @@ Discourse.Composer = Discourse.Model.extend({ imageSizes: opts.imageSizes, cooked: this.getCookedHtml(), reply_count: 0, + name: currentUser.get('name'), display_username: currentUser.get('name'), username: currentUser.get('username'), user_id: currentUser.get('id'), + user_title: currentUser.get('title'), uploaded_avatar_id: currentUser.get('uploaded_avatar_id'), user_custom_fields: currentUser.get('custom_fields'), post_type: Discourse.Site.currentProp('post_types.regular'), diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb index 7264c65a31..f07cd907e6 100644 --- a/app/serializers/current_user_serializer.rb +++ b/app/serializers/current_user_serializer.rb @@ -9,6 +9,7 @@ class CurrentUserSerializer < BasicUserSerializer :site_flagged_posts_count, :moderator?, :staff?, + :title, :reply_count, :topic_count, :enable_quoting, From 7741e4dc027fc2b316f7c0aa6be4b8b80a9e1940 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 21 Jan 2015 16:20:43 -0500 Subject: [PATCH 079/230] Change the `admin-menu` outlet to use a tagName of `li` --- app/assets/javascripts/admin/templates/admin.hbs | 2 +- .../javascripts/discourse/helpers/plugin-outlet.js.es6 | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/admin/templates/admin.hbs b/app/assets/javascripts/admin/templates/admin.hbs index 0958fe0d42..6a903c922b 100644 --- a/app/assets/javascripts/admin/templates/admin.hbs +++ b/app/assets/javascripts/admin/templates/admin.hbs @@ -23,7 +23,7 @@
    • {{#link-to 'admin.api'}}{{i18n 'admin.api.title'}}{{/link-to}}
    • {{#link-to 'admin.backups'}}{{i18n 'admin.backups.title'}}{{/link-to}}
    • {{/if}} - {{plugin-outlet "admin-menu"}} + {{plugin-outlet "admin-menu" tagName="li"}}
    diff --git a/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 b/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 index 120eb208b8..42802f8bce 100644 --- a/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 +++ b/app/assets/javascripts/discourse/helpers/plugin-outlet.js.es6 @@ -88,20 +88,21 @@ export default function(connectionName, options) { if (!_connectorCache) { buildConnectorCache(); } if (_connectorCache[connectionName]) { - var view; + var viewClass; var childViews = _connectorCache[connectionName]; // If there is more than one view, create a container. Otherwise // just shove it in. if (childViews.length > 1) { - view = Ember.ContainerView.extend({ + viewClass = Ember.ContainerView.extend({ childViews: childViews }); } else { - view = childViews[0]; + viewClass = childViews[0]; } + delete options.fn; // we don't need the default template since we have a connector - return Ember.Handlebars.helpers.view.call(this, view, options); + return Ember.Handlebars.helpers.view.call(this, viewClass, options); } else if (options.fn) { // If a block is passed, render its content. return Ember.Handlebars.helpers.view.call(this, From 356ad4459b5c8ce1760d3a32077fb9212318c1fe Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 22 Jan 2015 00:30:30 -0800 Subject: [PATCH 080/230] add unhandled flags to the contact email help --- config/locales/server.en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 2dfe2267df..b3f1a3e375 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -658,7 +658,7 @@ en: educate_until_posts: "When the user starts typing their first (n) new posts, show the pop-up new user education panel in the composer." title: "The name of this site, as used in the title tag." site_description: "Describe this site in one sentence, as used in the meta description tag." - contact_email: "Email address of key contact responsible for this site. Used for critical notifications only, as well as on the /about contact form for urgent matters." + contact_email: "Email address of key contact responsible for this site. Used for critical notifications such as unhandled flags, as well as on the /about contact form for urgent matters." queue_jobs: "DEVELOPER ONLY! WARNING! By default, queue jobs in sidekiq. If disabled, your site will be broken." crawl_images: "Retrieve images from remote URLs to insert the correct width and height dimensions." download_remote_images_to_local: "Convert remote images to local images by downloading them; this prevents broken images." From 91d36b2532096570b2cd5167eb333ef65f1b4fba Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Thu, 22 Jan 2015 15:46:52 +0530 Subject: [PATCH 081/230] FIX: months were not getting translated --- config/locales/server.en.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 2dfe2267df..ef52b181ea 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -31,11 +31,18 @@ en: short_date_no_year: "D MMM" short_date: "D MMM, YYYY" long_date: "MMMM D, YYYY h:mma" - time: + + datetime: &datetime + month_names: + [~, January, February, March, April, May, June, July, August, September, October, November, December] formats: short: "%m-%d-%Y" short_no_year: "%B %-d" date_only: "%b %-d, %Y" + date: + <<: *datetime + time: + <<: *datetime title: "Discourse" topics: "Topics" From 63556a904a52423b5b55dccda6080331763eb0aa Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Thu, 22 Jan 2015 22:34:35 +0530 Subject: [PATCH 082/230] FIX: full user names were showing up in crawlers and rss feeds in spite enables_names setting being disabled --- app/views/list/list.rss.erb | 4 ++-- app/views/topics/plain.html.erb | 2 +- app/views/topics/show.html.erb | 2 +- app/views/topics/show.rss.erb | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/views/list/list.rss.erb b/app/views/list/list.rss.erb index afeed83e0a..2e48d6737b 100644 --- a/app/views/list/list.rss.erb +++ b/app/views/list/list.rss.erb @@ -14,10 +14,10 @@ <% topic_url = Discourse.base_url + topic.relative_url -%> <%= topic.title %> - <%= "no-reply@example.com (@#{topic.user.username}#{" #{topic.user.name}" if topic.user.name.present?})" -%> + <%= "no-reply@example.com (@#{topic.user.username}#{" #{topic.user.name}" if (topic.user.name.present? && SiteSetting.enable_names?)})" -%> <%= topic.category.name %> <%= t('author_wrote', author: link_to(topic.user.name, user_url(topic.user.username_lower))).html_safe %>

    +

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

    <%= topic.posts.first.cooked.html_safe %>
    diff --git a/app/views/topics/plain.html.erb b/app/views/topics/plain.html.erb index 7c852a3b69..88385ff7da 100644 --- a/app/views/topics/plain.html.erb +++ b/app/views/topics/plain.html.erb @@ -11,7 +11,7 @@ <% @topic_view.posts.each do |post| %> <% if post.user %>
    - <%= post.user.username %> <%= "(#{post.user.name})" if SiteSetting.display_name_on_posts %> at <%= post.created_at.to_formatted_s(:long_ordinal) %> — #<%= post.post_number %> + <%= post.user.username %> <%= "(#{post.user.name})" if (SiteSetting.display_name_on_posts && SiteSetting.enable_names?) %> at <%= post.created_at.to_formatted_s(:long_ordinal) %> — #<%= post.post_number %>
    <% if post.hidden %> diff --git a/app/views/topics/show.html.erb b/app/views/topics/show.html.erb index 42421fb7b8..846bdc38c8 100644 --- a/app/views/topics/show.html.erb +++ b/app/views/topics/show.html.erb @@ -10,7 +10,7 @@ <% if post.user %>
    <%= post.user.username %> - <%= "(#{post.user.name})" if SiteSetting.display_name_on_posts %> — + <%= "(#{post.user.name})" if (SiteSetting.display_name_on_posts && SiteSetting.enable_names?) %> — <%= post.created_at.to_formatted_s(:iso8601) %> — #<%= post.post_number %>
    diff --git a/app/views/topics/show.rss.erb b/app/views/topics/show.rss.erb index 2e1271b4b9..be903c7a29 100644 --- a/app/views/topics/show.rss.erb +++ b/app/views/topics/show.rss.erb @@ -15,10 +15,10 @@ <% next unless post.user %> <%= @topic_view.title %> - <%= "no-reply@example.com (@#{post.user.username}#{" #{post.user.name}" if post.user.name.present?})" -%> + <%= "no-reply@example.com (@#{post.user.username}#{" #{post.user.name}" if (post.user.name.present? && SiteSetting.enable_names?)})" -%> -

    <%= t('author_wrote', author: link_to(post.user.name, user_url(post.user.username_lower))).html_safe %>

    +

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

    <% if post.hidden %> <%= t('flagging.user_must_edit').html_safe %> From b3a2c0c45b369b263ee50d3018a21e9472fe0bb1 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 22 Jan 2015 12:20:17 -0500 Subject: [PATCH 083/230] SECURITY: The SSO `return_path` was an open redirect This security fix needs SSO to be configured, and the user has to go through the entire auth process before being redirected to the wrong host so it is probably lower priority for most installs. --- app/controllers/session_controller.rb | 11 +++++++ spec/controllers/session_controller_spec.rb | 34 +++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb index 66a8c99512..12c736d1d2 100644 --- a/app/controllers/session_controller.rb +++ b/app/controllers/session_controller.rb @@ -72,6 +72,17 @@ class SessionController < ApplicationController else log_on_user user end + + # If it's not a relative URL check the host + if return_path !~ /^\/[^\/]/ + begin + uri = URI(return_path) + return_path = "/" unless uri.host == Discourse.current_hostname + rescue + return_path = "/" + end + end + redirect_to return_path else render text: I18n.t("sso.not_found"), status: 500 diff --git a/spec/controllers/session_controller_spec.rb b/spec/controllers/session_controller_spec.rb index 584e1f3e6e..2a6dccae2a 100644 --- a/spec/controllers/session_controller_spec.rb +++ b/spec/controllers/session_controller_spec.rb @@ -26,6 +26,8 @@ describe SessionController do @sso_url = "http://somesite.com/discourse_sso" @sso_secret = "shjkfdhsfkjh" + request.host = Discourse.current_hostname + SiteSetting.enable_sso = true SiteSetting.sso_url = @sso_url SiteSetting.sso_secret = @sso_secret @@ -79,7 +81,39 @@ describe SessionController do logged_on_user = Discourse.current_user_provider.new(request.env).current_user expect(logged_on_user.admin).to eq(true) + end + it 'redirects to a non-relative url' do + sso = get_sso("#{Discourse.base_url}/b/") + sso.external_id = '666' # the number of the beast + sso.email = 'bob@bob.com' + sso.name = 'Sam Saffron' + sso.username = 'sam' + + get :sso_login, Rack::Utils.parse_query(sso.payload) + expect(response).to redirect_to('/b/') + end + + it 'redirects to root if the host of the return_path is different' do + sso = get_sso('//eviltrout.com') + sso.external_id = '666' # the number of the beast + sso.email = 'bob@bob.com' + sso.name = 'Sam Saffron' + sso.username = 'sam' + + get :sso_login, Rack::Utils.parse_query(sso.payload) + expect(response).to redirect_to('/') + end + + it 'redirects to root if the host of the return_path is different' do + sso = get_sso('http://eviltrout.com') + sso.external_id = '666' # the number of the beast + sso.email = 'bob@bob.com' + sso.name = 'Sam Saffron' + sso.username = 'sam' + + get :sso_login, Rack::Utils.parse_query(sso.payload) + expect(response).to redirect_to('/') end it 'allows you to create an account' do From 08cc9bc6efac6092de01e1994929578bfa44ab6c Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 22 Jan 2015 14:10:46 -0500 Subject: [PATCH 084/230] Update onebox gem for more Imgur support --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index d9a7d662df..d6e31776fd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -271,7 +271,7 @@ GEM omniauth-twitter (1.0.1) multi_json (~> 1.3) omniauth-oauth (~> 1.0) - onebox (1.5.7) + onebox (1.5.8) moneta (~> 0.7) multi_json (~> 1.7) mustache (~> 0.99) From 87cfbfa117e0a2d3321f411f350b46ee662746df Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 22 Jan 2015 14:53:11 -0500 Subject: [PATCH 085/230] Another imgur onebox fix --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index d6e31776fd..debd886241 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -271,7 +271,7 @@ GEM omniauth-twitter (1.0.1) multi_json (~> 1.3) omniauth-oauth (~> 1.0) - onebox (1.5.8) + onebox (1.5.9) moneta (~> 0.7) multi_json (~> 1.7) mustache (~> 0.99) From 22d580fe76c6a260c4b9deace24b1a85b137e4c4 Mon Sep 17 00:00:00 2001 From: Erick Guan Date: Fri, 23 Jan 2015 17:57:01 +0800 Subject: [PATCH 086/230] allow to translate image's exceprt --- config/locales/server.en.yml | 2 ++ lib/excerpt_parser.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 9f821c150a..e57e5d5c90 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -138,6 +138,8 @@ en: hot: "Hot topics" too_late_to_edit: "That post was created too long ago. It can no longer be edited or deleted." + excerpt_image: "image" + groups: errors: can_not_modify_automatic: "You can not modify an automatic group" diff --git a/lib/excerpt_parser.rb b/lib/excerpt_parser.rb index 4d4a107b99..bb2606a1a2 100644 --- a/lib/excerpt_parser.rb +++ b/lib/excerpt_parser.rb @@ -56,7 +56,7 @@ class ExcerptParser < Nokogiri::XML::SAX::Document elsif attributes["title"] characters("[#{attributes["title"]}]") else - characters("[image]") + characters("[#{I18n.t 'excerpt_image'}]") end characters("(#{attributes['src']})") if @markdown_images From 848d60d459ea0be4f066c3fdde1a78d0842800f3 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 23 Jan 2015 10:46:04 -0500 Subject: [PATCH 087/230] FIX: Posts weren't highlighting on initial load --- .../javascripts/discourse/routes/topic-from-params.js.es6 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/routes/topic-from-params.js.es6 b/app/assets/javascripts/discourse/routes/topic-from-params.js.es6 index cd6acccc81..9c414daac0 100644 --- a/app/assets/javascripts/discourse/routes/topic-from-params.js.es6 +++ b/app/assets/javascripts/discourse/routes/topic-from-params.js.es6 @@ -34,7 +34,11 @@ export default Discourse.Route.extend({ progressPosition: progress, expanded: false }); - self.appEvents.trigger('post:highlight', closest); + + // Highlight our post after the next render + Ember.run.scheduleOnce('afterRender', function() { + self.appEvents.trigger('post:highlight', closest); + }); Discourse.URL.jumpToPost(closest); if (topic.present('draft')) { From 2ea4c1c9dee4d5a5f3392ea7a8a50883c8b4d710 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 23 Jan 2015 11:13:27 -0500 Subject: [PATCH 088/230] FIX: Groups post page was broken. Also added integration tests so we don't miss this breaking in the future. --- .../discourse/helpers/category-link.js.es6 | 2 +- .../discourse/templates/group/members.hbs | 3 +-- test/javascripts/fixtures/group-fixtures.js.es6 | 6 ++++++ test/javascripts/integration/groups-test.js.es6 | 12 ++++++++++++ 4 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 test/javascripts/fixtures/group-fixtures.js.es6 create mode 100644 test/javascripts/integration/groups-test.js.es6 diff --git a/app/assets/javascripts/discourse/helpers/category-link.js.es6 b/app/assets/javascripts/discourse/helpers/category-link.js.es6 index ea71350dfa..628771d424 100644 --- a/app/assets/javascripts/discourse/helpers/category-link.js.es6 +++ b/app/assets/javascripts/discourse/helpers/category-link.js.es6 @@ -32,7 +32,7 @@ export function categoryBadgeHTML(category, opts) { var html = ""; - var parentCat = Discourse.Category.findById(category.get('parent_category_id')); + var parentCat = Discourse.Category.findById(get(category, 'parent_category_id')); if (opts.hideParent) { parentCat = null; } html += categoryStripe(tagName, parentCat, extraClasses, href); diff --git a/app/assets/javascripts/discourse/templates/group/members.hbs b/app/assets/javascripts/discourse/templates/group/members.hbs index 16728d740e..6d362606d8 100644 --- a/app/assets/javascripts/discourse/templates/group/members.hbs +++ b/app/assets/javascripts/discourse/templates/group/members.hbs @@ -4,7 +4,7 @@ {{i18n 'last_seen'}} {{#each m in members}} - + {{avatar m imageSize="large"}} @@ -16,7 +16,6 @@ {{bound-date m.last_seen_at}} -
    {{/each}} {{/if}} diff --git a/test/javascripts/fixtures/group-fixtures.js.es6 b/test/javascripts/fixtures/group-fixtures.js.es6 new file mode 100644 index 0000000000..03382b9a74 --- /dev/null +++ b/test/javascripts/fixtures/group-fixtures.js.es6 @@ -0,0 +1,6 @@ +export default { + "/groups/discourse.json": {"basic_group":{"id":47,"automatic":false,"name":"discourse","user_count":8,"alias_level":0,"visible":true}}, + "/groups/discourse/counts.json": {"counts":{"posts":17829,"members":7}}, + "/groups/discourse/members.json": {"members":[{"id":2770,"username":"awesomerobot","uploaded_avatar_id":33872,"avatar_template":"/user_avatar/meta.discourse.org/awesomerobot/{size}/33872.png","name":"","last_seen_at":"2015-01-23T15:53:17.844Z"},{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png","name":"Jeff Atwood","last_seen_at":"2015-01-23T06:05:25.457Z"},{"id":19,"username":"eviltrout","uploaded_avatar_id":5275,"avatar_template":"/user_avatar/meta.discourse.org/eviltrout/{size}/5275.png","name":"Robin Ward","last_seen_at":"2015-01-23T16:03:45.098Z"},{"id":2,"username":"neil","uploaded_avatar_id":5245,"avatar_template":"/user_avatar/meta.discourse.org/neil/{size}/5245.png","name":"Neil Lalonde","last_seen_at":"2015-01-23T15:22:10.244Z"},{"id":1,"username":"sam","uploaded_avatar_id":5243,"avatar_template":"/user_avatar/meta.discourse.org/sam/{size}/5243.png","name":"Sam Saffron","last_seen_at":"2015-01-23T11:07:06.233Z"},{"id":3,"username":"supermathie","uploaded_avatar_id":34097,"avatar_template":"/user_avatar/meta.discourse.org/supermathie/{size}/34097.png","name":"Michael Brown","last_seen_at":"2015-01-22T05:16:42.254Z"},{"id":1995,"username":"zogstrip","uploaded_avatar_id":8630,"avatar_template":"/user_avatar/meta.discourse.org/zogstrip/{size}/8630.png","name":"Régis Hanol","last_seen_at":"2015-01-23T15:45:34.196Z"}],"meta":{"total":7,"limit":50,"offset":0}}, + "/groups/discourse/posts.json": [{"id":94607,"cooked":"

    Right now we have two entirely different styles for new topics and new posts within a topic... we can probably fix that pretty easily.

    \n\n

    \n\n

    So the simple change would be:

    \n\n

    \n\n

    but... while the dot makes the \"• new\" stand out more... it doesn't communicate any information other than \"look at me\" — can we add more context without adding more noise?

    \n\n

    ","created_at":"2015-01-23T15:13:01.935Z","title":"Consistent new indicator","url":"/t/consistent-new-indicator/24355/1","user_title":"designerator","user_long_name":"","category":{"id":9,"name":"ux","color":"5F497A","topic_id":2628,"topic_count":540,"created_at":"2013-02-10T03:52:21.322Z","updated_at":"2015-01-22T18:05:32.152Z","user_id":32,"topics_year":370,"topics_month":33,"topics_week":3,"slug":"ux","description":"Discussion about the user interface of Discourse, how features are presented to the user in the client, including language and UI elements.","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":5823,"latest_post_id":94610,"latest_topic_id":24355,"position":25,"parent_category_id":null,"posts_year":4264,"posts_month":609,"posts_week":103,"email_in":null,"email_in_allow_strangers":false,"topics_day":0,"posts_day":28,"logo_url":null,"background_url":null,"allow_badges":true,"name_lower":"ux","auto_close_based_on_last_post":false},"user":{"id":2770,"username":"awesomerobot","uploaded_avatar_id":33872,"avatar_template":"/user_avatar/meta.discourse.org/awesomerobot/{size}/33872.png"}},{"id":94603,"cooked":"

    Agree that the markup isn't ideal - it's kind of hacked together at the moment; especially because we have two different styles. I think once we settle on the specifics it can be re-written entirely.

    ","created_at":"2015-01-23T14:59:21.941Z","title":"The end of Clown Vomit, or, simplified category styles","url":"/t/the-end-of-clown-vomit-or-simplified-category-styles/24249/63","user_title":"designerator","user_long_name":"","category":{"id":9,"name":"ux","color":"5F497A","topic_id":2628,"topic_count":540,"created_at":"2013-02-10T03:52:21.322Z","updated_at":"2015-01-22T18:05:32.152Z","user_id":32,"topics_year":370,"topics_month":33,"topics_week":3,"slug":"ux","description":"Discussion about the user interface of Discourse, how features are presented to the user in the client, including language and UI elements.","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":5823,"latest_post_id":94610,"latest_topic_id":24355,"position":25,"parent_category_id":null,"posts_year":4264,"posts_month":609,"posts_week":103,"email_in":null,"email_in_allow_strangers":false,"topics_day":0,"posts_day":28,"logo_url":null,"background_url":null,"allow_badges":true,"name_lower":"ux","auto_close_based_on_last_post":false},"user":{"id":2770,"username":"awesomerobot","uploaded_avatar_id":33872,"avatar_template":"/user_avatar/meta.discourse.org/awesomerobot/{size}/33872.png"}},{"id":94601,"cooked":"

    Yeah I think this category arrangement is the way to go at the very least - much easier to scan two columns...

    \n\n

    Also, maybe square off the bars?

    \n\n

    \n\n

    ","created_at":"2015-01-23T14:51:55.497Z","title":"The end of Clown Vomit, or, simplified category styles","url":"/t/the-end-of-clown-vomit-or-simplified-category-styles/24249/62","user_title":"designerator","user_long_name":"","category":{"id":9,"name":"ux","color":"5F497A","topic_id":2628,"topic_count":540,"created_at":"2013-02-10T03:52:21.322Z","updated_at":"2015-01-22T18:05:32.152Z","user_id":32,"topics_year":370,"topics_month":33,"topics_week":3,"slug":"ux","description":"Discussion about the user interface of Discourse, how features are presented to the user in the client, including language and UI elements.","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":5823,"latest_post_id":94610,"latest_topic_id":24355,"position":25,"parent_category_id":null,"posts_year":4264,"posts_month":609,"posts_week":103,"email_in":null,"email_in_allow_strangers":false,"topics_day":0,"posts_day":28,"logo_url":null,"background_url":null,"allow_badges":true,"name_lower":"ux","auto_close_based_on_last_post":false},"user":{"id":2770,"username":"awesomerobot","uploaded_avatar_id":33872,"avatar_template":"/user_avatar/meta.discourse.org/awesomerobot/{size}/33872.png"}},{"id":94577,"cooked":"

    Yup, that's the latest version \"wink\"

    \n\n

    \n\n

    (click to view animated version)

    ","created_at":"2015-01-23T10:50:55.846Z","title":"Quote reply insertion at cursor position","url":"/t/quote-reply-insertion-at-cursor-position/24344/4","user_title":"team","user_long_name":"Régis Hanol","category":{"id":2,"name":"feature","color":"0E76BD","topic_id":11,"topic_count":1592,"created_at":"2013-02-02T21:42:52.552Z","updated_at":"2015-01-22T18:05:32.647Z","user_id":1,"topics_year":919,"topics_month":60,"topics_week":20,"slug":"feature","description":"Discussion about features or potential features of Discourse: how they work, why they work, etc.","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":14360,"latest_post_id":94600,"latest_topic_id":24344,"position":25,"parent_category_id":null,"posts_year":8617,"posts_month":690,"posts_week":190,"email_in":null,"email_in_allow_strangers":false,"topics_day":2,"posts_day":8,"logo_url":null,"background_url":null,"allow_badges":true,"name_lower":"feature","auto_close_based_on_last_post":false},"user":{"id":1995,"username":"zogstrip","uploaded_avatar_id":8630,"avatar_template":"/user_avatar/meta.discourse.org/zogstrip/{size}/8630.png"}},{"id":94574,"cooked":"\n\n

    It used to be that but that was fixed a while ago. Are you running a recent version?

    ","created_at":"2015-01-23T10:31:29.222Z","title":"Quote reply insertion at cursor position","url":"/t/quote-reply-insertion-at-cursor-position/24344/2","user_title":"team","user_long_name":"Régis Hanol","category":{"id":2,"name":"feature","color":"0E76BD","topic_id":11,"topic_count":1592,"created_at":"2013-02-02T21:42:52.552Z","updated_at":"2015-01-22T18:05:32.647Z","user_id":1,"topics_year":919,"topics_month":60,"topics_week":20,"slug":"feature","description":"Discussion about features or potential features of Discourse: how they work, why they work, etc.","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":14360,"latest_post_id":94600,"latest_topic_id":24344,"position":25,"parent_category_id":null,"posts_year":8617,"posts_month":690,"posts_week":190,"email_in":null,"email_in_allow_strangers":false,"topics_day":2,"posts_day":8,"logo_url":null,"background_url":null,"allow_badges":true,"name_lower":"feature","auto_close_based_on_last_post":false},"user":{"id":1995,"username":"zogstrip","uploaded_avatar_id":8630,"avatar_template":"/user_avatar/meta.discourse.org/zogstrip/{size}/8630.png"}},{"id":94572,"cooked":"\n\n

    That's an Ember update that introduced this change.

    ","created_at":"2015-01-23T09:46:00.901Z","title":"Translations frequently broken","url":"/t/translations-frequently-broken/22546/27","user_title":"team","user_long_name":"Régis Hanol","category":{"id":27,"name":"translations","color":"808281","topic_id":14549,"topic_count":146,"created_at":"2014-04-07T20:30:17.623Z","updated_at":"2015-01-22T18:05:33.111Z","user_id":2,"topics_year":134,"topics_month":5,"topics_week":3,"slug":"translations","description":"This category is for discussion about localizing Discourse.","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":1167,"latest_post_id":94575,"latest_topic_id":24301,"position":25,"parent_category_id":7,"posts_year":965,"posts_month":60,"posts_week":29,"email_in":null,"email_in_allow_strangers":false,"topics_day":1,"posts_day":5,"logo_url":null,"background_url":null,"allow_badges":true,"name_lower":"translations","auto_close_based_on_last_post":false},"user":{"id":1995,"username":"zogstrip","uploaded_avatar_id":8630,"avatar_template":"/user_avatar/meta.discourse.org/zogstrip/{size}/8630.png"}},{"id":94555,"cooked":"

    I don't know how to pronounce that in English, but this makes me think of the French word \"disquette\" (floppy disk) \"smile\"

    ","created_at":"2015-01-23T08:17:31.700Z","title":"Introducing Discette - a minimal ember-cli front end to Discourse","url":"/t/introducing-discette-a-minimal-ember-cli-front-end-to-discourse/24321/3","user_title":"team","user_long_name":"Régis Hanol","category":{"id":7,"name":"dev","color":"000","topic_id":1026,"topic_count":574,"created_at":"2013-02-06T08:43:41.550Z","updated_at":"2015-01-22T18:05:32.855Z","user_id":32,"topics_year":298,"topics_month":29,"topics_week":2,"slug":"dev","description":"This category is for topics related to hacking on Discourse: submitting pull requests, configuring development environments, coding conventions, and so forth.","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":4196,"latest_post_id":94590,"latest_topic_id":24349,"position":25,"parent_category_id":null,"posts_year":2095,"posts_month":172,"posts_week":16,"email_in":null,"email_in_allow_strangers":false,"topics_day":0,"posts_day":3,"logo_url":null,"background_url":null,"allow_badges":true,"name_lower":"dev","auto_close_based_on_last_post":false},"user":{"id":1995,"username":"zogstrip","uploaded_avatar_id":8630,"avatar_template":"/user_avatar/meta.discourse.org/zogstrip/{size}/8630.png"}},{"id":94544,"cooked":"

    @techapj fixed this for 1.2.

    ","created_at":"2015-01-23T05:49:35.881Z","title":"After sign-in, I'm not redirected to the conversation","url":"/t/after-sign-in-im-not-redirected-to-the-conversation/17753/8","user_title":"co-founder","user_long_name":"Jeff Atwood","category":{"id":9,"name":"ux","color":"5F497A","topic_id":2628,"topic_count":540,"created_at":"2013-02-10T03:52:21.322Z","updated_at":"2015-01-22T18:05:32.152Z","user_id":32,"topics_year":370,"topics_month":33,"topics_week":3,"slug":"ux","description":"Discussion about the user interface of Discourse, how features are presented to the user in the client, including language and UI elements.","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":5823,"latest_post_id":94610,"latest_topic_id":24355,"position":25,"parent_category_id":null,"posts_year":4264,"posts_month":609,"posts_week":103,"email_in":null,"email_in_allow_strangers":false,"topics_day":0,"posts_day":28,"logo_url":null,"background_url":null,"allow_badges":true,"name_lower":"ux","auto_close_based_on_last_post":false},"user":{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png"}},{"id":94543,"cooked":"

    Oh yes IOS 8.2 -- well, let's see what happens because there is really no fix on our end. Basic HTML / CSS stuff is broken.

    ","created_at":"2015-01-23T05:45:40.306Z","title":"Dealing with iOS 8 Mobile Safari bugs?","url":"/t/dealing-with-ios-8-mobile-safari-bugs/24101/7","user_title":"co-founder","user_long_name":"Jeff Atwood","category":{"id":2,"name":"feature","color":"0E76BD","topic_id":11,"topic_count":1592,"created_at":"2013-02-02T21:42:52.552Z","updated_at":"2015-01-22T18:05:32.647Z","user_id":1,"topics_year":919,"topics_month":60,"topics_week":20,"slug":"feature","description":"Discussion about features or potential features of Discourse: how they work, why they work, etc.","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":14360,"latest_post_id":94600,"latest_topic_id":24344,"position":25,"parent_category_id":null,"posts_year":8617,"posts_month":690,"posts_week":190,"email_in":null,"email_in_allow_strangers":false,"topics_day":2,"posts_day":8,"logo_url":null,"background_url":null,"allow_badges":true,"name_lower":"feature","auto_close_based_on_last_post":false},"user":{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png"}},{"id":94542,"cooked":"

    Hmm that looks like a bug, @techapj can you have a look?

    ","created_at":"2015-01-23T05:43:55.602Z","title":"RSS is not valid","url":"/t/rss-is-not-valid/24338/2","user_title":"co-founder","user_long_name":"Jeff Atwood","category":{"id":6,"name":"support","color":"CEA9A9","topic_id":389,"topic_count":1781,"created_at":"2013-02-05T22:16:38.672Z","updated_at":"2015-01-22T18:05:33.572Z","user_id":1,"topics_year":1541,"topics_month":167,"topics_week":49,"slug":"support","description":"Support on configuring and using Discourse after it is up and running. For installation questions, use the install category.","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":12272,"latest_post_id":94602,"latest_topic_id":24346,"position":25,"parent_category_id":null,"posts_year":10571,"posts_month":1254,"posts_week":413,"email_in":null,"email_in_allow_strangers":false,"topics_day":5,"posts_day":70,"logo_url":"","background_url":"","allow_badges":true,"name_lower":"support","auto_close_based_on_last_post":false},"user":{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png"}},{"id":94522,"cooked":"

    Oh I see. @zogstrip can you have a look?

    ","created_at":"2015-01-23T03:00:20.485Z","title":"Pasted image upload size error","url":"/t/pasted-image-upload-size-error/24320/4","user_title":"co-founder","user_long_name":"Jeff Atwood","category":{"id":1,"name":"bug","color":"e9dd00","topic_id":2,"topic_count":1729,"created_at":"2013-02-01T04:56:34.914Z","updated_at":"2015-01-22T18:05:33.426Z","user_id":1,"topics_year":1114,"topics_month":69,"topics_week":22,"slug":"bug","description":"A bug report means something is broken, preventing normal/typical use of Discourse. Do be sure to search prior to submitting bugs. Include repro steps, and only describe one bug per topic please.","text_color":"000000","read_restricted":false,"auto_close_hours":null,"post_count":11179,"latest_post_id":94611,"latest_topic_id":24350,"position":25,"parent_category_id":null,"posts_year":7138,"posts_month":397,"posts_week":121,"email_in":null,"email_in_allow_strangers":false,"topics_day":1,"posts_day":6,"logo_url":null,"background_url":null,"allow_badges":true,"name_lower":"bug","auto_close_based_on_last_post":false},"user":{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png"}},{"id":94521,"cooked":"\n\n

    Yeah probably.

    \n\n\n\n

    Definitely a good idea. We have seen some eye melting color schemes people have picked for categories.. Much less subcategories.

    \n\n\n\n

    Sure try http://talk.folksy.com -- it's still too much color in boxes. Particularly anywhere a bunch of categories are displayed together, which is a lot of places considering the topic list is the main form of nav, both on the homepage default of latest and in suggested topics at the bottom of every topic...

    \n\n

    ","created_at":"2015-01-23T02:58:27.451Z","title":"The end of Clown Vomit, or, simplified category styles","url":"/t/the-end-of-clown-vomit-or-simplified-category-styles/24249/57","user_title":"co-founder","user_long_name":"Jeff Atwood","category":{"id":9,"name":"ux","color":"5F497A","topic_id":2628,"topic_count":540,"created_at":"2013-02-10T03:52:21.322Z","updated_at":"2015-01-22T18:05:32.152Z","user_id":32,"topics_year":370,"topics_month":33,"topics_week":3,"slug":"ux","description":"Discussion about the user interface of Discourse, how features are presented to the user in the client, including language and UI elements.","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":5823,"latest_post_id":94610,"latest_topic_id":24355,"position":25,"parent_category_id":null,"posts_year":4264,"posts_month":609,"posts_week":103,"email_in":null,"email_in_allow_strangers":false,"topics_day":0,"posts_day":28,"logo_url":null,"background_url":null,"allow_badges":true,"name_lower":"ux","auto_close_based_on_last_post":false},"user":{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png"}},{"id":94519,"cooked":"

    What would you suggest writing here that would be more clear?

    ","created_at":"2015-01-23T02:45:36.859Z","title":"What is \"Born mobile, born to touch\" supposed to tell me?","url":"/t/what-is-born-mobile-born-to-touch-supposed-to-tell-me/24329/3","user_title":"co-founder","user_long_name":"Jeff Atwood","category":{"id":3,"name":"meta","color":"aaa","topic_id":24,"topic_count":139,"created_at":"2013-02-03T00:00:15.230Z","updated_at":"2015-01-22T18:05:32.797Z","user_id":1,"topics_year":68,"topics_month":5,"topics_week":1,"slug":"meta","description":"Discussion about meta.discourse.org itself, the organization of this forum about Discourse, how it works, and how we can improve this site.","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":1116,"latest_post_id":94559,"latest_topic_id":24208,"position":25,"parent_category_id":null,"posts_year":553,"posts_month":33,"posts_week":8,"email_in":null,"email_in_allow_strangers":false,"topics_day":0,"posts_day":0,"logo_url":null,"background_url":null,"allow_badges":true,"name_lower":"meta","auto_close_based_on_last_post":false},"user":{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png"}},{"id":94518,"cooked":"

    You should generally create topics to host things like this, then make them wiki, close them, etc.

    ","created_at":"2015-01-23T02:42:20.053Z","title":"How to Create Static Pages in Discourse?","url":"/t/how-to-create-static-pages-in-discourse/24313/2","user_title":"co-founder","user_long_name":"Jeff Atwood","category":{"id":6,"name":"support","color":"CEA9A9","topic_id":389,"topic_count":1781,"created_at":"2013-02-05T22:16:38.672Z","updated_at":"2015-01-22T18:05:33.572Z","user_id":1,"topics_year":1541,"topics_month":167,"topics_week":49,"slug":"support","description":"Support on configuring and using Discourse after it is up and running. For installation questions, use the install category.","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":12272,"latest_post_id":94602,"latest_topic_id":24346,"position":25,"parent_category_id":null,"posts_year":10571,"posts_month":1254,"posts_week":413,"email_in":null,"email_in_allow_strangers":false,"topics_day":5,"posts_day":70,"logo_url":"","background_url":"","allow_badges":true,"name_lower":"support","auto_close_based_on_last_post":false},"user":{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png"}},{"id":94517,"cooked":"

    Doubtful this is a bug, probably dependent on the PNG encoding.

    \n\n

    Try using PNGOUT, or converting to 8 bit PNGOUT, to see some of the differences. And PNGOUT is lossless!

    ","created_at":"2015-01-23T02:41:30.287Z","title":"Pasted image upload size error","url":"/t/pasted-image-upload-size-error/24320/2","user_title":"co-founder","user_long_name":"Jeff Atwood","category":{"id":1,"name":"bug","color":"e9dd00","topic_id":2,"topic_count":1729,"created_at":"2013-02-01T04:56:34.914Z","updated_at":"2015-01-22T18:05:33.426Z","user_id":1,"topics_year":1114,"topics_month":69,"topics_week":22,"slug":"bug","description":"A bug report means something is broken, preventing normal/typical use of Discourse. Do be sure to search prior to submitting bugs. Include repro steps, and only describe one bug per topic please.","text_color":"000000","read_restricted":false,"auto_close_hours":null,"post_count":11179,"latest_post_id":94611,"latest_topic_id":24350,"position":25,"parent_category_id":null,"posts_year":7138,"posts_month":397,"posts_week":121,"email_in":null,"email_in_allow_strangers":false,"topics_day":1,"posts_day":6,"logo_url":null,"background_url":null,"allow_badges":true,"name_lower":"bug","auto_close_based_on_last_post":false},"user":{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png"}},{"id":94516,"cooked":"

    I would worry about getting your expenses down to $5 per month, that seems more likely over time as hosting for Docker compliant sites gets cheaper.

    ","created_at":"2015-01-23T02:40:11.726Z","title":"Monetizing Discourse Talk","url":"/t/monetizing-discourse-talk/24316/4","user_title":"co-founder","user_long_name":"Jeff Atwood","category":{"id":6,"name":"support","color":"CEA9A9","topic_id":389,"topic_count":1781,"created_at":"2013-02-05T22:16:38.672Z","updated_at":"2015-01-22T18:05:33.572Z","user_id":1,"topics_year":1541,"topics_month":167,"topics_week":49,"slug":"support","description":"Support on configuring and using Discourse after it is up and running. For installation questions, use the install category.","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":12272,"latest_post_id":94602,"latest_topic_id":24346,"position":25,"parent_category_id":null,"posts_year":10571,"posts_month":1254,"posts_week":413,"email_in":null,"email_in_allow_strangers":false,"topics_day":5,"posts_day":70,"logo_url":"","background_url":"","allow_badges":true,"name_lower":"support","auto_close_based_on_last_post":false},"user":{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png"}},{"id":94515,"cooked":"

    Liked just for the word \"Discettes\" which is adorable \"heart_eyes\"

    ","created_at":"2015-01-23T02:38:29.185Z","title":"Introducing Discette - a minimal ember-cli front end to Discourse","url":"/t/introducing-discette-a-minimal-ember-cli-front-end-to-discourse/24321/2","user_title":"co-founder","user_long_name":"Jeff Atwood","category":{"id":7,"name":"dev","color":"000","topic_id":1026,"topic_count":574,"created_at":"2013-02-06T08:43:41.550Z","updated_at":"2015-01-22T18:05:32.855Z","user_id":32,"topics_year":298,"topics_month":29,"topics_week":2,"slug":"dev","description":"This category is for topics related to hacking on Discourse: submitting pull requests, configuring development environments, coding conventions, and so forth.","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":4196,"latest_post_id":94590,"latest_topic_id":24349,"position":25,"parent_category_id":null,"posts_year":2095,"posts_month":172,"posts_week":16,"email_in":null,"email_in_allow_strangers":false,"topics_day":0,"posts_day":3,"logo_url":null,"background_url":null,"allow_badges":true,"name_lower":"dev","auto_close_based_on_last_post":false},"user":{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png"}},{"id":94514,"cooked":"\n\n

    This is a good idea, are the documents public web URLs? Perhaps we could help build this onebox if so.

    \n\n\n\n

    Hmm. I suspect this could be done via the API. Query all new topics (assuming older topics are already synced), and for those with a certain URL within the topic (first post only? All posts?) ping those URLs.

    \n\n

    This could potentially be done with a webhook on save on the Discourse side.

    \n\n

    Let us know how we can help, very interested in public projects like this.

    ","created_at":"2015-01-23T02:37:39.518Z","title":"How to do \"Object Oriented Discussion\" through Oneboxes?","url":"/t/how-to-do-object-oriented-discussion-through-oneboxes/24328/2","user_title":"co-founder","user_long_name":"Jeff Atwood","category":{"id":5,"name":"extensibility","color":"FE8432","topic_id":28,"topic_count":295,"created_at":"2013-02-03T08:42:06.329Z","updated_at":"2015-01-22T18:05:32.698Z","user_id":1,"topics_year":187,"topics_month":17,"topics_week":7,"slug":"extensibility","description":"Topics about extending the functionality of Discourse with plugins, themes, add-ons, or other mechanisms for extensibility. ","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":2574,"latest_post_id":94582,"latest_topic_id":24328,"position":25,"parent_category_id":null,"posts_year":1485,"posts_month":196,"posts_week":52,"email_in":null,"email_in_allow_strangers":false,"topics_day":2,"posts_day":8,"logo_url":null,"background_url":null,"allow_badges":true,"name_lower":"extensibility","auto_close_based_on_last_post":false},"user":{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png"}},{"id":94512,"cooked":"

    Hmm, have not seen problems updating on 1gb instance provided swap is there.

    \n\n

    Anything else running on the machine?

    \n\n

    Maybe reboot, then upgrade Docker from command line, then upgrade Discourse from command line.

    ","created_at":"2015-01-23T02:32:31.383Z","title":"Update Failed and Now Showing Currently Upgrading","url":"/t/update-failed-and-now-showing-currently-upgrading/24332/2","user_title":"co-founder","user_long_name":"Jeff Atwood","category":{"id":6,"name":"support","color":"CEA9A9","topic_id":389,"topic_count":1781,"created_at":"2013-02-05T22:16:38.672Z","updated_at":"2015-01-22T18:05:33.572Z","user_id":1,"topics_year":1541,"topics_month":167,"topics_week":49,"slug":"support","description":"Support on configuring and using Discourse after it is up and running. For installation questions, use the install category.","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":12272,"latest_post_id":94602,"latest_topic_id":24346,"position":25,"parent_category_id":null,"posts_year":10571,"posts_month":1254,"posts_week":413,"email_in":null,"email_in_allow_strangers":false,"topics_day":5,"posts_day":70,"logo_url":"","background_url":"","allow_badges":true,"name_lower":"support","auto_close_based_on_last_post":false},"user":{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png"}},{"id":94511,"cooked":"

    Hmm, not sure about that, good odds they will be fixed in iOS 8.1 which is due soon.

    ","created_at":"2015-01-23T02:27:16.786Z","title":"Dealing with iOS 8 Mobile Safari bugs?","url":"/t/dealing-with-ios-8-mobile-safari-bugs/24101/5","user_title":"co-founder","user_long_name":"Jeff Atwood","category":{"id":2,"name":"feature","color":"0E76BD","topic_id":11,"topic_count":1592,"created_at":"2013-02-02T21:42:52.552Z","updated_at":"2015-01-22T18:05:32.647Z","user_id":1,"topics_year":919,"topics_month":60,"topics_week":20,"slug":"feature","description":"Discussion about features or potential features of Discourse: how they work, why they work, etc.","text_color":"FFFFFF","read_restricted":false,"auto_close_hours":null,"post_count":14360,"latest_post_id":94600,"latest_topic_id":24344,"position":25,"parent_category_id":null,"posts_year":8617,"posts_month":690,"posts_week":190,"email_in":null,"email_in_allow_strangers":false,"topics_day":2,"posts_day":8,"logo_url":null,"background_url":null,"allow_badges":true,"name_lower":"feature","auto_close_based_on_last_post":false},"user":{"id":32,"username":"codinghorror","uploaded_avatar_id":5297,"avatar_template":"/user_avatar/meta.discourse.org/codinghorror/{size}/5297.png"}}] +}; diff --git a/test/javascripts/integration/groups-test.js.es6 b/test/javascripts/integration/groups-test.js.es6 new file mode 100644 index 0000000000..3d52deb95c --- /dev/null +++ b/test/javascripts/integration/groups-test.js.es6 @@ -0,0 +1,12 @@ +integration("Groups"); + +test("Browsing Groups", function() { + visit("/groups/discourse"); + andThen(function() { + ok(count('.user-stream .item') > 0, "it has stream items"); + }); + visit("/groups/discourse/members"); + andThen(function() { + ok(count('.group-members tr') > 0, "it lists group members"); + }); +}); From a2099110aa3b0ec622ab42e31b552f2032e29cc1 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Fri, 23 Jan 2015 12:19:46 -0500 Subject: [PATCH 089/230] FEATURE: Ning importer --- script/import_scripts/base.rb | 26 +++ script/import_scripts/ning.rb | 306 ++++++++++++++++++++++++++++++++ script/import_scripts/phpbb3.rb | 15 +- 3 files changed, 338 insertions(+), 9 deletions(-) create mode 100644 script/import_scripts/ning.rb diff --git a/script/import_scripts/base.rb b/script/import_scripts/base.rb index 664a3088f6..d128cc36fb 100644 --- a/script/import_scripts/base.rb +++ b/script/import_scripts/base.rb @@ -11,10 +11,16 @@ if ARGV.include?('bbcode-to-md') require 'ruby-bbcode-to-md' end +require_dependency 'url_helper' +require_dependency 'file_helper' + + module ImportScripts; end class ImportScripts::Base + include ActionView::Helpers::NumberHelper + def initialize require File.expand_path(File.dirname(__FILE__) + "/../../config/environment") preload_i18n @@ -269,6 +275,7 @@ class ImportScripts::Base bio_raw = opts.delete(:bio_raw) website = opts.delete(:website) + location = opts.delete(:location) avatar_url = opts.delete(:avatar_url) opts[:name] = User.suggest_name(opts[:email]) unless opts[:name] @@ -296,6 +303,7 @@ class ImportScripts::Base if bio_raw.present? || website.present? u.user_profile.bio_raw = bio_raw if bio_raw.present? u.user_profile.website = website if website.present? + u.user_profile.location = location if location.present? u.user_profile.save! end end @@ -323,6 +331,8 @@ class ImportScripts::Base results.each do |c| params = yield(c) + next if params.nil? # block returns nil to skip + # Basic massaging on the category name params[:name] = "Blank" if params[:name].blank? params[:name].strip! @@ -552,6 +562,22 @@ class ImportScripts::Base end end + def html_for_upload(upload, display_filename) + if FileHelper.is_image?(upload.url) + embedded_image_html(upload) + else + attachment_html(upload, display_filename) + end + end + + def embedded_image_html(upload) + %Q[
    ] + end + + def attachment_html(upload, display_filename) + "#{display_filename} (#{number_to_human_size(upload.filesize)})" + end + def print_status(current, max) print "\r%9d / %d (%5.1f%%) " % [current, max, ((current.to_f / max.to_f) * 100).round(1)] end diff --git a/script/import_scripts/ning.rb b/script/import_scripts/ning.rb new file mode 100644 index 0000000000..8700a55dd8 --- /dev/null +++ b/script/import_scripts/ning.rb @@ -0,0 +1,306 @@ +require File.expand_path(File.dirname(__FILE__) + "/../../config/environment") +require File.expand_path(File.dirname(__FILE__) + "/base.rb") + +# Edit the constants and initialize method for your import data. + +class ImportScripts::Ning < ImportScripts::Base + + JSON_FILES_DIR = "/path/to/json/archive/json/files" + ATTACHMENT_PREFIXES = ["discussions", "pages", "blogs", "members", "photos"] + EXTRA_AUTHORIZED_EXTENSIONS = ["bmp", "ico", "txt", "pdf"] + + def initialize + super + + @system_user = Discourse.system_user + + @users_json = load_ning_json("ning-members-local.json") + @discussions_json = load_ning_json("ning-discussions-local.json") + @blogs_json = load_ning_json("ning-blogs-local.json") + @pages_json = load_ning_json("ning-pages-local.json") + + #SiteSetting.max_image_size_kb = 3072 + #SiteSetting.max_attachment_size_kb = 1024 + SiteSetting.authorized_extensions = (SiteSetting.authorized_extensions.split("|") + EXTRA_AUTHORIZED_EXTENSIONS).uniq.join("|") + end + + def execute + puts "", "Importing from Ning..." + + import_users + import_categories + import_discussions + import_blogs + import_pages + suspend_users + + puts "", "Done" + end + + def load_ning_json(arg) + filename = File.join(JSON_FILES_DIR, arg) + raise RuntimeError.new("File #{filename} not found!") if !File.exists?(filename) + JSON.parse(repair_json(File.read(filename))).reverse + end + + def repair_json(arg) + arg.gsub!(/^\(/, "") # content of file is surround by ( ) + arg.gsub!(/\)$/, "") + arg.gsub!(/\}\{/, "},{") # missing commas sometimes! + arg + end + + def import_users + puts '', "Importing users" + + staff_levels = ["admin", "moderator", "owner"] + + create_users(@users_json) do |u| + { + id: u["contributorName"], + name: u["fullName"], + email: u["email"], + created_at: Time.zone.parse(u["createdDate"]), + date_of_birth: u["birthdate"] ? Time.zone.parse(u["birthdate"]) : nil, + location: "#{u["location"]} #{u["country"]}", + avatar_url: u["profilePhoto"], + bio_raw: u["profileQuestions"].is_a?(Hash) ? u["profileQuestions"]["About Me"] : nil, + post_create_action: proc do |newuser| + if staff_levels.include?(u["level"].downcase) + if u["level"].downcase == "admin" || u["level"].downcase == "owner" + newuser.admin = true + newuser.save + else + newuser.moderator = true + newuser.save + end + end + + if u["profilePhoto"] + photo_path = file_full_path(u["profilePhoto"]) + if File.exists?(photo_path) + begin + upload = create_upload(newuser.id, photo_path, File.basename(photo_path)) + if upload.persisted? + newuser.import_mode = false + newuser.create_user_avatar + newuser.import_mode = true + newuser.user_avatar.update(custom_upload_id: upload.id) + newuser.update(uploaded_avatar_id: upload.id) + else + puts "Error: Upload did not persist for #{photo_path}!" + end + rescue SystemCallError => err + puts "Could not import avatar #{photo_path}: #{err.message}" + end + else + puts "avatar file not found at #{photo_path}" + end + end + end + } + end + end + + def suspend_users + puts '', "Updating suspended users" + + count = 0 + suspended = 0 + total = @users_json.size + + @users_json.each do |u| + if u["state"].downcase == "suspended" + if user = find_user_by_import_id(u["contributorName"]) + user.suspended_at = Time.zone.now + user.suspended_till = 200.years.from_now + + if user.save + StaffActionLogger.new(@system_user).log_user_suspend(user, "Import data indicates account is suspended.") + suspended += 1 + else + puts "Failed to suspend user #{user.username}. #{user.errors.try(:full_messages).try(:inspect)}" + end + end + end + + count += 1 + print_status count, total + end + + puts "", "Marked #{suspended} users as suspended." + end + + + def import_categories + puts "", "Importing categories" + create_categories((["Blog", "Pages"] + @discussions_json.map { |d| d["category"] }).uniq.compact) do |name| + if name.downcase == "uncategorized" + nil + else + { + id: name, # ning has no id for categories, so use the name + name: name + } + end + end + end + + + def import_discussions + puts "", "Importing discussions" + import_topics(@discussions_json) + end + + def import_blogs + puts "", "Importing blogs" + import_topics(@blogs_json, "Blog") + end + + def import_pages + puts "", "Importing pages" + import_topics(@pages_json, "Pages") + end + + def import_topics(topics_json, default_category=nil) + topics = 0 + posts = 0 + total = topics_json.size # number of topics. posts are embedded in the topic json, so we can't get total post count quickly. + + topics_json.each do |topic| + if topic["title"].present? && topic["description"].present? + @current_topic_title = topic["title"] # for debugging + mapped = {} + mapped[:id] = topic["id"] + mapped[:user_id] = user_id_from_imported_user_id(topic["contributorName"]) || -1 + mapped[:created_at] = Time.zone.parse(topic["createdDate"]) + unless topic["category"].nil? || topic["category"].downcase == "uncategorized" + mapped[:category] = category_from_imported_category_id(topic["category"]).try(:name) + end + if topic["category"].nil? && default_category + mapped[:category] = default_category + end + mapped[:title] = CGI.unescapeHTML(topic["title"]) + mapped[:raw] = process_ning_post_body(topic["description"]) + + if topic["fileAttachments"] + mapped[:raw] = add_file_attachments(mapped[:raw], topic["fileAttachments"]) + end + + parent_post = create_post(mapped, mapped[:id]) + unless parent_post.is_a?(Post) + puts "Error creating topic #{mapped[:id]}. Skipping." + puts parent_post.inspect + end + + if topic["comments"].present? + topic["comments"].reverse.each do |post| + raw = process_ning_post_body(post["description"]) + if post["fileAttachments"] + raw = add_file_attachments(raw, post["fileAttachments"]) + end + + new_post = create_post({ + id: post["id"], + topic_id: parent_post.topic_id, + user_id: user_id_from_imported_user_id(post["contributorName"]) || -1, + raw: raw, + created_at: Time.zone.parse(post["createdDate"]) + }, post["id"]) + + if new_post.is_a?(Post) + posts += 1 + else + puts "Error creating post #{post["id"]}. Skipping." + puts new_post.inspect + end + end + end + end + topics += 1 + print_status topics, total + end + + puts "", "Imported #{topics} topics with #{topics + posts} posts." + + [topics, posts] + end + + def file_full_path(relpath) + File.join JSON_FILES_DIR, relpath.split("?").first + end + + def attachment_regex + @_attachment_regex ||= Regexp.new(%Q[]*)href="(?:#{ATTACHMENT_PREFIXES.join('|')})\/(?:[^"]+)"(?:[^>]*)>]*)src="([^"]+)"(?:[^>]*)><\/a>]) + end + + def youtube_iframe_regex + @_youtube_iframe_regex ||= Regexp.new(%Q[

    ]*)src="\/\/www.youtube.com\/embed\/([^"]+)"(?:[^>]*)><\/iframe>(?:[^<]*)<\/p>]) + end + + def process_ning_post_body(arg) + raw = arg.gsub("

    \n", "

    ") + + # youtube iframe + raw.gsub!(youtube_iframe_regex) do |s| + matches = youtube_iframe_regex.match(s) + video_id = matches[1].split("?").first + + next s unless video_id + + "\n\nhttps://www.youtube.com/watch?v=#{video_id}\n" + end + + # attachments + raw.gsub!(attachment_regex) do |s| + matches = attachment_regex.match(s) + ning_filename = matches[1] + + filename = File.join(JSON_FILES_DIR, ning_filename.split("?").first) + if !File.exists?(filename) + puts "Attachment file doesn't exist: #{filename}" + next s + end + + upload = create_upload(@system_user.id, filename, File.basename(filename)) + + if upload.nil? || !upload.valid? + puts "Upload not valid :( #{filename}" + puts upload.errors.inspect if upload + next s + end + + html_for_upload(upload, File.basename(filename)) + end + + raw + end + + def add_file_attachments(arg, file_names) + raw = arg + + file_names.each do |f| + filename = File.join(JSON_FILES_DIR, f.split("?").first) + if !File.exists?(filename) + puts "Attachment file doesn't exist: #{filename}" + next + end + + upload = create_upload(@system_user.id, filename, File.basename(filename)) + + if upload.nil? || !upload.valid? + puts "Upload not valid :( #{filename}" + puts upload.errors.inspect if upload + next + end + + raw += "\n" + attachment_html(upload, File.basename(filename)) + end + + raw + end +end + +if __FILE__==$0 + ImportScripts::Ning.new.perform +end diff --git a/script/import_scripts/phpbb3.rb b/script/import_scripts/phpbb3.rb index 01f80f7feb..c163868f5b 100644 --- a/script/import_scripts/phpbb3.rb +++ b/script/import_scripts/phpbb3.rb @@ -8,8 +8,6 @@ require "mysql2" class ImportScripts::PhpBB3 < ImportScripts::Base - include ActionView::Helpers::NumberHelper - PHPBB_DB = "phpbb" BATCH_SIZE = 1000 @@ -86,8 +84,10 @@ class ImportScripts::PhpBB3 < ImportScripts::Base admin: user['group_name'] == 'ADMINISTRATORS', post_create_action: proc do |newmember| if not PHPBB_BASE_DIR.nil? and IMPORT_AVATARS.include?(user['user_avatar_type']) and newmember.uploaded_avatar_id.blank? - path = phpbb_avatar_fullpath(user['user_avatar_type'], user['user_avatar']) and begin - upload = create_upload(newmember.id, path, user['user_avatar']) + path = phpbb_avatar_fullpath(user['user_avatar_type'], user['user_avatar']) + if path + begin + upload = create_upload(newmember.id, path, user['user_avatar']) if upload.persisted? newmember.import_mode = false newmember.create_user_avatar @@ -99,6 +99,7 @@ class ImportScripts::PhpBB3 < ImportScripts::Base end rescue SystemCallError => err puts "Could not import avatar: #{err.message}" + end end end end @@ -411,11 +412,7 @@ class ImportScripts::PhpBB3 < ImportScripts::Base success_count += 1 - if FileHelper.is_image?(upload.url) - %Q[
    ] - else - "
    #{real_filename} (#{number_to_human_size(upload.filesize)})" - end + html_for_upload(upload) end if new_raw != post.raw From 256519dddfb97743b71f9295708769edfbaf0176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 23 Jan 2015 18:25:43 +0100 Subject: [PATCH 090/230] FEATURE: automatic group membership based on email address --- .../javascripts/admin/templates/group.hbs | 11 +++++++++ .../javascripts/discourse/models/group.js | 4 +++- .../stylesheets/common/admin/admin_base.scss | 8 +++++++ app/controllers/admin/groups_controller.rb | 7 +++++- .../regular/automatic_group_membership.rb | 23 +++++++++++++++++++ app/models/group.rb | 7 ++++++ app/models/user.rb | 12 ++++++++++ app/serializers/basic_group_serializer.rb | 13 ++++++++++- config/locales/client.en.yml | 2 ++ ...45128_add_automatic_membership_to_group.rb | 6 +++++ .../admin/groups_controller_spec.rb | 16 ++++++++++++- spec/jobs/automatic_group_membership_spec.rb | 22 ++++++++++++++++++ spec/models/user_spec.rb | 11 +++++++++ 13 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 app/jobs/regular/automatic_group_membership.rb create mode 100644 db/migrate/20150123145128_add_automatic_membership_to_group.rb create mode 100644 spec/jobs/automatic_group_membership_spec.rb diff --git a/app/assets/javascripts/admin/templates/group.hbs b/app/assets/javascripts/admin/templates/group.hbs index 03d9d697e6..ac76d7056a 100644 --- a/app/assets/javascripts/admin/templates/group.hbs +++ b/app/assets/javascripts/admin/templates/group.hbs @@ -43,6 +43,17 @@ {{combo-box name="alias" valueAttribute="value" value=alias_level content=aliasLevelOptions}}
    + {{#unless automatic}} +
    + + {{list-setting name="automatic_membership" settingValue=automatic_membership_email_domains}} + +
    + {{/unless}} +
    {{#unless automatic}} diff --git a/app/assets/javascripts/discourse/models/group.js b/app/assets/javascripts/discourse/models/group.js index c492cce545..a598b9822b 100644 --- a/app/assets/javascripts/discourse/models/group.js +++ b/app/assets/javascripts/discourse/models/group.js @@ -68,7 +68,9 @@ Discourse.Group = Discourse.Model.extend({ return { name: this.get('name'), alias_level: this.get('alias_level'), - visible: !!this.get('visible') + visible: !!this.get('visible'), + automatic_membership_email_domains: this.get('automatic_membership_email_domains'), + automatic_membership_retroactive: !!this.get('automatic_membership_retroactive') }; }, diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index dca9a5403b..33ce6d2ef9 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -463,6 +463,7 @@ section.details { .groups { .ac-wrap { width: 100% !important; + border-color: scale-color($primary, $lightness: 75%); .item { width: 190px; margin-right: 0 !important; @@ -480,6 +481,13 @@ section.details { .controls { margin-top: 10px; } + .select2-container { + width: 100%; + } + .select2-choices { + width: 100%; + border-color: scale-color($primary, $lightness: 75%); + } } // Customise area diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 0cdf041369..65b737392e 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -21,8 +21,11 @@ class Admin::GroupsController < Admin::AdminController def create group = Group.new + group.name = (params[:name] || '').strip group.visible = params[:visible] == "true" + group.automatic_membership_email_domains = params[:automatic_membership_email_domains] + group.automatic_membership_retroactive = params[:automatic_membership_retroactive] == "true" if group.save render_serialized(group, BasicGroupSerializer) @@ -36,11 +39,13 @@ class Admin::GroupsController < Admin::AdminController group.alias_level = params[:alias_level].to_i if params[:alias_level].present? group.visible = params[:visible] == "true" + group.automatic_membership_email_domains = params[:automatic_membership_email_domains] + group.automatic_membership_retroactive = params[:automatic_membership_retroactive] == "true" # group rename is ignored for automatic groups group.name = params[:name] if params[:name] && !group.automatic if group.save - render json: success_json + render_serialized(group, BasicGroupSerializer) else render_json_error group end diff --git a/app/jobs/regular/automatic_group_membership.rb b/app/jobs/regular/automatic_group_membership.rb new file mode 100644 index 0000000000..d49d5b3798 --- /dev/null +++ b/app/jobs/regular/automatic_group_membership.rb @@ -0,0 +1,23 @@ +module Jobs + + class AutomaticGroupMembership < Jobs::Base + + def execute(args) + group_id = args[:group_id] + + raise Discourse::InvalidParameters.new(:group_id) if group_id.blank? + + group = Group.find(group_id) + + return unless group.automatic_membership_retroactive + + domains = group.automatic_membership_email_domains.gsub('.', '\.') + + User.where("email ~* '@(#{domains})'").find_each do |user| + group.add(user) rescue ActiveRecord::RecordNotUnique + end + end + + end + +end diff --git a/app/models/group.rb b/app/models/group.rb index d9985367df..33ed718aed 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -11,6 +11,7 @@ class Group < ActiveRecord::Base has_many :managers, through: :group_managers after_save :destroy_deletions + after_save :automatic_group_membership validate :name_format_validator validates_uniqueness_of :name, case_sensitive: false @@ -301,6 +302,12 @@ class Group < ActiveRecord::Base @deletions = nil end + def automatic_group_membership + if self.automatic_membership_retroactive + Jobs.enqueue(:automatic_group_membership, group_id: self.id) + end + end + end # == Schema Information diff --git a/app/models/user.rb b/app/models/user.rb index 8b597298f6..64e9749bed 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -79,6 +79,7 @@ class User < ActiveRecord::Base after_create :create_user_stat after_create :create_user_profile after_create :ensure_in_trust_level_group + after_create :automatic_group_membership before_save :update_username_lower before_save :ensure_password_is_hashed @@ -715,6 +716,17 @@ class User < ActiveRecord::Base Group.user_trust_level_change!(id, trust_level) end + def automatic_group_membership + Group.where(automatic: false) + .where("LENGTH(COALESCE(automatic_membership_email_domains, '')) > 0") + .each do |group| + domains = group.automatic_membership_email_domains.gsub('.', '\.') + if self.email =~ Regexp.new("@(#{domains})", true) + group.add(self) rescue ActiveRecord::RecordNotUnique + end + end + end + def create_user_stat stat = UserStat.new(new_since: Time.now) stat.user_id = id diff --git a/app/serializers/basic_group_serializer.rb b/app/serializers/basic_group_serializer.rb index 8f1574cb97..2bf2d111be 100644 --- a/app/serializers/basic_group_serializer.rb +++ b/app/serializers/basic_group_serializer.rb @@ -1,3 +1,14 @@ class BasicGroupSerializer < ApplicationSerializer - attributes :id, :automatic, :name, :user_count, :alias_level, :visible + attributes :id, + :automatic, + :name, + :user_count, + :alias_level, + :visible, + :automatic_membership_email_domains, + :automatic_membership_retroactive + + def automatic_membership_email_domains + object.automatic_membership_email_domains.presence || "" + end end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index d24229b9cb..3c37e443d2 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1633,6 +1633,8 @@ en: add_members: "Add members" custom: "Custom" automatic: "Automatic" + automatic_membership_email_domains: "Users registering with an email from that list will automatically be a member of this group" + automatic_membership_retroactive: "Registered users matching this list are also member of the group" api: generate_master: "Generate Master API Key" diff --git a/db/migrate/20150123145128_add_automatic_membership_to_group.rb b/db/migrate/20150123145128_add_automatic_membership_to_group.rb new file mode 100644 index 0000000000..2ea654656e --- /dev/null +++ b/db/migrate/20150123145128_add_automatic_membership_to_group.rb @@ -0,0 +1,6 @@ +class AddAutomaticMembershipToGroup < ActiveRecord::Migration + def change + add_column :groups, :automatic_membership_email_domains, :text + add_column :groups, :automatic_membership_retroactive, :boolean, default: false + end +end diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb index 8c83768e0d..8efd09bd5e 100644 --- a/spec/controllers/admin/groups_controller_spec.rb +++ b/spec/controllers/admin/groups_controller_spec.rb @@ -25,7 +25,9 @@ describe Admin::GroupsController do "user_count"=>1, "automatic"=>false, "alias_level"=>0, - "visible"=>true + "visible"=>true, + "automatic_membership_email_domains"=>"", + "automatic_membership_retroactive"=>false }]) end @@ -57,6 +59,18 @@ describe Admin::GroupsController do expect(group.visible).to eq(true) end + it "doesn't launch the 'automatic group membership' job when it's not retroactive" do + Jobs.expects(:enqueue).never + xhr :put, :update, id: 1, automatic_membership_retroactive: "false" + expect(response).to be_success + end + + it "launches the 'automatic group membership' job when it's retroactive" do + Jobs.expects(:enqueue).with(:automatic_group_membership, group_id: 1) + xhr :put, :update, id: 1, automatic_membership_retroactive: "true" + expect(response).to be_success + end + end context ".destroy" do diff --git a/spec/jobs/automatic_group_membership_spec.rb b/spec/jobs/automatic_group_membership_spec.rb new file mode 100644 index 0000000000..fe80c04cae --- /dev/null +++ b/spec/jobs/automatic_group_membership_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' +require_dependency 'jobs/regular/automatic_group_membership' + +describe Jobs::AutomaticGroupMembership do + + it "raises an error when the group id is missing" do + expect { Jobs::AutomaticGroupMembership.new.execute({}) }.to raise_error(Discourse::InvalidParameters) + end + + it "updates the membership" do + user1 = Fabricate(:user, email: "foo@wat.com") + user2 = Fabricate(:user, email: "foo@bar.com") + group = Fabricate(:group, automatic_membership_email_domains: "wat.com", automatic_membership_retroactive: true) + + Jobs::AutomaticGroupMembership.new.execute(group_id: group.id) + + group.reload + expect(group.users.include?(user1)).to eq(true) + expect(group.users.include?(user2)).to eq(false) + end + +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 96aa8286c5..b918d44467 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1213,4 +1213,15 @@ describe User do end + describe "automatic group membership" do + + it "is automatically added to a group when the email matches" do + group = Fabricate(:group, automatic_membership_email_domains: "bar.com|wat.com") + user = Fabricate(:user, email: "foo@bar.com") + group.reload + expect(group.users.include?(user)).to eq(true) + end + + end + end From e6f849f87301ef17b5eb20a781a9ac4f1d6cbbfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 23 Jan 2015 19:17:33 +0100 Subject: [PATCH 091/230] FIX: make sure pasting an image in the composer is considered as an image instead of an attachment --- app/assets/javascripts/discourse/lib/utilities.js | 3 ++- test/javascripts/lib/utilities-test.js.es6 | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/utilities.js b/app/assets/javascripts/discourse/lib/utilities.js index 8fe483a16d..81afac78a6 100644 --- a/app/assets/javascripts/discourse/lib/utilities.js +++ b/app/assets/javascripts/discourse/lib/utilities.js @@ -162,11 +162,12 @@ Discourse.Utilities = { } var upload = files[0]; - var type = Discourse.Utilities.isAnImage(upload.name) ? 'image' : 'attachment'; // CHROME ONLY: if the image was pasted, sets its name to a default one if (upload instanceof Blob && !(upload instanceof File) && upload.type === "image/png") { upload.name = "blob.png"; } + var type = Discourse.Utilities.isAnImage(upload.name) ? 'image' : 'attachment'; + return Discourse.Utilities.validateUploadedFile(upload, type, bypassNewUserRestriction); }, diff --git a/test/javascripts/lib/utilities-test.js.es6 b/test/javascripts/lib/utilities-test.js.es6 index 0052767c4b..15ee484673 100644 --- a/test/javascripts/lib/utilities-test.js.es6 +++ b/test/javascripts/lib/utilities-test.js.es6 @@ -60,14 +60,16 @@ test("prevents files that are too big from being uploaded", function() { ok(bootbox.alert.calledWith(I18n.t('post.errors.file_too_large', { max_size_kb: 5 }))); }); +var imageSize = 10 * 1024; + var dummyBlob = function() { var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; if (BlobBuilder) { var bb = new BlobBuilder(); - bb.append([1]); + bb.append([new Int8Array(imageSize)]); return bb.getBlob("image/png"); } else { - return new Blob([1], { "type" : "image\/png" }); + return new Blob([new Int8Array(imageSize)], { "type" : "image\/png" }); } }; @@ -75,10 +77,11 @@ test("allows valid uploads to go through", function() { Discourse.User.resetCurrent(Discourse.User.create()); Discourse.User.currentProp("trust_level", 1); Discourse.SiteSettings.max_image_size_kb = 15; + Discourse.SiteSettings.max_attachment_size_kb = 1; sandbox.stub(bootbox, "alert"); // image - var image = { name: "image.png", size: 10 * 1024 }; + var image = { name: "image.png", size: imageSize }; ok(validUpload([image])); // pasted image var pastedImage = dummyBlob(); From 05006389002b704ff6d8af5b841e9a46ff33efa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 23 Jan 2015 19:57:01 +0100 Subject: [PATCH 092/230] FIX: :arrow_up: update onebox gem for more dailymotion.com support --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index debd886241..6f663bb562 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -271,7 +271,7 @@ GEM omniauth-twitter (1.0.1) multi_json (~> 1.3) omniauth-oauth (~> 1.0) - onebox (1.5.9) + onebox (1.5.10) moneta (~> 0.7) multi_json (~> 1.7) mustache (~> 0.99) From f7f5e39f75adb8f5336322aae007f70f5e683f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 23 Jan 2015 20:31:48 +0100 Subject: [PATCH 093/230] FIX: Minor Admin bug with a setting when creating a new group --- app/assets/javascripts/admin/templates/group.hbs | 2 +- app/assets/javascripts/discourse/models/group.js | 7 ++++++- app/controllers/admin/groups_controller.rb | 5 +++-- app/serializers/basic_group_serializer.rb | 4 ---- spec/controllers/admin/groups_controller_spec.rb | 3 ++- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/admin/templates/group.hbs b/app/assets/javascripts/admin/templates/group.hbs index ac76d7056a..2800ac2b9c 100644 --- a/app/assets/javascripts/admin/templates/group.hbs +++ b/app/assets/javascripts/admin/templates/group.hbs @@ -46,7 +46,7 @@ {{#unless automatic}}
    - {{list-setting name="automatic_membership" settingValue=automatic_membership_email_domains}} + {{list-setting name="automatic_membership" settingValue=emailDomains}}
    {{/if}} {{/if}} + + {{plugin-outlet "post-revisions"}} +
    {{{bodyDiff}}}
    diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index aef950324f..a395b98fb1 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -414,7 +414,7 @@ class PostsController < ApplicationController result[:is_warning] = (params[:is_warning] == "true") end - PostRevisor.tracked_fields.keys.each do |f| + PostRevisor.tracked_topic_fields.keys.each do |f| params.permit(f => []) result[f] = params[f] if params.has_key?(f) end diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index b9cdbf9223..c2f9be7c65 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -126,7 +126,7 @@ class TopicsController < ApplicationController guardian.ensure_can_edit!(topic) changes = {} - PostRevisor.tracked_fields.keys.each do |f| + PostRevisor.tracked_topic_fields.keys.each do |f| changes[f] = params[f] if params.has_key?(f) end diff --git a/app/serializers/post_revision_serializer.rb b/app/serializers/post_revision_serializer.rb index 1d24b6630d..fceac5e35c 100644 --- a/app/serializers/post_revision_serializer.rb +++ b/app/serializers/post_revision_serializer.rb @@ -40,9 +40,7 @@ class PostRevisionSerializer < ApplicationSerializer end end - add_compared_field :category_id add_compared_field :wiki - add_compared_field :post_type def previous_hidden previous["hidden"] @@ -167,19 +165,27 @@ class PostRevisionSerializer < ApplicationSerializer return @all_revisions if @all_revisions post_revisions = PostRevision.where(post_id: object.post_id).order(:number).to_a + + latest_modifications = { + "raw" => [post.raw], + "cooked" => [post.cooked], + "edit_reason" => [post.edit_reason], + "wiki" => [post.wiki], + "post_type" => [post.post_type], + "user_id" => [post.user_id] + } + + # For the topic fields, let's get the values from a serializer + PostRevisor.tracked_topic_fields.keys.each do |field| + if topic.respond_to?(field) + latest_modifications[field.to_s] = [topic.send(field)] + end + end + post_revisions << PostRevision.new( number: post_revisions.last.number + 1, hidden: post.hidden, - modifications: { - "raw" => [post.raw], - "cooked" => [post.cooked], - "edit_reason" => [post.edit_reason], - "wiki" => [post.wiki], - "post_type" => [post.post_type], - "user_id" => [post.user_id], - "title" => [topic.title], - "category_id" => [topic.category_id], - } + modifications: latest_modifications ) @all_revisions = [] diff --git a/app/serializers/topic_view_serializer.rb b/app/serializers/topic_view_serializer.rb index d0fc7f281a..eec7b2c447 100644 --- a/app/serializers/topic_view_serializer.rb +++ b/app/serializers/topic_view_serializer.rb @@ -3,29 +3,35 @@ require_dependency 'pinned_check' class TopicViewSerializer < ApplicationSerializer include PostStreamSerializerMixin - # These attributes will be delegated to the topic - def self.topic_attributes - [:id, - :title, - :fancy_title, - :posts_count, - :created_at, - :views, - :reply_count, - :participant_count, - :like_count, - :last_posted_at, - :visible, - :closed, - :archived, - :has_summary, - :archetype, - :slug, - :category_id, - :word_count, - :deleted_at] + def self.attributes_from_topic(*list) + [list].flatten.each do |attribute| + attributes(attribute) + class_eval %{def #{attribute} + object.topic.#{attribute} + end} + end end + attributes_from_topic :id, + :title, + :fancy_title, + :posts_count, + :created_at, + :views, + :reply_count, + :participant_count, + :like_count, + :last_posted_at, + :visible, + :closed, + :archived, + :has_summary, + :archetype, + :slug, + :category_id, + :word_count, + :deleted_at + attributes :draft, :draft_key, :draft_sequence, @@ -45,14 +51,6 @@ class TopicViewSerializer < ApplicationSerializer :chunk_size, :bookmarked - # Define a delegator for each attribute of the topic we want - attributes(*topic_attributes) - topic_attributes.each do |ta| - class_eval %{def #{ta} - object.topic.#{ta} - end} - end - # TODO: Split off into proper object / serializer def details result = { diff --git a/lib/post_revisor.rb b/lib/post_revisor.rb index 594162853f..c445668e76 100644 --- a/lib/post_revisor.rb +++ b/lib/post_revisor.rb @@ -4,7 +4,7 @@ class PostRevisor # Helps us track changes to a topic. # - # It's passed to `Topic.track_field` callbacks so they can record if they + # It's passed to `track_topic_fields` callbacks so they can record if they # changed a value or not. This is needed for things like custom fields. class TopicChanges attr_reader :topic, :user @@ -47,13 +47,29 @@ class PostRevisor @topic = topic || post.topic end - def self.tracked_fields - @@tracked_fields ||= {} - @@tracked_fields + def self.tracked_topic_fields + @@tracked_topic_fields ||= {} + @@tracked_topic_fields end - def self.track_field(field, &block) - tracked_fields[field] = block + def self.track_topic_field(field, &block) + tracked_topic_fields[field] = block + + # Define it in the serializer unless it already has been defined + unless PostRevisionSerializer.instance_methods(false).include?("#{field}_changes".to_sym) + PostRevisionSerializer.add_compared_field(field) + end + end + + # Fields we want to record revisions for by default + track_topic_field(:title) do |tc, title| + tc.record_change('title', tc.topic.title, title) + tc.topic.title = title + end + + track_topic_field(:category_id) do |tc, category_id| + tc.record_change('category_id', tc.topic.category_id, category_id) + tc.check_result(tc.topic.change_category_to_id(category_id)) end # AVAILABLE OPTIONS: @@ -139,7 +155,7 @@ class PostRevisor end def topic_changed? - PostRevisor.tracked_fields.keys.any? {|f| @fields.has_key?(f)} + PostRevisor.tracked_topic_fields.keys.any? {|f| @fields.has_key?(f)} end def revise_post @@ -217,7 +233,7 @@ class PostRevisor def update_topic Topic.transaction do - PostRevisor.tracked_fields.each do |f, cb| + PostRevisor.tracked_topic_fields.each do |f, cb| if !@topic_changes.errored? && @fields.has_key?(f) cb.call(@topic_changes, @fields[f]) end @@ -360,13 +376,3 @@ class PostRevisor end -# Fields we want to record revisions for by default -PostRevisor.track_field(:title) do |tc, title| - tc.record_change('title', tc.topic.title, title) - tc.topic.title = title -end - -PostRevisor.track_field(:category_id) do |tc, category_id| - tc.record_change('category_id', tc.topic.category_id, category_id) - tc.check_result(tc.topic.change_category_to_id(category_id)) -end From 053d3120f74c12e221dbb24400ddd8c5825bdc5d Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 28 Jan 2015 13:38:40 -0500 Subject: [PATCH 138/230] Updated incorrect comment --- app/serializers/post_revision_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/serializers/post_revision_serializer.rb b/app/serializers/post_revision_serializer.rb index fceac5e35c..0565a1a003 100644 --- a/app/serializers/post_revision_serializer.rb +++ b/app/serializers/post_revision_serializer.rb @@ -175,7 +175,7 @@ class PostRevisionSerializer < ApplicationSerializer "user_id" => [post.user_id] } - # For the topic fields, let's get the values from a serializer + # Retrieve any `tracked_topic_fields` PostRevisor.tracked_topic_fields.keys.each do |field| if topic.respond_to?(field) latest_modifications[field.to_s] = [topic.send(field)] From cd2c9edb46ce870c050ab0fc17730f8c3eb3c55a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 28 Jan 2015 19:33:11 +0100 Subject: [PATCH 139/230] FIX: :bug: upload on IE9 wasn't working :'( - FIX: make sure we set a default name to a pasted image only on Chrome (the only browser that supports it) - FIX: use ".json" extension to uploads endpoints since IE9 doesn't pass the correct header - FIX: pass the CSRF token in a query parameter since IE9 doesn't pass it in the headers - FIX: display error messages comming from the server when there is one over the default error message - FIX: HACK around IE9 security issue when clicking a file input via JavaScript (use a label and set `visibility:hidden` on the input) - FIX: hide the "cancel" upload on IE9 since it's not supported - FIX: return "text/plain" content-type when uploading a file for IE9 in order to prevent it from displaying the save dialog - FIX: check the maximum file size on the server :boom: - update jQuery File Upload Plugin to v. 5.42.2 - update JQuery IFram Transport Plugin to v. 1.8.5 - update jQuery UI Widget to v. 1.11.1 --- .../javascripts/discourse/lib/utilities.js | 6 +- .../discourse/mixins/upload.js.es6 | 45 ++++---- .../templates/components/avatar-uploader.hbs | 8 +- .../templates/components/emoji-uploader.hbs | 6 +- .../templates/components/image-uploader.hbs | 6 +- .../discourse/views/composer.js.es6 | 67 ++++++----- app/assets/stylesheets/desktop/compose.scss | 3 + app/assets/stylesheets/desktop/upload.scss | 3 + app/controllers/uploads_controller.rb | 10 +- app/controllers/users_controller.rb | 3 + app/models/upload.rb | 74 +++++++----- app/views/layouts/application.html.erb | 3 +- config/locales/server.en.yml | 4 +- spec/models/upload_spec.rb | 12 ++ .../assets/javascripts/jquery.fileupload.js | 76 ++++++++---- .../javascripts/jquery.iframe-transport.js | 85 ++++++++++---- vendor/assets/javascripts/jquery.ui.widget.js | 109 ++++++++++++------ 17 files changed, 341 insertions(+), 179 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/utilities.js b/app/assets/javascripts/discourse/lib/utilities.js index 81afac78a6..243910e019 100644 --- a/app/assets/javascripts/discourse/lib/utilities.js +++ b/app/assets/javascripts/discourse/lib/utilities.js @@ -164,7 +164,9 @@ Discourse.Utilities = { var upload = files[0]; // CHROME ONLY: if the image was pasted, sets its name to a default one - if (upload instanceof Blob && !(upload instanceof File) && upload.type === "image/png") { upload.name = "blob.png"; } + if (typeof Blob !== "undefined" && typeof File !== "undefined") { + if (upload instanceof Blob && !(upload instanceof File) && upload.type === "image/png") { upload.name = "blob.png"; } + } var type = Discourse.Utilities.isAnImage(upload.name) ? 'image' : 'attachment'; @@ -287,7 +289,7 @@ Discourse.Utilities = { // deal with meaningful errors first if (data.jqXHR) { switch (data.jqXHR.status) { - // cancel from the user + // cancelled by the user case 0: return; // entity too large, usually returned from the web server diff --git a/app/assets/javascripts/discourse/mixins/upload.js.es6 b/app/assets/javascripts/discourse/mixins/upload.js.es6 index f9b98024f4..dceab04b34 100644 --- a/app/assets/javascripts/discourse/mixins/upload.js.es6 +++ b/app/assets/javascripts/discourse/mixins/upload.js.es6 @@ -11,17 +11,15 @@ export default Em.Mixin.create({ }, _initializeUploader: function() { - // NOTE: we can't cache this as fileupload replaces the input after upload - // cf. https://github.com/blueimp/jQuery-File-Upload/wiki/Frequently-Asked-Questions#why-is-the-file-input-field-cloned-and-replaced-after-each-selection - var $upload = this.$('input[type=file]'), - self = this; + var $upload = this.$(), + self = this, + csrf = Discourse.Session.currentProp("csrfToken"); $upload.fileupload({ - url: this.get('uploadUrl'), + url: this.get('uploadUrl') + ".json?authenticity_token=" + encodeURIComponent(csrf), dataType: "json", - fileInput: $upload, - dropZone: this.$(), - pasteZone: this.$() + dropZone: $upload, + pasteZone: $upload }); $upload.on('fileuploadsubmit', function (e, data) { @@ -39,14 +37,20 @@ export default Em.Mixin.create({ }); $upload.on("fileuploaddone", function(e, data) { - if(data.result.url) { - self.uploadDone(data); - } else { - if (data.result.message) { - bootbox.alert(data.result.message); + if (data.result) { + if (data.result.url) { + self.uploadDone(data); } else { - bootbox.alert(I18n.t('post.errors.upload')); + if (data.result.message) { + bootbox.alert(data.result.message); + } else if (data.result.length > 0) { + bootbox.alert(data.result.join("\n")); + } else { + bootbox.alert(I18n.t('post.errors.upload')); + } } + } else { + bootbox.alert(I18n.t('post.errors.upload')); } }); @@ -60,12 +64,9 @@ export default Em.Mixin.create({ }.on('didInsertElement'), _destroyUploader: function() { - this.$('input[type=file]').fileupload('destroy'); - }.on('willDestroyElement'), - - actions: { - selectFile: function() { - this.$('input[type=file]').click(); - } - } + var $upload = this.$(); + try { $upload.fileupload('destroy'); } + catch (e) { /* wasn't initialized yet */ } + $upload.off(); + }.on('willDestroyElement') }); diff --git a/app/assets/javascripts/discourse/templates/components/avatar-uploader.hbs b/app/assets/javascripts/discourse/templates/components/avatar-uploader.hbs index 028b30eab5..21761c67bd 100644 --- a/app/assets/javascripts/discourse/templates/components/avatar-uploader.hbs +++ b/app/assets/javascripts/discourse/templates/components/avatar-uploader.hbs @@ -1,7 +1,7 @@ - - + {{#if uploading}} {{i18n 'upload_selector.uploading'}} {{view.uploadProgress}}% {{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/emoji-uploader.hbs b/app/assets/javascripts/discourse/templates/components/emoji-uploader.hbs index 8d0166e736..8d9b297cf9 100644 --- a/app/assets/javascripts/discourse/templates/components/emoji-uploader.hbs +++ b/app/assets/javascripts/discourse/templates/components/emoji-uploader.hbs @@ -1,6 +1,6 @@ {{text-field name="name" placeholderKey="admin.emoji.name" value=name}} - - + + diff --git a/app/assets/javascripts/discourse/templates/components/image-uploader.hbs b/app/assets/javascripts/discourse/templates/components/image-uploader.hbs index ccb6a03ae2..32c0dce129 100644 --- a/app/assets/javascripts/discourse/templates/components/image-uploader.hbs +++ b/app/assets/javascripts/discourse/templates/components/image-uploader.hbs @@ -1,7 +1,9 @@ -
    - + {{#if backgroundStyle}} {{/if}} diff --git a/app/assets/javascripts/discourse/views/composer.js.es6 b/app/assets/javascripts/discourse/views/composer.js.es6 index be2a8c14cc..7fe4ec8efd 100644 --- a/app/assets/javascripts/discourse/views/composer.js.es6 +++ b/app/assets/javascripts/discourse/views/composer.js.es6 @@ -307,11 +307,14 @@ var ComposerView = Discourse.View.extend(Ember.Evented, { // in case it's still bound somehow this._unbindUploadTarget(); - var $uploadTarget = $('#reply-control'); + var $uploadTarget = $('#reply-control'), + csrf = Discourse.Session.currentProp('csrfToken'), + cancelledByTheUser; + // NOTE: we need both the .json extension and the CSRF token as a query parameter for IE9 $uploadTarget.fileupload({ - url: Discourse.getURL('/uploads'), - dataType: 'json', + url: Discourse.getURL('/uploads.json?authenticity_token=' + encodeURIComponent(csrf)), + dataType: 'json' }); // submit - this event is triggered for each upload @@ -324,22 +327,27 @@ var ComposerView = Discourse.View.extend(Ember.Evented, { // send - this event is triggered when the upload request is about to start $uploadTarget.on('fileuploadsend', function (e, data) { + cancelledByTheUser = false; // hide the "file selector" modal self.get('controller').send('closeModal'); - // cf. https://github.com/blueimp/jQuery-File-Upload/wiki/API#how-to-cancel-an-upload - var jqXHR = data.xhr(); - // need to wait for the link to show up in the DOM - Em.run.schedule('afterRender', function() { - // bind on the click event on the cancel link - $('#cancel-file-upload').on('click', function() { - // cancel the upload - self.set('isUploading', false); - // NOTE: this might trigger a 'fileuploadfail' event with status = 0 - if (jqXHR) jqXHR.abort(); - // unbind - $(this).off('click'); - }); - }); + // NOTE: IE9 doesn't support XHR + if (data["xhr"]) { + var jqHXR = data.xhr(); + if (jqHXR) { + // need to wait for the link to show up in the DOM + Em.run.schedule('afterRender', function() { + // bind on the click event on the cancel link + $('#cancel-file-upload').on('click', function() { + // cancel the upload + self.set('isUploading', false); + // NOTE: this might trigger a 'fileuploadfail' event with status = 0 + if (jqHXR) { cancelledByTheUser = true; jqHXR.abort(); } + // unbind + $(this).off('click'); + }); + }); + } + } }); // progress all @@ -350,14 +358,17 @@ var ComposerView = Discourse.View.extend(Ember.Evented, { // done $uploadTarget.on('fileuploaddone', function (e, data) { - // make sure we have a url - if (data.result.url) { - var markdown = Discourse.Utilities.getUploadMarkdown(data.result); - // appends a space at the end of the inserted markdown - self.addMarkdown(markdown + " "); - self.set('isUploading', false); - } else { - bootbox.alert(I18n.t('post.errors.upload')); + if (!cancelledByTheUser) { + // make sure we have a url + if (data.result.url) { + var markdown = Discourse.Utilities.getUploadMarkdown(data.result); + // appends a space at the end of the inserted markdown + self.addMarkdown(markdown + " "); + self.set('isUploading', false); + } else { + // display the error message sent by the server + bootbox.alert(data.result.join("\n")); + } } }); @@ -365,8 +376,10 @@ var ComposerView = Discourse.View.extend(Ember.Evented, { $uploadTarget.on('fileuploadfail', function (e, data) { // hide upload status self.set('isUploading', false); - // display an error message - Discourse.Utilities.displayErrorForUpload(data); + if (!cancelledByTheUser) { + // display an error message + Discourse.Utilities.displayErrorForUpload(data); + } }); // contenteditable div hack for getting image paste to upload working in diff --git a/app/assets/stylesheets/desktop/compose.scss b/app/assets/stylesheets/desktop/compose.scss index 5c9032b742..ca0af4639d 100644 --- a/app/assets/stylesheets/desktop/compose.scss +++ b/app/assets/stylesheets/desktop/compose.scss @@ -81,6 +81,9 @@ margin-left: 10px; } +// hide cancel upload link on IE9 (not supported) +.ie9 #cancel-file-upload { display: none; } + #reply-control { .toggle-preview, #draft-status, #file-uploading { position: absolute; diff --git a/app/assets/stylesheets/desktop/upload.scss b/app/assets/stylesheets/desktop/upload.scss index 80d0667cac..431130be68 100644 --- a/app/assets/stylesheets/desktop/upload.scss +++ b/app/assets/stylesheets/desktop/upload.scss @@ -36,4 +36,7 @@ .image-upload-controls { padding: 10px; + label.btn { + padding: 7px 10px 5px 10px; + } } diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index a8f1dad6f6..4ceb981095 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -4,17 +4,17 @@ class UploadsController < ApplicationController def create file = params[:file] || params[:files].first - filesize = File.size(file.tempfile) upload = Upload.create_for(current_user.id, file.tempfile, file.original_filename, filesize, { content_type: file.content_type }) - if current_user.admin? + if upload.errors.empty? && current_user.admin? retain_hours = params[:retain_hours].to_i - if retain_hours > 0 - upload.update_columns(retain_hours: retain_hours) - end + upload.update_columns(retain_hours: retain_hours) if retain_hours > 0 end + # HACK FOR IE9 to prevent the "download dialog" + response.headers["Content-Type"] = "text/plain" if request.user_agent =~ /MSIE 9/ + if upload.errors.empty? render_serialized(upload, UploadSerializer, root: false) else diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index b2ff46cecd..192791818f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -441,6 +441,9 @@ class UsersController < ApplicationController file = params[:file] || params[:files].first + # HACK FOR IE9 to prevent the "download dialog" + response.headers["Content-Type"] = "text/plain" if request.user_agent =~ /MSIE 9/ + begin image = build_user_image_from(file) rescue Discourse::InvalidParameters diff --git a/app/models/upload.rb b/app/models/upload.rb index 33432a28a4..81ab4e5fe8 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -72,39 +72,23 @@ class Upload < ActiveRecord::Base # trim the origin if any upload.origin = options[:origin][0...1000] if options[:origin] - # deal with width & height for images + # check the size of the upload if FileHelper.is_image?(filename) - begin - if filename =~ /\.svg$/i - svg = Nokogiri::XML(file).at_css("svg") - width, height = svg["width"].to_i, svg["height"].to_i - if width == 0 || height == 0 - upload.errors.add(:base, I18n.t("upload.images.size_not_found")) - else - upload.width, upload.height = ImageSizer.resize(width, height) - end - else - # fix orientation first - Upload.fix_image_orientation(file.path) - # retrieve image info - image_info = FastImage.new(file, raise_on_failure: true) - # compute image aspect ratio - upload.width, upload.height = ImageSizer.resize(*image_info.size) - end - # make sure we're at the beginning of the file - # (FastImage and Nokogiri move the pointer) - file.rewind - rescue FastImage::ImageFetchFailure - upload.errors.add(:base, I18n.t("upload.images.fetch_failure")) - rescue FastImage::UnknownImageType - upload.errors.add(:base, I18n.t("upload.images.unknown_image_type")) - rescue FastImage::SizeNotFound - upload.errors.add(:base, I18n.t("upload.images.size_not_found")) + if SiteSetting.max_image_size_kb > 0 && filesize >= SiteSetting.max_image_size_kb.kilobytes + upload.errors.add(:base, I18n.t("upload.images.too_large", max_size_kb: SiteSetting.max_image_size_kb)) + else + # deal with width & height for images + upload = Upload.resize_image(filename, file, upload) + end + else + if SiteSetting.max_attachment_size_kb > 0 && filesize >= SiteSetting.max_attachment_size_kb.kilobytes + upload.errors.add(:base, I18n.t("upload.attachments.too_large", max_size_kb: SiteSetting.max_attachment_size_kb)) end - - return upload unless upload.errors.empty? end + # make sure there is no error + return upload unless upload.errors.empty? + # create a db record (so we can use the id) return upload unless upload.save @@ -122,6 +106,38 @@ class Upload < ActiveRecord::Base upload end + def self.resize_image(filename, file, upload) + begin + if filename =~ /\.svg$/i + svg = Nokogiri::XML(file).at_css("svg") + width, height = svg["width"].to_i, svg["height"].to_i + if width == 0 || height == 0 + upload.errors.add(:base, I18n.t("upload.images.size_not_found")) + else + upload.width, upload.height = ImageSizer.resize(width, height) + end + else + # fix orientation first + Upload.fix_image_orientation(file.path) + # retrieve image info + image_info = FastImage.new(file, raise_on_failure: true) + # compute image aspect ratio + upload.width, upload.height = ImageSizer.resize(*image_info.size) + end + # make sure we're at the beginning of the file + # (FastImage and Nokogiri move the pointer) + file.rewind + rescue FastImage::ImageFetchFailure + upload.errors.add(:base, I18n.t("upload.images.fetch_failure")) + rescue FastImage::UnknownImageType + upload.errors.add(:base, I18n.t("upload.images.unknown_image_type")) + rescue FastImage::SizeNotFound + upload.errors.add(:base, I18n.t("upload.images.size_not_found")) + end + + upload + end + def self.get_from_url(url) return if url.blank? # we store relative urls, so we need to remove any host/cdn diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 533587e354..f4c61ef136 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,5 +1,6 @@ - + + <%= content_for?(:title) ? yield(:title) + ' - ' + SiteSetting.title : SiteSetting.title %> diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 22ad4af979..e4166e10d0 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1828,9 +1828,9 @@ en: pasted_image_filename: "Pasted image" store_failure: "Failed to store upload #%{upload_id} for user #%{user_id}." attachments: - too_large: "Sorry, the file you are trying to upload is too big (maximum size is %{max_size_kb}%kb)." + too_large: "Sorry, the file you are trying to upload is too big (maximum size is %{max_size_kb}KB)." images: - too_large: "Sorry, the image you are trying to upload is too big (maximum size is %{max_size_kb}%kb), please resize it and try again." + too_large: "Sorry, the image you are trying to upload is too big (maximum size is %{max_size_kb}KB), please resize it and try again." fetch_failure: "Sorry, there has been an error while fetching the image." unknown_image_type: "Sorry, but the file you tried to upload doesn't appear to be an image." size_not_found: "Sorry, but we couldn't determine the size of the image. Maybe your image is corrupted?" diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb index fce6461389..092006b38c 100644 --- a/spec/models/upload_spec.rb +++ b/spec/models/upload_spec.rb @@ -84,6 +84,18 @@ describe Upload do expect(upload.errors.size).to be > 0 end + it "generates an error when the image is too large" do + SiteSetting.stubs(:max_image_size_kb).returns(1) + upload = Upload.create_for(user_id, image, image_filename, image_filesize) + expect(upload.errors.size).to be > 0 + end + + it "generates an error when the attachment is too large" do + SiteSetting.stubs(:max_attachment_size_kb).returns(1) + upload = Upload.create_for(user_id, attachment, attachment_filename, attachment_filesize) + expect(upload.errors.size).to be > 0 + end + it "saves proper information" do store = {} Discourse.expects(:store).returns(store) diff --git a/vendor/assets/javascripts/jquery.fileupload.js b/vendor/assets/javascripts/jquery.fileupload.js index 833d353232..e99f3091e6 100644 --- a/vendor/assets/javascripts/jquery.fileupload.js +++ b/vendor/assets/javascripts/jquery.fileupload.js @@ -1,5 +1,5 @@ /* - * jQuery File Upload Plugin 5.40.3 + * jQuery File Upload Plugin 5.42.2 * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2010, Sebastian Tschan @@ -10,7 +10,7 @@ */ /* jshint nomen:false */ -/* global define, window, document, location, Blob, FormData */ +/* global define, require, window, document, location, Blob, FormData */ (function (factory) { 'use strict'; @@ -20,6 +20,12 @@ 'jquery', 'jquery.ui.widget' ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('./vendor/jquery.ui.widget') + ); } else { // Browser globals: factory(window.jQuery); @@ -51,6 +57,25 @@ $.support.blobSlice = window.Blob && (Blob.prototype.slice || Blob.prototype.webkitSlice || Blob.prototype.mozSlice); + // Helper function to create drag handlers for dragover/dragenter/dragleave: + function getDragHandler(type) { + var isDragOver = type === 'dragover'; + return function (e) { + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; + var dataTransfer = e.dataTransfer; + if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 && + this._trigger( + type, + $.Event(type, {delegatedEvent: e}) + ) !== false) { + e.preventDefault(); + if (isDragOver) { + dataTransfer.dropEffect = 'copy'; + } + } + }; + } + // The fileupload widget listens for change events on file input fields defined // via fileInput setting and paste or drop events of the given dropZone. // In addition to the default jQuery Widget methods, the fileupload widget @@ -65,9 +90,9 @@ // The drop target element(s), by the default the complete document. // Set to null to disable drag & drop support: dropZone: $(document), - // The paste target element(s), by the default the complete document. - // Set to null to disable paste support: - pasteZone: $(document), + // The paste target element(s), by the default undefined. + // Set to a DOM node or jQuery object to enable file pasting: + pasteZone: undefined, // The file input field(s), that are listened to for change events. // If undefined, it is set to the file input fields inside // of the widget element on plugin initialization. @@ -1015,8 +1040,11 @@ return result; }, - _replaceFileInput: function (input) { - var inputClone = input.clone(true); + _replaceFileInput: function (data) { + var input = data.fileInput, + inputClone = input.clone(true); + // Add a reference for the new cloned file input to the data argument: + data.fileInputClone = inputClone; $('
    ').append(inputClone)[0].reset(); // Detaching allows to insert the fileInput on another form // without loosing the file input value: @@ -1187,7 +1215,7 @@ this._getFileInputFiles(data.fileInput).always(function (files) { data.files = files; if (that.options.replaceFileInput) { - that._replaceFileInput(data.fileInput); + that._replaceFileInput(data); } if (that._trigger( 'change', @@ -1240,24 +1268,21 @@ } }, - _onDragOver: function (e) { - e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; - var dataTransfer = e.dataTransfer; - if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 && - this._trigger( - 'dragover', - $.Event('dragover', {delegatedEvent: e}) - ) !== false) { - e.preventDefault(); - dataTransfer.dropEffect = 'copy'; - } - }, + _onDragOver: getDragHandler('dragover'), + + _onDragEnter: getDragHandler('dragenter'), + + _onDragLeave: getDragHandler('dragleave'), _initEventHandlers: function () { if (this._isXHRUpload(this.options)) { this._on(this.options.dropZone, { dragover: this._onDragOver, - drop: this._onDrop + drop: this._onDrop, + // event.preventDefault() on dragenter is required for IE10+: + dragenter: this._onDragEnter, + // dragleave is not required, but added for completeness: + dragleave: this._onDragLeave }); this._on(this.options.pasteZone, { paste: this._onPaste @@ -1271,7 +1296,7 @@ }, _destroyEventHandlers: function () { - this._off(this.options.dropZone, 'dragover drop'); + this._off(this.options.dropZone, 'dragenter dragleave dragover drop'); this._off(this.options.pasteZone, 'paste'); this._off(this.options.fileInput, 'change'); }, @@ -1319,10 +1344,13 @@ _initDataAttributes: function () { var that = this, options = this.options, - clone = $(this.element[0].cloneNode(false)); + clone = $(this.element[0].cloneNode(false)), + data = clone.data(); + // Avoid memory leaks: + clone.remove(); // Initialize options set via HTML5 data-attributes: $.each( - clone.data(), + data, function (key, value) { var dataAttributeName = 'data-' + // Convert camelCase to hyphen-ated key: diff --git a/vendor/assets/javascripts/jquery.iframe-transport.js b/vendor/assets/javascripts/jquery.iframe-transport.js index 4749f46993..b7581f23f4 100644 --- a/vendor/assets/javascripts/jquery.iframe-transport.js +++ b/vendor/assets/javascripts/jquery.iframe-transport.js @@ -1,5 +1,5 @@ /* - * jQuery Iframe Transport Plugin 1.5 + * jQuery Iframe Transport Plugin 1.8.3 * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2011, Sebastian Tschan @@ -9,14 +9,16 @@ * http://www.opensource.org/licenses/MIT */ -/*jslint unparam: true, nomen: true */ -/*global define, window, document */ +/* global define, require, window, document */ (function (factory) { 'use strict'; if (typeof define === 'function' && define.amd) { // Register as an anonymous AMD module: define(['jquery'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory(require('jquery')); } else { // Browser globals: factory(window.jQuery); @@ -27,7 +29,7 @@ // Helper variable to create unique names for the transport iframes: var counter = 0; - // The iframe transport accepts three additional options: + // The iframe transport accepts four additional options: // options.fileInput: a jQuery collection of file input fields // options.paramName: the parameter name for the file form data, // overrides the name property of the file input field(s), @@ -35,22 +37,41 @@ // options.formData: an array of objects with name and value properties, // equivalent to the return data of .serializeArray(), e.g.: // [{name: 'a', value: 1}, {name: 'b', value: 2}] + // options.initialIframeSrc: the URL of the initial iframe src, + // by default set to "javascript:false;" $.ajaxTransport('iframe', function (options) { - if (options.async && (options.type === 'POST' || options.type === 'GET')) { - var form, - iframe; + if (options.async) { + // javascript:false as initial iframe src + // prevents warning popups on HTTPS in IE6: + /*jshint scripturl: true */ + var initialIframeSrc = options.initialIframeSrc || 'javascript:false;', + /*jshint scripturl: false */ + form, + iframe, + addParamChar; return { send: function (_, completeCallback) { form = $('
    '); form.attr('accept-charset', options.formAcceptCharset); - // javascript:false as initial iframe src - // prevents warning popups on HTTPS in IE6. + addParamChar = /\?/.test(options.url) ? '&' : '?'; + // XDomainRequest only supports GET and POST: + if (options.type === 'DELETE') { + options.url = options.url + addParamChar + '_method=DELETE'; + options.type = 'POST'; + } else if (options.type === 'PUT') { + options.url = options.url + addParamChar + '_method=PUT'; + options.type = 'POST'; + } else if (options.type === 'PATCH') { + options.url = options.url + addParamChar + '_method=PATCH'; + options.type = 'POST'; + } // IE versions below IE8 cannot set the name property of // elements that have already been added to the DOM, // so we set the name along with the iframe HTML markup: + counter += 1; iframe = $( - '' + '' ).bind('load', function () { var fileInputClones, paramNames = $.isArray(options.paramName) ? @@ -81,9 +102,14 @@ ); // Fix for IE endless progress bar activity bug // (happens on form submits to iframe targets): - $('') + $('') .appendTo(form); - form.remove(); + window.setTimeout(function () { + // Removing the form in a setTimeout call + // allows Chrome's developer tools to display + // the response result + form.remove(); + }, 0); }); form .prop('target', iframe.prop('name')) @@ -119,6 +145,8 @@ .prop('enctype', 'multipart/form-data') // enctype must be set as encoding for IE: .prop('encoding', 'multipart/form-data'); + // Remove the HTML5 form attribute from the input(s): + options.fileInput.removeAttr('form'); } form.submit(); // Insert the file input fields at their original location @@ -126,7 +154,10 @@ if (fileInputClones && fileInputClones.length) { options.fileInput.each(function (index, input) { var clone = $(fileInputClones[index]); - $(input).prop('name', clone.prop('name')); + // Restore the original name and form properties: + $(input) + .prop('name', clone.prop('name')) + .attr('form', clone.attr('form')); clone.replaceWith(input); }); } @@ -140,7 +171,7 @@ // concat is used to avoid the "Script URL" JSLint error: iframe .unbind('load') - .prop('src', 'javascript'.concat(':false;')); + .prop('src', initialIframeSrc); } if (form) { form.remove(); @@ -151,20 +182,34 @@ }); // The iframe transport returns the iframe content document as response. - // The following adds converters from iframe to text, json, html, and script: + // The following adds converters from iframe to text, json, html, xml + // and script. + // Please note that the Content-Type for JSON responses has to be text/plain + // or text/html, if the browser doesn't include application/json in the + // Accept header, else IE will show a download dialog. + // The Content-Type for XML responses on the other hand has to be always + // application/xml or text/xml, so IE properly parses the XML response. + // See also + // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation $.ajaxSetup({ converters: { 'iframe text': function (iframe) { - return $(iframe[0].body).text(); + return iframe && $(iframe[0].body).text(); }, 'iframe json': function (iframe) { - return $.parseJSON($(iframe[0].body).text()); + return iframe && $.parseJSON($(iframe[0].body).text()); }, 'iframe html': function (iframe) { - return $(iframe[0].body).html(); + return iframe && $(iframe[0].body).html(); + }, + 'iframe xml': function (iframe) { + var xmlDoc = iframe && iframe[0]; + return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc : + $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) || + $(xmlDoc.body).html()); }, 'iframe script': function (iframe) { - return $.globalEval($(iframe[0].body).text()); + return iframe && $.globalEval($(iframe[0].body).text()); } } }); diff --git a/vendor/assets/javascripts/jquery.ui.widget.js b/vendor/assets/javascripts/jquery.ui.widget.js index c430419971..5ac2ed5a57 100644 --- a/vendor/assets/javascripts/jquery.ui.widget.js +++ b/vendor/assets/javascripts/jquery.ui.widget.js @@ -1,6 +1,27 @@ +/*! jQuery UI - v1.11.1+CommonJS - 2014-09-17 +* http://jqueryui.com +* Includes: widget.js +* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ + +(function( factory ) { + if ( typeof define === "function" && define.amd ) { + + // AMD. Register as an anonymous module. + define([ "jquery" ], factory ); + + } else if (typeof exports === "object") { + // Node/CommonJS: + factory(require("jquery")); + + } else { + + // Browser globals + factory( jQuery ); + } +}(function( $ ) { /*! - * jQuery UI Widget 1.10.4+amd - * https://github.com/blueimp/jQuery-File-Upload + * jQuery UI Widget 1.11.1 + * http://jqueryui.com * * Copyright 2014 jQuery Foundation and other contributors * Released under the MIT license. @@ -9,28 +30,28 @@ * http://api.jqueryui.com/jQuery.widget/ */ -(function (factory) { - if (typeof define === "function" && define.amd) { - // Register as an anonymous AMD module: - define(["jquery"], factory); - } else { - // Browser globals: - factory(jQuery); - } -}(function( $, undefined ) { -var uuid = 0, - slice = Array.prototype.slice, - _cleanData = $.cleanData; -$.cleanData = function( elems ) { - for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { - try { - $( elem ).triggerHandler( "remove" ); - // http://bugs.jquery.com/ticket/8235 - } catch( e ) {} - } - _cleanData( elems ); -}; +var widget_uuid = 0, + widget_slice = Array.prototype.slice; + +$.cleanData = (function( orig ) { + return function( elems ) { + var events, elem, i; + for ( i = 0; (elem = elems[i]) != null; i++ ) { + try { + + // Only trigger remove when necessary to save time + events = $._data( elem, "events" ); + if ( events && events.remove ) { + $( elem ).triggerHandler( "remove" ); + } + + // http://bugs.jquery.com/ticket/8235 + } catch( e ) {} + } + orig( elems ); + }; +})( $.cleanData ); $.widget = function( name, base, prototype ) { var fullName, existingConstructor, constructor, basePrototype, @@ -143,10 +164,12 @@ $.widget = function( name, base, prototype ) { } $.widget.bridge( name, constructor ); + + return constructor; }; $.widget.extend = function( target ) { - var input = slice.call( arguments, 1 ), + var input = widget_slice.call( arguments, 1 ), inputIndex = 0, inputLength = input.length, key, @@ -175,7 +198,7 @@ $.widget.bridge = function( name, object ) { var fullName = object.prototype.widgetFullName || name; $.fn[ name ] = function( options ) { var isMethodCall = typeof options === "string", - args = slice.call( arguments, 1 ), + args = widget_slice.call( arguments, 1 ), returnValue = this; // allow multiple hashes to be passed on init @@ -187,6 +210,10 @@ $.widget.bridge = function( name, object ) { this.each(function() { var methodValue, instance = $.data( this, fullName ); + if ( options === "instance" ) { + returnValue = instance; + return false; + } if ( !instance ) { return $.error( "cannot call methods on " + name + " prior to initialization; " + "attempted to call method '" + options + "'" ); @@ -206,7 +233,10 @@ $.widget.bridge = function( name, object ) { this.each(function() { var instance = $.data( this, fullName ); if ( instance ) { - instance.option( options || {} )._init(); + instance.option( options || {} ); + if ( instance._init ) { + instance._init(); + } } else { $.data( this, fullName, new object( options, this ) ); } @@ -233,7 +263,7 @@ $.Widget.prototype = { _createWidget: function( options, element ) { element = $( element || this.defaultElement || this )[ 0 ]; this.element = $( element ); - this.uuid = uuid++; + this.uuid = widget_uuid++; this.eventNamespace = "." + this.widgetName + this.uuid; this.options = $.widget.extend( {}, this.options, @@ -276,9 +306,6 @@ $.Widget.prototype = { // all event bindings should go through this._on() this.element .unbind( this.eventNamespace ) - // 1.9 BC for #7810 - // TODO remove dual storage - .removeData( this.widgetName ) .removeData( this.widgetFullName ) // support: jquery <1.6.3 // http://bugs.jquery.com/ticket/9413 @@ -354,20 +381,23 @@ $.Widget.prototype = { if ( key === "disabled" ) { this.widget() - .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) - .attr( "aria-disabled", value ); - this.hoverable.removeClass( "ui-state-hover" ); - this.focusable.removeClass( "ui-state-focus" ); + .toggleClass( this.widgetFullName + "-disabled", !!value ); + + // If the widget is becoming disabled, then nothing is interactive + if ( value ) { + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + } } return this; }, enable: function() { - return this._setOption( "disabled", false ); + return this._setOptions({ disabled: false }); }, disable: function() { - return this._setOption( "disabled", true ); + return this._setOptions({ disabled: true }); }, _on: function( suppressDisabledCheck, element, handlers ) { @@ -387,7 +417,6 @@ $.Widget.prototype = { element = this.element; delegateElement = this.widget(); } else { - // accept selectors, DOM elements element = delegateElement = $( element ); this.bindings = this.bindings.add( element ); } @@ -412,7 +441,7 @@ $.Widget.prototype = { handler.guid || handlerProxy.guid || $.guid++; } - var match = event.match( /^(\w+)\s*(.*)$/ ), + var match = event.match( /^([\w:-]*)\s*(.*)$/ ), eventName = match[1] + instance.eventNamespace, selector = match[2]; if ( selector ) { @@ -527,4 +556,8 @@ $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { }; }); +var widget = $.widget; + + + })); From c710d105a0ab655b5c8082bc7898510ba12cb65c Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Thu, 29 Jan 2015 00:26:53 +0530 Subject: [PATCH 140/230] Update Translations --- config/locales/client.de.yml | 26 +++++++++++++++- config/locales/client.es.yml | 26 ++++++++++++++-- config/locales/client.he.yml | 1 - config/locales/client.ja.yml | 1 - config/locales/client.pl_PL.yml | 29 ++++++++++++++++- config/locales/client.pt.yml | 1 - config/locales/client.pt_BR.yml | 1 - config/locales/client.sq.yml | 1 - config/locales/client.sv.yml | 1 - config/locales/client.uk.yml | 1 - config/locales/server.es.yml | 52 +++++++++++-------------------- config/locales/server.he.yml | 7 ----- config/locales/server.ko.yml | 55 ++++++++++++++++++++++++++++++--- config/locales/server.pt.yml | 27 ---------------- config/locales/server.pt_BR.yml | 1 - config/locales/server.sq.yml | 1 - config/locales/server.zh_TW.yml | 1 - 17 files changed, 146 insertions(+), 86 deletions(-) diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index 28b8ba21bc..8d33b7a3e4 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -91,6 +91,7 @@ de: google+: 'diesen Link auf Google+ teilen' email: 'diesen Link per E-Mail senden' topic_admin_menu: "Thema administrieren" + emails_are_disabled: "Die ausgehende E-Mail-Kommunikation wurde von einem Administrator global deaktiviert. Es werden keinerlei E-Mail Benachrichtigungen verschickt." edit: 'Titel und Kategorie dieses Themas ändern' not_implemented: "Entschuldige, diese Funktion wurde noch nicht implementiert!" no_value: "Nein" @@ -143,6 +144,13 @@ de: topic_count: "Themen" post_count: "Beiträge" user_count: "Benutzer" + contact: "Kontaktiere uns" + contact_info: "Im Falle eines kritischen Problems oder einer dringenden Sache, die dieses Seite betrifft, kontaktiere uns bitte unter %{contact_email}. " + bookmarked: + title: "Lesezeichen setzen" + help: + bookmark: "setze ein Lesezeichen für dieses Thema" + unbookmark: "entferne des Lesezeichen von diesem Thema" bookmarks: not_logged_in: "Entschuldige, du musst angemeldet sein, um ein Lesezeichen setzen zu können." created: "du hast ein Lesezeichen zu diesem Beitrag hinzugefügt" @@ -328,8 +336,10 @@ de: error: "Beim Ändern der E-Mail-Adresse ist ein Fehler aufgetreten. Möglicherweise wird diese Adresse schon benutzt." success: "Wir haben eine E-Mail an die angegebene E-Mail-Adresse gesendet. Folge zur Bestätigung der Adresse bitte den darin enthaltenen Anweisungen." change_avatar: + title: "Ändere dein Profilbild" gravatar: "Gravatar, basierend auf" refresh_gravatar_title: "Deinen Gravatar aktualisieren" + letter_based: "ein vom System zugewiesenes Profilbild" uploaded_avatar: "Eigenes Bild" uploaded_avatar_empty: "Eigenes Bild hinzufügen" upload_title: "Lade dein Bild hoch" @@ -452,6 +462,8 @@ de: title: "Letzte IP-Adresse" registration_ip_address: title: "IP-Adresse bei Registrierung" + avatar: + title: "Profilbild" title: title: "Titel" filters: @@ -890,7 +902,7 @@ de: to_forum: "Wir senden deinem Freund eine kurze E-Mail, die es ihm ermöglicht, dem Forum sofort beizutreten. Es ist keine Anmeldung erforderlich." email_placeholder: 'name@example.com' success: "Wir haben an {{email}} eine Einladung verschickt. Wir werden dich benachrichtigen, sobald die Einladung angenommen wurde. Auf deiner Benutzerseite kannst du den Status deiner Einladungen verfolgen." - error: "Entschuldige, wir konnten diese Person nicht einladen. Vielleicht ist sie schon ein Nutzer?" + error: "Es tut uns leid, wir konnten diese Person nicht einladen. Hat diese Person schon einen Benutzeraccount? (Einladungen sind in ihrer Zahl beschränkt.)" login_reply: 'Anmelden, um zu antworten' filters: n_posts: @@ -1228,11 +1240,13 @@ de: help: "Dieses Thema ist geschlossen; Antworten sind nicht mehr möglich" unpinned: title: "Losgelöst" + 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" archived: help: "Dieses Thema ist archiviert; es ist eingefroren und kann nicht mehr geändert werden" invisible: @@ -1536,7 +1550,14 @@ de: long_title: "Website-Anpassungen" css: "CSS" header: "Kopfbereich" + top: "Anfang" footer: "Fußzeile" + head_tag: + text: "" + title: "HTML das vor dem Tag eingefügt wird." + body_tag: + text: "" + title: "HTML das vor dem Tag eingefügt wird." override_default: "Das Standard-Stylesheet nicht verwenden" enabled: "Aktiviert?" preview: "Vorschau" @@ -1654,6 +1675,7 @@ de: do_nothing: "nichts tun" staff_actions: title: "Mitarbeiter-Aktionen" + instructions: "Klicke auf die Benutzernamen und Aktionen, um die Liste zu filtern. Klicke auf das Profilbild, um die Benutzerseiten zu sehen." clear_filters: "Alles anzeigen" staff_user: "Mitarbeiter" target_user: "Betroffener Benutzer" @@ -1671,6 +1693,7 @@ de: actions: delete_user: "Benutzer löschen" change_trust_level: "Vertrauensstufe ändern" + change_username: "Benutzernamen ändern" change_site_setting: "Website-Einstellungen ändern" change_site_customization: "Website-Anpassungen ändern" delete_site_customization: "Website-Anpassungen löschen" @@ -1881,6 +1904,7 @@ de: external_username: "Benutzername" external_name: "Name" external_email: "E-Mail" + external_avatar_url: "URL des Profilbilds" user_fields: title: "Benutzerfelder" help: "Füge Felder hinzu, welche deine Benutzer ausfüllen können." diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index 945d5b56ce..9313bb68b8 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -91,6 +91,7 @@ es: google+: 'comparte este enlace en Google+' email: 'comparte este enlace por email' 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' not_implemented: "Esta característica no ha sido implementada aún, ¡lo sentimos!" no_value: "No" @@ -145,6 +146,11 @@ es: user_count: "Usuarios" contact: "Contáctanos" contact_info: "En caso de error crítico o asunto urgente con respecto a este sitio, por favor, contáctanos a %{contact_email}." + bookmarked: + title: "Marcador" + help: + bookmark: "Clic para guardar este tema en marcadores" + unbookmark: "Clic para eliminar este tema de marcadores" bookmarks: not_logged_in: "Lo sentimos, debes iniciar sesión para guardar posts en marcadores." created: "has marcado este post como favorito" @@ -330,8 +336,10 @@ es: error: "Ha ocurrido un error al cambiar tu email. ¿Tal vez esa dirección ya está en uso?" success: "Te hemos enviado un e-mail a esa dirección. Por favor sigue las instrucciones de confirmación." change_avatar: + title: "Cambiar tu imagen de perfil" gravatar: "Gravatar, basado en" refresh_gravatar_title: "Actualizar tu Gravatar" + letter_based: "Imagen de perfil asignada por el sistema" uploaded_avatar: "Foto personalizada" uploaded_avatar_empty: "Añade una foto personalizada" upload_title: "Sube tu foto" @@ -454,6 +462,8 @@ es: title: "Última dirección IP" registration_ip_address: title: "Dirección IP de Registro" + avatar: + title: "Imagen de perfil" title: title: "Título" filters: @@ -890,7 +900,7 @@ es: to_forum: "Enviaremos un correo electrónico breve permitiendo a tu amigo unirse inmediatamente al hacer clic en un enlace, sin necesidad de iniciar sesión." email_placeholder: 'nombre@ejemplo.com' success: "Hemos enviado una invitación por email a {{email}}. Te notificaremos cuando la invitación sea aceptada. Revisa la pestaña de invitaciones en tu página de perfil para llevar el seguimiento de tus invitaciones." - error: "Lo sentimos, no podemos invitar a esa persona. ¿Tal vez ya es un usuario?" + error: "Lo sentimos, no pudimos invitar a esa persona. ¿Quizás es ya usuario? (La ratio de invitaciones es limitada)" login_reply: 'Inicia Sesión para Responder' filters: n_posts: @@ -1228,11 +1238,13 @@ es: help: "este tema está cerrado; ya no aceptan nuevas respuestas" unpinned: title: "Deseleccionado como destacado" + help: "Este tema se ha dejado de destacar para ti; en tu listado de temas se mostrará en orden normal" pinned_globally: title: "Destacado globalmente" help: "Este tema ha sido destacado globalmente, se mostrará en la parte superior de todas las listas" pinned: title: "Destacado" + help: "Este tema ha sido destacado para ti; se mostrará en la parte superior de su categoría" archived: help: "este tema está archivado; está congelado y no puede ser cambiado" invisible: @@ -1403,7 +1415,7 @@ es: disagree_flag: "No coincido" disagree_flag_title: "Denegar esta indicación como inválida o incorrecta" clear_topic_flags: "Hecho" - clear_topic_flags_title: "Este tema ha sido investigado y los problemas han sido resueltos. Haz click en Hecho para eliminar los reportes." + clear_topic_flags_title: "Este tema ha sido investigado y los problemas han sido resueltos. Haz clic en Hecho para eliminar los reportes." more: "(más respuestas...)" dispositions: agreed: "coincidió" @@ -1536,7 +1548,14 @@ es: long_title: "Personalizaciones del sitio" css: "CSS" header: "Encabezado" + top: "Top" footer: "Pie de página" + head_tag: + text: "" + title: "HTML insertado antes de la etiqueta " + body_tag: + text: "" + title: "HTML insertado antes de la etiqueta " override_default: "No incluir hoja de estilo estándar" enabled: "¿Activado?" preview: "vista previa" @@ -1654,6 +1673,7 @@ es: do_nothing: "no hacer nada" staff_actions: title: "Acciones del staff" + instructions: "Clic en los usuarios y acciones para filtrar la lista. Clic en las imágenes de perfil para ir a páginas de usuario." clear_filters: "Mostrar todo" staff_user: "Usuario administrador" target_user: "Usuario enfocado" @@ -1671,6 +1691,7 @@ es: actions: delete_user: "Borrar usuario" change_trust_level: "cambiar nivel de confianza" + change_username: "cambiar nombre de usuario" change_site_setting: "cambiar configuración del sitio" change_site_customization: "cambiar customización del sitio" delete_site_customization: "borrar customización del sitio" @@ -1881,6 +1902,7 @@ es: external_username: "Nombre de usuario" external_name: "Nombre" external_email: "Email" + external_avatar_url: "URL de imagen de perfil" user_fields: title: "Campos de Usuario" help: "Añadir campos que tus usuarios pueden llenar." diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index 367c869cdb..7369ad6a43 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -879,7 +879,6 @@ he: to_forum: "נשלח מייל קצר המאפשר לחברך להצטרף באופן מיידי באמצעות לחיצה על קישור, ללא צורך בהתחברות למערכת הפורומים." email_placeholder: 'name@example.com' success: "שלחנו הזמנה אל {{email}}. נודיע לך כאשר ההזמנה תתקבל. בדקו את לשונית ההזמנות בעמוד המשתמש/ת שלך כדי לעקוב אחר ההזמנות ששלחת." - error: "סליחה, לא יכולנו להזמין את האדם הזה, אולי הוא כבר רשום?" login_reply: 'התחברו כדי להשיב' filters: n_posts: diff --git a/config/locales/client.ja.yml b/config/locales/client.ja.yml index 722185295b..3039b6e002 100644 --- a/config/locales/client.ja.yml +++ b/config/locales/client.ja.yml @@ -775,7 +775,6 @@ ja: to_forum: "ログインしなくてもこの投稿に返信ができることを、あなたの友人に知らせます。" email_placeholder: 'メールアドレス' success: "{{email}}に招待を送りました。" - error: "申し訳ありませんが招待に失敗しました。既にユーザ登録済かもしれません。" login_reply: 'ログインして返信' filters: n_posts: diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml index 3de6f55d6c..cc17384105 100644 --- a/config/locales/client.pl_PL.yml +++ b/config/locales/client.pl_PL.yml @@ -107,6 +107,7 @@ pl_PL: google+: 'udostępnij ten odnośnik na Google+' email: 'wyślij ten odnośnik przez email' topic_admin_menu: "akcje administratora" + emails_are_disabled: "Wysyłanie e-maili zostało globalnie wyłączone przez administrację. Powiadomienia e-mail nie będą dostarczane." edit: 'edytuj tytuł i kategorię tego tematu' not_implemented: "Bardzo nam przykro, ale ta funkcja nie została jeszcze zaimplementowana." no_value: "Nie" @@ -160,6 +161,13 @@ pl_PL: topic_count: "Tematy" post_count: "Wpisy" user_count: "Użytkownicy" + contact: "Kontakt" + contact_info: "W sprawach wymagających szybkiej reakcji lub związanych z funkcjonowaniem serwisu, prosimy o kontakt pod adresem %{contact_email}." + bookmarked: + title: "Zakładka" + help: + bookmark: "Kliknij, aby dodać temat do zakładek." + unbookmark: "Kliknij, aby usunąć temat z zakładek." bookmarks: not_logged_in: "przykro nam, ale należy się zalogować, aby dodawać zakładki" created: "zakładka dodana" @@ -351,8 +359,10 @@ pl_PL: error: "Wystąpił błąd podczas próby zmiany twojego adresu email. Być może ten email jest już zarejestrowany?" success: "Wysłaliśmy wiadomość do potwierdzenia na podany adres email." change_avatar: + title: "Zmień swój awatar" gravatar: "bazujący na Gravatar" refresh_gravatar_title: "Zaktualizuj swój Gravatar" + letter_based: "Awatar przyznany przez system" uploaded_avatar: "Zwyczajny obrazek" uploaded_avatar_empty: "Dodaj zwyczajny obrazek" upload_title: "Wyślij swoją grafikę" @@ -479,6 +489,8 @@ pl_PL: title: "Ostatni adres IP" registration_ip_address: title: "Adres IP rejestracji" + avatar: + title: "Awatar" title: title: "Tytuł" filters: @@ -924,7 +936,7 @@ pl_PL: to_forum: "Wyślemy krótki email pozwalający twojemu znajomemu błyskawicznie dołączyć przez kliknięcie w link (bez logowania)." email_placeholder: 'nazwa@example.com' success: "Wysłaliśmy zaproszenie do {{email}}. Powiadomimy cię gdy zaproszenie zostanie przyjęte. Status swoich zaproszeń możesz śledzić na dedykowanej zakładce w swoim profilu." - error: "Przepraszamy, nie mogliśmy zaprosić tej osoby. Być może jest już na forum?" + error: "Przepraszamy, nie mogliśmy zaprosić tej osoby. Być może jest już użytkownikiem? (Możliwe też, że zapraszasz zbyt szybko, zbyt wiele osób)" login_reply: 'Zaloguj się, aby odpowiedzieć' filters: n_posts: @@ -1284,15 +1296,19 @@ pl_PL: topic_statuses: warning: help: "To jest oficjalne ostrzeżenie." + bookmarked: + help: "Temat został dodany do zakładek." locked: help: "Temat został zamknięty. Dodawanie nowych odpowiedzi nie jest możliwe." unpinned: title: "Nieprzypięty" + 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." pinned: title: "Przypięty" + help: "Temat przypięty dla twojego konta. Będzie wyświetlany na początku swojej kategorii." archived: help: "Ten temat został zarchiwizowany i nie można go zmieniać" invisible: @@ -1595,7 +1611,14 @@ pl_PL: long_title: "Personalizacja strony" css: "CSS" header: "Nagłówki" + top: "Nagłówek" footer: "Stopka" + head_tag: + text: "" + title: "Kod HTML, który zostanie umieszczony przed tagiem " + body_tag: + text: "" + title: "Kod HTML, który zostanie umieszczony przed tagiem ." override_default: "Nie dołączaj standardowego arkusza stylów" enabled: "Włączone?" preview: "podgląd" @@ -1713,6 +1736,7 @@ pl_PL: do_nothing: "nic nie rób" staff_actions: title: "Działania obsługi" + instructions: "Klikając nazwę użytkownika i akcję możesz filtrować listę. Kliknij awatary aby przejść na stronę użytkownika." clear_filters: "Pokaż wszystko" staff_user: "Użytkownik obsługi" target_user: "Użytkownik będący Obiektem" @@ -1730,6 +1754,7 @@ pl_PL: actions: delete_user: "usunięcie użytkownika" change_trust_level: "zmiana poziomu zaufania" + change_username: "zmień nazwę użytkownika" change_site_setting: "zmiana ustawień serwisu" change_site_customization: "modyfikacja personalizacji serwisu" delete_site_customization: "usunięcie personalizacji strony" @@ -1947,6 +1972,7 @@ pl_PL: external_username: "Nazwa użytkownika" external_name: "Nazwa" external_email: "Email" + external_avatar_url: "URL awatara" user_fields: title: "Pola użytkownika" help: "Dodaj pola które użytkownicy mogą wypełnić." @@ -2105,6 +2131,7 @@ pl_PL: dismiss_topics: 'x, t wyczyść listę tematów' actions: title: 'Operacje' + bookmark_topic: 'f Dodaj temat do zakładek' pin_unpin_topic: 'shift+p przypnij/odepnij temat' share_topic: 'shift+s Udostępnij temat' share_post: 's udostępnij wpis' diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index f67998a0a9..4266a2856a 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -890,7 +890,6 @@ pt: to_forum: "Enviaremos um breve email que permitirá ao seu amigo juntar-se imediatamente clicando numa hiperligação, não sendo necessário ter sessão iniciada." email_placeholder: 'nome@exemplo.com' success: "Enviámos um convite para {{email}}. Será notificado quando o convite for resgatado. Verifique o separador de convites na sua página de utilizador para poder acompanhar os seus convites." - error: "Pedimos desculpa, não conseguimos convidar essa pessoa. Talvez já seja um utilizador?" login_reply: 'Iniciar sessão para Responder' filters: n_posts: diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml index d20c09a17d..9ea76922b1 100644 --- a/config/locales/client.pt_BR.yml +++ b/config/locales/client.pt_BR.yml @@ -879,7 +879,6 @@ pt_BR: to_forum: "Nós vamos mandar um email curto permitindo seu amigo a entrar e responder a esse tópico clicando em um link, sem necessidade de entrar." email_placeholder: 'nome@exemplo.com' success: "Enviamos um convite para {{email}}. Você saberá quando eles utilizarem o convite. Você pode ir até a seção de Convites na sua página de usuário para saber quem você já convidou." - error: "Desculpe não podemos convidar essa pessoa. Talvez já seja um usuário?" login_reply: 'Logar para Responder' filters: n_posts: diff --git a/config/locales/client.sq.yml b/config/locales/client.sq.yml index b4580a7eb8..f120e3e3eb 100644 --- a/config/locales/client.sq.yml +++ b/config/locales/client.sq.yml @@ -847,7 +847,6 @@ sq: to_forum: "We'll send a brief email allowing your friend to immediately join by clicking a link, no login required." email_placeholder: 'name@example.com' success: "We mailed out an invitation to {{email}}. We'll notify you when the invitation is redeemed. Check the invitations tab on your user page to keep track of your invites." - error: "Sorry, we couldn't invite that person. Perhaps they are already a user?" login_reply: 'Përgjigju tek Diskutimi' filters: n_posts: diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index 79370a17e8..430d763b8b 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -879,7 +879,6 @@ sv: to_forum: "Vi skickar ett kort e-postmeddelande som tillåter din vän att omedelbart delta genom att klicka på en länk, ingen inloggning krävs." email_placeholder: 'namn@exampel.se' success: "Vi har mailat ut ett inbjudan till {{email}}. Vi låter dig veta när de löst in sin inbjudan. Kolla in fliken med Inbjudningar på din användarsida för att hålla koll på vem du har bjudit in." - error: "Tyvärr vi kunde inte bjudan in den personen. Kanske är den redan en användare?" login_reply: 'Logga in för att svara' filters: n_posts: diff --git a/config/locales/client.uk.yml b/config/locales/client.uk.yml index 47cd836f64..95f6b78974 100644 --- a/config/locales/client.uk.yml +++ b/config/locales/client.uk.yml @@ -614,7 +614,6 @@ uk: action: 'Надіслати запрошення' help: 'надіслати запрошення друзям, щоб вони могли відповісти на цю тему в один клац' email_placeholder: 'електронна скринька' - error: "Вибачте, ми не змогли запросити цю особу. Можливо, вона вже є користувачем?" split_topic: title: "Перенесення до нової теми" action: "перенести до нової теми" diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml index 02dc24c931..d2753763cf 100644 --- a/config/locales/server.es.yml +++ b/config/locales/server.es.yml @@ -437,7 +437,7 @@ es: unsubscribed: title: 'Dado de bajada' description: "Has sido dado de baja. ¡No te contactaremos de nuevo!" - oops: "En caso de que no quieras hacer esto, haz click abajo." + oops: "En caso de que no quieras hacer esto, haz clic abajo." error: "Error al darse de baja" preferences_link: "También puedes darte de baja de los emails de resumen en tu página de preferencias" different_user_description: "Actualmente estás logueado/a con un usuario diferente al que se le ha enviado el email de resumen. Por favor, cierra sesión e inténtalo de nuevo." @@ -510,18 +510,18 @@ es: top_referrers: title: "Los que más recomiendan" xaxis: "Usuario" - num_clicks: "Clicks" + num_clicks: "Clics" num_topics: "Temas" top_traffic_sources: title: "Las mayores fuentes de tráfico" xaxis: "Dominio" - num_clicks: "Clicks" + num_clicks: "Clics" num_topics: "Temas" num_users: "Usuarios" top_referred_topics: title: "Los temas más referidos" xaxis: "Tema" - num_clicks: "Clicks" + num_clicks: "Clics" dashboard: rails_env_warning: "Tu servidor está funcionando en modo de %{env}." ruby_version_warning: "Tú estás utilizando una versión de Ruby 2.0.0 que es conocida por tener problemas. Actualiza al nivel de parche 247 o más nuevo." @@ -541,7 +541,6 @@ es: image_magick_warning: 'El servidor está configurado para permitir miniaturas de imágenes grandes, pero ImageMagick no está instalado. Instala ImageMagick usando tu administrador de paquetes favorito o descárgate la última versión.' failing_emails_warning: 'Hay %{num_failed_jobs} de tareas de email que fallaron. Revisa tu archivo config/discourse.conf y asegúrate que la configuración del servidor de email esté correcta. Revisa las tareas fallidas en Sidekiq.' default_logo_warning: "Establece los imágenes del logo para tu sitio. Actualiza logo_url, logo_small_url, y favicon_url en la Configuración del Sitio." - contact_email_missing: "Introduce una dirección de correo electrónico de contacto para que te puedan llegar mensajes urgentes sobre tu sitio. Actualízalo en Ajustes del sitio." contact_email_invalid: "La dirección email de contacto es inválida. Actualízala en la Configuración del Sitio." title_nag: "Introduce un nombre de tu sitio. Actualiza el nombre en Configuración del Sitio." site_description_missing: "Introduce una breve descripción sobre tu sitio que aparecerá en los resultados de búsqueda. Actualiza site_description en Ajustes del sitio." @@ -598,6 +597,7 @@ es: educate_until_posts: "Cuando el usuario comienza a escribir su primer o primeros (n) posts, mostrar un pop-up con el panel de consejos para nuevos usuarios en el editor." title: "El nombre de este sitio, utilizado en la etiqueta title." site_description: "Describe este sitio en una frase, utilizada en la etiqueta meta description." + contact_email: "Dirección de correo electrónico del responsable de este sitio. Utilizado para notificaciones críticas y en la página /about de contacto para asuntos urgentes." queue_jobs: "¡SOLO DESARROLLADORES! ¡ADVERTENCIA! Poner varios trabajos en la cola de Sidekiq. Si se desactiva, el sitio dejará de funcionar." crawl_images: "Recuperar imágenes desde URLs remotas para insertarlas con las dimensiones correctas de ancho y de largo." download_remote_images_to_local: "Convertir imágenes remotas a imágenes locales descargándolas; esto previene imágenes rotas." @@ -617,8 +617,11 @@ es: post_excerpt_maxlength: "Extensión máxima del resumen / extracto de un post." post_onebox_maxlength: "Extensión máxima en caracteres de un post de Discourse en formato Onebox." onebox_domains_whitelist: "Una lista de dominios a los que permitir el formato Onebox; estos dominios deberán soportar OpenGraph u oEmbed. Pruébalos en http://iframely.com/debug" + logo_url: "El logo situado en la esquina superior izquierda de tu sitio; si se deja en blanco, se mostrará un texto con el título del sitio." digest_logo_url: "El logo alternativo que irá arriba en los emails de resumen de tu sitio. Si se deja en blanco, se utilizará `logo_url`. Ejemplo: http://example.com/logo.png" + logo_small_url: "El logo pequeño situado en la esquina superior izquierda del sitio, mostrado cuando se hace scroll hacia abajo. Si se deja en blanco, se mostrará un glifo de una casita." favicon_url: "Un favicon para tu sitio, mira http://es.wikipedia.org/wiki/Favicon" + mobile_logo_url: "El logo con posición fijada utilizado en la esquina superior izquierda de la versión móvil de tu sitio. Si se deja en blanco, se mostrará el texto del título del sitio." apple_touch_icon_url: "Icono para los dispositivos táctiles de Apple. Se recomienda un tamaño de 144px por 144px." notification_email: "La dirección de correo electrónico \"remitente\", utilizada al enviar todos los emails esenciales de sistema. El dominio especificado debe tener correctamente configurados los registros SPF, DKIM y PTR inversos para que los emails se reciban correctamente." email_custom_headers: "Lista de emails separados por una barra" @@ -662,6 +665,7 @@ es: post_menu_hidden_items: "Los elementos del menú a ocultar por defecto en el menú de cada post a menos que se haga clic en el botón para expandir las opciones." share_links: "Determina qué elementos aparecen en el cuadro de compartir y en qué orden" track_external_right_clicks: "Hacer seguimiento de los enlaces externos en los que se ha hecho clic con el botón derecho (ej: abrir en nueva pestaña) desactivado por defecto porque reescribe URLs" + site_contact_username: "Nombre de usuario de moderador o administrador desde donde se enviarán mensajes privados automáticos. Si se deja en blanco, se utilizará la cuenta de sistema por defecto." send_welcome_message: "Enviar a todos los nuevos usuarios un mensaje privado de bienvenida con una guía rápida para comenzar." suppress_reply_directly_below: "No mostrar el contador de respuestas desplegable en un post si solo hay una única respuesta y está justamente debajo del post en cuestión." suppress_reply_directly_above: "No mostrar el en-respuesta-a desplegable en un post cuando solo hay una sola respuesta justo encima del post." @@ -691,6 +695,7 @@ es: max_username_length: "Longitud máxima del nombre de usuario en caracteres. ADVERTENCIA: TODOS LOS USUARIOS EXISTENTES CON NOMBRES MÁS LARGOS QUE ESTE VALOR NO PODRÁN ACCEDER AL SITIO" min_password_length: "Longitud mínima de contraseña." block_common_passwords: "No permitir contraseñas que están entre las 10.000 más comunes." + enable_sso: "Activar single sign on a través de un sitio externo (AVISO: puede hacer que nadie se pueda registrar si no está configurado de forma apropiada; además, desactiva las invitaciones)" enable_sso_provider: "Implementar el protocolo de SSO de Discourse en el endpoint /session/sso_provider requiere establecer un sso_secret" sso_url: "URL del endpoint para el single sign on" sso_secret: "Cadena secreta utilizada para cifrar/descifrar la información del SSO, asegúrate de que es de 10 caracteres o más" @@ -730,7 +735,6 @@ es: max_flags_per_day: "Máximo número de reportes por usuario y día." max_bookmarks_per_day: "Máximo número de marcadores por usuario y día." max_edits_per_day: "Máximo número de ediciones por usuario y día." - max_stars_per_day: "Máximo número temas que pueden ser añadidos a favoritos por usuario y día." max_topics_per_day: "Máximo número de temas que un usuario puede crear al día." max_private_messages_per_day: "Máximo número de mensajes privados por usuario y día." suggested_topics: "Número de temas sugeridos mostrados al pie del tema." @@ -983,6 +987,7 @@ es: errors: "%{errors}" not_available: "No disponible. Prueba %{suggestion}?" something_already_taken: "Algo ha salido mal, quizá el nombre de usuario o el email ya han sido registrados. Prueba con el enlace de olvidé mi contraseña." + omniauth_error: "Lo sentimos, hubo un error al autorizar tu cuenta. ¿Quizás no has aprobado la autorización?" omniauth_error_unknown: "Algo ha salido mal procesando tu inicio de sesión, por favor, vuelve a intentarlo." new_registrations_disabled: "El registro de nuevas cuentas no está permitido en este momento." password_too_long: "Las contraseñas están limitadas a 200 caracteres" @@ -1022,7 +1027,7 @@ es: Esta invitación proviene de un usuario de confianza, por lo que puedes comentar en el tema inmediatamente. invite_forum_mailer: subject_template: "%{invitee_name} te invitó a unirte a %{site_domain_name}" - text_body_template: "%{invitee_name} lo ha invitado a unirse a\n\n > **%{site_title}**\n\n > %{site_description}\n\n Si esta interesado, haga click en el link de abajo: \n\n %{invite_link}\n\nEsta invitación proviene de un usuario de confianza, por lo cual no necesitara autenticarse.\n" + text_body_template: "%{invitee_name} te ha invitado a unirte a\n\n > **%{site_title}**\n\n > %{site_description}\n\n Si te interesa, haz clic en el siguiente enlace: \n\n %{invite_link}\n\nEsta invitación procede de un usuario de confianza, por lo que no necesitarás autenticarte.\n" invite_password_instructions: subject_template: "Asigna una contraseña para tu cuenta en %{site_name}" text_body_template: | @@ -1118,6 +1123,11 @@ es: subject_template: one: "1 reporte esperando ser atendidos." other: "%{count} reportes esperando ser atendidos." + flag_reasons: + off_topic: "Tu post fue reportado como **off-topic**: la comunidad piensa que no se ajusta debidamente al tema, definido por el título o el primer post." + inappropriate: "Tu post fue reportado como **inapropiado**: la comunidad piensa que es ofensivo, abusivo o que vulnera alguna de [nuestros consejos de uso](/guidelines)." + spam: "Tu post fue reportado como **spam**: la comunidad piensa que se trata de un anuncio, algo de naturaleza promocional en vez de resultar útil o relevante para lo que se espera del tema." + notify_moderators: "Tu post fue reportado para la **atención de un moderador**: la comunidad piensa que algo de este post requiere la intervención manual de un miembro del staff." flags_dispositions: agreed: "Gracias por avisarnos. Coincidimos con tu reporte en que hay un problema y lo estamos revisando." agreed_and_deleted: "Gracias por avisarnos. Coincidimos con tu reporte en que hay un problema y hemos quitado el post." @@ -1162,26 +1172,6 @@ es: ¡Disfruta de tu estancia! welcome_invite: subject_template: "¡Bienvenido a %{site_name}!" - text_body_template: | - Gracias por aceptar tu invitación a %{site_name} -- ¡bienvenido! - - Hemos generado automáticamente un nombre de usuario por ti: **%{username}**, pero puedes cambiarlo en cualquier momento visitando [tu perfil de usuario][prefs]. - - Para iniciar sesión otra vez, puedes: - - 1. Iniciar sesión usando cualquier método que desees -- pero debe de resolver a la **misma dirección e-mail** que tu recibiste en tu invitación original. ¡De otra manera no seremos capaces de reconocer que eres tu! - - 2. Crea una contraseña unica para [tu perfil de usuario][prefs], y úsala para iniciar sesión. - - %{new_user_tips} - - Creemos en una comunidad con un [comportamiento civilizado](%{base_url}/guidelines). - - (Si necesitas comunicarte por privado con [algún miembro del staff](/about) como nuevo usuario, simplemente puedes responder este mensaje privado.) - - ¡Disfruta de tu estancia! - - [prefs]: %{user_preferences_url} backup_succeeded: subject_template: "La copia de seguridad se completo exitosamente" text_body_template: "La copia de seguridad fue exitosa.\nVisita la [sección admin > copia de seguridad](/admin/backups) para descargar tu nueva copia de seguridad." @@ -1223,12 +1213,6 @@ es: ``` csv_export_succeeded: subject_template: "Exportación de datos completada" - text_body_template: | - ¡La exportación de datos se ha completado con éxito! :dvd: - - %{file_name} - - El enlace de descarga de arriba será válido durante 48 horas. csv_export_failed: subject_template: "La exportación de datos falló" text_body_template: "Lo sentimos, pero la exportación de datos falló. Por favor, verifica los registros o contacta a un miembro del staff." @@ -1443,7 +1427,7 @@ es: Si no formulaste esta petición, por favor, ignora este correo. - Haz click en el siguiente enlace para escoger una contraseña: + Haz clic en el siguiente enlace para escoger una contraseña: %{base_url}/users/password-reset/%{email_token} account_created: subject_template: "[%{site_name}] Tu nueva cuenta" diff --git a/config/locales/server.he.yml b/config/locales/server.he.yml index 7bc857a19d..1725d09824 100644 --- a/config/locales/server.he.yml +++ b/config/locales/server.he.yml @@ -711,7 +711,6 @@ he: max_flags_per_day: "מספר מקסימלי של סימונים.מותרים למשתמש/ת ביום." max_bookmarks_per_day: "מספר מקסימלי של סימניות למשתמש/ת ביום." max_edits_per_day: "מספר עריכות מקסימלי מותר למשתמש ליום." - max_stars_per_day: "מספר נושאים מקסימלי שיותר למשתמש/ת לסמן (בכוכב) ביום." max_topics_per_day: "מספר מקסימלי של נושאים שמשתמש/ת יכולים ליצור ביום." max_private_messages_per_day: "מספר מקסימלי של מסרים פרטיים שמשתמש/ת יכולים ליצור ביום." suggested_topics: "מספר הנושאים המוצעים שיופיעו בתחתית הנושא המוצג." @@ -1149,12 +1148,6 @@ he: ``` csv_export_succeeded: subject_template: "יצוא הנתונים הושלם" - text_body_template: | - יצוא הנתונים שלכם הסתיים בהצלחה! :dvd: - - %{file_name} - - קישור זה יהיה זמין במשך 48 שעות. csv_export_failed: subject_template: "ייצוא הנתונים נכשל" text_body_template: "צר לנו, אך ייצוא הנתונים שלכם נכשל. אנא בדקו את רישומי המערכת או צרו קשר עם איש/אשת צוות." diff --git a/config/locales/server.ko.yml b/config/locales/server.ko.yml index a382bb227e..6215d2a48a 100644 --- a/config/locales/server.ko.yml +++ b/config/locales/server.ko.yml @@ -33,11 +33,13 @@ ko: log_in: "로그인" via: "%{site_name}의 %{username}" is_reserved: "예약됨" + purge_reason: "비활성화된 계정은 자동적으로 삭제됩니다." disable_remote_images_download_reason: "서버에 저장공간이 부족해서 원격 이미지 저장이 비활성화됨. " errors: messages: too_long_validation: "%{length}자를 입력하셨습니다. 최대 %{max}자까지 입력 가능합니다. " invalid_boolean: "잘못된 값" + taken: "이미 사용중입니다. (그룹 이름은 대소문자를 구분하지 않습니다.)" embed: load_from_remote: "글 로딩 중 오류가 발생하였습니다." bulk_invite: @@ -193,6 +195,8 @@ ko: base: warning_requires_pm: "개인 메시지를 통해 경고를 보낼 수 있습니다." too_many_users: "한 번에 한 명의 사용자에게 경고를 보낼 수 있습니다." + cant_send_pm: "죄송합니다. 그 사용자에게 개인 메세지를 보낼 수 없습니다." + no_user_selected: "유효한 사용자를 선택해주십시오" user: attributes: password: @@ -542,6 +546,7 @@ ko: allow_duplicate_topic_titles: "같은 제목의 동일한 토픽을 허용한다." unique_posts_mins: "같은 컨텐츠를 다시 포스트 할 수 있는 기간(분)" educate_until_posts: "새로운 사용자가 포스트를 작성할 시 포스트 작성 방법에 대한 교육패널을 보여주는데, 해당 패널이 보여지는 초기 포스트 개수" + title: "타이틀 태그에 쓰일 이 사이트의 이름" queue_jobs: "오직 개발자만!! 주의!! By default, queue jobs in sidekiq. If disabled, your site will be broken." crawl_images: "제대로된 넓이와 높이를 추가하기 위해, 원격 URL로 부터 이미지를 회수해온다." download_remote_images_to_local: "이미지를 다운로드하면 원격 이미지를 로컬이미지로 변경한다. 이미지가 깨지는 것을 막을 수 있다." @@ -561,7 +566,9 @@ ko: post_excerpt_maxlength: "게시글 인용에 허용되는 최대 글자수" post_onebox_maxlength: "onebox가 적용된 Discourse 포스트에 허용되는 최대 글자수" onebox_domains_whitelist: "onebox 사용을 허가할 도메일 리스트; 이 도메인은 OpenGraph나 oEmbed를 지원하여야한다. http://iframely.com/debug를 통해 테스트해볼 수 있다." + logo_url: "로고 이미지는 사이트의 왼쪽 상단에 나타납니다. 만약 왼쪽이 비어있다면 사이트의 이름이 나타납니다." digest_logo_url: "요약 이메일 발송시 사용될 로고. 공백으로 남겨두면 `logo_url`이 사용됩니다. 예: http://example.com/logo.png" + logo_small_url: "작은 로고 이미지는 사이트 왼쪽 상단에 스크롤링할 때 나타납니다. 만일 왼쪽이 빈다면 홈으로 가는 글자가 표시됩니다." favicon_url: "이 사이트의 favicon, 참고 http://en.wikipedia.org/wiki/Favicon" apple_touch_icon_url: "애플 디바이스는 144px의 아이콘을 사용함. 144px X 144px 사이즈를 추천함" notification_email: "The from: 이 이메일 주소는 모든 기본 시스템 메일을 보내는데 사용됩니다. 여기에 명시된 도메인은 SPF, DKIM가 적용되어 있어야하며, reverse PTR 레코드가 제대로 설정되어 있어야 메일이 도착 할 수 있습니다." @@ -660,14 +667,15 @@ ko: max_flags_per_day: "사용자가 하루동안 할 수 있는 최대 신고 개수" max_bookmarks_per_day: "사용자가 하루동안 할 수 있는 최대 북마크 개수" max_edits_per_day: "사용자가 하루동안 할 수 있는 최대 편집 수" - max_stars_per_day: "사용자당 하루동안 즐겨찾기 할 수 있는 최대 토픽 개수" max_topics_per_day: "사용자가 하루동안 생성할 수 있는 최대 토픽 개수" max_private_messages_per_day: "사용자가 하루동안 생성할 수 있는 개인 메시지 개수" + max_invites_per_day: "하루에 보낼 수 있는 초대장의 최대치입니다." suggested_topics: "토픽 아랫부분에 몇개의 토픽을 제안하여 보여준다." limit_suggested_to_category: "제안된 토픽은 현재의 카테고리에 있는 토픽 중에서만 보여준다." clean_up_uploads: "불법 호스팅을 막기 위해서 참조되지 않은 업로드 파일은 제거한다. 주의 : 이 설정을 활성화 하기 전에 `/uploads` 디렉토리를 백업하는 것이 좋다." clean_orphan_uploads_grace_period_hours: "참조되지 않은 업로드 파일을 제거하기 전 기간(시간)" purge_deleted_uploads_grace_period_days: "참조되지 않은 업로드 파일을 완전 삭제하지 전 기간(일)" + purge_unactivated_users_grace_period_days: "유예기간(하루) 동안 활성화하지 않으면 계정이 삭제됩니다." s3_access_key_id: "이미지를 업로드 할 때 사용할 Amazon S3의 access key id" s3_secret_access_key: "이미지를 업로드 할 때 사용할 Amazon S3의 secret access key" s3_region: "이미지를 업로드 할 때 사용할 Amazon S3 region" @@ -766,10 +774,12 @@ ko: suppress_uncategorized_badge: "토픽 리스트에서 카테고리가 없는 토픽에 대한 훈장을 보여주지 않는다." enable_names: "사용자의 전체 이름을 보여준다." display_name_on_posts: "포스트에 @username 뿐만 아니라 사용자의 전체 이름도 보여준다." + invites_per_page: "사용자 페이지에 방문자를 표시합니다." short_progress_text_threshold: "토픽의 포스트 개수가 이 값을 넘어서면, 포스트 프로그래스바는 오직 현재 포스트 넘버만 보여준다. 만약 포스트 프로그래스바의 넓이는 변경하면, 이 숫자도 변경해야한다." default_code_lang: "기본 programming language syntax highlighting은 GitHub code blocks이 적용된다. (lang-auto, ruby, python etc.)" warn_reviving_old_topic_age: "이 값보다 오래된 토픽에 답글을 달면, 오래된 토론이라는 것을 상기시키기 위해 주의 알림이 보여진다. 비활성화 값음 0 이다. " autohighlight_all_code: "형식이 지정되기 전의(되지 않은) 모든 코드 블럭들에 대해, 사용자가 언어를 지정하지 않아도 강제로 code highlighting이 적용된다." + embed_post_limit: "포스트 개수가 최대치입니다." errors: invalid_email: "유효하지 않은 이메일 주소입니다." invalid_username: "해당 이름의 사용자가 없습니다." @@ -801,6 +811,9 @@ ko: category: '카테고리' topic: '검색 결과' user: '사용자' + sso: + unknown_error: "업데이트 정보에 오류가 있습니다. 사이트 관리자에게 문의해주세요." + timeout_expired: "로그인 연결 시간이 초과되었습니다. 다시한번 시도해주세요" original_poster: "원본 게시자" most_posts: "최대 게시자" most_recent_poster: "최근 게시자" @@ -815,6 +828,9 @@ ko: other: "%{count}개의 글을 기존 토픽으로 옮겼습니다: %{topic_link}" change_owner: post_revision_text: "소유권이 %{old_user}에서 %{new_user}으로 이전되었습니다." + emoji: + errors: + name_already_exists: "죄송합니다. '%{name}'은/는 이미 다른 이모티콘이 사용하고 있습니다." topic_statuses: archived_enabled: "이 토픽은 보관되었고 더이상 변경하실 수 없습니다." archived_disabled: "이 토픽의 보관이 풀렸고 이제 변경하실 수 있습니다." @@ -828,6 +844,8 @@ ko: other: "이 토픽은 %{count}분 뒤 자동적으로 닫혔습니다. 새로운 답글을 다실 수 없습니다." autoclosed_enabled_lastpost_days: other: "이 토픽은 마지막 답글이 달리고 %{count}일 뒤 자동적으로 닫혔습니다. 새로운 답글을 다실 수 없습니다." + autoclosed_enabled_lastpost_hours: + other: "마지막 댓글로부터 %{count} 시간이 지나 토픽이 자동으로 닫혔습니다. 새로운 댓글을 다실 수 없습니다." autoclosed_enabled_lastpost_minutes: other: "이 토픽은 마지막 답글이 달리고 %{count}분 뒤 자동적으로 닫혔습니다. 새로운 답글을 다실 수 없습니다." autoclosed_disabled: "이 토픽은 이제 열렸습니다. 새로운 답글을 허용합니다." @@ -892,6 +910,7 @@ ko: new_version_mailer_with_notes: subject_template: "[%{site_name}] 업데이트 가능." flags_reminder: + please_review: "후기를 써주세요." post_number: "게시글" flags_dispositions: agreed: "알려줘서 감사합니다. 문제가 있음에 동의하며 내용을 살펴보겠습니다." @@ -911,10 +930,15 @@ ko: backup_failed: subject_template: "백업에 실패했습니다." restore_succeeded: - subject_template: "복구 성공" - text_body_template: "복구 성공하였습니다." + subject_template: "복원 성공" + text_body_template: "복원에 성공했습니다." restore_failed: - subject_template: "복구 실패" + subject_template: "복원 실패" + csv_export_succeeded: + subject_template: "데이터 추출이 완료되었습니다." + csv_export_failed: + subject_template: "데이터 추출 실패" + text_body_template: "죄송합니다. 데이터 추출에 실패하였습니다. 로그를 확인하시거나 관리자에게 문의해주세요." email_reject_no_account: subject_template: "이메일 문제 -- 모르는 계정" email_reject_empty: @@ -923,6 +947,12 @@ ko: subject_template: "이메일 문제 -- 글쓰기 에러" email_reject_post_error_specified: subject_template: "이메일 문제 -- 글쓰기 에러" + email_reject_destination: + subject_template: "이메일 오류 -- 잘못된 주소" + email_reject_topic_not_found: + subject_template: "이메일 오류 -- 주제를 찾을 수 없습니다" + email_reject_topic_closed: + subject_template: "이메일 오류 -- 토픽이 닫힘" too_many_spam_flags: subject_template: "새로운 계정은 블락되었습니다." blocked_by_staff: @@ -989,6 +1019,15 @@ ko: %{context} + --- + %{respond_instructions} + user_posted_pm: + subject_template: "[%{site_name}] [PM] %{topic_title}" + text_body_template: | + %{message} + + %{context} + --- %{respond_instructions} digest: @@ -1037,11 +1076,16 @@ ko: see_more: "더" search_title: "이 사이트 검색" search_google: "Google" + login_required: + welcome_message: | + #[%{title}에 오신 것을 환영합니다.](#welcome) + 계정이 필요합니다. 회원가입 또는 로그인 해주세요 terms_of_service: title: "서비스 이용약관" signup_form_message: 'I have read and accept the Terms of Service.' deleted: '삭제되었습니다' upload: + edit_reason: "이미지를 다운로드 합니다." unauthorized: "업로드 하시려는 파일 확장자는 사용이 불가능합니다 (사용가능 확장자: %{authorized_extensions})." pasted_image_filename: "게시된 이미지" attachments: @@ -1072,6 +1116,9 @@ ko: guidelines: "가이드라인" privacy: "개인정보 취급방침" edit_this_page: "이 페이지 수정" + csv_export: + boolean_yes: "네" + boolean_no: "아니오" static_topic_first_reply: |+ 이 토픽인 %{page_name} 페이지의 첫글 수정. diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml index c3f63c2f77..d3ad00da9e 100644 --- a/config/locales/server.pt.yml +++ b/config/locales/server.pt.yml @@ -708,7 +708,6 @@ pt: max_flags_per_day: "Número máximo de sinalizações por utilizador por dia." max_bookmarks_per_day: "Número máximo de marcadores por utilizador por dia." max_edits_per_day: "Número máximo de edições por utilizador por dia." - max_stars_per_day: "Número máximo de tópicos que podem ser colocados como favoritos por utilizador por dia." max_topics_per_day: "Número máximo de tópicos que um utilizador pode criar por dia." max_private_messages_per_day: "Número máximo de mensagens privadas que os utilizadores podem criar por dia." suggested_topics: "Número de tópicos sugeridos mostrados no final de um tópico." @@ -1109,26 +1108,6 @@ pt: Desfrute da sua estadia! welcome_invite: subject_template: "Bem-vindo a %{site_name}!" - text_body_template: | - Obrigado por ter aceite o convite para %{site_name} -- bem-vindo! - - Nós gerámos, automaticamente um nome de utilizador para si: **%{username}**, mas pode alterá-lo a qualquer momento no [seu perfil de utilizador][prefs]. - - Para iniciar a sessão novamente, pode: - - 1. Iniciar a sessão utilizando um qualquer método preferido -- desde que faça referência ao **mesmo endereço eletrónico** através do qual recebeu o convite inicial. De outro modo, nós não poderemos saber se é a mesma pessoa!! - - 2. Criar uma palavra-passe única para [o seu perfil de utilizador][prefs], e utilizá-la para iniciar a sessão. - - %{new_user_tips} - - Nós acreditamos num constante [comportamento civilizado da comunidade](%{base_url/guidelines). - - (Se precisar de entrar em contacto privado com os [membros da equipa](/sobre) como um novo utilizador, basta responder a esta mensagem privada.) - - Desfrute da sua estadia! - - [prefs]: %{user_preferences_url} backup_succeeded: subject_template: "Cópia de segurança completa corretamente" text_body_template: "A cópia de segurança foi feita corretamente.\nVisite a [admin > secção de cópias de segurança](/admin/backups) para descarregar a sua nova cópia de segurança." @@ -1170,12 +1149,6 @@ pt: ``` csv_export_succeeded: subject_template: "Exportação de dados completa" - text_body_template: | - A exportação dos dados foi bem sucedida! :dvd: - - %{file_name} - - A hiperligação acima será válida durante 48 horas. csv_export_failed: subject_template: "A exportação dos dados falhou" text_body_template: "Pedimos desculpa mas a sua exportação de dados falhou. Por favor verifique os registos do log ou contacte um membro do pessoal." diff --git a/config/locales/server.pt_BR.yml b/config/locales/server.pt_BR.yml index e3501ad895..9fb703bde7 100644 --- a/config/locales/server.pt_BR.yml +++ b/config/locales/server.pt_BR.yml @@ -657,7 +657,6 @@ pt_BR: active_user_rate_limit_secs: "Qual a frequencia de atualização do campo 'última vez visto em', em segundos." previous_visit_timeout_hours: "Quanto tempo uma visita dura antes de considerarmos como 'última visita', em horas." max_edits_per_day: "Número máximo de edições que um usuário pode fazer por dia." - max_stars_per_day: "O número máximo de tópicos que podem ser favoritados por usuário por dia." max_topics_per_day: "Número máximo de postagens que um usuário pode criar por dia." max_private_messages_per_day: "Número máximo de mensagens privadas que os usuários podem criar por dia." suggested_topics: "Número de tópicos sugeridos mostrados no final de um tópico." diff --git a/config/locales/server.sq.yml b/config/locales/server.sq.yml index d4ec2a6fdf..5e58030e5e 100644 --- a/config/locales/server.sq.yml +++ b/config/locales/server.sq.yml @@ -716,7 +716,6 @@ sq: max_flags_per_day: "Maximum number of flags per user per day." max_bookmarks_per_day: "Maximum number of bookmarks per user per day." max_edits_per_day: "Maximum number of edits per user per day." - max_stars_per_day: "Maximum number of topics that can be starred per user per day." max_topics_per_day: "Maximum number of topics a user can create per day." max_private_messages_per_day: "Maximum number of private messages users can create per day." suggested_topics: "Number of suggested topics shown at the bottom of a topic." diff --git a/config/locales/server.zh_TW.yml b/config/locales/server.zh_TW.yml index 9b6380e579..55196e6daf 100644 --- a/config/locales/server.zh_TW.yml +++ b/config/locales/server.zh_TW.yml @@ -544,7 +544,6 @@ zh_TW: max_flags_per_day: "每個用戶每天最多能\"標記\"的數量" max_bookmarks_per_day: "每個用戶每天最多能建立\"書籤\"的數量" max_edits_per_day: "每個用戶每天最大的\"編輯次數\"的數量" - max_stars_per_day: "每個用戶一天內最多能標星的數量" max_topics_per_day: "每個用戶每天最多建立\"討論話題\"的數量" max_private_messages_per_day: "每個用戶每天最多能發\"私訊\"的數量" suggested_topics: "討論話題下的推薦話題數量" From d6fa2480934e19c25981cdb8d4a9ad926d4854b2 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 28 Jan 2015 14:56:18 -0500 Subject: [PATCH 141/230] UX: Update 404 page category badge to use centralized helper and style --- app/assets/stylesheets/desktop/discourse.scss | 4 ++++ app/helpers/application_helper.rb | 4 ++++ app/helpers/user_notifications_helper.rb | 5 ----- app/mailers/user_notifications.rb | 1 + app/views/exceptions/not_found.html.erb | 8 ++++++-- app/views/user_notifications/digest.html.erb | 4 ++-- 6 files changed, 17 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/desktop/discourse.scss b/app/assets/stylesheets/desktop/discourse.scss index e35b97994c..2e9ee7e841 100644 --- a/app/assets/stylesheets/desktop/discourse.scss +++ b/app/assets/stylesheets/desktop/discourse.scss @@ -136,6 +136,10 @@ body { margin-bottom: 10px; } + .not-found-topic { + a[href] { margin-right: 10px; line-height: 2;} + } + .page-not-found-topics .span8 { line-height: 1.5em; margin-right: 20px; diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 7cf3271f3a..9bf4db32db 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -5,6 +5,7 @@ require_dependency 'unread' require_dependency 'age_words' require_dependency 'configurable_urls' require_dependency 'mobile_detection' +require_dependency 'category_badge' module ApplicationHelper include CurrentUser @@ -153,5 +154,8 @@ module ApplicationHelper controller.class.name.split("::").first == "Admin" || session[:disable_customization] end + def category_badge(category, opts=nil) + CategoryBadge.html_for(category, opts).html_safe + end end diff --git a/app/helpers/user_notifications_helper.rb b/app/helpers/user_notifications_helper.rb index 689e860ed3..77dea02d7a 100644 --- a/app/helpers/user_notifications_helper.rb +++ b/app/helpers/user_notifications_helper.rb @@ -1,5 +1,3 @@ -require_dependency 'category_badge' - module UserNotificationsHelper def indent(text, by=2) @@ -60,7 +58,4 @@ module UserNotificationsHelper PrettyText.format_for_email(html).html_safe end - def email_category(category, opts=nil) - CategoryBadge.html_for(category, opts).html_safe - end end diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb index 736e5d64d5..6e5f600609 100644 --- a/app/mailers/user_notifications.rb +++ b/app/mailers/user_notifications.rb @@ -3,6 +3,7 @@ require_dependency 'email/message_builder' require_dependency 'age_words' class UserNotifications < ActionMailer::Base + helper :application default charset: 'UTF-8' include Email::BuildEmailHelper diff --git a/app/views/exceptions/not_found.html.erb b/app/views/exceptions/not_found.html.erb index 4a4a7db01c..47ab572429 100644 --- a/app/views/exceptions/not_found.html.erb +++ b/app/views/exceptions/not_found.html.erb @@ -6,7 +6,9 @@ +
    + +
    {{else}}
    diff --git a/app/assets/javascripts/discourse/controllers/user.js.es6 b/app/assets/javascripts/discourse/controllers/user.js.es6 index 247ebfffd4..b066362775 100644 --- a/app/assets/javascripts/discourse/controllers/user.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user.js.es6 @@ -42,6 +42,21 @@ export default ObjectController.extend(CanCheckEmails, { return this.get('can_be_deleted') && this.get('can_delete_all_posts'); }.property('can_be_deleted', 'can_delete_all_posts'), + publicUserFields: function() { + var siteUserFields = this.site.get('user_fields'); + if (!Ember.isEmpty(siteUserFields)) { + var userFields = this.get('user_fields'); + return siteUserFields.filterProperty('show_on_profile', true).sortBy('id').map(function(uf) { + var val = userFields ? userFields[uf.get('id').toString()] : null; + if (Ember.isEmpty(val)) { + return null; + } else { + return Ember.Object.create({value: val, field: uf}); + } + }).compact(); + } + }.property('user_fields.@each.value'), + privateMessagesActive: Em.computed.equal('pmView', 'index'), privateMessagesMineActive: Em.computed.equal('pmView', 'mine'), privateMessagesUnreadActive: Em.computed.equal('pmView', 'unread'), diff --git a/app/assets/javascripts/discourse/templates/user/user.hbs b/app/assets/javascripts/discourse/templates/user/user.hbs index cd1eecbd7a..689d272989 100644 --- a/app/assets/javascripts/discourse/templates/user/user.hbs +++ b/app/assets/javascripts/discourse/templates/user/user.hbs @@ -89,6 +89,19 @@ {{{bio_cooked}}}
    + {{#if publicUserFields}} +
    + {{#each uf in publicUserFields}} + {{#if uf.value}} +
    + {{uf.field.name}}: + {{uf.value}} +
    + {{/if}} + {{/each}} +
    + {{/if}} + {{plugin-outlet "user-profile-primary"}}
    diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss index 004c09f5db..f1495540f7 100644 --- a/app/assets/stylesheets/common/base/user.scss +++ b/app/assets/stylesheets/common/base/user.scss @@ -24,6 +24,16 @@ } } +.public-user-fields { + .user-field-name { + font-weight: bold; + } +} + +.collapsed-info .public-user-fields { + display: none; +} + .notification-buttons { margin: 10px 0; text-align: right; diff --git a/app/controllers/admin/user_fields_controller.rb b/app/controllers/admin/user_fields_controller.rb index 56ab3889de..e5e8c9c3a9 100644 --- a/app/controllers/admin/user_fields_controller.rb +++ b/app/controllers/admin/user_fields_controller.rb @@ -1,7 +1,7 @@ class Admin::UserFieldsController < Admin::AdminController def self.columns - [:name, :field_type, :editable, :description, :required] + [:name, :field_type, :editable, :description, :required, :show_on_profile] end def create diff --git a/app/serializers/user_field_serializer.rb b/app/serializers/user_field_serializer.rb index ff2da805e1..8638b2c04d 100644 --- a/app/serializers/user_field_serializer.rb +++ b/app/serializers/user_field_serializer.rb @@ -4,5 +4,6 @@ class UserFieldSerializer < ApplicationSerializer :description, :field_type, :editable, - :required + :required, + :show_on_profile end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 0f9efe06bb..a29d3a4d72 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2108,6 +2108,10 @@ en: title: "Editable after signup?" enabled: "editable" disabled: "not editable" + show_on_profile: + title: "Show on public profile?" + enabled: "shown on profile" + disabled: "not shown on profile" field_types: text: 'Text Field' diff --git a/db/migrate/20150129204520_add_show_on_profile_to_user_fields.rb b/db/migrate/20150129204520_add_show_on_profile_to_user_fields.rb new file mode 100644 index 0000000000..e4a641f98d --- /dev/null +++ b/db/migrate/20150129204520_add_show_on_profile_to_user_fields.rb @@ -0,0 +1,5 @@ +class AddShowOnProfileToUserFields < ActiveRecord::Migration + def change + add_column :user_fields, :show_on_profile, :boolean, default: false, null: false + end +end From 561278eb387d257cc7cc89db37dfaccba7d78010 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 30 Jan 2015 12:09:42 +1100 Subject: [PATCH 175/230] correct broken mobile styling --- app/assets/stylesheets/mobile/topic-list.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/stylesheets/mobile/topic-list.scss b/app/assets/stylesheets/mobile/topic-list.scss index abefe68402..84339793ff 100644 --- a/app/assets/stylesheets/mobile/topic-list.scss +++ b/app/assets/stylesheets/mobile/topic-list.scss @@ -144,8 +144,7 @@ tr.category-topic-link:nth-of-type(odd) { margin-bottom: 10px; th .badge-category { - float: left; - margin: 1px 4px 0 0; + margin: 0; font-size: 110%; } From e77eaf9c951397a28b26c65ee08f27b61e3e123d Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 30 Jan 2015 12:33:27 +1100 Subject: [PATCH 176/230] adjust styling for tagged topics --- .../stylesheets/common/components/badges.css.scss | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/common/components/badges.css.scss b/app/assets/stylesheets/common/components/badges.css.scss index 48dcc26dcb..29b270a885 100644 --- a/app/assets/stylesheets/common/components/badges.css.scss +++ b/app/assets/stylesheets/common/components/badges.css.scss @@ -56,10 +56,16 @@ h3 .badge-wrapper { } -header .title-wrapper .badge-category-bg { - vertical-align: top; - padding-top:0; - padding-bottom:0; +header .title-wrapper { + + .badge-category-bg, .badge-category { + vertical-align: middle; + } + + .badge-category-bg { + padding-top:0; + padding-bottom:0; + } } .badge-category-parent-bg, .badge-category-bg { From 8b7afd644fc053a40f00e5110300cc64fd807076 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 30 Jan 2015 12:47:45 +1100 Subject: [PATCH 177/230] regression, too many "new" badges on the "new" tab --- app/assets/javascripts/discourse/templates/discovery/topics.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/discourse/templates/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/discovery/topics.hbs index 9731eb00f8..3916464e27 100644 --- a/app/assets/javascripts/discourse/templates/discovery/topics.hbs +++ b/app/assets/javascripts/discourse/templates/discovery/topics.hbs @@ -37,6 +37,7 @@ {{#if hasTopics}} {{topic-list + showTopicPostBadges=showTopicPostBadges showPosters=true currentUser=currentUser canBulkSelect=canBulkSelect From 1b1ea8e71851463e32a5a2ee8d285b52e8ccdb35 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 30 Jan 2015 15:11:24 +1100 Subject: [PATCH 178/230] FEATURE: 'b' as a keyboard shortcut for bookmarking a topic --- .../discourse/lib/keyboard_shortcuts.js | 47 ++++++++++++++----- .../javascripts/discourse/models/topic.js | 24 ++++++---- .../discourse/views/topic-list-item.js.es6 | 21 ++++++++- 3 files changed, 71 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js b/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js index f96e7fd33c..76ec7b4c90 100644 --- a/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js +++ b/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js @@ -8,7 +8,6 @@ var PATH_BINDINGS = { }, SELECTED_POST_BINDINGS = { - 'b': 'toggleBookmark', 'd': 'deletePost', 'e': 'editPost', 'l': 'toggleLike', @@ -50,7 +49,8 @@ var PATH_BINDINGS = { 'ctrl+f': 'showBuiltinSearch', 'command+f': 'showBuiltinSearch', '?': 'showHelpModal', // open keyboard shortcut help - 'q': 'quoteReply' + 'q': 'quoteReply', + 'b': 'toggleBookmark' }; @@ -65,6 +65,11 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({ _.each(FUNCTION_BINDINGS, this._bindToFunction, this); }, + toggleBookmark: function(){ + this.sendToSelectedPost('toggleBookmark'); + this.sendToTopicListItemView('toggleBookmark'); + }, + quoteReply: function(){ $('.topic-post.selected button.create').click(); // lazy but should work for now @@ -157,19 +162,32 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({ Discourse.__container__.lookup('controller:application').send('showKeyboardShortcutsHelp'); }, - _bindToSelectedPost: function(action, binding) { + sendToTopicListItemView: function(action){ + var elem = $('tr.selected.topic-list-item.ember-view')[0]; + if(elem){ + var view = Ember.View.views[elem.id]; + view.send(action); + } + }, + + sendToSelectedPost: function(action){ var container = this.container; + // TODO: We should keep track of the post without a CSS class + var selectedPostId = parseInt($('.topic-post.selected article.boxed').data('post-id'), 10); + if (selectedPostId) { + var topicController = container.lookup('controller:topic'), + post = topicController.get('postStream.posts').findBy('id', selectedPostId); + if (post) { + topicController.send(action, post); + } + } + }, + + _bindToSelectedPost: function(action, binding) { + var self = this; this.keyTrapper.bind(binding, function() { - // TODO: We should keep track of the post without a CSS class - var selectedPostId = parseInt($('.topic-post.selected article.boxed').data('post-id'), 10); - if (selectedPostId) { - var topicController = container.lookup('controller:topic'), - post = topicController.get('postStream.posts').findBy('id', selectedPostId); - if (post) { - topicController.send(action, post); - } - } + self.sendToSelectedPost(action); }); }, @@ -244,9 +262,14 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({ var $article = $articles.eq(index + direction); if ($article.size() > 0) { + $articles.removeClass('selected'); $article.addClass('selected'); + if($article.is('.topic-list-item')){ + this.sendToTopicListItemView('select'); + } + if ($article.is('.topic-post')) { var tabLoc = $article.find('a.tabLoc'); if (tabLoc.length === 0) { diff --git a/app/assets/javascripts/discourse/models/topic.js b/app/assets/javascripts/discourse/models/topic.js index dc20e44308..3103726655 100644 --- a/app/assets/javascripts/discourse/models/topic.js +++ b/app/assets/javascripts/discourse/models/topic.js @@ -188,15 +188,23 @@ Discourse.Topic = Discourse.Model.extend({ return Discourse.ajax('/t/' + this.get('id') + '/bookmark', { type: 'PUT', - data: { bookmarked: self.get('bookmarked') } - }).then(null, function (error) { - self.toggleProperty('bookmarked'); - if (self.get("postStream.firstPostPresent")) { firstPost.toggleProperty('bookmarked'); } + data: { bookmarked: self.get('bookmarked') }, + error: function(error){ + self.toggleProperty('bookmarked'); + if (self.get("postStream.firstPostPresent")) { firstPost.toggleProperty('bookmarked'); } - if (error && error.responseText) { - bootbox.alert($.parseJSON(error.responseText).errors); - } else { - bootbox.alert(I18n.t('generic_error')); + var showGenericError = true; + + if (error && error.responseText) { + try { + bootbox.alert($.parseJSON(error.responseText).errors); + showGenericError = false; + } catch(e){} + } + + if(showGenericError){ + bootbox.alert(I18n.t('generic_error')); + } } }); }, diff --git a/app/assets/javascripts/discourse/views/topic-list-item.js.es6 b/app/assets/javascripts/discourse/views/topic-list-item.js.es6 index c3afd0cce5..5c68fc75b3 100644 --- a/app/assets/javascripts/discourse/views/topic-list-item.js.es6 +++ b/app/assets/javascripts/discourse/views/topic-list-item.js.es6 @@ -6,13 +6,32 @@ export default Discourse.View.extend(StringBuffer, { rawTemplate: 'list/topic_list_item.raw', classNameBindings: ['controller.checked', ':topic-list-item', - 'unboundClassNames' + 'unboundClassNames', + 'selected' ], + actions: { + select: function(){ + this.set('controller.selected', this); + }, + + toggleBookmark: function(){ + var self = this; + this.get('topic').toggleBookmark().catch(function(){ + self.rerender(); + }); + self.rerender(); + } + }, + + selected: function(){ + return this.get('controller.selected')===this; + }.property('controller.selected'), unboundClassNames: function(){ var classes = []; var topic = this.get('topic'); + if (topic.get('category')) { classes.push("category-" + topic.get('category.slug')); } From 784697bf128f714ade8d807c0e99599e079464c3 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 30 Jan 2015 15:11:46 +1100 Subject: [PATCH 179/230] added todo --- app/assets/javascripts/discourse/mixins/ajax.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/discourse/mixins/ajax.js b/app/assets/javascripts/discourse/mixins/ajax.js index b9f84255f5..c32abf8b18 100644 --- a/app/assets/javascripts/discourse/mixins/ajax.js +++ b/app/assets/javascripts/discourse/mixins/ajax.js @@ -63,6 +63,8 @@ Discourse.Ajax = Em.Mixin.create({ xhr.jqTextStatus = textStatus; xhr.requestedUrl = url; + // TODO is this sequence correct? we are calling catch defined externally before + // the error that was defined inline, it should probably be in reverse Ember.run(null, reject, xhr); if (oldError) oldError(xhr); }; From 52bc03b5e622719a185374361b9c7a3ca7f7a5e1 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 30 Jan 2015 17:19:42 +1100 Subject: [PATCH 180/230] FIX: summary mode was broken and missing a bunch of posts --- app/models/post.rb | 19 +++++++++++++++++-- lib/topic_view.rb | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/models/post.rb b/app/models/post.rb index 0d6252e199..b13e2d7806 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -251,8 +251,23 @@ class Post < ActiveRecord::Base order('sort_order desc, post_number desc') end - def self.summary - where(["(post_number = 1) or (percent_rank <= ?)", SiteSetting.summary_percent_filter.to_f / 100.0]).limit(SiteSetting.summary_max_results) + def self.summary(topic_id=nil) + # PERF: if you pass in nil it is WAY slower + # pg chokes getting a reasonable plan + topic_id = topic_id ? topic_id.to_i : "posts.topic_id" + + # percent rank has tons of ties + where(["post_number = 1 or id in ( + SELECT p1.id + FROM posts p1 + WHERE p1.percent_rank <= ? AND + p1.topic_id = #{topic_id} + ORDER BY p1.percent_rank + LIMIT ? + )", + SiteSetting.summary_percent_filter.to_f / 100.0, + SiteSetting.summary_max_results + ]) end def update_flagged_posts_count diff --git a/lib/topic_view.rb b/lib/topic_view.rb index 6a440eade5..b9ffd64341 100644 --- a/lib/topic_view.rb +++ b/lib/topic_view.rb @@ -342,7 +342,7 @@ class TopicView # Filters if @filter == 'summary' - @filtered_posts = @filtered_posts.summary + @filtered_posts = @filtered_posts.summary(@topic.id) @contains_gaps = true end From b4f1ffd4eaaa47e0e3a39dde27d414890aa5de78 Mon Sep 17 00:00:00 2001 From: lidlanca Date: Fri, 30 Jan 2015 01:57:44 -0500 Subject: [PATCH 181/230] Fix: Resize event handler does not get cleared from window object,by wrong usage of jQuery api. An handler get added each time a topic is loaded to the window object jQuery resize api only pass data to the handler when triggered. ```.resize( [eventData ], handler ) ``` The unbind followed in willDestroyElement had no affect. .on(...) or bind(..) support the event.namespace fix was not tested. --- app/assets/javascripts/discourse/views/topic.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/views/topic.js.es6 b/app/assets/javascripts/discourse/views/topic.js.es6 index a54ac07386..bed33a452d 100644 --- a/app/assets/javascripts/discourse/views/topic.js.es6 +++ b/app/assets/javascripts/discourse/views/topic.js.es6 @@ -40,7 +40,7 @@ var TopicView = Discourse.View.extend(AddCategoryClass, Discourse.Scrolling, { this.bindScrolling({name: 'topic-view'}); var self = this; - $(window).resize('resize.discourse-on-scroll', function() { + $(window).on('resize.discourse-on-scroll', function() { self.scrolled(); }); From 833b7b8f0bca976efff15f785e067bd1b8e60155 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 30 Jan 2015 17:57:46 +1100 Subject: [PATCH 182/230] fix header primary missing color --- app/assets/stylesheets/common/components/badges.css.scss | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/common/components/badges.css.scss b/app/assets/stylesheets/common/components/badges.css.scss index 29b270a885..042c1db011 100644 --- a/app/assets/stylesheets/common/components/badges.css.scss +++ b/app/assets/stylesheets/common/components/badges.css.scss @@ -95,10 +95,12 @@ h1 a.badge-category div {vertical-align: top;} // specific styles for badge categories -.bar { - .badge-category { +.bar .badge-category { color: $primary !important; - } +} + +header .bar .badge-category { + color: $header-primary !important; } From d9ae4e791efe265e4dd22aab31626a0aa51f8b93 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 30 Jan 2015 19:17:26 +1100 Subject: [PATCH 183/230] woops should be a bit more specific --- app/assets/stylesheets/common/components/badges.css.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/common/components/badges.css.scss b/app/assets/stylesheets/common/components/badges.css.scss index 042c1db011..0e6aae9649 100644 --- a/app/assets/stylesheets/common/components/badges.css.scss +++ b/app/assets/stylesheets/common/components/badges.css.scss @@ -99,7 +99,7 @@ h1 a.badge-category div {vertical-align: top;} color: $primary !important; } -header .bar .badge-category { +header .title-wrapper .bar .badge-category { color: $header-primary !important; } From 64c4bd5dbf5b6ee05909beb26eadaaee6196748b Mon Sep 17 00:00:00 2001 From: Dan Singerman Date: Wed, 28 Jan 2015 16:52:07 +0000 Subject: [PATCH 184/230] Fix force_avatar_update.to_i error as force_avatar_update is a boolean If force_avatar_update is passed in sso attributes it errors on force_avatar_update.to_i. The SingleSignOn class forces avatar_force_update to a boolean, so it should be treated as such. --- app/models/discourse_single_sign_on.rb | 3 +-- ...rb => single_sign_on_record_fabricator.rb} | 0 spec/models/discourse_single_sign_on_spec.rb | 27 ++++++++++++++----- 3 files changed, 21 insertions(+), 9 deletions(-) rename spec/fabricators/{single_sign_on_record_fabrictor.rb => single_sign_on_record_fabricator.rb} (100%) diff --git a/app/models/discourse_single_sign_on.rb b/app/models/discourse_single_sign_on.rb index 8d12a17f8a..bbd849c0e8 100644 --- a/app/models/discourse_single_sign_on.rb +++ b/app/models/discourse_single_sign_on.rb @@ -126,8 +126,7 @@ class DiscourseSingleSignOn < SingleSignOn end if SiteSetting.sso_overrides_avatar && avatar_url.present? && ( - avatar_force_update == "true" || - avatar_force_update.to_i != 0 || + avatar_force_update || sso_record.external_avatar_url != avatar_url) begin diff --git a/spec/fabricators/single_sign_on_record_fabrictor.rb b/spec/fabricators/single_sign_on_record_fabricator.rb similarity index 100% rename from spec/fabricators/single_sign_on_record_fabrictor.rb rename to spec/fabricators/single_sign_on_record_fabricator.rb diff --git a/spec/models/discourse_single_sign_on_spec.rb b/spec/models/discourse_single_sign_on_spec.rb index 22c0f0f5f7..f6aa259c30 100644 --- a/spec/models/discourse_single_sign_on_spec.rb +++ b/spec/models/discourse_single_sign_on_spec.rb @@ -105,23 +105,36 @@ describe DiscourseSingleSignOn do end context 'when sso_overrides_avatar is enabled' do + let!(:sso_record) { Fabricate(:single_sign_on_record, external_avatar_url: "http://example.com/an_image.png") } + let!(:sso) { + sso = DiscourseSingleSignOn.new + sso.username = "test" + sso.name = "test" + sso.email = sso_record.user.email + sso.external_id = sso_record.external_id + sso + } + let(:logo) { file_from_fixtures("logo.png") } before do SiteSetting.sso_overrides_avatar = true end it "deal with no avatar url passed for an existing user with an avatar" do - sso_record = Fabricate(:single_sign_on_record, external_avatar_url: "http://example.com/an_image.png") - - sso = DiscourseSingleSignOn.new - sso.username = "test" - sso.name = "test" - sso.email = sso_record.user.email - sso.external_id = sso_record.external_id # Deliberately not setting avatar_url. user = sso.lookup_or_create_user expect(user).to_not be_nil end + + it "deal with no avatar_force_update passed as a boolean" do + FileHelper.stubs(:download).returns(logo) + + sso.avatar_url = "http://example.com/a_different_image.png" + sso.avatar_force_update = true + + user = sso.lookup_or_create_user + expect(user).to_not be_nil + end end end From 750b27f973f6d22cc05cf2bc7d15cfb4d1027242 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Fri, 30 Jan 2015 17:23:52 -0500 Subject: [PATCH 185/230] FEATURE: show number of active users in the last 7 days on about page --- app/assets/javascripts/discourse/templates/about.hbs | 5 +++++ app/models/about.rb | 1 + config/locales/client.en.yml | 3 ++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/templates/about.hbs b/app/assets/javascripts/discourse/templates/about.hbs index 183269be58..96e592c140 100644 --- a/app/assets/javascripts/discourse/templates/about.hbs +++ b/app/assets/javascripts/discourse/templates/about.hbs @@ -53,6 +53,11 @@ {{number stats.user_count}} {{number stats.users_7_days}} + + {{i18n 'about.active_user_count'}} + + {{number stats.active_users_7_days}} + {{i18n 'about.like_count'}} {{number stats.like_count}} diff --git a/app/models/about.rb b/app/models/about.rb index 83dea16ec1..aa76b2243e 100644 --- a/app/models/about.rb +++ b/app/models/about.rb @@ -42,6 +42,7 @@ class About topics_7_days: Topic.listable_topics.where('created_at > ?', 7.days.ago).count, posts_7_days: Post.where('created_at > ?', 7.days.ago).count, users_7_days: User.where('created_at > ?', 7.days.ago).count, + active_users_7_days: User.where('last_seen_at > ?', 7.days.ago).count, like_count: UserAction.where(action_type: UserAction::LIKE).count, likes_7_days: UserAction.where(action_type: UserAction::LIKE) .where("created_at > ?", 7.days.ago) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index a29d3a4d72..02d1f7f001 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -159,7 +159,8 @@ en: like_count: "Likes" topic_count: "Topics" post_count: "Posts" - user_count: "Users" + user_count: "New Users" + active_user_count: "Active Users" contact: "Contact Us" contact_info: "In the event of a critical issue or urgent matter affecting this site, please contact us at %{contact_email}." From f923d7e205aecdb47aac67953bb5359dd814524d Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 30 Jan 2015 13:29:32 -0500 Subject: [PATCH 186/230] Support appending routes within the admin section by plugins --- .../admin/routes/admin-route-map.js.es6 | 11 +++-- .../discourse/routes/discourse_route.js | 48 +++++++++++++++---- lib/post_destroyer.rb | 7 ++- 3 files changed, 48 insertions(+), 18 deletions(-) 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 c5ce804efa..b71d9ce99c 100644 --- a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 @@ -1,5 +1,7 @@ -export default function() { - this.resource('admin', function() { +export default { + resource: 'admin', + + map: function() { this.route('dashboard', { path: '/' }); this.resource('adminSiteSettings', { path: '/site_settings' }, function() { this.resource('adminSiteSettingsCategory', { path: 'category/:category_id'} ); @@ -60,6 +62,5 @@ export default function() { this.resource('adminBadges', { path: '/badges' }, function() { this.route('show', { path: '/:badge_id' }); }); - - }); -} + } +}; diff --git a/app/assets/javascripts/discourse/routes/discourse_route.js b/app/assets/javascripts/discourse/routes/discourse_route.js index c3af37a09c..75be93d63a 100644 --- a/app/assets/javascripts/discourse/routes/discourse_route.js +++ b/app/assets/javascripts/discourse/routes/discourse_route.js @@ -89,23 +89,53 @@ Discourse.Route.reopenClass({ }, mapRoutes: function() { + var resources = {}; + + // If a module is defined as `route-map` in discourse or a plugin, its routes + // will be built automatically. You can supply a `resource` property to + // automatically put it in that resource, such as `admin`. That way plugins + // can define admin routes. + Ember.keys(requirejs._eak_seen).forEach(function(key) { + if (/route-map$/.test(key)) { + var module = require(key, null, null, true); + if (!module || !module.default) { throw new Error(key + ' must export a route map.'); } + + var mapObj = module.default; + if (typeof mapObj === 'function') { + mapObj = { resource: 'root', map: mapObj }; + } + + if (!resources[mapObj.resource]) { resources[mapObj.resource] = []; } + resources[mapObj.resource].push(mapObj.map); + } + }); + Discourse.Router.map(function() { var router = this; + // Do the root resources first + if (resources.root) { + resources.root.forEach(function(m) { + m.call(router); + }); + delete resources.root; + } + + // Apply other resources next + Object.keys(resources).forEach(function(r) { + router.resource(r, function() { + var res = this; + resources[r].forEach(function(m) { + m.call(res); + }); + }); + }); + if (routeBuilder) { Ember.warn("The Discourse `routeBuilder` is deprecated. Export a `route-map` instead"); routeBuilder.call(router); } - // If a module is defined as `route-map` in discourse or a plugin, its routes - // will be built automatically. - Ember.keys(requirejs._eak_seen).forEach(function(key) { - if (/route-map$/.test(key)) { - var module = require(key, null, null, true); - if (!module) { throw new Error(key + ' must export a map function.'); } - module.default.call(router); - } - }); this.route('unknown', {path: '*path'}); }); diff --git a/lib/post_destroyer.rb b/lib/post_destroyer.rb index fe58ea5a0f..fe99b6c9fe 100644 --- a/lib/post_destroyer.rb +++ b/lib/post_destroyer.rb @@ -116,16 +116,15 @@ class PostDestroyer end end - private def make_previous_post_the_last_one last_post = Post.where("topic_id = ? and id <> ?", @post.topic_id, @post.id).order('created_at desc').limit(1).first if last_post.present? @post.topic.update_attributes( - last_posted_at: last_post.created_at, - last_post_user_id: last_post.user_id, - highest_post_number: last_post.post_number + last_posted_at: last_post.created_at, + last_post_user_id: last_post.user_id, + highest_post_number: last_post.post_number ) end end From d7b7ec9e0a7b2d41ced633509778f50d11e20a6b Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 30 Jan 2015 18:01:53 -0500 Subject: [PATCH 187/230] FIX: Wiki editing was broken due to extra topic update --- app/assets/javascripts/discourse/models/composer.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/models/composer.js b/app/assets/javascripts/discourse/models/composer.js index 991f606aca..d146808084 100644 --- a/app/assets/javascripts/discourse/models/composer.js +++ b/app/assets/javascripts/discourse/models/composer.js @@ -472,7 +472,10 @@ Discourse.Composer = Discourse.Model.extend({ // Update the title if we've changed it, otherwise consider it a // successful resolved promise - if (this.get('title') && post.get('post_number') === 1) { + if (this.get('title') && + post.get('post_number') === 1 && + this.get('topic.details.can_edit')) { + var topicProps = this.getProperties(Object.keys(_edit_topic_serializer)); promise = Discourse.Topic.update(this.get('topic'), topicProps); } else { From cabd519f1419dcdbb2da28bcb742d84c6461208f Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 30 Jan 2015 15:45:04 -0800 Subject: [PATCH 188/230] add a little breathing room for topic title tags --- app/assets/stylesheets/desktop/topic.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/stylesheets/desktop/topic.scss b/app/assets/stylesheets/desktop/topic.scss index e9e2ecf45c..e17e9fa2e3 100644 --- a/app/assets/stylesheets/desktop/topic.scss +++ b/app/assets/stylesheets/desktop/topic.scss @@ -41,8 +41,7 @@ line-height: 1.2em; overflow: hidden; a {color: $primary;} - - + margin-bottom: 5px; } From 674e91724363b9cf9a5290baa599802936187c6b Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 30 Jan 2015 16:34:09 -0800 Subject: [PATCH 189/230] Update INSTALL.md --- docs/INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 0b6ea04378..9d4c47289f 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -6,7 +6,7 @@ Simple 30 minute basic install: Powerful, flexible, large or multiple server install: [**Advanced Docker install guide**][docker] -The only officially supported installs of Discourse are the [Docker](https://www.docker.io/) based beginner and advanced installs. We regret that we cannot support any other methods of installation. (Alternately, you can try the [unofficial Heroku install guide][heroku], the [unofficial Ubuntu install guide][ubuntu], the [BitNami Discourse Virtual Machine package][bitnami] or [Cloud66][cloud66].) +The only officially supported installs of Discourse are the [Docker](https://www.docker.io/) based beginner and advanced installs. We regret that we cannot support any other methods of installation. ### Why do you only officially support Docker? From 3948ea89ebcf0986232e4e7f1b05ea0dd38114e7 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 30 Jan 2015 16:39:44 -0800 Subject: [PATCH 190/230] Update INSTALL.md --- docs/INSTALL.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 9d4c47289f..8a05c1f449 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -1,10 +1,10 @@ # How Do I Install Discourse? Simple 30 minute basic install: -[**Beginner Docker install guide for Digital Ocean**][do] +[**Beginner Docker install guide**][basic] Powerful, flexible, large or multiple server install: -[**Advanced Docker install guide**][docker] +[**Advanced Docker install guide**][advanced] The only officially supported installs of Discourse are the [Docker](https://www.docker.io/) based beginner and advanced installs. We regret that we cannot support any other methods of installation. @@ -30,8 +30,8 @@ Hosting Rails applications is complicated. Even if you already have Postgres, Re We take security very seriously at Discourse, and all our code is 100% open source and peer reviewed. Please read [our security guide](https://github.com/discourse/discourse/blob/master/docs/SECURITY.md) for an overview of security measures in Discourse. -[do]: https://github.com/discourse/discourse/blob/master/docs/INSTALL-digital-ocean.md -[docker]: https://github.com/discourse/discourse_docker +[basic]: https://github.com/discourse/discourse/blob/master/docs/INSTALL-digital-ocean.md +[advanced]: https://github.com/discourse/discourse_docker [bitnami]: http://bitnami.com/stack/discourse [cloud66]: https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud66.md [heroku]: https://github.com/discourse/discourse/blob/master/docs/install-HEROKU.md From 827daf7f0f4b50945dcf4760a4d34137a6040ce3 Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Sat, 31 Jan 2015 15:42:39 +0100 Subject: [PATCH 191/230] FIX: The order of includes in the base importer was wrong. https://meta.discourse.org/t/importer-for-simple-machines-2-forums/17656/58 --- script/import_scripts/base.rb | 3 +-- script/import_scripts/ning.rb | 1 - script/import_scripts/phpbb3.rb | 6 +----- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/script/import_scripts/base.rb b/script/import_scripts/base.rb index 2b2d249cf4..bad0093298 100644 --- a/script/import_scripts/base.rb +++ b/script/import_scripts/base.rb @@ -11,10 +11,10 @@ if ARGV.include?('bbcode-to-md') require 'ruby-bbcode-to-md' end +require_relative '../../config/environment' require_dependency 'url_helper' require_dependency 'file_helper' - module ImportScripts; end class ImportScripts::Base @@ -22,7 +22,6 @@ class ImportScripts::Base include ActionView::Helpers::NumberHelper def initialize - require_relative '../../config/environment' preload_i18n @bbcode_to_md = true if ARGV.include?('bbcode-to-md') diff --git a/script/import_scripts/ning.rb b/script/import_scripts/ning.rb index 1c6414ff3d..b9d8ad080a 100644 --- a/script/import_scripts/ning.rb +++ b/script/import_scripts/ning.rb @@ -1,4 +1,3 @@ -require File.expand_path(File.dirname(__FILE__) + "/../../config/environment") require File.expand_path(File.dirname(__FILE__) + "/base.rb") # Edit the constants and initialize method for your import data. diff --git a/script/import_scripts/phpbb3.rb b/script/import_scripts/phpbb3.rb index c163868f5b..c5d0019b33 100644 --- a/script/import_scripts/phpbb3.rb +++ b/script/import_scripts/phpbb3.rb @@ -1,8 +1,4 @@ -require File.expand_path(File.dirname(__FILE__) + "/../../config/environment") require File.expand_path(File.dirname(__FILE__) + "/base.rb") -require_dependency 'url_helper' -require_dependency 'file_helper' - require "mysql2" @@ -313,7 +309,7 @@ class ImportScripts::PhpBB3 < ImportScripts::Base s.gsub!(/\[list=1\](.*?)\[\/list:o\]/m, '[ol]\1[/ol]') # convert *-tags to li-tags so bbcode-to-md can do its magic on phpBB's lists: s.gsub!(/\[\*\](.*?)\[\/\*:m\]/, '[li]\1[/li]') - + s end From 3e7cc18276c626f60df3656708a02faa5441d466 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Mon, 2 Feb 2015 01:10:34 +0530 Subject: [PATCH 192/230] =?UTF-8?q?FIX:=20Toggling=20staff=20color=20on=20?= =?UTF-8?q?a=20post=20doesn=E2=80=99t=20get=20applied?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/assets/javascripts/discourse/views/post-menu.js.es6 | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/discourse/views/post-menu.js.es6 b/app/assets/javascripts/discourse/views/post-menu.js.es6 index 1d05fa51bb..8b600b3341 100644 --- a/app/assets/javascripts/discourse/views/post-menu.js.es6 +++ b/app/assets/javascripts/discourse/views/post-menu.js.es6 @@ -57,6 +57,7 @@ export default Discourse.View.extend(StringBuffer, { 'post.topic.deleted_at', 'post.replies.length', 'post.wiki', + 'post.post_type', 'collapsed'], _collapsedByDefault: function() { From 8b95511816f46af7ae4ba3dc7af7607a125c21e3 Mon Sep 17 00:00:00 2001 From: Sam Date: Sat, 31 Jan 2015 17:04:53 +1100 Subject: [PATCH 193/230] correct bad styling in user->pref->categories --- .../category-group-autocomplete.raw.hbs | 2 +- .../common/components/badges.css.scss | 18 ++++++++++++++++++ app/assets/stylesheets/desktop/user.scss | 9 --------- app/assets/stylesheets/mobile/user.scss | 9 --------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/category-group-autocomplete.raw.hbs b/app/assets/javascripts/discourse/templates/category-group-autocomplete.raw.hbs index 45533d2d7a..76ba9ebddf 100644 --- a/app/assets/javascripts/discourse/templates/category-group-autocomplete.raw.hbs +++ b/app/assets/javascripts/discourse/templates/category-group-autocomplete.raw.hbs @@ -1,7 +1,7 @@
    diff --git a/app/assets/stylesheets/common/components/badges.css.scss b/app/assets/stylesheets/common/components/badges.css.scss index 0e6aae9649..bff9117196 100644 --- a/app/assets/stylesheets/common/components/badges.css.scss +++ b/app/assets/stylesheets/common/components/badges.css.scss @@ -93,6 +93,24 @@ h1 a.badge-category div {vertical-align: top;} float: left; } +.user-preferences .autocomplete .badge-wrapper .badge-category { + margin: 2px; + font-weight: normal; +} + +.user-preferences .autocomplete .selected .badge-wrapper .badge-category { + font-weight: bold; +} + +.ac-wrap { + .badge-wrapper span { + padding-top: 3px; + padding-bottom: 0; + height: 20px; + max-width: 200px; + } +} + // specific styles for badge categories .bar .badge-category { diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss index 577612b09c..cc3fe23158 100644 --- a/app/assets/stylesheets/desktop/user.scss +++ b/app/assets/stylesheets/desktop/user.scss @@ -26,15 +26,6 @@ width: 500px; } - .autocomplete .badge-category { - margin: 2px; - font-weight: normal; - } - - .autocomplete .badge-category.selected { - font-weight: bold; - } - textarea { width: 530px; height: 100px; diff --git a/app/assets/stylesheets/mobile/user.scss b/app/assets/stylesheets/mobile/user.scss index 4bcdc90685..7bddfa1792 100644 --- a/app/assets/stylesheets/mobile/user.scss +++ b/app/assets/stylesheets/mobile/user.scss @@ -94,15 +94,6 @@ input.category-group { } - .autocomplete .badge-category { - margin: 2px; - font-weight: normal; - } - - .autocomplete .badge-category.selected { - font-weight: bold; - } - textarea { height: 100px; } From f4609fd6aea86548e9452556d5d7c9dc518a60a8 Mon Sep 17 00:00:00 2001 From: Sam Date: Sat, 31 Jan 2015 17:16:08 +1100 Subject: [PATCH 194/230] selection refactor caused wrench to show up in topic list for non-admins --- .../javascripts/discourse/views/topic-list-item.js.es6 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/views/topic-list-item.js.es6 b/app/assets/javascripts/discourse/views/topic-list-item.js.es6 index 5c68fc75b3..00f4aa7cf2 100644 --- a/app/assets/javascripts/discourse/views/topic-list-item.js.es6 +++ b/app/assets/javascripts/discourse/views/topic-list-item.js.es6 @@ -11,7 +11,7 @@ export default Discourse.View.extend(StringBuffer, { ], actions: { select: function(){ - this.set('controller.selected', this); + this.set('controller.selectedRow', this); }, toggleBookmark: function(){ @@ -24,8 +24,8 @@ export default Discourse.View.extend(StringBuffer, { }, selected: function(){ - return this.get('controller.selected')===this; - }.property('controller.selected'), + return this.get('controller.selectedRow')===this; + }.property('controller.selectedRow'), unboundClassNames: function(){ var classes = []; From f680374820f8775d939e2a3b2ba033b6dee75cba Mon Sep 17 00:00:00 2001 From: Sam Date: Sat, 31 Jan 2015 18:05:50 +1100 Subject: [PATCH 195/230] FIX: auto orientation code causing grey images to appear blackish --- app/models/upload.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/upload.rb b/app/models/upload.rb index 81ab4e5fe8..099fe47262 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -146,7 +146,7 @@ class Upload < ActiveRecord::Base end def self.fix_image_orientation(path) - `convert #{path} -auto-orient #{path}` + `convert #{path} -set colorspace RGB -auto-orient #{path}` end end From 05a56b25a9e77cb50f656251ac8561751982578f Mon Sep 17 00:00:00 2001 From: Sam Date: Sat, 31 Jan 2015 23:42:20 +1100 Subject: [PATCH 196/230] FIX: setting custom avatar would not work a lot of the time Due to internal structure we were often caching "redirect" images. --- .../discourse/components/avatar-uploader.js.es6 | 8 +++++--- app/assets/javascripts/discourse/models/user.js | 5 ++++- .../discourse/routes/preferences.js.es6 | 14 ++++++++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/discourse/components/avatar-uploader.js.es6 b/app/assets/javascripts/discourse/components/avatar-uploader.js.es6 index f711802a40..b67da9c65f 100644 --- a/app/assets/javascripts/discourse/components/avatar-uploader.js.es6 +++ b/app/assets/javascripts/discourse/components/avatar-uploader.js.es6 @@ -14,9 +14,6 @@ export default Em.Component.extend(UploadMixin, { uploadDone: function(data) { var self = this; - // indicates the users is using an uploaded avatar - this.set("custom_avatar_upload_id", data.result.upload_id); - // display a warning whenever the image is not a square this.set("imageIsNotASquare", data.result.width !== data.result.height); @@ -26,6 +23,11 @@ export default Em.Component.extend(UploadMixin, { // this will also capture the first frame of animated avatars when they're not allowed Discourse.Utilities.cropAvatar(data.result.url, data.files[0].type).then(function(avatarTemplate) { self.set("uploadedAvatarTemplate", avatarTemplate); + + // indicates the users is using an uploaded avatar (must happen after cropping, otherwise + // we will attempt to load an invalid avatar and cache a redirect to old one, uploadedAvatarTemplate + // trumps over custom avatar upload id) + self.set("custom_avatar_upload_id", data.result.upload_id); }); // the upload is now done diff --git a/app/assets/javascripts/discourse/models/user.js b/app/assets/javascripts/discourse/models/user.js index fde7692255..c650d1704e 100644 --- a/app/assets/javascripts/discourse/models/user.js +++ b/app/assets/javascripts/discourse/models/user.js @@ -351,10 +351,13 @@ Discourse.User = Discourse.Model.extend({ Change avatar selection */ pickAvatar: function(uploadId) { - this.set("uploaded_avatar_id", uploadId); + var self = this; + return Discourse.ajax("/users/" + this.get("username_lower") + "/preferences/avatar/pick", { type: 'PUT', data: { upload_id: uploadId } + }).then(function(){ + self.set('uploaded_avatar_id', uploadId); }); }, diff --git a/app/assets/javascripts/discourse/routes/preferences.js.es6 b/app/assets/javascripts/discourse/routes/preferences.js.es6 index 6ad9efb529..3f28b1b409 100644 --- a/app/assets/javascripts/discourse/routes/preferences.js.es6 +++ b/app/assets/javascripts/discourse/routes/preferences.js.es6 @@ -45,15 +45,17 @@ export default RestrictedUserRoute.extend(ShowFooter, { // sends the information to the server if it has changed if (avatarSelector.get('selectedUploadId') !== user.get('uploaded_avatar_id')) { - user.pickAvatar(avatarSelector.get('selectedUploadId')); + user.pickAvatar(avatarSelector.get('selectedUploadId')) + .then(function(){ + user.setProperties(avatarSelector.getProperties( + 'system_avatar_upload_id', + 'gravatar_avatar_upload_id', + 'custom_avatar_upload_id' + )); + }); } // saves the data back - user.setProperties(avatarSelector.getProperties( - 'system_avatar_upload_id', - 'gravatar_avatar_upload_id', - 'custom_avatar_upload_id' - )); avatarSelector.send('closeModal'); }, From e6d9f02f0a58513789c0ea4b95a6e0601d6f60b9 Mon Sep 17 00:00:00 2001 From: David McClure Date: Sun, 1 Feb 2015 15:28:40 -0800 Subject: [PATCH 197/230] Update install docs: 1. add deprecation warning for Heroku install guide 2. remove incomplete and outdated cloud66 guide 3. remove unused links to unofficial guides from main install doc --- docs/INSTALL-cloud66.md | 17 ----------------- docs/INSTALL.md | 6 ------ docs/install-HEROKU.md | 5 +++++ 3 files changed, 5 insertions(+), 23 deletions(-) delete mode 100644 docs/INSTALL-cloud66.md diff --git a/docs/INSTALL-cloud66.md b/docs/INSTALL-cloud66.md deleted file mode 100644 index da6322c259..0000000000 --- a/docs/INSTALL-cloud66.md +++ /dev/null @@ -1,17 +0,0 @@ -# Deploying on Cloud 66 - -![Logo](http://cdn.cloud66.com/images/easy-deploy.png) - - -Simply follow 7 steps on [building your stack](https://www.cloud66.com/help/first_stack), sign up for a Sendgrid account (for sending emails) and set -the environment variables below to have your own fully functioning Discourse installation up and running. - -Note: Setting environment variables is done during step five, before you click 'deploy': -![Environment variables](http://cdn.cloud66.com/images/environment_variables.png) - -1. SMTP_ADDRESS = your SMTP host -2. SMTP_PORT = the port on your SMTP host -3. SMTP_DOMAIN = the domain you will be sending emails from -4. SMTP_USERNAME = your SMTP username -5. SMTP_PASSWORD = your SMTP password -6. HOST_NAME = the domain hosting your site \ No newline at end of file diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 8a05c1f449..ca051f42bd 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -24,16 +24,10 @@ Hosting Rails applications is complicated. Even if you already have Postgres, Re - [Redis 2.6+](http://redis.io/download) - [Ruby 2.0+](http://www.ruby-lang.org/en/downloads/) (we recommend 2.0.0-p353 or higher) - - ## Security We take security very seriously at Discourse, and all our code is 100% open source and peer reviewed. Please read [our security guide](https://github.com/discourse/discourse/blob/master/docs/SECURITY.md) for an overview of security measures in Discourse. [basic]: https://github.com/discourse/discourse/blob/master/docs/INSTALL-digital-ocean.md [advanced]: https://github.com/discourse/discourse_docker -[bitnami]: http://bitnami.com/stack/discourse -[cloud66]: https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud66.md -[heroku]: https://github.com/discourse/discourse/blob/master/docs/install-HEROKU.md -[ubuntu]: https://github.com/discourse/discourse/blob/master/docs/INSTALL-ubuntu.md [swap]: https://meta.discourse.org/t/create-a-swapfile-for-your-linux-server/13880 diff --git a/docs/install-HEROKU.md b/docs/install-HEROKU.md index 3d33c6ac9a..d7c5c8b42e 100644 --- a/docs/install-HEROKU.md +++ b/docs/install-HEROKU.md @@ -1,3 +1,8 @@ +# Warning: This Guide is Deprecated +We only support Docker based installs now. Please see [our **official install guide**](https://github.com/discourse/discourse/blob/master/docs/INSTALL.md) for supported install instructions. + +----- + # Basic Heroku deployment This guide takes you through the steps for deploying Discourse to the [Heroku](http://www.heroku.com/) cloud application platform. If you're unfamiliar with Heroku, [read this first](https://devcenter.heroku.com/articles/quickstart). The basic deployment of Discourse requires several services that will cost you money. In addition to the [750 free Dyno hours](https://devcenter.heroku.com/articles/usage-and-billing) provided by Heroku, the application requires one additional process to be running for the Sidekiq queue ($34 monthly), and a Redis database plan that supports a minimum of 2 databases (average $10 monthly). From a29970275af45ffac779c9802d1ada04e84eec5e Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Mon, 2 Feb 2015 10:38:42 +0530 Subject: [PATCH 198/230] FIX: Not allow TL4 user to set staff color --- app/assets/javascripts/discourse/views/post-menu.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/views/post-menu.js.es6 b/app/assets/javascripts/discourse/views/post-menu.js.es6 index 8b600b3341..6dd4ce27d0 100644 --- a/app/assets/javascripts/discourse/views/post-menu.js.es6 +++ b/app/assets/javascripts/discourse/views/post-menu.js.es6 @@ -329,7 +329,7 @@ export default Discourse.View.extend(StringBuffer, { '

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

    ' + '
      ' + '
    • ' + wikiIcon + wikiText + '
    • ' + - '
    • ' + postTypeIcon + postTypeText + '
    • ' + + (Discourse.User.currentProp('staff') ? '
    • ' + postTypeIcon + postTypeText + '
    • ' : '') + '
    • ' + rebakePostIcon + rebakePostText + '
    • ' + (post.hidden ? '
    • ' + unhidePostIcon + unhidePostText + '
    • ' : '') + '
    ' + From c6e27399b357cb329e90d269da163b961c3e5d5a Mon Sep 17 00:00:00 2001 From: riking Date: Mon, 2 Feb 2015 00:49:58 -0800 Subject: [PATCH 199/230] FIX: Add Google Analytics code to crawler view --- app/views/layouts/crawler.html.erb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/views/layouts/crawler.html.erb b/app/views/layouts/crawler.html.erb index 5e69ce9141..9ef563c554 100644 --- a/app/views/layouts/crawler.html.erb +++ b/app/views/layouts/crawler.html.erb @@ -8,6 +8,7 @@ <%- unless customization_disabled? %> <%= raw SiteCustomization.custom_head_tag(session[:preview_style]) %> <%- end %> + <%= render_google_universal_analytics_code %> <%= yield :head %>