mirror of
https://github.com/trwnh/hugo-content-adapter-mastodon.git
synced 2024-12-27 09:08:46 +00:00
WIP: Add content adapter and basic user profile layout
This commit is contained in:
parent
08fc095ddd
commit
b99f7d2c2c
10 changed files with 572 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
.hugo_build.lock
|
||||
assets/mastodon-archive/*.json
|
||||
assets/mastodon-archive/avatar.*
|
||||
assets/mastodon-archive/header.*
|
||||
|
|
1
README.md
Normal file
1
README.md
Normal file
|
@ -0,0 +1 @@
|
|||
# hugo-content-adapter-mastodon
|
166
content/mastodon-archive/_content.gotmpl
Normal file
166
content/mastodon-archive/_content.gotmpl
Normal file
|
@ -0,0 +1,166 @@
|
|||
{{ with resources.GetMatch "mastodon-archive/avatar.*" }}
|
||||
|
||||
{{ $avatar_content := dict "mediaType" .MediaType.Type "value" .Content }}
|
||||
{{ $avatar_resource := dict
|
||||
"path" (printf "avatar.%s" (index .MediaType.Suffixes 0))
|
||||
"content" $avatar_content
|
||||
}}
|
||||
{{ $.AddResource $avatar_resource }}
|
||||
|
||||
{{ end }}
|
||||
|
||||
{{ with resources.GetMatch "mastodon-archive/header.*" }}
|
||||
|
||||
{{ $header_content := dict "mediaType" .MediaType.Type "value" .Content }}
|
||||
{{ $header_resource := dict
|
||||
"path" (printf "header.%s" (index .MediaType.Suffixes 0))
|
||||
"content" $header_content
|
||||
}}
|
||||
{{ $.AddResource $header_resource }}
|
||||
|
||||
{{ end }}
|
||||
|
||||
{{ with unmarshal (resources.Get "mastodon-archive/actor.json") }}
|
||||
|
||||
{{ $account_page := dict
|
||||
"path" "."
|
||||
"kind" "section"
|
||||
"title" .name
|
||||
"summary" (.summary | plainify)
|
||||
"content" (dict
|
||||
"mediaType" "text/html"
|
||||
"value" ""
|
||||
)
|
||||
"type" "mastodon"
|
||||
"layout" "user"
|
||||
"params" (dict
|
||||
"mastodon" (dict
|
||||
"_model" "Account"
|
||||
"username" .preferredUsername
|
||||
"created_at" (.published | time)
|
||||
"note" .summary
|
||||
"display_name" .name
|
||||
)
|
||||
)
|
||||
}}
|
||||
{{ $.AddPage $account_page }}
|
||||
|
||||
{{ end }}
|
||||
|
||||
{{ with unmarshal (resources.Get "mastodon-archive/outbox.json") }}
|
||||
|
||||
{{ $total := .totalItems }}
|
||||
{{ $activities := last 1000 .orderedItems }}
|
||||
|
||||
{{ range $activities }}
|
||||
|
||||
{{ $published := .published | time }}
|
||||
|
||||
{{/* Extract database ID to use as a path later */}}
|
||||
{{ $status_id :=
|
||||
.id
|
||||
| strings.TrimPrefix .actor
|
||||
| strings.TrimPrefix "/statuses/"
|
||||
| strings.TrimSuffix "/activity"
|
||||
}}
|
||||
|
||||
{{/* Calculate Mastodon-style visibility scope */}}
|
||||
{{ $followers := printf "%s/followers" .actor }}
|
||||
{{ $visibility := "direct" }}
|
||||
{{ if or
|
||||
(in .to "https://www.w3.org/ns/activitystreams#Public")
|
||||
(in .to "as:Public")
|
||||
(in .to "Public")
|
||||
}}
|
||||
{{ $visibility = "public" }}
|
||||
{{ else if or
|
||||
(in .cc "https://www.w3.org/ns/activitystreams#Public")
|
||||
(in .cc "as:Public")
|
||||
(in .cc "Public")
|
||||
}}
|
||||
{{ $visibility = "unlisted" }}
|
||||
{{ else if or
|
||||
(in .to $followers)
|
||||
(in .cc $followers)
|
||||
}}
|
||||
{{ $visibility = "private" }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Convert Create activities into a status */}}
|
||||
{{ if eq .type "Create" }}
|
||||
|
||||
{{/* Initialize Hugo Page and add it */}}
|
||||
{{ $page := dict
|
||||
"path" $status_id
|
||||
"content" (dict
|
||||
"mediaType" "text/html"
|
||||
"value" .object.content
|
||||
)
|
||||
"dates" (dict
|
||||
"date" $published
|
||||
)
|
||||
"type" "mastodon"
|
||||
"layout" "status"
|
||||
"params" (dict
|
||||
"mastodon" (dict
|
||||
"_model" "Status"
|
||||
"_post_type" "original"
|
||||
"id" $status_id
|
||||
"visibility" $visibility
|
||||
"spoiler_text" .object.summary
|
||||
)
|
||||
)
|
||||
}}
|
||||
{{ $.AddPage $page }}
|
||||
|
||||
{{ else if eq .type "Announce" }}
|
||||
|
||||
{{ $page := dict
|
||||
"path" $status_id
|
||||
"dates" (dict
|
||||
"date" $published
|
||||
)
|
||||
"type" "mastodon"
|
||||
"layout" "status"
|
||||
"params" (dict
|
||||
"mastodon" (dict
|
||||
"_model" "Status"
|
||||
"_post_type" "reblog"
|
||||
"_reblog_of_uri" .object
|
||||
)
|
||||
)
|
||||
}}
|
||||
{{ $.AddPage $page }}
|
||||
|
||||
{{ else }}
|
||||
|
||||
{{ $page := dict
|
||||
"path" $status_id
|
||||
"dates" (dict
|
||||
"date" $published
|
||||
)
|
||||
"params" (dict
|
||||
"mastodon" (dict
|
||||
"_model" ""
|
||||
"_post_type" "unknown"
|
||||
)
|
||||
)
|
||||
}}
|
||||
{{ $.AddPage $page }}
|
||||
|
||||
{{ end }}
|
||||
|
||||
{{/* Add the activity as a resource */}}
|
||||
|
||||
{{ $resource := dict
|
||||
"path" (printf "%s/activity" $status_id)
|
||||
"content" (dict
|
||||
"mediaType" "application/json"
|
||||
"value" (jsonify .)
|
||||
)
|
||||
}}
|
||||
{{ $.AddResource $resource }}
|
||||
|
||||
{{ end }}
|
||||
|
||||
{{ end }}
|
72
content/mastodon-live-activitypub/_content.gotmpl
Normal file
72
content/mastodon-live-activitypub/_content.gotmpl
Normal file
|
@ -0,0 +1,72 @@
|
|||
{{ $actor_data := dict }}
|
||||
{{ $actor_url := "https://mastodon.social/users/trwnh.json" }}
|
||||
{{ with resources.GetRemote $actor_url }}
|
||||
{{ with .Err }}
|
||||
{{ errorf "Unable to get remote resource %s: %s" $actor_url . }}
|
||||
{{ else }}
|
||||
{{ $actor_data = . | unmarshal }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ errorf "Unable to get remote resource %s" $actor_url }}
|
||||
{{ end }}
|
||||
|
||||
{{ with $actor_data }}
|
||||
|
||||
{{ $username := .preferredUsername }}
|
||||
|
||||
{{/* Fetch and save avatar */}}
|
||||
{{ $avatar_url := .icon.url }}
|
||||
{{ with resources.GetRemote $avatar_url }}
|
||||
{{ with .Err }}
|
||||
{{ warnf "Unable to get avatar from %s with error: %s" $avatar_url . }}
|
||||
{{ else }}
|
||||
{{ $avatar_content := dict "mediaType" .MediaType.Type "value" .Content }}
|
||||
{{ $avatar_resource := dict
|
||||
"path" (printf "avatar.%s" (index .MediaType.Suffixes 0))
|
||||
"content" $avatar_content
|
||||
}}
|
||||
{{ $.AddResource $avatar_resource }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Fetch and save header */}}
|
||||
{{ $header_url := .image.url }}
|
||||
{{ with resources.GetRemote $header_url }}
|
||||
{{ with .Err }}
|
||||
{{ warnf "Unable to get header from %s with error: %s" $avatar_url . }}
|
||||
{{ else }}
|
||||
{{ $header_content := dict "mediaType" .MediaType.Type "value" .Content }}
|
||||
{{ $header_resource := dict
|
||||
"path" (printf "header.%s" (index .MediaType.Suffixes 0))
|
||||
"content" $header_content
|
||||
}}
|
||||
{{ $.AddResource $header_resource }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Add account page */}}
|
||||
{{ $account_page := dict
|
||||
"path" "."
|
||||
"kind" "section"
|
||||
"title" .name
|
||||
"summary" (.summary | plainify)
|
||||
"content" (dict
|
||||
"mediaType" "text/html"
|
||||
"value" ""
|
||||
)
|
||||
"type" "mastodon"
|
||||
"layout" "user"
|
||||
"params" (dict
|
||||
"mastodon" (dict
|
||||
"_model" "Account"
|
||||
"username" .preferredUsername
|
||||
"created_at" (.published | time)
|
||||
"note" .summary
|
||||
"display_name" .name
|
||||
|
||||
)
|
||||
)
|
||||
}}
|
||||
{{ $.AddPage $account_page }}
|
||||
|
||||
{{ end }}
|
7
hugo.toml
Normal file
7
hugo.toml
Normal file
|
@ -0,0 +1,7 @@
|
|||
baseURL = 'https://example.org/'
|
||||
languageCode = 'en-us'
|
||||
title = 'My New Hugo Site'
|
||||
|
||||
[caches.getresource]
|
||||
dir = ':resourceDir/_cache/:project'
|
||||
maxage = -1
|
15
layouts/mastodon/baseof.html
Normal file
15
layouts/mastodon/baseof.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{.Title}}</title>
|
||||
{{ block "head" . }}
|
||||
{{ end }}
|
||||
</head>
|
||||
{{ block "body" . }}
|
||||
<body>
|
||||
|
||||
</body>
|
||||
{{ end }}
|
||||
</html>
|
38
layouts/mastodon/status.html
Normal file
38
layouts/mastodon/status.html
Normal file
|
@ -0,0 +1,38 @@
|
|||
{{ define "head" }}
|
||||
<style>
|
||||
|
||||
</style>
|
||||
{{ end }}
|
||||
|
||||
{{ define "body" }}
|
||||
<body>
|
||||
<main>
|
||||
<h1>Mastodon/Status view</h1>
|
||||
|
||||
<h2>Status</h2>
|
||||
|
||||
{{ with .Params.mastodon.spoiler_text }}
|
||||
<p>{{.}}</p>
|
||||
{{ end }}
|
||||
|
||||
{{ with .Content }}
|
||||
<div class="status__content">{{.}}</div>
|
||||
{{ end }}
|
||||
|
||||
<h2>Status params</h2>
|
||||
|
||||
<dl>
|
||||
{{ range $k, $v := .Params.mastodon }}
|
||||
<dt>{{$k}}</dt>
|
||||
<dd>{{$v}}</dd>
|
||||
{{ end }}
|
||||
</dl>
|
||||
|
||||
<h2>Activity</h2>
|
||||
{{ with .Resources.Get "activity" }}
|
||||
<pre>{{.Content | unmarshal | jsonify (dict "indent" " ") }}</pre>
|
||||
{{ end }}
|
||||
|
||||
</main>
|
||||
</body>
|
||||
{{ end }}
|
28
layouts/mastodon/statuses.html
Normal file
28
layouts/mastodon/statuses.html
Normal file
|
@ -0,0 +1,28 @@
|
|||
{{ define "head" }}
|
||||
<style>
|
||||
|
||||
</style>
|
||||
{{ end }}
|
||||
|
||||
{{ define "body" }}
|
||||
<body>
|
||||
<main>
|
||||
<h1>Mastodon/Statuses view</h1>
|
||||
<ul>
|
||||
{{ range where .RegularPages.ByDate "Params.mastodon.visibility" "public" }}
|
||||
<li>
|
||||
{{ with .Params.mastodon._reblog_of_uri }}
|
||||
<div>Reblogged {{ . }}</div>
|
||||
{{ else }}
|
||||
{{ with .Params.mastodon.spoiler_text }}
|
||||
<p style="border-inline: thick solid gold; background: #ddd">{{.}}</p>
|
||||
{{ end }}
|
||||
<div>{{.Content}}</div>
|
||||
{{ end }}
|
||||
<a href="{{.Permalink}}">{{.Permalink}}</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</main>
|
||||
</body>
|
||||
{{ end }}
|
226
layouts/mastodon/user.html
Normal file
226
layouts/mastodon/user.html
Normal file
|
@ -0,0 +1,226 @@
|
|||
{{ define "head" }}
|
||||
<style>
|
||||
:root {
|
||||
--color-primary-bg: #181821;
|
||||
--color-primary-text: white;
|
||||
--color-primary-text-faded: rgb(156, 156, 201);
|
||||
--color-primary-accent: rgb(140, 141, 255);
|
||||
--color-primary-link: var(--color-primary-accent);
|
||||
--color-primary-link-visited: var(--color-primary-link);
|
||||
--color-secondary-bg: #20202c;
|
||||
--color-outline: rgb(49, 49, 68);
|
||||
--radius: 10px;
|
||||
--size-avatar: 100px;
|
||||
--size-padding: calc(var(--spacing-1u, 10px) * 2);
|
||||
--spacing-1u: 10px;
|
||||
|
||||
line-height: 1.6;
|
||||
}
|
||||
a {
|
||||
word-break: break-all;
|
||||
}
|
||||
a:link {
|
||||
color: var(--color-primary-link);
|
||||
}
|
||||
a:visited {
|
||||
color: var(--color-primary-link-visited);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--color-secondary-bg);
|
||||
}
|
||||
@media (min-width: 610px) {
|
||||
main {
|
||||
padding-block: var(--spacing-1u, 10px);
|
||||
}
|
||||
}
|
||||
.main-title {
|
||||
display: none;
|
||||
}
|
||||
.account, .statuses {
|
||||
max-width: 600px;
|
||||
margin-inline: auto;
|
||||
background-color: var(--color-primary-bg);
|
||||
color: var(--color-primary-text);
|
||||
}
|
||||
.account {
|
||||
|
||||
}
|
||||
.account-prepend {
|
||||
background-color: var(--color-primary-bg);
|
||||
color: rgb(140, 141, 255);
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
padding-inline: var(--size-padding, 20px);
|
||||
border-color: var(--color-outline);
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.account-images {
|
||||
position: relative;
|
||||
}
|
||||
.account-header {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
object-fit: cover;
|
||||
background-color: var(--color-primary-accent);
|
||||
}
|
||||
.account-avatar {
|
||||
display: block;
|
||||
width: var(--size-avatar, 100px);
|
||||
height: var(--size-avatar, 100px);
|
||||
background-color: var(--color-primary-bg);
|
||||
border-color: var(--color-outline);
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: var(--radius, 10px);
|
||||
position: absolute;
|
||||
inset-inline-start: var(--size-padding, 20px);
|
||||
inset-block-end: calc(var(--size-avatar, 100px) / 2 * -1);
|
||||
}
|
||||
.account-info {
|
||||
padding-inline: var(--size-padding, 20px);
|
||||
padding-block-start: calc(var(--size-avatar, 100px) / 2);
|
||||
padding-block-end: var(--size-padding, 20px)
|
||||
}
|
||||
.account-display_name {
|
||||
font-weight: bolder;
|
||||
font-size: 1.5rem;
|
||||
line-height: 2;
|
||||
margin: 0;
|
||||
}
|
||||
.account-bio {
|
||||
|
||||
}
|
||||
.account-bio *:first-child {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
.account-bio *:last-child {
|
||||
margin-block-end: 0;
|
||||
}
|
||||
.account-created_at {
|
||||
margin: 0;
|
||||
margin-block-start: var(--size-padding, 20px);
|
||||
color: var(--color-primary-text-faded);
|
||||
}
|
||||
.statuses {
|
||||
|
||||
}
|
||||
.statuses-prepend {
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
padding-inline: var(--size-padding, 20px);
|
||||
font-size: 1rem;
|
||||
color: rgb(140, 141, 255);
|
||||
border-color: var(--color-outline);
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-inline: 0;
|
||||
}
|
||||
.statuses-empty-hint {
|
||||
margin: 0;
|
||||
padding: var(--size-padding, 20px);
|
||||
}
|
||||
.statuses > ul, .statuses > ul > li {
|
||||
display: contents;
|
||||
}
|
||||
.status {
|
||||
border-color: var(--color-outline);
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-inline: 0;
|
||||
border-block-start: 0;
|
||||
padding: var(--size-padding, 20px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.statuses > ul > li:last-child .status {
|
||||
border-block-end: 0;
|
||||
}
|
||||
.status-header {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
column-gap: var(--spacing-1u, 10px);
|
||||
place-items: center start;
|
||||
}
|
||||
.status-avatar {
|
||||
grid-column: 1;
|
||||
grid-row: 1 / span 2;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
.status-author {
|
||||
margin: 0;
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
line-height: 25px;
|
||||
}
|
||||
.status-meta {
|
||||
margin: 0;
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
line-height: 25px;
|
||||
}
|
||||
.status-prepend {
|
||||
|
||||
}
|
||||
.status-cw {
|
||||
background-color: hsl(50,100%,50%,10%);
|
||||
padding: 0.5rem 1rem;
|
||||
border-inline: thick solid goldenrod;
|
||||
}
|
||||
.status-body {
|
||||
|
||||
}
|
||||
@media (min-width: 480px) {
|
||||
.status-cw, .status-body {
|
||||
margin-left: 60px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{{ end }}
|
||||
|
||||
{{ define "body" }}
|
||||
<body>
|
||||
<main class="user-profile h-feed">
|
||||
<h1 class="main-title">Mastodon/User view</h1>
|
||||
<section class="account h-card">
|
||||
<h2 class="account-prepend">Account</h2>
|
||||
<div class="account-images">
|
||||
{{ $header := .Resources.GetMatch "header.*" }}
|
||||
{{ with $header }}
|
||||
<img class="account-header" src="{{.Permalink}}" alt="" width="320">
|
||||
{{ else }}
|
||||
<div class="account-header"></div>
|
||||
{{ end }}
|
||||
{{ $avatar := .Resources.GetMatch "avatar.*" }}
|
||||
{{ with $avatar }}
|
||||
<img class="account-avatar u-logo u-photo" src="{{.Permalink}}" alt="" width="120" height="120">
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="account-info">
|
||||
<p class="account-display_name p-name">{{ .Params.mastodon.display_name }}</p>
|
||||
<div class="account-bio e-summary">{{ .Params.mastodon.note | safeHTML }}</div>
|
||||
<p class="account-created_at">Created <time datetime="{{.Params.mastodon.created_at.Format "2006-01-02"}}" class="dt-published">{{.Params.mastodon.created_at.Format "January 2, 2006"}}</time></p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="statuses">
|
||||
<h2 class="statuses-prepend">Statuses</h2>
|
||||
{{ with where .RegularPages "Params.mastodon.visibility" "public" }}
|
||||
<ul>
|
||||
{{ range . }}
|
||||
<li>
|
||||
{{ partial "mastodon/status.html" (dict "status_ctx" . "account_ctx" $) }}
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ else }}
|
||||
<p class="statuses-empty-hint">No statuses</p>
|
||||
{{ end }}
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
{{ end }}
|
18
layouts/partials/mastodon/status.html
Normal file
18
layouts/partials/mastodon/status.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
{{ $status := .status_ctx }}
|
||||
{{ $account := .account_ctx }}
|
||||
{{ $avatar := $account.Resources.GetMatch "avatar.*" }}
|
||||
<article class="status h-entry">
|
||||
<header class="status-header h-card">
|
||||
<img src="{{ $avatar.Permalink }}" alt="" width="50" height="50" class="status-avatar u-logo u-photo">
|
||||
<p class="status-author p-name">{{ $account.Params.mastodon.display_name }}</p>
|
||||
<p class="status-meta"><a href="{{$status.Permalink}}" class="status-permalink"><time datetime="{{ $status.Date.UTC.Format "2006-01-02T15:04:05Z" }}" class="status-created_at">{{ $status.Date.Format "Jan 02, 2006 - 3:04 PM MST"}}</time></a></p>
|
||||
</header>
|
||||
<section class="status-prepend">
|
||||
{{ with $status.Params.mastodon.spoiler_text }}
|
||||
<p class="status-cw">{{ . }}</p>
|
||||
{{ end }}
|
||||
</section>
|
||||
<section class="status-body">
|
||||
{{ $status.Content }}
|
||||
</section>
|
||||
</article>
|
Loading…
Reference in a new issue