tent-status 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/.gitignore +21 -0
  2. data/.kick +8 -0
  3. data/Gemfile +12 -0
  4. data/Gemfile.lock +232 -0
  5. data/LICENSE.txt +22 -0
  6. data/Procfile +1 -0
  7. data/README.md +14 -0
  8. data/Rakefile +43 -0
  9. data/assets/images/.gitkeep +0 -0
  10. data/assets/images/chosen-sprite.png +0 -0
  11. data/assets/images/glyphicons-halflings-white.png +0 -0
  12. data/assets/images/glyphicons-halflings.png +0 -0
  13. data/assets/javascripts/.gitkeep +0 -0
  14. data/assets/javascripts/application.js.coffee +122 -0
  15. data/assets/javascripts/backbone.js +1443 -0
  16. data/assets/javascripts/backbone_sync.js.coffee +60 -0
  17. data/assets/javascripts/boot.js.coffee +3 -0
  18. data/assets/javascripts/chosen.jquery.js +1129 -0
  19. data/assets/javascripts/collections/.gitkeep +0 -0
  20. data/assets/javascripts/collections/followers.js.coffee +5 -0
  21. data/assets/javascripts/collections/followings.js.coffee +5 -0
  22. data/assets/javascripts/collections/groups.js.coffee +5 -0
  23. data/assets/javascripts/collections/posts.js.coffee +5 -0
  24. data/assets/javascripts/fetch_pool.js.coffee +25 -0
  25. data/assets/javascripts/helpers/.gitkeep +0 -0
  26. data/assets/javascripts/helpers/formatting.js.coffee +17 -0
  27. data/assets/javascripts/hogan.js +706 -0
  28. data/assets/javascripts/http.js.coffee +18 -0
  29. data/assets/javascripts/jquery.js +9301 -0
  30. data/assets/javascripts/models/.gitkeep +0 -0
  31. data/assets/javascripts/models/follower.js.coffee +27 -0
  32. data/assets/javascripts/models/following.js.coffee +27 -0
  33. data/assets/javascripts/models/group.js.coffee +4 -0
  34. data/assets/javascripts/models/post.js.coffee +32 -0
  35. data/assets/javascripts/models/profile.js.coffee +30 -0
  36. data/assets/javascripts/moment.js +1106 -0
  37. data/assets/javascripts/paginator.js.coffee +67 -0
  38. data/assets/javascripts/router.js.coffee +71 -0
  39. data/assets/javascripts/routers/.gitkeep +0 -0
  40. data/assets/javascripts/routers/followers.js.coffee +14 -0
  41. data/assets/javascripts/routers/followings.js.coffee +14 -0
  42. data/assets/javascripts/routers/posts.js.coffee +98 -0
  43. data/assets/javascripts/templates/.gitkeep +0 -0
  44. data/assets/javascripts/templates/_follower.js.mustache.slim +17 -0
  45. data/assets/javascripts/templates/_following.js.mustache.slim +17 -0
  46. data/assets/javascripts/templates/_new_post_form.js.mustache.slim +10 -0
  47. data/assets/javascripts/templates/_post.js.mustache.slim +10 -0
  48. data/assets/javascripts/templates/_post_inner.js.mustache.slim +71 -0
  49. data/assets/javascripts/templates/_profile_stats.js.mustache.slim +11 -0
  50. data/assets/javascripts/templates/_reply_form.js.mustache.slim +13 -0
  51. data/assets/javascripts/templates/conversation.js.mustache.slim +13 -0
  52. data/assets/javascripts/templates/followers.js.mustache.slim +19 -0
  53. data/assets/javascripts/templates/followings.js.mustache.slim +22 -0
  54. data/assets/javascripts/templates/posts.js.mustache.slim +25 -0
  55. data/assets/javascripts/templates/profile.js.mustache.slim +39 -0
  56. data/assets/javascripts/underscore.js +1059 -0
  57. data/assets/javascripts/view.js.coffee +140 -0
  58. data/assets/javascripts/views/.gitkeep +0 -0
  59. data/assets/javascripts/views/container.js.coffee +6 -0
  60. data/assets/javascripts/views/expanding_textarea.js.coffee +14 -0
  61. data/assets/javascripts/views/fetch_posts_pool.js.coffee +61 -0
  62. data/assets/javascripts/views/follower_groups_form.js.coffee +26 -0
  63. data/assets/javascripts/views/followers.js.coffee +28 -0
  64. data/assets/javascripts/views/following_groups_form.js.coffee +25 -0
  65. data/assets/javascripts/views/followings.js.coffee +27 -0
  66. data/assets/javascripts/views/new_following_form.js.coffee +15 -0
  67. data/assets/javascripts/views/new_post_form.js.coffee +178 -0
  68. data/assets/javascripts/views/post.js.coffee +139 -0
  69. data/assets/javascripts/views/posts.js.coffee +55 -0
  70. data/assets/javascripts/views/posts/conversation.js.coffee +18 -0
  71. data/assets/javascripts/views/profile.js.coffee +29 -0
  72. data/assets/javascripts/views/profile_follow_button.js.coffee +29 -0
  73. data/assets/javascripts/views/profile_stats.js.coffee +38 -0
  74. data/assets/javascripts/views/remove_follower_btn.js.coffee +18 -0
  75. data/assets/javascripts/views/reply_post_form.js.coffee +30 -0
  76. data/assets/javascripts/views/unfollow_btn.js.coffee +18 -0
  77. data/assets/stylesheets/.gitkeep +0 -0
  78. data/assets/stylesheets/application.css.sass +117 -0
  79. data/assets/stylesheets/bootstrap-responsive.css +1040 -0
  80. data/assets/stylesheets/bootstrap.css.erb +5624 -0
  81. data/assets/stylesheets/chosen.css.erb +397 -0
  82. data/config.ru +14 -0
  83. data/config/asset_sync.rb +12 -0
  84. data/config/evergreen.rb +16 -0
  85. data/lib/tent-status.rb +6 -0
  86. data/lib/tent-status/app.rb +263 -0
  87. data/lib/tent-status/models/user.rb +39 -0
  88. data/lib/tent-status/sprockets/environment.rb +25 -0
  89. data/lib/tent-status/sprockets/helpers.rb +5 -0
  90. data/lib/tent-status/views/application.slim +69 -0
  91. data/lib/tent-status/views/auth.slim +8 -0
  92. data/tent-status.gemspec +34 -0
  93. 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,6 @@
1
+ TentStatus.Views.container = new class ContainerView extends Backbone.View
2
+ initialize: ->
3
+ @setElement document.getElementById('main')
4
+
5
+ render: (html) =>
6
+ @$el.html(html)
@@ -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
+