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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 21581fa0a3642f3dacb9e62b698bdbaf69eff296c6742fd2f2c10b71b610a650
4
- data.tar.gz: f29f049cc12f44ac29fdefbf2de9a0e601c45eb11f96f23d06bb48c1e71a31e9
3
+ metadata.gz: d791371caa0d64c3b9f0be8b0166c0dee989a8ba462e58c07ccef19625652fae
4
+ data.tar.gz: 56525b68f3c8ffb2568792b64b1e0e7c271d82cb742d77eaba772db3f7cd5112
5
5
  SHA512:
6
- metadata.gz: d20810d19c468067b25dccc8ecd427e9da8ef3ea64aaf31975bdf94693140be28d76ce4b657a79111e09850aa3a0e98e3bffb1cc74d901225190834a035b1186
7
- data.tar.gz: 6988b43697baf0cefed3379e3a58ab560d542b41c766cabed705420aa23babb079b18d51ecdccba1623ef4f1a2b709cbf3db842d7ea1bb6f09da7e5267c3d87a
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 (0.0.3)
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
- This plugin adds `rel` and `target` attributes to all external links in your Jekyll site.
4
- The default configuration opens external links in a new tab and conserves domain authority for your site.
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
- You can override the default configuration by adding the following section to your Jekyll site's `config.yml`:
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
- ### Default Values
35
- | Key | Default Value | Description |
36
- | ---------------------------- | ---------------------------- | -------------------------------------------------- |
37
- | `external_links.enabled` | `true` | Enable attribute modifications for external links. |
38
- | `external_links.rel` | `external nofollow noopener` | The `rel` attribute to add to external links. |
39
- | `external_links.target` | `_blank` | The `target` attribute to add to external links. |
40
- | `external_links.exclude` | `[]` | A list of URLs to exclude from processing. |
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
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jekyll
4
4
  class LinkAttributes
5
- VERSION = '0.0.3'
5
+ VERSION = '2.0.1'
6
6
  end
7
7
  end
@@ -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['rel'] = external_link_rel(config: config)
24
- a['target'] = external_link_target(config: config)
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
- def self.excludes_external_link?(config:, url:)
33
- excludes = (config.dig('external_links', 'exclude') || [])
34
- excludes.any? { |exclude| Regexp.new("^#{exclude}$").match? url }
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.external_link_rel(config:)
48
- config.dig('external_links', 'rel') || 'external nofollow noopener'
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.external_link_target(config:)
52
- config.dig('external_links', 'target') || '_blank'
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: 0.0.3
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: 2022-09-05 00:00:00.000000000 Z
11
+ date: 2026-03-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler