character 0.1.0

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 (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