dokno 1.1.1 → 1.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -6
- data/app/assets/javascripts/dokno.js +48 -37
- data/app/assets/stylesheets/dokno/application.css +1 -1
- data/app/controllers/dokno/application_controller.rb +1 -1
- data/app/controllers/dokno/articles_controller.rb +19 -11
- data/app/controllers/dokno/categories_controller.rb +10 -7
- data/app/controllers/dokno/pagination_concern.rb +5 -5
- data/app/controllers/dokno/user_concern.rb +6 -4
- data/app/helpers/dokno/application_helper.rb +1 -1
- data/app/models/dokno/application_record.rb +3 -0
- data/app/models/dokno/article.rb +91 -42
- data/app/models/dokno/category.rb +20 -46
- data/app/views/dokno/_article_formatting.html.erb +12 -12
- data/app/views/dokno/articles/_article_form.html.erb +49 -10
- data/app/views/dokno/articles/show.html.erb +44 -41
- data/app/views/dokno/categories/_category_form.html.erb +17 -6
- data/app/views/dokno/categories/index.html.erb +54 -41
- data/app/views/layouts/dokno/application.html.erb +36 -33
- data/app/views/partials/_category_header.html.erb +30 -0
- data/app/views/partials/_form_errors.html.erb +0 -1
- data/app/views/partials/_logs.html.erb +7 -5
- data/app/views/partials/_pagination.html.erb +20 -17
- data/config/routes.rb +1 -1
- data/db/migrate/20201203190330_baseline.rb +4 -4
- data/db/migrate/20201211192306_add_review_due_at_to_articles.rb +6 -0
- data/db/migrate/20201213165700_add_starred_to_article.rb +5 -0
- data/lib/dokno/config/config.rb +53 -40
- data/lib/dokno/engine.rb +5 -5
- data/lib/dokno/version.rb +1 -1
- data/lib/generators/dokno/templates/config/initializers/dokno.rb +18 -5
- metadata +82 -11
@@ -1,26 +1,33 @@
|
|
1
|
+
<% if !current_page?(up_for_review_path) && (@category.blank? || @search_term.present?) %>
|
2
|
+
<div class="text-center m-auto mb-10 w-full max-w-screen-xl">
|
3
|
+
<% if @search_term.present? %>
|
4
|
+
<div class="text-gray-600 text-2xl uppercase">
|
5
|
+
<%= @total_records.positive? ? "#{@total_records} #{'article'.pluralize(@total_records)}" : 'No articles' %>
|
6
|
+
found containing the search term
|
7
|
+
<div class="text-4xl leading-tight"><span class="font-serif">“</span> <%= @search_term %> <span class="font-serif">”</span> </div>
|
8
|
+
</div>
|
9
|
+
<% else %>
|
10
|
+
<div class="text-gray-600 text-2xl">
|
11
|
+
Browse or search
|
12
|
+
<% if (article_count = Dokno::Article.count) > 1 %>
|
13
|
+
<%= number_with_delimiter(article_count, delimiter: ',') %> articles in
|
14
|
+
<% end %>
|
15
|
+
the
|
16
|
+
</div>
|
17
|
+
<div class="text-gray-800 text-4xl leading-tight uppercase"><%= Dokno.config.app_name %> knowledgebase</div>
|
18
|
+
<% end %>
|
19
|
+
</div>
|
20
|
+
<% end %>
|
21
|
+
|
1
22
|
<% if @category&.parent.present? %>
|
2
23
|
<div class="text-gray-500 mb-5">
|
3
|
-
<div
|
24
|
+
<div><span class="text-gray-500 mr-1">Under</span> <%= @category.breadcrumb(search_term: @search_term, order: @order, hide_self: true) %></div>
|
4
25
|
</div>
|
5
26
|
<% end %>
|
6
27
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
<select aria-label="Category" name="category" id="category" size="1" class="rounded text-xl shadow-inner bg-gray-100 p-2 w-full max-w-full" onchange="changeCategory(this.value, elem('#search_term').value, '<%= @order %>');">
|
12
|
-
<option value="">Select a category</option>
|
13
|
-
<% cache Dokno::Category do %>
|
14
|
-
<%= Dokno::Category.select_option_markup.html_safe %>
|
15
|
-
<% end %>
|
16
|
-
</select>
|
17
|
-
|
18
|
-
</div>
|
19
|
-
<% end %>
|
20
|
-
<div class="w-<%= Dokno::Category.exists? ? '1/2 pl-5' : 'full' %>">
|
21
|
-
<input onsearch="search(this.value, '<%= @order %>');" placeholder="Search article content, titles, and slugs" type="search" name="search_term" id="search_term" value="<%= @search_term %>" class="rounded text-xl shadow-inner bg-gray-100 p-2 w-full" />
|
22
|
-
</div>
|
23
|
-
</div>
|
28
|
+
<% if !current_page?(up_for_review_path) && (Dokno::Category.exists? || Dokno::Article.exists?) %>
|
29
|
+
<%= render 'partials/category_header' %>
|
30
|
+
<% end %>
|
24
31
|
|
25
32
|
<% if @articles.blank? %>
|
26
33
|
|
@@ -46,10 +53,10 @@
|
|
46
53
|
</div>
|
47
54
|
<div class="w-1/3 text-right">
|
48
55
|
<i data-feather="corner-right-down" class="h-5 inline-block" title="Sort order"></i>
|
49
|
-
<a class="ml-3 pb-1 <%= 'border-b-2 border-blue-900' if @order == 'updated' %>" href="?search_term=<%= CGI.escape @search_term.to_s %>&order=updated">Updated</a>
|
50
|
-
<a class="ml-3 pb-1 <%= 'border-b-2 border-blue-900' if @order == 'newest' %>" href="?search_term=<%= CGI.escape @search_term.to_s %>&order=newest">Newest</a>
|
51
|
-
<a class="ml-3 pb-1 <%= 'border-b-2 border-blue-900' if @order == 'views' %>" href="?search_term=<%= CGI.escape @search_term.to_s %>&order=views">Views</a>
|
52
|
-
<a class="ml-3 pb-1 <%= 'border-b-2 border-blue-900' if @order == 'alpha' %>" href="?search_term=<%= CGI.escape @search_term.to_s %>&order=alpha">Title</a>
|
56
|
+
<a id="dokno-order-link-updated" class="ml-3 pb-1 <%= 'border-b-2 border-blue-900' if @order == 'updated' %>" href="?search_term=<%= CGI.escape @search_term.to_s %>&order=updated">Updated</a>
|
57
|
+
<a id="dokno-order-link-newest" class="ml-3 pb-1 <%= 'border-b-2 border-blue-900' if @order == 'newest' %>" href="?search_term=<%= CGI.escape @search_term.to_s %>&order=newest">Newest</a>
|
58
|
+
<a id="dokno-order-link-views" class="ml-3 pb-1 <%= 'border-b-2 border-blue-900' if @order == 'views' %>" href="?search_term=<%= CGI.escape @search_term.to_s %>&order=views">Views</a>
|
59
|
+
<a id="dokno-order-link-alpha" class="ml-3 pb-1 <%= 'border-b-2 border-blue-900' if @order == 'alpha' %>" href="?search_term=<%= CGI.escape @search_term.to_s %>&order=alpha">Title</a>
|
53
60
|
</div>
|
54
61
|
</div>
|
55
62
|
|
@@ -58,32 +65,43 @@
|
|
58
65
|
<section class="border-t border-gray-300 py-10 text-xl flex">
|
59
66
|
<div class="w-1/3 pr-10">
|
60
67
|
<div class="flex">
|
61
|
-
<div class="no-print w-10 text-gray-300"><i data-feather="chevron-right" class="inline-block"></i></div>
|
68
|
+
<div title="<%= 'Starred article' if article.starred %>" class="no-print w-10 text-gray-300"><i data-feather="<%= article.starred ? 'star' : 'chevron-right' %>" class="inline-block"></i></div>
|
62
69
|
<div class="w-full">
|
63
|
-
<a class="" href="<%= article_path article.slug %>?search_term=<%= @search_term %>" title="View article"><%= article.title %></a>
|
70
|
+
<a class="dokno-article-title <% unless article.active %>text-gray-500 italic<% end %>" href="<%= article_path article.slug %>?search_term=<%= @search_term %>&cat_code=<%= @category&.code %>&order=<%= @order %>" title="View article"><%= article.title %></a>
|
64
71
|
</div>
|
65
72
|
</div>
|
66
73
|
</div>
|
67
74
|
<div class="dokno-article-summary w-2/3 <% unless article.active %>text-gray-500 italic<% end %>">
|
68
|
-
|
69
|
-
<div class="bg-yellow-700 p-4 mb-5 rounded text-lg border-t-4 border-yellow-900 text-white font-base not-italic">
|
70
|
-
<i data-feather="alert-circle" class="inline-block"></i> This article is no longer active
|
71
|
-
</div>
|
72
|
-
<% end %>
|
75
|
+
<div class="dokno-article-content-highlight mb-2"><%= article.summary.presence || 'No summary provided' %></div>
|
73
76
|
|
74
|
-
<
|
77
|
+
<div class="text-base text-gray-500">
|
78
|
+
<%= article.category_name_list(context_category_id: @category&.id, order: @order, search_term: @search_term) %>
|
79
|
+
</div>
|
75
80
|
|
76
81
|
<% unless @order == 'alpha' %>
|
77
|
-
<div class="text-base
|
82
|
+
<div class="text-base">
|
78
83
|
<% if @order == 'views' %>
|
79
|
-
<div class="text-gray-500">
|
84
|
+
<div class="text-gray-500">This article was viewed <%= number_with_delimiter(article.views, delimiter: ',') %> <%= 'time'.pluralize(article.views) %></div>
|
80
85
|
<% elsif @order == 'updated' %>
|
81
|
-
<div class="text-gray-500">
|
86
|
+
<div class="text-gray-500">This article was last updated <%= time_ago_in_words article.updated_at %> ago</div>
|
82
87
|
<% elsif @order == 'newest' %>
|
83
|
-
<div class="text-gray-500">
|
88
|
+
<div class="text-gray-500">This article was added <%= time_ago_in_words article.created_at %> ago</div>
|
84
89
|
<% end %>
|
85
90
|
</div>
|
86
91
|
<% end %>
|
92
|
+
|
93
|
+
<% if !article.active %>
|
94
|
+
<div class="bg-yellow-700 p-4 mt-5 rounded text-lg border-t-4 border-yellow-900 text-white font-base not-italic">
|
95
|
+
<i data-feather="info" class="inline-block mr-1"></i> This article is no longer active
|
96
|
+
</div>
|
97
|
+
<% end %>
|
98
|
+
|
99
|
+
<% if article.up_for_review? %>
|
100
|
+
<div class="bg-<%= article.review_due_days.negative? ? 'red' : 'gray' %>-800 p-4 mt-5 rounded text-lg border-t-4 border-<%= article.review_due_days.negative? ? 'red' : 'gray' %>-900 text-white font-base not-italic">
|
101
|
+
<i data-feather="bell" class="inline-block mr-1"></i> <%= article.review_due_days_string %>
|
102
|
+
</div>
|
103
|
+
<% end %>
|
104
|
+
|
87
105
|
</div>
|
88
106
|
</section>
|
89
107
|
<% end %>
|
@@ -97,11 +115,6 @@
|
|
97
115
|
</div>
|
98
116
|
<% end %>
|
99
117
|
|
100
|
-
|
101
|
-
|
102
|
-
selectOption('category', '<%= j @category&.code %>');
|
103
|
-
</script>
|
104
|
-
|
105
|
-
<% if @search_term.present? %>
|
106
|
-
<script> highlightTerm(['<%= j @search_term.strip %>'], 'dokno-article-content-highlight'); </script>
|
118
|
+
<% if @search_term.present? && @articles.blank? %>
|
119
|
+
<script> handleSearchHotKey(); </script>
|
107
120
|
<% end %>
|
@@ -2,6 +2,8 @@
|
|
2
2
|
<html>
|
3
3
|
<head>
|
4
4
|
<title><%= Dokno.config.app_name %> KNOWLEDGEBASE</title>
|
5
|
+
|
6
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
5
7
|
<%= csrf_meta_tags %>
|
6
8
|
<%= csp_meta_tag %>
|
7
9
|
|
@@ -12,7 +14,8 @@
|
|
12
14
|
var dokno__base_path = '<%= root_path %>';
|
13
15
|
</script>
|
14
16
|
</head>
|
15
|
-
<body class="bg-white font-sans font-light subpixel-antialiased">
|
17
|
+
<body class="bg-white font-sans font-light subpixel-antialiased text-lg">
|
18
|
+
|
16
19
|
|
17
20
|
<nav id="dokno-nav-container" class="bg-blue-900 text-white py-10 px-16 text-lg">
|
18
21
|
<div class="flex items-center m-auto w-full max-w-screen-xl">
|
@@ -24,12 +27,12 @@
|
|
24
27
|
<div class="w-2/3 text-right">
|
25
28
|
<% if can_edit? %>
|
26
29
|
<% if action_name != 'new' %>
|
27
|
-
<button title="Add a new article" class="bg-gray-700 text-gray-300 hover:text-white hover:bg-gray-900 rounded ml-3 py-2 px-3 font-bold text-base" onclick="location.href='<%= new_article_path %>/?category_code=<%= @category&.code %>';"><i data-feather="plus
|
28
|
-
<button title="Add a new category" class="bg-gray-700 text-gray-300 hover:text-white hover:bg-gray-900 rounded ml-3 py-2 px-3 font-bold text-base" onclick="location.href='<%= new_category_path %>/?parent_category_code=<%= @category&.code %>';"><i data-feather="plus
|
30
|
+
<button title="Add a new article" class="bg-gray-700 text-gray-300 hover:text-white hover:bg-gray-900 rounded ml-3 py-2 px-3 font-bold text-base" onclick="location.href='<%= new_article_path %>/?category_code=<%= @category&.code %>';"><i data-feather="plus" class="inline h-5"></i> ARTICLE</button>
|
31
|
+
<button title="Add a new category" class="bg-gray-700 text-gray-300 hover:text-white hover:bg-gray-900 rounded ml-3 py-2 px-3 font-bold text-base" onclick="location.href='<%= new_category_path %>/?parent_category_code=<%= @category&.code %>';"><i data-feather="plus" class="inline h-5"></i> CATEGORY</button>
|
29
32
|
<% end %>
|
30
33
|
|
31
34
|
<% if @category&.persisted? && action_name != 'edit' %>
|
32
|
-
<button title="Edit this category" class="bg-gray-700 text-gray-100 hover:text-white hover:bg-gray-900 rounded ml-3 py-2 px-3 font-bold text-base" onclick="location.href='<%= edit_category_path(@category) %>';"><i data-feather="edit" class="inline h-5"></i> CATEGORY</button>
|
35
|
+
<button title="Edit this category" class="bg-gray-700 text-gray-100 hover:text-white hover:bg-gray-900 rounded ml-3 py-2 px-3 font-bold text-base" onclick="location.href='<%= edit_category_path(@category) %>';"><i data-feather="edit-2" class="inline h-5"></i> CATEGORY</button>
|
33
36
|
<% end %>
|
34
37
|
<% end %>
|
35
38
|
|
@@ -40,40 +43,22 @@
|
|
40
43
|
</div>
|
41
44
|
</nav>
|
42
45
|
|
43
|
-
<main class="py-10 px-16">
|
44
|
-
<% if @article.blank? && (@category.blank? || @search_term.present?) %>
|
45
|
-
<div class="text-center m-auto mb-10 w-full max-w-screen-xl">
|
46
|
-
<% if @search_term.present? %>
|
47
|
-
<div class="text-gray-600 text-2xl uppercase">
|
48
|
-
<%= @total_records.positive? ? "#{@total_records} #{'article'.pluralize(@total_records)}" : 'No articles' %>
|
49
|
-
found containing the search term
|
50
|
-
<div class="text-4xl leading-tight"><span class="font-serif">“</span> <%= @search_term %> <span class="font-serif">”</span> </div>
|
51
|
-
</div>
|
52
|
-
<% else %>
|
53
|
-
<div class="text-gray-600 text-2xl">
|
54
|
-
Browse or search
|
55
|
-
<% if (article_count = Dokno::Article.count) > 1 %>
|
56
|
-
<%= number_with_delimiter(article_count, delimiter: ',') %> articles in
|
57
|
-
<% end %>
|
58
|
-
the
|
59
|
-
</div>
|
60
|
-
<div class="text-gray-800 text-4xl leading-tight uppercase"><%= Dokno.config.app_name %> knowledgebase</div>
|
61
|
-
<% end %>
|
62
|
-
</div>
|
63
|
-
<% end %>
|
64
46
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
<% end %>
|
47
|
+
<% flash.each do |type, msg| %>
|
48
|
+
<div class="bg-<%= type %>-800 text-lg text-white font-base py-10 px-16 text-center">
|
49
|
+
<i data-feather="<%= (type == 'green' ? 'smile' : (type == 'yellow' ? 'info' : (type == 'gray' ? 'bell' : 'frown'))) %>" class="inline mr-1"></i>
|
50
|
+
<%= sanitize(msg, tags: %w[a], attributes: %w[href class]) %>
|
51
|
+
</div>
|
52
|
+
<% end %>
|
72
53
|
|
54
|
+
|
55
|
+
<main class="py-10 px-16">
|
56
|
+
<div id="dokno-content-container" class="w-full max-w-screen-xl m-auto print-this">
|
73
57
|
<%= yield %>
|
74
58
|
</div>
|
75
59
|
</main>
|
76
60
|
|
61
|
+
|
77
62
|
<footer id="dokno-footer-container">
|
78
63
|
<% if @article.present? && action_name == 'show' %>
|
79
64
|
<div id="dokno-article-log-container" data-category-id="<%= @category&.id %>" data-article-id="<%= @article.id %>">
|
@@ -81,10 +66,24 @@
|
|
81
66
|
</div>
|
82
67
|
<% end %>
|
83
68
|
|
69
|
+
<% if @show_up_for_review && (up_for_review_count = Dokno::Article.up_for_review.count).positive? %>
|
70
|
+
<div id="dokno-articles-up-for-review-container">
|
71
|
+
<div class="py-10 px-16 bg-gray-900">
|
72
|
+
<div class="w-full max-w-screen-xl m-auto">
|
73
|
+
<div class="text-xl text-white cursor-pointer" onclick="location.href='<%= up_for_review_path %>';">
|
74
|
+
<i data-feather="bell" class="inline mr-1"></i>
|
75
|
+
There <%= "#{up_for_review_count == 1 ? 'is' : 'are'} #{up_for_review_count}" %> <%= 'article'.pluralize(up_for_review_count) %> up for accuracy / relevance review
|
76
|
+
</div>
|
77
|
+
</div>
|
78
|
+
</div>
|
79
|
+
</div>
|
80
|
+
<% end %>
|
81
|
+
|
84
82
|
<div class="py-10 px-16 text-gray-400 bg-blue-900">
|
85
83
|
<div class="w-full max-w-screen-xl m-auto flex">
|
86
84
|
<div class="w-1/2">
|
87
|
-
<
|
85
|
+
<i data-feather="github" class="inline mr-1"></i>
|
86
|
+
<a target="_blank" href="https://github.com/cpayne624/dokno" title="Dokno on GitHub">dokno</a>
|
88
87
|
</div>
|
89
88
|
<div class="w-1/2 text-right">
|
90
89
|
<% if user.present? %>
|
@@ -101,5 +100,9 @@
|
|
101
100
|
</footer>
|
102
101
|
|
103
102
|
<%= javascript_include_tag 'init' %>
|
103
|
+
|
104
|
+
<% if @search_term.present? %>
|
105
|
+
<script> highlightTerm(['<%= j @search_term.strip %>'], 'dokno-article-content-highlight'); </script>
|
106
|
+
<% end %>
|
104
107
|
</body>
|
105
108
|
</html>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<div class="no-print flex items-center mb-10">
|
2
|
+
<% if Dokno::Category.exists? %>
|
3
|
+
<div class="w-1/2 pr-5">
|
4
|
+
<select aria-label="Category" name="category" id="category" size="1" class="rounded text-xl shadow-inner bg-gray-100 p-2 w-full max-w-full" onchange="applyCategoryCriteria(this.value, elem('#search_term').value, '<%= @order %>');">
|
5
|
+
<option value="">Uncategorized</option>
|
6
|
+
|
7
|
+
<optgroup label="Categories">
|
8
|
+
<% cache Dokno::Category do %>
|
9
|
+
<%= Dokno::Category.select_option_markup.html_safe %>
|
10
|
+
<% end %>
|
11
|
+
</optgroup>
|
12
|
+
</select>
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<script>
|
16
|
+
// Client-side select of cached select list
|
17
|
+
selectOption('category', '<%= j @category&.code %>');
|
18
|
+
</script>
|
19
|
+
<% end %>
|
20
|
+
|
21
|
+
<% if Dokno::Article.exists? %>
|
22
|
+
<div class="relative w-<%= Dokno::Category.exists? ? '1/2 pl-5' : 'full' %>">
|
23
|
+
<i data-feather="search" class="absolute ml-4 mt-3 inline-block text-gray-300" title="Search"></i>
|
24
|
+
<input title="Press / to search" onsearch="applyCategoryCriteria('<%= @category&.code %>', this.value, '<%= @order %>');" onfocus="disableSearchHotkey();" onblur="enableSearchHotkey();" placeholder="Search<%= @category.present? ? ' under this category' : ', hotkey /' %>" type="search" name="search_term" id="search_term" value="<%= @search_term %>" class="pl-12 pr-8 py-2 rounded text-xl shadow-inner bg-gray-100 w-full" />
|
25
|
+
<% if @category.present? %><div title="Press / to search" class="absolute -ml-6 mt-2 inline-block text-gray-300 font-semibold">/</div><% end %>
|
26
|
+
</div>
|
27
|
+
<% end %>
|
28
|
+
</div>
|
29
|
+
|
30
|
+
<script> enableSearchHotkey(); </script>
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<div class="py-10 px-16 text-gray-200 bg-gray-900">
|
3
3
|
<div class="w-full max-w-screen-xl m-auto">
|
4
4
|
<div class="text-xl text-gray-600 cursor-pointer" onclick="toggleVisibility('change-log');">
|
5
|
-
Change
|
5
|
+
Change history for this article
|
6
6
|
|
7
7
|
<div class="inline toggle-visibility-indicator-container change-log">
|
8
8
|
<i data-feather="chevron-left" class="inline toggle-visibility-indicator change-log"></i>
|
@@ -20,7 +20,7 @@
|
|
20
20
|
<% end %>
|
21
21
|
|
22
22
|
<div class="text-gray-500 bg-gray-700 p-5 pr-10 rounded <%= 'cursor-pointer' if log.diff_left != log.diff_right %> flex items-center" onclick="toggleVisibility('article-diff-<%= log.id %>');" title="Show / Hide Diff">
|
23
|
-
<div class="w
|
23
|
+
<div class="w-<%= log.diff_left != log.diff_right ? '11/12' : 'full' %>">
|
24
24
|
<%= time_ago_in_words log.created_at %> ago
|
25
25
|
<% if log.username.present? %>
|
26
26
|
by <%= log.username %>
|
@@ -31,9 +31,11 @@
|
|
31
31
|
<% end %>
|
32
32
|
</div>
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
<% if log.diff_left != log.diff_right %>
|
35
|
+
<div class="w-1/12 text-right toggle-visibility-indicator-container article-diff-<%= log.id %>">
|
36
|
+
<i data-feather="chevron-left" class="inline toggle-visibility-indicator article-diff-<%= log.id %>"></i>
|
37
|
+
</div>
|
38
|
+
<% end %>
|
37
39
|
</div>
|
38
40
|
|
39
41
|
<% if log.diff_left != log.diff_right %>
|
@@ -1,28 +1,31 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
<% if @total_pages > 1 %>
|
2
|
+
<span class="mr-5">
|
3
|
+
<% if @page > 1 %>
|
4
|
+
<span class="mr-1 inline-block"><a href="?search_term=<%= CGI.escape @search_term.to_s %>&order=<%= @order %>&page=<%= (@page - 1) %>"><i data-feather="arrow-left" class="h-5 inline-block" title="Previous page"></i></a></span>
|
5
|
+
<% end %>
|
5
6
|
|
6
|
-
|
7
|
+
<span class="mr-1 inline-block">Page</span>
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
<%= form_with(url: article_index_path(@category&.code), method: :get, class: 'inline') do %>
|
10
|
+
<input type="hidden" name="search_term" value="<%= @search_term %>">
|
11
|
+
<input type="hidden" name="order" value="<%= @order %>">
|
12
|
+
<input aria-label="Page" type="text" name="page" value="<%= @page %>" onclick="this.select();" class="w-10 text-center bg-gray-200 rounded" />
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
<span class="mx-1 inline-block">of</span>
|
15
|
+
<span class="text-center inline-block"><%= @total_pages %></span>
|
16
|
+
<% end %>
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
</span>
|
18
|
+
<% if @page < @total_pages %>
|
19
|
+
<span class="ml-1 inline-block"><a href="?search_term=<%= CGI.escape @search_term.to_s %>&order=<%= @order %>&page=<%= (@page + 1) %>"><i data-feather="arrow-right" class="h-5 inline-block" title="Next page"></i></a></span>
|
20
|
+
<% end %>
|
21
|
+
</span>
|
22
|
+
<% end %>
|
21
23
|
|
22
24
|
<span class="text-gray-400">
|
23
25
|
<%= @total_records %>
|
24
|
-
<%= 'uncategorized' if @category.blank? && @search_term.blank? %>
|
26
|
+
<%= 'uncategorized' if !current_page?(up_for_review_path) && @category.blank? && @search_term.blank? %>
|
25
27
|
<%= 'article'.pluralize(@total_records) %>
|
28
|
+
<%= 'up for review' if current_page?(up_for_review_path) %>
|
26
29
|
|
27
30
|
<% if @search_term.present? %>
|
28
31
|
containing <span class="font-serif">“</span><%= @search_term %><span class="font-serif">”</span>
|
data/config/routes.rb
CHANGED
@@ -3,8 +3,8 @@ Dokno::Engine.routes.draw do
|
|
3
3
|
resources :articles
|
4
4
|
|
5
5
|
get '/(:cat_code)', to: 'categories#index', as: :article_index
|
6
|
+
get '/up_for_review', to: 'categories#index', as: :up_for_review
|
6
7
|
get 'article_panel/(:slug)', to: 'articles#panel', as: :panel
|
7
|
-
post 'article_log', to: 'articles#article_log', as: :article_log
|
8
8
|
post 'article_preview', to: 'articles#preview', as: :preview
|
9
9
|
post 'article_status', to: 'articles#status', as: :status
|
10
10
|
root 'categories#index'
|
@@ -13,12 +13,12 @@ class Baseline < ActiveRecord::Migration[6.0]
|
|
13
13
|
t.string "slug"
|
14
14
|
t.string "title"
|
15
15
|
t.text "markdown"
|
16
|
-
t.datetime "created_at", precision: 6, null: false
|
17
|
-
t.datetime "updated_at", precision: 6, null: false
|
18
16
|
t.text "summary"
|
19
17
|
t.boolean "active", default: true
|
20
18
|
t.bigint "views", default: 0
|
21
19
|
t.datetime "last_viewed_at"
|
20
|
+
t.datetime "created_at", precision: 6, null: false
|
21
|
+
t.datetime "updated_at", precision: 6, null: false
|
22
22
|
t.index ["slug"], name: "index_dokno_articles_on_slug", unique: true
|
23
23
|
end
|
24
24
|
|
@@ -32,10 +32,10 @@ class Baseline < ActiveRecord::Migration[6.0]
|
|
32
32
|
|
33
33
|
create_table "dokno_categories", force: :cascade do |t|
|
34
34
|
t.string "name"
|
35
|
-
t.datetime "created_at", precision: 6, null: false
|
36
|
-
t.datetime "updated_at", precision: 6, null: false
|
37
35
|
t.bigint "category_id"
|
38
36
|
t.string "code", null: false
|
37
|
+
t.datetime "created_at", precision: 6, null: false
|
38
|
+
t.datetime "updated_at", precision: 6, null: false
|
39
39
|
t.index ["category_id"], name: "index_dokno_categories_on_category_id"
|
40
40
|
t.index ["code"], name: "index_dokno_categories_on_code", unique: true
|
41
41
|
t.index ["name"], name: "index_dokno_categories_on_name", unique: true
|
data/lib/dokno/config/config.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
module Dokno
|
2
|
+
module Error
|
3
|
+
class Config < StandardError; end
|
4
|
+
end
|
5
|
+
|
2
6
|
def self.configure
|
3
7
|
yield config
|
4
8
|
config.validate
|
@@ -8,62 +12,71 @@ module Dokno
|
|
8
12
|
@config ||= Config.new
|
9
13
|
end
|
10
14
|
|
11
|
-
def self.config=(val)
|
12
|
-
@config = val
|
13
|
-
end
|
14
|
-
|
15
15
|
class Config
|
16
|
-
#
|
17
|
-
|
16
|
+
# Dokno configuration options
|
17
|
+
#
|
18
|
+
# app_name (String)
|
19
|
+
# Host app name for display within the mounted dashboard
|
20
|
+
# tag_whitelist (Enumerable)
|
21
|
+
# Determines which HTML tags are allowed in Article markdown
|
22
|
+
# attr_whitelist (Enumerable)
|
23
|
+
# Determines which HTML attributes are allowed in Article markdown
|
24
|
+
# app_user_object (String)
|
25
|
+
# Host app's user object
|
26
|
+
# app_user_auth_method (Symbol)
|
27
|
+
# Host app's user object method to be used for edit authorization.
|
28
|
+
# Should return boolean
|
29
|
+
# app_user_name_method (Symbol)
|
30
|
+
# Host app's user object method that returns the authenticated user's name or other
|
31
|
+
# identifier that will be included in change log events.
|
32
|
+
# Should return a string
|
33
|
+
# article_review_period (ActiveSupport::Duration)
|
34
|
+
# The amount of time before articles should be reviewed for accuracy/relevance
|
35
|
+
# article_review_prompt_days (Integer)
|
36
|
+
# The number of days prior to an article being up for review that users should be prompted
|
18
37
|
|
19
|
-
|
38
|
+
attr_accessor :app_name
|
20
39
|
attr_accessor :tag_whitelist
|
21
|
-
|
22
|
-
# (Enumerable) Determines which HTML attributes are allowed in Article markdown
|
23
40
|
attr_accessor :attr_whitelist
|
24
|
-
|
25
|
-
# (String) Host application's user object
|
26
41
|
attr_accessor :app_user_object
|
27
|
-
|
28
|
-
# (Symbol) Host application's user object method that should be used to authorize users to edit Dokno data
|
29
|
-
# Should return boolean.
|
30
42
|
attr_accessor :app_user_auth_method
|
31
|
-
|
32
|
-
# (Symbol) Host application's user object method that should return the authenticated user's name or other
|
33
|
-
# identifier that will be included in change log events. Should return a string.
|
34
43
|
attr_accessor :app_user_name_method
|
44
|
+
attr_accessor :article_review_period
|
45
|
+
attr_accessor :article_review_prompt_days
|
35
46
|
|
36
47
|
# Defaults
|
37
|
-
TAG_WHITELIST
|
38
|
-
ATTR_WHITELIST
|
39
|
-
APP_USER_OBJECT
|
40
|
-
APP_USER_AUTH_METHOD
|
41
|
-
APP_USER_NAME_METHOD
|
48
|
+
TAG_WHITELIST = %w[code img h1 h2 h3 h4 h5 h6 a em u i b strong ol ul li table thead tbody tfoot tr th td blockquote hr br p]
|
49
|
+
ATTR_WHITELIST = %w[src alt title href target]
|
50
|
+
APP_USER_OBJECT = 'current_user'
|
51
|
+
APP_USER_AUTH_METHOD = :admin?
|
52
|
+
APP_USER_NAME_METHOD = :name
|
53
|
+
ARTICLE_REVIEW_PERIOD = 1.year
|
54
|
+
ARTICLE_REVIEW_PROMPT_DAYS = 30
|
42
55
|
|
43
56
|
def initialize
|
44
|
-
self.app_name
|
45
|
-
self.tag_whitelist
|
46
|
-
self.attr_whitelist
|
47
|
-
self.app_user_object
|
48
|
-
self.app_user_auth_method
|
49
|
-
self.app_user_name_method
|
57
|
+
self.app_name = Rails.application.class.module_parent.name.underscore.humanize.upcase
|
58
|
+
self.tag_whitelist = TAG_WHITELIST
|
59
|
+
self.attr_whitelist = ATTR_WHITELIST
|
60
|
+
self.app_user_object = APP_USER_OBJECT
|
61
|
+
self.app_user_auth_method = APP_USER_AUTH_METHOD
|
62
|
+
self.app_user_name_method = APP_USER_NAME_METHOD
|
63
|
+
self.article_review_period = ARTICLE_REVIEW_PERIOD
|
64
|
+
self.article_review_prompt_days = ARTICLE_REVIEW_PROMPT_DAYS
|
50
65
|
end
|
51
66
|
|
52
67
|
def validate
|
53
|
-
|
54
|
-
|
68
|
+
validate_config_option(option: 'tag_whitelist', expected_class: Enumerable, example: '%w[a p strong]')
|
69
|
+
validate_config_option(option: 'attr_whitelist', expected_class: Enumerable, example: '%w[class href]')
|
70
|
+
validate_config_option(option: 'app_user_object', expected_class: String, example: 'current_user')
|
71
|
+
validate_config_option(option: 'app_user_auth_method', expected_class: Symbol, example: ':admin?')
|
72
|
+
validate_config_option(option: 'app_user_name_method', expected_class: Symbol, example: ':name')
|
73
|
+
validate_config_option(option: 'article_review_period', expected_class: ActiveSupport::Duration, example: '1.year')
|
74
|
+
validate_config_option(option: 'article_review_prompt_days', expected_class: Integer, example: '30')
|
55
75
|
end
|
56
76
|
|
57
|
-
def
|
58
|
-
return unless !
|
59
|
-
|
60
|
-
raise "#{config_error_prefix} tag_whitelist must be Enumerable"
|
61
|
-
end
|
62
|
-
|
63
|
-
def validate_attr_whitelist
|
64
|
-
return unless !attr_whitelist.is_a?(Enumerable)
|
65
|
-
|
66
|
-
raise "#{config_error_prefix} attr_whitelist must be Enumerable"
|
77
|
+
def validate_config_option(option:, expected_class:, example:)
|
78
|
+
return unless !send(option.to_sym).is_a? expected_class
|
79
|
+
raise Error::Config, "#{config_error_prefix} #{option} must be #{expected_class}, e.g. #{example}"
|
67
80
|
end
|
68
81
|
|
69
82
|
def config_error_prefix
|