mirror of
https://github.com/samsonjs/samhuri.net.git
synced 2026-04-27 14:57:40 +00:00
Fix detecting gemini:// links in JS and remove unused properties from site.toml
This commit is contained in:
parent
5255247823
commit
d2fc41fb1e
6 changed files with 122 additions and 65 deletions
|
|
@ -214,7 +214,9 @@ module Pressa
|
||||||
remote_links = build_output_links(
|
remote_links = build_output_links(
|
||||||
format_config["remote_links"],
|
format_config["remote_links"],
|
||||||
context: "site.toml outputs.html.remote_links",
|
context: "site.toml outputs.html.remote_links",
|
||||||
allow_icon: true
|
allow_icon: true,
|
||||||
|
allow_label_optional: false,
|
||||||
|
allow_string_entries: false
|
||||||
)
|
)
|
||||||
|
|
||||||
HTMLOutputOptions.new(
|
HTMLOutputOptions.new(
|
||||||
|
|
@ -236,7 +238,9 @@ module Pressa
|
||||||
home_links = build_output_links(
|
home_links = build_output_links(
|
||||||
format_config["home_links"],
|
format_config["home_links"],
|
||||||
context: "site.toml outputs.gemini.home_links",
|
context: "site.toml outputs.gemini.home_links",
|
||||||
allow_icon: false
|
allow_icon: false,
|
||||||
|
allow_label_optional: true,
|
||||||
|
allow_string_entries: true
|
||||||
)
|
)
|
||||||
recent_posts_limit = build_recent_posts_limit(
|
recent_posts_limit = build_recent_posts_limit(
|
||||||
format_config["recent_posts_limit"],
|
format_config["recent_posts_limit"],
|
||||||
|
|
@ -339,11 +343,21 @@ module Pressa
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_output_links(value, context:, allow_icon:)
|
def build_output_links(value, context:, allow_icon:, allow_label_optional:, allow_string_entries:)
|
||||||
entries = array_or_empty(value, context)
|
entries = array_or_empty(value, context)
|
||||||
entries.map.with_index do |entry, index|
|
entries.map.with_index do |entry, index|
|
||||||
|
if allow_string_entries && entry.is_a?(String)
|
||||||
|
href = entry
|
||||||
|
unless !href.strip.empty?
|
||||||
|
raise ValidationError, "Expected #{context}[#{index}] to be a non-empty String"
|
||||||
|
end
|
||||||
|
validate_link_href!(href.strip, context: "#{context}[#{index}]")
|
||||||
|
|
||||||
|
next OutputLink.new(label: nil, href: href.strip, icon: nil)
|
||||||
|
end
|
||||||
|
|
||||||
unless entry.is_a?(Hash)
|
unless entry.is_a?(Hash)
|
||||||
raise ValidationError, "Expected #{context}[#{index}] to be a table"
|
raise ValidationError, "Expected #{context}[#{index}] to be a String or table"
|
||||||
end
|
end
|
||||||
|
|
||||||
allowed_keys = allow_icon ? %w[label href icon] : %w[label href]
|
allowed_keys = allow_icon ? %w[label href icon] : %w[label href]
|
||||||
|
|
@ -353,16 +367,23 @@ module Pressa
|
||||||
context: "#{context}[#{index}]"
|
context: "#{context}[#{index}]"
|
||||||
)
|
)
|
||||||
|
|
||||||
label = entry["label"]
|
|
||||||
href = entry["href"]
|
href = entry["href"]
|
||||||
unless label.is_a?(String) && !label.strip.empty?
|
|
||||||
raise ValidationError, "Expected #{context}[#{index}].label to be a non-empty String"
|
|
||||||
end
|
|
||||||
unless href.is_a?(String) && !href.strip.empty?
|
unless href.is_a?(String) && !href.strip.empty?
|
||||||
raise ValidationError, "Expected #{context}[#{index}].href to be a non-empty String"
|
raise ValidationError, "Expected #{context}[#{index}].href to be a non-empty String"
|
||||||
end
|
end
|
||||||
validate_link_href!(href.strip, context: "#{context}[#{index}].href")
|
validate_link_href!(href.strip, context: "#{context}[#{index}].href")
|
||||||
|
|
||||||
|
label = entry["label"]
|
||||||
|
if label.nil?
|
||||||
|
unless allow_label_optional
|
||||||
|
raise ValidationError, "Expected #{context}[#{index}].label to be a non-empty String"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
unless label.is_a?(String) && !label.strip.empty?
|
||||||
|
raise ValidationError, "Expected #{context}[#{index}].label to be a non-empty String"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
icon = entry["icon"]
|
icon = entry["icon"]
|
||||||
unless allow_icon
|
unless allow_icon
|
||||||
if entry.key?("icon")
|
if entry.key?("icon")
|
||||||
|
|
@ -376,7 +397,7 @@ module Pressa
|
||||||
raise ValidationError, "Expected #{context}[#{index}].icon to be a non-empty String"
|
raise ValidationError, "Expected #{context}[#{index}].icon to be a non-empty String"
|
||||||
end
|
end
|
||||||
|
|
||||||
OutputLink.new(label: label.strip, href: href.strip, icon: icon&.strip)
|
OutputLink.new(label: label&.strip, href: href.strip, icon: icon&.strip)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,12 @@ module Pressa
|
||||||
def write_recent_posts(target_path:, limit: RECENT_POSTS_LIMIT)
|
def write_recent_posts(target_path:, limit: RECENT_POSTS_LIMIT)
|
||||||
rows = ["# #{@site.title}", ""]
|
rows = ["# #{@site.title}", ""]
|
||||||
home_links.each do |link|
|
home_links.each do |link|
|
||||||
rows << "=> #{link.href}"
|
label = link.label&.strip
|
||||||
|
rows << if label.nil? || label.empty?
|
||||||
|
"=> #{link.href}"
|
||||||
|
else
|
||||||
|
"=> #{link.href} #{label}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
rows << "" unless home_links.empty?
|
rows << "" unless home_links.empty?
|
||||||
rows << "## Recent posts"
|
rows << "## Recent posts"
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ module Pressa
|
||||||
end
|
end
|
||||||
|
|
||||||
class OutputLink < Dry::Struct
|
class OutputLink < Dry::Struct
|
||||||
attribute :label, Types::String
|
# label is required for HTML remote links, but Gemini home_links may omit it.
|
||||||
|
attribute :label, Types::String.optional.default(nil)
|
||||||
attribute :href, Types::String
|
attribute :href, Types::String
|
||||||
attribute :icon, Types::String.optional.default(nil)
|
attribute :icon, Types::String.optional.default(nil)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -199,60 +199,64 @@ module Pressa
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupGeminiFallback() {
|
function setupGeminiFallback() {
|
||||||
var link = document.querySelector(
|
var links = document.querySelectorAll(
|
||||||
'header.primary nav.remote li.gemini a[href^="gemini://"]'
|
'header.primary nav.remote a[href^="gemini://"]'
|
||||||
);
|
);
|
||||||
if (!link) return;
|
if (!links || links.length === 0) return;
|
||||||
|
|
||||||
link.addEventListener("click", function (e) {
|
for (var i = 0; i < links.length; i++) {
|
||||||
if (!isPlainLeftClick(e)) return;
|
(function (link) {
|
||||||
|
link.addEventListener("click", function (e) {
|
||||||
|
if (!isPlainLeftClick(e)) return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
var geminiHref = link.getAttribute("href");
|
var geminiHref = link.getAttribute("href");
|
||||||
var fallbackHref = "https://geminiprotocol.net";
|
var fallbackHref = "https://geminiprotocol.net";
|
||||||
|
|
||||||
var done = false;
|
var done = false;
|
||||||
var fallbackTimer = null;
|
var fallbackTimer = null;
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
if (fallbackTimer) window.clearTimeout(fallbackTimer);
|
if (fallbackTimer) window.clearTimeout(fallbackTimer);
|
||||||
document.removeEventListener("visibilitychange", onVisibilityChange);
|
document.removeEventListener("visibilitychange", onVisibilityChange);
|
||||||
window.removeEventListener("pagehide", onPageHide);
|
window.removeEventListener("pagehide", onPageHide);
|
||||||
window.removeEventListener("blur", onBlur);
|
window.removeEventListener("blur", onBlur);
|
||||||
}
|
}
|
||||||
|
|
||||||
function markDone() {
|
function markDone() {
|
||||||
done = true;
|
done = true;
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onVisibilityChange() {
|
function onVisibilityChange() {
|
||||||
// If a handler opens and the browser backgrounded, consider it "successful".
|
// If a handler opens and the browser backgrounded, consider it "successful".
|
||||||
if (document.visibilityState === "hidden") markDone();
|
if (document.visibilityState === "hidden") markDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPageHide() {
|
function onPageHide() {
|
||||||
markDone();
|
markDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBlur() {
|
function onBlur() {
|
||||||
// Some browsers blur the page when a protocol handler is invoked.
|
// Some browsers blur the page when a protocol handler is invoked.
|
||||||
markDone();
|
markDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("visibilitychange", onVisibilityChange);
|
document.addEventListener("visibilitychange", onVisibilityChange);
|
||||||
window.addEventListener("pagehide", onPageHide, { once: true });
|
window.addEventListener("pagehide", onPageHide, { once: true });
|
||||||
window.addEventListener("blur", onBlur, { once: true });
|
window.addEventListener("blur", onBlur, { once: true });
|
||||||
|
|
||||||
// If we're still here shortly after attempting navigation, assume it failed.
|
// If we're still here shortly after attempting navigation, assume it failed.
|
||||||
fallbackTimer = window.setTimeout(function () {
|
fallbackTimer = window.setTimeout(function () {
|
||||||
if (done) return;
|
if (done) return;
|
||||||
window.location.href = fallbackHref;
|
window.location.href = fallbackHref;
|
||||||
}, 900);
|
}, 900);
|
||||||
|
|
||||||
window.location.href = geminiHref;
|
window.location.href = geminiHref;
|
||||||
});
|
});
|
||||||
|
})(links[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
|
|
|
||||||
16
site.toml
16
site.toml
|
|
@ -29,14 +29,14 @@ remote_links = [
|
||||||
[outputs.gemini]
|
[outputs.gemini]
|
||||||
recent_posts_limit = 20
|
recent_posts_limit = 20
|
||||||
home_links = [
|
home_links = [
|
||||||
{"label": "About", "href": "/about"},
|
"/about",
|
||||||
{"label": "Posts", "href": "/posts"},
|
"/posts",
|
||||||
{"label": "Projects", "href": "/projects"},
|
"/projects",
|
||||||
{"label": "Mastodon", "href": "https://techhub.social/@sjs"},
|
"https://techhub.social/@sjs",
|
||||||
{"label": "GitHub", "href": "https://github.com/samsonjs"},
|
"https://github.com/samsonjs",
|
||||||
{"label": "RSS (Gemini)", "href": "/posts/feed.gmi"},
|
"/posts/feed.gmi",
|
||||||
{"label": "RSS (Web)", "href": "https://samhuri.net/feed.xml"},
|
"https://samhuri.net/feed.xml",
|
||||||
{"label": "Email", "href": "mailto:sami@samhuri.net"}
|
"mailto:sami@samhuri.net"
|
||||||
]
|
]
|
||||||
exclude_public = [
|
exclude_public = [
|
||||||
"tweets/**",
|
"tweets/**",
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ class Pressa::Config::LoaderTest < Minitest::Test
|
||||||
assert_equal(["Pressa::Utils::GeminiMarkdownRenderer"], site.renderers.map(&:class).map(&:name))
|
assert_equal(["Pressa::Utils::GeminiMarkdownRenderer"], site.renderers.map(&:class).map(&:name))
|
||||||
assert_equal(["tweets/**"], site.public_excludes)
|
assert_equal(["tweets/**"], site.public_excludes)
|
||||||
assert_equal(20, site.gemini_output_options&.recent_posts_limit)
|
assert_equal(20, site.gemini_output_options&.recent_posts_limit)
|
||||||
assert_equal(["About", "GitHub"], site.gemini_output_options&.home_links&.map(&:label))
|
assert_equal(["/about/", "https://github.com/samsonjs"], site.gemini_output_options&.home_links&.map(&:href))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -561,6 +561,35 @@ class Pressa::Config::LoaderTest < Minitest::Test
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_build_site_allows_string_home_links_and_optional_labels_for_gemini
|
||||||
|
Dir.mktmpdir do |dir|
|
||||||
|
File.write(File.join(dir, "site.toml"), <<~TOML)
|
||||||
|
author = "Sami Samhuri"
|
||||||
|
email = "sami@samhuri.net"
|
||||||
|
title = "samhuri.net"
|
||||||
|
description = "blog"
|
||||||
|
url = "https://samhuri.net"
|
||||||
|
|
||||||
|
[outputs.gemini]
|
||||||
|
home_links = [
|
||||||
|
"/about/",
|
||||||
|
{"href": "/posts/"},
|
||||||
|
{"label": "GitHub", "href": "https://github.com/samsonjs"}
|
||||||
|
]
|
||||||
|
TOML
|
||||||
|
File.write(File.join(dir, "projects.toml"), "projects = []\n")
|
||||||
|
|
||||||
|
loader = Pressa::Config::Loader.new(source_path: dir)
|
||||||
|
site = loader.build_site(output_format: "gemini")
|
||||||
|
|
||||||
|
assert_equal("gemini", site.output_format)
|
||||||
|
assert_equal(3, site.gemini_output_options&.home_links&.length)
|
||||||
|
assert_nil(site.gemini_output_options&.home_links&.at(0)&.label)
|
||||||
|
assert_nil(site.gemini_output_options&.home_links&.at(1)&.label)
|
||||||
|
assert_equal("GitHub", site.gemini_output_options&.home_links&.at(2)&.label)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def with_temp_config
|
def with_temp_config
|
||||||
|
|
@ -588,10 +617,7 @@ class Pressa::Config::LoaderTest < Minitest::Test
|
||||||
|
|
||||||
[outputs.gemini]
|
[outputs.gemini]
|
||||||
recent_posts_limit = 20
|
recent_posts_limit = 20
|
||||||
home_links = [
|
home_links = ["/about/", "https://github.com/samsonjs"]
|
||||||
{"label": "About", "href": "/about/"},
|
|
||||||
{"label": "GitHub", "href": "https://github.com/samsonjs"}
|
|
||||||
]
|
|
||||||
exclude_public = ["tweets/**"]
|
exclude_public = ["tweets/**"]
|
||||||
TOML
|
TOML
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue