dokno 1.2.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -6
- data/app/assets/javascripts/dokno.js +19 -0
- 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 +14 -6
- data/app/controllers/dokno/categories_controller.rb +6 -4
- data/app/models/dokno/article.rb +86 -38
- data/app/models/dokno/category.rb +2 -7
- data/app/views/dokno/articles/_article_form.html.erb +44 -5
- data/app/views/dokno/articles/show.html.erb +15 -12
- data/app/views/dokno/categories/index.html.erb +13 -6
- data/app/views/layouts/dokno/application.html.erb +25 -9
- data/app/views/partials/_logs.html.erb +6 -4
- data/app/views/partials/_pagination.html.erb +20 -17
- data/config/routes.rb +1 -0
- 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 +4 -4
- data/lib/dokno/version.rb +1 -1
- data/lib/generators/dokno/templates/config/initializers/dokno.rb +18 -5
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: edd6bc7f282e312599b5d844e48382a5eff63604c13b05d41859c1beb587e878
|
4
|
+
data.tar.gz: b692a98a50654ae4d7d7a77b123289da686ab37131a0bbdf0f4ffc9151b5a1ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce6b652432db90585a4b72fe2327a5201409d9872c3408b74d7e5a52253d960b2ce1286fad4c762e09f2d4b72134d49174c4bbe3d4fc02203de1b3d95cc8077c
|
7
|
+
data.tar.gz: ae70e70feb6f78cc30daf751085ef58f50a26b45c9faa1276b5723fdb685c08ada77ff02ba5dee1a781a90c3852d0822702e7a410a2e952baef1545ded3408b2
|
data/README.md
CHANGED
@@ -36,11 +36,11 @@ To enable [in-context articles](#in-context-article-links) in your app, add the
|
|
36
36
|
<%= render 'dokno/article_panel' %>
|
37
37
|
```
|
38
38
|
|
39
|
-
|
39
|
+
### Configuration
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
#### Dokno Settings
|
42
|
+
|
43
|
+
Running `rails g dokno:install` creates `/config/initializers/dokno.rb` within your app, containing the available Dokno configuration options. Remember to restart your app whenever you make configuration changes.
|
44
44
|
|
45
45
|
### Articles
|
46
46
|
|
@@ -72,8 +72,6 @@ Clicking a link fetches the article asynchronously and reveals it to the user vi
|
|
72
72
|
|
73
73
|
<%= dokno_article_link({link-text}, slug: {unique-article-slug}) %>
|
74
74
|
|
75
|
-
<img src="./README/host_app_flyout.png" width="50%">
|
76
|
-
|
77
75
|
### Dokno Data Querying
|
78
76
|
You typically won't need to interact with Dokno data directly, but it is stored within your database and is accessible via ActiveRecord as is any other model.
|
79
77
|
|
@@ -175,3 +175,22 @@ function highlightTerm(terms, containers_selector) {
|
|
175
175
|
function wrapTermWithHTML(term) {
|
176
176
|
return `<span title="Matching search term" class="dokno-search-term bg-yellow-300 text-gray-900 p-2 rounded mx-1">${term}</span>`
|
177
177
|
}
|
178
|
+
|
179
|
+
function setReviewForm() {
|
180
|
+
const reset_review_date_checkbox = elem('input#reset_review_date');
|
181
|
+
const review_notes_textarea = elem('textarea#review_notes');
|
182
|
+
|
183
|
+
if (!reset_review_date_checkbox) {
|
184
|
+
return true;
|
185
|
+
}
|
186
|
+
|
187
|
+
if (reset_review_date_checkbox.checked) {
|
188
|
+
review_notes_textarea.removeAttribute('disabled');
|
189
|
+
review_notes_textarea.classList.remove('cursor-not-allowed');
|
190
|
+
review_notes_textarea.focus();
|
191
|
+
} else {
|
192
|
+
review_notes_textarea.setAttribute('disabled', 'disabled');
|
193
|
+
review_notes_textarea.classList.add('cursor-not-allowed');
|
194
|
+
reset_review_date_checkbox.focus();
|
195
|
+
}
|
196
|
+
}
|
@@ -14,7 +14,13 @@ module Dokno
|
|
14
14
|
@category = Category.find_by(code: params[:cat_code].to_s.strip) if params[:cat_code].present?
|
15
15
|
@category = @article.categories.first if @category.blank?
|
16
16
|
|
17
|
-
|
17
|
+
if !@article.active
|
18
|
+
flash.now[:yellow] = 'This article is no longer active'
|
19
|
+
elsif @article.up_for_review?
|
20
|
+
flash_msg = @article.review_due_days_string
|
21
|
+
flash_msg += " - <a href='#{edit_article_path(@article.slug)}' class='font-bold'>review it now</a>" if can_edit?
|
22
|
+
flash.now[:gray] = flash_msg
|
23
|
+
end
|
18
24
|
end
|
19
25
|
|
20
26
|
def new
|
@@ -33,7 +39,7 @@ module Dokno
|
|
33
39
|
set_editor_username
|
34
40
|
|
35
41
|
if @article.save
|
36
|
-
flash[:green]
|
42
|
+
flash[:green] = 'Article was created'
|
37
43
|
@article.categories = Category.where(code: params[:category_code]) if params[:category_code].present?
|
38
44
|
redirect_to article_path @article.slug
|
39
45
|
else
|
@@ -50,12 +56,14 @@ module Dokno
|
|
50
56
|
set_editor_username
|
51
57
|
|
52
58
|
if @article.update(article_params)
|
53
|
-
flash[:green]
|
59
|
+
flash[:green] = 'Article was updated'
|
54
60
|
@article.categories = Category.where(code: params[:category_code])
|
55
61
|
redirect_to article_path @article.slug
|
56
62
|
else
|
57
|
-
flash.now[:red]
|
58
|
-
@category_codes
|
63
|
+
flash.now[:red] = 'Article could not be updated'
|
64
|
+
@category_codes = params[:category_code]
|
65
|
+
@reset_review_date = params[:reset_review_date]
|
66
|
+
@review_notes = params[:review_notes]
|
59
67
|
render :edit
|
60
68
|
end
|
61
69
|
end
|
@@ -88,7 +96,7 @@ module Dokno
|
|
88
96
|
private
|
89
97
|
|
90
98
|
def article_params
|
91
|
-
params.permit(:slug, :title, :summary, :markdown)
|
99
|
+
params.permit(:slug, :title, :summary, :markdown, :reset_review_date, :review_notes, :starred)
|
92
100
|
end
|
93
101
|
|
94
102
|
def fetch_article
|
@@ -10,7 +10,9 @@ module Dokno
|
|
10
10
|
@order = params[:order]&.strip
|
11
11
|
@order = 'updated' unless %w(updated newest views alpha).include?(@order)
|
12
12
|
|
13
|
-
articles = if
|
13
|
+
articles = if request.path.include? up_for_review_path
|
14
|
+
Article.up_for_review(order: @order&.to_sym)
|
15
|
+
elsif @search_term.present?
|
14
16
|
Article.search(term: @search_term, category_id: @category&.id, order: @order&.to_sym)
|
15
17
|
elsif @category.present?
|
16
18
|
@category.articles_in_branch(order: @order&.to_sym)
|
@@ -22,7 +24,7 @@ module Dokno
|
|
22
24
|
end
|
23
25
|
|
24
26
|
def new
|
25
|
-
@category
|
27
|
+
@category = Category.new
|
26
28
|
@parent_category_code = params[:parent_category_code]
|
27
29
|
end
|
28
30
|
|
@@ -38,7 +40,7 @@ module Dokno
|
|
38
40
|
flash[:green] = 'Category was created'
|
39
41
|
redirect_to article_index_path(@category.code)
|
40
42
|
else
|
41
|
-
flash.now[:red]
|
43
|
+
flash.now[:red] = 'Category could not be created'
|
42
44
|
@parent_category_code = params[:parent_category_code]
|
43
45
|
render :new
|
44
46
|
end
|
@@ -51,7 +53,7 @@ module Dokno
|
|
51
53
|
flash[:green] = 'Category was updated'
|
52
54
|
redirect_to article_index_path(@category.code)
|
53
55
|
else
|
54
|
-
flash.now[:red]
|
56
|
+
flash.now[:red] = 'Category could not be updated'
|
55
57
|
@parent_category_code = params[:parent_category_code]
|
56
58
|
render :edit
|
57
59
|
end
|
data/app/models/dokno/article.rb
CHANGED
@@ -15,19 +15,28 @@ module Dokno
|
|
15
15
|
validates :title, length: { in: 5..255 }
|
16
16
|
validate :unique_slug_check
|
17
17
|
|
18
|
+
attr_accessor :editor_username, :reset_review_date, :review_notes
|
19
|
+
|
20
|
+
before_save :set_review_date, if: :should_set_review_date?
|
18
21
|
before_save :log_changes
|
19
22
|
before_save :track_slug
|
20
23
|
|
21
24
|
scope :active, -> { where(active: true) }
|
22
|
-
scope :alpha_order, -> { order(active: :desc, title: :asc) }
|
23
|
-
scope :
|
24
|
-
scope :newest_order, -> { order(active: :desc, created_at: :desc, title: :asc) }
|
25
|
-
scope :updated_order, -> { order(active: :desc, updated_at: :desc, title: :asc) }
|
26
|
-
|
27
|
-
attr_accessor :editor_username
|
25
|
+
scope :alpha_order, -> { order(active: :desc, starred: :desc, title: :asc) }
|
26
|
+
scope :views_order, -> { order(active: :desc, starred: :desc, views: :desc, title: :asc) }
|
27
|
+
scope :newest_order, -> { order(active: :desc, starred: :desc, created_at: :desc, title: :asc) }
|
28
|
+
scope :updated_order, -> { order(active: :desc, starred: :desc, updated_at: :desc, title: :asc) }
|
28
29
|
|
29
30
|
MARKDOWN_PARSER = Redcarpet::Markdown.new(Redcarpet::Render::HTML, autolink: true, tables: true)
|
30
31
|
|
32
|
+
def review_due_at
|
33
|
+
super || (Date.today + 30.years)
|
34
|
+
end
|
35
|
+
|
36
|
+
def markdown
|
37
|
+
super || ''
|
38
|
+
end
|
39
|
+
|
31
40
|
def reading_time
|
32
41
|
minutes_decimal = (("#{summary} #{markdown}".squish.scan(/[\w-]+/).size) / 200.0)
|
33
42
|
approx_minutes = minutes_decimal.ceil
|
@@ -36,8 +45,22 @@ module Dokno
|
|
36
45
|
"~ #{approx_minutes} minutes"
|
37
46
|
end
|
38
47
|
|
39
|
-
def
|
40
|
-
|
48
|
+
def review_due_days
|
49
|
+
(review_due_at.to_date - Date.today).to_i
|
50
|
+
end
|
51
|
+
|
52
|
+
def up_for_review?
|
53
|
+
active && review_due_days <= Dokno.config.article_review_prompt_days
|
54
|
+
end
|
55
|
+
|
56
|
+
def review_due_days_string
|
57
|
+
if review_due_days.positive?
|
58
|
+
"This article is up for an accuracy / relevance review in #{review_due_days} #{'day'.pluralize(review_due_days)}"
|
59
|
+
elsif review_due_days.negative?
|
60
|
+
"This article was up for an accuracy / relevance review #{review_due_days.abs} #{'day'.pluralize(review_due_days)} ago"
|
61
|
+
else
|
62
|
+
"This article is up for an accuracy / relevance review today"
|
63
|
+
end
|
41
64
|
end
|
42
65
|
|
43
66
|
def markdown_parsed
|
@@ -112,34 +135,24 @@ module Dokno
|
|
112
135
|
.to_sentence
|
113
136
|
end
|
114
137
|
|
115
|
-
# All
|
116
|
-
def self.
|
138
|
+
# All articles up for review
|
139
|
+
def self.up_for_review(order: :updated)
|
117
140
|
records = Article
|
118
141
|
.includes(:categories_dokno_articles, :categories)
|
119
|
-
.
|
120
|
-
.where(
|
142
|
+
.where(active: true)
|
143
|
+
.where('review_due_at <= ?', Date.today + Dokno.config.article_review_prompt_days)
|
121
144
|
|
122
|
-
records
|
123
|
-
records = records.newest_order if order == :newest
|
124
|
-
records = records.view_order if order == :views
|
125
|
-
records = records.alpha_order if order == :alpha
|
126
|
-
|
127
|
-
records
|
145
|
+
apply_sort(records, order: order)
|
128
146
|
end
|
129
147
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
end
|
137
|
-
|
138
|
-
def self.template
|
139
|
-
template_file = File.join(Rails.root, 'config', 'dokno_template.md')
|
140
|
-
return unless File.exist?(template_file)
|
148
|
+
# All uncategorized Articles
|
149
|
+
def self.uncategorized(order: :updated)
|
150
|
+
records = Article
|
151
|
+
.includes(:categories_dokno_articles, :categories)
|
152
|
+
.left_joins(:categories)
|
153
|
+
.where(dokno_categories: { id: nil })
|
141
154
|
|
142
|
-
|
155
|
+
apply_sort(records, order: order)
|
143
156
|
end
|
144
157
|
|
145
158
|
def self.search(term:, category_id: nil, order: :updated)
|
@@ -153,10 +166,7 @@ module Dokno
|
|
153
166
|
.includes(:categories_dokno_articles)
|
154
167
|
.includes(:categories)
|
155
168
|
|
156
|
-
records = records
|
157
|
-
records = records.newest_order if order == :newest
|
158
|
-
records = records.view_order if order == :views
|
159
|
-
records = records.alpha_order if order == :alpha
|
169
|
+
records = apply_sort(records, order: order)
|
160
170
|
|
161
171
|
return records unless category_id.present?
|
162
172
|
|
@@ -170,6 +180,28 @@ module Dokno
|
|
170
180
|
)
|
171
181
|
end
|
172
182
|
|
183
|
+
def self.parse_markdown(content)
|
184
|
+
ActionController::Base.helpers.sanitize(
|
185
|
+
MARKDOWN_PARSER.render(content),
|
186
|
+
tags: Dokno.config.tag_whitelist,
|
187
|
+
attributes: Dokno.config.attr_whitelist
|
188
|
+
)
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.template
|
192
|
+
template_file = File.join(Rails.root, 'config', 'dokno_template.md')
|
193
|
+
return unless File.exist?(template_file)
|
194
|
+
|
195
|
+
File.read(template_file).to_s
|
196
|
+
end
|
197
|
+
|
198
|
+
def self.apply_sort(records, order: :updated)
|
199
|
+
order_scope = "#{order}_order"
|
200
|
+
return records unless records.respond_to? order_scope
|
201
|
+
|
202
|
+
records.send(order_scope.to_sym)
|
203
|
+
end
|
204
|
+
|
173
205
|
private
|
174
206
|
|
175
207
|
# Ensure there isn't another Article with the same slug
|
@@ -189,7 +221,7 @@ module Dokno
|
|
189
221
|
end
|
190
222
|
|
191
223
|
def log_changes
|
192
|
-
return if changes.blank?
|
224
|
+
return if changes.blank? && !reset_review_date
|
193
225
|
|
194
226
|
meta_changes = changes.with_indifferent_access.slice(:slug, :title, :active)
|
195
227
|
content_changes = changes.with_indifferent_access.slice(:summary, :markdown)
|
@@ -207,10 +239,26 @@ module Dokno
|
|
207
239
|
content[:after] += values.last.to_s + ' '
|
208
240
|
end
|
209
241
|
|
210
|
-
|
242
|
+
if meta.present?
|
243
|
+
diff = Diffy::SplitDiff.new(content[:before].squish, content[:after].squish, format: :html)
|
244
|
+
logs << Log.new(username: editor_username, meta: meta.to_sentence, diff_left: diff.left, diff_right: diff.right)
|
245
|
+
end
|
246
|
+
|
247
|
+
# Reviewed for accuracy / relevance?
|
248
|
+
return unless reset_review_date
|
249
|
+
|
250
|
+
review_log = "Reviewed for accuracy / relevance. Next review date reset to #{review_due_at.to_date}."
|
251
|
+
review_log += " Review notes: #{review_notes.squish}" if review_notes.present?
|
252
|
+
logs << Log.new(username: editor_username, meta: review_log)
|
253
|
+
end
|
254
|
+
|
255
|
+
def should_set_review_date?
|
256
|
+
# User requested a reset or it's a new article w/out a review due date
|
257
|
+
reset_review_date || (!persisted? && review_due_at.blank?)
|
258
|
+
end
|
211
259
|
|
212
|
-
|
213
|
-
|
260
|
+
def set_review_date
|
261
|
+
self.review_due_at = Date.today + Dokno.config.article_review_period
|
214
262
|
end
|
215
263
|
end
|
216
264
|
end
|
@@ -45,12 +45,7 @@ module Dokno
|
|
45
45
|
.joins(:categories)
|
46
46
|
.where(dokno_categories: { id: self.class.branch(parent_category_id: id).pluck(:id) })
|
47
47
|
|
48
|
-
records
|
49
|
-
records = records.newest_order if order == :newest
|
50
|
-
records = records.view_order if order == :views
|
51
|
-
records = records.alpha_order if order == :alpha
|
52
|
-
|
53
|
-
records
|
48
|
+
Article.apply_sort(records, order: order)
|
54
49
|
end
|
55
50
|
|
56
51
|
def branch
|
@@ -59,7 +54,7 @@ module Dokno
|
|
59
54
|
|
60
55
|
# Used to invalidate the fragment cache of the hierarchical category select options
|
61
56
|
def self.cache_key
|
62
|
-
|
57
|
+
[maximum(:updated_at), Article.maximum(:updated_at)].compact.max
|
63
58
|
end
|
64
59
|
|
65
60
|
# The given Category and all child Categories. Useful for filtering associated articles.
|
@@ -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
|
7
|
-
<hr class="mb-
|
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="
|
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>
|
@@ -52,11 +75,27 @@
|
|
52
75
|
<div class="text-gray-500 mt-2">Detailed documentation of the described topic. Basic HTML & <a target="_blank" href="https://commonmark.org/help/" title="Markdown formatting examples">markdown</a> OK.</div>
|
53
76
|
</div>
|
54
77
|
|
55
|
-
<div class="
|
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">
|
56
92
|
<span class="text-lg mr-5"><a class="no-underline" href="<%= article.persisted? ? article_path(article.slug) : root_path %>">Cancel</a></span>
|
57
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>
|
58
94
|
</div>
|
59
95
|
|
60
96
|
</section>
|
61
97
|
|
62
|
-
<script>
|
98
|
+
<script>
|
99
|
+
elem('input#slug').focus();
|
100
|
+
setReviewForm();
|
101
|
+
</script>
|
@@ -13,7 +13,10 @@
|
|
13
13
|
<section>
|
14
14
|
<div class="flex">
|
15
15
|
<div id="dokno-article-sidebar" class="w-2/5 pr-10">
|
16
|
-
<h1 class="dokno-article-content-highlight text-4xl mb-8 leading-tight font-light"
|
16
|
+
<h1 class="dokno-article-content-highlight text-4xl mb-8 leading-tight font-light">
|
17
|
+
<%= @article.title %>
|
18
|
+
<% if @article.starred %><i title="Starred article" data-feather="star" class="inline align-middle"></i><% end %>
|
19
|
+
</h1>
|
17
20
|
|
18
21
|
<div>
|
19
22
|
<div class="mb-8 no-print">
|
@@ -23,7 +26,7 @@
|
|
23
26
|
</div>
|
24
27
|
|
25
28
|
<div class="flex">
|
26
|
-
<div class="no-print w-8"><i data-feather="clock" class="inline-block h-5"></i></div>
|
29
|
+
<div class="no-print w-8"><i data-feather="clock" class="inline-block h-5 mr-2"></i></div>
|
27
30
|
<div class="w-full">
|
28
31
|
Last updated:<br />
|
29
32
|
<%= time_ago_in_words @article.updated_at %> ago
|
@@ -34,7 +37,7 @@
|
|
34
37
|
</div>
|
35
38
|
|
36
39
|
<div class="mt-5 flex no-print">
|
37
|
-
<div class="no-print w-8"><i data-feather="eye" class="inline-block h-5"></i></div>
|
40
|
+
<div class="no-print w-8"><i data-feather="eye" class="inline-block h-5 mr-2"></i></div>
|
38
41
|
<div class="w-full">
|
39
42
|
Views:<br />
|
40
43
|
<%= number_with_delimiter(@article.views, delimiter: ',') %>
|
@@ -43,7 +46,7 @@
|
|
43
46
|
|
44
47
|
<% if @article.contributors.present? %>
|
45
48
|
<div class="mt-5 flex">
|
46
|
-
<div class="no-print w-8"><i data-feather="user-check" class="inline-block h-5"></i></div>
|
49
|
+
<div class="no-print w-8"><i data-feather="user-check" class="inline-block h-5 mr-2"></i></div>
|
47
50
|
<div class="w-full">
|
48
51
|
Contributors:<br />
|
49
52
|
<%= @article.contributors %>
|
@@ -53,7 +56,7 @@
|
|
53
56
|
|
54
57
|
<% if @article.reading_time.present? %>
|
55
58
|
<div class="mt-5 flex no-print">
|
56
|
-
<div class="no-print w-8"><i data-feather="watch" class="inline-block h-5"></i></div>
|
59
|
+
<div class="no-print w-8"><i data-feather="watch" class="inline-block h-5 mr-2"></i></div>
|
57
60
|
<div class="w-full">
|
58
61
|
Reading time:<br />
|
59
62
|
<%= @article.reading_time %>
|
@@ -62,7 +65,7 @@
|
|
62
65
|
<% end %>
|
63
66
|
|
64
67
|
<div class="mt-5 flex">
|
65
|
-
<div class="no-print w-8"><i data-feather="link" class="inline-block h-5"></i></div>
|
68
|
+
<div class="no-print w-8"><i data-feather="link" class="inline-block h-5 mr-2"></i></div>
|
66
69
|
<div class="w-full">
|
67
70
|
Permalink:<br />
|
68
71
|
<a class="inline-block mt-1 -ml-2 px-2 py-1 bg-blue-100 rounded" title="Copy to clipboard" href="javascript:;" onclick="copyToClipboard('<%= @article.permalink(request.base_url) %>');"><%= @article.permalink(request.base_url) %></a>
|
@@ -71,7 +74,7 @@
|
|
71
74
|
|
72
75
|
<% if can_edit? %>
|
73
76
|
<div class="mt-5 flex no-print">
|
74
|
-
<div class="no-print w-8"><i data-feather="crosshair" class="inline-block h-5"></i></div>
|
77
|
+
<div class="no-print w-8"><i data-feather="crosshair" class="inline-block h-5 mr-2"></i></div>
|
75
78
|
<div class="w-full">
|
76
79
|
Unique slug:<br />
|
77
80
|
<a class="inline-block mt-1 -ml-2 px-2 py-1 bg-blue-100 rounded" title="Copy to clipboard" href="javascript:;" onclick="copyToClipboard('<%= j @article.slug %>');"><%= @article.slug %></a>
|
@@ -90,7 +93,7 @@
|
|
90
93
|
|
91
94
|
<% if (category_name_list = @article.category_name_list(context_category_id: @category&.id)).present? %>
|
92
95
|
<div class="mt-5 flex no-print">
|
93
|
-
<div class="no-print w-8"><i data-feather="folder" class="inline-block h-5"></i></div>
|
96
|
+
<div class="no-print w-8"><i data-feather="folder" class="inline-block h-5 mr-2"></i></div>
|
94
97
|
<div class="w-full">
|
95
98
|
<%= category_name_list.sub(':', ':<br />').html_safe %>
|
96
99
|
</div>
|
@@ -120,14 +123,14 @@
|
|
120
123
|
<div class="no-print">
|
121
124
|
<hr class="mt-5 mb-10" />
|
122
125
|
<div class="text-right">
|
123
|
-
<button title="Edit article" class="bg-blue-900 text-gray-300 hover:text-white rounded mr-5 py-2 px-3 font-bold text-sm" onclick="location.href='<%= edit_article_path(@article.slug) %>';"><i data-feather="edit" class="inline h-5"></i> EDIT</button>
|
126
|
+
<button title="Edit article" class="bg-blue-900 text-gray-300 hover:text-white rounded mr-5 py-2 px-3 font-bold text-sm" onclick="location.href='<%= edit_article_path(@article.slug) %>';"><i data-feather="edit-2" class="inline h-5"></i> EDIT</button>
|
124
127
|
|
125
128
|
<% if @article.active %>
|
126
|
-
<button title="Deactivate article" id="article-deactivate-button" class="bg-yellow-700 text-gray-300 hover:text-white rounded mr-5 py-2 px-3 font-bold text-base" onclick="deactivateArticle('<%= @article.slug %>');"><i data-feather="
|
129
|
+
<button title="Deactivate article" id="article-deactivate-button" class="bg-yellow-700 text-gray-300 hover:text-white rounded mr-5 py-2 px-3 font-bold text-base" onclick="deactivateArticle('<%= @article.slug %>');"><i data-feather="file-minus" class="inline h-5"></i> DEACTIVATE</button>
|
127
130
|
<% else %>
|
128
|
-
<button title="Re-activate article" id="article-activate-button" class="bg-green-700 text-gray-300 hover:text-white rounded mr-5 py-2 px-3 font-bold text-base" onclick="activateArticle('<%= @article.slug %>');"><i data-feather="
|
131
|
+
<button title="Re-activate article" id="article-activate-button" class="bg-green-700 text-gray-300 hover:text-white rounded mr-5 py-2 px-3 font-bold text-base" onclick="activateArticle('<%= @article.slug %>');"><i data-feather="file-plus" class="inline h-5"></i> RE-ACTIVATE</button>
|
129
132
|
<% end %>
|
130
|
-
<button title="Permanently delete article" class="bg-red-700 text-gray-300 hover:text-white rounded py-2 px-3 font-bold text-base" onclick="deleteArticle('<%= @article.id %>');"><i data-feather="
|
133
|
+
<button title="Permanently delete article" class="bg-red-700 text-gray-300 hover:text-white rounded py-2 px-3 font-bold text-base" onclick="deleteArticle('<%= @article.id %>');"><i data-feather="slash" class="inline h-5"></i> DELETE</button>
|
131
134
|
</div>
|
132
135
|
</div>
|
133
136
|
<% end %>
|
@@ -4,7 +4,7 @@
|
|
4
4
|
</div>
|
5
5
|
<% end %>
|
6
6
|
|
7
|
-
<% if Dokno::Category.exists? || Dokno::Article.exists? %>
|
7
|
+
<% if !current_page?(up_for_review_path) && (Dokno::Category.exists? || Dokno::Article.exists?) %>
|
8
8
|
<%= render 'partials/category_header' %>
|
9
9
|
<% end %>
|
10
10
|
|
@@ -44,7 +44,7 @@
|
|
44
44
|
<section class="border-t border-gray-300 py-10 text-xl flex">
|
45
45
|
<div class="w-1/3 pr-10">
|
46
46
|
<div class="flex">
|
47
|
-
<div class="no-print w-10 text-gray-300"><i data-feather="chevron-right" class="inline-block"></i></div>
|
47
|
+
<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>
|
48
48
|
<div class="w-full">
|
49
49
|
<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>
|
50
50
|
</div>
|
@@ -53,7 +53,7 @@
|
|
53
53
|
<div class="dokno-article-summary w-2/3 <% unless article.active %>text-gray-500 italic<% end %>">
|
54
54
|
<% unless article.active %>
|
55
55
|
<div class="bg-yellow-700 p-4 mb-5 rounded text-lg border-t-4 border-yellow-900 text-white font-base not-italic">
|
56
|
-
<i data-feather="
|
56
|
+
<i data-feather="info" class="inline-block"></i> This article is no longer active
|
57
57
|
</div>
|
58
58
|
<% end %>
|
59
59
|
|
@@ -66,14 +66,21 @@
|
|
66
66
|
<% unless @order == 'alpha' %>
|
67
67
|
<div class="text-base">
|
68
68
|
<% if @order == 'views' %>
|
69
|
-
<div class="text-gray-500">
|
69
|
+
<div class="text-gray-500">This article was viewed <%= number_with_delimiter(article.views, delimiter: ',') %> <%= 'time'.pluralize(article.views) %></div>
|
70
70
|
<% elsif @order == 'updated' %>
|
71
|
-
<div class="text-gray-500">
|
71
|
+
<div class="text-gray-500">This article was last updated <%= time_ago_in_words article.updated_at %> ago</div>
|
72
72
|
<% elsif @order == 'newest' %>
|
73
|
-
<div class="text-gray-500">
|
73
|
+
<div class="text-gray-500">This article was added <%= time_ago_in_words article.created_at %> ago</div>
|
74
74
|
<% end %>
|
75
75
|
</div>
|
76
76
|
<% end %>
|
77
|
+
|
78
|
+
<% if article.up_for_review? %>
|
79
|
+
<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">
|
80
|
+
<i data-feather="bell" class="inline-block mr-1"></i> <%= article.review_due_days_string %>
|
81
|
+
</div>
|
82
|
+
<% end %>
|
83
|
+
|
77
84
|
</div>
|
78
85
|
</section>
|
79
86
|
<% 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
|
|
@@ -24,12 +26,12 @@
|
|
24
26
|
<div class="w-2/3 text-right">
|
25
27
|
<% if can_edit? %>
|
26
28
|
<% 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
|
29
|
+
<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>
|
30
|
+
<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
31
|
<% end %>
|
30
32
|
|
31
33
|
<% 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>
|
34
|
+
<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
35
|
<% end %>
|
34
36
|
<% end %>
|
35
37
|
|
@@ -40,15 +42,15 @@
|
|
40
42
|
</div>
|
41
43
|
</nav>
|
42
44
|
|
43
|
-
<% flash.each do |
|
44
|
-
<div class="bg-<%=
|
45
|
-
<i data-feather="<%= (
|
46
|
-
<%= msg %>
|
45
|
+
<% flash.each do |type, msg| %>
|
46
|
+
<div class="bg-<%= type %>-800 text-lg text-white font-base py-10 px-16 text-center">
|
47
|
+
<i data-feather="<%= (type == 'green' ? 'smile' : (type == 'yellow' ? 'info' : (type == 'gray' ? 'bell' : 'frown'))) %>" class="inline mr-1"></i>
|
48
|
+
<%= sanitize(msg, tags: %w[a], attributes: %w[href class]) %>
|
47
49
|
</div>
|
48
50
|
<% end %>
|
49
51
|
|
50
52
|
<main class="py-10 px-16">
|
51
|
-
<% if @article.blank? && (@category.blank? || @search_term.present?) %>
|
53
|
+
<% if !current_page?(up_for_review_path) && @article.blank? && (@category.blank? || @search_term.present?) %>
|
52
54
|
<div class="text-center m-auto mb-10 w-full max-w-screen-xl">
|
53
55
|
<% if @search_term.present? %>
|
54
56
|
<div class="text-gray-600 text-2xl uppercase">
|
@@ -81,10 +83,24 @@
|
|
81
83
|
</div>
|
82
84
|
<% end %>
|
83
85
|
|
86
|
+
<% if can_edit? && !current_page?(up_for_review_path) && action_name == 'index' && (up_for_review_count = Dokno::Article.up_for_review.count).positive? %>
|
87
|
+
<div id="dokno-articles-up-for-review-container">
|
88
|
+
<div class="py-10 px-16 bg-gray-900">
|
89
|
+
<div class="w-full max-w-screen-xl m-auto">
|
90
|
+
<div class="text-xl text-white cursor-pointer" onclick="location.href='<%= up_for_review_path %>';">
|
91
|
+
<i data-feather="bell" class="inline mr-1"></i>
|
92
|
+
There <%= "#{up_for_review_count == 1 ? 'is' : 'are'} #{up_for_review_count}" %> <%= 'article'.pluralize(up_for_review_count) %> up for accuracy / relevance review
|
93
|
+
</div>
|
94
|
+
</div>
|
95
|
+
</div>
|
96
|
+
</div>
|
97
|
+
<% end %>
|
98
|
+
|
84
99
|
<div class="py-10 px-16 text-gray-400 bg-blue-900">
|
85
100
|
<div class="w-full max-w-screen-xl m-auto flex">
|
86
101
|
<div class="w-1/2">
|
87
|
-
<
|
102
|
+
<i data-feather="github" class="inline mr-1"></i>
|
103
|
+
<a target="_blank" href="https://github.com/cpayne624/dokno" title="Dokno on GitHub">dokno</a>
|
88
104
|
</div>
|
89
105
|
<div class="w-1/2 text-right">
|
90
106
|
<% if user.present? %>
|
@@ -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,6 +3,7 @@ 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
8
|
post 'article_preview', to: 'articles#preview', as: :preview
|
8
9
|
post 'article_status', to: 'articles#status', as: :status
|
@@ -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
|
data/lib/dokno/engine.rb
CHANGED
@@ -4,10 +4,6 @@ module Dokno
|
|
4
4
|
class Engine < ::Rails::Engine
|
5
5
|
isolate_namespace Dokno
|
6
6
|
|
7
|
-
initializer 'Dokno precompile', group: :all do |app|
|
8
|
-
app.config.assets.precompile << "dokno_manifest.js"
|
9
|
-
end
|
10
|
-
|
11
7
|
config.generators do |g|
|
12
8
|
g.test_framework :rspec
|
13
9
|
end
|
@@ -18,6 +14,10 @@ module Dokno
|
|
18
14
|
end
|
19
15
|
end
|
20
16
|
|
17
|
+
initializer 'Dokno precompile', group: :all do |app|
|
18
|
+
app.config.assets.precompile << "dokno_manifest.js"
|
19
|
+
end
|
20
|
+
|
21
21
|
initializer 'local_helper.action_controller' do
|
22
22
|
ActiveSupport.on_load :action_controller do
|
23
23
|
helper Dokno::ApplicationHelper
|
data/lib/dokno/version.rb
CHANGED
@@ -1,14 +1,27 @@
|
|
1
1
|
Dokno.configure do |config|
|
2
|
-
|
3
|
-
|
2
|
+
# To control the permitted HTML tags and attributes within articles,
|
3
|
+
# uncomment and change the defaults.
|
4
|
+
# (Enumerable) tag_whitelist
|
5
|
+
# (Enumerable) attr_whitelist
|
6
|
+
# config.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]
|
7
|
+
# config.attr_whitelist = %w[src alt title href target]
|
4
8
|
|
5
9
|
# To restrict Dokno data modification and include indentifying information
|
6
10
|
# in change log entries, provide the appropriate user values for your app below.
|
7
11
|
# (String) app_user_object
|
8
12
|
# (Symbol) app_user_auth_method
|
9
13
|
# (Symbol) app_user_name_method
|
14
|
+
# config.app_user_object = 'current_user'
|
15
|
+
# config.app_user_auth_method = :admin?
|
16
|
+
# config.app_user_name_method = :name
|
10
17
|
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
18
|
+
# To control the amount of time before a created/updated article is flagged
|
19
|
+
# for accuracy/relevance review, uncomment and change the default.
|
20
|
+
# (ActiveSupport::Duration) article_review_period
|
21
|
+
# config.article_review_period = 1.year
|
22
|
+
|
23
|
+
# To control the number of days prior to an article being up for review
|
24
|
+
# that users should be prompted to re-review, uncomment and change the default.
|
25
|
+
# (Integer) article_review_prompt_days
|
26
|
+
# config.article_review_prompt_days = 30
|
14
27
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dokno
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Courtney Payne
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-12-
|
11
|
+
date: 2020-12-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: diffy
|
@@ -284,6 +284,8 @@ files:
|
|
284
284
|
- app/views/partials/_pagination.html.erb
|
285
285
|
- config/routes.rb
|
286
286
|
- db/migrate/20201203190330_baseline.rb
|
287
|
+
- db/migrate/20201211192306_add_review_due_at_to_articles.rb
|
288
|
+
- db/migrate/20201213165700_add_starred_to_article.rb
|
287
289
|
- lib/dokno.rb
|
288
290
|
- lib/dokno/config/config.rb
|
289
291
|
- lib/dokno/engine.rb
|
@@ -298,7 +300,7 @@ licenses:
|
|
298
300
|
metadata:
|
299
301
|
bug_tracker_uri: https://github.com/cpayne624/dokno/issues
|
300
302
|
changelog_uri: https://github.com/cpayne624/dokno/blob/master/CHANGELOG.md
|
301
|
-
post_install_message:
|
303
|
+
post_install_message:
|
302
304
|
rdoc_options: []
|
303
305
|
require_paths:
|
304
306
|
- lib
|
@@ -313,8 +315,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
313
315
|
- !ruby/object:Gem::Version
|
314
316
|
version: '0'
|
315
317
|
requirements: []
|
316
|
-
rubygems_version: 3.1
|
317
|
-
signing_key:
|
318
|
+
rubygems_version: 3.2.0.rc.1
|
319
|
+
signing_key:
|
318
320
|
specification_version: 4
|
319
321
|
summary: Dokno (dough-no) is a lightweight mountable Rails Engine for storing and
|
320
322
|
managing your app's domain knowledge.
|