jekyll-link-decorator 1.2.2 → 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/lib/jekyll-link-decorator.rb +87 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 85f578a55d98076ae39a47427c191f790d9f7a804b095849d39897db778a86e7
|
|
4
|
+
data.tar.gz: 023bfd45a25c076a0b7fe8bc632195fc8e85d60d983e63cfb2c50abbd2c9c807
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c32eecd6e74d8ab46a664268c0371f63401ff9c94f40b10984f04fbd0b211e44d6e2f375f3f3cabaceeceda17b4ac4f009666ae472f62e3163a7664caeb6b4b1
|
|
7
|
+
data.tar.gz: b9b1c79b09cb02402fd89740829de88077b59a649936c0e11c4b2034ed0b067e7ae8da6f8bc5e9469af813ac37b2882364e463477e77d46c366869652b15ef4a
|
|
@@ -41,6 +41,23 @@
|
|
|
41
41
|
# (target="_blank", rel="noopener noreferrer") are still applied.
|
|
42
42
|
# Example: `external_link_icon_excluded_tags: ["img", "svg"]`
|
|
43
43
|
#
|
|
44
|
+
# === Heading Anchor Support ===
|
|
45
|
+
#
|
|
46
|
+
# The plugin can inject a Font Awesome link icon inside each heading (h1–h6),
|
|
47
|
+
# giving users a direct, copyable URL to every section. This feature is configured
|
|
48
|
+
# via two top-level keys (separate from `with_link_decorator_data`):
|
|
49
|
+
#
|
|
50
|
+
# * `with_heading_anchor`: Boolean (default: false) that controls whether heading anchors
|
|
51
|
+
# are injected. Set to true to enable.
|
|
52
|
+
#
|
|
53
|
+
# * `with_heading_anchor_data`: A dictionary containing optional configuration keys:
|
|
54
|
+
# - `icon`: Font Awesome icon classes (default: "fa-solid fa-link")
|
|
55
|
+
# - `icon_size`: Font Awesome size modifier appended after icon classes (default: "fa-xs")
|
|
56
|
+
# - `copy_success_message`: Feedback text after copying (consumed by heading-anchor.js,
|
|
57
|
+
# default: "Copied!")
|
|
58
|
+
# - `reset_delay`: Milliseconds before the icon resets (consumed by heading-anchor.js,
|
|
59
|
+
# default: 2000)
|
|
60
|
+
#
|
|
44
61
|
# If no configuration is provided, the plugin will use default fallback classes
|
|
45
62
|
# and enable external link icons.
|
|
46
63
|
#
|
|
@@ -55,6 +72,13 @@
|
|
|
55
72
|
# external_link_icon_excluded_tags:
|
|
56
73
|
# - img
|
|
57
74
|
# - svg
|
|
75
|
+
#
|
|
76
|
+
# with_heading_anchor: true
|
|
77
|
+
# with_heading_anchor_data:
|
|
78
|
+
# icon: "fa-solid fa-link"
|
|
79
|
+
# icon_size: "fa-xs"
|
|
80
|
+
# copy_success_message: "Copied!"
|
|
81
|
+
# reset_delay: 2000
|
|
58
82
|
# ```
|
|
59
83
|
|
|
60
84
|
require 'nokogiri'
|
|
@@ -79,6 +103,9 @@ module Jekyll
|
|
|
79
103
|
DEFAULT_ALERT_LINK_CLASSES = 'alert-link'
|
|
80
104
|
DEFAULT_EXTERNAL_LINK_ICON = true
|
|
81
105
|
DEFAULT_EXTERNAL_LINK_ICON_EXCLUDED_TAGS = [].freeze
|
|
106
|
+
DEFAULT_HEADING_ANCHOR = false
|
|
107
|
+
DEFAULT_HEADING_ANCHOR_ICON = 'fa-solid fa-link'
|
|
108
|
+
DEFAULT_HEADING_ANCHOR_SIZE = 'fa-xs'
|
|
82
109
|
|
|
83
110
|
def self.name
|
|
84
111
|
'LinkDecorator'
|
|
@@ -196,6 +223,7 @@ module Jekyll
|
|
|
196
223
|
|
|
197
224
|
apply_link_styles(doc, config)
|
|
198
225
|
add_external_link_features(doc, config)
|
|
226
|
+
add_heading_anchors(doc)
|
|
199
227
|
|
|
200
228
|
doc.to_html
|
|
201
229
|
rescue StandardError => e
|
|
@@ -262,6 +290,65 @@ module Jekyll
|
|
|
262
290
|
extract_domain(site_url)
|
|
263
291
|
end
|
|
264
292
|
|
|
293
|
+
# Inject a Font Awesome anchor link inside each heading (h1–h6).
|
|
294
|
+
# Reads with_heading_anchor and with_heading_anchor_data from @config directly.
|
|
295
|
+
def add_heading_anchors(doc)
|
|
296
|
+
enabled = @config.key?('with_heading_anchor') ? @config['with_heading_anchor'] : DEFAULT_HEADING_ANCHOR
|
|
297
|
+
return unless enabled
|
|
298
|
+
|
|
299
|
+
ha_data = @config['with_heading_anchor_data'] || {}
|
|
300
|
+
icon_class = ha_data.fetch('icon', DEFAULT_HEADING_ANCHOR_ICON)
|
|
301
|
+
icon_size = ha_data.fetch('icon_size', DEFAULT_HEADING_ANCHOR_SIZE)
|
|
302
|
+
full_icon = "#{icon_class} #{icon_size}"
|
|
303
|
+
id_counts = {}
|
|
304
|
+
|
|
305
|
+
doc.css('h1, h2, h3, h4, h5, h6').each do |heading|
|
|
306
|
+
next if heading.css('.heading-anchor').any?
|
|
307
|
+
|
|
308
|
+
id = resolve_heading_id(heading, id_counts)
|
|
309
|
+
|
|
310
|
+
anchor = Nokogiri::XML::Node.new('a', doc)
|
|
311
|
+
anchor['class'] = 'heading-anchor mx-1'
|
|
312
|
+
anchor['href'] = "##{id}"
|
|
313
|
+
anchor['data-copy-anchor'] = "##{id}"
|
|
314
|
+
anchor['aria-label'] = "Link to #{heading.text.strip}"
|
|
315
|
+
|
|
316
|
+
icon = Nokogiri::XML::Node.new('i', doc)
|
|
317
|
+
icon['class'] = full_icon
|
|
318
|
+
icon['aria-hidden'] = 'true'
|
|
319
|
+
anchor.add_child(icon)
|
|
320
|
+
|
|
321
|
+
heading.add_child(anchor)
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Return the heading's existing id, or generate and assign one from its text.
|
|
326
|
+
# id_counts tracks generated slugs to produce unique suffixes for duplicates.
|
|
327
|
+
def resolve_heading_id(heading, id_counts)
|
|
328
|
+
existing = heading['id'].to_s.strip
|
|
329
|
+
return existing unless existing.empty?
|
|
330
|
+
|
|
331
|
+
base = slugify(heading.text)
|
|
332
|
+
n = id_counts[base].to_i
|
|
333
|
+
id_counts[base] = n + 1
|
|
334
|
+
final_id = n.zero? ? base : "#{base}-#{n}"
|
|
335
|
+
heading['id'] = final_id
|
|
336
|
+
final_id
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# Convert heading text to a URL-safe slug matching the link.html.liquid algorithm:
|
|
340
|
+
# strip → downcase → spaces to hyphens → remove ", ', ?, &, #, / → collapse hyphens
|
|
341
|
+
def slugify(text)
|
|
342
|
+
text.strip
|
|
343
|
+
.downcase
|
|
344
|
+
.tr(' ', '-')
|
|
345
|
+
.delete('"\'?&#/')
|
|
346
|
+
.gsub(/-{2,}/, '-')
|
|
347
|
+
.gsub(/\A-|-\z/, '')
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
private :add_heading_anchors, :resolve_heading_id, :slugify
|
|
351
|
+
|
|
265
352
|
# Add external-link icon to cross-domain external links
|
|
266
353
|
def add_external_icon(link, site_domain, config, doc)
|
|
267
354
|
external_link_icon = config.fetch('external_link_icon', DEFAULT_EXTERNAL_LINK_ICON)
|