dokno 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +130 -0
  4. data/Rakefile +22 -0
  5. data/app/assets/config/dokno_manifest.js +3 -0
  6. data/app/assets/javascripts/dokno.js +144 -0
  7. data/app/assets/javascripts/dokno/application.js +15 -0
  8. data/app/assets/javascripts/feather.min.js +13 -0
  9. data/app/assets/javascripts/init.js +5 -0
  10. data/app/assets/stylesheets/dokno/application.css +137 -0
  11. data/app/assets/stylesheets/dokno/tailwind.min.css +1 -0
  12. data/app/controllers/dokno/application_controller.rb +7 -0
  13. data/app/controllers/dokno/articles_controller.rb +115 -0
  14. data/app/controllers/dokno/categories_controller.rb +63 -0
  15. data/app/controllers/dokno/pagination_concern.rb +15 -0
  16. data/app/controllers/dokno/user_concern.rb +38 -0
  17. data/app/helpers/dokno/application_helper.rb +17 -0
  18. data/app/models/dokno/application_record.rb +5 -0
  19. data/app/models/dokno/article.rb +215 -0
  20. data/app/models/dokno/article_slug.rb +7 -0
  21. data/app/models/dokno/category.rb +100 -0
  22. data/app/models/dokno/log.rb +7 -0
  23. data/app/views/dokno/_article_formatting.html.erb +89 -0
  24. data/app/views/dokno/_article_panel.html.erb +112 -0
  25. data/app/views/dokno/_panel_formatting.html.erb +95 -0
  26. data/app/views/dokno/_reset_formatting.html.erb +55 -0
  27. data/app/views/dokno/articles/_article_form.html.erb +60 -0
  28. data/app/views/dokno/articles/edit.html.erb +7 -0
  29. data/app/views/dokno/articles/new.html.erb +7 -0
  30. data/app/views/dokno/articles/show.html.erb +130 -0
  31. data/app/views/dokno/categories/_category_form.html.erb +33 -0
  32. data/app/views/dokno/categories/edit.html.erb +7 -0
  33. data/app/views/dokno/categories/index.html.erb +96 -0
  34. data/app/views/dokno/categories/new.html.erb +7 -0
  35. data/app/views/layouts/dokno/application.html.erb +100 -0
  36. data/app/views/partials/_form_errors.html.erb +18 -0
  37. data/app/views/partials/_logs.html.erb +52 -0
  38. data/app/views/partials/_pagination.html.erb +35 -0
  39. data/config/routes.rb +11 -0
  40. data/db/migrate/20201203190330_baseline.rb +62 -0
  41. data/lib/dokno.rb +4 -0
  42. data/lib/dokno/config/config.rb +73 -0
  43. data/lib/dokno/engine.rb +27 -0
  44. data/lib/dokno/version.rb +3 -0
  45. data/lib/generators/dokno/install/install_generator.rb +13 -0
  46. data/lib/generators/dokno/templates/config/dokno_template.md +5 -0
  47. data/lib/generators/dokno/templates/config/initializers/dokno.rb +14 -0
  48. data/lib/tasks/dokno_tasks.rake +4 -0
  49. metadata +253 -0
@@ -0,0 +1,7 @@
1
+ module Dokno
2
+ class ArticleSlug < ApplicationRecord
3
+ belongs_to :article
4
+
5
+ validates :slug, presence: true
6
+ end
7
+ end
@@ -0,0 +1,100 @@
1
+ module Dokno
2
+ class Category < ApplicationRecord
3
+ belongs_to :parent,
4
+ class_name: 'Dokno::Category',
5
+ primary_key: 'id',
6
+ foreign_key: 'category_id',
7
+ optional: true,
8
+ inverse_of: :children
9
+ has_many :children,
10
+ class_name: 'Dokno::Category',
11
+ primary_key: 'id',
12
+ foreign_key: 'category_id',
13
+ dependent: :nullify,
14
+ inverse_of: :parent
15
+
16
+ has_and_belongs_to_many :articles
17
+
18
+ validates :name, :code, presence: true, uniqueness: true
19
+ validate :circular_parent_check
20
+
21
+ before_validation :set_code
22
+
23
+ # The display breadcrumb for the Category
24
+ def breadcrumb
25
+ crumbs = [name]
26
+ parent_category_id = category_id
27
+
28
+ loop do
29
+ break if parent_category_id.blank?
30
+
31
+ parent_category = self.class.find(parent_category_id)
32
+ crumbs.prepend parent_category.name
33
+ parent_category_id = parent_category.category_id
34
+ end
35
+
36
+ crumbs.join(' > ')
37
+ end
38
+
39
+ # All Articles in the Category, including all child Categories
40
+ def articles_in_branch(order: :updated)
41
+ records = Article
42
+ .includes(:categories_dokno_articles, :categories)
43
+ .joins(:categories)
44
+ .where(dokno_categories: { id: self.class.branch(parent_category_id: id).pluck(:id) })
45
+
46
+ records = records.updated_order if order == :updated
47
+ records = records.newest_order if order == :newest
48
+ records = records.view_order if order == :views
49
+ records = records.alpha_order if order == :alpha
50
+
51
+ records
52
+ end
53
+
54
+ def branch
55
+ self.class.branch(parent_category_id: id)
56
+ end
57
+
58
+ # The given Category and all child Categories. Useful for filtering associated articles.
59
+ def self.branch(parent_category_id:, at_top: true)
60
+ return if parent_category_id.blank?
61
+
62
+ categories = []
63
+ parent_category = find(parent_category_id)
64
+ child_categories = parent_category.children.to_a
65
+
66
+ child_categories.each do |child_category|
67
+ categories << child_category << branch(parent_category_id: child_category.id, at_top: false)
68
+ end
69
+
70
+ categories.prepend parent_category if at_top
71
+ categories.flatten
72
+ end
73
+
74
+ # HTML markup for Category SELECT field OPTION lists
75
+ def self.select_option_markup(selected_category_codes: nil, exclude_category_id: nil)
76
+ breadcrumbs = all
77
+ .reject { |category| category.id == exclude_category_id.to_i }
78
+ .map { |category| { code: category.code, name: category.breadcrumb } }
79
+ breadcrumbs.sort_by { |category_hash| category_hash[:name] }.map do |category_hash|
80
+ selected = selected_category_codes&.include?(category_hash[:code])
81
+ %(<option value="#{category_hash[:code]}" #{'selected="selected"' if selected}>#{category_hash[:name]}</option>)
82
+ end.join
83
+ end
84
+
85
+ private
86
+
87
+ # Never allow setting of parent to self
88
+ def circular_parent_check
89
+ return unless persisted? && id.to_i == category_id.to_i
90
+
91
+ errors.add(:category_id, "can't set parent to self")
92
+ end
93
+
94
+ def set_code
95
+ return unless name.present?
96
+
97
+ self.code = name.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '')
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,7 @@
1
+ module Dokno
2
+ class Log < ApplicationRecord
3
+ belongs_to :article
4
+
5
+ default_scope { order(created_at: :desc) }
6
+ end
7
+ end
@@ -0,0 +1,89 @@
1
+ <style>
2
+ /* Article formatting */
3
+ .dokno-article-content-markup {
4
+ font-weight: 300;
5
+ line-height: 1.75rem;
6
+ font-size: 1rem;
7
+ }
8
+
9
+ .dokno-article-content-markup code {
10
+ font-size: 80%;
11
+ background-color: #eee;
12
+ padding: 3px 5px;
13
+ border-radius: 3px;
14
+ }
15
+
16
+ .dokno-article-content-markup b,
17
+ .dokno-article-content-markup strong {
18
+ font-weight: 500;
19
+ }
20
+
21
+ .dokno-article-content-markup p,
22
+ .dokno-article-content-markup ul,
23
+ .dokno-article-content-markup ol,
24
+ .dokno-article-content-markup hr,
25
+ .dokno-article-content-markup table {
26
+ margin-bottom: 1.25rem;
27
+ }
28
+
29
+ .dokno-article-content-markup h1,
30
+ .dokno-article-content-markup h2,
31
+ .dokno-article-content-markup h3,
32
+ .dokno-article-content-markup h4,
33
+ .dokno-article-content-markup h5,
34
+ .dokno-article-content-markup h6 {
35
+ margin-bottom: 1.25rem;
36
+ font-weight: 600;
37
+ }
38
+
39
+ .dokno-article-content-markup h1 {
40
+ font-size: 2rem;
41
+ }
42
+
43
+ .dokno-article-content-markup h2 {
44
+ font-size: 1.625rem;
45
+ }
46
+
47
+ .dokno-article-content-markup h3 {
48
+ font-size: 1.25rem;
49
+ }
50
+
51
+ .dokno-article-content-markup h4 {
52
+ font-size: 1rem;
53
+ }
54
+
55
+ .dokno-article-content-markup blockquote {
56
+ border-left: 4px solid #edf2f7;
57
+ padding-left: 1.25rem;
58
+ }
59
+
60
+ .dokno-article-content-markup ul,
61
+ .dokno-article-content-markup ol {
62
+ list-style-position: inside;
63
+ }
64
+
65
+ .dokno-article-content-markup ul {
66
+ list-style-type: disc;
67
+ }
68
+
69
+ .dokno-article-content-markup ol {
70
+ list-style-type: decimal;
71
+ }
72
+
73
+ .dokno-article-content-markup table {
74
+ table-layout: auto;
75
+ }
76
+
77
+ .dokno-article-content-markup table th,
78
+ .dokno-article-content-markup table td {
79
+ padding-left: 1rem;
80
+ padding-right: 1rem;
81
+ padding-top: 0.5rem;
82
+ padding-bottom: 0.5rem;
83
+ font-weight: normal;
84
+ }
85
+
86
+ .dokno-article-content-markup table td {
87
+ border-width: 1px;
88
+ }
89
+ </style>
@@ -0,0 +1,112 @@
1
+ <!-- DOKNO SLIDE-OUT PANEL -->
2
+ <!-- This file is injected into the host layout when included via render ` 'dokno/article_panel' `. -->
3
+ <!-- It provides the necessary markup, CSS, and JS to support in-context flyout article panels. -->
4
+
5
+ <!-- Reset styles -->
6
+ <%= render 'dokno/reset_formatting' %>
7
+
8
+ <!-- Slide-out panel styles -->
9
+ <%= render 'dokno/panel_formatting' %>
10
+
11
+ <!-- Article content styles -->
12
+ <%= render 'dokno/article_formatting' %>
13
+
14
+ <!-- Slide-out panel markup -->
15
+ <div id="dokno-panel-container">
16
+ <div id="dokno-panel-close" class="dokno-hidden">
17
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="#eee" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x-circle">
18
+ <circle cx="12" cy="12" r="10"></circle>
19
+ <line x1="15" y1="9" x2="9" y2="15"></line>
20
+ <line x1="9" y1="9" x2="15" y2="15"></line>
21
+ </svg>
22
+ </div>
23
+ <div id="dokno-panel-title"></div>
24
+ <div id="dokno-panel-summary"></div>
25
+ <div id="dokno-panel-markdown" class="dokno-article-content-markup"></div>
26
+ <div id="dokno-panel-footer"></div>
27
+ </div>
28
+
29
+ <!-- Slide-out panel behavior -->
30
+ <script>
31
+ function doknoOpenPanel(slug) {
32
+ if (slug == dokno__slug) {
33
+ doknoClosePanel();
34
+ return true;
35
+ }
36
+ dokno__slug = slug;
37
+
38
+ // Can't use fetch API; IE
39
+ const request = new XMLHttpRequest();
40
+
41
+ request.open('GET', '<%= Dokno::Engine.routes.url_helpers.root_path %>article_panel/' + slug, true);
42
+ request.onload = function() {
43
+ if (request.readyState == 4 && request.status == 200) {
44
+ try {
45
+ var data = JSON.parse(request.responseText);
46
+ } catch(err) {
47
+ console.log("Dokno: " + err.message + " in " + request.responseText);
48
+ return;
49
+ }
50
+
51
+ // Populate panel
52
+ dokno__id = data.id;
53
+ dokno__panel_title.innerHTML = data.title;
54
+ dokno__panel_summary.innerHTML = data.summary;
55
+ dokno__panel_footer.innerHTML = data.footer;
56
+ dokno__panel_markdown.innerHTML = data.markdown;
57
+
58
+ doknoRevealAppropriatePanelSections(data);
59
+
60
+ // Reveal panel
61
+ dokno__panel.scrollTop = 0;
62
+ dokno__panel.classList.add('open');
63
+ document.body.classList.add('dokno-no-scroll');
64
+
65
+ // Reveal fixed close icon
66
+ setTimeout(function() { dokno__close.classList.remove('dokno-hidden'); }, 200);
67
+
68
+ // Close on escape
69
+ document.addEventListener('keydown', dokno__keydown_listener, false);
70
+ }
71
+ };
72
+
73
+ request.send();
74
+ }
75
+
76
+ function doknoClosePanel() {
77
+ dokno__close.classList.add('dokno-hidden');
78
+ dokno__panel.classList.remove('open');
79
+ document.body.classList.remove('dokno-no-scroll');
80
+ document.removeEventListener('keydown', dokno__keydown_listener, false);
81
+
82
+ dokno__slug = null;
83
+ }
84
+
85
+ function doknoRevealAppropriatePanelSections(data) {
86
+ const markdown_classes = dokno__panel_markdown.classList;
87
+ const summary_classes = dokno__panel_summary.classList;
88
+
89
+ if (data.markdown != '') {
90
+ markdown_classes.remove('dokno-hidden');
91
+ if (data.summary == '') { summary_classes.add('dokno-hidden'); } else { summary_classes.remove('dokno-hidden'); }
92
+ } else {
93
+ markdown_classes.add('dokno-hidden');
94
+ summary_classes.remove('dokno-hidden');
95
+ }
96
+ }
97
+
98
+ const dokno__panel = document.getElementById('dokno-panel-container');
99
+ const dokno__close = document.querySelector('div#dokno-panel-close');
100
+ const dokno__panel_title = document.getElementById('dokno-panel-title');
101
+ const dokno__panel_summary = document.getElementById('dokno-panel-summary');
102
+ const dokno__panel_footer = document.getElementById('dokno-panel-footer');
103
+ const dokno__panel_markdown = document.getElementById('dokno-panel-markdown');
104
+
105
+ const dokno__keydown_listener = function(e) {
106
+ if (e.key === 'Escape') { doknoClosePanel(); }
107
+ }
108
+
109
+ dokno__close.onclick = function() { doknoClosePanel(); }
110
+
111
+ var dokno__id, dokno__slug;
112
+ </script>
@@ -0,0 +1,95 @@
1
+ <style>
2
+ .dokno-no-scroll {
3
+ overflow: hidden !important;
4
+ }
5
+
6
+ .dokno-hidden {
7
+ display: none !important;
8
+ }
9
+
10
+ div#dokno-panel-container {
11
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
12
+ font-size: 1rem;
13
+ line-height: 1.5rem;
14
+ right: -600px;
15
+ z-index: 9999;
16
+ position: fixed;
17
+ top: 0;
18
+ bottom: 0;
19
+ width: 600px;
20
+ max-width: 100vw;
21
+ overflow: hidden;
22
+ overflow-y: auto;
23
+ background-color: #fff;
24
+ color: #2d3748;
25
+ transition: all .15s ease-in-out;
26
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
27
+ }
28
+
29
+ div#dokno-panel-container.open {
30
+ right: 0;
31
+ }
32
+
33
+ div#dokno-panel-close {
34
+ position: fixed;
35
+ background-color: transparent;
36
+ color: #222;
37
+ cursor: pointer;
38
+ top: 1.5rem;
39
+ right: 2.5rem;
40
+ }
41
+
42
+ div#dokno-panel-title {
43
+ padding: 2.5rem;
44
+ font-size: 2.25rem;
45
+ line-height: 2.75rem;
46
+ font-weight: 300;
47
+ background-color: rgb(42,67,101);
48
+ color: #ebf8ff;
49
+ }
50
+
51
+ div#dokno-panel-title div#article-deprecated-alert {
52
+ font-size: 1rem;
53
+ line-height: 1.5rem;
54
+ padding: 1rem;
55
+ margin-bottom: 1rem;
56
+ border-top-width: 4px;
57
+ border-radius: .25rem;
58
+ border-top-style: solid;
59
+ border-color: rgb(116,66,16);
60
+ background-color: rgb(183,121,31);
61
+ color: rgb(255,255,255);
62
+ }
63
+
64
+ div#dokno-panel-title > span,
65
+ div#dokno-panel-footer > svg {
66
+ cursor: pointer;
67
+ }
68
+
69
+ div#dokno-panel-summary {
70
+ font-size: 1.5rem;
71
+ line-height: 2.25rem;
72
+ padding: 2.5rem;
73
+ font-weight: 300;
74
+ }
75
+
76
+ div#dokno-panel-markdown {
77
+ padding: 2.5rem;
78
+ background-color: #f7fafc;
79
+ }
80
+
81
+ div#dokno-panel-footer {
82
+ text-align: center;
83
+ position: relative;
84
+ padding: 2.5rem;
85
+ color: #999;
86
+ background-color: #edf2f7;
87
+ font-weight: 300;
88
+ font-size: 1rem;
89
+ line-height: 1.5rem;
90
+ }
91
+
92
+ div#dokno-panel-footer p {
93
+ margin: .5rem 0;
94
+ }
95
+ </style>
@@ -0,0 +1,55 @@
1
+ <style>
2
+ /* Resets */
3
+ .dokno-article-content-markup blockquote,
4
+ .dokno-article-content-markup dl,
5
+ .dokno-article-content-markup dd,
6
+ .dokno-article-content-markup h1,
7
+ .dokno-article-content-markup h2,
8
+ .dokno-article-content-markup h3,
9
+ .dokno-article-content-markup h4,
10
+ .dokno-article-content-markup h5,
11
+ .dokno-article-content-markup h6,
12
+ .dokno-article-content-markup hr,
13
+ .dokno-article-content-markup figure,
14
+ .dokno-article-content-markup p,
15
+ .dokno-article-content-markup pre {
16
+ margin: 0;
17
+ }
18
+
19
+ .dokno-article-content-markup h1,
20
+ .dokno-article-content-markup h2,
21
+ .dokno-article-content-markup h3,
22
+ .dokno-article-content-markup h4,
23
+ .dokno-article-content-markup h5,
24
+ .dokno-article-content-markup h6 {
25
+ font-size: inherit;
26
+ font-weight: inherit;
27
+ }
28
+
29
+ .dokno-article-content-markup ol,
30
+ .dokno-article-content-markup ul {
31
+ list-style: none;
32
+ margin: 0;
33
+ padding: 0;
34
+ }
35
+
36
+ .dokno-article-content-markup img,
37
+ .dokno-article-content-markup svg,
38
+ .dokno-article-content-markup video,
39
+ .dokno-article-content-markup canvas,
40
+ .dokno-article-content-markup audio,
41
+ .dokno-article-content-markup iframe,
42
+ .dokno-article-content-markup embed,
43
+ .dokno-article-content-markup object {
44
+ display: block;
45
+ vertical-align: middle;
46
+ }
47
+
48
+ .dokno-article-content-markup *,
49
+ .dokno-article-content-markup ::before,
50
+ .dokno-article-content-markup ::after {
51
+ border-width: 0;
52
+ border-style: solid;
53
+ border-color: currentColor;
54
+ }
55
+ </style>