dokno 1.2.1 → 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.
- 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.
|