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.coffee
2013-02-05 14:16:51 -05:00

308 lines
9.3 KiB
CoffeeScript

Discourse.Topic = Discourse.Model.extend Discourse.Presence,
categoriesBinding: 'Discourse.site.categories'
fewParticipants: (->
return null unless @present('participants')
return @get('participants').slice(0, 3)
).property('participants')
canConvertToRegular: (->
a = @get('archetype')
a != 'regular' && a != 'private_message'
).property('archetype')
convertArchetype: (archetype) ->
a = @get('archetype')
if a != 'regular' && a != 'private_message'
@set('archetype','regular')
jQuery.post @get('url'),
_method: 'put'
archetype: 'regular'
category: (->
if @get('categories')
@get('categories').findProperty('name', @get('categoryName'))
).property('categoryName', 'categories')
url: (->
"/t/#{@get('slug')}/#{@get('id')}"
).property('id', 'slug')
# Helper to build a Url with a post number
urlForPostNumber: (postNumber) ->
url = @get('url')
url += "/#{postNumber}" if postNumber and (postNumber > 1)
url
lastReadUrl: (-> @urlForPostNumber(@get('last_read_post_number')) ).property('url', 'last_read_post_number')
lastPostUrl: (-> @urlForPostNumber(@get('highest_post_number')) ).property('url', 'highest_post_number')
# The last post in the topic
lastPost: -> @get('posts').last()
postsChanged: ( ->
posts = @get('posts')
last = posts.last()
return unless last && last.set && !last.lastPost
posts.each (p)->
p.set('lastPost', false) if p.lastPost
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: (->
if highestSeen = Discourse.get('highestSeenByTopic')[@get('id')]
delta = highestSeen - @get('last_read_post_number')
if delta > 0
result = (@get('new_posts') - delta)
result = 0 if result < 0
return result
@get('new_posts')
).property('new_posts', 'id')
displayTitle: (->
return null unless @get('title')
return @get('title') unless @get('category')
matches = @get('title').match(/^([a-zA-Z0-9]+\: )?(.*)/)
return matches[2]
).property('title')
# The coldmap class for the age of the topic
ageCold: (->
return unless lastPost = @get('last_posted_at')
return unless createdAt = @get('created_at')
daysSinceEpoch = (dt) ->
# 1000 * 60 * 60 * 24 = days since epoch
dt.getTime() / 86400000
# Show heat on age
nowDays = daysSinceEpoch(new Date())
createdAtDays = daysSinceEpoch(new Date(createdAt))
if daysSinceEpoch(new Date(lastPost)) > nowDays - 90
return 'coldmap-high' if createdAtDays < nowDays - 60
return 'coldmap-med' if createdAtDays < nowDays - 30
return 'coldmap-low' if createdAtDays < nowDays - 14
null
).property('age', 'created_at')
archetypeObject: (->
Discourse.get('site.archetypes').findProperty('id', @get('archetype'))
).property('archetype')
isPrivateMessage: (->
@get('archetype') == 'private_message'
).property('archetype')
# Does this topic only have a single post?
singlePost: (->
@get('posts_count') == 1
).property('posts_count')
toggleStatus: (property) ->
@toggleProperty(property)
jQuery.post "#{@get('url')}/status", _method: 'put', status: property, enabled: if @get(property) then 'true' else 'false'
toggleStar: ->
@toggleProperty('starred')
jQuery.ajax
url: "#{@get('url')}/star"
type: 'PUT'
data:
starred: if @get('starred') then true else false
error: (error) =>
@toggleProperty('starred')
errors = jQuery.parseJSON(error.responseText).errors
bootbox.alert(errors[0])
# Save any changes we've made to the model
save: ->
# Don't save unless we can
return unless @get('can_edit')
jQuery.post @get('url'),
_method: 'put'
title: @get('title')
category: @get('category.name')
# Reset our read data for this topic
resetRead: (callback) ->
$.ajax "/t/#{@get('id')}/timings",
type: 'DELETE'
success: -> callback?()
# Invite a user to this topic
inviteUser: (user) ->
$.ajax
type: 'POST'
url: "/t/#{@get('id')}/invite",
data: {user: user}
# Delete this topic
delete: (callback) ->
$.ajax "/t/#{@get('id')}",
type: 'DELETE'
success: -> callback?()
# Load the posts for this topic
loadPosts: (opts) ->
opts = {} unless opts
# Load the first post by default
opts.nearPost ||= 1 unless opts.bestOf
# If we already have that post in the DOM, jump to it
return if Discourse.TopicView.scrollTo @get('id'), opts.nearPost
Discourse.Topic.find @get('id'),
nearPost: opts.nearPost
bestOf: opts.bestOf
trackVisit: opts.trackVisit
.then (result) =>
# If loading the topic succeeded...
# Update the slug if different
@set('slug', result.slug) if 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)
closestPostNumber = 0
postDiff = Number.MAX_VALUE
result.posts.each (p) ->
diff = Math.abs(p.post_number - opts.nearPost)
if diff < postDiff
postDiff = diff
closestPostNumber = p.post_number
return false if diff == 0
opts.nearPost = closestPostNumber
@get('participants').clear() if @get('participants')
@set('suggested_topics', Em.A()) if result.suggested_topics
@mergeAttributes result, suggested_topics: Discourse.Topic
@set('posts', Em.A())
if opts.trackVisit and result.draft and result.draft.length > 0
Discourse.openComposer
draft: Discourse.Draft.getLocal(result.draft_key, result.draft)
draftKey: result.draft_key
draftSequence: result.draft_sequence
topic: @
ignoreIfChanged: true
# Okay this is weird, but let's store the length of the next post
# when there
lastPost = null
result.posts.each (p) =>
# Determine whether there is a short reply below
if (lastPost && lastPost.get('reply_count') == 1) && (p.reply_to_post_number == lastPost.get('post_number')) && (p.cooked.length < Discourse.SiteSettings.max_length_show_reply)
lastPost.set('replyFollowing', true)
p.scrollToAfterInsert = opts.nearPost
post = Discourse.Post.create(p)
post.set('topic', @)
@get('posts').pushObject(post)
lastPost = post
@set('loaded', true)
, (result) =>
@set('missing', true)
@set('message', Em.String.i18n('topic.not_found.description'))
notificationReasonText: (->
locale_string = "topic.notifications.reasons.#{@notification_level}"
if typeof @notifications_reason_id == 'number'
locale_string += "_#{@notifications_reason_id}"
Em.String.i18n(locale_string, username: Discourse.currentUser.username.toLowerCase())
).property('notifications_reason_id')
updateNotifications: (v)->
@set('notification_level', v)
@set('notifications_reason_id', null)
$.ajax
url: "/t/#{@get('id')}/notifications"
type: 'POST'
data: {notification_level: v}
# use to add post to topics protecting from dupes
pushPosts: (newPosts)->
map = {}
posts = @get('posts')
posts.each (p)->
map["#{p.post_number}"] = true
newPosts.each (p)->
posts.pushObject(p) unless map[p.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: (topicId, opts) ->
url = "/t/#{topicId}"
url += "/#{opts.nearPost}" if opts.nearPost
data = {}
data.posts_after = opts.postsAfter if opts.postsAfter
data.posts_before = opts.postsBefore if opts.postsBefore
data.track_visit = true if opts.trackVisit
# Add username filters if we have them
if opts.userFilters and opts.userFilters.length > 0
data.username_filters = []
opts.userFilters.forEach (username) => data.username_filters.push(username)
# Add the best of filter if we have it
data.best_of = true if opts.bestOf == true
# Check the preload store. If not, load it via JSON
promise = new RSVP.Promise()
PreloadStore.get("topic_#{topicId}", -> jQuery.getJSON url + ".json", data).then (result) ->
first = result.posts.first()
first.bestOfFirst = true if first and opts and opts.bestOf
promise.resolve(result)
, (result) -> promise.reject(result)
promise
# Create a topic from posts
movePosts: (topicId, title, postIds) ->
$.ajax "/t/#{topicId}/move-posts",
type: 'POST'
data:
title: title
post_ids: postIds
create: (obj, topicView) ->
Object.tap @_super(obj), (result) =>
if result.participants
result.participants = result.participants.map (u) => Discourse.User.create(u)
result.fewParticipants = Em.A()
result.participants.each (p) =>
return false if result.fewParticipants.length >= 8
result.fewParticipants.pushObject(p)
true