jekyll-link-attributes 0.0.3 → 2.0.1
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/Gemfile.lock +4 -1
- data/README.md +65 -13
- data/lib/jekyll-link-attributes/version.rb +1 -1
- data/lib/jekyll-link-attributes.rb +89 -10
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d791371caa0d64c3b9f0be8b0166c0dee989a8ba462e58c07ccef19625652fae
|
|
4
|
+
data.tar.gz: 56525b68f3c8ffb2568792b64b1e0e7c271d82cb742d77eaba772db3f7cd5112
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0de1970490f37a7cce17f43303b234957aaf23f9f6fa10514d2e04da2a2c654d4d6b2aa4c9f0099ae1a555701da14f5724c72606c9f2ac610dd3b50a9dbf3bb7
|
|
7
|
+
data.tar.gz: 10bdda6fbaa8ef263b9a3648391fef15031ef507e36307632fceb011037f38c201b131dc718ce8dd6abd8154afec57cdfbd8febb4bd6290c37ea617d6227f050
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
jekyll-link-attributes (
|
|
4
|
+
jekyll-link-attributes (2.0.1)
|
|
5
5
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: https://rubygems.org/
|
|
@@ -50,6 +50,8 @@ GEM
|
|
|
50
50
|
mercenary (0.4.0)
|
|
51
51
|
nokogiri (1.13.8-x86_64-darwin)
|
|
52
52
|
racc (~> 1.4)
|
|
53
|
+
nokogiri (1.13.8-x86_64-linux)
|
|
54
|
+
racc (~> 1.4)
|
|
53
55
|
pathutil (0.16.2)
|
|
54
56
|
forwardable-extended (~> 2.6)
|
|
55
57
|
public_suffix (5.0.0)
|
|
@@ -82,6 +84,7 @@ GEM
|
|
|
82
84
|
|
|
83
85
|
PLATFORMS
|
|
84
86
|
x86_64-darwin-20
|
|
87
|
+
x86_64-linux
|
|
85
88
|
|
|
86
89
|
DEPENDENCIES
|
|
87
90
|
bundler (>= 2.0.0)
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Jekyll Link Attributes
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
A Jekyll plugin for managing external link behavior: `rel` attributes, `target` attributes, and UTM tracking parameters.
|
|
4
|
+
Each concern is independently configurable with its own value and exclude list.
|
|
5
5
|
|
|
6
6
|
## Setup
|
|
7
7
|
|
|
@@ -18,7 +18,35 @@ The default configuration opens external links in a new tab and conserves domain
|
|
|
18
18
|
|
|
19
19
|
## Configuration
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
### Recommended (v2+)
|
|
22
|
+
|
|
23
|
+
Each attribute type is its own section with a `value` and optional `exclude` list:
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
external_links:
|
|
27
|
+
enabled: true
|
|
28
|
+
|
|
29
|
+
rel:
|
|
30
|
+
value: external nofollow noopener
|
|
31
|
+
exclude:
|
|
32
|
+
- https://myotherapp.com(/?|/.*)?
|
|
33
|
+
|
|
34
|
+
target:
|
|
35
|
+
value: _blank
|
|
36
|
+
exclude:
|
|
37
|
+
- https://myotherapp.com(/?|/.*)?
|
|
38
|
+
|
|
39
|
+
utm:
|
|
40
|
+
enabled: true
|
|
41
|
+
source: mysite.com
|
|
42
|
+
medium: website
|
|
43
|
+
exclude:
|
|
44
|
+
- https://github.com(/?|/.*)?
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Legacy (v1, still supported)
|
|
48
|
+
|
|
49
|
+
The original flat configuration style continues to work. The top-level `rel`, `target`, and `exclude` keys are used as fallbacks when the new-style section config is not present:
|
|
22
50
|
|
|
23
51
|
```yaml
|
|
24
52
|
external_links:
|
|
@@ -26,18 +54,42 @@ external_links:
|
|
|
26
54
|
rel: external nofollow noopener
|
|
27
55
|
target: _blank
|
|
28
56
|
exclude:
|
|
29
|
-
- https://example.com
|
|
30
|
-
- https://another.example.com/test.html
|
|
31
|
-
- https://regex.example.com/.+
|
|
57
|
+
- https://example.com(/?|/.*)?
|
|
32
58
|
```
|
|
33
59
|
|
|
34
|
-
###
|
|
35
|
-
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
| `external_links.rel`
|
|
39
|
-
| `external_links.
|
|
40
|
-
| `external_links.
|
|
60
|
+
### Resolution order
|
|
61
|
+
|
|
62
|
+
| Setting | Resolved from | Fallback |
|
|
63
|
+
| ------- | ------------- | -------- |
|
|
64
|
+
| rel value | `external_links.rel.value` | `external_links.rel` (string) or `external nofollow noopener` |
|
|
65
|
+
| rel excludes | `external_links.rel.exclude` | `external_links.exclude` |
|
|
66
|
+
| target value | `external_links.target.value` | `external_links.target` (string) or `_blank` |
|
|
67
|
+
| target excludes | `external_links.target.exclude` | `external_links.exclude` |
|
|
68
|
+
| utm excludes | `external_links.utm.exclude` | *(none, defaults to empty)* |
|
|
69
|
+
|
|
70
|
+
### UTM tracking parameters
|
|
71
|
+
|
|
72
|
+
When `external_links.utm.enabled` is `true`, UTM query parameters are automatically appended to external links:
|
|
73
|
+
|
|
74
|
+
| Param | Value | Source |
|
|
75
|
+
| -------------- | -------------------- | ---------------------------------------------------------------- |
|
|
76
|
+
| `utm_source` | Configured `source` | Falls back to the site `url` with the protocol stripped. |
|
|
77
|
+
| `utm_medium` | Configured `medium` | Falls back to `website`. |
|
|
78
|
+
| `utm_campaign` | Auto-derived | `blog` for post/blog layouts, otherwise the first URL path segment (e.g., `about`), or `homepage` for the root page. |
|
|
79
|
+
| `utm_content` | Auto-derived | The page slug (e.g., `my-great-post` or `index`). |
|
|
80
|
+
|
|
81
|
+
Existing query parameters on links are preserved. UTM parameters already present on a link will not be overwritten.
|
|
82
|
+
|
|
83
|
+
### Skipping individual links
|
|
84
|
+
|
|
85
|
+
The `rel` or `target` attributes will not be modified for links that already have those existing attributes.
|
|
86
|
+
This allows you to skip individual links without having to modify the plugin's configuration.
|
|
87
|
+
|
|
88
|
+
```html
|
|
89
|
+
<a href="https://example.com" rel="nofollow">Example</a> <!-- rel will not be modified, but target will be added. -->
|
|
90
|
+
<a href="https://example.com" target="_self">Example</a> <!-- target will not be modified, but rel will be added. -->
|
|
91
|
+
<a href="https://example.com" rel="nofollow" target="_self">Example</a> <!-- Neither rel nor target will be modified. -->
|
|
92
|
+
```
|
|
41
93
|
|
|
42
94
|
## Contributing
|
|
43
95
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'jekyll-link-attributes/hooks'
|
|
4
4
|
require 'jekyll-link-attributes/version'
|
|
5
5
|
require 'nokogiri'
|
|
6
|
+
require 'uri'
|
|
6
7
|
|
|
7
8
|
module Jekyll
|
|
8
9
|
|
|
@@ -15,13 +16,37 @@ module Jekyll
|
|
|
15
16
|
config = article.site.config
|
|
16
17
|
return unless external_links_enabled?(config: config)
|
|
17
18
|
|
|
19
|
+
ext_config = config['external_links'] || {}
|
|
20
|
+
utm_params = build_utm_params(ext_config: ext_config, site_config: config, article: article)
|
|
21
|
+
|
|
18
22
|
output = Nokogiri::HTML(article.output)
|
|
19
23
|
output.css('a').each do |a|
|
|
20
24
|
next unless external_link?(config: config, url: a['href'])
|
|
21
|
-
next if excludes_external_link?(config: config, url: a['href'])
|
|
22
25
|
|
|
23
|
-
a['
|
|
24
|
-
|
|
26
|
+
original_href = a['href']
|
|
27
|
+
|
|
28
|
+
# UTM: applied to all external links with its own exclude list
|
|
29
|
+
if utm_params && !excluded?(ext_config: ext_config, section: 'utm', url: original_href)
|
|
30
|
+
a['href'] = append_utm_params(url: a['href'], utm_params: utm_params)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# rel: new-style section config falls back to legacy top-level keys
|
|
34
|
+
unless a['rel']
|
|
35
|
+
rel_value = resolve_value(ext_config: ext_config, section: 'rel', legacy_key: 'rel',
|
|
36
|
+
default: 'external nofollow noopener')
|
|
37
|
+
unless excluded?(ext_config: ext_config, section: 'rel', url: original_href)
|
|
38
|
+
a['rel'] = rel_value
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# target: new-style section config falls back to legacy top-level keys
|
|
43
|
+
unless a['target']
|
|
44
|
+
target_value = resolve_value(ext_config: ext_config, section: 'target', legacy_key: 'target',
|
|
45
|
+
default: '_blank')
|
|
46
|
+
unless excluded?(ext_config: ext_config, section: 'target', url: original_href)
|
|
47
|
+
a['target'] = target_value
|
|
48
|
+
end
|
|
49
|
+
end
|
|
25
50
|
end
|
|
26
51
|
|
|
27
52
|
article.output = output.to_s
|
|
@@ -29,9 +54,32 @@ module Jekyll
|
|
|
29
54
|
|
|
30
55
|
private
|
|
31
56
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
57
|
+
# Resolve value for a section, falling back to legacy top-level key.
|
|
58
|
+
# New style: external_links.rel.value / external_links.target.value
|
|
59
|
+
# Legacy: external_links.rel / external_links.target (string value)
|
|
60
|
+
def self.resolve_value(ext_config:, section:, legacy_key:, default:)
|
|
61
|
+
section_config = ext_config[section]
|
|
62
|
+
if section_config.is_a?(Hash)
|
|
63
|
+
section_config['value'] || default
|
|
64
|
+
else
|
|
65
|
+
section_config || default
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Check if a URL is excluded for a given section.
|
|
70
|
+
# New style: external_links.<section>.exclude
|
|
71
|
+
# Legacy fallback (rel/target only): external_links.exclude
|
|
72
|
+
def self.excluded?(ext_config:, section:, url:)
|
|
73
|
+
section_config = ext_config[section]
|
|
74
|
+
excludes = if section_config.is_a?(Hash)
|
|
75
|
+
section_config['exclude'] || []
|
|
76
|
+
elsif section == 'utm'
|
|
77
|
+
[]
|
|
78
|
+
else
|
|
79
|
+
ext_config['exclude'] || []
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
excludes.any? { |pattern| Regexp.new("^#{pattern}$").match?(url) }
|
|
35
83
|
end
|
|
36
84
|
|
|
37
85
|
def self.external_link?(config:, url:)
|
|
@@ -44,12 +92,43 @@ module Jekyll
|
|
|
44
92
|
enabled.nil? || enabled
|
|
45
93
|
end
|
|
46
94
|
|
|
47
|
-
def self.
|
|
48
|
-
|
|
95
|
+
def self.utm_enabled?(ext_config:)
|
|
96
|
+
ext_config.dig('utm', 'enabled') == true
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def self.build_utm_params(ext_config:, site_config:, article:)
|
|
100
|
+
return nil unless utm_enabled?(ext_config: ext_config)
|
|
101
|
+
|
|
102
|
+
utm_config = ext_config['utm'] || {}
|
|
103
|
+
source = utm_config['source'] || site_config['url']&.sub(%r{\Ahttps?://}, '') || 'website'
|
|
104
|
+
medium = utm_config['medium'] || 'website'
|
|
105
|
+
|
|
106
|
+
campaign = case article.data['layout']
|
|
107
|
+
when 'post', 'blog' then 'blog'
|
|
108
|
+
else
|
|
109
|
+
path = article.url.to_s.gsub(%r{\A/|/\z}, '')
|
|
110
|
+
path.empty? ? 'homepage' : path.split('/').first
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
content = article.data['slug'] || File.basename(article.url.to_s.chomp('/'))
|
|
114
|
+
content = 'index' if content.empty?
|
|
115
|
+
|
|
116
|
+
{
|
|
117
|
+
'utm_source' => source,
|
|
118
|
+
'utm_medium' => medium,
|
|
119
|
+
'utm_campaign' => campaign,
|
|
120
|
+
'utm_content' => content,
|
|
121
|
+
}
|
|
49
122
|
end
|
|
50
123
|
|
|
51
|
-
def self.
|
|
52
|
-
|
|
124
|
+
def self.append_utm_params(url:, utm_params:)
|
|
125
|
+
uri = URI.parse(url)
|
|
126
|
+
existing = URI.decode_www_form(uri.query || '').to_h
|
|
127
|
+
utm_params.each { |k, v| existing[k] = v unless existing.key?(k) }
|
|
128
|
+
uri.query = URI.encode_www_form(existing)
|
|
129
|
+
uri.to_s
|
|
130
|
+
rescue URI::InvalidURIError
|
|
131
|
+
url
|
|
53
132
|
end
|
|
54
133
|
end
|
|
55
134
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-link-attributes
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- twinsunllc
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-03-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|