tent-status 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|