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,7 @@
1
+ import { Controller } from "stimulus"
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ this.element.innerHTML = "Loaded!"
6
+ }
7
+ }
@@ -0,0 +1,45 @@
1
+ export const axiosPostable = axios => {
2
+ return axios.create({
3
+ headers: {
4
+ 'X-CSRF-Token': document.getElementsByName('csrf-token')[0].getAttribute('content'),
5
+ 'Accept': 'application/json'
6
+ }
7
+ })
8
+ }
9
+
10
+ export const replaceEditorSelection = (cm, active, startEnd, url) => {
11
+ // Cribbed directly from EasyMDE code
12
+
13
+ if (/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
14
+ return;
15
+
16
+ var text;
17
+ var start = startEnd[0];
18
+ var end = startEnd[1];
19
+ var startPoint = {},
20
+ endPoint = {};
21
+ Object.assign(startPoint, cm.getCursor('start'));
22
+ Object.assign(endPoint, cm.getCursor('end'));
23
+ if (url) {
24
+ end = end.replace('#url#', url);
25
+ }
26
+ if (active) {
27
+ text = cm.getLine(startPoint.line);
28
+ start = text.slice(0, startPoint.ch);
29
+ end = text.slice(startPoint.ch);
30
+ cm.replaceRange(start + end, {
31
+ line: startPoint.line,
32
+ ch: 0,
33
+ });
34
+ } else {
35
+ text = cm.getSelection();
36
+ cm.replaceSelection(start + text + end);
37
+
38
+ startPoint.ch += start.length;
39
+ if (startPoint !== endPoint) {
40
+ endPoint.ch += start.length;
41
+ }
42
+ }
43
+ cm.setSelection(startPoint, endPoint);
44
+ cm.focus();
45
+ }
@@ -0,0 +1,4 @@
1
+ require("@rails/ujs").start()
2
+
3
+ import '../../styles/application.scss'
4
+ import "controllers"
@@ -0,0 +1,4 @@
1
+ module Yarii
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Yarii
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,26 @@
1
+ module YariiEditor
2
+ module ModelCallbacks
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ after_save :add_to_repository_index
7
+ around_destroy :remove_from_repository
8
+ end
9
+
10
+ def add_to_repository_index
11
+ if persisted?
12
+ CurrentSite.site&.repository&.add(file_path)
13
+ CurrentSite.site&.build_preview
14
+ end
15
+ end
16
+
17
+ def remove_from_repository
18
+ if persisted?
19
+ past_file_path = file_path
20
+ yield
21
+ CurrentSite.site&.repository&.remove(past_file_path)
22
+ CurrentSite.site&.build_preview
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ module YariiEditor
2
+ module Previewing
3
+ extend ActiveSupport::Concern
4
+
5
+ def preview_url
6
+ "#{CurrentSite.site.preview_base_url}/#{preview_path}"
7
+ end
8
+
9
+ def preview_path
10
+ # override in subclasses
11
+ if respond_to?(:permalink) and permalink.present?
12
+ permalink.sub(/^\//, '')
13
+ else
14
+ relative_path.sub(/^\//, '').sub(/\.md|\.html|\.markdown/, '')
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ class Page < ::ApplicationContentModel
2
+ variables :layout, :date, :published, :title, :subtitle, :tags, :permalink
3
+
4
+ def generate_new_file_path
5
+ slug = if title
6
+ title.gsub(/['|"]/,'').parameterize
7
+ else
8
+ "untitled_#{self.class.name.parameterize}"
9
+ end
10
+
11
+ self.class.absolute_path("#{slug}.md")
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ class Post < ::ApplicationContentModel
2
+ folder '_posts'
3
+
4
+ variables :layout,
5
+ :date,
6
+ :published,
7
+ :category,
8
+ :title,
9
+ :subtitle,
10
+ :tags
11
+
12
+ def generate_new_file_path
13
+ if use_date = date && date.to_datetime
14
+ date_prefix = "#{use_date.strftime('%Y-%m-%d')}-"
15
+ else
16
+ date_prefix = "#{Date.current.strftime('%Y-%m-%d')}-"
17
+ end
18
+
19
+ slug = if title
20
+ title.gsub(/['|"]/,'').parameterize
21
+ else
22
+ "untitled_#{self.class.name.parameterize}"
23
+ end
24
+
25
+ year = date_prefix.split('-').first
26
+
27
+ self.class.absolute_path("#{year}/#{date_prefix}#{slug}.md")
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ module Yarii
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,50 @@
1
+ require 'git'
2
+
3
+ class Yarii::Repository
4
+ attr_accessor :repo_dir, :git, :remote, :branch
5
+
6
+ Changeset = Struct.new(:total, :added, :modified, :deleted, keyword_init: true)
7
+
8
+ def initialize(repo_dir)
9
+ @repo_dir = repo_dir
10
+ @git = Git.open(@repo_dir)
11
+ @remote = "origin"
12
+ @branch = @git.current_branch
13
+ end
14
+
15
+ def add(filepath)
16
+ @git.add(filepath)
17
+ end
18
+
19
+ def remove(filepath)
20
+ @git.remove(filepath)
21
+ end
22
+
23
+ def changes(only_indexed: true)
24
+ indexed_flag = only_indexed ? '--cached' : nil
25
+ git_diff = @git.lib.diff_name_status('HEAD', indexed_flag)
26
+ Changeset.new(
27
+ total: git_diff.length,
28
+ added: git_diff.select{|k,v| v == 'A'}.keys,
29
+ modified: git_diff.select{|k,v| v == 'M'}.keys,
30
+ deleted: git_diff.select{|k,v| v == 'D'}.keys
31
+ )
32
+ end
33
+
34
+ def pull(remote: nil)
35
+ @git.pull(@remote || remote, @branch)
36
+ end
37
+
38
+ def needs_pull?(remote: nil)
39
+ @git.fetch(@remote || remote)
40
+ @git.lib.diff_name_status('HEAD','@{upstream}').length > 0
41
+ end
42
+
43
+ def commit(message:)
44
+ @git.commit(message)
45
+ end
46
+
47
+ def push(remote: nil)
48
+ @git.push(@remote || remote, @branch)
49
+ end
50
+ end
@@ -0,0 +1,107 @@
1
+ require 'safe_yaml'
2
+
3
+ module Yarii
4
+ class Site < ApplicationRecord
5
+ def self.table_name_prefix
6
+ 'yarii_'
7
+ end
8
+
9
+ def repository
10
+ @repository ||= Yarii::Repository.new(git_repo_path)
11
+ end
12
+
13
+ def content_models
14
+ return @content_models if @content_models
15
+
16
+ yaml_path = File.join(git_repo_path, '.yarii', 'content_models.yml')
17
+ if (File.exist?(yaml_path))
18
+ @content_models = ::SafeYAML.load(File.open(yaml_path))
19
+ @content_models = @content_models[Rails.env]&.with_indifferent_access
20
+ if @content_models.nil?
21
+ raise "No content models were found for the #{Rails.env} environment"
22
+ end
23
+
24
+ setup_content_model_variables
25
+
26
+ @content_models
27
+ else
28
+ raise "No content models YAML file was found at #{yaml_path}"
29
+ end
30
+ end
31
+
32
+ def update_status_before_commit!
33
+ well_known_path = File.join(content_base_path, '.well-known')
34
+ unless Dir.exist?(well_known_path)
35
+ Dir.mkdir(well_known_path)
36
+ end
37
+ status_path = File.join(content_base_path, '.well-known', 'yarii-status')
38
+ File.open(status_path, 'w') do |f|
39
+ update_digest = Digest::SHA256.hexdigest(Time.now.to_i.to_s)
40
+ f.puts update_digest
41
+ end
42
+ repository.add(status_path)
43
+ true
44
+ end
45
+
46
+ def commit!(message:)
47
+ update_status_before_commit!
48
+ repository.commit(message: message)
49
+ end
50
+
51
+ def push
52
+ repository.push
53
+
54
+ post_push_path = File.join(git_repo_path, '.git', 'hooks', 'post-push')
55
+ if File.exist?(post_push_path)
56
+ Bundler.with_clean_env do
57
+ trap('SIGINT') { exit }
58
+ puts "*** Executing post push hook"
59
+ output = system("#{ENV["SHELL"]} --login -c \"cd #{git_repo_path} && #{post_push_path}\"")
60
+ end
61
+ end
62
+ end
63
+
64
+ def remote_is_up_to_date?
65
+ # NOTE: the Jekyll site config needs to include .well-known in the list of
66
+ # folders to build! Otherwise this won't work!
67
+ status_path = File.join(content_base_path, '.well-known', 'yarii-status')
68
+ if (File.exist?(status_path))
69
+ update_digest = File.open(status_path).read.chomp
70
+ published_status = Net::HTTP.get(URI(published_base_url + "/.well-known/yarii-status"))
71
+ published_status.chomp == update_digest
72
+ else
73
+ false
74
+ end
75
+ end
76
+
77
+ def preview_build_command
78
+ if "preview_build_command".in?(self.class.columns.map(&:name))
79
+ attributes["preview_build_command"]
80
+ else
81
+ 'bundle exec jekyll build --unpublished'
82
+ end
83
+ end
84
+
85
+ def build_preview
86
+ Bundler.with_clean_env do
87
+ trap('SIGINT') { exit }
88
+ puts "*** Building Preview Site: #{title}"
89
+ output = system("#{ENV["SHELL"]} --login -c \"cd #{git_repo_path} && #{preview_build_command}\"")
90
+ end
91
+ end
92
+
93
+ def setup_content_model_variables
94
+ content_models.values.each do |content_model|
95
+ model_class = Kernel.const_get(content_model['class_name'])
96
+ fields = [content_model['primary_fields'], content_model['additional_fields'], content_model['content_fields']].compact.flatten
97
+ fields.each do |field|
98
+ field_name = field['field_name'].to_sym
99
+ unless model_class.variable_names.include?(field_name) or field_name == :content
100
+ model_class.variable_names << field_name
101
+ model_class.attr_accessor(field_name)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
Binary file
Binary file
@@ -0,0 +1,48 @@
1
+ @font-face {
2
+ font-family: 'FiraGO';
3
+ font-style: normal;
4
+ font-weight: normal;
5
+ src: url('FiraGO/FiraGO-Book.woff') format('woff');
6
+ }
7
+
8
+ @font-face {
9
+ font-family: 'FiraGO';
10
+ font-style: italic;
11
+ font-weight: normal;
12
+ src: url('FiraGO/FiraGO-BookItalic.woff') format('woff');
13
+ }
14
+
15
+ @font-face {
16
+ font-family: 'FiraGO';
17
+ font-style: normal;
18
+ font-weight: bold;
19
+ src: url('FiraGO/FiraGO-SemiBold.woff') format('woff');
20
+ }
21
+
22
+ @font-face {
23
+ font-family: 'FiraGO';
24
+ font-style: italic;
25
+ font-weight: bold;
26
+ src: url('FiraGO/FiraGO-SemiBoldItalic.woff') format('woff');
27
+ }
28
+
29
+ @font-face {
30
+ font-family: 'FiraGO';
31
+ font-style: normal;
32
+ font-weight: 800;
33
+ src: url('FiraGO/FiraGO-Bold.woff') format('woff');
34
+ }
35
+
36
+ @font-face {
37
+ font-family: 'FiraGO';
38
+ font-style: italic;
39
+ font-weight: 800;
40
+ src: url('FiraGO/FiraGO-BoldItalic.woff') format('woff');
41
+ }
42
+
43
+ @font-face {
44
+ font-family: 'FiraGO';
45
+ font-style: italic;
46
+ font-weight: 900;
47
+ src: url('FiraGO/FiraGO-HeavyItalic.woff') format('woff');
48
+ }
@@ -0,0 +1,6 @@
1
+ @font-face {
2
+ font-family: 'Vidaloka';
3
+ src: url('Vidaloka/Vidaloka-Regular.woff') format('woff');
4
+ font-weight: normal;
5
+ font-style: normal;
6
+ }
@@ -0,0 +1,141 @@
1
+ @charset "utf-8";
2
+
3
+ @import "./FiraGO/FiraGO.scss";
4
+ @import "./Vidaloka/Vidaloka.scss";
5
+
6
+ @import "../../node_modules/easymde/dist/easymde.min.css";
7
+
8
+ /* Colors */
9
+ $orange: #FF6F59;
10
+ $darkorange: #DB504A;
11
+ $blue: #00324c;
12
+ $lightblue: #c9e3ff;
13
+ $green: #43AA8B;
14
+ $darkgreen: #254441;
15
+ $lightgreen: rgb(211, 237, 221);
16
+ $beige-lighter: #dddbc4;
17
+ $beige-light: #d1cfba;
18
+ $beige-medium: #b6b394;
19
+ $beige-dark: #42403B;
20
+ $beige-darker: #363430;
21
+ $yellow: rgb(255, 251, 241);
22
+
23
+ /* Set up Bulma color scheme */
24
+ $grey-dark: $beige-dark;
25
+ $grey-darker: $beige-darker;
26
+ $grey-light: $beige-light;
27
+ $primary: $orange;
28
+ $link: $darkorange;
29
+ $dark: $blue;
30
+ $success: $green;
31
+ $warning: darken($yellow, 25%);
32
+
33
+ $text: $beige-darker;
34
+ $body-background-color: $beige-light;
35
+ $family-serif: 'Vidaloka', serif;
36
+ $family-sans-serif: 'FiraGO', sans-serif;
37
+ //$title-family: rufina, sans-serif;
38
+ //$title-weight: 700;
39
+ $title-family: 'FiraGO', sans-serif;
40
+ $title-weight: 900;
41
+ $title-style: italic;
42
+ $title-spacing: -0.03em;
43
+ .title {
44
+ font-style: $title-style;
45
+ letter-spacing: $title-spacing;
46
+ }
47
+ $title-size: 2.2rem;
48
+
49
+ $button-border-width: 0;
50
+
51
+ $card-shadow: 0 0px 3px rgba(10, 10, 10, 0.2);
52
+ $card-header-shadow: none;
53
+ $card-content-padding: 0.9rem 0.8rem 0.8rem;
54
+ $card-header-color: $darkorange;
55
+ $card-header-family: $family-serif;
56
+ $card-header-weight: 400;
57
+ $card-header-background-color: #fafaf6;
58
+ $card-header-border-bottom: 1px solid darken($card-header-background-color, 7%);
59
+ .card-header-title {
60
+ font-family: $card-header-family;
61
+ font-size: 1.85em;
62
+ line-height: 1.15;
63
+ border-bottom: $card-header-border-bottom;
64
+ }
65
+ $card-footer-border-top: $card-header-border-bottom;
66
+ $card-footer-background-color: $card-header-background-color;
67
+ $card-footer-padding: 0.5rem;
68
+ .card-footer-item:hover {
69
+ background-color: #f0f0e3;
70
+ }
71
+
72
+ $modal-card-head-background-color: $dark;
73
+ $modal-card-foot-background-color: $dark;
74
+ $modal-content-width: 800px;
75
+ $modal-card-body-background-color: $card-header-background-color;
76
+ $modal-card-title-color: $orange;
77
+ $modal-card-title-size: 1.75em;
78
+ .modal-card-title {
79
+ font-family: $card-header-family;
80
+ }
81
+
82
+ $content-blockquote-background-color: $yellow;
83
+ $content-blockquote-border-left: 6px solid $orange;
84
+
85
+ $radius: 5px;
86
+ $radius-large: 10px;
87
+
88
+ @import "~bulma/bulma.sass";
89
+ @import "helpers.scss";
90
+ @import "dashboard.scss";
91
+ @import "editor.scss";
92
+
93
+ .navbar.is-dark {
94
+ background-color: $dark;
95
+ box-shadow: 0px 3px 4px rgba(0,0,0,0.2);
96
+ -webkit-backdrop-filter: blur(16px);
97
+ backdrop-filter: blur(16px);
98
+ }
99
+
100
+ .navbar-brand h1 {
101
+ color: $primary !important;
102
+ font-family: $title-family;
103
+ font-weight: $title-weight;
104
+ font-size: 1.35em;
105
+ font-style: $title-style;
106
+ letter-spacing: $title-spacing;
107
+
108
+ img {
109
+ // width: 45px;
110
+ width: 35.33px;
111
+ max-height: 100% !important;
112
+ margin-right: 0.3em;
113
+ transform: rotate(-10deg);
114
+ }
115
+ }
116
+
117
+ .button {
118
+ &.is-primary:not(.is-outlined), &.is-success:not(.is-outlined) {
119
+ box-shadow: 2px 3px 3px rgba(0,0,0,0.1), -2px -3px 6px rgba(255,255,255,0.12);
120
+ }
121
+ }
122
+
123
+ .card-footer {
124
+ a {
125
+ color: $beige-dark;
126
+ font-size: 0.9em;
127
+ }
128
+ }
129
+ .card-footer-item:not(:last-child) {
130
+ border-right: none;
131
+ }
132
+
133
+ .modal-background {
134
+ background-color: rgba(12, 11, 10, 0.8);
135
+ }
136
+ .modal-card {
137
+ box-shadow: 0px 30px 50px 10px rgba(12,11,10,0.5);
138
+ }
139
+ .modal-card-foot {
140
+ justify-content: flex-end;
141
+ }