tent-status 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +21 -0
- data/.kick +8 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +232 -0
- data/LICENSE.txt +22 -0
- data/Procfile +1 -0
- data/README.md +14 -0
- data/Rakefile +43 -0
- data/assets/images/.gitkeep +0 -0
- data/assets/images/chosen-sprite.png +0 -0
- data/assets/images/glyphicons-halflings-white.png +0 -0
- data/assets/images/glyphicons-halflings.png +0 -0
- data/assets/javascripts/.gitkeep +0 -0
- data/assets/javascripts/application.js.coffee +122 -0
- data/assets/javascripts/backbone.js +1443 -0
- data/assets/javascripts/backbone_sync.js.coffee +60 -0
- data/assets/javascripts/boot.js.coffee +3 -0
- data/assets/javascripts/chosen.jquery.js +1129 -0
- data/assets/javascripts/collections/.gitkeep +0 -0
- data/assets/javascripts/collections/followers.js.coffee +5 -0
- data/assets/javascripts/collections/followings.js.coffee +5 -0
- data/assets/javascripts/collections/groups.js.coffee +5 -0
- data/assets/javascripts/collections/posts.js.coffee +5 -0
- data/assets/javascripts/fetch_pool.js.coffee +25 -0
- data/assets/javascripts/helpers/.gitkeep +0 -0
- data/assets/javascripts/helpers/formatting.js.coffee +17 -0
- data/assets/javascripts/hogan.js +706 -0
- data/assets/javascripts/http.js.coffee +18 -0
- data/assets/javascripts/jquery.js +9301 -0
- data/assets/javascripts/models/.gitkeep +0 -0
- data/assets/javascripts/models/follower.js.coffee +27 -0
- data/assets/javascripts/models/following.js.coffee +27 -0
- data/assets/javascripts/models/group.js.coffee +4 -0
- data/assets/javascripts/models/post.js.coffee +32 -0
- data/assets/javascripts/models/profile.js.coffee +30 -0
- data/assets/javascripts/moment.js +1106 -0
- data/assets/javascripts/paginator.js.coffee +67 -0
- data/assets/javascripts/router.js.coffee +71 -0
- data/assets/javascripts/routers/.gitkeep +0 -0
- data/assets/javascripts/routers/followers.js.coffee +14 -0
- data/assets/javascripts/routers/followings.js.coffee +14 -0
- data/assets/javascripts/routers/posts.js.coffee +98 -0
- data/assets/javascripts/templates/.gitkeep +0 -0
- data/assets/javascripts/templates/_follower.js.mustache.slim +17 -0
- data/assets/javascripts/templates/_following.js.mustache.slim +17 -0
- data/assets/javascripts/templates/_new_post_form.js.mustache.slim +10 -0
- data/assets/javascripts/templates/_post.js.mustache.slim +10 -0
- data/assets/javascripts/templates/_post_inner.js.mustache.slim +71 -0
- data/assets/javascripts/templates/_profile_stats.js.mustache.slim +11 -0
- data/assets/javascripts/templates/_reply_form.js.mustache.slim +13 -0
- data/assets/javascripts/templates/conversation.js.mustache.slim +13 -0
- data/assets/javascripts/templates/followers.js.mustache.slim +19 -0
- data/assets/javascripts/templates/followings.js.mustache.slim +22 -0
- data/assets/javascripts/templates/posts.js.mustache.slim +25 -0
- data/assets/javascripts/templates/profile.js.mustache.slim +39 -0
- data/assets/javascripts/underscore.js +1059 -0
- data/assets/javascripts/view.js.coffee +140 -0
- data/assets/javascripts/views/.gitkeep +0 -0
- data/assets/javascripts/views/container.js.coffee +6 -0
- data/assets/javascripts/views/expanding_textarea.js.coffee +14 -0
- data/assets/javascripts/views/fetch_posts_pool.js.coffee +61 -0
- data/assets/javascripts/views/follower_groups_form.js.coffee +26 -0
- data/assets/javascripts/views/followers.js.coffee +28 -0
- data/assets/javascripts/views/following_groups_form.js.coffee +25 -0
- data/assets/javascripts/views/followings.js.coffee +27 -0
- data/assets/javascripts/views/new_following_form.js.coffee +15 -0
- data/assets/javascripts/views/new_post_form.js.coffee +178 -0
- data/assets/javascripts/views/post.js.coffee +139 -0
- data/assets/javascripts/views/posts.js.coffee +55 -0
- data/assets/javascripts/views/posts/conversation.js.coffee +18 -0
- data/assets/javascripts/views/profile.js.coffee +29 -0
- data/assets/javascripts/views/profile_follow_button.js.coffee +29 -0
- data/assets/javascripts/views/profile_stats.js.coffee +38 -0
- data/assets/javascripts/views/remove_follower_btn.js.coffee +18 -0
- data/assets/javascripts/views/reply_post_form.js.coffee +30 -0
- data/assets/javascripts/views/unfollow_btn.js.coffee +18 -0
- data/assets/stylesheets/.gitkeep +0 -0
- data/assets/stylesheets/application.css.sass +117 -0
- data/assets/stylesheets/bootstrap-responsive.css +1040 -0
- data/assets/stylesheets/bootstrap.css.erb +5624 -0
- data/assets/stylesheets/chosen.css.erb +397 -0
- data/config.ru +14 -0
- data/config/asset_sync.rb +12 -0
- data/config/evergreen.rb +16 -0
- data/lib/tent-status.rb +6 -0
- data/lib/tent-status/app.rb +263 -0
- data/lib/tent-status/models/user.rb +39 -0
- data/lib/tent-status/sprockets/environment.rb +25 -0
- data/lib/tent-status/sprockets/helpers.rb +5 -0
- data/lib/tent-status/views/application.slim +69 -0
- data/lib/tent-status/views/auth.slim +8 -0
- data/tent-status.gemspec +34 -0
- metadata +415 -0
@@ -0,0 +1,140 @@
|
|
1
|
+
# Custom View Class extending Backbone.View
|
2
|
+
#
|
3
|
+
# Sample View Class:
|
4
|
+
# class TentStatus.Views.DashboardArticles extends TentStatus.View
|
5
|
+
# templateName: 'foo/bar'
|
6
|
+
#
|
7
|
+
# # e.g. dashboard/_header is available as @partials.header
|
8
|
+
# partialNames: ['baz/_header']
|
9
|
+
#
|
10
|
+
# # wait for attributes to be set with @set before rendering
|
11
|
+
# dependentRenderAttributes: ['foos']
|
12
|
+
#
|
13
|
+
# initialize: (options) ->
|
14
|
+
# super
|
15
|
+
#
|
16
|
+
# # Custom view initialization, e.g.:
|
17
|
+
# @on 'ready', @bindEvents
|
18
|
+
#
|
19
|
+
# # setup context object passed to template
|
20
|
+
# # context is the data object for the view
|
21
|
+
# # See (mustache syntax): http://mustache.github.com/mustache.5.html
|
22
|
+
# # See (hogan example): http://twitter.github.com/hogan.js/
|
23
|
+
# context: =>
|
24
|
+
# foos: foos.toJSON()
|
25
|
+
#
|
26
|
+
# render: =>
|
27
|
+
# # do anything special here
|
28
|
+
#
|
29
|
+
# super
|
30
|
+
|
31
|
+
class TentStatus.View extends Backbone.View
|
32
|
+
# simpler version of @set and @get than given with Backbone.Model
|
33
|
+
# @set sets the key directly on the View instance
|
34
|
+
# it's used to pass data from the router to the view
|
35
|
+
set: (key, val) =>
|
36
|
+
@[key] = val
|
37
|
+
@trigger "change:#{key}"
|
38
|
+
|
39
|
+
# see render method in above sample
|
40
|
+
get: (key) =>
|
41
|
+
@[key]
|
42
|
+
|
43
|
+
# fetch template and partials
|
44
|
+
# once loaded @template will hold the compiled hogan template specified with @templateName
|
45
|
+
# and @partials will hold all compiled templates defined in @partialNames
|
46
|
+
initialize: (options) ->
|
47
|
+
@container ||= TentStatus.Views.container
|
48
|
+
|
49
|
+
unless @container
|
50
|
+
TentStatus.devWarning @, "You need to define @container View"
|
51
|
+
|
52
|
+
# fetch main template
|
53
|
+
if @templateName
|
54
|
+
TentStatus.fetchTemplate @templateName, (@template) =>
|
55
|
+
@trigger 'template:load'
|
56
|
+
|
57
|
+
# load all partials listed in partialNames
|
58
|
+
if @partialNames
|
59
|
+
@partials = {}
|
60
|
+
for p in @partialNames
|
61
|
+
do (p) =>
|
62
|
+
TentStatus.fetchTemplate p, (template) =>
|
63
|
+
name = @getPartialName(p)
|
64
|
+
@partials[name] = template
|
65
|
+
@trigger "partials:#{name}:load"
|
66
|
+
|
67
|
+
@on 'ready', @bindViews
|
68
|
+
@on 'ready', @bindEvents
|
69
|
+
|
70
|
+
loadMore: (key) =>
|
71
|
+
@get(key)?.nextPage()
|
72
|
+
|
73
|
+
bindEvents: =>
|
74
|
+
_.each $('.btn.load-more', @container?.el), (el) =>
|
75
|
+
viewKey = $(el).attr('data-key')
|
76
|
+
|
77
|
+
$(el).hide() if @get(viewKey)?.onLastPage
|
78
|
+
@get(viewKey)?.on 'fetch:start', => $(el).hide()
|
79
|
+
@get(viewKey)?.on 'fetch:success', => $(el).show() unless @get(viewKey)?.onLastPage
|
80
|
+
|
81
|
+
$(el).off().on 'click', (=> @loadMore viewKey)
|
82
|
+
|
83
|
+
bindViews: =>
|
84
|
+
_.each $('[data-view]', @container?.el), (el) =>
|
85
|
+
viewClassName = $(el).attr 'data-view'
|
86
|
+
if viewClass = TentStatus.Views[viewClassName]
|
87
|
+
view = new viewClass el: el, parentView: @
|
88
|
+
else
|
89
|
+
TentStatus.devWarning @, "TentStatus.Views.#{viewClassName} is not defined!"
|
90
|
+
console.log el
|
91
|
+
|
92
|
+
getPartialName: (path) =>
|
93
|
+
path.replace(/.+\/_(.+)$/, "$1")
|
94
|
+
|
95
|
+
# called before fetching data in the router
|
96
|
+
empty: =>
|
97
|
+
@container?.render("")
|
98
|
+
|
99
|
+
context: =>
|
100
|
+
TentStatus.devWarning @, "You need to override context in your view class!"
|
101
|
+
{}
|
102
|
+
|
103
|
+
# wait for @template and @partials to load
|
104
|
+
# then render @template with @context and @partials
|
105
|
+
# and insert html into @container.el
|
106
|
+
render: =>
|
107
|
+
# wait for template to be loaded
|
108
|
+
if @templateName
|
109
|
+
unless @template
|
110
|
+
@once 'template:load', => @render(arguments...)
|
111
|
+
return false
|
112
|
+
|
113
|
+
# wait for partials to be loaded
|
114
|
+
if @partialNames
|
115
|
+
for p in @partialNames
|
116
|
+
name = @getPartialName(p)
|
117
|
+
unless @partials[name]
|
118
|
+
@once "partials:#{name}:load", => @render(arguments...)
|
119
|
+
return false
|
120
|
+
|
121
|
+
# wait for data to be loaded
|
122
|
+
if @dependentRenderAttributes
|
123
|
+
for key in @dependentRenderAttributes
|
124
|
+
if @get(key) == null
|
125
|
+
@once "change:#{key}", => @render(arguments...)
|
126
|
+
return false
|
127
|
+
|
128
|
+
context = _.extend {
|
129
|
+
authenticated: TentStatus.authenticated
|
130
|
+
guest_authenticated: TentStatus.guest_authenticated
|
131
|
+
}, @context()
|
132
|
+
|
133
|
+
html = @template.render(context, @partials)
|
134
|
+
if @container
|
135
|
+
@container.render(html)
|
136
|
+
@trigger 'ready'
|
137
|
+
true
|
138
|
+
else
|
139
|
+
html
|
140
|
+
|
File without changes
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class TentStatus.Views.ExpandingTextArea extends Backbone.View
|
2
|
+
initialize: ->
|
3
|
+
@$el.on 'keyup', @adjustSize
|
4
|
+
|
5
|
+
@initialHeight = @$el.height()
|
6
|
+
|
7
|
+
adjustSize: (shouldExecute = false) =>
|
8
|
+
@_padding ||= (parseInt(@$el.css 'padding-top') || 0) + (parseInt(@$el.css 'padding-bottom') || 0)
|
9
|
+
scrollY = window.scrollY
|
10
|
+
scrollX = window.scrollX
|
11
|
+
@$el.height(0)
|
12
|
+
@$el.css('height', "#{ Math.max(@el.scrollHeight - @_padding, @initialHeight) }px")
|
13
|
+
window.scrollTo scrollX, scrollY
|
14
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class TentStatus.Views.FetchPostsPool extends Backbone.View
|
2
|
+
initialize: (options = {}) ->
|
3
|
+
@parentView = options.parentView
|
4
|
+
|
5
|
+
@$numNewPosts = ($ '.num_new_posts', @$el)
|
6
|
+
@numNewPosts = 0
|
7
|
+
|
8
|
+
@$postsList = ($ 'ul.posts', @parentView.container.$el)
|
9
|
+
|
10
|
+
@sinceId = @parentView.posts.first()?.get('id')
|
11
|
+
@pool = new TentStatus.FetchPool( new TentStatus.Collections.Posts, { sinceId: @sinceId })
|
12
|
+
@pool.on 'fetch:success', @update
|
13
|
+
|
14
|
+
@fetchDelay = 3000
|
15
|
+
@fetchDelayOffset = 0
|
16
|
+
|
17
|
+
@setFetchInterval()
|
18
|
+
|
19
|
+
@$el.on 'click', (e)=>
|
20
|
+
e.preventDefault()
|
21
|
+
@emptyPool()
|
22
|
+
false
|
23
|
+
|
24
|
+
setFetchInterval: (interval=@fetchDelay+@fetchDelayOffset) =>
|
25
|
+
clearInterval TentStatus._fetchPostsPoolInterval
|
26
|
+
TentStatus._fetchPostsPoolInterval = setInterval @pool.fetch, interval
|
27
|
+
|
28
|
+
update: =>
|
29
|
+
lastSinceId = @sinceId
|
30
|
+
@sinceId = @pool.sinceId
|
31
|
+
if lastSinceId == @sinceId
|
32
|
+
@fetchDelayOffset = Math.min(@fetchDelayOffset + @fetchDelay, 57000) # max delay: 1 min
|
33
|
+
@setFetchInterval()
|
34
|
+
else
|
35
|
+
@fetchDelayOffset = 0
|
36
|
+
@setFetchInterval()
|
37
|
+
|
38
|
+
@numNewPosts = @pool.collection.length
|
39
|
+
@$numNewPosts.text @numNewPosts
|
40
|
+
@show() if @numNewPosts > 0
|
41
|
+
|
42
|
+
show: => @$el.show()
|
43
|
+
hide: => @$el.hide()
|
44
|
+
|
45
|
+
emptyPool: =>
|
46
|
+
for i in [0...@numNewPosts]
|
47
|
+
post = @pool.collection.shift()
|
48
|
+
TentStatus.Collections.posts.unshift(post)
|
49
|
+
# @createPostView(post)
|
50
|
+
@parentView.render()
|
51
|
+
@pool.sinceId = @parentView.posts.first()?.get('id') || @pool.sinceId
|
52
|
+
@numNewPosts = 0
|
53
|
+
@$numNewPosts.text @numNewPosts
|
54
|
+
@hide()
|
55
|
+
|
56
|
+
createPostView: (post) =>
|
57
|
+
el = ($ '<li>').prependTo(@$postsList)
|
58
|
+
view = new TentStatus.Views.Post el: el, parentView: @parentView
|
59
|
+
view.post = post
|
60
|
+
context = view.context(post)
|
61
|
+
view.render(context)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class TentStatus.Views.FollowerGroupsForm extends Backbone.View
|
2
|
+
initialize: (options = {}) ->
|
3
|
+
@parentView = options.parentView
|
4
|
+
|
5
|
+
followerId = @$el.attr 'data-follower-id'
|
6
|
+
@follower = TentStatus.Collections.followers.find (follower) -> follower.get('id') == followerId
|
7
|
+
|
8
|
+
@$groupsSelect = ($ 'select[name=groups]', @$el)
|
9
|
+
@$groupsSelect.chosen
|
10
|
+
persistent_create_option: true
|
11
|
+
no_results_text: 'No groups match'
|
12
|
+
create_option_text: 'Create new group'
|
13
|
+
create_option: (name) =>
|
14
|
+
group = new TentStatus.Models.Group({ name: name })
|
15
|
+
group.once 'sync', =>
|
16
|
+
TentStatus.Collections.groups.push(group)
|
17
|
+
@follower.set 'groups', (@follower.get('groups') || []).concat([group.get('id')])
|
18
|
+
@follower.save()
|
19
|
+
@parentView.render()
|
20
|
+
group.save()
|
21
|
+
|
22
|
+
@$groupsSelect.change =>
|
23
|
+
@follower.set 'groups', @$groupsSelect.val()
|
24
|
+
@follower.once 'sync', => @parentView.render()
|
25
|
+
@follower.save()
|
26
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class TentStatus.Views.Followers extends TentStatus.View
|
2
|
+
templateName: 'followers'
|
3
|
+
partialNames: ['_follower']
|
4
|
+
|
5
|
+
dependentRenderAttributes: ['followers', 'groups']
|
6
|
+
|
7
|
+
initialize: ->
|
8
|
+
@container = TentStatus.Views.container
|
9
|
+
super
|
10
|
+
|
11
|
+
@on 'ready', @initAutoPaginate
|
12
|
+
|
13
|
+
context: =>
|
14
|
+
followers: _.map(@followers.toArray(), (follower) => _.extend follower.toJSON(), {
|
15
|
+
name: follower.name()
|
16
|
+
avatar: follower.avatar()
|
17
|
+
groups: _.map(@groups.toArray(), (group) -> _.extend group.toJSON(), {
|
18
|
+
selected: follower.get('groups')?.indexOf(group.id) != -1
|
19
|
+
})
|
20
|
+
})
|
21
|
+
|
22
|
+
initAutoPaginate: =>
|
23
|
+
($ window).off 'scroll.followers'
|
24
|
+
($ window).on 'scroll.followers', (e)=>
|
25
|
+
height = $(document).height() - $(window).height()
|
26
|
+
delta = height - window.scrollY
|
27
|
+
if delta < 200
|
28
|
+
@followers?.nextPage() unless @followers.onLastPage
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class TentStatus.Views.FollowingGroupsForm extends Backbone.View
|
2
|
+
initialize: (options = {}) ->
|
3
|
+
@parentView = options.parentView
|
4
|
+
|
5
|
+
followingId = @$el.attr 'data-following-id'
|
6
|
+
@following = TentStatus.Collections.followings.get(followingId)
|
7
|
+
|
8
|
+
@$groupsSelect = ($ 'select[name=groups]', @$el)
|
9
|
+
@$groupsSelect.chosen
|
10
|
+
persistent_create_option: true
|
11
|
+
no_results_text: 'No groups match'
|
12
|
+
create_option_text: 'Create new group'
|
13
|
+
create_option: (name) =>
|
14
|
+
group = new TentStatus.Models.Group({ name: name })
|
15
|
+
group.once 'sync', =>
|
16
|
+
TentStatus.Collections.groups.push(group)
|
17
|
+
@following.set 'groups', (@following.get('groups') || []).concat([group.get('id')])
|
18
|
+
@following.save()
|
19
|
+
@parentView.render()
|
20
|
+
group.save()
|
21
|
+
|
22
|
+
@$groupsSelect.change =>
|
23
|
+
@following.set 'groups', @$groupsSelect.val()
|
24
|
+
@following.once 'sync', => @parentView.render()
|
25
|
+
@following.save()
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class TentStatus.Views.Followings extends TentStatus.View
|
2
|
+
templateName: 'followings'
|
3
|
+
partialNames: ['_following']
|
4
|
+
|
5
|
+
dependentRenderAttributes: ['followings', 'groups']
|
6
|
+
|
7
|
+
initialize: ->
|
8
|
+
@container = TentStatus.Views.container
|
9
|
+
super
|
10
|
+
@on 'ready', @initAutoPaginate
|
11
|
+
|
12
|
+
context: =>
|
13
|
+
followings: _.map(@followings.toArray(), (following) => _.extend following.toJSON(), {
|
14
|
+
name: following.name()
|
15
|
+
avatar: following.avatar()
|
16
|
+
groups: _.map(@groups.toArray(), (group) -> _.extend group.toJSON(), {
|
17
|
+
selected: following.get('groups')?.indexOf(group.id) != -1
|
18
|
+
})
|
19
|
+
})
|
20
|
+
|
21
|
+
initAutoPaginate: =>
|
22
|
+
($ window).off 'scroll.followings'
|
23
|
+
($ window).on 'scroll.followings', (e)=>
|
24
|
+
height = $(document).height() - $(window).height()
|
25
|
+
delta = height - window.scrollY
|
26
|
+
if delta < 200
|
27
|
+
@followings?.nextPage() unless @followings.onLastPage
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class TentStatus.Views.NewFollowingForm extends Backbone.View
|
2
|
+
initialize: (options = {}) ->
|
3
|
+
@parentView = options.parentView
|
4
|
+
|
5
|
+
@$el.on 'submit', @submit
|
6
|
+
|
7
|
+
submit: (e) =>
|
8
|
+
e.preventDefault()
|
9
|
+
entity = ($ '[name=entity]', @$el).val()
|
10
|
+
following = new TentStatus.Models.Following { entity: entity }
|
11
|
+
following.once 'sync', =>
|
12
|
+
TentStatus.Collections.followings.unshift(following)
|
13
|
+
@parentView.render()
|
14
|
+
following.save()
|
15
|
+
false
|
@@ -0,0 +1,178 @@
|
|
1
|
+
class TentStatus.Views.NewPostForm extends Backbone.View
|
2
|
+
initialize: (options = {}) ->
|
3
|
+
@parentView = options.parentView
|
4
|
+
|
5
|
+
@action = "#{TentStatus.api_root}/posts"
|
6
|
+
|
7
|
+
@$errors = ($ '.alert-error', @$el).first().hide()
|
8
|
+
|
9
|
+
@$post_btn = ($ 'input[type=submit]', @$el)
|
10
|
+
|
11
|
+
@$el.off().on 'submit', @submit
|
12
|
+
|
13
|
+
## form validation and character limit counter
|
14
|
+
@$textarea = ($ 'textarea', @$el)
|
15
|
+
@$charLimit = ($ '.char-limit', @$el).first()
|
16
|
+
@charLimit = parseInt(@$charLimit.text())
|
17
|
+
@$textarea.on 'keyup', =>
|
18
|
+
clearTimeout @_validateTimeout
|
19
|
+
@_validateTimeout = setTimeout @validate, 300
|
20
|
+
@updateCharCounter()
|
21
|
+
null
|
22
|
+
@updateCharCounter()
|
23
|
+
|
24
|
+
## cmd/ctr enter == submit
|
25
|
+
@$textarea.off('keydown.keysubmit').on 'keydown.keysubmit', (e) =>
|
26
|
+
if (e.metaKey || e.ctrlKey) && e.keyCode == 13
|
27
|
+
e.preventDefault()
|
28
|
+
@submit()
|
29
|
+
false
|
30
|
+
else
|
31
|
+
true
|
32
|
+
|
33
|
+
## permissions
|
34
|
+
@$publicCheckbox = ($ '[name=public]', @$el)
|
35
|
+
@$permissions = ($ 'select[name=permissions]', @$el)
|
36
|
+
@$permissions.chosen
|
37
|
+
no_results_text: 'No matching entities or groups'
|
38
|
+
|
39
|
+
# disable public checkbox when permissions are added
|
40
|
+
@$permissions.change @checkPublicEnabled
|
41
|
+
|
42
|
+
## mentions
|
43
|
+
@$mentionsSelect = ($ 'select[name=mentions]', @$el)
|
44
|
+
@$mentionsSelect.chosen
|
45
|
+
no_results_text: 'No matching entities'
|
46
|
+
|
47
|
+
## licenses
|
48
|
+
@$licensesSelect = ($ 'select[name=licenses]', @$el)
|
49
|
+
@$licensesSelect.chosen
|
50
|
+
no_results_text: 'No matching licenses'
|
51
|
+
|
52
|
+
## advanced options toggle
|
53
|
+
@$advancedOptions = ($ '.advanced-options', @$el).hide()
|
54
|
+
@$advancedOptionsToggle = ($ '.advanced-options-toggle', @$el)
|
55
|
+
@$advancedOptionsToggle.on 'click', (e) =>
|
56
|
+
e.preventDefault()
|
57
|
+
@$advancedOptions.toggle()
|
58
|
+
false
|
59
|
+
|
60
|
+
checkPublicEnabled: =>
|
61
|
+
if @$permissions.val() == null
|
62
|
+
@enablePublic()
|
63
|
+
else
|
64
|
+
@disablePublic()
|
65
|
+
|
66
|
+
enablePublic: =>
|
67
|
+
@$publicCheckbox.removeAttr('disabled')
|
68
|
+
|
69
|
+
disablePublic: =>
|
70
|
+
@$publicCheckbox.removeAttr('checked')
|
71
|
+
@$publicCheckbox.attr('disabled', 'disabled')
|
72
|
+
|
73
|
+
submit: (e) =>
|
74
|
+
e.preventDefault() if e
|
75
|
+
data = @getData()
|
76
|
+
return false unless @validate data
|
77
|
+
|
78
|
+
post = new TentStatus.Models.Post data
|
79
|
+
post.once 'sync', =>
|
80
|
+
window.location.reload() unless @parentView.emptyPool
|
81
|
+
@parentView.emptyPool()
|
82
|
+
TentStatus.Collections.posts.unshift(post)
|
83
|
+
@parentView.set('posts', TentStatus.Collections.posts)
|
84
|
+
@parentView.render()
|
85
|
+
post.save()
|
86
|
+
false
|
87
|
+
|
88
|
+
buildPermissions: (data) =>
|
89
|
+
_public = if data['public'] == 'on' then true else false
|
90
|
+
permissions = {
|
91
|
+
public: _public
|
92
|
+
}
|
93
|
+
|
94
|
+
_groups = _.each _.flatten(Array data.permissions), (entityOrGroupId) ->
|
95
|
+
return unless entityOrGroupId
|
96
|
+
[type, value] = [entityOrGroupId.slice(0,2), entityOrGroupId.slice(2, entityOrGroupId.length)]
|
97
|
+
switch type
|
98
|
+
when 'g:'
|
99
|
+
permissions.groups ||= []
|
100
|
+
permissions.groups.push { id: value }
|
101
|
+
when 'f:'
|
102
|
+
permissions.entities ||= {}
|
103
|
+
permissions.entities[value] = true
|
104
|
+
|
105
|
+
delete data['public']
|
106
|
+
delete data.permissible_groups
|
107
|
+
delete data.permissible_entities
|
108
|
+
|
109
|
+
data.permissions = permissions
|
110
|
+
data
|
111
|
+
|
112
|
+
buildMentions: (data) =>
|
113
|
+
mentions = _.compact (_.map _.flatten(Array data.mentions), (entity) ->
|
114
|
+
return unless entity
|
115
|
+
{ entity: entity }
|
116
|
+
)
|
117
|
+
delete data.mentions
|
118
|
+
|
119
|
+
for entity in (data.text.match(/\^(\S+)/g) || [])
|
120
|
+
entity = entity.replace(/^\^/, '')
|
121
|
+
entity = entity.replace(/^/, 'https://') unless entity.match(/^https?/)
|
122
|
+
mentions.push { entity: entity }
|
123
|
+
|
124
|
+
data.mentions = mentions if mentions.length
|
125
|
+
data
|
126
|
+
|
127
|
+
buildLicenses: (data) =>
|
128
|
+
data.licenses = _.flatten(Array data.licenses) if data.licenses
|
129
|
+
data
|
130
|
+
|
131
|
+
buildDataObject: (serializedArray) =>
|
132
|
+
data = {}
|
133
|
+
for i in serializedArray
|
134
|
+
if data[i.name]
|
135
|
+
if data[i.name].push
|
136
|
+
data[i.name].push i.value
|
137
|
+
else
|
138
|
+
data[i.name] = [data[i.name], i.value]
|
139
|
+
else
|
140
|
+
data[i.name] = i.value
|
141
|
+
|
142
|
+
@buildLicenses(@buildMentions(@buildPermissions(data)))
|
143
|
+
|
144
|
+
getData: =>
|
145
|
+
@buildDataObject @$el.serializeArray()
|
146
|
+
|
147
|
+
validate: (data = @getData()) =>
|
148
|
+
post = new TentStatus.Models.Post data
|
149
|
+
errors = post.validate(data)
|
150
|
+
@$el.find(".error").removeClass('error')
|
151
|
+
@$errors.hide()
|
152
|
+
@showErrors(errors) if errors
|
153
|
+
return !errors
|
154
|
+
|
155
|
+
showErrors: (errors) =>
|
156
|
+
error_messages = []
|
157
|
+
for err in errors
|
158
|
+
for name, msg of err
|
159
|
+
$input = @$el.find("[name='#{name}']")
|
160
|
+
$input.addClass('error')
|
161
|
+
error_messages.push msg
|
162
|
+
@$errors.html error_messages.join('<br/>')
|
163
|
+
@$errors.show()
|
164
|
+
|
165
|
+
updateCharCounter: =>
|
166
|
+
charCount = @$textarea.val()?.length
|
167
|
+
delta = @charLimit - charCount
|
168
|
+
if delta < 0
|
169
|
+
@$post_btn.attr 'disabled', 'disabled'
|
170
|
+
@$charLimit.addClass 'alert-error'
|
171
|
+
else
|
172
|
+
if delta == @charLimit
|
173
|
+
@$post_btn.attr 'disabled', 'disabled'
|
174
|
+
else
|
175
|
+
@$post_btn.removeAttr 'disabled'
|
176
|
+
@$charLimit.removeClass 'alert-error'
|
177
|
+
@$charLimit.text delta
|
178
|
+
|