yarii-editor 0.4.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 (87) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +31 -0
  4. data/Rakefile +32 -0
  5. data/app/assets/images/yarii_editor/butterfly-small.png +0 -0
  6. data/app/controllers/concerns/yarii_editor/controller_authorization.rb +22 -0
  7. data/app/controllers/concerns/yarii_editor/repository_pullable.rb +11 -0
  8. data/app/controllers/yarii_editor/application_controller.rb +22 -0
  9. data/app/controllers/yarii_editor/dashboard_controller.rb +17 -0
  10. data/app/controllers/yarii_editor/documents_controller.rb +147 -0
  11. data/app/controllers/yarii_editor/publish_controller.rb +24 -0
  12. data/app/helpers/yarii_editor/application_helper.rb +9 -0
  13. data/app/helpers/yarii_editor/document_helper.rb +15 -0
  14. data/app/helpers/yarii_editor/editor_helper.rb +19 -0
  15. data/app/javascript/controllers/building_controller.js +28 -0
  16. data/app/javascript/controllers/card_controller.js +49 -0
  17. data/app/javascript/controllers/commit_modal_controller.js +55 -0
  18. data/app/javascript/controllers/editor_modal_controller.js +76 -0
  19. data/app/javascript/controllers/index.js +85 -0
  20. data/app/javascript/controllers/list_loading_controller.js +33 -0
  21. data/app/javascript/controllers/markdown_editor_controller.js +157 -0
  22. data/app/javascript/controllers/new_document_controller.js +16 -0
  23. data/app/javascript/controllers/push_to_public_controller.js +17 -0
  24. data/app/javascript/controllers/testengine_controller.js +7 -0
  25. data/app/javascript/lib/utils.js +45 -0
  26. data/app/javascript/packs/application.js +4 -0
  27. data/app/jobs/yarii/application_job.rb +4 -0
  28. data/app/mailers/yarii/application_mailer.rb +6 -0
  29. data/app/models/concerns/yarii_editor/model_callbacks.rb +26 -0
  30. data/app/models/concerns/yarii_editor/previewing.rb +18 -0
  31. data/app/models/page.rb +13 -0
  32. data/app/models/post.rb +29 -0
  33. data/app/models/yarii/application_record.rb +5 -0
  34. data/app/models/yarii/repository.rb +50 -0
  35. data/app/models/yarii/site.rb +107 -0
  36. data/app/styles/FiraGO/FiraGO-Bold.woff +0 -0
  37. data/app/styles/FiraGO/FiraGO-BoldItalic.woff +0 -0
  38. data/app/styles/FiraGO/FiraGO-Book.woff +0 -0
  39. data/app/styles/FiraGO/FiraGO-BookItalic.woff +0 -0
  40. data/app/styles/FiraGO/FiraGO-HeavyItalic.woff +0 -0
  41. data/app/styles/FiraGO/FiraGO-SemiBold.woff +0 -0
  42. data/app/styles/FiraGO/FiraGO-SemiBoldItalic.woff +0 -0
  43. data/app/styles/FiraGO/FiraGO.scss +48 -0
  44. data/app/styles/Vidaloka/Vidaloka-Regular.woff +0 -0
  45. data/app/styles/Vidaloka/Vidaloka.scss +6 -0
  46. data/app/styles/application.scss +141 -0
  47. data/app/styles/dashboard.scss +139 -0
  48. data/app/styles/editor.scss +70 -0
  49. data/app/styles/helpers.scss +45 -0
  50. data/app/views/application/yarii_extra_head.html.erb +3 -0
  51. data/app/views/layouts/yarii_editor/application.html.erb +21 -0
  52. data/app/views/yarii_editor/application/render_engine_stylesheet_tag.html.erb +1 -0
  53. data/app/views/yarii_editor/dashboard/_card.html.erb +3 -0
  54. data/app/views/yarii_editor/dashboard/_extras.html.erb +1 -0
  55. data/app/views/yarii_editor/dashboard/_inner_list.html.erb +42 -0
  56. data/app/views/yarii_editor/dashboard/_list.html.erb +34 -0
  57. data/app/views/yarii_editor/dashboard/index.html.erb +18 -0
  58. data/app/views/yarii_editor/documents/modal.html.erb +78 -0
  59. data/app/views/yarii_editor/editor/_dropdown.html.erb +14 -0
  60. data/app/views/yarii_editor/editor/_markdown.html.erb +13 -0
  61. data/app/views/yarii_editor/editor/_text.html.erb +13 -0
  62. data/app/views/yarii_editor/editor/_textarea.html.erb +13 -0
  63. data/app/views/yarii_editor/model_cards/_hashtags.html.erb +5 -0
  64. data/app/views/yarii_editor/model_cards/_page.html.erb +18 -0
  65. data/app/views/yarii_editor/model_cards/_post.html.erb +17 -0
  66. data/app/views/yarii_editor/model_cards/_standard_footer_buttons.html.erb +20 -0
  67. data/app/views/yarii_editor/publish/commit.html.erb +45 -0
  68. data/app/views/yarii_editor/shared/_navbar.html.erb +38 -0
  69. data/app/views/yarii_editor/shared/_publishing_menu.html.erb +42 -0
  70. data/config/initializers/assets.rb +1 -0
  71. data/config/routes.rb +16 -0
  72. data/config/webpack/development.js +5 -0
  73. data/config/webpack/environment.js +3 -0
  74. data/config/webpack/production.js +5 -0
  75. data/config/webpack/staging.js +5 -0
  76. data/config/webpack/test.js +5 -0
  77. data/config/webpacker.yml +109 -0
  78. data/db/migrate/20190923000809_create_yarii_sites.rb +12 -0
  79. data/db/migrate/20191014203116_add_git_repo_path_to_yarii_sites.rb +5 -0
  80. data/db/migrate/20200420221048_add_preview_build_command_to_yarii_sites.rb +5 -0
  81. data/lib/tasks/yarii_editor_tasks.rake +66 -0
  82. data/lib/yarii-editor.rb +24 -0
  83. data/lib/yarii-editor/engine.rb +22 -0
  84. data/lib/yarii-editor/setup_current_site.rb +20 -0
  85. data/lib/yarii-editor/setup_current_user.rb +13 -0
  86. data/lib/yarii-editor/version.rb +3 -0
  87. metadata +172 -0
@@ -0,0 +1,55 @@
1
+ import { Controller } from "stimulus"
2
+ import axios from "axios"
3
+ import { axiosPostable } from "../lib/utils"
4
+
5
+ export default class extends Controller {
6
+ static targets = [ "status" ]
7
+
8
+ initialize() {
9
+ this.axios = axiosPostable(axios)
10
+ }
11
+
12
+ connect() {
13
+ // We'll handle saving ourselves. Disable normal form operation
14
+ this.element.querySelector('.modal-card-body > form').addEventListener('submit', event => {
15
+ event.preventDefault()
16
+ })
17
+ }
18
+ async commit() {
19
+ const form = this.element.querySelector('.modal-card-body > form');
20
+ const data = new FormData(form);
21
+
22
+ this.element.querySelector('.modal-card-body > form').classList.add('dimmed')
23
+ this.element.querySelector('button[data-action="commit-modal#commit"]').classList.add('is-loading')
24
+ this.statusTarget.classList.add('in-progress')
25
+ this.statusTarget.innerHTML = "Pushing Updates…"
26
+
27
+ try {
28
+ let response = null
29
+ response = await this.axios.post(this.data.get('path'), data)
30
+ this.refreshPublishingMenu()
31
+ this.closeModal()
32
+ } catch (error) {
33
+ alert("I'm sorry, I ran into trouble communicating with the server. Try pushing again in a moment.")
34
+ console.log(error)
35
+ }
36
+ }
37
+
38
+ cancel() {
39
+ this.closeModal()
40
+ }
41
+
42
+ closeModal() {
43
+ this.element.remove()
44
+ }
45
+
46
+ async refreshPublishingMenu() {
47
+ try {
48
+ let response = null
49
+ response = await this.axios.get(this.data.get('publishing-menu-path'))
50
+ document.getElementById('publishing-menu').innerHTML = response.data
51
+ } catch (error) {
52
+ console.log(error)
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,76 @@
1
+ import { Controller } from "stimulus"
2
+ import axios from "axios"
3
+ import { axiosPostable } from "../lib/utils"
4
+
5
+ export default class extends Controller {
6
+ static targets = [ "additionalFields", "status" ]
7
+
8
+ initialize() {
9
+ this.axios = axiosPostable(axios)
10
+ }
11
+
12
+ connect() {
13
+ // We'll handle saving ourselves. Disable normal form operation
14
+ this.element.querySelector('.modal-card-body > form').addEventListener('submit', event => {
15
+ event.preventDefault()
16
+ })
17
+ }
18
+ async save() {
19
+ const form = this.element.querySelector('.modal-card-body > form');
20
+ const data = new FormData(form);
21
+
22
+ const savePath = this.data.get('path')
23
+ const httpMethod = this.data.get('http-method')
24
+
25
+ this.element.querySelector('.modal-card-body > form').classList.add('dimmed')
26
+ this.element.querySelector('button[data-action="editor-modal#save"]').classList.add('is-loading')
27
+ this.statusTarget.classList.add('in-progress')
28
+ this.statusTarget.innerHTML = "Generating Preview…"
29
+
30
+ try {
31
+ let response = null
32
+ if (httpMethod == 'post') {
33
+ response = await this.axios.post(savePath, data)
34
+ const newCard = document.createElement('template')
35
+ newCard.innerHTML = response.data.document_html
36
+ const list = document.getElementById('list-' + this.data.get('content-model'))
37
+ list.prepend(newCard.content.cloneNode(true))
38
+ } else if (httpMethod == 'put') {
39
+ response = await this.axios.put(savePath, data)
40
+ const updatedCard = response.data.document_html
41
+ document.getElementById(this.data.get('content-model') + '-' + this.data.get('model-id')).outerHTML = updatedCard
42
+ }
43
+ this.refreshPublishingMenu()
44
+ this.closeModal()
45
+ } catch (error) {
46
+ this.element.querySelector('.modal-card-body > form').classList.remove('dimmed')
47
+ this.element.querySelector('button[data-action="editor-modal#save"]').classList.remove('is-loading')
48
+ this.statusTarget.classList.remove('in-progress')
49
+ this.statusTarget.innerHTML = ""
50
+ alert("I'm sorry, I ran into trouble communicating with the server. Please copy your content to another text editor to preserve your work if this problem persists.")
51
+ console.error(error)
52
+ }
53
+ }
54
+
55
+ cancel() {
56
+ this.closeModal()
57
+ }
58
+
59
+ closeModal() {
60
+ this.element.remove()
61
+ }
62
+
63
+ showAdditionalFields() {
64
+ this.additionalFieldsTarget.classList.remove('is-hidden')
65
+ }
66
+
67
+ async refreshPublishingMenu() {
68
+ try {
69
+ let response = null
70
+ response = await this.axios.get(this.data.get('publishing-menu-path'))
71
+ document.getElementById('publishing-menu').innerHTML = response.data
72
+ } catch (error) {
73
+ console.log(error)
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,85 @@
1
+ // Load all the controllers within this directory and all subdirectories.
2
+ // Controller files must be named *_controller.js.
3
+
4
+ import { Application } from "stimulus"
5
+ import { definitionsFromContext } from "stimulus/webpack-helpers"
6
+
7
+ const application = Application.start()
8
+ const context = require.context("controllers", true, /_controller\.js$/)
9
+ application.load(definitionsFromContext(context))
10
+
11
+
12
+ document.addEventListener('DOMContentLoaded', () => {
13
+ // Fixes body scrolling issues on iPad
14
+ setInterval(() => {
15
+ if (document.querySelector('.modal.is-active') == null && document.querySelector('html').scrollTop > 0) {
16
+
17
+ // scroll it!
18
+ scrollToY(0)
19
+ // document.querySelector('html').scrollTop = 0
20
+ }
21
+ }, 1000)
22
+ })
23
+
24
+ // first add raf shim
25
+ // http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
26
+ window.requestAnimFrame = (function(){
27
+ return window.requestAnimationFrame ||
28
+ window.webkitRequestAnimationFrame ||
29
+ window.mozRequestAnimationFrame ||
30
+ function( callback ){
31
+ window.setTimeout(callback, 1000 / 60);
32
+ };
33
+ })();
34
+
35
+ // main function
36
+ function scrollToY(scrollTargetY, speed, easing) {
37
+ // scrollTargetY: the target scrollY property of the window
38
+ // speed: time in pixels per second
39
+ // easing: easing equation to use
40
+
41
+ var scrollY = window.scrollY || document.documentElement.scrollTop,
42
+ scrollTargetY = scrollTargetY || 0,
43
+ speed = speed || 2000,
44
+ easing = easing || 'easeOutSine',
45
+ currentTime = 0;
46
+
47
+ // min time .1, max time .8 seconds
48
+ var time = Math.max(.1, Math.min(Math.abs(scrollY - scrollTargetY) / speed, .8));
49
+
50
+ // easing equations from https://github.com/danro/easing-js/blob/master/easing.js
51
+ var easingEquations = {
52
+ easeOutSine: function (pos) {
53
+ return Math.sin(pos * (Math.PI / 2));
54
+ },
55
+ easeInOutSine: function (pos) {
56
+ return (-0.5 * (Math.cos(Math.PI * pos) - 1));
57
+ },
58
+ easeInOutQuint: function (pos) {
59
+ if ((pos /= 0.5) < 1) {
60
+ return 0.5 * Math.pow(pos, 5);
61
+ }
62
+ return 0.5 * (Math.pow((pos - 2), 5) + 2);
63
+ }
64
+ };
65
+
66
+ // add animation loop
67
+ function tick() {
68
+ currentTime += 1 / 60;
69
+
70
+ var p = currentTime / time;
71
+ var t = easingEquations[easing](p);
72
+
73
+ if (p < 1) {
74
+ requestAnimFrame(tick);
75
+
76
+ window.scrollTo(0, scrollY + ((scrollTargetY - scrollY) * t));
77
+ } else {
78
+ console.log('scroll done');
79
+ window.scrollTo(0, scrollTargetY);
80
+ }
81
+ }
82
+
83
+ // call it once to get started
84
+ tick();
85
+ }
@@ -0,0 +1,33 @@
1
+ import { Controller } from "stimulus"
2
+ import axios from "axios"
3
+
4
+ export default class extends Controller {
5
+ async more(event) {
6
+ event.preventDefault()
7
+ const button = event.currentTarget
8
+ const path = event.currentTarget.href
9
+ button.classList.add('is-loading')
10
+
11
+ try {
12
+ const response = await axios.get(path)
13
+ const listContents = document.createElement('template')
14
+ listContents.innerHTML = response.data
15
+ const listId = this.element.id.split('-')[1]
16
+ const list = document.getElementById('list-' + listId)
17
+ console.log(list, this.element, listContents)
18
+ list.insertBefore(listContents.content.cloneNode(true), this.element)
19
+
20
+ let nextPage = path.match(/page=(\d+)/)[1]
21
+ nextPage = parseInt(nextPage, 10) + 1
22
+ button.href = button.href.replace(/page=(\d+)/, "page=" + nextPage)
23
+ button.classList.remove('is-loading')
24
+
25
+ if (response.data.includes("data-list-done=\"true\"")) {
26
+ button.remove()
27
+ }
28
+ } catch (error) {
29
+ button.classList.remove('is-loading')
30
+ console.error(error)
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,157 @@
1
+ import EasyMDE from "easymde"
2
+ import { replaceEditorSelection } from "../lib/utils.js"
3
+
4
+ import { Controller } from "stimulus"
5
+
6
+ export default class extends Controller {
7
+ static targets = [ "editor" ]
8
+
9
+ yariiCustomImage(editor) {
10
+ var cm = editor.codemirror
11
+
12
+ // TODO: figure out how to hook Cloudinary into this custom action
13
+ /*
14
+ document.querySelector('#imagemodal').classList.add('is-active')
15
+ document.querySelector('#imagemodal button.button').addEventListener('click', () => {
16
+ document.querySelector('#imagemodal').classList.remove('is-active')
17
+ replaceEditorSelection(cm, null, ["{% 'foobar' | cloudinary_image:'q_50' %}", ''])
18
+ })
19
+ */
20
+ }
21
+
22
+ connect() {
23
+ this.easyMDE = new EasyMDE({
24
+ autoDownloadFontAwesome: false,
25
+ spellChecker: false,
26
+ forceSync: true,
27
+ element: this.editorTarget,
28
+ toolbar: [
29
+ {
30
+ name: 'bold',
31
+ action: EasyMDE.toggleBold,
32
+ className: 'fa fa-bold',
33
+ title: 'Bold',
34
+ },
35
+ {
36
+ name: 'italic',
37
+ action: EasyMDE.toggleItalic,
38
+ className: 'fa fa-italic',
39
+ title: 'Italic',
40
+ },
41
+ {
42
+ name: 'strikethrough',
43
+ action: EasyMDE.toggleStrikethrough,
44
+ className: 'fa fa-strikethrough',
45
+ title: 'Strikethrough',
46
+ },
47
+ {
48
+ name: 'heading-1',
49
+ action: EasyMDE.toggleHeading1,
50
+ className: 'fa fa-header fa-heading header-1',
51
+ title: 'Big Heading',
52
+ },
53
+ {
54
+ name: 'heading-2',
55
+ action: EasyMDE.toggleHeading2,
56
+ className: 'fa fa-header fa-heading header-2',
57
+ title: 'Medium Heading',
58
+ },
59
+ {
60
+ name: 'heading-3',
61
+ action: EasyMDE.toggleHeading3,
62
+ className: 'fa fa-header fa-heading header-3',
63
+ title: 'Small Heading',
64
+ },
65
+ '|',
66
+ {
67
+ name: 'code',
68
+ action: EasyMDE.toggleCodeBlock,
69
+ className: 'fa fa-code',
70
+ title: 'Code',
71
+ },
72
+ {
73
+ name: 'quote',
74
+ action: EasyMDE.toggleBlockquote,
75
+ className: 'fa fa-quote-left',
76
+ title: 'Quote'
77
+ },
78
+ {
79
+ name: 'unordered-list',
80
+ action: EasyMDE.toggleUnorderedList,
81
+ className: 'fa fa-list-ul',
82
+ title: 'Generic List'
83
+ },
84
+ {
85
+ name: 'ordered-list',
86
+ action: EasyMDE.toggleOrderedList,
87
+ className: 'fa fa-list-ol',
88
+ title: 'Numbered List'
89
+ },
90
+ {
91
+ name: 'clean-block',
92
+ action: EasyMDE.cleanBlock,
93
+ className: 'fa fa-eraser',
94
+ title: 'Clean block',
95
+ },
96
+ '|',
97
+ {
98
+ name: 'link',
99
+ action: EasyMDE.drawLink,
100
+ className: 'fa fa-link',
101
+ title: 'Create Link'
102
+ },
103
+ {
104
+ name: 'yarii-custom-image',
105
+ action: (editor) => {this.yariiCustomImage(editor)},
106
+ className: 'fa fa-image',
107
+ title: 'Insert Image'
108
+ },
109
+ {
110
+ name: 'horizontal-rule',
111
+ action: EasyMDE.drawHorizontalRule,
112
+ className: 'fa fa-minus',
113
+ title: 'Insert Horizontal Line',
114
+ },
115
+ '|',
116
+ {
117
+ name: 'preview',
118
+ action: EasyMDE.togglePreview,
119
+ className: 'fa fa-eye',
120
+ noDisable: true,
121
+ title: 'Toggle Preview'
122
+ },
123
+ {
124
+ name: 'side-by-side',
125
+ action: EasyMDE.toggleSideBySide,
126
+ className: 'fa fa-columns',
127
+ noDisable: true,
128
+ noMobile: true,
129
+ title: 'Toggle Side by Side'
130
+ },
131
+ {
132
+ name: 'fullscreen',
133
+ action: EasyMDE.toggleFullScreen,
134
+ className: 'fa fa-arrows-alt',
135
+ noDisable: true,
136
+ noMobile: true,
137
+ title: 'Toggle Fullscreen'
138
+ },
139
+ '|',
140
+ {
141
+ name: 'undo',
142
+ action: EasyMDE.undo,
143
+ className: 'fa fa-undo',
144
+ noDisable: true,
145
+ title: 'Undo',
146
+ },
147
+ {
148
+ name: 'redo',
149
+ action: EasyMDE.redo,
150
+ className: 'fa fa-repeat fa-redo',
151
+ noDisable: true,
152
+ title: 'Redo',
153
+ },
154
+ ]
155
+ });
156
+ }
157
+ }
@@ -0,0 +1,16 @@
1
+ import { Controller } from "stimulus"
2
+ import axios from "axios"
3
+
4
+ export default class extends Controller {
5
+ async open() {
6
+ const newPath = this.data.get('path')
7
+
8
+ try {
9
+ const response = await axios.get(newPath)
10
+ document.querySelector('#editor-modal-wrapper').innerHTML = response.data
11
+ document.querySelector('#editor-modal input').focus()
12
+ } catch (error) {
13
+ console.log(error)
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,17 @@
1
+ import { Controller } from "stimulus"
2
+ import axios from "axios"
3
+
4
+ export default class extends Controller {
5
+ async open(event) {
6
+ event.preventDefault()
7
+ const path = this.element.href
8
+
9
+ try {
10
+ const response = await axios.get(path)
11
+ document.querySelector('#commit-modal-wrapper').innerHTML = response.data
12
+ document.querySelector('#commit-modal input').focus()
13
+ } catch (error) {
14
+ console.error(error)
15
+ }
16
+ }
17
+ }