abqari 1.1.0 → 1.1.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: a20fb790c6db4c0a3d9a357373667acbfb346811e2b5909c97a3b085b3e99580
4
- data.tar.gz: 05321cf63a57429e11ac958512ec6628f99e2a2345ce9b4d708faee1feded544
3
+ metadata.gz: '035944943314205611728976da674b3459ebe5b38cc7bcf7ac037dac5726dc39'
4
+ data.tar.gz: ca10d8e3ba566d54f81b6d6ad20779a8252e9eb768e508d5d911a75807e5f2a2
5
5
  SHA512:
6
- metadata.gz: b578071dfb5c70920b40503e1d60a7f0a340242687273cb3dd769be19e79faa2328763a9d88e2a520d3b414d1b9be7f1cd7d99bc7e812c1293374aea25a00dcb
7
- data.tar.gz: 5085055521aa63a6be54eecb66213bfb05a64bb470886113bc951a5bd3fd441ff516013e6c770997dae14ace656d024946cb8cad2ca605f0f69ff53a59542805
6
+ metadata.gz: 987bc6ee6e1e9186193f6a35745559a3e5b1c383b634e681df38c94016b45ab766b2d8d0d6215e6bca88c741d95f4ffff70c2e9be904ea1e93b32ee0c9280397
7
+ data.tar.gz: 94f4ec0a8576c75d6b30b5f7a306dc44bb0cad5fe2312ab075b419efe3b9227f9a02a7676b289fa062ad4d46e7bf470a288f45ad0503d937fd009281de20e5d8
data/CHANGELOG.md CHANGED
@@ -9,6 +9,80 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  _Nothing yet._
11
11
 
12
+ ## [1.1.1] — 2026-08-11
13
+
14
+ Bug-fix release, all of it found while building a site against the
15
+ published 1.1.0 gem.
16
+
17
+ Upgrade if you minify (the default in production): a tag name written
18
+ inside an HTML comment could silently delete everything between that
19
+ comment and the next one, and layouts carry explanatory comments as a
20
+ matter of course. Upgrade too if you use the contact button — it never
21
+ worked at all.
22
+
23
+ ### Fixed
24
+
25
+ - **A tag name written inside an HTML comment silently deleted page
26
+ content — production builds only.** `Minifier.minify` stashed
27
+ `<pre>` / `<textarea>` / `<script>` / `<style>` blocks *before* it
28
+ stripped comments, so ordinary explanatory prose in a layout or
29
+ partial — `<!-- see the <script> tag below -->` — opened a
30
+ "preserved" region at the comment's own text. That region ran to the
31
+ next genuine `</script>` further down the document, swallowing the
32
+ comment's `-->` on the way; the orphaned `<!--` then paired with some
33
+ later `-->`, and the comment pass deleted the placeholder along with
34
+ everything between the two. Pages shipped with an empty `<main>`
35
+ against a green build, and only in production, because dev doesn't
36
+ minify.
37
+
38
+ Comments and preserved blocks are now matched in one ordered
39
+ left-to-right pass, so whichever construct starts first consumes the
40
+ other and tag-like text inside a comment can't open a region. This is
41
+ the same single-pass discipline (and the same failure class) as the
42
+ CSS minifier's comment/string handling. Conditional comments
43
+ (`<!--[if …]>`) are stashed rather than stripped, so they now survive
44
+ verbatim instead of having their contents whitespace-collapsed.
45
+
46
+ - **A document beginning or ending with a `<pre>` / `<script>` /
47
+ `<style>` block leaked the minifier's internal placeholder.** The
48
+ final trim used `String#strip`, which removes NUL bytes as well as
49
+ whitespace, truncating the stash sentinel so the placeholder never
50
+ matched on restore and shipped as literal `ABQARI_PRESERVED_0` text.
51
+ Real pages end in `</html>`, which is why this stayed hidden. Both
52
+ the HTML and CSS minifiers now trim whitespace only.
53
+
54
+ - **The contact button did nothing when clicked.** `contact-button.js`
55
+ delegates on `.contact-button[data-user][data-domain]`, but the
56
+ partial only ever emitted the styling classes (`btn
57
+ btn-outline-secondary`, or whatever `css_class:` was set to) — the
58
+ wrapper's `.contact-button-wrapper` is a different class token, so
59
+ the selector matched nothing on every site that enabled the button.
60
+ No console error; the button just sat there. `.contact-button` is now
61
+ emitted as a styling-free JS hook alongside `css_class:`, so
62
+ overriding the variant can't break the handler.
63
+
64
+ - **`contact.subject:` rendered a malformed `data-subject` attribute.**
65
+ `_contact_button.html.erb` built the whole attribute as a Ruby string
66
+ and emitted it through `<%= %>`, which auto-escapes under Erubi — so
67
+ the attribute's own quote characters came out as entities
68
+ (`data-subject=&quot;Project enquiry&quot;`) and the subject would
69
+ not have reached `contact-button.js` even once the click handler
70
+ fired. The attribute name and quotes are now literal template text;
71
+ only the value passes through the escape.
72
+
73
+ ### Changed
74
+
75
+ - `Minifier::PRESERVE_REGEX` and `Minifier::COMMENT_REGEX` are gone,
76
+ replaced by the single `Minifier::HTML_TOKEN_REGEX`. Both were
77
+ internal to `minify` and nothing outside the engine has reason to
78
+ reference them; they're called out because 1.1.0 is on RubyGems and
79
+ removing a public constant is technically breaking. `PRESERVE_TAGS`
80
+ is unchanged.
81
+
82
+ - `test/low_priority_hardening_test.rb` no longer relies on
83
+ `Object#stub`. Minitest 6 dropped `minitest/mock` from the default
84
+ install, so that test errored on every run of the suite.
85
+
12
86
  ## [1.1.0] — 2026-08-06
13
87
 
14
88
  First public release on RubyGems. 1.0.0 below was tagged in git but
@@ -884,6 +958,7 @@ run a real working author / publisher site.
884
958
  commonmarker ≥ 2.4 ships precompiled 4.0 binaries, which is what
885
959
  the `~> 2.4` floor bought.
886
960
 
887
- [Unreleased]: https://github.com/grantrayner/abqari/compare/v1.1.0...HEAD
961
+ [Unreleased]: https://github.com/grantrayner/abqari/compare/v1.1.1...HEAD
962
+ [1.1.1]: https://github.com/grantrayner/abqari/compare/v1.1.0...v1.1.1
888
963
  [1.1.0]: https://github.com/grantrayner/abqari/compare/v1.0.0...v1.1.0
889
964
  [1.0.0]: https://github.com/grantrayner/abqari/releases/tag/v1.0.0
data/README.md CHANGED
@@ -11,7 +11,7 @@ optional overrides), while the engine — layouts, themes, helpers, asset
11
11
  pipeline — lives in the dependency. Forking the engine is also
12
12
  supported for users who want to own all the code.
13
13
 
14
- > **Status:** v1.1.0first public release, see
14
+ > **Status:** v1.1.1 — see
15
15
  > [CHANGELOG.md](https://github.com/grantrayner/abqari/blob/main/CHANGELOG.md).
16
16
  > Distributed via RubyGems (`gem install abqari`) and as a Bundler
17
17
  > path/git source. The API follows [semver](https://semver.org/) from
@@ -174,7 +174,7 @@ Test that it worked:
174
174
  abqari version
175
175
  ```
176
176
 
177
- You should see a version number, e.g. `abqari 1.1.0`. If you get
177
+ You should see a version number, e.g. `abqari 1.1.1`. If you get
178
178
  `command not found`, see [Troubleshooting](#troubleshooting) below.
179
179
 
180
180
  ### Step 3 — Create your first site
@@ -470,7 +470,7 @@ end
470
470
  Or — pin to a git ref while tracking pre-release work:
471
471
 
472
472
  ```ruby
473
- gem 'abqari', git: 'https://github.com/grantrayner/abqari.git', tag: 'v1.1.0'
473
+ gem 'abqari', git: 'https://github.com/grantrayner/abqari.git', tag: 'v1.1.1'
474
474
  ```
475
475
 
476
476
  Install the libvips C library once (hard dep for the image pipeline):
@@ -2550,7 +2550,7 @@ taking a feature release. To track a fork or an unreleased branch
2550
2550
  instead, swap the line for a git source:
2551
2551
 
2552
2552
  ```ruby
2553
- gem 'abqari', git: 'https://github.com/grantrayner/abqari.git', tag: 'v1.1.0'
2553
+ gem 'abqari', git: 'https://github.com/grantrayner/abqari.git', tag: 'v1.1.1'
2554
2554
  ```
2555
2555
 
2556
2556
  The cadence: read [CHANGELOG.md](https://github.com/grantrayner/abqari/blob/main/CHANGELOG.md) before each upgrade,
@@ -15,6 +15,10 @@
15
15
  Place anywhere by calling `<%%= render 'partials/contact_button' %%>`
16
16
  from any layout. Already wired into the default about layout.
17
17
 
18
+ `.contact-button` is the JS hook contact-button.js delegates on. It's
19
+ always emitted and carries no styling, so `css_class:` can be anything
20
+ without breaking the click handler — keep the two roles separate.
21
+
18
22
  Default styling: `.btn .btn-outline-secondary` from _common.css.
19
23
  Override with `css_class:` to swap the variant — e.g.
20
24
  <%%= render 'partials/contact_button', css_class: 'btn btn-success' %%>
@@ -31,10 +35,10 @@
31
35
  <% subject = cfg['subject'] %>
32
36
  <% button_class = defined?(css_class) ? css_class.to_s : 'btn btn-outline-secondary' %>
33
37
  <p class="contact-button-wrapper">
34
- <button type="button" class="<%= button_class %>"
38
+ <button type="button" class="contact-button <%= button_class %>"
35
39
  data-user="<%= user %>"
36
40
  data-domain="<%= domain %>"
37
- <%= subject ? %(data-subject="#{h(subject)}") : '' %>>
41
+ <% if subject %>data-subject="<%= subject %>"<% end %>>
38
42
  <%= label %>
39
43
  </button>
40
44
  </p>
@@ -6,20 +6,46 @@ module Abqari
6
6
  # <pre>, <textarea>, <script>, and <style> blocks where whitespace matters.
7
7
  module Minifier
8
8
  PRESERVE_TAGS = %w[pre textarea script style].freeze
9
- PRESERVE_REGEX = /<(#{PRESERVE_TAGS.join('|')})\b[^>]*>.*?<\/\1>/im.freeze
10
9
  PLACEHOLDER = "\x00ABQARI_PRESERVED_%d\x00"
11
- COMMENT_REGEX = /<!--(?!\[if).*?-->/m.freeze
10
+
11
+ # Comments and preserved blocks are recognised in ONE left-to-right
12
+ # pass, via ordered alternation, so whichever construct STARTS first
13
+ # consumes the other — the same single-pass discipline (and for the
14
+ # same reason) as CSS_TOKEN_REGEX below.
15
+ #
16
+ # Stashing preserved blocks first and stripping comments afterwards
17
+ # is what this replaced, and it was wrong: a tag-like string written
18
+ # inside a comment — `<!-- see the <script> tag -->` in a layout —
19
+ # opened a preserved region at the `<script>` text, which then ran
20
+ # to the next GENUINE `</script>` further down the document,
21
+ # swallowing the comment's own `-->` on the way. The orphaned `<!--`
22
+ # then paired with some later `-->`, and the comment pass deleted
23
+ # the placeholder and everything between. Pages came out with an
24
+ # empty `<main>`, in production builds only (dev doesn't minify).
25
+ #
26
+ # Comments matched here are dropped, except conditional comments
27
+ # (`<!--[if …]>`), which are stashed so they survive verbatim.
28
+ HTML_TOKEN_REGEX = %r{
29
+ (?<comment><!--.*?-->)
30
+ |
31
+ (?<preserved><(?<tag>#{PRESERVE_TAGS.join('|')})\b[^>]*>.*?</\k<tag>>)
32
+ }imx.freeze
33
+ CONDITIONAL_COMMENT_REGEX = /\A<!--\[if/i.freeze
12
34
 
13
35
  def self.minify(html)
14
36
  preserved = []
15
37
 
16
- protected_html = html.gsub(PRESERVE_REGEX) do |match|
17
- preserved << match
18
- format(PLACEHOLDER, preserved.length - 1)
38
+ protected_html = html.gsub(HTML_TOKEN_REGEX) do
39
+ match = ::Regexp.last_match
40
+ if match[:comment] && !match[:comment].match?(CONDITIONAL_COMMENT_REGEX)
41
+ ''
42
+ else
43
+ preserved << match[0]
44
+ format(PLACEHOLDER, preserved.length - 1)
45
+ end
19
46
  end
20
47
 
21
48
  minified = protected_html
22
- .gsub(COMMENT_REGEX, '')
23
49
  # Collapse inter-tag whitespace to a SINGLE space, not
24
50
  # nothing. `<em>a</em> <strong>b</strong>` must stay
25
51
  # "a b" — deleting the space renders "ab". The size win
@@ -29,12 +55,28 @@ module Abqari
29
55
  # significant whitespace inside them is untouched.)
30
56
  .gsub(/>\s+</, '> <')
31
57
  .gsub(/\s{2,}/, ' ')
32
- .strip
58
+ minified = trim(minified)
33
59
 
34
- minified.gsub(/\x00ABQARI_PRESERVED_(\d+)\x00/) do
35
- preserved[::Regexp.last_match(1).to_i]
36
- end
60
+ restore(minified, preserved)
61
+ end
62
+
63
+ # `String#strip` also removes NUL bytes, which would bite the
64
+ # PLACEHOLDER's trailing \x00 whenever a document starts or ends
65
+ # with a preserved block — the placeholder then fails to match on
66
+ # restore and ships to the browser as literal
67
+ # "\x00ABQARI_PRESERVED_0" text. `\s` in a Ruby regex is whitespace
68
+ # only, so trimming this way leaves the sentinels intact.
69
+ TRIM_REGEX = /\A\s+|\s+\z/.freeze
70
+ PLACEHOLDER_REGEX = /\x00ABQARI_PRESERVED_(\d+)\x00/.freeze
71
+
72
+ def self.trim(text)
73
+ text.gsub(TRIM_REGEX, '')
74
+ end
75
+
76
+ def self.restore(text, preserved)
77
+ text.gsub(PLACEHOLDER_REGEX) { preserved[::Regexp.last_match(1).to_i] }
37
78
  end
79
+ private_class_method :trim, :restore
38
80
 
39
81
  # Comments and quoted strings are recognised in ONE left-to-right
40
82
  # pass, via ordered alternation, so whichever construct STARTS first
@@ -81,11 +123,9 @@ module Abqari
81
123
  .gsub(/\s+/, ' ')
82
124
  .gsub(/\s*([{};,>])\s*/, '\1')
83
125
  .gsub(';}', '}')
84
- .strip
126
+ minified = trim(minified)
85
127
 
86
- minified.gsub(/\x00ABQARI_PRESERVED_(\d+)\x00/) do
87
- preserved[::Regexp.last_match(1).to_i]
88
- end
128
+ restore(minified, preserved)
89
129
  end
90
130
 
91
131
  JS_LINE_COMMENT_REGEX = %r{^[ \t]*//.*$}.freeze
@@ -14,5 +14,5 @@
14
14
  #
15
15
  # Keep this file dependency-free.
16
16
  module Abqari
17
- VERSION = '1.1.0'
17
+ VERSION = '1.1.1'
18
18
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abqari
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Grant Rayner