mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
Merge branch 'master' into mobile
This commit is contained in:
commit
45d7765936
6
Gemfile
6
Gemfile
@ -187,7 +187,10 @@ gem 'lru_redux'
|
|||||||
# IMPORTANT: mini profiler monkey patches, so it better be required last
|
# IMPORTANT: mini profiler monkey patches, so it better be required last
|
||||||
# If you want to amend mini profiler to do the monkey patches in the railstie
|
# If you want to amend mini profiler to do the monkey patches in the railstie
|
||||||
# we are open to it. by deferring require to the initializer we can configure disourse installs without it
|
# we are open to it. by deferring require to the initializer we can configure disourse installs without it
|
||||||
gem 'rack-mini-profiler', '0.1.29', require: false # require: false #, git: 'git://github.com/SamSaffron/MiniProfiler'
|
|
||||||
|
# gem 'rack-mini-profiler', '0.1.30', require: false
|
||||||
|
gem 'flamegraph', require: false
|
||||||
|
gem 'rack-mini-profiler', require: false
|
||||||
|
|
||||||
# used for caching, optional
|
# used for caching, optional
|
||||||
# redis-rack-cache is missing a sane expiry policy, it hogs redis
|
# redis-rack-cache is missing a sane expiry policy, it hogs redis
|
||||||
@ -196,6 +199,7 @@ gem 'redis-rack-cache', git: 'https://github.com/SamSaffron/redis-rack-cache.git
|
|||||||
gem 'rack-cache', require: false
|
gem 'rack-cache', require: false
|
||||||
gem 'rack-cors', require: false
|
gem 'rack-cors', require: false
|
||||||
gem 'unicorn', require: false
|
gem 'unicorn', require: false
|
||||||
|
gem 'puma', require: false
|
||||||
|
|
||||||
# perftools only works on 1.9 atm
|
# perftools only works on 1.9 atm
|
||||||
group :profile do
|
group :profile do
|
||||||
|
19
Gemfile.lock
19
Gemfile.lock
@ -180,9 +180,14 @@ GEM
|
|||||||
fast_blank (0.0.1)
|
fast_blank (0.0.1)
|
||||||
rake
|
rake
|
||||||
rake-compiler
|
rake-compiler
|
||||||
|
fast_stack (0.0.5)
|
||||||
|
rake
|
||||||
|
rake-compiler
|
||||||
fast_xs (0.8.0)
|
fast_xs (0.8.0)
|
||||||
fastimage (1.3.0)
|
fastimage (1.3.0)
|
||||||
ffi (1.8.1)
|
ffi (1.8.1)
|
||||||
|
flamegraph (0.0.2)
|
||||||
|
fast_stack
|
||||||
fog (1.14.0)
|
fog (1.14.0)
|
||||||
builder
|
builder
|
||||||
excon (~> 0.25.0)
|
excon (~> 0.25.0)
|
||||||
@ -219,7 +224,7 @@ GEM
|
|||||||
librarian (0.1.0)
|
librarian (0.1.0)
|
||||||
highline
|
highline
|
||||||
thor (~> 0.15)
|
thor (~> 0.15)
|
||||||
libv8 (3.11.8.17)
|
libv8 (3.16.14.3)
|
||||||
listen (0.7.3)
|
listen (0.7.3)
|
||||||
lru_redux (0.0.6)
|
lru_redux (0.0.6)
|
||||||
mail (2.4.4)
|
mail (2.4.4)
|
||||||
@ -287,6 +292,8 @@ GEM
|
|||||||
pry (~> 0.9.10)
|
pry (~> 0.9.10)
|
||||||
pry-rails (0.2.2)
|
pry-rails (0.2.2)
|
||||||
pry (>= 0.9.10)
|
pry (>= 0.9.10)
|
||||||
|
puma (2.5.1)
|
||||||
|
rack (>= 1.1, < 2.0)
|
||||||
qunit-rails (0.0.3)
|
qunit-rails (0.0.3)
|
||||||
railties (>= 3.2.3)
|
railties (>= 3.2.3)
|
||||||
rack (1.4.5)
|
rack (1.4.5)
|
||||||
@ -294,7 +301,7 @@ GEM
|
|||||||
rack (>= 0.4)
|
rack (>= 0.4)
|
||||||
rack-cors (0.2.7)
|
rack-cors (0.2.7)
|
||||||
rack
|
rack
|
||||||
rack-mini-profiler (0.1.29)
|
rack-mini-profiler (0.1.31)
|
||||||
rack (>= 1.1.3)
|
rack (>= 1.1.3)
|
||||||
rack-openid (1.3.1)
|
rack-openid (1.3.1)
|
||||||
rack (>= 1.1.0)
|
rack (>= 1.1.0)
|
||||||
@ -423,8 +430,8 @@ GEM
|
|||||||
activemodel (~> 3.0)
|
activemodel (~> 3.0)
|
||||||
railties (~> 3.0)
|
railties (~> 3.0)
|
||||||
temple (0.6.4)
|
temple (0.6.4)
|
||||||
therubyracer (0.11.4)
|
therubyracer (0.12.0)
|
||||||
libv8 (~> 3.11.8.12)
|
libv8 (~> 3.16.14.0)
|
||||||
ref
|
ref
|
||||||
thin (1.5.1)
|
thin (1.5.1)
|
||||||
daemons (>= 1.0.9)
|
daemons (>= 1.0.9)
|
||||||
@ -472,6 +479,7 @@ DEPENDENCIES
|
|||||||
fast_xor!
|
fast_xor!
|
||||||
fast_xs
|
fast_xs
|
||||||
fastimage
|
fastimage
|
||||||
|
flamegraph
|
||||||
fog
|
fog
|
||||||
handlebars-source (= 1.0.12)
|
handlebars-source (= 1.0.12)
|
||||||
highline
|
highline
|
||||||
@ -501,10 +509,11 @@ DEPENDENCIES
|
|||||||
pg
|
pg
|
||||||
pry-nav
|
pry-nav
|
||||||
pry-rails
|
pry-rails
|
||||||
|
puma
|
||||||
qunit-rails
|
qunit-rails
|
||||||
rack-cache
|
rack-cache
|
||||||
rack-cors
|
rack-cors
|
||||||
rack-mini-profiler (= 0.1.29)
|
rack-mini-profiler
|
||||||
rails (= 3.2.12)
|
rails (= 3.2.12)
|
||||||
rails_multisite!
|
rails_multisite!
|
||||||
rake
|
rake
|
||||||
|
@ -33,7 +33,7 @@ GIT
|
|||||||
|
|
||||||
GIT
|
GIT
|
||||||
remote: git://github.com/rails/rails.git
|
remote: git://github.com/rails/rails.git
|
||||||
revision: e36692a7466011ab51393ac8ca6dfffcb9d79ec0
|
revision: 025b63db308fbbf942a3bc2673d4aadab968c524
|
||||||
branch: 4-0-stable
|
branch: 4-0-stable
|
||||||
specs:
|
specs:
|
||||||
actionmailer (4.0.0)
|
actionmailer (4.0.0)
|
||||||
@ -216,9 +216,14 @@ GEM
|
|||||||
fast_blank (0.0.1)
|
fast_blank (0.0.1)
|
||||||
rake
|
rake
|
||||||
rake-compiler
|
rake-compiler
|
||||||
|
fast_stack (0.0.5)
|
||||||
|
rake
|
||||||
|
rake-compiler
|
||||||
fast_xs (0.8.0)
|
fast_xs (0.8.0)
|
||||||
fastimage (1.5.0)
|
fastimage (1.5.0)
|
||||||
ffi (1.9.0)
|
ffi (1.9.0)
|
||||||
|
flamegraph (0.0.2)
|
||||||
|
fast_stack
|
||||||
fog (1.14.0)
|
fog (1.14.0)
|
||||||
builder
|
builder
|
||||||
excon (~> 0.25.0)
|
excon (~> 0.25.0)
|
||||||
@ -256,7 +261,7 @@ GEM
|
|||||||
librarian (0.1.0)
|
librarian (0.1.0)
|
||||||
highline
|
highline
|
||||||
thor (~> 0.15)
|
thor (~> 0.15)
|
||||||
libv8 (3.11.8.17)
|
libv8 (3.16.14.3)
|
||||||
listen (1.2.2)
|
listen (1.2.2)
|
||||||
rb-fsevent (>= 0.9.3)
|
rb-fsevent (>= 0.9.3)
|
||||||
rb-inotify (>= 0.9)
|
rb-inotify (>= 0.9)
|
||||||
@ -267,7 +272,7 @@ GEM
|
|||||||
treetop (~> 1.4.8)
|
treetop (~> 1.4.8)
|
||||||
metaclass (0.0.1)
|
metaclass (0.0.1)
|
||||||
method_source (0.8.1)
|
method_source (0.8.1)
|
||||||
mime-types (1.24)
|
mime-types (1.25)
|
||||||
mini_portile (0.5.1)
|
mini_portile (0.5.1)
|
||||||
minitest (4.7.5)
|
minitest (4.7.5)
|
||||||
mocha (0.14.0)
|
mocha (0.14.0)
|
||||||
@ -327,6 +332,8 @@ GEM
|
|||||||
pry (~> 0.9.10)
|
pry (~> 0.9.10)
|
||||||
pry-rails (0.3.1)
|
pry-rails (0.3.1)
|
||||||
pry (>= 0.9.10)
|
pry (>= 0.9.10)
|
||||||
|
puma (2.5.1)
|
||||||
|
rack (>= 1.1, < 2.0)
|
||||||
qunit-rails (0.0.3)
|
qunit-rails (0.0.3)
|
||||||
railties (>= 3.2.3)
|
railties (>= 3.2.3)
|
||||||
rack (1.5.2)
|
rack (1.5.2)
|
||||||
@ -334,7 +341,7 @@ GEM
|
|||||||
rack (>= 0.4)
|
rack (>= 0.4)
|
||||||
rack-cors (0.2.8)
|
rack-cors (0.2.8)
|
||||||
rack
|
rack
|
||||||
rack-mini-profiler (0.1.29)
|
rack-mini-profiler (0.1.31)
|
||||||
rack (>= 1.1.3)
|
rack (>= 1.1.3)
|
||||||
rack-openid (1.3.1)
|
rack-openid (1.3.1)
|
||||||
rack (>= 1.1.0)
|
rack (>= 1.1.0)
|
||||||
@ -432,8 +439,8 @@ GEM
|
|||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
sprockets (~> 2.8)
|
sprockets (~> 2.8)
|
||||||
temple (0.6.5)
|
temple (0.6.5)
|
||||||
therubyracer (0.11.4)
|
therubyracer (0.12.0)
|
||||||
libv8 (~> 3.11.8.12)
|
libv8 (~> 3.16.14.0)
|
||||||
ref
|
ref
|
||||||
thin (1.5.1)
|
thin (1.5.1)
|
||||||
daemons (>= 1.0.9)
|
daemons (>= 1.0.9)
|
||||||
@ -482,6 +489,7 @@ DEPENDENCIES
|
|||||||
fast_xor!
|
fast_xor!
|
||||||
fast_xs
|
fast_xs
|
||||||
fastimage
|
fastimage
|
||||||
|
flamegraph
|
||||||
fog
|
fog
|
||||||
handlebars-source (= 1.0.12)
|
handlebars-source (= 1.0.12)
|
||||||
highline
|
highline
|
||||||
@ -511,10 +519,11 @@ DEPENDENCIES
|
|||||||
pg
|
pg
|
||||||
pry-nav
|
pry-nav
|
||||||
pry-rails
|
pry-rails
|
||||||
|
puma
|
||||||
qunit-rails
|
qunit-rails
|
||||||
rack-cache
|
rack-cache
|
||||||
rack-cors
|
rack-cors
|
||||||
rack-mini-profiler (= 0.1.29)
|
rack-mini-profiler
|
||||||
rails!
|
rails!
|
||||||
rails-observers
|
rails-observers
|
||||||
rails_multisite!
|
rails_multisite!
|
||||||
|
@ -3,6 +3,49 @@
|
|||||||
|
|
||||||
@module $.fn.autocomplete
|
@module $.fn.autocomplete
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
var shiftMap = [];
|
||||||
|
shiftMap[192] = "~";
|
||||||
|
shiftMap[49] = "!";
|
||||||
|
shiftMap[50] = "@";
|
||||||
|
shiftMap[51] = "#";
|
||||||
|
shiftMap[52] = "$";
|
||||||
|
shiftMap[53] = "%";
|
||||||
|
shiftMap[54] = "^";
|
||||||
|
shiftMap[55] = "&";
|
||||||
|
shiftMap[56] = "*";
|
||||||
|
shiftMap[57] = "(";
|
||||||
|
shiftMap[48] = ")";
|
||||||
|
shiftMap[109] = "_";
|
||||||
|
shiftMap[107] = "+";
|
||||||
|
shiftMap[219] = "{";
|
||||||
|
shiftMap[221] = "}";
|
||||||
|
shiftMap[220] = "|";
|
||||||
|
shiftMap[59] = ":";
|
||||||
|
shiftMap[222] = "\"";
|
||||||
|
shiftMap[188] = "<";
|
||||||
|
shiftMap[190] = ">";
|
||||||
|
shiftMap[191] = "?";
|
||||||
|
shiftMap[32] = " ";
|
||||||
|
|
||||||
|
function mapKeyPressToActualCharacter(isShiftKey, characterCode) {
|
||||||
|
if ( characterCode === 27 || characterCode === 8 || characterCode === 9 || characterCode === 20 || characterCode === 16 || characterCode === 17 || characterCode === 91 || characterCode === 13 || characterCode === 92 || characterCode === 18 ) { return false; }
|
||||||
|
|
||||||
|
if (isShiftKey) {
|
||||||
|
if ( characterCode >= 65 && characterCode <= 90 ) {
|
||||||
|
return String.fromCharCode(characterCode);
|
||||||
|
} else {
|
||||||
|
return shiftMap[characterCode];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ( characterCode >= 65 && characterCode <= 90 ) {
|
||||||
|
return String.fromCharCode(characterCode).toLowerCase();
|
||||||
|
} else {
|
||||||
|
return String.fromCharCode(characterCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$.fn.autocomplete = function(options) {
|
$.fn.autocomplete = function(options) {
|
||||||
|
|
||||||
var autocompletePlugin = this;
|
var autocompletePlugin = this;
|
||||||
@ -338,11 +381,15 @@ $.fn.autocomplete = function(options) {
|
|||||||
}
|
}
|
||||||
term = me.val().substring(completeStart + (options.key ? 1 : 0), caretPosition);
|
term = me.val().substring(completeStart + (options.key ? 1 : 0), caretPosition);
|
||||||
if (e.which >= 48 && e.which <= 90) {
|
if (e.which >= 48 && e.which <= 90) {
|
||||||
term += String.fromCharCode(e.which);
|
term += mapKeyPressToActualCharacter(e.shiftKey, e.which);
|
||||||
} else if (e.which === 187) {
|
} else if (e.which === 187) {
|
||||||
term += "+";
|
term += "+";
|
||||||
} else if (e.which === 189) {
|
} else if (e.which === 189) {
|
||||||
term += (e.shiftKey) ? "_" : "-";
|
term += (e.shiftKey) ? "_" : "-";
|
||||||
|
} else if (e.which === 220) {
|
||||||
|
term += (e.shiftKey) ? "|" : "]";
|
||||||
|
} else if (e.which === 222) {
|
||||||
|
term += (e.shiftKey) ? "\"" : "'";
|
||||||
} else {
|
} else {
|
||||||
if (e.which !== 8) {
|
if (e.which !== 8) {
|
||||||
term += ",";
|
term += ",";
|
||||||
|
@ -13,13 +13,29 @@ Discourse.Formatter = (function(){
|
|||||||
|
|
||||||
var firstPart = string.substr(0, maxLength);
|
var firstPart = string.substr(0, maxLength);
|
||||||
|
|
||||||
var betterSplit = firstPart.substr(1).search(/[^a-z]/);
|
// work backward to split stuff like ABPoop to AB Poop
|
||||||
if (betterSplit >= 0) {
|
var i;
|
||||||
var offset = 1;
|
for(i=firstPart.length-1;i>0;i--){
|
||||||
if(string[betterSplit+1] === "_") {
|
if(firstPart[i].match(/[A-Z]/)){
|
||||||
offset = 2;
|
break;
|
||||||
}
|
}
|
||||||
return string.substr(0, betterSplit + offset) + " " + string.substring(betterSplit + offset);
|
}
|
||||||
|
|
||||||
|
// work forwards to split stuff like ab111 to ab 111
|
||||||
|
if(i===0) {
|
||||||
|
for(i=1;i<firstPart.length;i++){
|
||||||
|
if(firstPart[i].match(/[^a-z]/)){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i > 0 && i < firstPart.length) {
|
||||||
|
var offset = 0;
|
||||||
|
if(string[i] === "_") {
|
||||||
|
offset = 1;
|
||||||
|
}
|
||||||
|
return string.substr(0, i + offset) + " " + string.substring(i + offset);
|
||||||
} else {
|
} else {
|
||||||
return firstPart + " " + string.substr(maxLength);
|
return firstPart + " " + string.substr(maxLength);
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,10 @@ Discourse.Quote = {
|
|||||||
sansQuotes = contents.replace(this.REGEXP, '').trim();
|
sansQuotes = contents.replace(this.REGEXP, '').trim();
|
||||||
if (sansQuotes.length === 0) return "";
|
if (sansQuotes.length === 0) return "";
|
||||||
|
|
||||||
|
// Escape the content of the quote
|
||||||
|
sansQuotes = sansQuotes.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">");
|
||||||
|
|
||||||
result = "[quote=\"" + post.get('username') + ", post:" + post.get('post_number') + ", topic:" + post.get('topic_id');
|
result = "[quote=\"" + post.get('username') + ", post:" + post.get('post_number') + ", topic:" + post.get('topic_id');
|
||||||
|
|
||||||
/* Strip the HTML from cooked */
|
/* Strip the HTML from cooked */
|
||||||
|
@ -8,7 +8,15 @@
|
|||||||
@module Discourse
|
@module Discourse
|
||||||
**/
|
**/
|
||||||
Discourse.AvatarSelectorController = Discourse.Controller.extend(Discourse.ModalFunctionality, {
|
Discourse.AvatarSelectorController = Discourse.Controller.extend(Discourse.ModalFunctionality, {
|
||||||
toggleUseUploadedAvatar: function(toggle) {
|
useUploadedAvatar: function() {
|
||||||
this.set("use_uploaded_avatar", toggle);
|
this.set("use_uploaded_avatar", true);
|
||||||
}
|
},
|
||||||
|
|
||||||
|
useGravatar: function() {
|
||||||
|
this.set("use_uploaded_avatar", false);
|
||||||
|
},
|
||||||
|
|
||||||
|
avatarTemplate: function() {
|
||||||
|
return this.get("use_uploaded_avatar") ? this.get("uploaded_avatar_template") : this.get("gravatar_template");
|
||||||
|
}.property("use_uploaded_avatar", "uploaded_avatar_template", "gravatar_template")
|
||||||
});
|
});
|
||||||
|
@ -13,7 +13,7 @@ Discourse.EditTopicAutoCloseController = Discourse.ObjectController.extend(Disco
|
|||||||
if( this.get('details.auto_close_at') ) {
|
if( this.get('details.auto_close_at') ) {
|
||||||
var closeTime = new Date( this.get('details.auto_close_at') );
|
var closeTime = new Date( this.get('details.auto_close_at') );
|
||||||
if (closeTime > new Date()) {
|
if (closeTime > new Date()) {
|
||||||
this.set('auto_close_days', closeTime.daysSince());
|
this.set('auto_close_days', Math.round(moment(closeTime).diff(new Date(), 'days', true)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.set('details.auto_close_days', '');
|
this.set('details.auto_close_days', '');
|
||||||
|
@ -58,9 +58,11 @@ Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunc
|
|||||||
|
|
||||||
if (opts) params = $.extend(params, opts);
|
if (opts) params = $.extend(params, opts);
|
||||||
|
|
||||||
|
$('#discourse-modal').modal('hide');
|
||||||
postAction.act(params).then(function() {
|
postAction.act(params).then(function() {
|
||||||
flagController.send('closeModal');
|
flagController.send('closeModal');
|
||||||
}, function(errors) {
|
}, function(errors) {
|
||||||
|
$('#discourse-modal').modal('show');
|
||||||
flagController.displayErrors(errors);
|
flagController.displayErrors(errors);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -95,6 +95,11 @@ Discourse.LoginController = Discourse.Controller.extend(Discourse.ModalFunctiona
|
|||||||
},
|
},
|
||||||
|
|
||||||
authenticationComplete: function(options) {
|
authenticationComplete: function(options) {
|
||||||
|
if (options.requires_invite) {
|
||||||
|
this.flash(I18n.t('login.requires_invite'), 'success');
|
||||||
|
this.set('authenticate', null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (options.awaiting_approval) {
|
if (options.awaiting_approval) {
|
||||||
this.flash(I18n.t('login.awaiting_approval'), 'success');
|
this.flash(I18n.t('login.awaiting_approval'), 'success');
|
||||||
this.set('authenticate', null);
|
this.set('authenticate', null);
|
||||||
|
@ -12,6 +12,7 @@ Discourse.MergeTopicController = Discourse.ObjectController.extend(Discourse.Sel
|
|||||||
|
|
||||||
topicController: Em.computed.alias('controllers.topic'),
|
topicController: Em.computed.alias('controllers.topic'),
|
||||||
selectedPosts: Em.computed.alias('topicController.selectedPosts'),
|
selectedPosts: Em.computed.alias('topicController.selectedPosts'),
|
||||||
|
selectedReplies: Em.computed.alias('topicController.selectedReplies'),
|
||||||
allPostsSelected: Em.computed.alias('topicController.allPostsSelected'),
|
allPostsSelected: Em.computed.alias('topicController.allPostsSelected'),
|
||||||
|
|
||||||
buttonDisabled: function() {
|
buttonDisabled: function() {
|
||||||
@ -31,10 +32,13 @@ Discourse.MergeTopicController = Discourse.ObjectController.extend(Discourse.Sel
|
|||||||
if (this.get('allPostsSelected')) {
|
if (this.get('allPostsSelected')) {
|
||||||
promise = Discourse.Topic.mergeTopic(this.get('id'), this.get('selectedTopicId'));
|
promise = Discourse.Topic.mergeTopic(this.get('id'), this.get('selectedTopicId'));
|
||||||
} else {
|
} else {
|
||||||
var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); });
|
var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); }),
|
||||||
|
replyPostIds = this.get('selectedReplies').map(function(p) { return p.get('id'); });
|
||||||
|
|
||||||
promise = Discourse.Topic.movePosts(this.get('id'), {
|
promise = Discourse.Topic.movePosts(this.get('id'), {
|
||||||
destination_topic_id: this.get('selectedTopicId'),
|
destination_topic_id: this.get('selectedTopicId'),
|
||||||
post_ids: postIds
|
post_ids: postIds,
|
||||||
|
reply_post_ids: replyPostIds
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ Discourse.SplitTopicController = Discourse.ObjectController.extend(Discourse.Sel
|
|||||||
|
|
||||||
topicController: Em.computed.alias('controllers.topic'),
|
topicController: Em.computed.alias('controllers.topic'),
|
||||||
selectedPosts: Em.computed.alias('topicController.selectedPosts'),
|
selectedPosts: Em.computed.alias('topicController.selectedPosts'),
|
||||||
|
selectedReplies: Em.computed.alias('topicController.selectedReplies'),
|
||||||
|
|
||||||
buttonDisabled: function() {
|
buttonDisabled: function() {
|
||||||
if (this.get('saving')) return true;
|
if (this.get('saving')) return true;
|
||||||
@ -30,21 +31,23 @@ Discourse.SplitTopicController = Discourse.ObjectController.extend(Discourse.Sel
|
|||||||
movePostsToNewTopic: function() {
|
movePostsToNewTopic: function() {
|
||||||
this.set('saving', true);
|
this.set('saving', true);
|
||||||
|
|
||||||
var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); });
|
var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); }),
|
||||||
var splitTopicController = this;
|
replyPostIds = this.get('selectedReplies').map(function(p) { return p.get('id'); }),
|
||||||
|
self = this;
|
||||||
|
|
||||||
Discourse.Topic.movePosts(this.get('id'), {
|
Discourse.Topic.movePosts(this.get('id'), {
|
||||||
title: this.get('topicName'),
|
title: this.get('topicName'),
|
||||||
post_ids: postIds
|
post_ids: postIds,
|
||||||
|
reply_post_ids: replyPostIds
|
||||||
}).then(function(result) {
|
}).then(function(result) {
|
||||||
// Posts moved
|
// Posts moved
|
||||||
splitTopicController.send('closeModal');
|
self.send('closeModal');
|
||||||
splitTopicController.get('topicController').toggleMultiSelect();
|
self.get('topicController').toggleMultiSelect();
|
||||||
Em.run.next(function() { Discourse.URL.routeTo(result.url); });
|
Em.run.next(function() { Discourse.URL.routeTo(result.url); });
|
||||||
}, function() {
|
}, function() {
|
||||||
// Error moving posts
|
// Error moving posts
|
||||||
splitTopicController.flash(I18n.t('topic.split_topic.error'));
|
self.flash(I18n.t('topic.split_topic.error'));
|
||||||
splitTopicController.set('saving', false);
|
self.set('saving', false);
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,15 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
|
|||||||
summaryCollapsed: true,
|
summaryCollapsed: true,
|
||||||
needs: ['header', 'modal', 'composer', 'quoteButton'],
|
needs: ['header', 'modal', 'composer', 'quoteButton'],
|
||||||
allPostsSelected: false,
|
allPostsSelected: false,
|
||||||
selectedPosts: new Em.Set(),
|
|
||||||
editingTopic: false,
|
editingTopic: false,
|
||||||
|
selectedPosts: null,
|
||||||
|
selectedReplies: null,
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
this._super();
|
||||||
|
this.set('selectedPosts', new Em.Set());
|
||||||
|
this.set('selectedReplies', new Em.Set());
|
||||||
|
},
|
||||||
|
|
||||||
jumpTopDisabled: function() {
|
jumpTopDisabled: function() {
|
||||||
return (this.get('progressPosition') === 1);
|
return (this.get('progressPosition') === 1);
|
||||||
@ -82,18 +89,48 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
|
|||||||
return false;
|
return false;
|
||||||
}.property('postStream.loaded', 'currentPost', 'postStream.filteredPostsCount'),
|
}.property('postStream.loaded', 'currentPost', 'postStream.filteredPostsCount'),
|
||||||
|
|
||||||
selectPost: function(post) {
|
deselectPost: function(post) {
|
||||||
|
this.get('selectedPosts').removeObject(post);
|
||||||
|
|
||||||
|
var selectedReplies = this.get('selectedReplies');
|
||||||
|
selectedReplies.removeObject(post);
|
||||||
|
|
||||||
|
var selectedReply = selectedReplies.findProperty('post_number', post.get('reply_to_post_number'));
|
||||||
|
if (selectedReply) { selectedReplies.removeObject(selectedReply); }
|
||||||
|
|
||||||
|
this.set('allPostsSelected', false);
|
||||||
|
},
|
||||||
|
|
||||||
|
postSelected: function(post) {
|
||||||
|
if (this.get('allPostsSelected')) { return true; }
|
||||||
|
if (this.get('selectedPosts').contains(post)) { return true; }
|
||||||
|
if (this.get('selectedReplies').findProperty('post_number', post.get('reply_to_post_number'))) { return true; }
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
toggledSelectedPost: function(post) {
|
||||||
var selectedPosts = this.get('selectedPosts');
|
var selectedPosts = this.get('selectedPosts');
|
||||||
if (selectedPosts.contains(post)) {
|
if (this.postSelected(post)) {
|
||||||
selectedPosts.removeObject(post);
|
this.deselectPost(post);
|
||||||
this.set('allPostsSelected', false);
|
return false;
|
||||||
} else {
|
} else {
|
||||||
selectedPosts.addObject(post);
|
selectedPosts.addObject(post);
|
||||||
|
|
||||||
// If the user manually selects all posts, all posts are selected
|
// If the user manually selects all posts, all posts are selected
|
||||||
if (selectedPosts.length === this.get('posts_count')) {
|
if (selectedPosts.length === this.get('posts_count')) {
|
||||||
this.set('allPostsSelected');
|
this.set('allPostsSelected', true);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
toggledSelectedPostReplies: function(post) {
|
||||||
|
var selectedReplies = this.get('selectedReplies');
|
||||||
|
if (this.toggledSelectedPost(post)) {
|
||||||
|
selectedReplies.addObject(post);
|
||||||
|
} else {
|
||||||
|
selectedReplies.removeObject(post);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -108,6 +145,7 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
|
|||||||
|
|
||||||
deselectAll: function() {
|
deselectAll: function() {
|
||||||
this.get('selectedPosts').clear();
|
this.get('selectedPosts').clear();
|
||||||
|
this.get('selectedReplies').clear();
|
||||||
this.set('allPostsSelected', false);
|
this.set('allPostsSelected', false);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -177,19 +215,28 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
|
|||||||
},
|
},
|
||||||
|
|
||||||
deleteSelected: function() {
|
deleteSelected: function() {
|
||||||
var topicController = this;
|
var self = this;
|
||||||
bootbox.confirm(I18n.t("post.delete.confirm", { count: this.get('selectedPostsCount')}), function(result) {
|
bootbox.confirm(I18n.t("post.delete.confirm", { count: this.get('selectedPostsCount')}), function(result) {
|
||||||
if (result) {
|
if (result) {
|
||||||
|
|
||||||
// If all posts are selected, it's the same thing as deleting the topic
|
// If all posts are selected, it's the same thing as deleting the topic
|
||||||
if (topicController.get('allPostsSelected')) {
|
if (self.get('allPostsSelected')) {
|
||||||
return topicController.deleteTopic();
|
return self.deleteTopic();
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectedPosts = topicController.get('selectedPosts');
|
var selectedPosts = self.get('selectedPosts'),
|
||||||
Discourse.Post.deleteMany(selectedPosts);
|
selectedReplies = self.get('selectedReplies'),
|
||||||
topicController.get('model.postStream').removePosts(selectedPosts);
|
postStream = self.get('postStream'),
|
||||||
topicController.toggleMultiSelect();
|
toRemove = new Ember.Set();
|
||||||
|
|
||||||
|
|
||||||
|
Discourse.Post.deleteMany(selectedPosts, selectedReplies);
|
||||||
|
postStream.get('posts').forEach(function (p) {
|
||||||
|
if (self.postSelected(p)) { toRemove.addObject(p); }
|
||||||
|
});
|
||||||
|
|
||||||
|
postStream.removePosts(toRemove);
|
||||||
|
self.toggleMultiSelect();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -410,7 +457,33 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
|
|||||||
},
|
},
|
||||||
|
|
||||||
deletePost: function(post) {
|
deletePost: function(post) {
|
||||||
post.destroy(Discourse.User.current());
|
var user = Discourse.User.current(),
|
||||||
|
replyCount = post.get('reply_count'),
|
||||||
|
self = this;
|
||||||
|
|
||||||
|
// If the user is staff and the post has replies, ask if they want to delete replies too.
|
||||||
|
if (user.get('staff') && replyCount > 0) {
|
||||||
|
bootbox.confirm(I18n.t("post.controls.delete_replies.confirm", {count: replyCount}),
|
||||||
|
I18n.t("post.controls.delete_replies.no_value"),
|
||||||
|
I18n.t("post.controls.delete_replies.yes_value"),
|
||||||
|
function(result) {
|
||||||
|
|
||||||
|
// If the user wants to delete replies, do that, otherwise delete the post as normal.
|
||||||
|
if (result) {
|
||||||
|
Discourse.Post.deleteMany([post], [post]);
|
||||||
|
self.get('postStream.posts').forEach(function (p) {
|
||||||
|
if (p === post || p.get('reply_to_post_number') === post.get('post_number')) {
|
||||||
|
p.setDeletedState(user);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
post.destroy(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
post.destroy(user);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
removeAllowedUser: function(username) {
|
removeAllowedUser: function(username) {
|
||||||
|
@ -24,5 +24,6 @@ Discourse.UserActivityController = Discourse.ObjectController.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
privateMessagesActive: Em.computed.equal('pmView', 'index'),
|
privateMessagesActive: Em.computed.equal('pmView', 'index'),
|
||||||
privateMessagesSentActive: Em.computed.equal('pmView', 'sent')
|
privateMessagesMineActive: Em.computed.equal('pmView', 'mine'),
|
||||||
|
privateMessagesUnreadActive: Em.computed.equal('pmView', 'unread')
|
||||||
});
|
});
|
||||||
|
@ -1,45 +1,19 @@
|
|||||||
/**
|
/**
|
||||||
This addition handles auto linking of text. When included, it will parse out links and create
|
This addition handles auto linking of text. When included, it will parse out links and create
|
||||||
a hrefs for them.
|
a hrefs for them.
|
||||||
|
|
||||||
@event register
|
|
||||||
@namespace Discourse.Dialect
|
|
||||||
**/
|
**/
|
||||||
Discourse.Dialect.on("register", function(event) {
|
var urlReplacerArgs = {
|
||||||
|
matcher: /^((?:https?:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.])(?:[^\s()<>]+|\([^\s()<>]+\))+(?:\([^\s()<>]+\)|[^`!()\[\]{};:'".,<>?«»“”‘’\s]))/gm,
|
||||||
|
spaceBoundary: true,
|
||||||
|
|
||||||
var dialect = event.dialect,
|
emitter: function(matches) {
|
||||||
MD = event.MD;
|
var url = matches[1],
|
||||||
|
displayUrl = url;
|
||||||
|
|
||||||
/**
|
if (url.match(/^www/)) { url = "http://" + url; }
|
||||||
Parses out links from HTML.
|
return ['a', {href: url}, displayUrl];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@method autoLink
|
Discourse.Dialect.inlineRegexp(_.merge({start: 'http'}, urlReplacerArgs));
|
||||||
@param {String} text the text match
|
Discourse.Dialect.inlineRegexp(_.merge({start: 'www'}, urlReplacerArgs));
|
||||||
@param {Array} match the match found
|
|
||||||
@param {Array} prev the previous jsonML
|
|
||||||
@return {Array} an array containing how many chars we've replaced and the jsonML content for it.
|
|
||||||
@namespace Discourse.Dialect
|
|
||||||
**/
|
|
||||||
dialect.inline['http'] = dialect.inline['www'] = function autoLink(text, match, prev) {
|
|
||||||
|
|
||||||
// We only care about links on boundaries
|
|
||||||
if (prev && (prev.length > 0)) {
|
|
||||||
var last = prev[prev.length - 1];
|
|
||||||
if (typeof last === "string" && (!last.match(/\s$/))) { return; }
|
|
||||||
}
|
|
||||||
|
|
||||||
var pattern = /(^|\s)((?:https?:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.])(?:[^\s()<>]+|\([^\s()<>]+\))+(?:\([^\s()<>]+\)|[^`!()\[\]{};:'".,<>?«»“”‘’\s]))/gm,
|
|
||||||
m = pattern.exec(text);
|
|
||||||
|
|
||||||
if (m) {
|
|
||||||
var url = m[2],
|
|
||||||
displayUrl = m[2];
|
|
||||||
|
|
||||||
if (url.match(/^www/)) { url = "http://" + url; }
|
|
||||||
return [m[0].length, ['a', {href: url}, displayUrl]];
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
@ -1,205 +1,114 @@
|
|||||||
/**
|
/**
|
||||||
Regsiter all functionality for supporting BBCode in Discourse.
|
Create a simple BBCode tag handler
|
||||||
|
|
||||||
@event register
|
@method replaceBBCode
|
||||||
@namespace Discourse.Dialect
|
@param {tag} tag the tag we want to match
|
||||||
|
@param {function} emitter the function that creates JsonML for the tag
|
||||||
**/
|
**/
|
||||||
Discourse.Dialect.on("register", function(event) {
|
function replaceBBCode(tag, emitter) {
|
||||||
|
Discourse.Dialect.inlineBetween({
|
||||||
var dialect = event.dialect,
|
start: "[" + tag + "]",
|
||||||
MD = event.MD;
|
stop: "[/" + tag + "]",
|
||||||
|
emitter: emitter
|
||||||
var createBBCode = function(tag, builder, hasArgs) {
|
|
||||||
return function(text, orig_match) {
|
|
||||||
var bbcodePattern = new RegExp("\\[" + tag + "=?([^\\[\\]]+)?\\]([\\s\\S]*?)\\[\\/" + tag + "\\]", "igm");
|
|
||||||
var m = bbcodePattern.exec(text);
|
|
||||||
if (m && m[0]) {
|
|
||||||
return [m[0].length, builder(m, this)];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
var bbcodes = {'b': ['span', {'class': 'bbcode-b'}],
|
|
||||||
'i': ['span', {'class': 'bbcode-i'}],
|
|
||||||
'u': ['span', {'class': 'bbcode-u'}],
|
|
||||||
's': ['span', {'class': 'bbcode-s'}],
|
|
||||||
'spoiler': ['span', {'class': 'spoiler'}],
|
|
||||||
'li': ['li'],
|
|
||||||
'ul': ['ul'],
|
|
||||||
'ol': ['ol']};
|
|
||||||
|
|
||||||
Object.keys(bbcodes).forEach(function(tag) {
|
|
||||||
var element = bbcodes[tag];
|
|
||||||
dialect.inline["[" + tag + "]"] = createBBCode(tag, function(m, self) {
|
|
||||||
return element.concat(self.processInline(m[2]));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
dialect.inline["[img]"] = createBBCode('img', function(m) {
|
/**
|
||||||
return ['img', {href: m[2]}];
|
Creates a BBCode handler that accepts parameters. Passes them to the emitter.
|
||||||
});
|
|
||||||
|
|
||||||
dialect.inline["[email]"] = createBBCode('email', function(m) {
|
@method replaceBBCodeParamsRaw
|
||||||
return ['a', {href: "mailto:" + m[2], 'data-bbcode': true}, m[2]];
|
@param {tag} tag the tag we want to match
|
||||||
});
|
@param {function} emitter the function that creates JsonML for the tag
|
||||||
|
**/
|
||||||
|
function replaceBBCodeParamsRaw(tag, emitter) {
|
||||||
|
Discourse.Dialect.inlineBetween({
|
||||||
|
start: "[" + tag + "=",
|
||||||
|
stop: "[/" + tag + "]",
|
||||||
|
rawContents: true,
|
||||||
|
emitter: function(contents) {
|
||||||
|
var regexp = /^([^\]]+)\](.*)$/,
|
||||||
|
m = regexp.exec(contents);
|
||||||
|
|
||||||
dialect.inline["[url]"] = createBBCode('url', function(m) {
|
if (m) { return emitter.call(this, m[1], m[2]); }
|
||||||
return ['a', {href: m[2], 'data-bbcode': true}, m[2]];
|
|
||||||
});
|
|
||||||
|
|
||||||
dialect.inline["[url="] = createBBCode('url', function(m, self) {
|
|
||||||
return ['a', {href: m[1], 'data-bbcode': true}].concat(self.processInline(m[2]));
|
|
||||||
});
|
|
||||||
|
|
||||||
dialect.inline["[email="] = createBBCode('email', function(m, self) {
|
|
||||||
return ['a', {href: "mailto:" + m[1], 'data-bbcode': true}].concat(self.processInline(m[2]));
|
|
||||||
});
|
|
||||||
|
|
||||||
dialect.inline["[size="] = createBBCode('size', function(m, self) {
|
|
||||||
return ['span', {'class': "bbcode-size-" + m[1]}].concat(self.processInline(m[2]));
|
|
||||||
});
|
|
||||||
|
|
||||||
dialect.inline["[color="] = function(text, orig_match) {
|
|
||||||
var bbcodePattern = new RegExp("\\[color=?([^\\[\\]]+)?\\]([\\s\\S]*?)\\[\\/color\\]", "igm"),
|
|
||||||
m = bbcodePattern.exec(text);
|
|
||||||
|
|
||||||
if (m && m[0]) {
|
|
||||||
if (!/^(\#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?)|(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|purple|red|silver|teal|white|yellow)$/.test(m[1])) {
|
|
||||||
return [m[0].length].concat(this.processInline(m[2]));
|
|
||||||
}
|
|
||||||
return [m[0].length, ['span', {style: "color: " + m[1]}].concat(this.processInline(m[2]))];
|
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Support BBCode [code] blocks
|
Creates a BBCode handler that accepts parameters. Passes them to the emitter.
|
||||||
|
Processes the inside recursively so it can be nested.
|
||||||
|
|
||||||
@method bbcodeCode
|
@method replaceBBCodeParams
|
||||||
@param {Markdown.Block} block the block to examine
|
@param {tag} tag the tag we want to match
|
||||||
@param {Array} next the next blocks in the sequence
|
@param {function} emitter the function that creates JsonML for the tag
|
||||||
@return {Array} the JsonML containing the markup or undefined if nothing changed.
|
**/
|
||||||
@namespace Discourse.Dialect
|
function replaceBBCodeParams(tag, emitter) {
|
||||||
**/
|
replaceBBCodeParamsRaw(tag, function (param, contents) {
|
||||||
dialect.inline["[code]"] = function bbcodeCode(text, orig_match) {
|
return emitter(param, this.processInline(contents));
|
||||||
var bbcodePattern = new RegExp("\\[code\\]([\\s\\S]*?)\\[\\/code\\]", "igm"),
|
});
|
||||||
m = bbcodePattern.exec(text);
|
}
|
||||||
|
|
||||||
if (m) {
|
replaceBBCode('b', function(contents) { return ['span', {'class': 'bbcode-b'}].concat(contents); });
|
||||||
var contents = m[1].trim().split("\n");
|
replaceBBCode('i', function(contents) { return ['span', {'class': 'bbcode-i'}].concat(contents); });
|
||||||
|
replaceBBCode('u', function(contents) { return ['span', {'class': 'bbcode-u'}].concat(contents); });
|
||||||
|
replaceBBCode('s', function(contents) { return ['span', {'class': 'bbcode-s'}].concat(contents); });
|
||||||
|
|
||||||
var html = ['pre', "\n"];
|
replaceBBCode('ul', function(contents) { return ['ul'].concat(contents); });
|
||||||
contents.forEach(function (n) {
|
replaceBBCode('ol', function(contents) { return ['ol'].concat(contents); });
|
||||||
html.push(n.trim());
|
replaceBBCode('li', function(contents) { return ['li'].concat(contents); });
|
||||||
html.push(["br"]);
|
|
||||||
html.push("\n");
|
|
||||||
});
|
|
||||||
|
|
||||||
return [m[0].length, html];
|
replaceBBCode('spoiler', function(contents) { return ['span', {'class': 'spoiler'}].concat(contents); });
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
Discourse.Dialect.inlineBetween({
|
||||||
Support BBCode [quote] blocks
|
start: '[img]',
|
||||||
|
stop: '[/img]',
|
||||||
|
rawContents: true,
|
||||||
|
emitter: function(contents) { return ['img', {href: contents}]; }
|
||||||
|
});
|
||||||
|
|
||||||
@method bbcodeQuote
|
Discourse.Dialect.inlineBetween({
|
||||||
@param {Markdown.Block} block the block to examine
|
start: '[email]',
|
||||||
@param {Array} next the next blocks in the sequence
|
stop: '[/email]',
|
||||||
@return {Array} the JsonML containing the markup or undefined if nothing changed.
|
rawContents: true,
|
||||||
@namespace Discourse.Dialect
|
emitter: function(contents) { return ['a', {href: "mailto:" + contents, 'data-bbcode': true}, contents]; }
|
||||||
**/
|
});
|
||||||
dialect.block['quote'] = function bbcodeQuote(block, next) {
|
|
||||||
var m = new RegExp("\\[quote=?([^\\[\\]]+)?\\]([\\s\\S]*)", "igm").exec(block);
|
|
||||||
if (m) {
|
|
||||||
var paramsString = m[1].replace(/\"/g, ''),
|
|
||||||
params = {'class': 'quote'},
|
|
||||||
paramsSplit = paramsString.split(/\, */),
|
|
||||||
username = paramsSplit[0],
|
|
||||||
opts = dialect.options,
|
|
||||||
startPos = block.indexOf(m[0]),
|
|
||||||
leading,
|
|
||||||
quoteContents = [],
|
|
||||||
result = [];
|
|
||||||
|
|
||||||
if (startPos > 0) {
|
|
||||||
leading = block.slice(0, startPos);
|
|
||||||
|
|
||||||
var para = ['p'];
|
|
||||||
this.processInline(leading).forEach(function (l) {
|
|
||||||
para.push(l);
|
|
||||||
});
|
|
||||||
|
|
||||||
result.push(para);
|
|
||||||
}
|
|
||||||
|
|
||||||
paramsSplit.forEach(function(p,i) {
|
|
||||||
if (i > 0) {
|
|
||||||
var assignment = p.split(':');
|
|
||||||
if (assignment[0] && assignment[1]) {
|
|
||||||
params['data-' + assignment[0]] = assignment[1].trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var avatarImg;
|
|
||||||
if (opts.lookupAvatarByPostNumber) {
|
|
||||||
// client-side, we can retrieve the avatar from the post
|
|
||||||
var postNumber = parseInt(params['data-post'], 10);
|
|
||||||
avatarImg = opts.lookupAvatarByPostNumber(postNumber);
|
|
||||||
} else if (opts.lookupAvatar) {
|
|
||||||
// server-side, we need to lookup the avatar from the username
|
|
||||||
avatarImg = opts.lookupAvatar(username);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m[2]) { next.unshift(MD.mk_block(m[2])); }
|
|
||||||
|
|
||||||
while (next.length > 0) {
|
|
||||||
var b = next.shift(),
|
|
||||||
n = b.match(/([\s\S]*)\[\/quote\]([\s\S]*)/m);
|
|
||||||
|
|
||||||
if (n) {
|
|
||||||
if (n[2]) {
|
|
||||||
next.unshift(MD.mk_block(n[2]));
|
|
||||||
}
|
|
||||||
quoteContents.push(n[1]);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
quoteContents.push(b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var contents = this.processInline(quoteContents.join(" \n \n"));
|
|
||||||
contents.unshift('blockquote');
|
|
||||||
|
|
||||||
|
|
||||||
result.push(['p', ['aside', params,
|
|
||||||
['div', {'class': 'title'},
|
|
||||||
['div', {'class': 'quote-controls'}],
|
|
||||||
avatarImg ? avatarImg : "",
|
|
||||||
I18n.t('user.said',{username: username})
|
|
||||||
],
|
|
||||||
contents
|
|
||||||
]]);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
Discourse.Dialect.inlineBetween({
|
||||||
|
start: '[url]',
|
||||||
|
stop: '[/url]',
|
||||||
|
rawContents: true,
|
||||||
|
emitter: function(contents) { return ['a', {href: contents, 'data-bbcode': true}, contents]; }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Discourse.Dialect.on("parseNode", function(event) {
|
replaceBBCodeParamsRaw("url", function(param, contents) {
|
||||||
|
return ['a', {href: param, 'data-bbcode': true}, contents];
|
||||||
|
});
|
||||||
|
|
||||||
var node = event.node,
|
replaceBBCodeParamsRaw("email", function(param, contents) {
|
||||||
path = event.path;
|
return ['a', {href: "mailto:" + param, 'data-bbcode': true}, contents];
|
||||||
|
});
|
||||||
|
|
||||||
// Make sure any quotes are followed by a <br>. The formatting looks weird otherwise.
|
replaceBBCodeParams("size", function(param, contents) {
|
||||||
if (node[0] === 'aside' && node[1] && node[1]['class'] === 'quote') {
|
return ['span', {'class': "bbcode-size-" + param}].concat(contents);
|
||||||
var parent = path[path.length - 1],
|
});
|
||||||
location = parent.indexOf(node)+1,
|
|
||||||
trailing = parent.slice(location);
|
|
||||||
|
|
||||||
if (trailing.length) {
|
replaceBBCodeParams("color", function(param, contents) {
|
||||||
parent.splice(location, 0, ['br']);
|
// Only allow valid HTML colors.
|
||||||
}
|
if (/^(\#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?)|(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|purple|red|silver|teal|white|yellow)$/.test(param)) {
|
||||||
|
return ['span', {style: "color: " + param}].concat(contents);
|
||||||
|
} else {
|
||||||
|
return ['span'].concat(contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handles `[code] ... [/code]` blocks
|
||||||
|
Discourse.Dialect.replaceBlock({
|
||||||
|
start: /(\[code\])([\s\S]*)/igm,
|
||||||
|
stop: '[/code]',
|
||||||
|
|
||||||
|
emitter: function(blockContents) {
|
||||||
|
return ['p', ['pre'].concat(blockContents)];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
@ -1,32 +1,42 @@
|
|||||||
/**
|
/**
|
||||||
Markdown.js doesn't seem to do bold and italics at the same time if you surround code with
|
markdown-js doesn't ensure that em/strong codes are present on word boundaries.
|
||||||
three asterisks. This adds that support.
|
So we create our own handlers here.
|
||||||
|
|
||||||
@event register
|
|
||||||
@namespace Discourse.Dialect
|
|
||||||
**/
|
**/
|
||||||
Discourse.Dialect.on("register", function(event) {
|
|
||||||
|
|
||||||
|
// Support for simultaneous bold and italics
|
||||||
|
Discourse.Dialect.inlineBetween({
|
||||||
|
between: '***',
|
||||||
|
wordBoundary: true,
|
||||||
|
emitter: function(contents) { return ['strong', ['em'].concat(contents)]; }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Builds a common markdown replacer
|
||||||
|
var replaceMarkdown = function(match, tag) {
|
||||||
|
Discourse.Dialect.inlineBetween({
|
||||||
|
between: match,
|
||||||
|
wordBoundary: true,
|
||||||
|
emitter: function(contents) { return [tag].concat(contents) }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
replaceMarkdown('**', 'strong');
|
||||||
|
replaceMarkdown('__', 'strong');
|
||||||
|
replaceMarkdown('*', 'em');
|
||||||
|
replaceMarkdown('_', 'em');
|
||||||
|
|
||||||
|
|
||||||
|
// There's a weird issue with the markdown parser where it won't process simple blockquotes
|
||||||
|
// when they are prefixed with spaces. This fixes it.
|
||||||
|
Discourse.Dialect.on("register", function(event) {
|
||||||
var dialect = event.dialect,
|
var dialect = event.dialect,
|
||||||
MD = event.MD;
|
MD = event.MD;
|
||||||
|
|
||||||
/**
|
dialect.block["fix_simple_quotes"] = function(block, next) {
|
||||||
Handles simultaneous bold and italics
|
var m = /^ +(\>[\s\S]*)/.exec(block);
|
||||||
|
if (m && m[1] && m[1].length) {
|
||||||
@method parseMentions
|
next.unshift(MD.mk_block(m[1]));
|
||||||
@param {String} text the text match
|
return [];
|
||||||
@param {Array} match the match found
|
|
||||||
@param {Array} prev the previous jsonML
|
|
||||||
@return {Array} an array containing how many chars we've replaced and the jsonML content for it.
|
|
||||||
@namespace Discourse.Dialect
|
|
||||||
**/
|
|
||||||
dialect.inline['***'] = function boldItalics(text, match, prev) {
|
|
||||||
var regExp = /^\*{3}([^\*]+)\*{3}/,
|
|
||||||
m = regExp.exec(text);
|
|
||||||
|
|
||||||
if (m) {
|
|
||||||
return [m[0].length, ['strong', ['em'].concat(this.processInline(m[1]))]];
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
@ -3,91 +3,70 @@
|
|||||||
Discourse uses the Markdown.js as its main parser. `Discourse.Dialect` is the framework
|
Discourse uses the Markdown.js as its main parser. `Discourse.Dialect` is the framework
|
||||||
for extending it with additional formatting.
|
for extending it with additional formatting.
|
||||||
|
|
||||||
To extend the dialect, you can register a handler, and you will receive an `event` object
|
|
||||||
with a handle to the markdown `Dialect` from Markdown.js that we are defining. Here's
|
|
||||||
a sample dialect that replaces all occurrences of "evil trout" with a link that says
|
|
||||||
"EVIL TROUT IS AWESOME":
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
|
|
||||||
Discourse.Dialect.on("register", function(event) {
|
|
||||||
var dialect = event.dialect;
|
|
||||||
|
|
||||||
// To see how this works, review one of our samples or the Markdown.js code:
|
|
||||||
dialect.inline["evil trout"] = function(text) {
|
|
||||||
return ["evil trout".length, ['a', {href: "http://eviltrout.com"}, "EVIL TROUT IS AWESOME"] ];
|
|
||||||
};
|
|
||||||
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also manipulate the JsonML tree that is produced by the parser before it converted to HTML.
|
|
||||||
This is useful if the markup you want needs a certain structure of HTML elements. Rather than
|
|
||||||
writing regular expressions to match HTML, consider parsing the tree instead! We use this for
|
|
||||||
making sure a onebox is on one line, as an example.
|
|
||||||
|
|
||||||
This example changes the content of any `<code>` tags.
|
|
||||||
|
|
||||||
The `event.path` attribute contains the current path to the node.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Discourse.Dialect.on("parseNode", function(event) {
|
|
||||||
var node = event.node;
|
|
||||||
|
|
||||||
if (node[0] === 'code') {
|
|
||||||
node[node.length-1] = "EVIL TROUT HACKED YOUR CODE";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**/
|
**/
|
||||||
var parser = window.BetterMarkdown,
|
var parser = window.BetterMarkdown,
|
||||||
MD = parser.Markdown,
|
MD = parser.Markdown,
|
||||||
|
|
||||||
// Our dialect
|
|
||||||
dialect = MD.dialects.Discourse = MD.subclassDialect( MD.dialects.Gruber ),
|
dialect = MD.dialects.Discourse = MD.subclassDialect( MD.dialects.Gruber ),
|
||||||
|
initialized = false;
|
||||||
|
|
||||||
initialized = false,
|
/**
|
||||||
|
Initialize our dialects for processing.
|
||||||
|
|
||||||
/**
|
@method initializeDialects
|
||||||
Initialize our dialects for processing.
|
**/
|
||||||
|
function initializeDialects() {
|
||||||
|
Discourse.Dialect.trigger('register', {dialect: dialect, MD: MD});
|
||||||
|
MD.buildBlockOrder(dialect.block);
|
||||||
|
MD.buildInlinePatterns(dialect.inline);
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
@method initializeDialects
|
/**
|
||||||
**/
|
Parse a JSON ML tree, using registered handlers to adjust it if necessary.
|
||||||
initializeDialects = function() {
|
|
||||||
Discourse.Dialect.trigger('register', {dialect: dialect, MD: MD});
|
|
||||||
MD.buildBlockOrder(dialect.block);
|
|
||||||
MD.buildInlinePatterns(dialect.inline);
|
|
||||||
initialized = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
@method parseTree
|
||||||
Parse a JSON ML tree, using registered handlers to adjust it if necessary.
|
@param {Array} tree the JsonML tree to parse
|
||||||
|
@param {Array} path the path of ancestors to the current node in the tree. Can be used for matching.
|
||||||
|
@param {Object} insideCounts counts what tags we're inside
|
||||||
|
@returns {Array} the parsed tree
|
||||||
|
**/
|
||||||
|
function parseTree(tree, path, insideCounts) {
|
||||||
|
if (tree instanceof Array) {
|
||||||
|
Discourse.Dialect.trigger('parseNode', {node: tree, path: path, dialect: dialect, insideCounts: insideCounts || {}});
|
||||||
|
|
||||||
@method parseTree
|
path = path || [];
|
||||||
@param {Array} tree the JsonML tree to parse
|
insideCounts = insideCounts || {};
|
||||||
@param {Array} path the path of ancestors to the current node in the tree. Can be used for matching.
|
|
||||||
@param {Object} insideCounts counts what tags we're inside
|
|
||||||
@returns {Array} the parsed tree
|
|
||||||
**/
|
|
||||||
parseTree = function parseTree(tree, path, insideCounts) {
|
|
||||||
if (tree instanceof Array) {
|
|
||||||
Discourse.Dialect.trigger('parseNode', {node: tree, path: path, dialect: dialect, insideCounts: insideCounts || {}});
|
|
||||||
|
|
||||||
path = path || [];
|
path.push(tree);
|
||||||
insideCounts = insideCounts || {};
|
tree.slice(1).forEach(function (n) {
|
||||||
|
var tagName = n[0];
|
||||||
|
insideCounts[tagName] = (insideCounts[tagName] || 0) + 1;
|
||||||
|
parseTree(n, path, insideCounts);
|
||||||
|
insideCounts[tagName] = insideCounts[tagName] - 1;
|
||||||
|
});
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
path.push(tree);
|
/**
|
||||||
tree.slice(1).forEach(function (n) {
|
Returns true if there's an invalid word boundary for a match.
|
||||||
var tagName = n[0];
|
|
||||||
insideCounts[tagName] = (insideCounts[tagName] || 0) + 1;
|
@method invalidBoundary
|
||||||
parseTree(n, path, insideCounts);
|
@param {Object} args our arguments, including whether we care about boundaries
|
||||||
insideCounts[tagName] = insideCounts[tagName] - 1;
|
@param {Array} prev the previous content, if exists
|
||||||
});
|
@returns {Boolean} whether there is an invalid word boundary
|
||||||
path.pop();
|
**/
|
||||||
}
|
function invalidBoundary(args, prev) {
|
||||||
return tree;
|
|
||||||
};
|
if (!args.wordBoundary && !args.spaceBoundary) { return; }
|
||||||
|
|
||||||
|
var last = prev[prev.length - 1];
|
||||||
|
if (typeof last !== "string") { return; }
|
||||||
|
|
||||||
|
if (args.wordBoundary && (last.match(/(\w|\/)$/))) { return true; }
|
||||||
|
if (args.spaceBoundary && (!last.match(/\s$/))) { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
An object used for rendering our dialects.
|
An object used for rendering our dialects.
|
||||||
@ -110,7 +89,281 @@ Discourse.Dialect = {
|
|||||||
dialect.options = opts;
|
dialect.options = opts;
|
||||||
var tree = parser.toHTMLTree(text, 'Discourse');
|
var tree = parser.toHTMLTree(text, 'Discourse');
|
||||||
return parser.renderJsonML(parseTree(tree));
|
return parser.renderJsonML(parseTree(tree));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
The simplest kind of replacement possible. Replace a stirng token with JsonML.
|
||||||
|
|
||||||
|
For example to replace all occurrances of :) with a smile image:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Discourse.Dialect.inlineReplace(':)', function (text) {
|
||||||
|
return ['img', {src: '/images/smile.png'}];
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
@method inlineReplace
|
||||||
|
@param {String} token The token we want to replace
|
||||||
|
@param {Function} emitter A function that emits the JsonML for the replacement.
|
||||||
|
**/
|
||||||
|
inlineReplace: function(token, emitter) {
|
||||||
|
dialect.inline[token] = function(text, match, prev) {
|
||||||
|
return [token.length, emitter.call(this, token)];
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Matches inline using a regular expression. The emitter function is passed
|
||||||
|
the matches from the regular expression.
|
||||||
|
|
||||||
|
For example, this auto links URLs:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Discourse.Dialect.inlineRegexp({
|
||||||
|
matcher: /((?:https?:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.])(?:[^\s()<>]+|\([^\s()<>]+\))+(?:\([^\s()<>]+\)|[^`!()\[\]{};:'".,<>?«»“”‘’\s]))/gm,
|
||||||
|
spaceBoundary: true,
|
||||||
|
|
||||||
|
emitter: function(matches) {
|
||||||
|
var url = matches[1];
|
||||||
|
return ['a', {href: url}, url];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
@method inlineRegexp
|
||||||
|
@param {Object} args Our replacement options
|
||||||
|
@param {Function} [opts.emitter] The function that will be called with the contents and regular expresison match and returns JsonML.
|
||||||
|
@param {String} [opts.start] The starting token we want to find
|
||||||
|
@param {String} [opts.matcher] The regular expression to match
|
||||||
|
@param {Boolean} [opts.wordBoundary] If true, the match must be on a word boundary
|
||||||
|
@param {Boolean} [opts.spaceBoundary] If true, the match must be on a sppace boundary
|
||||||
|
**/
|
||||||
|
inlineRegexp: function(args) {
|
||||||
|
dialect.inline[args.start] = function(text, match, prev) {
|
||||||
|
if (invalidBoundary(args, prev)) { return; }
|
||||||
|
|
||||||
|
args.matcher.lastIndex = 0;
|
||||||
|
var m = args.matcher.exec(text);
|
||||||
|
if (m) {
|
||||||
|
var result = args.emitter.call(this, m);
|
||||||
|
if (result) {
|
||||||
|
return [m[0].length, result];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Handles inline replacements surrounded by tokens.
|
||||||
|
|
||||||
|
For example, to handle markdown style bold. Note we use `concat` on the array because
|
||||||
|
the contents are JsonML too since we didn't pass `rawContents` as true. This supports
|
||||||
|
recursive markup.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
|
||||||
|
Discourse.Dialect.inlineBetween({
|
||||||
|
between: '**',
|
||||||
|
wordBoundary: true.
|
||||||
|
emitter: function(contents) {
|
||||||
|
return ['strong'].concat(contents);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
@method inlineBetween
|
||||||
|
@param {Object} args Our replacement options
|
||||||
|
@param {Function} [opts.emitter] The function that will be called with the contents and returns JsonML.
|
||||||
|
@param {String} [opts.start] The starting token we want to find
|
||||||
|
@param {String} [opts.stop] The ending token we want to find
|
||||||
|
@param {String} [opts.between] A shortcut for when the `start` and `stop` are the same.
|
||||||
|
@param {Boolean} [opts.rawContents] If true, the contents between the tokens will not be parsed.
|
||||||
|
@param {Boolean} [opts.wordBoundary] If true, the match must be on a word boundary
|
||||||
|
@param {Boolean} [opts.spaceBoundary] If true, the match must be on a sppace boundary
|
||||||
|
**/
|
||||||
|
inlineBetween: function(args) {
|
||||||
|
var start = args.start || args.between,
|
||||||
|
stop = args.stop || args.between,
|
||||||
|
startLength = start.length;
|
||||||
|
|
||||||
|
dialect.inline[start] = function(text, match, prev) {
|
||||||
|
if (invalidBoundary(args, prev)) { return; }
|
||||||
|
|
||||||
|
var endPos = text.indexOf(stop, startLength);
|
||||||
|
if (endPos === -1) { return; }
|
||||||
|
|
||||||
|
var between = text.slice(startLength, endPos);
|
||||||
|
|
||||||
|
// If rawcontents is set, don't process inline
|
||||||
|
if (!args.rawContents) {
|
||||||
|
between = this.processInline(between);
|
||||||
|
}
|
||||||
|
|
||||||
|
var contents = args.emitter.call(this, between);
|
||||||
|
if (contents) {
|
||||||
|
return [endPos+stop.length, contents];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Replaces a block of text between a start and stop. As opposed to inline, these
|
||||||
|
might span multiple lines.
|
||||||
|
|
||||||
|
Here's an example that takes the content between `[code]` ... `[/code]` and
|
||||||
|
puts them inside a `pre` tag:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Discourse.Dialect.replaceBlock({
|
||||||
|
start: /(\[code\])([\s\S]*)/igm,
|
||||||
|
stop: '[/code]',
|
||||||
|
|
||||||
|
emitter: function(blockContents) {
|
||||||
|
return ['p', ['pre'].concat(blockContents)];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
@method replaceBlock
|
||||||
|
@param {Object} args Our replacement options
|
||||||
|
@param {String} [opts.start] The starting regexp we want to find
|
||||||
|
@param {String} [opts.stop] The ending token we want to find
|
||||||
|
@param {Function} [opts.emitter] The emitting function to transform the contents of the block into jsonML
|
||||||
|
|
||||||
|
**/
|
||||||
|
replaceBlock: function(args) {
|
||||||
|
dialect.block[args.start.toString()] = function(block, next) {
|
||||||
|
args.start.lastIndex = 0;
|
||||||
|
var m = (args.start).exec(block);
|
||||||
|
if (!m) { return; }
|
||||||
|
|
||||||
|
var startPos = block.indexOf(m[0]),
|
||||||
|
leading,
|
||||||
|
blockContents = [],
|
||||||
|
result = [],
|
||||||
|
lineNumber = block.lineNumber;
|
||||||
|
|
||||||
|
if (startPos > 0) {
|
||||||
|
leading = block.slice(0, startPos);
|
||||||
|
lineNumber += (leading.split("\n").length - 1);
|
||||||
|
|
||||||
|
var para = ['p'];
|
||||||
|
this.processInline(leading).forEach(function (l) {
|
||||||
|
para.push(l);
|
||||||
|
});
|
||||||
|
|
||||||
|
result.push(para);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m[2]) {
|
||||||
|
next.unshift(MD.mk_block(m[2], null, lineNumber + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
lineNumber++;
|
||||||
|
while (next.length > 0) {
|
||||||
|
var b = next.shift(),
|
||||||
|
blockLine = b.lineNumber,
|
||||||
|
diff = ((typeof blockLine === "undefined") ? lineNumber : blockLine) - lineNumber;
|
||||||
|
|
||||||
|
var endFound = b.indexOf(args.stop),
|
||||||
|
leadingContents = b.slice(0, endFound),
|
||||||
|
trailingContents = b.slice(endFound+args.stop.length);
|
||||||
|
|
||||||
|
for (var i=1; i<diff; i++) {
|
||||||
|
blockContents.push("");
|
||||||
|
}
|
||||||
|
lineNumber = blockLine + b.split("\n").length - 1;
|
||||||
|
|
||||||
|
if (endFound !== -1) {
|
||||||
|
if (trailingContents) {
|
||||||
|
next.unshift(MD.mk_block(trailingContents));
|
||||||
|
}
|
||||||
|
|
||||||
|
blockContents.push(leadingContents.replace(/\s+$/, ""));
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
blockContents.push(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var test = args.emitter.call(this, blockContents, m, dialect.options);
|
||||||
|
result.push(test);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
After the parser has been executed, post process any text nodes in the HTML document.
|
||||||
|
This is useful if you want to apply a transformation to the text.
|
||||||
|
|
||||||
|
If you are generating HTML from the text, it is preferable to use the replacer
|
||||||
|
functions and do it in the parsing part of the pipeline. This function is best for
|
||||||
|
simple transformations or transformations that have to happen after all earlier
|
||||||
|
processing is done.
|
||||||
|
|
||||||
|
For example, to convert all text to upper case:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
|
||||||
|
Discourse.Dialect.postProcessText(function (text) {
|
||||||
|
return text.toUpperCase();
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
@method postProcessText
|
||||||
|
@param {Function} emitter The function to call with the text. It returns JsonML to modify the tree.
|
||||||
|
**/
|
||||||
|
postProcessText: function(emitter) {
|
||||||
|
Discourse.Dialect.on("parseNode", function(event) {
|
||||||
|
var node = event.node;
|
||||||
|
if (node.length < 2) { return; }
|
||||||
|
|
||||||
|
for (var j=1; j<node.length; j++) {
|
||||||
|
var textContent = node[j];
|
||||||
|
if (typeof textContent === "string") {
|
||||||
|
var result = emitter(textContent, event);
|
||||||
|
if (result) {
|
||||||
|
if (result instanceof Array) {
|
||||||
|
node.splice.apply(node, [j, 1].concat(result));
|
||||||
|
} else {
|
||||||
|
node[j] = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
After the parser has been executed, change the contents of a HTML tag.
|
||||||
|
|
||||||
|
Let's say you want to replace the contents of all code tags to prepend
|
||||||
|
"EVIL TROUT HACKED YOUR CODE!":
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Discourse.Dialect.postProcessTag('code', function (contents) {
|
||||||
|
return "EVIL TROUT HACKED YOUR CODE!\n\n" + contents;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
@method postProcessTag
|
||||||
|
@param {String} tag The HTML tag you want to match on
|
||||||
|
@param {Function} emitter The function to call with the text. It returns JsonML to modify the tree.
|
||||||
|
**/
|
||||||
|
postProcessTag: function(tag, emitter) {
|
||||||
|
Discourse.Dialect.on('parseNode', function (event) {
|
||||||
|
var node = event.node;
|
||||||
|
if (node[0] === tag) {
|
||||||
|
node[node.length-1] = emitter(node[node.length-1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
RSVP.EventTarget.mixin(Discourse.Dialect);
|
RSVP.EventTarget.mixin(Discourse.Dialect);
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,129 +5,16 @@
|
|||||||
@event register
|
@event register
|
||||||
@namespace Discourse.Dialect
|
@namespace Discourse.Dialect
|
||||||
**/
|
**/
|
||||||
Discourse.Dialect.on("register", function(event) {
|
Discourse.Dialect.replaceBlock({
|
||||||
var dialect = event.dialect,
|
start: /^`{3}([^\n\[\]]+)?\n?([\s\S]*)?/gm,
|
||||||
MD = event.MD;
|
stop: '```',
|
||||||
|
emitter: function(blockContents, matches) {
|
||||||
/**
|
return ['p', ['pre', ['code', {'class': matches[1] || 'lang-auto'}, blockContents.join("\n") ]]];
|
||||||
Support for github style code blocks
|
|
||||||
|
|
||||||
@method githubCode
|
|
||||||
@param {Markdown.Block} block the block to examine
|
|
||||||
@param {Array} next the next blocks in the sequence
|
|
||||||
@return {Array} the JsonML containing the markup or undefined if nothing changed.
|
|
||||||
@namespace Discourse.Dialect
|
|
||||||
**/
|
|
||||||
dialect.block.github_code = function githubCode(block, next) {
|
|
||||||
|
|
||||||
var m = /^`{3}([^\n]+)?\n?([\s\S]*)?/gm.exec(block);
|
|
||||||
|
|
||||||
if (m) {
|
|
||||||
var startPos = block.indexOf(m[0]),
|
|
||||||
leading,
|
|
||||||
codeContents = [],
|
|
||||||
result = [],
|
|
||||||
lineNumber = block.lineNumber;
|
|
||||||
|
|
||||||
if (startPos > 0) {
|
|
||||||
leading = block.slice(0, startPos);
|
|
||||||
lineNumber += (leading.split("\n").length - 1);
|
|
||||||
|
|
||||||
var para = ['p'];
|
|
||||||
this.processInline(leading).forEach(function (l) {
|
|
||||||
para.push(l);
|
|
||||||
});
|
|
||||||
|
|
||||||
result.push(para);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m[2]) { next.unshift(MD.mk_block(m[2], null, lineNumber + 1)); }
|
|
||||||
|
|
||||||
lineNumber++;
|
|
||||||
while (next.length > 0) {
|
|
||||||
var b = next.shift(),
|
|
||||||
blockLine = b.lineNumber,
|
|
||||||
diff = ((typeof blockLine === "undefined") ? lineNumber : blockLine) - lineNumber;
|
|
||||||
|
|
||||||
var endFound = b.indexOf('```'),
|
|
||||||
leadingCode = b.slice(0, endFound),
|
|
||||||
trailingCode = b.slice(endFound+3);
|
|
||||||
|
|
||||||
for (var i=1; i<diff; i++) {
|
|
||||||
codeContents.push("");
|
|
||||||
}
|
|
||||||
lineNumber = blockLine + b.split("\n").length - 1;
|
|
||||||
|
|
||||||
if (endFound !== -1) {
|
|
||||||
if (trailingCode) {
|
|
||||||
next.unshift(MD.mk_block(trailingCode));
|
|
||||||
}
|
|
||||||
|
|
||||||
codeContents.push(leadingCode.replace(/\s+$/, ""));
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
codeContents.push(b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
result.push(['p', ['pre', ['code', {'class': m[1] || 'lang-auto'}, codeContents.join("\n") ]]]);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
Ensure that content in a code block is fully escaped. This way it's not white listed
|
|
||||||
and we can use HTML and Javascript examples.
|
|
||||||
|
|
||||||
@event parseNode
|
|
||||||
@namespace Discourse.Dialect
|
|
||||||
**/
|
|
||||||
Discourse.Dialect.on("parseNode", function(event) {
|
|
||||||
var node = event.node;
|
|
||||||
|
|
||||||
if (node[0] === 'code') {
|
|
||||||
node[node.length-1] = Handlebars.Utils.escapeExpression(node[node.length-1]);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Ensure that content in a code block is fully escaped. This way it's not white listed
|
||||||
Discourse.Dialect.on("parseNode", function(event) {
|
// and we can use HTML and Javascript examples.
|
||||||
|
Discourse.Dialect.postProcessTag('code', function (contents) {
|
||||||
var node = event.node,
|
return Handlebars.Utils.escapeExpression(contents);
|
||||||
opts = event.dialect.options,
|
|
||||||
insideCounts = event.insideCounts,
|
|
||||||
linebreaks = opts.traditional_markdown_linebreaks || Discourse.SiteSettings.traditional_markdown_linebreaks;
|
|
||||||
|
|
||||||
if (!linebreaks) {
|
|
||||||
// We don't add line breaks inside a pre
|
|
||||||
if (insideCounts.pre > 0) { return; }
|
|
||||||
|
|
||||||
if (node.length > 1) {
|
|
||||||
for (var j=1; j<node.length; j++) {
|
|
||||||
var textContent = node[j];
|
|
||||||
|
|
||||||
if (typeof textContent === "string") {
|
|
||||||
|
|
||||||
if (textContent === "\n") {
|
|
||||||
node[j] = ['br'];
|
|
||||||
} else {
|
|
||||||
var split = textContent.split(/\n+/);
|
|
||||||
if (split.length) {
|
|
||||||
var spliceInstructions = [j, 1];
|
|
||||||
for (var i=0; i<split.length; i++) {
|
|
||||||
if (split[i].length > 0) {
|
|
||||||
spliceInstructions.push(split[i]);
|
|
||||||
if (i !== split.length-1) { spliceInstructions.push(['br']); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
node.splice.apply(node, spliceInstructions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
@ -2,47 +2,20 @@
|
|||||||
Supports Discourse's custom @mention syntax for calling out a user in a post.
|
Supports Discourse's custom @mention syntax for calling out a user in a post.
|
||||||
It will add a special class to them, and create a link if the user is found in a
|
It will add a special class to them, and create a link if the user is found in a
|
||||||
local map.
|
local map.
|
||||||
|
|
||||||
@event register
|
|
||||||
@namespace Discourse.Dialect
|
|
||||||
**/
|
**/
|
||||||
Discourse.Dialect.on("register", function(event) {
|
Discourse.Dialect.inlineRegexp({
|
||||||
|
start: '@',
|
||||||
|
matcher: /^(@[A-Za-z0-9][A-Za-z0-9_]{2,14})/m,
|
||||||
|
wordBoundary: true,
|
||||||
|
|
||||||
var dialect = event.dialect,
|
emitter: function(matches) {
|
||||||
MD = event.MD;
|
var username = matches[1],
|
||||||
|
mentionLookup = this.dialect.options.mentionLookup || Discourse.Mention.lookupCache;
|
||||||
|
|
||||||
/**
|
if (mentionLookup(username.substr(1))) {
|
||||||
Parses out @username mentions.
|
return ['a', {'class': 'mention', href: Discourse.getURL("/users/") + username.substr(1).toLowerCase()}, username];
|
||||||
|
} else {
|
||||||
@method parseMentions
|
return ['span', {'class': 'mention'}, username];
|
||||||
@param {String} text the text match
|
|
||||||
@param {Array} match the match found
|
|
||||||
@param {Array} prev the previous jsonML
|
|
||||||
@return {Array} an array containing how many chars we've replaced and the jsonML content for it.
|
|
||||||
@namespace Discourse.Dialect
|
|
||||||
**/
|
|
||||||
dialect.inline['@'] = function parseMentions(text, match, prev) {
|
|
||||||
|
|
||||||
// We only care about mentions on word boundaries
|
|
||||||
if (prev && (prev.length > 0)) {
|
|
||||||
var last = prev[prev.length - 1];
|
|
||||||
if (typeof last === "string" && (!last.match(/\W$/))) { return; }
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
var pattern = /^(@[A-Za-z0-9][A-Za-z0-9_]{2,14})(?=(\W|$))/m,
|
});
|
||||||
m = pattern.exec(text);
|
|
||||||
|
|
||||||
if (m) {
|
|
||||||
var username = m[1],
|
|
||||||
mentionLookup = dialect.options.mentionLookup || Discourse.Mention.lookupCache;
|
|
||||||
|
|
||||||
if (mentionLookup(username.substr(1))) {
|
|
||||||
return [username.length, ['a', {'class': 'mention', href: Discourse.getURL("/users/") + username.substr(1).toLowerCase()}, username]];
|
|
||||||
} else {
|
|
||||||
return [username.length, ['span', {'class': 'mention'}, username]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
});
|
|
@ -1,42 +1,32 @@
|
|||||||
/**
|
/**
|
||||||
Support for the newline behavior in markdown that most expect.
|
Support for the newline behavior in markdown that most expect. Look through all text nodes
|
||||||
|
in the tree, replace any new lines with `br`s.
|
||||||
@event parseNode
|
|
||||||
@namespace Discourse.Dialect
|
|
||||||
**/
|
**/
|
||||||
Discourse.Dialect.on("parseNode", function(event) {
|
Discourse.Dialect.postProcessText(function (text, event) {
|
||||||
var node = event.node,
|
var opts = event.dialect.options,
|
||||||
opts = event.dialect.options,
|
|
||||||
insideCounts = event.insideCounts,
|
insideCounts = event.insideCounts,
|
||||||
linebreaks = opts.traditional_markdown_linebreaks || Discourse.SiteSettings.traditional_markdown_linebreaks;
|
linebreaks = opts.traditional_markdown_linebreaks || Discourse.SiteSettings.traditional_markdown_linebreaks;
|
||||||
|
|
||||||
if (!linebreaks) {
|
if (linebreaks || (insideCounts.pre > 0)) { return; }
|
||||||
// We don't add line breaks inside a pre
|
|
||||||
if (insideCounts.pre > 0) { return; }
|
|
||||||
|
|
||||||
if (node.length > 1) {
|
if (text === "\n") {
|
||||||
for (var j=1; j<node.length; j++) {
|
// If the tage is just a new line, replace it with a `<br>`
|
||||||
var textContent = node[j];
|
return [['br']];
|
||||||
|
} else {
|
||||||
|
|
||||||
if (typeof textContent === "string") {
|
// If the text node contains new lines, perhaps with text between them, insert the
|
||||||
|
// `<br>` tags.
|
||||||
if (textContent === "\n") {
|
var split = text.split(/\n+/);
|
||||||
node[j] = ['br'];
|
if (split.length) {
|
||||||
} else {
|
var replacement = [];
|
||||||
var split = textContent.split(/\n+/);
|
for (var i=0; i<split.length; i++) {
|
||||||
if (split.length) {
|
if (split[i].length > 0) {
|
||||||
var spliceInstructions = [j, 1];
|
replacement.push(split[i]);
|
||||||
for (var i=0; i<split.length; i++) {
|
if (i !== split.length-1) { replacement.push(['br']); }
|
||||||
if (split[i].length > 0) {
|
|
||||||
spliceInstructions.push(split[i]);
|
|
||||||
if (i !== split.length-1) { spliceInstructions.push(['br']); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
node.splice.apply(node, spliceInstructions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return replacement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
62
app/assets/javascripts/discourse/dialects/quote_dialect.js
Normal file
62
app/assets/javascripts/discourse/dialects/quote_dialect.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
Support for quoting other users.
|
||||||
|
**/
|
||||||
|
Discourse.Dialect.replaceBlock({
|
||||||
|
start: new RegExp("\\[quote=?([^\\[\\]]+)?\\]([\\s\\S]*)", "igm"),
|
||||||
|
stop: '[/quote]',
|
||||||
|
emitter: function(blockContents, matches, options) {
|
||||||
|
|
||||||
|
var paramsString = matches[1].replace(/\"/g, ''),
|
||||||
|
params = {'class': 'quote'},
|
||||||
|
paramsSplit = paramsString.split(/\, */),
|
||||||
|
username = paramsSplit[0];
|
||||||
|
|
||||||
|
paramsSplit.forEach(function(p,i) {
|
||||||
|
if (i > 0) {
|
||||||
|
var assignment = p.split(':');
|
||||||
|
if (assignment[0] && assignment[1]) {
|
||||||
|
params['data-' + assignment[0]] = assignment[1].trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var avatarImg;
|
||||||
|
if (options.lookupAvatarByPostNumber) {
|
||||||
|
// client-side, we can retrieve the avatar from the post
|
||||||
|
var postNumber = parseInt(params['data-post'], 10);
|
||||||
|
avatarImg = options.lookupAvatarByPostNumber(postNumber);
|
||||||
|
} else if (options.lookupAvatar) {
|
||||||
|
// server-side, we need to lookup the avatar from the username
|
||||||
|
avatarImg = options.lookupAvatar(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
var contents = this.processInline(blockContents.join(" \n \n"));
|
||||||
|
contents.unshift('blockquote');
|
||||||
|
|
||||||
|
return ['p', ['aside', params,
|
||||||
|
['div', {'class': 'title'},
|
||||||
|
['div', {'class': 'quote-controls'}],
|
||||||
|
avatarImg ? avatarImg : "",
|
||||||
|
I18n.t('user.said', {username: username})
|
||||||
|
],
|
||||||
|
contents
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Discourse.Dialect.on("parseNode", function(event) {
|
||||||
|
var node = event.node,
|
||||||
|
path = event.path;
|
||||||
|
|
||||||
|
// Make sure any quotes are followed by a <br>. The formatting looks weird otherwise.
|
||||||
|
if (node[0] === 'aside' && node[1] && node[1]['class'] === 'quote') {
|
||||||
|
var parent = path[path.length - 1],
|
||||||
|
location = parent.indexOf(node)+1,
|
||||||
|
trailing = parent.slice(location);
|
||||||
|
|
||||||
|
if (trailing.length) {
|
||||||
|
parent.splice(location, 0, ['br']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
@ -78,8 +78,6 @@ DiscourseGroupedEach.prototype = {
|
|||||||
template = this.template;
|
template = this.template;
|
||||||
|
|
||||||
data.insideEach = true;
|
data.insideEach = true;
|
||||||
data.insideGroup = true;
|
|
||||||
|
|
||||||
for (var i = 0; i < contentLength; i++) {
|
for (var i = 0; i < contentLength; i++) {
|
||||||
template(content.objectAt(i), { data: data });
|
template(content.objectAt(i), { data: data });
|
||||||
}
|
}
|
||||||
@ -124,5 +122,6 @@ Ember.Handlebars.registerHelper('groupedEach', function(path, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options.hash.dataSourceBinding = path;
|
options.hash.dataSourceBinding = path;
|
||||||
|
options.data.insideGroup = true;
|
||||||
new DiscourseGroupedEach(this, path, options).render();
|
new DiscourseGroupedEach(this, path, options).render();
|
||||||
});
|
});
|
@ -11,10 +11,15 @@ Discourse.SelectedPostsCount = Em.Mixin.create({
|
|||||||
selectedPostsCount: function() {
|
selectedPostsCount: function() {
|
||||||
if (this.get('allPostsSelected')) return this.get('posts_count') || this.get('topic.posts_count');
|
if (this.get('allPostsSelected')) return this.get('posts_count') || this.get('topic.posts_count');
|
||||||
|
|
||||||
if (!this.get('selectedPosts')) return 0;
|
var sum = this.get('selectedPosts.length') || 0;
|
||||||
|
if (this.get('selectedReplies')) {
|
||||||
|
this.get('selectedReplies').forEach(function (p) {
|
||||||
|
sum += p.get('reply_count') || 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return this.get('selectedPosts.length');
|
return sum;
|
||||||
}.property('selectedPosts.length', 'allPostsSelected')
|
}.property('selectedPosts.length', 'allPostsSelected', 'selectedReplies.length')
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -442,6 +442,7 @@ Discourse.Composer = Discourse.Model.extend({
|
|||||||
postStream = this.get('topic.postStream'),
|
postStream = this.get('topic.postStream'),
|
||||||
addedToStream = false;
|
addedToStream = false;
|
||||||
|
|
||||||
|
|
||||||
// Build the post object
|
// Build the post object
|
||||||
var createdPost = Discourse.Post.create({
|
var createdPost = Discourse.Post.create({
|
||||||
raw: this.get('reply'),
|
raw: this.get('reply'),
|
||||||
@ -482,6 +483,8 @@ Discourse.Composer = Discourse.Model.extend({
|
|||||||
|
|
||||||
var composer = this;
|
var composer = this;
|
||||||
return Ember.Deferred.promise(function(promise) {
|
return Ember.Deferred.promise(function(promise) {
|
||||||
|
|
||||||
|
composer.set('composeState', SAVING);
|
||||||
createdPost.save(function(result) {
|
createdPost.save(function(result) {
|
||||||
var addedPost = false,
|
var addedPost = false,
|
||||||
saving = true;
|
saving = true;
|
||||||
@ -515,8 +518,16 @@ Discourse.Composer = Discourse.Model.extend({
|
|||||||
if (postStream) {
|
if (postStream) {
|
||||||
postStream.undoPost(createdPost);
|
postStream.undoPost(createdPost);
|
||||||
}
|
}
|
||||||
promise.reject($.parseJSON(error.responseText).errors[0]);
|
|
||||||
composer.set('composeState', OPEN);
|
composer.set('composeState', OPEN);
|
||||||
|
// TODO extract error handling code
|
||||||
|
var parsedError;
|
||||||
|
try {
|
||||||
|
parsedError = $.parseJSON(error.responseText).errors[0];
|
||||||
|
}
|
||||||
|
catch(ex) {
|
||||||
|
parsedError = "Unknown error saving post, try again. Error: " + error.status + " " + error.statusText;
|
||||||
|
}
|
||||||
|
promise.reject(parsedError);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -30,6 +30,7 @@ Discourse.Post = Discourse.Model.extend({
|
|||||||
deletedViaTopic: Em.computed.and('firstPost', 'topic.deleted_at'),
|
deletedViaTopic: Em.computed.and('firstPost', 'topic.deleted_at'),
|
||||||
deleted: Em.computed.or('deleted_at', 'deletedViaTopic'),
|
deleted: Em.computed.or('deleted_at', 'deletedViaTopic'),
|
||||||
notDeleted: Em.computed.not('deleted'),
|
notDeleted: Em.computed.not('deleted'),
|
||||||
|
userDeleted: Em.computed.empty('user_id'),
|
||||||
|
|
||||||
postDeletedBy: function() {
|
postDeletedBy: function() {
|
||||||
if (this.get('firstPost')) { return this.get('topic.deleted_by'); }
|
if (this.get('firstPost')) { return this.get('topic.deleted_by'); }
|
||||||
@ -224,17 +225,18 @@ Discourse.Post = Discourse.Model.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Deletes a post
|
Changes the state of the post to be deleted. Does not call the server, that should be
|
||||||
|
done elsewhere.
|
||||||
|
|
||||||
@method destroy
|
@method setDeletedState
|
||||||
@param {Discourse.User} deleted_by The user deleting the post
|
@param {Discourse.User} deletedBy The user deleting the post
|
||||||
**/
|
**/
|
||||||
destroy: function(deleted_by) {
|
setDeletedState: function(deletedBy) {
|
||||||
// Moderators can delete posts. Regular users can only trigger a deleted at message.
|
// Moderators can delete posts. Regular users can only trigger a deleted at message.
|
||||||
if (deleted_by.get('staff')) {
|
if (deletedBy.get('staff')) {
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
deleted_at: new Date(),
|
deleted_at: new Date(),
|
||||||
deleted_by: deleted_by,
|
deleted_by: deletedBy,
|
||||||
can_delete: false
|
can_delete: false
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -247,7 +249,16 @@ Discourse.Post = Discourse.Model.extend({
|
|||||||
user_deleted: true
|
user_deleted: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Deletes a post
|
||||||
|
|
||||||
|
@method destroy
|
||||||
|
@param {Discourse.User} deletedBy The user deleting the post
|
||||||
|
**/
|
||||||
|
destroy: function(deletedBy) {
|
||||||
|
this.setDeletedState(deletedBy);
|
||||||
return Discourse.ajax("/posts/" + (this.get('id')), { type: 'DELETE' });
|
return Discourse.ajax("/posts/" + (this.get('id')), { type: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -327,8 +338,7 @@ Discourse.Post = Discourse.Model.extend({
|
|||||||
|
|
||||||
// Whether to show replies directly below
|
// Whether to show replies directly below
|
||||||
showRepliesBelow: function() {
|
showRepliesBelow: function() {
|
||||||
var reply_count, topic;
|
var reply_count = this.get('reply_count');
|
||||||
reply_count = this.get('reply_count');
|
|
||||||
|
|
||||||
// We don't show replies if there aren't any
|
// We don't show replies if there aren't any
|
||||||
if (reply_count === 0) return false;
|
if (reply_count === 0) return false;
|
||||||
@ -340,7 +350,7 @@ Discourse.Post = Discourse.Model.extend({
|
|||||||
if (reply_count > 1) return true;
|
if (reply_count > 1) return true;
|
||||||
|
|
||||||
// If we have *exactly* one reply, we have to consider if it's directly below us
|
// If we have *exactly* one reply, we have to consider if it's directly below us
|
||||||
topic = this.get('topic');
|
var topic = this.get('topic');
|
||||||
return !topic.isReplyDirectlyBelow(this);
|
return !topic.isReplyDirectlyBelow(this);
|
||||||
|
|
||||||
}.property('reply_count'),
|
}.property('reply_count'),
|
||||||
@ -376,11 +386,12 @@ Discourse.Post.reopenClass({
|
|||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteMany: function(posts) {
|
deleteMany: function(selectedPosts, selectedReplies) {
|
||||||
return Discourse.ajax("/posts/destroy_many", {
|
return Discourse.ajax("/posts/destroy_many", {
|
||||||
type: 'DELETE',
|
type: 'DELETE',
|
||||||
data: {
|
data: {
|
||||||
post_ids: posts.map(function(p) { return p.get('id'); })
|
post_ids: selectedPosts.map(function(p) { return p.get('id'); }),
|
||||||
|
reply_post_ids: selectedReplies.map(function(p) { return p.get('id'); })
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -28,7 +28,11 @@ Discourse.User = Discourse.Model.extend({
|
|||||||
|
|
||||||
|
|
||||||
searchContext: function() {
|
searchContext: function() {
|
||||||
return ({ type: 'user', id: this.get('username_lower'), user: this });
|
return {
|
||||||
|
type: 'user',
|
||||||
|
id: this.get('username_lower'),
|
||||||
|
user: this
|
||||||
|
};
|
||||||
}.property('username_lower'),
|
}.property('username_lower'),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -101,7 +105,7 @@ Discourse.User = Discourse.Model.extend({
|
|||||||
@returns Result of ajax call
|
@returns Result of ajax call
|
||||||
**/
|
**/
|
||||||
changeUsername: function(newUsername) {
|
changeUsername: function(newUsername) {
|
||||||
return Discourse.ajax("/users/" + (this.get('username_lower')) + "/preferences/username", {
|
return Discourse.ajax("/users/" + this.get('username_lower') + "/preferences/username", {
|
||||||
type: 'PUT',
|
type: 'PUT',
|
||||||
data: { new_username: newUsername }
|
data: { new_username: newUsername }
|
||||||
});
|
});
|
||||||
@ -115,7 +119,7 @@ Discourse.User = Discourse.Model.extend({
|
|||||||
@returns Result of ajax call
|
@returns Result of ajax call
|
||||||
**/
|
**/
|
||||||
changeEmail: function(email) {
|
changeEmail: function(email) {
|
||||||
return Discourse.ajax("/users/" + (this.get('username_lower')) + "/preferences/email", {
|
return Discourse.ajax("/users/" + this.get('username_lower') + "/preferences/email", {
|
||||||
type: 'PUT',
|
type: 'PUT',
|
||||||
data: { email: email }
|
data: { email: email }
|
||||||
});
|
});
|
||||||
@ -173,9 +177,7 @@ Discourse.User = Discourse.Model.extend({
|
|||||||
changePassword: function() {
|
changePassword: function() {
|
||||||
return Discourse.ajax("/session/forgot_password", {
|
return Discourse.ajax("/session/forgot_password", {
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
data: {
|
data: { login: this.get('username') },
|
||||||
login: this.get('username')
|
|
||||||
},
|
|
||||||
type: 'POST'
|
type: 'POST'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -266,11 +268,14 @@ Discourse.User = Discourse.Model.extend({
|
|||||||
Change avatar selection
|
Change avatar selection
|
||||||
|
|
||||||
@method toggleAvatarSelection
|
@method toggleAvatarSelection
|
||||||
|
@param {Boolean} useUploadedAvatar true if the user is using the uploaded avatar
|
||||||
@returns {Promise} the result of the toggle avatar selection
|
@returns {Promise} the result of the toggle avatar selection
|
||||||
*/
|
*/
|
||||||
toggleAvatarSelection: function() {
|
toggleAvatarSelection: function(useUploadedAvatar) {
|
||||||
var data = { use_uploaded_avatar: this.get("use_uploaded_avatar") };
|
return Discourse.ajax("/users/" + this.get("username_lower") + "/preferences/avatar/toggle", {
|
||||||
return Discourse.ajax("/users/" + this.get("username") + "/preferences/avatar/toggle", { type: 'PUT', data: data });
|
type: 'PUT',
|
||||||
|
data: { use_uploaded_avatar: useUploadedAvatar }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -10,7 +10,7 @@ Discourse.Route.buildRoutes(function() {
|
|||||||
// Topic routes
|
// Topic routes
|
||||||
this.resource('topic', { path: '/t/:slug/:id' }, function() {
|
this.resource('topic', { path: '/t/:slug/:id' }, function() {
|
||||||
this.route('fromParams', { path: '/' });
|
this.route('fromParams', { path: '/' });
|
||||||
this.route('fromParams', { path: '/:nearPost' });
|
this.route('fromParamsNear', { path: '/:nearPost' });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Generate static page routes
|
// Generate static page routes
|
||||||
@ -50,7 +50,8 @@ Discourse.Route.buildRoutes(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.resource('userPrivateMessages', { path: '/private-messages' }, function() {
|
this.resource('userPrivateMessages', { path: '/private-messages' }, function() {
|
||||||
this.route('sent', {path: '/messages-sent'});
|
this.route('mine', {path: '/mine'});
|
||||||
|
this.route('unread', {path: '/unread'});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.resource('preferences', { path: '/preferences' }, function() {
|
this.resource('preferences', { path: '/preferences' }, function() {
|
||||||
|
@ -18,35 +18,29 @@ Discourse.PreferencesRoute = Discourse.RestrictedUserRoute.extend({
|
|||||||
events: {
|
events: {
|
||||||
showAvatarSelector: function() {
|
showAvatarSelector: function() {
|
||||||
Discourse.Route.showModal(this, 'avatarSelector');
|
Discourse.Route.showModal(this, 'avatarSelector');
|
||||||
var user = this.modelFor("user");
|
// all the properties needed for displaying the avatar selector modal
|
||||||
console.log(user);
|
var avatarSelector = this.modelFor('user').getProperties(
|
||||||
this.controllerFor("avatarSelector").setProperties(user.getProperties(
|
'username', 'email',
|
||||||
"username",
|
'has_uploaded_avatar', 'use_uploaded_avatar',
|
||||||
"email",
|
'gravatar_template', 'uploaded_avatar_template');
|
||||||
"has_uploaded_avatar",
|
this.controllerFor('avatarSelector').setProperties(avatarSelector);
|
||||||
"use_uploaded_avatar",
|
|
||||||
"gravatar_template",
|
|
||||||
"uploaded_avatar_template"
|
|
||||||
));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
saveAvatarSelection: function() {
|
saveAvatarSelection: function() {
|
||||||
var user = this.modelFor("user");
|
var user = this.modelFor('user');
|
||||||
var avatar = this.controllerFor("avatarSelector");
|
var avatarSelector = this.controllerFor('avatarSelector');
|
||||||
// sends the information to the server if it has changed
|
// sends the information to the server if it has changed
|
||||||
if (avatar.get("use_uploaded_avatar") !== user.get("use_uploaded_avatar")) { user.toggleAvatarSelection(); }
|
if (avatarSelector.get('use_uploaded_avatar') !== user.get('use_uploaded_avatar')) {
|
||||||
// saves the data back
|
user.toggleAvatarSelection(avatarSelector.get('use_uploaded_avatar'));
|
||||||
user.setProperties(avatar.getProperties(
|
|
||||||
"has_uploaded_avatar",
|
|
||||||
"use_uploaded_avatar",
|
|
||||||
"gravatar_template",
|
|
||||||
"uploaded_avatar_template"
|
|
||||||
));
|
|
||||||
if (avatar.get("use_uploaded_avatar")) {
|
|
||||||
user.set("avatar_template", avatar.get("uploaded_avatar_template"));
|
|
||||||
} else {
|
|
||||||
user.set("avatar_template", avatar.get("gravatar_template"));
|
|
||||||
}
|
}
|
||||||
|
// saves the data back
|
||||||
|
user.setProperties(avatarSelector.getProperties(
|
||||||
|
'has_uploaded_avatar',
|
||||||
|
'use_uploaded_avatar',
|
||||||
|
'gravatar_template',
|
||||||
|
'uploaded_avatar_template'
|
||||||
|
));
|
||||||
|
user.set('avatar_template', avatarSelector.get('avatarTemplate'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -58,4 +58,5 @@ Discourse.TopicFromParamsRoute = Discourse.Route.extend({
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Discourse.TopicFromParamsNearRoute = Discourse.TopicFromParamsRoute;
|
||||||
|
|
||||||
|
@ -171,33 +171,26 @@ Discourse.UserTopicListRoute = Discourse.Route.extend({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Discourse.UserPrivateMessagesIndexRoute = Discourse.UserTopicListRoute.extend({
|
function createPMRoute(viewName, path, type) {
|
||||||
userActionType: Discourse.UserAction.TYPES.messages_received,
|
return Discourse.UserTopicListRoute.extend({
|
||||||
|
userActionType: Discourse.UserAction.TYPES.messages_received,
|
||||||
|
|
||||||
model: function() {
|
model: function() {
|
||||||
return Discourse.TopicList.find('topics/private-messages/' + this.modelFor('user').get('username_lower'));
|
return Discourse.TopicList.find('topics/' + path + '/' + this.modelFor('user').get('username_lower'));
|
||||||
},
|
},
|
||||||
|
|
||||||
setupController: function(controller, model) {
|
setupController: function(controller, model) {
|
||||||
this._super(controller, model);
|
this._super(controller, model);
|
||||||
controller.set('hideCategories', true);
|
controller.set('hideCategories', true);
|
||||||
this.controllerFor('userActivity').set('pmView', 'index');
|
this.controllerFor('userActivity').set('pmView', viewName);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
});
|
Discourse.UserPrivateMessagesIndexRoute = createPMRoute('index', 'private-messages');
|
||||||
Discourse.UserPrivateMessagesSentRoute = Discourse.UserTopicListRoute.extend({
|
Discourse.UserPrivateMessagesMineRoute = createPMRoute('mine', 'private-messages-sent');
|
||||||
userActionType: Discourse.UserAction.TYPES.messages_sent,
|
Discourse.UserPrivateMessagesUnreadRoute = createPMRoute('unread', 'private-messages-unread');
|
||||||
|
|
||||||
model: function() {
|
|
||||||
return Discourse.TopicList.find('topics/private-messages-sent/' + this.modelFor('user').get('username_lower'));
|
|
||||||
},
|
|
||||||
|
|
||||||
setupController: function(controller, model) {
|
|
||||||
this._super(controller, model);
|
|
||||||
controller.set('hideCategories', true);
|
|
||||||
this.controllerFor('userActivity').set('pmView', 'sent');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Discourse.UserActivityTopicsRoute = Discourse.UserTopicListRoute.extend({
|
Discourse.UserActivityTopicsRoute = Discourse.UserTopicListRoute.extend({
|
||||||
userActionType: Discourse.UserAction.TYPES.topics,
|
userActionType: Discourse.UserAction.TYPES.topics,
|
||||||
@ -205,7 +198,6 @@ Discourse.UserActivityTopicsRoute = Discourse.UserTopicListRoute.extend({
|
|||||||
model: function() {
|
model: function() {
|
||||||
return Discourse.TopicList.find('topics/created-by/' + this.modelFor('user').get('username_lower'));
|
return Discourse.TopicList.find('topics/created-by/' + this.modelFor('user').get('username_lower'));
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Discourse.UserActivityFavoritesRoute = Discourse.UserTopicListRoute.extend({
|
Discourse.UserActivityFavoritesRoute = Discourse.UserTopicListRoute.extend({
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" id="avatar" name="avatar" value="gravatar" {{action toggleUseUploadedAvatar false}}>
|
<input type="radio" id="avatar" name="avatar" value="gravatar" {{action useGravatar}}>
|
||||||
<label class="radio" for="avatar">{{avatar controller imageSize="large" template="gravatar_template"}} {{{i18n user.change_avatar.gravatar}}} {{email}}</label>
|
<label class="radio" for="avatar">{{avatar controller imageSize="large" template="gravatar_template"}} {{{i18n user.change_avatar.gravatar}}} {{email}}</label>
|
||||||
<a href="//gravatar.com/emails" target="_blank" title="{{i18n user.change_avatar.gravatar_title}}" class="btn"><i class="icon-pencil"></i></a>
|
<a href="//gravatar.com/emails" target="_blank" title="{{i18n user.change_avatar.gravatar_title}}" class="btn"><i class="icon-pencil"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" id="uploaded_avatar" name="avatar" value="uploaded_avatar" {{action toggleUseUploadedAvatar true}}>
|
<input type="radio" id="uploaded_avatar" name="avatar" value="uploaded_avatar" {{action useUploadedAvatar}}>
|
||||||
<label class="radio" for="uploaded_avatar">
|
<label class="radio" for="uploaded_avatar">
|
||||||
{{#if has_uploaded_avatar}}
|
{{#if has_uploaded_avatar}}
|
||||||
{{boundAvatar controller imageSize="large" template="uploaded_avatar_template"}} {{i18n user.change_avatar.uploaded_avatar}}
|
{{boundAvatar controller imageSize="large" template="uploaded_avatar_template"}} {{i18n user.change_avatar.uploaded_avatar}}
|
||||||
|
@ -17,15 +17,25 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<div class='topic-meta-data span2'>
|
<div class='topic-meta-data span2'>
|
||||||
<div {{bindAttr class=":contents byTopicCreator:topic-creator"}}>
|
{{#unless userDeleted}}
|
||||||
<a href='{{unbound usernameUrl}}'>{{avatar this imageSize="large"}}</a>
|
<div {{bindAttr class=":contents byTopicCreator:topic-creator"}}>
|
||||||
<h3 {{bindAttr class="staff new_user"}}><a href='{{unbound usernameUrl}}'>{{breakUp username}}</a></h3>
|
<a href='{{unbound usernameUrl}}'>{{avatar this imageSize="large"}}</a>
|
||||||
{{#if user_title}}<div class="user-title">{{user_title}}</div>{{/if}}
|
<h3 {{bindAttr class="staff new_user"}}><a href='{{unbound usernameUrl}}'>{{breakUp username}}</a></h3>
|
||||||
</div>
|
{{#if user_title}}<div class="user-title">{{user_title}}</div>{{/if}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="contents">
|
||||||
|
<i class="icon icon-trash deleted-user-avatar"></i>
|
||||||
|
<h3 class="deleted-username">{{i18n user.deleted}}</h3>
|
||||||
|
</div>
|
||||||
|
{{/unless}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class='topic-body span14'>
|
<div class='topic-body span14'>
|
||||||
<button {{action selectPost this}} {{bindAttr class=":post-select controller.multiSelect::hidden"}}>{{view.selectText}}</button>
|
<div {{bindAttr class=":select-posts controller.multiSelect::hidden"}}>
|
||||||
|
<button {{action toggledSelectedPostReplies this}} {{bindAttr class="view.canSelectReplies::hidden"}}>{{i18n topic.multi_select.select_replies}}</button>
|
||||||
|
<button {{action toggledSelectedPost this}} class="select-post">{{view.selectPostText}}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div {{bindAttr class="showUserReplyTab:avoid-tab view.repliesShown::bottom-round :contents :regular view.extraClass"}}>
|
<div {{bindAttr class="showUserReplyTab:avoid-tab view.repliesShown::bottom-round :contents :regular view.extraClass"}}>
|
||||||
{{#unless controller.multiSelect}}
|
{{#unless controller.multiSelect}}
|
||||||
|
@ -16,10 +16,13 @@
|
|||||||
<ul class='action-list nav-stacked side-nav'>
|
<ul class='action-list nav-stacked side-nav'>
|
||||||
{{#if privateMessageView}}
|
{{#if privateMessageView}}
|
||||||
<li {{bindAttr class=":noGlyph privateMessagesActive:active"}}>
|
<li {{bindAttr class=":noGlyph privateMessagesActive:active"}}>
|
||||||
{{#linkTo 'userPrivateMessages.index' model}}{{i18n user.private_messages}}{{/linkTo}}
|
{{#linkTo 'userPrivateMessages.index' model}}{{i18n user.messages.all}}{{/linkTo}}
|
||||||
</li>
|
</li>
|
||||||
<li {{bindAttr class=":noGlyph privateMessagesSentActive:active"}}>
|
<li {{bindAttr class=":noGlyph privateMessagesMineActive:active"}}>
|
||||||
{{#linkTo 'userPrivateMessages.sent' model}}{{i18n user.private_messages_sent}}{{/linkTo}}
|
{{#linkTo 'userPrivateMessages.mine' model}}{{i18n user.messages.mine}}{{/linkTo}}
|
||||||
|
</li>
|
||||||
|
<li {{bindAttr class=":noGlyph privateMessagesUnreadActive:active"}}>
|
||||||
|
{{#linkTo 'userPrivateMessages.unread' model}}{{i18n user.messages.unread}}{{/linkTo}}
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{{else}}
|
{{else}}
|
||||||
|
@ -29,17 +29,20 @@ Discourse.PostView = Discourse.GroupedView.extend({
|
|||||||
|
|
||||||
mouseUp: function(e) {
|
mouseUp: function(e) {
|
||||||
if (this.get('controller.multiSelect') && (e.metaKey || e.ctrlKey)) {
|
if (this.get('controller.multiSelect') && (e.metaKey || e.ctrlKey)) {
|
||||||
this.get('controller').selectPost(this.get('post'));
|
this.get('controller').toggledSelectedPost(this.get('post'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
selected: function() {
|
selected: function() {
|
||||||
var selectedPosts = this.get('controller.selectedPosts');
|
return this.get('controller').postSelected(this.get('post'));
|
||||||
if (!selectedPosts) return false;
|
|
||||||
return selectedPosts.contains(this.get('post'));
|
|
||||||
}.property('controller.selectedPostsCount'),
|
}.property('controller.selectedPostsCount'),
|
||||||
|
|
||||||
selectText: function() {
|
canSelectReplies: function() {
|
||||||
|
if (this.get('post.reply_count') === 0) { return false; }
|
||||||
|
return !this.get('selected');
|
||||||
|
}.property('post.reply_count', 'selected'),
|
||||||
|
|
||||||
|
selectPostText: function() {
|
||||||
return this.get('selected') ? I18n.t('topic.multi_select.selected', { count: this.get('controller.selectedPostsCount') }) : I18n.t('topic.multi_select.select');
|
return this.get('selected') ? I18n.t('topic.multi_select.selected', { count: this.get('controller.selectedPostsCount') }) : I18n.t('topic.multi_select.select');
|
||||||
}.property('selected', 'controller.selectedPostsCount'),
|
}.property('selected', 'controller.selectedPostsCount'),
|
||||||
|
|
||||||
|
@ -1,3 +1,22 @@
|
|||||||
|
/*
|
||||||
|
This is a fork of markdown-js with a few changes to support discourse:
|
||||||
|
|
||||||
|
* We have replaced the strong/em handlers because we prefer them only to work on word
|
||||||
|
boundaries.
|
||||||
|
|
||||||
|
* We removed the maraku support as we don't use it.
|
||||||
|
|
||||||
|
* We don't escape the contents of HTML as we prefer to use a whitelist.
|
||||||
|
|
||||||
|
* We fixed a bug where references can be created directly following a list.
|
||||||
|
|
||||||
|
* Fix to blockquote to handle spaces in front and when nested.
|
||||||
|
|
||||||
|
* Note the name BetterMarkdown doesn't mean it's *better* than markdown-js, it refers
|
||||||
|
to it being better than our previous markdown parser!
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
// Released under MIT license
|
// Released under MIT license
|
||||||
// Copyright (c) 2009-2010 Dominic Baggott
|
// Copyright (c) 2009-2010 Dominic Baggott
|
||||||
// Copyright (c) 2009-2010 Ash Berlin
|
// Copyright (c) 2009-2010 Ash Berlin
|
||||||
@ -190,6 +209,35 @@ Markdown.prototype.split_blocks = function splitBlocks( input, startLine ) {
|
|||||||
return blocks;
|
return blocks;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function create_attrs() {
|
||||||
|
if ( !extract_attr( this.tree ) ) {
|
||||||
|
this.tree.splice( 1, 0, {} );
|
||||||
|
}
|
||||||
|
|
||||||
|
var attrs = extract_attr( this.tree );
|
||||||
|
|
||||||
|
// make a references hash if it doesn't exist
|
||||||
|
if ( attrs.references === undefined ) {
|
||||||
|
attrs.references = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_reference(attrs, m) {
|
||||||
|
if ( m[2] && m[2][0] == "<" && m[2][m[2].length-1] == ">" )
|
||||||
|
m[2] = m[2].substring( 1, m[2].length - 1 );
|
||||||
|
|
||||||
|
var ref = attrs.references[ m[1].toLowerCase() ] = {
|
||||||
|
href: m[2]
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( m[4] !== undefined )
|
||||||
|
ref.title = m[4];
|
||||||
|
else if ( m[5] !== undefined )
|
||||||
|
ref.title = m[5];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Markdown#processBlock( block, next ) -> undefined | [ JsonML, ... ]
|
* Markdown#processBlock( block, next ) -> undefined | [ JsonML, ... ]
|
||||||
* - block (String): the block to process
|
* - block (String): the block to process
|
||||||
@ -516,6 +564,7 @@ Markdown.dialects.Gruber = {
|
|||||||
|
|
||||||
// The matcher function
|
// The matcher function
|
||||||
return function( block, next ) {
|
return function( block, next ) {
|
||||||
|
|
||||||
var m = block.match( is_list_re );
|
var m = block.match( is_list_re );
|
||||||
if ( !m ) return undefined;
|
if ( !m ) return undefined;
|
||||||
|
|
||||||
@ -667,6 +716,7 @@ Markdown.dialects.Gruber = {
|
|||||||
})(),
|
})(),
|
||||||
|
|
||||||
blockquote: function blockquote( block, next ) {
|
blockquote: function blockquote( block, next ) {
|
||||||
|
|
||||||
if ( !block.match( /^>/m ) )
|
if ( !block.match( /^>/m ) )
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
||||||
@ -702,7 +752,7 @@ Markdown.dialects.Gruber = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Strip off the leading "> " and re-process as a block.
|
// Strip off the leading "> " and re-process as a block.
|
||||||
var input = block.replace( /^> ?/gm, "" ),
|
var input = block.replace( /^> */gm, "" ),
|
||||||
old_tree = this.tree,
|
old_tree = this.tree,
|
||||||
processedBlock = this.toTree( input, [ "blockquote" ] ),
|
processedBlock = this.toTree( input, [ "blockquote" ] ),
|
||||||
attr = extract_attr( processedBlock );
|
attr = extract_attr( processedBlock );
|
||||||
@ -721,39 +771,18 @@ Markdown.dialects.Gruber = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
referenceDefn: function referenceDefn( block, next) {
|
referenceDefn: function referenceDefn( block, next) {
|
||||||
|
|
||||||
var re = /^\s*\[(.*?)\]:\s*(\S+)(?:\s+(?:(['"])(.*?)\3|\((.*?)\)))?\n?/;
|
var re = /^\s*\[(.*?)\]:\s*(\S+)(?:\s+(?:(['"])(.*?)\3|\((.*?)\)))?\n?/;
|
||||||
// interesting matches are [ , ref_id, url, , title, title ]
|
// interesting matches are [ , ref_id, url, , title, title ]
|
||||||
|
|
||||||
if ( !block.match(re) )
|
if ( !block.match(re) )
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
||||||
// make an attribute node if it doesn't exist
|
var attrs = create_attrs.call(this);
|
||||||
if ( !extract_attr( this.tree ) ) {
|
|
||||||
this.tree.splice( 1, 0, {} );
|
|
||||||
}
|
|
||||||
|
|
||||||
var attrs = extract_attr( this.tree );
|
|
||||||
|
|
||||||
// make a references hash if it doesn't exist
|
|
||||||
if ( attrs.references === undefined ) {
|
|
||||||
attrs.references = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
var b = this.loop_re_over_block(re, block, function( m ) {
|
var b = this.loop_re_over_block(re, block, function( m ) {
|
||||||
|
create_reference(attrs, m);
|
||||||
if ( m[2] && m[2][0] == "<" && m[2][m[2].length-1] == ">" )
|
});
|
||||||
m[2] = m[2].substring( 1, m[2].length - 1 );
|
|
||||||
|
|
||||||
var ref = attrs.references[ m[1].toLowerCase() ] = {
|
|
||||||
href: m[2]
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( m[4] !== undefined )
|
|
||||||
ref.title = m[4];
|
|
||||||
else if ( m[5] !== undefined )
|
|
||||||
ref.title = m[5];
|
|
||||||
|
|
||||||
} );
|
|
||||||
|
|
||||||
if ( b.length )
|
if ( b.length )
|
||||||
next.unshift( mk_block( b, block.trailing ) );
|
next.unshift( mk_block( b, block.trailing ) );
|
||||||
@ -876,6 +905,7 @@ Markdown.dialects.Gruber.inline = {
|
|||||||
"[": function link( text ) {
|
"[": function link( text ) {
|
||||||
|
|
||||||
var orig = String(text);
|
var orig = String(text);
|
||||||
|
|
||||||
// Inline content is possible inside `link text`
|
// Inline content is possible inside `link text`
|
||||||
var res = Markdown.DialectHelpers.inline_until_char.call( this, text.substr(1), "]" );
|
var res = Markdown.DialectHelpers.inline_until_char.call( this, text.substr(1), "]" );
|
||||||
|
|
||||||
@ -939,7 +969,6 @@ Markdown.dialects.Gruber.inline = {
|
|||||||
m = text.match( /^\s*\[(.*?)\]/ );
|
m = text.match( /^\s*\[(.*?)\]/ );
|
||||||
|
|
||||||
if ( m ) {
|
if ( m ) {
|
||||||
|
|
||||||
consumed += m[ 0 ].length;
|
consumed += m[ 0 ].length;
|
||||||
|
|
||||||
// [links][] uses links as its reference
|
// [links][] uses links as its reference
|
||||||
@ -953,6 +982,15 @@ Markdown.dialects.Gruber.inline = {
|
|||||||
return [ consumed, link ];
|
return [ consumed, link ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m = orig.match(/^\s*\[(.*?)\]:\s*(\S+)(?:\s+(?:(['"])(.*?)\3|\((.*?)\)))?\n?/);
|
||||||
|
if (m) {
|
||||||
|
|
||||||
|
var attrs = create_attrs.call(this);
|
||||||
|
create_reference(attrs, m);
|
||||||
|
|
||||||
|
return [ m[0].length ]
|
||||||
|
}
|
||||||
|
|
||||||
// [id]
|
// [id]
|
||||||
// Only if id is plain (no formatting.)
|
// Only if id is plain (no formatting.)
|
||||||
if ( children.length == 1 && typeof children[0] == "string" ) {
|
if ( children.length == 1 && typeof children[0] == "string" ) {
|
||||||
@ -1004,69 +1042,6 @@ Markdown.dialects.Gruber.inline = {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Meta Helper/generator method for em and strong handling
|
|
||||||
function strong_em( tag, md ) {
|
|
||||||
|
|
||||||
var state_slot = tag + "_state",
|
|
||||||
other_slot = tag == "strong" ? "em_state" : "strong_state";
|
|
||||||
|
|
||||||
function CloseTag(len) {
|
|
||||||
this.len_after = len;
|
|
||||||
this.name = "close_" + md;
|
|
||||||
}
|
|
||||||
|
|
||||||
return function ( text, orig_match ) {
|
|
||||||
|
|
||||||
if ( this[state_slot][0] == md ) {
|
|
||||||
// Most recent em is of this type
|
|
||||||
//D:this.debug("closing", md);
|
|
||||||
this[state_slot].shift();
|
|
||||||
|
|
||||||
// "Consume" everything to go back to the recrusion in the else-block below
|
|
||||||
return[ text.length, new CloseTag(text.length-md.length) ];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Store a clone of the em/strong states
|
|
||||||
var other = this[other_slot].slice(),
|
|
||||||
state = this[state_slot].slice();
|
|
||||||
|
|
||||||
this[state_slot].unshift(md);
|
|
||||||
|
|
||||||
//D:this.debug_indent += " ";
|
|
||||||
|
|
||||||
// Recurse
|
|
||||||
var res = this.processInline( text.substr( md.length ) );
|
|
||||||
//D:this.debug_indent = this.debug_indent.substr(2);
|
|
||||||
|
|
||||||
var last = res[res.length - 1];
|
|
||||||
|
|
||||||
//D:this.debug("processInline from", tag + ": ", uneval( res ) );
|
|
||||||
|
|
||||||
var check = this[state_slot].shift();
|
|
||||||
if ( last instanceof CloseTag ) {
|
|
||||||
res.pop();
|
|
||||||
// We matched! Huzzah.
|
|
||||||
var consumed = text.length - last.len_after;
|
|
||||||
return [ consumed, [ tag ].concat(res) ];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Restore the state of the other kind. We might have mistakenly closed it.
|
|
||||||
this[other_slot] = other;
|
|
||||||
this[state_slot] = state;
|
|
||||||
|
|
||||||
// We can't reuse the processed result as it could have wrong parsing contexts in it.
|
|
||||||
return [ md.length, md ];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}; // End returned function
|
|
||||||
}
|
|
||||||
|
|
||||||
Markdown.dialects.Gruber.inline["**"] = strong_em("strong", "**");
|
|
||||||
Markdown.dialects.Gruber.inline["__"] = strong_em("strong", "__");
|
|
||||||
Markdown.dialects.Gruber.inline["*"] = strong_em("em", "*");
|
|
||||||
Markdown.dialects.Gruber.inline["_"] = strong_em("em", "_");
|
|
||||||
|
|
||||||
|
|
||||||
// Build default order from insertion order.
|
// Build default order from insertion order.
|
||||||
Markdown.buildBlockOrder = function(d) {
|
Markdown.buildBlockOrder = function(d) {
|
||||||
var ord = [];
|
var ord = [];
|
||||||
@ -1084,7 +1059,7 @@ Markdown.buildInlinePatterns = function(d) {
|
|||||||
for ( var i in d ) {
|
for ( var i in d ) {
|
||||||
// __foo__ is reserved and not a pattern
|
// __foo__ is reserved and not a pattern
|
||||||
if ( i.match( /^__.*__$/) ) continue;
|
if ( i.match( /^__.*__$/) ) continue;
|
||||||
var l = i.replace( /([\\.*+?|()\[\]{}])/g, "\\$1" )
|
var l = i.replace( /([\\.*+?$|()\[\]{}])/g, "\\$1" )
|
||||||
.replace( /\n/, "\\n" );
|
.replace( /\n/, "\\n" );
|
||||||
patterns.push( i.length == 1 ? l : "(?:" + l + ")" );
|
patterns.push( i.length == 1 ? l : "(?:" + l + ")" );
|
||||||
}
|
}
|
@ -355,6 +355,10 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
|
.deleted-user-avatar {
|
||||||
|
font-size: 36px;
|
||||||
|
line-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
.staff a {
|
.staff a {
|
||||||
@include border-radius-all(3px);
|
@include border-radius-all(3px);
|
||||||
@ -496,9 +500,11 @@
|
|||||||
}
|
}
|
||||||
&.selected {
|
&.selected {
|
||||||
article.boxed {
|
article.boxed {
|
||||||
.post-select {
|
.select-posts {
|
||||||
background-color: $blue;
|
button.select-post {
|
||||||
color: $white;
|
background-color: $blue;
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.topic-body {
|
.topic-body {
|
||||||
.contents {
|
.contents {
|
||||||
@ -515,20 +521,23 @@
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
|
||||||
.post-select {
|
.select-posts {
|
||||||
@include border-radius-all(4px);
|
|
||||||
background-color: $light_gray;
|
|
||||||
border-top: 1px solid $white;
|
|
||||||
border-left: 1px solid $white;
|
|
||||||
border-bottom: 1px solid $gray;
|
|
||||||
border-right: 1px solid $gray;
|
|
||||||
color: $darkish_gray;
|
|
||||||
top: 4px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 5px;
|
right: 5px;
|
||||||
font-size: 12px;
|
|
||||||
padding: 2px 5px;
|
|
||||||
z-index: 490;
|
z-index: 490;
|
||||||
|
top: 4px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
@include border-radius-all(4px);
|
||||||
|
background-color: $light_gray;
|
||||||
|
border-top: 1px solid $white;
|
||||||
|
border-left: 1px solid $white;
|
||||||
|
border-bottom: 1px solid $gray;
|
||||||
|
border-right: 1px solid $gray;
|
||||||
|
color: $darkish_gray;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
@ -196,6 +196,16 @@ class ApplicationController < ActionController::Base
|
|||||||
user
|
user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def post_ids_including_replies
|
||||||
|
post_ids = params[:post_ids].map {|p| p.to_i}
|
||||||
|
if params[:reply_post_ids]
|
||||||
|
post_ids << PostReply.where(post_id: params[:reply_post_ids].map {|p| p.to_i}).pluck(:reply_id)
|
||||||
|
post_ids.flatten!
|
||||||
|
post_ids.uniq!
|
||||||
|
end
|
||||||
|
post_ids
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def preload_anonymous_data
|
def preload_anonymous_data
|
||||||
|
@ -53,6 +53,14 @@ class ListController < ApplicationController
|
|||||||
respond(list)
|
respond(list)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def private_messages_unread
|
||||||
|
list_opts = build_topic_list_options
|
||||||
|
list = TopicQuery.new(current_user, list_opts).list_private_messages_unread(fetch_user_from_params)
|
||||||
|
list.more_topics_url = url_for(topics_private_messages_unread_path(list_opts.merge(format: 'json', page: next_page)))
|
||||||
|
|
||||||
|
respond(list)
|
||||||
|
end
|
||||||
|
|
||||||
def category
|
def category
|
||||||
query = TopicQuery.new(current_user, page: params[:page])
|
query = TopicQuery.new(current_user, page: params[:page])
|
||||||
|
|
||||||
|
@ -150,16 +150,16 @@ class PostsController < ApplicationController
|
|||||||
|
|
||||||
params.require(:post_ids)
|
params.require(:post_ids)
|
||||||
|
|
||||||
posts = Post.where(id: params[:post_ids])
|
posts = Post.where(id: post_ids_including_replies)
|
||||||
raise Discourse::InvalidParameters.new(:post_ids) if posts.blank?
|
raise Discourse::InvalidParameters.new(:post_ids) if posts.blank?
|
||||||
|
|
||||||
# Make sure we can delete the posts
|
# Make sure we can delete the posts
|
||||||
|
|
||||||
posts.each {|p| guardian.ensure_can_delete!(p) }
|
posts.each {|p| guardian.ensure_can_delete!(p) }
|
||||||
|
|
||||||
Post.transaction do
|
Post.transaction do
|
||||||
topic_id = posts.first.topic_id
|
topic_id = posts.first.topic_id
|
||||||
posts.each {|p| p.destroy }
|
posts.each {|p| PostDestroyer.new(current_user, p).destroy }
|
||||||
Topic.reset_highest(topic_id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
render nothing: true
|
render nothing: true
|
||||||
|
@ -244,7 +244,7 @@ class TopicsController < ApplicationController
|
|||||||
topic = Topic.where(id: params[:topic_id]).first
|
topic = Topic.where(id: params[:topic_id]).first
|
||||||
guardian.ensure_can_move_posts!(topic)
|
guardian.ensure_can_move_posts!(topic)
|
||||||
|
|
||||||
dest_topic = move_post_to_destination(topic)
|
dest_topic = move_posts_to_destination(topic)
|
||||||
render_topic_changes(dest_topic)
|
render_topic_changes(dest_topic)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -333,12 +333,12 @@ class TopicsController < ApplicationController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def move_post_to_destination(topic)
|
def move_posts_to_destination(topic)
|
||||||
args = {}
|
args = {}
|
||||||
args[:title] = params[:title] if params[:title].present?
|
args[:title] = params[:title] if params[:title].present?
|
||||||
args[:destination_topic_id] = params[:destination_topic_id].to_i if params[:destination_topic_id].present?
|
args[:destination_topic_id] = params[:destination_topic_id].to_i if params[:destination_topic_id].present?
|
||||||
|
|
||||||
topic.move_posts(current_user, params[:post_ids].map {|p| p.to_i}, args)
|
topic.move_posts(current_user, post_ids_including_replies, args)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -11,7 +11,8 @@ class Users::OmniauthCallbacksController < ApplicationController
|
|||||||
Auth::OpenIdAuthenticator.new("yahoo", "https://me.yahoo.com", trusted: true),
|
Auth::OpenIdAuthenticator.new("yahoo", "https://me.yahoo.com", trusted: true),
|
||||||
Auth::GithubAuthenticator.new,
|
Auth::GithubAuthenticator.new,
|
||||||
Auth::TwitterAuthenticator.new,
|
Auth::TwitterAuthenticator.new,
|
||||||
Auth::PersonaAuthenticator.new
|
Auth::PersonaAuthenticator.new,
|
||||||
|
Auth::CasAuthenticator.new
|
||||||
]
|
]
|
||||||
|
|
||||||
skip_before_filter :redirect_to_login_if_required
|
skip_before_filter :redirect_to_login_if_required
|
||||||
@ -37,9 +38,13 @@ class Users::OmniauthCallbacksController < ApplicationController
|
|||||||
@data = authenticator.after_authenticate(auth)
|
@data = authenticator.after_authenticate(auth)
|
||||||
@data.authenticator_name = authenticator.name
|
@data.authenticator_name = authenticator.name
|
||||||
|
|
||||||
user_found(@data.user) if @data.user
|
if @data.user
|
||||||
|
user_found(@data.user)
|
||||||
session[:authentication] = @data.session_data
|
elsif SiteSetting.invite_only?
|
||||||
|
@data.requires_invite = true
|
||||||
|
else
|
||||||
|
session[:authentication] = @data.session_data
|
||||||
|
end
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html
|
format.html
|
||||||
@ -87,7 +92,7 @@ class Users::OmniauthCallbacksController < ApplicationController
|
|||||||
session[:authentication] = nil
|
session[:authentication] = nil
|
||||||
@data.authenticated = true
|
@data.authenticated = true
|
||||||
else
|
else
|
||||||
if SiteSetting.invite_only?
|
if SiteSetting.must_approve_users? && !user.approved?
|
||||||
@data.awaiting_approval = true
|
@data.awaiting_approval = true
|
||||||
else
|
else
|
||||||
@data.awaiting_activation = true
|
@data.awaiting_activation = true
|
||||||
|
@ -18,12 +18,7 @@ class CategoryFeaturedTopic < ActiveRecord::Base
|
|||||||
CategoryFeaturedTopic.transaction do
|
CategoryFeaturedTopic.transaction do
|
||||||
CategoryFeaturedTopic.delete_all(category_id: c.id)
|
CategoryFeaturedTopic.delete_all(category_id: c.id)
|
||||||
|
|
||||||
# fake an admin
|
query = TopicQuery.new(self.fake_admin, per_page: SiteSetting.category_featured_topics, except_topic_id: c.topic_id, visible: true)
|
||||||
admin = User.new
|
|
||||||
admin.admin = true
|
|
||||||
admin.id = -1
|
|
||||||
|
|
||||||
query = TopicQuery.new(admin, per_page: SiteSetting.category_featured_topics, except_topic_id: c.topic_id, visible: true)
|
|
||||||
results = query.list_category(c)
|
results = query.list_category(c)
|
||||||
if results.present?
|
if results.present?
|
||||||
results.topic_ids.each_with_index do |topic_id, idx|
|
results.topic_ids.each_with_index do |topic_id, idx|
|
||||||
@ -33,6 +28,15 @@ class CategoryFeaturedTopic < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def self.fake_admin
|
||||||
|
# fake an admin
|
||||||
|
admin = User.new
|
||||||
|
admin.admin = true
|
||||||
|
admin.id = -1
|
||||||
|
admin
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
|
@ -8,6 +8,24 @@ InviteRedeemer = Struct.new(:invite) do
|
|||||||
invited_user
|
invited_user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# extracted from User cause it is very specific to invites
|
||||||
|
def self.create_user_for_email(email)
|
||||||
|
username = UserNameSuggester.suggest(email)
|
||||||
|
|
||||||
|
DiscourseHub.nickname_operation do
|
||||||
|
match, available, suggestion = DiscourseHub.nickname_match?(username, email)
|
||||||
|
username = suggestion unless match || available
|
||||||
|
end
|
||||||
|
|
||||||
|
user = User.new(email: email, username: username, name: username, active: true)
|
||||||
|
user.trust_level = SiteSetting.default_invitee_trust_level
|
||||||
|
user.save!
|
||||||
|
|
||||||
|
DiscourseHub.nickname_operation { DiscourseHub.register_nickname(username, email) }
|
||||||
|
|
||||||
|
user
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def invited_user
|
def invited_user
|
||||||
@ -34,7 +52,7 @@ InviteRedeemer = Struct.new(:invite) do
|
|||||||
|
|
||||||
def get_invited_user
|
def get_invited_user
|
||||||
result = get_existing_user
|
result = get_existing_user
|
||||||
result ||= create_new_user
|
result ||= InviteRedeemer.create_user_for_email(invite.email)
|
||||||
result.send_welcome_message = false
|
result.send_welcome_message = false
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
@ -43,9 +61,6 @@ InviteRedeemer = Struct.new(:invite) do
|
|||||||
User.where(email: invite.email).first
|
User.where(email: invite.email).first
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_new_user
|
|
||||||
User.create_for_email(invite.email, trust_level: SiteSetting.default_invitee_trust_level)
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_to_private_topics_if_invited
|
def add_to_private_topics_if_invited
|
||||||
invite.topics.private_messages.each do |t|
|
invite.topics.private_messages.each do |t|
|
||||||
|
18
app/models/min_trust_to_create_topic_setting.rb
Normal file
18
app/models/min_trust_to_create_topic_setting.rb
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
require_dependency 'enum_site_setting'
|
||||||
|
|
||||||
|
class MinTrustToCreateTopicSetting < EnumSiteSetting
|
||||||
|
|
||||||
|
def self.valid_value?(val)
|
||||||
|
valid_values.any? { |v| v.to_s == val.to_s }
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.values
|
||||||
|
@values ||= valid_values.map {|x| {name: x.to_s, value: x} }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def self.valid_values
|
||||||
|
TrustLevel.levels.values.sort
|
||||||
|
end
|
||||||
|
end
|
@ -2,3 +2,22 @@ class Oauth2UserInfo < ActiveRecord::Base
|
|||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: oauth2_user_infos
|
||||||
|
#
|
||||||
|
# id :integer not null, primary key
|
||||||
|
# user_id :integer not null
|
||||||
|
# uid :string(255) not null
|
||||||
|
# provider :string(255) not null
|
||||||
|
# email :string(255)
|
||||||
|
# name :string(255)
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_oauth2_user_infos_on_uid_and_provider (uid,provider) UNIQUE
|
||||||
|
#
|
||||||
|
|
||||||
|
@ -1,2 +1,18 @@
|
|||||||
class PluginStoreRow < ActiveRecord::Base
|
class PluginStoreRow < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: plugin_store_rows
|
||||||
|
#
|
||||||
|
# id :integer not null, primary key
|
||||||
|
# plugin_name :string(255) not null
|
||||||
|
# key :string(255) not null
|
||||||
|
# type_name :string(255) not null
|
||||||
|
# value :text
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_plugin_store_rows_on_plugin_name_and_key (plugin_name,key) UNIQUE
|
||||||
|
#
|
||||||
|
|
||||||
|
@ -45,7 +45,6 @@ class Post < ActiveRecord::Base
|
|||||||
scope :public_posts, -> { joins(:topic).where('topics.archetype <> ?', Archetype.private_message) }
|
scope :public_posts, -> { joins(:topic).where('topics.archetype <> ?', Archetype.private_message) }
|
||||||
scope :private_posts, -> { joins(:topic).where('topics.archetype = ?', Archetype.private_message) }
|
scope :private_posts, -> { joins(:topic).where('topics.archetype = ?', Archetype.private_message) }
|
||||||
scope :with_topic_subtype, ->(subtype) { joins(:topic).where('topics.subtype = ?', subtype) }
|
scope :with_topic_subtype, ->(subtype) { joins(:topic).where('topics.subtype = ?', subtype) }
|
||||||
scope :without_nuked_users, -> { where(nuked_user: false) }
|
|
||||||
|
|
||||||
def self.hidden_reasons
|
def self.hidden_reasons
|
||||||
@hidden_reasons ||= Enum.new(:flag_threshold_reached, :flag_threshold_reached_again, :new_user_spam_threshold_reached)
|
@hidden_reasons ||= Enum.new(:flag_threshold_reached, :flag_threshold_reached_again, :new_user_spam_threshold_reached)
|
||||||
@ -383,7 +382,7 @@ end
|
|||||||
# Table name: posts
|
# Table name: posts
|
||||||
#
|
#
|
||||||
# id :integer not null, primary key
|
# id :integer not null, primary key
|
||||||
# user_id :integer not null
|
# user_id :integer
|
||||||
# topic_id :integer not null
|
# topic_id :integer not null
|
||||||
# post_number :integer not null
|
# post_number :integer not null
|
||||||
# raw :text not null
|
# raw :text not null
|
||||||
@ -419,7 +418,6 @@ end
|
|||||||
# notify_user_count :integer default(0), not null
|
# notify_user_count :integer default(0), not null
|
||||||
# like_score :integer default(0), not null
|
# like_score :integer default(0), not null
|
||||||
# deleted_by_id :integer
|
# deleted_by_id :integer
|
||||||
# nuked_user :boolean default(FALSE)
|
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
|
@ -23,3 +23,23 @@ class ScreenedEmail < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: screened_emails
|
||||||
|
#
|
||||||
|
# id :integer not null, primary key
|
||||||
|
# email :string(255) not null
|
||||||
|
# action_type :integer not null
|
||||||
|
# match_count :integer default(0), not null
|
||||||
|
# last_match_at :datetime
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# ip_address :string
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_blocked_emails_on_email (email) UNIQUE
|
||||||
|
# index_blocked_emails_on_last_match_at (last_match_at)
|
||||||
|
#
|
||||||
|
|
||||||
|
@ -24,3 +24,24 @@ class ScreenedUrl < ActiveRecord::Base
|
|||||||
find_by_url(url) || create(opts.slice(:action_type, :ip_address).merge(url: url, domain: domain))
|
find_by_url(url) || create(opts.slice(:action_type, :ip_address).merge(url: url, domain: domain))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: screened_urls
|
||||||
|
#
|
||||||
|
# id :integer not null, primary key
|
||||||
|
# url :string(255) not null
|
||||||
|
# domain :string(255) not null
|
||||||
|
# action_type :integer not null
|
||||||
|
# match_count :integer default(0), not null
|
||||||
|
# last_match_at :datetime
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# ip_address :string
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_screened_urls_on_last_match_at (last_match_at)
|
||||||
|
# index_screened_urls_on_url (url) UNIQUE
|
||||||
|
#
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ class SiteSetting < ActiveRecord::Base
|
|||||||
|
|
||||||
setting(:num_flags_to_block_new_user, 3)
|
setting(:num_flags_to_block_new_user, 3)
|
||||||
setting(:num_users_to_block_new_user, 3)
|
setting(:num_users_to_block_new_user, 3)
|
||||||
setting(:notify_mods_when_user_blocked, true)
|
setting(:notify_mods_when_user_blocked, false)
|
||||||
|
|
||||||
# used mainly for dev, force hostname for Discourse.base_url
|
# used mainly for dev, force hostname for Discourse.base_url
|
||||||
# You would usually use multisite for this
|
# You would usually use multisite for this
|
||||||
@ -205,6 +205,8 @@ class SiteSetting < ActiveRecord::Base
|
|||||||
setting(:regular_requires_likes_given, 1)
|
setting(:regular_requires_likes_given, 1)
|
||||||
setting(:regular_requires_topic_reply_count, 3)
|
setting(:regular_requires_topic_reply_count, 3)
|
||||||
|
|
||||||
|
setting(:min_trust_to_create_topic, 0, enum: 'MinTrustToCreateTopicSetting')
|
||||||
|
|
||||||
# Reply by Email Settings
|
# Reply by Email Settings
|
||||||
setting(:reply_by_email_enabled, false)
|
setting(:reply_by_email_enabled, false)
|
||||||
setting(:reply_by_email_address, '')
|
setting(:reply_by_email_address, '')
|
||||||
|
@ -53,11 +53,15 @@ end
|
|||||||
# context :string(255)
|
# context :string(255)
|
||||||
# ip_address :string(255)
|
# ip_address :string(255)
|
||||||
# email :string(255)
|
# email :string(255)
|
||||||
|
# subject :text
|
||||||
|
# previous_value :text
|
||||||
|
# new_value :text
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_staff_action_logs_on_action_and_id (action,id)
|
# index_staff_action_logs_on_action_and_id (action,id)
|
||||||
# index_staff_action_logs_on_staff_user_id_and_id (staff_user_id,id)
|
# index_staff_action_logs_on_staff_user_id_and_id (staff_user_id,id)
|
||||||
|
# index_staff_action_logs_on_subject_and_id (subject,id)
|
||||||
# index_staff_action_logs_on_target_user_id_and_id (target_user_id,id)
|
# index_staff_action_logs_on_target_user_id_and_id (target_user_id,id)
|
||||||
#
|
#
|
||||||
|
|
||||||
|
@ -10,6 +10,16 @@ class Topic < ActiveRecord::Base
|
|||||||
include ActionView::Helpers::SanitizeHelper
|
include ActionView::Helpers::SanitizeHelper
|
||||||
include RateLimiter::OnCreateRecord
|
include RateLimiter::OnCreateRecord
|
||||||
include Trashable
|
include Trashable
|
||||||
|
extend Forwardable
|
||||||
|
|
||||||
|
def_delegator :featured_users, :user_ids, :featured_user_ids
|
||||||
|
def_delegator :featured_users, :choose, :feature_topic_users
|
||||||
|
|
||||||
|
def_delegator :notifier, :watch!, :notify_watch!
|
||||||
|
def_delegator :notifier, :tracking!, :notify_tracking!
|
||||||
|
def_delegator :notifier, :regular!, :notify_regular!
|
||||||
|
def_delegator :notifier, :muted!, :notify_muted!
|
||||||
|
def_delegator :notifier, :toggle_mute, :toggle_mute
|
||||||
|
|
||||||
def self.max_sort_order
|
def self.max_sort_order
|
||||||
2**31 - 1
|
2**31 - 1
|
||||||
@ -21,14 +31,6 @@ class Topic < ActiveRecord::Base
|
|||||||
@featured_users ||= TopicFeaturedUsers.new(self)
|
@featured_users ||= TopicFeaturedUsers.new(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
def featured_user_ids
|
|
||||||
featured_users.user_ids
|
|
||||||
end
|
|
||||||
|
|
||||||
def feature_topic_users(args={})
|
|
||||||
featured_users.choose(args)
|
|
||||||
end
|
|
||||||
|
|
||||||
def trash!(trashed_by=nil)
|
def trash!(trashed_by=nil)
|
||||||
update_category_topic_count_by(-1) if deleted_at.nil?
|
update_category_topic_count_by(-1) if deleted_at.nil?
|
||||||
super(trashed_by)
|
super(trashed_by)
|
||||||
@ -561,34 +563,12 @@ class Topic < ActiveRecord::Base
|
|||||||
@topic_notifier ||= TopicNotifier.new(self)
|
@topic_notifier ||= TopicNotifier.new(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
# notification stuff
|
|
||||||
def notify_watch!(user)
|
|
||||||
notifier.watch! user
|
|
||||||
end
|
|
||||||
|
|
||||||
def notify_tracking!(user)
|
|
||||||
notifier.tracking! user
|
|
||||||
end
|
|
||||||
|
|
||||||
def notify_regular!(user)
|
|
||||||
notifier.regular! user
|
|
||||||
end
|
|
||||||
|
|
||||||
def notify_muted!(user)
|
|
||||||
notifier.muted! user
|
|
||||||
end
|
|
||||||
|
|
||||||
def muted?(user)
|
def muted?(user)
|
||||||
if user && user.id
|
if user && user.id
|
||||||
notifier.muted?(user.id)
|
notifier.muted?(user.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Enable/disable the mute on the topic
|
|
||||||
def toggle_mute(user_id)
|
|
||||||
notifier.toggle_mute user_id
|
|
||||||
end
|
|
||||||
|
|
||||||
def auto_close_days=(num_days)
|
def auto_close_days=(num_days)
|
||||||
@ignore_category_auto_close = true
|
@ignore_category_auto_close = true
|
||||||
set_auto_close(num_days)
|
set_auto_close(num_days)
|
||||||
|
@ -204,7 +204,7 @@ end
|
|||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_forum_thread_links_on_forum_thread_id (topic_id)
|
# index_forum_thread_links_on_forum_thread_id (topic_id)
|
||||||
# index_forum_thread_links_on_forum_thread_id_and_post_id_and_url (topic_id,post_id,url) UNIQUE
|
# unique_post_links (topic_id,post_id,url) UNIQUE
|
||||||
#
|
#
|
||||||
|
|
||||||
|
@ -53,6 +53,6 @@ end
|
|||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_forum_thread_link_clicks_on_forum_thread_link_id (topic_link_id)
|
# by_link (topic_link_id)
|
||||||
#
|
#
|
||||||
|
|
||||||
|
@ -13,22 +13,22 @@ class User < ActiveRecord::Base
|
|||||||
include Roleable
|
include Roleable
|
||||||
|
|
||||||
has_many :posts
|
has_many :posts
|
||||||
has_many :notifications
|
has_many :notifications, dependent: :destroy
|
||||||
has_many :topic_users
|
has_many :topic_users, dependent: :destroy
|
||||||
has_many :topics
|
has_many :topics
|
||||||
has_many :user_open_ids, dependent: :destroy
|
has_many :user_open_ids, dependent: :destroy
|
||||||
has_many :user_actions
|
has_many :user_actions, dependent: :destroy
|
||||||
has_many :post_actions
|
has_many :post_actions, dependent: :destroy
|
||||||
has_many :email_logs
|
has_many :email_logs, dependent: :destroy
|
||||||
has_many :post_timings
|
has_many :post_timings
|
||||||
has_many :topic_allowed_users
|
has_many :topic_allowed_users, dependent: :destroy
|
||||||
has_many :topics_allowed, through: :topic_allowed_users, source: :topic
|
has_many :topics_allowed, through: :topic_allowed_users, source: :topic
|
||||||
has_many :email_tokens
|
has_many :email_tokens, dependent: :destroy
|
||||||
has_many :views
|
has_many :views
|
||||||
has_many :user_visits
|
has_many :user_visits, dependent: :destroy
|
||||||
has_many :invites
|
has_many :invites, dependent: :destroy
|
||||||
has_many :topic_links
|
has_many :topic_links, dependent: :destroy
|
||||||
has_many :uploads
|
has_many :uploads, dependent: :destroy
|
||||||
|
|
||||||
has_one :facebook_user_info, dependent: :destroy
|
has_one :facebook_user_info, dependent: :destroy
|
||||||
has_one :twitter_user_info, dependent: :destroy
|
has_one :twitter_user_info, dependent: :destroy
|
||||||
@ -37,11 +37,11 @@ class User < ActiveRecord::Base
|
|||||||
has_one :oauth2_user_info, dependent: :destroy
|
has_one :oauth2_user_info, dependent: :destroy
|
||||||
belongs_to :approved_by, class_name: 'User'
|
belongs_to :approved_by, class_name: 'User'
|
||||||
|
|
||||||
has_many :group_users
|
has_many :group_users, dependent: :destroy
|
||||||
has_many :groups, through: :group_users
|
has_many :groups, through: :group_users
|
||||||
has_many :secure_categories, through: :groups, source: :categories
|
has_many :secure_categories, through: :groups, source: :categories
|
||||||
|
|
||||||
has_one :user_search_data
|
has_one :user_search_data, dependent: :destroy
|
||||||
|
|
||||||
belongs_to :uploaded_avatar, class_name: 'Upload', dependent: :destroy
|
belongs_to :uploaded_avatar, class_name: 'Upload', dependent: :destroy
|
||||||
|
|
||||||
@ -61,6 +61,12 @@ class User < ActiveRecord::Base
|
|||||||
|
|
||||||
after_create :create_email_token
|
after_create :create_email_token
|
||||||
|
|
||||||
|
before_destroy do
|
||||||
|
# These tables don't have primary keys, so destroying them with activerecord is tricky:
|
||||||
|
PostTiming.delete_all(user_id: self.id)
|
||||||
|
View.delete_all(user_id: self.id)
|
||||||
|
end
|
||||||
|
|
||||||
# Whether we need to be sending a system message after creation
|
# Whether we need to be sending a system message after creation
|
||||||
attr_accessor :send_welcome_message
|
attr_accessor :send_welcome_message
|
||||||
|
|
||||||
@ -96,23 +102,6 @@ class User < ActiveRecord::Base
|
|||||||
user
|
user
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.create_for_email(email, opts={})
|
|
||||||
username = UserNameSuggester.suggest(email)
|
|
||||||
|
|
||||||
discourse_hub_nickname_operation do
|
|
||||||
match, available, suggestion = DiscourseHub.nickname_match?(username, email)
|
|
||||||
username = suggestion unless match || available
|
|
||||||
end
|
|
||||||
|
|
||||||
user = User.new(email: email, username: username, name: username)
|
|
||||||
user.trust_level = opts[:trust_level] if opts[:trust_level].present?
|
|
||||||
user.save!
|
|
||||||
|
|
||||||
discourse_hub_nickname_operation { DiscourseHub.register_nickname(username, email) }
|
|
||||||
|
|
||||||
user
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.suggest_name(email)
|
def self.suggest_name(email)
|
||||||
return "" unless email
|
return "" unless email
|
||||||
name = email.split(/[@\+]/)[0]
|
name = email.split(/[@\+]/)[0]
|
||||||
@ -154,7 +143,7 @@ class User < ActiveRecord::Base
|
|||||||
self.username = new_username
|
self.username = new_username
|
||||||
|
|
||||||
if current_username.downcase != new_username.downcase && valid?
|
if current_username.downcase != new_username.downcase && valid?
|
||||||
User.discourse_hub_nickname_operation { DiscourseHub.change_nickname(current_username, new_username) }
|
DiscourseHub.nickname_operation { DiscourseHub.change_nickname(current_username, new_username) }
|
||||||
end
|
end
|
||||||
|
|
||||||
save
|
save
|
||||||
@ -612,17 +601,6 @@ class User < ActiveRecord::Base
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def self.discourse_hub_nickname_operation
|
|
||||||
if SiteSetting.call_discourse_hub?
|
|
||||||
begin
|
|
||||||
yield
|
|
||||||
rescue DiscourseHub::NicknameUnavailable
|
|
||||||
false
|
|
||||||
rescue => e
|
|
||||||
Rails.logger.error e.message + "\n" + e.backtrace.join("\n")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
@ -647,7 +625,7 @@ end
|
|||||||
# website :string(255)
|
# website :string(255)
|
||||||
# admin :boolean default(FALSE), not null
|
# admin :boolean default(FALSE), not null
|
||||||
# last_emailed_at :datetime
|
# last_emailed_at :datetime
|
||||||
# email_digests :boolean default(TRUE), not null
|
# email_digests :boolean not null
|
||||||
# trust_level :integer not null
|
# trust_level :integer not null
|
||||||
# bio_cooked :text
|
# bio_cooked :text
|
||||||
# email_private_messages :boolean default(TRUE)
|
# email_private_messages :boolean default(TRUE)
|
||||||
@ -657,7 +635,7 @@ end
|
|||||||
# approved_at :datetime
|
# approved_at :datetime
|
||||||
# topics_entered :integer default(0), not null
|
# topics_entered :integer default(0), not null
|
||||||
# posts_read_count :integer default(0), not null
|
# posts_read_count :integer default(0), not null
|
||||||
# digest_after_days :integer default(7), not null
|
# digest_after_days :integer
|
||||||
# previous_visit_at :datetime
|
# previous_visit_at :datetime
|
||||||
# banned_at :datetime
|
# banned_at :datetime
|
||||||
# banned_till :datetime
|
# banned_till :datetime
|
||||||
@ -690,3 +668,4 @@ end
|
|||||||
# index_users_on_username (username) UNIQUE
|
# index_users_on_username (username) UNIQUE
|
||||||
# index_users_on_username_lower (username_lower) UNIQUE
|
# index_users_on_username_lower (username_lower) UNIQUE
|
||||||
#
|
#
|
||||||
|
|
||||||
|
@ -196,10 +196,13 @@ ORDER BY p.created_at desc
|
|||||||
group_ids = topic.category.groups.pluck("groups.id")
|
group_ids = topic.category.groups.pluck("groups.id")
|
||||||
end
|
end
|
||||||
|
|
||||||
MessageBus.publish("/users/#{action.user.username.downcase}",
|
if action.user
|
||||||
action.id,
|
MessageBus.publish("/users/#{action.user.username.downcase}",
|
||||||
user_ids: [user_id],
|
action.id,
|
||||||
group_ids: group_ids )
|
user_ids: [user_id],
|
||||||
|
group_ids: group_ids )
|
||||||
|
end
|
||||||
|
|
||||||
action
|
action
|
||||||
|
|
||||||
rescue ActiveRecord::RecordNotUnique
|
rescue ActiveRecord::RecordNotUnique
|
||||||
|
@ -8,15 +8,15 @@ class BasicPostSerializer < ApplicationSerializer
|
|||||||
:cooked
|
:cooked
|
||||||
|
|
||||||
def name
|
def name
|
||||||
object.user.name
|
object.user.try(:name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def username
|
def username
|
||||||
object.user.username
|
object.user.try(:username)
|
||||||
end
|
end
|
||||||
|
|
||||||
def avatar_template
|
def avatar_template
|
||||||
object.user.avatar_template
|
object.user.try(:avatar_template)
|
||||||
end
|
end
|
||||||
|
|
||||||
def cooked
|
def cooked
|
||||||
|
@ -46,11 +46,11 @@ class PostSerializer < BasicPostSerializer
|
|||||||
|
|
||||||
|
|
||||||
def moderator?
|
def moderator?
|
||||||
object.user.moderator?
|
object.user.try(:moderator?) || false
|
||||||
end
|
end
|
||||||
|
|
||||||
def staff?
|
def staff?
|
||||||
object.user.staff?
|
object.user.try(:staff?) || false
|
||||||
end
|
end
|
||||||
|
|
||||||
def yours
|
def yours
|
||||||
@ -70,7 +70,7 @@ class PostSerializer < BasicPostSerializer
|
|||||||
end
|
end
|
||||||
|
|
||||||
def display_username
|
def display_username
|
||||||
object.user.name
|
object.user.try(:name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def link_counts
|
def link_counts
|
||||||
@ -101,11 +101,11 @@ class PostSerializer < BasicPostSerializer
|
|||||||
end
|
end
|
||||||
|
|
||||||
def user_title
|
def user_title
|
||||||
object.user.title
|
object.user.try(:title)
|
||||||
end
|
end
|
||||||
|
|
||||||
def trust_level
|
def trust_level
|
||||||
object.user.trust_level
|
object.user.try(:trust_level)
|
||||||
end
|
end
|
||||||
|
|
||||||
def reply_to_user
|
def reply_to_user
|
||||||
|
@ -15,15 +15,13 @@ module PostStreamSerializerMixin
|
|||||||
@highest_number_in_posts = 0
|
@highest_number_in_posts = 0
|
||||||
if object.posts.present?
|
if object.posts.present?
|
||||||
object.posts.each_with_index do |p, idx|
|
object.posts.each_with_index do |p, idx|
|
||||||
if p.user
|
@highest_number_in_posts = p.post_number if p.post_number > @highest_number_in_posts
|
||||||
@highest_number_in_posts = p.post_number if p.post_number > @highest_number_in_posts
|
ps = PostSerializer.new(p, scope: scope, root: false)
|
||||||
ps = PostSerializer.new(p, scope: scope, root: false)
|
ps.topic_slug = object.topic.slug
|
||||||
ps.topic_slug = object.topic.slug
|
ps.topic_view = object
|
||||||
ps.topic_view = object
|
p.topic = object.topic
|
||||||
p.topic = object.topic
|
|
||||||
|
|
||||||
@posts << ps.as_json
|
@posts << ps.as_json
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@posts
|
@posts
|
||||||
|
@ -15,12 +15,6 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<%# load the selected locale before any other scripts %>
|
|
||||||
<%= javascript_include_tag "locales/#{I18n.locale}" %>
|
|
||||||
<%= javascript_include_tag "application" %>
|
|
||||||
<%- if staff? %>
|
|
||||||
<%= javascript_include_tag "admin"%>
|
|
||||||
<%- end %>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
Discourse.CDN = '<%= Rails.configuration.action_controller.asset_host %>';
|
Discourse.CDN = '<%= Rails.configuration.action_controller.asset_host %>';
|
||||||
|
@ -13,6 +13,12 @@
|
|||||||
<link rel="apple-touch-icon" type="image/png" href="<%=SiteSetting.apple_touch_icon_url%>">
|
<link rel="apple-touch-icon" type="image/png" href="<%=SiteSetting.apple_touch_icon_url%>">
|
||||||
<%= javascript_include_tag "preload_store" %>
|
<%= javascript_include_tag "preload_store" %>
|
||||||
|
|
||||||
|
<%= javascript_include_tag "locales/#{I18n.locale}" %>
|
||||||
|
<%= javascript_include_tag "application" %>
|
||||||
|
<%- if staff? %>
|
||||||
|
<%= javascript_include_tag "admin"%>
|
||||||
|
<%- end %>
|
||||||
|
|
||||||
<%= render :partial => "common/special_font_face" %>
|
<%= render :partial => "common/special_font_face" %>
|
||||||
<%= render :partial => "common/discourse_stylesheet" %>
|
<%= render :partial => "common/discourse_stylesheet" %>
|
||||||
|
|
||||||
@ -26,24 +32,6 @@
|
|||||||
|
|
||||||
<%=SiteCustomization.custom_header(session[:preview_style])%>
|
<%=SiteCustomization.custom_header(session[:preview_style])%>
|
||||||
<section id='main'>
|
<section id='main'>
|
||||||
<noscript data-path="<%= request.env['PATH_INFO'] %>">
|
|
||||||
<header class="d-header">
|
|
||||||
<div class="container">
|
|
||||||
<div class="contents">
|
|
||||||
<div class="row">
|
|
||||||
<div class="title span13">
|
|
||||||
<a href="/"><img src="<%=SiteSetting.logo_url%>" alt="<%=SiteSetting.title%>" id="site-logo"></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div id="main-outlet" class="container">
|
|
||||||
<!-- preload-content: -->
|
|
||||||
<%= yield %>
|
|
||||||
<!-- :preload-content -->
|
|
||||||
</div>
|
|
||||||
</noscript>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<% unless current_user %>
|
<% unless current_user %>
|
||||||
@ -70,6 +58,24 @@
|
|||||||
<%= render :partial => "common/discourse_javascript" %>
|
<%= render :partial => "common/discourse_javascript" %>
|
||||||
<%= render_google_analytics_code %>
|
<%= render_google_analytics_code %>
|
||||||
|
|
||||||
|
<noscript data-path="<%= request.env['PATH_INFO'] %>">
|
||||||
|
<header class="d-header">
|
||||||
|
<div class="container">
|
||||||
|
<div class="contents">
|
||||||
|
<div class="row">
|
||||||
|
<div class="title span13">
|
||||||
|
<a href="/"><img src="<%=SiteSetting.logo_url%>" alt="<%=SiteSetting.title%>" id="site-logo"></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div id="main-outlet" class="container">
|
||||||
|
<!-- preload-content: -->
|
||||||
|
<%= yield %>
|
||||||
|
<!-- :preload-content -->
|
||||||
|
</div>
|
||||||
|
</noscript>
|
||||||
<!-- Discourse Version: <%= Discourse::VERSION::STRING %> -->
|
<!-- Discourse Version: <%= Discourse::VERSION::STRING %> -->
|
||||||
<!-- Git Version: <%= Discourse.git_version %> -->
|
<!-- Git Version: <%= Discourse.git_version %> -->
|
||||||
</body>
|
</body>
|
||||||
|
@ -66,4 +66,9 @@ Discourse::Application.configure do
|
|||||||
# For origin pull cdns all you need to do is register an account and configure
|
# For origin pull cdns all you need to do is register an account and configure
|
||||||
# config.action_controller.asset_host = "http://YOUR_CDN_HERE"
|
# config.action_controller.asset_host = "http://YOUR_CDN_HERE"
|
||||||
|
|
||||||
|
# a comma delimited list of emails your devs have
|
||||||
|
# developers have god like rights and may impersonate anyone in the system
|
||||||
|
# normal admins may only impersonate other moderators (not admins)
|
||||||
|
config.developer_emails = []
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -4,6 +4,8 @@ Discourse::Application.configure do
|
|||||||
# Code is not reloaded between requests
|
# Code is not reloaded between requests
|
||||||
config.cache_classes = true
|
config.cache_classes = true
|
||||||
|
|
||||||
|
config.log_level = :info
|
||||||
|
|
||||||
# Full error reports are disabled and caching is turned on
|
# Full error reports are disabled and caching is turned on
|
||||||
config.consider_all_requests_local = false
|
config.consider_all_requests_local = false
|
||||||
config.action_controller.perform_caching = true
|
config.action_controller.perform_caching = true
|
||||||
@ -37,7 +39,7 @@ Discourse::Application.configure do
|
|||||||
config.handlebars.precompile = true
|
config.handlebars.precompile = true
|
||||||
|
|
||||||
# this setting enable rack_cache so it caches various requests in redis
|
# this setting enable rack_cache so it caches various requests in redis
|
||||||
# config.enable_rack_cache = true
|
config.enable_rack_cache = false
|
||||||
|
|
||||||
# allows users to use mini profiler
|
# allows users to use mini profiler
|
||||||
config.enable_mini_profiler = false
|
config.enable_mini_profiler = false
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# If Mini Profiler is included via gem
|
# If Mini Profiler is included via gem
|
||||||
if Rails.configuration.respond_to?(:enable_mini_profiler) && Rails.configuration.enable_mini_profiler
|
if Rails.configuration.respond_to?(:enable_mini_profiler) && Rails.configuration.enable_mini_profiler
|
||||||
require 'rack-mini-profiler'
|
require 'rack-mini-profiler'
|
||||||
|
require 'flamegraph'
|
||||||
# initialization is skipped so trigger it
|
# initialization is skipped so trigger it
|
||||||
Rack::MiniProfilerRails.initialize!(Rails.application)
|
Rack::MiniProfilerRails.initialize!(Rails.application)
|
||||||
end
|
end
|
||||||
@ -41,6 +42,9 @@ if defined?(Rack::MiniProfiler)
|
|||||||
Rack::MiniProfiler.config.backtrace_ignores << /config\/initializers\/silence_logger/
|
Rack::MiniProfiler.config.backtrace_ignores << /config\/initializers\/silence_logger/
|
||||||
Rack::MiniProfiler.config.backtrace_ignores << /config\/initializers\/quiet_logger/
|
Rack::MiniProfiler.config.backtrace_ignores << /config\/initializers\/quiet_logger/
|
||||||
|
|
||||||
|
|
||||||
|
# Rack::MiniProfiler.counter_method(ActiveRecord::QueryMethods, 'build_arel')
|
||||||
|
# Rack::MiniProfiler.counter_method(Array, 'uniq')
|
||||||
# require "#{Rails.root}/vendor/backports/notification"
|
# require "#{Rails.root}/vendor/backports/notification"
|
||||||
|
|
||||||
# inst = Class.new
|
# inst = Class.new
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
# We have had lots of config issues with SECRET_TOKEN to avoid this mess we are moving it to redis
|
# We have had lots of config issues with SECRET_TOKEN to avoid this mess we are moving it to redis
|
||||||
# if you feel strongly that it does not belong there use ENV['SECRET_TOKEN']
|
# if you feel strongly that it does not belong there use ENV['SECRET_TOKEN']
|
||||||
#
|
#
|
||||||
token = ENV['SECRET_TOKEN'] || $redis.get('SECRET_TOKEN')
|
token = ENV['SECRET_TOKEN']
|
||||||
unless token && token.length == 128
|
unless token
|
||||||
token = SecureRandom.hex(64)
|
token = $redis.get('SECRET_TOKEN')
|
||||||
$redis.set('SECRET_TOKEN',token)
|
unless token && token.length == 128
|
||||||
|
token = SecureRandom.hex(64)
|
||||||
|
$redis.set('SECRET_TOKEN',token)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Discourse::Application.config.secret_token = token
|
Discourse::Application.config.secret_token = token
|
||||||
|
@ -5,6 +5,11 @@ Sidekiq.configure_server do |config|
|
|||||||
Sidetiq::Clock.start!
|
Sidetiq::Clock.start!
|
||||||
end
|
end
|
||||||
|
|
||||||
Sidekiq.configure_client { |config| config.redis = sidekiq_redis }
|
Sidetiq.configure do |config|
|
||||||
|
# we only check for new jobs once every 5 seconds
|
||||||
|
# to cut down on cpu cost
|
||||||
|
config.resolution = 5
|
||||||
|
end
|
||||||
|
|
||||||
|
Sidekiq.configure_client { |config| config.redis = sidekiq_redis }
|
||||||
Sidekiq.logger.level = Logger::WARN
|
Sidekiq.logger.level = Logger::WARN
|
||||||
|
@ -92,6 +92,7 @@ predef:
|
|||||||
- find
|
- find
|
||||||
- sinon
|
- sinon
|
||||||
- controllerFor
|
- controllerFor
|
||||||
|
- testController
|
||||||
- Favcount
|
- Favcount
|
||||||
|
|
||||||
browser: true # true if the standard browser globals should be predefined
|
browser: true # true if the standard browser globals should be predefined
|
||||||
|
@ -20,9 +20,6 @@ de:
|
|||||||
mb: MB
|
mb: MB
|
||||||
tb: TB
|
tb: TB
|
||||||
dates:
|
dates:
|
||||||
short_date_no_year: "D MMM"
|
|
||||||
short_date: "D. MMM YYYY"
|
|
||||||
long_date: "D. MMMM YYYY, H:mm"
|
|
||||||
tiny:
|
tiny:
|
||||||
half_a_minute: "< 1Min"
|
half_a_minute: "< 1Min"
|
||||||
less_than_x_seconds:
|
less_than_x_seconds:
|
||||||
@ -43,12 +40,6 @@ de:
|
|||||||
x_days:
|
x_days:
|
||||||
one: "1T"
|
one: "1T"
|
||||||
other: "%{count}T"
|
other: "%{count}T"
|
||||||
about_x_months:
|
|
||||||
one: "1Mon"
|
|
||||||
other: "%{count}Mon"
|
|
||||||
x_months:
|
|
||||||
one: "1Mon"
|
|
||||||
other: "%{count}Mon"
|
|
||||||
about_x_years:
|
about_x_years:
|
||||||
one: "1J"
|
one: "1J"
|
||||||
other: "%{count}J"
|
other: "%{count}J"
|
||||||
@ -93,6 +84,7 @@ de:
|
|||||||
yes_value: "Ja"
|
yes_value: "Ja"
|
||||||
of_value: "von"
|
of_value: "von"
|
||||||
generic_error: "Entschuldigung, ein Fehler ist aufgetreten."
|
generic_error: "Entschuldigung, ein Fehler ist aufgetreten."
|
||||||
|
generic_error_with_reason: "Ein Fehler ist aufgetreten: %{error}"
|
||||||
log_in: "Anmelden"
|
log_in: "Anmelden"
|
||||||
age: "Alter"
|
age: "Alter"
|
||||||
last_post: "Letzter Beitrag"
|
last_post: "Letzter Beitrag"
|
||||||
@ -101,10 +93,20 @@ de:
|
|||||||
show_more: "zeige mehr"
|
show_more: "zeige mehr"
|
||||||
links: Links
|
links: Links
|
||||||
faq: "FAQ"
|
faq: "FAQ"
|
||||||
|
privacy_policy: "Datenschutzrichtlinie"
|
||||||
you: "Du"
|
you: "Du"
|
||||||
or: "oder"
|
or: "oder"
|
||||||
now: "gerade eben"
|
now: "gerade eben"
|
||||||
read_more: 'weiterlesen'
|
read_more: 'weiterlesen'
|
||||||
|
more: "Mehr"
|
||||||
|
less: "Weniger"
|
||||||
|
never: "nie"
|
||||||
|
daily: "täglich"
|
||||||
|
weekly: "wöchentlich"
|
||||||
|
every_two_weeks: "jede zweite Woche"
|
||||||
|
character_count:
|
||||||
|
one: "{{count}} Zeichen"
|
||||||
|
other: "{{count}} Zeichen"
|
||||||
|
|
||||||
in_n_seconds:
|
in_n_seconds:
|
||||||
one: "in einer Sekunde"
|
one: "in einer Sekunde"
|
||||||
@ -137,6 +139,10 @@ de:
|
|||||||
saving: "Wird gespeichert..."
|
saving: "Wird gespeichert..."
|
||||||
saved: "Gespeichert!"
|
saved: "Gespeichert!"
|
||||||
|
|
||||||
|
upload: "Hochladen"
|
||||||
|
uploading: "Hochladen..."
|
||||||
|
uploaded: "Hochgeladen!"
|
||||||
|
|
||||||
choose_topic:
|
choose_topic:
|
||||||
none_found: "Keine Themen gefunden."
|
none_found: "Keine Themen gefunden."
|
||||||
title:
|
title:
|
||||||
@ -175,6 +181,7 @@ de:
|
|||||||
"13": "Eingänge"
|
"13": "Eingänge"
|
||||||
|
|
||||||
user:
|
user:
|
||||||
|
said: "{{username}} sagte:"
|
||||||
profile: Profil
|
profile: Profil
|
||||||
title: "Benutzer"
|
title: "Benutzer"
|
||||||
mute: Ignorieren
|
mute: Ignorieren
|
||||||
@ -182,6 +189,7 @@ de:
|
|||||||
download_archive: "Archiv meiner Beiträge herunterladen"
|
download_archive: "Archiv meiner Beiträge herunterladen"
|
||||||
private_message: "Private Nachricht"
|
private_message: "Private Nachricht"
|
||||||
private_messages: "Nachrichten"
|
private_messages: "Nachrichten"
|
||||||
|
private_messages_sent: "Gesendete Nachrichten"
|
||||||
activity_stream: "Aktivität"
|
activity_stream: "Aktivität"
|
||||||
preferences: "Einstellungen"
|
preferences: "Einstellungen"
|
||||||
bio: "Über mich"
|
bio: "Über mich"
|
||||||
@ -191,30 +199,41 @@ de:
|
|||||||
dynamic_favicon: "Zeige eingehende Nachrichten im Favicon"
|
dynamic_favicon: "Zeige eingehende Nachrichten im Favicon"
|
||||||
external_links_in_new_tab: "Öffne alle externen Links in neuen Tabs"
|
external_links_in_new_tab: "Öffne alle externen Links in neuen Tabs"
|
||||||
enable_quoting: "Markierten Text bei Antwort zitieren"
|
enable_quoting: "Markierten Text bei Antwort zitieren"
|
||||||
|
change: "ändern"
|
||||||
moderator: "{{user}} ist Moderator"
|
moderator: "{{user}} ist Moderator"
|
||||||
admin: "{{user}} ist Administrator"
|
admin: "{{user}} ist Administrator"
|
||||||
|
|
||||||
change_password:
|
change_password:
|
||||||
action: "ändern"
|
|
||||||
success: "(Mail gesendet)"
|
success: "(Mail gesendet)"
|
||||||
in_progress: "(sende Mail)"
|
in_progress: "(sende Mail)"
|
||||||
error: "(Fehler)"
|
error: "(Fehler)"
|
||||||
|
action: "Passwort zurücksetzten Mail senden"
|
||||||
|
|
||||||
|
change_about:
|
||||||
|
title: "Über mich ändern"
|
||||||
|
|
||||||
change_username:
|
change_username:
|
||||||
action: "ändern"
|
|
||||||
title: "Benutzername ändern"
|
title: "Benutzername ändern"
|
||||||
confirm: "Den Benutzernamen zu ändern kann Konsequenzen nach sich ziehen. Bist Du sicher, dass du fortfahren willst?"
|
confirm: "Den Benutzernamen zu ändern kann Konsequenzen nach sich ziehen. Bist Du sicher, dass du fortfahren willst?"
|
||||||
taken: "Entschuldige, der Benutzername ist schon vergeben."
|
taken: "Entschuldige, der Benutzername ist schon vergeben."
|
||||||
error: "Beim Ändern des Benutzernamens ist ein Fehler aufgetreten."
|
error: "Beim Ändern des Benutzernamens ist ein Fehler aufgetreten."
|
||||||
invalid: "Dieser Benutzername ist ungültig, sie dürfen nur aus Zahlen und Buchstaben bestehen."
|
invalid: "Dieser Benutzername ist ungültig, sie dürfen nur aus Zahlen und Buchstaben bestehen."
|
||||||
|
|
||||||
change_email:
|
change_email:
|
||||||
action: 'ändern'
|
|
||||||
title: "Mailadresse ändern"
|
title: "Mailadresse ändern"
|
||||||
taken: "Entschuldige, diese Mailadresse ist nicht verfügbar."
|
taken: "Entschuldige, diese Mailadresse ist nicht verfügbar."
|
||||||
error: "Beim ändern der Mailadresse ist ein Fehler aufgetreten. Möglicherweise wird diese Adresse schon benutzt."
|
error: "Beim ändern der Mailadresse ist ein Fehler aufgetreten. Möglicherweise wird diese Adresse schon benutzt."
|
||||||
success: "Eine Bestätigungsmail wurde an diese Adresse verschickt. Bitte folge den darin enthaltenen Anweisungen."
|
success: "Eine Bestätigungsmail wurde an diese Adresse verschickt. Bitte folge den darin enthaltenen Anweisungen."
|
||||||
|
|
||||||
|
change_avatar:
|
||||||
|
title: "Ändere dein Avatar"
|
||||||
|
gravatar: "<a href='//gravatar.com/emails' target='_blank'>Gravatar</a>, basierend auf"
|
||||||
|
gravatar_title: "Wechsle dein Avatar auf der Gravatar Webseite"
|
||||||
|
uploaded_avatar: "Eigenes Bild"
|
||||||
|
uploaded_avatar_empty: "Eigenes Bild hinzufügen"
|
||||||
|
upload_title: "Lade dein Bild hoch"
|
||||||
|
image_is_not_a_square: "Achtung: wir haben den Bild angeschnitten, da es nicht rechteckig war."
|
||||||
|
|
||||||
email:
|
email:
|
||||||
title: "Mail"
|
title: "Mail"
|
||||||
instructions: "Deine Mailadresse wird niemals öffentlich angezeigt."
|
instructions: "Deine Mailadresse wird niemals öffentlich angezeigt."
|
||||||
@ -378,6 +397,7 @@ de:
|
|||||||
authenticating: "Authentisiere..."
|
authenticating: "Authentisiere..."
|
||||||
awaiting_confirmation: 'Dein Konto ist noch nicht aktiviert. Benutze den "Passwort vergesse"-Link um eine neue Aktivierungsmail zu erhalten.'
|
awaiting_confirmation: 'Dein Konto ist noch nicht aktiviert. Benutze den "Passwort vergesse"-Link um eine neue Aktivierungsmail zu erhalten.'
|
||||||
awaiting_approval: "Dein Konto wurde noch nicht von einem Moderator bewilligt. Du bekommst eine Mail, sobald das geschehen ist."
|
awaiting_approval: "Dein Konto wurde noch nicht von einem Moderator bewilligt. Du bekommst eine Mail, sobald das geschehen ist."
|
||||||
|
requires_invite: "Entschuldige, der Zugriff auf dieses Forum ist nur mit einer Einladung erlaubt."
|
||||||
not_activated: "Du kannst Dich noch nicht anmelden. Wir haben Dir kürzlich eine Aktivierungsmail an <b>{{sentTo}}</b> geschickt. Bitte folge den Anweisungen darin, um dein Konto zu aktivieren."
|
not_activated: "Du kannst Dich noch nicht anmelden. Wir haben Dir kürzlich eine Aktivierungsmail an <b>{{sentTo}}</b> geschickt. Bitte folge den Anweisungen darin, um dein Konto zu aktivieren."
|
||||||
resend_activation_email: "Klick hier, um ein neue Aktivierungsmail zu erhalten."
|
resend_activation_email: "Klick hier, um ein neue Aktivierungsmail zu erhalten."
|
||||||
sent_activation_email_again: "Wir haben noch eine Aktivierungsmail an <b>{{currentEmail}}</b> verschickt. Es kann einige Minuten dauern, bis sie ankommt. Im Zweifel schaue auch im Spam-Ordner nach."
|
sent_activation_email_again: "Wir haben noch eine Aktivierungsmail an <b>{{currentEmail}}</b> verschickt. Es kann einige Minuten dauern, bis sie ankommt. Im Zweifel schaue auch im Spam-Ordner nach."
|
||||||
@ -489,16 +509,23 @@ de:
|
|||||||
total_flagged: "total markierte Einträge"
|
total_flagged: "total markierte Einträge"
|
||||||
|
|
||||||
upload_selector:
|
upload_selector:
|
||||||
title: "Bild einfügen"
|
title: "Bild hochladen"
|
||||||
from_my_computer: "von meinem Gerät"
|
title_with_attachments: "Bild oder Datei hochladen"
|
||||||
from_the_web: "aus dem Web"
|
from_my_computer: "Von meinem Gerät"
|
||||||
|
from_the_web: "Aus dem Web"
|
||||||
add_title: "Bild hinzufügen"
|
add_title: "Bild hinzufügen"
|
||||||
|
add_title_with_attachments: "Bild oder Datei hinzufügen"
|
||||||
remote_title: "Entferntes Bild"
|
remote_title: "Entferntes Bild"
|
||||||
|
remote_title_with_attachments: "Entferntes Bild oder Datei"
|
||||||
remote_tip: "Gib die Adresse eines Bildes wie folgt ein: http://example.com/image.jpg"
|
remote_tip: "Gib die Adresse eines Bildes wie folgt ein: http://example.com/image.jpg"
|
||||||
|
remote_tip_with_attachments: "Gib die Adresse eines Bildes oder Datei wie folgt ein http://example.com/file.ext (Erlaubte Dateiendungen: {{authorized_extensions}})."
|
||||||
local_title: "Lokales Bild"
|
local_title: "Lokales Bild"
|
||||||
|
local_title_with_attachments: "Lokales Bild oder Datei"
|
||||||
local_tip: "Klicke hier, um ein Bild von deinem Gerät zu wählen."
|
local_tip: "Klicke hier, um ein Bild von deinem Gerät zu wählen."
|
||||||
upload_title: "Hochladen"
|
local_tip_with_attachments: "Klicke hier, um ein Bild oder eine Datei von deinem Gerät zu wählen (Erlaubte Dateiendungen: {{authorized_extensions}})"
|
||||||
uploading: "Bild wird hochgeladen"
|
upload_title: "Bild hochladen"
|
||||||
|
upload_title_with_attachments: "Bild oder Datei hochladen"
|
||||||
|
uploading: "Hochgeladen..."
|
||||||
|
|
||||||
search:
|
search:
|
||||||
title: "Such nach Themen, Beiträgen, Nutzern oder Kategorien"
|
title: "Such nach Themen, Beiträgen, Nutzern oder Kategorien"
|
||||||
@ -745,10 +772,13 @@ de:
|
|||||||
edit: "Editing {{link}} von {{replyAvatar}} {{username}}"
|
edit: "Editing {{link}} von {{replyAvatar}} {{username}}"
|
||||||
post_number: "Beitrag {{number}}"
|
post_number: "Beitrag {{number}}"
|
||||||
in_reply_to: "Antwort auf"
|
in_reply_to: "Antwort auf"
|
||||||
|
last_edited_on: "Antwort zuletzt bearbeitet am"
|
||||||
reply_as_new_topic: "Mit Themenwechsel antworten"
|
reply_as_new_topic: "Mit Themenwechsel antworten"
|
||||||
continue_discussion: "Fortsetzung des Gesprächs {{postLink}}:"
|
continue_discussion: "Fortsetzung des Gesprächs {{postLink}}:"
|
||||||
follow_quote: "Springe zu zitiertem Beitrag"
|
follow_quote: "Springe zu dem zitiertem Beitrag"
|
||||||
deleted_by_author: "(Beitrag vom Autor entfernt)"
|
deleted_by_author:
|
||||||
|
one: "(Antwort vom Autor zurückgezogen, wird automatisch in %{count} Stunde gelöscht falls nicht gemeldet)"
|
||||||
|
other: "(Antwort vom Autor zurückgezogen, wird automatisch in %{count} Stunden gelöscht falls nicht gemeldet)"
|
||||||
deleted_by: "Entfernt von"
|
deleted_by: "Entfernt von"
|
||||||
expand_collapse: "mehr/weniger"
|
expand_collapse: "mehr/weniger"
|
||||||
|
|
||||||
@ -760,11 +790,11 @@ de:
|
|||||||
create: "Entschuldige, es gab einen Fehler beim Anlegen des Beitrags. Bitte versuche es noch einmal."
|
create: "Entschuldige, es gab einen Fehler beim Anlegen des Beitrags. Bitte versuche es noch einmal."
|
||||||
edit: "Entschuldige, es gab einen Fehler beim Bearbeiten des Beitrags. Bitte versuche es noch einmal."
|
edit: "Entschuldige, es gab einen Fehler beim Bearbeiten des Beitrags. Bitte versuche es noch einmal."
|
||||||
upload: "Entschuldige, es gab einen Fehler beim Hochladen der Datei. Bitte versuche es noch einmal."
|
upload: "Entschuldige, es gab einen Fehler beim Hochladen der Datei. Bitte versuche es noch einmal."
|
||||||
image_too_large: "Entschuldige, das Bild, das du hochladen wolltest, ist zu groß (Maximalgröße {{max_size_kb}}kb), bitte reduziere die Dateigröße und versuche es nochmal."
|
|
||||||
image_upload_not_allowed_for_new_user: "Entschuldige, neue Benutzer dürfen keine Bilder hochladen."
|
|
||||||
attachment_too_large: "Entschuldige, die Datei, die du hochladen wolltest, ist zu groß (Maximalgröße {{max_size_kb}}kb)."
|
attachment_too_large: "Entschuldige, die Datei, die du hochladen wolltest, ist zu groß (Maximalgröße {{max_size_kb}}kb)."
|
||||||
|
image_too_large: "Entschuldige, das Bild, das du hochladen wolltest, ist zu groß (Maximalgröße {{max_size_kb}}kb), bitte reduziere die Dateigröße und versuche es nochmal."
|
||||||
too_many_uploads: "Entschuldige, du darfst immer nur eine Datei hochladen."
|
too_many_uploads: "Entschuldige, du darfst immer nur eine Datei hochladen."
|
||||||
upload_not_authorized: "Entschuldige, die Datei, die du hochladen wolltest, ist nicht erlaubt (erlaubte Endungen: {{authorized_extensions}})."
|
upload_not_authorized: "Entschuldige, die Datei, die du hochladen wolltest, ist nicht erlaubt (erlaubte Endungen: {{authorized_extensions}})."
|
||||||
|
image_upload_not_allowed_for_new_user: "Entschuldige, neue Benutzer dürfen keine Bilder hochladen."
|
||||||
attachment_upload_not_allowed_for_new_user: "Entschuldige, neue Benutzer dürfen keine Dateien hochladen."
|
attachment_upload_not_allowed_for_new_user: "Entschuldige, neue Benutzer dürfen keine Dateien hochladen."
|
||||||
|
|
||||||
abandon: "Willst Du diesen Beitrag wirklich verwerfen?"
|
abandon: "Willst Du diesen Beitrag wirklich verwerfen?"
|
||||||
@ -884,6 +914,7 @@ de:
|
|||||||
other: "Bist Du sicher, dass Du all diesen Beiträge löschen willst?"
|
other: "Bist Du sicher, dass Du all diesen Beiträge löschen willst?"
|
||||||
|
|
||||||
category:
|
category:
|
||||||
|
can: 'kann… '
|
||||||
none: '(keine Kategorie)'
|
none: '(keine Kategorie)'
|
||||||
edit: 'Bearbeiten'
|
edit: 'Bearbeiten'
|
||||||
edit_long: "Kategorie bearbeiten"
|
edit_long: "Kategorie bearbeiten"
|
||||||
@ -912,18 +943,19 @@ de:
|
|||||||
change_in_category_topic: "Besuche die Themen dieser Kategorie um einen Eindruck für eine gute Beschreibung zu gewinnen."
|
change_in_category_topic: "Besuche die Themen dieser Kategorie um einen Eindruck für eine gute Beschreibung zu gewinnen."
|
||||||
hotness: "Beliebtheit"
|
hotness: "Beliebtheit"
|
||||||
already_used: 'Diese Farbe wird bereits für eine andere Kategorie verwendet'
|
already_used: 'Diese Farbe wird bereits für eine andere Kategorie verwendet'
|
||||||
is_secure: "Sichere Kategorie?"
|
|
||||||
add_group: "Gruppe hinzufügen"
|
|
||||||
security: "Sicherheit"
|
security: "Sicherheit"
|
||||||
allowed_groups: "Erlaubte Gruppen:"
|
|
||||||
auto_close_label: "Thema automatisch schließen nach:"
|
auto_close_label: "Thema automatisch schließen nach:"
|
||||||
|
edit_permissions: "Berechtigung bearbeiten"
|
||||||
|
add_permission: "Berechtigung hinzufügen"
|
||||||
|
|
||||||
flagging:
|
flagging:
|
||||||
title: 'Aus welchem Grund meldest Du diesen Beitrag?'
|
title: 'Aus welchem Grund meldest Du diesen Beitrag?'
|
||||||
action: 'Beitrag melden'
|
action: 'Beitrag melden'
|
||||||
take_action: "Reagieren"
|
take_action: "Reagieren"
|
||||||
notify_action: 'Melden'
|
notify_action: 'Melden'
|
||||||
|
delete_spammer: "Spammer löschen"
|
||||||
|
delete_confirm: "Du wirst <b>%{posts}</b> Beiträge und <b>%{topics}</b> Themen von diesem Benutzer löschen, das Konto entfernen und die Mail <b>%{email}</b> permanent blockieren. Bist du sicher, dass dieser Benutzer wirklich ein Spammer ist?"
|
||||||
|
yes_delete_spammer: "Ja, lösche den Spammer"
|
||||||
cant: "Entschuldige, Du kannst diesen Beitrag augenblicklich nicht melden."
|
cant: "Entschuldige, Du kannst diesen Beitrag augenblicklich nicht melden."
|
||||||
custom_placeholder_notify_user: "Weshalb erfordert der Beitrag, dass du den Benutzer direkt und privat kontaktieren möchtest? Sei spezifisch, konstruktiv und immer freundlich."
|
custom_placeholder_notify_user: "Weshalb erfordert der Beitrag, dass du den Benutzer direkt und privat kontaktieren möchtest? Sei spezifisch, konstruktiv und immer freundlich."
|
||||||
custom_placeholder_notify_moderators: "Warum soll ein Moderator sich diesen Beitrag ansehen? Bitte lass uns wissen, was genau Dich beunruhigt, und wenn möglich dafür relevante Links."
|
custom_placeholder_notify_moderators: "Warum soll ein Moderator sich diesen Beitrag ansehen? Bitte lass uns wissen, was genau Dich beunruhigt, und wenn möglich dafür relevante Links."
|
||||||
@ -956,6 +988,7 @@ de:
|
|||||||
views_long: "Dieses Thema wurde {{number}} aufgerufen"
|
views_long: "Dieses Thema wurde {{number}} aufgerufen"
|
||||||
activity: "Aktivität"
|
activity: "Aktivität"
|
||||||
likes: "Gefällt mir"
|
likes: "Gefällt mir"
|
||||||
|
likes_long: "es gibt {{number}} „Gefällt mir“ in diesem Thema"
|
||||||
top_contributors: "Teilnehmer"
|
top_contributors: "Teilnehmer"
|
||||||
category_title: "Kategorie"
|
category_title: "Kategorie"
|
||||||
history: "Verlauf"
|
history: "Verlauf"
|
||||||
@ -1004,6 +1037,11 @@ de:
|
|||||||
|
|
||||||
browser_update: '<a href="http://www.discourse.org/faq/#browser">Dein Webbrowser ist leider zu alt um dieses Forum zu besuchen</a>. Bitte <a href="http://browsehappy.com">installiere einen neueren Browser</a>.'
|
browser_update: '<a href="http://www.discourse.org/faq/#browser">Dein Webbrowser ist leider zu alt um dieses Forum zu besuchen</a>. Bitte <a href="http://browsehappy.com">installiere einen neueren Browser</a>.'
|
||||||
|
|
||||||
|
permission_types:
|
||||||
|
full: "Erstellen / Antworten / Anschauen"
|
||||||
|
create_post: "Antworten / Anschauen"
|
||||||
|
readonly: "Anschauen"
|
||||||
|
|
||||||
# This section is exported to the javascript for i18n in the admin section
|
# This section is exported to the javascript for i18n in the admin section
|
||||||
admin_js:
|
admin_js:
|
||||||
type_to_filter: "Tippe etwas ein, um zu filtern..."
|
type_to_filter: "Tippe etwas ein, um zu filtern..."
|
||||||
@ -1014,6 +1052,7 @@ de:
|
|||||||
|
|
||||||
dashboard:
|
dashboard:
|
||||||
title: "Übersicht"
|
title: "Übersicht"
|
||||||
|
last_updated: "Übersicht zuletzt aktualisiert:"
|
||||||
version: "Version"
|
version: "Version"
|
||||||
up_to_date: "Discourse ist aktuell."
|
up_to_date: "Discourse ist aktuell."
|
||||||
critical_available: "Ein kritisches Update ist verfügbar."
|
critical_available: "Ein kritisches Update ist verfügbar."
|
||||||
@ -1065,6 +1104,7 @@ de:
|
|||||||
disagree_unhide_title: "Verwerfe alle Meldungen über diesen Beitrag (blendet verstecke Beiträge ein)"
|
disagree_unhide_title: "Verwerfe alle Meldungen über diesen Beitrag (blendet verstecke Beiträge ein)"
|
||||||
disagree: "Ablehnen"
|
disagree: "Ablehnen"
|
||||||
disagree_title: "Meldung ablehnen, alle Meldungen über diesen Beitrag annullieren"
|
disagree_title: "Meldung ablehnen, alle Meldungen über diesen Beitrag annullieren"
|
||||||
|
delete_spammer_title: "Lösche den Benutzer und alle seine Beiträge und Themen."
|
||||||
|
|
||||||
flagged_by: "Gemeldet von"
|
flagged_by: "Gemeldet von"
|
||||||
error: "Etwas ist schief gelaufen"
|
error: "Etwas ist schief gelaufen"
|
||||||
@ -1145,6 +1185,48 @@ de:
|
|||||||
last_seen_user: "Letzer Benutzer:"
|
last_seen_user: "Letzer Benutzer:"
|
||||||
reply_key: "Antwort-Schlüssel"
|
reply_key: "Antwort-Schlüssel"
|
||||||
|
|
||||||
|
logs:
|
||||||
|
title: "Logs"
|
||||||
|
action: "Aktion"
|
||||||
|
created_at: "Erstellt"
|
||||||
|
last_match_at: "Letzte Übereinstimmung"
|
||||||
|
match_count: "Übereinstimmungen"
|
||||||
|
ip_address: "IP"
|
||||||
|
screened_actions:
|
||||||
|
block: "blockieren"
|
||||||
|
do_nothing: "nichts machen"
|
||||||
|
staff_actions:
|
||||||
|
title: "Mitarbeiter Aktion"
|
||||||
|
instructions: "Kilcke auf die Benutzernamen und Aktionen um die Liste zu filtern. Klicke den Avatar um die Benutzerseite zu sehen."
|
||||||
|
clear_filters: "Alles anzeigen"
|
||||||
|
staff_user: "Mitarbeiter"
|
||||||
|
target_user: "Zielnutzer"
|
||||||
|
subject: "Betreff"
|
||||||
|
when: "Wann"
|
||||||
|
context: "Kontext"
|
||||||
|
details: "Details"
|
||||||
|
previous_value: "Vorangehend"
|
||||||
|
new_value: "Neu"
|
||||||
|
diff: "Diff"
|
||||||
|
show: "Anzeigen"
|
||||||
|
modal_title: "Details"
|
||||||
|
no_previous: "Es gibt keinen vorgängigen Wert."
|
||||||
|
deleted: "Kein neuer Wert. Der Eintrag wurde gelöscht."
|
||||||
|
actions:
|
||||||
|
delete_user: "Benutzer löschen"
|
||||||
|
change_trust_level: "Vertrauensstufe ändern"
|
||||||
|
change_site_setting: "Seiten Einstellungen ändern"
|
||||||
|
change_site_customization: "Seiten Anpassungen ändern"
|
||||||
|
delete_site_customization: "Seiten Anpassungen löschen"
|
||||||
|
screened_emails:
|
||||||
|
title: "Geschützte Mails"
|
||||||
|
description: "Wen jemand ein Konto erstellt, werden die folgenden Mail überprüft und die Registration blockiert, oder eine andere Aktion ausgeführt."
|
||||||
|
email: "Mail Adresse"
|
||||||
|
screened_urls:
|
||||||
|
title: "Geschützte URLs"
|
||||||
|
description: "Die aufgelisteten URLs wurden in Beiträgen von identifizierten Spammen verwendet."
|
||||||
|
url: "URL"
|
||||||
|
|
||||||
impersonate:
|
impersonate:
|
||||||
title: "Aus Nutzersicht betrachten"
|
title: "Aus Nutzersicht betrachten"
|
||||||
username_or_email: "Benutzername oder Mailadresse des Nutzers"
|
username_or_email: "Benutzername oder Mailadresse des Nutzers"
|
||||||
@ -1170,6 +1252,9 @@ de:
|
|||||||
approved_selected:
|
approved_selected:
|
||||||
one: "Benutzer zulassen"
|
one: "Benutzer zulassen"
|
||||||
other: "Benutzer zulassen ({{count}})"
|
other: "Benutzer zulassen ({{count}})"
|
||||||
|
reject_selected:
|
||||||
|
one: "Benutzer ablehnen"
|
||||||
|
other: "Lehne ({{count}}) Benutzer ab"
|
||||||
titles:
|
titles:
|
||||||
active: 'Aktive Benutzer'
|
active: 'Aktive Benutzer'
|
||||||
new: 'Neue Benutzer'
|
new: 'Neue Benutzer'
|
||||||
@ -1183,12 +1268,19 @@ de:
|
|||||||
moderators: 'Moderatoren'
|
moderators: 'Moderatoren'
|
||||||
blocked: 'Gesperrte Benutzer'
|
blocked: 'Gesperrte Benutzer'
|
||||||
banned: "Gebannte Benutzer"
|
banned: "Gebannte Benutzer"
|
||||||
|
reject_successful:
|
||||||
|
one: "Erfolgreich 1 Benutzer abgelehnt."
|
||||||
|
other: "Erfolgreich %{count} Benutzer abgelehnt."
|
||||||
|
reject_failures:
|
||||||
|
one: "Konnte 1 Benutzer nicht ablehnen."
|
||||||
|
other: "Konnte %{count} Benutzer nicht ablehnen."
|
||||||
|
|
||||||
user:
|
user:
|
||||||
ban_failed: "Beim Sperren dieses Benutzers ist etwas schief gegangen {{error}}"
|
ban_failed: "Beim Sperren dieses Benutzers ist etwas schief gegangen {{error}}"
|
||||||
unban_failed: "Beim Entsperren dieses Benutzers ist etwas schief gegangen {{error}}"
|
unban_failed: "Beim Entsperren dieses Benutzers ist etwas schief gegangen {{error}}"
|
||||||
ban_duration: "Wie lange soll dieser Benutzer gesperrt werden? (Tage)"
|
ban_duration: "Wie lange soll dieser Benutzer gesperrt werden? (Tage)"
|
||||||
delete_all_posts: "Lösche alle Beiträge"
|
delete_all_posts: "Lösche alle Beiträge"
|
||||||
|
delete_all_posts_confirm: "Du löschst %{posts} Beiträge und %{topics} Themen. Bist du sicher?"
|
||||||
ban: "Sperren"
|
ban: "Sperren"
|
||||||
unban: "Entsperren"
|
unban: "Entsperren"
|
||||||
banned: "Gesperrt?"
|
banned: "Gesperrt?"
|
||||||
@ -1219,12 +1311,18 @@ de:
|
|||||||
flags_received_count: Erhaltene Meldungen
|
flags_received_count: Erhaltene Meldungen
|
||||||
approve: 'Genehmigen'
|
approve: 'Genehmigen'
|
||||||
approved_by: "genehmigt von"
|
approved_by: "genehmigt von"
|
||||||
approve_success: "Benutzer freigeschalten und Mail mit den Anweisungen zur Aktivierung gesendet."
|
approve_success: "Benutzer freigeschalten und Mail mit den Anweisungen zur Aktivierung
|
||||||
approve_bulk_success: "Erfolg! Alle ausgewählten Benutzer wurden freigeschalten und benachrichtigt."
|
gesendet."
|
||||||
|
approve_bulk_success: "Erfolg! Alle ausgewählten Benutzer wurden freigeschalten und
|
||||||
|
benachrichtigt."
|
||||||
time_read: "Lesezeit"
|
time_read: "Lesezeit"
|
||||||
delete: Benutzer löschen
|
delete: Benutzer löschen
|
||||||
delete_forbidden: "Der Benutzer kann nicht gelöscht werden, da er noch Beiträge hat. Lösche zuerst seine Beträge."
|
delete_forbidden:
|
||||||
|
one: "Benutzer können nicht gelöscht werden, wenn sie sich vor mehr als %{count} Tag angemeldet oder noch Beiträge haben. Lösche zuerst seine Beträge."
|
||||||
|
other: "Benutzer können nicht gelöscht werden, wenn sie sich vor mehr als %{count} Tagen angemeldet oder noch Beiträge haben. Lösche zuerst seine Beträge."
|
||||||
delete_confirm: "Bist du SICHER das du diesen Benutzer permanent von der Seite entfernen möchtest? Diese Aktion kann nicht rückgängig gemacht werden!"
|
delete_confirm: "Bist du SICHER das du diesen Benutzer permanent von der Seite entfernen möchtest? Diese Aktion kann nicht rückgängig gemacht werden!"
|
||||||
|
delete_and_block: "<b>Ja</b>, und <b>blockiere</b> Anmeldungen von dieser Mail Adresse"
|
||||||
|
delete_dont_block: "<b>Ja</b>, aber <b>erlaube</b> Anmeldungen von dieser Mail Adresse"
|
||||||
deleted: "Der Benutzer wurde gelöscht."
|
deleted: "Der Benutzer wurde gelöscht."
|
||||||
delete_failed: "Beim Löschen des Benutzers ist ein Fehler aufgetreten. Stelle sicher, dass dieser Benutzer keine Beiträge mehr hat."
|
delete_failed: "Beim Löschen des Benutzers ist ein Fehler aufgetreten. Stelle sicher, dass dieser Benutzer keine Beiträge mehr hat."
|
||||||
send_activation_email: "Aktivierungsmail senden"
|
send_activation_email: "Aktivierungsmail senden"
|
||||||
@ -1239,7 +1337,7 @@ de:
|
|||||||
deactivate_explanation: "Ein deaktivierter Benutzer muss seine E-Mail erneut bestätigen."
|
deactivate_explanation: "Ein deaktivierter Benutzer muss seine E-Mail erneut bestätigen."
|
||||||
banned_explanation: "Ein gesperrter Benutzer kann sich nicht einloggen."
|
banned_explanation: "Ein gesperrter Benutzer kann sich nicht einloggen."
|
||||||
block_explanation: "Ein geblockter Benutzer kann keine Themen erstellen oder Beiträge veröffentlichen."
|
block_explanation: "Ein geblockter Benutzer kann keine Themen erstellen oder Beiträge veröffentlichen."
|
||||||
|
trust_level_change_failed: "Beim Wechsel der Vertrauensstufe ist ein Fehler aufgetreten."
|
||||||
|
|
||||||
site_content:
|
site_content:
|
||||||
none: "Wähle einen Inhaltstyp um mit dem Bearbeiten zu beginnen."
|
none: "Wähle einen Inhaltstyp um mit dem Bearbeiten zu beginnen."
|
||||||
@ -1251,3 +1349,5 @@ de:
|
|||||||
title: 'Einstellungen'
|
title: 'Einstellungen'
|
||||||
reset: 'Zurücksetzen'
|
reset: 'Zurücksetzen'
|
||||||
none: "Keine"
|
none: "Keine"
|
||||||
|
|
||||||
|
|
||||||
|
@ -190,7 +190,6 @@ en:
|
|||||||
download_archive: "download archive of my posts"
|
download_archive: "download archive of my posts"
|
||||||
private_message: "Private Message"
|
private_message: "Private Message"
|
||||||
private_messages: "Messages"
|
private_messages: "Messages"
|
||||||
private_messages_sent: "Sent Messages"
|
|
||||||
activity_stream: "Activity"
|
activity_stream: "Activity"
|
||||||
preferences: "Preferences"
|
preferences: "Preferences"
|
||||||
bio: "About me"
|
bio: "About me"
|
||||||
@ -203,6 +202,12 @@ en:
|
|||||||
change: "change"
|
change: "change"
|
||||||
moderator: "{{user}} is a moderator"
|
moderator: "{{user}} is a moderator"
|
||||||
admin: "{{user}} is an admin"
|
admin: "{{user}} is an admin"
|
||||||
|
deleted: "User Was Deleted"
|
||||||
|
|
||||||
|
messages:
|
||||||
|
all: "All"
|
||||||
|
mine: "Mine"
|
||||||
|
unread: "Unread"
|
||||||
|
|
||||||
change_password:
|
change_password:
|
||||||
success: "(email sent)"
|
success: "(email sent)"
|
||||||
@ -398,6 +403,7 @@ en:
|
|||||||
authenticating: "Authenticating..."
|
authenticating: "Authenticating..."
|
||||||
awaiting_confirmation: "Your account is awaiting activation, use the forgot password link to issue another activation email."
|
awaiting_confirmation: "Your account is awaiting activation, use the forgot password link to issue another activation email."
|
||||||
awaiting_approval: "Your account has not been approved by a staff member yet. You will be sent an email when it is approved."
|
awaiting_approval: "Your account has not been approved by a staff member yet. You will be sent an email when it is approved."
|
||||||
|
requires_invite: "Sorry, access to this forum is by invite only."
|
||||||
not_activated: "You can't log in yet. We previously sent an activation email to you at <b>{{sentTo}}</b>. Please follow the instructions in that email to activate your account."
|
not_activated: "You can't log in yet. We previously sent an activation email to you at <b>{{sentTo}}</b>. Please follow the instructions in that email to activate your account."
|
||||||
resend_activation_email: "Click here to send the activation email again."
|
resend_activation_email: "Click here to send the activation email again."
|
||||||
sent_activation_email_again: "We sent another activation email to you at <b>{{currentEmail}}</b>. It might take a few minutes for it to arrive; be sure to check your spam folder."
|
sent_activation_email_again: "We sent another activation email to you at <b>{{currentEmail}}</b>. It might take a few minutes for it to arrive; be sure to check your spam folder."
|
||||||
@ -505,7 +511,7 @@ en:
|
|||||||
private_message: "<i class='icon icon-envelope-alt' title='private message'></i> {{username}} {{link}}"
|
private_message: "<i class='icon icon-envelope-alt' title='private message'></i> {{username}} {{link}}"
|
||||||
invited_to_private_message: "<i class='icon icon-envelope-alt' title='private message'></i> {{username}} {{link}}"
|
invited_to_private_message: "<i class='icon icon-envelope-alt' title='private message'></i> {{username}} {{link}}"
|
||||||
invitee_accepted: "<i title='accepted your invitation' class='icon icon-signin'></i> {{username}} accepted your invitation"
|
invitee_accepted: "<i title='accepted your invitation' class='icon icon-signin'></i> {{username}} accepted your invitation"
|
||||||
moved_post: "<i title='moved post' class='icon icon-arrow-right'></i> {{username}} moved to {{link}}"
|
moved_post: "<i title='moved post' class='icon icon-arrow-right'></i> {{username}} moved {{link}}"
|
||||||
total_flagged: "total flagged posts"
|
total_flagged: "total flagged posts"
|
||||||
|
|
||||||
upload_selector:
|
upload_selector:
|
||||||
@ -759,6 +765,7 @@ en:
|
|||||||
multi_select:
|
multi_select:
|
||||||
select: 'select'
|
select: 'select'
|
||||||
selected: 'selected ({{count}})'
|
selected: 'selected ({{count}})'
|
||||||
|
select_replies: 'select +replies'
|
||||||
delete: delete selected
|
delete: delete selected
|
||||||
cancel: cancel selecting
|
cancel: cancel selecting
|
||||||
description:
|
description:
|
||||||
@ -811,6 +818,12 @@ en:
|
|||||||
undelete: "undelete this post"
|
undelete: "undelete this post"
|
||||||
share: "share a link to this post"
|
share: "share a link to this post"
|
||||||
more: "More"
|
more: "More"
|
||||||
|
delete_replies:
|
||||||
|
confirm:
|
||||||
|
one: "Do you also want to delete the direct reply to this post?"
|
||||||
|
other: "Do you also want to delete the {{count}} direct replies to this post?"
|
||||||
|
yes_value: "Yes, delete the replies too"
|
||||||
|
no_value: "No, just this post"
|
||||||
|
|
||||||
actions:
|
actions:
|
||||||
flag: 'Flag'
|
flag: 'Flag'
|
||||||
|
@ -220,7 +220,6 @@ ru:
|
|||||||
download_archive: скачать архив ваших сообщений
|
download_archive: скачать архив ваших сообщений
|
||||||
private_message: Личное сообщение
|
private_message: Личное сообщение
|
||||||
private_messages: Личные сообщения
|
private_messages: Личные сообщения
|
||||||
private_messages_sent: Отправленные сообщения
|
|
||||||
activity_stream: Активность
|
activity_stream: Активность
|
||||||
preferences: Настройки
|
preferences: Настройки
|
||||||
bio: Обо мне
|
bio: Обо мне
|
||||||
@ -233,6 +232,10 @@ ru:
|
|||||||
change: изменить
|
change: изменить
|
||||||
moderator: '{{user}} - модератор'
|
moderator: '{{user}} - модератор'
|
||||||
admin: '{{user}} - админ'
|
admin: '{{user}} - админ'
|
||||||
|
messages:
|
||||||
|
all: Все
|
||||||
|
mine: Мои
|
||||||
|
unread: Непрочитанные
|
||||||
change_password:
|
change_password:
|
||||||
success: (письмо отправлено)
|
success: (письмо отправлено)
|
||||||
in_progress: (отправка письма)
|
in_progress: (отправка письма)
|
||||||
@ -258,6 +261,7 @@ ru:
|
|||||||
uploaded_avatar: Собственный аватар
|
uploaded_avatar: Собственный аватар
|
||||||
uploaded_avatar_empty: Добавить собственный аватар
|
uploaded_avatar_empty: Добавить собственный аватар
|
||||||
upload_title: Загрузка собственного аватара
|
upload_title: Загрузка собственного аватара
|
||||||
|
image_is_not_a_square: 'Внимание: изображение было кадрировано, т.к. оно не квадратное.'
|
||||||
email:
|
email:
|
||||||
title: Email
|
title: Email
|
||||||
instructions: Ваш адрес электронной почты всегда скрыт.
|
instructions: Ваш адрес электронной почты всегда скрыт.
|
||||||
@ -407,6 +411,7 @@ ru:
|
|||||||
authenticating: Проверка...
|
authenticating: Проверка...
|
||||||
awaiting_confirmation: Ваша учетная запись требует активации. Для того чтобы получить активационное письмо повторно, воспользуйтесь опцией сброса пароля.
|
awaiting_confirmation: Ваша учетная запись требует активации. Для того чтобы получить активационное письмо повторно, воспользуйтесь опцией сброса пароля.
|
||||||
awaiting_approval: Ваша учетная запись еще не одобрена. Вы получите письмо, когда это случится.
|
awaiting_approval: Ваша учетная запись еще не одобрена. Вы получите письмо, когда это случится.
|
||||||
|
requires_invite: К сожалению, доступ к форуму только по приглашениям.
|
||||||
not_activated: 'Прежде чем вы сможете воспользоваться новой учетной записью, вам необходимо ее активировать. Мы отправили вам на почту <b>{{sentTo}}</b> подробные инструкции, как это cделать.'
|
not_activated: 'Прежде чем вы сможете воспользоваться новой учетной записью, вам необходимо ее активировать. Мы отправили вам на почту <b>{{sentTo}}</b> подробные инструкции, как это cделать.'
|
||||||
resend_activation_email: Щелкните здесь, чтобы мы повторно выслали вам письмо для активации учетной записи.
|
resend_activation_email: Щелкните здесь, чтобы мы повторно выслали вам письмо для активации учетной записи.
|
||||||
sent_activation_email_again: 'По адресу <b>{{currentEmail}}</b> повторно отправлено письмо с кодом активации. Доставка сообщения может занять несколько минут. Имейте в виду, что иногда по ошибке письмо может попасть в папку Спам.'
|
sent_activation_email_again: 'По адресу <b>{{currentEmail}}</b> повторно отправлено письмо с кодом активации. Доставка сообщения может занять несколько минут. Имейте в виду, что иногда по ошибке письмо может попасть в папку Спам.'
|
||||||
@ -506,7 +511,7 @@ ru:
|
|||||||
private_message: "<i class='icon icon-envelope-alt' title='private message'></i> {{username}} {{link}}"
|
private_message: "<i class='icon icon-envelope-alt' title='private message'></i> {{username}} {{link}}"
|
||||||
invited_to_private_message: "<i class='icon icon-envelope-alt' title='private message'></i> {{username}} {{link}}"
|
invited_to_private_message: "<i class='icon icon-envelope-alt' title='private message'></i> {{username}} {{link}}"
|
||||||
invitee_accepted: "<i title='принятое приглашение' class='icon icon-signin'></i> {{username}} принял ваше приглашение"
|
invitee_accepted: "<i title='принятое приглашение' class='icon icon-signin'></i> {{username}} принял ваше приглашение"
|
||||||
moved_post: "<i title='перенесенное сообщение' class='icon icon-arrow-right'></i> {{username}} перенес сообщение в {{link}}"
|
moved_post: "<i title='moved post' class='icon icon-arrow-right'></i> {{username}} переместил сообщение в {{link}}"
|
||||||
total_flagged: всего сообщений с жалобами
|
total_flagged: всего сообщений с жалобами
|
||||||
upload_selector:
|
upload_selector:
|
||||||
title: Загрузить изображение
|
title: Загрузить изображение
|
||||||
|
@ -188,7 +188,6 @@ zh_CN:
|
|||||||
download_archive: "下载我的帖子的存档"
|
download_archive: "下载我的帖子的存档"
|
||||||
private_message: "私信"
|
private_message: "私信"
|
||||||
private_messages: "消息"
|
private_messages: "消息"
|
||||||
private_messages_sent: "已发送消息"
|
|
||||||
activity_stream: "活动"
|
activity_stream: "活动"
|
||||||
preferences: "设置"
|
preferences: "设置"
|
||||||
bio: "关于我"
|
bio: "关于我"
|
||||||
@ -201,6 +200,11 @@ zh_CN:
|
|||||||
change: "修改"
|
change: "修改"
|
||||||
moderator: "{{user}} 是版主"
|
moderator: "{{user}} 是版主"
|
||||||
admin: "{{user}} 是管理员"
|
admin: "{{user}} 是管理员"
|
||||||
|
|
||||||
|
messages:
|
||||||
|
all: "所有"
|
||||||
|
mine: "我的"
|
||||||
|
unread: "未读"
|
||||||
|
|
||||||
change_password:
|
change_password:
|
||||||
success: "(电子邮件已发送)"
|
success: "(电子邮件已发送)"
|
||||||
@ -396,6 +400,7 @@ zh_CN:
|
|||||||
authenticating: "验证中……"
|
authenticating: "验证中……"
|
||||||
awaiting_confirmation: "你的帐号尚未激活,点击忘记密码链接来重新发送激活邮件。"
|
awaiting_confirmation: "你的帐号尚未激活,点击忘记密码链接来重新发送激活邮件。"
|
||||||
awaiting_approval: "你的帐号尚未被论坛版主批准。一旦你的帐号获得批准,你会收到一封电子邮件。"
|
awaiting_approval: "你的帐号尚未被论坛版主批准。一旦你的帐号获得批准,你会收到一封电子邮件。"
|
||||||
|
requires_invite: "抱歉,本论坛仅接受邀请注册。"
|
||||||
not_activated: "你还不能登录。我们之前在<b>{{sentTo}}</b>发送了一封激活邮件给你。请按照邮件中的介绍来激活你的帐号。"
|
not_activated: "你还不能登录。我们之前在<b>{{sentTo}}</b>发送了一封激活邮件给你。请按照邮件中的介绍来激活你的帐号。"
|
||||||
resend_activation_email: "点击此处来重新发送激活邮件。"
|
resend_activation_email: "点击此处来重新发送激活邮件。"
|
||||||
sent_activation_email_again: "我们在<b>{{currentEmail}}</b>又发送了一封激活邮件给你,邮件送达可能需要几分钟,有的电子邮箱服务商可能会认为此邮件为垃圾邮件,请检查一下你邮箱的垃圾邮件文件夹。"
|
sent_activation_email_again: "我们在<b>{{currentEmail}}</b>又发送了一封激活邮件给你,邮件送达可能需要几分钟,有的电子邮箱服务商可能会认为此邮件为垃圾邮件,请检查一下你邮箱的垃圾邮件文件夹。"
|
||||||
@ -503,7 +508,7 @@ zh_CN:
|
|||||||
private_message: "<i class='icon icon-envelope-alt' title='私信'></i> {{username}} 发送给你一条私信:{{link}}"
|
private_message: "<i class='icon icon-envelope-alt' title='私信'></i> {{username}} 发送给你一条私信:{{link}}"
|
||||||
invited_to_private_message: "{{username}} 邀请你进行私下交流:{{link}}"
|
invited_to_private_message: "{{username}} 邀请你进行私下交流:{{link}}"
|
||||||
invitee_accepted: "<i title='已接受你的邀请' class='icon icon-signin'></i> {{username}} 已接受你的邀请"
|
invitee_accepted: "<i title='已接受你的邀请' class='icon icon-signin'></i> {{username}} 已接受你的邀请"
|
||||||
moved_post: "<i title='移动帖子' class='icon icon-arrow-right'></i> {{username}} 已将帖子移动到 {{link}}"
|
moved_post: "<i title='移动帖子' class='icon icon-arrow-right'></i> {{username}} 移动了该帖: {{link}}"
|
||||||
total_flagged: "被报告帖子的总数"
|
total_flagged: "被报告帖子的总数"
|
||||||
|
|
||||||
upload_selector:
|
upload_selector:
|
||||||
|
@ -5,9 +5,15 @@
|
|||||||
# http://yamllint.com/
|
# http://yamllint.com/
|
||||||
|
|
||||||
de:
|
de:
|
||||||
|
dates:
|
||||||
|
short_date_no_year: "D MMM"
|
||||||
|
short_date: "D. MMM YYYY"
|
||||||
|
long_date: "D. MMMM YYYY, H:mm"
|
||||||
time:
|
time:
|
||||||
formats:
|
formats:
|
||||||
short: "%d. %m. %Y"
|
short: "%d. %m. %Y"
|
||||||
|
short_no_year: "%-d. %B"
|
||||||
|
date_only: "%-d. %b %Y"
|
||||||
|
|
||||||
title: "Discourse"
|
title: "Discourse"
|
||||||
topics: "Themen"
|
topics: "Themen"
|
||||||
@ -33,6 +39,10 @@ de:
|
|||||||
zero: "Entschuldige, neue Benutzer können Beiträge keine Bilder hinzufügen."
|
zero: "Entschuldige, neue Benutzer können Beiträge keine Bilder hinzufügen."
|
||||||
one: "Entschuldige, neue Benutzer können Beiträgen nur ein Bild hinzufügen."
|
one: "Entschuldige, neue Benutzer können Beiträgen nur ein Bild hinzufügen."
|
||||||
other: "Entschuldige, neue Benutzer können Beiträge nur %{count} Bilde hinzufügen."
|
other: "Entschuldige, neue Benutzer können Beiträge nur %{count} Bilde hinzufügen."
|
||||||
|
too_many_attachments:
|
||||||
|
zero: "Entschuldige, neue Benutzer können Beiträge keine Dateien hinzufügen."
|
||||||
|
one: "Entschuldige, neue Benutzer können Beiträgen nur eine Datei hinzufügen."
|
||||||
|
other: "Entschuldige, neue Benutzer können Beiträgen nur %{count} Dateien hinzufügen."
|
||||||
too_many_links:
|
too_many_links:
|
||||||
zero: "Entschuldige, neue Benutzer können Beiträgen keine Links hinzufügen."
|
zero: "Entschuldige, neue Benutzer können Beiträgen keine Links hinzufügen."
|
||||||
one: "Entschuldige, neue Benutzer können Beiträgen nur einen Link hinzufügen."
|
one: "Entschuldige, neue Benutzer können Beiträgen nur einen Link hinzufügen."
|
||||||
@ -50,8 +60,13 @@ de:
|
|||||||
rss_topics_in_category: "RSS-Feed von Themen in der Kategorie '%{category}'"
|
rss_topics_in_category: "RSS-Feed von Themen in der Kategorie '%{category}'"
|
||||||
author_wrote: "%{author} schrieb:"
|
author_wrote: "%{author} schrieb:"
|
||||||
private_message_abbrev: "PN"
|
private_message_abbrev: "PN"
|
||||||
|
rss_description:
|
||||||
|
latest: "Neuste Themen"
|
||||||
|
hot: Angesagte Themen"
|
||||||
|
|
||||||
groups:
|
groups:
|
||||||
|
errors:
|
||||||
|
can_not_modify_automatic: "Du kannst eine automatische Gruppe nicht bearbeiten"
|
||||||
default_names:
|
default_names:
|
||||||
admins: "admins"
|
admins: "admins"
|
||||||
moderators: "moderatoren"
|
moderators: "moderatoren"
|
||||||
@ -70,8 +85,6 @@ de:
|
|||||||
'new-topic': |
|
'new-topic': |
|
||||||
Willkommen auf %{site_name} — **Danke, dass Du ein neues Thema erstellst!**
|
Willkommen auf %{site_name} — **Danke, dass Du ein neues Thema erstellst!**
|
||||||
|
|
||||||
Beachte dabei bitte die Folgenden Dinge:
|
|
||||||
|
|
||||||
- Ist der Titel eines adäquate Beschreibung dessen, was ein Nutzer vorzufinden erwartet, wenn er dieses Thema aufruft?
|
- Ist der Titel eines adäquate Beschreibung dessen, was ein Nutzer vorzufinden erwartet, wenn er dieses Thema aufruft?
|
||||||
|
|
||||||
- Der erste Beitrag umschreibt das Thema: Worum geht es? Wer wäre interessiert daran? Warum ist es wichtig? Welche Arten von Antworten erhoffst Du dir von der Community?
|
- Der erste Beitrag umschreibt das Thema: Worum geht es? Wer wäre interessiert daran? Warum ist es wichtig? Welche Arten von Antworten erhoffst Du dir von der Community?
|
||||||
@ -83,8 +96,6 @@ de:
|
|||||||
'new-reply': |
|
'new-reply': |
|
||||||
Willkommen auf %{site_name} — **Danke für deinen Beitrag zum Thema!**
|
Willkommen auf %{site_name} — **Danke für deinen Beitrag zum Thema!**
|
||||||
|
|
||||||
Beachte bitte folgende Dinge während des Schreibens:
|
|
||||||
|
|
||||||
- Fügt dein Beitrag dem Gespräch etwas Neues hinzu, und sei es auch wenig?
|
- Fügt dein Beitrag dem Gespräch etwas Neues hinzu, und sei es auch wenig?
|
||||||
|
|
||||||
- Behandle deine Gesprächspartner mit demselben Respekt, den Du von ihnen erwartest.
|
- Behandle deine Gesprächspartner mit demselben Respekt, den Du von ihnen erwartest.
|
||||||
@ -130,6 +141,8 @@ de:
|
|||||||
title: "Anführer"
|
title: "Anführer"
|
||||||
elder:
|
elder:
|
||||||
title: "Ältester"
|
title: "Ältester"
|
||||||
|
change_failed_explanation: "Du wolltest %{user_name} auf '%{new_trust_level}' zurückstufen. Jedoch ist seine Vertrauensstufe bereits '%{current_trust_level}'. %{user_name} verbleibt auf '%{current_trust_level}'"
|
||||||
|
|
||||||
|
|
||||||
rate_limiter:
|
rate_limiter:
|
||||||
too_many_requests: "Du machst das zu häufig. Bitte warte %{time_left} vor dem nächsten Versuch."
|
too_many_requests: "Du machst das zu häufig. Bitte warte %{time_left} vor dem nächsten Versuch."
|
||||||
@ -382,12 +395,17 @@ de:
|
|||||||
cas_config_warning: 'Der Server erlaubt die Anmeldung mit CAS (enable_cas_logins), aber der Hostname und die Domäne sind nicht gesetzt.'
|
cas_config_warning: 'Der Server erlaubt die Anmeldung mit CAS (enable_cas_logins), aber der Hostname und die Domäne sind nicht gesetzt.'
|
||||||
twitter_config_warning: 'Der Server erlaubt die Anmeldung mit Facebook Twitter (enable_twitter_logins), aber der Schlüssel und der Geheimcode sind nicht gesetzt. Besuche <a href="/admin/site_settings">die Einstellungen</a> um die fehlenden Einträge hinzuzufügen. <a href="https://github.com/discourse/discourse/wiki/The-Discourse-Admin-Quick-Start-Guide#enable-twitter-logins" target="_blank">Besuche den Leitfaden um mehr zu erfahren</a>.'
|
twitter_config_warning: 'Der Server erlaubt die Anmeldung mit Facebook Twitter (enable_twitter_logins), aber der Schlüssel und der Geheimcode sind nicht gesetzt. Besuche <a href="/admin/site_settings">die Einstellungen</a> um die fehlenden Einträge hinzuzufügen. <a href="https://github.com/discourse/discourse/wiki/The-Discourse-Admin-Quick-Start-Guide#enable-twitter-logins" target="_blank">Besuche den Leitfaden um mehr zu erfahren</a>.'
|
||||||
github_config_warning: 'Der Server erlaubt die Anmeldung mit Facebook GitHub (enable_github_logins), aber die Kunden ID und der Geheimcode sind nicht gesetzt. Besuche <a href="/admin/site_settings">die Einstellungen</a> um die fehlenden Einträge hinzuzufügen. <a href="https://github.com/discourse/discourse/wiki/The-Discourse-Admin-Quick-Start-Guide" target="_blank">Besuche den Leitfaden um mehr zu erfahren</a>.'
|
github_config_warning: 'Der Server erlaubt die Anmeldung mit Facebook GitHub (enable_github_logins), aber die Kunden ID und der Geheimcode sind nicht gesetzt. Besuche <a href="/admin/site_settings">die Einstellungen</a> um die fehlenden Einträge hinzuzufügen. <a href="https://github.com/discourse/discourse/wiki/The-Discourse-Admin-Quick-Start-Guide" target="_blank">Besuche den Leitfaden um mehr zu erfahren</a>.'
|
||||||
|
s3_config_warning: 'Der Server wurde konfiguriert um Dateien nach s3 hochzuladen, aber mindestens der folgenden Einstellungen fehlt: s3_access_key_id, s3_secret_access_key oder s3_upload_bucket. Besuche <a href="/admin/site_settings">die Einstellungen</a> um die fehlenden Einträge hinzuzufügen. <a href="http://meta.discourse.org/t/how-to-set-up-image-uploads-to-s3/7229" target="_blank">Besuche "How to set up image uploads to S3?" um mehr zu erfahren</a>.'
|
||||||
|
image_magick_warning: 'Der Server wurde konfiguriert um Vorschaubilder von grossen Bildern zu erstellen, aber ImageMagick ist nicht installiertd. Installiere ImageMagick mit deinem bevorzugten Packetmanager oder besuche <a href="http://www.imagemagick.org/script/binary-releases.php" target="_blank">um das aktuelle Paket herunterzuladen</a>.'
|
||||||
failing_emails_warning: 'Es konnten insgesamt %{num_failed_jobs} Mails nicht versendet werden. Bitte überprüfe die Einstellungen in config/environments/production.rb und stelle die Richtigkeit der config.action_mailer Einstellungen. <a href="/sidekiq/retries" target="_blank">Zu den Fehlern in Sidekiq</a>.'
|
failing_emails_warning: 'Es konnten insgesamt %{num_failed_jobs} Mails nicht versendet werden. Bitte überprüfe die Einstellungen in config/environments/production.rb und stelle die Richtigkeit der config.action_mailer Einstellungen. <a href="/sidekiq/retries" target="_blank">Zu den Fehlern in Sidekiq</a>.'
|
||||||
default_logo_warning: "Das Logo der Seite wurde noch nicht angepasst. Bitte bearbeite dieses in den <a href='/admin/site_settings'>Einstellungen</a> (siehe logo_url, logo_small_url und favicon_url)."
|
default_logo_warning: "Das Logo der Seite wurde noch nicht angepasst. Bitte bearbeite dieses in den <a href='/admin/site_settings'>Einstellungen</a> (siehe logo_url, logo_small_url und favicon_url)."
|
||||||
contact_email_missing: "Du hast noch keine Kontaktmail für die Seite hinterlegt. Bitte hinterlege diese in den <a href='/admin/site_settings'>Einstellungen</a> (siehe contact_email)."
|
contact_email_missing: "Du hast noch keine Kontaktmail für die Seite hinterlegt. Bitte hinterlege diese in den <a href='/admin/site_settings'>Einstellungen</a> (siehe contact_email)."
|
||||||
contact_email_invalid: "Die Kontaktmail der Seite ist ungültig. Bitte bearbeite diese in den <a href='/admin/site_settings'>Einstellungen</a> (siehe contact_email)."
|
contact_email_invalid: "Die Kontaktmail der Seite ist ungültig. Bitte bearbeite diese in den <a href='/admin/site_settings'>Einstellungen</a> (siehe contact_email)."
|
||||||
title_nag: "Der Titel der Seite wurde noch nicht angepasst. Bitte bearbeite diesen in den <a href='/admin/site_settings'>Einstellungen</a>."
|
title_nag: "Der Titel der Seite wurde noch nicht angepasst. Bitte bearbeite diesen in den <a href='/admin/site_settings'>Einstellungen</a>."
|
||||||
consumer_email_warning: "Deine Seite verwendet Gmail um Mails zu senden. <a href='http://support.google.com/a/bin/answer.py?hl=en&answer=166852' target='_blank'>Gmail hat eine Limite zum Senden von Mails</a>. Um die Mail-Zustellung zu gewährleisten, solltest du einen anderen Mail Service in Erwägung ziehen."
|
consumer_email_warning: "Deine Seite verwendet Gmail um Mails zu senden. <a href='http://support.google.com/a/bin/answer.py?hl=en&answer=166852' target='_blank'>Gmail hat eine Limite zum Senden von Mails</a>. Um die Mail-Zustellung zu gewährleisten, solltest du einen anderen Mail Service in Erwägung ziehen."
|
||||||
|
access_password_removal: "Deine Seite hat die Einstellung access_password verwendet, welche entfernt wurde. Die Einstellungen login_required und must_approve_users wurden eingeschalten und werden sofort verwendet. Du kannst diese in <a href='/admin/site_settings'>den Einstellungen</a> wechseln. Stelle sicher, <a href='/admin/users/list/pending'>dass die Benutzer in der Warteliste</a> aktiviert werden. (Diese Meldung wird in 2 Tagen nicht mehr angezeigt.)"
|
||||||
|
system_username_warning: "Die Einstellung system_username ist leer. Bitte ändere diese in <a href='/admin/site_settings'>den Einstellungen</a>. Setzte einen Benutzernamen eines Administrators, welcher als Sender der Systemnachrichten verwendet werden soll."
|
||||||
|
notification_email_warning: "Die Einstellung notification_email ist leer. Bitte ändere diese in <a href='/admin/site_settings'>den Einstellungen</a>."
|
||||||
|
|
||||||
content_types:
|
content_types:
|
||||||
education_new_reply:
|
education_new_reply:
|
||||||
@ -405,22 +423,30 @@ de:
|
|||||||
welcome_invite:
|
welcome_invite:
|
||||||
title: "Willkommen: Eingeladener Benutzer"
|
title: "Willkommen: Eingeladener Benutzer"
|
||||||
description: "Eine private Nachricht welche automatisch an alle eingeladenen Benutzer gesendet wird, wenn diese die Einladung annehmen."
|
description: "Eine private Nachricht welche automatisch an alle eingeladenen Benutzer gesendet wird, wenn diese die Einladung annehmen."
|
||||||
|
privacy_policy:
|
||||||
|
title: "Datenschutzrichtlinie"
|
||||||
|
description: "Die Datenschutzrichtlinie deiner Seite. Leer lassen um die Vorgabe zu verwenden."
|
||||||
|
faq:
|
||||||
|
title: "FAQ"
|
||||||
|
description: "Die FAQ deiner Seite. Leer lassen um die Vorgabe zu verwenden."
|
||||||
login_required_welcome_message:
|
login_required_welcome_message:
|
||||||
title: "Anmeldung erforderlich: Willkommensnachricht"
|
title: "Anmeldung erforderlich: Willkommensnachricht"
|
||||||
description: "Willkommensnachricht welche angezeigt wird wenn der Benutzer nicht angemeldet ist und die
|
description: "Willkommensnachricht welche angezeigt wird wenn der Benutzer nicht angemeldet ist und die
|
||||||
Einstellung 'login required' aktiviert ist."
|
Einstellung 'login required' aktiviert ist."
|
||||||
|
|
||||||
tos_user_content_license:
|
tos_user_content_license:
|
||||||
title: "Nutzungsbedingungen: Lizenz"
|
title: "Nutzungsbedingungen: Lizenz"
|
||||||
description: "Der Text für die Lizenz-Sektion in den Nutzungsbedingungen."
|
description: "Der Text für die Lizenz-Sektion in den Nutzungsbedingungen."
|
||||||
tos_miscellaneous:
|
tos_miscellaneous:
|
||||||
title: "Nutzungsbedingungen: Verschiedenes"
|
title: "Nutzungsbedingungen: Verschiedenes"
|
||||||
description: "Der Text für die Verschiedene-Sektion in den Nutzungsbedingungen."
|
description: "Der Text für die Verschiedene-Sektion in den Nutzungsbedingungen."
|
||||||
|
login_required:
|
||||||
|
title: "Anmeldung erforderlich: Hauptseite"
|
||||||
|
description: "Der Text welcher nicht angemeldeten Benutzer angezeigt wird, wenn eine Anmeldung erforderlich ist."
|
||||||
|
|
||||||
site_settings:
|
site_settings:
|
||||||
default_locale: "Die Standardsprache dieser Discourse-Instanz (kodiert in ISO 639-1)."
|
default_locale: "Die Standardsprache dieser Discourse-Instanz (kodiert in ISO 639-1)."
|
||||||
min_post_length: "Minimale Beitragslänge in Zeichen."
|
min_post_length: "Minimale Beitragslänge in Zeichen."
|
||||||
|
min_private_message_post_length: "Minimale Beitragslänge in Zeichen für private Nachrichten"
|
||||||
max_post_length: "Maximale Beitragslänge in Zeichen."
|
max_post_length: "Maximale Beitragslänge in Zeichen."
|
||||||
min_topic_title_length: "Minimale Titellänge von Themen in Zeichen."
|
min_topic_title_length: "Minimale Titellänge von Themen in Zeichen."
|
||||||
max_topic_title_length: "Maximale Titellänge von Themen in Zeichen."
|
max_topic_title_length: "Maximale Titellänge von Themen in Zeichen."
|
||||||
@ -441,12 +467,15 @@ de:
|
|||||||
queue_jobs: "Benutze die Sidekiq-Queue, falls falsche Queues inline sind."
|
queue_jobs: "Benutze die Sidekiq-Queue, falls falsche Queues inline sind."
|
||||||
crawl_images: "Lade Bilder von Dritten herunter, um ihre Höhe und Breite zu bestimmen."
|
crawl_images: "Lade Bilder von Dritten herunter, um ihre Höhe und Breite zu bestimmen."
|
||||||
ninja_edit_window: "Sekunden nach Empfang eines Beitrag, in denen Bearbeitungen nicht als neue Version gelten."
|
ninja_edit_window: "Sekunden nach Empfang eines Beitrag, in denen Bearbeitungen nicht als neue Version gelten."
|
||||||
|
edit_history_visible_to_public: "Erlaube jedem vorherige Versionen eines beitrages zu sehen. Wenn deaktiviert, konnen nur Mitarbeiter die Bearbeitungshistorie anschauen."
|
||||||
|
delete_removed_posts_after: "Anzahl Stunden nach welchem Beiträge die von ihrem Author entfernt wurden endgültig gelöscht werden."
|
||||||
max_image_width: "Maximalbreite von Bildern in einem Beitrag."
|
max_image_width: "Maximalbreite von Bildern in einem Beitrag."
|
||||||
|
max_image_height: "Maximalhöhe von Bildern in einem Beitrag."
|
||||||
category_featured_topics: "Zahl der angezeigten Themen je Kategorie auf der Kategorieseite /categories."
|
category_featured_topics: "Zahl der angezeigten Themen je Kategorie auf der Kategorieseite /categories."
|
||||||
add_rel_nofollow_to_user_content: "Füge mit Ausnahme interner Links allen nutzergenerierten Inhalten 'rel nofollow' hinzu (inkludiert übergeordnete Domains). Die Änderung dieser Einstellung erfordert, dass Du sämtliche Markdown-Beiträge aktualisierst."
|
add_rel_nofollow_to_user_content: "Füge mit Ausnahme interner Links allen nutzergenerierten Inhalten 'rel nofollow' hinzu (inkludiert übergeordnete Domains). Die Änderung dieser Einstellung erfordert, dass Du sämtliche Markdown-Beiträge aktualisierst."
|
||||||
exclude_rel_nofollow_domains: "Kommaseparierte Liste aller Domains, bei denen 'nofollow' nicht hinzugefügt wird (tld.com erlaubt auch sub.tld.com)."
|
exclude_rel_nofollow_domains: "Kommaseparierte Liste aller Domains, bei denen 'nofollow' nicht hinzugefügt wird (tld.com erlaubt auch sub.tld.com)."
|
||||||
|
|
||||||
post_excerpt_maxlength: "Maximale Länge des Exzerpts eines Beitrags in Zeichen."
|
post_excerpt_maxlength: "Maximale Länge des Zitates eines Beitrags in Zeichen."
|
||||||
post_onebox_maxlength: "Maximale Länge eines Onebox-Discourse-Beitrags."
|
post_onebox_maxlength: "Maximale Länge eines Onebox-Discourse-Beitrags."
|
||||||
category_post_template: "Die Beitragsvorlage zur Kategoriedefinition beim erstellen einer neuen Kategorie."
|
category_post_template: "Die Beitragsvorlage zur Kategoriedefinition beim erstellen einer neuen Kategorie."
|
||||||
onebox_max_chars: "Maximale Zahl der Zeichen, die eine Onebox von einer externen Webseite in einen Beitrag lädt."
|
onebox_max_chars: "Maximale Zahl der Zeichen, die eine Onebox von einer externen Webseite in einen Beitrag lädt."
|
||||||
@ -457,6 +486,7 @@ de:
|
|||||||
apple_touch_icon_url: "Icon für berührungsempfindliche Apple Geräte. Empfohlene Grösse ist 144px auf 144px."
|
apple_touch_icon_url: "Icon für berührungsempfindliche Apple Geräte. Empfohlene Grösse ist 144px auf 144px."
|
||||||
|
|
||||||
notification_email: "Die Antwortadresse, die in Systemmails (zum Beispiel zur Passwortwiederherstellung, neuen Konten, etc.) eingetragen wird."
|
notification_email: "Die Antwortadresse, die in Systemmails (zum Beispiel zur Passwortwiederherstellung, neuen Konten, etc.) eingetragen wird."
|
||||||
|
email_custom_headers: "Eine Pipe-getrennte (|) Liste von eigenen Mail Headern"
|
||||||
use_ssl: "Soll die Seite via SSL nutzbar sein?"
|
use_ssl: "Soll die Seite via SSL nutzbar sein?"
|
||||||
best_of_score_threshold: "Der Minimalscore eines Beitrags, um zu den Top Beiträgen zu zählen."
|
best_of_score_threshold: "Der Minimalscore eines Beitrags, um zu den Top Beiträgen zu zählen."
|
||||||
best_of_posts_required: "Minimale Zahl der Beiträge zu einem Thema bevor der Modus 'Top Beiträge' aktiviert wird."
|
best_of_posts_required: "Minimale Zahl der Beiträge zu einem Thema bevor der Modus 'Top Beiträge' aktiviert wird."
|
||||||
@ -476,13 +506,15 @@ de:
|
|||||||
cooldown_minutes_after_hiding_posts: "Minuten, die ein Nutzer warten muss, bevor ein Beitrag, der wegen Meldungen versteckt wurde, bearbeitet werden kann."
|
cooldown_minutes_after_hiding_posts: "Minuten, die ein Nutzer warten muss, bevor ein Beitrag, der wegen Meldungen versteckt wurde, bearbeitet werden kann."
|
||||||
num_flags_to_block_new_user: "Wenn ein Beitrag eines neuen Benutzers von (n) anderen Benutzern als Werbung gemeldet wird, verstecke alle Beiträge des Benutzers und erlaube keine neue Beiträge mehr. 0 stellt diese Funktion ab."
|
num_flags_to_block_new_user: "Wenn ein Beitrag eines neuen Benutzers von (n) anderen Benutzern als Werbung gemeldet wird, verstecke alle Beiträge des Benutzers und erlaube keine neue Beiträge mehr. 0 stellt diese Funktion ab."
|
||||||
num_users_to_block_new_user: "Wenn ein Beitrag eines neuen Benutzers von nderen Benutzern (n) mal als Werbung gemeldet wird, verstecke alle Beiträge des Benutzers und erlaube keine neue Beiträge mehr. 0 stellt diese Funktion ab."
|
num_users_to_block_new_user: "Wenn ein Beitrag eines neuen Benutzers von nderen Benutzern (n) mal als Werbung gemeldet wird, verstecke alle Beiträge des Benutzers und erlaube keine neue Beiträge mehr. 0 stellt diese Funktion ab."
|
||||||
|
notify_mods_when_user_blocked: "Wenn ein Benutzer automatisch gesperrt wird, sende eine Mail an alle Moderatoren."
|
||||||
|
|
||||||
traditional_markdown_linebreaks: "Traditionelle Zeilenumbrüche in Markdown, anstatt zwei nachfolgende Leerzeichen als Zeilenumbruch zu verwenden."
|
traditional_markdown_linebreaks: "Traditionelle Zeilenumbrüche in Markdown, anstatt zwei nachfolgende Leerzeichen als Zeilenumbruch zu verwenden."
|
||||||
post_undo_action_window_mins: "Sekunden, die ein Nutzer hat, um Aktionen auf Beiträgen rückgängig zu machen (Like, Meldung, etc.)."
|
post_undo_action_window_mins: "Sekunden, die ein Nutzer hat, um Aktionen auf Beiträgen rückgängig zu machen (Like, Meldung, etc.)."
|
||||||
must_approve_users: "Administratoren müssen Nutzer freischalten, bevor sie Zugriff erlangen."
|
must_approve_users: "Administratoren müssen Nutzer freischalten, bevor sie Zugriff erlangen."
|
||||||
ga_tracking_code: "Google Analytics Trackingcode, zum Beispiel: UA-12345678-9; siehe http://google.com/analytics"
|
ga_tracking_code: "Google Analytics Trackingcode, zum Beispiel: UA-12345678-9; siehe http://google.com/analytics"
|
||||||
ga_domain_name: "Google Analytics Domänenname, zum Beispiel: mysite.com; siehe http://google.com/analytics"
|
ga_domain_name: "Google Analytics Domänenname, zum Beispiel: mysite.com; siehe http://google.com/analytics"
|
||||||
|
enable_escaped_fragments: "Aktiviere Umgehungslösung um älteren Suchmaschinen-Webcrawler zu helfen die Seite zu indexieren. ACHTUNG: Nur aktivieren falls wirklich nötig."
|
||||||
|
enable_noscript_support: "Aktiviere standard Suchmaschinen-Webcrawler Unterstützung durch den noscript Tag"
|
||||||
top_menu: "Bestimme, welche Navigationselemente in welcher Reihenfolge auftauchen. Beispiel: latest|hot|read|favorited|unread|new|posted|categories"
|
top_menu: "Bestimme, welche Navigationselemente in welcher Reihenfolge auftauchen. Beispiel: latest|hot|read|favorited|unread|new|posted|categories"
|
||||||
post_menu: "Bestimme, welche Funktionen in welcher Reihenfolge im Beitragsmenü auftauchen. Beispiel: like|edit|flag|delete|share|bookmark|reply"
|
post_menu: "Bestimme, welche Funktionen in welcher Reihenfolge im Beitragsmenü auftauchen. Beispiel: like|edit|flag|delete|share|bookmark|reply"
|
||||||
share_links: "Bestimme, welche Dienste in welcher Reihenfolge im Teilen-Dialog auftauchen. Beispiel: twitter|facebook|google+|email"
|
share_links: "Bestimme, welche Dienste in welcher Reihenfolge im Teilen-Dialog auftauchen. Beispiel: twitter|facebook|google+|email"
|
||||||
@ -491,11 +523,14 @@ de:
|
|||||||
posts_per_page: "Zahl der Beiträge, die auf einer Themenseite gezeigt werden."
|
posts_per_page: "Zahl der Beiträge, die auf einer Themenseite gezeigt werden."
|
||||||
system_username: "Benutzername des Autors für automatisch vom Forum versendete private Nachrichten."
|
system_username: "Benutzername des Autors für automatisch vom Forum versendete private Nachrichten."
|
||||||
send_welcome_message: "Bekommen neue Nutzer eine Willkommensnachricht?"
|
send_welcome_message: "Bekommen neue Nutzer eine Willkommensnachricht?"
|
||||||
suppress_reply_directly_below: "Zeige die Zahl der Antworten auf einen Beitrag nicht, falls die einzige Antwort direkt darauf folgt."
|
suppress_reply_directly_below: "Zeige die Zahl der Antworten auf einen Beitrag nicht, falls die einzige Antwort direkt darunter folgt."
|
||||||
|
suppress_reply_directly_above: "Zeige 'In Antwort auf' nicht, falls der Beitrag direkt über der einzigen Antwort folgt."
|
||||||
|
|
||||||
allow_index_in_robots_txt: "Diese Seite soll durch Suchmaschinen indiziert werden (aktualisiert robots.txt)."
|
allow_index_in_robots_txt: "Diese Seite soll durch Suchmaschinen indiziert werden (aktualisiert robots.txt)."
|
||||||
email_domains_blacklist: "Eine durch senkrechte Striche getrennte Liste von unerlaubten Maildomains. Beispiel: mailinator.com|trashmail.net"
|
email_domains_blacklist: "Eine durch senkrechte Striche getrennte Liste von unerlaubten Maildomains. Beispiel: mailinator.com|trashmail.net"
|
||||||
email_domains_whitelist: "Eine durch senkrechte Striche getrennte Liste von erlaubte Maildomains. WARNUNG: Benutzer mit Mailadressen anderer Domains können sich nicht registrieren."
|
email_domains_whitelist: "Eine durch senkrechte Striche getrennte Liste von erlaubte Maildomains. WARNUNG: Benutzer mit Mailadressen anderer Domains können sich nicht registrieren."
|
||||||
version_checks: "Erfrage Versionsupdate bei Discourse Hub und zeige Versionsbenachrichtigungen auf der Administratorkonsole /admin."
|
version_checks: "Erfrage Versionsupdate bei Discourse Hub und zeige Versionsbenachrichtigungen auf der Administratorkonsole /admin."
|
||||||
|
new_version_emails: "Sende eine Mail an contact_email Adresse wenn eine neue Version verfügbar ist."
|
||||||
|
|
||||||
port: "NUR FÜR ENTWICKLER! ACHTUNG! Benutze diesen HTTP-Port anstatt den Standardport 80. Diese Feld leer lassen heißt 'keinen'. Dient hauptsächlich Entwicklungszwecken."
|
port: "NUR FÜR ENTWICKLER! ACHTUNG! Benutze diesen HTTP-Port anstatt den Standardport 80. Diese Feld leer lassen heißt 'keinen'. Dient hauptsächlich Entwicklungszwecken."
|
||||||
force_hostname: "NUR FÜR ENTWICKLER! ACHTUNG! Spezifiziere einen Hostnamen in der URL. Dieses Feld leer lassen heißt 'keinen'. Dient hauptsächlich Entwicklungszwecken."
|
force_hostname: "NUR FÜR ENTWICKLER! ACHTUNG! Spezifiziere einen Hostnamen in der URL. Dieses Feld leer lassen heißt 'keinen'. Dient hauptsächlich Entwicklungszwecken."
|
||||||
@ -559,6 +594,8 @@ de:
|
|||||||
s3_secret_access_key: "Der geheime Schlüssel von Amazon S3 welcher für das Hochladen verwendet wird"
|
s3_secret_access_key: "Der geheime Schlüssel von Amazon S3 welcher für das Hochladen verwendet wird"
|
||||||
s3_region: "Der Name der Amazon S3 Region welche für das Hochladen verwendet wird"
|
s3_region: "Der Name der Amazon S3 Region welche für das Hochladen verwendet wird"
|
||||||
|
|
||||||
|
enable_flash_video_onebox: "Aktiviere das Einbinden von swf und flv Links in einer Onebox. ACHTUNG: Kann eine Sicherheitsrisiko sein"
|
||||||
|
|
||||||
default_invitee_trust_level: "Standardwert für die Stufe eines eingeladenen Nutzers (0-4)."
|
default_invitee_trust_level: "Standardwert für die Stufe eines eingeladenen Nutzers (0-4)."
|
||||||
default_trust_level: "Standardwert für die Stufe von Nutzern (0-4)."
|
default_trust_level: "Standardwert für die Stufe von Nutzern (0-4)."
|
||||||
|
|
||||||
@ -576,10 +613,14 @@ de:
|
|||||||
|
|
||||||
newuser_max_links: "Maximale Zahl der Links, die neue Benutzer Beiträgen hinzufügen dürfen."
|
newuser_max_links: "Maximale Zahl der Links, die neue Benutzer Beiträgen hinzufügen dürfen."
|
||||||
newuser_max_images: "Maximale Zahl der Bilder, die neue Benutzer Beiträgen hinzufügen dürfen."
|
newuser_max_images: "Maximale Zahl der Bilder, die neue Benutzer Beiträgen hinzufügen dürfen."
|
||||||
|
newuser_max_attachments: "Maximale Zahl der Dateien, die neue Benutzer Beiträgen hinzufügen dürfen."
|
||||||
newuser_max_mentions_per_post: "Maximale Zahl der @Namens-Erwähnungen, die neue Benutzer in Beiträgen nutzen dürfen."
|
newuser_max_mentions_per_post: "Maximale Zahl der @Namens-Erwähnungen, die neue Benutzer in Beiträgen nutzen dürfen."
|
||||||
max_mentions_per_post: "Maximale Zahl der @Namens-Erwähnungen, die man in einem Beitrag nutzen kann."
|
max_mentions_per_post: "Maximale Zahl der @Namens-Erwähnungen, die man in einem Beitrag nutzen kann."
|
||||||
|
|
||||||
|
create_thumbnails: "Erstelle Vorschaubilder für Bilder in einer Lightbox"
|
||||||
|
|
||||||
email_time_window_mins: "Minuten Wartezeit, bevor eine Mail an Nutzer verschickt wird, um ihnen die Chance zu geben, eine Neuigkeit zuerst zu sehen."
|
email_time_window_mins: "Minuten Wartezeit, bevor eine Mail an Nutzer verschickt wird, um ihnen die Chance zu geben, eine Neuigkeit zuerst zu sehen."
|
||||||
|
email_posts_context: "Anzahl der Antworten welche als Konext einer Notifikations-Mail hinzugefügt werden."
|
||||||
flush_timings_secs: "Sekunden, nach denen Zeiteinstellungen auf den Server übertragen werden."
|
flush_timings_secs: "Sekunden, nach denen Zeiteinstellungen auf den Server übertragen werden."
|
||||||
max_word_length: "Maximale Wortlänge in Zeichen in Thementiteln."
|
max_word_length: "Maximale Wortlänge in Zeichen in Thementiteln."
|
||||||
title_min_entropy: "Minimal nötige Entropie (einzigartige Zeichen) in einem Thementitel."
|
title_min_entropy: "Minimal nötige Entropie (einzigartige Zeichen) in einem Thementitel."
|
||||||
@ -591,7 +632,9 @@ de:
|
|||||||
min_body_similar_length: "Minimale Länge eines Beitragstextes, bevor nach ähnlichen Themen gesucht wird."
|
min_body_similar_length: "Minimale Länge eines Beitragstextes, bevor nach ähnlichen Themen gesucht wird."
|
||||||
|
|
||||||
category_colors: "Eine durch senkrechte Striche getrennte Liste hexadezimaler Farbwerte, die als Kategoriefarben erlaubt sind."
|
category_colors: "Eine durch senkrechte Striche getrennte Liste hexadezimaler Farbwerte, die als Kategoriefarben erlaubt sind."
|
||||||
max_image_size_kb: "Maximale Größe in Kilobytes (kB), die von Benutzern hochgeladene Bilder groß sein dürfen. Stelle sicher, dass dieser Wert auch in nginx (client_max_body_size) / apache und Proxies konfiguriert ist."
|
max_image_size_kb: "Maximale Größe in Kilobytes (kB), die von Benutzern hochgeladene Bilder groß sein dürfen. Stelle sicher, dass dieser Wert auch in nginx (client_max_body_size) / Apache und Proxies konfiguriert ist."
|
||||||
|
max_attachment_size_kb: "Maximale Größe in Kilobytes (kB), die von Benutzern hochgeladenen Dateien groß sein dürfen. Stelle sicher, dass dieser Wert auch in nginx (client_max_body_size) / Apache und Proxies konfiguriert ist."
|
||||||
|
authorized_extensions: "Eine Pipe-getrennte (|) Liste von Dateiendungen welche hochgeladen werden dürfen."
|
||||||
max_similar_results: "Anzahl ähnlicher Themen, die ein Nutzer sieht, während er ein neues Thema erstellen."
|
max_similar_results: "Anzahl ähnlicher Themen, die ein Nutzer sieht, während er ein neues Thema erstellen."
|
||||||
|
|
||||||
title_prettify: "Verhindert gängige Fehler im Titel, wie reine Grossschreibung, Kleinbuchstaben am Anfang, mehrere ! und ?, überflüssiger . am Ende, etc."
|
title_prettify: "Verhindert gängige Fehler im Titel, wie reine Grossschreibung, Kleinbuchstaben am Anfang, mehrere ! und ?, überflüssiger . am Ende, etc."
|
||||||
@ -600,12 +643,33 @@ de:
|
|||||||
topic_views_heat_medium: "Die Anzahl der Aufrufe bis die Popularität des Themas mittel ist."
|
topic_views_heat_medium: "Die Anzahl der Aufrufe bis die Popularität des Themas mittel ist."
|
||||||
topic_views_heat_high: "Die Anzahl der Aufrufe bis die Popularität des Themas hoch ist."
|
topic_views_heat_high: "Die Anzahl der Aufrufe bis die Popularität des Themas hoch ist."
|
||||||
|
|
||||||
|
faq_url: "URL zu einer externen FAQ welche Du gerne verwenden möchtest."
|
||||||
tos_url: "URL zu einer externen Dienstleistungsbedingung welche Du gerne verwenden möchtest."
|
tos_url: "URL zu einer externen Dienstleistungsbedingung welche Du gerne verwenden möchtest."
|
||||||
privacy_policy_url: "URL zu einer externen Datenschutzrichtlinie welche Du gerne verwenden möchtest."
|
privacy_policy_url: "URL zu einer externen Datenschutzrichtlinie welche Du gerne verwenden möchtest."
|
||||||
|
|
||||||
newuser_spam_host_threshold: "Die Anzahl welche ein Frischling Beiträge mit Links auf die gleiche Seite innerhalb ihrer `newuser_spam_host_posts` veröffentlichen , bevor der Beitrag als Spam klassifiziert wird."
|
newuser_spam_host_threshold: "Die Anzahl welche ein Frischling Beiträge mit Links auf die gleiche Seite innerhalb ihrer `newuser_spam_host_posts` veröffentlichen , bevor der Beitrag als Spam klassifiziert wird."
|
||||||
staff_like_weight: "Zusätzlicher Gewichtungsfaktor wenn Mitglieder „Gefällt mir“ verteilen."
|
staff_like_weight: "Zusätzlicher Gewichtungsfaktor wenn Mitglieder „Gefällt mir“ verteilen."
|
||||||
|
|
||||||
|
reply_by_email_enabled: "Erlaube das Antworten auf Themen via Mail"
|
||||||
|
reply_by_email_address: "Vorgabe der Antwort-Mail Adresse in der Form von: %{reply_key}@reply.myforum.com"
|
||||||
|
|
||||||
|
pop3s_polling_enabled: "Antworten via POP3S anfragen"
|
||||||
|
pop3s_polling_port: "Der Port für die POP3S Anfrage"
|
||||||
|
pop3s_polling_host: "Der Host für die POP3S Anfrage"
|
||||||
|
pop3s_polling_username: "Der Benutzername für die POP3S Anfrage"
|
||||||
|
pop3s_polling_password: "Das Passwort für die POP3S Anfrage"
|
||||||
|
|
||||||
|
minimum_topics_similar: "Wie viele Themen in der Datenbank existieren müssen, bevor ähnliche Themen angezeigt werden."
|
||||||
|
|
||||||
|
relative_date_duration: "Anzahl von Tagen nach nach welchen das Beitragsdatum relativ und nicht absolut angezeigt wird. Beispiel: relatives Datum: 7T, absolutes Datum: 20 Feb"
|
||||||
|
delete_user_max_age: "Nach wievielen Tagen ein Benutzerkonto von einem Administrator gelöscht werden kann."
|
||||||
|
delete_all_posts_max: "Die maximale Anzahl von Beiträgen welche auf einmal gelöscht werden kann. Hat ein Benutzer mehr Beiträge, so können die Beiträge nicht auf einmal und der Benutzer nicht gelöscht werden."
|
||||||
|
username_change_period: "Wie lange neu registrierte Benutzer ihren Benutzernamen ändern können."
|
||||||
|
|
||||||
|
allow_uploaded_avatars: "Erlaube das Hochladen eines eigenen Avatars"
|
||||||
|
allow_animated_avatars: "Erlaube den Benutzern animierte GIFs als Avatar zu benutzen"
|
||||||
|
default_digest_email_frequency: "Wie oft man Zusammenfassungen per Mail standardmässig erhält. Diese Einstellung kann von jedem geändert werden."
|
||||||
|
|
||||||
notification_types:
|
notification_types:
|
||||||
mentioned: "%{display_username} hat Dich in %{link} erwähnt."
|
mentioned: "%{display_username} hat Dich in %{link} erwähnt."
|
||||||
liked: "%{display_username} gefällt deinen Beitrag in %{link}."
|
liked: "%{display_username} gefällt deinen Beitrag in %{link}."
|
||||||
@ -633,6 +697,9 @@ de:
|
|||||||
moderator_post:
|
moderator_post:
|
||||||
one: "Ich habe einen Beitrag in ein neues Thema verschoben: %{topic_link}"
|
one: "Ich habe einen Beitrag in ein neues Thema verschoben: %{topic_link}"
|
||||||
other: "Ich habe %{count} Beiträge in ein neues Thema verschoben: %{topic_link}"
|
other: "Ich habe %{count} Beiträge in ein neues Thema verschoben: %{topic_link}"
|
||||||
|
existing_topic_moderator_post:
|
||||||
|
one: "Ich habe den Beitrag in ein vorhandenes Thema verschoben: %{topic_link}"
|
||||||
|
other: "Ich hab %{count} Beiträge in ein vorhandenes Thema verschoben: %{topic_link}"
|
||||||
|
|
||||||
topic_statuses:
|
topic_statuses:
|
||||||
archived_enabled: "Dieses Thema ist nun archiviert. Es ist eingefroren und kann in keiner Weise mehr verändert werden."
|
archived_enabled: "Dieses Thema ist nun archiviert. Es ist eingefroren und kann in keiner Weise mehr verändert werden."
|
||||||
@ -656,6 +723,7 @@ de:
|
|||||||
active: "Dein Konto ist nun freigeschaltet und einsatzbereit."
|
active: "Dein Konto ist nun freigeschaltet und einsatzbereit."
|
||||||
activate_email: "Fast fertig! Wir haben eine Aktivierungsmail an <b>%{email}</b> verschickt. Bitte folge den Anweisungen in der Mail, um Dein Konto zu aktivieren."
|
activate_email: "Fast fertig! Wir haben eine Aktivierungsmail an <b>%{email}</b> verschickt. Bitte folge den Anweisungen in der Mail, um Dein Konto zu aktivieren."
|
||||||
not_activated: "Du kannst Dich noch nicht anmelden. Wir haben Dir eine Aktivierungsmail geschickt. Bitte folge zunächst den Anweisungen aus der Mail, um Dein Konto zu aktivieren."
|
not_activated: "Du kannst Dich noch nicht anmelden. Wir haben Dir eine Aktivierungsmail geschickt. Bitte folge zunächst den Anweisungen aus der Mail, um Dein Konto zu aktivieren."
|
||||||
|
banned: "Du kannst dich bis am %{date} nicht mehr anmelden."
|
||||||
errors: "%{errors}"
|
errors: "%{errors}"
|
||||||
not_available: " Nicht verfügbar. Versuche %{suggestion}?"
|
not_available: " Nicht verfügbar. Versuche %{suggestion}?"
|
||||||
something_already_taken: "Etwas ist schief gelaufen. Möglicherweise ist der Benutzername bereits registriert. Probiere den 'Passwort vergessen'-Link."
|
something_already_taken: "Etwas ist schief gelaufen. Möglicherweise ist der Benutzername bereits registriert. Probiere den 'Passwort vergessen'-Link."
|
||||||
@ -716,6 +784,8 @@ de:
|
|||||||
|
|
||||||
Deine Freunde von %{site_name}.
|
Deine Freunde von %{site_name}.
|
||||||
|
|
||||||
|
:smile:
|
||||||
|
|
||||||
[0]: %{base_url}
|
[0]: %{base_url}
|
||||||
[1]: http://www.kitterman.com/spf/validate.html
|
[1]: http://www.kitterman.com/spf/validate.html
|
||||||
[2]: http://mxtoolbox.com/SuperTool.aspx
|
[2]: http://mxtoolbox.com/SuperTool.aspx
|
||||||
@ -728,6 +798,17 @@ de:
|
|||||||
|
|
||||||
<small>Am Fuß jeder Mail, die Du verschickst, sollte eine Möglichkeit zum Abbestellen gegeben werden. Hier ein Beispiel: Diese Mail wurde von Unternehmensname, Hauptstraße 55, 12345 Stadtname, Deutschland, versendet. Wenn Du zukünftig keine weiteren Mail erhalten möchtest, [klicke hier, um dich abzumelden][5].</small>
|
<small>Am Fuß jeder Mail, die Du verschickst, sollte eine Möglichkeit zum Abbestellen gegeben werden. Hier ein Beispiel: Diese Mail wurde von Unternehmensname, Hauptstraße 55, 12345 Stadtname, Deutschland, versendet. Wenn Du zukünftig keine weiteren Mail erhalten möchtest, [klicke hier, um dich abzumelden][5].</small>
|
||||||
|
|
||||||
|
new_version_mailer:
|
||||||
|
subject_template: "[%{site_name}] neue Version verfügbar"
|
||||||
|
text_body_template: |
|
||||||
|
Eine neue Version von Discourse ist verfügbar.
|
||||||
|
|
||||||
|
**Neue Version: %{new_version}**
|
||||||
|
|
||||||
|
Deine Version: %{installed_version}
|
||||||
|
|
||||||
|
Bitte aktuallisiere die Installation so bald wie möglich um die neusten Fehlerbehebungen und Funktionen zu erhalten.
|
||||||
|
|
||||||
system_messages:
|
system_messages:
|
||||||
post_hidden:
|
post_hidden:
|
||||||
subject_template: "Beitrag wegen Meldungen aus der Community versteckt"
|
subject_template: "Beitrag wegen Meldungen aus der Community versteckt"
|
||||||
@ -839,6 +920,15 @@ de:
|
|||||||
|
|
||||||
Weitere Hilfe findest du in unserer [FAQ](%{base_url}/faq).
|
Weitere Hilfe findest du in unserer [FAQ](%{base_url}/faq).
|
||||||
|
|
||||||
|
blocked_by_staff:
|
||||||
|
subject_template: "Konto gesperrt"
|
||||||
|
text_body_template: |
|
||||||
|
Hallo,
|
||||||
|
|
||||||
|
Dies ist eine automatische Nachricht von %{site_name} um dich zu informierenm, dass dein Konto durch einem Moderator gesperrt wurde.
|
||||||
|
|
||||||
|
Weitere Hilfe findest du in unserer [FAQ](%{base_url}/faq).
|
||||||
|
|
||||||
user_automatically_blocked:
|
user_automatically_blocked:
|
||||||
subject_template: "Benutzer %{username} wurde automatisch gesperrt"
|
subject_template: "Benutzer %{username} wurde automatisch gesperrt"
|
||||||
text_body_template: |
|
text_body_template: |
|
||||||
@ -846,6 +936,13 @@ de:
|
|||||||
|
|
||||||
Bitte [überprüfe die Beanstandungen](/admin/flags). Wenn %{username} nicht mehr gesperrt sein soll, schalte den Benutzer in der [Benuzeradministration](%{user_url}) wieder frei.
|
Bitte [überprüfe die Beanstandungen](/admin/flags). Wenn %{username} nicht mehr gesperrt sein soll, schalte den Benutzer in der [Benuzeradministration](%{user_url}) wieder frei.
|
||||||
|
|
||||||
|
spam_post_blocked:
|
||||||
|
subject_template: "Spam wirde in einem Beitrag von %{username} entdeckt"
|
||||||
|
text_body_template: |
|
||||||
|
Dies ist eine automatische Nachricht um dich zu informieren, dass [%{username}](%{user_url}) versucht hat einen Beitrag mit Links zu erstellen, was aber basierend auf der Einstellung newuser_spam_host_threshold unterbunden wurde.
|
||||||
|
|
||||||
|
Bitte [überprüfe den Benutzer](%{user_url}).
|
||||||
|
|
||||||
unblocked:
|
unblocked:
|
||||||
subject_template: "Benutzerkonto entsperrt"
|
subject_template: "Benutzerkonto entsperrt"
|
||||||
text_body_template: |
|
text_body_template: |
|
||||||
@ -855,13 +952,28 @@ de:
|
|||||||
|
|
||||||
Du kannst nun wieder Themen erstellen und Beiträge veröffentlichen.
|
Du kannst nun wieder Themen erstellen und Beiträge veröffentlichen.
|
||||||
|
|
||||||
|
pending_users_reminder:
|
||||||
|
subject_template:
|
||||||
|
one: "Es gibt einen nicht freigegebenen Benutzer"
|
||||||
|
other: "Es gibt %{count} nicht freigegebene Benutzer"
|
||||||
|
text_body_template: |
|
||||||
|
Es warten neuen Benutzer auf ihre Freigabe.
|
||||||
|
|
||||||
|
[Bitte bewerte diese im Administrationsbereich](/admin/users/list/pending).
|
||||||
|
|
||||||
unsubscribe_link: "Wenn Du diese Mails nicht mehr erhalten möchtest, verändere deine [Benutzereinstellungen](%{user_preferences_url})."
|
unsubscribe_link: "Wenn Du diese Mails nicht mehr erhalten möchtest, verändere deine [Benutzereinstellungen](%{user_preferences_url})."
|
||||||
|
|
||||||
user_notifications:
|
user_notifications:
|
||||||
|
previous_discussion: "Vorangehende Antworten"
|
||||||
unsubscribe:
|
unsubscribe:
|
||||||
title: "Mails Abbestellen"
|
title: "Mails Abbestellen"
|
||||||
description: "Nicht interessiert an diesen Mails? Kein Problem! Klicke unten um Dich abzumelden:"
|
description: "Nicht interessiert an diesen Mails? Kein Problem! Klicke unten um Dich abzumelden:"
|
||||||
|
|
||||||
|
reply_by_email: "Um zu Antworten, antworte auf diese Email oder besuche %{base_url}%{url} in deinem Browser."
|
||||||
|
visit_link_to_respond: "Um zu Antworten, besuche %{base_url}%{url} in deinem Browser."
|
||||||
|
|
||||||
|
posted_by: "Erstellt von %{username} am %{post_date}"
|
||||||
|
|
||||||
user_invited_to_private_message:
|
user_invited_to_private_message:
|
||||||
subject_template: "[%{site_name}] %{username} hat Dich zu einem privaten Gespräch eingeladen: '%{topic_title}'"
|
subject_template: "[%{site_name}] %{username} hat Dich zu einem privaten Gespräch eingeladen: '%{topic_title}'"
|
||||||
text_body_template: |
|
text_body_template: |
|
||||||
@ -872,52 +984,49 @@ de:
|
|||||||
user_replied:
|
user_replied:
|
||||||
subject_template: "[%{site_name}] %{username} hat auf deinen Beitrag '%{topic_title}' geantwortet"
|
subject_template: "[%{site_name}] %{username} hat auf deinen Beitrag '%{topic_title}' geantwortet"
|
||||||
text_body_template: |
|
text_body_template: |
|
||||||
%{username} hat auf deinen Beitrag '%{topic_title}' auf %{site_name} geantwortet:
|
|
||||||
|
|
||||||
---
|
|
||||||
%{message}
|
%{message}
|
||||||
|
|
||||||
|
%{context}
|
||||||
|
|
||||||
---
|
---
|
||||||
Um zu antworten, besuche den folgenden Link: %{base_url}%{url}
|
%{respond_instructions}
|
||||||
|
|
||||||
user_quoted:
|
user_quoted:
|
||||||
subject_template: "[%{site_name}] %{username} hat Dich in '%{topic_title}' zitiert"
|
subject_template: "[%{site_name}] %{username} hat Dich in '%{topic_title}' zitiert"
|
||||||
text_body_template: |
|
text_body_template: |
|
||||||
%{username} hat Dich in '%{topic_title}' auf %{site_name} zitiert:
|
|
||||||
|
|
||||||
---
|
|
||||||
%{message}
|
%{message}
|
||||||
|
|
||||||
|
%{context}
|
||||||
|
|
||||||
---
|
---
|
||||||
Um zu antworten, besuche den folgenden Link: %{base_url}%{url}
|
%{respond_instructions}
|
||||||
|
|
||||||
user_mentioned:
|
user_mentioned:
|
||||||
subject_template: "[%{site_name}] %{username} hat Dich in '%{topic_title}' erwähnt"
|
subject_template: "[%{site_name}] %{username} hat Dich in '%{topic_title}' erwähnt"
|
||||||
text_body_template: |
|
text_body_template: |
|
||||||
%{username} hat Dich in '%{topic_title}' auf %{site_name} erwähnt:
|
|
||||||
|
|
||||||
---
|
|
||||||
%{message}
|
%{message}
|
||||||
|
|
||||||
|
%{context}
|
||||||
|
|
||||||
---
|
---
|
||||||
Um zu antworten, besuche den folgenden Link: %{base_url}%{url}
|
%{respond_instructions}
|
||||||
|
|
||||||
user_posted:
|
user_posted:
|
||||||
subject_template: "[%{site_name}] %{subject_prefix}%{username} hat auf '%{topic_title}' geantwortet"
|
subject_template: "[%{site_name}] %{subject_prefix}%{username} hat auf '%{topic_title}' geantwortet"
|
||||||
text_body_template: |
|
text_body_template: |
|
||||||
%{username} hat in '%{topic_title}' auf %{site_name} geantwortet:
|
|
||||||
|
|
||||||
---
|
|
||||||
%{message}
|
%{message}
|
||||||
|
|
||||||
|
%{context}
|
||||||
|
|
||||||
---
|
---
|
||||||
Um zu antworten, besuche den folgenden Link: %{base_url}%{url}
|
%{respond_instructions}
|
||||||
|
|
||||||
digest:
|
digest:
|
||||||
why: "Hier eine kurze Zusammenfassung, was auf %{site_link} passiert ist, seit Du das letzte Mal am %{last_seen_at} da warst."
|
why: "Hier eine kurze Zusammenfassung, was auf %{site_link} passiert ist, seit Du das letzte Mal am %{last_seen_at} da warst."
|
||||||
subject_template: "[%{site_name}] Forenaktivität für den %{date}"
|
subject_template: "[%{site_name}] Forenaktivität für den %{date}"
|
||||||
new_activity: "Neues in deinen Themen und Beiträgen:"
|
new_activity: "Neues in deinen Themen und Beiträgen:"
|
||||||
top_topics: "Inhalte die dich vielleicht interessieren:"
|
top_topics: "Inhalte die dich vielleicht interessieren:"
|
||||||
|
other_new_topics: "Andere neue Themen:"
|
||||||
unsubscribe: "Diese Zusammenfassung wurde Dir von %{site_link} geschickt, damit Du auf dem Laufenden bleibst, und weil wir nicht eine Weile nicht begrüßen durften.\nWenn Du diese Benachrichtigungen nicht mehr erhalten möchtest, kannst Du sie in deinen Maileinstellungen abschalten: %{unsubscribe_link}."
|
unsubscribe: "Diese Zusammenfassung wurde Dir von %{site_link} geschickt, damit Du auf dem Laufenden bleibst, und weil wir nicht eine Weile nicht begrüßen durften.\nWenn Du diese Benachrichtigungen nicht mehr erhalten möchtest, kannst Du sie in deinen Maileinstellungen abschalten: %{unsubscribe_link}."
|
||||||
click_here: "klicke hier"
|
click_here: "klicke hier"
|
||||||
from: "%{site_name} Übersicht"
|
from: "%{site_name} Übersicht"
|
||||||
@ -992,8 +1101,12 @@ de:
|
|||||||
deleted: 'gelöscht'
|
deleted: 'gelöscht'
|
||||||
|
|
||||||
upload:
|
upload:
|
||||||
pasted_image_filename: ""
|
unauthorized: "Entschuldige, die Datei die du hochladen möchtest ist nicht erlaubt (Erlaubte Dateiendungen: %{authorized_extensions})."
|
||||||
|
pasted_image_filename: "Hinzugefügtes Bild"
|
||||||
|
attachments:
|
||||||
|
too_large: "Entschuldige, die Datei die du hochladen möchtest ist zu gross (Maximale Dateigrösse ist %{max_size_kb}%kb)."
|
||||||
images:
|
images:
|
||||||
fetch_failure: "Entschuldige, beim Laden des Bildes ist ein Fehler aufgetreten."
|
too_large: "Entschuldige, das Bild welches du hochladen möchtest ist zu gross (Maximale Dateigrösse ist %{max_size_kb}%kb), bitte verkleinere es und versuche es nochmals."
|
||||||
|
fetch_failure: "Sorry, there has been an error while fetching the image."
|
||||||
unknown_image_type: "Entschuldige, aber die Datei die Du hochladen möchtest schein kein Bild zu sein."
|
unknown_image_type: "Entschuldige, aber die Datei die Du hochladen möchtest schein kein Bild zu sein."
|
||||||
size_not_found: "Entschuldige, aber wir konnten die Grösse des Bildes nicht feststellen. Vielleicht ist das Bild defekt?"
|
size_not_found: "Entschuldige, aber wir konnten die Grösse des Bildes nicht feststellen. Vielleicht ist das Bild defekt?"
|
||||||
|
@ -94,13 +94,13 @@ en:
|
|||||||
For more guidance, [see our FAQ](/faq). This panel will only appear for your first %{education_posts_text}.
|
For more guidance, [see our FAQ](/faq). This panel will only appear for your first %{education_posts_text}.
|
||||||
|
|
||||||
'new-reply': |
|
'new-reply': |
|
||||||
Welcome to %{site_name} — **thanks for contributing to the conversation!**
|
Welcome to %{site_name} — **thanks for contributing!**
|
||||||
|
|
||||||
- Does your reply improve the conversation in some way?
|
- Does your reply improve the conversation in some way?
|
||||||
|
|
||||||
- Be kind to your fellow community members.
|
- Be kind to your fellow community members.
|
||||||
|
|
||||||
- Constructive criticism is welcome, but remember to criticize *ideas*, not people.
|
- Constructive criticism is welcome, but criticize *ideas*, not people.
|
||||||
|
|
||||||
For more guidance, [see our FAQ](/faq). This panel will only appear for your first %{education_posts_text}.
|
For more guidance, [see our FAQ](/faq). This panel will only appear for your first %{education_posts_text}.
|
||||||
|
|
||||||
@ -611,6 +611,8 @@ en:
|
|||||||
regular_requires_likes_given: "How many likes a basic user must cast before promotion to regular (2) trust level"
|
regular_requires_likes_given: "How many likes a basic user must cast before promotion to regular (2) trust level"
|
||||||
regular_requires_topic_reply_count: "How many topics a basic user must reply to before promotion to regular (2) trust level"
|
regular_requires_topic_reply_count: "How many topics a basic user must reply to before promotion to regular (2) trust level"
|
||||||
|
|
||||||
|
min_trust_to_create_topic: "The minimum trust level required to create a new topic."
|
||||||
|
|
||||||
newuser_max_links: "How many links a new user can add to a post"
|
newuser_max_links: "How many links a new user can add to a post"
|
||||||
newuser_max_images: "How many images a new user can add to a post"
|
newuser_max_images: "How many images a new user can add to a post"
|
||||||
newuser_max_attachments: "How many attachments a new user can add to a post"
|
newuser_max_attachments: "How many attachments a new user can add to a post"
|
||||||
@ -937,9 +939,9 @@ en:
|
|||||||
Please [review the flags](/admin/flags). If %{username} was incorrectly blocked from posting, click the unblock button on [the admin page for this user](%{user_url}).
|
Please [review the flags](/admin/flags). If %{username} was incorrectly blocked from posting, click the unblock button on [the admin page for this user](%{user_url}).
|
||||||
|
|
||||||
spam_post_blocked:
|
spam_post_blocked:
|
||||||
subject_template: "Spam was detected in a post by %{username}"
|
subject_template: "New user %{username} is posting repeated links"
|
||||||
text_body_template: |
|
text_body_template: |
|
||||||
This is an automated message to inform you that [%{username}](%{user_url}) tried to make a post with links, but it was stopped as spam based on the newuser_spam_host_threshold site setting.
|
This is an automated message to inform you that the new user [%{username}](%{user_url}) tried to create multiple posts with links to the same domain, but they were blocked based on the newuser_spam_host_threshold site setting.
|
||||||
|
|
||||||
Please [review the user](%{user_url}).
|
Please [review the user](%{user_url}).
|
||||||
|
|
||||||
@ -1027,7 +1029,7 @@ en:
|
|||||||
new_activity: "New activity on your topics and posts:"
|
new_activity: "New activity on your topics and posts:"
|
||||||
top_topics: "Recent posts the community enjoyed:"
|
top_topics: "Recent posts the community enjoyed:"
|
||||||
other_new_topics: "Other New Topics:"
|
other_new_topics: "Other New Topics:"
|
||||||
unsubscribe: "This summary email is sent as a courtesy notification from %{site_link} when we haven't seen you in a while.\nTo unsubscribe or change your email preferences, %{unsubscribe_link}."
|
unsubscribe: "This summary email is sent as a courtesy notification from %{site_link} when we haven't seen you in a while. To unsubscribe or change your email preferences, %{unsubscribe_link}."
|
||||||
click_here: "click here"
|
click_here: "click here"
|
||||||
from: "%{site_name} digest"
|
from: "%{site_name} digest"
|
||||||
read_more: "Read More"
|
read_more: "Read More"
|
||||||
|
@ -47,13 +47,13 @@ id:
|
|||||||
For more guidance, [see our FAQ](/faq). This panel will only appear for your first %{education_posts_text}.
|
For more guidance, [see our FAQ](/faq). This panel will only appear for your first %{education_posts_text}.
|
||||||
|
|
||||||
'new-reply': |
|
'new-reply': |
|
||||||
Welcome to %{site_name} — **thanks for contributing to the conversation!**
|
Welcome to %{site_name} — **thanks for contributing!**
|
||||||
|
|
||||||
- Does your reply improve the conversation in some way?
|
- Does your reply improve the conversation in some way?
|
||||||
|
|
||||||
- Be kind to your fellow community members.
|
- Be kind to your fellow community members.
|
||||||
|
|
||||||
- Constructive criticism is welcome, but remember to criticize *ideas*, not people.
|
- Constructive criticism is welcome, but criticize *ideas*, not people.
|
||||||
|
|
||||||
For more guidance, [see our FAQ](/faq). This panel will only appear for your first %{education_posts_text}.
|
For more guidance, [see our FAQ](/faq). This panel will only appear for your first %{education_posts_text}.
|
||||||
|
|
||||||
@ -725,7 +725,7 @@ id:
|
|||||||
subject_template: "[%{site_name}] Forum Activity for %{date}"
|
subject_template: "[%{site_name}] Forum Activity for %{date}"
|
||||||
new_activity: "New activity on your topics and posts:"
|
new_activity: "New activity on your topics and posts:"
|
||||||
new_topics: "New topics:"
|
new_topics: "New topics:"
|
||||||
unsubscribe: "This summary email is sent as a courtesy notification from %{site_link} when we haven't seen you in a while.\nTo unsubscribe or change your email preferences, %{unsubscribe_link}."
|
unsubscribe: "This summary email is sent as a courtesy notification from %{site_link} when we haven't seen you in a while. To unsubscribe or change your email preferences, %{unsubscribe_link}."
|
||||||
click_here: "click here"
|
click_here: "click here"
|
||||||
from: "%{site_name} digest"
|
from: "%{site_name} digest"
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ ko:
|
|||||||
For more guidance, [see our FAQ](/faq). This panel will only appear for your first %{education_posts_text}.
|
For more guidance, [see our FAQ](/faq). This panel will only appear for your first %{education_posts_text}.
|
||||||
|
|
||||||
'new-reply': |
|
'new-reply': |
|
||||||
Welcome to %{site_name} — **thanks for contributing to the conversation!**
|
Welcome to %{site_name} — **thanks for contributing!**
|
||||||
|
|
||||||
Keep in mind as you compose your reply:
|
Keep in mind as you compose your reply:
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ ko:
|
|||||||
|
|
||||||
- Be kind to your fellow community members.
|
- Be kind to your fellow community members.
|
||||||
|
|
||||||
- Constructive criticism is welcome, but remember to criticize *ideas*, not people.
|
- Constructive criticism is welcome, but criticize *ideas*, not people.
|
||||||
|
|
||||||
For more guidance, [see our FAQ](/faq). This panel will only appear for your first %{education_posts_text}.
|
For more guidance, [see our FAQ](/faq). This panel will only appear for your first %{education_posts_text}.
|
||||||
|
|
||||||
@ -870,7 +870,7 @@ ko:
|
|||||||
subject_template: "[%{site_name}] Forum Activity for %{date}"
|
subject_template: "[%{site_name}] Forum Activity for %{date}"
|
||||||
new_activity: "New activity on your topics and posts:"
|
new_activity: "New activity on your topics and posts:"
|
||||||
new_topics: "New topics:"
|
new_topics: "New topics:"
|
||||||
unsubscribe: "This summary email is sent as a courtesy notification from %{site_link} when we haven't seen you in a while.\nTo unsubscribe or change your email preferences, %{unsubscribe_link}."
|
unsubscribe: "This summary email is sent as a courtesy notification from %{site_link} when we haven't seen you in a while. To unsubscribe or change your email preferences, %{unsubscribe_link}."
|
||||||
click_here: "click here"
|
click_here: "click here"
|
||||||
from: "%{site_name} digest"
|
from: "%{site_name} digest"
|
||||||
|
|
||||||
|
@ -532,6 +532,7 @@ ru:
|
|||||||
edit_history_visible_to_public: Позволить всем видеть предыдущие версии сообщения. Когда отключено, историю изменений может видеть только персонал.
|
edit_history_visible_to_public: Позволить всем видеть предыдущие версии сообщения. Когда отключено, историю изменений может видеть только персонал.
|
||||||
delete_removed_posts_after: Количество часов, после которого сообщение, удаленное пользователем, удаляется.
|
delete_removed_posts_after: Количество часов, после которого сообщение, удаленное пользователем, удаляется.
|
||||||
max_image_width: Максимальная ширина изображений, добавляемых в сообщение
|
max_image_width: Максимальная ширина изображений, добавляемых в сообщение
|
||||||
|
max_image_height: Максимальная высота изображения в сообщении
|
||||||
category_featured_topics: Количество отображаемых тем в категориях на странице /categories
|
category_featured_topics: Количество отображаемых тем в категориях на странице /categories
|
||||||
add_rel_nofollow_to_user_content: 'Добавить "rel nofollow" для всех ссылок за исключением внутренних (включая родительский домен). Изменение данной настройки потребует обновления всех сообщений (<code>rake posts:rebake</code>)'
|
add_rel_nofollow_to_user_content: 'Добавить "rel nofollow" для всех ссылок за исключением внутренних (включая родительский домен). Изменение данной настройки потребует обновления всех сообщений (<code>rake posts:rebake</code>)'
|
||||||
exclude_rel_nofollow_domains: Разделенный запятыми список доменов, в которых nofollow не добавлено (tld.com автоматически позволит также и sub.tld.com)
|
exclude_rel_nofollow_domains: Разделенный запятыми список доменов, в которых nofollow не добавлено (tld.com автоматически позволит также и sub.tld.com)
|
||||||
@ -941,11 +942,11 @@ ru:
|
|||||||
Пожалуйста [проверьте жалобы](/admin/flags). Если пользователь %{username} был заблокирован неверно, нажмите кнопку разблокировки [на странице управления пользователем](%{user_url}).
|
Пожалуйста [проверьте жалобы](/admin/flags). Если пользователь %{username} был заблокирован неверно, нажмите кнопку разблокировки [на странице управления пользователем](%{user_url}).
|
||||||
|
|
||||||
spam_post_blocked:
|
spam_post_blocked:
|
||||||
subject_template: 'В сообщении пользователя %{username} обнаружен спам'
|
subject_template: 'Новый пользователь %{username} отправляет одинаковые ссылки'
|
||||||
text_body_template: |
|
text_body_template: |
|
||||||
Это автоматическое сообщение для информирования о том, что пользователь [%{username}](%{user_url}) попытался создать сообщение со ссылками, но был остановлен политикой антиспама на основе настройки сайта newuser_spam_host_threshold.
|
Это автоматическое сообщение. Новый пользователь [%{username}](%{user_url}) попытался создать множество сообщений со ссылкой на один и тот же домен, однако был заблокирован на основании настройки newuser_spam_host_threshold.
|
||||||
|
|
||||||
Пожалуйста [проверьте действия пользователя](%{user_url}).
|
Пожалуйста [проверьте блокировку](%{user_url}).
|
||||||
|
|
||||||
unblocked:
|
unblocked:
|
||||||
subject_template: Учетная запись разблокирована
|
subject_template: Учетная запись разблокирована
|
||||||
@ -954,6 +955,17 @@ ru:
|
|||||||
|
|
||||||
Это автоматическое сообщение сайта %{site_name}. Ваш аккаунт был разблокирован. Теперь вы можете создавать новые темы и отвечать в них.
|
Это автоматическое сообщение сайта %{site_name}. Ваш аккаунт был разблокирован. Теперь вы можете создавать новые темы и отвечать в них.
|
||||||
|
|
||||||
|
pending_users_reminder:
|
||||||
|
subject_template:
|
||||||
|
one: Один неутвержденный пользователь
|
||||||
|
other: '%{count} неутвержденных пользователей'
|
||||||
|
few: '%{count} неутвержденных пользователя'
|
||||||
|
many: '%{count} неутвержденных пользователей'
|
||||||
|
text_body_template: |
|
||||||
|
Новые пользователи ожидают утверждения.
|
||||||
|
|
||||||
|
[Пожалуйста, проверьте их список в секции администрирования](/admin/users/list/pending).
|
||||||
|
|
||||||
unsubscribe_link: 'Для того, чтобы отписаться от подобных сообщений, перейдите в [настройки профиля](%{user_preferences_url}).'
|
unsubscribe_link: 'Для того, чтобы отписаться от подобных сообщений, перейдите в [настройки профиля](%{user_preferences_url}).'
|
||||||
user_notifications:
|
user_notifications:
|
||||||
previous_discussion: Предыдущие ответы
|
previous_discussion: Предыдущие ответы
|
||||||
@ -1016,9 +1028,7 @@ ru:
|
|||||||
new_activity: 'Новая активность в ваших темах и сообщениях:'
|
new_activity: 'Новая активность в ваших темах и сообщениях:'
|
||||||
top_topics: 'Последние темы, которые были оценены пользователями форума:'
|
top_topics: 'Последние темы, которые были оценены пользователями форума:'
|
||||||
other_new_topics: 'Другие новые темы:'
|
other_new_topics: 'Другие новые темы:'
|
||||||
unsubscribe: |
|
unsubscribe: 'Данное сообщение отправлено как напоминание с сайта %{site_link} потому что вы давно не заходили к нам. Для того, чтобы отписаться от наших сообщений, пройдите по ссылке %{unsubscribe_link}.'
|
||||||
Данное сообщение отправлено как напоминание с сайта %{site_link} потому что вы давно не заходили к нам.
|
|
||||||
Для того, чтобы отписаться от наших сообщений, пройдите по ссылке %{unsubscribe_link}.
|
|
||||||
click_here: нажмите здесь
|
click_here: нажмите здесь
|
||||||
from: 'Cводка новостей сайта %{site_name}'
|
from: 'Cводка новостей сайта %{site_name}'
|
||||||
read_more: Читать еще
|
read_more: Читать еще
|
||||||
|
@ -783,7 +783,7 @@ sv:
|
|||||||
subject_template: "[%{site_name}] Forum Activity for %{date}"
|
subject_template: "[%{site_name}] Forum Activity for %{date}"
|
||||||
new_activity: "New activity on your topics and posts:"
|
new_activity: "New activity on your topics and posts:"
|
||||||
new_topics: "New topics:"
|
new_topics: "New topics:"
|
||||||
unsubscribe: "This summary email is sent as a courtesy notification from %{site_link} when we haven't seen you in a while.\nTo unsubscribe or change your email preferences, %{unsubscribe_link}."
|
unsubscribe: "This summary email is sent as a courtesy notification from %{site_link} when we haven't seen you in a while. To unsubscribe or change your email preferences, %{unsubscribe_link}."
|
||||||
click_here: "click here"
|
click_here: "click here"
|
||||||
from: "%{site_name} digest"
|
from: "%{site_name} digest"
|
||||||
|
|
||||||
|
@ -216,6 +216,7 @@ Discourse::Application.routes.draw do
|
|||||||
get 'topics/created-by/:username' => 'list#topics_by', as: 'topics_by', constraints: {username: USERNAME_ROUTE_FORMAT}
|
get 'topics/created-by/:username' => 'list#topics_by', as: 'topics_by', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||||
get 'topics/private-messages/:username' => 'list#private_messages', as: 'topics_private_messages', constraints: {username: USERNAME_ROUTE_FORMAT}
|
get 'topics/private-messages/:username' => 'list#private_messages', as: 'topics_private_messages', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||||
get 'topics/private-messages-sent/:username' => 'list#private_messages_sent', as: 'topics_private_messages_sent', constraints: {username: USERNAME_ROUTE_FORMAT}
|
get 'topics/private-messages-sent/:username' => 'list#private_messages_sent', as: 'topics_private_messages_sent', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||||
|
get 'topics/private-messages-unread/:username' => 'list#private_messages_unread', as: 'topics_private_messages_unread', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||||
|
|
||||||
# Topic routes
|
# Topic routes
|
||||||
get 't/:slug/:topic_id/wordpress' => 'topics#wordpress', constraints: {topic_id: /\d+/}
|
get 't/:slug/:topic_id/wordpress' => 'topics#wordpress', constraints: {topic_id: /\d+/}
|
||||||
|
19
db/migrate/20130828192526_fix_optimized_images_urls.rb
Normal file
19
db/migrate/20130828192526_fix_optimized_images_urls.rb
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
class FixOptimizedImagesUrls < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
# `AddUrlToOptimizedImages` was wrongly computing the URLs. This fixes it!
|
||||||
|
execute "UPDATE optimized_images
|
||||||
|
SET url = substring(oi.url from '^\\/uploads\\/[^/]+\\/_optimized\\/[0-9a-f]{3}\\/[0-9a-f]{3}\\/[0-9a-f]{11}')
|
||||||
|
|| '_'
|
||||||
|
|| oi.width
|
||||||
|
|| 'x'
|
||||||
|
|| oi.height
|
||||||
|
|| substring(oi.url from '\\.\\w{3,4}$')
|
||||||
|
FROM optimized_images oi
|
||||||
|
WHERE optimized_images.id = oi.id
|
||||||
|
AND oi.url ~ '^\\/uploads\\/[^/]+\\/_optimized\\/[0-9a-f]{3}\\/[0-9a-f]{3}\\/[0-9a-f]{11}\\.';"
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
raise ActiveRecord::IrreversibleMigration
|
||||||
|
end
|
||||||
|
end
|
12
db/migrate/20130903154323_allow_null_user_id_on_posts.rb
Normal file
12
db/migrate/20130903154323_allow_null_user_id_on_posts.rb
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
class AllowNullUserIdOnPosts < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
change_column :posts, :user_id, :integer, null: true
|
||||||
|
execute "UPDATE posts SET user_id = NULL WHERE nuked_user = true"
|
||||||
|
remove_column :posts, :nuked_user
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
add_column :posts, :nuked_user, :boolean, default: false
|
||||||
|
change_column :posts, :user_id, :integer, null: false
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,9 @@
|
|||||||
|
class AllowNullUserIdOnTopics < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
change_column :topics, :user_id, :integer, null: true
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
change_column :topics, :user_id, :integer, null: false
|
||||||
|
end
|
||||||
|
end
|
@ -19,11 +19,9 @@ Note: If you are developing on a Mac, you will probably want to look at [these i
|
|||||||
## Before you start Rails
|
## Before you start Rails
|
||||||
|
|
||||||
1. `bundle install`
|
1. `bundle install`
|
||||||
2. `bundle exec rake db:migrate`
|
2. `bundle exec rake db:migrate db:test:prepare db:seed_fu`
|
||||||
3. `bundle exec rake db:test:prepare`
|
4. Try running the specs: `bundle exec rake autospec`
|
||||||
4. `bundle exec rake db:seed_fu`
|
5. `bundle exec rails server`
|
||||||
5. Try running the specs: `bundle exec rake autospec`
|
|
||||||
6. `bundle exec rails server`
|
|
||||||
|
|
||||||
You should now be able to connect to rails on [http://localhost:3000](http://localhost:3000) - try it out! The seed data includes a pinned topic that explains how to get an admin account, so start there! Happy hacking!
|
You should now be able to connect to rails on [http://localhost:3000](http://localhost:3000) - try it out! The seed data includes a pinned topic that explains how to get an admin account, so start there! Happy hacking!
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ If you have a mail server responsible for handling the egress of email from your
|
|||||||
Install necessary packages:
|
Install necessary packages:
|
||||||
|
|
||||||
# Run these commands as your normal login (e.g. "michael")
|
# Run these commands as your normal login (e.g. "michael")
|
||||||
sudo apt-get -y install build-essential libssl-dev libyaml-dev git libtool libxslt-dev libxml2-dev libpq-dev gawk curl pngcrush python-software-properties
|
sudo apt-get -y install build-essential libssl-dev libyaml-dev git libtool libxslt-dev libxml2-dev libpq-dev gawk curl pngcrush imagemagick python-software-properties
|
||||||
|
|
||||||
# If you're on Ubuntu >= 12.10, change:
|
# If you're on Ubuntu >= 12.10, change:
|
||||||
# python-software-properties to software-properties-common
|
# python-software-properties to software-properties-common
|
||||||
@ -187,7 +187,6 @@ Edit /var/www/discourse/config/discourse.pill
|
|||||||
|
|
||||||
- change application name from 'discourse' if necessary
|
- change application name from 'discourse' if necessary
|
||||||
- Ensure appropriate Bluepill.application line is uncommented
|
- Ensure appropriate Bluepill.application line is uncommented
|
||||||
- search for "host to run on" and change to current hostname
|
|
||||||
|
|
||||||
Edit /var/www/discourse/config/environments/production.rb
|
Edit /var/www/discourse/config/environments/production.rb
|
||||||
- browse througn all the settings
|
- browse througn all the settings
|
||||||
|
@ -7,3 +7,4 @@ require_dependency 'auth/open_id_authenticator'
|
|||||||
require_dependency 'auth/github_authenticator'
|
require_dependency 'auth/github_authenticator'
|
||||||
require_dependency 'auth/twitter_authenticator'
|
require_dependency 'auth/twitter_authenticator'
|
||||||
require_dependency 'auth/persona_authenticator'
|
require_dependency 'auth/persona_authenticator'
|
||||||
|
require_dependency 'auth/cas_authenticator'
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
class Auth::Result
|
class Auth::Result
|
||||||
attr_accessor :user, :name, :username, :email, :user,
|
attr_accessor :user, :name, :username, :email, :user,
|
||||||
:email_valid, :extra_data, :awaiting_activation,
|
:email_valid, :extra_data, :awaiting_activation,
|
||||||
:awaiting_approval, :authenticated, :authenticator_name
|
:awaiting_approval, :authenticated, :authenticator_name,
|
||||||
|
:requires_invite
|
||||||
|
|
||||||
def session_data
|
def session_data
|
||||||
{
|
{
|
||||||
@ -15,7 +16,9 @@ class Auth::Result
|
|||||||
end
|
end
|
||||||
|
|
||||||
def to_client_hash
|
def to_client_hash
|
||||||
if user
|
if requires_invite
|
||||||
|
{ requires_invite: true }
|
||||||
|
elsif user
|
||||||
{
|
{
|
||||||
authenticated: !!authenticated,
|
authenticated: !!authenticated,
|
||||||
awaiting_activation: !!awaiting_activation,
|
awaiting_activation: !!awaiting_activation,
|
||||||
|
@ -110,4 +110,16 @@ module DiscourseHub
|
|||||||
def self.accepts
|
def self.accepts
|
||||||
[:json, 'application/vnd.discoursehub.v1']
|
[:json, 'application/vnd.discoursehub.v1']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.nickname_operation
|
||||||
|
if SiteSetting.call_discourse_hub?
|
||||||
|
begin
|
||||||
|
yield
|
||||||
|
rescue DiscourseHub::NicknameUnavailable
|
||||||
|
false
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.error e.message + "\n" + e.backtrace.join("\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -2,13 +2,14 @@ require 'digest/sha1'
|
|||||||
require 'open-uri'
|
require 'open-uri'
|
||||||
|
|
||||||
class S3Store
|
class S3Store
|
||||||
|
@fog_loaded ||= require 'fog'
|
||||||
|
|
||||||
def store_upload(file, upload)
|
def store_upload(file, upload)
|
||||||
# <id><sha1><extension>
|
# <id><sha1><extension>
|
||||||
path = "#{upload.id}#{upload.sha1}#{upload.extension}"
|
path = "#{upload.id}#{upload.sha1}#{upload.extension}"
|
||||||
|
|
||||||
# if this fails, it will throw an exception
|
# if this fails, it will throw an exception
|
||||||
upload(file.tempfile, path, file.content_type)
|
upload(file.tempfile, path, upload.original_filename, file.content_type)
|
||||||
|
|
||||||
# returns the url of the uploaded file
|
# returns the url of the uploaded file
|
||||||
"#{absolute_base_url}/#{path}"
|
"#{absolute_base_url}/#{path}"
|
||||||
@ -58,9 +59,7 @@ class S3Store
|
|||||||
end
|
end
|
||||||
|
|
||||||
def remove_file(url)
|
def remove_file(url)
|
||||||
return unless has_been_uploaded?(url)
|
remove File.basename(url) if has_been_uploaded?(url)
|
||||||
name = File.basename(url)
|
|
||||||
remove(name)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_been_uploaded?(url)
|
def has_been_uploaded?(url)
|
||||||
@ -102,19 +101,17 @@ class S3Store
|
|||||||
raise Discourse::SiteSettingMissing.new("s3_secret_access_key") if SiteSetting.s3_secret_access_key.blank?
|
raise Discourse::SiteSettingMissing.new("s3_secret_access_key") if SiteSetting.s3_secret_access_key.blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_or_create_directory(name)
|
def get_or_create_directory(bucket)
|
||||||
check_missing_site_settings
|
check_missing_site_settings
|
||||||
|
|
||||||
@fog_loaded ||= require 'fog'
|
fog = Fog::Storage.new s3_options
|
||||||
|
|
||||||
fog = Fog::Storage.new generate_options
|
directory = fog.directories.get(bucket)
|
||||||
|
directory = fog.directories.create(key: bucket) unless directory
|
||||||
directory = fog.directories.get(name)
|
|
||||||
directory = fog.directories.create(key: name) unless directory
|
|
||||||
directory
|
directory
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_options
|
def s3_options
|
||||||
options = {
|
options = {
|
||||||
provider: 'AWS',
|
provider: 'AWS',
|
||||||
aws_access_key_id: SiteSetting.s3_access_key_id,
|
aws_access_key_id: SiteSetting.s3_access_key_id,
|
||||||
@ -124,22 +121,21 @@ class S3Store
|
|||||||
options
|
options
|
||||||
end
|
end
|
||||||
|
|
||||||
def upload(file, name, content_type=nil)
|
def upload(file, unique_filename, filename=nil, content_type=nil)
|
||||||
args = {
|
args = {
|
||||||
key: name,
|
key: unique_filename,
|
||||||
public: true,
|
public: true,
|
||||||
body: file,
|
body: file
|
||||||
}
|
}
|
||||||
|
args[:content_disposition] = "attachment; filename=\"#{filename}\"" if filename
|
||||||
args[:content_type] = content_type if content_type
|
args[:content_type] = content_type if content_type
|
||||||
directory.files.create(args)
|
|
||||||
|
get_or_create_directory(s3_bucket).files.create(args)
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove(name)
|
def remove(unique_filename)
|
||||||
directory.files.destroy(key: name)
|
fog = Fog::Storage.new s3_options
|
||||||
end
|
fog.delete_object(s3_bucket, unique_filename)
|
||||||
|
|
||||||
def directory
|
|
||||||
get_or_create_directory(s3_bucket)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
33
lib/freedom_patches/ar_result.rb
Normal file
33
lib/freedom_patches/ar_result.rb
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#see: https://github.com/rails/rails/pull/12065
|
||||||
|
if rails4?
|
||||||
|
module ActiveRecord
|
||||||
|
class Result
|
||||||
|
private
|
||||||
|
def hash_rows
|
||||||
|
@hash_rows ||=
|
||||||
|
begin
|
||||||
|
# We freeze the strings to prevent them getting duped when
|
||||||
|
# used as keys in ActiveRecord::Base's @attributes hash
|
||||||
|
columns = @columns.map { |c| c.dup.freeze }
|
||||||
|
@rows.map { |row|
|
||||||
|
# In the past we used Hash[columns.zip(row)]
|
||||||
|
# though elegant, the verbose way is much more efficient
|
||||||
|
# both time and memory wise cause it avoids a big array allocation
|
||||||
|
# this method is called a lot and needs to be micro optimised
|
||||||
|
hash = {}
|
||||||
|
|
||||||
|
index = 0
|
||||||
|
length = columns.length
|
||||||
|
|
||||||
|
while index < length
|
||||||
|
hash[columns[index]] = row[index]
|
||||||
|
index += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
hash
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
8
lib/freedom_patches/arel_patch.rb
Normal file
8
lib/freedom_patches/arel_patch.rb
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
if rails4?
|
||||||
|
# https://github.com/rails/arel/pull/206
|
||||||
|
class Arel::Table
|
||||||
|
def hash
|
||||||
|
@name.hash
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -9,6 +9,7 @@ class Guardian
|
|||||||
def secure_category_ids; []; end
|
def secure_category_ids; []; end
|
||||||
def topic_create_allowed_category_ids; []; end
|
def topic_create_allowed_category_ids; []; end
|
||||||
def has_trust_level?(level); false; end
|
def has_trust_level?(level); false; end
|
||||||
|
def email; nil; end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(user=nil)
|
def initialize(user=nil)
|
||||||
@ -36,6 +37,13 @@ class Guardian
|
|||||||
@user.staff?
|
@user.staff?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def is_developer?
|
||||||
|
@user &&
|
||||||
|
is_admin? &&
|
||||||
|
Rails.configuration.respond_to?(:developer_emails) &&
|
||||||
|
Rails.configuration.developer_emails.include?(@user.email)
|
||||||
|
end
|
||||||
|
|
||||||
# Can the user see the object?
|
# Can the user see the object?
|
||||||
def can_see?(obj)
|
def can_see?(obj)
|
||||||
if obj
|
if obj
|
||||||
@ -89,8 +97,8 @@ class Guardian
|
|||||||
# You must be an admin to impersonate
|
# You must be an admin to impersonate
|
||||||
is_admin? &&
|
is_admin? &&
|
||||||
|
|
||||||
# You may not impersonate other admins
|
# You may not impersonate other admins unless you are a dev
|
||||||
not(target.admin?)
|
(!target.admin? || is_developer?)
|
||||||
|
|
||||||
# Additionally, you may not impersonate yourself;
|
# Additionally, you may not impersonate yourself;
|
||||||
# but the two tests for different admin statuses
|
# but the two tests for different admin statuses
|
||||||
@ -229,11 +237,11 @@ class Guardian
|
|||||||
end
|
end
|
||||||
|
|
||||||
def can_create_topic?(parent)
|
def can_create_topic?(parent)
|
||||||
can_create_post?(parent)
|
user && user.trust_level >= SiteSetting.min_trust_to_create_topic.to_i && can_create_post?(parent)
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_create_topic_on_category?(category)
|
def can_create_topic_on_category?(category)
|
||||||
can_create_post?(nil) && (
|
can_create_topic?(nil) && (
|
||||||
!category ||
|
!category ||
|
||||||
Category.topic_create_allowed(self).where(:id => category.id).count == 1
|
Category.topic_create_allowed(self).where(:id => category.id).count == 1
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
module Jobs
|
module Jobs
|
||||||
class DashboardStats < Jobs::Scheduled
|
class DashboardStats < Jobs::Scheduled
|
||||||
recurrence { minutely(AdminDashboardData.recalculate_interval.minutes) }
|
recurrence { hourly.minute_of_hour(0,30) }
|
||||||
|
|
||||||
def execute(args)
|
def execute(args)
|
||||||
stats_json = AdminDashboardData.fetch_stats.as_json
|
stats_json = AdminDashboardData.fetch_stats.as_json
|
||||||
@ -13,4 +13,4 @@ module Jobs
|
|||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -5,15 +5,14 @@ module Jobs
|
|||||||
recurrence { daily.hour_of_day(6) }
|
recurrence { daily.hour_of_day(6) }
|
||||||
|
|
||||||
def execute(args)
|
def execute(args)
|
||||||
target_users.each do |u|
|
target_user_ids.each do |user_id|
|
||||||
Jobs.enqueue(:user_email, type: :digest, user_id: u.id)
|
Jobs.enqueue(:user_email, type: :digest, user_id: user_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def target_users
|
def target_user_ids
|
||||||
# Users who want to receive emails and haven't been emailed in the last day
|
# Users who want to receive emails and haven't been emailed in the last day
|
||||||
query = User.select(:id)
|
query = User.where(email_digests: true, active: true)
|
||||||
.where(email_digests: true, active: true)
|
|
||||||
.where("COALESCE(last_emailed_at, '2010-01-01') <= CURRENT_TIMESTAMP - ('1 DAY'::INTERVAL * digest_after_days)")
|
.where("COALESCE(last_emailed_at, '2010-01-01') <= CURRENT_TIMESTAMP - ('1 DAY'::INTERVAL * digest_after_days)")
|
||||||
.where("COALESCE(last_seen_at, '2010-01-01') <= CURRENT_TIMESTAMP - ('1 DAY'::INTERVAL * digest_after_days)")
|
.where("COALESCE(last_seen_at, '2010-01-01') <= CURRENT_TIMESTAMP - ('1 DAY'::INTERVAL * digest_after_days)")
|
||||||
|
|
||||||
@ -22,7 +21,7 @@ module Jobs
|
|||||||
query = query.where("approved OR moderator OR admin")
|
query = query.where("approved OR moderator OR admin")
|
||||||
end
|
end
|
||||||
|
|
||||||
query
|
query.pluck(:id)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
module Plugin; end
|
module Plugin; end
|
||||||
|
|
||||||
class Plugin::Metadata
|
class Plugin::Metadata
|
||||||
FIELDS = [:name, :about, :version, :authors]
|
FIELDS ||= [:name, :about, :version, :authors]
|
||||||
attr_accessor *FIELDS
|
attr_accessor *FIELDS
|
||||||
|
|
||||||
def self.parse(text)
|
def self.parse(text)
|
||||||
|
@ -104,8 +104,10 @@ module PrettyText
|
|||||||
ctx.eval("var window = {}; window.devicePixelRatio = 2;") # hack to make code think stuff is retina
|
ctx.eval("var window = {}; window.devicePixelRatio = 2;") # hack to make code think stuff is retina
|
||||||
ctx.eval("var I18n = {}; I18n.t = function(a,b){ return helpers.t(a,b); }");
|
ctx.eval("var I18n = {}; I18n.t = function(a,b){ return helpers.t(a,b); }");
|
||||||
|
|
||||||
|
decorate_context(ctx)
|
||||||
|
|
||||||
ctx_load(ctx,
|
ctx_load(ctx,
|
||||||
"app/assets/javascripts/external/markdown.js",
|
"app/assets/javascripts/external/better_markdown.js",
|
||||||
"app/assets/javascripts/discourse/dialects/dialect.js",
|
"app/assets/javascripts/discourse/dialects/dialect.js",
|
||||||
"app/assets/javascripts/discourse/components/utilities.js",
|
"app/assets/javascripts/discourse/components/utilities.js",
|
||||||
"app/assets/javascripts/discourse/components/markdown.js")
|
"app/assets/javascripts/discourse/components/markdown.js")
|
||||||
@ -145,6 +147,13 @@ module PrettyText
|
|||||||
@ctx
|
@ctx
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.decorate_context(context)
|
||||||
|
context.eval("Discourse.SiteSettings = #{SiteSetting.client_settings_json};")
|
||||||
|
context.eval("Discourse.CDN = '#{Rails.configuration.action_controller.asset_host}';")
|
||||||
|
context.eval("Discourse.BaseUrl = 'http://#{RailsMultisite::ConnectionManagement.current_hostname}';")
|
||||||
|
context.eval("Discourse.getURL = function(url) {return '#{Discourse::base_uri}' + url};")
|
||||||
|
end
|
||||||
|
|
||||||
def self.markdown(text, opts=nil)
|
def self.markdown(text, opts=nil)
|
||||||
# we use the exact same markdown converter as the client
|
# we use the exact same markdown converter as the client
|
||||||
# TODO: use the same extensions on both client and server (in particular the template for mentions)
|
# TODO: use the same extensions on both client and server (in particular the template for mentions)
|
||||||
@ -154,9 +163,7 @@ module PrettyText
|
|||||||
@mutex.synchronize do
|
@mutex.synchronize do
|
||||||
context = v8
|
context = v8
|
||||||
# we need to do this to work in a multi site environment, many sites, many settings
|
# we need to do this to work in a multi site environment, many sites, many settings
|
||||||
context.eval("Discourse.SiteSettings = #{SiteSetting.client_settings_json};")
|
decorate_context(context)
|
||||||
context.eval("Discourse.BaseUrl = 'http://#{RailsMultisite::ConnectionManagement.current_hostname}';")
|
|
||||||
context.eval("Discourse.getURL = function(url) {return '#{Discourse::base_uri}' + url};")
|
|
||||||
context['opts'] = opts || {}
|
context['opts'] = opts || {}
|
||||||
context['raw'] = text
|
context['raw'] = text
|
||||||
context.eval('opts["mentionLookup"] = function(u){return helpers.is_username_valid(u);}')
|
context.eval('opts["mentionLookup"] = function(u){return helpers.is_username_valid(u);}')
|
||||||
@ -175,9 +182,7 @@ module PrettyText
|
|||||||
@mutex.synchronize do
|
@mutex.synchronize do
|
||||||
v8['avatarTemplate'] = avatar_template
|
v8['avatarTemplate'] = avatar_template
|
||||||
v8['size'] = size
|
v8['size'] = size
|
||||||
v8.eval("Discourse.SiteSettings = #{SiteSetting.client_settings_json};")
|
decorate_context(v8)
|
||||||
v8.eval("Discourse.CDN = '#{Rails.configuration.action_controller.asset_host}';")
|
|
||||||
v8.eval("Discourse.BaseUrl = '#{RailsMultisite::ConnectionManagement.current_hostname}';")
|
|
||||||
r = v8.eval("Discourse.Utilities.avatarImg({ avatarTemplate: avatarTemplate, size: size });")
|
r = v8.eval("Discourse.Utilities.avatarImg({ avatarTemplate: avatarTemplate, size: size });")
|
||||||
end
|
end
|
||||||
r
|
r
|
||||||
|
@ -231,30 +231,25 @@ module SiteSettingExtension
|
|||||||
|
|
||||||
# trivial multi db support, we can optimize this later
|
# trivial multi db support, we can optimize this later
|
||||||
current[name] = current_value
|
current[name] = current_value
|
||||||
|
clean_name = name.to_s.sub("?", "")
|
||||||
|
|
||||||
setter = ("#{name}=").sub("?","")
|
eval "define_singleton_method :#{clean_name} do
|
||||||
|
|
||||||
eval "define_singleton_method :#{name} do
|
|
||||||
c = @@containers[provider.current_site]
|
c = @@containers[provider.current_site]
|
||||||
c = c[name] if c
|
c = c[name] if c
|
||||||
c
|
c
|
||||||
end
|
end
|
||||||
|
|
||||||
define_singleton_method :#{setter} do |val|
|
define_singleton_method :#{clean_name}? do
|
||||||
|
#{clean_name}
|
||||||
|
end
|
||||||
|
|
||||||
|
define_singleton_method :#{clean_name}= do |val|
|
||||||
add_override!(:#{name}, val)
|
add_override!(:#{name}, val)
|
||||||
refresh!
|
refresh!
|
||||||
end
|
end
|
||||||
"
|
"
|
||||||
end
|
end
|
||||||
|
|
||||||
def method_missing(method, *args, &block)
|
|
||||||
as_question = method.to_s.gsub(/\?$/, '')
|
|
||||||
if respond_to?(as_question)
|
|
||||||
return send(as_question, *args, &block)
|
|
||||||
end
|
|
||||||
super(method, *args, &block)
|
|
||||||
end
|
|
||||||
|
|
||||||
def enum_class(name)
|
def enum_class(name)
|
||||||
enums[name]
|
enums[name]
|
||||||
end
|
end
|
||||||
|
@ -3,14 +3,15 @@ require_dependency 'topic_list'
|
|||||||
class SuggestedTopicsBuilder
|
class SuggestedTopicsBuilder
|
||||||
|
|
||||||
attr_reader :excluded_topic_ids
|
attr_reader :excluded_topic_ids
|
||||||
attr_reader :results
|
|
||||||
|
|
||||||
def initialize(topic)
|
def initialize(topic)
|
||||||
@excluded_topic_ids = [topic.id]
|
@excluded_topic_ids = [topic.id]
|
||||||
|
@category_id = topic.category_id
|
||||||
@results = []
|
@results = []
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_results(results)
|
|
||||||
|
def add_results(results, priority=:low)
|
||||||
|
|
||||||
# WARNING .blank? will execute an Active Record query
|
# WARNING .blank? will execute an Active Record query
|
||||||
return unless results
|
return unless results
|
||||||
@ -23,16 +24,46 @@ class SuggestedTopicsBuilder
|
|||||||
unless results.empty?
|
unless results.empty?
|
||||||
# Keep track of the ids we've added
|
# Keep track of the ids we've added
|
||||||
@excluded_topic_ids.concat results.map {|r| r.id}
|
@excluded_topic_ids.concat results.map {|r| r.id}
|
||||||
|
splice_results(results,priority)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def splice_results(results, priority)
|
||||||
|
if @category_id &&
|
||||||
|
priority == :high &&
|
||||||
|
non_category_index = @results.index{|r| r.category_id != @category_id}
|
||||||
|
|
||||||
|
category_results, non_category_results = results.partition{|r| r.category_id == @category_id}
|
||||||
|
|
||||||
|
@results.insert non_category_index, *category_results
|
||||||
|
@results.concat non_category_results
|
||||||
|
else
|
||||||
@results.concat results
|
@results.concat results
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def results
|
||||||
|
@results.first(SiteSetting.suggested_topics)
|
||||||
|
end
|
||||||
|
|
||||||
def results_left
|
def results_left
|
||||||
SiteSetting.suggested_topics - @results.size
|
SiteSetting.suggested_topics - @results.size
|
||||||
end
|
end
|
||||||
|
|
||||||
def full?
|
def full?
|
||||||
results_left == 0
|
results_left <= 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def category_results_left
|
||||||
|
SiteSetting.suggested_topics - @results.count{|r| r.category_id == @category_id}
|
||||||
|
end
|
||||||
|
|
||||||
|
def category_full?
|
||||||
|
if @category_id
|
||||||
|
|
||||||
|
else
|
||||||
|
full?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def size
|
def size
|
||||||
|
@ -5,6 +5,8 @@ class TextSentinel
|
|||||||
|
|
||||||
attr_accessor :text
|
attr_accessor :text
|
||||||
|
|
||||||
|
ENTROPY_SCALE ||= 0.7
|
||||||
|
|
||||||
def initialize(text, opts=nil)
|
def initialize(text, opts=nil)
|
||||||
@opts = opts || {}
|
@opts = opts || {}
|
||||||
@text = text.to_s.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')
|
@text = text.to_s.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')
|
||||||
@ -15,14 +17,20 @@ class TextSentinel
|
|||||||
if opts[:private_message]
|
if opts[:private_message]
|
||||||
scale_entropy = SiteSetting.min_private_message_post_length.to_f / SiteSetting.min_post_length.to_f
|
scale_entropy = SiteSetting.min_private_message_post_length.to_f / SiteSetting.min_post_length.to_f
|
||||||
entropy = (entropy * scale_entropy).to_i
|
entropy = (entropy * scale_entropy).to_i
|
||||||
|
entropy = (SiteSetting.min_private_message_post_length.to_f * ENTROPY_SCALE).to_i if entropy > SiteSetting.min_private_message_post_length
|
||||||
|
else
|
||||||
|
entropy = (SiteSetting.min_post_length.to_f * ENTROPY_SCALE).to_i if entropy > SiteSetting.min_post_length
|
||||||
end
|
end
|
||||||
TextSentinel.new(text, min_entropy: entropy)
|
TextSentinel.new(text, min_entropy: entropy)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.title_sentinel(text)
|
def self.title_sentinel(text)
|
||||||
TextSentinel.new(text,
|
entropy = if SiteSetting.min_topic_title_length > SiteSetting.title_min_entropy
|
||||||
min_entropy: SiteSetting.title_min_entropy,
|
SiteSetting.title_min_entropy
|
||||||
max_word_length: SiteSetting.max_word_length)
|
else
|
||||||
|
(SiteSetting.min_topic_title_length.to_f * ENTROPY_SCALE).to_i
|
||||||
|
end
|
||||||
|
TextSentinel.new(text, min_entropy: entropy, max_word_length: SiteSetting.max_word_length)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Entropy is a number of how many unique characters the string needs.
|
# Entropy is a number of how many unique characters the string needs.
|
||||||
|
@ -87,10 +87,10 @@ class TopicQuery
|
|||||||
|
|
||||||
# When logged in we start with different results
|
# When logged in we start with different results
|
||||||
if @user
|
if @user
|
||||||
builder.add_results(unread_results(topic: topic, per_page: builder.results_left))
|
builder.add_results(unread_results(topic: topic, per_page: builder.results_left), :high)
|
||||||
builder.add_results(new_results(per_page: builder.results_left)) unless builder.full?
|
builder.add_results(new_results(topic: topic, per_page: builder.category_results_left), :high) unless builder.category_full?
|
||||||
end
|
end
|
||||||
builder.add_results(random_suggested(topic, builder.results_left)) unless builder.full?
|
builder.add_results(random_suggested(topic, builder.results_left), :low) unless builder.full?
|
||||||
|
|
||||||
create_list(:suggested, {}, builder.results)
|
create_list(:suggested, {}, builder.results)
|
||||||
end
|
end
|
||||||
@ -146,6 +146,11 @@ class TopicQuery
|
|||||||
TopicList.new(:private_messages, user, list)
|
TopicList.new(:private_messages, user, list)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def list_private_messages_unread(user)
|
||||||
|
list = private_messages_for(user)
|
||||||
|
list = TopicQuery.unread_filter(list)
|
||||||
|
TopicList.new(:private_messages, user, list)
|
||||||
|
end
|
||||||
|
|
||||||
def list_uncategorized
|
def list_uncategorized
|
||||||
create_list(:uncategorized, unordered: true) do |list|
|
create_list(:uncategorized, unordered: true) do |list|
|
||||||
|
@ -84,12 +84,13 @@ class TopicView
|
|||||||
|
|
||||||
def summary
|
def summary
|
||||||
return nil if desired_post.blank?
|
return nil if desired_post.blank?
|
||||||
|
# TODO, this is actually quite slow, should be cached in the post table
|
||||||
Summarize.new(desired_post.cooked).summary
|
Summarize.new(desired_post.cooked).summary
|
||||||
end
|
end
|
||||||
|
|
||||||
def image_url
|
def image_url
|
||||||
return nil if desired_post.blank?
|
return nil if desired_post.blank?
|
||||||
desired_post.user.small_avatar_url
|
desired_post.user.try(:small_avatar_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_posts(opts = {})
|
def filter_posts(opts = {})
|
||||||
@ -256,7 +257,7 @@ class TopicView
|
|||||||
|
|
||||||
def setup_filtered_posts
|
def setup_filtered_posts
|
||||||
@filtered_posts = @topic.posts
|
@filtered_posts = @topic.posts
|
||||||
@filtered_posts = @filtered_posts.with_deleted.without_nuked_users if @user.try(:staff?)
|
@filtered_posts = @filtered_posts.with_deleted if @user.try(:staff?)
|
||||||
@filtered_posts = @filtered_posts.best_of if @filter == 'best_of'
|
@filtered_posts = @filtered_posts.best_of if @filter == 'best_of'
|
||||||
@filtered_posts = @filtered_posts.where('posts.post_type <> ?', Post.types[:moderator_action]) if @best.present?
|
@filtered_posts = @filtered_posts.where('posts.post_type <> ?', Post.types[:moderator_action]) if @best.present?
|
||||||
return unless @username_filters.present?
|
return unless @username_filters.present?
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user