583 lines
15 KiB
JavaScript
583 lines
15 KiB
JavaScript
/**
|
|
* blog_router.js Holds the router for Blogs. It gets the ball rolling once the page is loaded.
|
|
*
|
|
*/
|
|
|
|
AoPS.Blog = (function (Blog) {
|
|
var Lang = AoPS.Community.Lang,
|
|
Constants = AoPS.Community.Constants;
|
|
|
|
Blog.Router = AoPS.Community.Utils.routerBase.extend({
|
|
routes: {
|
|
"": "constructAoPSMasterCollection",
|
|
},
|
|
|
|
initialize: function (options) {
|
|
var self = this;
|
|
|
|
this.models = {};
|
|
this.models.search_categories = {};
|
|
this.models.master = options.master;
|
|
|
|
// Will this take care of everything?
|
|
this.myPage = AoPS.Page.constructPage("wrapper");
|
|
|
|
this.route(
|
|
/((?:c\d+)?(?:t\d+)?(?:f\d+)?(?:h\d+)?(?:p\d+)?(?:s\d+)?)/,
|
|
"parseEncodedUrl"
|
|
);
|
|
this.route(/((?:c\d+)(.*)\/(.*))/, "search");
|
|
|
|
this.bind("route", this.onFinishRoute);
|
|
this.first_pass = true;
|
|
|
|
// If there is a preloaded topic for directly navigating to a topic on page load,
|
|
// then this will be set to the preloaded id when the preloaded data is parsed.
|
|
// We then set this back to -1 once the preloaded topic is set as the focus_topic.
|
|
// This flag is used to prevent a redundant Ajax call (change_focus_topic) when the topic is built.
|
|
this.preloaded_topic_id = -1;
|
|
|
|
Backbone.on("logged_out", this.onAjaxDetectLogout);
|
|
Backbone.on("logged_in", this.onAjaxDetectLogin);
|
|
|
|
if (AoPS.bootstrap_data.hasOwnProperty("blog_base")) {
|
|
this.models.blog = this.models.master.constructNewCategory(
|
|
AoPS.bootstrap_data.blog_base
|
|
);
|
|
// TODO : check that topics exist
|
|
_.each(AoPS.bootstrap_data.blog_topics.topics_data, function (topic) {
|
|
self.models.master.processPotentialNewTopic(topic, true);
|
|
});
|
|
|
|
if (String(AoPS.bootstrap_data.blog_topics.no_more_topics) === "1") {
|
|
this.models.blog.set("all_topics_loaded", true);
|
|
}
|
|
} else {
|
|
// TODO : Throw error, you are in the wrong place.
|
|
}
|
|
this.models.master.set("focus_category", this.models.blog);
|
|
|
|
AoPS.Community.Utils.activateLatexOnclick();
|
|
|
|
$("body").addClass("community");
|
|
},
|
|
|
|
goToTopic: function (args) {
|
|
var url = "#";
|
|
|
|
if (args.hasOwnProperty("category_id")) {
|
|
url += "c" + args.category_id;
|
|
}
|
|
|
|
url += "h" + args.topic_id;
|
|
this.navigate(url, {
|
|
trigger: true,
|
|
replace: true, //(args.hasOwnProperty('replace') ? args.replace : false)
|
|
});
|
|
},
|
|
|
|
buildBreadcrumbs: function (data) {
|
|
var input = '<a href="/community">Community</a> » ';
|
|
|
|
input +=
|
|
'<a href="/community/c' +
|
|
Constants.aops_blogroll_id +
|
|
'">Blogs</a> » '; // TODO : POINT THIS AT THE BLOGS CATEGORY
|
|
input +=
|
|
'<a data-blog href="/community/c' +
|
|
data.blog.category_id +
|
|
'">' +
|
|
data.blog.name +
|
|
"</a>";
|
|
|
|
if (data.hasOwnProperty("topic")) {
|
|
input +=
|
|
' » <a data-blog href="/community/c' +
|
|
data.blog.category_id +
|
|
"h" +
|
|
data.topic.topic_id +
|
|
'">' +
|
|
data.topic.title +
|
|
"</a>";
|
|
}
|
|
|
|
$("#left_navigation_box").html(input);
|
|
},
|
|
|
|
onFinishRoute: function (name, args) {
|
|
this.previous_route = name;
|
|
if (!this.keep_fullscreen_mode) {
|
|
$("body").removeClass("fullscreen");
|
|
}
|
|
|
|
$(window).trigger("resize");
|
|
this.keep_fullscreen_mode = false;
|
|
},
|
|
|
|
/**
|
|
* Search this blog
|
|
*
|
|
* @param string of form /c<id>/<encoded search text>
|
|
**/
|
|
search: function (url) {
|
|
var pieces, str, coords, search_settings;
|
|
|
|
pieces = url.split("/");
|
|
|
|
if (pieces[1].length === 0) {
|
|
this.parseEncodedUrl(pieces[0]);
|
|
return;
|
|
}
|
|
|
|
str = pieces[1];
|
|
|
|
coords = {
|
|
category_id: 0,
|
|
tag_id: 0,
|
|
tag_forum_id: 0,
|
|
search_text: str,
|
|
is_search: true,
|
|
};
|
|
|
|
if (this.models.search_categories.hasOwnProperty(str)) {
|
|
coords.category = this.models.search_categories[str].category;
|
|
coords.topic_list = this.models.search_categories[str].topic_list;
|
|
} else {
|
|
search_settings = {
|
|
blog_id: this.models.blog.get("category_id"),
|
|
search_text: decodeURIComponent(pieces[1]),
|
|
};
|
|
|
|
coords.category =
|
|
this.models.master.buildSearchCategory(search_settings);
|
|
this.models.search_categories[str] = {
|
|
category: coords.category,
|
|
};
|
|
}
|
|
|
|
this.startConstructingPage(coords);
|
|
},
|
|
/** Start functions for building pages that have ids encoded in URL **/
|
|
|
|
/**
|
|
* parseEncodedUrl parses the url and then routes us to the appropriate function
|
|
* to construct the page once we're sure we have some topics to play with.
|
|
* (We'll likely handle that "check for some topics" differently when we build the real system.)
|
|
*
|
|
* @param url: everything after # in the url.
|
|
*
|
|
*/
|
|
parseEncodedUrl: function (url) {
|
|
var coords = {
|
|
topic: null,
|
|
category: null,
|
|
};
|
|
|
|
function extractValueFromUrl(stub, url_in) {
|
|
var match_array;
|
|
if (_.isNull(url_in)) {
|
|
return 0;
|
|
}
|
|
match_array = url_in.match(new RegExp(stub + "(\\d+)"));
|
|
if (_.isNull(match_array)) {
|
|
return 0;
|
|
} else {
|
|
return parseInt(match_array[1]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If we're hitting a category, topic, or post on page load (first_pass through the
|
|
* router), then we check the preload_cmty_data property of bootstrap to see if
|
|
* there's anything we can preload, and to see if we need to rewrite the url.
|
|
*
|
|
* There are later url rewrite checks, but they shouldn't ever be triggered.
|
|
* I'm leaving them in, in case down the line we introduce ways that users can
|
|
* travel down routes missing properties (like topic id with no category id) in
|
|
* some way other than the initial page load.
|
|
**/
|
|
if (this.first_pass) {
|
|
if (AoPS.bootstrap_data.hasOwnProperty("preload_cmty_data")) {
|
|
this.first_pass = false;
|
|
if (this.parsePreloadedData()) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fill coords object from url
|
|
_.each(this.url_parameters, function (item) {
|
|
coords[item.property] = extractValueFromUrl(item.letter, url);
|
|
});
|
|
|
|
coords.is_search = false;
|
|
|
|
this.startConstructingPage(coords);
|
|
},
|
|
|
|
startConstructingPage: function (coords) {
|
|
this.myPage.clearPage();
|
|
|
|
this.myPage.showElement({
|
|
id: "blog-top",
|
|
constructor: _.bind(function () {
|
|
return new AoPS.Community.Views.CategoryCellBlogHeading({
|
|
model: this.models.blog,
|
|
});
|
|
}, this),
|
|
});
|
|
|
|
if (coords.is_search) {
|
|
if (coords.hasOwnProperty("topic_list")) {
|
|
this.prepareBlogFront(coords);
|
|
} else {
|
|
this.checkTopicListThenContinue(coords);
|
|
}
|
|
} else if (coords.topic_id > 0) {
|
|
this.prepareBlogTopic(coords);
|
|
} else {
|
|
this.prepareBlogFront(coords);
|
|
}
|
|
|
|
return;
|
|
},
|
|
|
|
/**
|
|
* The sidebar and the clearfix are needed everywhere.
|
|
**/
|
|
finishBuildingPage: function (coords) {
|
|
this.myPage.showElement({
|
|
id: "blog-sidebar",
|
|
constructor: _.bind(function () {
|
|
return new AoPS.Community.Views.BlogSidebar({
|
|
model: this.models.blog,
|
|
});
|
|
}, this),
|
|
location: "content",
|
|
});
|
|
|
|
this.myPage.showElement({
|
|
id: "blog-clearfix",
|
|
type: "jQuery_object",
|
|
jQuery_object: $('<div class="clear"></div>'),
|
|
location: "content",
|
|
});
|
|
},
|
|
|
|
prepareBlogTopic: function (coords) {
|
|
var self = this;
|
|
coords.topic = this.models.master.fetchTopicById(coords.topic_id);
|
|
if (_.isNull(coords.topic) && !_.isUndefined(coords.topic)) {
|
|
this.listenTo(
|
|
this.models.master,
|
|
"single_topic_load",
|
|
_.bind(function (obj) {
|
|
self.myPage.hideLoader();
|
|
if (obj.topic_id === coords.topic_id) {
|
|
// We found the topic
|
|
this.stopListening(this.models.master, "single_topic_load");
|
|
if (obj.ok) {
|
|
coords.topic = obj.topic;
|
|
this.buildBlogTopic(coords);
|
|
} else {
|
|
// Database fetch failed dues to lack of permissions
|
|
if (obj.error_code === "E_NO_PERMISSION") {
|
|
if (AoPS.session.logged_in) {
|
|
this.throwError(
|
|
Lang["router-err-no-topic-perms-logged-in"]
|
|
);
|
|
} else {
|
|
this.throwError(
|
|
Lang["router-err-no-topic-perms-logged-out"]
|
|
);
|
|
}
|
|
} else if (obj.error_code === "E_NO_SUCH_TOPIC") {
|
|
this.throwError(Lang["router-err-no-topic"]);
|
|
}
|
|
}
|
|
}
|
|
}, this)
|
|
);
|
|
this.myPage.showLoader();
|
|
|
|
this.models.master.fetchTopicFromDb({
|
|
topic_id: coords.topic_id,
|
|
});
|
|
return;
|
|
} else {
|
|
// If we have a topic, process all the tags on it (add them to their categories).
|
|
this.buildBlogTopic(coords);
|
|
return;
|
|
}
|
|
},
|
|
|
|
buildBlogTopic: function (coords) {
|
|
var topic_page_element,
|
|
blog_name = this.models.blog.get("category_name"),
|
|
topic_title = coords.topic.get("topic_title");
|
|
|
|
this.myPage.setTitle(
|
|
_.unescape(blog_name) + " : " + _.unescape(topic_title)
|
|
);
|
|
coords.topic.set("category", this.models.blog);
|
|
|
|
this.models.master.set("focus_topic", coords.topic, {
|
|
suppress_ajax: coords.topic_id === this.preloaded_topic_id,
|
|
fetch_all: true,
|
|
});
|
|
|
|
this.preloaded_topic_id = -1;
|
|
/**
|
|
* If we navigate in the blog to this top, the scrollbar might be set somewhere
|
|
* far down the page
|
|
**/
|
|
$(window).scrollTop(0);
|
|
|
|
topic_page_element = this.myPage.showElement({
|
|
id: "blog-topic-" + coords.topic_id,
|
|
constructor: _.bind(function () {
|
|
return new AoPS.Community.Views.BlogTopicFull({
|
|
model: coords.topic,
|
|
post_id: coords.post_id,
|
|
});
|
|
}, this),
|
|
on_add_settings: {
|
|
post_id: coords.post_id,
|
|
},
|
|
location: "content",
|
|
});
|
|
|
|
this.finishBuildingPage(coords);
|
|
// Reply triggered
|
|
if (coords.state === 3) {
|
|
if (coords.topic.getPermission("c_can_reply")) {
|
|
topic_page_element.dom_element.trigger("open_reply");
|
|
}
|
|
}
|
|
|
|
this.buildBreadcrumbs({
|
|
blog: {
|
|
category_id: this.models.blog.get("category_id"),
|
|
name: blog_name,
|
|
},
|
|
topic: {
|
|
title: topic_title,
|
|
topic_id: coords.topic.get("topic_id"),
|
|
},
|
|
});
|
|
},
|
|
|
|
buildBlogFrontPage: function (coords) {
|
|
var topic_list,
|
|
blog_name = this.models.blog.get("category_name");
|
|
|
|
if (coords.hasOwnProperty("topic_list")) {
|
|
topic_list = coords.topic_list;
|
|
} else {
|
|
topic_list = this.models.master.fetchFilteredTopicList({
|
|
category: this.models.blog,
|
|
category_id: this.models.blog.get("category_id"),
|
|
tag_ids: [],
|
|
});
|
|
if (coords.is_search) {
|
|
this.models.search_categories[coords.search_text].topic_list =
|
|
topic_list;
|
|
}
|
|
}
|
|
|
|
this.myPage.hideLoader();
|
|
|
|
this.myPage.setTitle(_.unescape(blog_name));
|
|
this.myPage.showElement({
|
|
id:
|
|
"blog-topics" +
|
|
coords.tag_id +
|
|
(coords.hasOwnProperty("search_text") ? coords.search_text : ""),
|
|
constructor: _.bind(function () {
|
|
return new AoPS.Community.Views.TopicsListBlog({
|
|
collection: topic_list,
|
|
blog: this.models.blog,
|
|
});
|
|
}, this),
|
|
location: "content",
|
|
});
|
|
this.models.master.set("focus_topic", null);
|
|
this.buildBreadcrumbs({
|
|
blog: {
|
|
category_id: this.models.blog.get("category_id"),
|
|
name: blog_name,
|
|
},
|
|
});
|
|
this.finishBuildingPage(coords);
|
|
},
|
|
|
|
/***
|
|
* Construct the topic list that we use for the page. If the topic list
|
|
* has no topics, then we go get some topics before building.
|
|
*
|
|
* @param coords: settings used to build the page.
|
|
*/
|
|
checkTopicListThenContinue: function (coords) {
|
|
var self = this;
|
|
// Construct or fetch the Community.Models.FilteredTopicList that matches
|
|
// the set of parameters we seek.
|
|
// Gets called twice for topics that are shorn of tags, but we can live with that, I think
|
|
// That will be rare. We could do a property check, though. Might be more expensive than it's worth.
|
|
coords.topic_list = this.models.master.fetchFilteredTopicList({
|
|
category:
|
|
coords.hasOwnProperty("category") && !_.isNull(coords.category)
|
|
? coords.category
|
|
: this.models.blog,
|
|
category_id: coords.category_id,
|
|
tag_ids:
|
|
coords.tag_id > 0
|
|
? [
|
|
{
|
|
tag_id: coords.tag_id,
|
|
tag_forum_id: coords.tag_forum_id,
|
|
},
|
|
]
|
|
: [], //currently assumes at most one tag_id.
|
|
});
|
|
|
|
// Not enough topics in this list; I'm gonna get more.
|
|
if (
|
|
coords.topic_list.length <=
|
|
AoPS.Community.Constants.min_topic_list_initial_length &&
|
|
!coords.topic_list.all_topics_fetched
|
|
) {
|
|
this.myPage.showLoader();
|
|
coords.topic_list.fetchMoreTopics({
|
|
onFinish: _.bind(function () {
|
|
this.buildBlogFrontPage(coords);
|
|
}, this),
|
|
onError: function (data) {
|
|
var msg;
|
|
if (data.error_code === "E_AJAX_CANCEL") {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
typeof Lang["initial-fetch-err-" + data.error_code] === "string"
|
|
) {
|
|
msg = AoPS.Community.Utils.formatString(
|
|
Lang["initial-fetch-blog-err-" + data.error_code],
|
|
[coords.category_id]
|
|
);
|
|
console.log(msg);
|
|
console.log(coords.category_id);
|
|
} else {
|
|
msg = Lang["unexpected-error-code"] + data.error_code;
|
|
}
|
|
// Force page reload after error
|
|
$("a[data-blog]").removeAttr("data-blog");
|
|
self.throwError(msg);
|
|
},
|
|
});
|
|
} else {
|
|
this.buildBlogFrontPage(coords);
|
|
}
|
|
},
|
|
|
|
prepareBlogFront: function (coords) {
|
|
if (coords.tag_id > 0) {
|
|
coords.tag_forum_id = coords.category_id;
|
|
this.checkTopicListThenContinue(coords);
|
|
} else {
|
|
this.buildBlogFrontPage(coords);
|
|
}
|
|
},
|
|
|
|
setBreadcrumbs: function (crumbs) {
|
|
crumbs = _.map(crumbs, function (crumb) {
|
|
crumb.data = "data-cmty";
|
|
return crumb;
|
|
});
|
|
this.myPage.setBreadcrumbs(this.breadcrumb_base.concat(crumbs));
|
|
},
|
|
|
|
onAjaxDetectLogout: function () {
|
|
if (!AoPS.login.user_clicked_logout) {
|
|
AoPS.Ui.buildLoginConfirm(Lang["unexpected-logout"]);
|
|
AoPS.login.onUserAjaxLogout();
|
|
}
|
|
},
|
|
|
|
onAjaxDetectLogin: function () {
|
|
if (!AoPS.login.user_clicked_login) {
|
|
document.location.reload(true);
|
|
}
|
|
},
|
|
|
|
onAjaxDetectLoginChange: function () {
|
|
//if (
|
|
},
|
|
});
|
|
|
|
$(window).load(function () {
|
|
var master, app;
|
|
|
|
master = new AoPS.Community.Models.Master();
|
|
|
|
app = new Blog.Router({
|
|
master: master,
|
|
});
|
|
|
|
$("#page-wrapper").append(app.myPage.el);
|
|
|
|
Backbone.history.start({
|
|
pushState: true,
|
|
root: "community",
|
|
});
|
|
|
|
$("a.blog-login").on("click", function (e) {
|
|
AoPS.login.display();
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
});
|
|
|
|
$("a.blog-logout").on("click", function (e) {
|
|
AoPS.login.logout(e);
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
});
|
|
|
|
$(document).on("click", "a[data-blog]", function (e) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
Backbone.history.navigate($(this).attr("href").substring(10), {
|
|
trigger: true,
|
|
});
|
|
});
|
|
|
|
if (AoPS.session.logged_in) {
|
|
$("#blog-subscribe").on("click", function (e) {
|
|
AoPS.Community.Views.toggleBlogSubscription({
|
|
blog: app.models.blog,
|
|
user: master.get("current_user"),
|
|
});
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
});
|
|
}
|
|
|
|
/* FEED MUST COME AFTER Backbone start!*/
|
|
// experimenting with delay to let the page render before Feed is built.
|
|
// If this seems to work well, we'll want to animate the feed in.
|
|
setTimeout(function () {
|
|
var feed = new AoPS.Community.Models.Feed({
|
|
master: master,
|
|
}),
|
|
feed_view = new AoPS.Feed.Views.FeedMaster({
|
|
model: feed,
|
|
});
|
|
|
|
$("body").append(feed_view.$el);
|
|
}, 100);
|
|
// console.log('end page ' + (new Date().getTime() - start));
|
|
// });
|
|
});
|
|
|
|
return Blog;
|
|
})(AoPS.Blog || {});
|