jekyll-highlight-cards 0.3.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 +7 -0
- data/.cursorignore +1 -0
- data/.github/workflows/ci.yaml +21 -0
- data/.github/workflows/release-please.yaml +69 -0
- data/.github/workflows/update-gemfile-lock.yaml +52 -0
- data/.gitignore +59 -0
- data/.release-please-manifest.json +3 -0
- data/.rspec +5 -0
- data/.rubocop.yml +64 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +54 -0
- data/CONTRIBUTING.md +219 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +196 -0
- data/LICENSE +661 -0
- data/README.md +228 -0
- data/_includes/highlight-cards/linkcard.html +13 -0
- data/_includes/highlight-cards/polaroid.html +22 -0
- data/_sass/_highlight-cards.scss +92 -0
- data/docs/linkcard-example.jpg +0 -0
- data/docs/polaroid-example.jpg +0 -0
- data/docs/polaroid-sidebyside-example.jpg +0 -0
- data/docs/polaroid-stacked-example.jpg +0 -0
- data/jekyll-highlight-cards.gemspec +47 -0
- data/lib/jekyll-highlight-cards/archive_helper.rb +151 -0
- data/lib/jekyll-highlight-cards/dimension_parser.rb +62 -0
- data/lib/jekyll-highlight-cards/expression_evaluator.rb +113 -0
- data/lib/jekyll-highlight-cards/image_sizing_hooks.rb +188 -0
- data/lib/jekyll-highlight-cards/linkcard_tag.rb +211 -0
- data/lib/jekyll-highlight-cards/polaroid_tag.rb +223 -0
- data/lib/jekyll-highlight-cards/template_renderer.rb +113 -0
- data/lib/jekyll-highlight-cards/version.rb +5 -0
- data/lib/jekyll-highlight-cards.rb +57 -0
- data/release-please-config.json +12 -0
- metadata +234 -0
data/README.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# jekyll-highlight-cards
|
|
2
|
+
|
|
3
|
+
A Jekyll plugin providing styled card components for links and images with Internet Archive integration.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **`{% linkcard %}`** - Styled link cards with optional titles and archive links
|
|
8
|
+
- **`{% polaroid %}`** - Polaroid-style image cards with titles, links, and archive support
|
|
9
|
+
- **Markdown Image Sizing** - Extended syntax for image dimensions: ``
|
|
10
|
+
- **Internet Archive Integration** - Automatic lookup and archival for both tags
|
|
11
|
+
- **Customizable** - Override HTML templates and CSS styles
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Add to your `Gemfile`:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem 'jekyll-highlight-cards'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Add to your `_config.yml`:
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
plugins:
|
|
25
|
+
- jekyll-highlight-cards
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Run:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bundle install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Add to your `main.scss` file:
|
|
35
|
+
|
|
36
|
+
```scss
|
|
37
|
+
@import "highlight-cards";
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### Linkcard Tag
|
|
43
|
+
|
|
44
|
+
Highlight links:
|
|
45
|
+
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
```liquid
|
|
49
|
+
{% linkcard https://example.com %}
|
|
50
|
+
{% linkcard https://example.com My Cool Title %}
|
|
51
|
+
{% linkcard https://example.com Title archive:none %}
|
|
52
|
+
{% linkcard https://example.com archive:https://web.archive.org/... %}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Parameters:**
|
|
56
|
+
| Parameter | Description |
|
|
57
|
+
|------------------|------------------------------------------------------------------|
|
|
58
|
+
| URL | (required, first parameter) |
|
|
59
|
+
| Title | (optional, everything after URL until `archive:`) |
|
|
60
|
+
| `archive:URL` | Explicit archive URL |
|
|
61
|
+
| `archive:none` | Disable archive lookup |
|
|
62
|
+
|
|
63
|
+
### Polaroid Tag
|
|
64
|
+
|
|
65
|
+
Create polaroid-style image cards:
|
|
66
|
+
|
|
67
|
+

|
|
68
|
+
|
|
69
|
+
```liquid
|
|
70
|
+
{% polaroid /assets/image.jpg %}
|
|
71
|
+
{% polaroid /assets/image.jpg size=300x200 %}
|
|
72
|
+
{% polaroid /assets/image.jpg size=400x title="My Photo" %}
|
|
73
|
+
{% polaroid /assets/image.jpg alt="Screen reader description" %}
|
|
74
|
+
{% polaroid /assets/image.jpg alt="Alt text" title="Visible Title" %}
|
|
75
|
+
{% polaroid /assets/image.jpg title="Photo" link="https://example.com" %}
|
|
76
|
+
{% polaroid {{ page.image }} size=x400 title={{ page.title }} %}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Parameters:**
|
|
80
|
+
| Parameter | Description |
|
|
81
|
+
|--------------------|----------------------------------------------------------------------------------------------------------|
|
|
82
|
+
| Image URL | (required, first parameter) |
|
|
83
|
+
| `size=WxH` | Image dimensions. Formats: `300x200`, `300x`, `x200`, `300`, `400pxx300px` |
|
|
84
|
+
| `alt="..."` | Alt text for image (for accessibility) |
|
|
85
|
+
| `title="..."` | Title text displayed below image (also used as alt fallback) |
|
|
86
|
+
| `link="..."` | Explicit URL to link to |
|
|
87
|
+
| `archive="..."` | Archive URL or `none` to disable |
|
|
88
|
+
|
|
89
|
+
**Image Alt Text:**
|
|
90
|
+
The `alt` attribute priority: explicit `alt` parameter → `title` parameter → empty string.
|
|
91
|
+
|
|
92
|
+
This allows you to:
|
|
93
|
+
- Set accessible alt text without displaying a visible title
|
|
94
|
+
- Use title as both visual label and screen reader description
|
|
95
|
+
- Separate concerns: detailed alt for accessibility, brief title for display
|
|
96
|
+
|
|
97
|
+
**Link Display:**
|
|
98
|
+
- **No `link` parameter:** Image links to itself, no visible link text shown
|
|
99
|
+
- **With `link` parameter:** Image and visible link text both point to the specified URL
|
|
100
|
+
|
|
101
|
+
**Stacking:**
|
|
102
|
+
|
|
103
|
+
By default, the Polaroids are displayed centered in their available space. Two Polaroids in a row will be [stacked vertically](docs/polaroid-stacked-example.jpg).
|
|
104
|
+
|
|
105
|
+
If you want Polaroids to fill the available width [side-by-side](docs/polaroid-sidebyside-example.jpg), add the following to your `main.scss` file:
|
|
106
|
+
|
|
107
|
+
```css
|
|
108
|
+
.polaroid-container {
|
|
109
|
+
display: inline-block;
|
|
110
|
+
width: auto;
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Markdown Image Sizing
|
|
115
|
+
|
|
116
|
+
Add dimensions to Markdown images:
|
|
117
|
+
|
|
118
|
+
```markdown
|
|
119
|
+

|
|
120
|
+

|
|
121
|
+

|
|
122
|
+

|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Sized images are automatically wrapped in links to themselves.
|
|
126
|
+
|
|
127
|
+
## Configuration
|
|
128
|
+
|
|
129
|
+
### Internet Archive
|
|
130
|
+
|
|
131
|
+
Enable automatic archive lookup:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
export JEKYLL_HIGHLIGHT_CARDS_ARCHIVE=1
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Or in your shell config:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# In .bashrc, .zshrc, etc.
|
|
141
|
+
export JEKYLL_HIGHLIGHT_CARDS_ARCHIVE=1
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### CSS Styles
|
|
145
|
+
|
|
146
|
+
Import defaults and override specific properties:
|
|
147
|
+
|
|
148
|
+
```scss
|
|
149
|
+
@import "highlight-cards";
|
|
150
|
+
|
|
151
|
+
.link-card {
|
|
152
|
+
border-color: red; // Override
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The default style are structural only - they create the shapes but don't set colors, fonts, etc.
|
|
157
|
+
The recommended approach is to use the default styles and then add aesthetics to the provided classes.
|
|
158
|
+
|
|
159
|
+
### Template Customization
|
|
160
|
+
|
|
161
|
+
If the provided HTML structure doesn't work for you, you can override templates by creating files in your Jekyll site:
|
|
162
|
+
|
|
163
|
+
**Linkcard template:**
|
|
164
|
+
- Create `_includes/highlight-cards/linkcard.html`
|
|
165
|
+
|
|
166
|
+
**Polaroid template:**
|
|
167
|
+
- Create `_includes/highlight-cards/polaroid.html`
|
|
168
|
+
|
|
169
|
+
Templates receive these variables:
|
|
170
|
+
|
|
171
|
+
**Linkcard variables:**
|
|
172
|
+
- `url`, `display_url`, `title`, `archive_url`
|
|
173
|
+
- `escaped_url`, `escaped_display_url`, `escaped_title`, `escaped_archive_url`
|
|
174
|
+
|
|
175
|
+
**Polaroid variables:**
|
|
176
|
+
- `image_url`, `link_url`, `title`, `link_display`, `archive_url`, `width`, `height`
|
|
177
|
+
- `escaped_*` versions of all text fields
|
|
178
|
+
|
|
179
|
+
See default templates in gem's `_includes/` directory for examples.
|
|
180
|
+
|
|
181
|
+
## Examples
|
|
182
|
+
|
|
183
|
+
### Blog post with link card
|
|
184
|
+
|
|
185
|
+
```markdown
|
|
186
|
+
---
|
|
187
|
+
title: My Blog Post
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
Check out this cool site:
|
|
191
|
+
|
|
192
|
+
{% linkcard https://jekyllrb.com Jekyll - Simple, blog-aware, static sites %}
|
|
193
|
+
|
|
194
|
+
More content here...
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Gallery with polaroids
|
|
198
|
+
|
|
199
|
+
```markdown
|
|
200
|
+
---
|
|
201
|
+
title: Photo Gallery
|
|
202
|
+
photos:
|
|
203
|
+
- url: /assets/photo1.jpg
|
|
204
|
+
title: Sunset
|
|
205
|
+
- url: /assets/photo2.jpg
|
|
206
|
+
title: Mountains
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
{% for photo in page.photos %}
|
|
210
|
+
{% polaroid {{ photo.url }} size=300x300 title={{ photo.title }} %}
|
|
211
|
+
{% endfor %}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Sized images in Markdown
|
|
215
|
+
|
|
216
|
+
```markdown
|
|
217
|
+
Here's a large image:
|
|
218
|
+
|
|
219
|
+

|
|
220
|
+
|
|
221
|
+
And a smaller one:
|
|
222
|
+
|
|
223
|
+

|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Development
|
|
227
|
+
|
|
228
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<blockquote class="link-card">
|
|
2
|
+
{% if title %}
|
|
3
|
+
<h1>{{ escaped_title }}</h1>
|
|
4
|
+
{% endif %}
|
|
5
|
+
<a href="{{ escaped_url }}" target="_blank" rel="noopener">{{ escaped_display_url }}</a>
|
|
6
|
+
{% if archive_url %}
|
|
7
|
+
<small class="link-card-archive">
|
|
8
|
+
(<a href="{{ escaped_archive_url }}" target="_blank" rel="noopener">archive</a>)
|
|
9
|
+
</small>
|
|
10
|
+
{% endif %}
|
|
11
|
+
</blockquote>
|
|
12
|
+
|
|
13
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<div class="polaroid-container">
|
|
2
|
+
<div class="polaroid">
|
|
3
|
+
<a href="{{ escaped_link_url }}"{% unless link_url == image_url %} target="_blank" rel="noopener"{% endunless %}>
|
|
4
|
+
<img src="{{ escaped_image_url }}" alt="{% if alt %}{{ escaped_alt }}{% elsif title %}{{ escaped_title }}{% endif %}"{% if width %} width="{{ width }}"{% endif %}{% if height %} height="{{ height }}"{% endif %} class="polaroid-image">
|
|
5
|
+
</a>
|
|
6
|
+
<div class="polaroid-title">{{ escaped_title }}</div>
|
|
7
|
+
<div class="polaroid-link">
|
|
8
|
+
{% if link_display %}
|
|
9
|
+
<a href="{{ escaped_link_url }}" target="_blank" rel="noopener">{{ escaped_link_display }}</a>
|
|
10
|
+
{% else %}
|
|
11
|
+
|
|
12
|
+
{% endif %}
|
|
13
|
+
</div>
|
|
14
|
+
<small class="polaroid-archive">
|
|
15
|
+
{% if archive_url %}
|
|
16
|
+
(<a href="{{ escaped_archive_url }}" target="_blank" rel="noopener">archive</a>)
|
|
17
|
+
{% else %}
|
|
18
|
+
|
|
19
|
+
{% endif %}
|
|
20
|
+
</small>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* jekyll-highlight-cards Default Styles
|
|
3
|
+
*
|
|
4
|
+
* These styles provide a sensible default appearance for linkcard and polaroid tags.
|
|
5
|
+
* You can override these styles in your Jekyll site by:
|
|
6
|
+
* 1. Not importing this file (fully custom CSS)
|
|
7
|
+
* 2. Importing and overriding specific classes
|
|
8
|
+
* 3. Using higher specificity selectors
|
|
9
|
+
*
|
|
10
|
+
* To use these styles, add to your site's _config.yml or main SCSS:
|
|
11
|
+
* @use "highlight-cards";
|
|
12
|
+
*
|
|
13
|
+
* All styles use low specificity for easy customization.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/* ============================================================================
|
|
17
|
+
Linkcard Styles
|
|
18
|
+
========================================================================= */
|
|
19
|
+
|
|
20
|
+
.link-card {
|
|
21
|
+
padding: 1em 1.25em;
|
|
22
|
+
text-align: center;
|
|
23
|
+
position: relative;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.link-card-archive {
|
|
27
|
+
position: absolute;
|
|
28
|
+
right: 0.75rem;
|
|
29
|
+
bottom: 0.5rem;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* ============================================================================
|
|
33
|
+
Polaroid Styles
|
|
34
|
+
========================================================================= */
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
.polaroid-container {
|
|
38
|
+
display: block;
|
|
39
|
+
width: 100%;
|
|
40
|
+
text-align: center;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.polaroid {
|
|
44
|
+
display: inline-block;
|
|
45
|
+
position: relative;
|
|
46
|
+
border: 1px solid #333;
|
|
47
|
+
padding: 10px 10px 25px 10px;
|
|
48
|
+
margin: 1em;
|
|
49
|
+
background-color: #fff;
|
|
50
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
|
51
|
+
text-align: center;
|
|
52
|
+
max-width: 100%;
|
|
53
|
+
transition: box-shadow 0.2s ease, transform 0.2s ease;
|
|
54
|
+
|
|
55
|
+
.polaroid-image {
|
|
56
|
+
display: block;
|
|
57
|
+
max-width: 100%;
|
|
58
|
+
border: 1px solid #333;
|
|
59
|
+
margin: 0 auto;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.polaroid-title {
|
|
63
|
+
margin-top: 5px;
|
|
64
|
+
padding: 0 10px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.polaroid-link {
|
|
68
|
+
margin-top: 5px;
|
|
69
|
+
padding: 0 10px;
|
|
70
|
+
word-break: break-all;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.polaroid-archive {
|
|
74
|
+
position: absolute;
|
|
75
|
+
right: 0.75rem;
|
|
76
|
+
bottom: 0.5rem;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* ============================================================================
|
|
81
|
+
Print Styles
|
|
82
|
+
========================================================================= */
|
|
83
|
+
|
|
84
|
+
@media print {
|
|
85
|
+
.link-card,
|
|
86
|
+
.polaroid {
|
|
87
|
+
box-shadow: none;
|
|
88
|
+
border: 1px solid #333;
|
|
89
|
+
page-break-inside: avoid;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/jekyll-highlight-cards/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "jekyll-highlight-cards"
|
|
7
|
+
spec.version = JekyllHighlightCards::VERSION
|
|
8
|
+
spec.authors = ["Texarkanine"]
|
|
9
|
+
spec.email = ["texarkanine@protonmail.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Jekyll plugin providing linkcard and polaroid Liquid tags with archive integration"
|
|
12
|
+
spec.description = "A Jekyll gem that provides two Liquid tags (linkcard and polaroid) for creating " \
|
|
13
|
+
"styled card components with integrated Internet Archive functionality and image sizing. " \
|
|
14
|
+
"Also includes Markdown image sizing hooks."
|
|
15
|
+
spec.homepage = "https://github.com/texarkanine/jekyll-highlight-cards"
|
|
16
|
+
spec.license = "AGPL-3.0-or-later"
|
|
17
|
+
spec.required_ruby_version = ">= 3.1.0"
|
|
18
|
+
|
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
20
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
21
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
22
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
23
|
+
|
|
24
|
+
# Specify which files should be added to the gem when it is released.
|
|
25
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
26
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
27
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
28
|
+
f.match(%r{^(test|spec|features|planning|examples)/})
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
spec.require_paths = ["lib"]
|
|
33
|
+
|
|
34
|
+
# Runtime dependencies
|
|
35
|
+
spec.add_dependency "jekyll", ">= 4.0", "< 5.0"
|
|
36
|
+
spec.add_dependency "liquid", ">= 4.0", "< 5.0"
|
|
37
|
+
|
|
38
|
+
# Development dependencies
|
|
39
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
|
40
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
41
|
+
spec.add_development_dependency "rspec", "~> 3.12"
|
|
42
|
+
spec.add_development_dependency "rubocop", "~> 1.50"
|
|
43
|
+
spec.add_development_dependency "rubocop-rake", "~> 0.6"
|
|
44
|
+
spec.add_development_dependency "rubocop-rspec", "~> 2.20"
|
|
45
|
+
spec.add_development_dependency "simplecov", "~> 0.22"
|
|
46
|
+
spec.add_development_dependency "webmock", "~> 3.18"
|
|
47
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JekyllHighlightCards
|
|
4
|
+
# Internet Archive integration for automatic URL archival
|
|
5
|
+
#
|
|
6
|
+
# Provides methods for looking up existing archives and submitting
|
|
7
|
+
# URLs to the Internet Archive's Wayback Machine. Results are cached
|
|
8
|
+
# per-site-build to avoid redundant API calls.
|
|
9
|
+
#
|
|
10
|
+
# @example Enable archiving
|
|
11
|
+
# export JEKYLL_HIGHLIGHT_CARDS_ARCHIVE=1
|
|
12
|
+
#
|
|
13
|
+
# @example Enable auto-submission
|
|
14
|
+
# export JEKYLL_HIGHLIGHT_CARDS_ARCHIVE_SAVE=1
|
|
15
|
+
module ArchiveHelper
|
|
16
|
+
# Shared cache for archive URLs across all tag instances
|
|
17
|
+
@archive_cache = {}
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
attr_accessor :archive_cache
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Get archive URL for a given URL, with caching and optional submission
|
|
24
|
+
#
|
|
25
|
+
# @param url [String] the original URL to archive
|
|
26
|
+
# @return [String, nil] the archive URL, or nil if not found
|
|
27
|
+
def archive_url_for(url)
|
|
28
|
+
ArchiveHelper.archive_cache[url] ||= begin
|
|
29
|
+
log_info("Looking up archive for #{url}")
|
|
30
|
+
archive_url = lookup_archive(url)
|
|
31
|
+
log_info("Archive URL: #{archive_url || ""}")
|
|
32
|
+
|
|
33
|
+
if archive_save_enabled?
|
|
34
|
+
log_info("Submitting to SavePageNow: #{url}")
|
|
35
|
+
archive_url = submit_archive(url) || archive_url
|
|
36
|
+
log_info("SavePageNow archived #{url} -> #{archive_url}")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
archive_url.to_s.empty? ? nil : archive_url
|
|
40
|
+
end
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
log_debug("Archive lookup failed for #{url}: #{e.message}")
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Check if archiving is enabled via environment variables
|
|
47
|
+
#
|
|
48
|
+
# @return [Boolean] true if archiving is enabled
|
|
49
|
+
def archive_enabled?
|
|
50
|
+
ENV["JEKYLL_HIGHLIGHT_CARDS_ARCHIVE"] == "1" || archive_save_enabled?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Check if SavePageNow submission is enabled
|
|
54
|
+
#
|
|
55
|
+
# @return [Boolean] true if submission is enabled
|
|
56
|
+
def archive_save_enabled?
|
|
57
|
+
ENV["JEKYLL_HIGHLIGHT_CARDS_ARCHIVE_SAVE"] == "1"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Get User-Agent string for archive HTTP requests
|
|
61
|
+
#
|
|
62
|
+
# @return [String] User-Agent header value
|
|
63
|
+
def archive_user_agent
|
|
64
|
+
ENV["JEKYLL_HIGHLIGHT_CARDS_ARCHIVE_UA"] ||
|
|
65
|
+
"jekyll:highlight-cards (+#{ENV.fetch("JEKYLL_HIGHLIGHT_CARDS_ARCHIVE_CONTACT", "mailto:unknown")})"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
# Look up the latest archived snapshot for a URL via Internet Archive CDX API
|
|
71
|
+
#
|
|
72
|
+
# @param url [String] the URL to look up
|
|
73
|
+
# @return [String, nil] the archive URL if found, nil otherwise
|
|
74
|
+
def lookup_archive(url)
|
|
75
|
+
log_debug("lookup_archive(#{url})")
|
|
76
|
+
|
|
77
|
+
encoded_url = URI.encode_www_form_component(url)
|
|
78
|
+
cdx_url_str = "https://web.archive.org/cdx/search/cdx?url=#{encoded_url}&output=json&filter=statuscode:200&limit=-1&fl=timestamp,original"
|
|
79
|
+
cdx_url = URI.parse(cdx_url_str)
|
|
80
|
+
|
|
81
|
+
log_debug("CDX lookup URL: #{cdx_url_str}")
|
|
82
|
+
|
|
83
|
+
response = Net::HTTP.start(
|
|
84
|
+
cdx_url.host,
|
|
85
|
+
cdx_url.port,
|
|
86
|
+
use_ssl: cdx_url.scheme == "https",
|
|
87
|
+
open_timeout: 10,
|
|
88
|
+
read_timeout: 30
|
|
89
|
+
) do |http|
|
|
90
|
+
http.request(Net::HTTP::Get.new(cdx_url.request_uri))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
94
|
+
log_debug("CDX lookup failed: #{response.code} #{response.message}")
|
|
95
|
+
return nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
log_debug("CDX lookup found archived page...")
|
|
99
|
+
rows = JSON.parse(response.body)
|
|
100
|
+
|
|
101
|
+
# First row is header, so we need at least 2 rows
|
|
102
|
+
return nil if rows.length <= 1
|
|
103
|
+
|
|
104
|
+
latest = rows.last
|
|
105
|
+
timestamp = latest[0]
|
|
106
|
+
archive_url = "https://web.archive.org/web/#{timestamp}/#{url}"
|
|
107
|
+
|
|
108
|
+
log_debug("CDX lookup found archived page: #{archive_url}")
|
|
109
|
+
archive_url
|
|
110
|
+
rescue StandardError => e
|
|
111
|
+
log_debug("CDX lookup error for #{url}: #{e.message}")
|
|
112
|
+
nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Submit a URL to Internet Archive SavePageNow service
|
|
116
|
+
#
|
|
117
|
+
# @param url [String] the URL to submit for archiving
|
|
118
|
+
# @return [String, nil] the archive URL if successful, nil otherwise
|
|
119
|
+
def submit_archive(url)
|
|
120
|
+
log_debug("submit_archive(#{url})")
|
|
121
|
+
|
|
122
|
+
encoded_url = URI.encode_www_form_component(url)
|
|
123
|
+
save_url = URI.parse("https://web.archive.org/save/#{encoded_url}")
|
|
124
|
+
|
|
125
|
+
response = Net::HTTP.start(
|
|
126
|
+
save_url.host,
|
|
127
|
+
save_url.port,
|
|
128
|
+
use_ssl: save_url.scheme == "https",
|
|
129
|
+
open_timeout: 10,
|
|
130
|
+
read_timeout: 30
|
|
131
|
+
) do |http|
|
|
132
|
+
req = Net::HTTP::Get.new(save_url.request_uri, { "User-Agent" => archive_user_agent })
|
|
133
|
+
http.request(req)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
location = response["content-location"]
|
|
137
|
+
|
|
138
|
+
if location && !location.empty?
|
|
139
|
+
archive_url = "https://web.archive.org#{location}"
|
|
140
|
+
log_info("SavePageNow archived #{url} -> #{archive_url}")
|
|
141
|
+
archive_url
|
|
142
|
+
else
|
|
143
|
+
log_debug("Archive submission returned no location for #{url}")
|
|
144
|
+
nil
|
|
145
|
+
end
|
|
146
|
+
rescue StandardError => e
|
|
147
|
+
log_debug("Archive submission error for #{url}: #{e.message}")
|
|
148
|
+
nil
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JekyllHighlightCards
|
|
4
|
+
# Parse dimension specifications in "WxH" format
|
|
5
|
+
#
|
|
6
|
+
# Supports multiple formats for specifying image dimensions:
|
|
7
|
+
# - `WIDTHxHEIGHT`: Both dimensions (e.g., "300x200")
|
|
8
|
+
# - `WIDTHx`: Width only (e.g., "300x")
|
|
9
|
+
# - `xHEIGHT`: Height only (e.g., "x200")
|
|
10
|
+
# - `WIDTH`: Width only shorthand (e.g., "300")
|
|
11
|
+
# - Units: Supports px, em, %, etc. (e.g., "400pxx300px")
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# include DimensionParser
|
|
15
|
+
# width, height = parse_dimensions("300x200") #=> ["300", "200"]
|
|
16
|
+
module DimensionParser
|
|
17
|
+
# Parse dimension string into width and height components
|
|
18
|
+
#
|
|
19
|
+
# @param dim_str [String] dimension string (e.g., "300x200", "300x", "x200", "300")
|
|
20
|
+
# @return [Array<String, nil>] array of [width, height] where nil indicates unspecified
|
|
21
|
+
#
|
|
22
|
+
# @example
|
|
23
|
+
# parse_dimensions("300x200") #=> ["300", "200"]
|
|
24
|
+
# parse_dimensions("300x") #=> ["300", nil]
|
|
25
|
+
# parse_dimensions("x200") #=> [nil, "200"]
|
|
26
|
+
# parse_dimensions("300") #=> ["300", nil]
|
|
27
|
+
# parse_dimensions("400px") #=> ["400px", nil]
|
|
28
|
+
def parse_dimensions(dim_str)
|
|
29
|
+
return [nil, nil] if dim_str.nil? || dim_str.empty?
|
|
30
|
+
|
|
31
|
+
# Determine the separator:
|
|
32
|
+
# - If "xx" is present, the SECOND 'x' is the separator (first 'x' is part of the dimension)
|
|
33
|
+
# Example: "400pxx300px" → width="400px", height="300px"
|
|
34
|
+
# - Otherwise, check if there's an 'x' that's a separator (not part of a unit like "px")
|
|
35
|
+
# An 'x' is a separator if it's at the end, followed by a digit, or at the start
|
|
36
|
+
if dim_str.include?("xx")
|
|
37
|
+
# Find the position of "xx"
|
|
38
|
+
idx = dim_str.index("xx")
|
|
39
|
+
# Split at the second 'x' (include first 'x' in width, skip both 'x's for height)
|
|
40
|
+
width = dim_str[0..idx].empty? ? nil : dim_str[0..idx]
|
|
41
|
+
height = dim_str[(idx + 2)..]
|
|
42
|
+
height = nil if height.nil? || height.empty?
|
|
43
|
+
[width, height]
|
|
44
|
+
elsif dim_str =~ /x\d/ || dim_str =~ /(?<![a-z])x$/i || dim_str.start_with?("x")
|
|
45
|
+
# Single 'x' separator in one of these cases:
|
|
46
|
+
# 1. Followed by a digit: "300x200", "10emx20em"
|
|
47
|
+
# 2. At end but NOT preceded by a letter: "300x"
|
|
48
|
+
# 3. At start: "x200"
|
|
49
|
+
# This matches "300x", "300x200", "x200", "10emx20em", but NOT "400px"
|
|
50
|
+
parts = dim_str.split("x", 2)
|
|
51
|
+
width = parts[0].empty? ? nil : parts[0]
|
|
52
|
+
height = parts[1].nil? || parts[1].empty? ? nil : parts[1]
|
|
53
|
+
[width, height]
|
|
54
|
+
else
|
|
55
|
+
# No separator found - treat as width only
|
|
56
|
+
[dim_str, nil]
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
module_function :parse_dimensions
|
|
61
|
+
end
|
|
62
|
+
end
|