jekyll-structured-content 0.2.0
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/CHANGELOG.md +12 -0
- data/CONTRIBUTING.md +23 -0
- data/LICENSE.txt +21 -0
- data/README.md +149 -0
- data/_includes/jekyll_structured_content/faq.html +44 -0
- data/lib/jekyll/structured_content/faq_json_ld_tag.rb +54 -0
- data/lib/jekyll/structured_content/schema_filters.rb +20 -0
- data/lib/jekyll/structured_content/schema_json_ld_tag.rb +185 -0
- data/lib/jekyll/structured_content/version.rb +5 -0
- data/lib/jekyll/structured_content.rb +9 -0
- data/lib/jekyll-structured-content.rb +4 -0
- metadata +69 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6bb8d4fb4c737c5628ac46602e10d8d9cff60bd549dd5ae1c37c8539e7326295
|
|
4
|
+
data.tar.gz: b742f29446ec10f1a052dd730ba7428f5ba39425c9f679fce8f4d1fad4443d27
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: acc9cc12cfe3809e578cdb8287e0a4eaf8cef7dfeea33e91b01f8031032ad683f676724d39c6e56658755de9b2db413ee4b40d60c839508af4be050bba4c36ad
|
|
7
|
+
data.tar.gz: 0d79e70c5fe6397b5270d919963eafe3b62f66e2b397b1e38f4c7c5732fedcd7714e03cd5610293c8ee299f983022307915d651d97cf5873a247bcfc951ffeb7
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
- Added `schema_json_ld` tag for post, page, and project schema output
|
|
6
|
+
- Added FAQ JSON-LD support
|
|
7
|
+
- Omit optional schema fields like `dateModified` when values are absent
|
|
8
|
+
- Clarified GitHub Pages compatibility caveats
|
|
9
|
+
|
|
10
|
+
## 0.1.0
|
|
11
|
+
|
|
12
|
+
- Initial plugin scaffold
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in improving `jekyll-structured-content`.
|
|
4
|
+
|
|
5
|
+
## Before submitting changes
|
|
6
|
+
|
|
7
|
+
1. Keep the scope narrow and focused on structured data helpers.
|
|
8
|
+
2. Avoid adding theme-specific rendering assumptions.
|
|
9
|
+
3. Add or update tests for behavior changes.
|
|
10
|
+
|
|
11
|
+
## Local development
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bundle install
|
|
15
|
+
ruby -Itest test/faq_json_ld_tag_test.rb
|
|
16
|
+
ruby -Itest test/schema_json_ld_tag_test.rb
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Design philosophy
|
|
20
|
+
|
|
21
|
+
- structured data in the plugin
|
|
22
|
+
- visible theme markup in the site
|
|
23
|
+
- explicit behavior over magic
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jason Chance
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# jekyll-structured-content
|
|
2
|
+
|
|
3
|
+
A lightweight Jekyll plugin for structured data helpers in content-driven Jekyll sites.
|
|
4
|
+
|
|
5
|
+
- FAQPage JSON-LD generation
|
|
6
|
+
- JSON-LD helpers for posts, pages, and project-like content
|
|
7
|
+
|
|
8
|
+
## Scope
|
|
9
|
+
|
|
10
|
+
This package is intentionally narrow. It focuses on generating clean, reusable structured output from frontmatter and templates without prescribing site copy, citation systems, or a full SEO strategy.
|
|
11
|
+
|
|
12
|
+
## Who this is for
|
|
13
|
+
|
|
14
|
+
This plugin is for people who:
|
|
15
|
+
|
|
16
|
+
- publish articles or reference content with Jekyll
|
|
17
|
+
- want cleaner machine-readable structured data
|
|
18
|
+
- prefer to keep visible theme markup in the site itself
|
|
19
|
+
- want a small plugin instead of a full SEO framework
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
1. Emit FAQPage JSON-LD from FAQ frontmatter.
|
|
24
|
+
2. Emit JSON-LD for post, page, and project-like content via a Liquid tag.
|
|
25
|
+
3. Leave visible FAQ and theme markup to the site.
|
|
26
|
+
|
|
27
|
+
## What this plugin does not do
|
|
28
|
+
|
|
29
|
+
- It does **not** render your visible FAQ accordion or theme markup.
|
|
30
|
+
- It does **not** manage page titles, meta descriptions, or Open Graph tags.
|
|
31
|
+
- It does **not** integrate with citation or bibliography plugins.
|
|
32
|
+
- It does **not** replace a full SEO plugin.
|
|
33
|
+
|
|
34
|
+
## GitHub Pages compatibility
|
|
35
|
+
|
|
36
|
+
This plugin is compatible with Jekyll sites deployed to GitHub Pages **when the site is built through a custom GitHub Actions workflow**.
|
|
37
|
+
|
|
38
|
+
It should **not** be described as compatible with the default GitHub Pages safe-mode build, because custom plugins are not loaded there.
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
Add the gem to your `Gemfile`:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
gem "jekyll-structured-content"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then add it to `_config.yml`:
|
|
49
|
+
|
|
50
|
+
```yml
|
|
51
|
+
plugins:
|
|
52
|
+
- jekyll-structured-content
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
### FAQ JSON-LD
|
|
58
|
+
|
|
59
|
+
```liquid
|
|
60
|
+
<script type="application/ld+json">{% faq_json_ld %}</script>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This reads from page frontmatter like:
|
|
64
|
+
|
|
65
|
+
```yml
|
|
66
|
+
faq:
|
|
67
|
+
- question: "What is this?"
|
|
68
|
+
answer: "A short answer."
|
|
69
|
+
- question: "Can I use title/description instead?"
|
|
70
|
+
answer: "Yes, FAQ items may also use title/description keys."
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Post schema JSON-LD
|
|
74
|
+
|
|
75
|
+
```liquid
|
|
76
|
+
<script type="application/ld+json">{% schema_json_ld post %}</script>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Page schema JSON-LD
|
|
80
|
+
|
|
81
|
+
```liquid
|
|
82
|
+
<script type="application/ld+json">{% schema_json_ld page %}</script>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Project schema JSON-LD
|
|
86
|
+
|
|
87
|
+
```liquid
|
|
88
|
+
<script type="application/ld+json">{% schema_json_ld project %}</script>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## What this plugin expects
|
|
92
|
+
|
|
93
|
+
This plugin works best when your documents include standard Jekyll frontmatter like:
|
|
94
|
+
|
|
95
|
+
- `title`
|
|
96
|
+
- `description`
|
|
97
|
+
- `date` for posts
|
|
98
|
+
- `url`
|
|
99
|
+
- `image`
|
|
100
|
+
- `category`
|
|
101
|
+
- `tags`
|
|
102
|
+
- `faq`
|
|
103
|
+
|
|
104
|
+
At the site level, it looks for these values when available:
|
|
105
|
+
|
|
106
|
+
- `site.data.settings.title`
|
|
107
|
+
- `site.data.settings.logo`
|
|
108
|
+
- `site.data.settings.author.author_name`
|
|
109
|
+
- `site.data.settings.description`
|
|
110
|
+
|
|
111
|
+
If these settings are missing, the plugin falls back where it can, but richer structured output depends on good site metadata.
|
|
112
|
+
|
|
113
|
+
## Output behavior
|
|
114
|
+
|
|
115
|
+
- `faq_json_ld` emits a `FAQPage`
|
|
116
|
+
- `schema_json_ld post` emits a `BlogPosting`
|
|
117
|
+
- `schema_json_ld page` emits a `WebPage`, or `ProfilePage` for `/about/`
|
|
118
|
+
- `schema_json_ld project` emits a `CreativeWork`
|
|
119
|
+
- optional fields like `dateModified` are omitted when not present
|
|
120
|
+
|
|
121
|
+
## Live example
|
|
122
|
+
|
|
123
|
+
Production example using post schema plus FAQPage schema:
|
|
124
|
+
|
|
125
|
+
- https://jasonchance.com/blog/the-georgia-rural-zone-playbook-how-small-downtowns-stack-three-state-tax-credits/
|
|
126
|
+
|
|
127
|
+
## Tested assumptions
|
|
128
|
+
|
|
129
|
+
- Jekyll 4.x
|
|
130
|
+
- Liquid 4.x
|
|
131
|
+
- local Jekyll builds
|
|
132
|
+
- GitHub Pages deployments built through GitHub Actions
|
|
133
|
+
|
|
134
|
+
## Development
|
|
135
|
+
|
|
136
|
+
Install dependencies:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
bundle install
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Run tests:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
ruby -Itest test/faq_json_ld_tag_test.rb
|
|
146
|
+
ruby -Itest test/schema_json_ld_tag_test.rb
|
|
147
|
+
ruby -Itest test/schema_page_and_project_test.rb
|
|
148
|
+
ruby -Itest test/faq_formats_test.rb
|
|
149
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{% assign faq_items = include.items | default: page.faq | default: site.data.settings.faq.faq_items %}
|
|
2
|
+
{% assign faq_title = include.title %}
|
|
3
|
+
{% assign faq_description = include.description %}
|
|
4
|
+
{% assign faq_enabled = include.enabled %}
|
|
5
|
+
|
|
6
|
+
{% if faq_enabled == nil %}
|
|
7
|
+
{% if include.items or page.faq %}
|
|
8
|
+
{% assign faq_enabled = true %}
|
|
9
|
+
{% else %}
|
|
10
|
+
{% assign faq_enabled = site.data.settings.faq.enable %}
|
|
11
|
+
{% endif %}
|
|
12
|
+
{% endif %}
|
|
13
|
+
|
|
14
|
+
{% if faq_enabled and faq_items and faq_items.size > 0 %}
|
|
15
|
+
<section class="section faq">
|
|
16
|
+
<div class="container">
|
|
17
|
+
<div class="row">
|
|
18
|
+
<div class="col col-12">
|
|
19
|
+
{% if faq_title or faq_description %}
|
|
20
|
+
<div class="section__info">
|
|
21
|
+
{% if faq_title %}
|
|
22
|
+
<h2 class="section__title">{{ faq_title }}</h2>
|
|
23
|
+
{% endif %}
|
|
24
|
+
{% if faq_description %}
|
|
25
|
+
<p class="section__description">{{ faq_description }}</p>
|
|
26
|
+
{% endif %}
|
|
27
|
+
</div>
|
|
28
|
+
{% endif %}
|
|
29
|
+
|
|
30
|
+
<div class="faq">
|
|
31
|
+
{% for faq_item in faq_items %}
|
|
32
|
+
<div class="faq__item" tabindex="0" data-name="closed">
|
|
33
|
+
<h3 class="faq__title">{{ faq_item.title | default: faq_item.question }}<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" fill="none"><path fill="var(--heading-font-color)" d="M8.654 1.168a1.153 1.153 0 1 0-2.308 0V6.36H1.154a1.153 1.153 0 1 0 0 2.308h5.192v5.192a1.153 1.153 0 1 0 2.308 0V8.668h5.192c.638 0 1.154-.516 1.154-1.154 0-.639-.516-1.154-1.154-1.154H8.654V1.168Z"/></svg></h3>
|
|
34
|
+
<div class="faq__description">
|
|
35
|
+
<p>{{ faq_item.description | default: faq_item.answer }}</p>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
{% endfor %}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</section>
|
|
44
|
+
{% endif %}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module StructuredContent
|
|
5
|
+
class FaqJsonLdTag < Liquid::Tag
|
|
6
|
+
def render(context)
|
|
7
|
+
page = context.registers[:page] || {}
|
|
8
|
+
faq_items = page["faq"]
|
|
9
|
+
|
|
10
|
+
return "" unless faq_items.is_a?(Array) && faq_items.any?
|
|
11
|
+
|
|
12
|
+
entities = faq_items.filter_map do |item|
|
|
13
|
+
question = value_for(item, "question", "title")
|
|
14
|
+
answer = value_for(item, "answer", "description")
|
|
15
|
+
next unless question && answer
|
|
16
|
+
|
|
17
|
+
{
|
|
18
|
+
"@type" => "Question",
|
|
19
|
+
"name" => question,
|
|
20
|
+
"acceptedAnswer" => {
|
|
21
|
+
"@type" => "Answer",
|
|
22
|
+
"text" => normalize_text(answer)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
return "" if entities.empty?
|
|
28
|
+
|
|
29
|
+
{
|
|
30
|
+
"@context" => "https://schema.org",
|
|
31
|
+
"@type" => "FAQPage",
|
|
32
|
+
"mainEntity" => entities
|
|
33
|
+
}.to_json
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def value_for(item, *keys)
|
|
39
|
+
keys.each do |key|
|
|
40
|
+
value = item[key] || item[key.to_sym]
|
|
41
|
+
return value if value && !value.to_s.strip.empty?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def normalize_text(text)
|
|
48
|
+
text.to_s.gsub(%r{<[^>]+>}, " ").gsub(/\s+/, " ").strip
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Liquid::Template.register_tag("faq_json_ld", Jekyll::StructuredContent::FaqJsonLdTag)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Jekyll
|
|
2
|
+
module StructuredContent
|
|
3
|
+
module SchemaFilters
|
|
4
|
+
def schema_type_for(input)
|
|
5
|
+
case input.to_s
|
|
6
|
+
when "post"
|
|
7
|
+
"BlogPosting"
|
|
8
|
+
when "page"
|
|
9
|
+
"WebPage"
|
|
10
|
+
when "project"
|
|
11
|
+
"CreativeWork"
|
|
12
|
+
else
|
|
13
|
+
"WebPage"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
Liquid::Template.register_filter(Jekyll::StructuredContent::SchemaFilters)
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module StructuredContent
|
|
5
|
+
class SchemaJsonLdTag < Liquid::Tag
|
|
6
|
+
def initialize(tag_name, markup, tokens)
|
|
7
|
+
super
|
|
8
|
+
@schema_kind = markup.to_s.strip
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def render(context)
|
|
12
|
+
page = context.registers[:page] || {}
|
|
13
|
+
site = context.registers[:site]
|
|
14
|
+
data = build_schema(@schema_kind, page, site)
|
|
15
|
+
|
|
16
|
+
return "" unless data
|
|
17
|
+
|
|
18
|
+
data.to_json
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def build_schema(kind, page, site)
|
|
24
|
+
case kind
|
|
25
|
+
when "post"
|
|
26
|
+
build_post_schema(page, site)
|
|
27
|
+
when "page"
|
|
28
|
+
build_page_schema(page, site)
|
|
29
|
+
when "project"
|
|
30
|
+
build_project_schema(page, site)
|
|
31
|
+
else
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def build_post_schema(page, site)
|
|
37
|
+
site_title = site_value(site, "data", "settings", "title")
|
|
38
|
+
author_name = site_value(site, "data", "settings", "author", "author_name") || site_title
|
|
39
|
+
description = page["description"] || page["excerpt"]
|
|
40
|
+
image = absolute_url(page["image"], site)
|
|
41
|
+
tags = page["tags"] || []
|
|
42
|
+
|
|
43
|
+
data = {
|
|
44
|
+
"@context" => "https://schema.org",
|
|
45
|
+
"@type" => "BlogPosting",
|
|
46
|
+
"headline" => page["title"],
|
|
47
|
+
"description" => normalize_text(description),
|
|
48
|
+
"datePublished" => xmlschema(page["date"]),
|
|
49
|
+
"mainEntityOfPage" => {
|
|
50
|
+
"@type" => "WebPage",
|
|
51
|
+
"@id" => absolute_url(page["url"], site)
|
|
52
|
+
},
|
|
53
|
+
"url" => absolute_url(page["url"], site),
|
|
54
|
+
"articleSection" => page["category"] || "Blog",
|
|
55
|
+
"keywords" => Array(tags).join(", "),
|
|
56
|
+
"wordCount" => word_count(page["content"]),
|
|
57
|
+
"isPartOf" => {
|
|
58
|
+
"@type" => "Blog",
|
|
59
|
+
"name" => site_title,
|
|
60
|
+
"url" => absolute_url("/blog/", site)
|
|
61
|
+
},
|
|
62
|
+
"about" => build_about_entities(page),
|
|
63
|
+
"author" => {
|
|
64
|
+
"@type" => "Person",
|
|
65
|
+
"name" => author_name
|
|
66
|
+
},
|
|
67
|
+
"publisher" => {
|
|
68
|
+
"@type" => "Organization",
|
|
69
|
+
"name" => site_title,
|
|
70
|
+
"logo" => {
|
|
71
|
+
"@type" => "ImageObject",
|
|
72
|
+
"url" => absolute_url(site_value(site, "data", "settings", "logo"), site)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
date_modified = xmlschema(page["last_modified_at"])
|
|
78
|
+
data["dateModified"] = date_modified if present_value?(date_modified)
|
|
79
|
+
data["image"] = { "@type" => "ImageObject", "url" => image } if image
|
|
80
|
+
data
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def build_page_schema(page, site)
|
|
84
|
+
site_title = site_value(site, "data", "settings", "title")
|
|
85
|
+
description = page["description"] || site_value(site, "data", "settings", "description")
|
|
86
|
+
image = absolute_url(page["image"], site)
|
|
87
|
+
page_type = page["url"] == "/about/" ? "ProfilePage" : "WebPage"
|
|
88
|
+
|
|
89
|
+
data = {
|
|
90
|
+
"@context" => "https://schema.org",
|
|
91
|
+
"@type" => page_type,
|
|
92
|
+
"name" => page["title"],
|
|
93
|
+
"description" => normalize_text(description),
|
|
94
|
+
"url" => absolute_url(page["url"], site)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
data["image"] = { "@type" => "ImageObject", "url" => image } if image
|
|
98
|
+
|
|
99
|
+
if page["url"] == "/about/"
|
|
100
|
+
data["mainEntity"] = {
|
|
101
|
+
"@type" => "Person",
|
|
102
|
+
"name" => site_value(site, "data", "settings", "author", "author_name") || site_title,
|
|
103
|
+
"description" => normalize_text(description),
|
|
104
|
+
"url" => absolute_url(page["url"], site)
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
data
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def build_project_schema(page, site)
|
|
112
|
+
data = {
|
|
113
|
+
"@context" => "https://schema.org",
|
|
114
|
+
"@type" => "CreativeWork",
|
|
115
|
+
"name" => page["title"],
|
|
116
|
+
"description" => normalize_text(page["description"] || page["excerpt"]),
|
|
117
|
+
"url" => absolute_url(page["url"], site)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
image = absolute_url(page["image"], site)
|
|
121
|
+
data["image"] = image if image
|
|
122
|
+
data
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def build_about_entities(page)
|
|
126
|
+
entities = []
|
|
127
|
+
|
|
128
|
+
entities << { "@type" => "Thing", "name" => page["category"] } if page["category"]
|
|
129
|
+
Array(page["tags"]).each do |tag|
|
|
130
|
+
entities << { "@type" => "Thing", "name" => tag }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
entities
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def absolute_url(path, site)
|
|
137
|
+
return nil unless path && site.respond_to?(:config)
|
|
138
|
+
|
|
139
|
+
Jekyll::URL.new(
|
|
140
|
+
template: ":path",
|
|
141
|
+
placeholders: { path: path.to_s }
|
|
142
|
+
).to_s
|
|
143
|
+
|
|
144
|
+
base = site.config["url"].to_s.sub(%r{/$}, "")
|
|
145
|
+
prefix = site.config["baseurl"].to_s
|
|
146
|
+
normalized_path = path.to_s.start_with?("/") ? path.to_s : "/#{path}"
|
|
147
|
+
"#{base}#{prefix}#{normalized_path}"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def xmlschema(value)
|
|
151
|
+
return nil unless value.respond_to?(:xmlschema)
|
|
152
|
+
|
|
153
|
+
value.xmlschema
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def present_value?(value)
|
|
157
|
+
!value.nil? && !(value.respond_to?(:empty?) && value.empty?)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def word_count(content)
|
|
161
|
+
normalize_text(content).split.size
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def normalize_text(text)
|
|
165
|
+
text.to_s.gsub(%r{<[^>]+>}, " ").gsub(/\s+/, " ").strip
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def site_value(site, *keys)
|
|
169
|
+
current = site
|
|
170
|
+
keys.each do |key|
|
|
171
|
+
current =
|
|
172
|
+
if current.respond_to?(:[])
|
|
173
|
+
current[key]
|
|
174
|
+
elsif current.respond_to?(key)
|
|
175
|
+
current.public_send(key)
|
|
176
|
+
end
|
|
177
|
+
return nil if current.nil?
|
|
178
|
+
end
|
|
179
|
+
current
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
Liquid::Template.register_tag("schema_json_ld", Jekyll::StructuredContent::SchemaJsonLdTag)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
require_relative "structured_content/version"
|
|
2
|
+
require_relative "structured_content/faq_json_ld_tag"
|
|
3
|
+
require_relative "structured_content/schema_json_ld_tag"
|
|
4
|
+
require_relative "structured_content/schema_filters"
|
|
5
|
+
|
|
6
|
+
module Jekyll
|
|
7
|
+
module StructuredContent
|
|
8
|
+
end
|
|
9
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: jekyll-structured-content
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jason Chance
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-08-07 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: jekyll
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '4.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '4.0'
|
|
26
|
+
description: A focused Jekyll plugin for FAQPage JSON-LD and post, page, and project
|
|
27
|
+
schema helpers.
|
|
28
|
+
email:
|
|
29
|
+
- jchance@gmail.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- CHANGELOG.md
|
|
35
|
+
- CONTRIBUTING.md
|
|
36
|
+
- LICENSE.txt
|
|
37
|
+
- README.md
|
|
38
|
+
- _includes/jekyll_structured_content/faq.html
|
|
39
|
+
- lib/jekyll-structured-content.rb
|
|
40
|
+
- lib/jekyll/structured_content.rb
|
|
41
|
+
- lib/jekyll/structured_content/faq_json_ld_tag.rb
|
|
42
|
+
- lib/jekyll/structured_content/schema_filters.rb
|
|
43
|
+
- lib/jekyll/structured_content/schema_json_ld_tag.rb
|
|
44
|
+
- lib/jekyll/structured_content/version.rb
|
|
45
|
+
homepage: https://github.com/jchance/jekyll-structured-content
|
|
46
|
+
licenses:
|
|
47
|
+
- MIT
|
|
48
|
+
metadata:
|
|
49
|
+
homepage_uri: https://github.com/jchance/jekyll-structured-content
|
|
50
|
+
source_code_uri: https://github.com/jchance/jekyll-structured-content
|
|
51
|
+
changelog_uri: https://github.com/jchance/jekyll-structured-content/blob/main/CHANGELOG.md
|
|
52
|
+
rdoc_options: []
|
|
53
|
+
require_paths:
|
|
54
|
+
- lib
|
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '0'
|
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
|
+
requirements:
|
|
62
|
+
- - ">="
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
version: '0'
|
|
65
|
+
requirements: []
|
|
66
|
+
rubygems_version: 3.6.2
|
|
67
|
+
specification_version: 4
|
|
68
|
+
summary: Structured data helpers for Jekyll content
|
|
69
|
+
test_files: []
|