Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions custom-site-assets/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,72 @@
write_settings,
)


def extract_note_description(lines: List[str]) -> str:
"""Extract a concise, clean summary (140-160 chars) from the markdown note content for SEO and OpenGraph."""
cleaned_chunks: List[str] = []
in_code_block = False
in_frontmatter = False

for line in lines:
stripped = line.strip()

# Handle YAML frontmatter delimiters
if stripped == "---":
in_frontmatter = not in_frontmatter
continue
if in_frontmatter:
continue

# Handle code blocks
if stripped.startswith("```"):
in_code_block = not in_code_block
continue
if in_code_block:
continue

# Skip empty lines, metadata markers, headings, list markers, quotes, and images
if not stripped or stripped.startswith(
("#", "!", ">", "Created:", "Updated:", "|", "$$")
):
continue

# Remove wikilinks [[Target|Alias]] -> Alias, [[Target]] -> Target
text = re.sub(r"\[\[(?:[^|\]]*\|)?([^\]]+)\]\]", r"\1", stripped)
# Remove standard markdown links [text](url) -> text
text = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", text)
# Remove LaTeX inline math $...$
text = re.sub(r"\$[^$]+\$", "", text)
# Remove markdown bold/italics/code/strikethrough
text = re.sub(r"[*_`~]", "", text)
# Remove html tags
text = re.sub(r"<[^>]+>", "", text)
# Remove backslashes and replace double quotes with single quotes
text = text.replace("\\", "").replace('"', "'")
# Normalize whitespace
text = " ".join(text.split())

if text:
cleaned_chunks.append(text)
if sum(len(c) for c in cleaned_chunks) >= 150:
break

full_summary = " ".join(cleaned_chunks)
# Sanitize backslashes, double quotes, and newlines
full_summary = (
full_summary.replace("\\", "").replace('"', "'").replace("\n", " ").strip()
)

if not full_summary:
return "Notes and research on machine learning, AI security, and cybersecurity."

if len(full_summary) > 155:
# Trim cleanly at word boundary
truncated = full_summary[:155].rsplit(" ", 1)[0]
return f"{truncated}..."
return full_summary


if __name__ == "__main__":
Settings.parse_env()
Settings.sub_file(site_dir / "config.toml")
Expand Down Expand Up @@ -47,14 +113,19 @@
words = " ".join(content).split()
reading_time = max(1, (len(words) + 199) // 200)

# Extract description for SEO & OpenGraph
description = extract_note_description(content)

content_frontmatter = [
"---",
f'title: "{doc_path.page_title}"',
f'description: "{description}"',
f"date: {doc_path.modified}",
f"updated: {doc_path.modified}",
"template: docs/page.html",
"extra:",
f" reading_time: {reading_time}",
f' meta_description: "{description}"',
"---",
# To add last line-break
"",
Expand Down
169 changes: 169 additions & 0 deletions custom-site-assets/templates/macros/head.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
{% macro resource() %}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600;700&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;1,6..72,400&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
{% endmacro %}


{% macro stylesheet() %}
<link rel="stylesheet" href="{{ get_url(path="main.css") | safe }}">
{% endmacro %}


{% macro favicons() %}
<meta name="theme-color" content="{{ config.extra.theme_color | default(value="#1b1c1e") }}">
<link rel="apple-touch-icon" sizes="180x180" href="{{ get_url(path="apple-touch-icon.png") | safe }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ get_url(path="favicon-32x32.png") | safe }}">
<link rel="icon" type="image/png" sizes="16x16" href="{{ get_url(path="favicon-16x16.png") | safe }}">
{% if not config.extra.is_netlify %}
<link rel="manifest" href="{{ get_url(path="site.webmanifest") | safe }}">
{% endif %}
{% endmacro %}


{# SEO & OpenGraph Macro #}
{% macro seo(
title="",
title_addition="",
description="",
type="website",
is_home=false,
is_404=false,
is_page=false,
page_images="",
page_section="",
created_time="2026-08-17T12:00:00+02:00",
updated_time="2026-08-17T12:00:00+02:00"
)
%}

{% if is_404 %}
<meta name="robots" content="noindex, follow">
{% else %}
<meta name="robots" content="index, follow">
<meta name="googlebot" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
<meta name="bingbot" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
{% endif %}

{% if current_url %}
{% set page_url = current_url %}
{% else %}
{% set page_url = config.base_url ~ "/404.html" %}
{% endif %}

{% if description and description != "" %}
{% set meta_desc = description %}
{% else %}
{% set meta_desc = "Gianfranco Romani's digital garden and second brain covering Machine Learning, AI Security, MLOps, and Cybersecurity." %}
{% endif %}

{% if title and title != "" %}
{% set page_title = title %}
{% else %}
{% set page_title = config.title | default(value="Gianfranco's Notes") %}
{% endif %}

<title>{% if title_addition and title_addition != "" %}{{ title_addition }}{% else %}{{ page_title }}{% endif %}</title>
<meta name="description" content="{{ meta_desc }}">
<link rel="canonical" href="{{ page_url | safe }}">

{# Twitter Card Meta Tags #}
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@Gianfree_">
<meta name="twitter:creator" content="@Gianfree_">
<meta name="twitter:title" content="{{ page_title }}">
<meta name="twitter:description" content="{{ meta_desc }}">

{# OpenGraph Meta Tags #}
<meta property="og:title" content="{{ page_title }}">
<meta property="og:description" content="{{ meta_desc }}">
<meta property="og:type" content="{% if is_page %}article{% else %}website{% endif %}">
<meta property="og:url" content="{{ page_url | safe }}">
<meta property="og:site_name" content="Gianfranco's Notes">
<meta property="og:locale" content="en_US">

{# Structured Data (JSON-LD) #}
{% if is_home %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Person",
"url": "{{ config.base_url | safe }}",
"name": "Gianfranco Romani",
"jobTitle": "Senior ML Engineer",
"worksFor": {
"@type": "Organization",
"name": "Thomson Reuters"
},
"description": "Senior ML Engineer specializing in AI-driven cybersecurity systems and agentic AI.",
"sameAs": [
"https://github.com/GianRomani",
"https://twitter.com/Gianfree_"
]
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"url": "{{ config.base_url | safe }}",
"name": "Gianfranco's Notes",
"description": "{{ meta_desc }}"
}
</script>
{% endif %}

{% if is_page %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "TechArticle",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "{{ page_url | safe }}"
},
"headline": "{{ page_title }}",
"description": "{{ meta_desc }}",
"datePublished": "{{ created_time }}",
"dateModified": "{{ updated_time }}",
"author": {
"@type": "Person",
"name": "Gianfranco Romani",
"url": "{{ config.base_url | safe }}"
},
"publisher": {
"@type": "Person",
"name": "Gianfranco Romani",
"url": "{{ config.base_url | safe }}"
}
}
</script>
{% endif %}

{% set url_item = config.base_url ~ "/" %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{ url_item | safe }}"
}
]
}
</script>

{% if config.extra.ganalytics and config.extra.ganalytics != "" %}
<script async src="https://www.googletagmanager.com/gtag/js?id={{ config.extra.ganalytics }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ config.extra.ganalytics }}');
</script>
{% endif %}
{% endmacro %}