dokno 1.0.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +130 -0
- data/Rakefile +22 -0
- data/app/assets/config/dokno_manifest.js +3 -0
- data/app/assets/javascripts/dokno.js +144 -0
- data/app/assets/javascripts/dokno/application.js +15 -0
- data/app/assets/javascripts/feather.min.js +13 -0
- data/app/assets/javascripts/init.js +5 -0
- data/app/assets/stylesheets/dokno/application.css +137 -0
- data/app/assets/stylesheets/dokno/tailwind.min.css +1 -0
- data/app/controllers/dokno/application_controller.rb +7 -0
- data/app/controllers/dokno/articles_controller.rb +115 -0
- data/app/controllers/dokno/categories_controller.rb +63 -0
- data/app/controllers/dokno/pagination_concern.rb +15 -0
- data/app/controllers/dokno/user_concern.rb +38 -0
- data/app/helpers/dokno/application_helper.rb +17 -0
- data/app/models/dokno/application_record.rb +5 -0
- data/app/models/dokno/article.rb +215 -0
- data/app/models/dokno/article_slug.rb +7 -0
- data/app/models/dokno/category.rb +100 -0
- data/app/models/dokno/log.rb +7 -0
- data/app/views/dokno/_article_formatting.html.erb +89 -0
- data/app/views/dokno/_article_panel.html.erb +112 -0
- data/app/views/dokno/_panel_formatting.html.erb +95 -0
- data/app/views/dokno/_reset_formatting.html.erb +55 -0
- data/app/views/dokno/articles/_article_form.html.erb +60 -0
- data/app/views/dokno/articles/edit.html.erb +7 -0
- data/app/views/dokno/articles/new.html.erb +7 -0
- data/app/views/dokno/articles/show.html.erb +130 -0
- data/app/views/dokno/categories/_category_form.html.erb +33 -0
- data/app/views/dokno/categories/edit.html.erb +7 -0
- data/app/views/dokno/categories/index.html.erb +96 -0
- data/app/views/dokno/categories/new.html.erb +7 -0
- data/app/views/layouts/dokno/application.html.erb +100 -0
- data/app/views/partials/_form_errors.html.erb +18 -0
- data/app/views/partials/_logs.html.erb +52 -0
- data/app/views/partials/_pagination.html.erb +35 -0
- data/config/routes.rb +11 -0
- data/db/migrate/20201203190330_baseline.rb +62 -0
- data/lib/dokno.rb +4 -0
- data/lib/dokno/config/config.rb +73 -0
- data/lib/dokno/engine.rb +27 -0
- data/lib/dokno/version.rb +3 -0
- data/lib/generators/dokno/install/install_generator.rb +13 -0
- data/lib/generators/dokno/templates/config/dokno_template.md +5 -0
- data/lib/generators/dokno/templates/config/initializers/dokno.rb +14 -0
- data/lib/tasks/dokno_tasks.rake +4 -0
- metadata +253 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
module Dokno
|
2
|
+
class Category < ApplicationRecord
|
3
|
+
belongs_to :parent,
|
4
|
+
class_name: 'Dokno::Category',
|
5
|
+
primary_key: 'id',
|
6
|
+
foreign_key: 'category_id',
|
7
|
+
optional: true,
|
8
|
+
inverse_of: :children
|
9
|
+
has_many :children,
|
10
|
+
class_name: 'Dokno::Category',
|
11
|
+
primary_key: 'id',
|
12
|
+
foreign_key: 'category_id',
|
13
|
+
dependent: :nullify,
|
14
|
+
inverse_of: :parent
|
15
|
+
|
16
|
+
has_and_belongs_to_many :articles
|
17
|
+
|
18
|
+
validates :name, :code, presence: true, uniqueness: true
|
19
|
+
validate :circular_parent_check
|
20
|
+
|
21
|
+
before_validation :set_code
|
22
|
+
|
23
|
+
# The display breadcrumb for the Category
|
24
|
+
def breadcrumb
|
25
|
+
crumbs = [name]
|
26
|
+
parent_category_id = category_id
|
27
|
+
|
28
|
+
loop do
|
29
|
+
break if parent_category_id.blank?
|
30
|
+
|
31
|
+
parent_category = self.class.find(parent_category_id)
|
32
|
+
crumbs.prepend parent_category.name
|
33
|
+
parent_category_id = parent_category.category_id
|
34
|
+
end
|
35
|
+
|
36
|
+
crumbs.join(' > ')
|
37
|
+
end
|
38
|
+
|
39
|
+
# All Articles in the Category, including all child Categories
|
40
|
+
def articles_in_branch(order: :updated)
|
41
|
+
records = Article
|
42
|
+
.includes(:categories_dokno_articles, :categories)
|
43
|
+
.joins(:categories)
|
44
|
+
.where(dokno_categories: { id: self.class.branch(parent_category_id: id).pluck(:id) })
|
45
|
+
|
46
|
+
records = records.updated_order if order == :updated
|
47
|
+
records = records.newest_order if order == :newest
|
48
|
+
records = records.view_order if order == :views
|
49
|
+
records = records.alpha_order if order == :alpha
|
50
|
+
|
51
|
+
records
|
52
|
+
end
|
53
|
+
|
54
|
+
def branch
|
55
|
+
self.class.branch(parent_category_id: id)
|
56
|
+
end
|
57
|
+
|
58
|
+
# The given Category and all child Categories. Useful for filtering associated articles.
|
59
|
+
def self.branch(parent_category_id:, at_top: true)
|
60
|
+
return if parent_category_id.blank?
|
61
|
+
|
62
|
+
categories = []
|
63
|
+
parent_category = find(parent_category_id)
|
64
|
+
child_categories = parent_category.children.to_a
|
65
|
+
|
66
|
+
child_categories.each do |child_category|
|
67
|
+
categories << child_category << branch(parent_category_id: child_category.id, at_top: false)
|
68
|
+
end
|
69
|
+
|
70
|
+
categories.prepend parent_category if at_top
|
71
|
+
categories.flatten
|
72
|
+
end
|
73
|
+
|
74
|
+
# HTML markup for Category SELECT field OPTION lists
|
75
|
+
def self.select_option_markup(selected_category_codes: nil, exclude_category_id: nil)
|
76
|
+
breadcrumbs = all
|
77
|
+
.reject { |category| category.id == exclude_category_id.to_i }
|
78
|
+
.map { |category| { code: category.code, name: category.breadcrumb } }
|
79
|
+
breadcrumbs.sort_by { |category_hash| category_hash[:name] }.map do |category_hash|
|
80
|
+
selected = selected_category_codes&.include?(category_hash[:code])
|
81
|
+
%(<option value="#{category_hash[:code]}" #{'selected="selected"' if selected}>#{category_hash[:name]}</option>)
|
82
|
+
end.join
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# Never allow setting of parent to self
|
88
|
+
def circular_parent_check
|
89
|
+
return unless persisted? && id.to_i == category_id.to_i
|
90
|
+
|
91
|
+
errors.add(:category_id, "can't set parent to self")
|
92
|
+
end
|
93
|
+
|
94
|
+
def set_code
|
95
|
+
return unless name.present?
|
96
|
+
|
97
|
+
self.code = name.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
<style>
|
2
|
+
/* Article formatting */
|
3
|
+
.dokno-article-content-markup {
|
4
|
+
font-weight: 300;
|
5
|
+
line-height: 1.75rem;
|
6
|
+
font-size: 1rem;
|
7
|
+
}
|
8
|
+
|
9
|
+
.dokno-article-content-markup code {
|
10
|
+
font-size: 80%;
|
11
|
+
background-color: #eee;
|
12
|
+
padding: 3px 5px;
|
13
|
+
border-radius: 3px;
|
14
|
+
}
|
15
|
+
|
16
|
+
.dokno-article-content-markup b,
|
17
|
+
.dokno-article-content-markup strong {
|
18
|
+
font-weight: 500;
|
19
|
+
}
|
20
|
+
|
21
|
+
.dokno-article-content-markup p,
|
22
|
+
.dokno-article-content-markup ul,
|
23
|
+
.dokno-article-content-markup ol,
|
24
|
+
.dokno-article-content-markup hr,
|
25
|
+
.dokno-article-content-markup table {
|
26
|
+
margin-bottom: 1.25rem;
|
27
|
+
}
|
28
|
+
|
29
|
+
.dokno-article-content-markup h1,
|
30
|
+
.dokno-article-content-markup h2,
|
31
|
+
.dokno-article-content-markup h3,
|
32
|
+
.dokno-article-content-markup h4,
|
33
|
+
.dokno-article-content-markup h5,
|
34
|
+
.dokno-article-content-markup h6 {
|
35
|
+
margin-bottom: 1.25rem;
|
36
|
+
font-weight: 600;
|
37
|
+
}
|
38
|
+
|
39
|
+
.dokno-article-content-markup h1 {
|
40
|
+
font-size: 2rem;
|
41
|
+
}
|
42
|
+
|
43
|
+
.dokno-article-content-markup h2 {
|
44
|
+
font-size: 1.625rem;
|
45
|
+
}
|
46
|
+
|
47
|
+
.dokno-article-content-markup h3 {
|
48
|
+
font-size: 1.25rem;
|
49
|
+
}
|
50
|
+
|
51
|
+
.dokno-article-content-markup h4 {
|
52
|
+
font-size: 1rem;
|
53
|
+
}
|
54
|
+
|
55
|
+
.dokno-article-content-markup blockquote {
|
56
|
+
border-left: 4px solid #edf2f7;
|
57
|
+
padding-left: 1.25rem;
|
58
|
+
}
|
59
|
+
|
60
|
+
.dokno-article-content-markup ul,
|
61
|
+
.dokno-article-content-markup ol {
|
62
|
+
list-style-position: inside;
|
63
|
+
}
|
64
|
+
|
65
|
+
.dokno-article-content-markup ul {
|
66
|
+
list-style-type: disc;
|
67
|
+
}
|
68
|
+
|
69
|
+
.dokno-article-content-markup ol {
|
70
|
+
list-style-type: decimal;
|
71
|
+
}
|
72
|
+
|
73
|
+
.dokno-article-content-markup table {
|
74
|
+
table-layout: auto;
|
75
|
+
}
|
76
|
+
|
77
|
+
.dokno-article-content-markup table th,
|
78
|
+
.dokno-article-content-markup table td {
|
79
|
+
padding-left: 1rem;
|
80
|
+
padding-right: 1rem;
|
81
|
+
padding-top: 0.5rem;
|
82
|
+
padding-bottom: 0.5rem;
|
83
|
+
font-weight: normal;
|
84
|
+
}
|
85
|
+
|
86
|
+
.dokno-article-content-markup table td {
|
87
|
+
border-width: 1px;
|
88
|
+
}
|
89
|
+
</style>
|
@@ -0,0 +1,112 @@
|
|
1
|
+
<!-- DOKNO SLIDE-OUT PANEL -->
|
2
|
+
<!-- This file is injected into the host layout when included via render ` 'dokno/article_panel' `. -->
|
3
|
+
<!-- It provides the necessary markup, CSS, and JS to support in-context flyout article panels. -->
|
4
|
+
|
5
|
+
<!-- Reset styles -->
|
6
|
+
<%= render 'dokno/reset_formatting' %>
|
7
|
+
|
8
|
+
<!-- Slide-out panel styles -->
|
9
|
+
<%= render 'dokno/panel_formatting' %>
|
10
|
+
|
11
|
+
<!-- Article content styles -->
|
12
|
+
<%= render 'dokno/article_formatting' %>
|
13
|
+
|
14
|
+
<!-- Slide-out panel markup -->
|
15
|
+
<div id="dokno-panel-container">
|
16
|
+
<div id="dokno-panel-close" class="dokno-hidden">
|
17
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="#eee" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x-circle">
|
18
|
+
<circle cx="12" cy="12" r="10"></circle>
|
19
|
+
<line x1="15" y1="9" x2="9" y2="15"></line>
|
20
|
+
<line x1="9" y1="9" x2="15" y2="15"></line>
|
21
|
+
</svg>
|
22
|
+
</div>
|
23
|
+
<div id="dokno-panel-title"></div>
|
24
|
+
<div id="dokno-panel-summary"></div>
|
25
|
+
<div id="dokno-panel-markdown" class="dokno-article-content-markup"></div>
|
26
|
+
<div id="dokno-panel-footer"></div>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<!-- Slide-out panel behavior -->
|
30
|
+
<script>
|
31
|
+
function doknoOpenPanel(slug) {
|
32
|
+
if (slug == dokno__slug) {
|
33
|
+
doknoClosePanel();
|
34
|
+
return true;
|
35
|
+
}
|
36
|
+
dokno__slug = slug;
|
37
|
+
|
38
|
+
// Can't use fetch API; IE
|
39
|
+
const request = new XMLHttpRequest();
|
40
|
+
|
41
|
+
request.open('GET', '<%= Dokno::Engine.routes.url_helpers.root_path %>article_panel/' + slug, true);
|
42
|
+
request.onload = function() {
|
43
|
+
if (request.readyState == 4 && request.status == 200) {
|
44
|
+
try {
|
45
|
+
var data = JSON.parse(request.responseText);
|
46
|
+
} catch(err) {
|
47
|
+
console.log("Dokno: " + err.message + " in " + request.responseText);
|
48
|
+
return;
|
49
|
+
}
|
50
|
+
|
51
|
+
// Populate panel
|
52
|
+
dokno__id = data.id;
|
53
|
+
dokno__panel_title.innerHTML = data.title;
|
54
|
+
dokno__panel_summary.innerHTML = data.summary;
|
55
|
+
dokno__panel_footer.innerHTML = data.footer;
|
56
|
+
dokno__panel_markdown.innerHTML = data.markdown;
|
57
|
+
|
58
|
+
doknoRevealAppropriatePanelSections(data);
|
59
|
+
|
60
|
+
// Reveal panel
|
61
|
+
dokno__panel.scrollTop = 0;
|
62
|
+
dokno__panel.classList.add('open');
|
63
|
+
document.body.classList.add('dokno-no-scroll');
|
64
|
+
|
65
|
+
// Reveal fixed close icon
|
66
|
+
setTimeout(function() { dokno__close.classList.remove('dokno-hidden'); }, 200);
|
67
|
+
|
68
|
+
// Close on escape
|
69
|
+
document.addEventListener('keydown', dokno__keydown_listener, false);
|
70
|
+
}
|
71
|
+
};
|
72
|
+
|
73
|
+
request.send();
|
74
|
+
}
|
75
|
+
|
76
|
+
function doknoClosePanel() {
|
77
|
+
dokno__close.classList.add('dokno-hidden');
|
78
|
+
dokno__panel.classList.remove('open');
|
79
|
+
document.body.classList.remove('dokno-no-scroll');
|
80
|
+
document.removeEventListener('keydown', dokno__keydown_listener, false);
|
81
|
+
|
82
|
+
dokno__slug = null;
|
83
|
+
}
|
84
|
+
|
85
|
+
function doknoRevealAppropriatePanelSections(data) {
|
86
|
+
const markdown_classes = dokno__panel_markdown.classList;
|
87
|
+
const summary_classes = dokno__panel_summary.classList;
|
88
|
+
|
89
|
+
if (data.markdown != '') {
|
90
|
+
markdown_classes.remove('dokno-hidden');
|
91
|
+
if (data.summary == '') { summary_classes.add('dokno-hidden'); } else { summary_classes.remove('dokno-hidden'); }
|
92
|
+
} else {
|
93
|
+
markdown_classes.add('dokno-hidden');
|
94
|
+
summary_classes.remove('dokno-hidden');
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
const dokno__panel = document.getElementById('dokno-panel-container');
|
99
|
+
const dokno__close = document.querySelector('div#dokno-panel-close');
|
100
|
+
const dokno__panel_title = document.getElementById('dokno-panel-title');
|
101
|
+
const dokno__panel_summary = document.getElementById('dokno-panel-summary');
|
102
|
+
const dokno__panel_footer = document.getElementById('dokno-panel-footer');
|
103
|
+
const dokno__panel_markdown = document.getElementById('dokno-panel-markdown');
|
104
|
+
|
105
|
+
const dokno__keydown_listener = function(e) {
|
106
|
+
if (e.key === 'Escape') { doknoClosePanel(); }
|
107
|
+
}
|
108
|
+
|
109
|
+
dokno__close.onclick = function() { doknoClosePanel(); }
|
110
|
+
|
111
|
+
var dokno__id, dokno__slug;
|
112
|
+
</script>
|
@@ -0,0 +1,95 @@
|
|
1
|
+
<style>
|
2
|
+
.dokno-no-scroll {
|
3
|
+
overflow: hidden !important;
|
4
|
+
}
|
5
|
+
|
6
|
+
.dokno-hidden {
|
7
|
+
display: none !important;
|
8
|
+
}
|
9
|
+
|
10
|
+
div#dokno-panel-container {
|
11
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
|
12
|
+
font-size: 1rem;
|
13
|
+
line-height: 1.5rem;
|
14
|
+
right: -600px;
|
15
|
+
z-index: 9999;
|
16
|
+
position: fixed;
|
17
|
+
top: 0;
|
18
|
+
bottom: 0;
|
19
|
+
width: 600px;
|
20
|
+
max-width: 100vw;
|
21
|
+
overflow: hidden;
|
22
|
+
overflow-y: auto;
|
23
|
+
background-color: #fff;
|
24
|
+
color: #2d3748;
|
25
|
+
transition: all .15s ease-in-out;
|
26
|
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
27
|
+
}
|
28
|
+
|
29
|
+
div#dokno-panel-container.open {
|
30
|
+
right: 0;
|
31
|
+
}
|
32
|
+
|
33
|
+
div#dokno-panel-close {
|
34
|
+
position: fixed;
|
35
|
+
background-color: transparent;
|
36
|
+
color: #222;
|
37
|
+
cursor: pointer;
|
38
|
+
top: 1.5rem;
|
39
|
+
right: 2.5rem;
|
40
|
+
}
|
41
|
+
|
42
|
+
div#dokno-panel-title {
|
43
|
+
padding: 2.5rem;
|
44
|
+
font-size: 2.25rem;
|
45
|
+
line-height: 2.75rem;
|
46
|
+
font-weight: 300;
|
47
|
+
background-color: rgb(42,67,101);
|
48
|
+
color: #ebf8ff;
|
49
|
+
}
|
50
|
+
|
51
|
+
div#dokno-panel-title div#article-deprecated-alert {
|
52
|
+
font-size: 1rem;
|
53
|
+
line-height: 1.5rem;
|
54
|
+
padding: 1rem;
|
55
|
+
margin-bottom: 1rem;
|
56
|
+
border-top-width: 4px;
|
57
|
+
border-radius: .25rem;
|
58
|
+
border-top-style: solid;
|
59
|
+
border-color: rgb(116,66,16);
|
60
|
+
background-color: rgb(183,121,31);
|
61
|
+
color: rgb(255,255,255);
|
62
|
+
}
|
63
|
+
|
64
|
+
div#dokno-panel-title > span,
|
65
|
+
div#dokno-panel-footer > svg {
|
66
|
+
cursor: pointer;
|
67
|
+
}
|
68
|
+
|
69
|
+
div#dokno-panel-summary {
|
70
|
+
font-size: 1.5rem;
|
71
|
+
line-height: 2.25rem;
|
72
|
+
padding: 2.5rem;
|
73
|
+
font-weight: 300;
|
74
|
+
}
|
75
|
+
|
76
|
+
div#dokno-panel-markdown {
|
77
|
+
padding: 2.5rem;
|
78
|
+
background-color: #f7fafc;
|
79
|
+
}
|
80
|
+
|
81
|
+
div#dokno-panel-footer {
|
82
|
+
text-align: center;
|
83
|
+
position: relative;
|
84
|
+
padding: 2.5rem;
|
85
|
+
color: #999;
|
86
|
+
background-color: #edf2f7;
|
87
|
+
font-weight: 300;
|
88
|
+
font-size: 1rem;
|
89
|
+
line-height: 1.5rem;
|
90
|
+
}
|
91
|
+
|
92
|
+
div#dokno-panel-footer p {
|
93
|
+
margin: .5rem 0;
|
94
|
+
}
|
95
|
+
</style>
|
@@ -0,0 +1,55 @@
|
|
1
|
+
<style>
|
2
|
+
/* Resets */
|
3
|
+
.dokno-article-content-markup blockquote,
|
4
|
+
.dokno-article-content-markup dl,
|
5
|
+
.dokno-article-content-markup dd,
|
6
|
+
.dokno-article-content-markup h1,
|
7
|
+
.dokno-article-content-markup h2,
|
8
|
+
.dokno-article-content-markup h3,
|
9
|
+
.dokno-article-content-markup h4,
|
10
|
+
.dokno-article-content-markup h5,
|
11
|
+
.dokno-article-content-markup h6,
|
12
|
+
.dokno-article-content-markup hr,
|
13
|
+
.dokno-article-content-markup figure,
|
14
|
+
.dokno-article-content-markup p,
|
15
|
+
.dokno-article-content-markup pre {
|
16
|
+
margin: 0;
|
17
|
+
}
|
18
|
+
|
19
|
+
.dokno-article-content-markup h1,
|
20
|
+
.dokno-article-content-markup h2,
|
21
|
+
.dokno-article-content-markup h3,
|
22
|
+
.dokno-article-content-markup h4,
|
23
|
+
.dokno-article-content-markup h5,
|
24
|
+
.dokno-article-content-markup h6 {
|
25
|
+
font-size: inherit;
|
26
|
+
font-weight: inherit;
|
27
|
+
}
|
28
|
+
|
29
|
+
.dokno-article-content-markup ol,
|
30
|
+
.dokno-article-content-markup ul {
|
31
|
+
list-style: none;
|
32
|
+
margin: 0;
|
33
|
+
padding: 0;
|
34
|
+
}
|
35
|
+
|
36
|
+
.dokno-article-content-markup img,
|
37
|
+
.dokno-article-content-markup svg,
|
38
|
+
.dokno-article-content-markup video,
|
39
|
+
.dokno-article-content-markup canvas,
|
40
|
+
.dokno-article-content-markup audio,
|
41
|
+
.dokno-article-content-markup iframe,
|
42
|
+
.dokno-article-content-markup embed,
|
43
|
+
.dokno-article-content-markup object {
|
44
|
+
display: block;
|
45
|
+
vertical-align: middle;
|
46
|
+
}
|
47
|
+
|
48
|
+
.dokno-article-content-markup *,
|
49
|
+
.dokno-article-content-markup ::before,
|
50
|
+
.dokno-article-content-markup ::after {
|
51
|
+
border-width: 0;
|
52
|
+
border-style: solid;
|
53
|
+
border-color: currentColor;
|
54
|
+
}
|
55
|
+
</style>
|