Class: Dokno::Article

Inherits:
ApplicationRecord show all
Defined in:
app/models/dokno/article.rb

Constant Summary collapse

MARKDOWN_PARSER =
Redcarpet::Markdown.new(Redcarpet::Render::HTML, autolink: true, tables: true)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#editor_usernameObject

Returns the value of attribute editor_username.



15
16
17
# File 'app/models/dokno/article.rb', line 15

def editor_username
  @editor_username
end

#reset_review_dateObject

Returns the value of attribute reset_review_date.



15
16
17
# File 'app/models/dokno/article.rb', line 15

def reset_review_date
  @reset_review_date
end

#review_notesObject

Returns the value of attribute review_notes.



15
16
17
# File 'app/models/dokno/article.rb', line 15

def review_notes
  @review_notes
end

Class Method Details

.apply_sort(records, order: :updated) ⇒ Object



198
199
200
201
202
203
# File 'app/models/dokno/article.rb', line 198

def self.apply_sort(records, order: :updated)
  order_scope = "#{order}_order"
  return records unless records.respond_to? order_scope

  records.send(order_scope.to_sym)
end

.parse_markdown(content) ⇒ Object



183
184
185
186
187
188
189
# File 'app/models/dokno/article.rb', line 183

def self.parse_markdown(content)
  ActionController::Base.helpers.sanitize(
    MARKDOWN_PARSER.render(content),
    tags:       Dokno.config.tag_whitelist,
    attributes: Dokno.config.attr_whitelist
  )
end

.search(term:, category_id: nil, order: :updated) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'app/models/dokno/article.rb', line 158

def self.search(term:, category_id: nil, order: :updated)
  records = where(
    'LOWER(title) LIKE :search_term OR '\
    'LOWER(summary) LIKE :search_term OR '\
    'LOWER(markdown) LIKE :search_term OR '\
    'LOWER(slug) LIKE :search_term',
    search_term: "%#{term.downcase}%"
  )
  .includes(:categories_dokno_articles)
  .includes(:categories)

  records = apply_sort(records, order: order)

  return records unless category_id.present?

  # Scope to the context category and its children
  records
    .joins(:categories)
    .where(
      dokno_categories: {
        id: Category.branch(parent_category_id: category_id).pluck(:id)
      }
    )
end

.templateObject



191
192
193
194
195
196
# File 'app/models/dokno/article.rb', line 191

def self.template
  template_file = File.join(Rails.root, 'config', 'dokno_template.md')
  return unless File.exist?(template_file)

  File.read(template_file).to_s
end

.uncategorized(order: :updated) ⇒ Object

All uncategorized Articles



149
150
151
152
153
154
155
156
# File 'app/models/dokno/article.rb', line 149

def self.uncategorized(order: :updated)
  records = Article
    .includes(:categories_dokno_articles, :categories)
    .left_joins(:categories)
    .where(dokno_categories: { id: nil })

  apply_sort(records, order: order)
end

.up_for_review(order: :updated) ⇒ Object

All articles up for review



139
140
141
142
143
144
145
146
# File 'app/models/dokno/article.rb', line 139

def self.up_for_review(order: :updated)
  records = Article
    .includes(:categories_dokno_articles, :categories)
    .where(active: true)
    .where('review_due_at <= ?', Date.today + Dokno.config.article_review_prompt_days)

  apply_sort(records, order: order)
end

Instance Method Details

#category_name_list(context_category_id: nil, order: nil, search_term: nil) ⇒ Object

Breadcrumbs for all associated categories; limits to sub-categories if in the context of a category



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'app/models/dokno/article.rb', line 68

def category_name_list(context_category_id: nil, order: nil, search_term: nil)
  return '' if categories.blank?

  names = Category
    .joins(articles_dokno_categories: :article)
    .where(dokno_articles_categories: { article_id: id })
    .all
    .map do |category|
      next if context_category_id == category.id

      "<a class='underline' href='#{article_index_path(category.code)}?search_term="\
      "#{CGI.escape(search_term.to_s)}&order=#{CGI.escape(order.to_s)}'>#{category.name}</a>"
    end.compact

  return '' if names.blank?

  list = (context_category_id.present? ? 'In other category' : 'Category').pluralize(names.length)
  list += ': ' + names.to_sentence
  list.html_safe
end

#contributorsObject



128
129
130
131
132
133
134
135
136
# File 'app/models/dokno/article.rb', line 128

def contributors
  logs
    .where('meta LIKE ? OR meta LIKE ?', '%Markdown%', '%Summary%')
    .pluck(:username)
    .reject(&:blank?)
    .uniq
    .sort
    .to_sentence
end

#host_panel_hashObject

Hash returned for the ajax-fetched slide-in article panel for the host app



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'app/models/dokno/article.rb', line 90

def host_panel_hash
  footer = %Q(
    <p><a href="#{article_path(slug)}" target="_blank" title="Open in the knowledgebase">Print / email / edit / delete</a></p>
    <p>#{categories.present? ? category_name_list : 'Uncategorized'}</p>
    <p>Knowledgebase slug : #{slug}</p>
    <p>Last updated : #{time_ago_in_words(updated_at)} ago</p>
  )

  footer += "<p>Contributors : #{contributors}</p>" if contributors.present?

  title_markup = %Q(
    <span title="Open full article page" onclick="window.open('#{article_path(slug)}');">#{title}</span>
  )

  unless active
    title_markup = title_markup.prepend(
      %Q(
        <div id="article-deprecated-alert">
          This article is no longer active
        </div>
      )
    )
  end

  {
    title:    title_markup,
    id:       id,
    slug:     slug,
    summary:  summary.presence || (markdown_parsed.present? ? '' : 'No content'),
    markdown: markdown_parsed,
    footer:   footer
  }
end

#markdownObject



33
34
35
# File 'app/models/dokno/article.rb', line 33

def markdown
  super || ''
end

#markdown_parsedObject



63
64
65
# File 'app/models/dokno/article.rb', line 63

def markdown_parsed
  self.class.parse_markdown markdown
end


124
125
126
# File 'app/models/dokno/article.rb', line 124

def permalink(base_url)
  "#{base_url}#{article_path(slug)}"
end

#reading_timeObject



37
38
39
40
41
42
43
# File 'app/models/dokno/article.rb', line 37

def reading_time
  minutes_decimal = (("#{summary} #{markdown}".squish.scan(/[\w-]+/).size) / 200.0)
  approx_minutes  = minutes_decimal.ceil
  return '' unless approx_minutes > 1

  "~ #{approx_minutes} minutes"
end

#review_due_atObject



29
30
31
# File 'app/models/dokno/article.rb', line 29

def review_due_at
  super || (Date.today + 30.years)
end

#review_due_daysObject



45
46
47
# File 'app/models/dokno/article.rb', line 45

def review_due_days
  (review_due_at.to_date - Date.today).to_i
end

#review_due_days_stringObject



53
54
55
56
57
58
59
60
61
# File 'app/models/dokno/article.rb', line 53

def review_due_days_string
  if review_due_days.positive?
    "This article is up for an accuracy / relevance review in #{review_due_days} #{'day'.pluralize(review_due_days)}"
  elsif review_due_days.negative?
    "This article was up for an accuracy / relevance review #{review_due_days.abs} #{'day'.pluralize(review_due_days)} ago"
  else
    "This article is up for an accuracy / relevance review today"
  end
end

#up_for_review?Boolean

Returns:

  • (Boolean)


49
50
51
# File 'app/models/dokno/article.rb', line 49

def up_for_review?
  active && review_due_days <= Dokno.config.article_review_prompt_days
end