2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2025-09-06 10:50:21 +08:00

change it so all topics MUST include a category, we store a special uncategorized category to compensate

this cleans up a bunch of internals and removes some settings
This commit is contained in:
Sam 2013-10-24 10:05:51 +11:00
parent 6f66d4876a
commit 666264879c
63 changed files with 183 additions and 369 deletions

View file

@ -40,8 +40,9 @@ Discourse.Utilities = {
@param {Discourse.Category} category the category whose link we want @param {Discourse.Category} category the category whose link we want
@returns {String} the html category badge @returns {String} the html category badge
**/ **/
categoryLink: function(category) { categoryLink: function(category, allowUncategorized) {
if (!category) return ""; if (!category) return "";
if (!allowUncategorized && Em.get(category, 'id') === Discourse.Site.currentProp("uncategorized_category_id")) return "";
var color = Em.get(category, 'color'), var color = Em.get(category, 'color'),
textColor = Em.get(category, 'text_color'), textColor = Em.get(category, 'text_color'),

View file

@ -30,9 +30,6 @@ Discourse.EditCategoryController = Discourse.ObjectController.extend(Discourse.M
if (this.get('id')) { if (this.get('id')) {
return I18n.t("category.edit_long") + " : " + this.get('model.name'); return I18n.t("category.edit_long") + " : " + this.get('model.name');
} }
if (this.get('isUncategorized')){
return I18n.t("category.edit_uncategorized");
}
return I18n.t("category.create") + (this.get('model.name') ? (" : " + this.get('model.name')) : ''); return I18n.t("category.create") + (this.get('model.name') ? (" : " + this.get('model.name')) : '');
}.property('id', 'model.name'), }.property('id', 'model.name'),
@ -127,37 +124,16 @@ Discourse.EditCategoryController = Discourse.ObjectController.extend(Discourse.M
saveCategory: function() { saveCategory: function() {
var categoryController = this; var categoryController = this;
this.set('saving', true); this.set('saving', true);
this.get('model').save().then(function(result) {
// success
if( this.get('isUncategorized') ) { categoryController.send('closeModal');
$.when( Discourse.URL.redirectTo("/category/" + Discourse.Category.slugFor(result.category));
Discourse.SiteSetting.update('uncategorized_color', this.get('color')), }, function(errors) {
Discourse.SiteSetting.update('uncategorized_text_color', this.get('text_color')), // errors
Discourse.SiteSetting.update('uncategorized_name', this.get('name')) if(errors.length === 0) errors.push(I18n.t("category.creation_error"));
).then(function(result) { categoryController.displayErrors(errors);
// success categoryController.set('saving', false);
categoryController.send('closeModal'); });
// We can't redirect to the uncategorized category on save because the slug
// might have changed.
Discourse.URL.redirectTo("/categories");
}, function(errors) {
// errors
if(errors.length === 0) errors.push(I18n.t("category.save_error"));
categoryController.displayErrors(errors);
categoryController.set('saving', false);
});
} else {
this.get('model').save().then(function(result) {
// success
categoryController.send('closeModal');
Discourse.URL.redirectTo("/category/" + Discourse.Category.slugFor(result.category));
}, function(errors) {
// errors
if(errors.length === 0) errors.push(I18n.t("category.creation_error"));
categoryController.displayErrors(errors);
categoryController.set('saving', false);
});
}
}, },
deleteCategory: function() { deleteCategory: function() {

View file

@ -42,8 +42,9 @@ Handlebars.registerHelper('topicLink', function(property, options) {
@for Handlebars @for Handlebars
**/ **/
Handlebars.registerHelper('categoryLink', function(property, options) { Handlebars.registerHelper('categoryLink', function(property, options) {
var allowUncategorized = options.hash && options.hash.allowUncategorized;
var category = Ember.Handlebars.get(this, property, options); var category = Ember.Handlebars.get(this, property, options);
return new Handlebars.SafeString(Discourse.Utilities.categoryLink(category)); return new Handlebars.SafeString(Discourse.Utilities.categoryLink(category, allowUncategorized));
}); });

View file

@ -117,19 +117,6 @@ Discourse.Category = Discourse.Model.extend({
Discourse.Category.reopenClass({ Discourse.Category.reopenClass({
uncategorizedInstance: function() {
if (this.uncategorized) return this.uncategorized;
this.uncategorized = this.create({
slug: 'uncategorized',
name: Discourse.SiteSettings.uncategorized_name,
isUncategorized: true,
color: Discourse.SiteSettings.uncategorized_color,
text_color: Discourse.SiteSettings.uncategorized_text_color
});
return this.uncategorized;
},
slugFor: function(category) { slugFor: function(category) {
if (!category) return ""; if (!category) return "";

View file

@ -45,19 +45,8 @@ Discourse.CategoryList.reopenClass({
}); });
} }
if (c.is_uncategorized) { categories.pushObject(Discourse.Category.create(c));
var uncategorized = Discourse.Category.uncategorizedInstance();
uncategorized.setProperties({
topics: c.topics,
featured_users: c.featured_users,
topics_week: c.topics_week,
topics_month: c.topics_month,
topics_year: c.topics_year
});
categories.pushObject(uncategorized);
} else {
categories.pushObject(Discourse.Category.create(c));
}
}); });
return categories; return categories;
}, },

View file

@ -1,7 +1,7 @@
<table id='topic-list'> <table id='topic-list'>
<tr> <tr>
<th class="main-link"> <th class="main-link">
{{categoryLink this}} {{categoryLink this allowUncategorized=true}}
<div class='posters'> <div class='posters'>
{{#each featured_users}} {{#each featured_users}}

View file

@ -20,7 +20,7 @@
{{#if controller.ordering}} {{#if controller.ordering}}
<i class="icon-reorder"></i> <i class="icon-reorder"></i>
{{/if}} {{/if}}
{{categoryLink this}} {{categoryLink this allowUncategorized=true}}
{{#if unreadTopics}} {{#if unreadTopics}}
<a href={{unbound url}} class='badge new-posts badge-notification' title='{{i18n topic.unread_topics count="unreadTopics"}}'>{{unbound unreadTopics}}</a> <a href={{unbound url}} class='badge new-posts badge-notification' title='{{i18n topic.unread_topics count="unreadTopics"}}'>{{unbound unreadTopics}}</a>
{{/if}} {{/if}}

View file

@ -15,8 +15,10 @@ Discourse.CategoryChooserView = Discourse.ComboboxView.extend({
init: function() { init: function() {
this._super(); this._super();
// TODO perhaps allow passing a param in to select if we need full or not // TODO perhaps allow passing a param in to select if we need full or not
var uncategorized_id = Discourse.Site.currentProp("uncategorized_category_id");
this.set('content', _.filter(Discourse.Category.list(), function(c){ this.set('content', _.filter(Discourse.Category.list(), function(c){
return c.permission === Discourse.PermissionType.FULL; return c.permission === Discourse.PermissionType.FULL && c.id !== uncategorized_id;
})); }));
}, },

View file

@ -49,18 +49,13 @@ class ListController < ApplicationController
def category def category
query = TopicQuery.new(current_user, page: params[:page]) query = TopicQuery.new(current_user, page: params[:page])
# If they choose uncategorized, return topics NOT in a category if !@category
if request_is_for_uncategorized? raise Discourse::NotFound
list = query.list_uncategorized return
else
if !@category
raise Discourse::NotFound
return
end
guardian.ensure_can_see!(@category)
list = query.list_category(@category)
@description = @category.description
end end
guardian.ensure_can_see!(@category)
list = query.list_category(@category)
@description = @category.description
if params[:parent_category].present? if params[:parent_category].present?
list.more_topics_url = url_for(category_list_parent_path(params[:parent_category], params[:category], page: next_page, format: "json")) list.more_topics_url = url_for(category_list_parent_path(params[:parent_category], params[:category], page: next_page, format: "json"))
@ -72,8 +67,6 @@ class ListController < ApplicationController
end end
def category_feed def category_feed
raise Discourse::InvalidParameters.new('Category RSS of "uncategorized"') if request_is_for_uncategorized?
guardian.ensure_can_see!(@category) guardian.ensure_can_see!(@category)
discourse_expires_in 1.minute discourse_expires_in 1.minute
@ -137,12 +130,6 @@ class ListController < ApplicationController
Category.where(id: slug.to_i, parent_category_id: parent_category_id).includes(:featured_users).first Category.where(id: slug.to_i, parent_category_id: parent_category_id).includes(:featured_users).first
end end
def request_is_for_uncategorized?
params[:category] == Slug.for(SiteSetting.uncategorized_name) ||
params[:category] == SiteSetting.uncategorized_name ||
params[:category] == 'uncategorized'
end
def build_topic_list_options def build_topic_list_options
# html format means we need to parse exclude category (aka filter) from the site options top menu # html format means we need to parse exclude category (aka filter) from the site options top menu
menu_items = SiteSetting.top_menu_items menu_items = SiteSetting.top_menu_items

View file

@ -117,7 +117,7 @@ class Category < ActiveRecord::Base
topics_with_post_count = Topic topics_with_post_count = Topic
.select("topics.category_id, COUNT(*) topic_count, SUM(topics.posts_count) post_count") .select("topics.category_id, COUNT(*) topic_count, SUM(topics.posts_count) post_count")
.where("topics.id NOT IN (select cc.topic_id from categories cc)") .where("topics.id NOT IN (select cc.topic_id from categories cc WHERE topic_id IS NOT NULL)")
.group("topics.category_id") .group("topics.category_id")
.visible.to_sql .visible.to_sql
@ -150,10 +150,11 @@ SQL
end end
def create_category_definition def create_category_definition
create_topic!(title: I18n.t("category.topic_prefix", category: name), user: user, pinned_at: Time.now) t = Topic.new(title: I18n.t("category.topic_prefix", category: name), user: user, pinned_at: Time.now, category_id: id)
update_column(:topic_id, topic.id) t.skip_callbacks = true
topic.update_column(:category_id, id) t.save!
topic.posts.create(raw: post_template, user: user) update_column(:topic_id, t.id)
t.posts.create(raw: post_template, user: user)
end end
def topic_url def topic_url

View file

@ -16,7 +16,6 @@ class CategoryList
find_categories find_categories
prune_empty prune_empty
add_uncategorized
find_user_data find_user_data
end end
@ -91,63 +90,14 @@ class CategoryList
end end
end end
# Add the uncategorized "magic" category
# TODO: remove this entire hack, not needed
def add_uncategorized
# Support for uncategorized topics
uncategorized_topics = Topic
.listable_topics
.visible
.where(category_id: nil)
.topic_list_order
.limit(SiteSetting.category_featured_topics)
if uncategorized_topics.present?
totals = Topic.exec_sql("SELECT SUM(CASE WHEN created_at >= (CURRENT_TIMESTAMP - INTERVAL '1 WEEK') THEN 1 ELSE 0 END) as topics_week,
SUM(CASE WHEN created_at >= (CURRENT_TIMESTAMP - INTERVAL '1 MONTH') THEN 1 ELSE 0 END) as topics_month,
SUM(CASE WHEN created_at >= (CURRENT_TIMESTAMP - INTERVAL '1 YEAR') THEN 1 ELSE 0 END) as topics_year,
COUNT(*) AS topic_count
FROM topics
WHERE topics.visible
AND topics.deleted_at IS NULL
AND topics.category_id IS NULL
AND topics.archetype <> '#{Archetype.private_message}'").first
uncategorized = Category.new({name: SiteSetting.uncategorized_name,
slug: Slug.for(SiteSetting.uncategorized_name),
color: SiteSetting.uncategorized_color,
text_color: SiteSetting.uncategorized_text_color,
featured_topics: uncategorized_topics}.merge(totals))
# Find the appropriate place to insert it:
insert_at = nil
unless latest_post_only?
@categories.each_with_index do |c, idx|
if (uncategorized.topics_week || 0) > (c.topics_week || 0)
insert_at = idx
break
end
end
end
@categories.insert(insert_at || @categories.size, uncategorized)
end
if uncategorized.present?
@all_topics ||= []
uncategorized.displayable_topics = uncategorized_topics
@all_topics << uncategorized_topics
@all_topics.flatten!
end
end
# Remove any empty topics unless we can create them (so we can see the controls) # Remove any empty topics unless we can create them (so we can see the controls)
def prune_empty def prune_empty
unless @guardian.can_create?(Category) unless @guardian.can_create?(Category)
# Remove categories with no featured topics unless we have the ability to edit one # Remove categories with no featured topics unless we have the ability to edit one
@categories.delete_if { |c| c.displayable_topics.blank? && c.description.nil? } @categories.delete_if { |c|
c.displayable_topics.blank? && c.description.blank?
}
end end
end end

View file

@ -84,7 +84,7 @@ class Post < ActiveRecord::Base
super super
update_flagged_posts_count update_flagged_posts_count
TopicLink.extract_from(self) TopicLink.extract_from(self)
if topic && topic.category_id if topic && topic.category_id && topic.category
topic.category.update_latest topic.category.update_latest
end end
end end

View file

@ -103,10 +103,6 @@ class SiteSetting < ActiveRecord::Base
setting(:max_mentions_per_post, 10) setting(:max_mentions_per_post, 10)
setting(:newuser_max_mentions_per_post, 2) setting(:newuser_max_mentions_per_post, 2)
client_setting(:uncategorized_name, 'uncategorized')
client_setting(:uncategorized_color, 'AB9364');
client_setting(:uncategorized_text_color, 'FFFFFF');
setting(:unique_posts_mins, Rails.env.test? ? 0 : 5) setting(:unique_posts_mins, Rails.env.test? ? 0 : 5)
# Rate Limits # Rate Limits
@ -267,11 +263,13 @@ class SiteSetting < ActiveRecord::Base
setting(:max_daily_gravatar_crawls, 500) setting(:max_daily_gravatar_crawls, 500)
setting(:sequential_replies_threshold, 2) setting(:sequential_replies_threshold, 2)
client_setting(:enable_mobile_theme, true) client_setting(:enable_mobile_theme, true)
setting(:dominating_topic_minimum_percent, 20) setting(:dominating_topic_minimum_percent, 20)
# hidden setting only used by system
hidden_setting(:uncategorized_category_id, -1, hidden: true)
def self.call_discourse_hub? def self.call_discourse_hub?
self.enforce_global_nicknames? && self.discourse_org_access_key.present? self.enforce_global_nicknames? && self.discourse_org_access_key.present?
end end

View file

@ -56,11 +56,12 @@ class Topic < ActiveRecord::Base
:case_sensitive => false, :case_sensitive => false,
:collection => Proc.new{ Topic.listable_topics } } :collection => Proc.new{ Topic.listable_topics } }
# The allow_uncategorized_topics site setting can be changed at any time, so there may be validates :category_id, :presence => true ,:exclusion => {:in => [SiteSetting.uncategorized_category_id]},
# existing topics with nil category. We'll allow that, but when someone tries to make a new :if => Proc.new { |t|
# topic or change a topic's category, perform validation. (t.new_record? || t.category_id_changed?) &&
attr_accessor :do_category_validation !SiteSetting.allow_uncategorized_topics
validates :category_id, :presence => { :if => Proc.new { @do_category_validation && !SiteSetting.allow_uncategorized_topics } } }
before_validation do before_validation do
self.sanitize_title self.sanitize_title
@ -142,13 +143,16 @@ class Topic < ActiveRecord::Base
before_create do before_create do
self.bumped_at ||= Time.now self.bumped_at ||= Time.now
self.last_post_user_id ||= user_id self.last_post_user_id ||= user_id
self.do_category_validation = true
if !@ignore_category_auto_close and self.category and self.category.auto_close_days and self.auto_close_at.nil? if !@ignore_category_auto_close and self.category and self.category.auto_close_days and self.auto_close_at.nil?
set_auto_close(self.category.auto_close_days) set_auto_close(self.category.auto_close_days)
end end
end end
attr_accessor :skip_callbacks
after_create do after_create do
return if skip_callbacks
changed_to_category(category) changed_to_category(category)
if archetype == Archetype.private_message if archetype == Archetype.private_message
DraftSequence.next!(user, Draft::NEW_PRIVATE_MESSAGE) DraftSequence.next!(user, Draft::NEW_PRIVATE_MESSAGE)
@ -158,14 +162,21 @@ class Topic < ActiveRecord::Base
end end
before_save do before_save do
return if skip_callbacks
if (auto_close_at_changed? and !auto_close_at_was.nil?) or (auto_close_user_id_changed? and auto_close_at) if (auto_close_at_changed? and !auto_close_at_was.nil?) or (auto_close_user_id_changed? and auto_close_at)
self.auto_close_started_at ||= Time.zone.now if auto_close_at self.auto_close_started_at ||= Time.zone.now if auto_close_at
Jobs.cancel_scheduled_job(:close_topic, {topic_id: id}) Jobs.cancel_scheduled_job(:close_topic, {topic_id: id})
true true
end end
if category_id.nil? && (archetype.nil? || archetype == "regular")
self.category_id = SiteSetting.uncategorized_category_id
end
end end
after_save do after_save do
return if skip_callbacks
if auto_close_at and (auto_close_at_changed? or auto_close_user_id_changed?) if auto_close_at and (auto_close_at_changed? or auto_close_user_id_changed?)
Jobs.enqueue_at(auto_close_at, :close_topic, {topic_id: id, user_id: auto_close_user_id || user_id}) Jobs.enqueue_at(auto_close_at, :close_topic, {topic_id: id, user_id: auto_close_user_id || user_id})
end end
@ -333,8 +344,13 @@ class Topic < ActiveRecord::Base
Category.where(['id = ?', category_id]).update_all 'topic_count = topic_count - 1' Category.where(['id = ?', category_id]).update_all 'topic_count = topic_count - 1'
end end
self.category_id = cat.id success = true
if save if self.category_id != cat.id
self.category_id = cat.id
success = save
end
if success
CategoryFeaturedTopic.feature_topics_for(old_category) CategoryFeaturedTopic.feature_topics_for(old_category)
Category.where(id: cat.id).update_all 'topic_count = topic_count + 1' Category.where(id: cat.id).update_all 'topic_count = topic_count + 1'
CategoryFeaturedTopic.feature_topics_for(cat) unless old_category.try(:id) == cat.try(:id) CategoryFeaturedTopic.feature_topics_for(cat) unless old_category.try(:id) == cat.try(:id)
@ -372,20 +388,15 @@ class Topic < ActiveRecord::Base
# Changes the category to a new name # Changes the category to a new name
def change_category(name) def change_category(name)
self.do_category_validation = true
# If the category name is blank, reset the attribute # If the category name is blank, reset the attribute
if name.blank? if name.blank?
if category_id.present? cat = Category.where(id: SiteSetting.uncategorized_category_id).first
CategoryFeaturedTopic.feature_topics_for(category) else
Category.where(id: category_id).update_all 'topic_count = topic_count - 1' cat = Category.where(name: name).first
end
self.category_id = nil
return save
end end
cat = Category.where(name: name).first
return true if cat == category return true if cat == category
return false unless cat
changed_to_category(cat) changed_to_category(cat)
end end

View file

@ -23,7 +23,7 @@ class CategoryDetailedSerializer < BasicCategorySerializer
end end
def is_uncategorized def is_uncategorized
name == SiteSetting.uncategorized_name object.id == SiteSetting.uncategorized_category_id
end end
def include_is_uncategorized? def include_is_uncategorized?

View file

@ -3,8 +3,8 @@ class SiteSerializer < ApplicationSerializer
attributes :default_archetype, attributes :default_archetype,
:notification_types, :notification_types,
:post_types, :post_types,
:uncategorized_slug, :group_names,
:group_names :uncategorized_category_id # this is hidden so putting it here
has_many :categories, serializer: BasicCategorySerializer, embed: :objects has_many :categories, serializer: BasicCategorySerializer, embed: :objects
@ -21,8 +21,8 @@ class SiteSerializer < ApplicationSerializer
Post.types Post.types
end end
def uncategorized_slug def uncategorized_category_id
Slug.for(SiteSetting.uncategorized_name) SiteSetting.uncategorized_category_id
end end
end end

View file

@ -935,7 +935,6 @@ cs:
none: '(bez kategorie)' none: '(bez kategorie)'
edit: 'upravit' edit: 'upravit'
edit_long: "Upravit kategorii" edit_long: "Upravit kategorii"
edit_uncategorized: "Upravit nekategorizované"
view: 'Zobrazit témata v kategorii' view: 'Zobrazit témata v kategorii'
general: 'Obecné' general: 'Obecné'
settings: 'Nastavení' settings: 'Nastavení'

View file

@ -925,7 +925,6 @@ de:
none: '(keine Kategorie)' none: '(keine Kategorie)'
edit: 'Bearbeiten' edit: 'Bearbeiten'
edit_long: "Kategorie bearbeiten" edit_long: "Kategorie bearbeiten"
edit_uncategorized: "Unkategorisierte bearbeiten"
view: 'Zeige Themen dieser Kategorie' view: 'Zeige Themen dieser Kategorie'
general: 'Generell' general: 'Generell'
settings: 'Einstellungen' settings: 'Einstellungen'

View file

@ -945,7 +945,6 @@ en:
choose: 'Select a category&hellip;' choose: 'Select a category&hellip;'
edit: 'edit' edit: 'edit'
edit_long: "Edit Category" edit_long: "Edit Category"
edit_uncategorized: "Edit Uncategorized"
view: 'View Topics in Category' view: 'View Topics in Category'
general: 'General' general: 'General'
settings: 'Settings' settings: 'Settings'

View file

@ -831,7 +831,6 @@ fr:
choose: 'Sélectionner une catégorie&hellip;' choose: 'Sélectionner une catégorie&hellip;'
edit: 'éditer' edit: 'éditer'
edit_long: "Editer la catégorie" edit_long: "Editer la catégorie"
edit_uncategorized: "Editer les sans catégorie"
view: 'Voir les discussions dans cette catégorie' view: 'Voir les discussions dans cette catégorie'
general: 'Général' general: 'Général'
settings: 'Paramètres' settings: 'Paramètres'

View file

@ -885,7 +885,6 @@ it:
none: '(nessuna categoria)' none: '(nessuna categoria)'
edit: 'modifica' edit: 'modifica'
edit_long: "Modifica Categoria" edit_long: "Modifica Categoria"
edit_uncategorized: "Modifica Non categorizzata"
view: 'Mostra Topic nella Categoria' view: 'Mostra Topic nella Categoria'
general: 'Generale' general: 'Generale'
settings: 'Impostazioni' settings: 'Impostazioni'

View file

@ -733,7 +733,6 @@ ko:
none: '(카테고리 없음)' none: '(카테고리 없음)'
edit: '편집' edit: '편집'
edit_long: "카테고리 편집" edit_long: "카테고리 편집"
edit_uncategorized: "분류되지 않은 편집"
view: '카테고리 항목보기' view: '카테고리 항목보기'
delete: '카테고리 지우기' delete: '카테고리 지우기'
create: '카테고리 만들기' create: '카테고리 만들기'

View file

@ -797,7 +797,6 @@ nb_NO:
none: '(no category)' none: '(no category)'
edit: 'rediger' edit: 'rediger'
edit_long: "Rediger Kategori" edit_long: "Rediger Kategori"
edit_uncategorized: "Rediger Ukategorisert"
view: 'Se Emner i Kategori' view: 'Se Emner i Kategori'
general: 'Generellt' general: 'Generellt'
settings: 'Innstillinger' settings: 'Innstillinger'

View file

@ -890,7 +890,6 @@ nl:
none: (geen categorie) none: (geen categorie)
edit: bewerk edit: bewerk
edit_long: Bewerk categorie edit_long: Bewerk categorie
edit_uncategorized: "Wijzig ongecategoriseerd"
view: Bekijk topics in categorie view: Bekijk topics in categorie
general: Algemeen general: Algemeen
settings: Instellingen settings: Instellingen

View file

@ -851,7 +851,6 @@ pseudo:
none: '[[ (ɳó čáťéǧóřý) ]]' none: '[[ (ɳó čáťéǧóřý) ]]'
edit: '[[ éďíť ]]' edit: '[[ éďíť ]]'
edit_long: '[[ Éďíť Čáťéǧóřý ]]' edit_long: '[[ Éďíť Čáťéǧóřý ]]'
edit_uncategorized: '[[ Éďíť Ůɳčáťéǧóřížéď ]]'
view: '[[ Ѷíéŵ Ťóƿíčš íɳ Čáťéǧóřý ]]' view: '[[ Ѷíéŵ Ťóƿíčš íɳ Čáťéǧóřý ]]'
general: '[[ Ǧéɳéřáł ]]' general: '[[ Ǧéɳéřáł ]]'
settings: '[[ Šéťťíɳǧš ]]' settings: '[[ Šéťťíɳǧš ]]'

View file

@ -944,7 +944,6 @@ pt_BR:
choose: 'Selecione uma categoria&hellip;' choose: 'Selecione uma categoria&hellip;'
edit: 'editar' edit: 'editar'
edit_long: "Editar Categoria" edit_long: "Editar Categoria"
edit_uncategorized: "Editar Sem Categoria"
view: 'Visualizar Tópicos na Categoria' view: 'Visualizar Tópicos na Categoria'
general: 'Geral' general: 'Geral'
settings: 'Configurações' settings: 'Configurações'

View file

@ -942,7 +942,6 @@ ru:
choose: 'Select a category&hellip;' choose: 'Select a category&hellip;'
edit: изменить edit: изменить
edit_long: 'Изменить категорию' edit_long: 'Изменить категорию'
edit_uncategorized: 'Изменить "Без категории"'
view: 'Просмотр тем по категориям' view: 'Просмотр тем по категориям'
general: Общие general: Общие
settings: Настройки settings: Настройки

View file

@ -945,7 +945,6 @@ zh_CN:
choose: '选择分类……' choose: '选择分类……'
edit: '编辑' edit: '编辑'
edit_long: "编辑分类" edit_long: "编辑分类"
edit_uncategorized: "编辑未分类的"
view: '浏览分类下的主题' view: '浏览分类下的主题'
general: '通常' general: '通常'
settings: '设置' settings: '设置'

View file

@ -844,7 +844,6 @@ zh_TW:
none: '(未分類)' none: '(未分類)'
edit: '編輯' edit: '編輯'
edit_long: "編輯分類" edit_long: "編輯分類"
edit_uncategorized: "編輯未分類的"
view: '浏覽分類下的主題' view: '浏覽分類下的主題'
general: '通常' general: '通常'
settings: '設置' settings: '設置'

View file

@ -596,10 +596,6 @@ cs:
active_user_rate_limit_secs: "Jak často aktualizujeme informaci o poslední návštěvě uživatelů, v sekundách" active_user_rate_limit_secs: "Jak často aktualizujeme informaci o poslední návštěvě uživatelů, v sekundách"
previous_visit_timeout_hours: "Kolik času musí uplynout v hodinách, než je návštěva uživatele považována za uplynulou" previous_visit_timeout_hours: "Kolik času musí uplynout v hodinách, než je návštěva uživatele považována za uplynulou"
uncategorized_name: "Výchozí kategorie pro témata, která nemají nastavenou kategorii"
uncategorized_color: "Barva pozadí štítku kategorie pro témata, která nemají nastavenou kategorii"
uncategorized_text_color: "Barva textu na štítku kategorie pro témata, která nemají nastavenou kategorii"
rate_limit_create_topic: "Počet sekund, které je nutné počkat od vytvoření tématu, než smí uživatel vytvořit další" rate_limit_create_topic: "Počet sekund, které je nutné počkat od vytvoření tématu, než smí uživatel vytvořit další"
rate_limit_create_post: "Počet sekund, které je nutné počkat od zaslání příspěvku, než smí uživatel zaslat další" rate_limit_create_post: "Počet sekund, které je nutné počkat od zaslání příspěvku, než smí uživatel zaslat další"

View file

@ -385,7 +385,6 @@ da:
active_user_rate_limit_secs: "How frequently we update the 'last_seen_at' field, in seconds" active_user_rate_limit_secs: "How frequently we update the 'last_seen_at' field, in seconds"
previous_visit_timeout_hours: "How long a visit lasts before we consider it the 'previous' visit, in hours" 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" 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_topic: "How many seconds, after creating a topic, before you can create another topic"

View file

@ -570,10 +570,6 @@ de:
active_user_rate_limit_secs: "Sekunden, nach denen das 'last_seen_at'-Feld aktualisiert wird." active_user_rate_limit_secs: "Sekunden, nach denen das 'last_seen_at'-Feld aktualisiert wird."
previous_visit_timeout_hours: "Stunden, die ein Besuch dauert bevor er als 'früherer' Besuch gezählt wird." previous_visit_timeout_hours: "Stunden, die ein Besuch dauert bevor er als 'früherer' Besuch gezählt wird."
uncategorized_name: "Name der Standardkategorie für Themen, die keiner Kategorie zugeordnet wurden in der Kategorieübersicht /categories."
uncategorized_color: "Die Hintergrundfarbe der Plakette für die Kategorie der Themen welche keine Kategorie haben"
uncategorized_text_color: "Die Textfarbe der Plakette für die Kategorie der Themen welche keine Kategorie haben"
rate_limit_create_topic: "Sekunden Wartezeit nach Erstellung eines Themas, bevor man ein neues Thema erstellen kann." rate_limit_create_topic: "Sekunden Wartezeit nach Erstellung eines Themas, bevor man ein neues Thema erstellen kann."
rate_limit_create_post: "Sekunden Wartezeit nach Erstellung eines Beitrags, bevor man einen neuen Beitrag erstellen kann." rate_limit_create_post: "Sekunden Wartezeit nach Erstellung eines Beitrags, bevor man einen neuen Beitrag erstellen kann."

View file

@ -607,10 +607,6 @@ en:
active_user_rate_limit_secs: "How frequently we update the 'last_seen_at' field, in seconds" active_user_rate_limit_secs: "How frequently we update the 'last_seen_at' field, in seconds"
previous_visit_timeout_hours: "How long a visit lasts before we consider it the 'previous' visit, in hours" 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"
uncategorized_color: "The background color of the uncategorized topics category"
uncategorized_text_color: "The text color of the uncategorized topics category"
rate_limit_create_topic: "After creating a topic, users must wait this many seconds before they can create another topic" rate_limit_create_topic: "After creating a topic, users must wait this many seconds before they can create another topic"
rate_limit_create_post: "After posting, users must wait this many seconds before they can create another post" rate_limit_create_post: "After posting, users must wait this many seconds before they can create another post"

View file

@ -373,7 +373,6 @@ es:
active_user_rate_limit_secs: "Como de frecuentemente actualizaremos el campo 'last_seen_at', en segundos" active_user_rate_limit_secs: "Como de frecuentemente actualizaremos el campo 'last_seen_at', en segundos"
previous_visit_timeout_hours: "Cuanto tiempo debe pasar antes de que una visita sea considerada la 'visita previa', en horas" previous_visit_timeout_hours: "Cuanto tiempo debe pasar antes de que una visita sea considerada la 'visita previa', en horas"
uncategorized_name: "La categoría por defecto para los topics que no tienen categoría en la página /categories"
max_mentions_per_post: "Maximo número de notificaciones @name que se pueden usar en un post" max_mentions_per_post: "Maximo número de notificaciones @name que se pueden usar en un post"
rate_limit_create_topic: "Cuantos segundos, después de crear un topic, deben pasar antes de poder crear otro topic" rate_limit_create_topic: "Cuantos segundos, después de crear un topic, deben pasar antes de poder crear otro topic"

View file

@ -547,9 +547,6 @@ fr:
allow_import: "Autoriser l'importation qui remplacera TOUTES les données du site. Laisser non coché, sauf si vous prévoyez d'importer des données." allow_import: "Autoriser l'importation qui remplacera TOUTES les données du site. Laisser non coché, sauf si vous prévoyez d'importer des données."
active_user_rate_limit_secs: "A quelle fréquence mettre à jour le champ 'dernier_vu_a', en secondes." active_user_rate_limit_secs: "A quelle fréquence mettre à jour le champ 'dernier_vu_a', en secondes."
previous_visit_timeout_hours: "Combien de temps dure une visite avant de la considérer comme la visite 'précédente', en heures." previous_visit_timeout_hours: "Combien de temps dure une visite avant de la considérer comme la visite 'précédente', en heures."
uncategorized_name: "Catégorie par défaut pour les discussions sans catégorie sur la pages /categories"
uncategorized_color: "La couleur de fond de la catégorie des discussions sans catégorie"
uncategorized_text_color: "La couleur du texte de la catégorie des discussions sans catégorie"
rate_limit_create_topic: "Après la création d'une discussion, les utilisateurs doivent attendre ce nombre de secondes avant de pouvoir créer une nouvelle discussion" rate_limit_create_topic: "Après la création d'une discussion, les utilisateurs doivent attendre ce nombre de secondes avant de pouvoir créer une nouvelle discussion"
rate_limit_create_post: "Après avoir posté un message, les utilisateurs doivent attendre ce nombre de secondes avant de pouvoir en poster un nouveau" rate_limit_create_post: "Après avoir posté un message, les utilisateurs doivent attendre ce nombre de secondes avant de pouvoir en poster un nouveau"
max_likes_per_day: "Quantité maximale de J'aime qu'un utilisateur peut effectuer en un jour" max_likes_per_day: "Quantité maximale de J'aime qu'un utilisateur peut effectuer en un jour"

View file

@ -384,7 +384,6 @@ id:
active_user_rate_limit_secs: "How frequently we update the 'last_seen_at' field, in seconds" active_user_rate_limit_secs: "How frequently we update the 'last_seen_at' field, in seconds"
previous_visit_timeout_hours: "How long a visit lasts before we consider it the 'previous' visit, in hours" 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" 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_topic: "How many seconds, after creating a topic, before you can create another topic"

View file

@ -506,10 +506,6 @@ it:
active_user_rate_limit_secs: "Quanto frequentemente viene aggiornato il campo 'last_seen_at' field, in secondi" active_user_rate_limit_secs: "Quanto frequentemente viene aggiornato il campo 'last_seen_at' field, in secondi"
previous_visit_timeout_hours: "Durata di una visita prima che venga considerata la visita 'precedente', in ore" previous_visit_timeout_hours: "Durata di una visita prima che venga considerata la visita 'precedente', in ore"
uncategorized_name: "La categoria di default per i topic non categorizzati nella pagina /categories"
uncategorized_color: "Il colore di sfondo del badge per la categoria con i topic privi di categoria"
uncategorized_text_color: "Il colore del testo del badge per la categoria con i topic privi di categoria"
rate_limit_create_topic: "Quanti secondi, dopo aver creato un topic, per poter creare un nuovo topic" rate_limit_create_topic: "Quanti secondi, dopo aver creato un topic, per poter creare un nuovo topic"
rate_limit_create_post: "Quanti secondi, dopo aver creato un post, per poter creare un nuovo post" rate_limit_create_post: "Quanti secondi, dopo aver creato un post, per poter creare un nuovo post"

View file

@ -506,10 +506,6 @@ ko:
active_user_rate_limit_secs: "How frequently we update the 'last_seen_at' field, in seconds" active_user_rate_limit_secs: "How frequently we update the 'last_seen_at' field, in seconds"
previous_visit_timeout_hours: "How long a visit lasts before we consider it the 'previous' visit, in hours" 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"
uncategorized_color: "The background color of the badge for the category with topics that have no category"
uncategorized_text_color: "The text color of the badge for the category with topics that have no category"
rate_limit_create_topic: "How many seconds, after creating a topic, before you can create another topic" 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" rate_limit_create_post: "How many seconds, after creating a post, before you can create another post"

View file

@ -570,10 +570,6 @@ nl:
active_user_rate_limit_secs: "Hoe vaak we het 'last_seen_at'-veld updaten, in seconden." active_user_rate_limit_secs: "Hoe vaak we het 'last_seen_at'-veld updaten, in seconden."
previous_visit_timeout_hours: "Hoe lang een bezoek duurt voordat we het als het 'vorige' bezoek beschouwen, in uren." previous_visit_timeout_hours: "Hoe lang een bezoek duurt voordat we het als het 'vorige' bezoek beschouwen, in uren."
uncategorized_name: De naam voor ongecategoriseerde topics in de categorielijst
uncategorized_color: De achtergrondkleur van de badge voor de categorie met topics zonder categorie
uncategorized_text_color: De tekstkleur van de badge voor de categorie met topics zonder categorie
rate_limit_create_topic: Hoeveel seconden voordat je een ander topic kan aanmaken rate_limit_create_topic: Hoeveel seconden voordat je een ander topic kan aanmaken
rate_limit_create_post: Hoeveel seconden voordat je een ander bericht kan aanmaken rate_limit_create_post: Hoeveel seconden voordat je een ander bericht kan aanmaken

View file

@ -685,12 +685,6 @@ pseudo:
ƒíéłď, íɳ šéčóɳďš ]]' ƒíéłď, íɳ šéčóɳďš ]]'
previous_visit_timeout_hours: '[[ Ĥóŵ łóɳǧ á νíšíť łášťš ƀéƒóřé ŵé čóɳšíďéř íť previous_visit_timeout_hours: '[[ Ĥóŵ łóɳǧ á νíšíť łášťš ƀéƒóřé ŵé čóɳšíďéř íť
ťĥé ''ƿřéνíóůš'' νíšíť, íɳ ĥóůřš ]]' ťĥé ''ƿřéνíóůš'' νíšíť, íɳ ĥóůřš ]]'
uncategorized_name: '[[ Ťĥé ďéƒáůłť čáťéǧóřý ƒóř ťóƿíčš ťĥáť ĥáνé ɳó čáťéǧóřý
íɳ ťĥé /čáťéǧóříéš ƿáǧé ]]'
uncategorized_color: '[[ Ťĥé ƀáčǩǧřóůɳď čółóř óƒ ťĥé ƀáďǧé ƒóř ťĥé čáťéǧóřý ŵíťĥ
ťóƿíčš ťĥáť ĥáνé ɳó čáťéǧóřý ]]'
uncategorized_text_color: '[[ Ťĥé ťéхť čółóř óƒ ťĥé ƀáďǧé ƒóř ťĥé čáťéǧóřý ŵíťĥ
ťóƿíčš ťĥáť ĥáνé ɳó čáťéǧóřý ]]'
rate_limit_create_topic: '[[ Ĥóŵ ɱáɳý šéčóɳďš, áƒťéř čřéáťíɳǧ á ťóƿíč, ƀéƒóřé rate_limit_create_topic: '[[ Ĥóŵ ɱáɳý šéčóɳďš, áƒťéř čřéáťíɳǧ á ťóƿíč, ƀéƒóřé
ýóů čáɳ čřéáťé áɳóťĥéř ťóƿíč ]]' ýóů čáɳ čřéáťé áɳóťĥéř ťóƿíč ]]'
rate_limit_create_post: '[[ Ĥóŵ ɱáɳý šéčóɳďš, áƒťéř čřéáťíɳǧ á ƿóšť, ƀéƒóřé ýóů rate_limit_create_post: '[[ Ĥóŵ ɱáɳý šéčóɳďš, áƒťéř čřéáťíɳǧ á ƿóšť, ƀéƒóřé ýóů

View file

@ -313,7 +313,6 @@ pt:
active_user_rate_limit_secs: "How frequently we update the 'last_seen_at' field, in seconds." active_user_rate_limit_secs: "How frequently we update the 'last_seen_at' field, in seconds."
previous_visit_timeout_hours: "How long a visit lasts before we consider it the 'previous' visit, in hours." previous_visit_timeout_hours: "How long a visit lasts before we consider it the 'previous' visit, in hours."
uncategorized_name: "The name for the uncategorized topics on the category list"
max_mentions_per_post: "The maximum amount of @notifications you can add to a post" max_mentions_per_post: "The maximum amount of @notifications you can add to a post"
rate_limit_create_topic: "How many seconds before you can create another topic" rate_limit_create_topic: "How many seconds before you can create another topic"

View file

@ -601,10 +601,6 @@ pt_BR:
active_user_rate_limit_secs: "Qual a frequencia de atualização do campo 'última vez visto em', em segundos." active_user_rate_limit_secs: "Qual a frequencia de atualização do campo 'última vez visto em', em segundos."
previous_visit_timeout_hours: "Quanto tempo uma visita dura antes de considerarmos como 'última visita', em horas." previous_visit_timeout_hours: "Quanto tempo uma visita dura antes de considerarmos como 'última visita', em horas."
uncategorized_name: "Nome para tópicos sem categoria na lista de categorias"
uncategorized_color: "Cor de fundo da etiqueta da categoria que tem tópicos sem nenhuma categoria"
uncategorized_text_color: "Cor do texto da etiqueta da categoria que tem tópicos sem nenhuma categoria"
rate_limit_create_topic: "Quantos segundos você precisa aguardar antes de poder criar um novo tópico" rate_limit_create_topic: "Quantos segundos você precisa aguardar antes de poder criar um novo tópico"
rate_limit_create_post: "Quantos segundos você precisa aguardar antes de poder fazer uma nova postagem" rate_limit_create_post: "Quantos segundos você precisa aguardar antes de poder fazer uma nova postagem"

View file

@ -568,9 +568,6 @@ ru:
allow_import: 'Позволить импорт, который может заменить ВСЕ данные сайта. Оставьте false, если не планируете импортировать данные' allow_import: 'Позволить импорт, который может заменить ВСЕ данные сайта. Оставьте false, если не планируете импортировать данные'
active_user_rate_limit_secs: 'Как часто мы обновляем поле ''last_seen_at'', в секундах' active_user_rate_limit_secs: 'Как часто мы обновляем поле ''last_seen_at'', в секундах'
previous_visit_timeout_hours: 'Как долго должно длиться посещение сайта, чтобы мы посчитали его «предыдущим посещением», в часах' previous_visit_timeout_hours: 'Как долго должно длиться посещение сайта, чтобы мы посчитали его «предыдущим посещением», в часах'
uncategorized_name: 'Категория по умолчанию для тем, которые не отнесены ни к одной категории на странице /categories'
uncategorized_color: 'Фоновый цвет категории с темами без установленной категории'
uncategorized_text_color: 'Цвет текста категории с темами без установленной категории'
rate_limit_create_topic: 'После создания темы пользователи должны выждать указанное количество секунд перед созданием новой темы' rate_limit_create_topic: 'После создания темы пользователи должны выждать указанное количество секунд перед созданием новой темы'
rate_limit_create_post: 'После создания сообщения пользователи должны выждать указанное количество секунд перед созданием нового сообщения' rate_limit_create_post: 'После создания сообщения пользователи должны выждать указанное количество секунд перед созданием нового сообщения'
max_likes_per_day: 'Максимальное количество симпатий, выраженных одним пользователем в день' max_likes_per_day: 'Максимальное количество симпатий, выраженных одним пользователем в день'

View file

@ -428,8 +428,6 @@ sv:
active_user_rate_limit_secs: "How frequently we update the 'last_seen_at' field, in seconds" active_user_rate_limit_secs: "How frequently we update the 'last_seen_at' field, in seconds"
previous_visit_timeout_hours: "How long a visit lasts before we consider it the 'previous' visit, in hours" 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"
rate_limit_create_topic: "How many seconds, after creating a topic, before you can create another topic" 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" rate_limit_create_post: "How many seconds, after creating a post, before you can create another post"

View file

@ -525,8 +525,6 @@ zh_CN:
active_user_rate_limit_secs: "更新“最后一次见到”数据的频率,单位为秒" active_user_rate_limit_secs: "更新“最后一次见到”数据的频率,单位为秒"
previous_visit_timeout_hours: "系统判断一次访问之后多少小时后为“上一次”访问" previous_visit_timeout_hours: "系统判断一次访问之后多少小时后为“上一次”访问"
uncategorized_name: "在分类 /categories 页面,没有分类的主题的缺省分类"
rate_limit_create_topic: "在创建一个主题之后间隔多少秒你才能创建另一个主题" rate_limit_create_topic: "在创建一个主题之后间隔多少秒你才能创建另一个主题"
rate_limit_create_post: "在创建一个帖子之后间隔多少秒你才能创建另一个帖子" rate_limit_create_post: "在创建一个帖子之后间隔多少秒你才能创建另一个帖子"

View file

@ -506,8 +506,6 @@ zh_TW:
active_user_rate_limit_secs: "更新“最後一次見到”數據的頻率,單位爲秒" active_user_rate_limit_secs: "更新“最後一次見到”數據的頻率,單位爲秒"
previous_visit_timeout_hours: "系統判斷一次訪問之後多少小時後爲“上一次”訪問" previous_visit_timeout_hours: "系統判斷一次訪問之後多少小時後爲“上一次”訪問"
uncategorized_name: "在分類 /categories 頁面,沒有分類的主題的缺省分類"
rate_limit_create_topic: "在創建一個主題之後間隔多少秒你才能創建另一個主題" rate_limit_create_topic: "在創建一個主題之後間隔多少秒你才能創建另一個主題"
rate_limit_create_post: "在創建一個帖子之後間隔多少秒你才能創建另一個帖子" rate_limit_create_post: "在創建一個帖子之後間隔多少秒你才能創建另一個帖子"

View file

@ -0,0 +1,35 @@
class AddUncategorizedCategory < ActiveRecord::Migration
def up
result = execute "SELECT 1 FROM categories WHERE name = 'uncategorized'"
if result.count > 0
name << SecureRandom.hex
end
result = execute "INSERT INTO categories
(name,color,slug,description,text_color, user_id, created_at, updated_at, position)
VALUES ('uncategorized', 'AB9364', 'uncategorized', '', 'FFFFFF', -1, now(), now(), 1 )
RETURNING id
"
category_id = result[0]["id"].to_i
execute "INSERT INTO site_settings(name, data_type, value, created_at, updated_at)
VALUES ('uncategorized_category_id', 3, #{category_id}, now(), now())"
execute "DELETE from site_settings where name in ('uncategorized_name', 'uncategorized_text_color', 'uncategorized_color')"
execute "UPDATE topics SET category_id = #{category_id} WHERE archetype = 'regular' AND category_id IS NULL"
execute "ALTER table topics ADD CONSTRAINT has_category_id CHECK (category_id IS NOT NULL OR archetype <> 'regular')"
end
def down
execute "ALTER TABLE topics DROP CONSTRAINT has_category_id"
execute "DELETE from categories WHERE id in (select value::int from site_settings where name = 'uncategorized_category_id')"
execute "DELETE from site_settings where name = 'uncategorized_category_id'"
execute "UPDATE topics SET category_id = null WHERE category_id NOT IN (SELECT id from categories)"
end
end

View file

@ -140,7 +140,6 @@ class PostCreator
end end
def after_topic_create def after_topic_create
# Don't publish invisible topics # Don't publish invisible topics
return unless @topic.visible? return unless @topic.visible?

View file

@ -34,6 +34,10 @@ module SiteSettingExtension
@enums ||= {} @enums ||= {}
end end
def hidden_settings
@hidden_settings ||= []
end
def setting(name, default = nil, opts = {}) def setting(name, default = nil, opts = {})
mutex.synchronize do mutex.synchronize do
self.defaults[name] = default self.defaults[name] = default
@ -42,6 +46,9 @@ module SiteSettingExtension
enum = opts[:enum] enum = opts[:enum]
enums[name] = enum.is_a?(String) ? enum.constantize : enum enums[name] = enum.is_a?(String) ? enum.constantize : enum
end end
if opts[:hidden] == true
hidden_settings << name
end
setup_methods(name, current_value) setup_methods(name, current_value)
end end
end end
@ -76,16 +83,18 @@ module SiteSettingExtension
end end
# Retrieve all settings # Retrieve all settings
def all_settings def all_settings(include_hidden=false)
@defaults.map do |s, v| @defaults
value = send(s) .reject{|s, v| hidden_settings.include?(s) || include_hidden}
type = types[get_data_type(s, value)] .map do |s, v|
{setting: s, value = send(s)
description: description(s), type = types[get_data_type(s, value)]
default: v, {setting: s,
type: type.to_s, description: description(s),
value: value.to_s}.merge( type == :enum ? {valid_values: enum_class(s).values, translate_names: enum_class(s).translate_names?} : {}) default: v,
end type: type.to_s,
value: value.to_s}.merge( type == :enum ? {valid_values: enum_class(s).values, translate_names: enum_class(s).translate_names?} : {})
end
end end
def description(setting) def description(setting)

View file

@ -4,16 +4,20 @@ class SiteSettings::LocalProcessProvider
Setting = Struct.new(:name, :value, :data_type) unless defined? SiteSettings::LocalProcessProvider::Setting Setting = Struct.new(:name, :value, :data_type) unless defined? SiteSettings::LocalProcessProvider::Setting
def initialize def initialize(defaults = {})
@settings = {} @settings = {}
@defaults = {}
defaults.each do |name,(value,data_type)|
@defaults[name] = Setting.new(name,value,data_type)
end
end end
def all def all
@settings.values (@defaults.merge @settings).values
end end
def find(name) def find(name)
@settings[name] @settings[name] || @defaults[name]
end end
def save(name, value, data_type) def save(name, value, data_type)

View file

@ -50,7 +50,7 @@ class TopicQuery
# If you've clearned the pin, use bumped_at, otherwise put it at the top # If you've clearned the pin, use bumped_at, otherwise put it at the top
def order_nocategory_with_pinned_sql def order_nocategory_with_pinned_sql
"CASE "CASE
WHEN topics.category_id IS NULL and (COALESCE(topics.pinned_at, '#{lowest_date}') > COALESCE(tu.cleared_pinned_at, '#{lowest_date}')) WHEN topics.category_id = #{SiteSetting.uncategorized_category_id.to_i} and (COALESCE(topics.pinned_at, '#{lowest_date}') > COALESCE(tu.cleared_pinned_at, '#{lowest_date}'))
THEN '#{highest_date}' THEN '#{highest_date}'
ELSE topics.bumped_at ELSE topics.bumped_at
END DESC" END DESC"
@ -58,7 +58,7 @@ class TopicQuery
# For anonymous users # For anonymous users
def order_nocategory_basic_bumped def order_nocategory_basic_bumped
"CASE WHEN topics.category_id IS NULL and (topics.pinned_at IS NOT NULL) THEN 0 ELSE 1 END, topics.bumped_at DESC" "CASE WHEN topics.category_id = #{SiteSetting.uncategorized_category_id.to_i} and (topics.pinned_at IS NOT NULL) THEN 0 ELSE 1 END, topics.bumped_at DESC"
end end
def order_basic_bumped def order_basic_bumped
@ -152,18 +152,6 @@ class TopicQuery
TopicList.new(:private_messages, user, list) TopicList.new(:private_messages, user, list)
end end
def list_uncategorized
create_list(:uncategorized, unordered: true) do |list|
list = list.where(category_id: nil)
if @user
list.order(TopicQuery.order_with_pinned_sql)
else
list.order(TopicQuery.order_nocategory_basic_bumped)
end
end
end
def list_category(category) def list_category(category)
create_list(:category, unordered: true) do |list| create_list(:category, unordered: true) do |list|
list = list.where(category_id: category.id) list = list.where(category_id: category.id)

View file

@ -6,35 +6,6 @@ describe CategoryList do
let(:user) { Fabricate(:user) } let(:user) { Fabricate(:user) }
let(:category_list) { CategoryList.new(Guardian.new user) } let(:category_list) { CategoryList.new(Guardian.new user) }
context "with no categories" do
it "has no categories" do
category_list.categories.should be_blank
end
context "with an uncategorized topic" do
let!(:topic) { Fabricate(:topic)}
let(:category) { category_list.categories.first }
it "has the right category" do
category.should be_present
category.name.should == SiteSetting.uncategorized_name
category.slug.should == SiteSetting.uncategorized_name
category.topics_week.should == 1
category.featured_topics.should == [topic]
category.displayable_topics.should == [topic] # CategoryDetailedSerializer needs this attribute
end
it 'does not return an invisible topic' do
invisible_topic = Fabricate(:topic)
invisible_topic.update_status('visible', false, Fabricate(:admin))
expect(category.featured_topics).to_not include(invisible_topic)
end
end
end
context "security" do context "security" do
it "properly hide secure categories" do it "properly hide secure categories" do
admin = Fabricate(:admin) admin = Fabricate(:admin)
@ -45,7 +16,9 @@ describe CategoryList do
cat.set_permissions(:admins => :full) cat.set_permissions(:admins => :full)
cat.save cat.save
CategoryList.new(Guardian.new admin).categories.count.should == 1 # uncategorized + this
CategoryList.new(Guardian.new admin).categories.count.should == 2
CategoryList.new(Guardian.new user).categories.count.should == 0 CategoryList.new(Guardian.new user).categories.count.should == 0
CategoryList.new(Guardian.new nil).categories.count.should == 0 CategoryList.new(Guardian.new nil).categories.count.should == 0
end end
@ -75,13 +48,12 @@ describe CategoryList do
it 'returns the empty category and a non-empty category for those who can create them' do it 'returns the empty category and a non-empty category for those who can create them' do
category_with_topics = Fabricate(:topic, category: Fabricate(:category)) category_with_topics = Fabricate(:topic, category: Fabricate(:category))
Guardian.any_instance.expects(:can_create?).with(Category).returns(true) Guardian.any_instance.expects(:can_create?).with(Category).returns(true)
category_list.categories.should have(2).categories category_list.categories.should have(3).categories
category_list.categories.should include(topic_category) category_list.categories.should include(topic_category)
end end
end end
context "with a topic in a category" do context "with a topic in a category" do
let!(:topic) { Fabricate(:topic, category: topic_category)} let!(:topic) { Fabricate(:topic, category: topic_category)}
let(:category) { category_list.categories.first } let(:category) { category_list.categories.first }

View file

@ -687,9 +687,6 @@ describe Guardian do
end end
context 'can_delete?' do context 'can_delete?' do
it 'returns false with a nil object' do it 'returns false with a nil object' do
@ -697,6 +694,10 @@ describe Guardian do
end end
context 'a Topic' do context 'a Topic' do
before do
# pretend we have a real topic
topic.id = 9999999
end
it 'returns false when not logged in' do it 'returns false when not logged in' do
Guardian.new.can_delete?(topic).should be_false Guardian.new.can_delete?(topic).should be_false

View file

@ -49,7 +49,7 @@ describe TopicQuery do
context 'list_latest' do context 'list_latest' do
it "returns the topics in the correct order" do it "returns the topics in the correct order" do
topics.should == [pinned_topic, closed_topic, archived_topic, regular_topic] topics.map(&:title).should == [pinned_topic, closed_topic, archived_topic, regular_topic].map(&:title)
end end
it "includes the invisible topic if you're a moderator" do it "includes the invisible topic if you're a moderator" do
@ -80,10 +80,6 @@ describe TopicQuery do
let!(:topic_no_cat) { Fabricate(:topic) } let!(:topic_no_cat) { Fabricate(:topic) }
let!(:topic_in_cat) { Fabricate(:topic, category: category) } let!(:topic_in_cat) { Fabricate(:topic, category: category) }
it "returns the topic without a category when filtering uncategorized" do
topic_query.list_uncategorized.topics.should == [topic_no_cat]
end
it "returns the topic with a category when filtering by category" do it "returns the topic with a category when filtering by category" do
topic_query.list_category(category).topics.should == [topic_category, topic_in_cat] topic_query.list_category(category).topics.should == [topic_category, topic_in_cat]
end end

View file

@ -7,12 +7,8 @@ describe Trashable do
p1 = Fabricate(:post) p1 = Fabricate(:post)
p2 = Fabricate(:post) p2 = Fabricate(:post)
Post.count.should == 2 expect { p1.trash! }.to change{Post.count}.by(-1)
p1.trash! Post.with_deleted.count.should == Post.count + 1
Post.count.should == 1
Post.with_deleted.count.should == 2
end end
end end

View file

@ -58,7 +58,7 @@ describe CategoriesController do
} }
response.status.should == 200 response.status.should == 200
category = Category.first category = Category.where(name: "hello").first
category.category_groups.map{|g| [g.group_id, g.permission_type]}.sort.should == [ category.category_groups.map{|g| [g.group_id, g.permission_type]}.sort.should == [
[Group[:everyone].id, readonly],[Group[:staff].id,create_post] [Group[:everyone].id, readonly],[Group[:staff].id,create_post]
] ]

View file

@ -27,7 +27,8 @@ describe ListController do
end end
it 'allows users to filter on a set of topic ids' do it 'allows users to filter on a set of topic ids' do
p = Fabricate(:post) p = create_post
xhr :get, :latest, format: :json, topic_ids: "#{p.topic_id}" xhr :get, :latest, format: :json, topic_ids: "#{p.topic_id}"
response.should be_success response.should be_success
parsed = JSON.parse(response.body) parsed = JSON.parse(response.body)
@ -128,38 +129,7 @@ describe ListController do
response.content_type.should == 'application/rss+xml' response.content_type.should == 'application/rss+xml'
end end
end end
end end
context 'uncategorized' do
it "doesn't check access to see the category, since we didn't provide one" do
Guardian.any_instance.expects(:can_see?).never
xhr :get, :category, category: SiteSetting.uncategorized_name
end
it "responds with success" do
xhr :get, :category, category: SiteSetting.uncategorized_name
response.should be_success
end
context 'SiteSetting.uncategorized_name is non standard' do
before do
SiteSetting.stubs(:uncategorized_name).returns('testing')
end
it "responds with success given SiteSetting.uncategorized_name" do
xhr :get, :category, category: SiteSetting.uncategorized_name
response.should be_success
end
it 'responds with success given "uncategorized"' do
xhr :get, :category, category: 'uncategorized'
response.should be_success
end
end
end
end end
describe "topics_by" do describe "topics_by" do

View file

@ -1,6 +1,7 @@
Fabricator(:topic) do Fabricator(:topic) do
user user
title { sequence(:title) { |i| "This is a test topic #{i}" } } title { sequence(:title) { |i| "This is a test topic #{i}" } }
category_id { SiteSetting.uncategorized_category_id }
end end
Fabricator(:deleted_topic, from: :topic) do Fabricator(:deleted_topic, from: :topic) do

View file

@ -31,6 +31,9 @@ describe Category do
describe "topic_create_allowed and post_create_allowed" do describe "topic_create_allowed and post_create_allowed" do
it "works" do it "works" do
# NOTE we also have the uncategorized category ... hence the increased count
default_category = Fabricate(:category) default_category = Fabricate(:category)
full_category = Fabricate(:category) full_category = Fabricate(:category)
can_post_category = Fabricate(:category) can_post_category = Fabricate(:category)
@ -54,20 +57,20 @@ describe Category do
can_read_category.save can_read_category.save
guardian = Guardian.new(admin) guardian = Guardian.new(admin)
Category.topic_create_allowed(guardian).count.should == 4 Category.topic_create_allowed(guardian).count.should == 5
Category.post_create_allowed(guardian).count.should == 4 Category.post_create_allowed(guardian).count.should == 5
Category.secured(guardian).count.should == 4 Category.secured(guardian).count.should == 5
guardian = Guardian.new(user) guardian = Guardian.new(user)
Category.secured(guardian).count.should == 4 Category.secured(guardian).count.should == 5
Category.post_create_allowed(guardian).count.should == 3 Category.post_create_allowed(guardian).count.should == 4
Category.topic_create_allowed(guardian).count.should == 2 # explicitly allowed once, default allowed once Category.topic_create_allowed(guardian).count.should == 3 # explicitly allowed once, default allowed once
# everyone has special semantics, test it as well # everyone has special semantics, test it as well
can_post_category.set_permissions(:everyone => :create_post) can_post_category.set_permissions(:everyone => :create_post)
can_post_category.save can_post_category.save
Category.post_create_allowed(guardian).count.should == 3 Category.post_create_allowed(guardian).count.should == 4
# anonymous has permission to create no topics # anonymous has permission to create no topics
guardian = Guardian.new(nil) guardian = Guardian.new(nil)
@ -105,22 +108,16 @@ describe Category do
end end
it "lists all secured categories correctly" do it "lists all secured categories correctly" do
uncategorized = Category.first
group.add(user) group.add(user)
category.set_permissions(group.id => :full) category.set_permissions(group.id => :full)
category.save category.save
category_2.set_permissions(group.id => :full) category_2.set_permissions(group.id => :full)
category_2.save category_2.save
Category.secured.should =~ [] Category.secured.should =~ [uncategorized]
Category.secured(Guardian.new(user)).should =~ [category, category_2] Category.secured(Guardian.new(user)).should =~ [uncategorized,category, category_2]
end
end
describe "uncategorized name" do
let(:category) { Fabricate.build(:category, name: SiteSetting.uncategorized_name) }
it "is invalid to create a category with the reserved name" do
category.should_not be_valid
end end
end end

View file

@ -113,7 +113,7 @@ describe PostMover do
moved_to.highest_post_number.should == 3 moved_to.highest_post_number.should == 3
moved_to.featured_user1_id.should == another_user.id moved_to.featured_user1_id.should == another_user.id
moved_to.like_count.should == 1 moved_to.like_count.should == 1
moved_to.category.should be_blank moved_to.category_id.should == SiteSetting.uncategorized_category_id
# Posts should be re-ordered # Posts should be re-ordered
p2.reload p2.reload

View file

@ -6,12 +6,16 @@ describe Site do
category = Fabricate(:category) category = Fabricate(:category)
user = Fabricate(:user) user = Fabricate(:user)
Site.new(Guardian.new(user)).categories.count.should == 1 Site.new(Guardian.new(user)).categories.count.should == 2
category.set_permissions(:everyone => :create_post) category.set_permissions(:everyone => :create_post)
category.save category.save
# TODO clean up querying so we can make sure we have the correct permission set Site.new(Guardian.new(user))
Site.new(Guardian.new(user)).categories[0].permission.should_not == CategoryGroup.permission_types[:full] .categories
.keep_if{|c| c.name == category.name}
.first
.permission
.should_not == CategoryGroup.permission_types[:full]
end end
end end

View file

@ -171,8 +171,6 @@ describe Topic do
topic.fancy_title.should == "&ldquo;this topic&rdquo; &ndash; has &ldquo;fancy stuff&rdquo;" topic.fancy_title.should == "&ldquo;this topic&rdquo; &ndash; has &ldquo;fancy stuff&rdquo;"
end end
end end
end end
context 'category validation' do context 'category validation' do
@ -182,7 +180,7 @@ describe Topic do
end end
it "does not allow nil category" do it "does not allow nil category" do
topic = Fabricate(:topic, category: nil) topic = Fabricate.build(:topic, category: nil)
topic.should_not be_valid topic.should_not be_valid
topic.errors[:category_id].should be_present topic.errors[:category_id].should be_present
end end
@ -796,7 +794,7 @@ describe Topic do
describe 'without a previous category' do describe 'without a previous category' do
it 'should not change the topic_count when not changed' do it 'should not change the topic_count when not changed' do
lambda { @topic.change_category(nil); @category.reload }.should_not change(@category, :topic_count) lambda { @topic.change_category(@topic.category.name); @category.reload }.should_not change(@category, :topic_count)
end end
describe 'changed category' do describe 'changed category' do
@ -812,10 +810,9 @@ describe Topic do
end end
it "doesn't change the category when it can't be found" do it "doesn't change the category when it can't be found" do
@topic.change_category('made up') @topic.change_category('made up')
@topic.category.should be_blank @topic.category_id.should == SiteSetting.uncategorized_category_id
end end
end end
@ -876,7 +873,7 @@ describe Topic do
end end
it "resets the category" do it "resets the category" do
@topic.category_id.should be_blank @topic.category_id.should == SiteSetting.uncategorized_category_id
@category.topic_count.should == 0 @category.topic_count.should == 0
end end
end end
@ -955,7 +952,7 @@ describe Topic do
it "ignores the category's default auto-close" do it "ignores the category's default auto-close" do
Timecop.freeze(Time.zone.now) do Timecop.freeze(Time.zone.now) do
Jobs.expects(:enqueue_at).with(7.days.from_now, :close_topic, all_of( has_key(:topic_id), has_key(:user_id) )) Jobs.expects(:enqueue_at).with(7.days.from_now, :close_topic, all_of( has_key(:topic_id), has_key(:user_id) ))
Fabricate(:topic, auto_close_days: 7, user: Fabricate(:admin), category: Fabricate(:category, auto_close_days: 2)) Fabricate(:topic, auto_close_days: 7, user: Fabricate(:admin), category_id: Fabricate(:category, auto_close_days: 2).id)
end end
end end

View file

@ -77,8 +77,13 @@ Spork.prefork do
config.before(:all) do config.before(:all) do
DiscoursePluginRegistry.clear DiscoursePluginRegistry.clear
uncat_id = SiteSetting.uncategorized_category_id
Discourse.current_user_provider = TestCurrentUserProvider Discourse.current_user_provider = TestCurrentUserProvider
# a bit odd, but this setting is actually preloaded
SiteSetting.defaults[:uncategorized_category_id] = SiteSetting.uncategorized_category_id
require_dependency 'site_settings/local_process_provider' require_dependency 'site_settings/local_process_provider'
SiteSetting.provider = SiteSettings::LocalProcessProvider.new SiteSetting.provider = SiteSettings::LocalProcessProvider.new
end end