dokno 1.0.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -6
  3. data/app/assets/javascripts/dokno.js +79 -27
  4. data/app/assets/stylesheets/dokno/application.css +1 -1
  5. data/app/controllers/dokno/application_controller.rb +3 -0
  6. data/app/controllers/dokno/articles_controller.rb +22 -8
  7. data/app/controllers/dokno/categories_controller.rb +15 -2
  8. data/app/controllers/dokno/user_concern.rb +5 -3
  9. data/app/helpers/dokno/application_helper.rb +1 -3
  10. data/app/models/dokno/article.rb +87 -38
  11. data/app/models/dokno/category.rb +39 -15
  12. data/app/views/dokno/_article_formatting.html.erb +17 -18
  13. data/app/views/dokno/_article_panel.html.erb +16 -18
  14. data/app/views/dokno/_panel_formatting.html.erb +47 -57
  15. data/app/views/dokno/articles/_article_form.html.erb +47 -6
  16. data/app/views/dokno/articles/show.html.erb +45 -39
  17. data/app/views/dokno/categories/_category_form.html.erb +6 -1
  18. data/app/views/dokno/categories/index.html.erb +40 -37
  19. data/app/views/layouts/dokno/application.html.erb +34 -9
  20. data/app/views/partials/_category_header.html.erb +29 -0
  21. data/app/views/partials/_form_errors.html.erb +0 -1
  22. data/app/views/partials/_logs.html.erb +7 -5
  23. data/app/views/partials/_pagination.html.erb +20 -18
  24. data/config/routes.rb +1 -1
  25. data/db/migrate/20201203190330_baseline.rb +4 -4
  26. data/db/migrate/20201211192306_add_review_due_at_to_articles.rb +6 -0
  27. data/db/migrate/20201213165700_add_starred_to_article.rb +5 -0
  28. data/lib/dokno/config/config.rb +53 -40
  29. data/lib/dokno/engine.rb +4 -4
  30. data/lib/dokno/version.rb +1 -1
  31. data/lib/generators/dokno/templates/config/initializers/dokno.rb +18 -5
  32. metadata +87 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '08352501c3a71f5edef7bd7bf311d8459dc5d38adc20dff5540f755bf04033b6'
4
- data.tar.gz: 19aedf91902b6fc6e30378074740578c347041702d849999a7c62c4a0fff53cd
3
+ metadata.gz: edd6bc7f282e312599b5d844e48382a5eff63604c13b05d41859c1beb587e878
4
+ data.tar.gz: b692a98a50654ae4d7d7a77b123289da686ab37131a0bbdf0f4ffc9151b5a1ab
5
5
  SHA512:
6
- metadata.gz: f44728981b2004cb703d75641f72f02dc0cf4e92b697ae10d8ef69f1e1b099aa536499c139ae6931748331bbfe3c77bbcbeb3395e59b7fa99398aaeb45b25761
7
- data.tar.gz: 32a8b4f6cbf26d8ef35a6894e3d668f10b81f085c47b65fa44abba17fec7fa337c7e917a532af45bf258c80ecdf869c3ec48cdd6386f96e3e029e3100ee84776
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
- ## Screenshots
39
+ ### Configuration
40
40
 
41
- | Landing Page | Article | Editing an Article | Article Flyout |
42
- | ------------- | ------------- | ------------- | ------------- |
43
- | <img src="./README/landing_page.png" width="250"> | <img src="./README/article.png" width="250"> | <img src="./README/article_edit.png" width="250"> | <img src="./README/host_app_flyout.png" width="250"> |
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
 
@@ -119,6 +117,8 @@ $ bundle exec rspec
119
117
  ```
120
118
 
121
119
  ## Hat Tips
120
+ - [tailwindcss](https://tailwindcss.com/)
121
+ - CSS framework
122
122
  - [diffy](https://github.com/samg/diffy)
123
123
  - Text diffing Ruby gem
124
124
  - [Feather Icons](https://github.com/feathericons/feather)
@@ -1,5 +1,23 @@
1
+ const dokno__search_hotkey_listener = function(e) {
2
+ if (e.key === '/') { handleSearchHotKey(); }
3
+ }
4
+
5
+ function handleSearchHotKey() {
6
+ const search_input = elem('input#search_term');
7
+ search_input.focus();
8
+ search_input.select();
9
+ }
10
+
11
+ function enableSearchHotkey() {
12
+ document.addEventListener('keyup', dokno__search_hotkey_listener, false);
13
+ }
14
+
15
+ function disableSearchHotkey() {
16
+ document.removeEventListener('keyup', dokno__search_hotkey_listener, false);
17
+ }
18
+
1
19
  function copyToClipboard(text) {
2
- window.prompt('Copy to clipboard: CTRL+C, Enter', text);
20
+ window.prompt('Copy to clipboard: CTRL + C, Enter', text);
3
21
  }
4
22
 
5
23
  function elem(selector) {
@@ -10,6 +28,32 @@ function elems(selector) {
10
28
  return document.getElementsByClassName(selector);
11
29
  }
12
30
 
31
+ function selectOption(id, value) {
32
+ var sel = elem('#' + id);
33
+ var opts = sel.options;
34
+
35
+ for (var opt, j = 0; opt = opts[j]; j++) {
36
+ if (opt.value == value) {
37
+ sel.selectedIndex = j;
38
+ break;
39
+ }
40
+ }
41
+ }
42
+
43
+ function applyCategoryCriteria(category_code, term, order) {
44
+ goToPage(dokno__base_path + category_code + '?search_term=' + term + '&order=' + order);
45
+ }
46
+
47
+ function goToPage(url) {
48
+ var param_join = url.indexOf('?') >= 0 ? '&' : '?';
49
+ location.href=url + param_join + '_=' + Math.round(new Date().getTime());
50
+ }
51
+
52
+ function reloadPage() {
53
+ window.onbeforeunload = function () { window.scrollTo(0, 0); }
54
+ location.reload();
55
+ }
56
+
13
57
  function sendRequest(url, data, callback, method) {
14
58
  const request = new XMLHttpRequest();
15
59
  request.open(method, url, true);
@@ -30,23 +74,13 @@ function sendRequest(url, data, callback, method) {
30
74
  request.send(JSON.stringify(data));
31
75
  }
32
76
 
33
- function deactiveArticle(slug) {
34
- const callback = function(_data) {
35
- elem('button#article-deactivate-button').classList.add('hidden');
36
- elem('div#article-deprecated-alert').classList.remove('hidden');
37
- elem('button#article-activate-button').classList.remove('hidden');
38
- reloadLogs();
39
- }
77
+ function deactivateArticle(slug) {
78
+ const callback = function(_data) { reloadPage(); }
40
79
  sendRequest(dokno__base_path + 'article_status', { slug: slug, active: false }, callback, 'POST');
41
80
  }
42
81
 
43
- function activeArticle(slug) {
44
- const callback = function(_data) {
45
- elem('button#article-activate-button').classList.add('hidden');
46
- elem('div#article-deprecated-alert').classList.add('hidden');
47
- elem('button#article-deactivate-button').classList.remove('hidden');
48
- reloadLogs();
49
- }
82
+ function activateArticle(slug) {
83
+ const callback = function(_data) { reloadPage(); }
50
84
  sendRequest(dokno__base_path + 'article_status', { slug: slug, active: true }, callback, 'POST');
51
85
  }
52
86
 
@@ -61,6 +95,17 @@ function deleteArticle(id) {
61
95
  sendRequest(dokno__base_path + 'articles/' + id, {}, callback, 'DELETE');
62
96
  }
63
97
 
98
+ function deleteCategory(id) {
99
+ if (!confirm('Delete Category\n\nThis will remove this category. Any articles in this category will become uncategorized and appear on the home page until re-categorized.')) {
100
+ return true;
101
+ }
102
+
103
+ const callback = function(_data) {
104
+ location.href = dokno__base_path;
105
+ }
106
+ sendRequest(dokno__base_path + 'categories/' + id, {}, callback, 'DELETE');
107
+ }
108
+
64
109
  function previewArticleToggle() {
65
110
  const markdown = elem('div#dokno-content-container textarea#markdown').value;
66
111
  const callback = function(data) {
@@ -112,18 +157,6 @@ function toggleVisibility(selector_id) {
112
157
  initIcons();
113
158
  }
114
159
 
115
- function reloadLogs() {
116
- var $log_container = elem('div#dokno-article-log-container');
117
- var category_id = $log_container.getAttribute('data-category-id');
118
- var article_id = $log_container.getAttribute('data-article-id');
119
-
120
- const callback = function(markup) {
121
- elem('div#dokno-article-log-container').innerHTML = markup;
122
- initIcons();
123
- }
124
- sendRequest(dokno__base_path + 'article_log', { category_id: category_id, article_id: article_id }, callback, 'POST');
125
- }
126
-
127
160
  // Pass containers_selector as class name (no prefix)
128
161
  function highlightTerm(terms, containers_selector) {
129
162
  var containers = elems(containers_selector);
@@ -142,3 +175,22 @@ function highlightTerm(terms, containers_selector) {
142
175
  function wrapTermWithHTML(term) {
143
176
  return `<span title="Matching search term" class="dokno-search-term bg-yellow-300 text-gray-900 p-2 rounded mx-1">${term}</span>`
144
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
+ }
@@ -16,7 +16,7 @@
16
16
 
17
17
  /* Additional knowledgebase site styles that are not in tailwind */
18
18
 
19
- svg.feather {
19
+ button svg.feather {
20
20
  vertical-align: sub;
21
21
  }
22
22
 
@@ -1,7 +1,10 @@
1
1
  module Dokno
2
2
  class ApplicationController < ::ApplicationController
3
3
  protect_from_forgery with: :exception
4
+
4
5
  include UserConcern
5
6
  include PaginationConcern
7
+
8
+ add_flash_types :green, :yellow, :red, :gray
6
9
  end
7
10
  end
@@ -8,7 +8,19 @@ module Dokno
8
8
 
9
9
  def show
10
10
  redirect_to root_path if @article.blank?
11
+
11
12
  @search_term = params[:search_term]
13
+ @order = params[:order]
14
+ @category = Category.find_by(code: params[:cat_code].to_s.strip) if params[:cat_code].present?
15
+ @category = @article.categories.first if @category.blank?
16
+
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
12
24
  end
13
25
 
14
26
  def new
@@ -27,9 +39,11 @@ module Dokno
27
39
  set_editor_username
28
40
 
29
41
  if @article.save
42
+ flash[:green] = 'Article was created'
30
43
  @article.categories = Category.where(code: params[:category_code]) if params[:category_code].present?
31
44
  redirect_to article_path @article.slug
32
45
  else
46
+ flash.now[:red] = 'Article could not be created'
33
47
  @category_codes = params[:category_code]
34
48
  render :new
35
49
  end
@@ -42,16 +56,22 @@ module Dokno
42
56
  set_editor_username
43
57
 
44
58
  if @article.update(article_params)
59
+ flash[:green] = 'Article was updated'
45
60
  @article.categories = Category.where(code: params[:category_code])
46
61
  redirect_to article_path @article.slug
47
62
  else
48
- @category_codes = params[:category_code]
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]
49
67
  render :edit
50
68
  end
51
69
  end
52
70
 
53
71
  def destroy
54
72
  Article.find(params[:id].to_i).destroy!
73
+
74
+ flash[:green] = 'Article was deleted'
55
75
  render json: {}, layout: false
56
76
  end
57
77
 
@@ -73,16 +93,10 @@ module Dokno
73
93
  render json: {}, layout: false
74
94
  end
75
95
 
76
- # Ajax-fetched article change log
77
- def article_log
78
- render partial: '/partials/logs',
79
- locals: { article: Article.find_by(id: params[:article_id].to_i) }, layout: false
80
- end
81
-
82
96
  private
83
97
 
84
98
  def article_params
85
- params.permit(:slug, :title, :summary, :markdown)
99
+ params.permit(:slug, :title, :summary, :markdown, :reset_review_date, :review_notes, :starred)
86
100
  end
87
101
 
88
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 @search_term.present?
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 = Category.new
27
+ @category = Category.new
26
28
  @parent_category_code = params[:parent_category_code]
27
29
  end
28
30
 
@@ -35,8 +37,10 @@ module Dokno
35
37
  @category = Category.new(name: params[:name], parent: Category.find_by(code: params[:parent_category_code]))
36
38
 
37
39
  if @category.save
40
+ flash[:green] = 'Category was created'
38
41
  redirect_to article_index_path(@category.code)
39
42
  else
43
+ flash.now[:red] = 'Category could not be created'
40
44
  @parent_category_code = params[:parent_category_code]
41
45
  render :new
42
46
  end
@@ -46,13 +50,22 @@ module Dokno
46
50
  return redirect_to root_path if @category.blank?
47
51
 
48
52
  if @category.update(name: params[:name], parent: Category.find_by(code: params[:parent_category_code]))
53
+ flash[:green] = 'Category was updated'
49
54
  redirect_to article_index_path(@category.code)
50
55
  else
56
+ flash.now[:red] = 'Category could not be updated'
51
57
  @parent_category_code = params[:parent_category_code]
52
58
  render :edit
53
59
  end
54
60
  end
55
61
 
62
+ def destroy
63
+ Category.find(params[:id].to_i).destroy!
64
+
65
+ flash[:green] = 'Category was deleted'
66
+ render json: {}, layout: false
67
+ end
68
+
56
69
  private
57
70
 
58
71
  def fetch_category
@@ -8,8 +8,6 @@ module Dokno
8
8
 
9
9
  def user
10
10
  # Attempt to eval the currently signed in 'user' object from the host app
11
- sanitized_user_obj_string = Dokno.config.app_user_object.to_s.split(/\b/).first
12
-
13
11
  proc {
14
12
  $safe = 1
15
13
  eval sanitized_user_obj_string
@@ -19,13 +17,17 @@ module Dokno
19
17
  nil
20
18
  end
21
19
 
20
+ def sanitized_user_obj_string
21
+ Dokno.config.app_user_object.to_s.split(/\b/).first
22
+ end
23
+
22
24
  def username
23
25
  user&.send(Dokno.config.app_user_name_method.to_sym).to_s
24
26
  end
25
27
 
26
28
  def can_edit?
27
29
  # Allow editing by default if host app user object is not configured
28
- return true unless user.present?
30
+ return true unless sanitized_user_obj_string.present?
29
31
  return false unless user.respond_to? Dokno.config.app_user_auth_method.to_sym
30
32
 
31
33
  user.send(Dokno.config.app_user_auth_method.to_sym)
@@ -9,9 +9,7 @@ module Dokno
9
9
 
10
10
  return "Dokno article slug '#{slug}' not found" if article.blank?
11
11
 
12
- %Q(
13
- <a href="javascript:;" onclick="doknoOpenPanel('#{j article.slug}');">#{link_text.presence || article.title}</a>
14
- ).html_safe
12
+ %Q(<a href="javascript:;" onclick="doknoOpenPanel('#{j article.slug}');">#{link_text.presence || article.title}</a>).html_safe
15
13
  end
16
14
  end
17
15
  end
@@ -15,18 +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
- scope :alpha_order, -> { order(active: :desc, title: :asc) }
22
- scope :view_order, -> { order(active: :desc, views: :desc, title: :asc) }
23
- scope :newest_order, -> { order(active: :desc, created_at: :desc, title: :asc) }
24
- scope :updated_order, -> { order(active: :desc, updated_at: :desc, title: :asc) }
25
-
26
- attr_accessor :editor_username
24
+ scope :active, -> { where(active: true) }
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) }
27
29
 
28
30
  MARKDOWN_PARSER = Redcarpet::Markdown.new(Redcarpet::Render::HTML, autolink: true, tables: true)
29
31
 
32
+ def review_due_at
33
+ super || (Date.today + 30.years)
34
+ end
35
+
36
+ def markdown
37
+ super || ''
38
+ end
39
+
30
40
  def reading_time
31
41
  minutes_decimal = (("#{summary} #{markdown}".squish.scan(/[\w-]+/).size) / 200.0)
32
42
  approx_minutes = minutes_decimal.ceil
@@ -35,8 +45,22 @@ module Dokno
35
45
  "~ #{approx_minutes} minutes"
36
46
  end
37
47
 
38
- def markdown
39
- super || ''
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
40
64
  end
41
65
 
42
66
  def markdown_parsed
@@ -111,34 +135,24 @@ module Dokno
111
135
  .to_sentence
112
136
  end
113
137
 
114
- # All uncategorized Articles
115
- def self.uncategorized(order: :updated)
138
+ # All articles up for review
139
+ def self.up_for_review(order: :updated)
116
140
  records = Article
117
141
  .includes(:categories_dokno_articles, :categories)
118
- .left_joins(:categories)
119
- .where(active: true, dokno_categories: { id: nil })
142
+ .where(active: true)
143
+ .where('review_due_at <= ?', Date.today + Dokno.config.article_review_prompt_days)
120
144
 
121
- records = records.updated_order if order == :updated
122
- records = records.newest_order if order == :newest
123
- records = records.view_order if order == :views
124
- records = records.alpha_order if order == :alpha
125
-
126
- records
145
+ apply_sort(records, order: order)
127
146
  end
128
147
 
129
- def self.parse_markdown(content)
130
- ActionController::Base.helpers.sanitize(
131
- MARKDOWN_PARSER.render(content),
132
- tags: Dokno.config.tag_whitelist,
133
- attributes: Dokno.config.attr_whitelist
134
- )
135
- end
136
-
137
- def self.template
138
- template_file = File.join(Rails.root, 'config', 'dokno_template.md')
139
- 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 })
140
154
 
141
- File.read(template_file).to_s
155
+ apply_sort(records, order: order)
142
156
  end
143
157
 
144
158
  def self.search(term:, category_id: nil, order: :updated)
@@ -152,10 +166,7 @@ module Dokno
152
166
  .includes(:categories_dokno_articles)
153
167
  .includes(:categories)
154
168
 
155
- records = records.updated_order if order == :updated
156
- records = records.newest_order if order == :newest
157
- records = records.view_order if order == :views
158
- records = records.alpha_order if order == :alpha
169
+ records = apply_sort(records, order: order)
159
170
 
160
171
  return records unless category_id.present?
161
172
 
@@ -169,6 +180,28 @@ module Dokno
169
180
  )
170
181
  end
171
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
+
172
205
  private
173
206
 
174
207
  # Ensure there isn't another Article with the same slug
@@ -188,7 +221,7 @@ module Dokno
188
221
  end
189
222
 
190
223
  def log_changes
191
- return if changes.blank?
224
+ return if changes.blank? && !reset_review_date
192
225
 
193
226
  meta_changes = changes.with_indifferent_access.slice(:slug, :title, :active)
194
227
  content_changes = changes.with_indifferent_access.slice(:summary, :markdown)
@@ -206,10 +239,26 @@ module Dokno
206
239
  content[:after] += values.last.to_s + ' '
207
240
  end
208
241
 
209
- return unless meta.present?
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
210
259
 
211
- diff = Diffy::SplitDiff.new(content[:before].squish, content[:after].squish, format: :html)
212
- logs << Log.new(username: editor_username, meta: meta.to_sentence, diff_left: diff.left, diff_right: diff.right)
260
+ def set_review_date
261
+ self.review_due_at = Date.today + Dokno.config.article_review_period
213
262
  end
214
263
  end
215
264
  end