Routes and support for sub-categories
This commit is contained in:
parent
49a11e51df
commit
541620c115
|
@ -33,22 +33,26 @@ Discourse.Utilities = {
|
|||
}
|
||||
},
|
||||
|
||||
// Create a badge like category link
|
||||
/**
|
||||
Create a badge-like category link
|
||||
|
||||
@method categoryLink
|
||||
@param {Discourse.Category} category the category whose link we want
|
||||
@returns {String} the html category badge
|
||||
**/
|
||||
categoryLink: function(category) {
|
||||
if (!category) return "";
|
||||
|
||||
var color = Em.get(category, 'color');
|
||||
var textColor = Em.get(category, 'text_color');
|
||||
var name = Em.get(category, 'name');
|
||||
var description = Em.get(category, 'description');
|
||||
|
||||
// Build the HTML link
|
||||
var result = "<a href=\"" + Discourse.getURL("/category/") + Discourse.Category.slugFor(category) + "\" class=\"badge-category\" ";
|
||||
var color = Em.get(category, 'color'),
|
||||
textColor = Em.get(category, 'text_color'),
|
||||
name = Em.get(category, 'name'),
|
||||
description = Em.get(category, 'description'),
|
||||
html = "<a href=\"" + Discourse.getURL("/category/") + Discourse.Category.slugFor(category) + "\" class=\"badge-category\" ";
|
||||
|
||||
// Add description if we have it
|
||||
if (description) result += "title=\"" + Handlebars.Utils.escapeExpression(description) + "\" ";
|
||||
if (description) html += "title=\"" + Handlebars.Utils.escapeExpression(description) + "\" ";
|
||||
|
||||
return result + "style=\"background-color: #" + color + "; color: #" + textColor + ";\">" + name + "</a>";
|
||||
return html + "style=\"background-color: #" + color + "; color: #" + textColor + ";\">" + name + "</a>";
|
||||
},
|
||||
|
||||
avatarUrl: function(template, size) {
|
||||
|
|
|
@ -132,18 +132,56 @@ Discourse.Category.reopenClass({
|
|||
|
||||
slugFor: function(category) {
|
||||
if (!category) return "";
|
||||
var id = Em.get(category, 'id');
|
||||
var slug = Em.get(category, 'slug');
|
||||
if (!slug || slug.trim().length === 0) return "" + id + "-category";
|
||||
return slug;
|
||||
|
||||
var parentCategory = Em.get(category, 'parentCategory'),
|
||||
result = "";
|
||||
|
||||
if (parentCategory) {
|
||||
result = Discourse.Category.slugFor(parentCategory) + "/";
|
||||
}
|
||||
|
||||
var id = Em.get(category, 'id'),
|
||||
slug = Em.get(category, 'slug');
|
||||
|
||||
if (!slug || slug.trim().length === 0) return result + id + "-category";
|
||||
return result + slug;
|
||||
},
|
||||
|
||||
list: function() {
|
||||
return Discourse.Site.currentProp('categories');
|
||||
},
|
||||
|
||||
findBySlugOrId: function(slugOrId) {
|
||||
// TODO: all our routing around categories need a rethink
|
||||
findBySlug: function(slug, parentSlug) {
|
||||
|
||||
var uncategorized = Discourse.Category.uncategorizedInstance();
|
||||
if (slug === uncategorized.get('slug')) return uncategorized;
|
||||
|
||||
var categories = Discourse.Category.list(),
|
||||
category;
|
||||
|
||||
if (parentSlug) {
|
||||
var parentCategory = categories.findBy('slug', parentSlug);
|
||||
if (parentCategory) {
|
||||
category = categories.find(function(item) {
|
||||
return item && item.get('parentCategory') === parentCategory && item.get('slug') === slug;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
category = categories.findBy('slug', slug);
|
||||
|
||||
// If we have a parent category, we need to enforce it
|
||||
if (category.get('parentCategory')) return;
|
||||
}
|
||||
|
||||
// In case the slug didn't work, try to find it by id instead.
|
||||
if (!category) {
|
||||
category = categories.findBy('id', parseInt(slug, 10));
|
||||
}
|
||||
|
||||
return category;
|
||||
},
|
||||
|
||||
reloadBySlugOrId: function(slugOrId) {
|
||||
return Discourse.ajax("/category/" + slugOrId + "/show.json").then(function (result) {
|
||||
return Discourse.Category.create(result.category);
|
||||
});
|
||||
|
|
|
@ -24,18 +24,23 @@ Discourse.CategoryList = Ember.ArrayProxy.extend({
|
|||
Discourse.CategoryList.reopenClass({
|
||||
|
||||
categoriesFrom: function(result) {
|
||||
var categories = Discourse.CategoryList.create();
|
||||
var users = Discourse.Model.extractByKey(result.featured_users, Discourse.User);
|
||||
var categories = Discourse.CategoryList.create(),
|
||||
users = Discourse.Model.extractByKey(result.featured_users, Discourse.User),
|
||||
list = Discourse.Category.list();
|
||||
|
||||
result.category_list.categories.forEach(function(c) {
|
||||
|
||||
if (c.parent_category_id) {
|
||||
c.parentCategory = list.findBy('id', c.parent_category_id);
|
||||
}
|
||||
|
||||
_.each(result.category_list.categories,function(c) {
|
||||
if (c.featured_user_ids) {
|
||||
c.featured_users = _.map(c.featured_user_ids,function(u) {
|
||||
c.featured_users = c.featured_user_ids.map(function(u) {
|
||||
return users[u];
|
||||
});
|
||||
}
|
||||
if (c.topics) {
|
||||
c.topics = _.map(c.topics,function(t) {
|
||||
c.topics = c.topics.map(function(t) {
|
||||
return Discourse.Topic.create(t);
|
||||
});
|
||||
}
|
||||
|
@ -58,8 +63,9 @@ Discourse.CategoryList.reopenClass({
|
|||
},
|
||||
|
||||
list: function(filter) {
|
||||
var self = this;
|
||||
var finder = null;
|
||||
var self = this,
|
||||
finder = null;
|
||||
|
||||
if (filter === 'categories') {
|
||||
finder = PreloadStore.getAndRemove("categories_list", function() {
|
||||
return Discourse.ajax("/categories.json");
|
||||
|
|
|
@ -48,7 +48,7 @@ Discourse.Site.reopenClass(Discourse.Singleton, {
|
|||
var result = this._super(obj);
|
||||
|
||||
if (result.categories) {
|
||||
var byId = {}
|
||||
var byId = {};
|
||||
result.categories = _.map(result.categories, function(c) {
|
||||
byId[c.id] = Discourse.Category.create(c);
|
||||
return byId[c.id];
|
||||
|
@ -59,7 +59,7 @@ Discourse.Site.reopenClass(Discourse.Singleton, {
|
|||
if (c.get('parent_category_id')) {
|
||||
c.set('parentCategory', byId[c.get('parent_category_id')]);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (result.trust_levels) {
|
||||
|
|
|
@ -83,9 +83,9 @@ Discourse.TopicList = Discourse.Model.extend({
|
|||
Discourse.TopicList.reopenClass({
|
||||
|
||||
loadTopics: function(topic_ids, filter) {
|
||||
var defer = new Ember.Deferred();
|
||||
var defer = new Ember.Deferred(),
|
||||
url = Discourse.getURL("/") + filter + "?topic_ids=" + topic_ids.join(",");
|
||||
|
||||
var url = Discourse.getURL("/") + filter + "?topic_ids=" + topic_ids.join(",");
|
||||
Discourse.ajax({url: url}).then(function (result) {
|
||||
if (result) {
|
||||
// the new topics loaded from the server
|
||||
|
@ -107,37 +107,45 @@ Discourse.TopicList.reopenClass({
|
|||
return defer;
|
||||
},
|
||||
|
||||
/**
|
||||
Stitch together side loaded topic data
|
||||
|
||||
@method topicsFrom
|
||||
@param {Object} JSON object with topic data
|
||||
@returns {Array} the list of topics
|
||||
**/
|
||||
topicsFrom: function(result) {
|
||||
// Stitch together our side loaded data
|
||||
var categories, topics, users;
|
||||
categories = this.extractByKey(result.categories, Discourse.Category);
|
||||
users = this.extractByKey(result.users, Discourse.User);
|
||||
topics = Em.A();
|
||||
var categories = Discourse.Category.list(),
|
||||
users = this.extractByKey(result.users, Discourse.User),
|
||||
topics = Em.A();
|
||||
|
||||
_.each(result.topic_list.topics,function(ft) {
|
||||
ft.category = categories[ft.category_id];
|
||||
_.each(ft.posters,function(p) {
|
||||
return result.topic_list.topics.map(function (t) {
|
||||
t.category = categories.findBy('id', t.category_id);
|
||||
t.posters.forEach(function(p) {
|
||||
p.user = users[p.user_id];
|
||||
});
|
||||
topics.pushObject(Discourse.Topic.create(ft));
|
||||
return Discourse.Topic.create(t);
|
||||
});
|
||||
return topics;
|
||||
},
|
||||
|
||||
/**
|
||||
Lists topics on a given menu item
|
||||
|
||||
@method list
|
||||
@param {Object} The menu item to filter to
|
||||
@returns {Promise} a promise that resolves to the list of topics
|
||||
**/
|
||||
list: function(menuItem) {
|
||||
var filter = menuItem.get('name');
|
||||
var filter = menuItem.get('name'),
|
||||
session = Discourse.Session.current(),
|
||||
list = session.get('topicList');
|
||||
|
||||
var session = Discourse.Session.current();
|
||||
var list = session.get('topicList');
|
||||
if (list) {
|
||||
if ((list.get('filter') === filter) && window.location.pathname.indexOf('more') > 0) {
|
||||
list.set('loaded', true);
|
||||
return Ember.RSVP.resolve(list);
|
||||
}
|
||||
if (list && (list.get('filter') === filter) && window.location.pathname.indexOf('more') > 0) {
|
||||
list.set('loaded', true);
|
||||
return Ember.RSVP.resolve(list);
|
||||
}
|
||||
|
||||
session.set('topicList', null);
|
||||
session.set('topicListScrollPos', null);
|
||||
session.setProperties({topicList: null, topicListScrollPos: null});
|
||||
|
||||
return Discourse.TopicList.find(filter, menuItem.get('excludeCategory'));
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ Discourse.ApplicationRoute = Em.Route.extend({
|
|||
Discourse.Route.showModal(router, 'editCategory', category);
|
||||
router.controllerFor('editCategory').set('selectedTab', 'general');
|
||||
} else {
|
||||
Discourse.Category.findBySlugOrId(category.get('slug') || category.get('id')).then(function (c) {
|
||||
Discourse.Category.reloadBySlugOrId(category.get('slug') || category.get('id')).then(function (c) {
|
||||
Discourse.Site.current().updateCategory(c);
|
||||
Discourse.Route.showModal(router, 'editCategory', c);
|
||||
router.controllerFor('editCategory').set('selectedTab', 'general');
|
||||
|
|
|
@ -29,13 +29,14 @@ Discourse.Route.buildRoutes(function() {
|
|||
});
|
||||
|
||||
// the homepage is the first item of the 'top_menu' site setting
|
||||
var settings = Discourse.SiteSettings;
|
||||
var homepage = settings.top_menu.split("|")[0].split(",")[0];
|
||||
var homepage = Discourse.SiteSettings.top_menu.split("|")[0].split(",")[0];
|
||||
this.route(homepage, { path: '/' });
|
||||
|
||||
this.route('categories', { path: '/categories' });
|
||||
this.route('category', { path: '/category/:slug/more' });
|
||||
this.route('category', { path: '/category/:slug' });
|
||||
this.route('category', { path: '/category/:slug/more' });
|
||||
this.route('category', { path: '/category/:parentSlug/:slug' });
|
||||
this.route('category', { path: '/category/:parentSlug/:slug/more' });
|
||||
});
|
||||
|
||||
// User routes
|
||||
|
|
|
@ -9,21 +9,7 @@
|
|||
Discourse.ListCategoryRoute = Discourse.FilteredListRoute.extend({
|
||||
|
||||
model: function(params) {
|
||||
var categories = Discourse.Category.list();
|
||||
|
||||
var slug = Em.get(params, 'slug');
|
||||
|
||||
var uncategorized = Discourse.Category.uncategorizedInstance();
|
||||
if (slug === uncategorized.get('slug')) return uncategorized;
|
||||
|
||||
var category = categories.findProperty('slug', Em.get(params, 'slug'));
|
||||
|
||||
// In case the slug didn't work, try to find it by id instead.
|
||||
if (!category) {
|
||||
category = categories.findProperty('id', parseInt(slug, 10));
|
||||
}
|
||||
|
||||
return category;
|
||||
return Discourse.Category.findBySlug(Em.get(params, 'slug'), Em.get(params, 'parentSlug'));
|
||||
},
|
||||
|
||||
setupController: function(controller, category) {
|
||||
|
@ -35,16 +21,18 @@ Discourse.ListCategoryRoute = Discourse.FilteredListRoute.extend({
|
|||
}
|
||||
}
|
||||
|
||||
var listController = this.controllerFor('list');
|
||||
var urlId = Discourse.Category.slugFor(category);
|
||||
listController.set('filterMode', "category/" + urlId);
|
||||
var listController = this.controllerFor('list'),
|
||||
urlId = Discourse.Category.slugFor(category),
|
||||
self = this;
|
||||
|
||||
var router = this;
|
||||
listController.set('filterMode', "category/" + urlId);
|
||||
listController.load("category/" + urlId).then(function(topicList) {
|
||||
listController.set('canCreateTopic', topicList.get('can_create_topic'));
|
||||
listController.set('category', category);
|
||||
router.controllerFor('listTopics').set('content', topicList);
|
||||
router.controllerFor('listTopics').set('category', category);
|
||||
listController.setProperties({
|
||||
canCreateTopic: topicList.get('can_create_topic'),
|
||||
category: category
|
||||
});
|
||||
self.controllerFor('listTopics').set('content', topicList);
|
||||
self.controllerFor('listTopics').set('category', category);
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -62,7 +62,12 @@ class ListController < ApplicationController
|
|||
@description = @category.description
|
||||
end
|
||||
|
||||
list.more_topics_url = url_for(category_list_path(params[:category], page: next_page, format: "json"))
|
||||
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"))
|
||||
else
|
||||
list.more_topics_url = url_for(category_list_path(params[:category], page: next_page, format: "json"))
|
||||
end
|
||||
|
||||
respond(list)
|
||||
end
|
||||
|
||||
|
@ -118,7 +123,18 @@ class ListController < ApplicationController
|
|||
|
||||
def set_category
|
||||
slug = params.fetch(:category)
|
||||
@category = Category.where("slug = ?", slug).includes(:featured_users).first || Category.where("id = ?", slug.to_i).includes(:featured_users).first
|
||||
parent_slug = params[:parent_category]
|
||||
|
||||
parent_category_id = nil
|
||||
if parent_slug.present?
|
||||
parent_category_id = Category.where(slug: parent_slug).pluck(:id).first ||
|
||||
Category.where(id: parent_slug.to_i).pluck(:id).first
|
||||
|
||||
raise Discourse::NotFound.new if parent_category_id.blank?
|
||||
end
|
||||
|
||||
@category = Category.where(slug: slug, 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
|
||||
|
||||
def request_is_for_uncategorized?
|
||||
|
|
|
@ -13,4 +13,8 @@ class BasicCategorySerializer < ApplicationSerializer
|
|||
:permission,
|
||||
:parent_category_id
|
||||
|
||||
def include_parent_category_id?
|
||||
parent_category_id
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
class CategoryDetailedSerializer < ApplicationSerializer
|
||||
class CategoryDetailedSerializer < BasicCategorySerializer
|
||||
|
||||
attributes :id,
|
||||
:name,
|
||||
:color,
|
||||
:text_color,
|
||||
:slug,
|
||||
:topic_count,
|
||||
:post_count,
|
||||
attributes :post_count,
|
||||
:topics_week,
|
||||
:topics_month,
|
||||
:topics_year,
|
||||
:description,
|
||||
:description_excerpt,
|
||||
:is_uncategorized
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@ class TopicListItemSerializer < ListableTopicSerializer
|
|||
:has_best_of,
|
||||
:archetype,
|
||||
:rank_details,
|
||||
:last_poster_username
|
||||
:last_poster_username,
|
||||
:category_id
|
||||
|
||||
has_one :category, serializer: BasicCategorySerializer
|
||||
has_many :posters, serializer: TopicPosterSerializer, embed: :objects
|
||||
|
||||
def starred
|
||||
|
|
|
@ -196,6 +196,7 @@ Discourse::Application.routes.draw do
|
|||
|
||||
get 'category/:category.rss' => 'list#category_feed', format: :rss, as: 'category_feed'
|
||||
get 'category/:category' => 'list#category', as: 'category_list'
|
||||
get 'category/:parent_category/:category' => 'list#category', as: 'category_list_parent'
|
||||
get 'category/:category/more' => 'list#category', as: 'category_list_more'
|
||||
|
||||
# We've renamed popular to latest. If people access it we want a permanent redirect.
|
||||
|
|
|
@ -92,6 +92,35 @@ describe ListController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'a child category' do
|
||||
let(:sub_category) { Fabricate(:category, parent_category_id: category.id) }
|
||||
|
||||
context 'when parent and child are requested' do
|
||||
before do
|
||||
xhr :get, :category, parent_category: category.slug, category: sub_category.slug
|
||||
end
|
||||
|
||||
it { should respond_with(:success) }
|
||||
end
|
||||
|
||||
context 'when child is requested with the wrong parent' do
|
||||
before do
|
||||
xhr :get, :category, parent_category: 'not_the_right_slug', category: sub_category.slug
|
||||
end
|
||||
|
||||
it { should_not respond_with(:success) }
|
||||
end
|
||||
|
||||
context 'when child is requested without a parent' do
|
||||
before do
|
||||
xhr :get, :category, category: sub_category.slug
|
||||
end
|
||||
|
||||
it { should_not respond_with(:success) }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'feed' do
|
||||
it 'renders RSS' do
|
||||
get :category_feed, category: category.slug, format: :rss
|
||||
|
|
|
@ -2,13 +2,39 @@ module("Discourse.Category");
|
|||
|
||||
test('slugFor', function(){
|
||||
|
||||
var slugFor = function(args, val, text) {
|
||||
equal(Discourse.Category.slugFor(args), val, text);
|
||||
var slugFor = function(cat, val, text) {
|
||||
equal(Discourse.Category.slugFor(cat), val, text);
|
||||
};
|
||||
|
||||
slugFor({slug: 'hello'}, "hello", "It calculates the proper slug for hello");
|
||||
slugFor({id: 123, slug: ''}, "123-category", "It returns id-category for empty strings");
|
||||
slugFor({id: 456}, "456-category", "It returns id-category for undefined slugs");
|
||||
slugFor(Discourse.Category.create({slug: 'hello'}), "hello", "It calculates the proper slug for hello");
|
||||
slugFor(Discourse.Category.create({id: 123, slug: ''}), "123-category", "It returns id-category for empty strings");
|
||||
slugFor(Discourse.Category.create({id: 456}), "456-category", "It returns id-category for undefined slugs");
|
||||
|
||||
var parentCategory = Discourse.Category.create({id: 345, slug: 'darth'});
|
||||
slugFor(Discourse.Category.create({slug: 'luke', parentCategory: parentCategory}),
|
||||
"darth/luke",
|
||||
"it uses the parent slug before the child");
|
||||
|
||||
slugFor(Discourse.Category.create({id: 555, parentCategory: parentCategory}),
|
||||
"darth/555-category",
|
||||
"it uses the parent slug before the child and then uses id");
|
||||
|
||||
parentCategory.set('slug', null);
|
||||
slugFor(Discourse.Category.create({id: 555, parentCategory: parentCategory}),
|
||||
"345-category/555-category",
|
||||
"it uses the parent before the child and uses ids for both");
|
||||
});
|
||||
|
||||
|
||||
test('findBySlug', function() {
|
||||
var darth = Discourse.Category.create({id: 1, slug: 'darth'}),
|
||||
luke = Discourse.Category.create({id: 2, slug: 'luke', parentCategory: darth}),
|
||||
categoryList = [darth, luke];
|
||||
|
||||
this.stub(Discourse.Category, 'list').returns(categoryList);
|
||||
|
||||
equal(Discourse.Category.findBySlug('darth'), darth, 'we can find a parent category');
|
||||
equal(Discourse.Category.findBySlug('luke', 'darth'), luke, 'we can find a child with parent');
|
||||
blank(Discourse.Category.findBySlug('luke'), 'luke is blank without the parent');
|
||||
blank(Discourse.Category.findBySlug('luke', 'leia'), 'luke is blank with an incorrect parent');
|
||||
});
|
Loading…
Reference in New Issue