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.
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,139 @@
1
+ class TentStatus.Views.Post extends TentStatus.View
2
+ initialize: (options = {}) ->
3
+ @parentView = options.parentView
4
+ @template = @parentView?.partials['_post']
5
+
6
+ @postId = @$el.attr('data-parent-id') || ""
7
+ @postId = @$el.attr 'data-id' if @postId == ""
8
+ @post = @parentView.posts.get(@postId)
9
+ @parentPost = @post
10
+
11
+ if @post?.isRepost() && @$el.attr('data-post-found') != 'yes'
12
+ @$el.hide()
13
+ post = new TentStatus.Models.Post { id: @post.get('content')['id'] }
14
+ post.fetch
15
+ success: =>
16
+ return unless post.entity()
17
+ @$el.show()
18
+ @render @context(@post, @repostContext(@post, post))
19
+ @post = post
20
+ else
21
+ @setup()
22
+
23
+ @on 'ready', @setup
24
+ @on 'ready', @bindViews
25
+ @on 'ready', @bindEvents
26
+
27
+ setup: =>
28
+ @buttons = {
29
+ reply: ($ '.navigation .reply', @$el)
30
+ repost: ($ '.navigation .repost', @$el)
31
+ delete: ($ '.navigation .delete', @$el)
32
+ }
33
+
34
+ @buttons.reply.on 'click', (e) =>
35
+ e.preventDefault()
36
+ @showReply()
37
+ false
38
+
39
+ @$reply = ($ '.reply-container', @$el).hide()
40
+
41
+ @buttons.repost.on 'click', (e) =>
42
+ e.preventDefault()
43
+ @repost()
44
+ false
45
+
46
+ @buttons.delete.on 'click', (e) =>
47
+ e.preventDefault()
48
+ @delete()
49
+ false
50
+
51
+ showReply: =>
52
+ return if @post.get('entity') == TentStatus.current_entity
53
+ @$reply.toggle()
54
+
55
+ repost: =>
56
+ return if @buttons.repost.hasClass 'disabled'
57
+ return if @post.get('entity') == TentStatus.current_entity
58
+ shouldRepost = confirm(@buttons.repost.attr 'data-confirm')
59
+ return unless shouldRepost
60
+ post = new TentStatus.Models.Post {
61
+ type: "https://tent.io/types/post/repost/v0.1.0"
62
+ content:
63
+ entity: @post.get('entity')
64
+ id: @post.get('id')
65
+ }
66
+ post.once 'sync', =>
67
+ @buttons.repost.addClass 'disabled'
68
+ TentStatus.Collections.posts.unshift(post)
69
+ @parentView.emptyPool()
70
+ @parentView.fetchPoolView.createPostView(post)
71
+ post.save()
72
+
73
+ delete: =>
74
+ post = @post
75
+ unless post == @parentPost
76
+ post = @parentPost
77
+ return unless post.get('entity') == TentStatus.current_entity
78
+ shouldDelete = confirm(@buttons.delete.attr 'data-confirm')
79
+ return unless shouldDelete
80
+ @$el.hide()
81
+ @post.destroy
82
+ success: =>
83
+ @$el.remove()
84
+ error: =>
85
+ @$el.show()
86
+
87
+ replyToPost: (post) =>
88
+ return unless post.get('mentions')?.length
89
+ for mention in post.get('mentions')
90
+ if mention.entity and mention.post
91
+ mention.url = "#{TentStatus.url_root}entities/#{encodeURIComponent(mention.entity)}/#{mention.post}"
92
+ mention.name = (_.find @parentView.follows(), (follow) => follow.get('entity') == mention.entity)?.name()
93
+ mention.name ?= (_.find [@parentView.profile], (profile) => profile.entity() == mention.entity)?.name()
94
+ return mention
95
+ null
96
+
97
+ repostContext: (post, repost) =>
98
+ return false unless post.isRepost()
99
+
100
+ repost ?= _.find @parentView.posts.toArray(), ((p) => p.get('id') == post.get('content')['id'])
101
+ return false unless repost
102
+ _.extend( @context(repost), { parent: { name: post.name(), id: post.get('id') } } )
103
+
104
+ licenseName: (url) =>
105
+ for l in @licenses || []
106
+ return l.name if l.url == url
107
+ url
108
+
109
+ context: (post, repostContext) =>
110
+ _.extend( post.toJSON(),
111
+ isValid: true
112
+ shouldShowReply: true
113
+ isRepost: post.isRepost()
114
+ repost: repostContext || @repostContext(post)
115
+ inReplyTo: @replyToPost(post)
116
+ url: "#{TentStatus.url_root}entities/#{encodeURIComponent(post.get('entity'))}/#{post.get('id')}"
117
+ profileUrl: "#{TentStatus.url_root}entities/#{encodeURIComponent(post.get('entity'))}"
118
+ name: post.name()
119
+ hasName: post.hasName()
120
+ avatar: post.avatar()
121
+ licenses: _.map( post.get('licenses') || [], (url) => { name: @licenseName(url), url: url } )
122
+ escaped:
123
+ entity: encodeURIComponent(post.get('entity'))
124
+ formatted:
125
+ entity: TentStatus.Helpers.formatUrl post.get('entity')
126
+ published_at: TentStatus.Helpers.formatTime post.get('published_at')
127
+ full_published_at: TentStatus.Helpers.rawTime post.get('published_at')
128
+ authenticated: TentStatus.authenticated
129
+ guest_authenticated: TentStatus.guest_authenticated
130
+ currentUserOwnsPost: TentStatus.current_entity == post.get('entity')
131
+ )
132
+
133
+ render: (context) =>
134
+ html = @template.render(context, @parentView.partials)
135
+ el = ($ html)
136
+ @$el.replaceWith(el)
137
+ @$el = el
138
+ @trigger 'ready'
139
+
@@ -0,0 +1,55 @@
1
+ class TentStatus.Views.Posts extends TentStatus.View
2
+ templateName: 'posts'
3
+ partialNames: ['_post', '_new_post_form', '_reply_form', '_post_inner']
4
+
5
+ dependentRenderAttributes: ['posts', 'followers', 'followings', 'profile']
6
+
7
+ initialize: ->
8
+ @container = TentStatus.Views.container
9
+ super
10
+
11
+ @on 'ready', @initPostViews
12
+ @on 'ready', @initFetchPool
13
+ @on 'ready', @initAutoPaginate
14
+
15
+ sortedPosts: => @posts.sortBy (post) -> -post.get('published_at')
16
+
17
+ uniqueFollowings: =>
18
+ @followings?.filter (following) =>
19
+ !@followers.find (follower) =>
20
+ follower.get('entity') == following.get('entity')
21
+
22
+ follows: =>
23
+ (@followers?.toArray() || []).concat(@uniqueFollowings() || [])
24
+
25
+ context: =>
26
+ @licenses = [{ url: "http://creativecommons.org/licenses/by-nc-sa/3.0/", name: "Creative Commons by-nc-sa 3.0" }]
27
+
28
+ follows: _.map(@follows(), (follow) -> _.extend follow.toJSON(), {
29
+ name: follow.name()
30
+ })
31
+ licenses: @licenses
32
+ posts: (_.map @posts.toArray(), (post) =>
33
+ view = new TentStatus.Views.Post parentView: @
34
+ view.context(post)
35
+ )
36
+
37
+ initPostViews: =>
38
+ _.each ($ 'li.post'), (el) =>
39
+ new TentStatus.Views.Post el: el, parentView: @
40
+
41
+ initFetchPool: =>
42
+ el = ($ '.fetch-pool', @container.$el).hide()
43
+ @fetchPoolView = new TentStatus.Views.FetchPostsPool el: el, parentView: @
44
+
45
+ initAutoPaginate: =>
46
+ ($ window).off 'scroll.posts'
47
+ ($ window).on 'scroll.posts', (e)=>
48
+ height = $(document).height() - $(window).height()
49
+ delta = height - window.scrollY
50
+ if delta < 300
51
+ @posts?.nextPage() unless @posts.onLastPage
52
+
53
+ emptyPool: =>
54
+ @fetchPoolView.emptyPool()
55
+
@@ -0,0 +1,18 @@
1
+ class TentStatus.Views.Conversation extends TentStatus.Views.Posts
2
+ templateName: 'conversation'
3
+
4
+ initialize: ->
5
+ @dependentRenderAttributes.push 'post'
6
+ super
7
+
8
+ context: =>
9
+ @posts.unshift(@post)
10
+ data = super
11
+ posts = []
12
+ for post in data.posts
13
+ if post.id == @post.get('id')
14
+ data.post = post
15
+ else
16
+ posts.push post
17
+ data.posts = posts
18
+ data
@@ -0,0 +1,29 @@
1
+ class TentStatus.Views.Profile extends TentStatus.Views.Posts
2
+ templateName: 'profile'
3
+ partialNames: ['_post', '_post_inner', '_reply_form']
4
+
5
+ initialize: ->
6
+ @dependentRenderAttributes.push 'currentProfile'
7
+ super
8
+
9
+ replyToPost: (post) =>
10
+ return unless post.get('mentions')?.length
11
+ for mention in post.get('mentions')
12
+ if mention.entity and mention.post
13
+ mention.url = "#{TentStatus.url_root}#{encodeURIComponent(mention.entity)}/#{mention.post}"
14
+ return mention
15
+ null
16
+
17
+ context: =>
18
+ _.extend super,
19
+ profile: _.extend( @currentProfile.toJSON(),
20
+ name: @currentProfile.name()
21
+ bio: @currentProfile.bio()
22
+ nameIsEntity: @currentProfile.name() == TentStatus.Helpers.formatUrl(@currentProfile.entity())
23
+ avatar: @currentProfile.avatar()
24
+ entity: @currentProfile.entity()
25
+ encoded:
26
+ entity: encodeURIComponent(@currentProfile.entity())
27
+ formatted:
28
+ entity: TentStatus.Helpers.formatUrl @currentProfile.entity()
29
+ )
@@ -0,0 +1,29 @@
1
+ class TentStatus.Views.ProfileFollowButton extends Backbone.View
2
+ initialize: (options = {}) ->
3
+ @parentView = options.parentView
4
+
5
+ @buttons = {}
6
+ @buttons.submit = ($ '[type=submit]', @$el)
7
+
8
+ following = new TentStatus.Models.Following
9
+ following.fetch
10
+ url: "#{TentStatus.api_root}/followings?entity=#{encodeURIComponent(TentStatus.domain_entity)}&guest=true"
11
+ success: (f, res) =>
12
+ if res.length
13
+ @setFollowing()
14
+
15
+ @$el.on 'submit', @submit
16
+
17
+ submit: (e) =>
18
+ e.preventDefault()
19
+ entity = TentStatus.domain_entity
20
+ @buttons.submit.attr 'disabled', 'disabled'
21
+ following = new TentStatus.Models.Following { entity: entity }
22
+ following.once 'sync', =>
23
+ @setFollowing()
24
+ following.save()
25
+ false
26
+
27
+ setFollowing: =>
28
+ @buttons.submit.val 'Following'
29
+ @buttons.submit.attr 'disabled', 'disabled'
@@ -0,0 +1,38 @@
1
+ class TentStatus.Views.ProfileStats extends TentStatus.View
2
+ templateName: '_profile_stats'
3
+
4
+ initialize: (options) ->
5
+ super
6
+ @container = null
7
+
8
+ api_root = if TentStatus.guest_authenticated
9
+ TentStatus.tent_api_root
10
+ else
11
+ TentStatus.api_root
12
+
13
+ @countKeys = ['postsCount', 'followingsCount', 'followersCount']
14
+ for key in @countKeys
15
+ @once "change:#{key}", @render
16
+
17
+ $.getJSON "#{api_root}/posts/count", (count) =>
18
+ @set 'postsCount', count
19
+
20
+ $.getJSON "#{api_root}/followers/count", (count) =>
21
+ @set 'followersCount', count
22
+
23
+ $.getJSON "#{api_root}/followings/count", (count) =>
24
+ @set 'followingsCount', count
25
+
26
+ @render()
27
+
28
+ context: =>
29
+ postsCount: @postsCount
30
+ followersCount: @followersCount
31
+ followingsCount: @followingsCount
32
+
33
+ render: =>
34
+ for key in @countKeys
35
+ val = @get(key)
36
+ return if val == null
37
+ html = super
38
+ @$el.html(html)
@@ -0,0 +1,18 @@
1
+ class TentStatus.Views.RemoveFollowerBtn extends Backbone.View
2
+ initialize: (options = {}) ->
3
+ @parentView = options.parentView
4
+
5
+ followerId = @$el.attr 'data-id'
6
+ @follower = TentStatus.Collections.followers.get(followerId)
7
+
8
+ @confirmMsg = @$el.attr 'data-confirm'
9
+
10
+ @$el.on 'click', @confirmUnfollow
11
+
12
+ confirmUnfollow: =>
13
+ shouldRemove = confirm @confirmMsg
14
+ return unless shouldRemove
15
+ @follower.destroy
16
+ success: =>
17
+ TentStatus.Collections.followers.remove(@follower)
18
+ @parentView.render()
@@ -0,0 +1,30 @@
1
+ class TentStatus.Views.ReplyPostForm extends TentStatus.Views.NewPostForm
2
+ initialize: (options = {}) ->
3
+ super
4
+
5
+ ## reply fields
6
+ @replyToPostId = ($ '[name=mentions_post_id]', @$el).val()
7
+ @replyToEntity = ($ '[name=mentions_post_entity]', @$el).val()
8
+
9
+ submit: (e) =>
10
+ e.preventDefault()
11
+ data = @getData()
12
+ return false unless @validate data
13
+
14
+ post = new TentStatus.Models.Post data
15
+ post.once 'sync', =>
16
+ window.location.reload() unless @parentView.emptyPool
17
+ @parentView.emptyPool()
18
+ TentStatus.Collections.posts.unshift(post)
19
+ @parentView.posts.unshift(post)
20
+ @parentView.render()
21
+ post.save()
22
+ false
23
+
24
+ buildMentions: (data) =>
25
+ data = super
26
+ if @replyToPostId and @replyToEntity
27
+ data.mentions ||= []
28
+ data.mentions.push { entity: @replyToEntity, post: @replyToPostId }
29
+ data
30
+
@@ -0,0 +1,18 @@
1
+ class TentStatus.Views.UnfollowBtn extends Backbone.View
2
+ initialize: (options = {}) ->
3
+ @parentView = options.parentView
4
+
5
+ followingId = @$el.attr 'data-id'
6
+ @following = TentStatus.Collections.followings.get(followingId)
7
+
8
+ @confirmMsg = @$el.attr 'data-confirm'
9
+
10
+ @$el.on 'click', @confirmUnfollow
11
+
12
+ confirmUnfollow: =>
13
+ shouldUnfollow = confirm @confirmMsg
14
+ return unless shouldUnfollow
15
+ @following.destroy
16
+ success: =>
17
+ TentStatus.Collections.followings.remove(@following)
18
+ @parentView.render()
File without changes
@@ -0,0 +1,117 @@
1
+ //= require bootstrap
2
+ //= require bootstrap-responsive
3
+ //= require chosen
4
+ //= require_self
5
+
6
+ @mixin rounded($amount)
7
+ -webkit-border-radius: $amount
8
+ -moz-border-radius: $amount
9
+ border-radius: $amount
10
+
11
+ textarea::-webkit-input-placeholder
12
+ padding-top: 20px
13
+ text-align: center
14
+
15
+ form input,
16
+ form select,
17
+ form textarea
18
+ &.error
19
+ color: #b94a48
20
+ border-color: #b94a48
21
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075)
22
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075)
23
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075)
24
+
25
+ &:focus
26
+ border-color: #953b39
27
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392
28
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392
29
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392
30
+
31
+ .permissions
32
+ .control-label
33
+ width: 20px
34
+ .controls
35
+ margin-left: 30px
36
+
37
+ .post .navigation ul.nav
38
+ margin-bottom: 0px
39
+ padding: 0px
40
+
41
+ li.post
42
+ border-bottom: 1px solid #eee
43
+ margin-bottom: 19px
44
+ &:hover
45
+ margin-bottom: 20px
46
+ background-color: #f5f5f5
47
+ border: 0px
48
+ @include rounded(4px)
49
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05)
50
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05)
51
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05)
52
+
53
+ .showOnHover
54
+ visibility: hidden
55
+
56
+ img.avatar
57
+ width: 50px
58
+ height: 50px
59
+ margin-right: 4px
60
+
61
+ &:hover
62
+ .showOnHover
63
+ visibility: visible
64
+
65
+ div.repost
66
+ margin-top: 6px
67
+ @include rounded(4px)
68
+ background-color: #eee
69
+
70
+ .navigation a
71
+ margin-right: 6px
72
+
73
+ .avatar-large
74
+ max-width: 100px
75
+ max-height: 150px
76
+ min-width: 100px
77
+ min-height: 100px
78
+
79
+ .gray
80
+ color: gray
81
+
82
+ h2.condensed
83
+ line-height: 24px
84
+
85
+ .offset0-5
86
+ margin-left: 42px
87
+
88
+ .offset0-6
89
+ margin-left: 62px
90
+
91
+ .no-offset
92
+ margin-left: 0px
93
+
94
+ .profile-stats
95
+ .stat
96
+ text-align: center
97
+ float: left
98
+ line-height: 32px
99
+ margin-right: 20px
100
+ .title
101
+ font-size: 16px
102
+ font-weight: 500
103
+ .value
104
+ font-size: 20px
105
+ font-weight: 600
106
+
107
+ body
108
+ margin-bottom: 60px
109
+
110
+ footer.navbar-fixed-bottom
111
+ background-color: #fff
112
+
113
+ text-align: center
114
+ margin-top: 20px
115
+ padding-top: 10px
116
+ padding-bottom: 10px
117
+ border-top: 1px grey solid