dokno 1.0.0 → 1.3.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -6
  3. data/app/assets/javascripts/dokno.js +79 -27
  4. data/app/assets/stylesheets/dokno/application.css +1 -1
  5. data/app/controllers/dokno/application_controller.rb +3 -0
  6. data/app/controllers/dokno/articles_controller.rb +22 -8
  7. data/app/controllers/dokno/categories_controller.rb +15 -2
  8. data/app/controllers/dokno/user_concern.rb +5 -3
  9. data/app/helpers/dokno/application_helper.rb +1 -3
  10. data/app/models/dokno/article.rb +87 -38
  11. data/app/models/dokno/category.rb +39 -15
  12. data/app/views/dokno/_article_formatting.html.erb +17 -18
  13. data/app/views/dokno/_article_panel.html.erb +16 -18
  14. data/app/views/dokno/_panel_formatting.html.erb +47 -57
  15. data/app/views/dokno/articles/_article_form.html.erb +47 -6
  16. data/app/views/dokno/articles/show.html.erb +45 -39
  17. data/app/views/dokno/categories/_category_form.html.erb +6 -1
  18. data/app/views/dokno/categories/index.html.erb +40 -37
  19. data/app/views/layouts/dokno/application.html.erb +34 -9
  20. data/app/views/partials/_category_header.html.erb +29 -0
  21. data/app/views/partials/_form_errors.html.erb +0 -1
  22. data/app/views/partials/_logs.html.erb +7 -5
  23. data/app/views/partials/_pagination.html.erb +20 -18
  24. data/config/routes.rb +1 -1
  25. data/db/migrate/20201203190330_baseline.rb +4 -4
  26. data/db/migrate/20201211192306_add_review_due_at_to_articles.rb +6 -0
  27. data/db/migrate/20201213165700_add_starred_to_article.rb +5 -0
  28. data/lib/dokno/config/config.rb +53 -40
  29. data/lib/dokno/engine.rb +4 -4
  30. data/lib/dokno/version.rb +1 -1
  31. data/lib/generators/dokno/templates/config/initializers/dokno.rb +18 -5
  32. metadata +87 -17
@@ -20,6 +20,8 @@ module Dokno
20
20
 
21
21
  before_validation :set_code
22
22
 
23
+ scope :alpha_order, -> { order(:name) }
24
+
23
25
  # The display breadcrumb for the Category
24
26
  def breadcrumb
25
27
  crumbs = [name]
@@ -43,18 +45,18 @@ module Dokno
43
45
  .joins(:categories)
44
46
  .where(dokno_categories: { id: self.class.branch(parent_category_id: id).pluck(:id) })
45
47
 
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
48
+ Article.apply_sort(records, order: order)
52
49
  end
53
50
 
54
51
  def branch
55
52
  self.class.branch(parent_category_id: id)
56
53
  end
57
54
 
55
+ # Used to invalidate the fragment cache of the hierarchical category select options
56
+ def self.cache_key
57
+ [maximum(:updated_at), Article.maximum(:updated_at)].compact.max
58
+ end
59
+
58
60
  # The given Category and all child Categories. Useful for filtering associated articles.
59
61
  def self.branch(parent_category_id:, at_top: true)
60
62
  return if parent_category_id.blank?
@@ -71,19 +73,41 @@ module Dokno
71
73
  categories.flatten
72
74
  end
73
75
 
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
76
+ def self.select_option_markup(selected_category_codes: nil, exclude_category_id: nil, context_category: nil, level: 0)
77
+ return '' if level.positive? && context_category.blank?
78
+
79
+ options = []
80
+ level_categories = where(category_id: context_category&.id).alpha_order
81
+
82
+ level_categories.each do |category|
83
+ options << option_markup(
84
+ category: category,
85
+ selected_category_codes: selected_category_codes,
86
+ exclude_category_id: exclude_category_id,
87
+ level: level
88
+ )
89
+
90
+ options << select_option_markup(
91
+ selected_category_codes: selected_category_codes,
92
+ exclude_category_id: exclude_category_id,
93
+ context_category: category,
94
+ level: (level + 1)
95
+ )
96
+ end
97
+
98
+ options.join
83
99
  end
84
100
 
85
101
  private
86
102
 
103
+ def self.option_markup(category:, selected_category_codes:, exclude_category_id:, level: 0)
104
+ return '' if category.id == exclude_category_id
105
+
106
+ selected = selected_category_codes&.include?(category.code)
107
+ article_count = category.articles_in_branch.size
108
+ %(<option value="#{category.code}" #{'selected="selected"' if selected}>#{('&nbsp;&nbsp;' * level)}#{category.name}#{' (' + article_count.to_s + ')' if article_count.positive?}</option>)
109
+ end
110
+
87
111
  # Never allow setting of parent to self
88
112
  def circular_parent_check
89
113
  return unless persisted? && id.to_i == category_id.to_i
@@ -1,9 +1,9 @@
1
1
  <style>
2
2
  /* Article formatting */
3
3
  .dokno-article-content-markup {
4
- font-weight: 300;
5
- line-height: 1.75rem;
6
- font-size: 1rem;
4
+ font-weight: 400;
5
+ line-height: 1.75em;
6
+ font-size: 1em;
7
7
  }
8
8
 
9
9
  .dokno-article-content-markup code {
@@ -23,7 +23,7 @@
23
23
  .dokno-article-content-markup ol,
24
24
  .dokno-article-content-markup hr,
25
25
  .dokno-article-content-markup table {
26
- margin-bottom: 1.25rem;
26
+ margin-bottom: 1.25em;
27
27
  }
28
28
 
29
29
  .dokno-article-content-markup h1,
@@ -32,29 +32,28 @@
32
32
  .dokno-article-content-markup h4,
33
33
  .dokno-article-content-markup h5,
34
34
  .dokno-article-content-markup h6 {
35
- margin-bottom: 1.25rem;
35
+ margin-bottom: 1.25em;
36
36
  font-weight: 600;
37
37
  }
38
38
 
39
39
  .dokno-article-content-markup h1 {
40
- font-size: 2rem;
40
+ font-size: 1.625em;
41
41
  }
42
42
 
43
43
  .dokno-article-content-markup h2 {
44
- font-size: 1.625rem;
44
+ font-size: 1.25em;
45
45
  }
46
46
 
47
- .dokno-article-content-markup h3 {
48
- font-size: 1.25rem;
49
- }
50
-
51
- .dokno-article-content-markup h4 {
52
- font-size: 1rem;
47
+ .dokno-article-content-markup h3,
48
+ .dokno-article-content-markup h4,
49
+ .dokno-article-content-markup h5,
50
+ .dokno-article-content-markup h6 {
51
+ font-size: 1em;
53
52
  }
54
53
 
55
54
  .dokno-article-content-markup blockquote {
56
55
  border-left: 4px solid #edf2f7;
57
- padding-left: 1.25rem;
56
+ padding-left: 1.25em;
58
57
  }
59
58
 
60
59
  .dokno-article-content-markup ul,
@@ -76,10 +75,10 @@
76
75
 
77
76
  .dokno-article-content-markup table th,
78
77
  .dokno-article-content-markup table td {
79
- padding-left: 1rem;
80
- padding-right: 1rem;
81
- padding-top: 0.5rem;
82
- padding-bottom: 0.5rem;
78
+ padding-left: 1em;
79
+ padding-right: 1em;
80
+ padding-top: 0.5em;
81
+ padding-bottom: 0.5em;
83
82
  font-weight: normal;
84
83
  }
85
84
 
@@ -13,13 +13,6 @@
13
13
 
14
14
  <!-- Slide-out panel markup -->
15
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
16
  <div id="dokno-panel-title"></div>
24
17
  <div id="dokno-panel-summary"></div>
25
18
  <div id="dokno-panel-markdown" class="dokno-article-content-markup"></div>
@@ -29,10 +22,12 @@
29
22
  <!-- Slide-out panel behavior -->
30
23
  <script>
31
24
  function doknoOpenPanel(slug) {
32
- if (slug == dokno__slug) {
33
- doknoClosePanel();
34
- return true;
35
- }
25
+ if (slug == dokno__slug) { return true; }
26
+
27
+ // Set flag to avoid flicker when closing the panel before opening the panel
28
+ dokno__link_just_clicked = true;
29
+ setTimeout(function() { dokno__link_just_clicked = false; }, 200);
30
+
36
31
  dokno__slug = slug;
37
32
 
38
33
  // Can't use fetch API; IE
@@ -62,11 +57,9 @@
62
57
  dokno__panel.classList.add('open');
63
58
  document.body.classList.add('dokno-no-scroll');
64
59
 
65
- // Reveal fixed close icon
66
- setTimeout(function() { dokno__close.classList.remove('dokno-hidden'); }, 200);
67
-
68
60
  // Close on escape
69
61
  document.addEventListener('keydown', dokno__keydown_listener, false);
62
+ document.addEventListener('click', dokno__click_listener, false);
70
63
  }
71
64
  };
72
65
 
@@ -74,10 +67,13 @@
74
67
  }
75
68
 
76
69
  function doknoClosePanel() {
77
- dokno__close.classList.add('dokno-hidden');
70
+ // Just clicked a link to open a panel, so don't close to avoid flicker
71
+ if (dokno__link_just_clicked) { return true; }
72
+
78
73
  dokno__panel.classList.remove('open');
79
74
  document.body.classList.remove('dokno-no-scroll');
80
75
  document.removeEventListener('keydown', dokno__keydown_listener, false);
76
+ document.removeEventListener('click', dokno__click_listener, false);
81
77
 
82
78
  dokno__slug = null;
83
79
  }
@@ -96,7 +92,6 @@
96
92
  }
97
93
 
98
94
  const dokno__panel = document.getElementById('dokno-panel-container');
99
- const dokno__close = document.querySelector('div#dokno-panel-close');
100
95
  const dokno__panel_title = document.getElementById('dokno-panel-title');
101
96
  const dokno__panel_summary = document.getElementById('dokno-panel-summary');
102
97
  const dokno__panel_footer = document.getElementById('dokno-panel-footer');
@@ -106,7 +101,10 @@
106
101
  if (e.key === 'Escape') { doknoClosePanel(); }
107
102
  }
108
103
 
109
- dokno__close.onclick = function() { doknoClosePanel(); }
104
+ const dokno__click_listener = function(e) {
105
+ var isClickInside = dokno__panel.contains(e.target);
106
+ if (!isClickInside) { doknoClosePanel(); }
107
+ }
110
108
 
111
- var dokno__id, dokno__slug;
109
+ var dokno__id, dokno__slug, dokno__link_just_clicked;
112
110
  </script>
@@ -9,87 +9,77 @@
9
9
 
10
10
  div#dokno-panel-container {
11
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);
12
+ font-size: 1rem !important;
13
+ line-height: 1.5rem !important;
14
+ right: -600px !important;
15
+ z-index: 9999 !important;
16
+ position: fixed !important;
17
+ top: 0 !important;
18
+ bottom: 0 !important;
19
+ width: 600px !important;
20
+ max-width: 100vw !important;
21
+ overflow: hidden !important;
22
+ overflow-y: auto !important;
23
+ background-color: #fff !important;
24
+ color: #2d3748 !important;
25
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) !important;
27
26
  }
28
27
 
29
28
  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;
29
+ right: 0 !important;
40
30
  }
41
31
 
42
32
  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;
33
+ padding: 2.5rem !important;
34
+ font-size: 2.25rem !important;
35
+ line-height: 2.75rem !important;
36
+ font-weight: 600 !important;
37
+ background-color: rgb(42,67,101) !important;
38
+ color: #ebf8ff !important;
49
39
  }
50
40
 
51
41
  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);
42
+ font-size: 1rem !important;
43
+ line-height: 1.5rem !important;
44
+ padding: 1rem !important;
45
+ margin-bottom: 1rem !important;
46
+ border-top-width: 4px !important;
47
+ border-radius: .25rem !important;
48
+ border-top-style: solid !important;
49
+ border-color: rgb(116,66,16) !important;
50
+ background-color: rgb(183,121,31) !important;
51
+ color: rgb(255,255,255) !important;
62
52
  }
63
53
 
64
54
  div#dokno-panel-title > span,
65
55
  div#dokno-panel-footer > svg {
66
- cursor: pointer;
56
+ cursor: pointer !important;
67
57
  }
68
58
 
69
59
  div#dokno-panel-summary {
70
- font-size: 1.5rem;
71
- line-height: 2.25rem;
72
- padding: 2.5rem;
73
- font-weight: 300;
60
+ font-size: 1.5rem !important;
61
+ line-height: 2.25rem !important;
62
+ padding: 2.5rem !important;
63
+ font-weight: 400 !important;
74
64
  }
75
65
 
76
66
  div#dokno-panel-markdown {
77
- padding: 2.5rem;
78
- background-color: #f7fafc;
67
+ padding: 2.5rem !important;
68
+ background-color: #f7fafc !important;
79
69
  }
80
70
 
81
71
  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;
72
+ text-align: center !important;
73
+ position: relative !important;
74
+ padding: 2.5rem !important;
75
+ color: #999 !important;
76
+ background-color: #edf2f7 !important;
77
+ font-weight: 400 !important;
78
+ font-size: 1rem !important;
79
+ line-height: 1.5rem !important;
90
80
  }
91
81
 
92
82
  div#dokno-panel-footer p {
93
- margin: .5rem 0;
83
+ margin: .5rem 0 !important;
94
84
  }
95
85
  </style>
@@ -3,8 +3,31 @@
3
3
  <section class="mb-10">
4
4
 
5
5
  <div class="text-2xl mb-5 text-gray-500"><%= article.persisted? ? 'Edit' : 'New' %> Article</div>
6
- <h1 class="text-4xl mb-5 leading-tight font-light"><%= article.title %></h1>
7
- <hr class="mb-5" />
6
+ <h1 class="text-4xl leading-tight font-light"><%= article.title %></h1>
7
+ <hr class="mt-8 mb-10" />
8
+
9
+ <% if article.up_for_review? %>
10
+ <div class="bg-gray-800 text-gray-100 rounded mb-10">
11
+ <h2 class="bg-gray-900 p-10 rounded-t text-2xl leading-tight font-light">Review for Accuracy / Relevance</h2>
12
+
13
+ <div class="flex p-10">
14
+ <div class="w-1/2 pr-5">
15
+ <div>
16
+ <span class="text-lg font-semibold"><label for="reset_review_date">Reset the next review date?</label></span>
17
+ <input onchange="setReviewForm();" type="checkbox" name="reset_review_date" id="reset_review_date" value="true" class="ml-2 text-lg" <%= "checked='checked'" if @reset_review_date %> />
18
+ </div>
19
+ <div class="text-gray-400">
20
+ <%= article.review_due_days_string %>.
21
+ Check the box above to mark this article as reviewed and reset the next review date to <span class="px-2 py-1 bg-gray-900 rounded text-white font-semibold"><%= "#{Date.today + Dokno.config.article_review_period}" %></span>
22
+ </div>
23
+ </div>
24
+ <div class="w-1/2">
25
+ <div class="text-lg font-semibold"><label for="review_notes">Reviewer Notes</label></div>
26
+ <textarea placeholder="Any review notes here will be added to the change history for this article" name="review_notes" id="review_notes" class="rounded text-xl shadow-inner bg-gray-100 p-3 mt-2 w-full text-gray-900" rows="2"><%= @review_notes %></textarea>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ <% end %>
8
31
 
9
32
  <div class="mb-5">
10
33
  <div class="text-lg font-semibold"><label for="slug">Slug</label></div>
@@ -27,7 +50,7 @@
27
50
  </div>
28
51
  <% end %>
29
52
 
30
- <hr class="mb-5" />
53
+ <hr class="my-10" />
31
54
 
32
55
  <div class="mb-5">
33
56
  <div class="text-lg font-semibold"><label for="title">Title</label></div>
@@ -47,14 +70,32 @@
47
70
  <a id="markdown_write_link" href="javascript:;" onclick="writeArticleToggle();" class="float-right hidden"><i data-feather="pen-tool" class="inline"></i> Write</a>
48
71
  <a id="markdown_preview_link" href="javascript:;" onclick="previewArticleToggle();" class="float-right"><i data-feather="eye" class="inline"></i> Preview</a>
49
72
  </div>
50
- <textarea placeholder="Full article content" name="markdown" id="markdown" class="rounded text-xl shadow-inner bg-gray-100 p-3 mt-2 w-full" rows="10"><%= article.persisted? ? article.markdown : (article.markdown.presence || @template) %></textarea>
73
+ <textarea placeholder="Full article content" name="markdown" id="markdown" class="rounded text-xl shadow-inner bg-gray-100 p-3 mt-2 w-full" rows="20"><%= article.persisted? ? article.markdown : (article.markdown.presence || @template) %></textarea>
51
74
  <div id="markdown_preview" class="dokno-article-content-markup hidden text-lg overflow-hidden overflow-y-auto rounded p-10 mt-2 bg-gray-100 shadow-inner"></div>
52
75
  <div class="text-gray-500 mt-2">Detailed documentation of the described topic. Basic HTML &amp; <a target="_blank" href="https://commonmark.org/help/" title="Markdown formatting examples">markdown</a> OK.</div>
53
76
  </div>
54
77
 
55
- <div class="mt-10">
78
+ <div class="mb-5">
79
+ <div>
80
+ <span class="text-lg font-semibold"><label for="starred">Starred article?</label></span>
81
+ <input type="hidden" name="starred" value="false" />
82
+ <input type="checkbox" name="starred" id="starred" value="true" class="ml-2 text-lg" <%= "checked='checked'" if article.starred %> />
83
+ </div>
84
+ <div class="text-gray-400">
85
+ Starred articles are always listed first when browsing.
86
+ </div>
87
+ </div>
88
+
89
+ <hr class="my-10" />
90
+
91
+ <div class="text-right">
92
+ <span class="text-lg mr-5"><a class="no-underline" href="<%= article.persisted? ? article_path(article.slug) : root_path %>">Cancel</a></span>
56
93
  <button type="submit" class="bg-gray-700 text-gray-300 hover:bg-gray-900 hover:text-white rounded py-2 px-3 font-bold"><i data-feather="check" class="inline h-5"></i> SAVE ARTICLE</button>
57
- <span class="text-lg ml-5"><a class="no-underline" href="<%= article.persisted? ? article_path(article.slug) : root_path %>">Cancel</a></span>
58
94
  </div>
59
95
 
60
96
  </section>
97
+
98
+ <script>
99
+ elem('input#slug').focus();
100
+ setReviewForm();
101
+ </script>