jekyll_outline 1.2.5 → 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/.rubocop.yml +7 -1
- data/CHANGELOG.md +16 -0
- data/README.md +195 -20
- data/jekyll_outline.gemspec +12 -11
- data/lib/jekyll_outline/version.rb +1 -1
- data/lib/outline_tag.rb +39 -224
- data/lib/structure/a_page_enrichment.rb +46 -0
- data/lib/structure/outline.rb +156 -0
- data/lib/structure/section.rb +38 -0
- data/lib/structure/yaml_parser.rb +58 -0
- data/spec/outline_spec.rb +148 -6
- data/spec/spec_helper.rb +17 -4
- data/spec/status_persistence.txt +3 -3
- data/spec/yaml_parser_spec.rb +84 -0
- metadata +15 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 88b2d41ecbd19787d53682b396bd2bbf9eabccf2ffa366026f4792079c3f827e
|
4
|
+
data.tar.gz: 44b1fbda200ea5c9a836538fc2f8e3565bef880b1dec9a77bedec513ef15f028
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5d432be906fbe832e4db55e2333c969b7b9beda06b7c391a5f43b6cd13771646cd285847d13510678adc548db642fa3f3eb3d7521737d9e0b1f48f28eaffd991
|
7
|
+
data.tar.gz: 056b5a89bdbb45d9f56016f1fd17be96c7d7a6e000c5cefbfecad28c496e540c60bcf712a024107aa34e789f59dbefdcd09d9a096077e7955ea852ae3176c41d
|
data/.rubocop.yml
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
plugins:
|
2
2
|
# - rubocop-jekyll
|
3
3
|
- rubocop-md
|
4
4
|
- rubocop-performance
|
@@ -37,12 +37,18 @@ Metrics/BlockLength:
|
|
37
37
|
- jekyll_plugin_support.gemspec
|
38
38
|
Max: 30
|
39
39
|
|
40
|
+
Metrics/ClassLength:
|
41
|
+
Max: 150
|
42
|
+
|
40
43
|
Metrics/CyclomaticComplexity:
|
41
44
|
Max: 25
|
42
45
|
|
43
46
|
Metrics/MethodLength:
|
44
47
|
Max: 50
|
45
48
|
|
49
|
+
Metrics/ParameterLists:
|
50
|
+
Enabled: false
|
51
|
+
|
46
52
|
Metrics/PerceivedComplexity:
|
47
53
|
Max: 25
|
48
54
|
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 1.3.0 / 2025-06-04
|
4
|
+
|
5
|
+
* Now relies on `jekyll_plugin_support` v3.1.0.
|
6
|
+
* Fixed missing </div>s.
|
7
|
+
* Reimplemented using nested classes, concluding with a call `to_s`
|
8
|
+
instead of implementing crazy nesting tracking.
|
9
|
+
* The `fields` parameter was renamed to `pattern`, however for compatibility, both parameter names are accepted.
|
10
|
+
If both are provided, `pattern` has priority.
|
11
|
+
|
12
|
+
|
13
|
+
## 1.2.6 / 2025-01-03
|
14
|
+
|
15
|
+
* Added `exclude_from_outline` optional front matter YAML element.
|
16
|
+
* Ignores all files named `index`, regardless of file type.
|
17
|
+
|
18
|
+
|
3
19
|
## 1.2.5 / 2024-08-20
|
4
20
|
|
5
21
|
* Added configurable error handling
|
data/README.md
CHANGED
@@ -3,14 +3,194 @@
|
|
3
3
|
`jekyll_outline` Jekyll tag plugin that creates a clickable table of contents.
|
4
4
|
|
5
5
|
|
6
|
+
## Order
|
7
|
+
|
8
|
+
This plugin requires every page in a collection to have an entry for
|
9
|
+
<code>order</code> in its front matter.
|
10
|
+
The value of <code>order</code> must be an integer.
|
11
|
+
This entry is normally used for sorting the pages.
|
12
|
+
|
13
|
+
The front matter for [this page](https://mslinn.com/jekyll_plugins/jekyll_outline.html) is:
|
14
|
+
|
15
|
+
```yaml
|
16
|
+
---
|
17
|
+
categories: [Jekyll]
|
18
|
+
date: 2020-10-03
|
19
|
+
description: "Organizes the index of a collection into chapters."
|
20
|
+
last_modified_at: 2025-01-09
|
21
|
+
layout: jekyll
|
22
|
+
order: 100
|
23
|
+
title: <span class="code">jekyll_outline</span>
|
24
|
+
---
|
25
|
+
```
|
26
|
+
|
27
|
+
|
28
|
+
## Examples
|
29
|
+
|
30
|
+
You can see this plugin in action in most of the index pages of [`mslinn.com`](https://mslinn.com).
|
31
|
+
Following is the source for two of them:
|
32
|
+
|
33
|
+
### [Django / Oscar](https://mslinn.com/django/index.html)
|
34
|
+
|
35
|
+
This is the simplest possible outline, without images.
|
36
|
+
|
37
|
+
```text
|
38
|
+
{% outline attribution django %}
|
39
|
+
0: Django / Oscar Evaluation
|
40
|
+
400: Notes
|
41
|
+
800: Digging Deeper
|
42
|
+
1900: Debugging
|
43
|
+
2700: Production
|
44
|
+
{% endoutline %}
|
45
|
+
```
|
46
|
+
|
47
|
+
### [A/V Studio Technology](https://mslinn.com/av_studio/index.html)
|
48
|
+
|
49
|
+
This outline features images associated with specific entries.
|
50
|
+
|
51
|
+
```html
|
52
|
+
{% outline attribution av_studio %}
|
53
|
+
0: Production Infrastructure
|
54
|
+
150: Audio
|
55
|
+
200: Video
|
56
|
+
300: RME TotalMix
|
57
|
+
400: OBS Studio
|
58
|
+
500: Pro Tools
|
59
|
+
550: Ableton Live & Push
|
60
|
+
600: Other Music Software
|
61
|
+
700: MIDI Hardware & Software
|
62
|
+
800: Davinci Resolve
|
63
|
+
1000: Computer Analysis
|
64
|
+
2000: Music Theory
|
65
|
+
3000: Business
|
66
|
+
4000: General
|
67
|
+
{% endoutline %}
|
68
|
+
|
69
|
+
<div style="display: none">
|
70
|
+
{% img
|
71
|
+
align="right"
|
72
|
+
class=""
|
73
|
+
id="outline_150"
|
74
|
+
src="/av_studio/images/everse8/everse8d.webp"
|
75
|
+
size="eighthsize"
|
76
|
+
style="margin-top: 0"
|
77
|
+
wrapper_class="clear"
|
78
|
+
%}
|
79
|
+
{% img
|
80
|
+
align="right"
|
81
|
+
class=""
|
82
|
+
id="outline_200"
|
83
|
+
src="./images/equipment/sony_a7iii/sony_a7iii.webp"
|
84
|
+
size="eighthsize"
|
85
|
+
style="margin-top: 0"
|
86
|
+
wrapper_class="clear"
|
87
|
+
%}
|
88
|
+
{% img
|
89
|
+
align="right"
|
90
|
+
class=""
|
91
|
+
id="outline_300"
|
92
|
+
src="./images/rme/rme_logo.webp"
|
93
|
+
size="eighthsize"
|
94
|
+
style="margin-top: 0"
|
95
|
+
wrapper_class="clear"
|
96
|
+
%}
|
97
|
+
{% img
|
98
|
+
align="right"
|
99
|
+
class=""
|
100
|
+
id="outline_400"
|
101
|
+
src="./images/obsStudio/obs_logo.webp"
|
102
|
+
size="eighthsize"
|
103
|
+
style="margin-top: 0"
|
104
|
+
wrapper_class="clear"
|
105
|
+
%}
|
106
|
+
{% img
|
107
|
+
align="right"
|
108
|
+
class=""
|
109
|
+
id="outline_500"
|
110
|
+
src="./images/proTools/proToolsLogo.webp"
|
111
|
+
size="eighthsize"
|
112
|
+
style="margin-top: 0"
|
113
|
+
wrapper_class="clear"
|
114
|
+
%}
|
115
|
+
{% img
|
116
|
+
align="right"
|
117
|
+
class=""
|
118
|
+
id="outline_550"
|
119
|
+
src="./images/ableton/ableton_live_logo.webp"
|
120
|
+
size="eighthsize"
|
121
|
+
style="margin-top: 0"
|
122
|
+
wrapper_class="clear"
|
123
|
+
%}
|
124
|
+
{% img
|
125
|
+
align="right"
|
126
|
+
class="rounded"
|
127
|
+
id="outline_600"
|
128
|
+
src="./images/music21.webp"
|
129
|
+
size="eighthsize"
|
130
|
+
style="margin-top: 0"
|
131
|
+
wrapper_class="clear"
|
132
|
+
%}
|
133
|
+
{% img
|
134
|
+
align="right"
|
135
|
+
class=""
|
136
|
+
id="outline_700"
|
137
|
+
src="./images/midi/MIDI_logo.webp"
|
138
|
+
size="eighthsize"
|
139
|
+
style="margin-top: 0"
|
140
|
+
wrapper_class="clear"
|
141
|
+
%}
|
142
|
+
{% img
|
143
|
+
align="right"
|
144
|
+
class=""
|
145
|
+
id="outline_800"
|
146
|
+
src="./images/davinci_resolve/daVinci_resolve_logo.webp"
|
147
|
+
size="eighthsize"
|
148
|
+
style="margin-top: 0"
|
149
|
+
wrapper_class="clear"
|
150
|
+
%}
|
151
|
+
{% img
|
152
|
+
align="right"
|
153
|
+
class="rounded"
|
154
|
+
id="outline_2000"
|
155
|
+
src="./images/music_theory.webp"
|
156
|
+
size="eighthsize"
|
157
|
+
style="margin-top: 0"
|
158
|
+
wrapper_class="clear"
|
159
|
+
%}
|
160
|
+
{% img
|
161
|
+
align="right"
|
162
|
+
class=""
|
163
|
+
id="outline_4000"
|
164
|
+
src="./images/handsfree/pageflip_firefly.webp"
|
165
|
+
size="eighthsize"
|
166
|
+
style="margin-top: 0"
|
167
|
+
wrapper_class="clear"
|
168
|
+
%}
|
169
|
+
</div>
|
170
|
+
|
171
|
+
{% outline_js wrap_in_script_tag %}
|
172
|
+
```
|
173
|
+
|
174
|
+
|
6
175
|
## Usage
|
7
176
|
|
8
|
-
|
177
|
+
All files in a collection are included in the outline,
|
178
|
+
except for those whose name starts with `index`,
|
179
|
+
and those with the following in their front matter:
|
180
|
+
|
181
|
+
```yaml
|
182
|
+
exclude_from_outline: true
|
183
|
+
```
|
184
|
+
|
185
|
+
Note that Jekyll requires all documents in a collection to have a value for `order` in their front matter.
|
186
|
+
This value is ignored by `outline_tag` if `exclude_from_outline` has a truthy value.
|
9
187
|
|
10
|
-
|
188
|
+
The following examples are taken from [`demo/index.html`](demo/index.html).
|
189
|
+
|
190
|
+
Sort by the `order` field:
|
11
191
|
|
12
192
|
```html
|
13
|
-
{% outline attribution
|
193
|
+
{% outline attribution pattern="<b> title </b> – <i> description </i>" stuff %}
|
14
194
|
000: A Topic 0..19
|
15
195
|
020: A Topic 20..39
|
16
196
|
040: A Topic 40..
|
@@ -20,7 +200,7 @@ Sort by `order` field:
|
|
20
200
|
Sort by `title` field:
|
21
201
|
|
22
202
|
```html
|
23
|
-
{% outline attribution sort_by_title
|
203
|
+
{% outline attribution sort_by_title pattern="<b> title </b> – <i> description </i>" stuff %}
|
24
204
|
000: B Topic 0..19
|
25
205
|
020: B Topic 20..39
|
26
206
|
040: B Topic 40..
|
@@ -51,21 +231,21 @@ By default, each displayed entry consists of a document title,
|
|
51
231
|
wrapped within an <a href> HTML tag that links to the page for that entry,
|
52
232
|
followed by an indication of whether the document is visible (a draft) or not.
|
53
233
|
|
54
|
-
Entry can also include following
|
234
|
+
Entry can also include following pattern:
|
55
235
|
`draft`, `categories`, `description`, `date`, `last_modified` or `last_modified_at`, `layout`, `order`, `title`, `slug`,
|
56
|
-
`ext`,
|
236
|
+
`ext`, and `tags`.
|
57
237
|
|
58
|
-
Specify the
|
238
|
+
Specify the pattern like this:
|
59
239
|
|
60
240
|
```html
|
61
|
-
{% outline
|
241
|
+
{% outline pattern="title – <i> description </i>" %}
|
62
242
|
000: Topic 0..19
|
63
243
|
020: Topic 20..39
|
64
244
|
040: Topic 40..
|
65
245
|
{% endoutline %}
|
66
246
|
```
|
67
247
|
|
68
|
-
Words that are not a
|
248
|
+
Words in the `pattern` argument that are not recognized as a field are transcribed into the output.
|
69
249
|
|
70
250
|
In the above example, notice that the HTML is space delimited from the field names.
|
71
251
|
The parser is simple and stupid: each token is matched against the known keywords.
|
@@ -75,21 +255,14 @@ Tokens are separated by white space.
|
|
75
255
|
### CSS
|
76
256
|
|
77
257
|
The CSS used for the demo website should be copied to your project.
|
78
|
-
See
|
79
|
-
[`demo/assets/css/styles.css`](/mslinn/jekyll_outline/blob/master/demo/assets/css/style.css#L252-L315) as shown:
|
258
|
+
See [`demo/assets/css/jekyll_outline.css`](https://github.com/mslinn/jekyll_outline/blob/master/demo/assets/css/jekyll_outline.css).
|
80
259
|
|
81
|
-
```css
|
82
|
-
/* Start of jekyll_plugin_support css */
|
83
|
-
... copy this portion ...
|
84
|
-
/* End of jekyll_plugin_support css */
|
85
|
-
|
86
|
-
/* Start of jekyll_outline css */
|
87
|
-
... copy this portion ...
|
88
|
-
/* End of jekyll_outline css */
|
89
|
-
```
|
90
260
|
|
91
261
|
### JavaScript
|
92
262
|
|
263
|
+
Copy [`jekyll_outline.js`](https://github.com/mslinn/jekyll_outline/blob/master/demo/assets/js/jekyll_outline.js)
|
264
|
+
to your Jekyll website's JavaScript directory.
|
265
|
+
|
93
266
|
This project's `outline_js` tag returns the Javascript necessary to position images relating to the outline.
|
94
267
|
If used without parameters it just returns the JavaScript.
|
95
268
|
Use the tag this way:
|
@@ -153,6 +326,8 @@ and have `id`s that correspond to outline sections.
|
|
153
326
|
Each of following image's `id`s have an `outline_` prefix, followed by a number, which corresponds to one of the sections.
|
154
327
|
Note that leading zeros in the first column above are not present in the `id`s below.
|
155
328
|
|
329
|
+
Headings that do not have corresponding pages are not displayed.
|
330
|
+
|
156
331
|
If you want to provide images to embed at appropriate locations within the outline,
|
157
332
|
wrap them within an invisible `div` so the web page does not jump around as the images are loaded.
|
158
333
|
|
data/jekyll_outline.gemspec
CHANGED
@@ -3,8 +3,8 @@ require_relative 'lib/jekyll_outline/version'
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
github = 'https://github.com/mslinn/jekyll_outline'
|
5
5
|
|
6
|
-
spec.authors
|
7
|
-
spec.bindir
|
6
|
+
spec.authors = ['Mike Slinn']
|
7
|
+
spec.bindir = 'exe'
|
8
8
|
spec.description = <<~END_OF_DESC
|
9
9
|
Jekyll tag plugin that creates a clickable table of contents.
|
10
10
|
END_OF_DESC
|
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
|
|
14
14
|
'demo/assets/js/jekyll_outline.js'
|
15
15
|
]
|
16
16
|
spec.homepage = 'https://www.mslinn.com/jekyll_plugins/jekyll_outline.html'
|
17
|
-
spec.license
|
17
|
+
spec.license = 'MIT'
|
18
18
|
spec.metadata = {
|
19
19
|
'allowed_push_host' => 'https://rubygems.org',
|
20
20
|
'bug_tracker_uri' => "#{github}/issues",
|
@@ -22,19 +22,20 @@ Gem::Specification.new do |spec|
|
|
22
22
|
'homepage_uri' => spec.homepage,
|
23
23
|
'source_code_uri' => github,
|
24
24
|
}
|
25
|
-
spec.name
|
25
|
+
spec.name = 'jekyll_outline'
|
26
|
+
spec.platform = Gem::Platform::RUBY
|
26
27
|
spec.post_install_message = <<~END_MESSAGE
|
27
28
|
|
28
29
|
Thanks for installing #{spec.name}!
|
29
30
|
|
30
31
|
END_MESSAGE
|
31
|
-
spec.require_paths
|
32
|
+
spec.require_paths = ['lib']
|
32
33
|
spec.required_ruby_version = '>= 2.6.0'
|
33
|
-
spec.summary
|
34
|
-
spec.test_files
|
35
|
-
spec.version
|
34
|
+
spec.summary = 'Jekyll tag plugin that creates a clickable table of contents.'
|
35
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
36
|
+
spec.version = JekyllOutlineVersion::VERSION
|
36
37
|
|
37
|
-
spec.add_dependency 'jekyll', '>=
|
38
|
-
spec.add_dependency 'jekyll_draft', '>=
|
39
|
-
spec.add_dependency 'jekyll_plugin_support', '>= 1.0
|
38
|
+
spec.add_dependency 'jekyll', '>= 4.4.0'
|
39
|
+
spec.add_dependency 'jekyll_draft', '>= 3.0.0'
|
40
|
+
spec.add_dependency 'jekyll_plugin_support', '>= 3.1.0'
|
40
41
|
end
|
data/lib/outline_tag.rb
CHANGED
@@ -1,79 +1,51 @@
|
|
1
|
-
# @author Copyright 2022 {https://www.mslinn.com Michael Slinn}
|
2
|
-
|
3
1
|
require 'jekyll_draft'
|
4
2
|
require 'jekyll_plugin_logger'
|
5
3
|
require 'jekyll_plugin_support'
|
6
|
-
require 'yaml'
|
7
4
|
require_relative 'jekyll_outline/version'
|
5
|
+
require_relative 'structure/outline'
|
6
|
+
require_relative 'structure/yaml_parser'
|
8
7
|
|
9
|
-
#
|
10
|
-
# <div class="outer_posts">
|
11
|
-
# <h3 class="post_title clear" id="title_0">Django / Oscar Evaluation</h3>
|
12
|
-
# <div id="posts_wrapper_0" class="clearfix">
|
13
|
-
# <div id="posts_0" class="posts">
|
14
|
-
# <span>2021-02-11</span> <span><a href="/collection/page1.html">Title 1</a></span>
|
15
|
-
# <span>2023-12-09</span> <span><a href="/collection/page2.html">Title 2</a></span>
|
16
|
-
# </div>
|
17
|
-
# <h3 class="post_title clear" id="title_NNN">Notes</h3>
|
18
|
-
# <div id="posts_wrapper_NNN" class="clearfix">
|
19
|
-
# <div id="posts_400" class="posts">
|
20
|
-
# <span>2021-04-14</span> <span><a href="/collection/page3.html">Title 3</a></span>
|
21
|
-
# <span>2021-03-29</span> <span><a href="/collection/page4.html">Title 4</a></span>
|
22
|
-
# </div>
|
23
|
-
# </div>
|
24
|
-
# <div id="jps_attribute_570007" class="jps_attribute">
|
25
|
-
# <div>
|
26
|
-
# <a href="https://www.mslinn.com/jekyll_plugins/jekyll_outline.html" target="_blank" rel="nofollow">
|
27
|
-
# Generated by the jekyll_outline v1.2.1 Jekyll plugin, written by Mike Slinn 2024-01-09.
|
28
|
-
# </a>
|
29
|
-
# </div>
|
30
|
-
# </div>
|
31
|
-
# </div>
|
32
|
-
# </div>
|
33
|
-
#
|
34
|
-
# Subclasses, such as jekyll_toc.rb, might generate other output.
|
35
|
-
|
8
|
+
# See spec/outline_spec for an example of HTML output.
|
36
9
|
module JekyllSupport
|
37
10
|
PLUGIN_NAME = 'outline'.freeze
|
38
|
-
OutlineError = JekyllSupport.define_error
|
39
|
-
|
40
|
-
# Interleaves with docs
|
41
|
-
# Duck type compatible with Jekyll doc
|
42
|
-
class Header
|
43
|
-
attr_accessor :order, :title
|
44
|
-
|
45
|
-
def initialize(yaml)
|
46
|
-
@order = yaml[0]
|
47
|
-
@published = true
|
48
|
-
@title = yaml[1]
|
49
|
-
end
|
50
11
|
|
51
|
-
|
52
|
-
" <h3 class='post_title clear' id=\"title_#{@order}\">#{@title}</h3>"
|
53
|
-
end
|
54
|
-
end
|
12
|
+
OutlineError = JekyllSupport.define_error
|
55
13
|
|
56
|
-
class OutlineTag < JekyllBlock
|
14
|
+
class OutlineTag < JekyllBlock
|
57
15
|
include JekyllOutlineVersion
|
58
16
|
|
59
|
-
FIXNUM_MAX = (2**((0.size * 8) - 2)) - 1
|
60
|
-
|
61
17
|
def render_impl(text)
|
62
|
-
|
18
|
+
block_content = super # Process the block content.
|
63
19
|
|
64
|
-
@helper.gem_file __FILE__
|
20
|
+
@helper.gem_file __FILE__ # For attribution
|
65
21
|
|
66
22
|
@die_on_outline_error = @tag_config['die_on_outline_error'] == true if @tag_config
|
67
23
|
@pry_on_outline_error = @tag_config['pry_on_outline_error'] == true if @tag_config
|
68
24
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
25
|
+
pattern = @helper.parameter_specified?('pattern')&.split ||
|
26
|
+
@helper.parameter_specified?('fields')&.split ||
|
27
|
+
['title']
|
28
|
+
sort_by = @helper.parameter_specified?('sort_by_title') ? :title : :order
|
29
|
+
collection_name = @helper.remaining_markup
|
30
|
+
raise OutlineError, 'collection_name was not specified' unless collection_name
|
31
|
+
|
32
|
+
outline_options = OutlineOptions.new(
|
33
|
+
attribution: @attribution,
|
34
|
+
collection_name: collection_name,
|
35
|
+
enable_attribution: @attribution,
|
36
|
+
pattern: pattern,
|
37
|
+
sort_by: sort_by
|
38
|
+
)
|
39
|
+
yaml_parser = YamlParser.new outline_options, block_content
|
40
|
+
outline = Outline.new(outline_options: outline_options)
|
41
|
+
outline.add_sections yaml_parser.sections
|
42
|
+
|
43
|
+
abort "#{collection_name} is not a valid collection." unless @site.collections&.key?(collection_name)
|
44
|
+
docs = @site
|
45
|
+
.collections[collection_name]
|
46
|
+
.docs
|
47
|
+
outline.add_entries(collection_apages(docs))
|
48
|
+
outline.to_s
|
77
49
|
rescue OutlineError => e # jekyll_plugin_support handles StandardError
|
78
50
|
@logger.error { JekyllPluginHelper.remove_html_tags e.logger_message }
|
79
51
|
binding.pry if @pry_on_outline_error # rubocop:disable Lint/Debugger
|
@@ -82,173 +54,16 @@ module JekyllSupport
|
|
82
54
|
e.html_message
|
83
55
|
end
|
84
56
|
|
85
|
-
# Overload this for a subclass
|
86
|
-
def render_outline(collection)
|
87
|
-
<<~HEREDOC
|
88
|
-
<div class="outer_posts">
|
89
|
-
#{make_entries collection}
|
90
|
-
</div>
|
91
|
-
#{@helper.attribute if @helper.attribution}
|
92
|
-
HEREDOC
|
93
|
-
end
|
94
|
-
|
95
|
-
def open_head; end
|
96
|
-
|
97
57
|
private
|
98
58
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
def
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
rescue NoMethodError => e
|
108
|
-
raise OutlineError, <<~END_MSG
|
109
|
-
Invalid YAML within {% outline %} tag. The offending content was:
|
110
|
-
|
111
|
-
<pre>#{content}</pre>
|
112
|
-
END_MSG
|
113
|
-
rescue Psych::SyntaxError => e
|
114
|
-
msg = <<~END_MSG
|
115
|
-
Invalid YAML found within {% outline %} tag:<br>
|
116
|
-
<pre>#{e.message}</pre>
|
117
|
-
END_MSG
|
118
|
-
@logger.error { e.message }
|
119
|
-
raise OutlineError, msg
|
120
|
-
end
|
121
|
-
|
122
|
-
# @section_state can have values: :head, :in_body
|
123
|
-
# @param collection Array of Jekyll::Document and JekyllSupport::Header
|
124
|
-
# @return Array of String
|
125
|
-
def make_entries(collection)
|
126
|
-
sorted = if @sort_by == 'order'
|
127
|
-
collection.sort_by(&obtain_order)
|
128
|
-
else
|
129
|
-
collection.sort_by(&obtain_field)
|
130
|
-
end
|
131
|
-
pruned = remove_empty_headers sorted
|
132
|
-
@section_state = :head
|
133
|
-
@section_id = 0
|
134
|
-
result = pruned.map do |entry|
|
135
|
-
handle entry
|
136
|
-
end
|
137
|
-
result << " </div>\n </div>" if @section_state == :in_body # Modify this for TOC
|
138
|
-
result&.join("\n")
|
139
|
-
end
|
140
|
-
|
141
|
-
KNOWN_FIELDS = %w[draft categories description date last_modified_at layout order title slug ext tags excerpt].freeze
|
142
|
-
|
143
|
-
def handle(entry)
|
144
|
-
if entry.instance_of? Header
|
145
|
-
@header_order = entry.order
|
146
|
-
section_end = " </div>\n" if @section_state == :in_body
|
147
|
-
@section_state = :head
|
148
|
-
entry = section_end + entry.to_s if section_end
|
149
|
-
entry
|
150
|
-
else
|
151
|
-
if @section_state == :head
|
152
|
-
section_start = <<~ENDTEXT # Modify this for TOC
|
153
|
-
<div id="posts_wrapper_#{@header_order}" class='clearfix'>
|
154
|
-
<div id="posts_#{@header_order}" class='posts'>
|
155
|
-
ENDTEXT
|
156
|
-
end
|
157
|
-
@section_state = :in_body
|
158
|
-
date = entry.data['last_modified_at'] # "%Y-%m-%d"
|
159
|
-
draft = Jekyll::Draft.draft_html(entry)
|
160
|
-
visible_line = handle_entry entry
|
161
|
-
result = " <span>#{date}</span> <span><a href='#{entry.url}'>#{visible_line.strip}</a>#{draft}</span>" # Modify this for TOC
|
162
|
-
result = section_start + result if section_start
|
163
|
-
result
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
def handle_entry(entry)
|
168
|
-
result = ''
|
169
|
-
@fields.each do |field|
|
170
|
-
if KNOWN_FIELDS.include? field
|
171
|
-
if entry.data.key? field
|
172
|
-
result += "#{entry.data[field]} "
|
173
|
-
else
|
174
|
-
@logger.warn { "#{field} is a known field, but it was not present in entry #{entry}" }
|
175
|
-
end
|
176
|
-
else
|
177
|
-
result += "#{field} "
|
178
|
-
end
|
179
|
-
end
|
180
|
-
result
|
181
|
-
end
|
182
|
-
|
183
|
-
# Find the given document
|
184
|
-
def obtain_doc(doc_name)
|
185
|
-
abort "#{@collection_name} is not a valid collection." unless @site.collections.key? @collection_name
|
186
|
-
@site
|
187
|
-
.collections[@collection_name]
|
188
|
-
.docs
|
189
|
-
.find { |doc| doc.path.end_with? "#{doc_name}.html" }
|
190
|
-
end
|
191
|
-
|
192
|
-
# Ignores files called index.html
|
193
|
-
def obtain_docs(collection_name)
|
194
|
-
abort "#{@collection_name} is not a valid collection." unless @site.collections.key? @collection_name
|
195
|
-
@site
|
196
|
-
.collections[collection_name]
|
197
|
-
.docs
|
198
|
-
.reject { |doc| doc.path.end_with? 'index.html' }
|
199
|
-
end
|
200
|
-
|
201
|
-
# Sort entries within the outline tag which do not have the property specified by @sort_by at the end
|
202
|
-
def obtain_field
|
203
|
-
sort_by = @sort_by.to_s
|
204
|
-
proc do |entry|
|
205
|
-
if entry.respond_to? :data # page
|
206
|
-
entry.data.key?(sort_by) ? entry.data[sort_by] || 'zzz' : 'zzz'
|
207
|
-
else # heading
|
208
|
-
entry.respond_to?(sort_by) ? entry.send(sort_by) || 'zzz' : 'zzz'
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
# Sort entries within the outline tag which do not have an order property at the end
|
214
|
-
def obtain_order
|
215
|
-
proc do |entry|
|
216
|
-
if entry.respond_to? :data # page
|
217
|
-
entry.data.key?('order') ? entry.data['order'] || FIXNUM_MAX : FIXNUM_MAX
|
218
|
-
else # heading
|
219
|
-
entry.order || FIXNUM_MAX
|
220
|
-
end
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
def remove_empty_headers(array)
|
225
|
-
i = 0
|
226
|
-
while i < array.length - 1
|
227
|
-
if header?(array[i]) && header?(array[i + 1])
|
228
|
-
array.delete_at(i)
|
229
|
-
else
|
230
|
-
i += 1
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
array.delete_at(array.length - 1) if header?(array.last)
|
235
|
-
array
|
236
|
-
end
|
237
|
-
|
238
|
-
def remove_leading_spaces(multiline)
|
239
|
-
multiline
|
240
|
-
.strip
|
241
|
-
.split("\n")
|
242
|
-
.map { |x| x.gsub(/\A\s+/, '') }
|
243
|
-
.join("\n")
|
244
|
-
end
|
245
|
-
|
246
|
-
def remove_leading_zeros(multiline)
|
247
|
-
multiline
|
248
|
-
.strip
|
249
|
-
.split("\n")
|
250
|
-
.map { |x| x.gsub(/(?<= |\A)0+(?=\d)/, '') }
|
251
|
-
.join("\n")
|
59
|
+
# Returns an APage for each document in the collection with the given named.
|
60
|
+
# Ignores files whose name starts with `index`,
|
61
|
+
# and those with the following in their front matter:
|
62
|
+
# exclude_from_outline: true
|
63
|
+
def collection_apages(pages)
|
64
|
+
pages
|
65
|
+
.reject { |doc| doc.url.match(/index(.\w*)?$/) || doc.data['exclude_from_outline'] }
|
66
|
+
.map { |x| ::JekyllSupport::APage.new(x, 'collection') if x }
|
252
67
|
end
|
253
68
|
|
254
69
|
JekyllPluginHelper.register(self, PLUGIN_NAME)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Enriches JekyllSupport.APage
|
2
|
+
module JekyllSupport
|
3
|
+
KNOWN_FIELDS = %w[draft categories description date last_modified_at layout order title slug ext tags excerpt].freeze
|
4
|
+
|
5
|
+
# Overrides definition from `jekyll_plugin_support`
|
6
|
+
class APage
|
7
|
+
def render_outline_apage(pattern)
|
8
|
+
<<~END_ENTRY
|
9
|
+
<span>#{@last_modified.strftime('%Y-%m-%d')}</span>
|
10
|
+
<span><a href='#{@url}'>#{render_entry_details pattern}</a>#{@draft}</span>
|
11
|
+
END_ENTRY
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param pattern can either be a String or [String]
|
15
|
+
# Renders a section entry as a string
|
16
|
+
# Recognized tokens are looked up, otherwise they are incorporated into the output
|
17
|
+
# Currently spaces are the only valid delimiters; HTML tags should be tokenized even when not delimited by spaces
|
18
|
+
def render_entry_details(pattern)
|
19
|
+
result = ''
|
20
|
+
fields = case pattern
|
21
|
+
when String
|
22
|
+
pattern.split
|
23
|
+
when Array
|
24
|
+
pattern
|
25
|
+
else
|
26
|
+
@logger.error { "Pattern is neither a String nor an Array (#{pattern})" }
|
27
|
+
end
|
28
|
+
fields.each do |field|
|
29
|
+
if KNOWN_FIELDS.include? field
|
30
|
+
if respond_to? field
|
31
|
+
value = send field
|
32
|
+
result += "#{value} "
|
33
|
+
elsif data.key?(field.to_sym) || data.key?(field.to_s)
|
34
|
+
value = data[field.to_sym] || data[field.to_s]
|
35
|
+
result += "#{value} "
|
36
|
+
else
|
37
|
+
@logger.warn { "'#{field}' is a known field, but it was not present in apage with url '#{@url}'." }
|
38
|
+
end
|
39
|
+
else
|
40
|
+
result += "#{field} "
|
41
|
+
end
|
42
|
+
end
|
43
|
+
result
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'jekyll_plugin_support'
|
2
|
+
require_relative 'section'
|
3
|
+
|
4
|
+
module JekyllSupport
|
5
|
+
# @param attribution sets the attribution message
|
6
|
+
# @param enable_attribution causes the attribution message to be displayed if truthy
|
7
|
+
# @param collection_name Name of the Jekyll collection the outline is organizing
|
8
|
+
# @param pattern String containing keyswords and literals; interpreted and displayed when an APage is rendered as a topic entry
|
9
|
+
# @param sort_by Either has value :order or :title
|
10
|
+
class OutlineOptions
|
11
|
+
attr_accessor :attribution, :enable_attribution, :collection_name, :logger, :pattern, :sort_by
|
12
|
+
|
13
|
+
def initialize(
|
14
|
+
collection_name: '_posts',
|
15
|
+
attribution: '',
|
16
|
+
enable_attribution: false,
|
17
|
+
logger: PluginMetaLogger.instance.new_logger(self, PluginMetaLogger.instance.config),
|
18
|
+
pattern: '<b> title </b> – <i> description </i>',
|
19
|
+
sort_by: :order
|
20
|
+
)
|
21
|
+
@attribution = attribution
|
22
|
+
@enable_attribution = enable_attribution
|
23
|
+
@collection_name = collection_name
|
24
|
+
@logger = logger
|
25
|
+
@pattern = pattern
|
26
|
+
@sort_by = sort_by
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Outline
|
31
|
+
attr_reader :logger, :options, :sections
|
32
|
+
|
33
|
+
# Sort all entries first so they are iteratable according to the desired order.
|
34
|
+
# This presorts the entries for each section.
|
35
|
+
#
|
36
|
+
# If options[:sort_by] == :order then place each APage into it's appropriate section.
|
37
|
+
# Otherwise place all entries into one section.
|
38
|
+
#
|
39
|
+
# options[:pattern] defaults to ['title'], but might be something like
|
40
|
+
# ["<b>", "title", "</b>", "–", "<i>", "description", "</i>"]
|
41
|
+
def initialize(outline_options: OutlineOptions.new)
|
42
|
+
@add_sections_called = false
|
43
|
+
@options = outline_options
|
44
|
+
|
45
|
+
@logger = @options.logger
|
46
|
+
@sections = @options.sort_by == :order ? [] : [Section.new(@options, [0, ''])]
|
47
|
+
rescue StandardError => e
|
48
|
+
error_short_trace @logger, e
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_entries(apages)
|
52
|
+
sorted_apages = make_entries sort apages
|
53
|
+
sorted_apages.each { |apage| add_apage apage }
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_section(section)
|
58
|
+
return unless @options.sort_by == :order
|
59
|
+
|
60
|
+
@sections << section
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_sections(sections)
|
65
|
+
sections.each { |x| add_section x }
|
66
|
+
@add_sections_called = true
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def make_entries(docs)
|
71
|
+
docs.map do |doc|
|
72
|
+
draft = Jekyll::Draft.draft_html doc
|
73
|
+
JekyllSupport.apage_from(
|
74
|
+
date: doc.date,
|
75
|
+
description: doc.description,
|
76
|
+
draft: draft,
|
77
|
+
last_modified: doc.last_modified,
|
78
|
+
order: doc.order,
|
79
|
+
title: doc.title,
|
80
|
+
url: doc.url
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# @param apages [APage]
|
86
|
+
# @return muliline String
|
87
|
+
def sort(apages)
|
88
|
+
if @options.sort_by == :order
|
89
|
+
apages.sort_by(&:order)
|
90
|
+
else
|
91
|
+
apages.sort_by { |apage| sort_property_value apage }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_s
|
96
|
+
return '' unless @sections&.count&.positive?
|
97
|
+
|
98
|
+
result = []
|
99
|
+
result << "<div class='outer_posts'>"
|
100
|
+
result << (@sections.map { |section| " #{section}" })
|
101
|
+
result << '</div>'
|
102
|
+
result << @options.attribution if @options.enable_attribution
|
103
|
+
result.join "\n"
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def add_apage(apage)
|
109
|
+
unless apage
|
110
|
+
raise ::OutlineError, 'add_apage called with nil apage'
|
111
|
+
puts
|
112
|
+
end
|
113
|
+
raise ::OutlineError, 'add_apage called without first calling add_sections' unless @add_sections_called
|
114
|
+
|
115
|
+
section = section_for apage
|
116
|
+
section.add_child apage
|
117
|
+
end
|
118
|
+
|
119
|
+
def default_sort_value(sort_by)
|
120
|
+
case sort_by
|
121
|
+
when :date, :last_modified, :last_modified_at
|
122
|
+
Date.today
|
123
|
+
else
|
124
|
+
''
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Obtain sort property value from APage instance, or return a default value
|
129
|
+
def sort_property_value(apage)
|
130
|
+
sort_by = @options.sort_by.to_s
|
131
|
+
if apage.data.key?(sort_by)
|
132
|
+
apage.data[sort_by] || default_sort_value(sort_by)
|
133
|
+
else
|
134
|
+
default_sort_value(sort_by)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Only called when entries are organized into multiple sections
|
139
|
+
# @param apage must have a property called `order`
|
140
|
+
def section_for(apage)
|
141
|
+
return @sections.first if @sections.count == 1
|
142
|
+
|
143
|
+
last = @sections.length - 1
|
144
|
+
(0..last).each do |i|
|
145
|
+
return @sections.last if i == last
|
146
|
+
|
147
|
+
page_order = apage.order
|
148
|
+
this_section = @sections[i]
|
149
|
+
next_section = @sections[i + 1]
|
150
|
+
return this_section if (page_order >= this_section.order) && (page_order < next_section.order)
|
151
|
+
end
|
152
|
+
@sections.last
|
153
|
+
# raise OutlineError, "No Section found for APage #{apage}"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative 'a_page_enrichment'
|
2
|
+
|
3
|
+
module JekyllSupport
|
4
|
+
class Section
|
5
|
+
attr_accessor :children, :title, :order
|
6
|
+
|
7
|
+
def initialize(outline_options, parameter_array)
|
8
|
+
@outline_options = outline_options
|
9
|
+
@order = parameter_array[0].to_i
|
10
|
+
@title = parameter_array[1]
|
11
|
+
@children = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_child(child)
|
15
|
+
@children << child
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
return '' if @children.count.zero?
|
20
|
+
|
21
|
+
unless @children.first.instance_of?(JekyllSupport::APage)
|
22
|
+
raise "First child of Section was a #{@children.first.class}, not an APage"
|
23
|
+
end
|
24
|
+
apages = @children
|
25
|
+
.map { |x| x.render_outline_apage @outline_options.pattern }
|
26
|
+
.join("\n ")
|
27
|
+
|
28
|
+
<<~END_SECTION
|
29
|
+
<h3 class='post_title clear' id="title_#{@order}">#{@title}</h3>
|
30
|
+
<div id='posts_wrapper_#{@order}' class='clearfix'>
|
31
|
+
<div id="posts_#{@order}" class='posts'>
|
32
|
+
#{apages}
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
END_SECTION
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require_relative 'section'
|
3
|
+
|
4
|
+
class OutlineError < StandardError; end
|
5
|
+
|
6
|
+
module JekyllSupport
|
7
|
+
class YamlParser
|
8
|
+
attr_reader :sections
|
9
|
+
|
10
|
+
# @return array of empty Sections
|
11
|
+
def initialize(outline_options, content = '')
|
12
|
+
@logger = outline_options.logger
|
13
|
+
@sections = if content && !content.strip.empty?
|
14
|
+
parse_sections outline_options, content
|
15
|
+
else
|
16
|
+
[]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return Array of Sections that do not contain children
|
21
|
+
def parse_sections(outline_options, content)
|
22
|
+
content = remove_leading_zeros remove_leading_spaces content
|
23
|
+
yaml = YAML.safe_load content
|
24
|
+
yaml.map { |entry| Section.new outline_options, entry }
|
25
|
+
rescue NoMethodError => e
|
26
|
+
raise OutlineError, <<~END_MSG
|
27
|
+
Invalid YAML within {% outline %} tag. The offending content was:
|
28
|
+
|
29
|
+
<pre>#{content}</pre>
|
30
|
+
END_MSG
|
31
|
+
rescue Psych::SyntaxError => e
|
32
|
+
msg = <<~END_MSG
|
33
|
+
Invalid YAML found within {% outline %} tag:<br>
|
34
|
+
<pre>#{e.message}</pre>
|
35
|
+
END_MSG
|
36
|
+
@logger.error { e.message }
|
37
|
+
raise OutlineError, msg
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def remove_leading_spaces(multiline)
|
43
|
+
multiline
|
44
|
+
.strip
|
45
|
+
.split("\n")
|
46
|
+
.map { |x| x.gsub(/\A\s+/, '') }
|
47
|
+
.join("\n")
|
48
|
+
end
|
49
|
+
|
50
|
+
def remove_leading_zeros(multiline)
|
51
|
+
multiline
|
52
|
+
.strip
|
53
|
+
.split("\n")
|
54
|
+
.map { |x| x.gsub(/(?<= |\A)0+(?=\d)/, '') }
|
55
|
+
.join("\n")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/spec/outline_spec.rb
CHANGED
@@ -1,10 +1,152 @@
|
|
1
|
-
require '
|
2
|
-
|
1
|
+
require 'jekyll_plugin_support'
|
2
|
+
require 'rspec/match_ignoring_whitespace'
|
3
|
+
require_relative 'spec_helper'
|
4
|
+
require_relative '../lib/structure/outline'
|
3
5
|
|
4
|
-
RSpec.describe(
|
5
|
-
|
6
|
+
RSpec.describe(JekyllSupport) do
|
7
|
+
outline_options = JekyllSupport::OutlineOptions.new(pattern: '<b> title </b> – <i> description </i>')
|
8
|
+
section1 = described_class::Section.new(outline_options, [0, 'Section 1'])
|
9
|
+
section2 = described_class::Section.new(outline_options, [3, 'Section 2'])
|
6
10
|
|
7
|
-
|
8
|
-
|
11
|
+
apages = [
|
12
|
+
described_class.apage_from(
|
13
|
+
collection_name: '_posts',
|
14
|
+
date: '2023-01-01',
|
15
|
+
description: 'This is the entry1 description.',
|
16
|
+
draft: true,
|
17
|
+
last_modified: '2023-01-01',
|
18
|
+
order: 1,
|
19
|
+
title: 'Entry 1',
|
20
|
+
url: 'https://example.com/entry1'
|
21
|
+
),
|
22
|
+
described_class.apage_from(
|
23
|
+
collection_name: '_posts',
|
24
|
+
date: '2023-01-02',
|
25
|
+
description: 'This is the entry2 description.',
|
26
|
+
last_modified: '2023-01-02',
|
27
|
+
order: 2,
|
28
|
+
title: 'Entry 2',
|
29
|
+
url: 'https://example.com/entry2'
|
30
|
+
),
|
31
|
+
described_class.apage_from(
|
32
|
+
collection_name: '_posts',
|
33
|
+
date: '2023-10-03',
|
34
|
+
description: 'This is the entry3 description.',
|
35
|
+
last_modified: '2024-10-03',
|
36
|
+
order: 3,
|
37
|
+
title: 'Entry 3',
|
38
|
+
url: 'https://example.com/entry3'
|
39
|
+
),
|
40
|
+
described_class.apage_from(
|
41
|
+
collection_name: '_posts',
|
42
|
+
date: '2023-10-04',
|
43
|
+
description: 'This is the entry4 description.',
|
44
|
+
draft: true,
|
45
|
+
last_modified: '2024-10-04',
|
46
|
+
order: 4,
|
47
|
+
title: 'Entry 4',
|
48
|
+
url: 'https://example.com/entry4'
|
49
|
+
),
|
50
|
+
described_class.apage_from(
|
51
|
+
collection_name: '_posts',
|
52
|
+
date: '2023-10-05',
|
53
|
+
description: 'This is the entry5 description.',
|
54
|
+
last_modified: '2024-10-05',
|
55
|
+
order: 5,
|
56
|
+
title: 'Entry 5',
|
57
|
+
url: 'https://example.com/entry5'
|
58
|
+
),
|
59
|
+
described_class.apage_from(
|
60
|
+
collection_name: '_posts',
|
61
|
+
date: '2023-10-06',
|
62
|
+
description: 'This is the entry6 description.',
|
63
|
+
last_modified: '2024-10-06',
|
64
|
+
order: 6,
|
65
|
+
title: 'Entry 6',
|
66
|
+
url: 'https://example.com/entry6'
|
67
|
+
)
|
68
|
+
]
|
69
|
+
|
70
|
+
outline = described_class::Outline.new
|
71
|
+
outline.add_sections [section1, section2]
|
72
|
+
outline.add_entries apages
|
73
|
+
|
74
|
+
_attribution = <<~END_ATT
|
75
|
+
<div id="jps_attribute_570007" class="jps_attribute">
|
76
|
+
<div>
|
77
|
+
<a href="https://www.mslinn.com/jekyll_plugins/jekyll_outline.html" target="_blank" rel="nofollow">
|
78
|
+
Generated by the jekyll_outline v1.2.1 Jekyll plugin, written by Mike Slinn 2024-01-09.
|
79
|
+
</a>
|
80
|
+
</div>
|
81
|
+
</div>
|
82
|
+
END_ATT
|
83
|
+
|
84
|
+
expected_section1 = <<~END_EXPECTED
|
85
|
+
<h3 class='post_title clear' id="title_0">Section 1</h3>
|
86
|
+
<div id='posts_wrapper_0' class='clearfix'>
|
87
|
+
<div id="posts_0" class='posts'>
|
88
|
+
<span>2023-01-01</span>
|
89
|
+
<span><a href='https://example.com/entry1'><b> Entry 1 </b> – <i> This is the entry1 description. </i> </a> <i class='jekyll_draft'>Draft</i></span>
|
90
|
+
|
91
|
+
<span>2023-01-02</span>
|
92
|
+
<span><a href='https://example.com/entry2'><b> Entry 2 </b> – <i> This is the entry2 description. </i> </a></span>
|
93
|
+
</div>
|
94
|
+
</div>
|
95
|
+
END_EXPECTED
|
96
|
+
|
97
|
+
expected_section2 = <<~END_EXPECTED
|
98
|
+
<h3 class='post_title clear' id="title_3">Section 2</h3>
|
99
|
+
<div id='posts_wrapper_3' class='clearfix'>
|
100
|
+
<div id="posts_3" class='posts'>
|
101
|
+
<span>2024-10-03</span>
|
102
|
+
<span><a href='https://example.com/entry3'><b> Entry 3 </b> – <i> This is the entry3 description. </i> </a></span>
|
103
|
+
|
104
|
+
<span>2024-10-04</span>
|
105
|
+
<span><a href='https://example.com/entry4'><b> Entry 4 </b> – <i> This is the entry4 description. </i> </a> <i class='jekyll_draft'>Draft</i></span>
|
106
|
+
|
107
|
+
<span>2024-10-05</span>
|
108
|
+
<span><a href='https://example.com/entry5'><b> Entry 5 </b> – <i> This is the entry5 description. </i> </a></span>
|
109
|
+
|
110
|
+
<span>2024-10-06</span>
|
111
|
+
<span><a href='https://example.com/entry6'><b> Entry 6 </b> – <i> This is the entry6 description. </i> </a></span>
|
112
|
+
</div>
|
113
|
+
</div>
|
114
|
+
END_EXPECTED
|
115
|
+
|
116
|
+
it 'verifies initial values' do
|
117
|
+
expect(outline.options.sort_by).to eq(:order)
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'sorts by :order' do
|
121
|
+
expect(outline.sort(apages)).to eq(apages)
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'checks html shape' do
|
125
|
+
expect(outline.sections.count).to eq(2)
|
126
|
+
expect(outline.sections[0].children.count).to eq(2)
|
127
|
+
expect(outline.sections[1].children.count).to eq(4)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'verifies html for first section' do
|
131
|
+
actual = outline.sections[0].to_s
|
132
|
+
expect(expected_section1).to match_ignoring_whitespace(actual)
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'verifies html for second section' do
|
136
|
+
actual = outline.sections[1].to_s
|
137
|
+
expect(expected_section2).to match_ignoring_whitespace(actual)
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'verifies generated html' do
|
141
|
+
actual = outline.to_s
|
142
|
+
|
143
|
+
expected = <<~END_EXPECTED
|
144
|
+
<div class='outer_posts'>
|
145
|
+
#{expected_section1}
|
146
|
+
#{expected_section2}
|
147
|
+
</div>
|
148
|
+
END_EXPECTED
|
149
|
+
|
150
|
+
expect(expected).to match_ignoring_whitespace(actual)
|
9
151
|
end
|
10
152
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,10 +1,23 @@
|
|
1
1
|
require 'jekyll'
|
2
|
-
require_relative '../lib/jekyll_outline'
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
# For testing Jekyll plugins based on jekyll_plugin_support:
|
4
|
+
require 'jekyll_plugin_logger'
|
5
|
+
require 'jekyll_plugin_support'
|
7
6
|
|
7
|
+
RSpec.configure do |config|
|
8
8
|
# See https://relishapp.com/rspec/rspec-core/docs/command-line/only-failures
|
9
9
|
config.example_status_persistence_file_path = 'spec/status_persistence.txt'
|
10
|
+
|
11
|
+
# See https://rspec.info/features/3-12/rspec-core/filtering/filter-run-when-matching/
|
12
|
+
# and https://github.com/rspec/rspec/issues/221
|
13
|
+
config.filter_run_when_matching :focus
|
14
|
+
|
15
|
+
# Other values: :progress, :html, :json, CustomFormatterClass
|
16
|
+
config.formatter = :documentation
|
17
|
+
|
18
|
+
# See https://rspec.info/features/3-12/rspec-core/command-line/order/
|
19
|
+
config.order = :defined
|
20
|
+
|
21
|
+
# See https://www.rubydoc.info/github/rspec/rspec-core/RSpec%2FCore%2FConfiguration:pending_failure_output
|
22
|
+
config.pending_failure_output = :skip
|
10
23
|
end
|
data/spec/status_persistence.txt
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
example_id
|
2
|
-
|
3
|
-
./spec/
|
1
|
+
example_id | status | run_time |
|
2
|
+
---------------------------------- | ------ | --------------- |
|
3
|
+
./spec/jekyll_outline_spec.rb[1:1] | passed | 0.00023 seconds |
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rspec/match_ignoring_whitespace'
|
3
|
+
require_relative '../lib/structure/outline'
|
4
|
+
require_relative '../lib/structure/yaml_parser'
|
5
|
+
|
6
|
+
module JekyllSupport
|
7
|
+
logger = PluginMetaLogger.instance.new_logger(self, PluginMetaLogger.instance.config)
|
8
|
+
outline_options = OutlineOptions.new
|
9
|
+
|
10
|
+
# includes leading zeros and leading spaces, which are invalid in YAML
|
11
|
+
yaml_parser_big = YamlParser.new outline_options, <<~END_DATA
|
12
|
+
0: Production Infrastructure
|
13
|
+
0015000: Audio
|
14
|
+
20000: Video
|
15
|
+
30000: RME
|
16
|
+
40000: OBS Studio
|
17
|
+
50000: Pro Tools
|
18
|
+
55000: Ableton Live & Push
|
19
|
+
60000: Other Music Software
|
20
|
+
70000: MIDI Hardware & Software
|
21
|
+
80000: Davinci Resolve
|
22
|
+
100000: Computer Analysis
|
23
|
+
200000: Music Theory
|
24
|
+
300000: Business
|
25
|
+
400000: General
|
26
|
+
END_DATA
|
27
|
+
|
28
|
+
RSpec.describe(YamlParser) do
|
29
|
+
it 'handles no section headings' do
|
30
|
+
yaml_parser = described_class.new outline_options
|
31
|
+
sections = yaml_parser.sections
|
32
|
+
expect(sections.count).to equal(0)
|
33
|
+
|
34
|
+
yaml_parser = described_class.new outline_options, ''
|
35
|
+
sections = yaml_parser.sections
|
36
|
+
expect(sections.count).to equal(0)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'finds only 1 section heading' do
|
40
|
+
yaml_parser = described_class.new outline_options, '0: General'
|
41
|
+
sections = yaml_parser.sections
|
42
|
+
expect(sections.count).to equal(1)
|
43
|
+
# expect(sections.first)
|
44
|
+
|
45
|
+
yaml_parser = described_class.new outline_options, <<~END_DATA
|
46
|
+
0: General
|
47
|
+
END_DATA
|
48
|
+
sections = yaml_parser.sections
|
49
|
+
expect(sections.count).to equal(1)
|
50
|
+
|
51
|
+
# Handle leading space (this is invalid YAML) and the lack of an end of line character
|
52
|
+
yaml_parser = described_class.new outline_options, ' 0: General'
|
53
|
+
sections = yaml_parser.sections
|
54
|
+
expect(sections.count).to equal(1)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'finds matching sections' do
|
58
|
+
sections = yaml_parser_big.sections
|
59
|
+
expect(sections.count).to equal(14)
|
60
|
+
|
61
|
+
section1 = sections.first
|
62
|
+
expect(section1.order).to eq(0)
|
63
|
+
expect(section1.title).to eq('Production Infrastructure')
|
64
|
+
|
65
|
+
outline = Outline.new.add_sections sections
|
66
|
+
|
67
|
+
apage0 = JekyllSupport.apage_from(date: Time.now, logger: logger, order: 0)
|
68
|
+
actual_section = outline.send :section_for, apage0
|
69
|
+
expect(actual_section.order).to eq(0)
|
70
|
+
|
71
|
+
apage1000 = JekyllSupport.apage_from(date: Time.now, logger: logger, order: 1000)
|
72
|
+
actual_section = outline.send :section_for, apage1000
|
73
|
+
expect(actual_section.order).to eq(0)
|
74
|
+
|
75
|
+
apage16000 = JekyllSupport.apage_from(date: Time.now, logger: logger, order: 16_000)
|
76
|
+
actual_section = outline.send :section_for, apage16000
|
77
|
+
expect(actual_section.order).to eq(15_000)
|
78
|
+
|
79
|
+
apage25000 = JekyllSupport.apage_from(date: Time.now, logger: logger, order: 25_000)
|
80
|
+
actual_section = outline.send :section_for, apage25000
|
81
|
+
expect(actual_section.order).to eq(20_000)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jekyll_outline
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Slinn
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: jekyll
|
@@ -16,42 +15,42 @@ dependencies:
|
|
16
15
|
requirements:
|
17
16
|
- - ">="
|
18
17
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
18
|
+
version: 4.4.0
|
20
19
|
type: :runtime
|
21
20
|
prerelease: false
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
23
22
|
requirements:
|
24
23
|
- - ">="
|
25
24
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
25
|
+
version: 4.4.0
|
27
26
|
- !ruby/object:Gem::Dependency
|
28
27
|
name: jekyll_draft
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
30
29
|
requirements:
|
31
30
|
- - ">="
|
32
31
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
32
|
+
version: 3.0.0
|
34
33
|
type: :runtime
|
35
34
|
prerelease: false
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
37
36
|
requirements:
|
38
37
|
- - ">="
|
39
38
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
39
|
+
version: 3.0.0
|
41
40
|
- !ruby/object:Gem::Dependency
|
42
41
|
name: jekyll_plugin_support
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
44
43
|
requirements:
|
45
44
|
- - ">="
|
46
45
|
- !ruby/object:Gem::Version
|
47
|
-
version: 1.0
|
46
|
+
version: 3.1.0
|
48
47
|
type: :runtime
|
49
48
|
prerelease: false
|
50
49
|
version_requirements: !ruby/object:Gem::Requirement
|
51
50
|
requirements:
|
52
51
|
- - ">="
|
53
52
|
- !ruby/object:Gem::Version
|
54
|
-
version: 1.0
|
53
|
+
version: 3.1.0
|
55
54
|
description: 'Jekyll tag plugin that creates a clickable table of contents.
|
56
55
|
|
57
56
|
'
|
@@ -72,9 +71,14 @@ files:
|
|
72
71
|
- lib/jekyll_outline/version.rb
|
73
72
|
- lib/outline_js.rb
|
74
73
|
- lib/outline_tag.rb
|
74
|
+
- lib/structure/a_page_enrichment.rb
|
75
|
+
- lib/structure/outline.rb
|
76
|
+
- lib/structure/section.rb
|
77
|
+
- lib/structure/yaml_parser.rb
|
75
78
|
- spec/outline_spec.rb
|
76
79
|
- spec/spec_helper.rb
|
77
80
|
- spec/status_persistence.txt
|
81
|
+
- spec/yaml_parser_spec.rb
|
78
82
|
homepage: https://www.mslinn.com/jekyll_plugins/jekyll_outline.html
|
79
83
|
licenses:
|
80
84
|
- MIT
|
@@ -102,12 +106,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
102
106
|
- !ruby/object:Gem::Version
|
103
107
|
version: '0'
|
104
108
|
requirements: []
|
105
|
-
rubygems_version: 3.
|
106
|
-
signing_key:
|
109
|
+
rubygems_version: 3.6.9
|
107
110
|
specification_version: 4
|
108
111
|
summary: Jekyll tag plugin that creates a clickable table of contents.
|
109
112
|
test_files:
|
110
113
|
- spec/outline_spec.rb
|
111
114
|
- spec/spec_helper.rb
|
112
115
|
- spec/status_persistence.txt
|
116
|
+
- spec/yaml_parser_spec.rb
|
113
117
|
...
|