This repository has been archived on 2023-03-18. You can view files and clone it, but cannot push or open issues or pull requests.
osr-discourse-src/app/assets/javascripts/discourse/models/topic.js

469 lines
13 KiB
JavaScript

(function() {
Discourse.Topic = Discourse.Model.extend(Discourse.Presence, {
categoriesBinding: 'Discourse.site.categories',
fewParticipants: (function() {
if (!this.present('participants')) {
return null;
}
return this.get('participants').slice(0, 3);
}).property('participants'),
canConvertToRegular: (function() {
var a;
a = this.get('archetype');
return a !== 'regular' && a !== 'private_message';
}).property('archetype'),
convertArchetype: function(archetype) {
var a;
a = this.get('archetype');
if (a !== 'regular' && a !== 'private_message') {
this.set('archetype', 'regular');
return jQuery.post(this.get('url'), {
_method: 'put',
archetype: 'regular'
});
}
},
category: (function() {
if (this.get('categories')) {
return this.get('categories').findProperty('name', this.get('categoryName'));
}
}).property('categoryName', 'categories'),
url: (function() {
var slug;
slug = this.get('slug');
if (slug.isBlank()) {
slug = "topic";
}
return "/t/" + slug + "/" + (this.get('id'));
}).property('id', 'slug'),
/* Helper to build a Url with a post number
*/
urlForPostNumber: function(postNumber) {
var url;
url = this.get('url');
if (postNumber && (postNumber > 1)) {
url += "/" + postNumber;
}
return url;
},
lastReadUrl: (function() {
return this.urlForPostNumber(this.get('last_read_post_number'));
}).property('url', 'last_read_post_number'),
lastPostUrl: (function() {
return this.urlForPostNumber(this.get('highest_post_number'));
}).property('url', 'highest_post_number'),
/* The last post in the topic
*/
lastPost: function() {
return this.get('posts').last();
},
postsChanged: (function() {
var last, posts;
posts = this.get('posts');
last = posts.last();
if (!(last && last.set && !last.lastPost)) {
return;
}
posts.each(function(p) {
if (p.lastPost) {
return p.set('lastPost', false);
}
});
last.set('lastPost', true);
return true;
}).observes('posts.@each', 'posts'),
/* The amount of new posts to display. It might be different than what the server
*/
/* tells us if we are still asynchronously flushing our "recently read" data.
*/
/* So take what the browser has seen into consideration.
*/
displayNewPosts: (function() {
var delta, highestSeen, result;
if (highestSeen = Discourse.get('highestSeenByTopic')[this.get('id')]) {
delta = highestSeen - this.get('last_read_post_number');
if (delta > 0) {
result = this.get('new_posts') - delta;
if (result < 0) {
result = 0;
}
return result;
}
}
return this.get('new_posts');
}).property('new_posts', 'id'),
/* The coldmap class for the age of the topic
*/
ageCold: (function() {
var createdAt, createdAtDays, daysSinceEpoch, lastPost, nowDays;
if (!(lastPost = this.get('last_posted_at'))) {
return;
}
if (!(createdAt = this.get('created_at'))) {
return;
}
daysSinceEpoch = function(dt) {
/* 1000 * 60 * 60 * 24 = days since epoch
*/
return dt.getTime() / 86400000;
};
/* Show heat on age
*/
nowDays = daysSinceEpoch(new Date());
createdAtDays = daysSinceEpoch(new Date(createdAt));
if (daysSinceEpoch(new Date(lastPost)) > nowDays - 90) {
if (createdAtDays < nowDays - 60) {
return 'coldmap-high';
}
if (createdAtDays < nowDays - 30) {
return 'coldmap-med';
}
if (createdAtDays < nowDays - 14) {
return 'coldmap-low';
}
}
return null;
}).property('age', 'created_at'),
archetypeObject: (function() {
return Discourse.get('site.archetypes').findProperty('id', this.get('archetype'));
}).property('archetype'),
isPrivateMessage: (function() {
return this.get('archetype') === 'private_message';
}).property('archetype'),
/* Does this topic only have a single post?
*/
singlePost: (function() {
return this.get('posts_count') === 1;
}).property('posts_count'),
toggleStatus: function(property) {
this.toggleProperty(property);
return jQuery.post("" + (this.get('url')) + "/status", {
_method: 'put',
status: property,
enabled: this.get(property) ? 'true' : 'false'
});
},
toggleStar: function() {
var _this = this;
this.toggleProperty('starred');
return jQuery.ajax({
url: "" + (this.get('url')) + "/star",
type: 'PUT',
data: {
starred: this.get('starred') ? true : false
},
error: function(error) {
var errors;
_this.toggleProperty('starred');
errors = jQuery.parseJSON(error.responseText).errors;
return bootbox.alert(errors[0]);
}
});
},
/* Save any changes we've made to the model
*/
save: function() {
/* Don't save unless we can
*/
if (!this.get('can_edit')) {
return;
}
return jQuery.post(this.get('url'), {
_method: 'put',
title: this.get('title'),
category: this.get('category.name')
});
},
/* Reset our read data for this topic
*/
resetRead: function(callback) {
return jQuery.ajax("/t/" + (this.get('id')) + "/timings", {
type: 'DELETE',
success: function() {
return typeof callback === "function" ? callback() : void 0;
}
});
},
/* Invite a user to this topic
*/
inviteUser: function(user) {
return jQuery.ajax({
type: 'POST',
url: "/t/" + (this.get('id')) + "/invite",
data: {
user: user
}
});
},
/* Delete this topic
*/
"delete": function(callback) {
return jQuery.ajax("/t/" + (this.get('id')), {
type: 'DELETE',
success: function() {
return typeof callback === "function" ? callback() : void 0;
}
});
},
/* Load the posts for this topic
*/
loadPosts: function(opts) {
var _this = this;
if (!opts) {
opts = {};
}
/* Load the first post by default
*/
if (!opts.bestOf) {
if (!opts.nearPost) opts.nearPost = 1
}
/* If we already have that post in the DOM, jump to it
*/
if (Discourse.TopicView.scrollTo(this.get('id'), opts.nearPost)) {
return;
}
return Discourse.Topic.find(this.get('id'), {
nearPost: opts.nearPost,
bestOf: opts.bestOf,
trackVisit: opts.trackVisit
}).then(function(result) {
/* If loading the topic succeeded...
*/
/* Update the slug if different
*/
var closestPostNumber, lastPost, postDiff;
if (result.slug) {
_this.set('slug', result.slug);
}
/* If we want to scroll to a post that doesn't exist, just pop them to the closest
*/
/* one instead. This is likely happening due to a deleted post.
*/
opts.nearPost = parseInt(opts.nearPost, 10);
closestPostNumber = 0;
postDiff = Number.MAX_VALUE;
result.posts.each(function(p) {
var diff;
diff = Math.abs(p.post_number - opts.nearPost);
if (diff < postDiff) {
postDiff = diff;
closestPostNumber = p.post_number;
if (diff === 0) {
return false;
}
}
});
opts.nearPost = closestPostNumber;
if (_this.get('participants')) {
_this.get('participants').clear();
}
if (result.suggested_topics) {
_this.set('suggested_topics', Em.A());
}
_this.mergeAttributes(result, {
suggested_topics: Discourse.Topic
});
_this.set('posts', Em.A());
if (opts.trackVisit && result.draft && result.draft.length > 0) {
Discourse.openComposer({
draft: Discourse.Draft.getLocal(result.draft_key, result.draft),
draftKey: result.draft_key,
draftSequence: result.draft_sequence,
topic: _this,
ignoreIfChanged: true
});
}
/* Okay this is weird, but let's store the length of the next post
*/
/* when there
*/
lastPost = null;
result.posts.each(function(p) {
var post;
p.scrollToAfterInsert = opts.nearPost;
post = Discourse.Post.create(p);
post.set('topic', _this);
_this.get('posts').pushObject(post);
lastPost = post;
});
return _this.set('loaded', true);
}, function(result) {
_this.set('missing', true);
return _this.set('message', Em.String.i18n('topic.not_found.description'));
});
},
notificationReasonText: (function() {
var locale_string;
locale_string = "topic.notifications.reasons." + this.notification_level;
if (typeof this.notifications_reason_id === 'number') {
locale_string += "_" + this.notifications_reason_id;
}
return Em.String.i18n(locale_string, {
username: Discourse.currentUser.username.toLowerCase()
});
}).property('notifications_reason_id'),
updateNotifications: function(v) {
this.set('notification_level', v);
this.set('notifications_reason_id', null);
return jQuery.ajax({
url: "/t/" + (this.get('id')) + "/notifications",
type: 'POST',
data: {
notification_level: v
}
});
},
/* use to add post to topics protecting from dupes
*/
pushPosts: function(newPosts) {
var map, posts;
map = {};
posts = this.get('posts');
posts.each(function(p) {
map["" + p.post_number] = true;
});
return newPosts.each(function(p) {
if (!map[p.get('post_number')]) {
return posts.pushObject(p);
}
});
},
/* Is the reply to a post directly below it?
*/
isReplyDirectlyBelow: function(post) {
var postBelow, posts;
posts = this.get('posts');
if (!posts) {
return;
}
postBelow = posts[posts.indexOf(post) + 1];
/* If the post directly below's reply_to_post_number is our post number, it's
considered directly below. */
return (postBelow ? postBelow.get('reply_to_post_number') : void 0) === post.get('post_number');
}
});
window.Discourse.Topic.reopenClass({
NotificationLevel: {
WATCHING: 3,
TRACKING: 2,
REGULAR: 1,
MUTE: 0
},
/* Load a topic, but accepts a set of filters
*/
/* options:
*/
/* onLoad - the callback after the topic is loaded
*/
find: function(topicId, opts) {
var data, promise, url,
_this = this;
url = "/t/" + topicId;
if (opts.nearPost) {
url += "/" + opts.nearPost;
}
data = {};
if (opts.postsAfter) {
data.posts_after = opts.postsAfter;
}
if (opts.postsBefore) {
data.posts_before = opts.postsBefore;
}
if (opts.trackVisit) {
data.track_visit = true;
}
/* Add username filters if we have them
*/
if (opts.userFilters && opts.userFilters.length > 0) {
data.username_filters = [];
opts.userFilters.forEach(function(username) {
return data.username_filters.push(username);
});
}
/* Add the best of filter if we have it
*/
if (opts.bestOf === true) {
data.best_of = true;
}
/* Check the preload store. If not, load it via JSON
*/
promise = new RSVP.Promise();
PreloadStore.get("topic_" + topicId, function() {
return jQuery.getJSON(url + ".json", data);
}).then(function(result) {
var first;
first = result.posts.first();
if (first && opts && opts.bestOf) {
first.bestOfFirst = true;
}
return promise.resolve(result);
}, function(result) {
return promise.reject(result);
});
return promise;
},
/* Create a topic from posts
*/
movePosts: function(topicId, title, postIds) {
return jQuery.ajax("/t/" + topicId + "/move-posts", {
type: 'POST',
data: {
title: title,
post_ids: postIds
}
});
},
create: function(obj, topicView) {
var _this = this;
return Object.tap(this._super(obj), function(result) {
if (result.participants) {
result.participants = result.participants.map(function(u) {
return Discourse.User.create(u);
});
result.fewParticipants = Em.A();
return result.participants.each(function(p) {
if (result.fewParticipants.length >= 8) {
return false;
}
result.fewParticipants.pushObject(p);
return true;
});
}
});
}
});
}).call(this);