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 +4 -4
- data/CHANGELOG.md +76 -1
- data/README.md +4 -4
- data/app/views/partials/_contact_button.html.erb +6 -2
- data/lib/abqari/minifier.rb +54 -14
- data/lib/abqari/version.rb +1 -1
- 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: '035944943314205611728976da674b3459ebe5b38cc7bcf7ac037dac5726dc39'
|
|
4
|
+
data.tar.gz: ca10d8e3ba566d54f81b6d6ad20779a8252e9eb768e508d5d911a75807e5f2a2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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="Project enquiry"`) 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
41
|
+
<% if subject %>data-subject="<%= subject %>"<% end %>>
|
|
38
42
|
<%= label %>
|
|
39
43
|
</button>
|
|
40
44
|
</p>
|
data/lib/abqari/minifier.rb
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
58
|
+
minified = trim(minified)
|
|
33
59
|
|
|
34
|
-
minified
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
126
|
+
minified = trim(minified)
|
|
85
127
|
|
|
86
|
-
minified
|
|
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
|
data/lib/abqari/version.rb
CHANGED