ecrire 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (233) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/Gemfile +4 -0
  4. data/LICENCE +9 -0
  5. data/README.md +62 -0
  6. data/Rakefile +72 -0
  7. data/bin/ecrire +42 -0
  8. data/ecrire.gemspec +26 -0
  9. data/lib/ecrire/app/assets/javascripts/admin/editor/content.coffee +319 -0
  10. data/lib/ecrire/app/assets/javascripts/admin/editor/ext.coffee +94 -0
  11. data/lib/ecrire/app/assets/javascripts/admin/editor/extensions/clipboard.coffee +98 -0
  12. data/lib/ecrire/app/assets/javascripts/admin/editor/extensions/image.coffee +19 -0
  13. data/lib/ecrire/app/assets/javascripts/admin/editor/navigation/delete.js.coffee +11 -0
  14. data/lib/ecrire/app/assets/javascripts/admin/editor/navigation/draft.js.coffee +42 -0
  15. data/lib/ecrire/app/assets/javascripts/admin/editor/navigation/help.js.coffee +6 -0
  16. data/lib/ecrire/app/assets/javascripts/admin/editor/navigation/save.js.coffee +61 -0
  17. data/lib/ecrire/app/assets/javascripts/admin/editor/navigation/sticky.js.coffee +11 -0
  18. data/lib/ecrire/app/assets/javascripts/admin/editor/navigation/toggle.js.coffee +10 -0
  19. data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/code.coffee +39 -0
  20. data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/heading.coffee +19 -0
  21. data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/image.coffee +129 -0
  22. data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/link.coffee +44 -0
  23. data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/lists.coffee +48 -0
  24. data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/word.coffee +56 -0
  25. data/lib/ecrire/app/assets/javascripts/admin/posts/header.coffee +69 -0
  26. data/lib/ecrire/app/assets/javascripts/admin/posts/new.js.coffee +7 -0
  27. data/lib/ecrire/app/assets/javascripts/admin/posts/title.coffee +15 -0
  28. data/lib/ecrire/app/assets/javascripts/admin.js +5 -0
  29. data/lib/ecrire/app/assets/javascripts/application.js +21 -0
  30. data/lib/ecrire/app/assets/javascripts/shared/overlay.js.coffee +29 -0
  31. data/lib/ecrire/app/assets/javascripts/shared/popup.js.coffee +7 -0
  32. data/lib/ecrire/app/assets/javascripts/vendor/moment.js +3043 -0
  33. data/lib/ecrire/app/assets/javascripts/vendor/prism/prism.ruby.js +25 -0
  34. data/lib/ecrire/app/assets/javascripts/vendor/prism.js +578 -0
  35. data/lib/ecrire/app/assets/stylesheets/admin/base.css.scss +98 -0
  36. data/lib/ecrire/app/assets/stylesheets/admin/documentation.css.scss +28 -0
  37. data/lib/ecrire/app/assets/stylesheets/admin/editor/base.css.scss +41 -0
  38. data/lib/ecrire/app/assets/stylesheets/admin/editor/content.css.scss +57 -0
  39. data/lib/ecrire/app/assets/stylesheets/admin/editor/header.css.scss +62 -0
  40. data/lib/ecrire/app/assets/stylesheets/admin/editor/image.css.scss +58 -0
  41. data/lib/ecrire/app/assets/stylesheets/admin/editor/publish.css.scss +40 -0
  42. data/lib/ecrire/app/assets/stylesheets/admin/editor/save.css.scss +44 -0
  43. data/lib/ecrire/app/assets/stylesheets/admin/images.css.scss +64 -0
  44. data/lib/ecrire/app/assets/stylesheets/admin/loading.css.scss +48 -0
  45. data/lib/ecrire/app/assets/stylesheets/admin/mixins/animations.css.scss +30 -0
  46. data/lib/ecrire/app/assets/stylesheets/admin/mixins/form.css.scss +22 -0
  47. data/lib/ecrire/app/assets/stylesheets/admin/navigation.css.scss +96 -0
  48. data/lib/ecrire/app/assets/stylesheets/admin/posts.css.scss +46 -0
  49. data/lib/ecrire/app/assets/stylesheets/admin/variables.css.scss +7 -0
  50. data/lib/ecrire/app/assets/stylesheets/admin.css.scss +5 -0
  51. data/lib/ecrire/app/assets/stylesheets/application.css.scss +12 -0
  52. data/lib/ecrire/app/assets/stylesheets/sessions.css.scss +97 -0
  53. data/lib/ecrire/app/assets/stylesheets/shared/main.css.scss +5 -0
  54. data/lib/ecrire/app/assets/stylesheets/shared/popup.css.scss +76 -0
  55. data/lib/ecrire/app/assets/stylesheets/vendor/prism/ruby.scss +11 -0
  56. data/lib/ecrire/app/assets/stylesheets/vendor/prism.scss +149 -0
  57. data/lib/ecrire/app/controllers/admin/application_controller.rb +16 -0
  58. data/lib/ecrire/app/controllers/admin/configurations/images_controller.rb +28 -0
  59. data/lib/ecrire/app/controllers/admin/images_controller.rb +35 -0
  60. data/lib/ecrire/app/controllers/admin/labels_controller.rb +18 -0
  61. data/lib/ecrire/app/controllers/admin/partials_controller.rb +43 -0
  62. data/lib/ecrire/app/controllers/admin/posts_controller.rb +90 -0
  63. data/lib/ecrire/app/controllers/admin/properties_controller.rb +35 -0
  64. data/lib/ecrire/app/controllers/application_controller.rb +29 -0
  65. data/lib/ecrire/app/controllers/concerns/.keep +0 -0
  66. data/lib/ecrire/app/controllers/ecrire/posts_controller.rb +42 -0
  67. data/lib/ecrire/app/controllers/images_controller.rb +2 -0
  68. data/lib/ecrire/app/controllers/onboarding/databases_controller.rb +67 -0
  69. data/lib/ecrire/app/controllers/onboarding/users_controller.rb +32 -0
  70. data/lib/ecrire/app/controllers/onboarding_controller.rb +20 -0
  71. data/lib/ecrire/app/controllers/partials_controller.rb +8 -0
  72. data/lib/ecrire/app/controllers/sessions_controller.rb +23 -0
  73. data/lib/ecrire/app/controllers/static_controller.rb +5 -0
  74. data/lib/ecrire/app/forms/admin/image_builder.rb +24 -0
  75. data/lib/ecrire/app/forms/admin/partial_builder.rb +105 -0
  76. data/lib/ecrire/app/forms/admin/post_builder.rb +47 -0
  77. data/lib/ecrire/app/helpers/admin/images_helper.rb +60 -0
  78. data/lib/ecrire/app/helpers/admin/labels_helper.rb +26 -0
  79. data/lib/ecrire/app/helpers/admin/posts_helper.rb +26 -0
  80. data/lib/ecrire/app/helpers/application_helper.rb +64 -0
  81. data/lib/ecrire/app/helpers/content_tag_helper.rb +91 -0
  82. data/lib/ecrire/app/helpers/open_graph_helper.rb +45 -0
  83. data/lib/ecrire/app/helpers/posts_helper.rb +19 -0
  84. data/lib/ecrire/app/models/admin/image.rb +108 -0
  85. data/lib/ecrire/app/models/admin/partial.rb +4 -0
  86. data/lib/ecrire/app/models/admin/post.rb +56 -0
  87. data/lib/ecrire/app/models/concerns/.keep +0 -0
  88. data/lib/ecrire/app/models/image.rb +5 -0
  89. data/lib/ecrire/app/models/label.rb +3 -0
  90. data/lib/ecrire/app/models/partial.rb +3 -0
  91. data/lib/ecrire/app/models/post.rb +90 -0
  92. data/lib/ecrire/app/models/property/image.rb +30 -0
  93. data/lib/ecrire/app/models/property/label.rb +28 -0
  94. data/lib/ecrire/app/models/user.rb +31 -0
  95. data/lib/ecrire/app/strategies/password_strategy.rb +17 -0
  96. data/lib/ecrire/app/views/admin/configurations/images/complete.html.erb +6 -0
  97. data/lib/ecrire/app/views/admin/configurations/images/show.html.erb +14 -0
  98. data/lib/ecrire/app/views/admin/images/_errors.html.erb +14 -0
  99. data/lib/ecrire/app/views/admin/images/_form.html.erb +15 -0
  100. data/lib/ecrire/app/views/admin/images/destroy.js.erb +0 -0
  101. data/lib/ecrire/app/views/admin/images/errors.js.erb +1 -0
  102. data/lib/ecrire/app/views/admin/images/update.js.erb +1 -0
  103. data/lib/ecrire/app/views/admin/posts/_documentation.html.erb +61 -0
  104. data/lib/ecrire/app/views/admin/posts/_list.html.erb +18 -0
  105. data/lib/ecrire/app/views/admin/posts/_post.html.erb +3 -0
  106. data/lib/ecrire/app/views/admin/posts/edit.html.erb +73 -0
  107. data/lib/ecrire/app/views/admin/posts/help.js.erb +1 -0
  108. data/lib/ecrire/app/views/admin/posts/index.js.erb +1 -0
  109. data/lib/ecrire/app/views/admin/posts/title.html.erb +10 -0
  110. data/lib/ecrire/app/views/admin/posts/update/content.js.erb +1 -0
  111. data/lib/ecrire/app/views/admin/posts/update/title.js.erb +1 -0
  112. data/lib/ecrire/app/views/admin/properties/image/create.js.erb +14 -0
  113. data/lib/ecrire/app/views/admin/properties/image/destroy.js.erb +6 -0
  114. data/lib/ecrire/app/views/admin/properties/label/create.js.erb +8 -0
  115. data/lib/ecrire/app/views/admin/properties/label/destroy.js.erb +2 -0
  116. data/lib/ecrire/app/views/layouts/admin/application.html.erb +47 -0
  117. data/lib/ecrire/app/views/partials/show.html.erb +14 -0
  118. data/lib/ecrire/app/views/sessions/_form.html.erb +18 -0
  119. data/lib/ecrire/app/views/sessions/_navigation.html.erb +24 -0
  120. data/lib/ecrire/app/views/sessions/new.html.erb +1 -0
  121. data/lib/ecrire/application.rb +18 -0
  122. data/lib/ecrire/commands/base.rb +9 -0
  123. data/lib/ecrire/commands/console.rb +21 -0
  124. data/lib/ecrire/commands/new.rb +47 -0
  125. data/lib/ecrire/commands/server.rb +27 -0
  126. data/lib/ecrire/commands.rb +19 -0
  127. data/lib/ecrire/config/environment/development.rb +12 -0
  128. data/lib/ecrire/config/environment/production.rb +12 -0
  129. data/lib/ecrire/config/environment/test.rb +3 -0
  130. data/lib/ecrire/config/environment.rb +46 -0
  131. data/lib/ecrire/config/onboarding_routes.rb +11 -0
  132. data/lib/ecrire/config/routes.rb +32 -0
  133. data/lib/ecrire/db/migrate/20131111222709_create_posts.rb +16 -0
  134. data/lib/ecrire/db/migrate/20131113200949_create_users.rb +12 -0
  135. data/lib/ecrire/db/migrate/20131114205041_add_excerpt_to_post.rb +5 -0
  136. data/lib/ecrire/db/migrate/20131221181640_add_javascript_to_posts.rb +5 -0
  137. data/lib/ecrire/db/migrate/20131221195338_create_partials.rb +12 -0
  138. data/lib/ecrire/db/migrate/20140115215122_create_images.rb +12 -0
  139. data/lib/ecrire/db/migrate/20140209144412_add_favorite_flag_to_image.rb +5 -0
  140. data/lib/ecrire/db/migrate/20140617224837_create_labels.rb +12 -0
  141. data/lib/ecrire/db/migrate/20140618321984_add_properties_to_posts.rb +6 -0
  142. data/lib/ecrire/db/migrate/20150113839713_convert_markdown_in_post_content.rb +6 -0
  143. data/lib/ecrire/db/migrate/20150120093481_add_preview_to_posts.rb +8 -0
  144. data/lib/ecrire/db/schema.rb +76 -0
  145. data/lib/ecrire/locales/admin/configurations/images/en.yml +3 -0
  146. data/lib/ecrire/locales/admin/en.yml +26 -0
  147. data/lib/ecrire/locales/admin/images/en.yml +5 -0
  148. data/lib/ecrire/locales/admin/partials/en.yml +18 -0
  149. data/lib/ecrire/locales/admin/posts/en.yml +14 -0
  150. data/lib/ecrire/locales/en.yml +9 -0
  151. data/lib/ecrire/onboarding/assets/images/.keep +0 -0
  152. data/lib/ecrire/onboarding/assets/images/background.png +0 -0
  153. data/lib/ecrire/onboarding/assets/images/github-logo.png +0 -0
  154. data/lib/ecrire/onboarding/assets/images/profile.png +0 -0
  155. data/lib/ecrire/onboarding/assets/images/twitter-logo.png +0 -0
  156. data/lib/ecrire/onboarding/assets/javascripts/base.js +2 -0
  157. data/lib/ecrire/onboarding/assets/javascripts/databases/information.js.coffee +17 -0
  158. data/lib/ecrire/onboarding/assets/javascripts/message.js.coffee +5 -0
  159. data/lib/ecrire/onboarding/assets/stylesheets/base.css.scss +80 -0
  160. data/lib/ecrire/onboarding/assets/stylesheets/complete.css.scss +3 -0
  161. data/lib/ecrire/onboarding/assets/stylesheets/fonts.css.scss +17 -0
  162. data/lib/ecrire/onboarding/assets/stylesheets/form.css.scss +25 -0
  163. data/lib/ecrire/onboarding/assets/stylesheets/message.css.scss +11 -0
  164. data/lib/ecrire/onboarding/assets/stylesheets/terminal.css.scss +16 -0
  165. data/lib/ecrire/onboarding/assets/stylesheets/variables.css.scss +3 -0
  166. data/lib/ecrire/onboarding/assets/stylesheets/welcome.css.scss +62 -0
  167. data/lib/ecrire/onboarding/views/layouts/application.html.erb +16 -0
  168. data/lib/ecrire/onboarding/views/onboarding/complete.html.erb +6 -0
  169. data/lib/ecrire/onboarding/views/onboarding/databases/index.html.erb +34 -0
  170. data/lib/ecrire/onboarding/views/onboarding/users/index.html.erb +16 -0
  171. data/lib/ecrire/onboarding/views/onboarding/welcome.html.erb +12 -0
  172. data/lib/ecrire/railtie/onboarding.rb +63 -0
  173. data/lib/ecrire/railtie/theme.rb +69 -0
  174. data/lib/ecrire/railtie.rb +30 -0
  175. data/lib/ecrire/tasks/assets.rake +15 -0
  176. data/lib/ecrire/tasks/routes.rake +6 -0
  177. data/lib/ecrire/template/Gemfile +31 -0
  178. data/lib/ecrire/template/Rakefile +4 -0
  179. data/lib/ecrire/template/assets/images/.keep +0 -0
  180. data/lib/ecrire/template/assets/javascripts/base.js.coffee +0 -0
  181. data/lib/ecrire/template/assets/javascripts/post.js.coffee +0 -0
  182. data/lib/ecrire/template/assets/stylesheets/base.css.scss +86 -0
  183. data/lib/ecrire/template/assets/stylesheets/code.css.scss +24 -0
  184. data/lib/ecrire/template/assets/stylesheets/footer.css.scss +5 -0
  185. data/lib/ecrire/template/assets/stylesheets/header.css.scss +18 -0
  186. data/lib/ecrire/template/assets/stylesheets/home.css.scss +35 -0
  187. data/lib/ecrire/template/config.ru +4 -0
  188. data/lib/ecrire/template/controllers/posts_controller.rb +2 -0
  189. data/lib/ecrire/template/helpers/blog_helper.rb +2 -0
  190. data/lib/ecrire/template/views/layouts/application.html.erb +35 -0
  191. data/lib/ecrire/template/views/posts/_post.html.erb +9 -0
  192. data/lib/ecrire/template/views/posts/index.html.erb +13 -0
  193. data/lib/ecrire/template/views/posts/show.html.erb +7 -0
  194. data/lib/ecrire/template/views/static/about.html +0 -0
  195. data/lib/ecrire/template/views/static/customize/_helper.html +7 -0
  196. data/lib/ecrire/template/views/static/customize/_helper_used.html +6 -0
  197. data/lib/ecrire/template/views/static/customize/_image.html +1 -0
  198. data/lib/ecrire/template/views/static/customize/_index.html +1 -0
  199. data/lib/ecrire/template/views/static/customize/_javascript.html +1 -0
  200. data/lib/ecrire/template/views/static/customize/_layout.html.erb +15 -0
  201. data/lib/ecrire/template/views/static/customize/_post.html +3 -0
  202. data/lib/ecrire/template/views/static/customize.html +95 -0
  203. data/lib/ecrire/version.rb +3 -0
  204. data/lib/ecrire.rb +24 -0
  205. data/test/editor/controllers/admin/images_controller_test.rb +6 -0
  206. data/test/editor/controllers/admin/posts_controller_test.rb +90 -0
  207. data/test/editor/controllers/base_controller_test.rb +26 -0
  208. data/test/editor/controllers/images_controller_test.rb +2 -0
  209. data/test/editor/controllers/partials_controller_test.rb +2 -0
  210. data/test/editor/controllers/posts_controller_test.rb +21 -0
  211. data/test/editor/controllers/sessions_controller_test.rb +21 -0
  212. data/test/editor/helpers/base_helper_test.rb +19 -0
  213. data/test/editor/helpers/posts_helper_test.rb +3 -0
  214. data/test/editor/models/image_test.rb +33 -0
  215. data/test/editor/models/partial_test.rb +4 -0
  216. data/test/editor/models/post_test.rb +109 -0
  217. data/test/editor/models/user_test.rb +4 -0
  218. data/test/editor/test_helper.rb +28 -0
  219. data/test/fixtures/images.yml +11 -0
  220. data/test/fixtures/partials.yml +9 -0
  221. data/test/fixtures/posts.yml +16 -0
  222. data/test/fixtures/users.yml +9 -0
  223. data/test/initializations/railtie_test.rb +49 -0
  224. data/test/isolation.rb +16 -0
  225. data/test/themes/template/Gemfile +28 -0
  226. data/test/themes/template/assets/javascripts/blog.js.coffee +0 -0
  227. data/test/themes/template/assets/javascripts/post.js.coffee +0 -0
  228. data/test/themes/template/assets/stylesheets/blog.css.scss +0 -0
  229. data/test/themes/template/assets/stylesheets/post.css.scss +0 -0
  230. data/test/themes/template/config.ru +0 -0
  231. data/test/themes/template/views/layouts/application.html.erb +1 -0
  232. data/test/themes/template/views/posts/index.html.erb +0 -0
  233. metadata +275 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 33bd25681de527f9d535b8d9f224ee339cc7538b
4
+ data.tar.gz: 698be69f0e07e6562f8f2640e92a19d00abeb61c
5
+ SHA512:
6
+ metadata.gz: c59825dc64583000eb193136d440b80133576ee22975ce33e350aaa1e62f15044ab6b3efa91520ec9d9c7f68c4123485188a67542f91416d1ec6d159c391a037
7
+ data.tar.gz: 72e412b0db648761f63f2e63e13621bbd508788c722ccb9ef2ca48fbd99f0ae5e08b17dc6bb3a7bab4d43090d275f986d1890d3a12bb5c5b3509ea16291a97f0
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ # See http://help.github.com/ignore-files/ for more about ignoring files.
2
+ #
3
+ # If you find yourself ignoring temporary files generated by your text editor
4
+ # or operating system, you probably want to add a global ignore instead:
5
+ # git config --global core.excludesfile '~/.gitignore_global'
6
+
7
+ # Ignore bundler config.
8
+ /.bundle
9
+
10
+ # Ignore the default SQLite database.
11
+ /db/*.sqlite3
12
+ /db/*.sqlite3-journal
13
+
14
+ # Ignore all logfiles and tempfiles.
15
+ log
16
+ /tmp
17
+
18
+ secrets.yml
19
+ .DS_Store
20
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
data/LICENCE ADDED
@@ -0,0 +1,9 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 pothibo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # Ecrire
2
+
3
+ ![The Editor](http://f.cl.ly/items/1u0r3g0E1r3x2z1g2G1P/Screen%20Shot%202014-06-16%20at%206.19.06%20PM.png)
4
+
5
+ Starting your own blog isn't an easy task. If you've ever started one, you know how hard it is to stay focused. With ecrire, you have all the tools you need to help you get started.
6
+
7
+ The interface is light and the editor is at the forefront. Because writing, that's what we do.
8
+
9
+ And it doesn't stop there. You have the ability to change personalize each and every post like you would for a web page. You can customize your page with custom Javascript and CSS. And to make your life even easier, those two are evaluated in **real time**.
10
+
11
+ Don't worry about saving your drafts, Ecrire does it for you every half a second. We're in 2014, after all.
12
+
13
+ Ecrire is built on top of Ruby on Rails, that means that every theme you build can be built with ruby helpers and decorators while having the power to write clean HTML. Your views can be neatly organized and clean.
14
+
15
+ Same goes for your CSS and Javascript files. Ecrire comes with SASS & Bourbon for the CSS interpreter. Coffeescript is also available for your javascript.
16
+
17
+ Go ahead, try it out!
18
+
19
+
20
+ ### Getting started
21
+
22
+ First, you need to have ruby version **higher than** 2.0.0.
23
+
24
+ You can install the gem with rubygems.
25
+ ``` gem install ecrire ```
26
+
27
+ Then create a new blog
28
+ ``` ecrire new your_blog_name ```
29
+
30
+ It will create a new blog using the default template. Before starting up your server, you will need to run Bundler to install all the dependency your blog needs.
31
+ ``` bundle install ```
32
+
33
+ You are ready to go. Ecrire will launch the onboarding process until you set up your database and you create a user.
34
+
35
+ ``` bundle exec ecrire server  ```
36
+
37
+ You can access the blog & configuration setup with your browser by going at the following address.
38
+ ``` http://localhost:3000  ```
39
+
40
+ ### Licence
41
+
42
+ The MIT License (MIT)
43
+
44
+ Copyright (c) 2014 pothibo
45
+
46
+ Permission is hereby granted, free of charge, to any person obtaining a copy
47
+ of this software and associated documentation files (the "Software"), to deal
48
+ in the Software without restriction, including without limitation the rights
49
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
50
+ copies of the Software, and to permit persons to whom the Software is
51
+ furnished to do so, subject to the following conditions:
52
+
53
+ The above copyright notice and this permission notice shall be included in
54
+ all copies or substantial portions of the Software.
55
+
56
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
57
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
58
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
59
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
60
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
61
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
62
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,72 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+
3
+ ENV['RAILS_ENV'] ||= 'test'
4
+
5
+ require 'rake/testtask'
6
+ require 'ecrire'
7
+
8
+ task default: :test
9
+
10
+ namespace :database do
11
+
12
+ task initialize: %w(configure) do
13
+
14
+ begin
15
+ # ActiveRecord's configuration assignation is done at the same time as the connection is tried
16
+ # If it fails, the configurations aren't set in AR::Base.
17
+ # This is why this is set here
18
+ ActiveRecord::Base.configurations = Rails.application.config.database_configuration
19
+
20
+ path = Rails.application.paths['db/migrate'].existent
21
+ ActiveRecord::Migrator.migrations_paths = path
22
+ if ActiveRecord::Migrator.needs_migration?
23
+ ActiveRecord::Migrator.migrate(path)
24
+ end
25
+ rescue ActiveRecord::NoDatabaseError
26
+ puts 'Database does not exist. Creating...'
27
+ ActiveRecord::Tasks::DatabaseTasks.create_current
28
+ puts 'Database created, migrating now...'
29
+ ActiveRecord::Migrator.migrate(path)
30
+ puts "Migration completed."
31
+ end
32
+ end
33
+
34
+ task :configure do
35
+ Dir.chdir Dir.pwd + '/test/themes/template' do
36
+ Ecrire::Application.initialize!
37
+ end
38
+ end
39
+
40
+ task :purge do
41
+ [User, Post, Image, Partial, Label].each(&:delete_all)
42
+ end
43
+
44
+ end
45
+
46
+ desc 'Configure postgres and run all tests'
47
+
48
+ task :test do
49
+ %w(test:initializations test:editor test:themes).each do |name|
50
+ Rake::Task[name].invoke
51
+ end
52
+ end
53
+
54
+ namespace :test do
55
+
56
+ tasks = %w(initializations editor themes).each do |name|
57
+ Rake::TestTask.new(name) do |t|
58
+ t.libs << "test"
59
+ t.test_files = FileList["test/#{name}/**/*_test.rb"]
60
+ t.verbose = false
61
+ Rake::Task['database:initialize'].invoke
62
+ end
63
+
64
+ Rake::Task["test:#{name}"].enhance do
65
+ Rake::Task['database:purge'].invoke
66
+ end
67
+ end
68
+ end
69
+
70
+ at_exit do
71
+ Rake::Task['database:purge'].invoke
72
+ end
data/bin/ecrire ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+
5
+ ARGV << '--help' if ARGV.empty?
6
+ COMMANDS = %w(new server console)
7
+
8
+ options = {
9
+ Port: 3000
10
+ }
11
+
12
+ parser = OptionParser.new do |opts|
13
+ opts.banner = "Usage: ecrire [ #{COMMANDS.join(' | ')} ] [options]:"
14
+
15
+ opts.separator ''
16
+ opts.separator 'ecrire new blog.domain.com'
17
+
18
+ opts.separator ''
19
+ opts.separator 'ecrire server [options]:'
20
+ opts.on '-p PORT', '--port=PORT', 'Run the server on a given port. Defaults to 3000' do |port|
21
+ options[:Port] = port
22
+ end
23
+
24
+ opts.separator ''
25
+ opts.separator 'ecrire console'
26
+
27
+ opts.on '-h', '--help', 'Show this menu' do
28
+ puts opts
29
+ end
30
+ end
31
+
32
+ if !COMMANDS.include?(ARGV[0])
33
+ parser.parse! %w(-h)
34
+ exit
35
+ end
36
+
37
+ require 'ecrire/commands'
38
+
39
+ parser.parse!
40
+
41
+ command = Ecrire::Commands.send(ARGV[0]).new(options, *ARGV[1..-1])
42
+ command.run!
data/ecrire.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $:.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'ecrire/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'ecrire'
8
+ s.version = Ecrire::VERSION
9
+ s.summary = 'Blog engine'
10
+ s.description = 'Blog engine on Rails'
11
+
12
+ s.required_ruby_version = '>= 2.0.0'
13
+
14
+ s.license = 'MIT'
15
+
16
+ s.author = 'Pier-Olivier Thibault'
17
+ s.email = 'pothibo@gmail.com'
18
+ s.homepage = 'http://pothibo.com'
19
+
20
+ s.files = `git ls-files -z`.split("\x0")
21
+ s.files += Dir['README.md']
22
+ s.executables << 'ecrire'
23
+
24
+ s.require_path = 'lib'
25
+
26
+ end
@@ -0,0 +1,319 @@
1
+ Joint.bind 'Editor.Content', class @Editor
2
+
3
+ loaded: =>
4
+ @on 'keydown', @linefeed
5
+
6
+ @parsers = Editor.Parsers
7
+
8
+ @extensions = Editor.Extensions.map (ext) =>
9
+ new ext(this)
10
+
11
+ unless @element().lastChild?
12
+ @element().innerHTML = '<p></p>'
13
+
14
+ lines = @lines(@element())
15
+ @element().textContent = ''
16
+ fragment = document.createDocumentFragment()
17
+ fragment.appendChild(line) for line in lines
18
+ @parse(fragment)
19
+ while fragment.firstChild
20
+ @element().appendChild(fragment.firstChild)
21
+
22
+ @observer = new MutationObserver(@outdated)
23
+ observerSettings = {
24
+ childList: true,
25
+ subtree: true,
26
+ characterData: true
27
+ }
28
+
29
+ @observer.observe @element(), observerSettings
30
+
31
+ @observer.hold = (cb) =>
32
+ @observer.disconnect()
33
+ cb()
34
+ @observer.observe @element(), observerSettings
35
+
36
+ event = new CustomEvent('Editor:loaded', {bubbles: true})
37
+ @element().dispatchEvent(event)
38
+
39
+
40
+
41
+ outdated: (mutations) =>
42
+ @observer.hold =>
43
+ for mutation in mutations
44
+ @appended(node, mutation.target) for node in mutation.addedNodes
45
+ @removed(node, mutation.target) for node in mutation.removedNodes
46
+ if mutation.type == 'characterData'
47
+ @update mutation.target
48
+
49
+ event = new CustomEvent('Editor:updated', {bubbles: true})
50
+ @element().dispatchEvent(event)
51
+
52
+
53
+
54
+ linefeed: (e) =>
55
+ return unless e.which == 13
56
+
57
+ e.preventDefault()
58
+ e.stopPropagation()
59
+
60
+ sel = window.getSelection()
61
+ node = focusNode = sel.focusNode
62
+ offset = sel.focusOffset
63
+
64
+ str = focusNode.textContent
65
+ focusNode.textContent = str.substr(0, offset) + "\n" + str.substr(offset)
66
+
67
+ while node? && node.parentElement != @element()
68
+ node = node.parentElement
69
+
70
+ offset = @lineOffset(node, focusNode, offset) + 1
71
+
72
+ lines = @parse(@cloneNodesFrom(node))
73
+
74
+ @observer.hold =>
75
+ lines = @updateDOM(node, lines)
76
+ @setCursorAt(lines[0], offset)
77
+
78
+
79
+ event = new CustomEvent('Editor:updated', {bubbles: true})
80
+ @element().dispatchEvent(event)
81
+
82
+
83
+
84
+ update: (node) ->
85
+ while node? && node.parentElement != @element()
86
+ node = node.parentElement
87
+
88
+ return unless node?
89
+
90
+ walker = @walker(node)
91
+
92
+ sel = window.getSelection()
93
+ offset = @lineOffset(node, (sel.focusNode || node), sel.focusOffset)
94
+
95
+ if node.previousElementSibling?
96
+ node = node.previousElementSibling
97
+ offset += node.toString().length + 1
98
+
99
+ lines = @parse(@cloneNodesFrom(node))
100
+
101
+ @observer.hold =>
102
+ lines = @updateDOM(node, lines)
103
+ for line in lines
104
+ if line.parentElement?
105
+ @setCursorAt(lines[0], offset)
106
+ break
107
+
108
+
109
+ removed: (node, line) =>
110
+ while line? && line.parentElement != @element()
111
+ line = line.parentElement
112
+
113
+ if node.nodeType != 1 || (node instanceof HTMLBRElement && node.parentElement?)
114
+ node.remove()
115
+
116
+ if line?
117
+ sel = window.getSelection()
118
+ offset = @lineOffset(line, sel.focusNode, sel.focusOffset)
119
+
120
+ lines = @parse(@cloneNodesFrom(line))
121
+
122
+ @observer.hold =>
123
+ lines = @updateDOM(line, lines)
124
+ if line != lines[0]
125
+ @setCursorAt(lines[0], offset)
126
+
127
+ if @element().childNodes.length == 0
128
+ p = "<p>".toHTML()
129
+ @element().appendChild(p)
130
+ @setCursorAt(@element(), 0)
131
+
132
+
133
+
134
+ appended: (node) =>
135
+ el = node
136
+ while el? && el.parentElement != @element()
137
+ el = el.parentElement
138
+
139
+ return unless el?
140
+
141
+ elements = el.querySelectorAll('[contenteditable=false]')
142
+
143
+ for element in elements
144
+ if element.contains(node)
145
+ return
146
+
147
+ if node instanceof HTMLBRElement
148
+ node.remove()
149
+ return
150
+
151
+ node = el
152
+
153
+ sel = window.getSelection()
154
+ offset = @lineOffset(el, sel.focusNode, sel.focusOffset)
155
+
156
+ lines = @parse(@cloneNodesFrom(node))
157
+
158
+ @observer.hold =>
159
+ lines = @updateDOM(node, lines)
160
+ if node != lines[0]
161
+ @setCursorAt(lines[0], offset)
162
+
163
+
164
+
165
+ updateDOM: (anchor, fragment) =>
166
+ return unless anchor?
167
+ current = anchor.parentElement.lastChild
168
+ lines = []
169
+ while (line = fragment.lastChild) && current != anchor
170
+ if @same(current, line)
171
+ lines.push(current)
172
+ line.remove()
173
+ current = current.previousSibling
174
+ if current?
175
+ continue
176
+ else
177
+ break
178
+ else
179
+ n = current.previousSibling
180
+ current.parentElement.replaceChild(line, current)
181
+ lines.push(line)
182
+ current = n
183
+
184
+ lines = lines.reverse()
185
+ return lines unless anchor.parentElement?
186
+
187
+ if fragment.childNodes.length == 1
188
+ current = anchor
189
+ line = fragment.lastChild
190
+ if !@same(current, line)
191
+ current.parentElement.replaceChild(line, current)
192
+ lines.splice(0, 0, line)
193
+ else
194
+ lines.splice(0,0, current)
195
+ else if fragment.childNodes.length > 1
196
+ current = anchor
197
+ while fragment.lastChild
198
+ line = fragment.lastChild
199
+ current.parentElement.insertBefore(line, current)
200
+ lines.splice(0, 0, line)
201
+ current = line
202
+ anchor.remove()
203
+ else if anchor != lines[0]
204
+ current = lines[0].previousSibling
205
+ while current != anchor
206
+ n = current.previousSibling
207
+ current.remove()
208
+ current = n
209
+
210
+ anchor.remove()
211
+
212
+ lines
213
+
214
+
215
+ lineOffset: (line, node, offset) =>
216
+ if node.textContent.length == 0
217
+ return 0
218
+
219
+ walker = @walker(line)
220
+ offset += line.offset(node, walker)
221
+
222
+
223
+
224
+ setCursorAt: (line, offset) ->
225
+ sel = window.getSelection()
226
+
227
+ while true
228
+ length = line.toString().length
229
+ if length >= offset
230
+ break
231
+ offset -= Math.max(length + 1, 1)
232
+
233
+ if line.nextSibling?
234
+ line = line.nextSibling
235
+ continue
236
+ break
237
+
238
+ walker = @walker(line)
239
+ range = line.getRange(offset , walker)
240
+
241
+ height = window.innerHeight
242
+ rect = line.getBoundingClientRect()
243
+
244
+ if rect.bottom > height
245
+ line.scrollIntoView()
246
+
247
+ sel.removeAllRanges()
248
+ sel.addRange(range)
249
+
250
+
251
+
252
+ cloneNodesFrom: (node) =>
253
+ fragment = document.createDocumentFragment()
254
+ while node
255
+ clone = node.cloneNode(true)
256
+ el.remove for el in clone.querySelectorAll('[contenteditable=false]')
257
+ fragment.appendChild(line) for line in @lines(clone)
258
+ node = node.nextSibling
259
+ fragment
260
+
261
+
262
+
263
+ lines: (node) =>
264
+ cached = "<p></p>".toHTML()
265
+ @lines = (node) =>
266
+ node.toString().split('\n').map (t) =>
267
+ el = cached.cloneNode(true)
268
+ el.textContent = t
269
+ el
270
+
271
+ @lines(node)
272
+
273
+
274
+
275
+ same: (node1, node2) =>
276
+ node1.nodeName == node2.nodeName &&
277
+ node1.innerHTML.trim() == node2.innerHTML.trim()
278
+
279
+
280
+
281
+ parse: (fragment) =>
282
+ for p in @parsers
283
+ line = fragment.firstChild
284
+ while line
285
+ parser = new p(line)
286
+ if parser.isMatched()
287
+ el = parser.render()
288
+ fragment.replaceChild(el, line)
289
+ line = el
290
+ line = line.nextSibling
291
+ fragment
292
+
293
+
294
+
295
+ walker: (node, filter = NodeFilter.SHOW_TEXT) ->
296
+ elements = node.querySelectorAll('[contenteditable=false]')
297
+ document.createTreeWalker node,
298
+ filter,
299
+ acceptNode: (node) ->
300
+ for el in elements
301
+ if el.contains(node)
302
+ return NodeFilter.FILTER_REJECT
303
+ break
304
+ return NodeFilter.FILTER_ACCEPT
305
+
306
+ toString: =>
307
+ texts = []
308
+ body = @element().cloneNode(true)
309
+ elements = body.querySelectorAll('[contenteditable=false]')
310
+ el.remove() for el in elements
311
+ for line in body.children
312
+ texts.push line.toString()
313
+
314
+ texts.join '\n'
315
+
316
+
317
+ Editor.Parsers = []
318
+ Editor.Extensions = []
319
+
@@ -0,0 +1,94 @@
1
+ String::toHTML = ->
2
+ el = document.createElement('div')
3
+ el.innerHTML = this
4
+ if el.children.length > 1
5
+ el.children
6
+ else
7
+ el.children[0]
8
+
9
+ Text::toString = ->
10
+ @textContent
11
+
12
+ HTMLOListElement::toString = HTMLUListElement::toString = ->
13
+ texts = Array.prototype.slice.call(@children).map (li) ->
14
+ li.textContent
15
+ texts.join("\n")
16
+
17
+ HTMLPictureElement::toString = ->
18
+ @lastChild.textContent
19
+
20
+ HTMLPreElement::toString = HTMLHeadingElement::toString = HTMLDivElement::toString = HTMLParagraphElement::toString = ->
21
+ @textContent
22
+
23
+ HTMLElement::offset = (node, walker) ->
24
+ offset = 0
25
+
26
+ while walker.nextNode()
27
+ if walker.currentNode != node
28
+ offset += walker.currentNode.length
29
+ else
30
+ break
31
+
32
+ offset
33
+
34
+ HTMLOListElement::offset = HTMLUListElement::offset = (node, walker) ->
35
+ offset = 0
36
+ li = this.firstElementChild
37
+
38
+ while walker.nextNode()
39
+ if !li.contains(walker.currentNode)
40
+ newList = walker.currentNode
41
+ while newList? && !(newList instanceof HTMLLIElement)
42
+ newList = newList.parentElement
43
+ li = newList
44
+ offset++
45
+
46
+ if walker.currentNode != node
47
+ offset += walker.currentNode.length
48
+ else
49
+ break
50
+
51
+ offset
52
+
53
+ HTMLElement::getRange = (offset, walker) ->
54
+ range = document.createRange()
55
+
56
+ if !@firstChild?
57
+ range.setStart(this, 0)
58
+ else
59
+ while walker.nextNode()
60
+ if walker.currentNode.length < offset
61
+ offset -= walker.currentNode.length
62
+ continue
63
+
64
+ range.setStart(walker.currentNode, offset)
65
+ break
66
+
67
+ range.collapse(true)
68
+ range
69
+
70
+
71
+ HTMLOListElement::getRange = HTMLUListElement::getRange = (offset, walker) ->
72
+ range = document.createRange()
73
+ if !@firstChild?
74
+ range.setStart(this, 0)
75
+ return
76
+
77
+ li = this.firstElementChild
78
+
79
+ while walker.nextNode()
80
+ if !li.contains(walker.currentNode)
81
+ newList = walker.currentNode
82
+ while newList? && !(newList instanceof HTMLLIElement)
83
+ newList = newList.parentElement
84
+ li = newList
85
+ offset--
86
+
87
+ if walker.currentNode.length < offset
88
+ offset -= walker.currentNode.length
89
+ continue
90
+ range.setStart(walker.currentNode, offset)
91
+ break
92
+
93
+ range.collapse(true)
94
+ range