character 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/.gitignore +1 -0
  2. data/Gemfile +4 -0
  3. data/README.md +20 -0
  4. data/Rakefile +7 -0
  5. data/app/controllers/character/posts_controller.rb +27 -0
  6. data/app/models/character/post.rb +20 -0
  7. data/character.gemspec +24 -0
  8. data/lib/character.rb +3 -0
  9. data/lib/character/engine.rb +5 -0
  10. data/lib/character/routing.rb +11 -0
  11. data/lib/character/version.rb +3 -0
  12. data/lib/generators/character/install_generator.rb +42 -0
  13. data/lib/generators/character/templates/README +1 -0
  14. data/lib/generators/character/templates/admin/character.rb +3 -0
  15. data/vendor/assets/fonts/general_foundicons.eot +0 -0
  16. data/vendor/assets/fonts/general_foundicons.svg +15 -0
  17. data/vendor/assets/fonts/general_foundicons.ttf +0 -0
  18. data/vendor/assets/fonts/general_foundicons.woff +0 -0
  19. data/vendor/assets/javascripts/backbone.js +1431 -0
  20. data/vendor/assets/javascripts/character/index.js.coffee +53 -0
  21. data/vendor/assets/javascripts/character/models/post.js.coffee +39 -0
  22. data/vendor/assets/javascripts/character/views/app.js.coffee +81 -0
  23. data/vendor/assets/javascripts/character/views/editor.js.coffee +231 -0
  24. data/vendor/assets/javascripts/character/views/editor_settings.js.coffee +44 -0
  25. data/vendor/assets/javascripts/character/views/index.js.coffee +116 -0
  26. data/vendor/assets/javascripts/character/views/preview.js.coffee +49 -0
  27. data/vendor/assets/javascripts/jquery.smartresize.js +30 -0
  28. data/vendor/assets/javascripts/lodash.js +4258 -0
  29. data/vendor/assets/javascripts/showdown.js +62 -0
  30. data/vendor/assets/javascripts/underscore.string.js +600 -0
  31. data/vendor/assets/stylesheets/character/_base.css.scss +84 -0
  32. data/vendor/assets/stylesheets/character/_icons.css.scss.erb +96 -0
  33. data/vendor/assets/stylesheets/character/_view_editor.css.scss +115 -0
  34. data/vendor/assets/stylesheets/character/_view_index.css.scss +73 -0
  35. data/vendor/assets/stylesheets/character/_view_preview.css.scss +49 -0
  36. data/vendor/assets/stylesheets/character/index.css.scss +32 -0
  37. metadata +103 -0
@@ -0,0 +1,53 @@
1
+ #= require ../lodash
2
+ #= require ../underscore.string
3
+ #= require ../backbone
4
+ #= require ../showdown
5
+ #= require ../jquery.smartresize
6
+
7
+ #= require_tree ./models
8
+ #= require_tree ./views
9
+ #= require_tree ./
10
+
11
+
12
+ window.set_routes = ->
13
+ Router = Backbone.Router.extend
14
+ routes:
15
+ 'new': 'newPost'
16
+ 'edit/:id': 'editPost'
17
+ 'preview/:id': 'showPostPreview'
18
+ '': 'showIndex'
19
+
20
+ router = new Router
21
+
22
+ router.on 'route:newPost', ->
23
+ window.app.show_editor()
24
+
25
+ router.on 'route:editPost', (id) ->
26
+ window.app.show_editor(id)
27
+
28
+ router.on 'route:showPostPreview', (id) ->
29
+ window.app.show_preview(id)
30
+
31
+ router.on 'route:showIndex', (id) ->
32
+ if window.app.preview_view
33
+ window.app.close_preview()
34
+ else
35
+ window.app.show_index()
36
+
37
+ window.router = router
38
+
39
+ # Start Backbone history a necessary step for bookmarkable URL's
40
+ Backbone.history.start()
41
+
42
+
43
+ window.initialize_character = (blog_url = 'http://this-is-blog.com/', app_container = '#wrapper') ->
44
+ window.posts = new Posts()
45
+
46
+ window.posts.fetch success: ->
47
+ window.app = new AppView(blog_url, app_container)
48
+ window.app.show_index()
49
+
50
+ window.set_routes()
51
+
52
+
53
+
@@ -0,0 +1,39 @@
1
+ class Post extends Backbone.Model
2
+ #id
3
+ #title
4
+ #category
5
+ #featured_image
6
+ #html
7
+ #md
8
+ #date
9
+ #views
10
+ #published
11
+
12
+ idAttribute: '_id'
13
+
14
+
15
+ date_formatted: ->
16
+ date = @get('date')
17
+ if date then date.replace(/-/g, '/') else "Date Not Set"
18
+
19
+
20
+ featured_image: ->
21
+ @get('featured_image_url')
22
+
23
+
24
+ state: ->
25
+ if @get('published')
26
+ "Published"
27
+ else "Draft"
28
+
29
+
30
+ @slugify: (text) ->
31
+ _.string.slugify(text)
32
+
33
+
34
+ class Posts extends Backbone.Collection
35
+ model: Post
36
+ url: '/admin/character/posts'
37
+
38
+ window.Posts = Posts
39
+ window.Post = Post
@@ -0,0 +1,81 @@
1
+ ############################
2
+ ### CHARACTER :: AppView ###
3
+ ############################
4
+
5
+ class AppView extends Backbone.View
6
+
7
+ initialize: (@blog_url, @el) ->
8
+ @render()
9
+ window.index_scroll_y = 0
10
+ @blog_url ||= 'http://default-blog-url.com/'
11
+
12
+
13
+ render: ->
14
+ html = """<div id='main'></div>"""
15
+ $(@el).append html
16
+
17
+
18
+ find_post: (id) ->
19
+ window.posts.get(id)
20
+
21
+
22
+ clear_view: ->
23
+ (@index_view.remove() ; delete @index_view) if @index_view
24
+ (@preview_view.remove() ; delete @preview_view) if @preview_view
25
+ (@editor_view.remove() ; delete @editor_view) if @editor_view
26
+
27
+
28
+ show_index: ->
29
+ @clear_view()
30
+ @index_view = new IndexView()
31
+
32
+
33
+ show_preview: (post_id) ->
34
+ if @editor_view
35
+ @clear_view()
36
+ @show_index()
37
+
38
+ @index_view.set_active(post_id)
39
+
40
+ if not @preview_view
41
+ @index_view.lock()
42
+
43
+ # render preview
44
+ @preview_view.remove() if @preview_view
45
+
46
+ post = @find_post(post_id)
47
+ @preview_view = new PostPreviewView model: post
48
+
49
+ html = @preview_view.render().el
50
+
51
+ $('#main').append(html)
52
+
53
+ window.scroll(0, 0)
54
+
55
+
56
+
57
+ close_preview: ->
58
+ @index_view.unlock()
59
+
60
+ # hide preview
61
+ if @preview_view
62
+ @preview_view.remove()
63
+ delete @preview_view
64
+
65
+ @index_view.unset_active()
66
+ window.index_scroll_y = 0
67
+
68
+
69
+
70
+ show_editor: (post_id) ->
71
+ @clear_view()
72
+
73
+ post = if post_id then @find_post(post_id) else null
74
+
75
+ @editor_view = new EditorView model: post
76
+
77
+
78
+ window.AppView = AppView
79
+
80
+
81
+
@@ -0,0 +1,231 @@
1
+ ###############################
2
+ ### CHARACTER :: EditorView ###
3
+ ###############################
4
+
5
+ # Code is partly taken from:
6
+ # - http://www.showdown.im/showdown/example/showdown-gui.js
7
+
8
+ class EditorView extends Backbone.View
9
+ tagName: 'div'
10
+ className: 'editor'
11
+
12
+
13
+ initialize: ->
14
+ html = @render().el
15
+ $('#main').append(html)
16
+
17
+ @settings = new EditorSettingsView model: @model
18
+
19
+ @converter = new Showdown.converter()
20
+ @last_text = null
21
+ @title = document.getElementById('title')
22
+ @update_permalink()
23
+
24
+ @slug = document.getElementById('slug')
25
+ @markdown = document.getElementById('markdown')
26
+ @html = document.getElementById('html')
27
+ @max_delay = 3000
28
+
29
+ @resize_panels()
30
+ @typing_events()
31
+ @convert_text()
32
+
33
+
34
+ update_or_create_post: (extra_attributes, callback) ->
35
+ attributes =
36
+ title: $(@title).val()
37
+ md: $(@markdown).val()
38
+ html: @html.innerHTML
39
+ slug: @slug.innerHTML
40
+ date: @settings.date()
41
+
42
+ _.extend attributes, extra_attributes
43
+
44
+ if @model
45
+ @model.save(attributes, {success: callback})
46
+ else
47
+ window.posts.create(attributes, {wait: true, success: callback})
48
+
49
+
50
+ #if @model.isNew()
51
+ # @model.set
52
+ # id: @model.cid
53
+ # views: 0
54
+ # @model.save()
55
+ # window.posts.add(@model, {at: 0})
56
+
57
+
58
+ save_draft: ->
59
+ @update_or_create_post {published: false}, =>
60
+ @back_to_index()
61
+
62
+
63
+ publish: ->
64
+ @update_or_create_post {published: true}, =>
65
+ @back_to_index()
66
+
67
+
68
+ back_to_index: ->
69
+ path = if @model then "#/preview/#{@model.id}" else '#/'
70
+ router.navigate(path, {trigger: true})
71
+
72
+
73
+ events:
74
+ 'click .save-draft': 'save_draft'
75
+ 'click .publish': 'publish'
76
+ 'click .cancel': 'back_to_index'
77
+
78
+
79
+ update_word_counter: ->
80
+ counter = 0
81
+ md_text = $(@markdown).val()
82
+ if md_text.length > 0
83
+ counter = md_text.match(/[^\s]+/g).length
84
+ $(document.getElementById('word_counter')).html "#{counter} words"
85
+
86
+
87
+ update_permalink: ->
88
+ blog_url = window.app.blog_url
89
+ slug = Post.slugify($(@title).val())
90
+ html = """<strong>Permalink:</strong> #{blog_url}<strong id='slug'>#{slug}</strong>"""
91
+ $('#permalink').html html
92
+
93
+
94
+ render: ->
95
+ post = if @model then @model.toJSON() else {title: 'Post Title', md: 'Post Text'}
96
+
97
+ html = """<header>
98
+ <div class='title'>
99
+ <input type='text' placeholder='Title' value='#{post.title}' id='title'/>
100
+ </div>
101
+ <div class='permalink' id='permalink'>
102
+ </div>
103
+ </header>
104
+
105
+ <div class='chr-panel left index fixed'>
106
+ <section class='container'>
107
+ <header>
108
+ <span class='title'>Markdown</span>
109
+ <span class='buttons'>
110
+ <a href='#' title='Markdown syntax' class='foundicon-help'></a>
111
+ </span>
112
+ </header>
113
+ <div>
114
+ <textarea id='markdown'>#{post.md}</textarea>
115
+ </div>
116
+ </section>
117
+ </div>
118
+
119
+ <div class='chr-panel right preview fixed'>
120
+ <section class='container'>
121
+ <header>
122
+ <span class='title'>Preview</span>
123
+ <span class='info' id='word_counter'>549 words</span>
124
+ </header>
125
+
126
+ <article>
127
+ <section class='content' id='html'></section>
128
+ </article>
129
+ </section>
130
+ </div>
131
+
132
+ <footer>
133
+ <button class='cancel'>Cancel</button>
134
+ <button class='publish'>Publish</button>
135
+ <button class='save-draft'>Save Draft</button>
136
+ </footer>"""
137
+ $(this.el).html html
138
+ return this
139
+
140
+
141
+ on_input: (callback) ->
142
+ # In "delayed" mode, we do the conversion at pauses in input.
143
+ # The pause is equal to the last runtime, so that slow
144
+ # updates happen less frequently.
145
+ #
146
+ # Use a timer to schedule updates. Each keystroke
147
+ # resets the timer.
148
+
149
+ # if we already have convertText scheduled, cancel it
150
+ if convert_text_timer
151
+ window.clearTimeout(convert_text_timer)
152
+ convert_text_timer = null
153
+
154
+ time_until_convert_text = 0
155
+
156
+ if time_until_convert_text > @max_delay
157
+ time_until_convert_text = @max_delay
158
+
159
+ # Schedule @convert_text().
160
+ # Even if we're updating every keystroke, use a timer at 0.
161
+ # This gives the browser time to handle other events.
162
+ convert_text_timer = window.setTimeout(callback, time_until_convert_text)
163
+
164
+
165
+ typing_events: ->
166
+ # First, try registering for keyup events
167
+ # (There's no harm in calling onInput() repeatedly)
168
+ @markdown.onkeyup = => @on_input(@convert_text)
169
+ @title.onkeyup = => @on_input(@update_permalink)
170
+
171
+ # In case we can't capture paste events, poll for them
172
+ polling_fallback = window.setInterval ( => @on_input() if not @markdown.value == @last_text ), 1000
173
+
174
+ # Try registering for paste events
175
+ @markdown.onpaste = =>
176
+ # It worked! Cancel paste polling.
177
+ if polling_fallback
178
+ window.clearInterval(polling_fallback)
179
+ polling_fallback = null
180
+ @on_input()
181
+
182
+ # Try registering for input events (the best solution)
183
+ if @markdown.addEventListener
184
+ # Let's assume input also fires on paste.
185
+ # No need to cancel our keyup handlers;
186
+ # they're basically free.
187
+ @markdown.addEventListener('input', @markdown.onpaste, false)
188
+
189
+ @markdown.focus()
190
+
191
+
192
+ convert_text: =>
193
+ text = @markdown.value
194
+
195
+ return if text and text == @last_text
196
+
197
+ @last_text = text
198
+
199
+ # do the conversion
200
+ @html.innerHTML = @converter.makeHtml(text)
201
+ @update_word_counter()
202
+
203
+
204
+ resize_panels: ->
205
+ markdown = $(@markdown)
206
+ html = $(@html).parent()
207
+ window_height = $(window).height()
208
+ footer_height = $('footer').outerHeight()
209
+
210
+ markdown.css 'height', window_height - markdown.offset().top - footer_height
211
+ html.css 'height', window_height - html.offset().top - footer_height
212
+
213
+ $(window).smartresize =>
214
+ @resize_panels()
215
+
216
+
217
+ destroy = ->
218
+ # clear smartresize event
219
+ @markdown.onkeyup = null
220
+ @title.onkeyup = null
221
+
222
+
223
+ window.EditorView = EditorView
224
+
225
+
226
+
227
+
228
+
229
+
230
+
231
+
@@ -0,0 +1,44 @@
1
+
2
+ class EditorSettingsView extends Backbone.View
3
+ tagName: 'div'
4
+ className: 'settings'
5
+ id: 'settings'
6
+
7
+
8
+ initialize: ->
9
+ html = @render().el
10
+ $('footer').append(html)
11
+
12
+ @settings_btn = document.getElementById('settings_btn')
13
+
14
+ @shown = false
15
+
16
+
17
+ render: ->
18
+ date = @model?.get('date')
19
+ html = """<div class='settings-box'>
20
+ Date: <input id='date' type='date' value='#{date}'>
21
+ </div>
22
+ <button class='foundicon-settings' id='settings_btn'></button>"""
23
+ $(this.el).html html
24
+ return this
25
+
26
+
27
+ date: ->
28
+ $('#date').val()
29
+
30
+
31
+ show_or_hide_settings_box: ->
32
+ if @shown
33
+ $(@el).removeClass('shown')
34
+ @shown = false
35
+ else
36
+ @shown = true
37
+ $(@el).addClass('shown')
38
+
39
+
40
+ events:
41
+ 'click #settings_btn': 'show_or_hide_settings_box'
42
+
43
+
44
+ window.EditorSettingsView = EditorSettingsView