mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-23 15:48:43 -05:00
FEATURE: new 'categories_and_latest' endpoint
This commit is contained in:
parent
6c8e6e9d2a
commit
e064e6f7a3
10 changed files with 145 additions and 71 deletions
|
@ -2,27 +2,6 @@ import { ajax } from 'discourse/lib/ajax';
|
||||||
import RestModel from 'discourse/models/rest';
|
import RestModel from 'discourse/models/rest';
|
||||||
import Model from 'discourse/models/model';
|
import Model from 'discourse/models/model';
|
||||||
|
|
||||||
function topicsFrom(result, store) {
|
|
||||||
if (!result) { return; }
|
|
||||||
|
|
||||||
// Stitch together our side loaded data
|
|
||||||
const categories = Discourse.Category.list(),
|
|
||||||
users = Model.extractByKey(result.users, Discourse.User);
|
|
||||||
|
|
||||||
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];
|
|
||||||
});
|
|
||||||
if (t.participants) {
|
|
||||||
t.participants.forEach(function(p) {
|
|
||||||
p.user = users[p.user_id];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return store.createRecord('topic', t);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const TopicList = RestModel.extend({
|
const TopicList = RestModel.extend({
|
||||||
canLoadMore: Em.computed.notEmpty("more_topics_url"),
|
canLoadMore: Em.computed.notEmpty("more_topics_url"),
|
||||||
|
|
||||||
|
@ -66,8 +45,8 @@ const TopicList = RestModel.extend({
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
// the new topics loaded from the server
|
// the new topics loaded from the server
|
||||||
const newTopics = topicsFrom(result, store),
|
const newTopics = TopicList.topicsFrom(store, result);
|
||||||
topics = self.get("topics");
|
const topics = self.get("topics");
|
||||||
|
|
||||||
self.forEachNew(newTopics, function(t) {
|
self.forEachNew(newTopics, function(t) {
|
||||||
t.set('highlight', topicsAdded++ === 0);
|
t.set('highlight', topicsAdded++ === 0);
|
||||||
|
@ -103,7 +82,7 @@ const TopicList = RestModel.extend({
|
||||||
|
|
||||||
return ajax({ url }).then(result => {
|
return ajax({ url }).then(result => {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
topicList.forEachNew(topicsFrom(result, store), function(t) {
|
topicList.forEachNew(TopicList.topicsFrom(store, result), function(t) {
|
||||||
// highlight the first of the new topics so we can get a visual feedback
|
// highlight the first of the new topics so we can get a visual feedback
|
||||||
t.set('highlight', true);
|
t.set('highlight', true);
|
||||||
topics.insertAt(i,t);
|
topics.insertAt(i,t);
|
||||||
|
@ -115,6 +94,26 @@ const TopicList = RestModel.extend({
|
||||||
});
|
});
|
||||||
|
|
||||||
TopicList.reopenClass({
|
TopicList.reopenClass({
|
||||||
|
topicsFrom(store, result) {
|
||||||
|
if (!result) { return; }
|
||||||
|
|
||||||
|
// Stitch together our side loaded data
|
||||||
|
const categories = Discourse.Category.list(),
|
||||||
|
users = Model.extractByKey(result.users, Discourse.User);
|
||||||
|
|
||||||
|
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];
|
||||||
|
});
|
||||||
|
if (t.participants) {
|
||||||
|
t.participants.forEach(function(p) {
|
||||||
|
p.user = users[p.user_id];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return store.createRecord('topic', t);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
munge(json, store) {
|
munge(json, store) {
|
||||||
json.inserted = json.inserted || [];
|
json.inserted = json.inserted || [];
|
||||||
|
@ -126,7 +125,7 @@ TopicList.reopenClass({
|
||||||
json.for_period = json.topic_list.for_period;
|
json.for_period = json.topic_list.for_period;
|
||||||
json.loaded = true;
|
json.loaded = true;
|
||||||
json.per_page = json.topic_list.per_page;
|
json.per_page = json.topic_list.per_page;
|
||||||
json.topics = topicsFrom(json, store);
|
json.topics = this.topicsFrom(store, json);
|
||||||
|
|
||||||
return json;
|
return json;
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,6 +3,8 @@ import OpenComposer from "discourse/mixins/open-composer";
|
||||||
import CategoryList from "discourse/models/category-list";
|
import CategoryList from "discourse/models/category-list";
|
||||||
import { defaultHomepage } from 'discourse/lib/utilities';
|
import { defaultHomepage } from 'discourse/lib/utilities';
|
||||||
import TopicList from "discourse/models/topic-list";
|
import TopicList from "discourse/models/topic-list";
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import PreloadStore from "preload-store";
|
||||||
|
|
||||||
const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, {
|
const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, {
|
||||||
renderTemplate() {
|
renderTemplate() {
|
||||||
|
@ -15,31 +17,66 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, {
|
||||||
},
|
},
|
||||||
|
|
||||||
model() {
|
model() {
|
||||||
return CategoryList.list(this.store, 'categories').then(list => {
|
const style = this.siteSettings.desktop_category_page_style;
|
||||||
|
const parentCategory = this.get("model.parentCategory");
|
||||||
|
|
||||||
|
let promise;
|
||||||
|
if (parentCategory) {
|
||||||
|
promise = CategoryList.listForParent(this.store, parentCategory);
|
||||||
|
} else if (style === "categories_and_latest_topics") {
|
||||||
|
promise = this._loadCategoriesAndLatestTopics();
|
||||||
|
} else {
|
||||||
|
promise = CategoryList.list(this.store);
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise.then(model => {
|
||||||
const tracking = this.topicTrackingState;
|
const tracking = this.topicTrackingState;
|
||||||
if (tracking) {
|
if (tracking) {
|
||||||
tracking.sync(list, "categories");
|
tracking.sync(model, "categories");
|
||||||
tracking.trackIncoming("categories");
|
tracking.trackIncoming("categories");
|
||||||
}
|
}
|
||||||
return list;
|
return model;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_loadCategoriesAndLatestTopics() {
|
||||||
|
const categoriesList = PreloadStore.get("categories_list");
|
||||||
|
const topicListLatest = PreloadStore.get("topic_list_latest");
|
||||||
|
if (categoriesList && topicListLatest) {
|
||||||
|
return new Ember.RSVP.Promise(resolve => {
|
||||||
|
const result = Ember.Object.create({
|
||||||
|
categories: CategoryList.categoriesFrom(this.store, categoriesList),
|
||||||
|
topics: TopicList.topicsFrom(this.store, topicListLatest),
|
||||||
|
can_create_category: categoriesList.can_create_category,
|
||||||
|
can_create_topic: categoriesList.can_create_topic,
|
||||||
|
draft_key: categoriesList.draft_key,
|
||||||
|
draft: categoriesList.draft,
|
||||||
|
draft_sequence: categoriesList.draft_sequence
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return ajax("/categories_and_latest").then(result => {
|
||||||
|
return Ember.Object.create({
|
||||||
|
categories: CategoryList.categoriesFrom(this.store, result),
|
||||||
|
topics: TopicList.topicsFrom(this.store, result),
|
||||||
|
can_create_category: result.category_list.can_create_category,
|
||||||
|
can_create_topic: result.category_list.can_create_topic,
|
||||||
|
draft_key: result.category_list.draft_key,
|
||||||
|
draft: result.category_list.draft,
|
||||||
|
draft_sequence: result.category_list.draft_sequence
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
titleToken() {
|
titleToken() {
|
||||||
if (defaultHomepage() === "categories") { return; }
|
if (defaultHomepage() === "categories") { return; }
|
||||||
return I18n.t("filters.categories.title");
|
return I18n.t("filters.categories.title");
|
||||||
},
|
},
|
||||||
|
|
||||||
setupController(controller, model) {
|
setupController(controller, model) {
|
||||||
const style = this.siteSettings.desktop_category_page_style;
|
|
||||||
if (style === "categories_and_latest_topics" && !this.get("model.parentCategory")) {
|
|
||||||
model.set("loadingTopics", true);
|
|
||||||
|
|
||||||
TopicList.find("latest")
|
|
||||||
.then(result => model.set("topicList", result))
|
|
||||||
.finally(() => model.set("loadingTopics", false));
|
|
||||||
}
|
|
||||||
|
|
||||||
controller.set("model", model);
|
controller.set("model", model);
|
||||||
|
|
||||||
this.controllerFor("navigation/categories").setProperties({
|
this.controllerFor("navigation/categories").setProperties({
|
||||||
|
@ -63,12 +100,8 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, {
|
||||||
// Lesson learned: Don't call `loading` yourself.
|
// Lesson learned: Don't call `loading` yourself.
|
||||||
controller.set("loading", true);
|
controller.set("loading", true);
|
||||||
|
|
||||||
const parentCategory = this.get("model.parentCategory");
|
this.model().then(model => {
|
||||||
const promise = parentCategory ? CategoryList.listForParent(this.store, parentCategory) :
|
this.setupController(controller, model);
|
||||||
CategoryList.list(this.store);
|
|
||||||
|
|
||||||
promise.then(list => {
|
|
||||||
this.setupController(controller, list);
|
|
||||||
controller.send("loadingComplete");
|
controller.send("loadingComplete");
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,25 +7,21 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{#if loadingTopics}}
|
{{#if topics}}
|
||||||
{{loading-spinner}}
|
{{#each topics as |t|}}
|
||||||
|
{{latest-topic-list-item topic=t}}
|
||||||
|
{{/each}}
|
||||||
|
<tr class="more-topics">
|
||||||
|
<td>
|
||||||
|
<a href="/latest" class="btn pull-right">{{i18n "more"}}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#if topics}}
|
<tr class="no-topics">
|
||||||
{{#each topics as |t|}}
|
<td>
|
||||||
{{latest-topic-list-item topic=t}}
|
<h3>{{i18n "topics.none.latest"}}</h3>
|
||||||
{{/each}}
|
</td>
|
||||||
<tr class="more-topics">
|
</tr>
|
||||||
<td>
|
|
||||||
<a href="/latest" class="btn pull-right">{{i18n "more"}}</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{else}}
|
|
||||||
<tr class="no-topics">
|
|
||||||
<td>
|
|
||||||
<h3>{{i18n "topics.none.latest"}}</h3>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -2,6 +2,5 @@
|
||||||
{{component controller.categoryPageStyle
|
{{component controller.categoryPageStyle
|
||||||
categories=model.categories
|
categories=model.categories
|
||||||
latestTopicOnly=controller.latestTopicOnly
|
latestTopicOnly=controller.latestTopicOnly
|
||||||
topics=model.topicList.topics
|
topics=model.topics}}
|
||||||
loadingTopics=model.loadingTopics}}
|
|
||||||
{{/discovery-categories}}
|
{{/discovery-categories}}
|
||||||
|
|
|
@ -2,10 +2,10 @@ require_dependency 'category_serializer'
|
||||||
|
|
||||||
class CategoriesController < ApplicationController
|
class CategoriesController < ApplicationController
|
||||||
|
|
||||||
before_filter :ensure_logged_in, except: [:index, :show, :redirect, :find_by_slug]
|
before_filter :ensure_logged_in, except: [:index, :categories_and_latest, :show, :redirect, :find_by_slug]
|
||||||
before_filter :fetch_category, only: [:show, :update, :destroy]
|
before_filter :fetch_category, only: [:show, :update, :destroy]
|
||||||
before_filter :initialize_staff_action_logger, only: [:create, :update, :destroy]
|
before_filter :initialize_staff_action_logger, only: [:create, :update, :destroy]
|
||||||
skip_before_filter :check_xhr, only: [:index, :redirect]
|
skip_before_filter :check_xhr, only: [:index, :categories_and_latest, :redirect]
|
||||||
|
|
||||||
def redirect
|
def redirect
|
||||||
redirect_to path("/c/#{params[:path]}")
|
redirect_to path("/c/#{params[:path]}")
|
||||||
|
@ -16,10 +16,6 @@ class CategoriesController < ApplicationController
|
||||||
|
|
||||||
@description = SiteSetting.site_description
|
@description = SiteSetting.site_description
|
||||||
|
|
||||||
include_topics = view_context.mobile_view? ||
|
|
||||||
params[:include_topics] ||
|
|
||||||
SiteSetting.desktop_category_page_style == "categories_with_featured_topics".freeze
|
|
||||||
|
|
||||||
category_options = {
|
category_options = {
|
||||||
is_homepage: current_homepage == "categories".freeze,
|
is_homepage: current_homepage == "categories".freeze,
|
||||||
parent_category_id: params[:parent_category_id],
|
parent_category_id: params[:parent_category_id],
|
||||||
|
@ -29,7 +25,7 @@ class CategoriesController < ApplicationController
|
||||||
@category_list = CategoryList.new(guardian, category_options)
|
@category_list = CategoryList.new(guardian, category_options)
|
||||||
@category_list.draft_key = Draft::NEW_TOPIC
|
@category_list.draft_key = Draft::NEW_TOPIC
|
||||||
@category_list.draft_sequence = DraftSequence.current(current_user, Draft::NEW_TOPIC)
|
@category_list.draft_sequence = DraftSequence.current(current_user, Draft::NEW_TOPIC)
|
||||||
@category_list.draft = Draft.get(current_user, @category_list.draft_key, @category_list.draft_sequence) if current_user
|
@category_list.draft = Draft.get(current_user, Draft::NEW_TOPIC, @category_list.draft_sequence) if current_user
|
||||||
|
|
||||||
@title = I18n.t('js.filters.categories.title') unless category_options[:is_homepage]
|
@title = I18n.t('js.filters.categories.title') unless category_options[:is_homepage]
|
||||||
|
|
||||||
|
@ -50,6 +46,37 @@ class CategoriesController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def categories_and_latest
|
||||||
|
discourse_expires_in 1.minute
|
||||||
|
|
||||||
|
category_options = {
|
||||||
|
is_homepage: current_homepage == "categories".freeze,
|
||||||
|
parent_category_id: params[:parent_category_id],
|
||||||
|
include_topics: false
|
||||||
|
}
|
||||||
|
|
||||||
|
topic_options = {
|
||||||
|
per_page: SiteSetting.categories_topics,
|
||||||
|
no_definitions: true
|
||||||
|
}
|
||||||
|
|
||||||
|
result = CategoryAndTopicLists.new
|
||||||
|
result.category_list = CategoryList.new(guardian, category_options)
|
||||||
|
result.topic_list = TopicQuery.new(current_user, topic_options).list_latest
|
||||||
|
|
||||||
|
draft_key = Draft::NEW_TOPIC
|
||||||
|
draft_sequence = DraftSequence.current(current_user, draft_key)
|
||||||
|
draft = Draft.get(current_user, draft_key, draft_sequence) if current_user
|
||||||
|
|
||||||
|
%w{category topic}.each do |type|
|
||||||
|
result.send(:"#{type}_list").draft = draft
|
||||||
|
result.send(:"#{type}_list").draft_key = draft_key
|
||||||
|
result.send(:"#{type}_list").draft_sequence = draft_sequence
|
||||||
|
end
|
||||||
|
|
||||||
|
render_serialized(result, CategoryAndTopicListsSerializer, root: false)
|
||||||
|
end
|
||||||
|
|
||||||
def move
|
def move
|
||||||
guardian.ensure_can_create_category!
|
guardian.ensure_can_create_category!
|
||||||
|
|
||||||
|
@ -225,4 +252,10 @@ class CategoriesController < ApplicationController
|
||||||
def initialize_staff_action_logger
|
def initialize_staff_action_logger
|
||||||
@staff_action_logger = StaffActionLogger.new(current_user)
|
@staff_action_logger = StaffActionLogger.new(current_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def include_topics
|
||||||
|
view_context.mobile_view? ||
|
||||||
|
params[:include_topics] ||
|
||||||
|
SiteSetting.desktop_category_page_style == "categories_with_featured_topics".freeze
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
5
app/models/category_and_topic_lists.rb
Normal file
5
app/models/category_and_topic_lists.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
class CategoryAndTopicLists
|
||||||
|
include ActiveModel::Serialization
|
||||||
|
|
||||||
|
attr_accessor :category_list, :topic_list
|
||||||
|
end
|
4
app/serializers/category_and_topic_lists_serializer.rb
Normal file
4
app/serializers/category_and_topic_lists_serializer.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
class CategoryAndTopicListsSerializer < ApplicationSerializer
|
||||||
|
has_one :category_list, serializer: CategoryListSerializer, embed: :objects
|
||||||
|
has_one :topic_list, serializer: TopicListSerializer, embed: :objects
|
||||||
|
end
|
|
@ -454,6 +454,8 @@ Discourse::Application.routes.draw do
|
||||||
post "category/:category_id/notifications" => "categories#set_notifications"
|
post "category/:category_id/notifications" => "categories#set_notifications"
|
||||||
put "category/:category_id/slug" => "categories#update_slug"
|
put "category/:category_id/slug" => "categories#update_slug"
|
||||||
|
|
||||||
|
get "categories_and_latest" => "categories#categories_and_latest"
|
||||||
|
|
||||||
get "c/:id/show" => "categories#show"
|
get "c/:id/show" => "categories#show"
|
||||||
get "c/:category_slug/find_by_slug" => "categories#find_by_slug"
|
get "c/:category_slug/find_by_slug" => "categories#find_by_slug"
|
||||||
get "c/:parent_category_slug/:category_slug/find_by_slug" => "categories#find_by_slug"
|
get "c/:parent_category_slug/:category_slug/find_by_slug" => "categories#find_by_slug"
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -133,6 +133,8 @@ export default function() {
|
||||||
return response({ valid: [{ slug: "bug", url: '/c/bugs' }] });
|
return response({ valid: [{ slug: "bug", url: '/c/bugs' }] });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.get("/categories_and_latest", () => response(fixturesByUrl["/categories_and_latest.json"]));
|
||||||
|
|
||||||
this.put('/categories/:category_id', request => {
|
this.put('/categories/:category_id', request => {
|
||||||
|
|
||||||
const category = parsePostData(request.requestBody);
|
const category = parsePostData(request.requestBody);
|
||||||
|
|
Loading…
Reference in a new issue