Merge some work: topic counts in category select; fix All count on dashboard

This commit is contained in:
Neil Lalonde 2013-04-08 10:22:08 -04:00
commit 467d59ec5a
87 changed files with 1134 additions and 1299 deletions

View File

@ -108,12 +108,12 @@ end
# this is an optional gem, it provides a high performance replacement
# to String#blank? a method that is called quite frequently in current
# ActiveRecord, this may change in the future
gem 'fast_blank', github: "SamSaffron/fast_blank"
gem 'fast_blank' #, github: "SamSaffron/fast_blank"
# 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
# we are open to it.
gem 'rack-mini-profiler', git: 'git://github.com/SamSaffron/MiniProfiler'
gem 'rack-mini-profiler' #, git: 'git://github.com/SamSaffron/MiniProfiler'
# perftools only works on 1.9 atm
group :profile do

View File

@ -1,18 +1,3 @@
GIT
remote: git://github.com/SamSaffron/MiniProfiler
revision: 08a421dc1411fb3f2e61c3d6e03bd3bd104f7130
specs:
rack-mini-profiler (0.1.23)
rack (>= 1.1.3)
GIT
remote: git://github.com/SamSaffron/fast_blank.git
revision: 5d63f521b228fe9aa386ed8b8bff280781b23004
specs:
fast_blank (0.0.1)
rake
rake-compiler
GIT
remote: git://github.com/callahad/omniauth-browserid.git
revision: af62d667626c1622de6fe13b60849c3640765ab1
@ -178,6 +163,9 @@ GEM
fakeweb (1.3.0)
faraday (0.8.5)
multipart-post (~> 1.1)
fast_blank (0.0.1)
rake
rake-compiler
fast_xor (1.1.1)
rake
rake-compiler
@ -331,6 +319,8 @@ GEM
rack (1.4.5)
rack-cache (1.2)
rack (>= 0.4)
rack-mini-profiler (0.1.25)
rack (>= 1.1.3)
rack-openid (1.3.1)
rack (>= 1.1.0)
ruby-openid (>= 2.1.8)
@ -466,7 +456,7 @@ GEM
railties (> 3.2.8, < 4.0.0)
sprockets (>= 2.0.0)
tzinfo (0.3.37)
uglifier (1.3.0)
uglifier (2.0.1)
execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2)
yajl-ruby (1.1.0)
@ -492,7 +482,7 @@ DEPENDENCIES
eventmachine
fabrication
fakeweb (~> 1.3.0)
fast_blank!
fast_blank
fast_xor
fast_xs
fastimage
@ -524,7 +514,7 @@ DEPENDENCIES
openid-redis-store
pg
pry-rails
rack-mini-profiler!
rack-mini-profiler
rails
rails_multisite!
rake

View File

@ -14,38 +14,39 @@ Discourse.ClickTrack = {
@param {jQuery.Event} e The click event that occurred
**/
trackClick: function(e) {
var $a, $article, $badge, count, destination, href, ownLink, postId, topicId, trackingUrl, userId;
$a = $(e.currentTarget);
if ($a.hasClass('lightbox')) {
return;
}
var $link = $(e.currentTarget);
if ($link.hasClass('lightbox')) return true;
e.preventDefault();
// We don't track clicks on quote back buttons
if ($a.hasClass('back') || $a.hasClass('quote-other-topic')) return true;
if ($link.hasClass('back') || $link.hasClass('quote-other-topic')) return true;
// We don't track clicks in oneboxes
// except when we force it with the "track-link" class
if ($link.closest('.onebox-result') && !$link.hasClass('track-link')) return true;
// Remove the href, put it as a data attribute
if (!$a.data('href')) {
$a.addClass('no-href');
$a.data('href', $a.attr('href'));
$a.attr('href', null);
if (!$link.data('href')) {
$link.addClass('no-href');
$link.data('href', $link.attr('href'));
$link.attr('href', null);
// Don't route to this URL
$a.data('auto-route', true);
$link.data('auto-route', true);
}
href = $a.data('href');
$article = $a.closest('article');
postId = $article.data('post-id');
topicId = $('#topic').data('topic-id');
userId = $a.data('user-id');
if (!userId) {
userId = $article.data('user-id');
}
ownLink = userId && (userId === Discourse.get('currentUser.id'));
var href = $link.data('href'),
$article = $link.closest('article'),
postId = $article.data('post-id'),
topicId = $('#topic').data('topic-id'),
userId = $link.data('user-id');
if (!userId) userId = $article.data('user-id');
var ownLink = userId && (userId === Discourse.get('currentUser.id'));
// Build a Redirect URL
trackingUrl = Discourse.getURL("/clicks/track?url=" + encodeURIComponent(href));
if (postId && (!$a.data('ignore-post-id'))) {
var trackingUrl = Discourse.getURL("/clicks/track?url=" + encodeURIComponent(href));
if (postId && (!$link.data('ignore-post-id'))) {
trackingUrl += "&post_id=" + encodeURI(postId);
}
if (topicId) {
@ -54,23 +55,21 @@ Discourse.ClickTrack = {
// Update badge clicks unless it's our own
if (!ownLink) {
$badge = $('span.badge', $a);
var $badge = $('span.badge', $link);
if ($badge.length === 1) {
count = parseInt($badge.html(), 10);
$badge.html(count + 1);
$badge.html(parseInt($badge.html(), 10) + 1);
}
}
// If they right clicked, change the destination href
if (e.which === 3) {
destination = Discourse.SiteSettings.track_external_right_clicks ? trackingUrl : href;
$a.attr('href', destination);
var destination = Discourse.SiteSettings.track_external_right_clicks ? trackingUrl : href;
$link.attr('href', destination);
return true;
}
// if they want to open in a new tab, do an AJAX request
if (e.metaKey || e.ctrlKey || e.which === 2) {
Discourse.ajax(Discourse.getURL("/clicks/track"), {
data: {
url: href,
@ -101,8 +100,7 @@ Discourse.ClickTrack = {
if (Discourse.get('currentUser.external_links_in_new_tab')) {
var win = window.open(trackingUrl, '_blank');
win.focus();
}
else {
} else {
window.location = trackingUrl;
}

View File

@ -28,12 +28,6 @@ Discourse.ScreenTrack = Ember.Object.extend({
};
},
guessedSeen: function(postNumber) {
if (postNumber > (this.highestSeen || 0)) {
this.highestSeen = postNumber;
}
},
// Reset our timers
reset: function() {
this.lastTick = new Date().getTime();
@ -93,16 +87,21 @@ Discourse.ScreenTrack = Ember.Object.extend({
timing.time = 0;
});
topicId = this.get('topic_id');
var highestSeen = 0;
$.each(newTimings, function(postNumber){
highestSeen = Math.max(highestSeen, parseInt(postNumber, 10));
});
highestSeenByTopic = Discourse.get('highestSeenByTopic');
if ((highestSeenByTopic[topicId] || 0) < this.highestSeen) {
highestSeenByTopic[topicId] = this.highestSeen;
if ((highestSeenByTopic[topicId] || 0) < highestSeen) {
highestSeenByTopic[topicId] = highestSeen;
}
if (!Object.isEmpty(newTimings)) {
Discourse.ajax(Discourse.getURL('/topics/timings'), {
data: {
timings: newTimings,
topic_time: this.topicTime,
highest_seen: this.highestSeen,
topic_id: topicId
},
cache: false,

View File

@ -18,8 +18,8 @@ Discourse.ComposerController = Discourse.Controller.extend({
this.get('content').importQuote();
},
resetDraftStatus: function() {
this.get('content').resetDraftStatus();
updateDraftStatus: function() {
this.get('content').updateDraftStatus();
},
appendText: function(text) {

View File

@ -16,25 +16,35 @@ Discourse.QuoteButtonController = Discourse.Controller.extend({
$LAB.script(assetPath('defer/html-sanitizer-bundle'));
},
// If the buffer is cleared, clear out other state (post)
bufferChanged: (function() {
/**
If the buffer is cleared, clear out other state (post)
**/
bufferChanged: function() {
if (this.blank('buffer')) this.set('post', null);
}).observes('buffer'),
}.observes('buffer'),
/**
Save the currently selected text and displays the
"quote reply" buttong
@method selectText
**/
selectText: function(e) {
// anonymous users cannot "quote-reply"
if (!Discourse.get('currentUser')) return;
// there is no need to display the "quote-reply" button if we can't create a post
// don't display the "quote-reply" button if we can't create a post
if (!this.get('controllers.topic.content.can_create_post')) return;
// retrieve the selected range
var range = window.getSelection().getRangeAt(0);
var cloned = range.cloneRange();
// do not be present the "quote reply" button if you select text spanning two posts
// don't display the "quote reply" button if you select text spanning two posts
// this basically look for the first "DIV" container...
var commonDivAncestorContainer = range.commonAncestorContainer;
while (commonDivAncestorContainer.nodeName !== 'DIV') commonDivAncestorContainer = commonDivAncestorContainer.parentNode;
while (commonDivAncestorContainer.nodeName !== 'DIV') {
commonDivAncestorContainer = commonDivAncestorContainer.parentNode;
}
// ... and check it has the 'cooked' class (which indicates we're in a post)
if (commonDivAncestorContainer.className.indexOf('cooked') === -1) return;
@ -55,23 +65,20 @@ Discourse.QuoteButtonController = Discourse.Controller.extend({
// insert it at the beginning of our range
range.insertNode(markerElement);
// work around chrome that would sometimes lose the selection
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(cloned);
// find marker position (cf. http://www.quirksmode.org/js/findpos.html)
var obj = markerElement, left = 0, top = 0;
do {
left += obj.offsetLeft;
top += obj.offsetTop;
obj = obj.offsetParent;
} while (obj.offsetParent);
// move the quote button at the beginning of the selection
var $quoteButton = $('.quote-button');
$quoteButton.css({
top: top - $quoteButton.outerHeight() - 5,
left: left
var markerOffset = $(markerElement).offset(),
$quoteButton = $('.quote-button');
Em.run.next(function(){
$quoteButton.offset({
top: markerOffset.top - $quoteButton.outerHeight() - 5,
left: markerOffset.left
});
});
// remove the marker

View File

@ -31,43 +31,43 @@ Discourse.Composer = Discourse.Model.extend({
this.set('archetypeId', Discourse.get('site.default_archetype'));
},
creatingTopic: (function() {
creatingTopic: function() {
return this.get('action') === CREATE_TOPIC;
}).property('action'),
}.property('action'),
creatingPrivateMessage: (function() {
creatingPrivateMessage: function() {
return this.get('action') === PRIVATE_MESSAGE;
}).property('action'),
}.property('action'),
editingPost: (function() {
editingPost: function() {
return this.get('action') === EDIT;
}).property('action'),
}.property('action'),
replyingToTopic: (function() {
replyingToTopic: function() {
return this.get('action') === REPLY;
}).property('action'),
}.property('action'),
viewOpen: (function() {
viewOpen: function() {
return this.get('composeState') === OPEN;
}).property('composeState'),
}.property('composeState'),
archetype: (function() {
archetype: function() {
return this.get('archetypes').findProperty('id', this.get('archetypeId'));
}).property('archetypeId'),
}.property('archetypeId'),
archetypeChanged: (function() {
archetypeChanged: function() {
return this.set('metaData', Em.Object.create());
}).observes('archetype'),
}.observes('archetype'),
editTitle: (function() {
editTitle: function() {
if (this.get('creatingTopic') || this.get('creatingPrivateMessage')) return true;
if (this.get('editingPost') && this.get('post.post_number') === 1) return true;
return false;
}).property('editingPost', 'creatingTopic', 'post.post_number'),
}.property('editingPost', 'creatingTopic', 'post.post_number'),
togglePreview: function() {
this.toggleProperty('showPreview');
return Discourse.KeyValueStore.set({ key: 'showPreview', value: this.get('showPreview') });
Discourse.KeyValueStore.set({ key: 'showPreview', value: this.get('showPreview') });
},
// Import a quote from the post
@ -97,7 +97,7 @@ Discourse.Composer = Discourse.Model.extend({
},
// Determine the appropriate title for this action
actionTitle: (function() {
actionTitle: function() {
var topic = this.get('topic');
var postLink, topicLink;
@ -128,28 +128,26 @@ Discourse.Composer = Discourse.Model.extend({
}
switch (this.get('action')) {
case PRIVATE_MESSAGE:
return Em.String.i18n('topic.private_message');
case CREATE_TOPIC:
return Em.String.i18n('topic.create_long');
case PRIVATE_MESSAGE: return Em.String.i18n('topic.private_message');
case CREATE_TOPIC: return Em.String.i18n('topic.create_long');
case REPLY:
case EDIT:
if (postDescription) return postDescription;
if (topic) return Em.String.i18n('post.reply_topic', { link: topicLink });
}
}).property('action', 'post', 'topic', 'topic.title'),
}.property('action', 'post', 'topic', 'topic.title'),
toggleText: (function() {
toggleText: function() {
return this.get('showPreview') ? Em.String.i18n('composer.hide_preview') : Em.String.i18n('composer.show_preview');
}).property('showPreview'),
}.property('showPreview'),
hidePreview: (function() {
hidePreview: function() {
return !this.get('showPreview');
}).property('showPreview'),
}.property('showPreview'),
// Whether to disable the post button
cantSubmitPost: (function() {
cantSubmitPost: function() {
// Can't submit while loading
if (this.get('loading')) return true;
@ -167,29 +165,22 @@ Discourse.Composer = Discourse.Model.extend({
if (this.get('replyLength') < Discourse.SiteSettings.min_post_length) return true;
return false;
}).property('loading', 'editTitle', 'titleLength', 'targetUsernames', 'replyLength'),
}.property('loading', 'editTitle', 'titleLength', 'targetUsernames', 'replyLength'),
// The text for the save button
saveText: (function() {
saveText: function() {
switch (this.get('action')) {
case EDIT:
return Em.String.i18n('composer.save_edit');
case REPLY:
return Em.String.i18n('composer.reply');
case CREATE_TOPIC:
return Em.String.i18n('composer.create_topic');
case PRIVATE_MESSAGE:
return Em.String.i18n('composer.create_pm');
case EDIT: return Em.String.i18n('composer.save_edit');
case REPLY: return Em.String.i18n('composer.reply');
case CREATE_TOPIC: return Em.String.i18n('composer.create_topic');
case PRIVATE_MESSAGE: return Em.String.i18n('composer.create_pm');
}
}).property('action'),
}.property('action'),
hasMetaData: (function() {
hasMetaData: function() {
var metaData = this.get('metaData');
if (!this.get('metaData')) {
return false;
}
return Em.empty(Em.keys(this.get('metaData')));
}).property('metaData'),
return metaData ? Em.empty(Em.keys(this.get('metaData'))) : false;
}.property('metaData'),
wouldLoseChanges: function() {
return this.get('reply') !== this.get('originalText');
@ -277,8 +268,7 @@ Discourse.Composer = Discourse.Model.extend({
},
save: function(opts) {
if (this.get('editingPost')) return this.editPost(opts);
return this.createPost(opts);
return this.get('editingPost') ? this.editPost(opts) : this.createPost(opts);
},
// When you edit a post
@ -458,31 +448,57 @@ Discourse.Composer = Discourse.Model.extend({
this.set('draftStatus', Em.String.i18n('composer.saving_draft_tip'));
var composer = this;
return Discourse.Draft.save(this.get('draftKey'), this.get('draftSequence'), data).then((function() {
composer.set('draftStatus', Em.String.i18n('composer.saved_draft_tip'));
}), (function() {
composer.set('draftStatus', Em.String.i18n('composer.drafts_offline'));
}));
// try to save the draft
return Discourse.Draft.save(this.get('draftKey'), this.get('draftSequence'), data)
.then(function() {
composer.set('draftStatus', Em.String.i18n('composer.saved_draft_tip'));
}, function() {
composer.set('draftStatus', Em.String.i18n('composer.drafts_offline'));
});
},
resetDraftStatus: (function() {
updateDraftStatus: function() {
var $title = $('#reply-title'),
$reply = $('#wmd-input');
// 'title' is focused
if ($('#reply-title').is(':focus')) {
var titleDiff = Discourse.SiteSettings.min_topic_title_length - this.get('titleLength');
if ($title.is(':focus')) {
var titleDiff = this.get('missingTitleCharacters');
if (titleDiff > 0) {
return this.set('draftStatus', Em.String.i18n('composer.min_length.need_more_for_title', { n: titleDiff }));
}
// 'reply' is focused
} else if ($('#wmd-input').is(':focus')) {
var replyDiff = Discourse.SiteSettings.min_post_length - this.get('replyLength');
} else if ($reply.is(':focus')) {
var replyDiff = this.get('missingReplyCharacters');
if (replyDiff > 0) {
return this.set('draftStatus', Em.String.i18n('composer.min_length.need_more_for_reply', { n: replyDiff }));
}
}
// hide the counters if the currently focused text field is OK
this.set('draftStatus', null);
}).observes('replyLength', 'titleLength'),
}.observes('missingTitleCharacters', 'missingReplyCharacters'),
/**
Number of missing characters in the title until valid.
@property missingTitleCharacters
**/
missingTitleCharacters: function() {
return Discourse.SiteSettings.min_topic_title_length - this.get('titleLength');
}.property('titleLength'),
/**
Number of missing characters in the reply until valid.
@property missingReplyCharacters
**/
missingReplyCharacters: function() {
return Discourse.SiteSettings.min_post_length - this.get('replyLength');
}.property('replyLength'),
/**
Computes the length of the title minus non-significant whitespaces

View File

@ -50,8 +50,6 @@ Discourse.TopicList = Discourse.Model.extend({
unseen: true,
highlight: true
});
console.log(newTopic);
this.get('inserted').unshiftObject(newTopic);
}

View File

@ -17,10 +17,7 @@ Discourse.PreferencesRoute = Discourse.RestrictedUserRoute.extend({
},
setupController: function(controller) {
console.log('prefereces');
controller.set('content', this.controllerFor('user').get('content'));
}
});

View File

@ -50,7 +50,7 @@
</div>
{{#if Discourse.currentUser}}
<a href="#" {{action togglePreview target="controller"}} class='toggle-preview'>{{{content.toggleText}}}</a>
<div class='saving-draft'></div>
<div class='draft-status'></div>
{{#if view.loadingImage}}
<div id="image-uploading">
{{i18n image_selector.uploading_image}} {{view.uploadProgress}}% <a id="cancel-image-upload">{{i18n cancel}}</a>

View File

@ -1,5 +1,5 @@
<div id='user-info'>
<div id='user-info'>
<nav class='buttons'>
{{#if content.can_edit}}
{{#linkTo "preferences" class="btn"}}{{i18n user.edit}}{{/linkTo}}
@ -13,7 +13,7 @@
{{/if}}
</nav>
<div class='clearfix'></div>
<ul class='action-list nav-stacked side-nav'>
{{view Discourse.ActivityFilterView countBinding="statsCountNonPM"}}
{{#each statsExcludingPms}}
@ -47,7 +47,7 @@
<button class='btn' data-not-implemented='true' disabled title="{{i18n not_implemented}}">{{i18n user.download_archive}}</button>
</div>
{{/if}}
</div>

View File

@ -27,7 +27,7 @@ Discourse.ComposerView = Discourse.View.extend({
}.property('content.composeState'),
draftStatus: function() {
this.$('.saving-draft').text(this.get('content.draftStatus') || "");
this.$('.draft-status').text(this.get('content.draftStatus') || "");
}.observes('content.draftStatus'),
// Disable fields when we're loading
@ -40,8 +40,7 @@ Discourse.ComposerView = Discourse.View.extend({
}.observes('loading'),
postMade: function() {
if (this.present('controller.createdPost')) return 'created-post';
return null;
return this.present('controller.createdPost') ? 'created-post' : null;
}.property('content.createdPost'),
observeReplyChanges: function() {
@ -87,7 +86,7 @@ Discourse.ComposerView = Discourse.View.extend({
focusIn: function() {
var controller = this.get('controller');
if(controller) controller.resetDraftStatus();
if (controller) controller.updateDraftStatus();
},
resize: function() {
@ -123,9 +122,9 @@ Discourse.ComposerView = Discourse.View.extend({
},
didInsertElement: function() {
var replyControl = $('#reply-control');
replyControl.DivResizer({ resize: this.resize, onDrag: this.movePanels });
Discourse.TransitionHelper.after(replyControl, this.resize);
var $replyControl = $('#reply-control');
$replyControl.DivResizer({ resize: this.resize, onDrag: this.movePanels });
Discourse.TransitionHelper.after($replyControl, this.resize);
},
click: function() {
@ -260,11 +259,26 @@ Discourse.ComposerView = Discourse.View.extend({
return true;
});
$('#reply-title').keyup(function() {
var $replyTitle = $('#reply-title');
$replyTitle.keyup(function() {
saveDraft();
// removes the red background once the requirements are met
if (_this.get('controller.content.missingTitleCharacters') <= 0) {
$replyTitle.removeClass("requirements-not-met");
}
return true;
});
// when the title field loses the focus...
$replyTitle.blur(function(){
// ...and the requirements are not met (ie. the minimum number of characters)
if (_this.get('controller.content.missingTitleCharacters') > 0) {
// then, "redify" the background
$replyTitle.toggleClass("requirements-not-met", true);
}
});
// In case it's still bound somehow
$uploadTarget.fileupload('destroy');
$uploadTarget.off();
@ -330,6 +344,10 @@ Discourse.ComposerView = Discourse.View.extend({
case 413:
bootbox.alert(Em.String.i18n('post.errors.upload_too_large', {max_size_kb: Discourse.SiteSettings.max_upload_size_kb}));
return;
// 415 == media type not recognized (ie. not an image)
case 415:
bootbox.alert(Em.String.i18n('post.errors.only_images_are_supported'));
return;
}
}
// otherwise, display a generic error message

View File

@ -15,9 +15,9 @@ Discourse.QuoteButtonView = Discourse.View.extend({
buffer.push(Em.String.i18n("post.quote_reply"));
},
hasBuffer: (function() {
hasBuffer: function() {
return this.present('controller.buffer') ? 'visible' : null;
}).property('controller.buffer'),
}.property('controller.buffer'),
willDestroyElement: function() {
$(document).off("mousedown.quote-button");

View File

@ -181,8 +181,6 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, {
}
if (!post.get('read')) {
post.set('read', true);
screenTrack = this.get('screenTrack');
if (screenTrack) { screenTrack.guessedSeen(postNumber); }
}
return post.get('post_number');
}

View File

@ -10,25 +10,33 @@ Discourse.ActivityFilterView = Discourse.View.extend({
tagName: 'li',
classNameBindings: ['active'],
active: (function() {
var content;
if (content = this.get('content')) {
countChanged: function(){
this.rerender();
}.observes('count'),
active: function() {
var content = this.get('content');
if (content) {
return parseInt(this.get('controller.content.streamFilter'), 10) === parseInt(Em.get(content, 'action_type'), 10);
} else {
return this.blank('controller.content.streamFilter');
}
}).property('controller.content.streamFilter', 'content.action_type'),
}.property('controller.content.streamFilter', 'content.action_type'),
render: function(buffer) {
var content, count, description;
if (content = this.get('content')) {
var content = this.get('content');
var count, description;
if (content) {
count = Em.get(content, 'count');
description = Em.get(content, 'description');
} else {
count = this.get('count');
description = Em.String.i18n("user.filters.all");
}
return buffer.push("<a href='#'>" + description + " <span class='count'>(" + count + ")</span><span class='icon-chevron-right'></span></a>");
buffer.push("<a href='#'>" + description +
" <span class='count'>(" + count + ")</span><span class='icon-chevron-right'></span></a>");
},
click: function() {

View File

@ -200,11 +200,7 @@ table {
margin-bottom: 20px;
background-color: whitesmoke;
border: 1px solid #e3e3e3;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
height: 638px;
margin-bottom: 10px;
@ -480,13 +476,7 @@ table {
border-bottom: solid 1px #ccc;
background-color:#e1e1e1;
background-image:-moz-linear-gradient(top, #f1f1f1, #e1e1e1);
background-image:-ms-linear-gradient(top, #f1f1f1, #e1e1e1);
background-image:-o-linear-gradient(top, #f1f1f1, #e1e1e1);
background-image:-webkit-gradient(linear, left top, left bottom, from(#f1f1f1), to(#e1e1e1));
background-image:-webkit-linear-gradient(top, #f1f1f1, #e1e1e1);
background-image:linear-gradient(center top, #f1f1f1 0%, #e1e1e1 100%);
filter:progid:DXImageTransform.Microsoft.gradient(startColorStr='#f1f1f1', EndColorStr='#e1e1e1');
cursor: pointer;
h1 {
@ -511,13 +501,7 @@ table {
padding: 6px 8px;
border-bottom: solid 1px #ccc;
background-color:#eee;
background-image:-moz-linear-gradient(top, #fafafa, #eee);
background-image:-ms-linear-gradient(top, #fafafa, #eee);
background-image:-o-linear-gradient(top, #fafafa, #eee);
background-image:-webkit-gradient(linear, left top, left bottom, from(#fafafa), to(#eee));
background-image:-webkit-linear-gradient(top, #fafafa, #eee);
background-image:linear-gradient(center top, #fafafa 0%, #eee 100%);
filter:progid:DXImageTransform.Microsoft.gradient(startColorStr='#f1f1f1', EndColorStr='#e1e1e1');
.left {
float: left;

View File

@ -55,7 +55,10 @@
}
#reply-control {
.toggle-preview, .saving-draft, #image-uploading {
.requirements-not-met {
background-color: rgba(255, 0, 0, 0.12);
}
.toggle-preview, .draft-status, #image-uploading {
position: absolute;
bottom: -31px;
margin-top: 0px;
@ -69,7 +72,7 @@
font-size: 12px;
color: darken($gray, 40);
}
.saving-draft {
.draft-status {
right: 51%;
color: lighten($black, 60);
}

View File

@ -51,18 +51,7 @@ header {
}
body {
.nav-pills .active .dropdown-toggle .caret {
border-top-color: white;
border-bottom-color: white;
}
.caret {
opacity: 0.9;
filter: alpha(opacity = 90);
}
.dropdown .caret {
margin-left: 6px;
}
button.ok {
@include linear-gradient(lighten($green, 5%), $green);
color: $white;

View File

@ -53,6 +53,7 @@
}
.icons {
float: left;
text-align: center;
margin: 0 0 0 15px;
list-style: none;
> li {

View File

@ -53,18 +53,11 @@
/* IE6-7 */
@include border-radius-all (6px);
-webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
-moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
-webkit-background-clip: padding-box;
-moz-background-clip: padding-box;
background-clip: padding-box;
}
.modal.fade {
-webkit-transition: opacity .3s linear, top .3s ease-out;
-moz-transition: opacity .3s linear, top .3s ease-out;
-ms-transition: opacity .3s linear, top .3s ease-out;
-o-transition: opacity .3s linear, top .3s ease-out;
transition: opacity .3s linear, top .3s ease-out;
top: -25%;
}

View File

@ -13,7 +13,6 @@ a.loading-onebox {
}
.onebox-result {
@include border-radius-all(8px);
font-size: 14px;
> .source {
margin-bottom: 10px;

View File

@ -113,8 +113,6 @@
}
transition:opacity 1s linear;
-webkit-transition:opacity 1s linear;
-moz-transition:opacity 1s linear;
}
.reply-new,
.track-link {
@ -602,6 +600,11 @@
margin-top: 0px !important;
}
// ditto for blockquotes, no extra margin for first element inside please
.cooked > blockquote *:first-child {
margin-top: 0px !important;
}
.cooked {
// set up proper header margins in posts
h1, h2, h3, h4, h5, h6 {

View File

@ -28,7 +28,7 @@
.star {
height: 40px;
font-size: 20px !important;
line-height: 40px !important;
line-height: 36px !important;
vertical-align: top;
}
.topic-statuses {

View File

@ -10,8 +10,6 @@ div.tagsinput {
div.tagsinput span.tag {
border: 1px solid #a5d24a;
-moz-border-radius:2px;
-webkit-border-radius:2px;
border-radius: 2px;
display: block;
float: left;

View File

@ -55,28 +55,22 @@
// Border radius
@mixin border-radius-all($radius) {
-webkit-border-radius: $radius;
border-radius: $radius;
}
@mixin border-radius-top($radius) {
-webkit-border-top-right-radius: $radius;
border-top-right-radius: $radius;
-webkit-border-top-left-radius: $radius;
border-top-left-radius: $radius;
}
@mixin border-radius-bottom($radius) {
-webkit-border-bottom-right-radius: $radius;
border-bottom-right-radius: $radius;
-webkit-border-bottom-left-radius: $radius;
border-bottom-left-radius: $radius;
}
// Box shadow
@mixin box-shadow($shadow) {
-webkit-box-shadow: $shadow;
box-shadow: $shadow;
}
@ -84,24 +78,18 @@
@mixin linear-gradient($start-color, $end-color) {
background-color: $start-color;
background-image: -webkit-gradient(linear, left top, left bottom, from($start-color), to($end-color));
background-image: -webkit-linear-gradient(top, $start-color, $end-color);
background-image: -moz-linear-gradient(top, $start-color, $end-color);
background-image: -o-linear-gradient(top, $start-color, $end-color);
background-image: linear-gradient(to bottom, $start-color, $end-color);
}
// Background size
@mixin background-size($size) {
-webkit-background-size: $size;
background-size: $size;
}
// Background clip
@mixin background-clip($clip) {
-webkit-background-clip: $clip;
background-clip: $clip;
}
@ -109,9 +97,6 @@
@mixin rotate($degrees) {
-webkit-transform: rotate($degrees);
-moz-transform: rotate($degrees);
-ms-transform: rotate($degrees);
-o-transform: rotate($degrees);
transform: rotate($degrees);
}
@ -119,9 +104,6 @@
@mixin scale($ratio) {
-webkit-transform: scale($ratio);
-moz-transform: scale($ratio);
-ms-transform: scale($ratio);
-o-transform: scale($ratio);
transform: scale($ratio);
}
@ -130,9 +112,7 @@
@mixin transition($transition) {
.discourse-no-touch & {
-webkit-transition: #{$transition};
-moz-transition: #{$transition};
-ms-transition: #{$transition};
-o-transition: #{$transition};
transition: #{$transition};
}
}
@ -153,28 +133,21 @@
visibility: hidden;
.discourse-no-touch & {
-webkit-transition: visibility 0s linear $time, opacity $time linear;
-moz-transition: visibility 0s linear $time, opacity $time linear;
-ms-transition: visibility 0s linear $time, opacity $time linear;
-o-transition: visibility 0s linear $time, opacity $time linear;
transition: visibility 0s linear $time, opacity $time linear;
}
}
@mixin fade-soft($time: 1s) {
transition: opacity $time ease-in-out;
-moz-transition: opacity $time ease-in-out;
-webkit-transition: opacity $time ease-in-out;
-ms-transition: opacity $time ease-in-out;
-o-transition: opacity $time ease-in-out;
-ms-transition: opacity $time ease-in-out;
transition: opacity $time ease-in-out;
}
@mixin visible {
opacity: 1;
visibility: visible;
-webkit-transition-delay: 0s;
-moz-transition-delay: 0s;
-ms-transition-delay: 0s;
-o-transition-delay: 0s;
transition-delay: 0s;
}
@ -184,7 +157,6 @@
// Glow
@mixin glow($color) {
border: 1px solid $color;
-webkit-box-shadow: 0 0 5px $color;
box-shadow: 0 0 5px $color;
border: 1px solid $color;
box-shadow: 0 0 5px $color;
}

View File

@ -41,30 +41,6 @@ img {
.span24 {
width: 1236px;
}
.span23 {
width: 1184px;
}
.span22 {
width: 1132px;
}
.span21 {
width: 1080px;
}
.span20 {
width: 1028px;
}
.span19 {
width: 976px;
}
.span18 {
width: 924px;
}
.span17 {
width: 872px;
}
.span16 {
width: 820px;
}
.span15 {
width: 768px;
}
@ -74,24 +50,15 @@ img {
.span13 {
width: 664px;
}
.span12 {
width: 612px;
}
.span11 {
width: 560px;
}
.span10 {
width: 508px;
}
.span9 {
width: 456px;
}
.span8 {
width: 404px;
}
.span7 {
width: 352px;
}
.span6 {
width: 300px;
}
@ -107,102 +74,13 @@ img {
.span2 {
width: 92px;
}
.span1 {
width: 40px;
}
.offset24 {
margin-left: 1260px;
}
.offset23 {
margin-left: 1208px;
}
.offset22 {
margin-left: 1156px;
}
.offset21 {
margin-left: 1104px;
}
.offset20 {
margin-left: 1052px;
}
.offset19 {
margin-left: 1000px;
}
.offset18 {
margin-left: 948px;
}
.offset17 {
margin-left: 896px;
}
.offset16 {
margin-left: 844px;
}
.offset15 {
margin-left: 792px;
}
.offset14 {
margin-left: 740px;
}
.offset13 {
margin-left: 688px;
}
.offset12 {
margin-left: 636px;
}
.offset11 {
margin-left: 584px;
}
.offset10 {
margin-left: 532px;
}
.offset9 {
margin-left: 480px;
}
.offset8 {
margin-left: 428px;
}
.offset7 {
margin-left: 376px;
}
.offset6 {
margin-left: 324px;
}
.offset5 {
margin-left: 272px;
}
.offset4 {
margin-left: 220px;
}
.offset3 {
margin-left: 168px;
}
.offset2 {
margin-left: 116px;
}
.offset1 {
margin-left: 64px;
}
.label {
font-size: 10.998px;
font-weight: bold;
line-height: 14px;
color: #ffffff;
vertical-align: baseline;
white-space: nowrap;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
background-color: #999999;
}
.label {
padding: 1px 4px 2px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
a.label:hover {
color: #ffffff;
text-decoration: none;
cursor: pointer;
}
button.btn::-moz-focus-inner,
input[type="submit"].btn::-moz-focus-inner {
padding: 0;
@ -236,25 +114,6 @@ input[type="submit"].btn::-moz-focus-inner {
.btn-group.open .dropdown-toggle {
outline: 0;
}
.btn .caret {
margin-top: 7px;
margin-left: 0;
border-top-color: #565656;
}
.btn:hover .caret,
.open.btn-group .caret {
opacity: 1;
filter: alpha(opacity=100);
}
.btn-small .caret {
margin-top: 6px;
}
.btn-large .caret {
margin-top: 6px;
border-left-width: 5px;
border-right-width: 5px;
border-top-width: 5px;
}
.dropdown {
position: relative;
@ -270,22 +129,11 @@ input[type="submit"].btn::-moz-focus-inner {
display: inline-block;
width: 0;
height: 0;
vertical-align: top;
vertical-align: middle;
border-top: 4px solid #000000;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
content: "";
opacity: 0.3;
filter: alpha(opacity=30);
}
.dropdown .caret {
margin-top: 8px;
margin-left: 2px;
}
.dropdown:hover .caret,
.open .caret {
opacity: 1;
filter: alpha(opacity=100);
}
.dropdown-menu {
position: absolute;
@ -303,29 +151,10 @@ input[type="submit"].btn::-moz-focus-inner {
border: 1px solid rgba(0, 0, 0, 0.2);
*border-right-width: 2px;
*border-bottom-width: 2px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box;
}
.dropdown-menu.pull-right {
right: 0;
left: auto;
}
.dropdown-menu .divider {
*width: 100%;
height: 1px;
margin: 8px 1px;
*margin: -5px 0 5px;
overflow: hidden;
background-color: #e5e5e5;
border-bottom: 1px solid #ffffff;
}
.dropdown-menu a {
display: block;
padding: 3px 15px;
@ -348,34 +177,14 @@ input[type="submit"].btn::-moz-focus-inner {
.open > .dropdown-menu {
display: block;
}
.pull-right > .dropdown-menu {
right: 0;
left: auto;
}
.fade {
opacity: 0;
-webkit-transition: opacity 0.15s linear;
-moz-transition: opacity 0.15s linear;
-ms-transition: opacity 0.15s linear;
-o-transition: opacity 0.15s linear;
transition: opacity 0.15s linear;
}
.fade.in {
opacity: 1;
}
.collapse {
position: relative;
height: 0;
overflow: hidden;
-webkit-transition: height 0.35s ease;
-moz-transition: height 0.35s ease;
-ms-transition: height 0.35s ease;
-o-transition: height 0.35s ease;
transition: height 0.35s ease;
}
.collapse.in {
height: auto;
}
body {
@ -482,48 +291,30 @@ body {
height: auto;
background-color: white;
border: 1px solid #cccccc;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
-moz-transition: border linear 0.2s, box-shadow linear 0.2s;
-ms-transition: border linear 0.2s, box-shadow linear 0.2s;
-o-transition: border linear 0.2s, box-shadow linear 0.2s;
transition: border linear 0.2s, box-shadow linear 0.2s;
}
input {
&[type="text"], &[type="password"], &[type="datetime"], &[type="datetime-local"], &[type="date"], &[type="month"], &[type="time"], &[type="week"], &[type="number"], &[type="email"], &[type="url"], &[type="search"], &[type="tel"], &[type="color"] {
background-color: white;
border: 1px solid #cccccc;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
-moz-transition: border linear 0.2s, box-shadow linear 0.2s;
-ms-transition: border linear 0.2s, box-shadow linear 0.2s;
-o-transition: border linear 0.2s, box-shadow linear 0.2s;
transition: border linear 0.2s, box-shadow linear 0.2s;
}
}
textarea:focus {
border-color: rgba(82, 168, 236, 0.8);
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
input {
&[type="text"]:focus, &[type="password"]:focus, &[type="datetime"]:focus, &[type="datetime-local"]:focus, &[type="date"]:focus, &[type="month"]:focus, &[type="time"]:focus, &[type="week"]:focus, &[type="number"]:focus, &[type="email"]:focus, &[type="url"]:focus, &[type="search"]:focus, &[type="tel"]:focus, &[type="color"]:focus {
border-color: rgba(82, 168, 236, 0.8);
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
}
@ -584,21 +375,6 @@ body {
.radio.inline .radio.inline, .checkbox.inline .checkbox.inline {
margin-left: 10px;
}
.input-mini {
width: 60px;
}
.input-small {
width: 90px;
}
.input-medium {
width: 150px;
}
.input-large {
width: 210px;
}
.input-xlarge {
width: 270px;
}
.input-xxlarge {
width: 530px;
}
@ -606,12 +382,6 @@ body {
float: none;
margin-left: 0;
}
.row-fluid {
input[class*="span"], select[class*="span"], textarea[class*="span"] {
float: none;
margin-left: 0;
}
}
.input-append {
input[class*="span"] {
display: inline-block;
@ -622,50 +392,9 @@ body {
display: inline-block;
}
}
.row-fluid {
.input-prepend [class*="span"], .input-append [class*="span"] {
display: inline-block;
}
}
input, textarea {
margin-left: 0;
}
input.span12, textarea.span12 {
width: 930px;
}
input.span11, textarea.span11 {
width: 850px;
}
input.span10, textarea.span10 {
width: 770px;
}
input.span9, textarea.span9 {
width: 690px;
}
input.span8, textarea.span8 {
width: 610px;
}
input.span7, textarea.span7 {
width: 530px;
}
input.span6, textarea.span6 {
width: 450px;
}
input.span5, textarea.span5 {
width: 370px;
}
input.span4, textarea.span4 {
width: 290px;
}
input.span3, textarea.span3 {
width: 210px;
}
input.span2, textarea.span2 {
width: 130px;
}
input.span1, textarea.span1 {
width: 50px;
}
input[disabled], select[disabled], textarea[disabled], input[readonly], select[readonly], textarea[readonly] {
cursor: not-allowed;
background-color: #eeeeee;
@ -678,7 +407,7 @@ body {
}
.control-group {
&.warning {
> label, .help-block, .help-inline {
> label {
color: #c09853;
}
.checkbox, .radio, input, select, textarea {
@ -687,8 +416,6 @@ body {
}
.checkbox:focus, .radio:focus, input:focus, select:focus, textarea:focus {
border-color: #a47e3c;
-webkit-box-shadow: 0 0 6px #dbc59e;
-moz-box-shadow: 0 0 6px #dbc59e;
box-shadow: 0 0 6px #dbc59e;
}
.input-prepend .add-on, .input-append .add-on {
@ -698,7 +425,7 @@ body {
}
}
&.error {
> label, .help-block, .help-inline {
> label {
color: #b94a48;
}
.checkbox, .radio, input, select, textarea {
@ -707,8 +434,6 @@ body {
}
.checkbox:focus, .radio:focus, input:focus, select:focus, textarea:focus {
border-color: #953b39;
-webkit-box-shadow: 0 0 6px #d59392;
-moz-box-shadow: 0 0 6px #d59392;
box-shadow: 0 0 6px #d59392;
}
.input-prepend .add-on, .input-append .add-on {
@ -718,7 +443,7 @@ body {
}
}
&.success {
> label, .help-block, .help-inline {
> label {
color: #468847;
}
.checkbox, .radio, input, select, textarea {
@ -727,8 +452,6 @@ body {
}
.checkbox:focus, .radio:focus, input:focus, select:focus, textarea:focus {
border-color: #356635;
-webkit-box-shadow: 0 0 6px #7aba7b;
-moz-box-shadow: 0 0 6px #7aba7b;
box-shadow: 0 0 6px #7aba7b;
}
.input-prepend .add-on, .input-append .add-on {
@ -744,8 +467,6 @@ body {
}
input:focus:required:invalid:focus, textarea:focus:required:invalid:focus, select:focus:required:invalid:focus {
border-color: #e9322d;
-webkit-box-shadow: 0 0 6px #f8b9b7;
-moz-box-shadow: 0 0 6px #f8b9b7;
box-shadow: 0 0 6px #f8b9b7;
}
@ -757,8 +478,6 @@ body {
margin-bottom: 0;
*margin-left: 0;
vertical-align: middle;
-webkit-border-radius: 0 3px 3px 0;
-moz-border-radius: 0 3px 3px 0;
border-radius: 0 3px 3px 0;
}
.input-prepend input:focus, .input-append input:focus, .input-prepend select:focus, .input-append select:focus {
@ -780,8 +499,6 @@ body {
}
.input-prepend .add-on, .input-append .add-on, .input-prepend .btn, .input-append .btn {
margin-left: -1px;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
.input-prepend .active, .input-append .active {
@ -793,44 +510,32 @@ body {
margin-right: -1px;
}
.add-on:first-child, .btn:first-child {
-webkit-border-radius: 3px 0 0 3px;
-moz-border-radius: 3px 0 0 3px;
border-radius: 3px 0 0 3px;
}
}
.input-append {
input, select {
-webkit-border-radius: 3px 0 0 3px;
-moz-border-radius: 3px 0 0 3px;
border-radius: 3px 0 0 3px;
}
.add-on:last-child, .btn:last-child {
-webkit-border-radius: 0 3px 3px 0;
-moz-border-radius: 0 3px 3px 0;
border-radius: 0 3px 3px 0;
}
}
.input-prepend.input-append {
input, select {
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
.add-on:first-child, .btn:first-child {
margin-right: -1px;
-webkit-border-radius: 3px 0 0 3px;
-moz-border-radius: 3px 0 0 3px;
border-radius: 3px 0 0 3px;
}
.add-on:last-child, .btn:last-child {
margin-left: -1px;
-webkit-border-radius: 0 3px 3px 0;
-moz-border-radius: 0 3px 3px 0;
border-radius: 0 3px 3px 0;
}
}
.form-horizontal input, .form-horizontal textarea, .form-horizontal select, .form-horizontal .help-inline, .form-horizontal .input-prepend, .form-horizontal .input-append {
.form-horizontal input, .form-horizontal textarea, .form-horizontal select, .form-horizontal .input-prepend, .form-horizontal .input-append {
display: inline-block;
*display: inline;
margin-bottom: 0;
@ -875,13 +580,6 @@ body {
*padding-left: 160px;
}
}
.help-block {
margin-top: 9px;
margin-bottom: 0;
}
.form-actions {
padding-left: 160px;
}
}
.alert {
@ -890,8 +588,6 @@ body {
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
background-color: #fcf8e3;
border: 1px solid #fbeed5;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
color: #c09853;
}
@ -906,7 +602,7 @@ body {
border-color: #d6e9c6;
color: #468847;
}
.alert-danger, .alert-error {
.alert-error {
background-color: #f2dede;
border-color: #eed3d7;
color: #b94a48;
@ -916,18 +612,6 @@ body {
border-color: #bce8f1;
color: #3a87ad;
}
.alert-block {
padding-top: 14px;
padding-bottom: 14px;
> {
p, ul {
margin-bottom: 0;
}
}
p p {
margin-top: 5px;
}
}
.alert {
.close {
float: right;
@ -1016,133 +700,13 @@ table {
}
}
table [class*=span], .row-fluid [class*=span] {
table [class*=span] {
display: table-cell;
float: none;
margin-left: 0;
}
.table {
.span1 {
float: none;
width: 44px;
margin-left: 0;
}
.span2 {
float: none;
width: 124px;
margin-left: 0;
}
.span3 {
float: none;
width: 204px;
margin-left: 0;
}
.span4 {
float: none;
width: 284px;
margin-left: 0;
}
.span5 {
float: none;
width: 364px;
margin-left: 0;
}
.span6 {
float: none;
width: 444px;
margin-left: 0;
}
.span7 {
float: none;
width: 524px;
margin-left: 0;
}
.span8 {
float: none;
width: 604px;
margin-left: 0;
}
.span9 {
float: none;
width: 684px;
margin-left: 0;
}
.span10 {
float: none;
width: 764px;
margin-left: 0;
}
.span11 {
float: none;
width: 844px;
margin-left: 0;
}
.span12 {
float: none;
width: 924px;
margin-left: 0;
}
.span13 {
float: none;
width: 1004px;
margin-left: 0;
}
.span14 {
float: none;
width: 1084px;
margin-left: 0;
}
.span15 {
float: none;
width: 1164px;
margin-left: 0;
}
.span16 {
float: none;
width: 1244px;
margin-left: 0;
}
.span17 {
float: none;
width: 1324px;
margin-left: 0;
}
.span18 {
float: none;
width: 1404px;
margin-left: 0;
}
.span19 {
float: none;
width: 1484px;
margin-left: 0;
}
.span20 {
float: none;
width: 1564px;
margin-left: 0;
}
.span21 {
float: none;
width: 1644px;
margin-left: 0;
}
.span22 {
float: none;
width: 1724px;
margin-left: 0;
}
.span23 {
float: none;
width: 1804px;
margin-left: 0;
}
.span24 {
float: none;
width: 1884px;
margin-left: 0;
}
tbody tr {
&.success td {
background-color: #dff0d8;

View File

@ -1,5 +1,7 @@
/* for the Chosen plugin http://harvesthq.github.com/chosen/ */
/* NOTE THAT ALL VENDOR PREFIX CSS JUNK HAS BEEN REMOVED */
/* @group Base */
.chzn-container {
font-size: 13px;
@ -15,9 +17,6 @@
position: absolute;
top: 29px;
left: 0;
-webkit-box-shadow: 0 4px 5px rgba(0,0,0,.15);
-moz-box-shadow : 0 4px 5px rgba(0,0,0,.15);
-o-box-shadow : 0 4px 5px rgba(0,0,0,.15);
box-shadow : 0 4px 5px rgba(0,0,0,.15);
z-index: 1010;
}
@ -26,22 +25,10 @@
/* @group Single Chosen */
.chzn-container-single .chzn-single {
background-color: #ffffff;
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0 );
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #ffffff), color-stop(50%, #f6f6f6), color-stop(52%, #eeeeee), color-stop(100%, #f4f4f4));
background-image: -webkit-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
background-image: -moz-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
background-image: -o-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
background-image: -ms-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
background-image: linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
-webkit-border-radius: 5px;
-moz-border-radius : 5px;
border-radius : 5px;
-moz-background-clip : padding;
-webkit-background-clip: padding-box;
background-clip : padding-box;
border: 1px solid #aaaaaa;
-webkit-box-shadow: 0 0 3px #ffffff inset, 0 1px 1px rgba(0,0,0,0.1);
-moz-box-shadow : 0 0 3px #ffffff inset, 0 1px 1px rgba(0,0,0,0.1);
box-shadow : 0 0 3px #ffffff inset, 0 1px 1px rgba(0,0,0,0.1);
display: block;
overflow: hidden;
@ -116,11 +103,6 @@
}
.chzn-container-single .chzn-search input {
background: #fff;
background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));
background: -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
background: -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
background: -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
background: -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
background: linear-gradient(top, #eeeeee 1%, #ffffff 15%);
margin: 1px 0;
padding: 7px 30px 7px 10px;
@ -130,11 +112,7 @@
font-size: 1em;
}
.chzn-container-single .chzn-drop {
-webkit-border-radius: 0 0 4px 4px;
-moz-border-radius : 0 0 4px 4px;
border-radius : 0 0 4px 4px;
-moz-background-clip : padding;
-webkit-background-clip: padding-box;
background-clip : padding-box;
}
/* @end */
@ -147,11 +125,6 @@
/* @group Multi Chosen */
.chzn-container-multi .chzn-choices {
background-color: #fff;
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));
background-image: -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
background-image: -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
background-image: -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
background-image: -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
background-image: linear-gradient(top, #eeeeee 1%, #ffffff 15%);
border: 1px solid #aaa;
margin: 0;
@ -181,31 +154,16 @@
padding: 5px;
margin: 1px 0;
outline: 0;
-webkit-box-shadow: none;
-moz-box-shadow : none;
-o-box-shadow : none;
box-shadow : none;
}
.chzn-container-multi .chzn-choices .search-field .default {
color: #999;
}
.chzn-container-multi .chzn-choices .search-choice {
-webkit-border-radius: 3px;
-moz-border-radius : 3px;
border-radius : 3px;
-moz-background-clip : padding;
-webkit-background-clip: padding-box;
background-clip : padding-box;
background-color: #e4e4e4;
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f4f4f4', endColorstr='#eeeeee', GradientType=0 );
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -ms-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
-webkit-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
-moz-box-shadow : 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
box-shadow : 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
color: #333;
border: 1px solid #aaaaaa;
@ -263,12 +221,6 @@
}
.chzn-container .chzn-results .highlighted {
background-color: #3875d7;
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#3875d7', endColorstr='#2a62bc', GradientType=0 );
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #3875d7), color-stop(90%, #2a62bc));
background-image: -webkit-linear-gradient(top, #3875d7 20%, #2a62bc 90%);
background-image: -moz-linear-gradient(top, #3875d7 20%, #2a62bc 90%);
background-image: -o-linear-gradient(top, #3875d7 20%, #2a62bc 90%);
background-image: -ms-linear-gradient(top, #3875d7 20%, #2a62bc 90%);
background-image: linear-gradient(top, #3875d7 20%, #2a62bc 90%);
color: #fff;
}
@ -321,30 +273,14 @@
/* @group Active */
.chzn-container-active .chzn-single {
-webkit-box-shadow: 0 0 5px rgba(0,0,0,.3);
-moz-box-shadow : 0 0 5px rgba(0,0,0,.3);
-o-box-shadow : 0 0 5px rgba(0,0,0,.3);
box-shadow : 0 0 5px rgba(0,0,0,.3);
border: 1px solid #5897fb;
}
.chzn-container-active .chzn-single-with-drop {
border: 1px solid #aaa;
-webkit-box-shadow: 0 1px 0 #fff inset;
-moz-box-shadow : 0 1px 0 #fff inset;
-o-box-shadow : 0 1px 0 #fff inset;
box-shadow : 0 1px 0 #fff inset;
background-color: #eee;
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0 );
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #eeeeee), color-stop(80%, #ffffff));
background-image: -webkit-linear-gradient(top, #eeeeee 20%, #ffffff 80%);
background-image: -moz-linear-gradient(top, #eeeeee 20%, #ffffff 80%);
background-image: -o-linear-gradient(top, #eeeeee 20%, #ffffff 80%);
background-image: -ms-linear-gradient(top, #eeeeee 20%, #ffffff 80%);
background-image: linear-gradient(top, #eeeeee 20%, #ffffff 80%);
-webkit-border-bottom-left-radius : 0;
-webkit-border-bottom-right-radius: 0;
-moz-border-radius-bottomleft : 0;
-moz-border-radius-bottomright: 0;
border-bottom-left-radius : 0;
border-bottom-right-radius: 0;
}
@ -356,9 +292,6 @@
background-position: -18px 1px;
}
.chzn-container-active .chzn-choices {
-webkit-box-shadow: 0 0 5px rgba(0,0,0,.3);
-moz-box-shadow : 0 0 5px rgba(0,0,0,.3);
-o-box-shadow : 0 0 5px rgba(0,0,0,.3);
box-shadow : 0 0 5px rgba(0,0,0,.3);
border: 1px solid #5897fb;
}
@ -398,11 +331,6 @@
.chzn-rtl.chzn-container-active .chzn-single-with-drop div { border-right: none; }
.chzn-rtl .chzn-search input {
background: #fff;
background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));
background: -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
background: -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
background: -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
background: -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
background: linear-gradient(top, #eeeeee 1%, #ffffff 15%);
padding: 4px 5px 4px 20px;
direction: rtl;

View File

@ -51,145 +51,22 @@ a [class^="icon-"],
a [class*=" icon-"] {
display: inline-block;
}
/* makes the font 33% larger relative to the icon container */
.icon-large:before {
vertical-align: -10%;
font-size: 1.3333333333333333em;
}
.btn [class^="icon-"],
.nav [class^="icon-"],
.btn [class*=" icon-"],
.nav [class*=" icon-"] {
display: inline;
/* keeps button heights with and without icons the same */
line-height: .6em;
}
.btn [class^="icon-"].icon-spin,
.nav [class^="icon-"].icon-spin,
.btn [class*=" icon-"].icon-spin,
.nav [class*=" icon-"].icon-spin {
display: inline-block;
}
li [class^="icon-"],
li [class*=" icon-"] {
display: inline-block;
width: 1.25em;
text-align: center;
}
li [class^="icon-"].icon-large,
li [class*=" icon-"].icon-large {
/* increased font size for icon-large */
width: 1.5625em;
}
ul.icons {
list-style-type: none;
}
ul.icons li [class^="icon-"],
ul.icons li [class*=" icon-"] {
width: .75em;
}
.icon-muted {
color: #eeeeee;
}
.icon-border {
border: solid 1px #eeeeee;
padding: .2em .25em .15em;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
.icon-2x {
font-size: 2em;
}
.icon-2x.icon-border {
border-width: 2px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.icon-3x {
font-size: 3em;
}
.icon-3x.icon-border {
border-width: 3px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
.icon-4x {
font-size: 4em;
}
.icon-4x.icon-border {
border-width: 4px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
}
.pull-right {
float: right;
}
.pull-left {
float: left;
}
[class^="icon-"].pull-left,
[class*=" icon-"].pull-left {
margin-right: .35em;
}
[class^="icon-"].pull-right,
[class*=" icon-"].pull-right {
margin-left: .35em;
}
.btn [class^="icon-"].pull-left.icon-2x,
.btn [class*=" icon-"].pull-left.icon-2x,
.btn [class^="icon-"].pull-right.icon-2x,
.btn [class*=" icon-"].pull-right.icon-2x {
margin-top: .35em;
}
.btn [class^="icon-"].icon-spin.icon-large,
.btn [class*=" icon-"].icon-spin.icon-large {
height: .75em;
}
.btn.btn-small [class^="icon-"].pull-left.icon-2x,
.btn.btn-small [class*=" icon-"].pull-left.icon-2x,
.btn.btn-small [class^="icon-"].pull-right.icon-2x,
.btn.btn-small [class*=" icon-"].pull-right.icon-2x {
margin-top: .45em;
}
.btn.btn-large [class^="icon-"].pull-left.icon-2x,
.btn.btn-large [class*=" icon-"].pull-left.icon-2x,
.btn.btn-large [class^="icon-"].pull-right.icon-2x,
.btn.btn-large [class*=" icon-"].pull-right.icon-2x {
margin-top: .2em;
}
.icon-spin {
display: inline-block;
-moz-animation: spin 2s infinite linear;
-o-animation: spin 2s infinite linear;
-webkit-animation: spin 2s infinite linear;
animation: spin 2s infinite linear;
}
@-moz-keyframes spin {
0% { -moz-transform: rotate(0deg); }
100% { -moz-transform: rotate(359deg); }
}
@-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(359deg); }
}
@-o-keyframes spin {
0% { -o-transform: rotate(0deg); }
100% { -o-transform: rotate(359deg); }
}
@-ms-keyframes spin {
0% { -ms-transform: rotate(0deg); }
100% { -ms-transform: rotate(359deg); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(359deg); }
li [class^="icon-"],li [class*=" icon-"] {
display: inline-block;
width: 1.25em;
text-align: center;
}
/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
readers do not read off random characters that represent icons */
.icon-glass:before { content: "\f000"; }
@ -459,4 +336,3 @@ ul.icons li [class*=" icon-"] {
.icon-github-alt:before { content: "\f113"; }
.icon-folder-close-alt:before { content: "\f114"; }
.icon-folder-open-alt:before { content: "\f115"; }

View File

@ -4,7 +4,7 @@ class Admin::ExportController < Admin::AdminController
job_id = Jobs.enqueue( :exporter, user_id: current_user.id )
render json: success_json.merge( job_id: job_id )
else
render json: failed_json.merge( message: "An #{Export.is_export_running? ? 'export' : 'import'} is currently running. Can't start a new export job right now.")
render json: failed_json.merge( message: I18n.t('operation_already_running', { operation: Export.is_export_running? ? 'export' : 'import' }))
end
end
end

View File

@ -207,7 +207,7 @@ class ApplicationController < ActionController::Base
def block_if_maintenance_mode
if Discourse.maintenance_mode?
if request.format.json?
render status: 503, json: failed_json.merge( message: 'Site is currently undergoing maintenance.' )
render status: 503, json: failed_json.merge(message: I18n.t('site_under_maintenance'))
else
render status: 503, file: File.join( Rails.root, 'public', '503.html' ), layout: false
end

View File

@ -6,11 +6,16 @@ class EducationController < ApplicationController
raise Discourse::InvalidAccess.new unless params[:id] =~ /^[a-z0-9\-\_]+$/
raise Discourse::NotFound.new if I18n.t("education.#{params[:id]}", default: MISSING_KEY) == MISSING_KEY
education_posts_text = I18n.t('education.until_posts', count: SiteSetting.educate_until_posts)
markdown_content = MultisiteI18n.t("education.#{params[:id]}",
site_name: SiteSetting.title,
education_posts_text: education_posts_text)
markdown_content = ""
if params[:id] == 'new-topic'
markdown_content = SiteContent.content_for(:education_new_topic, education_posts_text: education_posts_text)
else
markdown_content = SiteContent.content_for(:education_new_reply, education_posts_text: education_posts_text)
end
render text: PrettyText.cook(markdown_content)
end

View File

@ -151,7 +151,6 @@ class TopicsController < ApplicationController
PostTiming.process_timings(
current_user,
params[:topic_id].to_i,
params[:highest_seen].to_i,
params[:topic_time].to_i,
(params[:timings] || []).map{|post_number, t| [post_number.to_i, t.to_i]}
)

View File

@ -4,7 +4,9 @@ class UploadsController < ApplicationController
def create
requires_parameter(:topic_id)
file = params[:file] || params[:files].first
upload = Upload.create_for(current_user, file, params[:topic_id])
# only supports images for now
return render status: 415, json: failed_json unless file.content_type =~ /^image\/.+/
upload = Upload.create_for(current_user.id, file, params[:topic_id])
render_serialized(upload, UploadSerializer, root: false)
end
end

View File

@ -54,6 +54,10 @@ module ApplicationHelper
result
end
def markdown_content(key, replacements=nil)
PrettyText.cook(SiteContent.content_for(key, replacements || {})).html_safe
end
def faq_path
return "#{Discourse::base_uri}/faq"
end

View File

@ -7,7 +7,8 @@ class CategoryList
@categories = Category
.includes(featured_topics: [:category])
.includes(:featured_users)
.order('topics_week desc, topics_month desc, topics_year desc')
.where('topics.visible' => true)
.order('categories.topics_week desc, categories.topics_month desc, categories.topics_year desc')
.to_a
# Support for uncategorized topics

View File

@ -65,7 +65,7 @@ class Post < ActiveRecord::Base
# Stop us from posting the same thing too quickly
def unique_post_validator
return if SiteSetting.unique_posts_mins == 0
return if user.admin? || user.moderator?
return if acting_user.admin? || acting_user.moderator?
# If the post is empty, default to the validates_presence_of
return if raw.blank?
@ -125,8 +125,19 @@ class Post < ActiveRecord::Base
total
end
# Sometimes the post is being edited by someone else, for example, a mod.
# If that's the case, they should not be bound by the original poster's
# restrictions, for example on not posting images.
def acting_user
@acting_user || user
end
def acting_user=(pu)
@acting_user = pu
end
def max_mention_validator
if user.present? && user.has_trust_level?(:basic)
if acting_user.present? && acting_user.has_trust_level?(:basic)
errors.add(:base, I18n.t(:too_many_mentions, count: SiteSetting.max_mentions_per_post)) if raw_mentions.size > SiteSetting.max_mentions_per_post
else
errors.add(:base, I18n.t(:too_many_mentions_visitor, count: SiteSetting.visitor_max_mentions_per_post)) if raw_mentions.size > SiteSetting.visitor_max_mentions_per_post
@ -134,12 +145,12 @@ class Post < ActiveRecord::Base
end
def max_images_validator
return if user.present? && user.has_trust_level?(:basic)
return if acting_user.present? && acting_user.has_trust_level?(:basic)
errors.add(:base, I18n.t(:too_many_images, count: SiteSetting.visitor_max_images)) if image_count > SiteSetting.visitor_max_images
end
def max_links_validator
return if user.present? && user.has_trust_level?(:basic)
return if acting_user.present? && acting_user.has_trust_level?(:basic)
errors.add(:base, I18n.t(:too_many_links, count: SiteSetting.visitor_max_links)) if link_count > SiteSetting.visitor_max_links
end

View File

@ -37,15 +37,19 @@ class PostTiming < ActiveRecord::Base
end
def self.process_timings(current_user, topic_id, highest_seen, topic_time, timings)
def self.process_timings(current_user, topic_id, topic_time, timings)
current_user.update_time_read!
highest_seen = 1
timings.each do |post_number, time|
if post_number >= 0
PostTiming.record_timing(topic_id: topic_id,
post_number: post_number,
user_id: current_user.id,
msecs: time)
highest_seen = post_number.to_i > highest_seen ?
post_number.to_i : highest_seen
end
end

View File

@ -4,18 +4,19 @@ require_dependency 'site_content_class_methods'
class SiteContent < ActiveRecord::Base
extend SiteContentClassMethods
set_primary_key :content_type
self.primary_key = 'content_type'
validates_presence_of :content
def self.formats
@formats ||= Enum.new(:plain, :markdown, :html, :css)
end
content_type :usage_tips, :markdown, default_18n_key: 'system_messages.usage_tips.text_body_template'
content_type :welcome_user, :markdown, default_18n_key: 'system_messages.welcome_user.text_body_template'
content_type :welcome_invite, :markdown, default_18n_key: 'system_messages.welcome_invite.text_body_template'
content_type :education_new_topic, :markdown, default_18n_key: 'education.new-topic'
content_type :education_new_reply, :markdown, default_18n_key: 'education.new-reply'
add_content_type :usage_tips, default_18n_key: 'system_messages.usage_tips.text_body_template'
add_content_type :education_new_topic, default_18n_key: 'education.new-topic'
add_content_type :education_new_reply, default_18n_key: 'education.new-reply'
add_content_type :tos_user_content_license, default_18n_key: 'terms_of_service.user_content_license'
add_content_type :tos_miscellaneous, default_18n_key: 'terms_of_service.miscellaneous'
def site_content_type
@site_content_type ||= SiteContent.content_types.find {|t| t.content_type == content_type.to_sym}

View File

@ -1,5 +1,3 @@
require_dependency 'multisite_i18n'
class SiteContentType
attr_accessor :content_type, :format
@ -20,7 +18,7 @@ class SiteContentType
def default_content
if @opts[:default_18n_key].present?
return MultisiteI18n.t(@opts[:default_18n_key])
return I18n.t(@opts[:default_18n_key])
end
""
end

View File

@ -117,6 +117,10 @@ class TopicUser < ActiveRecord::Base
threshold: SiteSetting.auto_track_topics_after
}
# In case anyone seens "seen_post_count" and gets confused, like I do.
# seen_post_count represents the highest_post_number of the topic when
# the user visited it. It may be out of alignement with last_read, meaning
# ... user visited the topic but did not read the posts
rows = exec_sql("UPDATE topic_users
SET
last_read_post_number = greatest(:post_number, tu.last_read_post_number),
@ -176,17 +180,21 @@ class TopicUser < ActiveRecord::Base
UPDATE topic_users t
SET
last_read_post_number = last_read,
seen_post_count = post_count
seen_post_count = LEAST(max_post_number,GREATEST(t.seen_post_count, last_read))
FROM (
SELECT topic_id, user_id, COUNT(*) post_count, MAX(post_number) last_read
SELECT topic_id, user_id, MAX(post_number) last_read
FROM post_timings
GROUP BY topic_id, user_id
) as X
JOIN (
SELECT p.topic_id, MAX(p.post_number) max_post_number from posts p
GROUP BY p.topic_id
) as Y on Y.topic_id = X.topic_id
WHERE X.topic_id = t.topic_id AND
X.user_id = t.user_id AND
(
last_read_post_number <> last_read OR
seen_post_count <> post_count
seen_post_count <> LEAST(max_post_number,GREATEST(t.seen_post_count, last_read))
)
SQL
end

View File

@ -9,57 +9,33 @@ class Upload < ActiveRecord::Base
validates_presence_of :filesize
validates_presence_of :original_filename
# Create an upload given a user, file and topic
def self.create_for(user_id, file, topic_id)
return create_on_imgur(user_id, file, topic_id) if SiteSetting.enable_imgur?
return create_on_s3(user_id, file, topic_id) if SiteSetting.enable_s3_uploads?
return create_locally(user_id, file, topic_id)
end
# Create an upload given a user, file and optional topic_id
def self.create_for(user, file, topic_id = nil)
# TODO: Need specs/tests for this functionality
return create_on_imgur(user, file, topic_id) if SiteSetting.enable_imgur?
return create_on_s3(user, file, topic_id) if SiteSetting.enable_s3_uploads?
return create_locally(user, file, topic_id)
# Store uploads on imgur
def self.create_on_imgur(user_id, file, topic_id)
@imgur_loaded = require 'imgur' unless @imgur_loaded
info = Imgur.upload_file(file)
Upload.create!({
user_id: user_id,
topic_id: topic_id,
original_filename: file.original_filename
}.merge!(info))
end
# Store uploads on s3
def self.create_on_imgur(user, file, topic_id)
@imgur_loaded = require 'imgur' unless @imgur_loaded
info = Imgur.upload_file(file)
Upload.create!({user_id: user.id,
topic_id: topic_id,
original_filename: file.original_filename}.merge!(info))
end
def self.create_locally(user, file, topic_id)
upload = Upload.create!(user_id: user.id,
topic_id: topic_id,
url: "",
filesize: File.size(file.tempfile),
original_filename: file.original_filename)
# populate the rest of the info
clean_name = Digest::SHA1.hexdigest("#{Time.now.to_s}#{file.original_filename}")[0,16]
image_info = FastImage.new(file.tempfile)
clean_name += ".#{image_info.type}"
url_root = "/uploads/#{RailsMultisite::ConnectionManagement.current_db}/#{upload.id}"
path = "#{Rails.root}/public#{url_root}"
upload.width, upload.height = ImageSizer.resize(*image_info.size)
FileUtils.mkdir_p path
# not using cause mv, cause permissions are no good on move
File.open("#{path}/#{clean_name}", "wb") do |f|
f.write File.read(file.tempfile)
end
upload.url = Discourse::base_uri + "#{url_root}/#{clean_name}"
upload.save
upload
end
def self.create_on_s3(user, file, topic_id)
def self.create_on_s3(user_id, file, topic_id)
@fog_loaded = require 'fog' unless @fog_loaded
tempfile = file.tempfile
upload = Upload.new(user_id: user.id,
upload = Upload.new(user_id: user_id,
topic_id: topic_id,
filesize: File.size(tempfile),
original_filename: file.original_filename)
@ -76,7 +52,6 @@ class Upload < ActiveRecord::Base
location = "#{SiteSetting.s3_upload_bucket}#{path}"
directory = fog.directories.create(key: location)
Rails.logger.info "#{blob.size.inspect}"
file = directory.files.create(key: remote_filename,
body: tempfile,
public: true,
@ -88,4 +63,33 @@ class Upload < ActiveRecord::Base
upload
end
def self.create_locally(user_id, file, topic_id)
upload = Upload.create!({
user_id: user_id,
topic_id: topic_id,
url: "",
filesize: File.size(file.tempfile),
original_filename: file.original_filename
})
# populate the rest of the info
clean_name = Digest::SHA1.hexdigest("#{Time.now.to_s}#{file.original_filename}")[0,16]
image_info = FastImage.new(file.tempfile)
clean_name += ".#{image_info.type}"
url_root = "/uploads/#{RailsMultisite::ConnectionManagement.current_db}/#{upload.id}"
path = "#{Rails.root}/public#{url_root}"
upload.width, upload.height = ImageSizer.resize(*image_info.size)
FileUtils.mkdir_p path
# not using cause mv, cause permissions are no good on move
File.open("#{path}/#{clean_name}", "wb") do |f|
f.write File.read(file.tempfile)
end
upload.url = Discourse::base_uri + "#{url_root}/#{clean_name}"
upload.save
upload
end
end

View File

@ -1,6 +1,15 @@
class CategoryDetailedSerializer < CategorySerializer
class CategoryDetailedSerializer < ApplicationSerializer
attributes :topic_count, :topics_week, :topics_month, :topics_year
attributes :id,
:name,
:color,
:text_color,
:slug,
:topic_count,
:topics_week,
:topics_month,
:topics_year,
:description
has_many :featured_users, serializer: BasicUserSerializer
has_many :featured_topics, serializer: CategoryTopicSerializer, embed: :objects, key: :topics

View File

@ -3,7 +3,6 @@ require_dependency 'pinned_check'
class CategoryTopicSerializer < ListableTopicSerializer
attributes :visible, :closed, :archived, :pinned
has_one :category
def pinned
PinnedCheck.new(object, object.user_data).pinned?

View File

@ -36,17 +36,7 @@
<div id="3"></div>
<h2><a href="#3">3. User Content License</a></h2>
<!--<p>
By submitting Content to <%= SiteSetting.company_short_name %> for inclusion on your Website, you grant <%= SiteSetting.company_short_name %> a world-wide, royalty-free, and non-exclusive license to reproduce, modify, adapt and publish the Content solely for the purpose of displaying, distributing and promoting your content. If you delete Content, <%= SiteSetting.company_short_name %> will use reasonable efforts to remove it from the Website, but you acknowledge that caching or references to the Content may not be made immediately unavailable.
</p>-->
<p>
User contributions are licensed under a <a href="http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US">Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License</a>. Without limiting any of those representations or warranties, <%= SiteSetting.company_short_name %> has the right (though not the obligation) to, in <%= SiteSetting.company_short_name %>s sole discretion (i) refuse or remove any content that, in <%= SiteSetting.company_short_name %>s reasonable opinion, violates any <%= SiteSetting.company_short_name %> policy or is in any way harmful or objectionable, or (ii) terminate or deny access to and use of the Website to any individual or entity for any reason, in <%= SiteSetting.company_short_name %>s sole discretion. <%= SiteSetting.company_short_name %> will have no obligation to provide a refund of any amounts previously paid.
</p>
<p>
Without limiting any of those representations or warranties, <%= SiteSetting.company_short_name %> has the right (though not the obligation) to, in <%= SiteSetting.company_short_name %>s sole discretion (i) refuse or remove any content that, in <%= SiteSetting.company_short_name %>s reasonable opinion, violates any <%= SiteSetting.company_short_name %> policy or is in any way harmful or objectionable, or (ii) terminate or deny access to and use of the Website to any individual or entity for any reason, in <%= SiteSetting.company_short_name %>s sole discretion. <%= SiteSetting.company_short_name %> will have no obligation to provide a refund of any amounts previously paid.
</p>
<%= markdown_content(:tos_user_content_license) %>
<div id="4"></div>
<h2><a href="#4">4. Payment and Renewal</a></h2>
@ -133,6 +123,4 @@ You agree to indemnify and hold harmless <%= SiteSetting.company_short_name %>,
<div id="18"></div>
<h2><a href="#18">18. Miscellaneous</a></h2>
<p>
This Agreement constitutes the entire agreement between <%= SiteSetting.company_short_name %> and you concerning the subject matter hereof, and they may only be modified by a written amendment signed by an authorized executive of <%= SiteSetting.company_short_name %>, or by the posting by <%= SiteSetting.company_short_name %> of a revised version. Except to the extent applicable law, if any, provides otherwise, this Agreement, any access to or use of the Website will be governed by the laws of the state of California, U.S.A., excluding its conflict of law provisions, and the proper venue for any disputes arising out of or relating to any of the same will be the state and federal courts located in San Francisco County, California. Except for claims for injunctive or equitable relief or claims regarding intellectual property rights (which may be brought in any competent court without the posting of a bond), any dispute arising under this Agreement shall be finally settled in accordance with the Comprehensive Arbitration Rules of the Judicial Arbitration and Mediation Service, Inc. (“JAMS”) by three arbitrators appointed in accordance with such Rules. The arbitration shall take place in San Francisco, California, in the English language and the arbitral decision may be enforced in any court. The prevailing party in any action or proceeding to enforce this Agreement shall be entitled to costs and attorneys fees. If any part of this Agreement is held invalid or unenforceable, that part will be construed to reflect the parties original intent, and the remaining portions will remain in full force and effect. A waiver by either party of any term or condition of this Agreement or any breach thereof, in any one instance, will not waive such term or condition or any subsequent breach thereof. You may assign your rights under this Agreement to any party that consents to, and agrees to be bound by, its terms and conditions; <%= SiteSetting.company_short_name %> may assign its rights under this Agreement without condition. This Agreement will be binding upon and will inure to the benefit of the parties, their successors and permitted assigns.
</p>
<%= markdown_content(:tos_miscellaneous) %>

View File

@ -307,7 +307,7 @@ en:
link_dialog_title: "Insert Hyperlink"
link_optional_text: "optional title"
quote_title: "Blockquote"
quote_text: "Blockquote"
quote_text: "Quote whole post"
code_title: "Code Sample"
code_text: "enter code here"
image_title: "Image"

View File

@ -303,7 +303,7 @@ es:
link_dialog_title: "Insert Hyperlink"
link_optional_text: "optional title"
quote_title: "Blockquote"
quote_text: "Blockquote"
quote_text: "Quote whole post"
code_title: "Code Sample"
code_text: "enter code here"
image_title: "Image"

View File

@ -391,6 +391,7 @@ fr:
category: "Il n'y a plus de discussion sur {{category}} à lire."
rank_details:
toggle: "afficher/cacher le détail du classement des discussions"
show: "afficher le détail du classement des discussions"
title: "Détail du classement des discussions"
@ -593,15 +594,82 @@ fr:
clear_flags:
one: "Annuler le signalement"
other: "Annuler les signalements"
it_too: "{{alsoName}} aussi"
undo: "Annuler {{alsoName}}"
it_too:
off_topic: "Le signaler également"
spam: "Le signaler également"
inappropriate: "Le signaler également"
custom_flag: "Le signaler également"
bookmark: "L'ajouter également en favoris"
like: "L'aimer également"
vote: "Votez pour lui également"
undo:
off_topic: "Annuler le signalement"
spam: "Annuler le signalement"
inappropriate: "Annuler le signalement"
custom_flag: "Annuler le signalement"
bookmark: "L'enlever des favoris"
like: "Annuler j'aime"
vote: "Retirer votre vote"
people:
off_topic: "{{icons}} l'ont signalé comme étant hors-sujet"
spam: "{{icons}} l'ont signalé comme étant du spam"
inappropriate: "{{icons}} l'ont signalé comme inaproprié"
custom_flag: "{{icons}} l'ont signalé pour modération"
bookmark: "{{icons}} l'ont ajouté à leurs favoris"
like: "{{icons}} l'ont aimé"
vote: "{{icons}} ont voté pour"
by_you:
off_topic: "Vous l'avez signalé comme étant hors-sujet"
spam: "Vous l'avez signalé comme étant du spam"
inappropriate: "Vous l'avez signalé comme inaproprié"
custom_flag: "Vous l'avez signalé pour modération"
bookmark: "Vous l'avez ajouté à vos favoris"
like: "Vous l'avez aimé"
vote: "Vous avez voté pour"
by_you_and_others:
zero: "Vous {{long_form}}"
one: "Vous et une autre personne {{long_form}}"
other: "Vous et {{count}} autres personnes {{long_form}}"
off_topic:
one: "Vous et 1 autre personne l'avez signalé comme étant hors-sujet"
other: "Vous et {{count}} autres personnes l'avez signalé comme étant hors-sujet"
spam:
one: "Vous et 1 autre personne l'avez signalé comme étant du spam"
other: "Vous et {{count}} autres personnes l'avez signalé comme étant du spam"
inappropriate:
one: "Vous et 1 autre personne l'avez signalé comme inaproprié"
other: "Vous et {{count}} autres personnes l'avez signalé comme inaproprié"
custom_flag:
one: "Vous et 1 autre personne l'avez signalé pour modération"
other: "Vous et {{count}} autres personnes l'avez signalé pour modération"
bookmark:
one: "Vous et 1 autre personne l'avez ajouté à vos favoris"
other: "Vous et {{count}} autres personnes l'avez ajouté à vos favoris"
like:
one: "Vous et 1 autre personne l'avez aimé"
other: "Vous et {{count}} autres personnes l'avez aimé"
vote:
one: "Vous et 1 autre personne avez voté pour"
other: "Vous et {{count}} autres personnes avez voté pour"
by_others:
one: "Une personne {{long_form}}"
other: "{{count}} personnes {{long_form}}"
off_topic:
one: "1 personne l'a signalé comme étant hors-sujet"
other: "{{count}} personnes l'ont signalé comme étant hors-sujet"
spam:
one: "1 personne l'a signalé comme étant du spam"
other: "{{count}} personnes l'ont signalé comme étant du spam"
inappropriate:
one: "1 personne l'a signalé comme inaproprié"
other: "{{count}} personnes l'ont signalé comme inaproprié"
custom_flag:
one: "1 personne l'a signalé pour modération"
other: "{{count}} personnes l'ont signalé pour modération"
bookmark:
one: "1 personne l'a ajouté à vos favoris"
other: "{{count}} personnes l'ont ajouté à vos favoris"
like:
one: "1 personne l'a aimé"
other: "{{count}} personnes l'ont aimé"
vote:
one: "1 personne a voté pour"
other: "{{count}} personnes ont voté pour"
edits:
one: "une édition"
@ -722,6 +790,7 @@ fr:
admin:
title: 'Administation Discourse'
moderator: 'Modérateur'
dashboard:
title: "Panel d'administration"
@ -842,6 +911,11 @@ fr:
approved_by: "approuvé par"
time_read: "Temps de lecture"
site_content:
none: "Choisissez un type de contenu afin de commencer l'édition."
title: 'Contenu du site'
edit: "Modifier le contenu du site"
site_settings:
show_overriden: 'Ne montrer que ce qui a été changé'
title: 'Paramètres du site'

View File

@ -304,7 +304,7 @@ it:
link_dialog_title: "Inserisci Link"
link_optional_text: "titolo facoltativo"
quote_title: "Blockquote"
quote_text: "Blockquote"
quote_text: "Quote whole post"
code_title: "Code Sample"
code_text: "inserisci il codice qui"
image_title: "Immagine"

View File

@ -7,6 +7,10 @@ sv:
share:
topic: 'dela en länk till denna tråd'
post: 'dela en länk till denna tråd'
close: 'stäng'
twitter: 'dela denna länk via Twitter'
facebook: 'dela denna länk via Facebook'
google+: 'dela denna länk via Google+'
edit: 'ändra titel och kategori för denna tråd'
not_implemented: "Den funktionen har inte implementerats än, tyvärr!"
@ -23,8 +27,8 @@ sv:
links: Länkar
faq: "FAQ"
you: "Du"
ok: "ok"
or: "eller"
now: "just nu"
suggested_topics:
title: "Föreslagna Trådar"
@ -60,8 +64,10 @@ sv:
preferences: "Inställningar"
bio: "Om mig"
change_password: "byt"
invited_by: "Inbjuden AV"
invited_by: "Inbjuden Av"
trust_level: "Pålitlighetsnivå"
external_links_in_new_tab: "Öppna alla externa länkar i en ny flik"
enable_quoting: "Aktivera citatsvar för markerad text"
change_username:
action: "byt"
@ -92,8 +98,8 @@ sv:
ok: "Ditt namn ser bra ut."
username:
title: "Användarnamn"
#instructions: "Personer kan omnämna dig som @{{username}}. Det här är ett oregistrerat smeknamn. Du kan registrera det på <a href='http://discourse.org'>discourse.org</a>."
instructions: "Personer kan omnämna dig som @{{username}}."
instructions: "Måste vara unikt, inga mellanrum. Personer kan omnämna dig som @{{username}}."
short_instructions: "Personer kan omnämna dig som @{{username}}."
available: "Ditt användarnamn är tillgängligt."
global_match: "E-posten matchar det registrerade användarnamnet."
global_mismatch: "Redan registrerat. Prova {{suggestion}}?"
@ -103,6 +109,9 @@ sv:
checking: "Kollar användarnamnets tillgänglighet..."
enter_email: 'Användarnamn hittat. Ange matchande e-post.'
password_confirmation:
title: "Lösenord Igen"
last_posted: "Senaste Inlägg"
last_emailed: "Senast Mailad"
last_seen: "Senast Sedd"
@ -196,8 +205,10 @@ sv:
best_of:
title: "Bäst Av"
enabled_description: Just nu visar du "Bäst Av"-läget för denna tråd.
description: "Det finns <b>{{count}}</b> inlägg i den här tråden. Det är många! Vill du spara tid genom att byta så du bara ser de inlägg med flest interaktioner och svar?"
button: 'Byt till "Bäst Av"-läget'
enable: 'Byt till "Bäst Av"-läget'
disable: 'Avbryt "Bäst Av"'
private_message_info:
title: "Privat Konversation"
@ -238,32 +249,41 @@ sv:
resend_activation_email: "Klicka här för att skicka aktiveringsmailet igen."
sent_activation_email_again: "Vi har skickat ännu ett aktiveringsmail till dig via <b>{{currentEmail}}</b>. Det kan ta ett par minuter för det att komma fram; var noga med att kolla din skräppost."
google:
title: "Logga In med Google"
title: "med Google"
message: "Autentiserar med Google (kolla så att pop up-blockare inte är aktiverade)"
twitter:
title: "Logga In med Twitter"
title: "med Twitter"
message: "Autentiserar med Twitter (kolla så att pop up-blockare inte är aktiverade)"
facebook:
title: "Logga In med Facebook"
title: "med Facebook"
message: "Autentiserar med Facebook (kolla så att pop up-blockare inte är aktiverade)"
yahoo:
title: "Logga In med Yahoo"
title: "med Yahoo"
message: "Autentiserar med Yahoo (kolla så att pop up-blockare inte är aktiverade)"
github:
title: "Logga In med GitHub"
description: "Autentiserar med GitHub (kolla så att pop up-blockare inte är aktiverade)"
title: "med GitHub"
message: "Autentiserar med GitHub (kolla så att pop up-blockare inte är aktiverade)"
persona:
title: " med Persona"
message: "Autentiserar med Mozilla Persona (kolla så att pop up-blockare inte är aktiverade)"
composer:
posting_not_on_topic: "Du svarar på tråden \"{{title}}\", men du besöker just nu en annan tråd."
saving_draft_tip: "sparar"
saved_draft_tip: "sparat"
saved_local_draft_tip: "sparat lokalt"
similar_topics: "Din tråd liknar..."
drafts_offline: "utkast offline"
min_length:
at_least: "skriv minst {{n}} tecken"
more: "{{n}} fler..."
need_more_for_title: "{{n}} tecken kvar för titeln"
need_more_for_reply: "{{n}} tecken kvar för svaret"
save_edit: "Spara Ändring"
reply_original: "Svara på Ursprungstråd"
reply_here: "Svara Här"
reply: "Svara"
cancel: "Avbryt"
create_topic: "Skapa Tråd"
create_pm: "Skapa Privat Meddelande"
@ -278,6 +298,33 @@ sv:
show_preview: 'visa förhandsgranskning &raquo;'
hide_preview: '&laquo; dölj förhandsgranskning'
bold_title: "Fet"
bold_text: "fet text"
italic_title: "Kursiv"
italic_text: "kursiv text"
link_title: "Hyperlänk"
link_description: "skriv en länkbeskrivning här"
link_dialog_title: "Infoga Hyperlänk"
link_optional_text: "valfri titel"
quote_title: "Citat"
quote_text: "Citat"
code_title: "Kodexempel"
code_text: "skriv din kod här"
image_title: "Bild"
image_description: "skriv en bildbeskrivning här"
image_dialog_title: "Infoga Bild"
image_optional_text: "valfri titel"
image_hosting_hint: "Behöver du <a href='http://www.google.com/search?q=free+image+hosting' target='_blank'>gratis bilduppladdning?</a>"
olist_title: "Numrerad Lista"
ulist_title: "Punktlista"
list_item: "Listobjekt"
heading_title: "Rubrik"
heading_text: "Rubrik"
hr_title: "Horisontell linje"
undo_title: "Ångra"
redo_title: "Återställ"
help: "Markdown Redigeringshjälp"
notifications:
title: "notifikationer med omnämnanden av @namn, svar på dina inlägg och trådar, privata meddelanden, etc"
none: "Du har inte notifikationer just nu."
@ -294,6 +341,7 @@ sv:
moved_post: "<i title='flyttade inlägg' class='icon icon-arrow-right'></i> {{username}} flyttade inlägg till {{link}}"
image_selector:
title: "Infoga Bild"
from_my_computer: "Från Min Enhet"
from_the_web: "Från Internet"
add_image: "Lägg Till Bild"
@ -314,7 +362,9 @@ sv:
favorite:
title: 'Favorit'
help: 'lägg till denna tråd i din favoritlista'
help:
star: 'lägg till denna tråd i din favoritlista'
unstar: 'ta bort denna tråd från din favoritlista'
topics:
none:
@ -323,10 +373,12 @@ sv:
new: "Du har inga nya trådar att läsa."
read: "Du har inte läst några trådar än."
posted: "Du har inte postat i några trådar än."
latest: "Det finns inga populära trådar. Det är lite sorgligt."
latest: "Det finns inga senaste trådar. Det är lite sorgligt."
hot: "Det finns inga heta trådar."
category: "Det finns inga {{category}}-trådar."
bottom:
latest: "Det finns inga fler populära trådar att läsa."
latest: "Det finns inga fler senaste trådar att läsa."
hot: "Det finns inga fler heta trådar att läsa."
posted: "Det finns inga fler postade trådar att läsa"
read: "Det finns inga fler lästa trådar att läsa."
new: "Det finns inga fler nya trådar att läsa."
@ -334,6 +386,10 @@ sv:
favorited: "Det finns inga fler favoritmarkerade trådar att läsa."
category: "Det finns inga fler {{category}}-trådar."
rank_details:
toggle: växla på/av trådranksdetaljer
show: visa trådranksdetaljer
title: Trådranksdetaljer
topic:
create_in: 'Skapa {{categoryName}}-tråd'
@ -356,6 +412,7 @@ sv:
description: "Tyvärr, vi kunde inte hitta den tråden. Kanske har den tagits bort av en moderator?"
unread_posts: "du har {{unread}} gamla olästa inlägg i den här tråden"
new_posts: "det finns {{new_posts}} nya inlägg i den här tråden sen du senaste läste det"
likes:
one: "det finns 1 gillning i den här tråden"
other: "det finns {{count}} gillningar i den här tråden"
@ -367,11 +424,12 @@ sv:
read_more: "Vill du läsa mer? {{catLink}} eller {{latestLink}}."
browse_all_categories: Bläddra bland alla kategorier
view_latest_topics: visa populära trådar
view_latest_topics: visa senaste trådar
suggest_create_topic: Varför inte skapa en tråd?
read_position_reset: "Din läsposition har blivit återställd."
jump_reply_up: hoppa till tidigare svar
jump_reply_down: hoppa till senare svar
deleted: "Tråden har raderats"
progress:
title: trådplacering
@ -398,7 +456,7 @@ sv:
description: "samma som Följer, plus att du meddelas om alla nya inlägg."
tracking:
title: "Följer"
description: "du meddelas om olästa inlägg, omnämnanden av @namn, och svar på dina inlägg."
description: "du meddelas om omnämnanden av @namn, och svar på dina inlägg, plus att du ser antal olästa och nya inlägg.."
regular:
title: "Vanlig"
description: "du meddelas bara om någon nämner ditt @namn eller svarar på ditt inlägg."
@ -424,6 +482,10 @@ sv:
title: 'Svara'
help: 'börja komponera ett svar till denna tråd'
clear_pin:
title: "Rensa nål"
help: "Rensa den nålade statusen från denna tråd så den inte längre hamnar i toppen av din trådlista"
share:
title: 'Dela'
help: 'dela en länk till denna tråd'
@ -449,8 +511,21 @@ sv:
login_reply: 'Logga In för att Svara'
filters:
user: "Du visar bara inlägg av en eller flera specifika användare."
best_of: "Du visar bara 'Bäst Av'-inläggen."
user: "Du visar bara {{n_posts}} {{by_n_users}}."
n_posts:
one: "1 inlägg"
other: "{{count}} inlägg"
by_n_users:
one: "skapat av 1 specifik användare"
other: "skapat av {{count}} specifika användare"
best_of: "Du visar bara {{n_best_posts}} {{of_n_posts}}."
n_best_posts:
one: "1 bästa inlägg"
other: "{{count}} bästa inlägg"
of_n_posts:
one: "av 1 i tråden"
other: "av {{count}} i tråden"
cancel: "Visa alla inlägg i den här tråden igen."
move_selected:
@ -474,7 +549,9 @@ sv:
post:
reply: "Svarar på {{link}} av {{replyAvatar}} {{username}}"
reply_topic: "Svar på {{link}}"
edit: "Ändra {{link}}"
quote_reply: "citatsvar"
edit: "Ändra {{link}} av {{replyAvatar}} {{username}}"
post_number: "inlägg {{number}}"
in_reply_to: "som svar till"
reply_as_new_topic: "Svara som ny Tråd"
continue_discussion: "Fortsätter diskussionen från {{postLink}}:"
@ -489,6 +566,9 @@ sv:
create: "Tyvärr, det uppstod ett fel under skapandet av ditt inlägg. Var god försök igen."
edit: "Tyvärr, det uppstod ett fel under ändringen av ditt inlägg. Var god försök igen."
upload: "Tyvärr, det uppstod ett fel under uppladdandet av den filen. Vad god försök igen."
upload_too_large: "Tyvärr, filen som du försöker ladda upp är för stor (maxstorlek är {{max_size_kb}}kb), var god ändra storlek och försök igen."
upload_too_many_images: "Tyvärr, du kan bara ladda upp en bild i taget."
only_images_are_supported: "Tyvärr, du kan bara ladda upp bilder."
abandon: "Är du säker på att du vill överge ditt inlägg?"
@ -511,15 +591,82 @@ sv:
clear_flags:
one: "Ta bort flagga"
other: "Ta bort flaggningar"
it_too: "{{alsoName}} det också"
undo: "Ångra {{alsoName}}"
it_too:
off_topic: "Flagga det också"
spam: "Flagga det också"
inappropriate: "Flagga det också"
custom_flag: "Flagga det också"
bookmark: "Bokmärk det också"
like: "Gilla det också"
vote: "Rösta för det också"
undo:
off_topic: "Ångra flaggning"
spam: "Ångra flaggning"
inappropriate: "Ångra flaggning"
custom_flag: "Ångra flaggning"
bookmark: "Ångra bokmärkning"
like: "Ångra gillning"
vote: "Ångra röstning"
people:
off_topic: "{{icons}} markerade detta som off-topic"
spam: "{{icons}} markerade detta som spam"
inappropriate: "{{icons}} markerade detta som olämpligt"
custom_flag: "{{icons}} flaggade detta"
bookmark: "{{icons}} bokmärkte detta"
like: "{{icons}} gillade detta"
vote: "{{icons}} röstade för detta"
by_you:
off_topic: "Du flaggade detta som off-topic"
spam: "Du flaggade detta som spam"
inappropriate: "Du flaggade detta som olämpligt"
custom_flag: "Du flaggade detta för moderation"
bookmark: "Du bokmärkte detta inlägg"
like: "Du gillade detta"
vote: "Du röstade för detta inlägg"
by_you_and_others:
zero: "Du {{long_form}}"
one: "Du och 1 annan person {{long_form}}"
other: "Du och {{count}} andra personer {{long_form}}"
off_topic:
one: "Du och 1 annan flaggade detta som off-topic"
other: "Du och {{count}} andra personer flaggade detta som off-topic"
spam:
one: "Du och 1 annan flaggade detta som spam"
other: "Du och {{count}} andra personer flaggade detta som spam"
inappropriate:
one: "Du och 1 annan flaggade detta som olämpligt"
other: "Du och {{count}} andra personer flaggade detta som olämpligt"
custom_flag:
one: "Du och 1 annan flaggade detta för moderation"
other: "Du och {{count}} andra personer flaggade detta för moderation"
bookmark:
one: "Du och 1 annan bokmärkte detta inlägg"
other: "Du och {{count}} andra personer bokmärkte detta inlägg"
like:
one: "Du och 1 annan gillade detta"
other: "Du och {{count}} andra personer gillade detta"
vote:
one: "Du och 1 annan röstade för detta inlägg"
other: "Du och {{count}} andra personer röstade för detta inlägg"
by_others:
one: "1 person {{long_form}}"
other: "{{count}} personer {{long_form}}"
off_topic:
one: "1 person flaggade detta som off-topic"
other: "{{count}} personer flaggade detta som off-topic"
spam:
one: "1 person flaggade detta som spam"
other: "{{count}} personer flaggade detta som spam"
inappropriate:
one: "1 person flaggade detta som olämpligt"
other: "{{count}} personer flaggade detta som olämpligt"
custom_flag:
one: "1 person flaggade detta för moderation"
other: "{{count}} personer flaggade detta för moderation"
bookmark:
one: "1 person bokmärkte detta inlägg"
other: "{{count}} personer bokmärkte detta inlägg"
like:
one: "1 person gillade detta"
other: "{{count}} personer gillade detta"
vote:
one: "1 person röstade för detta inlägg"
other: "{{count}} personer röstade för detta inlägg"
edits:
one: 1 ändring
@ -537,18 +684,22 @@ sv:
edit_long: "Ändra Kategori"
view: 'Visa Trådar i Kategori'
delete: 'Radera Kategori'
create: 'Skapa Kategoriy'
create: 'Skapa Kategori'
creation_error: Det uppstod ett fel när kategorin skulle skapas.
more_posts: "visa alla {{posts}}..."
name: "Kategorinamn"
description: "Beskrivning"
topic: "Kategoristråd"
color: "Färg"
badge_colors: "Emblemsfärg"
background_color: "Bakgrundsfärg"
foreground_color: "Förgrundsfärg"
name_placeholder: "Ska vara kort och koncist."
color_placeholder: "Någon webbfärg"
delete_confirm: "Är du säker på att du vill radera den kategorin?"
list: "Lista Kategorier"
no_description: "Det finns ingen beskrivning för denna kategori."
change_in_category_topic: "besök kategorins tråd för att ändra beskrivning"
hotness: "Hethet"
flagging:
title: 'Varför flaggar du detta inlägg?'
@ -584,19 +735,24 @@ sv:
likes: "Gillningar"
top_contributors: "Deltagare"
category_title: "Kategori"
history: "Historik"
changed_by: "av {{author}}"
categories_list: "Kategorilista"
filters:
latest:
title: "Populära"
title: "Senaste"
help: "det populäraste trådarna nyligen"
hot:
title: "Hett"
help: "ett urval av de hetaste trådarna"
favorited:
title: "Favoriter"
help: "trådar du favoritmarkerat"
read:
title: "Lästa"
help: "trådar du har läst"
help: "trådar du har läst, i den ordningen du senast läste dem"
categories:
title: "Kategorier"
title_in: "Kategori - {{categoryName}}"
@ -612,7 +768,7 @@ sv:
zero: "Nya"
one: "Nya (1)"
other: "Nya ({{count}})"
help: "nya trådar sen tidd senaste besök, och följda trådar med nya inlägg"
help: "nya trådar sen ditt senaste besök"
posted:
title: "Mina Inlägg"
help: "trådar som du har postat i"
@ -621,24 +777,47 @@ sv:
zero: "{{categoryName}}"
one: "{{categoryName}} (1)"
other: "{{categoryName}} ({{count}})"
help: "populära trådar i {{categoryName}}-kategorin"
help: "senaste trådarna i {{categoryName}}-kategorin"
browser_update: 'Tyvärr, <a href="http://www.discourse.org/faq/#browser">din webbläsare är för gammal för att fungera på detta Discourse-forum</a>. Var god <a href="http://browsehappy.com">uppgradera din webbläsare</a>.'
# This section is exported to the javascript for i18n in the admin section
admin_js:
type_to_filter: "skriv för att filtrera..."
admin:
title: 'Discourse Adminn'
title: 'Discourse Admin'
moderator: 'Moderator'
dashboard:
title: "Översiktspanel"
version: "Installerad version"
up_to_date: "Du kör den senaste versionen av Discourse."
version: "Version"
up_to_date: "Du är aktuell!"
critical_available: "En kritisk uppdatering är tillgänglig."
updates_available: "Uppdateringar är tillgängliga."
please_upgrade: "Var god uppgradera!"
latest_version: "Senaste versionen"
installed_version: "Installerad"
latest_version: "Senaste"
update_often: "Snälla uppdatera ofta!"
problems_found: "Några problem har hittas med din installation av Discourse:"
moderators: 'Moderatorer:'
admins: 'Administratörer:'
reports:
today: "Idag"
yesterday: "Igår"
last_7_days: "Senaste 7 Dagarna"
last_30_days: "Senaste 30 Dagarna"
all_time: "Från Början"
7_days_ago: "7 Dagar Sedan"
30_days_ago: "30 Dagar Sedan"
all: "Alla"
view_table: "Visa som Tabell"
view_chart: "Visa som Stapeldiagram"
commits:
latest_changes: "Senaste ändringarna."
by: "av"
flags:
title: "Flaggningar"
@ -649,7 +828,10 @@ sv:
delete: "Radera Inlägg"
delete_title: "radera inlägg (om det är första inlägget radera tråd)"
flagged_by: "Flaggad av"
error: "Någonting gick snett"
api:
title: "API"
customize:
title: "Anpassa"
header: "Sidhuvud"
@ -659,6 +841,8 @@ sv:
preview: "förhandsgranska"
undo_preview: "ångra förhandsgranskning"
save: "Spara"
new: "Ny"
new_style: "Ny Stil"
delete: "Radera"
delete_confirm: "Radera denna anpassning?"
@ -724,6 +908,11 @@ sv:
approved_by: "godkänd av"
time_read: "Lästid"
site_content:
none: "Välj typ av innehåll för att börja ändra."
title: "Sidinnehåll"
edit: "Ändra sidinnehåll"
site_settings:
show_overriden: 'Visa bara överskrivna'
title: 'Webbplatsinställningar'

View File

@ -75,8 +75,8 @@ da:
category:
topic_prefix: "Category definition for %{category}"
replace_paragraph: "[Replace this first paragraph with a short description of your new category. Try to keep it below 200 characters.]"
post_template: "%{replace_paragraph}\n\nUse this space below for a longer description, as well as to establish any rules or discussion!"
replace_paragraph: "[Replace this first paragraph with a short description of your new category. This guidance will appear in the category selection area, so try to keep it below 200 characters.]"
post_template: "%{replace_paragraph}\n\nUse this space for a longer description, as well as to establish any category guidelines or rules.\n\nSome things to consider in any discussion replies below:\n\n- What is this category for? Why should people select this category for their topic?\n\n- How is this different than the other categories we already have?\n\n- Do we need this category?\n\n- Should we merge this with another category, or split it into more categories?\n"
trust_levels:
new:

View File

@ -12,6 +12,9 @@ en:
via: "%{username} via %{site_name}"
is_reserved: "is reserved"
site_under_maintenance: 'Site is currently undergoing maintenance.'
operation_already_running: "An %{operation} is currently running. Can't start a new %{operation} job right now."
too_many_mentions:
zero: "Sorry, you can't mention other users."
one: "Sorry, you can only mention one other user in a post."
@ -91,8 +94,8 @@ en:
category:
topic_prefix: "Category definition for %{category}"
replace_paragraph: "[Replace this first paragraph with a short description of your new category. Try to keep it below 200 characters.]"
post_template: "%{replace_paragraph}\n\nUse this space below for a longer description, as well as to establish any rules or discussion!"
replace_paragraph: "[Replace this first paragraph with a short description of your new category. This guidance will appear in the category selection area, so try to keep it below 200 characters.]"
post_template: "%{replace_paragraph}\n\nUse this space for a longer description, as well as to establish any category guidelines or rules.\n\nSome things to consider in any discussion replies below:\n\n- What is this category for? Why should people select this category for their topic?\n\n- How is this different than the other categories we already have?\n\n- Do we need this category?\n\n- Should we merge this with another category, or split it into more categories?\n"
trust_levels:
visitor:
@ -326,6 +329,12 @@ en:
welcome_invite:
title: "Welcome: Invited User"
description: "A private message automatically sent to all new invited users when they accept the invitation from another user to participate."
tos_user_content_license:
title: "Terms of Service: Content License"
description: "The text for the Content License section of the Terms of Service."
tos_miscellaneous:
title: "Terms of Service: Miscellaneous"
description: "The text for the Miscellaneous section of the Terms of Service."
site_settings:
default_locale: "The default language of this Discourse instance (ISO 639-1 Code)"
@ -862,3 +871,10 @@ en:
see_more: "See More"
search_title: "Search for this topic"
search_google: "Search Google"
terms_of_service:
user_content_license: |
User contributions are licensed under a [Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License](http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US). Without limiting any of those representations or warranties, %{company_short_name} has the right (though not the obligation) to, in %{company_short_name}s sole discretion (i) refuse or remove any content that, in %{company_short_name}s reasonable opinion, violates any %{company_short_name} policy or is in any way harmful or objectionable, or (ii) terminate or deny access to and use of the Website to any individual or entity for any reason, in %{company_short_name}s sole discretion. %{company_short_name} will have no obligation to provide a refund of any amounts previously paid.
Without limiting any of those representations or warranties, %{company_short_name} has the right (though not the obligation) to, in %{company_short_name}s sole discretion (i) refuse or remove any content that, in %{company_short_name}s reasonable opinion, violates any %{company_short_name} policy or is in any way harmful or objectionable, or (ii) terminate or deny access to and use of the Website to any individual or entity for any reason, in %{company_short_name}s sole discretion. %{company_short_name} will have no obligation to provide a refund of any amounts previously paid.
miscellaneous: "This Agreement constitutes the entire agreement between %{company_short_name} and you concerning the subject matter ereof, and they may only be modified by a written amendment signed by an authorized executive of %{company_short_name}, or by the posting by %{company_short_name} of a revised version. Except to the extent applicable law, if any, provides otherwise, this Agreement, any access to or use of the Website will be governed by the laws of the state of California, U.S.A., excluding its conflict of law provisions, and the proper venue for any disputes arising out of or relating to any of the same will be the state and federal courts located in San Francisco County, California. Except for claims for injunctive or equitable relief or claims regarding intellectual property rights (which may be brought in any competent court without the posting of a bond), any dispute arising under this Agreement shall be finally settled in accordance with the Comprehensive Arbitration Rules of the Judicial Arbitration and Mediation Service, Inc. (“JAMS”) by three arbitrators appointed in accordance with such Rules. The arbitration shall take place in San Francisco, California, in the English language and the arbitral decision may be enforced in any court. The prevailing party in any action or proceeding to enforce this Agreement shall be entitled to costs and attorneys fees. If any part of this Agreement is held invalid or unenforceable, that part will be construed to reflect the parties original intent, and the remaining portions will remain in full force and effect. A waiver by either party of any term or condition of this Agreement or any breach thereof, in any one instance, will not waive such term or condition or any subsequent breach thereof. You may assign your rights under this Agreement to any party that consents to, and agrees to be bound by, its terms and conditions; %{company_short_name} may assign its rights under this Agreement without condition. This Agreement will be binding upon and will inure to the benefit of the parties, their successors and permitted assigns."

View File

@ -77,8 +77,8 @@ es:
category:
topic_prefix: "Category definition for %{category}"
replace_paragraph: "[Replace this first paragraph with a short description of your new category. Try to keep it below 200 characters.]"
post_template: "%{replace_paragraph}\n\nUse this space below for a longer description, as well as to establish any rules or discussion!"
replace_paragraph: "[Replace this first paragraph with a short description of your new category. This guidance will appear in the category selection area, so try to keep it below 200 characters.]"
post_template: "%{replace_paragraph}\n\nUse this space for a longer description, as well as to establish any category guidelines or rules.\n\nSome things to consider in any discussion replies below:\n\n- What is this category for? Why should people select this category for their topic?\n\n- How is this different than the other categories we already have?\n\n- Do we need this category?\n\n- Should we merge this with another category, or split it into more categories?\n"
trust_levels:
new:

View File

@ -19,6 +19,9 @@ fr:
via: "%{username} via %{site_name}"
is_reserved: "est réservé"
site_under_maintenance: 'Le site est en cours de maintenance.'
operation_already_running: "Un %{operation} est en train de tourner. Impossible d'en démarrer un autre."
too_many_mentions:
zero: "Désolé, vous ne pouvez pas mentionner d'utilisateur."
one: "Désolé, vous ne pouvez mentionner qu'un seul utilisateur."
@ -303,6 +306,10 @@ fr:
title: Emails envoyés
xaxis: Jour
yaxis: "Nombre d'emails"
private_messages:
title: Messages privés
xaxis: Jour
yaxis: Nombre de messages privés
dashboard:
rails_env_warning: "Votre serveur fonctionne dans l'environnement de %{env}."
@ -315,6 +322,29 @@ fr:
twitter_config_warning: "Le serveur est configuré pour permettre l'authentification par Twitter (enable_twitter_logins), mais les paramètres twitter_consumer_key et twitter_consumer_secret ne sont pas renseignés. Vous pouvez modifier ces paramètres dans <a href=\"/admin/site_settings\">l'interface d'administration</a>."
github_config_warning: "Le serveur est configuré pour permettre l'authentification par GitHub (enable_github_logins), mais les paramètres github_client_id et github_client_secret ne sont pas renseignés. Vous pouvez modifier ces paramètres dans <a href=\"/admin/site_settings\">l'interface d'administration</a>."
content_types:
education_new_reply:
title: "Education du nouveau : premières réponses"
description: "Pop up affichée au dessus de la zone de saisie lorsqu'un nouvel utilisateur rédige ses 2 premières réponses."
education_new_topic:
title: "Education du nouveau : premières discussions"
description: "Pop up affichée au dessus de la zone de saisie lorsqu'un nouvel utilisateur rédige ses 2 premières discussions."
usage_tips:
title: "Astuce pour les nouveaux"
description: "Astuces, conseils et informations essentielles du forum destinés aux nouveaux utilisateurs."
welcome_user:
title: "Bienvenue : nouvel utilisateur"
description: "Un message privé envoyé automatiquement à tous les nouveaux utilisateurs lorsqu'ils s'enregistrent."
welcome_invite:
title: "Bienvenue : utilisateur invité"
description: "Un message privé envoyé automatiquement à tous les utilisateurs invités lorsqu'ils acceptent l'invitation d'un membre à participer au forum."
tos_user_content_license:
title: "CGU: license"
description: "Le texte de la section 'license' des CGUs."
tos_miscellaneous:
title: "CGU: divers"
description: "Le texte de la section 'divers' des CGUs."
site_settings:
default_locale: "Le langage par défaut de cette instance de Discourse (code ISO 639-1)"
min_post_length: "longueur minimale des messages"
@ -444,9 +474,17 @@ fr:
default_invitee_trust_level: "Le niveau de confiance par défaut des utilisateurs invités"
default_trust_level: "Le niveau de confiance par défaut des utilisateurs"
basic_requires_topics_entered: "A combien de discussions un utilisateur doit avoir participé pour être promu au rang de niveau basique"
basic_requires_read_posts: "Combien de discussions un utilisateur doit avoir lues pour être promu au rang de niveau basique"
basic_requires_time_spent_mins: "Combien de minutes un utilisateur doit avoir passées à lire des messages pour être promu au rang de niveau basique"
basic_requires_topics_entered: "A combien de discussions un utilisateur doit avoir participé pour être promu au niveau basique (1)"
basic_requires_read_posts: "Combien de discussions un utilisateur doit avoir lues pour être promu au niveau basique (1)"
basic_requires_time_spent_mins: "Combien de minutes un utilisateur doit avoir passées à lire des messages pour être promu au niveau basique (1)"
regular_requires_topics_entered: "A combien de discussions un utilisateur doit avoir participé pour être promu au niveau régulier (2)"
regular_requires_read_posts: "Combien de discussions un utilisateur doit avoir lues pour être promu au niveau régulier (2)"
regular_requires_time_spent_mins: "Combien de minutes un utilisateur doit avoir passées à lire des messages pour être promu au niveau régulier (2)"
regular_requires_days_visited: "Combien de jours un utilisateur doit visiter le site pour être promu au niveau régulier (2)"
regular_requires_likes_received: "Combien de j'aime un utilisateur doit reçevoir pour être promu au niveau régulier (2)"
regular_requires_likes_given: "Combien de j'aime un utilisateur doit donner pour être promu au niveau régulier (2)"
regular_requires_topic_reply_count: "Combien de réponses un utilisateur doit donner pour être promu au niveau régulier (2)"
auto_link_images_wider_than: ""
@ -842,3 +880,10 @@ fr:
see_more: "Voir plus"
search_title: "Rechercher pour cette discussion"
search_google: "Rechercher avec Google"
terms_of_service:
user_content_license: |
User contributions are licensed under a [Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License](http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US). Without limiting any of those representations or warranties, %{company_short_name} has the right (though not the obligation) to, in %{company_short_name}s sole discretion (i) refuse or remove any content that, in %{company_short_name}s reasonable opinion, violates any %{company_short_name} policy or is in any way harmful or objectionable, or (ii) terminate or deny access to and use of the Website to any individual or entity for any reason, in %{company_short_name}s sole discretion. %{company_short_name} will have no obligation to provide a refund of any amounts previously paid.
Without limiting any of those representations or warranties, %{company_short_name} has the right (though not the obligation) to, in %{company_short_name}s sole discretion (i) refuse or remove any content that, in %{company_short_name}s reasonable opinion, violates any %{company_short_name} policy or is in any way harmful or objectionable, or (ii) terminate or deny access to and use of the Website to any individual or entity for any reason, in %{company_short_name}s sole discretion. %{company_short_name} will have no obligation to provide a refund of any amounts previously paid.
miscellaneous: "This Agreement constitutes the entire agreement between %{company_short_name} and you concerning the subject matter ereof, and they may only be modified by a written amendment signed by an authorized executive of %{company_short_name}, or by the posting by %{company_short_name} of a revised version. Except to the extent applicable law, if any, provides otherwise, this Agreement, any access to or use of the Website will be governed by the laws of the state of California, U.S.A., excluding its conflict of law provisions, and the proper venue for any disputes arising out of or relating to any of the same will be the state and federal courts located in San Francisco County, California. Except for claims for injunctive or equitable relief or claims regarding intellectual property rights (which may be brought in any competent court without the posting of a bond), any dispute arising under this Agreement shall be finally settled in accordance with the Comprehensive Arbitration Rules of the Judicial Arbitration and Mediation Service, Inc. (“JAMS”) by three arbitrators appointed in accordance with such Rules. The arbitration shall take place in San Francisco, California, in the English language and the arbitral decision may be enforced in any court. The prevailing party in any action or proceeding to enforce this Agreement shall be entitled to costs and attorneys fees. If any part of this Agreement is held invalid or unenforceable, that part will be construed to reflect the parties original intent, and the remaining portions will remain in full force and effect. A waiver by either party of any term or condition of this Agreement or any breach thereof, in any one instance, will not waive such term or condition or any subsequent breach thereof. You may assign your rights under this Agreement to any party that consents to, and agrees to be bound by, its terms and conditions; %{company_short_name} may assign its rights under this Agreement without condition. This Agreement will be binding upon and will inure to the benefit of the parties, their successors and permitted assigns."

View File

@ -76,8 +76,8 @@ id:
category:
topic_prefix: "Category definition for %{category}"
replace_paragraph: "[Replace this first paragraph with a short description of your new category. Try to keep it below 200 characters.]"
post_template: "%{replace_paragraph}\n\nUse this space below for a longer description, as well as to establish any rules or discussion!"
replace_paragraph: "[Replace this first paragraph with a short description of your new category. This guidance will appear in the category selection area, so try to keep it below 200 characters.]"
post_template: "%{replace_paragraph}\n\nUse this space for a longer description, as well as to establish any category guidelines or rules.\n\nSome things to consider in any discussion replies below:\n\n- What is this category for? Why should people select this category for their topic?\n\n- How is this different than the other categories we already have?\n\n- Do we need this category?\n\n- Should we merge this with another category, or split it into more categories?\n"
trust_levels:
visitor:

View File

@ -5,103 +5,117 @@ sv:
short: "%m-%d-%Y"
title: "Discourse"
topics: "Topics"
loading: "Loading"
powered_by_html: 'Powered by <a href="http://www.discourse.org">Discourse</a>, best viewed with JavaScript enabled'
topics: "Trådar"
loading: "Laddar"
powered_by_html: 'Drivs av <a href="http://www.discourse.org">Discourse</a>, visas bäst med JavaScript påslaged'
via: "%{username} via %{site_name}"
is_reserved: "is reserved"
too_many_mentions: "has too many users mentioned"
too_many_images: "has too many images"
too_many_links: "has too many links"
just_posted_that: "is too similar to what you recently posted"
has_already_been_used: "has already been used"
invalid_characters: "contains invalid characters"
is_invalid: "is invalid; try to be a little more descriptive"
next_page: "next page →"
by: "By"
topics_in_category: "Topics in the '%{category}' category"
rss_posts_in_topic: "RSS feed of '%{topic}'"
rss_topics_in_category: "RSS feed of topics in the '%{category}' category"
author_wrote: "%{author} wrote:"
is_reserved: "är reserverat"
too_many_mentions:
zero: "Tyvärr, du kan inte omnämna andra användare."
one: "Tyvärr, du kan bara omnämna en annan användare i ett inlägg."
other: "Tyvärr, du kan bara omnämna %{count} användare i ett inlägg."
too_many_mentions_visitor:
zero: "Tyvärr, besökare kan inte omnämna andra användare."
one: "Tyvärr, besökare kan bara omnämna en annan användare i ett inlägg."
other: "Tyvärr, besökare kan bara omnämna %{count} användare i ett inlägg."
too_many_images:
zero: "Tyvärr, besökare kan inte ha bilder i inlägg."
one: "Tyvärr, besökare kan bara ha en bild i ett inlägg."
other: "Tyvärr, besökare kan bara ha %{count} bilder i ett inlägg."
too_many_links:
zero: "Tyvärr, besökare kan inte ha länkar i inlägg."
one: "Tyvärr, besökare kan bara ha en länk i ett inlägg."
other: "Tyvärr, besökare kan bara ha %{count} länkar i ett inlägg."
just_posted_that: "är för likt det du nyligen postade"
has_already_been_used: "har redan använts"
invalid_characters: "innehåller otillåtna tecken"
is_invalid: "är otillåtet; försök att vara lite mer beskrivande"
next_page: "nästa sida →"
by: "Av"
topics_in_category: "Trådar i kategorin '%{category}'"
rss_posts_in_topic: "RSS-flöde för '%{topic}'"
rss_topics_in_category: "RSS-flöde för trådar i kategorin '%{category}'"
author_wrote: "%{author} skrev:"
education:
until_posts:
one: "post"
other: "%{count} posts"
one: "inlägg"
other: "%{count} inlägg"
'new-topic': |
Welcome to %{site_name} &mdash; **thanks for starting a new conversation!**
Välkommen till %{site_name} &mdash; **tack för att du startar en ny konversation!**
Keep in mind as you compose your topic:
Ha i åtanke medans du komponerar din tråd:
- Does the title adequately describe what someone could reasonably expect to find if they opened your topic?
- Beskriver titeln adekvat vad någon rimligen kan förvänta sig att hitta om de öppnade din tråd?
- The first post defines your topic: What is this about? Who would be interested in it? Why does it matter? What kind of responses are you hoping for from the community?
- Det första inlägget definierar din tråd: Vad handlar den om? Vem skulle vara intresserad av den? Varför spelar det roll? Vilken typ av svar hoppas du på från gemenskapen?
- Try to choose the right words so others can *find* your topic. If it needs to be in a category, pick an appropriate category.
- Försök att välja rätt ord så att andra kan *hitta* din tråd. Om den måste vara i en kategori, välj en lämplig kategori.
For more guidance, [see our FAQ](/faq). This panel will only appear for your first %{education_posts_text}.
För mer vägledning, [se vår FAQ](/faq). Denna panel visas bara för dina första %{education_posts_text}.
'new-reply': |
Welcome to %{site_name} &mdash; **thanks for contributing to the conversation!**
Välkommen till %{site_name} &mdash; **tack för att du bidrar till konversationen!**
Keep in mind as you compose your reply:
Ha i åtanke medans du komponerar ditt svar:
- Does your reply improve the conversation in some way, however small?
- Förbättrar ditt svar konversationen på något sätt, om än litet?
- Treat your fellow community members with the same respect you'd wish to be treated.
- Bemöt andra medlemmar med samma respekt som du själv vill bli bemött.
- It's fine to be critical, but remember to criticize *ideas*, not people.
For more guidance, [see our FAQ](/faq). This panel will only appear for your first %{education_posts_text}.
- Det är okej att vara kritisk, men kom ihåg att kritisera *idéer*, inte personer.
För mer vägledning, [se vår FAQ](/faq). Denna panel visas bara för dina första %{education_posts_text}.
activerecord:
attributes:
category:
name: "Category Name"
name: "Kategorinamn"
post:
raw: "Body"
raw: "Inlägg"
errors:
models:
topic:
attributes:
archetype:
cant_send_pm: "Sorry, you cannot send a private message to that user."
cant_send_pm: "Tyvärr, du kan inte skicka ett privat meddelande till den användaren."
user_profile:
no_info_me: "<div class='missing-profile'>the About Me field of your profile is currently blank, <a href='/users/%{username_lower}/preferences'>would you like to fill it out?</a></div>"
no_info_other: "<div class='missing-profile'>%{name} hasn't entered anything in the About Me field of their profile yet</div>"
no_info_me: "<div class='missing-profile'>Din profils Om Mig-fält är för närvarande tomt, <a href='/users/%{username_lower}/preferences'>skulle du vilja fylla i det?</a></div>"
no_info_other: "<div class='missing-profile'>%{name} har inte skrivit någonting i dess profils Om Mig-fält ännu</div>"
category:
topic_prefix: "Category definition for %{category}"
replace_paragraph: "[Replace this first paragraph with a short description of your new category. Try to keep it below 200 characters.]"
post_template: "%{replace_paragraph}\n\nUse this space below for a longer description, as well as to establish any rules or discussion!"
replace_paragraph: "[Replace this first paragraph with a short description of your new category. This guidance will appear in the category selection area, so try to keep it below 200 characters.]"
post_template: "%{replace_paragraph}\n\nUse this space for a longer description, as well as to establish any category guidelines or rules.\n\nSome things to consider in any discussion replies below:\n\n- What is this category for? Why should people select this category for their topic?\n\n- How is this different than the other categories we already have?\n\n- Do we need this category?\n\n- Should we merge this with another category, or split it into more categories?\n"
trust_levels:
visitor:
title: "visitor"
title: "besökare"
basic:
title: "basic user"
title: "vanlig användare"
regular:
title: "regular user"
title: "regelbunden användare"
leader:
title: "leader"
title: "ledare"
elder:
title: "elder"
title: "församlingsäldste"
rate_limiter:
too_many_requests: "You're doing that too often. Please wait %{time_left} before trying again."
too_many_requests: "Du gör det där för ofta. Var god vänta %{time_left} innan du försöker igen."
hours:
one: "1 hour"
other: "%{count} hours"
one: "1 timme"
other: "%{count} timmar"
minutes:
one: "1 minute"
other: "%{count} minutes"
one: "1 minut"
other: "%{count} minuter"
seconds:
one: "1 second"
other: "%{count} seconds"
one: "1 sekund"
other: "%{count} sekunder"
datetime:
distance_in_words:
@ -125,132 +139,132 @@ sv:
one: "1d"
other: "%{count}d"
about_x_months:
one: "1mon"
other: "%{count}mon"
one: "1mån"
other: "%{count}mån"
x_months:
one: "1mon"
other: "%{count}mon"
one: "1mån"
other: "%{count}mån"
about_x_years:
one: "1y"
other: "%{count}y"
one: "1år"
other: "%{count}år"
over_x_years:
one: "> 1y"
other: "> %{count}y"
one: "> 1år"
other: "> %{count}år"
almost_x_years:
one: "1y"
other: "%{count}y"
one: "1år"
other: "%{count}år"
distance_in_words_verbose:
half_a_minute: "just now"
half_a_minute: "nyss"
less_than_x_seconds:
one: "just now"
other: "just now"
one: "nyss"
other: "nyss"
x_seconds:
one: "1 second ago"
other: "%{count} seconds ago"
one: "1 sekund sedan"
other: "%{count} sekunder sedan"
less_than_x_minutes:
one: "less than 1 minute ago"
other: "less than %{count} minutes ago"
one: "mindre än 1 minut sedan"
other: "mindre än %{count} minuter sedan"
x_minutes:
one: "1 minute ago"
other: "%{count} minutes ago"
one: "1 minut sedan"
other: "%{count} minuter sedan"
about_x_hours:
one: "1 hour ago"
other: "%{count} hours ago"
one: "1 timme sedan"
other: "%{count} timmar sedan"
x_days:
one: "1 day ago"
other: "%{count} days ago"
one: "1 dag sedan"
other: "%{count} dagar sedan"
about_x_months:
one: "about 1 month ago"
other: "about %{count} months ago"
one: "ungefär 1 månad sedan"
other: "ungefär %{count} månader sedan"
x_months:
one: "1 month ago"
other: "%{count} months ago"
one: "1 månad sedan"
other: "%{count} månader sedan"
about_x_years:
one: "about 1 year ago"
other: "about %{count} years ago"
one: "ungefär 1 år sedan"
other: "ungefär %{count} år sedan"
over_x_years:
one: "over 1 year ago"
other: "over %{count} years ago"
one: "över 1 år sedan"
other: "över %{count} år sedan"
almost_x_years:
one: "almost 1 year ago"
other: "almost %{count} years ago"
one: "nästan 1 år sedan"
other: "nästan %{count} år sedan"
password_reset:
no_token: "Sorry, your token has expired. Please try resetting your password again."
choose_new: "Please choose a new password"
update: 'update password'
title: 'reset password'
success: "You successfully changed your password and are now logged in."
success_unapproved: "You successfully changed your password."
no_token: "Tyvärr, din token har gått ut. Var god försök återställa ditt lösenord igen."
choose_new: "Var god välj ett nytt lösenord"
update: 'uppdatera lösenord'
title: 'återställ lösenord'
success: "Du har lyckats med att byta ditt lösenord och är nu inloggad."
success_unapproved: "Du lyckades med att byta ditt lösenord."
continue: "Continue to %{site_name}"
change_email:
confirmed: "Your email has been updated."
please_continue: "Please continue to %{link}"
error: "There was an error changing your email address. Perhaps the address is already in use?"
confirmed: "Din e-post har uppdaterats."
please_continue: "Var god fortsätt till %{link}"
error: "Det uppstod ett fel med ändringen av din e-postadress. Adressen kanske redan används?"
activation:
already_done: "Sorry, this account confirmation link is no longer valid. Perhaps your account is already active?"
please_continue: "Your new account is confirmed, and you are now logged in. Continue to %{link}"
welcome_to: "Welcome to %{site_name}!"
approval_required: "A moderator must manually approve your new account before you can access this forum. You'll get an email when your account is approved!"
already_done: "Tyvärr, denna kontoaktiveringslänk är inte längre giltig. Kanske är ditt konto redan aktiverat?"
please_continue: "Ditt nya konto är aktiverat, och du är nu inloggad. Fortsätt till %{link}"
welcome_to: "Välkommen till %{site_name}!"
approval_required: "En moderator måste manuellt godkänna ditt nya konto innan du kan komma åt detta forum. Du kommer få ett mail när ditt konto har godkänts!"
post_action_types:
off_topic:
title: 'Off-Topic'
description: 'This post is radically off-topic in the current conversation, and should probably be moved to a different topic. If this is a topic, perhaps it does not belong here.'
long_form: 'flagged this as off-topic'
description: 'Detta inlägg är radikalt off-topic i den nuvarande konversationen, och bör troligen flyttas till en annan tråd.'
long_form: 'flagga detta som off-topic'
spam:
title: 'Spam'
description: 'This post is effectively an advertisement with no disclosure. It is not useful or relevant to the current conversation, but promotional in nature.'
long_form: 'flagged this as spam'
description: 'Detta inlägg är faktiskt en annons utan något avslöjande. Det är inte användbart eller relevant för den aktuella konversationen, men har en säljfrämjande karaktär.'
long_form: 'flagga detta som spam'
inappropriate:
title: 'Inappropriate'
description: 'This post contains content that a reasonable person would consider offensive, abusive, or hate speech.'
long_form: 'flagged this as inappropriate'
title: 'Olämpligt'
description: 'Detta inlägg innehåller innehåll som en förnuftig person skulle betrakta som stötande, kränkande eller hatpropaganda.'
long_form: 'flagga detta som olämpligt'
custom_flag:
title: 'Other'
description: 'This post requires general moderator attention based on the <a href="/faq">FAQ</a>, <a href="/tos">TOS</a>, or for another reason not listed above.'
long_form: 'flagged this for moderation'
title: 'Annat'
description: 'Detta inlägg behöver allmänt en moderators uppmärksamhet baserat på forumets <a href="/faq">FAQ</a>, <a href="/tos">TOS</a>, eller annan anledning som inte listas ovan.'
long_form: 'flagga detta för moderation'
bookmark:
title: 'Bookmark'
description: 'Bookmark this post'
long_form: 'bookmarked this post'
title: 'Bokmärk'
description: 'Bokmärk detta inlägg'
long_form: 'bokmärkte detta inlägg'
like:
title: 'Like'
description: 'Like this post'
long_form: 'liked this'
title: 'Gilla'
description: 'Gilla detta inlägg'
long_form: 'gillade detta inlägg'
vote:
title: 'Vote'
description: 'Vote for this post'
long_form: 'voted for this post'
title: 'Rösta'
description: 'Rösta för detta inlägg'
long_form: 'röstade för detta inlägg'
flagging:
you_must_edit: '<p>Your post reached the flagging threshold. Please see your private messages.</p>'
user_must_edit: '<p>Flagged content hidden.</p>'
you_must_edit: '<p>Ditt inlägg har nått flaggningssnivån. Var god kolla dina privata meddelanden.</p>'
user_must_edit: '<p>Flaggat innehåll dolt.</p>'
archetypes:
regular:
title: "Regular Topic"
title: "Vanlig Tråd"
unsubscribed:
title: 'Unsubscribed'
description: "You have been unsubscribed. We won't contact you again!"
oops: "In case you didn't mean to do this, click below."
not_found: "Error Unsubscribing"
not_found_description: "Sorry, we couldn't unsubscribe you. It's possible the link in your email has expired."
title: 'Avprenumererad'
description: "Du har blivit avprenumererad. Vi kommer inte att kontakta dig igen!"
oops: "Om du inte menade att göra detta, klicka nedan."
not_found: "Fel vid Avprenumerering"
not_found_description: "Tyvärr, vi kunde inte avprenumerera dig. Det är möjligt att länken i ditt mail har löpt ut."
resubscribe:
action: "Re-Subscribe"
title: "Re-Subscribed!"
description: "You have been re-subscribed."
action: "Återprenumerera"
title: "Återprenumererad!"
description: "Du har blivit återprenumererad."
reports:
visits:
title: "User Visits"
xaxis: "Day"
yaxis: "Number of visits"
title: "Användarbesök efter Dag"
xaxis: "Dag"
yaxis: "Besök"
signups:
title: "Users"
xaxis: "Day"
@ -279,6 +293,10 @@ sv:
title: "Emails Sent"
xaxis: "Day"
yaxis: "Number of Emails"
private_messages:
title: "Private Messages"
xaxis: "Day"
yaxis: "Number of private messages"
dashboard:
rails_env_warning: "Your server is running in %{env} mode."
@ -287,46 +305,67 @@ sv:
clockwork_warning: 'Clockwork is not running. Ensure that a clockwork process is always running so that important jobs can be scheduled. <a href="https://github.com/tomykaira/clockwork">Learn about clockwork here</a>.'
sidekiq_warning: 'Sidekiq is not running. Many tasks, like sending emails, are executed asynchronously by sidekiq. Please ensure at least one sidekiq process is running. <a href="https://github.com/mperham/sidekiq">Learn about Sidekiq here</a>.'
memory_warning: 'Your server is running with less than 1 GB of total memory. At least 1 GB of memory is recommended.'
facebook_config_warning: 'The server is configured to allow signup and log in with Facebook (enable_facebook_logins), but the app id and app secret values are not set. Go to <a href="/admin/site_settings">the Site Settings</a> and update the settings. <a href="https://github.com/discourse/discourse/wiki/The-Discourse-Admin-Quick-Start-Guide#enable-facebook-logins" target="_blank">See this guide to learn more</a>.'
twitter_config_warning: 'The server is configured to allow signup and log in with Twitter (enable_twitter_logins), but the key and secret values are not set. Go to <a href="/admin/site_settings">the Site Settings</a> and update the settings. <a href="https://github.com/discourse/discourse/wiki/The-Discourse-Admin-Quick-Start-Guide#enable-twitter-logins" target="_blank">See this guide to learn more</a>.'
github_config_warning: 'The server is configured to allow signup and log in with GitHub (enable_github_logins), but the client id and secret values are not set. Go to <a href="/admin/site_settings">the Site Settings</a> and update the settings. <a href="https://github.com/discourse/discourse/wiki/The-Discourse-Admin-Quick-Start-Guide" target="_blank">See this guide to learn more</a>.'
content_types:
education_new_reply:
title: "New User Education: First Replies"
description: "Pop up just-in-time guidance automatically displayed above the composer when new users begin typing their first two new replies."
education_new_topic:
title: "New User Education: First Topics"
description: "Pop up just-in-time guidance automatically displayed above the composer when new users begin typing their first two new topics."
usage_tips:
title: "New User Tips"
description: "Common usage tips, essential forum information, and key guidance intended for new users."
welcome_user:
title: "Welcome: New User"
description: "A private message automatically sent to all new users when they sign up."
welcome_invite:
title: "Welcome: Invited User"
description: "A private message automatically sent to all new invited users when they accept the invitation from another user to participate."
site_settings:
default_locale: "The default language of this Discourse instance (ISO 639-1 Code)"
min_post_length: "Minimum post length in characters"
max_post_length: "Maximum post length in characters"
min_topic_title_length: "Minimum topic title length in characters"
max_topic_title_length: "Maximum topic title length in characters"
min_search_term_length: "Minimum search term length in characters"
allow_duplicate_topic_titles: "Allow topics with identical titles"
unique_posts_mins: "How many minutes before a user can make a post with the same content again"
enforce_global_nicknames: "Enforce global nickname uniqueness (WARNING: only change this during initial setup)"
discourse_org_access_key: "The access key used to access the Discourse Hub nickname registry at discourse.org"
educate_until_posts: "Show pop-up composer education panel until the user has made this many posts"
title: "Title of this site, will be used in the title tag and elsewhere"
company_full_name: "The full name of the company that runs this site, used in legal documents like the /tos"
company_short_name: "The short name of the company that runs this site, used in legal documents like the /tos"
company_domain: "The domain name owned by the company that runs this site, used in legal documents like the /tos"
access_password: "When restricted access is enabled, this password must be entered"
queue_jobs: "Queue various jobs in sidekiq, if false queues are inline"
crawl_images: "Enable retrieving images from third party sources to insert width and height dimensions"
ninja_edit_window: "Number of seconds after posting where edits do not create a new version"
enable_imgur: "Enable imgur api for uploading, don't host files locally"
imgur_api_key: "Your imgur.com api key, required for image upload to function"
imgur_endpoint: "End point for uploading imgur.com images"
max_image_width: "Maximum allowed width of images in a post"
category_featured_topics: "Number of topics displayed per category in the /categories page"
add_rel_nofollow_to_user_content: "Add rel nofollow to all submitted user content, except for internal links (including parent domains) changing this requires you update all your baked markdown"
exclude_rel_nofollow_domains: "A comma delimited list of domains where nofollow is not added (tld.com will automatically allow sub.tld.com as well)"
default_locale: "Standardspråk för den här instansen av Discourse (ISO 639-1-kod)"
min_post_length: "Minimumlängd på inlägg i tecken"
max_post_length: "Maxlängd på inlägg i tecken"
min_topic_title_length: "Minimumlängd på trådrubrik i tecken"
max_topic_title_length: "Maxlängd på trådrubrik i tecken"
min_search_term_length: "Minimumlängd på sökterm i tecken"
allow_duplicate_topic_titles: "Tillåt trådar med identiska rubriker"
unique_posts_mins: "Hur många minuter innan en användare kan göra ett inlägg med precis samma innehåll igen"
enforce_global_nicknames: "Tvinga globalt unika användarnamn (VARNING: ändra bara detta under den inledande installationen)"
discourse_org_access_key: "Åtkomstnyckeln för att komma åt Discourse Hubs användarnamnsregister på discourse.org"
educate_until_posts: "Visa hjälppanelen för komponering tills dess att användaren har gjort så här många inlägg"
title: "Titeln för denna webbplats, används i titel-taggar och på annat håll"
company_full_name: "Det fullständiga namnet för företaget som driver denna webbplats, används i juridiska dokument så som /tos"
company_short_name: "Det korta namnet för företaget som driver denna webbplats, används i juridiska dokument så som /tos"
company_domain: "Domännamnet som ägs av företaget som driver denna webbplats, används i juridiska dokument så som /tos"
api_key: "Den säkra API-nyckeln som används för att skapa och uppdatera trådar, använd /admin/api för att skapa en"
access_password: "När begränsad åtkomst är aktiverad, så måste detta lösenord användas"
queue_jobs: "Köa diverse jobb i sidekiq, om urkryssat så körs köer infogat"
crawl_images: "Aktivera hämtning av bilder från tredjepartskällor för att infoga bredd och höjd"
ninja_edit_window: "Antal sekunder efter ett inlägg när en ändring inte skapar en ny version"
enable_imgur: "Aktivera imgur.coms API för uppladdning, sparar inte filer lokalt"
imgur_api_key: "Din API-nyckel för imgur.com, krävs för att bilduppladdningen ska funka"
imgur_endpoint: "Ändpunkt för uppladdning av bilder till imgur.com"
max_image_width: "Maxbredd för bilder i ett inlägg"
category_featured_topics: "Antal visade trådar per kategori på sidan /categories"
add_rel_nofollow_to_user_content: "Lägg till rel nofollow på allt användargenererat innehåll, förutom interna länkar (inkluderat förälderdomäner) så kräver en ändring av detta att du uppdaterar all bakad markdown"
exclude_rel_nofollow_domains: "En kommaseparerad lista med domännamn där nofollow inte läggs till (tld.com kommer automatiskt tillåta sub.tld.com också)"
post_excerpt_maxlength: "Maximum length in chars of a post's excerpt"
post_onebox_maxlength: "Maximum length of a oneboxed Discourse post"
category_post_template: "The category definition post template used when you create a new category"
new_topics_rollup: "How many new topics can be inserted on the topic list before rolling up into a count"
onebox_max_chars: "Maximum characters a onebox will import from an external website into the post"
post_excerpt_maxlength: "Maxlängd på ett inläggs utdrag i tecken"
post_onebox_maxlength: "Maxlängd på ett onebox:at Discourse-inlägg"
category_post_template: "Inläggsmallen för kategoridefinitioner när du skapar en ny kategori"
new_topics_rollup: "Hur många nya trådar kan infogas i trådlistan innan den övergår till en siffra"
onebox_max_chars: "Max antal tecken en onebox importerar från en extern sida i ett inlägg"
logo_url: "The logo for your site eg: http://example.com/logo.png"
logo_small_url: "The small logo for your site used when scrolling down on topics eg: http://example.com/logo-small.png"
favicon_url: "A favicon for your site, see http://en.wikipedia.org/wiki/Favicon"
notification_email: "The return email address used when sending system emails such as notifying users of lost passwords, new accounts etc"
use_ssl: "Should the site be accessible via SSL?"
logo_url: "Logotypen för din webbplats t.ex.: http://xyz.com/x.png"
logo_small_url: "Den lilla logotypen för din webbplats som används när man bläddrar ner i trådar t.ex.: http://xyz.com/x-small.png"
favicon_url: "En favicon för till webbplats, se http://en.wikipedia.org/wiki/Favicon"
notification_email: "Avsändaradressen som används vid utskick av systemmail som t.ex. glömt lösenord, nya konton etc"
use_ssl: "Ska denna webbplats vara åtkomlig via SSL?"
best_of_score_threshold: "The minimum score of a post to be included in the 'best of'"
best_of_posts_required: "Minimum posts in a topic before 'best of' mode is enabled"
best_of_likes_required: "Minimum likes in a topic before the 'best of' mode will be enabled"
@ -350,6 +389,7 @@ sv:
ga_tracking_code: "Google analytics tracking code code, eg: UA-12345678-9; see http://google.com/analytics"
top_menu: "Determine which items appear in the homepage navigation, and in what order. Example latest|hot|read|favorited|unread|new|posted|categories"
post_menu: "Determine which items appear on the post menu, and in what order. Example like|edit|flag|delete|share|bookmark|reply"
share_links: "Determine which items appear on the share dialog, and in what order. Example twitter|facebook|google+"
track_external_right_clicks: "Track external links that are right clicked (eg: open in new tab) disabled by default because it rewrites URLs"
topics_per_page: "How many topics are loaded by default on the topics list page"
posts_per_page: "How many posts are returned on a topic page"
@ -358,6 +398,7 @@ sv:
supress_reply_directly_below: "Don't show reply count on a post when there is a single reply directly below"
allow_index_in_robots_txt: "Site should be indexed by search engines (update robots.txt)"
email_domains_blacklist: "A pipe-delimited list of email domains that are not allowed. Example: mailinator.com|trashmail.net"
email_domains_whitelist: "A pipe-delimited list of email domains that users may register with. WARNING: Users with email domains other than those listed will not be allowed."
version_checks: "Ping the Discourse Hub for version updates and show version messages on the /admin dashboard"
port: "Use this HTTP port rather than the default of port 80. Leave blank for none, mainly useful for development"
@ -390,7 +431,6 @@ sv:
previous_visit_timeout_hours: "How long a visit lasts before we consider it the 'previous' visit, in hours"
uncategorized_name: "The default category for topics that have no category in the /categories page"
max_mentions_per_post: "Maximum number of @name notifications you can use in a single post"
rate_limit_create_topic: "How many seconds, after creating a topic, before you can create another topic"
rate_limit_create_post: "How many seconds, after creating a post, before you can create another post"
@ -415,6 +455,19 @@ sv:
basic_requires_read_posts: "How many posts a new user must read before promotion to basic (1) trust level"
basic_requires_time_spent_mins: "How many minutes a new user must read posts before promotion to basic (1) trust level"
regular_requires_topics_entered: "How many a topics a basic user must enter before promotion to regular (2) trust level"
regular_requires_read_posts: "How many posts a basic user must read before promotion to regular (2) trust level"
regular_requires_time_spent_mins: "How many minutes a basic user must read posts before promotion to regular (2) trust level"
regular_requires_days_visited: "How many days a basic user must visit the site before promotion to regular (2) trust level"
regular_requires_likes_received: "How many likes a basic user must receive 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"
visitor_max_links: "How many links a visitor can add to a post"
visitor_max_images: "How many images a visitor can add to a post"
visitor_max_mentions_per_post: "Maximum number of @name notifications a visitor can use in a post"
max_mentions_per_post: "Maximum number of @name notifications you can use in a post"
auto_link_images_wider_than: "Images wider than this, in pixels, will get auto link and lightbox treatment"
email_time_window_mins: "How many minutes we wait before sending a user mail, to give them a chance to see it first"
@ -422,6 +475,7 @@ sv:
max_word_length: "The maximum allowed word length, in characters, in a topic title"
title_min_entropy: "The minimum allowed entropy (unique characters) required for a topic title"
body_min_entropy: "The minimum allowed entropy (unique characters) required for a post body"
title_fancy_entities: "Convert fancy HTML entities in topic titles"
min_title_similar_length: "The minimum length of a title before it will be checked for similar topics"

View File

@ -47,4 +47,3 @@ PostActionType.seed do |s|
s.is_flag = true
s.position = 7
end

View File

@ -1,31 +0,0 @@
# Allow us to override i18n keys based on the current site you're viewing.
module MultisiteI18n
class << self
# It would be nice if there was an easier way to detect if a key is missing.
def translation_or_nil(key, opts)
missing_text = "missing multisite translation"
result = I18n.t(key, opts.merge(default: missing_text))
return nil if result == missing_text
result
end
def site_translate(current_site, key, opts=nil)
opts ||= {}
translation = MultisiteI18n.translation_or_nil("#{current_site || ""}.#{key}", opts)
if translation.blank?
return I18n.t(key, opts)
else
return translation
end
end
def t(*args)
MultisiteI18n.site_translate(RailsMultisite::ConnectionManagement.current_db, *args)
end
alias :translate :t
end
end

View File

@ -1,6 +1,6 @@
<div class='onebox-result'>
{{#host}}
<a href='{{original_url}}' class='source' target="_blank">
<a href='{{original_url}}' class='source track-link' target="_blank">
{{#favicon}}<img class='favicon' src="{{favicon}}"> {{/favicon}}{{host}}
</a>
{{/host}}

View File

@ -1,6 +1,6 @@
<div class='onebox-result'>
{{#host}}
<a href='{{original_url}}' class='source' target="_blank">
<a href='{{original_url}}' class='source track-link' target="_blank">
{{#favicon}}<img class='favicon' src="{{favicon}}"> {{/favicon}}{{host}}
</a>
{{/host}}

View File

@ -2,7 +2,7 @@
{{#host}}
<div class="source">
<div class="info">
<a href="{{html_url}}" target="_blank">
<a href="{{html_url}}" class="track-link" target="_blank">
{{#favicon}}<img class="favicon" src="{{favicon}}"> {{/favicon}}{{host}}
</a>
</div>

View File

@ -2,7 +2,7 @@
{{#host}}
<div class="source">
<div class="info">
<a href='{{original_url}}' class='source' target="_blank">
<a href='{{original_url}}' class='source track-link' target="_blank">
{{#favicon}}<img class='favicon' src="{{favicon}}"> {{/favicon}}{{host}}
</a>
</div>

View File

@ -2,7 +2,7 @@
{{#host}}
<div class='source'>
<div class='info'>
<a href='{{original_url}}' target="_blank">
<a href='{{original_url}}' class="track-link" target="_blank">
{{#favicon}}<img class='favicon' src="{{favicon}}"> {{/favicon}}{{host}}
</a>
</div>

View File

@ -2,7 +2,7 @@
{{#host}}
<div class="source">
<div class="info">
<a href='{{original_url}}' target="_blank">
<a href='{{original_url}}' class="track-link" target="_blank">
{{#favicon}}<img class='favicon' src="{{favicon}}"> {{/favicon}}{{host}}
</a>
</div>

View File

@ -10,6 +10,8 @@ class PostRevisor
def revise!(user, new_raw, opts = {})
@user, @new_raw, @opts = user, new_raw, opts
return false if not should_revise?
@post.acting_user = @user
revise_post
update_category_description
post_process_post

View File

@ -4,19 +4,38 @@ module SiteContentClassMethods
@types || []
end
def content_type(content_type, format, opts=nil)
def find_content_type(ct)
SiteContent.content_types.find {|t| t.content_type == ct.to_sym}
end
def add_content_type(content_type, opts=nil)
opts ||= {}
@types ||= []
format = opts[:format] || :markdown
@types << SiteContentType.new(content_type, format, opts)
end
def content_for(content_type, replacements=nil)
replacements ||= {}
replacements = {site_name: SiteSetting.title}.merge!(replacements)
replacements = SiteSetting.settings_hash.merge!(replacements)
site_content = SiteContent.select(:content).where(content_type: content_type).first
return "" if site_content.blank?
site_content.content % replacements
result = ""
if site_content.blank?
ct = find_content_type(content_type)
result = ct.default_content if ct.present?
else
result = site_content.content
end
result.gsub!(/\%\{[^}]+\}/) do |m|
replacements[m[2..-2].to_sym] || m
end
result
end

View File

@ -38,6 +38,13 @@ module SiteSettingExtension
@@client_settings
end
def settings_hash
result = {}
@defaults.each do |s, v|
result[s] = send(s).to_s
end
result
end
def client_settings_json
Rails.cache.fetch(SiteSettingExtension.client_settings_cache_key, expires_in: 30.minutes) do

View File

@ -1,6 +1,5 @@
# Handle sending a message to a user from the system.
require_dependency 'post_creator'
require_dependency 'multisite_i18n'
class SystemMessage
@ -17,18 +16,18 @@ class SystemMessage
defaults = {site_name: SiteSetting.title,
username: @recipient.username,
user_preferences_url: "#{Discourse.base_url}/users/#{@recipient.username_lower}/preferences",
new_user_tips: MultisiteI18n.t("system_messages.usage_tips.text_body_template"),
new_user_tips: SiteContent.content_for(:usage_tips),
site_password: "",
base_url: Discourse.base_url}
params = defaults.merge(params)
if SiteSetting.access_password.present?
params[:site_password] = MultisiteI18n.t('system_messages.site_password', access_password: SiteSetting.access_password)
params[:site_password] = I18n.t('system_messages.site_password', access_password: SiteSetting.access_password)
end
title = MultisiteI18n.t("system_messages.#{type}.subject_template", params)
raw_body = MultisiteI18n.t("system_messages.#{type}.text_body_template", params)
title = I18n.t("system_messages.#{type}.subject_template", params)
raw_body = I18n.t("system_messages.#{type}.text_body_template", params)
PostCreator.create(SystemMessage.system_user,
raw: raw_body,

4
lib/tasks/db.rake Normal file
View File

@ -0,0 +1,4 @@
# we need to run seed_fu every time we run rake db:migrate
task 'db:migrate' => 'environment' do
SeedFu.seed
end

View File

@ -52,7 +52,7 @@ describe CategoryList do
it "returns empty categories for those who can create them" do
Guardian.any_instance.expects(:can_create?).with(Category).returns(true)
category_list.categories.should be_present
category_list.categories.should be_blank
end

View File

@ -1,37 +0,0 @@
require 'spec_helper'
require_dependency 'multisite_i18n'
describe MultisiteI18n do
before do
I18n.stubs(:t).with('test', {}).returns('default i18n')
MultisiteI18n.stubs(:translation_or_nil).with("default.test", {}).returns(nil)
MultisiteI18n.stubs(:translation_or_nil).with("other_site.test", {}).returns("overwritten i18n")
end
context "no value for a multisite key" do
it "it returns the default i18n key" do
MultisiteI18n.site_translate('default', 'test').should == "default i18n"
end
end
context "with a value for the multisite key" do
it "returns the overwritten value" do
MultisiteI18n.site_translate('other_site', 'test').should == "overwritten i18n"
end
end
context "when we call t, it uses the current site" do
it "returns the original" do
MultisiteI18n.t('test').should == 'default i18n'
end
it "returns the overwritten" do
RailsMultisite::ConnectionManagement.stubs(:current_db).returns('other_site')
MultisiteI18n.t('test').should == "overwritten i18n"
end
end
end

View File

@ -24,7 +24,7 @@ private
<div class='onebox-result'>
<div class='source'>
<div class='info'>
<a href='http://www.amazon.com/Ruby-Programming-Language-David-Flanagan/dp/0596516177' target="_blank">
<a href='http://www.amazon.com/Ruby-Programming-Language-David-Flanagan/dp/0596516177' class="track-link" target="_blank">
<img class='favicon' src="/assets/favicons/amazon.png"> amazon.com
</a>
</div>

View File

@ -20,7 +20,7 @@ private
<div class='onebox-result'>
<div class='source'>
<div class='info'>
<a href='https://play.google.com/store/apps/details?id=com.moosoft.parrot' target="_blank">
<a href='https://play.google.com/store/apps/details?id=com.moosoft.parrot' class="track-link" target="_blank">
<img class='favicon' src="/assets/favicons/google_play.png"> play.google.com
</a>
</div>

View File

@ -20,7 +20,7 @@ private
<div class='onebox-result'>
<div class='source'>
<div class='info'>
<a href='https://itunes.apple.com/us/app/minecraft-pocket-edition-lite/id479651754' target="_blank">
<a href='https://itunes.apple.com/us/app/minecraft-pocket-edition-lite/id479651754' class="track-link" target="_blank">
<img class='favicon' src="/assets/favicons/apple.png"> itunes.apple.com
</a>
</div>

View File

@ -24,7 +24,7 @@ private
<div class="onebox-result">
<div class="source">
<div class="info">
<a href="https://github.com/discourse/discourse/commit/ee76f1926defa8309b3a7ea64a25707519529a13" target="_blank">
<a href="https://github.com/discourse/discourse/commit/ee76f1926defa8309b3a7ea64a25707519529a13" class="track-link" target="_blank">
<img class="favicon" src="/assets/favicons/github.png"> github.com
</a>
</div>

View File

@ -39,7 +39,7 @@ private
<div class='onebox-result'>
<div class='source'>
<div class='info'>
<a href='http://en.wikipedia.org/wiki/Ruby' target="_blank">
<a href='http://en.wikipedia.org/wiki/Ruby' class="track-link" target="_blank">
<img class='favicon' src="/assets/favicons/wikipedia.png"> en.wikipedia.org
</a>
</div>

View File

@ -4,10 +4,10 @@ require 'post_revisor'
describe PostRevisor do
let(:topic) { Fabricate(:topic) }
let(:post_args) { {user: topic.user, topic: topic} }
let(:visitor) { Fabricate(:visitor) }
let(:post_args) { {user: visitor, topic: topic} }
context 'revise' do
let(:post) { Fabricate(:post, post_args) }
let(:first_version_at) { post.last_version_at }
@ -186,6 +186,32 @@ describe PostRevisor do
end
end
describe "admin editing a visitor's post" do
let(:changed_by) { Fabricate(:admin) }
before do
SiteSetting.stubs(:too_many_images).returns(0)
subject.revise!(changed_by, "So, post them here!\nhttp://i.imgur.com/FGg7Vzu.gif")
end
it "allows an admin to insert images into a visitor's post" do
post.errors.should be_blank
end
end
describe "visitor editing their own post" do
before do
SiteSetting.stubs(:too_many_images).returns(0)
subject.revise!(post.user, "So, post them here!\nhttp://i.imgur.com/FGg7Vzu.gif")
end
it "allows an admin to insert images into a visitor's post" do
post.errors.should be_present
end
end
describe 'with a new body' do
let(:changed_by) { Fabricate(:coding_horror) }
let!(:result) { subject.revise!(changed_by, 'updated body') }

View File

@ -26,7 +26,7 @@ describe EducationController do
let(:html_content) {"HTML Content"}
before do
MultisiteI18n.expects(:t).with("education.new-topic", anything).returns(markdown_content)
SiteContent.expects(:content_for).with(:education_new_topic, anything).returns(markdown_content)
PrettyText.expects(:cook).with(markdown_content).returns(html_content)
xhr :get, :show, id: 'new-topic'
end

View File

@ -25,7 +25,7 @@ describe UploadsController do
let(:logo) do
ActionDispatch::Http::UploadedFile.new({
filename: 'logo.png',
content_type: 'image/png',
type: 'image/png',
tempfile: File.new("#{Rails.root}/spec/fixtures/images/logo.png")
})
end
@ -33,11 +33,19 @@ describe UploadsController do
let(:logo_dev) do
ActionDispatch::Http::UploadedFile.new({
filename: 'logo-dev.png',
content_type: 'image/png',
type: 'image/png',
tempfile: File.new("#{Rails.root}/spec/fixtures/images/logo-dev.png")
})
end
let(:text_file) do
ActionDispatch::Http::UploadedFile.new({
filename: 'LICENSE.txt',
type: 'text/plain',
tempfile: File.new("#{Rails.root}/LICENSE.txt")
})
end
let(:files) { [ logo_dev, logo ] }
context 'with a file' do
@ -45,6 +53,11 @@ describe UploadsController do
xhr :post, :create, topic_id: 1234, file: logo
response.should be_success
end
it 'supports only images' do
xhr :post, :create, topic_id: 1234, file: text_file
response.status.should eq 415
end
end
context 'with some files' do

View File

@ -6,4 +6,9 @@ end
Fabricator(:site_content_basic, from: :site_content) do
content_type 'breaking.bad'
content "best show ever"
end
Fabricator(:site_content_site_setting, from: :site_content) do
content_type 'site.replacement'
content "%{title} is evil."
end

View File

@ -49,3 +49,9 @@ Fabricator(:another_admin, from: :user) do
admin true
end
Fabricator(:visitor, from: :user) do
name 'Newbie Newperson'
username 'newbie'
email 'newbie@new.com'
trust_level TrustLevel.levels[:visitor]
end

View File

@ -21,7 +21,7 @@ describe PostTiming do
post.user.unread_notifications_by_type.should == { Notification.types[:liked] => 1 }
PostTiming.process_timings(post.user, post.topic_id, 1, 100, [[post.post_number, 100]])
PostTiming.process_timings(post.user, post.topic_id, 1, [[post.post_number, 100]])
post.user.reload
post.user.unread_notifications_by_type.should == {}

View File

@ -11,6 +11,10 @@ describe SiteContent do
SiteContent.content_for('breaking.bad').should == ""
end
it "returns the default value for a content type with a default" do
SiteContent.content_for("usage_tips").should be_present
end
context "without replacements" do
let!(:site_content) { Fabricate(:site_content_basic) }
@ -32,11 +36,27 @@ describe SiteContent do
SiteContent.content_for('great.poem', replacements.merge(extra: 'key')).should == "roses are red. grapes are blue."
end
it "raises an error with missing keys" do
-> { SiteContent.content_for('great.poem', flower: 'roses') }.should raise_error
it "ignores missing keys" do
SiteContent.content_for('great.poem', flower: 'roses').should == "roses are red. %{food} are blue."
end
end
context "replacing site_settings" do
let!(:site_content) { Fabricate(:site_content_site_setting) }
it "replaces site_settings by default" do
SiteSetting.stubs(:title).returns("Evil Trout")
SiteContent.content_for('site.replacement').should == "Evil Trout is evil."
end
it "allows us to override the default site settings" do
SiteSetting.stubs(:title).returns("Evil Trout")
SiteContent.content_for('site.replacement', title: 'Good Tuna').should == "Good Tuna is evil."
end
end
end
end

View File

@ -212,7 +212,7 @@ describe TopicUser do
p1 = Fabricate(:post)
p2 = Fabricate(:post, user: p1.user, topic: p1.topic, post_number: 2)
TopicUser.exec_sql("UPDATE topic_users set seen_post_count=0, last_read_post_number=0
TopicUser.exec_sql("UPDATE topic_users set seen_post_count=100, last_read_post_number=0
WHERE topic_id = :topic_id AND user_id = :user_id", topic_id: p1.topic_id, user_id: p1.user_id)
[p1,p2].each do |p|

View File

@ -4,6 +4,58 @@ describe Upload do
it { should belong_to :user }
it { should belong_to :topic }
it { should validate_presence_of :original_filename }
it { should validate_presence_of :filesize }
context '.create_for' do
let(:user_id) { 1 }
let(:topic_id) { 42 }
let(:logo) do
ActionDispatch::Http::UploadedFile.new({
filename: 'logo.png',
content_type: 'image/png',
tempfile: File.new("#{Rails.root}/spec/fixtures/images/logo.png")
})
end
it "uses imgur when it is enabled" do
SiteSetting.stubs(:enable_imgur?).returns(true)
Upload.expects(:create_on_imgur).with(user_id, logo, topic_id)
Upload.create_for(user_id, logo, topic_id)
end
it "uses s3 when it is enabled" do
SiteSetting.stubs(:enable_s3_uploads?).returns(true)
Upload.expects(:create_on_s3).with(user_id, logo, topic_id)
Upload.create_for(user_id, logo, topic_id)
end
it "uses local storage otherwise" do
Upload.expects(:create_locally).with(user_id, logo, topic_id)
Upload.create_for(user_id, logo, topic_id)
end
context 'imgur' do
# TODO
end
context 's3' do
# TODO
end
context 'local' do
# TODO
end
end
end

View File

@ -12,6 +12,7 @@ describe UserVisit do
u.days_visited.should == 2
u.days_visited = 1
u.save
UserVisit.ensure_consistency!
u.reload

View File

@ -4,8 +4,8 @@ require File.expand_path('../lib/rails_multisite/version', __FILE__)
Gem::Specification.new do |gem|
gem.authors = ["Sam Saffron"]
gem.email = ["sam.saffron@gmail.com"]
gem.description = %q{TODO: Write a gem description}
gem.summary = %q{TODO: Write a gem summary}
gem.description = %q{Multi tenancy support for Rails}
gem.summary = %q{Multi tenancy support for Rails}
gem.homepage = ""
# when this is extracted comment it back in, prd has no .git