ecrire 0.20.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENCE +9 -0
- data/README.md +62 -0
- data/Rakefile +72 -0
- data/bin/ecrire +42 -0
- data/ecrire.gemspec +26 -0
- data/lib/ecrire/app/assets/javascripts/admin/editor/content.coffee +319 -0
- data/lib/ecrire/app/assets/javascripts/admin/editor/ext.coffee +94 -0
- data/lib/ecrire/app/assets/javascripts/admin/editor/extensions/clipboard.coffee +98 -0
- data/lib/ecrire/app/assets/javascripts/admin/editor/extensions/image.coffee +19 -0
- data/lib/ecrire/app/assets/javascripts/admin/editor/navigation/delete.js.coffee +11 -0
- data/lib/ecrire/app/assets/javascripts/admin/editor/navigation/draft.js.coffee +42 -0
- data/lib/ecrire/app/assets/javascripts/admin/editor/navigation/help.js.coffee +6 -0
- data/lib/ecrire/app/assets/javascripts/admin/editor/navigation/save.js.coffee +61 -0
- data/lib/ecrire/app/assets/javascripts/admin/editor/navigation/sticky.js.coffee +11 -0
- data/lib/ecrire/app/assets/javascripts/admin/editor/navigation/toggle.js.coffee +10 -0
- data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/code.coffee +39 -0
- data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/heading.coffee +19 -0
- data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/image.coffee +129 -0
- data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/link.coffee +44 -0
- data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/lists.coffee +48 -0
- data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/word.coffee +56 -0
- data/lib/ecrire/app/assets/javascripts/admin/posts/header.coffee +69 -0
- data/lib/ecrire/app/assets/javascripts/admin/posts/new.js.coffee +7 -0
- data/lib/ecrire/app/assets/javascripts/admin/posts/title.coffee +15 -0
- data/lib/ecrire/app/assets/javascripts/admin.js +5 -0
- data/lib/ecrire/app/assets/javascripts/application.js +21 -0
- data/lib/ecrire/app/assets/javascripts/shared/overlay.js.coffee +29 -0
- data/lib/ecrire/app/assets/javascripts/shared/popup.js.coffee +7 -0
- data/lib/ecrire/app/assets/javascripts/vendor/moment.js +3043 -0
- data/lib/ecrire/app/assets/javascripts/vendor/prism/prism.ruby.js +25 -0
- data/lib/ecrire/app/assets/javascripts/vendor/prism.js +578 -0
- data/lib/ecrire/app/assets/stylesheets/admin/base.css.scss +98 -0
- data/lib/ecrire/app/assets/stylesheets/admin/documentation.css.scss +28 -0
- data/lib/ecrire/app/assets/stylesheets/admin/editor/base.css.scss +41 -0
- data/lib/ecrire/app/assets/stylesheets/admin/editor/content.css.scss +57 -0
- data/lib/ecrire/app/assets/stylesheets/admin/editor/header.css.scss +62 -0
- data/lib/ecrire/app/assets/stylesheets/admin/editor/image.css.scss +58 -0
- data/lib/ecrire/app/assets/stylesheets/admin/editor/publish.css.scss +40 -0
- data/lib/ecrire/app/assets/stylesheets/admin/editor/save.css.scss +44 -0
- data/lib/ecrire/app/assets/stylesheets/admin/images.css.scss +64 -0
- data/lib/ecrire/app/assets/stylesheets/admin/loading.css.scss +48 -0
- data/lib/ecrire/app/assets/stylesheets/admin/mixins/animations.css.scss +30 -0
- data/lib/ecrire/app/assets/stylesheets/admin/mixins/form.css.scss +22 -0
- data/lib/ecrire/app/assets/stylesheets/admin/navigation.css.scss +96 -0
- data/lib/ecrire/app/assets/stylesheets/admin/posts.css.scss +46 -0
- data/lib/ecrire/app/assets/stylesheets/admin/variables.css.scss +7 -0
- data/lib/ecrire/app/assets/stylesheets/admin.css.scss +5 -0
- data/lib/ecrire/app/assets/stylesheets/application.css.scss +12 -0
- data/lib/ecrire/app/assets/stylesheets/sessions.css.scss +97 -0
- data/lib/ecrire/app/assets/stylesheets/shared/main.css.scss +5 -0
- data/lib/ecrire/app/assets/stylesheets/shared/popup.css.scss +76 -0
- data/lib/ecrire/app/assets/stylesheets/vendor/prism/ruby.scss +11 -0
- data/lib/ecrire/app/assets/stylesheets/vendor/prism.scss +149 -0
- data/lib/ecrire/app/controllers/admin/application_controller.rb +16 -0
- data/lib/ecrire/app/controllers/admin/configurations/images_controller.rb +28 -0
- data/lib/ecrire/app/controllers/admin/images_controller.rb +35 -0
- data/lib/ecrire/app/controllers/admin/labels_controller.rb +18 -0
- data/lib/ecrire/app/controllers/admin/partials_controller.rb +43 -0
- data/lib/ecrire/app/controllers/admin/posts_controller.rb +90 -0
- data/lib/ecrire/app/controllers/admin/properties_controller.rb +35 -0
- data/lib/ecrire/app/controllers/application_controller.rb +29 -0
- data/lib/ecrire/app/controllers/concerns/.keep +0 -0
- data/lib/ecrire/app/controllers/ecrire/posts_controller.rb +42 -0
- data/lib/ecrire/app/controllers/images_controller.rb +2 -0
- data/lib/ecrire/app/controllers/onboarding/databases_controller.rb +67 -0
- data/lib/ecrire/app/controllers/onboarding/users_controller.rb +32 -0
- data/lib/ecrire/app/controllers/onboarding_controller.rb +20 -0
- data/lib/ecrire/app/controllers/partials_controller.rb +8 -0
- data/lib/ecrire/app/controllers/sessions_controller.rb +23 -0
- data/lib/ecrire/app/controllers/static_controller.rb +5 -0
- data/lib/ecrire/app/forms/admin/image_builder.rb +24 -0
- data/lib/ecrire/app/forms/admin/partial_builder.rb +105 -0
- data/lib/ecrire/app/forms/admin/post_builder.rb +47 -0
- data/lib/ecrire/app/helpers/admin/images_helper.rb +60 -0
- data/lib/ecrire/app/helpers/admin/labels_helper.rb +26 -0
- data/lib/ecrire/app/helpers/admin/posts_helper.rb +26 -0
- data/lib/ecrire/app/helpers/application_helper.rb +64 -0
- data/lib/ecrire/app/helpers/content_tag_helper.rb +91 -0
- data/lib/ecrire/app/helpers/open_graph_helper.rb +45 -0
- data/lib/ecrire/app/helpers/posts_helper.rb +19 -0
- data/lib/ecrire/app/models/admin/image.rb +108 -0
- data/lib/ecrire/app/models/admin/partial.rb +4 -0
- data/lib/ecrire/app/models/admin/post.rb +56 -0
- data/lib/ecrire/app/models/concerns/.keep +0 -0
- data/lib/ecrire/app/models/image.rb +5 -0
- data/lib/ecrire/app/models/label.rb +3 -0
- data/lib/ecrire/app/models/partial.rb +3 -0
- data/lib/ecrire/app/models/post.rb +90 -0
- data/lib/ecrire/app/models/property/image.rb +30 -0
- data/lib/ecrire/app/models/property/label.rb +28 -0
- data/lib/ecrire/app/models/user.rb +31 -0
- data/lib/ecrire/app/strategies/password_strategy.rb +17 -0
- data/lib/ecrire/app/views/admin/configurations/images/complete.html.erb +6 -0
- data/lib/ecrire/app/views/admin/configurations/images/show.html.erb +14 -0
- data/lib/ecrire/app/views/admin/images/_errors.html.erb +14 -0
- data/lib/ecrire/app/views/admin/images/_form.html.erb +15 -0
- data/lib/ecrire/app/views/admin/images/destroy.js.erb +0 -0
- data/lib/ecrire/app/views/admin/images/errors.js.erb +1 -0
- data/lib/ecrire/app/views/admin/images/update.js.erb +1 -0
- data/lib/ecrire/app/views/admin/posts/_documentation.html.erb +61 -0
- data/lib/ecrire/app/views/admin/posts/_list.html.erb +18 -0
- data/lib/ecrire/app/views/admin/posts/_post.html.erb +3 -0
- data/lib/ecrire/app/views/admin/posts/edit.html.erb +73 -0
- data/lib/ecrire/app/views/admin/posts/help.js.erb +1 -0
- data/lib/ecrire/app/views/admin/posts/index.js.erb +1 -0
- data/lib/ecrire/app/views/admin/posts/title.html.erb +10 -0
- data/lib/ecrire/app/views/admin/posts/update/content.js.erb +1 -0
- data/lib/ecrire/app/views/admin/posts/update/title.js.erb +1 -0
- data/lib/ecrire/app/views/admin/properties/image/create.js.erb +14 -0
- data/lib/ecrire/app/views/admin/properties/image/destroy.js.erb +6 -0
- data/lib/ecrire/app/views/admin/properties/label/create.js.erb +8 -0
- data/lib/ecrire/app/views/admin/properties/label/destroy.js.erb +2 -0
- data/lib/ecrire/app/views/layouts/admin/application.html.erb +47 -0
- data/lib/ecrire/app/views/partials/show.html.erb +14 -0
- data/lib/ecrire/app/views/sessions/_form.html.erb +18 -0
- data/lib/ecrire/app/views/sessions/_navigation.html.erb +24 -0
- data/lib/ecrire/app/views/sessions/new.html.erb +1 -0
- data/lib/ecrire/application.rb +18 -0
- data/lib/ecrire/commands/base.rb +9 -0
- data/lib/ecrire/commands/console.rb +21 -0
- data/lib/ecrire/commands/new.rb +47 -0
- data/lib/ecrire/commands/server.rb +27 -0
- data/lib/ecrire/commands.rb +19 -0
- data/lib/ecrire/config/environment/development.rb +12 -0
- data/lib/ecrire/config/environment/production.rb +12 -0
- data/lib/ecrire/config/environment/test.rb +3 -0
- data/lib/ecrire/config/environment.rb +46 -0
- data/lib/ecrire/config/onboarding_routes.rb +11 -0
- data/lib/ecrire/config/routes.rb +32 -0
- data/lib/ecrire/db/migrate/20131111222709_create_posts.rb +16 -0
- data/lib/ecrire/db/migrate/20131113200949_create_users.rb +12 -0
- data/lib/ecrire/db/migrate/20131114205041_add_excerpt_to_post.rb +5 -0
- data/lib/ecrire/db/migrate/20131221181640_add_javascript_to_posts.rb +5 -0
- data/lib/ecrire/db/migrate/20131221195338_create_partials.rb +12 -0
- data/lib/ecrire/db/migrate/20140115215122_create_images.rb +12 -0
- data/lib/ecrire/db/migrate/20140209144412_add_favorite_flag_to_image.rb +5 -0
- data/lib/ecrire/db/migrate/20140617224837_create_labels.rb +12 -0
- data/lib/ecrire/db/migrate/20140618321984_add_properties_to_posts.rb +6 -0
- data/lib/ecrire/db/migrate/20150113839713_convert_markdown_in_post_content.rb +6 -0
- data/lib/ecrire/db/migrate/20150120093481_add_preview_to_posts.rb +8 -0
- data/lib/ecrire/db/schema.rb +76 -0
- data/lib/ecrire/locales/admin/configurations/images/en.yml +3 -0
- data/lib/ecrire/locales/admin/en.yml +26 -0
- data/lib/ecrire/locales/admin/images/en.yml +5 -0
- data/lib/ecrire/locales/admin/partials/en.yml +18 -0
- data/lib/ecrire/locales/admin/posts/en.yml +14 -0
- data/lib/ecrire/locales/en.yml +9 -0
- data/lib/ecrire/onboarding/assets/images/.keep +0 -0
- data/lib/ecrire/onboarding/assets/images/background.png +0 -0
- data/lib/ecrire/onboarding/assets/images/github-logo.png +0 -0
- data/lib/ecrire/onboarding/assets/images/profile.png +0 -0
- data/lib/ecrire/onboarding/assets/images/twitter-logo.png +0 -0
- data/lib/ecrire/onboarding/assets/javascripts/base.js +2 -0
- data/lib/ecrire/onboarding/assets/javascripts/databases/information.js.coffee +17 -0
- data/lib/ecrire/onboarding/assets/javascripts/message.js.coffee +5 -0
- data/lib/ecrire/onboarding/assets/stylesheets/base.css.scss +80 -0
- data/lib/ecrire/onboarding/assets/stylesheets/complete.css.scss +3 -0
- data/lib/ecrire/onboarding/assets/stylesheets/fonts.css.scss +17 -0
- data/lib/ecrire/onboarding/assets/stylesheets/form.css.scss +25 -0
- data/lib/ecrire/onboarding/assets/stylesheets/message.css.scss +11 -0
- data/lib/ecrire/onboarding/assets/stylesheets/terminal.css.scss +16 -0
- data/lib/ecrire/onboarding/assets/stylesheets/variables.css.scss +3 -0
- data/lib/ecrire/onboarding/assets/stylesheets/welcome.css.scss +62 -0
- data/lib/ecrire/onboarding/views/layouts/application.html.erb +16 -0
- data/lib/ecrire/onboarding/views/onboarding/complete.html.erb +6 -0
- data/lib/ecrire/onboarding/views/onboarding/databases/index.html.erb +34 -0
- data/lib/ecrire/onboarding/views/onboarding/users/index.html.erb +16 -0
- data/lib/ecrire/onboarding/views/onboarding/welcome.html.erb +12 -0
- data/lib/ecrire/railtie/onboarding.rb +63 -0
- data/lib/ecrire/railtie/theme.rb +69 -0
- data/lib/ecrire/railtie.rb +30 -0
- data/lib/ecrire/tasks/assets.rake +15 -0
- data/lib/ecrire/tasks/routes.rake +6 -0
- data/lib/ecrire/template/Gemfile +31 -0
- data/lib/ecrire/template/Rakefile +4 -0
- data/lib/ecrire/template/assets/images/.keep +0 -0
- data/lib/ecrire/template/assets/javascripts/base.js.coffee +0 -0
- data/lib/ecrire/template/assets/javascripts/post.js.coffee +0 -0
- data/lib/ecrire/template/assets/stylesheets/base.css.scss +86 -0
- data/lib/ecrire/template/assets/stylesheets/code.css.scss +24 -0
- data/lib/ecrire/template/assets/stylesheets/footer.css.scss +5 -0
- data/lib/ecrire/template/assets/stylesheets/header.css.scss +18 -0
- data/lib/ecrire/template/assets/stylesheets/home.css.scss +35 -0
- data/lib/ecrire/template/config.ru +4 -0
- data/lib/ecrire/template/controllers/posts_controller.rb +2 -0
- data/lib/ecrire/template/helpers/blog_helper.rb +2 -0
- data/lib/ecrire/template/views/layouts/application.html.erb +35 -0
- data/lib/ecrire/template/views/posts/_post.html.erb +9 -0
- data/lib/ecrire/template/views/posts/index.html.erb +13 -0
- data/lib/ecrire/template/views/posts/show.html.erb +7 -0
- data/lib/ecrire/template/views/static/about.html +0 -0
- data/lib/ecrire/template/views/static/customize/_helper.html +7 -0
- data/lib/ecrire/template/views/static/customize/_helper_used.html +6 -0
- data/lib/ecrire/template/views/static/customize/_image.html +1 -0
- data/lib/ecrire/template/views/static/customize/_index.html +1 -0
- data/lib/ecrire/template/views/static/customize/_javascript.html +1 -0
- data/lib/ecrire/template/views/static/customize/_layout.html.erb +15 -0
- data/lib/ecrire/template/views/static/customize/_post.html +3 -0
- data/lib/ecrire/template/views/static/customize.html +95 -0
- data/lib/ecrire/version.rb +3 -0
- data/lib/ecrire.rb +24 -0
- data/test/editor/controllers/admin/images_controller_test.rb +6 -0
- data/test/editor/controllers/admin/posts_controller_test.rb +90 -0
- data/test/editor/controllers/base_controller_test.rb +26 -0
- data/test/editor/controllers/images_controller_test.rb +2 -0
- data/test/editor/controllers/partials_controller_test.rb +2 -0
- data/test/editor/controllers/posts_controller_test.rb +21 -0
- data/test/editor/controllers/sessions_controller_test.rb +21 -0
- data/test/editor/helpers/base_helper_test.rb +19 -0
- data/test/editor/helpers/posts_helper_test.rb +3 -0
- data/test/editor/models/image_test.rb +33 -0
- data/test/editor/models/partial_test.rb +4 -0
- data/test/editor/models/post_test.rb +109 -0
- data/test/editor/models/user_test.rb +4 -0
- data/test/editor/test_helper.rb +28 -0
- data/test/fixtures/images.yml +11 -0
- data/test/fixtures/partials.yml +9 -0
- data/test/fixtures/posts.yml +16 -0
- data/test/fixtures/users.yml +9 -0
- data/test/initializations/railtie_test.rb +49 -0
- data/test/isolation.rb +16 -0
- data/test/themes/template/Gemfile +28 -0
- data/test/themes/template/assets/javascripts/blog.js.coffee +0 -0
- data/test/themes/template/assets/javascripts/post.js.coffee +0 -0
- data/test/themes/template/assets/stylesheets/blog.css.scss +0 -0
- data/test/themes/template/assets/stylesheets/post.css.scss +0 -0
- data/test/themes/template/config.ru +0 -0
- data/test/themes/template/views/layouts/application.html.erb +1 -0
- data/test/themes/template/views/posts/index.html.erb +0 -0
- metadata +275 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
Editor.Extensions.push class ClipBoard
|
2
|
+
constructor: (editor) ->
|
3
|
+
@editor = editor
|
4
|
+
@editor.on 'paste', @paste
|
5
|
+
|
6
|
+
paste: (e) =>
|
7
|
+
e.preventDefault()
|
8
|
+
e.stopPropagation()
|
9
|
+
|
10
|
+
data = e.clipboardData.getData('text/plain')
|
11
|
+
|
12
|
+
texts = data.split('\n').map (text) ->
|
13
|
+
document.createTextNode(text)
|
14
|
+
return unless texts.length > 0
|
15
|
+
|
16
|
+
_sel = window.getSelection()
|
17
|
+
sel = {
|
18
|
+
type: _sel.type
|
19
|
+
}
|
20
|
+
position = _sel.anchorNode.compareDocumentPosition(_sel.extentNode)
|
21
|
+
if _sel.anchorOffset <= _sel.extentOffset || position == document.DOCUMENT_POSITION_PRECEDING
|
22
|
+
sel.anchorNode = _sel.anchorNode
|
23
|
+
sel.anchorOffset = _sel.anchorOffset
|
24
|
+
sel.extentNode = _sel.extentNode
|
25
|
+
sel.extentOffset = _sel.extentOffset
|
26
|
+
else
|
27
|
+
sel.anchorNode = _sel.extentNode
|
28
|
+
sel.anchorOffset = _sel.extentOffset
|
29
|
+
sel.extentNode = _sel.anchorNode
|
30
|
+
sel.extentOffset = _sel.anchorOffset
|
31
|
+
|
32
|
+
if sel.anchorNode == @editor.element()
|
33
|
+
line = @editor.line()
|
34
|
+
@editor.element().appendChild(line)
|
35
|
+
sel.anchorNode = sel.extentNode = line
|
36
|
+
|
37
|
+
@editor.observer.hold =>
|
38
|
+
if sel.type == 'Range'
|
39
|
+
@replace(texts, sel)
|
40
|
+
else
|
41
|
+
@insert(texts, sel)
|
42
|
+
|
43
|
+
|
44
|
+
replace: (texts, sel) =>
|
45
|
+
|
46
|
+
node = sel.extentNode
|
47
|
+
line = node
|
48
|
+
nodes = [node]
|
49
|
+
|
50
|
+
while line.parentElement != @editor.element()
|
51
|
+
line = line.parentElement
|
52
|
+
|
53
|
+
offsets = {
|
54
|
+
start: sel.anchorOffset
|
55
|
+
end: sel.extentOffset
|
56
|
+
}
|
57
|
+
|
58
|
+
offsets.start = @editor.lineOffset(line, sel.anchorNode, offsets.start)
|
59
|
+
offsets.end = @editor.lineOffset(line, sel.extentNode, offsets.end)
|
60
|
+
|
61
|
+
text = texts.map((t) ->
|
62
|
+
t.textContent
|
63
|
+
).join()
|
64
|
+
|
65
|
+
|
66
|
+
line.textContent = line.textContent.substr(0, offsets.start) + text + line.textContent.substr(offsets.end)
|
67
|
+
|
68
|
+
fragment = @editor.parse(@editor.cloneNodesFrom(line))
|
69
|
+
|
70
|
+
@editor.updateDOM(line, fragment)
|
71
|
+
|
72
|
+
@editor.setCursorAt(line, offsets.start + text.length)
|
73
|
+
|
74
|
+
|
75
|
+
insert: (texts, sel) =>
|
76
|
+
|
77
|
+
node = sel.anchorNode
|
78
|
+
str = node.textContent
|
79
|
+
text = texts.map((t) ->
|
80
|
+
t.textContent
|
81
|
+
).join("\n")
|
82
|
+
|
83
|
+
node.textContent = str.substr(0, sel.anchorOffset)
|
84
|
+
node.textContent += text
|
85
|
+
node.textContent += str.substr(sel.anchorOffset)
|
86
|
+
|
87
|
+
offset = sel.anchorOffset + text.length
|
88
|
+
|
89
|
+
while node.parentElement != @editor.element()
|
90
|
+
node = node.parentElement
|
91
|
+
|
92
|
+
fragment = @editor.parse(@editor.cloneNodesFrom(node))
|
93
|
+
|
94
|
+
lines = @editor.updateDOM(node, fragment)
|
95
|
+
|
96
|
+
@editor.setCursorAt(lines[0], offset)
|
97
|
+
|
98
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Joint.bind 'Editor.Image', class
|
2
|
+
loaded: =>
|
3
|
+
@element().addEventListener 'dragover', @over
|
4
|
+
@element().addEventListener 'dragleave', @cancel
|
5
|
+
@element().addEventListener 'drop', @drop
|
6
|
+
|
7
|
+
over: (e) =>
|
8
|
+
e.preventDefault()
|
9
|
+
@element().classList.add 'dropping'
|
10
|
+
|
11
|
+
cancel: (e) =>
|
12
|
+
e.preventDefault()
|
13
|
+
@element().classList.remove 'dropping'
|
14
|
+
|
15
|
+
drop: (e) =>
|
16
|
+
e.preventDefault()
|
17
|
+
@element().classList.remove 'dropping'
|
18
|
+
|
19
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Joint.bind 'Post.Delete', class
|
2
|
+
loaded: =>
|
3
|
+
@on 'change', @element().querySelector('input[type=checkbox]'), @toggle
|
4
|
+
|
5
|
+
toggle: (e) =>
|
6
|
+
button = e.target.parentElement
|
7
|
+
if e.target.checked
|
8
|
+
button.removeAttribute('disabled')
|
9
|
+
else
|
10
|
+
button.setAttribute('disabled', 'disabled')
|
11
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
Joint.bind 'Editor.Draft', class
|
2
|
+
resetInterval: 4000
|
3
|
+
loaded: =>
|
4
|
+
@on 'Editor:updated', document, @update
|
5
|
+
@on 'change', @element().querySelector('input[type=checkbox]'), @toggle
|
6
|
+
|
7
|
+
toggle: (e) =>
|
8
|
+
if e.target.checked
|
9
|
+
@show()
|
10
|
+
else
|
11
|
+
@hide()
|
12
|
+
|
13
|
+
show: =>
|
14
|
+
el = @element().firstElementChild
|
15
|
+
el.style.marginLeft = "-#{el.getBoundingClientRect().width}px"
|
16
|
+
el.style.opacity = 0
|
17
|
+
@element().querySelector('form.destroy').classList.remove('hide')
|
18
|
+
@timerID = setTimeout @reset, @resetInterval
|
19
|
+
|
20
|
+
hide: =>
|
21
|
+
if @timerID?
|
22
|
+
clearTimeout(@timerID)
|
23
|
+
@timerID = null
|
24
|
+
|
25
|
+
el = @element().firstElementChild
|
26
|
+
el.style.marginLeft = ''
|
27
|
+
el.style.opacity = 1
|
28
|
+
@element().querySelector('form.destroy').classList.add('hide')
|
29
|
+
|
30
|
+
|
31
|
+
reset: =>
|
32
|
+
checkbox = @element().querySelector('input[type=checkbox]')
|
33
|
+
if checkbox.checked
|
34
|
+
checkbox.checked = false
|
35
|
+
@hide()
|
36
|
+
|
37
|
+
update: (e) =>
|
38
|
+
unless @cached?
|
39
|
+
@cached = e.target.textContent
|
40
|
+
return
|
41
|
+
|
42
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
Joint.bind 'Editor.Save', class
|
2
|
+
loaded: =>
|
3
|
+
@button = @element().querySelector('button')
|
4
|
+
@time = @element().querySelector('div.update > p')
|
5
|
+
|
6
|
+
@on 'Editor:loaded', document, @cache
|
7
|
+
@on 'Editor:updated', document, @update
|
8
|
+
@on 'posts:update', document, @saved
|
9
|
+
@on 'click', @button, @save
|
10
|
+
@on 'beforeunload', window, @confirm
|
11
|
+
|
12
|
+
@button.innerText = @button.getAttribute('persisted')
|
13
|
+
@refresh()
|
14
|
+
@cache()
|
15
|
+
@interval = setInterval(@refresh, 1000)
|
16
|
+
|
17
|
+
refresh: () =>
|
18
|
+
@time.textContent = moment(@time.getAttribute('time')).fromNow()
|
19
|
+
|
20
|
+
confirm: (e) =>
|
21
|
+
if @cache() != PostBody.instance.toString()
|
22
|
+
e.returnValue = "You have unsaved changed."
|
23
|
+
return e.returnValue
|
24
|
+
|
25
|
+
cache: =>
|
26
|
+
cache = PostBody.instance.toString()
|
27
|
+
@cache = (refresh) ->
|
28
|
+
if refresh
|
29
|
+
cache = PostBody.instance.toString()
|
30
|
+
@button.setAttribute('disabled', 'disabled')
|
31
|
+
@button.innerText = @button.getAttribute('persisted')
|
32
|
+
cache
|
33
|
+
@cache()
|
34
|
+
|
35
|
+
save: (e) =>
|
36
|
+
e.preventDefault()
|
37
|
+
e.stopPropagation()
|
38
|
+
|
39
|
+
xhr = new Joint.XHR(e.target.form)
|
40
|
+
xhr.data.set('post[content]', PostBody.instance.toString())
|
41
|
+
xhr.data.set('context', 'content')
|
42
|
+
xhr.send()
|
43
|
+
|
44
|
+
saved: (e) =>
|
45
|
+
if e.MessageHTML
|
46
|
+
event = new CustomEvent('Editor:message', { bubbles: true})
|
47
|
+
event.MessageHTML = e.MessageHTML
|
48
|
+
@element().dispatchEvent(event)
|
49
|
+
@time.setAttribute('time', e.UpdatedAtTime)
|
50
|
+
@refresh()
|
51
|
+
@cache(true)
|
52
|
+
|
53
|
+
update: (e) =>
|
54
|
+
return unless @cache()?
|
55
|
+
if @cache() != PostBody.instance.toString()
|
56
|
+
@button.removeAttribute('disabled')
|
57
|
+
@button.innerText = @button.getAttribute('dirty')
|
58
|
+
else
|
59
|
+
@button.setAttribute('disabled', 'disabled')
|
60
|
+
@button.innerText = @button.getAttribute('persisted')
|
61
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Joint.bind 'Post.Sticky', class
|
2
|
+
loaded: =>
|
3
|
+
@on 'scroll', document, @scrolled
|
4
|
+
|
5
|
+
scrolled: (e) =>
|
6
|
+
top = @element().getBoundingClientRect().top
|
7
|
+
if top < 0
|
8
|
+
@element().style.paddingTop = "#{Math.abs(top)}px"
|
9
|
+
else
|
10
|
+
@element().style.paddingTop = ''
|
11
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
Editor.Parsers.push class
|
2
|
+
rule: /^((~{3,})([a-z]+)?$)(.+)?(~{3,}$)?/mi
|
3
|
+
|
4
|
+
constructor: (node) ->
|
5
|
+
@nodes = [node]
|
6
|
+
@tildeCount = 0
|
7
|
+
|
8
|
+
isMatched: =>
|
9
|
+
@match = @rule.exec(@nodes[0].textContent)
|
10
|
+
if @match? && @match[0]?
|
11
|
+
@tildeCount = @match[2].length
|
12
|
+
@collectSiblingsUntilMatched(@tildeCount, @nodes[0])
|
13
|
+
|
14
|
+
collectSiblingsUntilMatched: (count, node) =>
|
15
|
+
node = node.nextSibling
|
16
|
+
while node
|
17
|
+
@nodes.push node
|
18
|
+
nextNode = node.nextSibling
|
19
|
+
node.remove()
|
20
|
+
|
21
|
+
match = @rule.exec(node.textContent)
|
22
|
+
|
23
|
+
if match? && match[2]? && match[2].length == count
|
24
|
+
break
|
25
|
+
|
26
|
+
node = nextNode
|
27
|
+
|
28
|
+
|
29
|
+
render: =>
|
30
|
+
pre = "<pre>".toHTML()
|
31
|
+
code = "<code>".toHTML()
|
32
|
+
if @match[3]?
|
33
|
+
code.classList.add("language-#{@match[3]}")
|
34
|
+
texts = @nodes.map (n) -> n.textContent
|
35
|
+
code.textContent = texts.join("\n")
|
36
|
+
Prism.highlightElement(code)
|
37
|
+
pre.appendChild(code)
|
38
|
+
pre
|
39
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Editor.Parsers.push class
|
2
|
+
rule: /^(#{1,6}) /i
|
3
|
+
|
4
|
+
constructor: (node) ->
|
5
|
+
@node = node
|
6
|
+
|
7
|
+
isMatched: =>
|
8
|
+
@match = @rule.exec(@node.textContent)
|
9
|
+
@match?
|
10
|
+
|
11
|
+
render: =>
|
12
|
+
header = "<h#{@match[1].length}>".toHTML()
|
13
|
+
|
14
|
+
if header.nodeName == @node.nodeName
|
15
|
+
return @node
|
16
|
+
else
|
17
|
+
header.appendChild(node.cloneNode(true)) for node in Array.prototype.slice.call(@node.childNodes)
|
18
|
+
return header
|
19
|
+
|
@@ -0,0 +1,129 @@
|
|
1
|
+
Editor.Parsers.push class
|
2
|
+
rule: /^(!{1}\[([^\]]+)\])(\(([^\s]+)?\))$/i
|
3
|
+
|
4
|
+
constructor: (node) ->
|
5
|
+
@walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT)
|
6
|
+
|
7
|
+
isMatched: =>
|
8
|
+
@match = @rule.exec(@walker.root.textContent)
|
9
|
+
@match?
|
10
|
+
|
11
|
+
render: =>
|
12
|
+
@container = new Container()
|
13
|
+
|
14
|
+
@uploader = new Editor.ImageUploader(@show)
|
15
|
+
|
16
|
+
|
17
|
+
@picture = "<picture as='Editor.Image'>".toHTML()
|
18
|
+
|
19
|
+
@title = "<em></em>".toHTML()
|
20
|
+
@title.appendChild document.createTextNode(@match[1])
|
21
|
+
@title.appendChild document.createTextNode(@match[3])
|
22
|
+
if @match[4]?
|
23
|
+
@title.setAttribute('name', @match[4])
|
24
|
+
|
25
|
+
@picture.appendChild @container.toHTML(@match[4])
|
26
|
+
@picture.appendChild @title
|
27
|
+
|
28
|
+
@container.input.addEventListener 'change', @update
|
29
|
+
@picture.addEventListener 'drop', @drop
|
30
|
+
|
31
|
+
return @picture
|
32
|
+
|
33
|
+
show: (e, url) =>
|
34
|
+
@container.image(url)
|
35
|
+
@title.lastChild.textContent = "(#{url})"
|
36
|
+
|
37
|
+
update: (e) =>
|
38
|
+
unless e.file?
|
39
|
+
e.file = e.target.files[0]
|
40
|
+
@container.loading()
|
41
|
+
@uploader.send(e)
|
42
|
+
|
43
|
+
drop: (e) =>
|
44
|
+
e.preventDefault()
|
45
|
+
e.file = e.dataTransfer.files[0]
|
46
|
+
@update(e)
|
47
|
+
|
48
|
+
|
49
|
+
class Container
|
50
|
+
constructor: ->
|
51
|
+
@el = "<div contenteditable=false></div>".toHTML()
|
52
|
+
@el.addEventListener 'click', @open
|
53
|
+
|
54
|
+
if !(@input = @el.querySelector('input'))?
|
55
|
+
@input = "<input type='file'>".toHTML()
|
56
|
+
|
57
|
+
@el.appendChild(@input)
|
58
|
+
|
59
|
+
image: (url) =>
|
60
|
+
@el.style.backgroundImage = "url(#{url})"
|
61
|
+
@el.innerHTML = ''
|
62
|
+
@el.classList.remove 'loading'
|
63
|
+
|
64
|
+
loading: =>
|
65
|
+
@loading = =>
|
66
|
+
@el.classList.add 'loading'
|
67
|
+
@el.innerHTML = ''
|
68
|
+
|
69
|
+
@loading()
|
70
|
+
|
71
|
+
placeholder: =>
|
72
|
+
el = "<p>Drop an image or click here to upload a picture.</p>".toHTML()
|
73
|
+
@placeholder = =>
|
74
|
+
@el.innerHTML = ''
|
75
|
+
@el.appendChild(el)
|
76
|
+
|
77
|
+
@placeholder()
|
78
|
+
|
79
|
+
open: (e) =>
|
80
|
+
@input.click()
|
81
|
+
|
82
|
+
toHTML: (url) =>
|
83
|
+
if url?
|
84
|
+
@image(url)
|
85
|
+
else if @el.classList.contains('loading')
|
86
|
+
@loading()
|
87
|
+
else
|
88
|
+
@placeholder()
|
89
|
+
|
90
|
+
@el
|
91
|
+
|
92
|
+
|
93
|
+
class Editor.ImageUploader
|
94
|
+
constructor: (@callback) ->
|
95
|
+
|
96
|
+
uploaded: (e) =>
|
97
|
+
xml = new DOMParser().parseFromString(e.target.response, 'text/xml')
|
98
|
+
url = xml.querySelector('Location').textContent
|
99
|
+
@callback(e, url)
|
100
|
+
|
101
|
+
send: (e) =>
|
102
|
+
id = PostBody.getAttribute('postid')
|
103
|
+
policy = PostBody.getAttribute('policy')
|
104
|
+
signature = PostBody.getAttribute('signature')
|
105
|
+
bucket = PostBody.getAttribute('bucket')
|
106
|
+
namespace = PostBody.getAttribute('namespace')
|
107
|
+
access_key = PostBody.getAttribute('access_key')
|
108
|
+
|
109
|
+
url = "https://s3.amazonaws.com/#{bucket}/"
|
110
|
+
dir = [id]
|
111
|
+
if namespace?
|
112
|
+
dir.splice(0,0, namespace)
|
113
|
+
|
114
|
+
dir = dir.join '/'
|
115
|
+
|
116
|
+
data = new FormData()
|
117
|
+
data.append 'AWSAccessKeyId', access_key
|
118
|
+
data.append 'success_action_status', 201
|
119
|
+
data.append 'acl', 'private'
|
120
|
+
data.append 'policy', policy
|
121
|
+
data.append 'signature', signature
|
122
|
+
data.append 'key', "#{dir}/#{e.file.name}"
|
123
|
+
data.append 'Content-Type', e.file.type
|
124
|
+
data.append 'file', e.file
|
125
|
+
|
126
|
+
xhr = new XMLHttpRequest()
|
127
|
+
xhr.open('POST', url, true)
|
128
|
+
xhr.onload = @uploaded
|
129
|
+
xhr.send(data)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
Editor.Parsers.push class
|
2
|
+
rule: /!{0}(\[([^\]]+)\])(\(([^\)]+)\))/gi
|
3
|
+
|
4
|
+
constructor: (node, el) ->
|
5
|
+
@walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT)
|
6
|
+
|
7
|
+
isMatched: =>
|
8
|
+
@matches().length > 0
|
9
|
+
|
10
|
+
matches: () =>
|
11
|
+
matches = []
|
12
|
+
while node = @walker.nextNode()
|
13
|
+
while (m = @rule.exec(node.textContent))?
|
14
|
+
matches.push m
|
15
|
+
if m.index > 0 || m[0].length < node.length
|
16
|
+
node = node.splitText(m.index)
|
17
|
+
node.splitText(m[0].length)
|
18
|
+
|
19
|
+
m.node = node
|
20
|
+
@walker.currentNode = node
|
21
|
+
|
22
|
+
@matches = ->
|
23
|
+
matches
|
24
|
+
@matches()
|
25
|
+
|
26
|
+
render: =>
|
27
|
+
|
28
|
+
for match in @matches()
|
29
|
+
if match.node.parentElement.nodeName == 'A'
|
30
|
+
continue
|
31
|
+
|
32
|
+
node = match.node
|
33
|
+
el = "<a>".toHTML()
|
34
|
+
el.href = match[4]
|
35
|
+
|
36
|
+
name = "<strong>".toHTML()
|
37
|
+
url = node.splitText(match[1].length)
|
38
|
+
name.textContent = match[1]
|
39
|
+
el.appendChild(name)
|
40
|
+
el.appendChild(url)
|
41
|
+
node.parentElement.replaceChild(el, node)
|
42
|
+
|
43
|
+
@walker.root
|
44
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
Editor.Parsers.push class
|
2
|
+
rules: [
|
3
|
+
{
|
4
|
+
type: 'ul'
|
5
|
+
regex: /^(-\s)(.+)?$/i
|
6
|
+
},
|
7
|
+
{
|
8
|
+
type: 'ol'
|
9
|
+
regex: /^(\d+\.)(.+)?$/i
|
10
|
+
}
|
11
|
+
]
|
12
|
+
|
13
|
+
constructor: (node) ->
|
14
|
+
@nodes = [node]
|
15
|
+
for rule in @rules
|
16
|
+
m = rule.regex.exec(@nodes[0].textContent)
|
17
|
+
if m?
|
18
|
+
@match = m
|
19
|
+
@type = rule.type
|
20
|
+
@rule = rule.regex
|
21
|
+
|
22
|
+
isMatched: =>
|
23
|
+
if @match?
|
24
|
+
@collectMatchingSiblings(@nodes[0])
|
25
|
+
|
26
|
+
@match?
|
27
|
+
|
28
|
+
collectMatchingSiblings: (root) =>
|
29
|
+
node = root.nextElementSibling
|
30
|
+
while node
|
31
|
+
sibling = node.nextElementSibling
|
32
|
+
m = @rule.exec(node.textContent)
|
33
|
+
if m?
|
34
|
+
@nodes.push node
|
35
|
+
node.remove()
|
36
|
+
node = sibling
|
37
|
+
continue
|
38
|
+
break
|
39
|
+
|
40
|
+
|
41
|
+
render: =>
|
42
|
+
list = "<#{@type}>".toHTML()
|
43
|
+
for node in @nodes
|
44
|
+
li = '<li>'.toHTML()
|
45
|
+
li.textContent = node.textContent
|
46
|
+
list.appendChild(li)
|
47
|
+
list
|
48
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
Editor.Parsers.push class
|
2
|
+
rule: /((\*{1,2})[^\*]+(\*{1,2}))/gi
|
3
|
+
|
4
|
+
constructor: (node, el) ->
|
5
|
+
@walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT)
|
6
|
+
|
7
|
+
isMatched: =>
|
8
|
+
@matches().length > 0
|
9
|
+
|
10
|
+
matches: () =>
|
11
|
+
matches = []
|
12
|
+
while node = @walker.nextNode()
|
13
|
+
while (m = @rule.exec(node.textContent))?
|
14
|
+
matches.push m
|
15
|
+
if m.index > 0 || m[0].length < node.length
|
16
|
+
node = node.splitText(m.index)
|
17
|
+
node.splitText(m[0].length)
|
18
|
+
|
19
|
+
m.node = node
|
20
|
+
@walker.currentNode = node
|
21
|
+
|
22
|
+
@matches = ->
|
23
|
+
matches
|
24
|
+
@matches()
|
25
|
+
|
26
|
+
render: =>
|
27
|
+
if @matches().length == 0
|
28
|
+
return @walker.root
|
29
|
+
|
30
|
+
for match in @matches()
|
31
|
+
if match[match.length - 1].length == match[match.length - 2].length
|
32
|
+
if match[match.length - 1].length == 2
|
33
|
+
@bold(match)
|
34
|
+
else
|
35
|
+
@italic(match)
|
36
|
+
|
37
|
+
@walker.root
|
38
|
+
|
39
|
+
bold: (match) =>
|
40
|
+
if match.node.parentElement.nodeName == 'STRONG'
|
41
|
+
return
|
42
|
+
node = match.node
|
43
|
+
el = "<strong>".toHTML()
|
44
|
+
node.parentElement.replaceChild(el, node)
|
45
|
+
el.appendChild(node)
|
46
|
+
el
|
47
|
+
|
48
|
+
italic: (match) =>
|
49
|
+
if match.node.parentElement.nodeName == 'EM'
|
50
|
+
return
|
51
|
+
node = match.node
|
52
|
+
el = "<em>".toHTML()
|
53
|
+
node.parentElement.replaceChild(el, node)
|
54
|
+
el.appendChild(node)
|
55
|
+
el
|
56
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
Joint.bind 'Post.Header', class
|
2
|
+
loaded: =>
|
3
|
+
@on 'dragover', @over
|
4
|
+
@on 'dragleave', @cancel
|
5
|
+
@on 'drop', @drop
|
6
|
+
@on 'click', @element().querySelector('div'), @open
|
7
|
+
@on 'submit', @element().querySelector('form.clear'), @loading
|
8
|
+
input = @element().querySelector('input[type=file]')
|
9
|
+
@on 'change', input, @submit
|
10
|
+
|
11
|
+
@on 'images:update', @update
|
12
|
+
@on 'images:destroy', @clear
|
13
|
+
@refresh()
|
14
|
+
|
15
|
+
open: (e) =>
|
16
|
+
@element().querySelector('input[type=file]').click()
|
17
|
+
|
18
|
+
over: (e) =>
|
19
|
+
e.preventDefault()
|
20
|
+
@element().classList.add 'dropping'
|
21
|
+
|
22
|
+
cancel: (e) =>
|
23
|
+
e.preventDefault()
|
24
|
+
@element().classList.remove 'dropping'
|
25
|
+
|
26
|
+
drop: (e) =>
|
27
|
+
@element().classList.remove 'dropping'
|
28
|
+
@loading()
|
29
|
+
e.preventDefault()
|
30
|
+
form = @element().querySelector('form.update')
|
31
|
+
xhr = new Joint.XHR(form)
|
32
|
+
xhr.data.set 'image[file]', e.dataTransfer.files[0]
|
33
|
+
xhr.send()
|
34
|
+
|
35
|
+
submit: (e) =>
|
36
|
+
@loading()
|
37
|
+
Joint.XHR.send(e.target.form)
|
38
|
+
|
39
|
+
loading: =>
|
40
|
+
@element().classList.add 'loading'
|
41
|
+
|
42
|
+
clear: (e) =>
|
43
|
+
@element().style.backgroundImage = null
|
44
|
+
@element().classList.remove 'loading'
|
45
|
+
@refresh()
|
46
|
+
|
47
|
+
update: (e) =>
|
48
|
+
@element().classList.remove 'loading'
|
49
|
+
|
50
|
+
if e.ErrorHTML
|
51
|
+
document.body.appendChild(e.ErrorHTML)
|
52
|
+
return
|
53
|
+
|
54
|
+
@element().style.backgroundImage = "url(#{e.headerURL})"
|
55
|
+
@refresh()
|
56
|
+
|
57
|
+
|
58
|
+
refresh: =>
|
59
|
+
clearForm = @element().querySelector('form.clear')
|
60
|
+
p = @element().querySelector('p')
|
61
|
+
@refresh = =>
|
62
|
+
if @element().style.backgroundImage.length > 0
|
63
|
+
p.style.visibility = 'hidden'
|
64
|
+
clearForm.style.visibility = 'visible'
|
65
|
+
else
|
66
|
+
clearForm.style.visibility = 'hidden'
|
67
|
+
p.style.visibility = 'visible'
|
68
|
+
|
69
|
+
@refresh()
|