dokno 1.0.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 (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>