Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add h-card and h-entry microformats #482

Merged
merged 1 commit into from
Dec 17, 2023
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
10 changes: 7 additions & 3 deletions server/web/templates/components/jumbotron.jinja
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
{% macro Jumbotron() %}
<div class="f-background-surface f-box f-stack" style="--box-padding: calc(4 * var(--w)); --stack-gap: calc(2 * var(--w))">
<div class="f-background-surface f-box f-stack h-card" style="--box-padding: calc(4 * var(--w)); --stack-gap: calc(2 * var(--w))">
<div class="f-container f-stack" style="--container-padding: 0">
<div class="f-cluster" style="--cluster-justify: center; --cluster-align: center; --cluster-gap: calc(4 * var(--w))">
<div class="f-cluster f-jumbotron-picture">
<img src="{{ url_for('static', path='img/me.png') }}" alt="" />
<img class="u-photo" src="{{ url_for('static', path='img/me.png') }}" alt="" />
</div>

<div class="f-stack f-jumbotron-content" style="--stack-gap: var(--w)">
<h1>Florimond Manca</h1>
<h1>
<a href="{{ url_for('home') }}" class="p-name u-url">
Florimond Manca
</a>
</h1>

<h2>
{{ _('Open Source Developer, Idealist On A Journey') }}
Expand Down
13 changes: 7 additions & 6 deletions server/web/templates/views/post/detail.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,35 @@
</p>
{%- endif -%}

<div class="f-stack" style="--stack-gap: calc(6 * var(--w))">
<article class="f-stack h-entry" style="--stack-gap: calc(6 * var(--w))">
<div class="f-stack" style="--stack-gap: calc(2 * var(--w))">
{{ PostMeta(post, editable=true) }}

<h1>
<h1 class="p-name">
{{ post.name }}
</h1>

{% if post.abstract -%}
<p class="fr-text-muted f-text-bold">
<p class="fr-text-muted f-text-bold p-summary">
{{ post.abstract }}
</p>
{%- endif %}

<p>
<time datetime="{{ post.date_published }}">
<a class="p-author h-card" href="{{ url_for('home') }}">Florimond Manca</a>,
<time class="dt-published" datetime="{{ post.date_published }}">
{{ post.date_published | dateformat }}
</time>
</p>
</div>

<div class="f-markdown">
<div class="f-markdown e-content">
{{ post.text | safe }}
</div>

<div class="f-print-hidden">
{{ UtterancesComments() }}
</div>
</div>
</article>
</main>
{% endblock content %}
42 changes: 39 additions & 3 deletions tests/test_indieweb.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

from server import settings

from .utils import find_rel_me_links, find_webmention_url
from .utils import find_rel_me_links, find_webmention_url, parse_hcard, parse_hentry


@pytest.mark.asyncio
async def test_webmentions_url(client: httpx.AsyncClient) -> None:
url = "https://florimond.dev"
url = "http://florimond.dev"
response = await client.get(url)
assert response.status_code == 200
html = response.text
Expand All @@ -19,10 +19,46 @@ async def test_webmentions_url(client: httpx.AsyncClient) -> None:

@pytest.mark.asyncio
async def test_rel_me_links(client: httpx.AsyncClient) -> None:
url = "https://florimond.dev"
url = "http://florimond.dev"
response = await client.get(url)
assert response.status_code == 200
html = response.text

urls = find_rel_me_links(html)
assert urls == [link["href"] for link in settings.SOCIAL_LINKS]


@pytest.mark.asyncio
async def test_hcard(client: httpx.AsyncClient) -> None:
url = "http://florimond.dev"
response = await client.get(url)
assert response.status_code == 200
html = response.text

hcard = parse_hcard(html)

assert hcard == {
"p-name": "http://florimond.dev/en/",
"u-url": "http://florimond.dev/en/",
"u-photo": "http://florimond.dev/static/img/me.png",
}


@pytest.mark.asyncio
async def test_hentry(client: httpx.AsyncClient) -> None:
url = "http://florimond.dev/en/posts/2018/07/let-the-journey-begin"
response = await client.get(url)
assert response.status_code == 200
html = response.text

hentry = parse_hentry(html)

assert hentry["p-name"].strip() == "Let the Journey begin"
assert hentry["p-summary"].strip() == (
"Hi! My name is Florimond. "
"I will be your captain for the length of this journey. 👨‍✈️"
)
assert hentry["p-author"] == "http://florimond.dev/en/"
assert hentry["h-card"] == "http://florimond.dev/en/"
assert hentry["dt-published"] == "2018-07-25"
assert hentry["e-content"].startswith("Welcome to CodeSail!")
83 changes: 83 additions & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,86 @@ def find_rel_me_links(html: str) -> list[str]:
parser.feed(html)

return [href for tag, rel, href in parser.rels if tag == "a" and rel == "me"]


class HCardParser(HTMLParser):
def __init__(self) -> None:
super().__init__()
self.hcard: dict = {}
self._in_hcard = False

def handle_starttag(self, tag: str, attrs: list) -> None:
attr = dict(attrs)

classattr = attr.get("class", "")

if "h-card" in classattr:
self._in_hcard = True

if self._in_hcard:
if tag == "img" and "u-photo" in classattr:
self.hcard["u-photo"] = attr["src"]

if tag == "a" and "p-name" in classattr:
self.hcard["p-name"] = attr["href"]

if tag == "a" and "u-url" in classattr:
self.hcard["u-url"] = attr["href"]


def parse_hcard(html: str) -> dict:
parser = HCardParser()
parser.feed(html)
return parser.hcard


class HEntryParser(HTMLParser):
def __init__(self) -> None:
super().__init__()
self.hentry: dict = {}
self._in_hentry = False
self._field = ""

def handle_starttag(self, tag: str, attrs: list) -> None:
attr = dict(attrs)

classattr = attr.get("class", "")

if tag == "article" and "h-entry" in classattr:
self._in_hentry = True

if self._in_hentry:
if tag == "h1" and "p-name" in classattr:
assert not self._field
self._field = "p-name"

if "p-summary" in classattr:
assert not self._field
self._field = "p-summary"

if tag == "a" and "p-author" in classattr:
self.hentry["p-author"] = attr["href"]

if tag == "a" and "h-card" in classattr:
self.hentry["h-card"] = attr["href"]

if tag == "time" and "dt-published" in classattr:
self.hentry["dt-published"] = attr["datetime"]

if "e-content" in classattr:
assert not self._field
self._field = "e-content"

def handle_data(self, data: str) -> None:
if self._field:
self.hentry[self._field] = data

def handle_endtag(self, _: str) -> None:
if self._field:
self._field = ""


def parse_hentry(html: str) -> dict:
parser = HEntryParser()
parser.feed(html)
return parser.hentry