1670 lines
44 KiB
JavaScript
1670 lines
44 KiB
JavaScript
/**
|
|
* Views used in constructing AoPS Blogs
|
|
*
|
|
* Relies heavily on views used for forums; many functions are mixed in throughout
|
|
* these views. An understanding of Views.TopicFull and Views.Post will be very
|
|
* helpful in sorting out what's going on here.
|
|
**/
|
|
AoPS.Community.Views = (function (ComViews) {
|
|
var Lang = AoPS.Community.Lang,
|
|
Modal = AoPS.Ui.Modal;
|
|
|
|
/**
|
|
* Construct a loader for the blogs. We have the AoPS default as
|
|
* the background image on this div, but users will be able to change that
|
|
* with their own CSS.
|
|
**/
|
|
ComViews.buildBlogLoader = function () {
|
|
return $('<div class="blog-loader"></div>');
|
|
};
|
|
|
|
/**
|
|
* Build a scrollbar in the blogs that isn't in default community style.
|
|
* Used in the shoutbox currently.
|
|
*/
|
|
ComViews.buildBlogScrollbar = function ($obj) {
|
|
var scrollbar_defaults = {
|
|
slider_end_tolerance: 2,
|
|
stop_scroll_propagation: true,
|
|
},
|
|
scrollbar_args =
|
|
arguments.length === 1
|
|
? scrollbar_defaults
|
|
: _.extend(scrollbar_defaults, arguments[1]),
|
|
$scrollwrap = $obj.wrapWithScroll(scrollbar_args);
|
|
/** Extra div needed for firefox centering and scrolling to work with stuff in the scrollbar **/
|
|
$scrollwrap.addClass("blog-scroll-outer");
|
|
return $scrollwrap;
|
|
};
|
|
|
|
ComViews.toggleBlogSubscription = function (obj) {
|
|
var new_text = obj.blog.get("is_subscribed")
|
|
? Lang["Subscribe"]
|
|
: Lang["Unsubscribe"];
|
|
obj.blog.set("is_subscribed", !obj.blog.get("is_subscribed"));
|
|
obj.user.setBlogSubscriptionStatus({
|
|
blog_id: obj.blog.get("category_id"),
|
|
status: obj.blog.get("is_subscribed"),
|
|
});
|
|
AoPS.Ui.Flyout.display(
|
|
Lang[
|
|
obj.blog.get("is_subscribed")
|
|
? "blog-subscribed-notify"
|
|
: "blog-unsubscribed-notify"
|
|
]
|
|
);
|
|
$("#blog-subscribe")[0].title =
|
|
Lang[
|
|
obj.blog.get("is_subscribed")
|
|
? "blog-unsubscribe-tooltip"
|
|
: "blog-subscribe-tooltip"
|
|
];
|
|
$("#blog-subscribe").text(new_text);
|
|
};
|
|
|
|
/**
|
|
* Top bit of a blog, very simple.
|
|
**/
|
|
ComViews.CategoryCellBlogHeading = AoPS.View.extend({
|
|
template_id: "#blog-category-cell-heading-tpl",
|
|
|
|
id: "header",
|
|
|
|
initialize: function () {
|
|
this.$el.html(
|
|
this.getTemplate(this.template_id, {
|
|
blog_title: this.model.get("category_name"),
|
|
category_id: this.model.get("category_id"),
|
|
})
|
|
);
|
|
},
|
|
});
|
|
|
|
/**
|
|
* A list of Blog Topics; may be filtered by tag.
|
|
*
|
|
* Collection : FilteredTopicList
|
|
*
|
|
* Each Topic in the list produces a TopicCellBlog
|
|
*/
|
|
ComViews.TopicsListBlog = AoPS.View.extend({
|
|
template_id: "#blog-topic-list-tpl",
|
|
|
|
id: "main",
|
|
|
|
initialize: function (options) {
|
|
var self = this,
|
|
no_topics_msg;
|
|
this.blog = options.blog;
|
|
// eslint-disable-next-line eqeqeq
|
|
var is_blog_category = options.blog == this.collection.category;
|
|
|
|
no_topics_msg =
|
|
Lang[is_blog_category ? "blog-no-topics" : "blog-no-results"];
|
|
this.$no_topics = $(
|
|
'<div class="blog-no-topics">' + no_topics_msg + "</div>"
|
|
);
|
|
|
|
this.$el.html(
|
|
this.getTemplate(this.template_id, {
|
|
can_post: this.blog.getPermission("c_can_start_topic"),
|
|
lang_post_new: Lang["blog-post-new-entry"],
|
|
})
|
|
);
|
|
|
|
this.topic_cells = [];
|
|
this.$loader = ComViews.buildBlogLoader();
|
|
|
|
// LOOP through existing topics in collection, add them.
|
|
this.collection.each(function (topic) {
|
|
self.addTopic(topic);
|
|
});
|
|
|
|
if (this.collection.length === 0) {
|
|
this.$el.append(this.$no_topics);
|
|
}
|
|
|
|
this.listenTo(this.collection, "add", this.addTopic);
|
|
this.listenTo(this.collection, "remove", this.removeTopic);
|
|
|
|
// When we scroll to the bottom of the browser, we go get more topics.
|
|
$(window).scroll(function () {
|
|
if (self.$el.is(":visible")) {
|
|
if (
|
|
window.innerHeight + window.pageYOffset + 50 >=
|
|
$(document).height()
|
|
) {
|
|
self.fetchMoreTopics();
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
events: {
|
|
"click #post-new-entry": "onClickNewTopic",
|
|
},
|
|
|
|
/**
|
|
* onClickNewTopic fires up the post topic atop the page in a modal.
|
|
*/
|
|
onClickNewTopic: function (e) {
|
|
this.openNewBlogPost(this.blog);
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
},
|
|
|
|
openNewBlogPost: function (category) {
|
|
if (!AoPS.session.logged_in) {
|
|
AoPS.Ui.Modal.showMessage(Lang["new-topic-not-logged-in"]);
|
|
} else if (!category.getPermission("c_can_start_topic")) {
|
|
AoPS.Ui.Modal.showMessage(Lang["cat-cell-no-perm-start-topic"]);
|
|
} else {
|
|
// eslint-disable-next-line no-new
|
|
new ComViews.NewBlogPost({
|
|
model: category,
|
|
master: category.get("master"),
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Any time this collection has a new topic, we build a new cell for the topic.
|
|
*
|
|
* @param AoPS.Community.Models.Topic
|
|
**/
|
|
addTopic: function (topic) {
|
|
var new_topic = new ComViews.TopicCellBlog({
|
|
model: topic,
|
|
category: this.blog,
|
|
}),
|
|
i,
|
|
first_post_time = topic.get("first_post_time"),
|
|
n_topics = this.topic_cells.length,
|
|
topic_placed = false,
|
|
current_topic;
|
|
|
|
// placing the topic in the DOM in the right place
|
|
for (i = 0; i < n_topics; i++) {
|
|
current_topic = this.topic_cells[i];
|
|
if (current_topic.model.get("first_post_time") < first_post_time) {
|
|
current_topic.$el.before(new_topic.$el);
|
|
this.topic_cells.splice(i, 0, new_topic);
|
|
|
|
topic_placed = true;
|
|
i = n_topics;
|
|
}
|
|
}
|
|
if (!topic_placed) {
|
|
// Put this topic at the end.
|
|
this.topic_cells.push(new_topic);
|
|
this.$el.append(new_topic.$el);
|
|
}
|
|
|
|
this.$loader.detach();
|
|
this.$no_topics.detach();
|
|
},
|
|
|
|
removeTopic: function (topic) {
|
|
var topic_cell = _.find(this.topic_cells, function (cell) {
|
|
return cell.model === topic;
|
|
});
|
|
|
|
if (!_.isUndefined(topic_cell)) {
|
|
this.topic_cells = _.without(this.topic_cells, topic_cell);
|
|
topic_cell.close();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Go get more topics for this awesome blog!
|
|
**/
|
|
fetchMoreTopics: function () {
|
|
var self = this;
|
|
if (!this.collection.topics_loading) {
|
|
if (!this.collection.all_topics_fetched) {
|
|
this.$el.append(this.$loader);
|
|
this.collection.fetchMoreTopics({
|
|
onFinish: _.bind(function () {
|
|
this.$loader.detach();
|
|
}, this),
|
|
onError: function (data) {
|
|
var msg;
|
|
self.$loader.detach();
|
|
|
|
if (
|
|
typeof Lang["topic-fetch-err-" + data.error_code] === "string"
|
|
) {
|
|
msg = Lang["topic-fetch-err-" + data.error_code];
|
|
} else {
|
|
msg = Lang["unexpected-error-code"] + data.error_code;
|
|
}
|
|
// Error; don't want to try fetching any more.
|
|
self.collection.all_topics_fetched = true;
|
|
ComViews.showError(msg);
|
|
},
|
|
});
|
|
}
|
|
}
|
|
},
|
|
});
|
|
|
|
ComViews.BlogTopicTagbox = AoPS.Community.Views.TopicTagbox.extend({
|
|
always_show_edit: false,
|
|
});
|
|
|
|
/**
|
|
* A single Topic in the blog: we render the full first post of the topic.
|
|
*
|
|
* model : AoPS.Community.Models.Topic
|
|
*/
|
|
ComViews.TopicCellBlog = AoPS.View.extend({
|
|
template_id: "#blog-topic-cell-tpl",
|
|
|
|
push_state_attribute: "data-blog",
|
|
|
|
staticTagboxView: AoPS.Community.Views.BlogTopicTagbox,
|
|
|
|
editableTagboxView: AoPS.Community.Views.EditableTagBoxExistingTopic,
|
|
|
|
initialize: function (options) {
|
|
this.category = options.category;
|
|
|
|
this.buildRoutes();
|
|
|
|
this.render();
|
|
|
|
this.listenTo(this.model, "change:num_posts", this.buildNumComments);
|
|
this.listenTo(this.model, "change:locked", this.render);
|
|
|
|
this.listenTo(
|
|
this.model.get("posts").first(),
|
|
"change:post_rendered",
|
|
this.render
|
|
);
|
|
},
|
|
|
|
render: function () {
|
|
var can_edit = this.model.getPermission("c_can_edit"),
|
|
can_moderate =
|
|
this.model.getPermission("c_can_lock_topic") ||
|
|
this.model.getPermission("c_can_delete");
|
|
|
|
this.has_poll = this.model.get("poll_id") > 0;
|
|
this.first_post = this.model.get("posts").first();
|
|
this.$el.html(
|
|
this.getTemplate(this.template_id, {
|
|
username: this.model.get("first_poster_name"),
|
|
user_id: this.model.get("first_poster_id"),
|
|
date: AoPS.Community.Utils.makePrettyTimeStatic(
|
|
this.model.get("first_post_time")
|
|
),
|
|
lang_permanent_link: Lang["permanent-link"],
|
|
lang_by: Lang["by"],
|
|
has_settleable_reports:
|
|
this.model.get("num_reports") > 0 &&
|
|
this.model.getPermission("c_can_settle_report"),
|
|
route_url: AoPS.Community.Views.makeLinkUrl(this.route_url),
|
|
topic_title: this.model.get("topic_title"),
|
|
can_reply:
|
|
!this.model.get("locked") &&
|
|
this.model.getPermission("c_can_reply"), //get from permissions
|
|
route_reply: AoPS.Community.Views.makeLinkUrl(this.comment_url), //TODO
|
|
post_comment_text: this.category.get("blog_post_comment_text"), //get from category
|
|
lang_edit_post: Lang["blog-edit-post"],
|
|
lang_moderate_post: Lang["blog-moderate-post"],
|
|
has_mod_actions: can_edit || can_moderate,
|
|
can_edit: can_edit,
|
|
has_poll: this.has_poll,
|
|
can_moderate: can_moderate,
|
|
topic_id: this.model.get("topic_id"),
|
|
has_tags: this.category.get("show_tags"), //(this.model.get('tags').length > 0),
|
|
})
|
|
);
|
|
|
|
this.finishRenderingPost();
|
|
|
|
if (this.category.get("show_tags")) {
|
|
this.constructStaticTagbox();
|
|
this.tagbox_state = "static";
|
|
}
|
|
|
|
if (this.has_poll) {
|
|
this.constructPoll();
|
|
}
|
|
this.buildNumComments();
|
|
this.checkSearchHighlighting();
|
|
},
|
|
|
|
/**
|
|
* We build polls in blogs by mixing in the relevant pieces of ComViews.Post.
|
|
*
|
|
* To get this all to work, we make a poll_object property of this view
|
|
* such that poll_object has all the pieces needed by the functions we're
|
|
* mixing in (initializePoll, constructPoll, onClickPollVote, onClickTogglePollResults).
|
|
***/
|
|
constructPoll: function () {
|
|
this.poll_object = {
|
|
$el: this.$el,
|
|
topic: {
|
|
model: this.model,
|
|
},
|
|
$: this.$,
|
|
listenTo: this.listenTo,
|
|
getTemplate: this.getTemplate,
|
|
constructPoll: ComViews.Post.prototype.constructPoll,
|
|
};
|
|
|
|
ComViews.Post.prototype.initializePoll.apply(this.poll_object);
|
|
},
|
|
|
|
onClickPollVote: function () {
|
|
ComViews.Post.prototype.onClickPollVote.apply(this.poll_object);
|
|
},
|
|
|
|
onClickTogglePollResults: function () {
|
|
ComViews.Post.prototype.onClickTogglePollResults.apply(this.poll_object);
|
|
},
|
|
|
|
checkSearchHighlighting: function () {
|
|
var search_text;
|
|
if (this.model.get("is_search_result")) {
|
|
search_text = this.model.fetchSearchText("post_text");
|
|
if (search_text.length > 0) {
|
|
this.$(".entry h1").extendedHighlightText(
|
|
"cmty-highlight",
|
|
search_text
|
|
);
|
|
this.$(".message").extendedHighlightText(
|
|
"cmty-highlight",
|
|
search_text
|
|
);
|
|
this.$(".cmty-tags-itembox-wrapper").extendedHighlightText(
|
|
"cmty-highlight",
|
|
search_text
|
|
);
|
|
|
|
/*if (this.model.get('post_id') === this.topic.model.get('focus_post').get('post_id')) {// pre-open hidden text in focus text
|
|
this.$('.cmty-hide-heading').trigger('click');
|
|
}*/
|
|
}
|
|
}
|
|
},
|
|
|
|
/** Mixed in to BlogComment. **/
|
|
finishRenderingPost: function () {
|
|
// pywindow causes this to be rendered separately. We will put this back in handlebars if we
|
|
// stick with onclick
|
|
var $m = this.$(".message");
|
|
$m.html(this.first_post.get("post_rendered"));
|
|
this.parseEditData();
|
|
this.listenTo(
|
|
this.first_post,
|
|
"change:last_edit_time_rendered",
|
|
this.parseEditData
|
|
);
|
|
|
|
this.$attachments = this.$(".cmty-post-attachments");
|
|
this.highlight($m);
|
|
this.parseAttachments();
|
|
this.listenTo(
|
|
this.first_post,
|
|
"change:attachment change:attachments",
|
|
this.parseAttachments
|
|
);
|
|
},
|
|
|
|
events: {
|
|
"click .blog-edit-post": "onClickEdit",
|
|
"click .blog-moderate-topic": "onClickModerate",
|
|
"click .cmty-itembox .cmty-edit-tag": "toggleTagbox",
|
|
"click .cmty-poll-results-toggle span": "onClickTogglePollResults",
|
|
"click .cmty-poll-vote-row .btn": "onClickPollVote",
|
|
},
|
|
|
|
/* eslint-disable no-undef */
|
|
highlight: function ($m) {
|
|
if ($m.length > 0 && typeof Prism !== "undefined") {
|
|
Prism.plugins.autoloader.languages_path =
|
|
"https://cdnjs.cloudflare.com/ajax/libs/prism/1.17.1/components/";
|
|
var elements = $m[0].querySelectorAll("pre code");
|
|
for (var i = 0; i < elements.length; i++) {
|
|
Prism.highlightElement(elements[i]);
|
|
}
|
|
}
|
|
},
|
|
/* eslint-enable no-undef */
|
|
|
|
parseEditData: function () {
|
|
ComViews.Post.prototype.parseEditData.apply({
|
|
$el: this.$el,
|
|
model: this.first_post,
|
|
watching_edit_time: false,
|
|
});
|
|
},
|
|
|
|
parseAttachments: function () {
|
|
ComViews.Post.prototype.parseAttachments.apply({
|
|
$el: this.$el,
|
|
model: this.first_post,
|
|
$attachments: this.$attachments,
|
|
isAttachmentImage: ComViews.Post.prototype.isAttachmentImage,
|
|
});
|
|
},
|
|
|
|
/**
|
|
* We mix in the Views.Post.onClickEdit function here; that function
|
|
* only uses the "model" and "topic" properties of View.Post,
|
|
* so we create an object with those properties here, and apply
|
|
* the function to the new object.
|
|
**/
|
|
onClickEdit: function (e) {
|
|
var obj;
|
|
|
|
e.stopPropagation();
|
|
e.preventDefault;
|
|
|
|
obj = {
|
|
topic: this,
|
|
model: this.model.get("posts").first(),
|
|
};
|
|
|
|
AoPS.Community.Views.Post.prototype.onClickEdit.apply(obj, [e]);
|
|
},
|
|
|
|
/**
|
|
* Throws up a modal for moderating this topic.
|
|
**/
|
|
onClickModerate: function (e) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
|
|
// eslint-disable-next-line no-new
|
|
new AoPS.Community.Views.ModerateTopic({
|
|
model: this.model,
|
|
topic_category_at_initialization: this.model.get("category_id"),
|
|
});
|
|
},
|
|
|
|
constructStaticTagbox:
|
|
AoPS.Community.Views.TopicFull.prototype.constructStaticTagbox,
|
|
|
|
constructEditableTagbox:
|
|
AoPS.Community.Views.TopicFull.prototype.constructEditableTagbox,
|
|
|
|
toggleTagbox: AoPS.Community.Views.TopicFull.prototype.toggleTagbox,
|
|
|
|
/**
|
|
* Called in the mixed-in functions above; we don't need to do anything here.
|
|
**/
|
|
setHeight: function () {
|
|
return;
|
|
},
|
|
|
|
/**
|
|
* Create the number of comments text
|
|
**/
|
|
buildNumComments: function () {
|
|
var num_comments_text,
|
|
num_comments = this.model.get("num_posts") - 1;
|
|
|
|
if (num_comments === 0) {
|
|
num_comments_text = this.category.get("blog_no_comments_text");
|
|
} else if (num_comments === 1) {
|
|
num_comments_text = this.category.get("blog_one_comment_text");
|
|
} else {
|
|
num_comments_text = this.category
|
|
.get("blog_comments_text")
|
|
.replace("%s", num_comments);
|
|
}
|
|
this.$el.find(".post-replies").text(num_comments_text);
|
|
},
|
|
|
|
/**
|
|
* Build the routes that will be used in the template
|
|
**/
|
|
buildRoutes: function () {
|
|
var title =
|
|
"_" + ComViews.convertToUrlFragment(this.model.get("topic_title")),
|
|
route_base =
|
|
"/c" +
|
|
this.model.get("category_id") +
|
|
"h" +
|
|
this.model.get("topic_id");
|
|
|
|
this.route_url = route_base + title;
|
|
this.comment_url = route_base + "s3" + title;
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Sidebar panel of a blog. Constructed from pieces the user chooses.
|
|
**/
|
|
ComViews.BlogSidebar = AoPS.View.extend({
|
|
template_id: "#blog-sidebar-tpl",
|
|
|
|
id: "side",
|
|
|
|
initialize: function () {
|
|
// ADD title. Always shown?
|
|
// if (this.model.get('show_custom_block')) {
|
|
this.sidebar_title = new AoPS.Community.Views.BlogSidebarTitle({
|
|
model: this.model,
|
|
});
|
|
this.$el.append(this.sidebar_title.$el);
|
|
// }
|
|
|
|
this.archive = new AoPS.Community.Views.BlogArchive({
|
|
model: this.model,
|
|
});
|
|
this.$el.append(this.archive.$el);
|
|
|
|
// Add Shoutbox
|
|
if (this.model.get("show_shoutbox")) {
|
|
this.shoutbox = new AoPS.Community.Views.BlogShoutbox({
|
|
model: this.model.get("shoutbox_topic"),
|
|
});
|
|
this.$el.append(this.shoutbox.$el);
|
|
}
|
|
|
|
// Only show the contributors if it's not silly (more than one contributor)
|
|
if (
|
|
this.model.get("show_contributors") &&
|
|
this.model.get("contributors").length > 1
|
|
) {
|
|
this.contributors = new AoPS.Community.Views.BlogContributors({
|
|
model: this.model,
|
|
});
|
|
this.$el.append(this.contributors.$el);
|
|
}
|
|
|
|
if (this.model.get("show_tags")) {
|
|
this.tags = new AoPS.Community.Views.BlogTagbox({
|
|
model: this.model,
|
|
});
|
|
this.$el.append(this.tags.$el);
|
|
}
|
|
|
|
// Add user profile
|
|
if (this.model.get("show_profile_info")) {
|
|
this.about_owner = new AoPS.Community.Views.BlogAboutOwner({
|
|
model: this.model,
|
|
});
|
|
this.$el.append(this.about_owner.$el);
|
|
}
|
|
|
|
// Add stats
|
|
if (this.model.get("show_stats")) {
|
|
this.stats = new AoPS.Community.Views.BlogStats({
|
|
model: this.model,
|
|
});
|
|
this.$el.append(this.stats.$el);
|
|
}
|
|
|
|
this.search = new AoPS.Community.Views.BlogSearch({
|
|
model: this.model,
|
|
});
|
|
|
|
this.$el.append(this.search.$el);
|
|
|
|
// TODO : RSS; Maybe spike?
|
|
},
|
|
|
|
onClose: function () {
|
|
this.sidebar_title.close();
|
|
if (this.hasOwnProperty("about_owner")) {
|
|
this.about_owner.close();
|
|
}
|
|
if (this.hasOwnProperty("stats")) {
|
|
this.stats.close();
|
|
}
|
|
|
|
if (this.hasOwnProperty("contributors")) {
|
|
this.contributors.close();
|
|
}
|
|
if (this.hasOwnProperty("shoutbox")) {
|
|
this.shoutbox.close();
|
|
}
|
|
if (this.hasOwnProperty("tags")) {
|
|
this.tags.close();
|
|
}
|
|
this.search.close();
|
|
},
|
|
});
|
|
|
|
ComViews.BlogContributors = AoPS.View.extend({
|
|
template_id: "#blog-contributors-tpl",
|
|
|
|
id: "contrib-widget",
|
|
|
|
className: "widget",
|
|
|
|
initialize: function () {
|
|
var contributors = _.map(this.model.get("contributors"), function (user) {
|
|
return (
|
|
'<a href="/community/user/' +
|
|
user.user_id +
|
|
'">' +
|
|
user.username +
|
|
"</a>"
|
|
);
|
|
}).join(" • ");
|
|
this.$el.html(
|
|
this.getTemplate(this.template_id, {
|
|
lang_contributors: Lang["blog-contributors-title"],
|
|
contributors: contributors,
|
|
})
|
|
);
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Used to build the tagbox on the side.
|
|
**/
|
|
ComViews.BlogItembox = AoPS.Community.Views.Itembox.extend({
|
|
scrollbarCreator: AoPS.Community.Views.buildBlogScrollbar,
|
|
|
|
push_state_attribute: "data-blog",
|
|
});
|
|
|
|
/**
|
|
* Creates the title box at the top.
|
|
**/
|
|
ComViews.BlogSidebarTitle = AoPS.View.extend({
|
|
template_id: "#blog-sidebar-title-tpl",
|
|
|
|
id: "user-menu-widget",
|
|
|
|
className: "block user_menu",
|
|
|
|
initialize: function () {
|
|
var user_route =
|
|
AoPS.Community.Constants.user_path + this.model.get("created_by");
|
|
|
|
this.$el.html(
|
|
this.getTemplate(this.template_id, {
|
|
description: this.model.get("short_description"),
|
|
avatar: this.model.get("creator_avatar"),
|
|
user_route: user_route,
|
|
username: this.model.get("creator_username"),
|
|
})
|
|
);
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Holds the details for the blog owner.
|
|
*/
|
|
ComViews.BlogAboutOwner = AoPS.View.extend({
|
|
template_id: "#blog-about-owner-tpl",
|
|
|
|
id: "about-owner-widget",
|
|
|
|
className: "block widget",
|
|
|
|
initialize: function () {
|
|
// TODO: Sort out location crap when we have it on the back end.
|
|
this.$el.html(
|
|
this.getTemplate(this.template_id, {
|
|
lang_about_owner: Lang["blog-about-owner"],
|
|
lang_posts: Lang["blog-posts"],
|
|
posts_count: this.model.get("creator_num_posts"),
|
|
joined_date: AoPS.Community.Utils.makePrettyTimeStaticDateOnly(
|
|
this.model.get("creator_joined_time")
|
|
),
|
|
lang_joined: Lang["blog-joined"],
|
|
has_location: this.model.get("creator_location").length > 0,
|
|
lang_location: Lang["blog-location"],
|
|
location: this.model.get("creator_location"),
|
|
})
|
|
);
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Holds the stats for the blog.
|
|
*/
|
|
ComViews.BlogStats = AoPS.View.extend({
|
|
template_id: "#blog-stats-tpl",
|
|
|
|
id: "stats-widget",
|
|
|
|
className: "block widget",
|
|
|
|
initialize: function () {
|
|
// TODO: Sort out location crap when we have it on the back end.
|
|
this.render();
|
|
this.listenTo(
|
|
this.model,
|
|
"change:num_topics change:num_posts",
|
|
this.render
|
|
);
|
|
},
|
|
|
|
render: function () {
|
|
this.$el.html(
|
|
this.getTemplate(this.template_id, {
|
|
lang_stats: Lang["blog-stats"],
|
|
lang_created: Lang["blog-created"],
|
|
lang_entries: Lang["blog-entries"],
|
|
lang_visits: Lang["blog-visits"],
|
|
lang_comments: Lang["blog-comments"],
|
|
created_date: this.model.get("created_at"),
|
|
num_entries: this.model.get("num_topics"),
|
|
num_visits: this.model.get("num_views"),
|
|
num_comments:
|
|
this.model.get("num_posts") - this.model.get("num_topics"),
|
|
})
|
|
);
|
|
},
|
|
});
|
|
|
|
ComViews.BlogSearch = AoPS.View.extend({
|
|
template_id: "#blog-search-tpl",
|
|
|
|
id: "search-widget",
|
|
|
|
className: "block widget",
|
|
|
|
initialize: function () {
|
|
this.$el.html(
|
|
this.getTemplate(this.template_id, {
|
|
lang_search_heading: Lang["blog-search-heading"],
|
|
lang_search_placeholder: Lang["blog-search-placeholder"],
|
|
lang_search_button: Lang["blog-search-button"],
|
|
lang_search_advanced: Lang["blog-search-advanced"],
|
|
lang_search_advanced_tooltip: Lang["blog-search-advanced-tooltip"],
|
|
blog_id: this.model.get("category_id"),
|
|
})
|
|
);
|
|
},
|
|
|
|
events: {
|
|
"click a": "onClickAdvancedSearch",
|
|
"submit #blog_searchform": "onSearch",
|
|
},
|
|
|
|
onClickAdvancedSearch: function (e) {
|
|
Modal.showMessage("Made you look! This is not implemented yet.");
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
},
|
|
|
|
onKeyDownSearch: function (e) {
|
|
var key_pressed = e.which || e.keyCode,
|
|
target;
|
|
if (key_pressed === 13) {
|
|
target = e.currentTarget.value;
|
|
if (target.length > 0) {
|
|
this.launchSearch(target.length);
|
|
// un-comment below to turn search input back on
|
|
e.currentTarget.value = "";
|
|
}
|
|
}
|
|
},
|
|
|
|
launchSearch: function (term) {
|
|
$("#blog_keywords").val("");
|
|
console.log(term);
|
|
console.log(
|
|
"/c" + this.model.get("category_id") + "/" + encodeURIComponent(term)
|
|
);
|
|
Backbone.history.navigate(
|
|
"/c" + this.model.get("category_id") + "/" + encodeURIComponent(term),
|
|
{trigger: true}
|
|
);
|
|
},
|
|
|
|
onSearch: function (e) {
|
|
var term = $("#blog_keywords").val();
|
|
if (term.length > 0) {
|
|
this.launchSearch(term);
|
|
}
|
|
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Archive for a blog.
|
|
*
|
|
* Model : Community.Models.Topic of the topic that is the shoutbox for this
|
|
* blog
|
|
*
|
|
* All topics for a blog are sorted into expandable menus by month, for easy
|
|
* navigation.
|
|
**/
|
|
ComViews.BlogArchive = AoPS.View.extend({
|
|
template_id: "#blog-archive-tpl",
|
|
|
|
id: "archive-widget",
|
|
|
|
className: "widget",
|
|
|
|
initialize: function () {
|
|
var archive_topics = this.model.get("blog_archive_topics");
|
|
|
|
archive_topics = this.convertNumericalMonthsToWords(archive_topics);
|
|
|
|
this.$el.html(
|
|
this.getTemplate(this.template_id, {
|
|
lang_archive: Lang["blog-archive-title"],
|
|
category_id: this.model.get("category_id"),
|
|
monthly_topic_data: archive_topics,
|
|
})
|
|
);
|
|
},
|
|
|
|
events: {
|
|
"click .archive-month-header": "toggleMonthMenu",
|
|
},
|
|
|
|
// Converts month "02" to February, for example. Uses community language file.
|
|
convertNumericalMonthsToWords: function (topic_data) {
|
|
for (var i = 0; i < topic_data.length; i++) {
|
|
topic_data[i].month_name = Lang["month-" + topic_data[i].month];
|
|
}
|
|
|
|
return topic_data;
|
|
},
|
|
|
|
// Expand/collapse month menus in archive.
|
|
toggleMonthMenu: function (e) {
|
|
var $element = $(e.currentTarget),
|
|
$month_menu = $element.siblings(".archive-month-topics"),
|
|
$menu_image = $element.find(".month-image");
|
|
|
|
if ($month_menu.hasClass("archive-month-collapsed")) {
|
|
$menu_image.attr("src", "/m/community/img/blogs/hyperion/minus.gif");
|
|
$menu_image.attr("alt", "-");
|
|
$month_menu.removeClass("archive-month-collapsed");
|
|
} else {
|
|
$menu_image.attr("src", "/m/community/img/blogs/hyperion/plus.gif");
|
|
$menu_image.attr("alt", "+");
|
|
$month_menu.addClass("archive-month-collapsed");
|
|
}
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Shoutbox for a blog.
|
|
*
|
|
* Model : Community.Models.Topic of the topic that is the shoutbox for this
|
|
* blog
|
|
*
|
|
* The html structure for the shoutbox was set in AoPS2.0, and had to be matched
|
|
* to allow user CSS to work.
|
|
*
|
|
* The posts in a shout topic are displayed with the most recent on top, which
|
|
* is the opposite of what we typically do in topics. So, a lot of things
|
|
* are reversed. We filter by those posts which are "show_from_end" posts,
|
|
* and a lot of the signs/directions in the addPost function are the opposite
|
|
* of those in Views.TopicFull.addPost()
|
|
**/
|
|
ComViews.BlogShoutbox = AoPS.View.extend({
|
|
template_id: "#blog-shoutbox-tpl",
|
|
|
|
id: "shouts-widget",
|
|
|
|
className: "widget",
|
|
|
|
initialize: function () {
|
|
var self = this;
|
|
var can_shout = this.model.getPermission("c_can_shout");
|
|
|
|
this.$el.html(
|
|
this.getTemplate(this.template_id, {
|
|
lang_shoutbox: Lang["blog-shoutbox-title"],
|
|
can_shout: can_shout,
|
|
lang_Submit: Lang["Submit"],
|
|
})
|
|
);
|
|
this.$loader = ComViews.buildBlogLoader();
|
|
this.$shouts_outer = this.$el.find("fieldset");
|
|
this.$shout_list = $("<ul></ul>");
|
|
this.$num_shouts = this.$el.find(".blog-num-shouts");
|
|
this.setNumShouts();
|
|
this.listenTo(this.model, "change:num_posts", this.setNumShouts);
|
|
|
|
this.$shouts_outer.prepend(
|
|
AoPS.Community.Views.buildBlogScrollbar(this.$shout_list)
|
|
);
|
|
this.$scrollbar = this.$shouts_outer.find(".aops-scroll-bar");
|
|
|
|
this.posts = [];
|
|
this.topic = this.model.get("posts").each(function (post) {
|
|
if (post.get("post_number") > 1) {
|
|
self.addPost(post);
|
|
}
|
|
});
|
|
|
|
this.applyPostClasses();
|
|
this.$scrollbar.on("slider_at_end", function () {
|
|
self.fetchMorePosts();
|
|
});
|
|
this.listenTo(this.model.get("posts"), "add", this.addPost);
|
|
this.listenTo(
|
|
this.model.get("posts"),
|
|
"change:deleted",
|
|
this.onDeletePost
|
|
);
|
|
if (can_shout) {
|
|
this.$post_box = this.$el.find("#message");
|
|
this.$chars_left = this.$el.find("#charsleft");
|
|
this.$shout_helper = this.$el.find("#shout-helper");
|
|
this.$post_box.on("keydown", function (e) {
|
|
var key_pressed = e.which || e.keyCode;
|
|
if (key_pressed === 13 && (e.ctrlKey || e.metaKey)) {
|
|
self.onClickSubmit();
|
|
}
|
|
});
|
|
this.$post_box.on("keyup", _.bind(this.onKeyPressShout, this));
|
|
}
|
|
|
|
// Allows us to mix-in AoPS.Community.Views.NewReply
|
|
this.settings = {
|
|
category_id: this.model.get("category_id"),
|
|
topic_id: this.model.get("topic_id"),
|
|
topic: this.model,
|
|
master: this.model.get("master"),
|
|
onSubmit: function () {
|
|
self.listenToOnce(
|
|
self.model,
|
|
"new_post_processed",
|
|
self.onSubmittedPostProcessed
|
|
);
|
|
},
|
|
};
|
|
this.extra_options = {};
|
|
|
|
this.model.startRealtimePostMonitor({
|
|
hash: this.model.get("master").get("watcher_hash"),
|
|
source: "master",
|
|
});
|
|
},
|
|
|
|
events: {
|
|
"click #submit": "onClickSubmit",
|
|
},
|
|
|
|
/**
|
|
* We show the user how many characters they have left in their shout.
|
|
**/
|
|
onKeyPressShout: function (e) {
|
|
var input_length = this.$post_box.val().length,
|
|
characters_left = 0,
|
|
character_message;
|
|
this.$shout_helper.toggle(input_length > 0);
|
|
|
|
if (input_length > AoPS.Community.Constants.max_shout_length) {
|
|
this.$post_box.val(
|
|
this.$post_box
|
|
.val()
|
|
.substr(0, AoPS.Community.Constants.max_shout_length)
|
|
);
|
|
} else {
|
|
characters_left =
|
|
AoPS.Community.Constants.max_shout_length - input_length;
|
|
}
|
|
|
|
character_message =
|
|
characters_left === 1
|
|
? Lang["blog-one-char-left"]
|
|
: Lang["blog-characters-left"];
|
|
|
|
this.$chars_left.html(characters_left + " " + character_message);
|
|
},
|
|
|
|
no_permission_lang_property: "blog-shout-no-permission",
|
|
|
|
no_permission_limited_lang_property: "blog-shout-no-permission-limited",
|
|
|
|
onClickSubmit: function () {
|
|
AoPS.Community.Views.NewReply.prototype.submitPost.apply(this);
|
|
},
|
|
|
|
checkCommonErrors:
|
|
AoPS.Community.Views.PostingEnviron.prototype.checkCommonErrors,
|
|
|
|
throwLatexErrorMessage: function (code) {
|
|
var message = Lang["blog-shout-err-" + code];
|
|
AoPS.Community.Views.showError(message);
|
|
},
|
|
|
|
throwBlockingMessage: function () {
|
|
ComViews.throwLoaderBlockingMessage(Lang["blog-shout-blocker"]);
|
|
},
|
|
|
|
onSubmittedPostProcessed: function () {
|
|
AoPS.Ui.Modal.closeAllModals();
|
|
},
|
|
|
|
onFinishSubmit: function () {
|
|
this.$post_box.val("");
|
|
this.$shout_helper.hide();
|
|
this.listenToOnce(this.model.get("posts"), "add", this.applyPostClasses);
|
|
},
|
|
|
|
showError: AoPS.Community.Views.NewReply.prototype.showError,
|
|
|
|
checkPost: function () {
|
|
var category;
|
|
// Also, how do we want to organize constants like the 8 below.
|
|
if (
|
|
this.$post_box.val().length < AoPS.Community.Constants.min_post_length
|
|
) {
|
|
this.showError(Lang["new-topic-post-too-short"]);
|
|
return false;
|
|
}
|
|
if (AoPS.session.user_id === this.model.get("last_poster_id")) {
|
|
category = this.model
|
|
.get("master")
|
|
.fetchCategory(this.model.get("category_id"));
|
|
|
|
// A little lazy here :( Hijacking c_can_edit_core_data --
|
|
// Blog owners and site admins can multi-shout.
|
|
if (
|
|
_.isUndefined(category) ||
|
|
!category.getPermission("c_can_edit_core_data")
|
|
) {
|
|
this.showError(Lang["blog-no-shoutbox-spamming"]);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
|
|
filterPost: function (post) {
|
|
return post.get("show_from_end");
|
|
},
|
|
|
|
applyPostFilter: function () {
|
|
var self = this;
|
|
_.each(this.posts, function (post) {
|
|
post.$el.toggle(self.filterPost(post.model));
|
|
});
|
|
|
|
this.applyPostClasses();
|
|
},
|
|
|
|
setNumShouts: function () {
|
|
// We subtract 1 for the placeholder.
|
|
var num_shouts = this.model.get("num_posts") - 1;
|
|
|
|
this.$num_shouts.html(
|
|
num_shouts +
|
|
" " +
|
|
(num_shouts === 1 ? Lang["blog-shout"] : Lang["blog-shouts"])
|
|
);
|
|
},
|
|
/**
|
|
* Add classes to the visible posts to give the zebra pattern
|
|
**/
|
|
applyPostClasses: function () {
|
|
var row_one = true,
|
|
n_posts = this.posts.length,
|
|
i;
|
|
|
|
for (i = 0; i < n_posts; i++) {
|
|
if (this.filterPost(this.posts[i].model)) {
|
|
this.posts[i].$el.removeClass(row_one ? "row2" : "row1");
|
|
this.posts[i].$el.addClass(row_one ? "row1" : "row2");
|
|
row_one = !row_one;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Add a post in the appropriate place
|
|
*
|
|
* @param ComModels.Post
|
|
**/
|
|
addPost: function (post) {
|
|
var new_post = new ComViews.BlogShout({
|
|
model: post,
|
|
}),
|
|
i,
|
|
post_number = post.get("post_number"),
|
|
current_post,
|
|
n_posts = this.posts.length,
|
|
post_placed = false;
|
|
|
|
// placing the post in the DOM to decide if we should show it.
|
|
new_post.$el.hide();
|
|
for (i = 0; i < n_posts; i++) {
|
|
current_post = this.posts[i];
|
|
if (current_post.model.get("post_number") < post_number) {
|
|
current_post.$el.before(new_post.$el);
|
|
this.posts.splice(i, 0, new_post);
|
|
|
|
if (this.filterPost(post)) {
|
|
new_post.$el.show();
|
|
}
|
|
post_placed = true;
|
|
i = n_posts;
|
|
}
|
|
}
|
|
if (!post_placed) {
|
|
// Put this post at the end.
|
|
this.posts.push(new_post);
|
|
this.$shout_list.append(new_post.$el);
|
|
if (this.filterPost(post)) {
|
|
new_post.$el.show();
|
|
}
|
|
}
|
|
},
|
|
|
|
fetchMorePosts: function () {
|
|
if (
|
|
this.model.get("all_posts_fetched") ||
|
|
this.model.get("is_fetching_posts")
|
|
) {
|
|
return;
|
|
}
|
|
|
|
this.$shout_list.append(this.$loader);
|
|
this.model.fetchMorePosts({
|
|
start_post_num: _.last(
|
|
this.model.get("posts").where({
|
|
show_from_end: true,
|
|
})
|
|
).get("post_number"),
|
|
onFinish: _.bind(function () {
|
|
this.applyPostFilter();
|
|
this.$loader.detach();
|
|
}, this),
|
|
direction: "backwards", // The shoutbox is in reverse order of a typical topic
|
|
show_from_setting: "show_from_end",
|
|
});
|
|
},
|
|
|
|
/**
|
|
* When a post is deleted from the shoutbox, we need to remove
|
|
* its view from the posts array and then reset the classes.
|
|
**/
|
|
onDeletePost: function (post) {
|
|
var post_view = _.findWhere(this.posts, {
|
|
model: post,
|
|
});
|
|
|
|
this.posts = _.without(this.posts, post_view);
|
|
this.applyPostClasses();
|
|
post_view.close();
|
|
},
|
|
});
|
|
|
|
/**
|
|
* A single shout in the shoutbox.
|
|
*
|
|
* model : AoPS.Community.Models.Post
|
|
**/
|
|
ComViews.BlogShout = AoPS.View.extend({
|
|
tagName: "li",
|
|
|
|
template_id: "#blog-shout-tpl",
|
|
|
|
className: "blog-shout",
|
|
|
|
initialize: function () {
|
|
var user_route =
|
|
AoPS.Community.Constants.user_path + this.model.get("poster_id");
|
|
|
|
this.$el.html(
|
|
this.getTemplate(this.template_id, {
|
|
lang_by: Lang["by"],
|
|
post_rendered: this.model.get("post_rendered"),
|
|
username: this.model.get("username"),
|
|
date: this.model.get("date_rendered"),
|
|
user_route: user_route,
|
|
can_delete: this.model.get("topic").getPermission("c_can_delete"),
|
|
})
|
|
);
|
|
},
|
|
|
|
events: {
|
|
"click .blog-delete-shout": "onClickDelete",
|
|
},
|
|
|
|
onClickDelete: AoPS.Community.Views.Post.prototype.onClickDelete,
|
|
});
|
|
|
|
/**
|
|
* Tagbox for a blog
|
|
*
|
|
* model : AoPS.Community.Models.CategoryBlog
|
|
*/
|
|
ComViews.BlogTagbox = AoPS.View.extend({
|
|
template_id: "#blog-tagbox-tpl",
|
|
|
|
initialize: function () {
|
|
this.$el.html(
|
|
this.getTemplate(this.template_id, {
|
|
lang_tags: Lang["blog-Tags"],
|
|
})
|
|
);
|
|
|
|
this.tagbox = new AoPS.Community.Views.BlogItembox({
|
|
model: this.model,
|
|
});
|
|
this.$el.find("fieldset").append(this.tagbox.$el);
|
|
},
|
|
|
|
onClose: function () {
|
|
this.tagbox.close();
|
|
},
|
|
});
|
|
|
|
/**
|
|
* View for a single comment on a Blog Post.
|
|
*
|
|
* model : AoPS.Community.Models.Post
|
|
*
|
|
* Many functions are mixed in from AoPS.Community.Views.Post.
|
|
* This View is much simpler than Views.Post, and has a few bits that
|
|
* are a very different structure, so rather than extend Post (or the reverse),
|
|
* we mix in the functions we need.
|
|
*/
|
|
ComViews.BlogComment = AoPS.View.extend({
|
|
template_id: "#blog-comment-tpl",
|
|
|
|
className: "comment",
|
|
|
|
// TODO : Permanent link to post
|
|
initialize: function (options) {
|
|
this.topic = options.topic;
|
|
|
|
this.render();
|
|
this.listenTo(this.model, "change:deleted", this.onChangeDeleted);
|
|
this.listenTo(this.model, "hard_delete", this.removePostFromTopic);
|
|
|
|
this.listenTo(this.model, "change:post_rendered", this.render);
|
|
},
|
|
|
|
events: {
|
|
"click .blog-edit-post": "onClickEdit",
|
|
"click .blog-delete-post": "onClickDelete",
|
|
"click .blog-report-post": "onClickReport",
|
|
"click .cmty-post-deleted-info": "onClickDeletedInfo",
|
|
"click .cmty-undelete-post": "onClickUndelete",
|
|
"click .blog-direct-post-link": "onClickDirectLink",
|
|
},
|
|
|
|
render: function () {
|
|
var report_data = this.constructReportTemplateData(),
|
|
can_edit = this.topic.model.getPermission("c_can_edit"),
|
|
can_delete = this.topic.model.getPermission("c_can_delete"),
|
|
has_mod_actions = report_data.can_report || can_edit || can_delete;
|
|
|
|
this.$el.html(
|
|
this.getTemplate(this.template_id, {
|
|
lang_by: Lang["by"],
|
|
is_reported: report_data.is_reported,
|
|
highlight_report_button: report_data.highlight_report_button,
|
|
date: AoPS.Community.Utils.makePrettyTimeStatic(
|
|
this.model.get("post_time")
|
|
),
|
|
username: this.model.get("username"),
|
|
route_user:
|
|
AoPS.Community.Constants.user_path + this.model.get("poster_id"),
|
|
has_mod_actions: has_mod_actions,
|
|
can_report: report_data.can_report,
|
|
can_delete: can_delete,
|
|
can_undelete: this.model.get("topic").getPermission("c_can_undelete"),
|
|
|
|
can_edit: can_edit,
|
|
report_title: report_data.title,
|
|
lang_edit: Lang["blog-edit-post"],
|
|
lang_delete: Lang["blog-delete-post"],
|
|
lang_report: Lang["blog-report-post"],
|
|
})
|
|
);
|
|
this.first_post = this.model;
|
|
this.setVisibility();
|
|
this.finishRenderingPost();
|
|
},
|
|
|
|
finishRenderingPost: ComViews.TopicCellBlog.prototype.finishRenderingPost,
|
|
|
|
parseEditData: ComViews.TopicCellBlog.prototype.parseEditData,
|
|
|
|
parseAttachments: ComViews.TopicCellBlog.prototype.parseAttachments,
|
|
|
|
highlight: ComViews.TopicCellBlog.prototype.highlight,
|
|
|
|
onClickEdit: AoPS.Community.Views.Post.prototype.onClickEdit,
|
|
|
|
onClickDelete: AoPS.Community.Views.Post.prototype.onClickDelete,
|
|
|
|
onClickReport: AoPS.Community.Views.Post.prototype.onClickReport,
|
|
|
|
onClickDirectLink: AoPS.Community.Views.Post.prototype.onClickDirectLink,
|
|
|
|
constructReportTemplateData:
|
|
AoPS.Community.Views.Post.prototype.constructReportTemplateData,
|
|
|
|
setVisibility: AoPS.Community.Views.Post.prototype.setVisibility,
|
|
|
|
onClickDeletedInfo: AoPS.Community.Views.Post.prototype.onClickDeletedInfo,
|
|
|
|
onClickUndelete: AoPS.Community.Views.Post.prototype.onClickUndelete,
|
|
|
|
onChangeDeleted: AoPS.Community.Views.Post.prototype.onChangeDeleted,
|
|
|
|
removePostFromTopic:
|
|
AoPS.Community.Views.Post.prototype.removePostFromTopic,
|
|
});
|
|
|
|
/**
|
|
* A full blog post and its comments.
|
|
*
|
|
* model : AoPS.Community.Topic
|
|
*/
|
|
ComViews.BlogTopicFull = AoPS.View.extend({
|
|
postView: AoPS.Community.Views.BlogComment,
|
|
|
|
id: "main",
|
|
|
|
url_cmty_path: "",
|
|
|
|
initialize: function (options) {
|
|
var blog_post,
|
|
i,
|
|
post,
|
|
posts_length = this.model.get("posts").length;
|
|
this.focus_post_id = options.post_id;
|
|
|
|
this.reply_open = false;
|
|
this.reply_box = null;
|
|
|
|
// Post new topic
|
|
this.$el.html(
|
|
this.getTemplate("#blog-topic-list-tpl", {
|
|
can_post: this.model
|
|
.get("category")
|
|
.getPermission("c_can_start_topic"),
|
|
lang_post_new: Lang["blog-post-new-entry"],
|
|
})
|
|
);
|
|
|
|
// Initial blog post
|
|
blog_post = new ComViews.TopicCellBlog({
|
|
model: this.model,
|
|
category: this.model.get("category"),
|
|
});
|
|
this.$el.append(blog_post.$el);
|
|
|
|
this.$el.append(
|
|
this.getTemplate("#blog-comments-header-tpl", {
|
|
lang_post_comment: Lang["blog-post-comment"],
|
|
can_comment:
|
|
!this.model.get("locked") &&
|
|
this.model.getPermission("c_can_reply"), //TODO : what if user turns off replies?
|
|
})
|
|
);
|
|
if (this.model.getPermission("c_can_reply")) {
|
|
this.$reply_holder = this.$el.find(".cmty-reply-window");
|
|
}
|
|
|
|
this.$num_comments = this.$el.find(".blog-num-comments");
|
|
this.renderNumComments();
|
|
|
|
this.$loader = ComViews.buildBlogLoader();
|
|
|
|
this.$posts_box = this.$el.find(".blog-posts-box");
|
|
|
|
this.posts = [];
|
|
|
|
for (i = 1; i < posts_length; i++) {
|
|
post = this.model.get("posts").models[i];
|
|
if (
|
|
!post.get("deleted") ||
|
|
this.model.getPermission("c_can_read_deleted")
|
|
) {
|
|
this.addPost(post);
|
|
}
|
|
}
|
|
this.applyPostClasses();
|
|
this.listenTo(
|
|
this.model,
|
|
"change:all_posts_fetched",
|
|
this.onAllPostsFetched
|
|
);
|
|
this.listenTo(this.model, "change:num_posts", this.renderNumComments);
|
|
this.listenTo(this.model, "change:locked", this.onChangeLocked);
|
|
this.listenTo(this.model, "change:deleted", this.onDelete);
|
|
|
|
this.listenTo(this.model.get("posts"), "add", this.addPost);
|
|
if (!this.model.get("all_posts_fetched")) {
|
|
this.fetchAllPosts();
|
|
}
|
|
this.listenTo(
|
|
this.model.get("posts"),
|
|
"change:deleted",
|
|
this.onDeletedPost
|
|
);
|
|
// Router can trigger the reply to open
|
|
|
|
this.$el.on(
|
|
"open_reply",
|
|
_.bind(function () {
|
|
this.openReplyWindow();
|
|
}, this)
|
|
);
|
|
},
|
|
|
|
events: {
|
|
"click .blog-post-comment": "onClickPostComment",
|
|
"click .post-comment": "onClickPostComment",
|
|
"click a#post-new-entry": "onClickNewTopic",
|
|
},
|
|
|
|
onChangeLocked: function () {
|
|
var can_reply =
|
|
!this.model.get("locked") && this.model.getPermission("c_can_reply");
|
|
this.$el.find(".blog-post-comment").toggle(can_reply);
|
|
this.$el.find(".blog-reply-window").toggle(can_reply);
|
|
},
|
|
|
|
/**
|
|
* If the full topic has already been loaded prior to initialization, or if we
|
|
* somehow can navigate within the blog to a particular post_id, then
|
|
* we need onAddToPage to tell the already-created view to navigate to the
|
|
* correct post.
|
|
**/
|
|
onAddToPage: function (options) {
|
|
if (options.hasOwnProperty("post_id")) {
|
|
this.focus_post_id = options.post_id;
|
|
}
|
|
if (this.model.get("all_posts_fetched")) {
|
|
this.goToFocusPost();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* When user clicks comment, we open up the reply window.
|
|
**/
|
|
onClickPostComment: function (e) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
this.openReplyWindow();
|
|
},
|
|
|
|
/**
|
|
* Open the window in which users post comments to the blog.
|
|
**/
|
|
openReplyWindow: function () {
|
|
if (this.reply_open) {
|
|
return;
|
|
}
|
|
|
|
this.$reply_holder.addClass("blog-reply-open");
|
|
this.$reply_holder.show();
|
|
this.reply_open = true;
|
|
|
|
this.reply_box = new ComViews.NewReply({
|
|
topic_id: this.model.get("topic_id"),
|
|
category_id: this.model.get("category_id"),
|
|
topic: this.model,
|
|
has_add_to_feed: false,
|
|
has_email_subscribe: true,
|
|
master: this.model.get("master"),
|
|
onSubmit: _.bind(function () {
|
|
// Will probably edit when we have to deal with
|
|
// possible errors.
|
|
this.closeNewReply();
|
|
|
|
// Here, we throw a modal and watch for new_post_processed, then
|
|
this.listenToOnce(
|
|
this.model,
|
|
"new_post_processed",
|
|
this.onSubmittedPostProcessed
|
|
);
|
|
}, this),
|
|
onCancel: _.bind(function () {
|
|
this.closeNewReply();
|
|
}, this),
|
|
});
|
|
this.$reply_holder.append(this.reply_box.$el);
|
|
this.reply_box.$post_box.focus();
|
|
},
|
|
|
|
/**
|
|
* Once the submitted post is processed, an event will trigger a jump to that post
|
|
* (or to the oldest non-fetched post, which will almost always be this one).
|
|
**/
|
|
onSubmittedPostProcessed: function (response) {
|
|
AoPS.Ui.Modal.closeAllModals();
|
|
this.focus_post_id = response.post_id;
|
|
this.goToFocusPost();
|
|
},
|
|
|
|
/**
|
|
* closeNewReply resets the page to what it should look like without the NewReply window open.
|
|
*/
|
|
closeNewReply: function () {
|
|
this.reply_open = false;
|
|
this.$reply_holder.removeClass("blog-reply-open");
|
|
|
|
this.reply_box.close();
|
|
},
|
|
|
|
/**
|
|
* Once we've loaded everything, we spike the loader, set the classes on the posts,
|
|
* and set up any future adds to reset the post classes. We don't set that listener
|
|
* at the start because we don't want it to fire over and over on the initial post load.
|
|
**/
|
|
onAllPostsFetched: function () {
|
|
this.$loader.detach();
|
|
this.applyPostClasses();
|
|
this.listenTo(this.model.get("posts"), "add", this.applyPostClasses);
|
|
|
|
if (this.focus_post_id > 0) {
|
|
this.goToFocusPost();
|
|
}
|
|
},
|
|
|
|
onClickNewTopic: function (e) {
|
|
this.openNewBlogPost(this.model.get("category"));
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
},
|
|
|
|
openNewBlogPost: ComViews.TopicsListBlog.prototype.openNewBlogPost,
|
|
|
|
/**
|
|
* If we are trying to load to a particular post, here's where we move the window
|
|
* scroll to the appropriate place.
|
|
**/
|
|
goToFocusPost: function () {
|
|
var target_post_cell,
|
|
target_id = this.focus_post_id;
|
|
if (target_id === 0) {
|
|
return;
|
|
}
|
|
|
|
target_post_cell = _.find(this.posts, function (post_cell) {
|
|
return post_cell.model.get("post_id") === target_id;
|
|
});
|
|
|
|
if (_.isUndefined(target_post_cell)) {
|
|
return;
|
|
}
|
|
window.scrollTo(0, target_post_cell.$el.offset().top);
|
|
},
|
|
|
|
removePost: ComViews.TopicFull.prototype.removePost,
|
|
/**
|
|
* If a post is deleted, we need to remove its cell from our view, and
|
|
* reset the post classes to keep the zebra pattern.
|
|
**/
|
|
onDeletedPost: function (post) {
|
|
return;
|
|
// var deleted_post = _.findWhere(this.posts, {
|
|
// model: post,
|
|
// });
|
|
|
|
// if (!_.isUndefined(deleted_post)) {
|
|
// this.posts = _.without(this.posts, deleted_post);
|
|
// deleted_post.close();
|
|
// this.applyPostClasses();
|
|
// }
|
|
},
|
|
|
|
/**
|
|
* We apply the same zebra pattern here as in the shoutbox.
|
|
**/
|
|
applyPostClasses:
|
|
AoPS.Community.Views.BlogShoutbox.prototype.applyPostClasses,
|
|
|
|
/**
|
|
* Build the number of comments line in the initial post box.
|
|
**/
|
|
renderNumComments: function () {
|
|
var num_comments = this.model.get("num_posts") - 1;
|
|
this.$num_comments.html(
|
|
num_comments +
|
|
(num_comments === 1 ? Lang["blog-Comment"] : Lang["blog-Comments"])
|
|
);
|
|
},
|
|
|
|
addPost: AoPS.Community.Views.TopicFull.prototype.addPost,
|
|
|
|
/**
|
|
* No deleted posts should make it to the front end, but just in case...
|
|
*/
|
|
filterPost: function (post) {
|
|
return !post.get("deleted") || this.model.getPermission("c_can_undelete");
|
|
},
|
|
|
|
/**
|
|
* Put up a loader and then go get all the comments for this blog post.
|
|
**/
|
|
fetchAllPosts: function () {
|
|
this.$posts_box.append(this.$loader);
|
|
/*this.model.fetchInitialPosts({
|
|
fetch_all : true
|
|
});*/
|
|
},
|
|
|
|
onDelete: ComViews.TopicFull.prototype.onDelete,
|
|
|
|
navigateAfterDelete: ComViews.TopicFull.prototype.navigateAfterDelete,
|
|
});
|
|
|
|
/**
|
|
* The only difference between a new blog post and a new topic in a forum is the
|
|
* topic_type field, which we want when we submit this post for processing.
|
|
**/
|
|
ComViews.NewBlogPost = ComViews.NewTopic.extend({
|
|
topic_type: "blog_post",
|
|
|
|
has_add_to_feed: false,
|
|
|
|
sending_blocker: Lang["new-blog-post-send-blocker"],
|
|
|
|
has_email_subscribe: true,
|
|
});
|
|
|
|
return ComViews;
|
|
})(AoPS.Community.Views || {});
|