asciidoctor-html 0.1.14 → 0.1.15
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/CHANGELOG.md +4 -0
- data/assets/css/styles.css +2 -2
- data/lib/asciidoctor/html/book.rb +16 -26
- data/lib/asciidoctor/html/cli.rb +1 -1
- data/lib/asciidoctor/html/pagination.rb +1 -1
- data/lib/asciidoctor/html/search.rb +146 -0
- data/lib/asciidoctor/html.rb +1 -0
- metadata +16 -1
@@ -11,6 +11,7 @@ require_relative "bi_inline_macro"
|
|
11
11
|
require_relative "text_inline_macro"
|
12
12
|
require_relative "template"
|
13
13
|
require_relative "pagination"
|
14
|
+
require_relative "search"
|
14
15
|
|
15
16
|
module Asciidoctor
|
16
17
|
module Html
|
@@ -53,16 +54,15 @@ module Asciidoctor
|
|
53
54
|
# - title
|
54
55
|
# - short_title
|
55
56
|
# - authors
|
56
|
-
# - se_id
|
57
57
|
# - chapname
|
58
58
|
def initialize(opts = {})
|
59
59
|
opts = DEFAULT_OPTS.merge opts
|
60
60
|
@title = ERB::Escape.html_escape opts[:title]
|
61
61
|
@short_title = ERB::Escape.html_escape opts[:short_title]
|
62
62
|
@authors = opts[:authors]
|
63
|
-
@se_id = opts[:se_id]
|
64
63
|
@base_url = opts[:base_url]
|
65
64
|
@chapname = opts[:chapname]
|
65
|
+
@search_index = {} # Hash(docname => Array[SearchData])
|
66
66
|
@refs = {} # Hash(docname => Hash(id => reftext))
|
67
67
|
@templates = {} # Hash(docname => TData)
|
68
68
|
end
|
@@ -94,32 +94,17 @@ module Asciidoctor
|
|
94
94
|
read(chapters, appendices).each do |name, html|
|
95
95
|
filename = "#{name}.html"
|
96
96
|
File.write("#{outdir}/#{filename}", html)
|
97
|
+
build_index(name, html) unless omit_search?
|
97
98
|
entries << Template.sitemap_entry("#{@base_url}#{filename}") if needs_sitemap
|
98
99
|
end
|
99
|
-
File.write
|
100
|
+
File.write "#{outdir}/#{SEARCH_PAGE}", search_page unless omit_search?
|
100
101
|
File.write("#{outdir}/sitemap.xml", Template.sitemap(entries)) if needs_sitemap
|
101
102
|
end
|
102
103
|
|
103
104
|
private
|
104
105
|
|
105
106
|
include Pagination
|
106
|
-
|
107
|
-
def search_page(se_id)
|
108
|
-
content = <<~HTML
|
109
|
-
<script async src="https://cse.google.com/cse.js?cx=#{se_id}"></script>
|
110
|
-
<div class="gcse-search"></div>
|
111
|
-
HTML
|
112
|
-
Template.html(
|
113
|
-
content,
|
114
|
-
nav_items,
|
115
|
-
title: @title,
|
116
|
-
short_title: @short_title,
|
117
|
-
authors: display_authors,
|
118
|
-
date: @date,
|
119
|
-
chapsubheading: "Search",
|
120
|
-
langs: []
|
121
|
-
)
|
122
|
-
end
|
107
|
+
include Search
|
123
108
|
|
124
109
|
def register!(docs, filename, doc)
|
125
110
|
key = key filename
|
@@ -194,17 +179,18 @@ module Asciidoctor
|
|
194
179
|
node.reftext || (node.title unless node.inline?) || "[#{node.id}]" if node.id
|
195
180
|
end
|
196
181
|
|
197
|
-
def display_authors(doc)
|
198
|
-
authors =
|
199
|
-
|
182
|
+
def display_authors(doc = nil)
|
183
|
+
authors = []
|
184
|
+
if doc
|
185
|
+
authors = doc.authors.map do |author|
|
186
|
+
doc.sub_replacements author.name
|
187
|
+
end
|
200
188
|
end
|
201
|
-
|
202
189
|
if authors.empty? && @authors
|
203
190
|
authors = @authors.map do |author|
|
204
191
|
ERB::Escape.html_escape author
|
205
192
|
end
|
206
193
|
end
|
207
|
-
|
208
194
|
return if authors.empty?
|
209
195
|
|
210
196
|
[
|
@@ -231,6 +217,10 @@ module Asciidoctor
|
|
231
217
|
html
|
232
218
|
end
|
233
219
|
|
220
|
+
def omit_search?
|
221
|
+
@templates.size < 2 && @search_index.empty?
|
222
|
+
end
|
223
|
+
|
234
224
|
def nav_items(active_key = -1, doc = nil)
|
235
225
|
items = @templates.map do |k, td|
|
236
226
|
active = (k == active_key)
|
@@ -238,7 +228,7 @@ module Asciidoctor
|
|
238
228
|
navtext = Template.nav_text td.chapprefix, td.chaptitle
|
239
229
|
Template.nav_item "#{k}.html", navtext, subnav, active:
|
240
230
|
end
|
241
|
-
return items
|
231
|
+
return items if omit_search?
|
242
232
|
|
243
233
|
items.unshift(Template.nav_item(
|
244
234
|
SEARCH_PAGE,
|
data/lib/asciidoctor/html/cli.rb
CHANGED
@@ -85,7 +85,7 @@ module Asciidoctor
|
|
85
85
|
|
86
86
|
def self.generate_bookopts(config)
|
87
87
|
book_opts = {}
|
88
|
-
%i[title short_title authors
|
88
|
+
%i[title short_title authors base_url chapname].each do |opt|
|
89
89
|
key = opt.to_s
|
90
90
|
book_opts[opt] = config[key] if config.include?(key)
|
91
91
|
end
|
@@ -5,7 +5,7 @@ module Asciidoctor
|
|
5
5
|
# Mixin to add pagination support to Book class
|
6
6
|
module Pagination
|
7
7
|
# Pagination item
|
8
|
-
PagItem = Struct.new
|
8
|
+
PagItem = Struct.new "PagItem", :url, :title
|
9
9
|
|
10
10
|
def display_paginator(prv, nxt)
|
11
11
|
return "" unless prv || nxt
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
|
5
|
+
module Asciidoctor
|
6
|
+
module Html
|
7
|
+
# Mixed in to Book class to provide full text search
|
8
|
+
module Search
|
9
|
+
SEARCH_RESULT_OVERFLOW = 10 # characters
|
10
|
+
|
11
|
+
def search_page
|
12
|
+
content = <<~HTML
|
13
|
+
<div class="search-form-container">
|
14
|
+
<form id="search-form" class="search-form">
|
15
|
+
<input type="text" id="search-text" name="search-text" class="form-control search-box" placeholder="Search">
|
16
|
+
<button type="submit" class="btn btn-primary search-btn">Go</button>
|
17
|
+
</form>
|
18
|
+
</div>
|
19
|
+
<div id="search-results-container" class="hidden">
|
20
|
+
<h4>Found <span id="search-nmatches">0 matches</span></h4>
|
21
|
+
<ul id="search-results" class="search-results list-group list-group-flush"></ul>
|
22
|
+
</div>
|
23
|
+
<script src="https://unpkg.com/lunr/lunr.js"></script>
|
24
|
+
<script>#{lunr_script}</script>
|
25
|
+
HTML
|
26
|
+
Template.html(
|
27
|
+
content,
|
28
|
+
nav_items,
|
29
|
+
title: @title,
|
30
|
+
short_title: @short_title,
|
31
|
+
authors: display_authors,
|
32
|
+
date: @date,
|
33
|
+
chapsubheading: "Search",
|
34
|
+
langs: []
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_index(key, html)
|
39
|
+
doctree = Nokogiri::HTML5.parse html
|
40
|
+
ref = @refs[key]
|
41
|
+
page_text = "#{doctree.at_css(".chaptitle")&.text} #{doctree.at_css(".preamble")&.text}"
|
42
|
+
index = [{
|
43
|
+
id: "#{key}.html",
|
44
|
+
title: ref["chapref"],
|
45
|
+
text: page_text
|
46
|
+
}]
|
47
|
+
doctree.css("section[id]").each do |section|
|
48
|
+
sectid = section["id"]
|
49
|
+
id = "#{key}.html##{sectid}"
|
50
|
+
title = "#{ref["chapref"]} › #{ref[sectid]}"
|
51
|
+
text = section.text
|
52
|
+
index << { id:, title:, text: }
|
53
|
+
end
|
54
|
+
@search_index[key] = index
|
55
|
+
end
|
56
|
+
|
57
|
+
def search_json
|
58
|
+
index_arr = []
|
59
|
+
@search_index.each_value do |search_data|
|
60
|
+
index_arr.concat search_data
|
61
|
+
end
|
62
|
+
index_arr.to_json
|
63
|
+
end
|
64
|
+
|
65
|
+
def lunr_script
|
66
|
+
<<~JS
|
67
|
+
(function() {
|
68
|
+
const resultsContainer = document.getElementById('search-results-container');
|
69
|
+
const nmatches = document.getElementById('search-nmatches');
|
70
|
+
const searchResults = document.getElementById('search-results');
|
71
|
+
const searchForm = document.getElementById('search-form');
|
72
|
+
const searchBox = document.getElementById('search-text');
|
73
|
+
const positionOverflow = 20;
|
74
|
+
const documents = #{search_json};
|
75
|
+
const idx = lunr(function() {
|
76
|
+
this.ref('id');
|
77
|
+
this.field('title');
|
78
|
+
this.field('text');
|
79
|
+
this.metadataWhitelist = ['position'];
|
80
|
+
|
81
|
+
documents.forEach(doc => {
|
82
|
+
this.add(doc)
|
83
|
+
});
|
84
|
+
});
|
85
|
+
function processSearchText(searchText) {
|
86
|
+
const results = idx.search(searchText).map(match => {
|
87
|
+
const doc = documents.find(d => d.id == match.ref);
|
88
|
+
const result = document.createElement('li');
|
89
|
+
result.classList.add('list-group-item');
|
90
|
+
const link = document.createElement('a');
|
91
|
+
const br = document.createElement('br');
|
92
|
+
link.setAttribute('href', match.ref);
|
93
|
+
link.innerHTML = doc.title;
|
94
|
+
result.append(link, br);
|
95
|
+
const metadata = match.matchData.metadata;
|
96
|
+
for(const term in metadata) {
|
97
|
+
const datum = metadata[term];
|
98
|
+
for(const type in datum) {
|
99
|
+
datum[type].position.forEach(pos => {
|
100
|
+
const start = pos[0];
|
101
|
+
const end = pos[0] + pos[1];
|
102
|
+
const text = doc[type];
|
103
|
+
const textMatch = text.substring(start, end)
|
104
|
+
const matchingText = document.createElement('mark');
|
105
|
+
const overflowLeft = document.createElement('span');
|
106
|
+
overflowLeft.classList.add('overflow-text-left');
|
107
|
+
const overflowRight = document.createElement('span');
|
108
|
+
overflowRight.classList.add('overflow-text-right');
|
109
|
+
const reLeft = /.{#{SEARCH_RESULT_OVERFLOW}}$/s;
|
110
|
+
let left = text.substring(0, start - 1).search(reLeft) + 1;
|
111
|
+
while(text[left] && text[left].trim() == text[left]) {
|
112
|
+
left--;
|
113
|
+
}
|
114
|
+
const reRight = /^.{#{SEARCH_RESULT_OVERFLOW}}/s;
|
115
|
+
let right = text.length;
|
116
|
+
if(rightMatch = text.substring(end + 1).match(reRight)) {
|
117
|
+
right = rightMatch[0].length + end;
|
118
|
+
while(text[right] && text[right].trim() == text[right]) {
|
119
|
+
right++;
|
120
|
+
}
|
121
|
+
}
|
122
|
+
overflowLeft.textContent = text.substring(left, start - 1);
|
123
|
+
matchingText.textContent = textMatch;
|
124
|
+
overflowRight.textContent = text.substring(end + 1, right);
|
125
|
+
result.append(overflowLeft, matchingText, overflowRight);
|
126
|
+
});
|
127
|
+
}
|
128
|
+
}
|
129
|
+
return result;
|
130
|
+
});
|
131
|
+
searchResults.replaceChildren(...results);
|
132
|
+
const n = results.length;
|
133
|
+
nmatches.textContent = n == 1 ? (n + ' match') : (n + ' matches');
|
134
|
+
resultsContainer.classList.remove('hidden');
|
135
|
+
}
|
136
|
+
searchForm.addEventListener('submit', e => {
|
137
|
+
e.preventDefault();
|
138
|
+
const searchText = searchBox.value;
|
139
|
+
processSearchText(searchText);
|
140
|
+
});
|
141
|
+
})();
|
142
|
+
JS
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/lib/asciidoctor/html.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: asciidoctor-html
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ravi Rajani
|
@@ -37,6 +37,20 @@ dependencies:
|
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: '2.1'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: nokogiri
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.18'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.18'
|
40
54
|
- !ruby/object:Gem::Dependency
|
41
55
|
name: psych
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
@@ -102,6 +116,7 @@ files:
|
|
102
116
|
- lib/asciidoctor/html/popovers.rb
|
103
117
|
- lib/asciidoctor/html/ref_tree_processor.rb
|
104
118
|
- lib/asciidoctor/html/scroll.rb
|
119
|
+
- lib/asciidoctor/html/search.rb
|
105
120
|
- lib/asciidoctor/html/sidebar.rb
|
106
121
|
- lib/asciidoctor/html/table.rb
|
107
122
|
- lib/asciidoctor/html/template.rb
|